feat(D.2b): write-mode movement gate that preserves autorun
In chat write mode the keyboard belongs to the input — typing "swd" must not walk the character — but AUTORUN must keep going (the user can chat while running). - InputDispatcher.IsActionHeld now returns false while WantCaptureKeyboard is set (a focused chat input), the polling-path twin of the existing gate on Fired actions. This SUPERSEDES the old per-frame OnUpdate early-return, which also killed autorun. Gating here instead lets the movement block keep running, so autorun — a separate latched bool ORed into Forward at the call site, not a polled key — survives. Test updated to encode the new contract. - GameWindow: the movement suppress-guard reverts to ImGui-devtools-only (the retail write mode no longer early-returns); wires DefaultTextInput = the chat input (Tab/Enter activation) and Input.Keyboard for clipboard. Drops the one-shot UI-scale diagnostic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
367a752078
commit
2284a376ae
3 changed files with 37 additions and 17 deletions
|
|
@ -1854,9 +1854,11 @@ public sealed class GameWindow : IDisposable
|
|||
vitalsDatFont, _debugFont, ResolveChrome);
|
||||
if (chatController is not null)
|
||||
{
|
||||
// Ctrl+C / Ctrl+A on the transcript need the keyboard for clipboard + modifiers.
|
||||
// _uiHost.Keyboard is set by WireKeyboard above — it is non-null here.
|
||||
// Ctrl+C / Ctrl+A on the transcript + Ctrl+C/X/V/A on the input need the
|
||||
// keyboard for clipboard + modifier (Ctrl/Shift) state. _uiHost.Keyboard
|
||||
// is set by WireKeyboard above — it is non-null here.
|
||||
chatController.Transcript.Keyboard = _uiHost.Keyboard;
|
||||
chatController.Input.Keyboard = _uiHost.Keyboard;
|
||||
// Wrap the dat content in the universal 8-piece beveled window chrome —
|
||||
// the SAME UiNineSlicePanel the vitals window uses. The chat's own dat
|
||||
// layout only carries flat background sprites, so without this the window
|
||||
|
|
@ -1887,6 +1889,10 @@ public sealed class GameWindow : IDisposable
|
|||
chatRoot.Draggable = false; chatRoot.Resizable = false;
|
||||
chatFrame.AddChild(chatRoot);
|
||||
_uiHost.Root.AddChild(chatFrame);
|
||||
// Tab / Enter enters "write mode" by focusing this input (retail's chat
|
||||
// activation); a focused input suppresses character movement (see the
|
||||
// WantsKeyboard gate in the movement poll).
|
||||
_uiHost.Root.DefaultTextInput = chatController.Input;
|
||||
Console.WriteLine("[D.2b] retail chat window from LayoutDesc importer (0x21000006).");
|
||||
}
|
||||
else Console.WriteLine("[D.2b] chat: required role elements missing in 0x21000006.");
|
||||
|
|
@ -7271,6 +7277,11 @@ public sealed class GameWindow : IDisposable
|
|||
// this guard adds defense-in-depth for the per-frame IsActionHeld
|
||||
// movement poll below (typing "walk" into a chat field shouldn't
|
||||
// walk).
|
||||
// ImGui dev-tools text fields fully pause game input (incl. autorun) — fine, it's a
|
||||
// debug overlay. The RETAIL chat "write mode" does NOT early-return here: the block
|
||||
// below still runs so AUTORUN keeps driving the character while you type. Held WASD
|
||||
// is silenced at the source instead — InputDispatcher.IsActionHeld returns false
|
||||
// while WantCaptureKeyboard (which includes a focused chat input) is set.
|
||||
bool suppressGameInput =
|
||||
DevToolsEnabled && ImGuiNET.ImGui.GetIO().WantCaptureKeyboard;
|
||||
if (suppressGameInput) return;
|
||||
|
|
|
|||
|
|
@ -141,6 +141,12 @@ public sealed class InputDispatcher
|
|||
public bool IsActionHeld(InputAction action)
|
||||
{
|
||||
if (action == InputAction.None) return false;
|
||||
// While a text field owns the keyboard ("write mode"), held game actions read as
|
||||
// released: typing "swd" must not move the character. This is the polling-path twin
|
||||
// of the WantCaptureKeyboard gate on Fired actions. NOTE: this suppresses KEY-driven
|
||||
// movement only — latched state that isn't a key (e.g. autorun, ORed into Forward at
|
||||
// the call site) keeps driving the character, so chat doesn't cancel autorun.
|
||||
if (_mouse.WantCaptureKeyboard) return false;
|
||||
foreach (var b in _bindings.ForAction(action))
|
||||
{
|
||||
if (IsChordHeld(b.Chord)) return true;
|
||||
|
|
|
|||
|
|
@ -148,27 +148,30 @@ public class InputDispatcherIsActionHeldTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void IsActionHeld_does_not_check_WantCaptureMouse()
|
||||
public void IsActionHeld_gated_off_while_keyboard_captured()
|
||||
{
|
||||
// Per-frame held-state lookup is independent of UI capture: even
|
||||
// with WantCaptureMouse=true a movement key already held when
|
||||
// ImGui took focus continues to read as held until KeyUp. Press
|
||||
// events ARE gated (the Press wouldn't fire while UI captures),
|
||||
// but IsActionHeld answers the keyboard's underlying "is the
|
||||
// physical key down right now" — which the legacy IsKeyPressed
|
||||
// also did. The per-frame OnUpdate guard on
|
||||
// ImGui.GetIO().WantCaptureKeyboard is what suppresses movement
|
||||
// when chat is focused.
|
||||
// Write-mode gate (2026-06-16): a focused chat input sets
|
||||
// WantCaptureKeyboard, and held-key polling then reads RELEASED so typing
|
||||
// "swd" doesn't move the character. This SUPERSEDES the old design (where the
|
||||
// per-frame OnUpdate guard early-returned out of the whole movement block) —
|
||||
// that approach also killed AUTORUN. By gating here instead, the movement block
|
||||
// keeps running, so autorun (a separate latched bool ORed into Forward at the
|
||||
// call site, NOT a polled key) survives write mode. WantCaptureMouse alone does
|
||||
// NOT gate held-key polling — only keyboard capture does.
|
||||
var (dispatcher, kb, mouse, bindings) = Build();
|
||||
bindings.Add(new Binding(new KeyChord(Key.W, ModifierMask.None), InputAction.MovementForward));
|
||||
kb.EmitKeyDown(Key.W, ModifierMask.None);
|
||||
|
||||
mouse.WantCaptureMouse = true;
|
||||
mouse.WantCaptureKeyboard = true;
|
||||
// Held, no capture → reads held.
|
||||
Assert.True(dispatcher.IsActionHeld(InputAction.MovementForward));
|
||||
|
||||
// Even with both capture flags set, IsActionHeld remains true
|
||||
// because W is physically held. The dispatcher only suppresses
|
||||
// press transitions.
|
||||
// Keyboard captured (write mode) → held-key polling reads released.
|
||||
mouse.WantCaptureKeyboard = true;
|
||||
Assert.False(dispatcher.IsActionHeld(InputAction.MovementForward));
|
||||
|
||||
// Mouse capture alone must NOT gate movement polling (only keyboard does).
|
||||
mouse.WantCaptureKeyboard = false;
|
||||
mouse.WantCaptureMouse = true;
|
||||
Assert.True(dispatcher.IsActionHeld(InputAction.MovementForward));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue