using System; using System.Collections.Generic; using EscapeRoomEngine.Engine.Runtime.Modules; using EscapeRoomEngine.Engine.Runtime.Utilities; using UnityEngine; using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger; using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType; using Object = UnityEngine.Object; using Range = EscapeRoomEngine.Engine.Runtime.Utilities.Range; namespace EscapeRoomEngine.Engine.Runtime { public class Space { /// /// The room relative (RR) dimensions of this space. /// internal readonly Placement rrPlacement; internal readonly Room room; /// /// A list of all modules. /// internal List Modules { get; } = new(2); /// /// A list of all staged and added modules. /// internal List AllModules { get { var modules = new List(Modules); if (_stagedModules.Count > 0) { modules.AddRange(_stagedModules); } return modules; } } private GameObject _spaceObject, _spaceTiles; private List _stagedModules = new(); internal Space(Room room, Passage entrance) { this.room = room; rrPlacement = GenerateSpaceDimensions( entrance, Engine.DefaultEngine.theme.minRoomSize.ProjectAtFloor(), Engine.DefaultEngine.theme.playSpace.ProjectAtFloor()); // connect the space to its passage entrance.ConnectTo(new DoorModule(this, ((DoorModuleDescription)entrance.fromOut.description).connectedDoorDescription)); Modules.Add(entrance.toIn); } internal bool AddModuleWithRequirements(Module module) { var requirementsFulfilled = module.CheckRequirements(); if (requirementsFulfilled) { // add all previously staged modules first ... for (var i = _stagedModules.Count - 1; i >= 0; i--) { Modules.Add(_stagedModules[i]); } // ... and then add the module itself Modules.Add(module); } // always clear the staged modules. if a module isn't added, its corresponding staged modules should be discarded _stagedModules = new List(); return requirementsFulfilled; } /// /// Stage a module if its requirements are currently met. It will be added as soon as the next module is added by . /// /// The module to stage /// Whether the requirements were met and the module was staged internal bool StageModuleWithRequirements(Module module) { var requirementsFulfilled = module.CheckRequirements(); if (requirementsFulfilled) { _stagedModules.Add(module); } return requirementsFulfilled; } internal void InstantiateSpace(Transform parent, string name) { _spaceObject = new GameObject($"Space {name}"); _spaceObject.transform.SetParent(parent, false); _spaceObject.transform.localPosition = new Vector3(rrPlacement.position.x, 0, rrPlacement.position.z); // build the space floor out of tiles _spaceTiles = new GameObject($"Space Geometry"); _spaceTiles.transform.SetParent(_spaceObject.transform, false); _spaceTiles.isStatic = true; for (var z = 0; z < rrPlacement.size.y; z++) { for (var x = 0; x < rrPlacement.size.x; x++) { var left = x == 0; var right = x == rrPlacement.size.x - 1; var bottom = z == 0; var top = z == rrPlacement.size.y - 1; TileLocation location; if (bottom) location = left ? TileLocation.SW : right ? TileLocation.SE : TileLocation.S; else if (top) location = left ? TileLocation.NW : right ? TileLocation.NE : TileLocation.N; else location = left ? TileLocation.W : right ? TileLocation.E : TileLocation.C; var tileObject = Object.Instantiate(Engine.DefaultEngine.theme.spaceTile, _spaceTiles.transform, false); tileObject.transform.localPosition = new Vector3(x, 0, z); tileObject.showTile = location; } } // instantiate all modules inside this space Modules.ForEach(module => module.InstantiateModule(_spaceObject.transform)); } /// /// Convert a position relative to this space to one relative to the room. /// /// The space relative (SR) position that should be converted to a room relative (RR) position. internal Vector3Int ToRoomRelative(Vector3Int srPosition) => srPosition + rrPlacement.position; /// /// Convert a position relative to the room to one relative to this space. /// /// The room relative (RR) position that should be converted to a space relative (SR) position. internal Vector3Int ToSpaceRelative(Vector3Int rrPosition) => rrPosition - rrPlacement.position; /// /// Generate space dimensions that fit the required size constraints and cover the position of the entrance. /// /// A room relative (RR) placement with the generated dimensions. Always faces North. private static Placement GenerateSpaceDimensions(Passage entrance, Vector3Int minSize, Vector3Int availableSpace) { var x = new Range(-1, -1); var z = new Range(-1, -1); var door = entrance.fromOut; var position = door.RrPosition; var bottomLeft = door.srPlacement.BottomLeft; var doorSize = door.Size; // fix the side the door is facing away from switch (door.Orientation) { case Orientation.North: z.min = position.z; z.max = Utilities.Utilities.RandomInclusive(z.min + minSize.z, availableSpace.z); break; case Orientation.East: x.min = position.x; x.max = Utilities.Utilities.RandomInclusive(x.min + minSize.x, availableSpace.x); break; case Orientation.South: z.max = position.z + 1; z.min = Utilities.Utilities.RandomInclusive(0, z.max - minSize.z); break; case Orientation.West: x.max = position.x + 1; x.min = Utilities.Utilities.RandomInclusive(0, x.max - minSize.x); break; default: throw new ArgumentOutOfRangeException(); } // calculate remaining values if they haven't been covered by the switch statement yet if(x.min == -1) x.min = Utilities.Utilities.RandomInclusive(0, Math.Min(bottomLeft.x, availableSpace.x - minSize.x)); if(x.max == -1) x.max = Utilities.Utilities.RandomInclusive(Math.Max(bottomLeft.x + doorSize.x, x.min + minSize.x), availableSpace.x); if(z.min == -1) z.min = Utilities.Utilities.RandomInclusive(0, Math.Min(bottomLeft.z, availableSpace.z - minSize.z)); if(z.max == -1) z.max = Utilities.Utilities.RandomInclusive(Math.Max(bottomLeft.z + doorSize.x, z.min + minSize.z), availableSpace.z); var dimensions = new Placement { position = new Vector3Int(x.min, 0, z.min), size = new Vector2Int(x.Length, z.Length) }; Logger.Log($"Generated space dimensions {dimensions} from entrance position {position}", LogType.RoomGeneration); return dimensions; } } }