using System.Collections.Concurrent; using NeonSprawl.Server.Game.Targeting; namespace NeonSprawl.Server.Game.Combat; /// Thread-safe in-memory HP for prototype combat dummies; empty at startup — lazy rows only (NEO-80). public sealed class InMemoryCombatEntityHealthStore : 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 || !PrototypeTargetRegistry.TryGet(key, out _)) { snapshot = default; return false; } lock (idLocks.GetOrAdd(key, _ => new object())) { EnsureRowInitialized(key); snapshot = CreateSnapshot(key, 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 || !PrototypeTargetRegistry.TryGet(key, out _)) { return false; } lock (idLocks.GetOrAdd(key, _ => new object())) { EnsureRowInitialized(key); var current = currentHpById[key]; var next = Math.Max(0, current - damage); currentHpById[key] = next; snapshot = CreateSnapshot(key, next); return true; } } /// public bool TryResetToFull(string? targetId, out CombatEntityHealthSnapshot snapshot) { var key = NormalizeTargetId(targetId); if (key.Length == 0 || !PrototypeTargetRegistry.TryGet(key, out _)) { snapshot = default; return false; } lock (idLocks.GetOrAdd(key, _ => new object())) { currentHpById[key] = PrototypeCombatConstants.MaxPrototypeTargetHp; snapshot = CreateSnapshot(key, PrototypeCombatConstants.MaxPrototypeTargetHp); return true; } } private void EnsureRowInitialized(string normalizedId) { if (!currentHpById.ContainsKey(normalizedId)) { currentHpById[normalizedId] = PrototypeCombatConstants.MaxPrototypeTargetHp; } } private static CombatEntityHealthSnapshot CreateSnapshot(string targetId, int currentHp) => new( targetId, PrototypeCombatConstants.MaxPrototypeTargetHp, currentHp, currentHp <= 0); private static string NormalizeTargetId(string? targetId) => targetId?.Trim().ToLowerInvariant() ?? string.Empty; }