DSL Syntax
Dialogue files use the .dlg extension and a DSL (domain-specific language), a small scripting format for branching conversations. Its keywords define nodes, choices, conditions, and effects.
File Structure
Section titled “File Structure”A .dlg file consists of:
- Optional
TRIGGERdeclaration (auto-start on location entry) - Optional top-level
REQUIREconditions (for triggered dialogues) - One or more
NODEblocks
TRIGGER tavernREQUIRE notFlag seenIntro
NODE start NARRATOR: @narrator.intro SET flag seenIntro
CHOICE @narrator.choice.continue END dialogue ENDStructure Keywords
Section titled “Structure Keywords”Defines a dialogue node, which is a single point in the conversation.
NODE greeting BARTENDER: @bartender.helloThe first NODE in the file is the start node (used as the dialogue’s startNode).
Closes a CHOICE or IF block:
CHOICE @option GOTO nextENDWhen used as END dialogue, it’s an effect that closes the conversation:
CHOICE @goodbye END dialogueENDRoutes to another node (within a choice or as auto-advance):
CHOICE @option GOTO next_nodeENDOr to a location (ends dialogue and moves player):
GOTO location marketGOTO location handles scripted movement by ending the dialogue and changing the current location. Map travel through travelTo() also calculates travel time and checks location triggers.
TRIGGER
Section titled “TRIGGER”Declares that this dialogue auto-starts when the player enters a location:
TRIGGER tavernMust be at the top of the file, before any NODE.
REQUIRE
Section titled “REQUIRE”Condition that must pass. At the top level (for triggered dialogues) or inside choice blocks:
# Top-level: controls when the trigger firesTRIGGER tavernREQUIRE notFlag seenIntro
# Inside a choice: controls when the choice is visibleCHOICE @buy_drink REQUIRE variableGreaterThan gold 4 GOTO drinkENDCondition names include hasFlag, notFlag, hasItem, variable comparisons, atLocation, questAtStage, questStatus, character and relationship checks, timeIs, itemAt, and roll.
Dialogue Keywords
Section titled “Dialogue Keywords”Speaker Line
Section titled “Speaker Line”BARTENDER: @bartender.greetingThe text before the first : is the speaker name. It is matched to a character ID (case-insensitive). The text after : is the dialogue line, which supports @key localization and may itself contain colons.
Each node has one speaker line. To let a different character speak, route to another NODE. That is how a conversation moves between speakers. A node with two speaker lines is a validation error.
NARRATOR
Section titled “NARRATOR”Narration with no speaker:
NARRATOR: @narrator.descriptionIn the snapshot, which contains the data sent to the renderer, the speaker is null and speakerName is "Narrator".
Optional voice audio file for the current node:
VOICE bartender_greeting.oggPORTRAIT
Section titled “PORTRAIT”Optional portrait override (e.g., different expression):
PORTRAIT bartender_angry.pngChoice Blocks
Section titled “Choice Blocks”CHOICE @choice_text REQUIRE condition # Optional, multiple allowed effect1 # Optional effects effect2 GOTO target_node # Destination (see below)ENDChoices are shown to the player as clickable options. They can have:
- Conditions: choice is hidden if any condition fails
- Effects: run when the choice is selected
- GOTO: required unless the choice terminates the dialogue
A choice holds button text, conditions, effects, and a route. To show narration when a choice is picked, route it to a node with GOTO and put the line in that node.
A choice terminates the dialogue (no GOTO needed) when it contains END dialogue or GOTO location:
# Terminal choice: ends the dialogueCHOICE "Look around." END dialogueEND
# Terminal choice: ends dialogue and travels to locationCHOICE "Head to the market." GOTO location marketENDConditional Blocks (IF)
Section titled “Conditional Blocks (IF)”IF condition GOTO target_nodeENDor with effects:
IF hasFlag metBartender SET flag returningCustomer GOTO returning_greetingENDor with effects that fall through to the node’s regular GOTO:
IF hasFlag metBartender ADD relationship bartender 1END
GOTO greetingHow IF blocks work:
- IF blocks are evaluated in order (top to bottom) after the node’s effects run
- The first condition that passes runs that IF block’s effects
- If the passing IF block has a
GOTO, that target is used - If the passing IF block has no
GOTO, the node falls through to its regularGOTO(if present) - If no IF conditions pass, the node falls through to its regular
GOTO(if present) - IF blocks are invisible to the player: they create conditional branches
- Multiple IF blocks can exist in a node, but only the first passing one executes
Example:
NODE check_reputation BARTENDER: @bartender.sizing_you_up
IF variableGreaterThan reputation 50 GOTO trusted_path END
IF variableGreaterThan reputation 20 GOTO neutral_path END
GOTO suspicious_pathIf reputation is 60, goes to trusted_path. If reputation is 30, goes to neutral_path. If reputation is 10, goes to suspicious_path.
Text Nodes and Silent Nodes
Section titled “Text Nodes and Silent Nodes”How the engine handles nodes with no CHOICE blocks depends on whether the node has text:
Text-only nodes (text, no choices)
Section titled “Text-only nodes (text, no choices)”The engine shows the text and waits for the player to click Continue. Only after the player clicks does the engine advance via GOTO or IF blocks.
NODE intro BARTENDER: @bartender.welcome GOTO main_menuThe player reads the bartender’s welcome, clicks Continue, then the engine moves to main_menu.
Text-only nodes are the natural way to present narration, character monologues, or story beats before branching.
Silent processing nodes (no text, no choices)
Section titled “Silent processing nodes (no text, no choices)”A node with no speaker line and no text is a silent processing node. The engine processes it instantly, applying effects and evaluating IF blocks, then advances to the next node without waiting for the player.
# Roll the dice and branch. The player never sees this node as a prompt.NODE skill_check ROLL result 1 20 IF variableGreaterThan result 14 SET flag passedSkillCheck ADD relationship bartender 1 GOTO success END GOTO failure
NODE success BARTENDER: "Impressive. This one's on the house."
CHOICE "Cheers!" GOTO start END
NODE failure BARTENDER: "Nope. Five gold."
CHOICE "Fine." ADD variable gold -5 GOTO start ENDskill_check is silent. It runs ROLL, evaluates the IF, applies the passing branch’s effects, and jumps to success or failure without the player seeing any prompt.
Summary
Section titled “Summary”| Node type | Has text? | Has choices? | Player sees |
|---|---|---|---|
| Text-only | Yes | No | Text + Continue button |
| Choice node | Yes or no | Yes | Text + choice buttons |
| Silent processing | No | No | Auto-advances instantly |
IF vs CHOICE REQUIRE
Section titled “IF vs CHOICE REQUIRE”IF blocks (conditional branching):
- Invisible to the player
- The first matching branch runs
- Effects inside the passing IF block run before its
GOTO - Used for conditional story flow based on game state
CHOICE REQUIRE (player-facing filtering):
- All passing choices are shown to the player
- Player selects which one to take
- Used for showing options only when their requirements pass (for example, “needs 50 gold”)
# IF: player never sees the branchingNODE greeting BARTENDER: @bartender.hello
IF hasFlag metBefore GOTO returning_customer END
GOTO new_customer
# CHOICE REQUIRE: player sees all available optionsNODE offer BARTENDER: @bartender.what_can_i_get_you
CHOICE @buy_ale REQUIRE variableGreaterThan gold 4 GOTO buy_ale END
CHOICE @buy_wine REQUIRE variableGreaterThan gold 10 GOTO buy_wine END
CHOICE @just_browsing GOTO leave ENDEffect Lines
Section titled “Effect Lines”Effects modify game state. See Effects Reference for the full list.
SET flag metBartenderCLEAR flag doorLockedSET variable gold 100ADD variable gold -5ADD item old_coinREMOVE item rusty_keyMOVE item sword armorySET questStage odd_jobs startedSET trackedQuest odd_jobsADD journalEntry tavern_discoverySET characterLocation merchant tavernADD toParty elisaREMOVE fromParty elisaSET relationship bartender 5ADD relationship bartender 1SET characterStat elisa level 5ADD characterStat elisa health -10SET mapEnabled falseADVANCE time 2START dialogue merchant_introEND dialogueMUSIC tension_theme.oggSOUND door_slam.oggVIDEO intro_cinematic.mp4INTERLUDE chapter_oneROLL bluffRoll 1 20NOTIFY @notification.quest_startedText Syntax
Section titled “Text Syntax”Dialogue text can be written in three forms:
Plain text: Just write the words. Works for most lines, including text that contains colons.
BARTENDER: Hello there, traveler!CHOICE What's the news?NARRATOR: The sign reads: closed until dawn.Quoted text: Wrap in double quotes when the text contains a # (otherwise everything from the # onward is treated as a comment). Quotes are stripped before display.
BARTENDER: "Room #3 is down the hall."Inside quoted text, write \" for a double quote and \\ for a backslash. This is how a line can contain both quotes and a #:
NARRATOR: "He said \"room #3\" and walked off."The player sees: He said “room #3” and walked off. Doodle Studio writes these escapes for you when you type quotes in the visual editor.
Quoted speaker and narrator text can also span several lines:
NARRATOR: "The road was empty.
By morning, the snow had covered our tracks."The line breaks are part of the displayed text, but the passage remains one dialogue entry. Use plain text without quotes for ordinary single-line dialogue.
Localization keys (prefixed with @): Reference a key from a locale file. Required for multi-language support.
BARTENDER: @bartender.greetingCHOICE @bartender.choice.ask_newsWhen the engine prepares the snapshot, it looks up the @key in the current locale. A missing key appears as the raw @key string.
For single-language games, plain or quoted text is simpler. Add @keys later when you need multiple languages.
Formatting dialogue text
Section titled “Formatting dialogue text”Speaker lines, narrator text, and choices support lightweight formatting:
BARTENDER: He hands you a cE5C453[*key*].NARRATOR: _The room falls silent._CHOICE Take the *key*.The player sees:
- Bartender: He hands you a key.
- Narrator: The room falls silent.
- Choice: Take the key.
| Effect | Syntax | Output |
|---|---|---|
| Bold | *key* |
key |
| Italic | _The room falls silent._ |
The room falls silent. |
| Color | cE5C453[key] |
key |
| Bold and color | cE5C453[*key*] |
key |
The same syntax works inside locale values:
bartender.gives_key: 'He hands you a cE5C453[*key*].'Color codes use exactly six hexadecimal digits without a leading #. To show
formatting punctuation literally, put a backslash before it:
NARRATOR: Write \*word\* to display *word* without bold formatting.Unclosed formatting is displayed literally. Enter formatting directly in dialogue text or locale values.
Comments
Section titled “Comments”Lines starting with # are ignored:
# This is a commentNODE start BARTENDER: @bartender.greeting # Inline comments work tooA # inside a quoted string is preserved as text. In plain text, # starts a comment and everything after it is ignored. Use quotes if you need a literal # in dialogue.
Complete Example
Section titled “Complete Example”Triggered intro and character conversation live in separate files, using localization keys for every displayed line.
content/dialogues/tavern_intro.dlg:
# Plays automatically the first time the player enters the tavernTRIGGER tavernREQUIRE notFlag seenTavernIntro
NODE start NARRATOR: @narrator.tavern_intro SET flag seenTavernIntro
CHOICE @narrator.choice.look_around END dialogue ENDcontent/dialogues/bartender_greeting.dlg:
# Plays when the player clicks the bartender characterNODE start BARTENDER: @bartender.greeting
CHOICE @bartender.choice.ask_rumors REQUIRE notFlag heardRumors SET flag heardRumors ADD relationship bartender 1 GOTO rumors END
CHOICE @bartender.choice.buy_drink REQUIRE variableGreaterThan gold 4 ADD variable gold -5 ADD variable _drinksBought 1 NOTIFY @notification.bought_drink GOTO after_drink END
CHOICE @bartender.choice.ask_quest REQUIRE questAtStage odd_jobs started GOTO quest_update END
CHOICE @bartender.choice.goodbye GOTO farewell END
NODE rumors BARTENDER: @bartender.rumors ADD item old_coin NOTIFY @notification.found_coin
CHOICE @bartender.choice.interesting GOTO start END
NODE after_drink BARTENDER: @bartender.after_drink # Player sees text, clicks Continue, then engine advances to start GOTO start
NODE quest_update BARTENDER: @bartender.quest_info SET questStage odd_jobs talked_to_merchant NOTIFY @notification.quest_updated GOTO start
NODE farewell BARTENDER: @bartender.farewell END dialogue