using System.Collections.Concurrent;
namespace NeonSprawl.Server.Game.Npc;
/// Thread-safe in-memory aggro holders for prototype NPC instances (NEO-92).
public sealed class InMemoryThreatStateStore : IThreatStateStore
{
private readonly ConcurrentDictionary holderByNpcId = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary idLocks = new(StringComparer.Ordinal);
///
public bool TryGet(string? npcInstanceId, out ThreatStateSnapshot snapshot)
{
var key = NormalizeNpcInstanceId(npcInstanceId);
if (key.Length == 0 || !PrototypeNpcRegistry.TryGet(key, out _))
{
snapshot = default;
return false;
}
lock (idLocks.GetOrAdd(key, _ => new object()))
{
if (!holderByNpcId.TryGetValue(key, out var holder))
{
holder = null;
holderByNpcId[key] = holder;
}
snapshot = new ThreatStateSnapshot(key, holder);
return true;
}
}
///
public bool TrySetHolder(string? npcInstanceId, string? playerIdLowercaseOrNull)
{
var key = NormalizeNpcInstanceId(npcInstanceId);
if (key.Length == 0 || !PrototypeNpcRegistry.TryGet(key, out _))
{
return false;
}
var normalizedHolder = NormalizePlayerId(playerIdLowercaseOrNull);
lock (idLocks.GetOrAdd(key, _ => new object()))
{
holderByNpcId[key] = normalizedHolder;
return true;
}
}
///
public bool TryClearHolder(string? npcInstanceId) => TrySetHolder(npcInstanceId, null);
private static string NormalizeNpcInstanceId(string? npcInstanceId) =>
npcInstanceId?.Trim().ToLowerInvariant() ?? string.Empty;
private static string? NormalizePlayerId(string? playerId)
{
if (string.IsNullOrWhiteSpace(playerId))
{
return null;
}
return playerId.Trim().ToLowerInvariant();
}
}