Files
llamamod/notes/mod-arena.md
T

110 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Alligator Arena (`arena`, LEVELS)
Package `mods/arena`: `ArenaMod` (writes `wads/T1L9M0nn.wad` + loading picture `T19n.COR`),
`Arena` (level script, village base like the test world), `ArenaScripts` (the strat scripts,
script language 2), `ArenaImport` (compile against a source level + `StpcMerge` import).
Registered in `ModRegistry` (one line). Test-only: `ArenaTestMod` (src/test, script options).
| Option | Default | Meaning |
|---|---|---|
| `lives` | 5 (1–10) | hits per alligator |
| `waves` | 3 (1–5) | wave n has min(n, 2) alligators (3 waves = 5 alligators) |
| `energy` | 4 (1–5) | llama energy cap (5 = game normal) |
| `coins` | 1 (0–5) | EnemyCoins per hit (+5 on defeat, +5 bonus at the exit) |
| `chapter` | 49 (32–49) | TestLevel chapter; a chapter already written by another mod → apply error; a stored 2–8 falls back to 49 |
Only chapters 32–49: the coin counter `0x584F1C[world·6 + chapter]` and the flag byte
`0x584EC8[…]` of world-9 chapters 2–31 overwrite other globals (chapter 8 = last played map,
chapter 9 = furthest progress – verified, see [testlevel-chapters.md](testlevel-chapters.md)).
The tally total (LNFO) is 2 placed coins + alligators · ((lives − 1) · coins + 5) + 5 bonus.
Script options (`Arena`, tests/`ArenaTestMod`): `probe=true` (event probe 1.6 ahead of the llama),
`master=false` (no alligators), `start=x,z,rot`, `debug=true` (HUD coin counter shows
kills·100 + wave·10 + alive).
## Layout
Island 10..30 in sea (death plane). Pit x/z 16..24 (sand), walls 1.1 high, corner towers. Llama
(20.5, 18) facing +z, start camera node 1.6 behind (inside the pit). CheckPointPost (16.8, 16.8).
Exit pad 1×1 cell at (20.5, 23.5), material 16, gate on the north wall; **ArenaMaster** stands on
it. Alligator spawn spots (20.5, 22.2), (18, 21.8), (23, 21.8). Two Coins at the side walls.
## How it works
`ArenaImport.append` compiles the units with `StratCompiler` and appends them to the STPC of
**t1l4m002** (Jungle by day 2, has the Alligator), so `model:47`, `anim:98–104`, `sound:58–62` and
`unit:56+0xf0` (EnemyCoin) / `unit:36+0x28` (Sparkles) are that level's. A unit may refer to an
earlier one as `unit:@Name` (→ `unit:N+off`; the compiler has no `@label` references).
`merge` imports the units into the village base like the editor's cross import (models,
animations, textures, 3 sound samples, EnemyCoin/Sparkles shared), adds one prototype MAP record
per type (nVars/slots/typeFlags/extra), then `LevelBuilder` builds the level.
* **ArenaMaster** (12 vars, no model): `g104/g105 = −1`, 2 s, then per wave: move to a spawn
spot, **yield**, `spawn<0, 16, 5, 0xC400, 3>(ArenaGator)`, `alive += 1`; waits until
`alive == 0` (gators do `parent.v3 −= 1`), 1.5 s pause. Win: `g101..g103` = door target
(1, 9, chapter), `g104/g105` = its cell (the llama's own material-16 check then ends the level
too), 5 EnemyCoins, Sparkles every 8 frames, `endLevel` when the llama stands on the pad (as
LevelDoor). Each frame `player.health2` is capped at `energy` (also after a respawn).
* **ArenaGator** (spawned, 16 vars): the Alligator's model, collision (0x4000, 3 bones),
particles and animations. `hitDamage = 1793` (1 | 256 | 512 | 1024). `on touched`: if
`damageTaken & 1792` (kick 256, dive 512, charge 1024) → hit flag, else → "bit" flag. Main
loop (switch state): 0 watch (turn to the player, after a timer snap if within 5 units and on
the same floor ±0.4), 1 snap anim, 2 chase (speed up to 0.03 + 0.006·wave), 4/5 lunge and
slow down (original numbers), 3 back off. Hit: lives −1, sound, green puff, `coins` EnemyCoins,
blink 45 frames (alpha 0.25 + red ambient every 4 frames, no further hits), back off. Bite:
back off and rest 90 frames. Clamped to the pit, y = floor. Last life: the original death
(shrink, puffs, 5 EnemyCoins), `parent.v3 −= 1`, remove. The `aim` sub (ported L_03d0) writes
g17..g20 so the llama's dive homes in on it. Fade-in on spawn (setAmbient 0→63).
## Engine facts found
* **Compiled `on touched` handlers fire** (event type 4): probe in game – the llama walks into
it, it removes itself and three EnemyCoins pop out and are collected (coin counter 3).
* Contact: each side's `hitDamage` is copied into the other's `damageTaken`, both get event 4.
Llama: kick = 256 (Action on the ground, anim 28, hind-leg collision bones), dive = 512 (Action
while jumping: homes in on the target in g17..g19 that enemies' scripts publish, best distance
g20), charge = 1024. The llama counts a contact as its hit if `damageTaken & hitDamage ≠ 0`,
else `damageTaken & 127 == 1` → `loseHeart` + knock-back (the original Alligator has 513 = only
the dive hurts it, a kick gets you bitten).
* `spawn<params, vars, slots, typeFlags, extra>`: same fields as a MAP record (FUN_0054bfc0);
`extra` = words for collision bones. A MAP-placed type that uses `collisionOn` needs collision
type flags (0x28000 like Coin) – with 0 the game crashed (page fault in the heap allocator
FUN_0041ae20).
* `spawn` copies the strat's matrix/translation block, not `x/y/z`: after `self.x = …` the child
appears at the old place unless the spawner yields first.
* Energy 0: the game's "Weiter / Beenden" screen; Weiter restarts the level (all strats fresh).
* The level tally after the exit showed "Münzen gesammelt 0 / 55" on chapter 8: 0 = the coin slot
after endLevel wrote the last played map into it, 55 = LNFO of the Village base. Fixed: chapter
32–49 and recounted LNFO ("2 / 12" for 1 alligator with 1 life).
## Verified in game (apptest :24, Wine 6, load 3–9, 2026-11-07)
* Probe (`probe=true;master=false`): handler fires, coins collected (counter 3).
* Default options (energy 5 and 4 runs): alligator fades in, watches, snaps, chases, bites (HUD
energy drops), backs off. Kicks (Z) hit: puff, blink, coins pop out, counter rises
(1 → 13 over two waves); defeat with puff and coins; wave 2 brings two alligators
(debug counter 122 = 1 kill, wave 2, 2 alive). Blind kicking dies in wave 2 → Weiter restarts
the arena with energy capped again.
* Win (`lives=1, waves=1, start=20.5,21,0`): one kick defeats it, bonus coins + sparkles on the
pad, walking onto it → tally screen with the arena loading picture.
* Not verified separately: the dive (jump + Action) as the hit (sequences mixed kicks and dives),
the charge, waves 4–5, coins option values other than 1.
![fight](img/mods/arena_fight.jpg)
![defeat](img/mods/arena_defeat.jpg)
![win: bonus coins on the exit pad, tally](img/mods/arena_win.jpg)
## Tests
`ArenaTest`: scripts compile for all option corners (EnemyCoin spawns = coins + 5, gator spawns =
alligators, energy cap code only below 5); wave sizes; settings validation; registered; built
level (CD): Llama, master on the exit cell with its vars, checkpoint, 2 coins, no LevelDoor, merged
code decodes, master spawns the gator unit, gator spawns EnemyCoin, `lives` in the code; another
mod's chapter → IOException; probe option.
## Issues / limits
* Loading picture and tally use the TestLevel texts ("TestLevel Kapitel 49").
* The exit target is the arena itself (1, 9, chapter): it restarts after the tally.
* Exiting chapter 49 unlocks "chapter 50" (progress 9/50); the map caps the cursor at 49 (checked).