Files
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00

224 lines
8.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Text.Json.Serialization;
namespace Theriapolis.Core.Data;
/// <summary>
/// Phase 7 M0 — a single hand-authored dungeon room. Loaded from
/// <c>Content/Data/room_templates/&lt;type&gt;/*.json</c>.
///
/// A template describes:
/// - The room's footprint in tactical tiles (perimeter walls included).
/// - The 2D ASCII <see cref="Grid"/>: one char per tactical tile.
/// Legend (per Phase 7 plan §5.1):
/// <c>#</c> wall, <c>.</c> floor, <c>,</c> rubble, <c>D</c> door slot,
/// <c>@</c> encounter slot, <c>C</c> container slot, <c>T</c> trap slot,
/// <c>P</c> pillar, <c>B</c> brazier, <c>M</c> mosaic (narrative),
/// <c>S</c> stairs (entry/exit only).
/// - Door positions on the perimeter (one entry per <c>D</c> in the grid).
/// - Encounter / container / trap slot positions (which the dungeon
/// populator fills with NPCs and loot).
/// - Optional narrative text surfaced by Scent Literacy / room-clear coda.
/// - The clade that <see cref="BuiltBy"/> the room — drives the
/// clade-responsive movement multiplier (Phase 7 plan §5.4).
///
/// Templates are designer-friendly to author: edit ASCII art + a couple
/// of metadata blocks. <see cref="ContentLoader.LoadRoomTemplates"/>
/// validates grid dimensions vs declared footprint, perimeter walls,
/// and that every <c>D</c>/<c>@</c>/<c>C</c>/<c>T</c> in the grid has
/// a matching slot record.
/// </summary>
public sealed record RoomTemplateDef
{
[JsonPropertyName("id")]
public string Id { get; init; } = "";
[JsonPropertyName("name")]
public string Name { get; init; } = "";
/// <summary>
/// Dungeon type the template belongs to: <c>imperium</c>, <c>mine</c>,
/// <c>cult</c>, <c>cave</c>, <c>overgrown</c>. Layout matchers filter
/// templates by type when assembling a dungeon.
/// </summary>
[JsonPropertyName("type")]
public string Type { get; init; } = "";
/// <summary>
/// Clade that built or originally inhabited the room. Drives Phase 7
/// clade-responsive movement (a Large PC in a Mustelid tunnel takes 2×
/// movement points). Allowed: <c>canid</c>, <c>felid</c>, <c>mustelid</c>,
/// <c>ursid</c>, <c>cervid</c>, <c>bovid</c>, <c>leporid</c>,
/// <c>imperium</c>, <c>none</c>. Phase-7 Imperium templates use
/// <c>imperium</c>; templates that would be at home in any dungeon use
/// <c>none</c>.
/// </summary>
[JsonPropertyName("built_by")]
public string BuiltBy { get; init; } = "none";
/// <summary>
/// Size class — <c>small</c>, <c>medium</c>, <c>large</c>. Used by
/// the layout matcher to pick room mixes appropriate to the dungeon's
/// size band (small dungeons prefer small rooms, etc.).
/// </summary>
[JsonPropertyName("size_class")]
public string SizeClass { get; init; } = "medium";
/// <summary>
/// Roles this template is eligible for: <c>entry</c>, <c>transit</c>,
/// <c>narrative</c>, <c>loot</c>, <c>boss</c>, <c>dead-end</c>. A
/// template can be eligible for multiple roles (a "pillar room" can
/// serve as transit OR as a loot stash).
/// </summary>
[JsonPropertyName("roles_eligible")]
public string[] RolesEligible { get; init; } = Array.Empty<string>();
/// <summary>Footprint width in tactical tiles. Includes perimeter walls. Must equal Grid[*].Length.</summary>
[JsonPropertyName("footprint_w_tiles")]
public int FootprintWTiles { get; init; } = 1;
/// <summary>Footprint height in tactical tiles. Includes perimeter walls. Must equal Grid.Length.</summary>
[JsonPropertyName("footprint_h_tiles")]
public int FootprintHTiles { get; init; } = 1;
/// <summary>
/// 2D ASCII art: one entry per row, one char per tactical tile.
/// Validated for perimeter wall completeness and slot-coordinate
/// matches at content-load time.
/// </summary>
[JsonPropertyName("grid")]
public string[] Grid { get; init; } = Array.Empty<string>();
/// <summary>Door positions in template-local coords (matches <c>D</c> chars in <see cref="Grid"/>).</summary>
[JsonPropertyName("doors")]
public RoomDoor[] Doors { get; init; } = Array.Empty<RoomDoor>();
/// <summary>Encounter slot positions (matches <c>@</c> chars).</summary>
[JsonPropertyName("encounter_slots")]
public RoomEncounterSlot[] EncounterSlots { get; init; } = Array.Empty<RoomEncounterSlot>();
/// <summary>Container slot positions (matches <c>C</c> chars).</summary>
[JsonPropertyName("container_slots")]
public RoomContainerSlot[] ContainerSlots { get; init; } = Array.Empty<RoomContainerSlot>();
/// <summary>Trap slot positions (matches <c>T</c> chars). Phase 7 ships only tripwire traps.</summary>
[JsonPropertyName("trap_slots")]
public RoomTrapSlot[] TrapSlots { get; init; } = Array.Empty<RoomTrapSlot>();
/// <summary>Decoration placements for non-slot decos (P pillar, B brazier, M mosaic).</summary>
[JsonPropertyName("decos")]
public RoomDecoPlacement[] Decos { get; init; } = Array.Empty<RoomDecoPlacement>();
/// <summary>
/// Environmental-story prose surfaced by Scent Literacy (Phase 6.5 M1)
/// in the InteractionScreen scent-overlay panel and by the dungeon-
/// clear coda. Null/empty for non-narrative templates.
/// </summary>
[JsonPropertyName("narrative_text")]
public string? NarrativeText { get; init; } = null;
/// <summary>Selection weight in layout assembly. Default 1.0.</summary>
[JsonPropertyName("weight")]
public float Weight { get; init; } = 1f;
}
public sealed record RoomDoor
{
[JsonPropertyName("x")]
public int X { get; init; }
[JsonPropertyName("y")]
public int Y { get; init; }
/// <summary>Compass facing: "N" / "E" / "S" / "W". Door always sits on a perimeter cell.</summary>
[JsonPropertyName("facing")]
public string Facing { get; init; } = "S";
/// <summary>
/// Optional lock difficulty for this door. Empty = unlocked. Allowed:
/// <c>trivial</c>, <c>easy</c>, <c>medium</c>, <c>hard</c> — mapped to
/// <c>LOCK_DC_*</c> constants in code.
/// </summary>
[JsonPropertyName("lock")]
public string Lock { get; init; } = "";
}
public sealed record RoomEncounterSlot
{
[JsonPropertyName("x")]
public int X { get; init; }
[JsonPropertyName("y")]
public int Y { get; init; }
/// <summary>
/// Spawn kind: <c>PoiGuard</c> / <c>WildAnimal</c> / <c>Brigand</c> /
/// <c>Boss</c>. Resolved against <c>npc_templates.json</c>'s
/// per-dungeon-type spawn-kind override map at populate time.
/// </summary>
[JsonPropertyName("kind")]
public string Kind { get; init; } = "PoiGuard";
/// <summary>Likelihood the slot fires when a layout calls for variability. 1.0 = always.</summary>
[JsonPropertyName("weight")]
public float Weight { get; init; } = 1f;
}
public sealed record RoomContainerSlot
{
[JsonPropertyName("x")]
public int X { get; init; }
[JsonPropertyName("y")]
public int Y { get; init; }
/// <summary>
/// Loot-table band: <c>t1</c> / <c>t2</c> / <c>t3</c>. The dungeon's
/// layout maps a band to a real loot-table id at populate time
/// (<see cref="DungeonLayoutDef.LootTablePerBand"/>).
/// </summary>
[JsonPropertyName("loot_table_band")]
public string LootTableBand { get; init; } = "t1";
/// <summary>True when the container is locked (key required, or STR/DEX check).</summary>
[JsonPropertyName("locked")]
public bool Locked { get; init; } = false;
/// <summary>Optional lock difficulty if <see cref="Locked"/>: trivial/easy/medium/hard.</summary>
[JsonPropertyName("lock")]
public string Lock { get; init; } = "";
}
public sealed record RoomTrapSlot
{
[JsonPropertyName("x")]
public int X { get; init; }
[JsonPropertyName("y")]
public int Y { get; init; }
/// <summary>Trap kind. Phase 7 ships only <c>tripwire</c>.</summary>
[JsonPropertyName("kind")]
public string Kind { get; init; } = "tripwire";
/// <summary>Disarm DC tier: <c>trivial</c>, <c>easy</c>, <c>medium</c>.</summary>
[JsonPropertyName("disarm_dc")]
public string DisarmDc { get; init; } = "easy";
}
public sealed record RoomDecoPlacement
{
[JsonPropertyName("x")]
public int X { get; init; }
[JsonPropertyName("y")]
public int Y { get; init; }
/// <summary>
/// Deco kind name. Allowed: <c>pillar</c>, <c>brazier</c>, <c>mosaic</c>,
/// <c>imperium_statue</c>. Trap / container / door / stairs decos are
/// declared via their respective slot collections, not here.
/// </summary>
[JsonPropertyName("deco")]
public string Deco { get; init; } = "";
}