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>
65 lines
3.2 KiB
Markdown
65 lines
3.2 KiB
Markdown
# 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.
|