feat(input): #24 Phase K.2 - auto-enter player mode at login + MMB mouse-look + DebugPanel free-fly + Tab to chat-input focus
Five changes:
1. PlayerModeAutoEntry — testable guard class that fires once after
EnterWorld + WorldSession.State.InWorld + player entity present +
PlayerController.State == InWorld. GameWindow arms the entry
after EnterWorld; per-frame Tick checks all four guards and
invokes the same fly-to-player transition the Tab handler runs.
User-initiated fly toggle (DebugPanel button) Cancel()s pending
entry. Skip in offline mode (no ACDREAM_LIVE) — Holtburg orbit
stays default for testing.
2. MouseLookState + KeyBindings.RetailDefaults() binds MMB Hold to
InputAction.CameraInstantMouseLook. GameWindow subscribes:
- Press: hide cursor, capture position, _mouseLookActive = true.
- Release: restore cursor, deactivate.
- WantCaptureMouse=true while held → suspend (release cursor).
- MouseMove while active: combined drive — chase camera yaw +
character heading move together (retail's signature mouse-look
behavior). Camera Y still pitches camera-only.
3. DebugPanel "Toggle Free-Fly Mode" button via DebugVM.ToggleFlyMode
action delegate — replaces the F-key as the primary discovery
path for free-fly. Gated on DevToolsEnabled.
4. ChatPanel.FocusInput() one-shot + IPanelRenderer.SetKeyboardFocusHere
primitive. GameWindow's ToggleChatEntry (Tab) subscriber calls
_chatPanel.FocusInput() so Tab moves focus to the chat input
field. Replaces the K.1c TODO stub.
5. WantCaptureMouse gating reinforcement on surviving mouse handlers
(no new code; verified intact from K.1b).
21 new tests (8 PlayerModeAutoEntry, 10 MouseLookState, 3 ChatPanel
focus). 1183 total green. 0 warnings, 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
da189103b8
commit
af74eac0c2
12 changed files with 972 additions and 66 deletions
124
src/AcDream.UI.Abstractions/Input/MouseLookState.cs
Normal file
124
src/AcDream.UI.Abstractions/Input/MouseLookState.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
using System;
|
||||
|
||||
namespace AcDream.UI.Abstractions.Input;
|
||||
|
||||
/// <summary>
|
||||
/// Phase K.2 — state machine for MMB-hold "instant mouse-look" mode
|
||||
/// (retail's <c>CameraInstantMouseLook</c>). While active, mouse-X
|
||||
/// delta drives the character's heading AND the chase camera yaw
|
||||
/// together (combined drive — the camera "instantly" follows the
|
||||
/// character because mouse-X moves the character, and the chase
|
||||
/// camera always tracks the character). Mouse-Y is left to the
|
||||
/// caller — typically pitches the chase camera only.
|
||||
///
|
||||
/// <para>
|
||||
/// The class owns three transitions:
|
||||
/// <list type="bullet">
|
||||
/// <item><see cref="Press"/> — MMB pressed AND ImGui isn't hovering a
|
||||
/// panel; activate, capture initial cursor position for restore.</item>
|
||||
/// <item><see cref="Release"/> — MMB released; deactivate, signal
|
||||
/// cursor restore.</item>
|
||||
/// <item><see cref="OnWantCaptureMouseChanged"/> — ImGui took mouse
|
||||
/// focus while we were active (e.g. a panel pop-up); deactivate AS IF
|
||||
/// the user released the button so the cursor is restored.</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <see cref="ApplyDelta"/> is the per-frame mouse-move hook: when
|
||||
/// active, scales <paramref name="dx"/> by sensitivity and feeds the
|
||||
/// caller-supplied yaw mutator. The mutator is the only side-channel —
|
||||
/// the class doesn't know about <c>PlayerMovementController</c> or
|
||||
/// <c>ChaseCamera</c>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class MouseLookState
|
||||
{
|
||||
private readonly Action<float> _applyYawDelta;
|
||||
|
||||
/// <summary>True while MMB is held AND ImGui isn't capturing the
|
||||
/// mouse. Mouse-X deltas drive yaw only when this is true.</summary>
|
||||
public bool Active { get; private set; }
|
||||
|
||||
/// <summary>Cursor X at the moment <see cref="Press"/> activated.
|
||||
/// Restore on release.</summary>
|
||||
public float CapturedCursorX { get; private set; }
|
||||
|
||||
/// <summary>Cursor Y at the moment <see cref="Press"/> activated.</summary>
|
||||
public float CapturedCursorY { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Per-radian yaw multiplier applied to mouse-X delta. The same
|
||||
/// chase-camera sensitivity factor used elsewhere in GameWindow is
|
||||
/// folded in by the caller; this class only owns the constant
|
||||
/// scale that converts pixels to radians. Default 0.004 matches
|
||||
/// the K.1b RMB-orbit factor.
|
||||
/// </summary>
|
||||
public float SensitivityRadiansPerPixel { get; set; } = 0.004f;
|
||||
|
||||
public MouseLookState(Action<float> applyYawDelta)
|
||||
{
|
||||
_applyYawDelta = applyYawDelta ?? throw new ArgumentNullException(nameof(applyYawDelta));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MMB press transition. Activates only when ImGui isn't capturing
|
||||
/// the mouse — the dispatcher should already gate this, but the
|
||||
/// guard adds defense in depth in case a binding fires through.
|
||||
/// </summary>
|
||||
/// <param name="cursorX">Current cursor X (captured for restore on release).</param>
|
||||
/// <param name="cursorY">Current cursor Y.</param>
|
||||
/// <param name="wantCaptureMouse">Mirror of
|
||||
/// <c>ImGui.GetIO().WantCaptureMouse</c>. When true, the press is
|
||||
/// ignored so a hover-over-panel MMB doesn't grab the cursor.</param>
|
||||
public void Press(float cursorX, float cursorY, bool wantCaptureMouse)
|
||||
{
|
||||
if (wantCaptureMouse) return;
|
||||
if (Active) return;
|
||||
Active = true;
|
||||
CapturedCursorX = cursorX;
|
||||
CapturedCursorY = cursorY;
|
||||
}
|
||||
|
||||
/// <summary>MMB release transition. Always deactivates if active.</summary>
|
||||
public void Release()
|
||||
{
|
||||
if (!Active) return;
|
||||
Active = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reactive deactivation when ImGui takes mouse focus mid-hold.
|
||||
/// E.g. a tooltip pops open over the cursor while MMB is held —
|
||||
/// we yield the cursor to the panel instead of staying captured.
|
||||
/// </summary>
|
||||
public void OnWantCaptureMouseChanged(bool wantCaptureMouse)
|
||||
{
|
||||
if (Active && wantCaptureMouse) Active = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a per-frame mouse-X delta. When active, scales by
|
||||
/// <see cref="SensitivityRadiansPerPixel"/> times the
|
||||
/// caller-supplied <paramref name="extraSensitivity"/> (typically
|
||||
/// the chase-camera sens) and feeds it to the yaw mutator. Sign
|
||||
/// matches retail: dragging the mouse RIGHT yaws the character to
|
||||
/// the right (positive yaw delta).
|
||||
/// </summary>
|
||||
/// <param name="dx">Pixels of horizontal mouse motion since the
|
||||
/// last frame.</param>
|
||||
/// <param name="extraSensitivity">Multiplier (e.g. chase-camera
|
||||
/// sensitivity) applied on top of the radians-per-pixel scale.</param>
|
||||
public void ApplyDelta(float dx, float extraSensitivity)
|
||||
{
|
||||
if (!Active) return;
|
||||
// Sign: dragging the mouse RIGHT (dx > 0) should yaw the
|
||||
// character to the right. With the acdream Yaw convention
|
||||
// (where Yaw 0 = +X, increasing to +Y), positive yaw is
|
||||
// counter-clockwise viewed top-down — so dragging right means
|
||||
// yaw goes DOWN (more clockwise). The dispatcher convention
|
||||
// for chase YawOffset is `YawOffset -= dx * factor`; we keep
|
||||
// the same sign here so character + camera rotate identically.
|
||||
_applyYawDelta(-dx * SensitivityRadiansPerPixel * extraSensitivity);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue