using Theriapolis.Core.Entities; namespace Theriapolis.Core.World.Settlements; /// /// Phase 6 M1 — runtime map between symbolic ids used by quest scripts / /// dialogue conditions and live world entities. Quest scripts never embed /// world coordinates; they reference NPCs by role tag and locations by /// anchor id (per master plan §8.4): /// /// /// anchor:millhaven → the live Settlement /// role:millhaven.innkeeper → the live NpcActor for that named role /// /// /// Built lazily as chunks stream in: when a settlement's buildings resolve /// (and any named NPC instantiates), the entry registers here. Phase 6 M2 /// persists the registry; M1 rebuilds it on every load from the live /// settlement list and active NpcActors. /// public sealed class AnchorRegistry { private readonly Dictionary _anchorToSettlementId = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _roleToNpcId = new(StringComparer.OrdinalIgnoreCase); /// Register a settlement under its anchor id (e.g. "anchor:millhaven"). public void RegisterAnchor(NarrativeAnchor anchor, int settlementId) { string key = $"anchor:{anchor.ToString().ToLowerInvariant()}"; _anchorToSettlementId[key] = settlementId; } /// Register an NpcActor under its named role tag (e.g. "role:millhaven.innkeeper"). public void RegisterRole(string roleTag, int npcId) { if (string.IsNullOrEmpty(roleTag)) return; if (!roleTag.Contains('.')) return; // generic role; only named (anchor.role) roles register _roleToNpcId[$"role:{roleTag.ToLowerInvariant()}"] = npcId; } /// Forget the role mapping (called on chunk evict / NPC despawn). public void UnregisterRole(string roleTag) { if (string.IsNullOrEmpty(roleTag)) return; _roleToNpcId.Remove($"role:{roleTag.ToLowerInvariant()}"); } /// Resolve "anchor:millhaven" → SettlementId (or null when not registered yet). public int? ResolveAnchor(string id) { return _anchorToSettlementId.TryGetValue(id, out int sid) ? sid : null; } /// Resolve "role:millhaven.innkeeper" → NpcId (or null when not loaded / not yet streamed). public int? ResolveRole(string id) { return _roleToNpcId.TryGetValue(id, out int nid) ? nid : null; } /// Bulk re-register every settlement's anchor (e.g. after world load). public void RegisterAllAnchors(WorldState world) { foreach (var s in world.Settlements) if (s.Anchor is { } a) RegisterAnchor(a, s.Id); } /// For diagnostics: every (id → entityId) mapping currently held. public IReadOnlyDictionary AllAnchors => _anchorToSettlementId; public IReadOnlyDictionary AllRoles => _roleToNpcId; public void Clear() { _anchorToSettlementId.Clear(); _roleToNpcId.Clear(); } }