using System;
using System.Collections.Generic;
using System.Linq;
using EscapeRoomEngine.Engine.Runtime.Requirements;
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;
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
///
/// The main representation of a module. Contains references to the module description, state and handles its placement.
///
public class Module
{
public readonly List relatedModules = new();
public ModuleState State { get; private set; }
internal Vector2Int Size => srPlacement.size;
internal Orientation Orientation => srPlacement.orientation;
///
/// Get the space relative (SR) position of this module.
///
internal Vector3Int SrPosition => srPlacement.position;
///
/// Get the room relative (RR) position of this module.
///
internal Vector3Int RrPosition => space?.ToRoomRelative(SrPosition) ?? SrPosition;
internal readonly ModuleDescription description;
///
/// The space relative (SR) placement of this module.
///
public Placement srPlacement;
protected readonly Space space;
internal Module(Space space, ModuleDescription description)
{
this.space = space;
this.description = description;
srPlacement.size = description.modulePrefab.size;
}
internal bool IsType(ModuleType type)
{
return description.types.Contains(type);
}
internal bool CheckRequirements()
{
return PreconditionRequirement.CheckPreconditions(this, space) &&
PlacementRequirement.TryPlacing(this, space);
}
///
/// Place this module with a position relative to the room.
///
/// The room relative (RR) position of this module. Must be inside the space dimensions.
/// The orientation of this module.
/// If the position is not inside the space dimensions.
internal void PlaceRoomRelative(Vector3Int rrPosition, Orientation orientation) =>
Place(new Placement
{
position = space.ToSpaceRelative(rrPosition),
size = srPlacement.size,
orientation = orientation
});
///
/// Place this module with a position relative to the space it is in.
///
/// The space relative (SR) placement of this module. Must be inside the space dimensions.
/// If the position is not inside the space dimensions.
internal void Place(Placement placement) {
if (space != null && !placement.position.IsInsideRelative(space.rrPlacement))
{
throw new EngineException($"Trying to place {this} at {placement.position} which is outside space dimensions {space.rrPlacement}.");
}
srPlacement = placement;
Logger.Log($"{this} has been placed at {placement.position} (SR)", LogType.ModulePlacement);
}
///
/// Convert a position relative to this module to one relative to its space.
/// The module relative position (0, 0, 1) should always be in front of the module, wherever it faces.
///
/// The module relative (MR) position that should be converted to a space relative (SR) position.
internal Vector3Int ToSpaceRelative(Vector3Int mrPosition)
{
// ReSharper disable once ArrangeRedundantParentheses
return srPlacement.position + (Orientation switch
{
Orientation.North => mrPosition,
Orientation.East => new Vector3Int(mrPosition.z, 0, -mrPosition.x),
Orientation.South => -mrPosition,
Orientation.West => new Vector3Int(-mrPosition.z, 0, mrPosition.x),
_ => throw new ArgumentOutOfRangeException()
});
}
internal virtual void InstantiateModule(Transform parent)
{
Logger.Log($"Instantiating {this}", LogType.ModuleInstantiation);
State = Object.Instantiate(description.modulePrefab, parent, false);
State.transform.localPosition = new Vector3(SrPosition.x + .5f, 0, SrPosition.z + .5f);
State.transform.Rotate(Vector3.up, Orientation.Angle());
State.name = ToString();
State.SetModule(this);
}
///
/// Creates a module of the correct type given a module description.
///
/// If the module description shows "Puzzle" as a type, this will try to create a puzzle module.
///
/// The space to create the module for.
/// The module description to create the module from.
/// A or .
/// If there is no module type fitting the description.
internal static Module CreateModuleByType(Space space, ModuleDescription description)
{
if (description.HasType(ModuleType.Puzzle) &&
description is PuzzleModuleDescription puzzleModuleDescription)
{
return new PuzzleModule(space, puzzleModuleDescription);
}
if((description.HasType(ModuleType.DoorEntrance) || description.HasType(ModuleType.DoorExit)) &&
description is DoorModuleDescription doorModuleDescription)
{
return new DoorModule(space, doorModuleDescription);
}
if (description.HasType(ModuleType.Generic))
{
return new Module(space, description);
}
throw new WrongTypeException("There is no module type fitting this description.");
}
public override string ToString()
{
return $"Module ({string.Join(", ", description.types.ToList().ConvertAll(type => type.ToString()))})";
}
}
}