110 lines
7.2 KiB
Markdown
110 lines
7.2 KiB
Markdown
# 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.
|
||
|
||

|
||

|
||

|
||
|
||
## 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).
|