//$ Copyright 2015-22, Code Respawn Technologies Pvt Ltd - All Rights Reserved $//
using UnityEngine;
using System;
using System.Collections.Generic;
namespace DungeonArchitect.Builders.Grid
{
///
/// The type of cell used in the grid builder
///
[System.Serializable]
public enum CellType
{
Room,
Corridor,
CorridorPadding,
Unknown
}
///
/// Data-structure to hold the stair information in the grid based builder
///
[System.Serializable]
public class StairInfo
{
[SerializeField]
public int OwnerCell;
[SerializeField]
public int ConnectedToCell;
[SerializeField]
public Vector3 Position;
[SerializeField]
public Quaternion Rotation;
[SerializeField]
public IntVector IPosition;
};
///
/// Data-structure to hold the Cell information. A cell is a piece of the dungeon layout and can be either a room or a corridor
///
[Serializable]
public class Cell
{
public Cell()
{
cellType = CellType.Unknown;
}
public Cell(int x, int z, int width, int length)
{
bounds = new Rectangle(x, z, width, length);
cellType = CellType.Unknown;
}
[SerializeField]
int id;
public int Id
{
get
{
return id;
}
set
{
id = value;
}
}
[SerializeField]
Rectangle bounds = new Rectangle();
public Rectangle Bounds
{
get { return bounds; }
set { bounds = value; }
}
[SerializeField]
CellType cellType = CellType.Unknown;
public CellType CellType
{
get { return cellType; }
set { cellType = value; }
}
public Bounds GetWorldBounds(Vector3 gridSize)
{
var bounds = new Bounds();
var width = this.Bounds.Width * gridSize.x;
var length = this.Bounds.Width * gridSize.z;
var center = Vector3.Scale(this.Bounds.CenterF(), gridSize);
bounds.center = center;
bounds.size = new Vector3(width, 2, length);
return bounds;
}
///
/// Indicates if the cell is user defined and not procedurally generated
///
[SerializeField]
bool userDefined;
public bool UserDefined
{
get
{
return userDefined;
}
set
{
userDefined = value;
}
}
[SerializeField]
HashSet connectedRooms = new HashSet();
public HashSet ConnectedRooms
{
get { return connectedRooms; }
set { connectedRooms = value; }
}
[SerializeField]
HashSet fixedRoomConnections = new HashSet();
public HashSet FixedRoomConnections
{
get { return fixedRoomConnections; }
set { fixedRoomConnections = value; }
}
[SerializeField]
HashSet adjacentCells = new HashSet();
public HashSet AdjacentCells
{
get
{
return adjacentCells;
}
set
{
adjacentCells = value;
}
}
public IntVector Center
{
get
{
return new IntVector(bounds.X + bounds.Width / 2, 0, bounds.Z + bounds.Length / 2);
}
}
public Vector3 CenterF
{
get
{
return new Vector3(bounds.X + bounds.Width / 2.0f, bounds.Location.y, bounds.Z + bounds.Length / 2.0f);
}
}
public override bool Equals(System.Object obj)
{
if (obj == null) return false;
if (obj is Cell)
{
var cell = obj as Cell;
return this.Id == cell.Id;
}
return false;
}
public override int GetHashCode()
{
return id;
}
}
///
/// Data-structure to hold the door information
///
[Serializable]
public class CellDoor
{
[SerializeField]
IntVector[] adjacentTiles = new IntVector[2];
///
/// The adjacent tile positions shared by this door (entry / exit tiles)
///
public IntVector[] AdjacentTiles
{
get
{
return adjacentTiles;
}
}
[SerializeField]
bool enabled = true;
public bool Enabled
{
get { return enabled; }
set { enabled = value; }
}
///
/// Cell Ids of one of the cells that owns this door
///
[SerializeField]
int[] adjacentCells = new int[2];
public int[] AdjacentCells
{
get
{
return adjacentCells;
}
set
{
adjacentCells = value;
}
}
public override string ToString()
{
return string.Format("[CellDoor: {0} <=> {1}]", AdjacentCells[0], AdjacentCells[1]);
}
}
///
/// Data-structure for IntVector pair. Used for caching
///
[Serializable]
public struct IntVector2Key
{
[SerializeField]
public IntVector a;
[SerializeField]
public IntVector b;
public IntVector2Key(IntVector a, IntVector b)
{
this.a = a;
this.b = b;
}
public override bool Equals(System.Object obj)
{
if (obj != null && obj is IntVector2Key)
{
var other = (IntVector2Key)obj;
return a.Equals(other.a) &&
b.Equals(other.b);
}
return false;
}
public override int GetHashCode()
{
return (a.GetHashCode() << 16) + b.GetHashCode();
}
}
///
/// Manages the doors in the grid based builder
///
[Serializable]
public class DoorManager
{
[SerializeField]
Dictionary doorLookupCache = new Dictionary();
[SerializeField]
List doors = new List();
public void Clear()
{
doorLookupCache.Clear();
doors.Clear();
}
public void RemoveDoor(CellDoor door)
{
var keysToRemove = new List();
foreach (var key in doorLookupCache.Keys)
{
if (doorLookupCache[key] == door)
{
keysToRemove.Add(key);
}
}
foreach (var key in keysToRemove)
{
doorLookupCache.Remove(key);
}
doors.Remove(door);
}
///
/// Creates a door between the two grid points
///
/// The grid poition 1
/// The grid poition 2
/// Cell Id of the first adjacent cell
/// Cell Id of the second adjacent cell
///
public CellDoor CreateDoor(IntVector p1, IntVector p2, int cellId1, int cellId2)
{
var key1 = new IntVector2Key(p1, p2);
if (doorLookupCache.ContainsKey(key1)) return doorLookupCache[key1];
var key2 = new IntVector2Key(p2, p1);
if (doorLookupCache.ContainsKey(key2)) return doorLookupCache[key2];
// Create a new door
var door = new CellDoor();
door.AdjacentTiles[0] = new IntVector(p1.x, p1.y, p1.z);
door.AdjacentTiles[1] = new IntVector(p2.x, p2.y, p2.z);
door.AdjacentCells[0] = cellId1;
door.AdjacentCells[1] = cellId2;
// Add to the memo lookup
doorLookupCache.Add(key1, door);
doorLookupCache.Add(key2, door);
doors.Add(door);
return door;
}
///
/// Check if a door exists between the two cells
///
/// Cell Id of the first cell
/// Cell Id of the second cell
///
public bool ContainsDoorBetweenCells(int cellA, int cellB)
{
foreach (var door in doors)
{
if (!door.Enabled)
{
continue;
}
if ((door.AdjacentCells[0] == cellA && door.AdjacentCells[1] == cellB) ||
(door.AdjacentCells[0] == cellB && door.AdjacentCells[1] == cellA))
{
return true;
}
}
return false;
}
public bool ContainsDoor(int x1, int z1, int x2, int z2) {
foreach (var door in doors) {
if (!door.Enabled) { continue; }
var containsDoor =
door.AdjacentTiles[0].x == x1 && door.AdjacentTiles[0].z == z1 &&
door.AdjacentTiles[1].x == x2 && door.AdjacentTiles[1].z == z2;
if (containsDoor) {
return true;
}
containsDoor =
door.AdjacentTiles[1].x == x1 && door.AdjacentTiles[1].z == z1 &&
door.AdjacentTiles[0].x == x2 && door.AdjacentTiles[0].z == z2;
if (containsDoor) {
return true;
}
}
return false;
}
///
/// List of registered doors
///
public CellDoor[] Doors
{
get
{
return doors.ToArray();
}
}
}
///
/// Data model for the grid based dungeon builder
///
//[System.Serializable]
public class GridDungeonModel : DungeonModel
{
[HideInInspector]
[SerializeField]
public DoorManager DoorManager = new DoorManager();
[HideInInspector]
public GridDungeonConfig Config;
[SerializeField]
[HideInInspector]
public List Cells = new List();
[HideInInspector]
[SerializeField]
public Dictionary> CellStairs = new Dictionary>();
[HideInInspector]
public Dictionary> GridCellInfoLookup = new Dictionary>();
///
/// Get meta-data about the grid in x, z grid coordinate
///
/// X value in grid coordinate
/// Z value in grid cooridnate
///
public GridCellInfo GetGridCellLookup(int x, int z)
{
if (!GridCellInfoLookup.ContainsKey(x) || !GridCellInfoLookup[x].ContainsKey(z))
{
return new GridCellInfo();
}
return GridCellInfoLookup[x][z];
}
///
/// Builds a lookup for fast data retrieval
///
public void BuildSpatialCellLookup()
{
// Cache the cell types based on their positions
GridCellInfoLookup.Clear();
foreach (var cell in Cells)
{
if (cell.CellType == CellType.Unknown) continue;
IntVector basePosition = cell.Bounds.Location;
for (int dx = 0; dx < cell.Bounds.Size.x; dx++)
{
for (int dz = 0; dz < cell.Bounds.Size.z; dz++)
{
int x = basePosition.x + dx;
int z = basePosition.z + dz;
// register the cell type in the lookup
if (!GridCellInfoLookup.ContainsKey(x)) GridCellInfoLookup.Add(x, new Dictionary());
if (!GridCellInfoLookup[x].ContainsKey(z))
{
GridCellInfoLookup[x].Add(z, new GridCellInfo(cell.Id, cell.CellType));
}
}
}
}
}
///
/// The list of registered doors
///
public CellDoor[] Doors
{
get
{
return DoorManager.Doors;
}
}
///
/// Builds the cell lookup for faster cell retrieval
///
public void BuildCellLookup()
{
CellLookup.Clear();
foreach (var cell in Cells)
{
CellLookup.Add(cell.Id, cell);
}
}
///
/// Gets the cell information
///
/// Id of the cell to lookup
///
public Cell GetCell(int Id)
{
return CellLookup.ContainsKey(Id) ? CellLookup[Id] : null;
}
///
/// Cell lookup based on the Cell Id
///
public Dictionary CellLookup = new Dictionary();
///
/// Finds the cell based on the position in grid coordinates
///
/// Position to lookup in grid cooridnates
/// Cell information at that location. Returns null if none found
public Cell FindCellByPosition(IntVector position)
{
foreach (var cell in Cells)
{
if (cell.Bounds.Contains(position))
{
return cell;
}
}
return null;
}
///
/// Clears the dungeon data model
///
public override void ResetModel()
{
DoorManager = new DoorManager();
Config = null;
Cells = new List| ();
CellStairs.Clear();
}
public bool ContainsStairAtLocation(int x, int z)
{
foreach (var stairList in CellStairs.Values) {
foreach (var stair in stairList) {
if (stair.IPosition.x == x && stair.IPosition.z == z) {
return true;
}
}
}
return false;
}
public StairInfo GetStairAtLocation(int x, int z) {
foreach (var stairList in CellStairs.Values) {
foreach (var stair in stairList) {
if (stair.IPosition.x == x && stair.IPosition.z == z) {
return stair;
}
}
}
return null;
}
///
/// Check if a stair exists between the two cells
///
///
///
///
public bool ContainsStair(int cellA, int cellB)
{
return CheckContainStair(cellA, cellB) || CheckContainStair(cellB, cellA);
}
bool CheckContainStair(int cellA, int cellB)
{
if (!CellStairs.ContainsKey(cellA)) return false;
foreach (var stair in CellStairs[cellA])
{
if (stair.ConnectedToCell == cellB) return true;
}
return false;
}
}
public class GridDungeonModelUtils {
struct LongestPathBFSData {
public Cell cell;
public int distance;
}
public static Cell[] FindFurthestRooms(GridDungeonModel model)
{
return FindFurthestRooms(model, CellType.Room, CellType.Room);
}
public static Cell[] FindFurthestRooms(GridDungeonModel model, CellType startCellType, CellType endCellType) {
var result = new Cell[2];
var bestDistance = 0;
foreach (var startCell in model.Cells) {
if (startCell.CellType != startCellType)
{
continue;
}
var queue = new Queue();
var startData = new LongestPathBFSData { cell = startCell, distance = 0 };
queue.Enqueue(startData);
LongestPathBFSData bestEndCell = startData;
var cellDistances = new Dictionary(); // CellId -> Distance mapping
while (queue.Count > 0) {
var front = queue.Dequeue();
if (front.cell == null) continue;
var processCell = false;
if (!cellDistances.ContainsKey(front.cell.Id))
{
// We never processed this cell
processCell = true;
}
else if (cellDistances[front.cell.Id] > front.distance)
{
// We found a shorter path to this cell
processCell = true;
}
if (processCell)
{
cellDistances[front.cell.Id] = front.distance;
foreach (var childId in front.cell.AdjacentCells) {
var child = model.GetCell(childId);
var childData = new LongestPathBFSData { cell = child, distance = front.distance + 1 };
queue.Enqueue(childData);
}
}
if (front.cell.CellType == endCellType && front.distance > bestEndCell.distance)
{
bestEndCell = front;
}
}
if (bestEndCell.distance > bestDistance)
{
result[0] = startData.cell;
result[1] = bestEndCell.cell;
bestDistance = bestEndCell.distance;
}
}
return result;
}
}
}
| | |