Files
TheriapolisV3/Theriapolis.Core/World/Settlements/AnchorRegistry.cs
T
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

78 lines
3.1 KiB
C#

using Theriapolis.Core.Entities;
namespace Theriapolis.Core.World.Settlements;
/// <summary>
/// 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):
///
/// <code>
/// anchor:millhaven → the live Settlement
/// role:millhaven.innkeeper → the live NpcActor for that named role
/// </code>
///
/// 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.
/// </summary>
public sealed class AnchorRegistry
{
private readonly Dictionary<string, int> _anchorToSettlementId = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, int> _roleToNpcId = new(StringComparer.OrdinalIgnoreCase);
/// <summary>Register a settlement under its anchor id (e.g. "anchor:millhaven").</summary>
public void RegisterAnchor(NarrativeAnchor anchor, int settlementId)
{
string key = $"anchor:{anchor.ToString().ToLowerInvariant()}";
_anchorToSettlementId[key] = settlementId;
}
/// <summary>Register an NpcActor under its named role tag (e.g. "role:millhaven.innkeeper").</summary>
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;
}
/// <summary>Forget the role mapping (called on chunk evict / NPC despawn).</summary>
public void UnregisterRole(string roleTag)
{
if (string.IsNullOrEmpty(roleTag)) return;
_roleToNpcId.Remove($"role:{roleTag.ToLowerInvariant()}");
}
/// <summary>Resolve "anchor:millhaven" → SettlementId (or null when not registered yet).</summary>
public int? ResolveAnchor(string id)
{
return _anchorToSettlementId.TryGetValue(id, out int sid) ? sid : null;
}
/// <summary>Resolve "role:millhaven.innkeeper" → NpcId (or null when not loaded / not yet streamed).</summary>
public int? ResolveRole(string id)
{
return _roleToNpcId.TryGetValue(id, out int nid) ? nid : null;
}
/// <summary>Bulk re-register every settlement's anchor (e.g. after world load).</summary>
public void RegisterAllAnchors(WorldState world)
{
foreach (var s in world.Settlements)
if (s.Anchor is { } a)
RegisterAnchor(a, s.Id);
}
/// <summary>For diagnostics: every (id → entityId) mapping currently held.</summary>
public IReadOnlyDictionary<string, int> AllAnchors => _anchorToSettlementId;
public IReadOnlyDictionary<string, int> AllRoles => _roleToNpcId;
public void Clear()
{
_anchorToSettlementId.Clear();
_roleToNpcId.Clear();
}
}