Skip to content

Recipes

Patterns that come up in almost every project, written the way ChatMapper authors have built them for years. Each one works in the simulator, in Play mode, in the HTML player and in every engine export — they use nothing but nodes, links, variables and a line or two of Lua.

Node numbers below are examples; turn on ⋯ → Show node IDs to read your own off the cards.

A conversation that autoplays begins the moment it loads, before the player has oriented themselves. Give them an anchor: one player-choice node between START and the first line, with [f] in its menu text so the single option is shown as a button rather than auto-played.

START ─▶ [Player] Menu: "[f]Begin" ─▶ first line…

That click is also the moment to set things up. Put initialisation in the Start node’s Script:

Variable["trust"] = 0
Variable["askedAboutStorm"] = false

On the web, that first click matters twice: browsers refuse to play audio until the user has interacted with the page, so a Start node is what makes the opening line’s voice-over audible.

If the player can re-enter the conversation, they should not see “Begin” again. Give the Start node a condition on its own status and a Passthrough fallback so the story continues past it:

-- Condition on the Start node (its ID is 1 here); If false: Passthrough
Dialog[1].SimStatus == "Untouched"

The mirror of the Start node: a choice at the end (“Play again”) whose script resets state, and which links back to the beginning.

Variable["trust"] = 0
Variable["hasToken"] = false
Dialog[1].SimStatus = "Untouched" -- so the Start node shows again
Dialog[7].SimStatus = "Untouched" -- and any other one-time line

Anything you gate on SimStatus must be reset here or it stays hidden on the second run.

A hub of questions the player can ask in any order, each disappearing once asked, with the exit appearing only when they are done.

"What do you want to know?" (group node 2)
├─ [f]About the ferry (3) cond: Dialog[3].SimStatus ~= "WasDisplayed" → answer → link back to 2
├─ [f]About the token (4) cond: Dialog[4].SimStatus ~= "WasDisplayed" → answer → link back to 2
├─ [f]About the storm (5) cond: Dialog[5].SimStatus ~= "WasDisplayed" → answer → link back to 2
└─ "Then we're done." (9) cond: Dialog[3].SimStatus == "WasDisplayed"
and Dialog[4].SimStatus == "WasDisplayed"
and Dialog[5].SimStatus == "WasDisplayed"
  • The answers link back to the group node, not to the question line, so the prompt is not repeated every time.
  • [f] on every question keeps the menu showing even when only one question is left; leave it off the last one if you want it to auto-play.
  • Node 9 has no menu text, so as soon as its condition is true it plays automatically and the loop ends.

A variant with a counter instead of statuses:

-- each answer's script
Variable["asked"] = Variable["asked"] + 1
-- the exit's condition
Variable["asked"] >= 3

The condition Dialog[12].SimStatus == "Untouched" on node 12 itself hides it after its first showing. Set If false: Passthrough so the nodes under it are still reachable when it is skipped.

Several conditional responses and one that always applies: give the conditional ones priority High and the fallback Normal (or the conditionals Normal and the fallback Low). Only the best priority present is offered, so the fallback appears only when none of the specific lines’ conditions hold — no need to write the inverse of every condition.

Remembering what the player said elsewhere

Section titled “Remembering what the player said elsewhere”

Variables and statuses persist across conversations within a playthrough. Set Variable["metCaptain"] = true in one conversation and test it in another; or test the other conversation’s node directly:

Conversation[2].Dialog[14].SimStatus == "WasDisplayed"

A training scenario that plays differently each time — the customer starts in a random mood, say. In the Start node’s script:

Variable["mood"] = math.random(1, 3)

Then condition the opening lines on Variable["mood"] == 1, == 2, == 3.

Put [var=?PlayerName] in a line — “And you are?[var=?PlayerName]” — and the story pauses for an answer, stores it in PlayerName, and continues. Use it later with [var=PlayerName]: “Welcome aboard, [var=PlayerName].”

To give a default when the player types nothing, follow the prompt with a node whose script does:

if Variable["PlayerName"] == "" then Variable["PlayerName"] = "stranger" end

(Real-Lua environments only; in the HTML player, write the fallback into the line itself instead.)

[var=trust] anywhere in dialogue or menu text: “You’ve asked me [var=questions] times already.”

Splitting a big story across conversations

Section titled “Splitting a big story across conversations”

A single conversation is comfortable to a few hundred nodes. Beyond that, split by scene and join with Link to conversation…. Two things to know before you cut:

  1. A cross-conversation link always enters the other conversation at its START, not at an arbitrary node. Cut at points where the next scene naturally begins, and use a group node under START as the switchboard if a scene has several entry situations (condition each branch on the variable the previous scene set).
  2. Variables and statuses carry across, so nothing is lost by splitting — but the exit conditions of a loop in one conversation cannot see menu state in another except through Conversation[c].Dialog[d].SimStatus.

To put a question above the choice buttons — “How do you answer?” — make it the Dialogue Text of the parent node (spoken by the narrator or the player themselves); the menu appears beneath it. Engines with a dedicated caption slot can read a custom field instead; see Extending the format.