//$ 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; } } }