110 lines
3.3 KiB
C#
110 lines
3.3 KiB
C#
using System.Collections.Concurrent;
|
|
using NeonSprawl.Server.Game.Npc;
|
|
|
|
namespace NeonSprawl.Server.Game.Combat;
|
|
|
|
/// <summary>Thread-safe in-memory HP for prototype NPC combat instances; empty at startup — lazy rows only (NEO-80, NEO-91).</summary>
|
|
public sealed class InMemoryCombatEntityHealthStore(INpcBehaviorDefinitionRegistry behaviorRegistry) : 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 || !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;
|
|
}
|
|
}
|
|
|
|
/// <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 || !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;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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;
|
|
}
|