Files
llamamod/docs/manual/22-strats.md
T

11 KiB
Raw Blame History

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 showing Bucky's script

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.

Editing a constant

  • 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:

Original and edited Coin script in the game

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:

Creating a custom type

  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.

Two squirrels running in circles, eight frames

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
  1. 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.