namespace Theriapolis.Core.Data; /// /// Pre-loaded content lookup tables. Constructing one calls every loader /// exactly once and indexes results by id, so subsequent /// resolver.Clades["canidae"] 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. /// public sealed class ContentResolver { public IReadOnlyDictionary Clades { get; } public IReadOnlyDictionary Species { get; } public IReadOnlyDictionary Classes { get; } public IReadOnlyDictionary Subclasses { get; } public IReadOnlyDictionary Backgrounds { get; } public IReadOnlyDictionary Items { get; } public IReadOnlyDictionary LootTables { get; } public NpcTemplateContent Npcs { get; } /// Phase 6 M0 — building templates + settlement layouts. public SettlementContent Settlements { get; } /// Phase 6 M1 — pre-meeting bias profiles per reputation.md §I-1. public IReadOnlyDictionary BiasProfiles { get; } /// Phase 6 M2 — faction definitions including opposition matrix entries. public IReadOnlyDictionary Factions { get; } /// Phase 6 M1 — generic + named friendly/neutral resident templates. public IReadOnlyDictionary Residents { get; } /// /// Phase 6 M1 — fast lookup of named residents by anchor-prefixed role /// tag (e.g. "millhaven.innkeeper" → ResidentTemplateDef). Generic /// templates live in ; this index only holds /// the entries with named: true. /// public IReadOnlyDictionary ResidentsByRoleTag { get; } /// Phase 6 M3 — dialogue trees indexed by id. public IReadOnlyDictionary Dialogues { get; } /// Phase 6 M4 — quest trees indexed by id. public IReadOnlyDictionary Quests { get; } /// Phase 7 M0 — room templates indexed by id (every dungeon type). public IReadOnlyDictionary RoomTemplates { get; } /// /// Phase 7 M0 — room templates indexed by dungeon type (e.g. imperium /// → IList of all imperium-typed templates). Used by the layout matcher /// to filter candidates without a linear scan. /// public IReadOnlyDictionary> RoomTemplatesByType { get; } /// Phase 7 M0 — dungeon layouts indexed by id. public IReadOnlyDictionary DungeonLayouts { get; } /// /// Phase 7 M0 — anchor-locked layouts indexed by anchor id (e.g. /// OldHowlMine → the pinned 3-room layout). Procedural pipeline /// never picks anchor-locked layouts; the anchor resolver consults this /// dict directly. /// public IReadOnlyDictionary 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)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); } }