121 lines
7.2 KiB
Markdown
121 lines
7.2 KiB
Markdown
# Strats: how objects work
|
||
|
||
Every object in the game – a coin, a squirrel, a moving platform, a camera, the llama itself – is
|
||
driven by a small program called a **strat** (Argonaut's "strategy" scripts, inherited from Croc).
|
||
The programs are compiled bytecode for a virtual machine in `groove.exe` (`FUN_0054d180`). LlamaMod
|
||
can read every one of them, change their numbers, write new ones and move them between levels.
|
||
|
||
| Page | For |
|
||
|---|---|
|
||
| this page | how the VM works: units, instances, frames, variables, events, spawning, collision |
|
||
| [Reading scripts](reading-scripts.md) | the editor's Script view and the assembly listing, with a worked example |
|
||
| [The script language](script-language.md) | writing new behaviour in LlamaMod's structured language |
|
||
| [Customising strats](custom-types.md) | changing constants, cloning types, custom types in the editor and in mods |
|
||
| [Example: the Alligator Arena](arena.md) | a complete minigame written in the script language, explained step by step |
|
||
| [Example: the roller coaster](roller-coaster.md) | a new ride for the original coaster cart: how the ride works, track and waypoint data, speed patch |
|
||
|
||
Lab notes and tables: [notes/strat-opcodes.md](../../notes/strat-opcodes.md) (all 434 opcodes with
|
||
semantics, confidence and Argonaut names), [notes/strat-language.md](../../notes/strat-language.md)
|
||
(complete syntax), [notes/editor-object-schemas.md](../../notes/editor-object-schemas.md)
|
||
(parameters of individual types). Machine-readable tables, used by the editor and the compiler:
|
||
[`ops.tsv`](../../src/main/resources/com/eliasmueller/llamamod/format/strat/ops.tsv),
|
||
[`props.tsv`](../../src/main/resources/com/eliasmueller/llamamod/format/strat/props.tsv).
|
||
|
||
## Code units and types
|
||
|
||
The strat code of a level sits at the end of its STPC chunk as a list of **code units**:
|
||
`u32 nUnits`, then per unit `u32 nWords` and the words. A unit is one compiled source file and can
|
||
contain several strats plus their name strings and data (for example `Spider` + `RopeForSpider`).
|
||
The village has 53 units, Lab 1 has 65.
|
||
|
||
A **type** is just an entry point into this code: a MAP record or a `spawn` instruction names a
|
||
byte offset in STPC, and the code there starts with `debugName "Name"`. Code refers to models,
|
||
animation sets and other code by **absolute STPC offsets** – which is why STPC cannot be
|
||
rearranged without relocating every reference, and why each level carries its own copy of the
|
||
code of every type it uses.
|
||
|
||
LlamaMod's disassembler covers the code of all 34 level WADs (1461 units, 670 555 words) and
|
||
assembles every unit back **byte-identically**.
|
||
|
||
## One frame in the life of a strat
|
||
|
||
The VM is a **stack machine** that runs every live strat once per frame (30 frames per second):
|
||
|
||
1. The strat continues where it stopped last frame.
|
||
2. It executes instructions – push values, compute, read and write properties, play animations,
|
||
move – until it reaches a **yield** (`jmpYield`, `jzYield`, `callYield` …).
|
||
3. At the yield its turn ends; the next strat runs. Next frame it continues after the yield.
|
||
|
||
So a typical object is "set up, then `loop { …; yield }`", and anything that takes time is a loop
|
||
that counts frames. Speeds and turn rates in scripts are **per frame**.
|
||
|
||
`end` / `remove` deletes the strat; `return` returns from a `call`.
|
||
|
||
## Data a strat works with
|
||
|
||
| Kind | Syntax | What it is |
|
||
|---|---|---|
|
||
| **variables** | `v0`, `v1`, … (or an alias name) | the strat's own variables (count = the type's *nVars*). The first *n* hold the instance's **MAP parameters** |
|
||
| **properties** | `self.x`, `self.rotY`, `self.floorHeight`, `self.damageTaken` … | 194 named engine fields of the strat: position, rotation, floor under it, distance to the player, animation state, alpha, collision damage … |
|
||
| other strats' properties | `player.x`, `parent.v3`, `camera.…`, `target.…`, `boss.…`, `dialog.…` | the player, the strat that spawned this one (`parent`), the camera and a few registered special strats |
|
||
| **globals** | `g0` … | shared by all strats; the engine itself never reads most of them – their meaning comes from how scripts use them (e.g. g95 = event bits, g101–g105 = exit target and cell, g17–g20 = dive target) |
|
||
| **numbers** | `1.0`, `90deg`, `204r` | 20.12 fixed point (4096 = 1.0 = one cell); angles 0x1000000 = 360°; `r` marks a raw integer |
|
||
|
||
Binary operators pop *b* (top) then *a* and push `a op b`; comparisons push 0/1; `mul` = a·b >> 12.
|
||
|
||
## Events
|
||
|
||
A strat registers **event handlers** in its slots (`onEvent`, in the script language
|
||
`on touched { … }` / `on event(type, param) { … }`). When the event fires, the handler runs like a
|
||
subroutine. Known event types:
|
||
|
||
| Type | Fires when |
|
||
|---|---|
|
||
| 4 | **touched**: contact with another strat (pick-ups, enemies). Needs collision (`collisionOn` + `collRadius`) |
|
||
| 0x40 | timer |
|
||
| 0x80 / 0x100 | player distance |
|
||
|
||
## Spawning
|
||
|
||
`spawn<params, vars, slots, typeFlags, extra>(arguments…, unit:N+offset)` creates a new strat from
|
||
code: the five inline values are exactly the type fields of a MAP record
|
||
([Objects](../reference/objects.md#strat-record-58-bytes-certain-unless-noted)), the stack
|
||
arguments become the child's parameters, and the child's `parent` is the spawner. Facts found
|
||
the hard way (by the arena):
|
||
|
||
* The child appears at the spawner's **matrix position** of the last frame, not at its current
|
||
`x/y/z`: after `self.x = …` the spawner must **yield** before spawning.
|
||
* A type that uses collision needs collision type flags (e.g. 0x28000 like Coin) and enough
|
||
`extra` words for its collision bones – otherwise the game crashes in the heap allocator.
|
||
* The strat pool must have room for every spawned strat (`poolExtra`).
|
||
|
||
## Collision and damage
|
||
|
||
On contact, each side's `hitDamage` is copied into the other's `damageTaken`, and both get event 4.
|
||
The llama's attacks set bits in its `hitDamage`: **256** kick (Action on the ground), **512** dive
|
||
(Action in the air; it homes in on the target an enemy publishes in g17–g19), **1024** charge.
|
||
The llama counts a contact as *its* hit if the other's `hitDamage` shares a bit with its own,
|
||
otherwise `damageTaken & 127 == 1` costs it a heart and knocks it back. The original Alligator has
|
||
513 (= 512 | 1): only the dive hurts it, a kick gets you bitten.
|
||
|
||
## References to level data
|
||
|
||
Some operands are indices into the level's own data and differ between levels; the cross-level
|
||
import rewrites exactly these:
|
||
|
||
| Operand | Points to |
|
||
|---|---|
|
||
| `model:N`, `anim:N`, `unit:N[+off]` (`address`, opcode 0xb2) | a model, an animation set, code of a unit; negative = shared animations in `DEFANIM.WAD` |
|
||
| `sound:N` (opcode 0xd7) | a sample of the level's sound bank |
|
||
| `text:N` (opcodes 0xf0, 0xfa) | a dialogue line in LGPC |
|
||
| `tex:N` (pushed before texture opcodes) | a texture entry (shadows, sprites, particles) |
|
||
|
||
## How the names were found
|
||
|
||
All 434 opcodes (0x000–0x1b1) were named by decompiling every handler with Ghidra; 252 of them
|
||
match Argonaut's own names from the Croc 2 PlayStation demo's jump table and the Croc 1
|
||
decompilation (knowledge only, no code used – see the sources in
|
||
[notes/strat-opcodes.md](../../notes/strat-opcodes.md#sources)). Confidence: 353 high, 79 medium,
|
||
2 low; high-confidence opcodes cover 97 % of all instructions in the levels. The names of rarely
|
||
used opcodes and properties can still be wrong.
|