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();
}
}