refactor(input): own gameplay action routing
Move the sole semantic action-priority graph, combat and diagnostic commands, and retained-root item-drop lifetime behind focused typed owners. Preserve retail toggle behavior, explicit auto-wield cancellation, shutdown quiescence, and symmetric callback cleanup. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
8b8afeefa3
commit
4eae9b4f5a
25 changed files with 2608 additions and 418 deletions
|
|
@ -0,0 +1,180 @@
|
|||
using AcDream.App.Combat;
|
||||
using AcDream.App.Diagnostics;
|
||||
using AcDream.App.Input;
|
||||
using AcDream.UI.Abstractions.Input;
|
||||
|
||||
namespace AcDream.App.Tests.Input;
|
||||
|
||||
public sealed class GameplayInputCommandControllerTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(InputAction.ToggleInventoryPanel, "inventory")]
|
||||
[InlineData(InputAction.AcdreamToggleDebugPanel, "debug")]
|
||||
[InlineData(InputAction.AcdreamToggleFlyMode, "fly-or-chase")]
|
||||
[InlineData(InputAction.AcdreamTogglePlayerMode, "player-mode")]
|
||||
[InlineData(InputAction.ToggleChatEntry, "chat")]
|
||||
[InlineData(InputAction.ToggleOptionsPanel, "settings")]
|
||||
[InlineData(InputAction.CombatToggleCombat, "combat")]
|
||||
public void RecognizedCommand_RoutesToTypedOwner(
|
||||
InputAction action,
|
||||
string expected)
|
||||
{
|
||||
var harness = new Harness();
|
||||
|
||||
bool handled = harness.Controller.Handle(action);
|
||||
|
||||
Assert.True(handled);
|
||||
Assert.Equal([expected], harness.Calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiagnosticCommand_PrecedesRemainingCommandSwitch()
|
||||
{
|
||||
var harness = new Harness();
|
||||
harness.Diagnostics.HandledAction = InputAction.AcdreamToggleDebugPanel;
|
||||
|
||||
bool handled = harness.Controller.Handle(
|
||||
InputAction.AcdreamToggleDebugPanel);
|
||||
|
||||
Assert.True(handled);
|
||||
Assert.Equal(["diagnostic"], harness.Calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownCommand_IsNotClaimed()
|
||||
{
|
||||
var harness = new Harness();
|
||||
|
||||
bool handled = harness.Controller.Handle(InputAction.MovementForward);
|
||||
|
||||
Assert.False(handled);
|
||||
Assert.Empty(harness.Calls);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, true, true, "cancel-target")]
|
||||
[InlineData(false, true, true, "exit-fly")]
|
||||
[InlineData(false, false, true, "exit-player")]
|
||||
[InlineData(false, false, false, "close")]
|
||||
public void Escape_PreservesTargetFlyPlayerWindowPriority(
|
||||
bool targetMode,
|
||||
bool flyMode,
|
||||
bool playerMode,
|
||||
string expected)
|
||||
{
|
||||
var harness = new Harness
|
||||
{
|
||||
TargetMode = { IsActive = targetMode },
|
||||
Camera = { IsFly = flyMode },
|
||||
Player = { IsPlayer = playerMode },
|
||||
};
|
||||
|
||||
bool handled = harness.Controller.Handle(InputAction.EscapeKey);
|
||||
|
||||
Assert.True(handled);
|
||||
Assert.Equal([expected], harness.Calls);
|
||||
}
|
||||
|
||||
private sealed class Harness
|
||||
{
|
||||
public Harness()
|
||||
{
|
||||
Retained = new FakeRetained(Calls);
|
||||
DevTools = new FakeDevTools(Calls);
|
||||
Diagnostics = new FakeDiagnostics(Calls);
|
||||
Player = new FakePlayerMode(Calls);
|
||||
TargetMode = new FakeTargetMode(Calls);
|
||||
Camera = new FakeCamera(Calls);
|
||||
Combat = new FakeCombat(Calls);
|
||||
Window = new FakeWindow(Calls);
|
||||
Controller = new GameplayInputCommandController(
|
||||
Retained,
|
||||
DevTools,
|
||||
Diagnostics,
|
||||
Player,
|
||||
TargetMode,
|
||||
Camera,
|
||||
Combat,
|
||||
Window);
|
||||
}
|
||||
|
||||
public List<string> Calls { get; } = [];
|
||||
public FakeRetained Retained { get; }
|
||||
public FakeDevTools DevTools { get; }
|
||||
public FakeDiagnostics Diagnostics { get; }
|
||||
public FakePlayerMode Player { get; }
|
||||
public FakeTargetMode TargetMode { get; }
|
||||
public FakeCamera Camera { get; }
|
||||
public FakeCombat Combat { get; }
|
||||
public FakeWindow Window { get; }
|
||||
public GameplayInputCommandController Controller { get; }
|
||||
}
|
||||
|
||||
private sealed class FakeRetained(List<string> calls)
|
||||
: IRetainedGameplayWindowCommands
|
||||
{
|
||||
public void ToggleInventory() => calls.Add("inventory");
|
||||
}
|
||||
|
||||
private sealed class FakeDevTools(List<string> calls)
|
||||
: IDevToolsGameplayCommands
|
||||
{
|
||||
public void ToggleDebugPanel() => calls.Add("debug");
|
||||
public void FocusChatInput() => calls.Add("chat");
|
||||
public void ToggleSettingsPanel() => calls.Add("settings");
|
||||
}
|
||||
|
||||
private sealed class FakeDiagnostics(List<string> calls)
|
||||
: IRuntimeDiagnosticCommands
|
||||
{
|
||||
public InputAction? HandledAction { get; set; }
|
||||
|
||||
public bool Handle(InputAction action)
|
||||
{
|
||||
if (action != HandledAction)
|
||||
return false;
|
||||
calls.Add("diagnostic");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CycleTimeOfDay() => calls.Add("time");
|
||||
public void CycleWeather() => calls.Add("weather");
|
||||
public void ToggleCollisionWireframes() => calls.Add("collision");
|
||||
}
|
||||
|
||||
private sealed class FakePlayerMode(List<string> calls)
|
||||
: IPlayerModeGameplayCommands
|
||||
{
|
||||
public bool IsPlayer { get; set; }
|
||||
public bool IsPlayerMode => IsPlayer;
|
||||
public void ToggleFlyOrChase() => calls.Add("fly-or-chase");
|
||||
public void TogglePlayerMode() => calls.Add("player-mode");
|
||||
public void ExitPlayerMode() => calls.Add("exit-player");
|
||||
}
|
||||
|
||||
private sealed class FakeTargetMode(List<string> calls)
|
||||
: IItemTargetModeCommands
|
||||
{
|
||||
public bool IsActive { get; set; }
|
||||
public bool IsAnyTargetModeActive => IsActive;
|
||||
public void CancelTargetMode() => calls.Add("cancel-target");
|
||||
}
|
||||
|
||||
private sealed class FakeCamera(List<string> calls)
|
||||
: IGameplayCameraModeCommands
|
||||
{
|
||||
public bool IsFly { get; set; }
|
||||
public bool IsFlyMode => IsFly;
|
||||
public void ExitFlyMode() => calls.Add("exit-fly");
|
||||
}
|
||||
|
||||
private sealed class FakeCombat(List<string> calls) : ILiveCombatModeCommand
|
||||
{
|
||||
public void Toggle() => calls.Add("combat");
|
||||
}
|
||||
|
||||
private sealed class FakeWindow(List<string> calls) : IGameplayWindowCommands
|
||||
{
|
||||
public void Close() => calls.Add("close");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue