93 lines
5.4 KiB
Markdown
93 lines
5.4 KiB
Markdown
# Mod "practice" – Practice tools (GAMEPLAY)
|
||
|
||
Package `mods.practice` (`PracticeMod`, `SavePositionPatch`, `ExtraCheckpoints`, plus the helpers
|
||
`Asm` (extra x86 encodings on top of `exe/X86`) and `Hotkeys` (key choices), also used by `freecam`).
|
||
|
||
## Options
|
||
|
||
| Key | Default | Meaning |
|
||
|---|---|---|
|
||
| `saveKey` | F5 | store the llama's position (choices F1–F5, F7–F9, F11, F12, Insert, Delete, Home, End; F6 = game screenshot, F10 = system key are left out) |
|
||
| `loadKey` | F8 | put the llama back to the stored position |
|
||
| `checkpoints` | true | add extra CheckPointPost strats to the level WADs |
|
||
| `spacing` | 10 (4–40) | minimum distance (tiles) between two checkpoints |
|
||
|
||
## Save / restore position (exe)
|
||
|
||
Hook: `call FUN_0054c4a0` (strat update) at **0x54b77e** inside the tick loop of the frame function
|
||
FUN_0054b590 is redirected to a cave:
|
||
|
||
1. poll `GetAsyncKeyState` (via `importAddress`, the game does not import it) with edge detection;
|
||
2. save: player strat `*(0x6d9e1c)` +0x30/+0x34/+0x38 (position, 20.12 fixed, z positive),
|
||
+0x24 (yaw), and the current world/chapter `0x584eb8`/`0x584ebc`;
|
||
3. restore (only if a position was saved in the same world/chapter and the player exists): write
|
||
position into +0x30.. **and** the previous-tick copy +0x50.. (the wall collision FUN_00403ee0
|
||
sweeps from previous to current position, so the jump must not look like movement), yaw into
|
||
+0x24/+0x44;
|
||
4. call the original strat update;
|
||
5. for 4 ticks after a restore the position is written again after the update ("pin"), so the
|
||
remaining fall speed of the llama script is used up on the floor collision instead of moving
|
||
the llama away.
|
||
|
||
Strat structure facts used (0x154 bytes per strat, FUN_0054c4a0/FUN_0054bc30): +0x04 next,
|
||
+0x20/24/28 rotation (angle<<12, 4096 = 360°), +0x30/34/38 position, +0x40..+0x5f copy of
|
||
+0x20..+0x3f from the previous tick, +0x70 3×3 matrix, +0x94/98/9c render position copy,
|
||
+0xc0 collision block, +0xe8 flags (0x8000 floor, 0x40000 wall collision). Velocity is not a
|
||
fixed field – movement comes from strat-script opcodes (e.g. FUN_00550690 = move along yaw), so
|
||
it lives in the llama's script variables.
|
||
|
||
The stored position survives deaths and restarts of the same level; it is ignored in another
|
||
level. Keys are polled with GetAsyncKeyState, so they also react when the game window is not
|
||
focused.
|
||
|
||
## Extra checkpoints (WAD)
|
||
|
||
`ExtraCheckpoints.process`: for every `t[1-8]l*m*.wad` that already contains `CheckPointPost`
|
||
instances (STPC code + type constants available):
|
||
|
||
* candidates = `Coin`/`Coin5` positions (the designers' route), sorted by distance from `Llama`;
|
||
* a candidate becomes a checkpoint if it is ≥ `spacing` from the start, every original and every
|
||
added checkpoint, and walkable floor (collision polygon with ny ≥ 0.7, material ≠ 30/17) lies
|
||
0.8 below … 0.2 above the coin (`Floor.below`, world-space collision of all tiles);
|
||
* new strat = copy of the first CheckPointPost (code, nVars 12, slots 1, flags 0x28000, extra 0,
|
||
parameter value 0x333 appended to the parameter pool), yaw = direction from the nearest previous
|
||
checkpoint, `poolExtra` +1 per post (max 40 per level);
|
||
* each original checkpoint owns exactly one path node = respawn point (position + yaw, `next` 0,
|
||
~0.3–0.5 tiles beside the post); new ones get a node 0.4 tiles ahead (or behind/sideways if
|
||
there is no floor ahead).
|
||
|
||
The floor finder reproduces the height of 68 of 102 original posts within 0.05 (the rest stand
|
||
on moving platforms/strat models). With spacing 10, 32 checkpoints are added in 17 levels
|
||
(e.g. Village 1: +2, jungle day 3: +5). Levels without CheckPointPost (Village 3/4, river,
|
||
chase, roller coaster, lab 2/6) are unchanged.
|
||
|
||
## Tests
|
||
|
||
`PracticeModTest`: instruction encodings (checked against nasm), all WADs: original posts on
|
||
floor, strat/node/poolExtra counts after processing. `FreeCamModTest` applies practice + freecam
|
||
to the real exe (no overlapping patches) and writes `build/tmp/practice-freecam.exe` for objdump.
|
||
|
||
## Verified in game (2026-11-06, Wine 6, apptest, Village 1 = t1l1m001)
|
||
|
||
* F5 → walk away → F8: the llama is back at the stored spot (`img/mods/practice-teleport.jpg`);
|
||
memory check with winedbg: player +0x30.. equals the stored position after F8.
|
||
* After restoring, the camera is not snapped; it swings back behind the llama on its own (it may
|
||
look from an unusual angle for a moment).
|
||
* Together with `freecam`: no patch conflicts; pause menu, leaving to the map and re-entering the
|
||
level, falling off the island (respawn) and game over → no crash; the stored position stays
|
||
valid after the level is restarted.
|
||
* Extra checkpoints: levels load with the added posts; a new post renders at its place
|
||
(`img/mods/practice-extra-checkpoint.jpg`, Village 1, (42.91, 2.9, 50.84), seen with the free
|
||
camera).
|
||
|
||
## Limits / not verified
|
||
|
||
* Activation of an added checkpoint and respawning at its path node were **not** verified in game
|
||
(falling off the island does not use checkpoints; reaching a real death in the test harness was
|
||
not attempted). The strat record, parameter and node layout are copied 1:1 from the level's own
|
||
CheckPointPost, so it is expected to behave like the original ones.
|
||
* Posts are placed at coins with floor below; a coin on a moving platform or behind a one-way
|
||
section can give a post the llama only reaches from one side.
|
||
|
||
Hotkeys are checked together with the other mods' hotkeys (`mods/Hotkeys.conflicts`): applying fails with a clear message if two enabled hotkey options share a key.
|