Files
sacred_k/docs/wasd.md
T
khallaandClaude Opus 5.5 cec2556fa3 Key bindings layer: skills on 1-7, weapon sets on F1-F7
Raw WM_KEYDOWN/WM_KEYUP are handled in both window procedures (the game
swaps 0x8110d0 and 0x8122a0 with SetWindowLong; 0x8110d0 is the in-game
one). Bound keys are swallowed and post the original hotkey's key event
to the kernel, so F-keys work and bindings are layout independent
([keys] in sacred_re.ini). Replaces the WASD hotkey remap and the digit
swap; W/A/S/D and the digit row no longer reach the game. Skill keys
still cast through the right mouse button. Enabled switches removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 00:11:55 +02:00

3.2 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::HandleMouseButton 0x617360): 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.