Quests and dialogue
Usage guide of missions, quest graphs, NPC dialogue and voiced dialogue: documents, node and step types, rewards, givers and tracking, settings and defaults, hooks, engines.
Quests, missions and dialogue (quest., mission., npc.dialogue, dialogue.voice) #
Objectives, branching stories and conversations as data. Readable offline: gripforge docs quests, or the MCP tool gripforge_docs (topic quests).
What it is for #
| Kit | What it adds |
|---|---|
mission.system | ONE active mission at a time: a list of objectives (sequential or parallel), conditions, actions, rewards, zone markers, plus trigger rules that run the same condition and action language outside any mission |
quest.graph | several quests at once as graphs of nodes with choices, branches, timeouts and failure; a quest log, a tracked objective with a world marker and a compass |
npc.dialogue | talking to an NPC: typed lines, choices that run actions and continue the conversation |
dialogue.voice | pre-recorded voices for those dialogues, with lip movement driven by the real audio clock; no AI call while the game runs |
mission.system is the small, shared, engine-portable machine; quest.graph is for stories with choices. They work together: a mission node of a quest starts a mission and waits for its outcome.
Turn it on #
gripforge_gamekit_install { project_id: "gkp_…", id: "quest.graph" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "quest.graph", config: { "failOnDeath": true, "arriveRadius": 4 } }
gripforge_gamekit_install { project_id: "gkp_…", id: "npc.dialogue" }
mission.system requires spawn.entities, world.zones and game.engine. quest.graph requires input.actions and game.engine and uses the inventory, money and level kits for rewards when they are installed. npc.dialogue requires npc.core, interaction.use and hud.bind. dialogue.voice requires npc.dialogue, audio.core and game.engine.
Missions (mission.system) #
Missions are documents of the missions collection (gripforge.mission/1); trigger rules are documents of triggers. Both are validated against the installed kits before play, so a missing kit is a readable error.
| Key | Default | Meaning |
|---|---|---|
autoStart | first | none, first, or a mission id to start when the world is ready |
markerColor | #ffcc00 | colour of the objective zone marker |
failOnDeath | true | fail the active mission when the player is arrested or dies |
Objective steps: go_to { zone }, wait { seconds }, interact { entity | tag }, event { topic, match? }, escape_zone { zone, until? }; other kits add theirs (vehicles bring enter_vehicle, exit_vehicle, deliver_vehicle; dialogue brings talk_to { npc?, topic? }). A timeout fails the mission unless the step is optional.
Conditions: { var, op, value }, { event, match? }, { in_zone }, { has_tag }, { all }, { any }, { not } and the conditions of the kits (has_item, money_at_least, quest_status...). Actions: set_var, inc_var, emit, spawn, despawn, notify, start_mission, complete_objective, fail_mission, teleport, wait, and the verbs of the kits. Rewards: money, reputation, items, vars. Variables: mission.status, mission.objective.label, mission.timer. Events: mission.started, mission.objective.completed, mission.completed, mission.failed, mission.aborted.
Quests (quest.graph) #
{ "id": "lost_lamb", "title": "The lost lamb", "giver": { "npc": "shepherd" }, "start": "find",
"rewards": { "money": 20, "xp": 50 },
"nodes": [
{ "id": "find", "type": "go_to", "label": "Search the old mill", "at": [40, 0, 12], "radius": 6, "next": "decide" },
{ "id": "decide", "type": "choice", "prompt": "The lamb is hurt.", "choices": [
{ "id": "carry", "text": "Carry it home", "next": "home" },
{ "id": "keep", "text": "Keep it", "next": "betray" } ] },
{ "id": "home", "type": "talk", "label": "Bring it to the shepherd", "npc": "shepherd", "timeout": 300 },
{ "id": "betray", "type": "fail", "reason": "kept the lamb" } ] }
Node types: go_to (zone, or at + radius), collect (item, count, take), talk (an NPC document or a tag), kill (count of a tag or kind), use (an item or a tagged entity), event (a bus topic, match, count), wait, choice, branch (first branches[].when that holds, else else), mission, complete, fail. A node without next completes the quest. Any node may carry timeout / onTimeout, failWhen, actions, rewards and marker: false; a quest has conditions.start and conditions.fail. Rewards: items, money, xp, vars. When a dialogue with giver.npc ends, the quest starts, unless giver.auto is false (then a dialogue choice runs { "type": "start_quest", "quest": "…" }).
| Key | Default | Meaning |
|---|---|---|
autoTrack | true | a started quest is tracked when nothing else is |
failOnDeath | false | player.died fails every running quest |
arriveRadius | 3 | m, for a go_to node that gives no radius |
markerColor | #7fd4ff | colour of the tracked objective marker |
presentationProfile | empty | a game-owned presentation profile (slot materials, objective) for the beacon |
Service quest.graph: quests(), runs(), start(id), choose(id, choice), fail(id, reason?), abandon(id), track(id | null), tracked(), objective(id?), target() (distance and bearing for a compass), openLog(). Verbs start_quest, quest_choose, fail_quest, track_quest; conditions quest_status, quest_choice, quest_at. Events quest.started, quest.node, quest.progress, quest.objective.completed, quest.choice, quest.completed, quest.failed, quest.tracked. The runs are saved.
Dialogue (npc.dialogue) #
A key (topic or dialogue id) resolves to a dialogues document with that id, then to a document of this NPC whose topic (default greet) matches, then to the dialogue.lines[key] of the NPC document.
{ "id": "job_offer", "npc": "barman", "topic": "greet", "speaker": "Sal",
"lines": ["Looking for work?", "I have a car that needs moving."],
"choices": [ { "id": "yes", "text": "I'm in.", "then": [{ "start_mission": "move_car" }] },
{ "id": "later", "text": "Not now.", "next": "bye" } ] }
Every NPC with something to say is registered with interaction.use ("Talk to ..."). Lines are typed at charsPerSecond (40); autoAdvanceSeconds (4, 0 disables) moves a typed line on, except the last line when choices wait (keys 1 to 4 pick them); pauseWorld (false) pauses the game while talking. The conversation also ends when the player walks more than 6 m away. HUD variables: dialogue.active, dialogue.speaker, dialogue.text, dialogue.choices, dialogue.choice.1 to .4. Service dialogue.lines: say(npc, topic?), current(), active(), advance(), choose(index | id), end(reason?), topics(npc). Verb start_dialogue. Events dialogue.started, dialogue.choice, dialogue.ended, dialogue.refused.
Voices (dialogue.voice) #
Recordings are produced once, before the game ships, and stored with it. A clip names a dialogue document, a zero-based line, a language, the exact translated text, a sound on the voice bus, its duration and timed mouth cues; changed text or another language never plays a stale clip. A character points at a rig with voice: { rig: "hero_face" }; the rig document maps each mouth cue to morph targets and/or a jaw bone. Without a recording the subtitles and choices carry on and one voice.missing event is emitted per line. Settings: enabled true, followDialogue true, volume 1, blendSeconds 0.04, loadTimeoutSeconds 20. Set audio.core.voiceDuringPause when pauseWorld is on. Collections: dialogue_voices, voice_rigs, voice_cast, dialogue_voice_directions.
Hooks and modding #
| Hook | Payload | What a tap may do |
|---|---|---|
mission.start | { mission } | cancel (the mission stays closed) |
mission.fail | { mission, reason } | change reason, cancel (the mission goes on) |
mission.complete | { mission, title, rewards } | change the rewards, cancel (the last objective is armed again); a kernel hook of game.engine |
quest.start | { quest } | cancel |
quest.choose | { quest, node, choice } | cancel |
quest.advance | { quest, from, to } | change to |
quest.complete | { quest, node, money, xp } | change money and xp |
quest.fail | { quest, node, reason } | change reason, cancel |
dialogue.start | { npc, npcId, dialogue, topic } | change topic, cancel |
dialogue.choose | { npc, npcId, topic, choice, index } | change choice, cancel |
dialogue.advance | { npc, dialogue, index, origin } (auto, input or service) | cancel (the voice kit holds an automatic advance while a line is spoken; an explicit skip still works) |
{ "id": "generous", "hook": "quest.complete", "match": { "quest": "lost_lamb" }, "mul": { "money": 2 } }
Moddable: the settings above and the collections missions, triggers, quests, dialogues. The failures a graph decides itself (a fail node, a timeout, failWhen) are not behind a hook.
Engines #
mission.system is delivered to Godot and Unity as files and to Unreal through the shared kit library, with the same step machine. npc.dialogue is delivered to Godot and Unity as files. quest.graph and dialogue.voice run on the web and in the native player; the hooks run in those two as well. In a web game the quest log and tracker are drawn by the interface layer; dialogue.voice plays through audio.core and depends on the audio device clock for the lips.
When it does not do what you expect #
| Symptom | Cause and fix |
|---|---|
| A mission never starts | autoStart is none, or the mission has a giver with auto: false (the NPC gives it) |
| A quest stays on its node | the node waits for something that never happens: check the tag, the zone, or the match fields of an event node |
| A dialogue offers nothing | the key resolves to no dialogues document and no dialogue.lines: use the topic greet or name one |
| A choice does not run its actions | read dialogue.refused: a dialogue.choose tap cancelled it |
| Voices do not play | no recording for this line and language (see voice.missing), or the text changed since the recording |