Files
dungeoncraft/docs/MISSION_SCRIPTING.md
T
2026-11-05 17:16:27 +01:00

119 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
Code: `src/blocks.rs` (blocks, values, checking, translation), `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 |
| | 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`)
- 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.
- 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,
circle, line, tiles); mission 2 is a random map behind a branch. Play it with *Test play* and watch
the debug view.