Wait and both kinds of until loops; blocks run directly

Script runs are resumable threads saved with the game; Rhai is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 18:57:36 +01:00
1 parent 95cf3ce81c
commit d17668f3b2
13 files changed
+466 -686

No files matched your search

+18 -22
View File
@@ -1,12 +1,12 @@
# Mission scripting with blocks
Missions of editor campaigns are scripted with **blocks** (like Blockly): coloured puzzle pieces
put together in the block editor (see [CAMPAIGN_EDITOR.md](CAMPAIGN_EDITOR.md)). Players never see
code – internally the blocks are translated into Rhai, compiled once per mission and run inside the
game's fixed simulation step, so a co-op match runs them identically on every computer.
put together in the block editor (see [CAMPAIGN_EDITOR.md](CAMPAIGN_EDITOR.md)). There is no code:
the game runs the blocks directly, inside its fixed simulation step, so a co-op match runs them
identically on every computer.
Code: `src/blocks.rs` (blocks, values, checking, translation), `src/mscript.rs` (run time, triggers,
zones), `src/campaign_doc.rs` (`MissionScripts`, `TriggerDoc`, `TriggerKind`).
Code: `src/blocks.rs` (blocks, values, checking), `src/mscript.rs` (run time, triggers, zones),
`src/campaign_doc.rs` (`MissionScripts`, `TriggerDoc`, `TriggerKind`).
## Scripts of a mission
@@ -64,6 +64,9 @@ creatures and heroes.
| | Gold + / Mana + | amount (negative takes) |
| Logic | If / else | condition; blocks inside |
| | Repeat | times (0–100); blocks inside |
| | Repeat until | condition, checked **before** each run (already true: the inside never runs); blocks inside |
| | Repeat, then check: until | condition, checked **after** each run (the inside runs at least once); blocks inside |
| | Wait | seconds – the script pauses here and goes on with the next block later (also inside loops and "if") |
| | Switch trigger | trigger, on/off |
| Variables | Set | variable, value |
| | Change | variable, value to add |
@@ -91,28 +94,21 @@ campaign file check (`CampaignDoc::validate`).
## At run time (`src/mscript.rs`)
- The mission's program is generated by `mscript::program` – one Rhai function per script and
trigger (`s_start`, `s_victory`, `s_defeat`, `s_tick`, `t_0`, `t_1`, ...) – and compiled once.
- Scripts read the game (`m.gold()`, `m.count(side, kind)`, `m.in_zone(zone, side, kind)`, ...)
and collect actions (`m.message`, `m.spawn_unit`, `m.terrain`, `m.win`, ...), which are applied
after the call. Variables, the random state, fired triggers and zone contents are part of
`ScriptState` and saved with the game.
- Every run of a script (the start, each second's checks, each fired trigger) is a **thread**: the
lists of blocks it is in and where. *Wait* parks a thread until its time; threads are saved with
the game, so a waiting script goes on after loading.
- The victory check, defeat check and clock start every second, but not while their previous run is
still waiting. A trigger that fires again while its last run waits starts a second run.
- Loops without a *Wait* run up to 2000 blocks per check (ten checks a second) and then go on at the
next check – a script can never hang the game.
- Blocks act at once, in order: a *Count* after a *Spawn* already sees the new units. Variables, the
random state, fired triggers, zone contents and waiting threads are part of `ScriptState`.
- Scripts always run from the first player's side; messages reach every player of a match.
- A script error stops nothing else: it is shown once as a message and written to the test log.
- Test play shows the variables and the trigger log at the top right, and the zones on the map.
### Rhai API (internal)
`gold() mana() time() get(name) set(name, v) count(side, kind) in_zone(zone, side, kind)
random(a, b) div(a, b) times(n) event_is(kind) message(t) objective(t) hint(t)
spawn_unit(side, kind, count, level, place, zone, name) wave(n) add_gold(n) add_mana(n) win() lose()
reveal(zone) camera(zone) terrain(zone, kind) kill(zone, side) trigger(name, on)` – numbers are
floats; `spawn` is a reserved word in Rhai, hence `spawn_unit`. The Rhai dependency keeps the
`no_time` feature (WASM).
## The demo
*Open the demo* in the campaign editor: mission 1 ("The Trial of Triggers") uses all 14 trigger
kinds, all special scripts and nearly every block on a painted map with five zones (rectangle,
kinds, all special scripts and every block (including both kinds of *until* loops and *Wait*) on a painted map with five zones (rectangle,
circle, line, tiles); mission 2 is a random map behind a branch. Play it with *Test play* and watch
the debug view.