neon-sprawl/server/NeonSprawl.Server.Tests/Game/Npc/NpcRuntimeSnapshotWorldApiT...

174 lines
7.8 KiB
C#

using System.Linq;
using System.Net;
using System.Net.Http.Json;
using NeonSprawl.Server.Game.AbilityInput;
using NeonSprawl.Server.Game.Npc;
using NeonSprawl.Server.Game.Targeting;
using NeonSprawl.Server.Tests;
using Xunit;
namespace NeonSprawl.Server.Tests.Game.Npc;
public sealed class NpcRuntimeSnapshotWorldApiTests
{
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();
}
private static AbilityCastRequest PulseCastRequest() =>
new()
{
SchemaVersion = AbilityCastRequest.CurrentSchemaVersion,
SlotIndex = 0,
AbilityId = PrototypeAbilityRegistry.PrototypePulse,
TargetId = PrototypeNpcRegistry.PrototypeNpcMeleeId,
};
[Fact]
public async Task GetNpcRuntimeSnapshot_ShouldReturnSchemaV1_WithThreeIdleInstances()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.GetAsync("/game/world/npc-runtime-snapshot");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<NpcRuntimeSnapshotResponse>();
Assert.NotNull(body);
Assert.Equal(NpcRuntimeSnapshotResponse.CurrentSchemaVersion, body!.SchemaVersion);
Assert.NotEqual(default, body.ServerTimeUtc);
Assert.NotNull(body.NpcInstances);
Assert.Equal(3, body.NpcInstances.Count);
var ids = body.NpcInstances.Select(static row => row.NpcInstanceId).ToList();
Assert.Equal(PrototypeNpcRegistryTests.InstancesInIdOrder, ids);
foreach (var row in body.NpcInstances)
{
Assert.Equal("idle", row.State);
Assert.Null(row.AggroHolderPlayerId);
Assert.Null(row.ActiveTelegraph);
}
var melee = body.NpcInstances.Single(row => row.NpcInstanceId == PrototypeNpcRegistry.PrototypeNpcMeleeId);
Assert.Equal(PrototypeNpcBehaviorRegistry.PrototypeMeleePressure, melee.BehaviorDefId);
}
[Fact]
public async Task GetNpcRuntimeSnapshot_ShouldShowAggro_OnMeleeAfterDamagingCast()
{
// 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();
// Act
var response = await client.GetAsync("/game/world/npc-runtime-snapshot");
var body = await response.Content.ReadFromJsonAsync<NpcRuntimeSnapshotResponse>();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(body);
var melee = body!.NpcInstances.Single(row => row.NpcInstanceId == PrototypeNpcRegistry.PrototypeNpcMeleeId);
Assert.Equal("aggro", melee.State);
Assert.Equal("dev-local-1", melee.AggroHolderPlayerId);
Assert.Null(melee.ActiveTelegraph);
var ranged = body.NpcInstances.Single(row => row.NpcInstanceId == PrototypeNpcRegistry.PrototypeNpcRangedId);
Assert.Equal("idle", ranged.State);
Assert.Null(ranged.AggroHolderPlayerId);
Assert.Null(ranged.ActiveTelegraph);
var elite = body.NpcInstances.Single(row => row.NpcInstanceId == PrototypeNpcRegistry.PrototypeNpcEliteId);
Assert.Equal("idle", elite.State);
Assert.Null(elite.AggroHolderPlayerId);
Assert.Null(elite.ActiveTelegraph);
}
[Fact]
public async Task GetNpcRuntimeSnapshot_ShouldShowTelegraph_AfterAttackCooldownElapses()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
Assert.NotNull(factory.FakeClock);
await BindSlot0PulseAsync(client);
await LockPrototypeNpcMeleeAsync(client);
var castResponse = await client.PostAsJsonAsync(
"/game/players/dev-local-1/ability-cast",
PulseCastRequest());
castResponse.EnsureSuccessStatusCode();
var initResponse = await client.GetAsync("/game/world/npc-runtime-snapshot");
initResponse.EnsureSuccessStatusCode();
factory.FakeClock!.Advance(TimeSpan.FromSeconds(3));
// Act
var response = await client.GetAsync("/game/world/npc-runtime-snapshot");
var body = await response.Content.ReadFromJsonAsync<NpcRuntimeSnapshotResponse>();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(body);
var melee = body!.NpcInstances.Single(row => row.NpcInstanceId == PrototypeNpcRegistry.PrototypeNpcMeleeId);
Assert.Equal("telegraph_windup", melee.State);
Assert.NotNull(melee.ActiveTelegraph);
Assert.Equal("melee_pressure", melee.ActiveTelegraph!.ArchetypeKind);
Assert.Equal(PrototypeNpcRegistry.PrototypeNpcMeleeId, melee.ActiveTelegraph.NpcInstanceId);
Assert.InRange(melee.ActiveTelegraph.WindupRemainingSeconds, 1.49, 1.51);
}
[Fact]
public async Task GetNpcRuntimeSnapshot_ShouldDecreaseWindupRemaining_AcrossPolls()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
Assert.NotNull(factory.FakeClock);
await BindSlot0PulseAsync(client);
await LockPrototypeNpcMeleeAsync(client);
var castResponse = await client.PostAsJsonAsync(
"/game/players/dev-local-1/ability-cast",
PulseCastRequest());
castResponse.EnsureSuccessStatusCode();
var initResponse = await client.GetAsync("/game/world/npc-runtime-snapshot");
initResponse.EnsureSuccessStatusCode();
factory.FakeClock!.Advance(TimeSpan.FromSeconds(3));
var firstResponse = await client.GetAsync("/game/world/npc-runtime-snapshot");
var firstBody = await firstResponse.Content.ReadFromJsonAsync<NpcRuntimeSnapshotResponse>();
factory.FakeClock.Advance(TimeSpan.FromSeconds(1));
// Act
var secondResponse = await client.GetAsync("/game/world/npc-runtime-snapshot");
var secondBody = await secondResponse.Content.ReadFromJsonAsync<NpcRuntimeSnapshotResponse>();
// Assert
Assert.NotNull(firstBody);
Assert.NotNull(secondBody);
var firstMelee = firstBody!.NpcInstances.Single(row => row.NpcInstanceId == PrototypeNpcRegistry.PrototypeNpcMeleeId);
var secondMelee = secondBody!.NpcInstances.Single(row => row.NpcInstanceId == PrototypeNpcRegistry.PrototypeNpcMeleeId);
Assert.NotNull(firstMelee.ActiveTelegraph);
Assert.NotNull(secondMelee.ActiveTelegraph);
Assert.True(secondMelee.ActiveTelegraph!.WindupRemainingSeconds < firstMelee.ActiveTelegraph!.WindupRemainingSeconds);
Assert.InRange(secondMelee.ActiveTelegraph.WindupRemainingSeconds, 0.49, 0.51);
}
}