144 lines
5.6 KiB
C#
144 lines
5.6 KiB
C#
using System;
|
||
using UnityEngine;
|
||
using TD.Core;
|
||
|
||
namespace TD.Levels
|
||
{
|
||
/// <summary>
|
||
/// Baked level data. Authored in a Unity scene via volume MonoBehaviours; produced by
|
||
/// <see cref="LevelAuthoring.BakeLevelData"/>; consumed at runtime as the canonical map
|
||
/// definition.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// LevelData is the canonical map identity. The lobby browses LevelData assets; loading a
|
||
/// map = read LevelData → load the referenced scene at <see cref="ScenePath"/>.
|
||
///
|
||
/// All flat grid arrays use row-major indexing: <c>grid[y * GridSize.x + x]</c>.
|
||
/// Origin-relative: <see cref="GridOriginTile"/> is the world-tile coordinate that grid index
|
||
/// (0,0) corresponds to. Convert world-tile to grid-index via
|
||
/// <c>worldTile - GridOriginTile</c>.
|
||
/// </remarks>
|
||
[CreateAssetMenu(fileName = "LevelData", menuName = "TD/Level Data", order = 1)]
|
||
public class LevelData : ScriptableObject
|
||
{
|
||
// -------------------------------------------------------------------
|
||
// Lobby-facing metadata.
|
||
// -------------------------------------------------------------------
|
||
|
||
[Header("Map Metadata")]
|
||
public string MapName;
|
||
public int PlayerCount;
|
||
public string MapDescription;
|
||
public string Author;
|
||
|
||
[Tooltip("Auto-generated by bake. Use the Refresh Thumbnail button on LevelAuthoring to " +
|
||
"re-render without doing a full bake.")]
|
||
public Sprite MapThumbnail;
|
||
|
||
[Tooltip("Path to the scene this LevelData was baked from. Auto-populated by bake.")]
|
||
public string ScenePath;
|
||
|
||
// -------------------------------------------------------------------
|
||
// Bake metadata (used for dirty-detection and diagnostic display).
|
||
// -------------------------------------------------------------------
|
||
|
||
[Header("Bake Metadata")]
|
||
[Tooltip("Hash of the authoring inputs at last bake. Compared at Play-mode entry to detect " +
|
||
"unbaked changes. Captures volumes + LevelAuthoring metadata; does NOT capture " +
|
||
"visual scene content.")]
|
||
public string AuthoringHash;
|
||
|
||
[Tooltip("ISO 8601 UTC timestamp of the last successful bake.")]
|
||
public string LastBakeTimestamp;
|
||
|
||
public BakeOutcome LastBakeOutcome;
|
||
public int LastBakeWarningCount;
|
||
|
||
// -------------------------------------------------------------------
|
||
// Grid metadata.
|
||
// -------------------------------------------------------------------
|
||
|
||
[Header("Grid")]
|
||
[Tooltip("World-tile coordinate that grid index (0,0) corresponds to. Origin-relative " +
|
||
"addressing: worldTile - GridOriginTile gives the index into the flat arrays.")]
|
||
public Vector2Int GridOriginTile;
|
||
|
||
[Tooltip("Width × height of the grid in tiles.")]
|
||
public Vector2Int GridSize;
|
||
|
||
// -------------------------------------------------------------------
|
||
// Per-tile flat arrays. All indexed as grid[y * GridSize.x + x].
|
||
// -------------------------------------------------------------------
|
||
|
||
[Tooltip("Per-tile placement state: Outside / Buildable / Restricted. Length = GridSize.x * GridSize.y.")]
|
||
public PlacementState[] PlacementGrid;
|
||
|
||
[Tooltip("Per-tile initial walkability (does NOT account for towers — they stamp footprints " +
|
||
"at runtime). Length = GridSize.x * GridSize.y.")]
|
||
public bool[] WalkabilityGrid;
|
||
|
||
[Tooltip("Per-tile owning player slot. PlayerSlot.None for tiles not in any player zone. " +
|
||
"Length = GridSize.x * GridSize.y.")]
|
||
public PlayerSlot[] OwnerGrid;
|
||
|
||
// -------------------------------------------------------------------
|
||
// Per-zone and per-goal structures (populated by bake from volume data).
|
||
// -------------------------------------------------------------------
|
||
|
||
[Tooltip("One entry per declared player zone, sorted by Owner.")]
|
||
public PlayerZoneData[] PlayerZones;
|
||
|
||
[Tooltip("One entry per GoalVolume, sorted by min tile coordinate.")]
|
||
public GoalData[] Goals;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Per-zone baked data. The runtime can use this to enumerate a player's spawners and leak
|
||
/// exits without scanning the full grid.
|
||
/// </summary>
|
||
[Serializable]
|
||
public class PlayerZoneData
|
||
{
|
||
public PlayerSlot Owner;
|
||
|
||
[Tooltip("Spawners in this zone, sorted by SpawnerIdInZone.")]
|
||
public SpawnerData[] Spawners;
|
||
|
||
[Tooltip("Leak exits OUT OF this zone, sorted by Target enum value. Empty for the final " +
|
||
"defender (whose zone is goal-adjacent and has no leak exit).")]
|
||
public LeakExitData[] LeakExits;
|
||
}
|
||
|
||
[Serializable]
|
||
public class SpawnerData
|
||
{
|
||
public int SpawnerIdInZone;
|
||
|
||
[Tooltip("Tile coordinate of the spawner's center (its volume's bounds center, projected to grid).")]
|
||
public Vector2Int TilePosition;
|
||
|
||
[Tooltip("Full tile coverage of the spawner volume.")]
|
||
public Vector2Int[] TileArea;
|
||
|
||
public Direction Facing;
|
||
}
|
||
|
||
[Serializable]
|
||
public class LeakExitData
|
||
{
|
||
public PlayerSlot Target;
|
||
|
||
[Tooltip("Full tile coverage of the leak exit volume.")]
|
||
public Vector2Int[] TileArea;
|
||
|
||
[Tooltip("Weight normalized so all leak exits sharing the same source zone sum to 1.0.")]
|
||
public float NormalizedWeight;
|
||
}
|
||
|
||
[Serializable]
|
||
public class GoalData
|
||
{
|
||
[Tooltip("Full tile coverage of the goal volume.")]
|
||
public Vector2Int[] TileArea;
|
||
}
|
||
}
|