Script runs are resumable threads saved with the game; Rhai is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.0 KiB
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). 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), src/mscript.rs (run time, triggers, zones),
src/campaign_doc.rs (MissionScripts, TriggerDoc, TriggerKind).
Scripts of a mission
| Script | When it runs |
|---|---|
| Start | once, when the mission begins |
| Victory check | every second – use If ... Win the mission |
| Defeat check | every second – use If ... Lose the mission |
| Clock | every second |
| Triggers | whenever their condition is met (see below) |
The normal goals of the mission still apply; scripts are for anything more complicated. Any script may call Win or Lose.
Triggers
Each trigger has a name, a kind with its condition, once (fires only the first time; switched on again by a Switch trigger block it may fire once more) and active (switched on at the start). Conditions are checked ten times a second.
| Kind | Condition | The "trigger unit" |
|---|---|---|
| Zone entered | a unit of a side/kind comes into a zone | that unit |
| Zone left | a unit of a side/kind leaves a zone | that unit |
| Line crossed | a unit crosses a line zone (from one side to the other, within its ends) | that unit |
| Zone claimed | the player claims more tiles in a zone | – |
| Dug in the zone | fewer solid tiles in a zone (dug, caved in) | – |
| Units in the zone | the count of a side/kind in a zone becomes < <= = != >= > n |
– |
| Timer | after n seconds, or every n seconds (repeat) | – |
| A unit falls | a unit of a side/kind dies (or the unit with this name, see Spawn) | the fallen unit (its place) |
| A tower falls | a tower of the dungeon / the knights / anyone is destroyed | – |
| A value is reached | gold, mana, creatures, heroes or a variable becomes op n |
– |
| Room built | a room of a kind (or any) grows | – |
| Spell cast | a spell (or any) is cast | – |
| Creature slapped | the player slaps one of his creatures | – |
| Research done | the library finishes a discovery | – |
"Becomes" means the trigger fires when the condition turns true, not every check while it stays true. Sides: player, heroes, Mossgrim, Lady Nightwell, rival 3, anyone. Kinds: any, or one of the 22 creatures and heroes.
Blocks
| Category | Block | Fields |
|---|---|---|
| Speak | Message | text – {name} shows the value of variable name |
| Objective | text (shown as the mission's objective) | |
| Crookfinger says | text (with a sound) | |
| Units | Spawn | count, kind, level, side, where (the core, the trigger unit, a zone), optional name |
| Hero wave | count (sent at once) | |
| Remove | side, zone (removes those units) | |
| World | Change ground | zone → floor, earth, rock, water, lava, gold (the core, portals, towers and the gate stay) |
| Reveal | zone (lifts the fog of war) | |
| Show | zone (moves the camera there) | |
| 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 | |
| Mission | Win the mission / Lose the mission | – |
Values
Every number field holds a value; the switch in front of it changes the value's kind:
- numbers: Number, Variable, Gold, Mana, Seconds played, Count (creatures of a
side/kind), Count in a zone, Random (whole number between a and b, deterministic),
Calculation (
+ - * /, division by 0 gives 0), - conditions (for If): Comparison (
< <= = != >= >), and, or, not, Yes/no, Trigger unit is a (kind).
Variables are numbers the mission remembers (saved with the game); a variable is known once some script of the mission sets it.
Checking
The editor checks while you build and marks problems red: empty texts, unknown zones, triggers or
creature kinds, variables never set, a condition where a number belongs (or the other way round).
A trigger whose zone is missing is marked in the trigger list. Problems are also listed in the
campaign file check (CampaignDoc::validate).
At run time (src/mscript.rs)
- 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.
- Test play shows the variables and the trigger log at the top right, and the zones on the map.
The demo
Open the demo in the campaign editor: mission 1 ("The Trial of Triggers") uses all 14 trigger 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.