acdream/tests/AcDream.Runtime.Tests/Gameplay/RuntimeCombatTargetStateTests.cs
Erik 20df9d155d refactor(runtime): own combat and magic intent
Move attack build/repeat state, combat-mode policy, authoritative auto-target transitions, and spell-cast intent beneath RuntimeActionState. Keep App as the input, world-query, DAT-policy, transport, and presentation adapter while preserving retail request and busy ordering. Add direct/graphical parity, reset, failure, and instance-isolation coverage.

Co-authored-by: Codex <noreply@openai.com>
2026-07-26 11:56:40 +02:00

117 lines
3.7 KiB
C#

using AcDream.Core.Combat;
using AcDream.Core.Physics;
using AcDream.Core.Selection;
using AcDream.Runtime.Gameplay;
namespace AcDream.Runtime.Tests.Gameplay;
public sealed class RuntimeCombatTargetStateTests
{
[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 RuntimeCombatTargetState(
combat,
selection,
new Operations(true, () =>
{
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 RuntimeCombatTargetState(
combat,
selection,
new Operations(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 RuntimeCombatTargetState(
combat,
selection,
new Operations(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 RuntimeCombatTargetState(
combat,
selection,
new Operations(
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 RuntimeCombatTargetState(
combat,
selection,
new Operations(true, () => { calls++; return null; }));
selection.Reset();
Assert.Equal(0, calls);
Assert.Null(selection.SelectedObjectId);
}
private sealed class Operations(
bool autoTarget,
Func<uint?> selectClosestTarget)
: IRuntimeCombatTargetOperations
{
public bool AutoTarget { get; } = autoTarget;
public uint? SelectClosestTarget() => selectClosestTarget();
}
}