using Microsoft.Extensions.Options; using NeonSprawl.Server.Game.Npc; using NeonSprawl.Server.Game.PositionState; using Xunit; namespace NeonSprawl.Server.Tests.Game.Npc; public sealed class AggroOperationsTests { private static InMemoryPositionStateStore CreatePositionStoreAt(double x, double y, double z) { var store = new InMemoryPositionStateStore(Options.Create(new GamePositionOptions { DevPlayerId = "dev-local-1", DefaultPosition = new DefaultPositionOptions { X = x, Y = y, Z = z }, })); return store; } [Fact] public void TryAcquire_ShouldSetHolder_WhenRowEmpty() { // Arrange var threatStore = new InMemoryThreatStateStore(); // Act var acquired = AggroOperations.TryAcquire( PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", threatStore); threatStore.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot); // Assert Assert.True(acquired); Assert.Equal("dev-local-1", snapshot.AggroHolderPlayerId); } [Fact] public void TryAcquire_ShouldNoOp_WhenHolderAlreadySet() { // Arrange var threatStore = new InMemoryThreatStateStore(); _ = AggroOperations.TryAcquire(PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", threatStore); // Act var secondAcquire = AggroOperations.TryAcquire( PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", threatStore); threatStore.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot); // Assert Assert.False(secondAcquire); Assert.Equal("dev-local-1", snapshot.AggroHolderPlayerId); } [Fact] public void TryClearOnLeashForPlayer_ShouldClearHolder_WhenPlayerBeyondLeashRadius() { // Arrange var behaviorRegistry = PrototypeNpcTestFixtures.CreateBehaviorRegistry(); var threatStore = new InMemoryThreatStateStore(); var positions = CreatePositionStoreAt(0, 0, 0); _ = AggroOperations.TryAcquire(PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", threatStore); positions.TryApplyMoveTarget("dev-local-1", 12, 0, 12, out _); // Act AggroOperations.TryClearOnLeashForPlayer("dev-local-1", positions, behaviorRegistry, threatStore); threatStore.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot); // Assert Assert.Null(snapshot.AggroHolderPlayerId); } [Fact] public void TryClearOnLeashForPlayer_ShouldKeepHolder_WhenPlayerInsideLeashRadius() { // Arrange var behaviorRegistry = PrototypeNpcTestFixtures.CreateBehaviorRegistry(); var threatStore = new InMemoryThreatStateStore(); var positions = CreatePositionStoreAt(0, 0, 0); _ = AggroOperations.TryAcquire(PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", threatStore); positions.TryApplyMoveTarget("dev-local-1", 1, 0, 1, out _); // Act AggroOperations.TryClearOnLeashForPlayer("dev-local-1", positions, behaviorRegistry, threatStore); threatStore.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot); // Assert Assert.Equal("dev-local-1", snapshot.AggroHolderPlayerId); } [Fact] public void TryClearOnLeashForPlayer_ShouldKeepHolder_WhenPlayerAtExactLeashRadius() { // Arrange var behaviorRegistry = PrototypeNpcTestFixtures.CreateBehaviorRegistry(); var threatStore = new InMemoryThreatStateStore(); var positions = CreatePositionStoreAt(0, 0, 0); _ = AggroOperations.TryAcquire(PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", threatStore); // Melee anchor (-3, -3); leash 16.0 — X = 13 is exactly 16 m horizontal from anchor. positions.TryApplyMoveTarget("dev-local-1", 13, 0, -3, out _); // Act AggroOperations.TryClearOnLeashForPlayer("dev-local-1", positions, behaviorRegistry, threatStore); threatStore.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot); // Assert Assert.Equal("dev-local-1", snapshot.AggroHolderPlayerId); } [Fact] public void TryAcquire_ShouldSetHolderAgain_AfterLeashClear() { // Arrange var behaviorRegistry = PrototypeNpcTestFixtures.CreateBehaviorRegistry(); var threatStore = new InMemoryThreatStateStore(); var positions = CreatePositionStoreAt(0, 0, 0); _ = AggroOperations.TryAcquire(PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", threatStore); positions.TryApplyMoveTarget("dev-local-1", 12, 0, 12, out _); AggroOperations.TryClearOnLeashForPlayer("dev-local-1", positions, behaviorRegistry, threatStore); positions.TryApplyMoveTarget("dev-local-1", 0, 0, 0, out _); // Act var reacquired = AggroOperations.TryAcquire( PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1", threatStore); threatStore.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var snapshot); // Assert Assert.True(reacquired); Assert.Equal("dev-local-1", snapshot.AggroHolderPlayerId); } }