Repository navigation
EN Create Scripts Events
Français · Type: reference · Audience: author · Context: engine repository, created project · Status: ready · Applies from: 4.3.0
Everything else in a game reacts to the player: a tap, a rule, its commands. Three primitives let the world act on its
own: scripts (things that happen without a tap), events (something just happened) and
moving characters. They are data like the rest, so validate, solve and the save
understand them.
A cat that stretches every few seconds, a neighbour who paces, a cook who comes when the gong rings: a script. A key found that sends Grandpa home and shows a message, wherever the player is: an event and its listener. Which to use for remembering things is on flags, variables and events.
A script is a list of commands that runs on its own, one command at a time, in the gaps between the player's
actions: never during an action, a cutscene, a conversation or a minigame. The engine resumes it at the next gap.
A room's scripts (scripts in the room) run while the player is in the room; the game's scripts (scripts in
game.ts) run everywhere.
scripts: [
// Biscuit stretches every seven seconds, forever.
{ id: 'biscuit_naps', loop: true, stepIds: ['wait', 'stretch'],
do: [{ wait: 7000 }, { anim: ['biscuit', 'stretch'], ms: 1200 }] },
// Lou strolls between two spots until the deal is done; when `while` turns false the script rewinds and waits.
{ id: 'lou_paces', loop: true, while: '!bouquet_given', stepIds: ['w1', 'to-a', 'w2', 'to-b'], do: [
{ wait: 6000 }, { walk: [235, 262], who: 'neighbor' }, { wait: 3000 }, { walk: [147, 278], who: 'neighbor' },
] },
// Once: the cook comes when the gong rings, then the script is done.
{ id: 'cook_comes', stepIds: ['gong', 'move', 'dinner'],
do: [{ waitEvent: 'gong' }, { moveActor: ['cook', 'hall'] }, { say: ['cook', 'Dinner!'] }] },
],| Field or command | Effect |
|---|---|
id |
Unique in the whole game (a script can be started, stopped or waited on from anywhere). |
stepIds |
One stable id per top-level command, required in schema 3: the save keeps the script's position by step id, so reordering its commands does not break a save. |
loop: true |
Starts again from the top when done. A loop must contain a wait, waitUntil or waitEvent (validate: loop script "…" never waits). |
while: COND |
Runs only while the condition holds; when it turns false, the script rewinds to its first command and waits. It is checked before every command: a script that loses the item its while needs stops right there (gain the new item first, then lose the old one). |
{ wait: 3000 } |
A pause: the player keeps playing meanwhile. |
{ waitUntil: COND } |
Pauses until the condition holds (checked at every gap). |
{ waitEvent: 'gong' } |
Pauses until the event is emitted, even from another room. |
{ startScript: 'id' }, { stopScript: 'id' }
|
From any script or rule: start a script from the top (again, if done or stopped), or stop it. |
A script's position is saved with the game: it resumes where it was. Since a script's commands interleave with the
player's actions at the level of a whole command, keep each command short (a walk, a line, a pose), and put a
sequence that must not be interrupted in a cutscene. The solver lets scripts run, one step at a time, as one of its
choices (what the solver proves).
Guarantee ·
scripts-between-actions· referenced · scope self-checked by the author (AI), not reviewed A script never runs a command while the engine is busy with an action, and a script whosewhileturns false rewinds to its first command.
Limit ·
script-not-skippableA script cannot be skipped by the player, and it is not real time: a script never runs in the middle of the player's action or line, only between them.
A flag is a state (the door is open). An event is a moment (the bell just rang). { emit: 'bell_rang' } runs,
right there and in order: first it moves on every script paused on { waitEvent: 'bell_rang' }, wherever it is; then
it runs the listeners of the current room (events in the room), then the game's (events in game.ts)
(src/engine/core/event-runtime.ts).
// market.ts, in a rule: { gain: 'key' }, { emit: 'key_found' }
// game.ts:
events: [
{ id: 'grandpa.goes-home', on: 'key_found', once: true, do: [{ moveActor: ['grandpa', 'house'] }, { toast: 'Grandpa went home.' }] },
],| Field | What |
|---|---|
id |
Stable id, required in schema 3; a once listener is remembered as event.<id>. |
on |
The event's name. |
if |
The listener runs only if this holds when the event is emitted. |
once |
Only the first time. |
do |
The commands. |
An event is not remembered: to react later (next time the player enters a room), set a flag in the listener and
test it in onEnter. validate warns about an event emitted that nothing listens to, a listener of an event never
emitted, and a script waiting for an event never emitted.
Guarantee ·
emit-order· referenced · scope self-checked by the author (AI), not reviewedemitruns the current room's listeners and the game's; aoncelistener runs once.
The room's listeners run first, then the game's.
A cat that stretches, a gong that calls the cook, a listener that reacts, checked by the validator:
{
"room": {
"id": "hall",
"name": "The hall",
"decor": "decor/hall",
"actors": { "biscuit": { "char": "biscuit" }, "cook": { "char": "cook", "visible": "dinner_called" } },
"hotspots": { "gong": { "name": "gong" } },
"look": { "biscuit": "Biscuit, asleep. Mostly.", "cook": "The cook.", "gong": "A brass gong." },
"on": [
{ "id": "hall.ring-gong", "verb": "use", "a": "gong", "do": ["Bong.", { "emit": "gong" }] }
],
"scripts": [
{ "id": "biscuit_naps", "loop": true, "stepIds": ["wait", "stretch"],
"do": [{ "wait": 7000 }, { "anim": ["biscuit", "stretch"], "ms": 1200 }] },
{ "id": "cook_comes", "stepIds": ["gong", "called", "show", "dinner"],
"do": [{ "waitEvent": "gong" }, { "set": "dinner_called" }, { "show": "cook", "fade": 800 }, { "say": ["cook", "Dinner!"] }] }
],
"events": [
{ "id": "hall.gong-heard", "on": "gong", "once": true, "do": [{ "say": ["biscuit", "Mrrp?"] }] }
],
"hints": [{ "id": "hall.hint", "until": "dinner_called", "lines": ["Ring the gong."] }]
},
"layout": {
"walk": { "area": [[20, 300], [620, 300], [630, 395], [10, 395]] },
"entries": { "default": [320, 360] },
"hotspots": { "gong": { "rect": [500, 160, 70, 90], "approach": [530, 330] } },
"actors": { "biscuit": { "x": 150, "y": 360 }, "cook": { "x": 420, "y": 350 } }
},
"characters": {
"biscuit": { "name": "Biscuit", "color": "#ffd28a", "kind": ["cat"], "sprites": { "idle": ["cat/idle"], "stretch": ["cat/stretch"] } },
"cook": { "name": "Cook", "color": "#8fd3ff", "sprites": { "idle": ["cook/idle"] } }
}
}
-
ScriptDef,EventRuleinsrc/engine/core/types/content.ts;src/engine/core/script-runtime.ts. -
emit,waitUntil,waitEvent,startScript,stopScript(commands). - Moving characters, flags, variables and events, the execution model.
Generated publication · publication générée — current documentation · documentation courante: quest'n'click 4.4.0 r1, documentation revision · révision de la documentation 8dc717c69acb62c4742b69c54a7e7dfb620f75d8.
Earlier versions and permalinks · versions précédentes et liens permanents: https://wanoo.github.io/quest-n-click/docs/
An edit made in this wiki is overwritten at the next publication · une modification faite dans ce wiki est écrasée à la publication suivante.
Edit the source · Modifier la source · Report an error · Signaler une erreur
Index (6)
Start (6)
Play (9)
Install (17)
- Requirements
- Install from the release archive
- Install from npm, once published
- Install from the repository to work on the engine
- doctor: checking your machine
- Image toolchain
- Audio toolchain: ffmpeg, fluidsynth, Furnace, uv
- Upgrade
- Upgrade notes: 4.4
- Upgrade notes: 4.3
- Upgrade notes: 4.2
- Upgrade notes: 4.1
- Upgrade notes: v3
- Upgrade notes: v2 to v3
- Coming from web-scumm
- Package reference
- Install troubleshooting
Create a game (55)
- Your first game
- Layout of a created project and of the engine repository
- The working loop
- Content principles and the identity contract
- Rooms
- Geometry: placement, walk zones, masks, layers, camera
- Something is visible but unreachable
- Reactions
- Game commands
- Conditions
- Flags, variables and events: which to use
- Custom code
- Characters
- Items and inventory
- World map
- Dialogues
- Scripts and events
- Moving characters: patrols and walks
- Exits
- Chapters and objectives
- State and saves: persistent or temporary, renaming an id, migrations
- Staging: wide rooms and animations
- The stage: lights, particles, floors
- Skin and fonts
- Title and credits
- Heroes: several characters and hero choice
- Built-in minigames
- A custom minigame
- Translations
- Music in stems
- Sound effects
- Voices: per language and per hero
- Images: prompts, cutting sheets, transparency, scale
- Asset provenance
- The storyboard, source of truth
- Puzzle design
- Classic mechanics
- Review pages
- DSL reference
- Checking your game
- What the solver proves
- Solver options
- Playtests
- Remix
- Speedrun for authors
- The sealed ending
- Production template
- Example games
- Working with an AI
- Build and release a game
- Publish on GitHub Pages
- The static game on Clever Cloud
- Other static hosts
- Sub-path, cache and service worker, incomplete deploys, going back
- Testing on a phone over the LAN
Local Studio (24)
- The local Studio: overview and launch
- Edit a room
- Placement and stage
- Preview, apply, undo
- Storyboard
- Assets and cutting
- Check: validator, solver, critical path, heat, health, coverage
- Before and after captures
- Semantic diff
- Play and replay a session
- Rule explainer
- Language and objectives
- Voices and the heroes × languages matrix
- Map
- Notes
- Remix in the Studio
- Music
- The Assistant: bring your own key
- Demo mode and studio-apply
- Local and LAN security
- Studio API
- Local MCP: setting up each client
- MCP tools
- Studio troubleshooting
Team Studio (21)
- What runs where
- Concepts: revision, transaction, span, lease, preview, build
- Starting as an invited dev
- Working together day to day
- Roles and actions: the full matrix
- Invitations
- Managing members
- Write refused on the hosted Studio: stale base revision (409)
- Write refused on the hosted Studio: span locked (423)
- Recovering after a lost lease, a disconnection or a stale preview
- Comments
- Map and semantic diff
- Publishing builds
- Remote MCP
- The game in Git
- Upgrading the engine of a hosted game
- Hosted assets
- The server's command line
- Health and metrics routes
- Trying it locally with compose.yml
- Known limits
Clever Cloud (23)
- Clever Cloud: overview, cost and prerequisites
- Apps and add-ons
- Domains and TLS
- Keycloak
- Generic OIDC and GitLab
- Configuration
- First deployment
- Backups
- RPO and RTO
- Restore the Studio on a fresh installation
- Redeploys
- Upgrading the server and checking state after a migration
- Limits of going back
- Monitoring
- Security
- Rotating keys
- Recovering admin access
- Changing domain or OIDC provider
- Worker, PostgreSQL or Cellar down
- Exporting the game and leaving the installation
- Retention
- Clever Cloud troubleshooting
- Costs and teardown
Reality Bridge and connectors (16)
- The Reality Bridge: what it is
- Quick start
- Guarantees
- Install and run the Bridge
- Backup and restore
- Several tenants
- Signals and the manifest
- The Bridge's command line
- Connectors overview
- Email connector
- Telnet connector
- SSH connector
- Open Badges connector
- The connector SDK
- Security: Biscuit and signatures
- Bridge and connector troubleshooting
Contribute (25)
- Welcome
- Developer setup
- The dev loop
- Before a pull request
- Conventions
- Writing documentation
- Architecture
- Code tour
- Import boundaries
- Which tests run when
- Mutation testing
- The coverage ratchet
- The quality baseline
- CI explained
- Field passes
- Benchmarks
- Releasing
- npm packages
- Decisions and ADRs
- Governance
- Bot pull requests
- Contributing with AI agents
- Security policy
- Code of conduct
- Licences and credits