using System.Collections.Concurrent;
using NeonSprawl.Server.Game.Npc;
namespace NeonSprawl.Server.Game.Combat;
/// Thread-safe in-memory HP for prototype NPC combat instances; empty at startup — lazy rows only (NEO-80, NEO-91).
public sealed class InMemoryCombatEntityHealthStore(INpcBehaviorDefinitionRegistry behaviorRegistry) : ICombatEntityHealthStore
{
private readonly ConcurrentDictionary currentHpById = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary idLocks = new(StringComparer.Ordinal);
///
public bool TryGet(string? targetId, out CombatEntityHealthSnapshot snapshot)
{
var key = NormalizeTargetId(targetId);
if (key.Length == 0 || !TryResolveMaxHp(key, out var maxHp))
{
snapshot = default;
return false;
}
lock (idLocks.GetOrAdd(key, _ => new object()))
{
EnsureRowInitialized(key, maxHp);
snapshot = CreateSnapshot(key, maxHp, currentHpById[key]);
return true;
}
}
///
public bool TryApplyDamage(string? targetId, int damage, out CombatEntityHealthSnapshot snapshot)
{
snapshot = default;
if (damage < 0)
{
return false;
}
var key = NormalizeTargetId(targetId);
if (key.Length == 0 || !TryResolveMaxHp(key, out var maxHp))
{
return false;
}
lock (idLocks.GetOrAdd(key, _ => new object()))
{
EnsureRowInitialized(key, maxHp);
var current = currentHpById[key];
var next = Math.Max(0, current - damage);
currentHpById[key] = next;
snapshot = CreateSnapshot(key, maxHp, next);
return true;
}
}
///
public bool TryResetToFull(string? targetId, out CombatEntityHealthSnapshot snapshot)
{
var key = NormalizeTargetId(targetId);
if (key.Length == 0 || !TryResolveMaxHp(key, out var maxHp))
{
snapshot = default;
return false;
}
lock (idLocks.GetOrAdd(key, _ => new object()))
{
currentHpById[key] = maxHp;
snapshot = CreateSnapshot(key, maxHp, maxHp);
return true;
}
}
private bool TryResolveMaxHp(string normalizedId, out int maxHp)
{
maxHp = 0;
if (!PrototypeNpcRegistry.TryGet(normalizedId, out var entry))
{
return false;
}
if (!behaviorRegistry.TryGetDefinition(entry.BehaviorDefId, out var definition))
{
return false;
}
maxHp = definition.MaxHp;
return true;
}
private void EnsureRowInitialized(string normalizedId, int maxHp)
{
if (!currentHpById.ContainsKey(normalizedId))
{
currentHpById[normalizedId] = maxHp;
}
}
private static CombatEntityHealthSnapshot CreateSnapshot(string targetId, int maxHp, int currentHp) =>
new(
targetId,
maxHp,
currentHp,
currentHp <= 0);
private static string NormalizeTargetId(string? targetId) =>
targetId?.Trim().ToLowerInvariant() ?? string.Empty;
}