using Microsoft.Extensions.DependencyInjection; using NeonSprawl.Server.Game.AbilityInput; using NeonSprawl.Server.Game.Combat; using NeonSprawl.Server.Game.Npc; using NeonSprawl.Server.Tests; using NeonSprawl.Server.Tests.Game.Npc; using Xunit; namespace NeonSprawl.Server.Tests.Game.Combat; public sealed class CombatOperationsTests { private static AbilityDefinitionRegistry CreatePrototypeRegistry() { var rows = new Dictionary(StringComparer.Ordinal) { [PrototypeAbilityRegistry.PrototypeBurst] = new AbilityDefRow( PrototypeAbilityRegistry.PrototypeBurst, "Prototype Burst", 40, 5.0, 6.0, "attack"), [PrototypeAbilityRegistry.PrototypeDash] = new AbilityDefRow( PrototypeAbilityRegistry.PrototypeDash, "Prototype Dash", 0, 4.0, 6.0, "movement"), [PrototypeAbilityRegistry.PrototypeGuard] = new AbilityDefRow( PrototypeAbilityRegistry.PrototypeGuard, "Prototype Guard", 0, 6.0, 6.0, "utility"), [PrototypeAbilityRegistry.PrototypePulse] = new AbilityDefRow( PrototypeAbilityRegistry.PrototypePulse, "Prototype Pulse", 25, 3.0, 6.0, "attack"), }; var catalog = new AbilityDefinitionCatalog("/tmp/abilities", rows, catalogJsonFileCount: 1); return new AbilityDefinitionRegistry(catalog); } [Fact] public void TryResolve_WithPulseOnFreshAlpha_ShouldDealCatalogDamage() { // Arrange var registry = CreatePrototypeRegistry(); var store = PrototypeNpcTestFixtures.CreateHealthStore(); // Act var result = CombatOperations.TryResolve( PrototypeAbilityRegistry.PrototypePulse, PrototypeNpcRegistry.PrototypeNpcMeleeId, registry, store); // Assert Assert.True(result.Success); Assert.Null(result.ReasonCode); Assert.Equal(25, result.DamageDealt); Assert.Equal(75, result.TargetRemainingHp); Assert.False(result.TargetDefeated); } [Fact] public void TryResolve_WithFourPulses_ShouldDefeatTargetOnFourthResolve() { // Arrange var registry = CreatePrototypeRegistry(); var store = PrototypeNpcTestFixtures.CreateHealthStore(); for (var i = 0; i < 3; i++) { _ = CombatOperations.TryResolve( PrototypeAbilityRegistry.PrototypePulse, PrototypeNpcRegistry.PrototypeNpcMeleeId, registry, store); } // Act var result = CombatOperations.TryResolve( PrototypeAbilityRegistry.PrototypePulse, PrototypeNpcRegistry.PrototypeNpcMeleeId, registry, store); // Assert Assert.True(result.Success); Assert.Equal(0, result.TargetRemainingHp); Assert.True(result.TargetDefeated); } [Fact] public void TryResolve_OnDefeatedTarget_ShouldDenyWithTargetDefeated() { // Arrange var registry = CreatePrototypeRegistry(); var store = PrototypeNpcTestFixtures.CreateHealthStore(); for (var i = 0; i < 4; i++) { _ = CombatOperations.TryResolve( PrototypeAbilityRegistry.PrototypePulse, PrototypeNpcRegistry.PrototypeNpcMeleeId, registry, store); } // Act var result = CombatOperations.TryResolve( PrototypeAbilityRegistry.PrototypePulse, PrototypeNpcRegistry.PrototypeNpcMeleeId, registry, store); store.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot); // Assert Assert.False(result.Success); Assert.Equal(CombatReasonCodes.TargetDefeated, result.ReasonCode); Assert.Equal(0, result.DamageDealt); Assert.Equal(0, result.TargetRemainingHp); Assert.False(result.TargetDefeated); Assert.Equal(0, snapshot.CurrentHp); Assert.True(snapshot.Defeated); } [Fact] public void TryResolve_WithGuardOnLiveTarget_ShouldSucceedWithZeroDamage() { // Arrange var registry = CreatePrototypeRegistry(); var store = PrototypeNpcTestFixtures.CreateHealthStore(); // Act var result = CombatOperations.TryResolve( PrototypeAbilityRegistry.PrototypeGuard, PrototypeNpcRegistry.PrototypeNpcMeleeId, registry, store); store.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot); // Assert Assert.True(result.Success); Assert.Null(result.ReasonCode); Assert.Equal(0, result.DamageDealt); Assert.Equal(100, result.TargetRemainingHp); Assert.False(result.TargetDefeated); Assert.Equal(100, snapshot.CurrentHp); } [Fact] public void TryResolve_WithGuardOnDefeatedTarget_ShouldDenyWithTargetDefeated() { // Arrange var registry = CreatePrototypeRegistry(); var store = PrototypeNpcTestFixtures.CreateHealthStore(); _ = store.TryApplyDamage(PrototypeNpcRegistry.PrototypeNpcMeleeId, 100, out _); // Act var result = CombatOperations.TryResolve( PrototypeAbilityRegistry.PrototypeGuard, PrototypeNpcRegistry.PrototypeNpcMeleeId, registry, store); // Assert Assert.False(result.Success); Assert.Equal(CombatReasonCodes.TargetDefeated, result.ReasonCode); Assert.Equal(0, result.DamageDealt); Assert.Equal(0, result.TargetRemainingHp); Assert.False(result.TargetDefeated); } [Fact] public void TryResolve_WithDashOnLiveTarget_ShouldSucceedWithZeroDamage() { // Arrange var registry = CreatePrototypeRegistry(); var store = PrototypeNpcTestFixtures.CreateHealthStore(); // Act var result = CombatOperations.TryResolve( PrototypeAbilityRegistry.PrototypeDash, PrototypeNpcRegistry.PrototypeNpcMeleeId, registry, store); // Assert Assert.True(result.Success); Assert.Null(result.ReasonCode); Assert.Equal(0, result.DamageDealt); Assert.Equal(100, result.TargetRemainingHp); Assert.False(result.TargetDefeated); } [Fact] public void TryResolve_WithUnknownAbility_ShouldDenyWithUnknownAbility() { // Arrange var registry = CreatePrototypeRegistry(); var store = PrototypeNpcTestFixtures.CreateHealthStore(); // Act var result = CombatOperations.TryResolve( "not_a_real_ability", PrototypeNpcRegistry.PrototypeNpcMeleeId, registry, store); // Assert Assert.False(result.Success); Assert.Equal(CombatReasonCodes.UnknownAbility, result.ReasonCode); Assert.Equal(0, result.DamageDealt); Assert.Null(result.TargetRemainingHp); } [Fact] public void TryResolve_WithUnknownTarget_ShouldDenyWithUnknownTarget() { // Arrange var registry = CreatePrototypeRegistry(); var store = PrototypeNpcTestFixtures.CreateHealthStore(); // Act var result = CombatOperations.TryResolve( PrototypeAbilityRegistry.PrototypePulse, "not_a_target", registry, store); // Assert Assert.False(result.Success); Assert.Equal(CombatReasonCodes.UnknownTarget, result.ReasonCode); Assert.Equal(0, result.DamageDealt); Assert.Null(result.TargetRemainingHp); } [Fact] public void TryResolve_WithBurstOnFreshAlpha_ShouldDealFortyDamage() { // Arrange var registry = CreatePrototypeRegistry(); var store = PrototypeNpcTestFixtures.CreateHealthStore(); // Act var result = CombatOperations.TryResolve( PrototypeAbilityRegistry.PrototypeBurst, PrototypeNpcRegistry.PrototypeNpcMeleeId, registry, store); // Assert Assert.True(result.Success); Assert.Equal(40, result.DamageDealt); Assert.Equal(60, result.TargetRemainingHp); Assert.False(result.TargetDefeated); } [Fact] public async Task Host_ShouldResolveCombatDependenciesFromDi_WhenStartupSucceeds() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); using var client = factory.CreateClient(); _ = await client.GetAsync("/health"); var registry = factory.Services.GetRequiredService(); var store = factory.Services.GetRequiredService(); // Act var result = CombatOperations.TryResolve( PrototypeAbilityRegistry.PrototypePulse, PrototypeNpcRegistry.PrototypeNpcMeleeId, registry, store); // Assert Assert.True(result.Success); Assert.Equal(25, result.DamageDealt); Assert.Equal(75, result.TargetRemainingHp); } }