using System.Text.Json.Serialization; namespace Theriapolis.Core.Data; /// /// Phase 7 M0 — per-dungeon-type rules for assembling rooms into a complete /// dungeon. Loaded from Content/Data/dungeon_layouts/*.json. /// /// Each layout declares: which dungeon type + size band it covers, the /// room-count band, branching policy (linear / branching / loop), required /// vs optional special-room roles (entry / narrative / boss / loot / /// dead-end), and the mapping from PoI level-band → loot-table tier. /// /// validates ranges, /// branching enum, and loot-table-per-band references against the loaded /// loot_tables.json. /// /// Anchor-locked dungeons (Old Howl mine, Imperium Ruin showcase) ship as /// special layouts whose field is set — these /// override the procedural pipeline so the experience is identical across /// seeds. /// public sealed record DungeonLayoutDef { [JsonPropertyName("id")] public string Id { get; init; } = ""; /// /// Dungeon type: ImperiumRuin, AbandonedMine, /// CultDen, NaturalCave, OvergrownSettlement. /// Matches the enum names exactly. /// [JsonPropertyName("dungeon_type")] public string DungeonType { get; init; } = ""; /// Size band: small / medium / large. [JsonPropertyName("size_band")] public string SizeBand { get; init; } = "small"; /// /// Optional anchor id. When set, this layout is the canonical fixed /// layout for the named anchor (Old Howl mine, Imperium Ruin showcase). /// The procedural pipeline never picks anchor-locked layouts via /// + ; only the anchor /// resolver consumes them. /// [JsonPropertyName("anchor")] public string Anchor { get; init; } = ""; [JsonPropertyName("room_count_min")] public int RoomCountMin { get; init; } = 3; [JsonPropertyName("room_count_max")] public int RoomCountMax { get; init; } = 5; /// /// Branching policy: linear (each room connects to the previous; /// chain), branching (each room past entry connects to one prior /// room — variable degree), loop (branching plus one extra /// connection that closes a loop). /// [JsonPropertyName("branching")] public string Branching { get; init; } = "linear"; /// Special-room roles that must be present in any successful assembly. [JsonPropertyName("required_roles")] public string[] RequiredRoles { get; init; } = Array.Empty(); /// Special-room roles eligible for inclusion if there's room left over. [JsonPropertyName("optional_roles")] public string[] OptionalRoles { get; init; } = Array.Empty(); /// /// Map from loot-table band (t1/t2/t3) to a real /// loot-table id (e.g. loot_dungeon_imperium_t2). Looked up by /// the dungeon populator when filling container slots. /// [JsonPropertyName("loot_table_per_band")] public Dictionary LootTablePerBand { get; init; } = new(); /// /// Spawn-kind distribution for filling generic encounter slots — keys /// are spawn-kind names (PoiGuard / WildAnimal / /// Brigand), values are weights that sum to ~1.0. /// [JsonPropertyName("spawn_kind_distribution")] public Dictionary SpawnKindDistribution { get; init; } = new(); /// /// Map from PoI LevelBand (0..3) to a loot-table band /// (t1/t2/t3). Keys are stringified ints /// because rejects integer dictionary /// keys without a custom converter. /// [JsonPropertyName("level_band_to_loot_band")] public Dictionary LevelBandToLootBand { get; init; } = new(); /// /// Optional anchor-pinned room sequence: when /// is set, this array names the exact templates to use, in order. /// Empty for non-anchor layouts (procedural pipeline picks instead). /// [JsonPropertyName("pinned_rooms")] public PinnedRoomEntry[] PinnedRooms { get; init; } = Array.Empty(); } public sealed record PinnedRoomEntry { /// Room template id. Must reference a real . [JsonPropertyName("template")] public string Template { get; init; } = ""; /// Role assigned to this room slot: entry / transit / narrative / loot / boss / dead-end. [JsonPropertyName("role")] public string Role { get; init; } = "transit"; }