neon-sprawl/server/NeonSprawl.Server/Game/Npc/AggroOperations.cs

109 lines
3.5 KiB
C#

using NeonSprawl.Server.Game.PositionState;
using NeonSprawl.Server.Game.World;
namespace NeonSprawl.Server.Game.Npc;
/// <summary>Deterministic prototype aggro acquire and leash-clear rules (NEO-92).</summary>
public static class AggroOperations
{
/// <summary>
/// Sets aggro holder to <paramref name="playerId"/> when the NPC row is empty.
/// Returns <c>true</c> only when holder was empty and is now set.
/// </summary>
public static bool TryAcquire(string npcInstanceId, string playerId, IThreatStateStore threatStore)
{
var npcKey = NormalizeId(npcInstanceId);
var playerKey = NormalizeId(playerId);
if (npcKey.Length == 0 || playerKey.Length == 0 || !PrototypeNpcRegistry.TryGet(npcKey, out _))
{
return false;
}
if (!threatStore.TryGet(npcKey, out var current))
{
return false;
}
if (!string.IsNullOrEmpty(current.AggroHolderPlayerId))
{
return false;
}
return threatStore.TrySetHolder(npcKey, playerKey);
}
/// <summary>
/// Clears aggro on every prototype NPC instance where <paramref name="playerId"/> is holder
/// and horizontal distance from the NPC anchor exceeds catalog <c>leashRadius</c>.
/// </summary>
public static void TryClearOnLeashForPlayer(
string playerId,
IPositionStateStore positions,
INpcBehaviorDefinitionRegistry behaviorRegistry,
IThreatStateStore threatStore)
{
var playerKey = NormalizeId(playerId);
if (playerKey.Length == 0 || !positions.TryGetPosition(playerKey, out var playerSnap))
{
return;
}
foreach (var npcInstanceId in PrototypeNpcRegistry.GetInstanceIdsInOrder())
{
TryClearOnLeashForNpc(
npcInstanceId,
playerKey,
in playerSnap,
behaviorRegistry,
threatStore);
}
}
/// <summary>Clears aggro for one NPC when the holder is outside that NPC's leash radius.</summary>
public static void TryClearOnLeashForNpc(
string npcInstanceId,
string playerIdLowercase,
in PositionSnapshot playerSnap,
INpcBehaviorDefinitionRegistry behaviorRegistry,
IThreatStateStore threatStore)
{
var npcKey = NormalizeId(npcInstanceId);
if (npcKey.Length == 0 ||
!PrototypeNpcRegistry.TryGet(npcKey, out var entry) ||
!threatStore.TryGet(npcKey, out var threat) ||
string.IsNullOrEmpty(threat.AggroHolderPlayerId) ||
!string.Equals(threat.AggroHolderPlayerId, playerIdLowercase, StringComparison.Ordinal))
{
return;
}
if (!behaviorRegistry.TryGetDefinition(entry.BehaviorDefId, out var behavior))
{
return;
}
var distance = HorizontalReach.HorizontalDistance(
playerSnap.X,
playerSnap.Z,
entry.X,
entry.Z);
if (distance > behavior.LeashRadius)
{
threatStore.TryClearHolder(npcKey);
}
}
/// <summary>Clears aggro holders on all prototype NPC instances (dev fixture).</summary>
public static void ClearAllPrototypeHolders(IThreatStateStore threatStore)
{
foreach (var npcInstanceId in PrototypeNpcRegistry.GetInstanceIdsInOrder())
{
threatStore.TryClearHolder(npcInstanceId);
}
}
private static string NormalizeId(string? raw) =>
raw?.Trim().ToLowerInvariant() ?? string.Empty;
}