using NeonSprawl.Server.Game.AbilityInput; using NeonSprawl.Server.Game.Combat; using NeonSprawl.Server.Game.Targeting; namespace NeonSprawl.Server.Tests.Game.Combat; public class CombatTargetsWorldApiTests { private static async Task BindSlot0PulseAsync(HttpClient client) { var post = await client.PostAsJsonAsync( "/game/players/dev-local-1/hotbar-loadout", new HotbarLoadoutUpdateRequest { SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion, Slots = [new HotbarSlotBindingJson { SlotIndex = 0, AbilityId = PrototypeAbilityRegistry.PrototypePulse }], }); post.EnsureSuccessStatusCode(); } private static async Task LockPrototypeNpcMeleeAsync(HttpClient client) { var response = await client.PostAsJsonAsync( "/game/players/dev-local-1/target/select", new TargetSelectRequest { SchemaVersion = TargetSelectRequest.CurrentSchemaVersion, TargetId = PrototypeNpcRegistry.PrototypeNpcMeleeId, }); response.EnsureSuccessStatusCode(); var body = await response.Content.ReadFromJsonAsync(); Assert.NotNull(body); Assert.True(body!.SelectionApplied); } private static AbilityCastRequest PulseCastRequest() => new() { SchemaVersion = AbilityCastRequest.CurrentSchemaVersion, SlotIndex = 0, AbilityId = PrototypeAbilityRegistry.PrototypePulse, TargetId = PrototypeNpcRegistry.PrototypeNpcMeleeId, }; [Fact] public async Task GetCombatTargets_ShouldReturnSchemaV1_WithThreeTargetsAtFullHp() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); // Act var response = await client.GetAsync("/game/world/combat-targets"); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadFromJsonAsync(); Assert.NotNull(body); Assert.Equal(CombatTargetsListResponse.CurrentSchemaVersion, body!.SchemaVersion); Assert.NotNull(body.Targets); Assert.Equal(3, body.Targets.Count); var ids = body.Targets.Select(static t => t.TargetId).ToList(); Assert.Equal(PrototypeNpcRegistryTests.InstancesInIdOrder, ids); var elite = body.Targets.Single(t => t.TargetId == PrototypeNpcRegistry.PrototypeNpcEliteId); Assert.Equal(200, elite.MaxHp); Assert.Equal(200, elite.CurrentHp); Assert.False(elite.Defeated); var melee = body.Targets.Single(t => t.TargetId == PrototypeNpcRegistry.PrototypeNpcMeleeId); Assert.Equal(100, melee.MaxHp); Assert.Equal(100, melee.CurrentHp); Assert.False(melee.Defeated); var ranged = body.Targets.Single(t => t.TargetId == PrototypeNpcRegistry.PrototypeNpcRangedId); Assert.Equal(80, ranged.MaxHp); Assert.Equal(80, ranged.CurrentHp); Assert.False(ranged.Defeated); } [Fact] public async Task GetCombatTargets_ShouldReflectCastDamage_AfterOnePulseOnMelee() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); await BindSlot0PulseAsync(client); await LockPrototypeNpcMeleeAsync(client); var castResponse = await client.PostAsJsonAsync( "/game/players/dev-local-1/ability-cast", PulseCastRequest()); castResponse.EnsureSuccessStatusCode(); var castBody = await castResponse.Content.ReadFromJsonAsync(); // Act var response = await client.GetAsync("/game/world/combat-targets"); var body = await response.Content.ReadFromJsonAsync(); // Assert Assert.NotNull(castBody); Assert.True(castBody!.Accepted); Assert.NotNull(castBody.CombatResolution); Assert.Equal(75, castBody.CombatResolution!.TargetRemainingHp); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotNull(body); var melee = body!.Targets.Single(t => t.TargetId == PrototypeNpcRegistry.PrototypeNpcMeleeId); Assert.Equal(75, melee.CurrentHp); Assert.False(melee.Defeated); var ranged = body.Targets.Single(t => t.TargetId == PrototypeNpcRegistry.PrototypeNpcRangedId); Assert.Equal(80, ranged.CurrentHp); Assert.False(ranged.Defeated); } [Fact] public async Task GetCombatTargets_ShouldShowDefeated_AfterFourPulsesOnMelee() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); Assert.NotNull(factory.FakeClock); await BindSlot0PulseAsync(client); await LockPrototypeNpcMeleeAsync(client); var cast = PulseCastRequest(); for (var i = 0; i < 4; i++) { var castResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/ability-cast", cast); castResponse.EnsureSuccessStatusCode(); if (i < 3) { factory.FakeClock!.Advance(TimeSpan.FromSeconds(3) + TimeSpan.FromMilliseconds(50)); } } // Act var response = await client.GetAsync("/game/world/combat-targets"); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadFromJsonAsync(); Assert.NotNull(body); var melee = body!.Targets.Single(t => t.TargetId == PrototypeNpcRegistry.PrototypeNpcMeleeId); Assert.Equal(0, melee.CurrentHp); Assert.True(melee.Defeated); var ranged = body.Targets.Single(t => t.TargetId == PrototypeNpcRegistry.PrototypeNpcRangedId); Assert.Equal(80, ranged.CurrentHp); Assert.False(ranged.Defeated); var elite = body.Targets.Single(t => t.TargetId == PrototypeNpcRegistry.PrototypeNpcEliteId); Assert.Equal(200, elite.CurrentHp); Assert.Equal(200, elite.MaxHp); Assert.False(elite.Defeated); } }