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