207 lines
11 KiB
Markdown
207 lines
11 KiB
Markdown
# Strat scripts: reading and changing object behaviour
|
||
|
||
Every object in the game – a coin, a squirrel, a moving platform, a camera – is driven by a small
|
||
program, its **strat** (Argonaut's "strategy" scripts). The level editor shows these programs in
|
||
the **Script** panel, lets you change their numbers (speeds, heights, timers, damage) and lets you
|
||
create **your own object types** by copying a script and editing it.
|
||
|
||

|
||
|
||
## The Script panel
|
||
|
||
Open it with **View → Panels → Script** (**Ctrl+Shift+K**), the **Open script** button in the
|
||
Inspector's *Script* section, or click its tab in the bottom dock.
|
||
|
||
| Part | What it does |
|
||
|---|---|
|
||
| **Type** | the code unit to show. A unit is one compiled script file and can hold several strats (Bucky + ThrowAcorn). Custom types are marked *[custom]* |
|
||
| **Script / Assembly** | *Script* is a readable view with expressions, `if`/`else` and `loop`; *Assembly* is the exact instruction list (the language you write new types in) |
|
||
| **Follow selection** | shows the script of the object you select in the viewport or the Outliner |
|
||
| **Find**, **Next** | search the text (Enter = next match) |
|
||
| **Uses** | lists every line that uses the selected line's variable, property or label |
|
||
| **Edit value…** | changes the numbers of the selected line (also: double-click) |
|
||
| **Clone as new type…** | copies this type into a new, editable object type |
|
||
|
||
The side panel explains the selected line: what the instruction does, how many values it takes
|
||
from the stack, its Argonaut name where known, and what properties like `floorHeight` mean. With
|
||
nothing selected it lists the unit's parameters, which other scripts **spawn or reference** this
|
||
one, and how many objects of it are placed.
|
||
|
||
### Reading a script
|
||
|
||
* `self.x`, `self.rotY`, `self.floorHeight` are properties of the object itself; `player.…`,
|
||
`parent.…` (the object that spawned it) and `camera.…` those of others.
|
||
* `v0`, `v1`, … are the object's variables. The first ones are the **parameters** you set in the
|
||
Inspector (*Object parameters*): parameter 0 = `v0`. Where the editor knows a parameter's
|
||
meaning, the script uses its name (`snapToFloor`).
|
||
* `g12` is a global variable shared by all scripts.
|
||
* Numbers are in game units: `1.0` = one cell (or "true"), angles are written like `5.625deg`.
|
||
Speeds and turn rates are **per frame** (the game runs at 30 frames per second). Small raw
|
||
numbers have an `r`: `204r` is 204/4096 of a cell (about 0.05).
|
||
* `break name` / `continue name` leave or repeat the outer loop labelled `name:`.
|
||
* `yield` ends the object's turn for this frame; a `loop { … yield }` runs once per frame.
|
||
* `onEvent` installs a handler, for example "when the player touches me" (event type 4).
|
||
* `spawn<…>(…)` creates another object, `address model:5` names a 3D model of the level.
|
||
|
||
## Changing numbers
|
||
|
||
Double-click a line with a number (or select it and press **Edit value…**). Lines whose numbers
|
||
can be edited show a tooltip; edited lines turn **orange**.
|
||
|
||

|
||
|
||
* Type the new value as `1.5` (fixed point), `90deg` (angle), `42` or `0x2a`. The dialog shows
|
||
the value in all three readings and refuses impossible ones (a `pushShort` holds only
|
||
−8.0 … 7.99).
|
||
* The change can be undone (**Ctrl+Z**) like every other edit.
|
||
* It applies to **every object of that type** in the level, also to objects other scripts spawn.
|
||
* It is saved in the project and written into the level when you build or play.
|
||
|
||
Example: in the Coin's script, `self.y = self.floorHeight + 204r` puts snapping coins just above
|
||
the floor. Changing `204` to `1.5` makes them float one and a half cells high:
|
||
|
||

|
||
|
||
## Your own object types
|
||
|
||
**Clone as new type…** in the Script panel (or **Level → New object type from script…** for an
|
||
empty template) opens the type editor:
|
||
|
||

|
||
|
||
1. **Name** – the name of the new type (letters, digits, `_`).
|
||
2. **Entry label** – where the script starts (for clones: the renamed strat).
|
||
3. **Type fields / parameters from** – which existing type's settings (parameters, waypoints,
|
||
flags) new objects of this type start with. In a script you can use that type's parameter
|
||
names (for a Coin: `snapToFloor` for parameter 0).
|
||
4. Edit the source. **Check** assembles it and verifies the result; errors name the line.
|
||
5. **Create type**. The type appears in **Assets → Objects** and can be placed like any other.
|
||
|
||
The new script is added to the level's code; the original type is not changed. Custom types are
|
||
stored in the project (as text) and rebuilt every time it is opened.
|
||
|
||
### Editing and deleting your types
|
||
|
||
Select an object of a custom type (or pick the type in the Script panel's *Type* list):
|
||
|
||
* **Edit type…** (Script panel or the Inspector's *Script* section) opens the type editor with
|
||
its script. Change it, **Check**, then **Apply**. Objects of the type that are already placed
|
||
keep their place and run the new script. **Ctrl+Z** undoes the change. *Type fields /
|
||
parameters from* can be changed here too: new objects start with the other type's settings,
|
||
placed ones keep theirs.
|
||
* **Delete type…** removes the type. If objects in the level use it, the editor tells you how many
|
||
and deletes them together with the type (also undoable).
|
||
|
||
### Writing a script instead of assembly
|
||
|
||
Set **Language** in the type editor to **Script** to write the behaviour in a structured language
|
||
that looks like the Script view:
|
||
|
||
```
|
||
strat Pulser {
|
||
alias size = v0
|
||
setModel(model:5)
|
||
size = 1.0
|
||
loop {
|
||
while (size < 4.0) { // grow to four times the size
|
||
size = size + 0.05
|
||
scale(size)
|
||
yield // wait for the next frame
|
||
}
|
||
do { // and shrink back
|
||
size = size - 0.05
|
||
scale(size)
|
||
yield
|
||
} while (size > 1.0)
|
||
}
|
||
}
|
||
```
|
||
|
||
* Statements: `x = value`, `x += value`, `if (…) { } else { }`, `while (…) { }`, `loop { }`,
|
||
`do { } while (…)`, `switch (…) { case 1: … break default: … }`, `break`, `continue`, `yield`,
|
||
`end`, `call name` (for `sub name { … }`).
|
||
* `on touched { … }` runs its block when the player touches the object (the object needs
|
||
`collisionOn<0x20000>()` and `collRadius(…)`, like a coin); `on event(type, param) { … }` for
|
||
other events. The block ends like a sub (`remove` deletes the object).
|
||
* `a && b` and `a || b` in conditions stop as soon as the answer is known.
|
||
* Any instruction can be called by name with its values in brackets: `turnLeft(4deg)`,
|
||
`scale(size)`; values that are part of the instruction go in `<>`: `collisionOn<0x8000>()`.
|
||
* Places: `v0`… (or a name given with `alias`), globals `g12`, properties `self.y`, `player.x`…
|
||
* Numbers: `10` and `10.0` both mean ten cells, angles are written `90deg`. For a raw value (as
|
||
the Script view shows them) add `r`: `215r` = 215/4096; hex like `0x8000` is raw too. Values
|
||
inside `<…>` are raw. Scripts written before this rule start with `use rawNumbers` when you open
|
||
them, so they keep working.
|
||
* **Check** compiles and verifies; errors name the line. Switching *Language* back to *Assembly*
|
||
shows the generated instructions. The full reference is in `notes/strat-language.md`.
|
||
|
||
Language reference: every instruction is one line, labels end with `:`, comments start with `;`.
|
||
`push 4deg` / `turnLeft` pushes a number and lets an instruction use it; `jmpYield label` waits
|
||
for the next frame and jumps. The full syntax and the list of all 434 instructions are in the
|
||
LlamaMod notes (`notes/strat-language.md`, `notes/strat-opcodes.md`).
|
||
|
||
## Tutorial: make the squirrel run in a circle
|
||
|
||
Bucky, the nut-throwing squirrel of *Jungle at Night*, can run in a circle in two ways. Both are
|
||
in one test level below: on the left the waypoint way, on the right the script way.
|
||
|
||

|
||
|
||
### Find out how Bucky moves
|
||
|
||
1. Import Bucky (**Assets → All worlds**, search "Bucky", drag it into the level).
|
||
2. Select it and open the **Script** panel. The script shows `wpFirst()`, `wpNext()` and
|
||
`wpTurnToY(v1)` / `moveForwardQ(215r)`: Bucky **runs along its waypoint chain**, turning
|
||
with the rate in parameter 1 and moving 215/4096 cells per frame.
|
||
3. At every node it reads `self.wpParam` (the node's field **+26**): the low part chooses run
|
||
(`0`) or jump (`1.0`, `2.0`), the high part `128.0` means "run on", `64.0` "wait until the
|
||
player comes close". At the end: `} while (!(!v0 && (self.wpEndFlags & 2r)))` – with
|
||
**parameter 0** set the chain repeats instead of ending.
|
||
|
||
### Way 1: a circular waypoint chain (no script change)
|
||
|
||
1. With Bucky selected, delete its imported path (**Delete path** in the Inspector) and draw a
|
||
new one with the waypoint tool (**N**): 12 nodes on a circle (radius 1.5–3 cells).
|
||
2. Select all nodes of the chain and set **Field +26** to `524288` (= 128.0, "run on without
|
||
waiting").
|
||
3. Set Bucky's **Parameter 0** to `1.0` (repeat the chain). Parameter 1 is the turn rate
|
||
(`64.0` = 5.6° per frame; larger turns faster).
|
||
4. Play. After the last node Bucky continues with the first one – forever.
|
||
|
||
### Way 2: a scripted circle (custom type)
|
||
|
||
1. Select Bucky, **Clone as new type…**, name it `BuckyCircle`.
|
||
2. In the source, find the label `L_05b0:` (the start of the main loop) and insert before it:
|
||
|
||
```
|
||
push 3.0
|
||
popVar v9 ; animation state: running
|
||
push 3.0
|
||
popVar v22
|
||
circle:
|
||
push 4deg ; turn 4° per frame
|
||
turnLeft
|
||
pushShort 215 ; forward speed per frame
|
||
moveForwardQ
|
||
forceCollision
|
||
pushProp floorHeight ; stay on the ground
|
||
popProp y
|
||
call L_0180 ; Bucky's animation routine
|
||
jmpYield circle ; next frame
|
||
```
|
||
|
||
3. **Check**, **Create type**, place a BuckyCircle from **Assets → Objects** and play. The
|
||
circle's size is speed ÷ turn rate: 215/4096 cells × 90 frames per turn ≈ 4.7 cells around.
|
||
|
||
## Limits
|
||
|
||
* Big structural changes are up to you: the editor checks that the code is well-formed and that
|
||
every model/animation/code reference points somewhere valid, not that the object behaves well.
|
||
* Names of rarely used instructions are educated guesses (the opcode list shows a confidence for
|
||
each).
|
||
* Small constants stored as short numbers only accept −8.0 … 7.99 (see *Changing numbers*).
|
||
* The Script view shows `while`, `do … while`, `switch`, `break` and `continue` where it can and
|
||
`goto` where the original jumps don't form nested blocks (31 places in the whole game, all in
|
||
the llama's script); the Assembly view is always exact.
|
||
* In the clone dialog, renaming the type doesn't yet rename the entry label or the name inside
|
||
the script text – edit those by hand if you want them to match.
|