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

135 lines
7.0 KiB
C#

namespace Theriapolis.Core.Data;
/// <summary>
/// Pre-loaded content lookup tables. Constructing one calls every loader
/// exactly once and indexes results by id, so subsequent
/// <c>resolver.Clades["canidae"]</c> lookups are O(1).
///
/// Used by character creation, save/load (id → def resolution), and Phase 5 M5
/// NPC instantiation. Shared across screens that need any combination of
/// these tables.
/// </summary>
public sealed class ContentResolver
{
public IReadOnlyDictionary<string, CladeDef> Clades { get; }
public IReadOnlyDictionary<string, SpeciesDef> Species { get; }
public IReadOnlyDictionary<string, ClassDef> Classes { get; }
public IReadOnlyDictionary<string, SubclassDef> Subclasses { get; }
public IReadOnlyDictionary<string, BackgroundDef> Backgrounds { get; }
public IReadOnlyDictionary<string, ItemDef> Items { get; }
public IReadOnlyDictionary<string, LootTableDef> LootTables { get; }
public NpcTemplateContent Npcs { get; }
/// <summary>Phase 6 M0 — building templates + settlement layouts.</summary>
public SettlementContent Settlements { get; }
/// <summary>Phase 6 M1 — pre-meeting bias profiles per <c>reputation.md §I-1</c>.</summary>
public IReadOnlyDictionary<string, BiasProfileDef> BiasProfiles { get; }
/// <summary>Phase 6 M2 — faction definitions including opposition matrix entries.</summary>
public IReadOnlyDictionary<string, FactionDef> Factions { get; }
/// <summary>Phase 6 M1 — generic + named friendly/neutral resident templates.</summary>
public IReadOnlyDictionary<string, ResidentTemplateDef> Residents { get; }
/// <summary>
/// Phase 6 M1 — fast lookup of named residents by anchor-prefixed role
/// tag (e.g. "millhaven.innkeeper" → ResidentTemplateDef). Generic
/// templates live in <see cref="Residents"/>; this index only holds
/// the entries with <c>named: true</c>.
/// </summary>
public IReadOnlyDictionary<string, ResidentTemplateDef> ResidentsByRoleTag { get; }
/// <summary>Phase 6 M3 — dialogue trees indexed by id.</summary>
public IReadOnlyDictionary<string, DialogueDef> Dialogues { get; }
/// <summary>Phase 6 M4 — quest trees indexed by id.</summary>
public IReadOnlyDictionary<string, QuestDef> Quests { get; }
/// <summary>Phase 7 M0 — room templates indexed by id (every dungeon type).</summary>
public IReadOnlyDictionary<string, RoomTemplateDef> RoomTemplates { get; }
/// <summary>
/// Phase 7 M0 — room templates indexed by dungeon type (e.g. <c>imperium</c>
/// → IList of all imperium-typed templates). Used by the layout matcher
/// to filter candidates without a linear scan.
/// </summary>
public IReadOnlyDictionary<string, IReadOnlyList<RoomTemplateDef>> RoomTemplatesByType { get; }
/// <summary>Phase 7 M0 — dungeon layouts indexed by id.</summary>
public IReadOnlyDictionary<string, DungeonLayoutDef> DungeonLayouts { get; }
/// <summary>
/// Phase 7 M0 — anchor-locked layouts indexed by anchor id (e.g.
/// <c>OldHowlMine</c> → the pinned 3-room layout). Procedural pipeline
/// never picks anchor-locked layouts; the anchor resolver consults this
/// dict directly.
/// </summary>
public IReadOnlyDictionary<string, DungeonLayoutDef> DungeonLayoutsByAnchor { get; }
public ContentResolver(ContentLoader loader)
{
var clades = loader.LoadClades();
Clades = clades.ToDictionary(c => c.Id, StringComparer.OrdinalIgnoreCase);
var speciesArr = loader.LoadSpecies(clades);
Species = speciesArr.ToDictionary(s => s.Id, StringComparer.OrdinalIgnoreCase);
var classes = loader.LoadClasses();
Classes = classes.ToDictionary(c => c.Id, StringComparer.OrdinalIgnoreCase);
Subclasses = loader.LoadSubclasses(classes).ToDictionary(s => s.Id, StringComparer.OrdinalIgnoreCase);
Backgrounds = loader.LoadBackgrounds().ToDictionary(b => b.Id, StringComparer.OrdinalIgnoreCase);
var items = loader.LoadItems();
Items = items.ToDictionary(i => i.Id, StringComparer.OrdinalIgnoreCase);
LootTables = loader.LoadLootTables(items).ToDictionary(t => t.Id, StringComparer.OrdinalIgnoreCase);
// Phase 6 M5 — factions loaded early so NpcTemplates can validate
// their faction field references against the canonical list.
var factionsArr = loader.LoadFactions();
Factions = factionsArr.ToDictionary(f => f.Id, StringComparer.OrdinalIgnoreCase);
Npcs = loader.LoadNpcTemplates(items, factionsArr);
// Phase 6 M0 — building/layout content.
var buildings = loader.LoadBuildingTemplates();
var layouts = loader.LoadSettlementLayouts(buildings);
var byId = buildings.ToDictionary(b => b.Id, StringComparer.OrdinalIgnoreCase);
var preset = layouts.Where(l => l.Kind == "preset")
.ToDictionary(l => l.Anchor, StringComparer.OrdinalIgnoreCase);
var proc = layouts.Where(l => l.Kind == "procedural")
.ToDictionary(l => l.Tier);
Settlements = new SettlementContent(byId, preset, proc);
// Phase 6 M1 — bias profiles + resident templates.
// (factionsArr already loaded above for NpcTemplates validation.)
var biasArr = loader.LoadBiasProfiles(clades, factionsArr);
BiasProfiles = biasArr.ToDictionary(b => b.Id, StringComparer.OrdinalIgnoreCase);
var residentArr = loader.LoadResidentTemplates(biasArr, clades, speciesArr, factionsArr);
Residents = residentArr.ToDictionary(r => r.Id, StringComparer.OrdinalIgnoreCase);
ResidentsByRoleTag = residentArr
.Where(r => r.Named)
.ToDictionary(r => r.RoleTag, StringComparer.OrdinalIgnoreCase);
// Phase 6 M3 — dialogue trees.
Dialogues = loader.LoadDialogues(items)
.ToDictionary(d => d.Id, StringComparer.OrdinalIgnoreCase);
// Phase 6 M4 — quest trees.
Quests = loader.LoadQuests(items)
.ToDictionary(q => q.Id, StringComparer.OrdinalIgnoreCase);
// Phase 7 M0 — room templates + dungeon layouts.
var roomArr = loader.LoadRoomTemplates();
RoomTemplates = roomArr.ToDictionary(r => r.Id, StringComparer.OrdinalIgnoreCase);
RoomTemplatesByType = roomArr
.GroupBy(r => r.Type, StringComparer.OrdinalIgnoreCase)
.ToDictionary(
g => g.Key,
g => (IReadOnlyList<RoomTemplateDef>)g.ToArray(),
StringComparer.OrdinalIgnoreCase);
var layoutArr = loader.LoadDungeonLayouts(roomArr, LootTables.Values.ToArray());
DungeonLayouts = layoutArr.ToDictionary(l => l.Id, StringComparer.OrdinalIgnoreCase);
DungeonLayoutsByAnchor = layoutArr
.Where(l => !string.IsNullOrEmpty(l.Anchor))
.ToDictionary(l => l.Anchor, StringComparer.OrdinalIgnoreCase);
}
}