NPCs & Dialogue
NPCs are characters the player can talk to, give items to, and interact with. They support branching dialogue trees with gated topics and quest mechanics.
Schema
"npc_id": { "name": "Old Wizard", "description": "A wizened old man in flowing robes.", "keywords": ["wizard", "old man", "mage"], "dialogue": { /* dialogue system */ }, "movement": { /* optional patrol/triggered movement */ }, "accepts_item": "magic_gem", "gives_item": "spell_book", "accept_message": "The wizard takes the gem eagerly...", "sets_flag": "helped_wizard" }
Base Fields
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | required | Display name shown to the player. |
description |
string | required | Text shown when the player examines the NPC. |
keywords |
array | optional | Alternative names for fuzzy matching. |
dialogue |
object | optional | The NPC's dialogue tree. See below. |
movement |
object | optional | Patrol or trigger-based movement. See Movement. |
Dialogue System
The dialogue object defines what the NPC says when talked to.
"dialogue": { "greeting": "Welcome, traveler! What brings you to these parts?", "default": "I don't know about that.", "sets_flag": "met_wizard", "topics": { /* topic definitions */ } }
| Field | Type | Description |
|---|---|---|
greeting |
string | Response when the player talks to the NPC without specifying a topic. |
default |
string | Fallback response when a topic isn't recognized. Used if greeting is not set. |
sets_flag |
string | Flag set when the player first greets this NPC. |
topics |
object | Named conversation topics the player can ask about. |
Dialogue Topics
Topics allow branching conversations. Players trigger them with talk to wizard about quest.
"topics": { "quest": { "keywords": ["quest", "mission", "task"], "text": "I need you to retrieve the Crystal of Power from the dark cave.", "sets_flag": "quest_started", "leads_to": ["crystal", "cave"] }, "crystal": { "keywords": ["crystal", "crystal of power"], "text": "The Crystal glows with ancient magic. Handle it carefully.", "requires_flag": "quest_started", "locked_text": "I have nothing to say about that yet." }, "cave": { "keywords": ["cave", "dark cave"], "text": "The cave lies to the north. Beware the guardian.", "requires_item": "map", "locked_text": "Find a map first, then we can discuss the cave." } }
Topic Fields
| Field | Type | Description |
|---|---|---|
keywords |
array | Words or phrases that trigger this topic. Case-insensitive. |
text |
string | The NPC's response when this topic is discussed. |
sets_flag |
string | Flag set when the player hears this topic. |
requires_flag |
string | Topic is locked until this flag is true. |
requires_item |
string | Topic is locked unless the player has this item. |
locked_text |
string | Message shown when the topic is locked. |
leads_to |
array | Topic IDs that become available after this topic is discussed. |
Dialogue Flow
1. Player says talk to wizard → Shows greeting or default
2. Player says talk to wizard about quest → Engine matches "quest" against topic keywords
3. If the topic is gated by requires_flag or requires_item, checks those conditions
4. If locked, shows locked_text. If unlocked, shows text
5. If the topic has leads_to, those topics become accessible
6. If the topic has sets_flag, that flag is set to true
Topics listed in leads_to are unlocked only when the parent topic has been discussed. This creates natural conversation progression where the player must first ask about a general topic before specific follow-ups become available.
Quest Mechanics (Item Trading)
NPCs can accept items from the player and optionally give items in return. This is the core quest/trade mechanic.
"blacksmith": { "name": "Blacksmith", "description": "A burly smith working the forge.", "accepts_item": "iron_ore", "gives_item": "iron_sword", "accept_message": "The blacksmith takes the ore and hammers it into a fine blade. 'Here you go!'", "sets_flag": "sword_forged" }
| Field | Type | Description |
|---|---|---|
accepts_item |
string | Item ID that this NPC will accept from the player. |
gives_item |
string | Item ID given to the player in return (optional). |
accept_message |
string | Custom message shown when the item is accepted. |
sets_flag |
string | Flag set when the NPC accepts the item. |
Players give items with: give iron_ore to blacksmith
Movement
NPCs (and creatures) can move between rooms on their own. Movement ticks once per player action and fires depart_msg and arrive_msg when the entity enters or leaves the room the player is currently in.
There are two movement types: patrol for cyclic routes and triggered for one-shot moves tied to a game flag. The schema applies equally to entries in both npcs and creatures.
A creature currently engaged in combat with the player will not move, even if its movement schedule says it should. Combat always wins.
Patrol Movement
Patrol movement cycles an entity through a list of rooms, each with a duration (in turns). When the duration is up, the entity moves to the next step. The schedule loops forever.
"movement": { "type": "patrol", "schedule": [ { "room": "tavern_floor", "duration": 3 }, { "room": "village_square", "duration": 3 } ], "depart_msg": "Bardric slings his lute over his shoulder and wanders off.", "arrive_msg": "Bardric strolls in, already mid-song." }
| Field | Type | Description |
|---|---|---|
type |
string | Must be "patrol". |
schedule |
array | Ordered list of schedule steps. Each step has a room and duration. The entity cycles through them in order, looping back to the first once the last is complete. |
depart_msg |
string | Message shown when the entity leaves the player's current room. Defaults to "The [name] leaves." |
arrive_msg |
string | Message shown when the entity arrives in the player's current room. Defaults to "The [name] arrives." |
Schedule Step Fields
| Field | Type | Description |
|---|---|---|
room |
string | Room ID where the entity sits for this step. |
duration |
number | Number of player turns the entity stays in this room before moving to the next step. |
blocked_while_player_in |
array | Optional list of room IDs. If the player is currently in any of them, the transition to this step is deferred until the player leaves. Use this to prevent an NPC from awkwardly walking into the player's face. |
Triggered Movement
Triggered movement is a one-shot: the entity moves to a destination room the first time a given flag is true, then stops moving forever. Use it for an NPC that arrives after a quest milestone, or a creature that storms in after the player rings a bell.
"movement": { "type": "triggered", "trigger_flag": "bell_rung", "destination": "village_square", "depart_msg": "The guard marches out, hand on her sword.", "arrive_msg": "A guard storms into the square, summoned by the alarm." }
| Field | Type | Description |
|---|---|---|
type |
string | Must be "triggered". |
trigger_flag |
string | Global flag that must be true for the move to fire. The move fires once and then the entity will not move again. |
destination |
string | Room ID the entity should move to when triggered. |
depart_msg |
string | Message shown when the entity leaves the player's current room. |
arrive_msg |
string | Message shown when the entity arrives in the player's current room. |
Full Example
"mysterious_wizard": { "name": "Mysterious Wizard", "description": "An ancient wizard with a long silver beard. His eyes shimmer with arcane knowledge.", "keywords": ["wizard", "mage", "old man"], "dialogue": { "greeting": "Ah, an adventurer! I have been expecting you. Ask me about the quest if you dare.", "default": "Hmm, I know not of such things.", "sets_flag": "met_wizard", "topics": { "quest": { "keywords": ["quest", "adventure", "mission"], "text": "Deep in the Darkwood Forest lies a crystal of immense power. Retrieve it, and I shall reward you greatly.", "sets_flag": "quest_accepted", "leads_to": ["forest", "reward"] }, "forest": { "keywords": ["forest", "darkwood", "woods"], "text": "The forest entrance is to the north. Take a torch — the darkness there is unnatural.", "locked_text": "First, ask me about the quest." }, "reward": { "keywords": ["reward", "payment"], "text": "Bring me the crystal and I will grant you a powerful spell.", "locked_text": "Complete the quest first, then we can discuss compensation." } } }, "accepts_item": "power_crystal", "gives_item": "spell_book", "accept_message": "The wizard's eyes light up. 'You found it! As promised, take this spell book. May it serve you well.'", "sets_flag": "quest_complete" }