using Microsoft.Extensions.Options; using NeonSprawl.Server.Game.AbilityInput; using NeonSprawl.Server.Game.Combat; using NeonSprawl.Server.Game.Npc; using NeonSprawl.Server.Game.PositionState; using NeonSprawl.Server.Tests.Game.Npc; using Xunit; namespace NeonSprawl.Server.Tests.Game.Combat; public sealed class NpcAttackOperationsTests { private static InMemoryPositionStateStore CreatePositionStoreAt(double x, double y, double z) => new(Options.Create(new GamePositionOptions { DevPlayerId = "dev-local-1", DefaultPosition = new DefaultPositionOptions { X = x, Y = y, Z = z }, })); [Fact] public void TryResolveTelegraphComplete_ShouldApplyCatalogDamage_WhenHolderMatchesAndInRange() { // Arrange var abilityRegistry = PrototypeNpcTestFixtures.CreateAbilityRegistry(); var threatStore = new InMemoryThreatStateStore(); var playerHealthStore = new InMemoryPlayerCombatHealthStore(); var positionStore = CreatePositionStoreAt(-3.0, 0.5, -3.0); _ = threatStore.TrySetHolder(PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1"); // Act var ok = NpcAttackOperations.TryResolveTelegraphComplete( PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", PrototypeNpcAttackAbilityRegistry.PrototypeNpcMeleeStrike, abilityRegistry, positionStore, playerHealthStore, threatStore); // Assert Assert.True(ok); playerHealthStore.TryGet("dev-local-1", out var snapshot); Assert.Equal(85, snapshot.CurrentHp); } [Fact] public void TryResolveTelegraphComplete_ShouldNoOp_WhenHolderOutOfAbilityMaxRange() { // Arrange var abilityRegistry = PrototypeNpcTestFixtures.CreateAbilityRegistry(); var threatStore = new InMemoryThreatStateStore(); var playerHealthStore = new InMemoryPlayerCombatHealthStore(); var positionStore = CreatePositionStoreAt(10.0, 0.5, 10.0); _ = threatStore.TrySetHolder(PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1"); // Act var ok = NpcAttackOperations.TryResolveTelegraphComplete( PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", PrototypeNpcAttackAbilityRegistry.PrototypeNpcMeleeStrike, abilityRegistry, positionStore, playerHealthStore, threatStore); // Assert Assert.False(ok); playerHealthStore.TryGet("dev-local-1", out var snapshot); Assert.Equal(PlayerCombatHealthDefaults.MaxHp, snapshot.CurrentHp); } [Fact] public void TryResolveTelegraphComplete_ShouldNoOp_WhenHolderBeyondAbilityMaxRangeButInsideAggroRadius() { // Arrange var abilityRegistry = PrototypeNpcTestFixtures.CreateAbilityRegistry(); var threatStore = new InMemoryThreatStateStore(); var playerHealthStore = new InMemoryPlayerCombatHealthStore(); var positionStore = CreatePositionStoreAt(-10.0, 0.5, -3.0); _ = threatStore.TrySetHolder(PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1"); // Act var ok = NpcAttackOperations.TryResolveTelegraphComplete( PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", PrototypeNpcAttackAbilityRegistry.PrototypeNpcMeleeStrike, abilityRegistry, positionStore, playerHealthStore, threatStore); // Assert Assert.False(ok); playerHealthStore.TryGet("dev-local-1", out var snapshot); Assert.Equal(PlayerCombatHealthDefaults.MaxHp, snapshot.CurrentHp); } [Fact] public void TryResolveTelegraphComplete_ShouldNoOp_WhenHolderCleared() { // Arrange var abilityRegistry = PrototypeNpcTestFixtures.CreateAbilityRegistry(); var threatStore = new InMemoryThreatStateStore(); var playerHealthStore = new InMemoryPlayerCombatHealthStore(); var positionStore = CreatePositionStoreAt(-3.0, 0.5, -3.0); _ = threatStore.TrySetHolder(PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1"); _ = threatStore.TryClearHolder(PrototypeNpcRegistry.PrototypeNpcMeleeId); // Act var ok = NpcAttackOperations.TryResolveTelegraphComplete( PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", PrototypeNpcAttackAbilityRegistry.PrototypeNpcMeleeStrike, abilityRegistry, positionStore, playerHealthStore, threatStore); // Assert Assert.False(ok); playerHealthStore.TryGet("dev-local-1", out var snapshot); Assert.Equal(PlayerCombatHealthDefaults.MaxHp, snapshot.CurrentHp); } [Fact] public void TryResolveTelegraphComplete_ShouldNoOp_WhenHolderPlayerDiffers() { // Arrange var abilityRegistry = PrototypeNpcTestFixtures.CreateAbilityRegistry(); var threatStore = new InMemoryThreatStateStore(); var playerHealthStore = new InMemoryPlayerCombatHealthStore(); var positionStore = CreatePositionStoreAt(-3.0, 0.5, -3.0); _ = threatStore.TrySetHolder(PrototypeNpcRegistry.PrototypeNpcMeleeId, "other-player"); // Act var ok = NpcAttackOperations.TryResolveTelegraphComplete( PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", PrototypeNpcAttackAbilityRegistry.PrototypeNpcMeleeStrike, abilityRegistry, positionStore, playerHealthStore, threatStore); // Assert Assert.False(ok); playerHealthStore.TryGet("dev-local-1", out var snapshot); Assert.Equal(PlayerCombatHealthDefaults.MaxHp, snapshot.CurrentHp); } [Fact] public void TryResolveTelegraphComplete_ShouldSucceedWithoutMutation_WhenAbilityBaseDamageZero() { // Arrange var abilityRegistry = PrototypeNpcTestFixtures.CreateAbilityRegistry(); var threatStore = new InMemoryThreatStateStore(); var playerHealthStore = new InMemoryPlayerCombatHealthStore(); var positionStore = CreatePositionStoreAt(10.0, 0.5, 10.0); _ = threatStore.TrySetHolder(PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1"); // Act var ok = NpcAttackOperations.TryResolveTelegraphComplete( PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", PrototypeAbilityRegistry.PrototypeGuard, abilityRegistry, positionStore, playerHealthStore, threatStore); // Assert Assert.True(ok); playerHealthStore.TryGet("dev-local-1", out var snapshot); Assert.Equal(PlayerCombatHealthDefaults.MaxHp, snapshot.CurrentHp); } }