neon-sprawl/server/NeonSprawl.Server/Game/Combat/InMemoryPlayerCombatHealthS...

92 lines
2.7 KiB
C#

using System.Collections.Concurrent;
namespace NeonSprawl.Server.Game.Combat;
/// <summary>Thread-safe in-memory session player combat HP; empty at startup — lazy rows only (NEO-95).</summary>
public sealed class InMemoryPlayerCombatHealthStore : IPlayerCombatHealthStore
{
private readonly ConcurrentDictionary<string, int> currentHpById = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, object> idLocks = new(StringComparer.Ordinal);
/// <inheritdoc />
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;
}
}
/// <inheritdoc />
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;
}
}
/// <inheritdoc />
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;
}