Character creation and the save window were not detected (the UI manager's dialog slot held a hidden busy dialog); bound keys were swallowed or posted their hotkey into the field. A text field now counts while it is the focused edit control and was drawn in the last 250 ms. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
4.0 KiB
WASD movement (src/mods/wasd.cpp)
How the game moves the hero
Movement is a creature order: cEvent_creature (vtable 0x88e95c, 0x44
bytes), command 4 = walk, sent with cEngine::SendCreatureOrder (0x6170d0,
(creature, event)), which snaps a ground target to a walkable point and
passes the order to creature->vf18 (cCreature::ReceiveOrder, 0x52e400).
| Offset | Field |
|---|---|
| +4 | command (4 walk, 0x105 ...) |
| +0x10 | target object id (attacks) |
| +0x14 | 2: x, y are a ground point; 0: no target |
| +0x18 / +0x1c | ground point (world units, same as creature +0x1c / +0x20) |
| +0x20 | 1: re-aim while a mouse button is held; 2: skip target snapping |
The controlled creature's object id is at cEngine+0x13c; objects come from
cObjectManager::GetObject (0x5fe060, manager *0xad3bc0), cast with
__RTDynamicCast to cCreature. Hero state: +0xfc action mode (2 while
following a held mouse button), +0x150 movement (1 walking, 2 arriving,
0 standing).
Mouse, measured with an order log:
- click (
cEngine::HandleMouseButton0x617360): one walk order to the ground point, then engine flag+0x54 |= 0x10(button held); - while held: every mouse event re-sends a plain walk order (+0x20 = 0) to the
point under the cursor, dozens per tick; the hero stays in mode 2 and walks
smoothly.
cEngine::Tick(0x60f1d0, 10 Hz) additionally sends a re-aim order (+0x20 = 1) per tick after 0.4 s of walking; - release after > 0.5 s: a walk order without target (+0x14 = 0), which stops the hero in place.
What the mod does
Every cEngine::Tick, while W/A/S/D are held (and no text field has the
focus, no mouse button is held): a plain walk order to hero position +
direction x 300 world units (about a cursor's distance). Screen directions in
world axes: up (-1,-1), down (1,1), left (-1,1), right (1,-1); combinations are
summed and normalised. All keys released: the target-less stop order.
Dead ends, for the record: re-aim orders (+0x20 = 1) on their own stop the hero after two orders; far targets (2000 units) are rejected or followed badly; targets near the stepwise-updated position make it arrive and stop.
Hotkeys
Keys reach the game as cEvent_key (category 4; key at +6/+8, modifiers at
+0xa) through the kernel queue to cEngine::ReceiveEvent (0x618130), which
offers them to cUI_Manager::ReceiveEvent (0x757120) first. Both compare
toupper(key) (0x84aa86). Presses come from WM_CHAR (the character), releases
from WM_KEYUP (the virtual key), so keys without a character (F-keys) never
reach the hotkeys. The key bindings (src/mods/input.cpp) handle the raw
WM_KEYDOWN/WM_KEYUP instead: W/A/S/D and the digit row are kept from the
game, bound keys post the original hotkey's key event (Kernel_Instance
0x808490, cKernel::PostEvent 0x808930); the temporary bindings for the
old W/A/S actions are listed in docs/todo.md.
Hotkeys found: engine A (pick up nearby), P (pause), Q/W/E/R/B/Space
(cEngine::UseActionSlot), ^ (console), Esc, Ctrl+B/F/X/Z (Z quicksave); UI
manager C F H I L M N O S T (S save game window). Arrow keys in
cEngine::ScrollInput (0x610650) walk object #2 or scroll the camera; not
reachable in normal play.
Text fields (2026-10-18)
Request: in character creation and the save window, bound keys were ignored or typed something else (K -> s: K posts the game's S hotkey).
Measured (key-press log): the UI manager's dialog slot (+0x144) held a hidden
cUI_BusyDlg in both cases, so the old "dialog shown + focused edit control"
test never found the field. The focused control (+0x148) is the right
cUI_EditControl, but neither the focus nor the control's flags (0x8005)
change when its window closes, and the control has no parent at +0x50. The
save window draws two edit controls per frame.
Now (src/mods/input.cpp, FocusedTextField): keys pass to the game while the
focused control is a writable edit control that cUI_EditControl::Render
(0x737ae0, thiscall (renderer), ret 4, returns at once while hidden) drew in
the last 250 ms. The render hook only notes the focused control.