using System;
using System.Collections.Generic;
using EscapeRoomEngine.Engine.Runtime.Modules;
using EscapeRoomEngine.Engine.Runtime.Modules.Description;
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
{
///
/// The space is responsible for placing and instantiating any modules in it.
///
public class Space
{
///
/// The room relative (RR) dimensions of this space.
///
internal readonly Placement rrPlacement;
///
/// The room this space is in.
///
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.Theme.minRoomSize.ProjectAtFloor(),
Engine.Theme.playSpace.ProjectAtFloor());
// connect the space to its entrance passage
entrance.PlaceEntrance(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.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;
}
}
}