109 lines
4.4 KiB
C#
109 lines
4.4 KiB
C#
using NeonSprawl.Server.Game.AbilityInput;
|
|
using NeonSprawl.Server.Game.PositionState;
|
|
using NeonSprawl.Server.Game.Targeting;
|
|
|
|
namespace NeonSprawl.Server.Tests.Game.Npc;
|
|
|
|
/// <summary>HTTP integration tests for NEO-92 aggro acquire + leash clear wiring.</summary>
|
|
public sealed class AggroThreatIntegrationTests
|
|
{
|
|
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 LockMeleeAsync(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 async Task TeleportDevPlayerAsync(HttpClient client, double x, double y, double z)
|
|
{
|
|
var response = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/move-stream",
|
|
new MoveStreamRequest
|
|
{
|
|
SchemaVersion = MoveStreamRequest.CurrentSchemaVersion,
|
|
Targets = [new PositionVector { X = x, Y = y, Z = z }],
|
|
});
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CastThenMoveBeyondLeash_ShouldAcquireThenClearAggroHolder()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
var threatStore = factory.Services.GetRequiredService<IThreatStateStore>();
|
|
await BindSlot0PulseAsync(client);
|
|
await LockMeleeAsync(client);
|
|
|
|
// Act
|
|
var castResponse = await client.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/ability-cast",
|
|
new AbilityCastRequest
|
|
{
|
|
SchemaVersion = AbilityCastRequest.CurrentSchemaVersion,
|
|
SlotIndex = 0,
|
|
AbilityId = PrototypeAbilityRegistry.PrototypePulse,
|
|
TargetId = PrototypeNpcRegistry.PrototypeNpcMeleeId,
|
|
});
|
|
castResponse.EnsureSuccessStatusCode();
|
|
threatStore.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var afterCast);
|
|
await TeleportDevPlayerAsync(client, 12, 0, 12);
|
|
threatStore.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var afterMove);
|
|
|
|
// Assert
|
|
Assert.Equal("dev-local-1", afterCast.AggroHolderPlayerId);
|
|
Assert.Null(afterMove.AggroHolderPlayerId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ReacquireAfterLeashClear_ShouldSetHolderOnNextDamagingCast()
|
|
{
|
|
// Arrange
|
|
await using var factory = new InMemoryWebApplicationFactory();
|
|
var client = factory.CreateClient();
|
|
Assert.NotNull(factory.FakeClock);
|
|
var threatStore = factory.Services.GetRequiredService<IThreatStateStore>();
|
|
await BindSlot0PulseAsync(client);
|
|
await LockMeleeAsync(client);
|
|
var cast = new AbilityCastRequest
|
|
{
|
|
SchemaVersion = AbilityCastRequest.CurrentSchemaVersion,
|
|
SlotIndex = 0,
|
|
AbilityId = PrototypeAbilityRegistry.PrototypePulse,
|
|
TargetId = PrototypeNpcRegistry.PrototypeNpcMeleeId,
|
|
};
|
|
var firstCast = await client.PostAsJsonAsync("/game/players/dev-local-1/ability-cast", cast);
|
|
firstCast.EnsureSuccessStatusCode();
|
|
await TeleportDevPlayerAsync(client, 12, 0, 12);
|
|
await TeleportDevPlayerAsync(client, 0, 0, 0);
|
|
await LockMeleeAsync(client);
|
|
factory.FakeClock!.Advance(TimeSpan.FromSeconds(3) + TimeSpan.FromMilliseconds(50));
|
|
|
|
// Act
|
|
var secondCast = await client.PostAsJsonAsync("/game/players/dev-local-1/ability-cast", cast);
|
|
secondCast.EnsureSuccessStatusCode();
|
|
threatStore.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var afterSecondCast);
|
|
|
|
// Assert
|
|
Assert.Equal("dev-local-1", afterSecondCast.AggroHolderPlayerId);
|
|
}
|
|
}
|