using System.Collections.Concurrent; namespace NeonSprawl.Server.Game.Combat; /// Thread-safe in-memory session player combat HP; empty at startup — lazy rows only (NEO-95). public sealed class InMemoryPlayerCombatHealthStore : IPlayerCombatHealthStore { private readonly ConcurrentDictionary currentHpById = new(StringComparer.Ordinal); private readonly ConcurrentDictionary idLocks = new(StringComparer.Ordinal); /// public bool TryGet(string? playerId, out PlayerCombatHealthSnapshot snapshot) { var key = NormalizePlayerId(playerId); if (key.Length == 0) { snapshot = default; return false; } lock (idLocks.GetOrAdd(key, _ => new object())) { EnsureRowInitialized(key); snapshot = CreateSnapshot(key, currentHpById[key]); return true; } } /// public bool TryApplyDamage(string? playerId, int damage, out PlayerCombatHealthSnapshot snapshot) { snapshot = default; if (damage < 0) { return false; } var key = NormalizePlayerId(playerId); if (key.Length == 0) { 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? playerId, out PlayerCombatHealthSnapshot snapshot) { var key = NormalizePlayerId(playerId); if (key.Length == 0) { snapshot = default; return false; } lock (idLocks.GetOrAdd(key, _ => new object())) { currentHpById[key] = PlayerCombatHealthDefaults.MaxHp; snapshot = CreateSnapshot(key, PlayerCombatHealthDefaults.MaxHp); return true; } } private void EnsureRowInitialized(string normalizedId) { if (!currentHpById.ContainsKey(normalizedId)) { currentHpById[normalizedId] = PlayerCombatHealthDefaults.MaxHp; } } private static PlayerCombatHealthSnapshot CreateSnapshot(string playerId, int currentHp) => new( playerId, PlayerCombatHealthDefaults.MaxHp, currentHp, currentHp <= 0); private static string NormalizePlayerId(string? playerId) => playerId?.Trim().ToLowerInvariant() ?? string.Empty; }