DSL Syntax
Dialogue files use the .dlg extension and contain a simple DSL (domain-specific language) for writing branching conversations. Unlike a general-purpose programming language, the dialogue DSL is a small set of keywords designed specifically for defining conversation 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 marketTRIGGER
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 drinkENDDialogue Keywords
Section titled “Dialogue Keywords”Speaker Line
Section titled “Speaker Line”BARTENDER: @bartender.greetingThe text before : is the speaker name. It’s matched to a character ID (case-insensitive). The text after : is the dialogue line (supports @key localization).
NARRATOR
Section titled “NARRATOR”Narration with no speaker:
NARRATOR: @narrator.descriptionIn the snapshot, 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 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_greetingENDHow IF blocks work:
- IF blocks are evaluated in order (top to bottom) after the node’s effects run
- The first condition that passes determines where to go next
- If no IF conditions pass, the node falls through to its regular
GOTO(if present) - IF blocks are invisible to the player: they create author-controlled branching
- 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 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, 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 (author-controlled branching):
- Invisible to the player
- First passing condition wins
- 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 gating options behind requirements (e.g., “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 startedADD 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.
BARTENDER: Hello there, traveller!CHOICE What's the news?Quoted text: Wrap in double quotes when the text contains :, #, or starts with @. Quotes are stripped before display.
BARTENDER: "Hello, friend! What'll it be?"CHOICE "Anything starting with @ is safe in quotes"Localization keys (prefixed with @): Reference a key from a locale file. Required for multi-language support.
BARTENDER: @bartender.greetingCHOICE @bartender.choice.ask_newsThe @key is resolved at snapshot build time against the current locale’s data. If the key isn’t found, the raw @key string is displayed.
For single-language games, plain or quoted text is simpler. Add @keys later when you need multiple languages.
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.
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