Switching between weapon classes resets the hero's animation state inside cUI_Taskbar2::SelectWeaponSet, which dropped the walk for a tick. The hook sends the WASD walk again at once. Checkpoint before the movement rework. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
4.7 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.
Weapon set switch while walking (2026-10-18)
Request: the hero stops for a moment when switching weapon sets (F-keys) while walking with WASD.
Measured (state per tick + orders): cUI_Taskbar2::SelectWeaponSet
(0x6e1440, ret 8) sends order 0x114 (set, items) and 0x107; between melee
sets the walk goes on, between a melee and a bow set the hero drops to idle
for one tick: the weapon class changed, and the switch resets the hero's
animation state (0x54e540(1, 0)). The next WASD tick restarts the walk.
Now: SelectWeaponSet is hooked (skillbar.cpp) and calls
mods::wasd::ResumeWalk, which sends the walk for the held keys at once.
Not yet confirmed in-game.