81 lines
2.5 KiB
C#
81 lines
2.5 KiB
C#
using NeonSprawl.Server.Game.Npc;
|
|
using NeonSprawl.Server.Game.PositionState;
|
|
using NeonSprawl.Server.Game.World;
|
|
|
|
namespace NeonSprawl.Server.Game.Combat;
|
|
|
|
/// <summary>Deterministic NPC telegraph-complete damage resolve (NEO-95 / NEO-98).</summary>
|
|
public static class NpcAttackOperations
|
|
{
|
|
/// <summary>
|
|
/// Applies catalog ability <c>baseDamage</c> to <paramref name="holderPlayerId"/> when the threat row still
|
|
/// names that holder for <paramref name="npcInstanceId"/> and the holder is within the attack ability's
|
|
/// <see cref="AbilityDefRow.MaxRange"/> of the NPC anchor.
|
|
/// </summary>
|
|
public static bool TryResolveTelegraphComplete(
|
|
string npcInstanceId,
|
|
string holderPlayerId,
|
|
string attackAbilityId,
|
|
IAbilityDefinitionRegistry abilityRegistry,
|
|
IPositionStateStore positionStore,
|
|
IPlayerCombatHealthStore playerHealthStore,
|
|
IThreatStateStore threatStore)
|
|
{
|
|
if (!abilityRegistry.TryGetDefinition(attackAbilityId, out var ability))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ability.BaseDamage <= 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(holderPlayerId) ||
|
|
!threatStore.TryGet(npcInstanceId, out var threat) ||
|
|
string.IsNullOrEmpty(threat.AggroHolderPlayerId) ||
|
|
!string.Equals(threat.AggroHolderPlayerId, holderPlayerId, StringComparison.Ordinal))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!IsHolderWithinAbilityMaxRange(npcInstanceId, holderPlayerId, ability.MaxRange, positionStore))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!playerHealthStore.TryApplyDamage(holderPlayerId, ability.BaseDamage, out var snapshot))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (snapshot.Defeated)
|
|
{
|
|
// TODO(E9.M1): player_death
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static bool IsHolderWithinAbilityMaxRange(
|
|
string npcInstanceId,
|
|
string holderPlayerId,
|
|
double maxRange,
|
|
IPositionStateStore positionStore)
|
|
{
|
|
if (maxRange <= 0 ||
|
|
!PrototypeNpcRegistry.TryGet(npcInstanceId, out var entry) ||
|
|
!positionStore.TryGetPosition(holderPlayerId, out var playerSnap))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return HorizontalReach.IsWithinHorizontalRadius(
|
|
playerSnap.X,
|
|
playerSnap.Z,
|
|
entry.X,
|
|
entry.Z,
|
|
maxRange);
|
|
}
|
|
}
|