88 lines
4.0 KiB
Markdown
88 lines
4.0 KiB
Markdown
# The script language
|
||
|
||
LlamaMod's **script language** (version 2) is a small structured language in the style of the
|
||
Script view. [`StratCompiler`](../../src/main/java/com/eliasmueller/llamamod/format/strat/StratCompiler.java)
|
||
compiles it to strat assembly, and
|
||
[`StratAssembler`](../../src/main/java/com/eliasmueller/llamamod/format/strat/StratAssembler.java)
|
||
turns that into bytecode with the same checks as hand-written assembly. It is used by the editor's
|
||
type editor (*Language: Script*) and by mods such as the [arena](arena.md). Complete reference:
|
||
[notes/strat-language.md](../../notes/strat-language.md#script-language-stratcompiler--writing-new-behaviour);
|
||
tests: `StratCompilerTest`.
|
||
|
||
## A first strat
|
||
|
||
```
|
||
// bobs one cell up and down and turns
|
||
strat Bobber {
|
||
alias base = v0
|
||
alias phase = v1
|
||
setModel(model:5) // any opcode: name<inline operands>(stack arguments)
|
||
base = self.y + 1 // 1 = one cell
|
||
phase = 0
|
||
on touched { // event handler (type 4), runs as a subroutine
|
||
soundPlay(sound:6)
|
||
}
|
||
loop {
|
||
phase = phase + 4deg
|
||
self.y = base + sin(phase)
|
||
turnLeft(4deg)
|
||
yield // end of this frame
|
||
}
|
||
}
|
||
|
||
sub wobble { // called with "call wobble", ends with return
|
||
self.rotZ = sin(self.rotY) * 0.1
|
||
}
|
||
```
|
||
|
||
## Building blocks
|
||
|
||
| Construct | Notes |
|
||
|---|---|
|
||
| `strat Name { … }` | a type; its label `Name` is the entry point |
|
||
| `sub name { … }`, `call name`, `return` | subroutines |
|
||
| `alias name = v3` | a name for a variable; parameter names from the object schema are predefined |
|
||
| `x = e`, `x += e`, `-=`, `*=`, `/=` | assignment; `x += 1` compiles to `inc` |
|
||
| places | `vN`, alias, `gN`, `self.prop`, `player.prop`, `parent.prop`, `parent.vN`, `camera.` / `target.` / `boss.` / `dialog.prop` |
|
||
| `if (c) { } else if (c) { } else { }` | |
|
||
| `while (c) { }`, `loop { }`, `do { } while (c)` | a `yield` as the last statement of a loop becomes the loop's frame-ending back jump |
|
||
| `switch (e) { case 1: … break default: … }` | C rules (fall through without `break`); case values are constants |
|
||
| `break`, `continue`, `yield`, `end`, `remove` | |
|
||
| `on touched { … }`, `on event(type[, param]) [slot n] { … }` | event handlers; touched needs `collisionOn<…>()` and `collRadius(…)` |
|
||
| `op<a, b>(e1, e2)` | **any opcode** by name: inline operands in `<>` (raw words), stack arguments in `()`; the count must match the opcode |
|
||
|
||
### Numbers
|
||
|
||
* `10` and `10.0` are ten cells (fixed point); `1.5`, `90deg` as in assembly.
|
||
* **Raw** integers: `215r` (= 215/4096) or hex `0x10`. Values inside `<…>` are always raw.
|
||
* Operators over constants are folded at compile time with the VM's own fixed-point rules:
|
||
`2 * 3 + 0.5` → `6.5`.
|
||
* `use rawNumbers` switches to the version-1 rule (plain integers raw) – old scripts get it
|
||
automatically.
|
||
|
||
### Operators (low → high precedence)
|
||
|
||
`||` · `&&` · `|` · `&` · `==` `!=` · `<` `>` `<=` `>=` · `<<` `>>` · `+` `-` · `*` `/` · unary
|
||
`-` `!`. In conditions `&&` / `||` short-circuit; in a value both sides are evaluated.
|
||
|
||
### References
|
||
|
||
`model:N`, `anim:N`, `unit:N[+off]` or `unit:Name` (another custom type), `sound:N`, `tex:N` –
|
||
indices into the level the code is compiled against.
|
||
|
||
## What the compiler does not do (yet)
|
||
|
||
* no `for` loop, no typed variables (everything is a 32-bit word, fixed point by convention);
|
||
* `spawn` takes raw type fields (`spawn<0, 16, 5, 0xC400, 3>(unit:…)`) – you must know the
|
||
values the spawned type needs;
|
||
* the Script view does not show event handlers as `on … { }` blocks.
|
||
|
||
## Tips
|
||
|
||
* Look at an original type that does something similar first (Script panel) and copy its numbers
|
||
– speeds, animation indices, sounds and type fields are level-specific.
|
||
* Keep each frame's work small and always reach a `yield` in loops; a loop without a yield freezes
|
||
the game.
|
||
* Test in the game early: the compiler checks the code's form and references, not whether the
|
||
object behaves well.
|