Carry local WorldEntity identity through render hits, lighting pulses, and deferred movement actions so GUID reuse cannot target a replacement. Reset all session-owned selection and ItemHolder state and prevent combat auto-target during teardown.
99 lines
3.3 KiB
C#
99 lines
3.3 KiB
C#
using AcDream.App.Combat;
|
|
using AcDream.Core.Combat;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.Selection;
|
|
|
|
namespace AcDream.App.Tests.Combat;
|
|
|
|
public sealed class CombatTargetControllerTests
|
|
{
|
|
[Fact]
|
|
public void DeadSelectedTarget_AutoTargetEnabled_SelectsClosestReplacement()
|
|
{
|
|
var combat = new CombatState();
|
|
combat.SetCombatMode(CombatMode.Melee);
|
|
var selection = new SelectionState();
|
|
selection.Select(0x50000001u, SelectionChangeSource.World);
|
|
int calls = 0;
|
|
using var controller = new CombatTargetController(
|
|
combat,
|
|
selection,
|
|
autoTarget: () => true,
|
|
selectClosestTarget: () =>
|
|
{
|
|
calls++;
|
|
selection.Select(0x50000002u, SelectionChangeSource.System);
|
|
return 0x50000002u;
|
|
});
|
|
|
|
controller.OnMotionApplied(0x50000001u, MotionCommand.Dead);
|
|
|
|
Assert.Equal(1, calls);
|
|
Assert.Equal(0x50000002u, selection.SelectedObjectId);
|
|
}
|
|
|
|
[Fact]
|
|
public void DeadSelectedTarget_AutoTargetDisabled_LeavesSelectionClear()
|
|
{
|
|
var combat = new CombatState();
|
|
combat.SetCombatMode(CombatMode.Melee);
|
|
var selection = new SelectionState();
|
|
selection.Select(0x50000001u, SelectionChangeSource.World);
|
|
int calls = 0;
|
|
using var controller = new CombatTargetController(
|
|
combat, selection, () => false, () => { calls++; return null; });
|
|
|
|
controller.OnMotionApplied(0x50000001u, MotionCommand.Dead);
|
|
|
|
Assert.Equal(0, calls);
|
|
Assert.Null(selection.SelectedObjectId);
|
|
}
|
|
|
|
[Fact]
|
|
public void DeadSelectedTarget_NonCombatMode_DoesNotAutoTarget()
|
|
{
|
|
var combat = new CombatState();
|
|
var selection = new SelectionState();
|
|
selection.Select(0x50000001u, SelectionChangeSource.World);
|
|
int calls = 0;
|
|
using var controller = new CombatTargetController(
|
|
combat, selection, () => true, () => { calls++; return null; });
|
|
|
|
controller.OnMotionApplied(0x50000001u, MotionCommand.Dead);
|
|
|
|
Assert.Equal(0, calls);
|
|
Assert.Null(selection.SelectedObjectId);
|
|
}
|
|
|
|
[Fact]
|
|
public void DeadUnselectedObject_DoesNotDisturbCurrentTarget()
|
|
{
|
|
var combat = new CombatState();
|
|
combat.SetCombatMode(CombatMode.Missile);
|
|
var selection = new SelectionState();
|
|
selection.Select(0x50000002u, SelectionChangeSource.World);
|
|
using var controller = new CombatTargetController(
|
|
combat, selection, () => true, () => throw new InvalidOperationException());
|
|
|
|
controller.OnMotionApplied(0x50000001u, MotionCommand.Dead);
|
|
|
|
Assert.Equal(0x50000002u, selection.SelectedObjectId);
|
|
}
|
|
|
|
[Fact]
|
|
public void SessionReset_DoesNotAcquireTargetFromDepartingWorld()
|
|
{
|
|
var combat = new CombatState();
|
|
combat.SetCombatMode(CombatMode.Missile);
|
|
var selection = new SelectionState();
|
|
selection.Select(0x50000002u, SelectionChangeSource.World);
|
|
int calls = 0;
|
|
using var controller = new CombatTargetController(
|
|
combat, selection, () => true, () => { calls++; return null; });
|
|
|
|
selection.Reset();
|
|
|
|
Assert.Equal(0, calls);
|
|
Assert.Null(selection.SelectedObjectId);
|
|
}
|
|
}
|