93 lines
2.9 KiB
C#
93 lines
2.9 KiB
C#
using System.Collections.Concurrent;
|
|
using NeonSprawl.Server.Game.Targeting;
|
|
|
|
namespace NeonSprawl.Server.Game.Combat;
|
|
|
|
/// <summary>Thread-safe in-memory HP for prototype combat dummies; empty at startup — lazy rows only (NEO-80).</summary>
|
|
public sealed class InMemoryCombatEntityHealthStore : ICombatEntityHealthStore
|
|
{
|
|
private readonly ConcurrentDictionary<string, int> currentHpById = new(StringComparer.Ordinal);
|
|
|
|
private readonly ConcurrentDictionary<string, object> idLocks = new(StringComparer.Ordinal);
|
|
|
|
/// <inheritdoc />
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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;
|
|
}
|