using System; using System.Collections.Generic; using EscapeRoomEngine.Engine.Runtime.Modules; using EscapeRoomEngine.Engine.Runtime.Requirements; using UnityEngine; using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger; using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType; using Object = UnityEngine.Object; namespace EscapeRoomEngine.Engine.Runtime { public class Space { /// /// The room relative (RR) dimensions of this space. /// internal readonly Dimensions rrDimensions; 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; rrDimensions = GenerateSpaceDimensions( entrance, Engine.DefaultEngine.theme.minRoomSize, Engine.DefaultEngine.theme.playSpace); // 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(rrDimensions.x, 0, rrDimensions.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 < rrDimensions.length; z++) { for (var x = 0; x < rrDimensions.width; x++) { var left = x == 0; var right = x == rrDimensions.width - 1; var bottom = z == 0; var top = z == rrDimensions.length - 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 Vector2Int ToRoomRelative(Vector2Int srPosition) => srPosition + rrDimensions.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 Vector2Int ToSpaceRelative(Vector2Int rrPosition) => rrPosition - rrDimensions.Position; /// /// Generate space dimensions that fit the required size constraints and cover the position of the entrance. /// /// The generated room relative (RR) dimensions. private static Dimensions GenerateSpaceDimensions(Passage entrance, Vector2Int minSize, Vector2Int availableSpace) { var xMin = -1; var xMax = -1; var zMin = -1; var zMax = -1; var position = entrance.rrPosition; var door = entrance.fromOut; // fix the side the door is facing away from switch (door.orientation) { case Orientation.North: zMin = position.y; zMax = Utilities.Utilities.RandomInclusive(zMin + minSize.y, availableSpace.y); break; case Orientation.East: xMin = position.x; xMax = Utilities.Utilities.RandomInclusive(xMin + minSize.x, availableSpace.x); break; case Orientation.South: zMax = position.y + 1; zMin = Utilities.Utilities.RandomInclusive(0, zMax - minSize.y); break; case Orientation.West: xMax = position.x + 1; xMin = Utilities.Utilities.RandomInclusive(0, xMax - minSize.x); break; default: throw new ArgumentOutOfRangeException(); } // calculate remaining values if they haven't been covered by the switch statement yet if(xMin == -1) xMin = Utilities.Utilities.RandomInclusive(0, Math.Min(position.x, availableSpace.x - minSize.x)); if(xMax == -1) xMax = Utilities.Utilities.RandomInclusive(Math.Max(position.x + 1, xMin + minSize.x), availableSpace.x); if(zMin == -1) zMin = Utilities.Utilities.RandomInclusive(0, Math.Min(position.y, availableSpace.y - minSize.y)); if(zMax == -1) zMax = Utilities.Utilities.RandomInclusive(Math.Max(position.y + 1, zMin + minSize.y), availableSpace.y); var dimensions = new Dimensions(xMax - xMin, zMax - zMin, xMin, zMin); Logger.Log($"Generated space dimensions {dimensions} from entrance position {position}", LogType.RoomGeneration); return dimensions; } } }