119 lines
6.1 KiB
Markdown
119 lines
6.1 KiB
Markdown
# 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.
|