An adventure is described using the JSON format. JSON data block consists of nested Objects with name-value pairs and Arrays (lists of values without names) like the following.
Object:
{
health: 10,
name: "John"
}
Array:
["sword", "axe"]
The value in an Objects or a value in an Array can be true/false, number, "text", an Object, or an Arrays. Name should consist of letters, underscore, and numbers (no spaces).
The main adventure object contains global game properties and references to all rooms, items, and game logic.
const advData = {
title: "My Adventure",
theme: "terminal",
startRoom: "entrance",
status: "Health: {player_health}/100",
gameState: {
player_health: 100,
door_unlocked: false
},
rooms: { /* room definitions */ },
items: { /* item definitions */ }
};
---
Rooms are the main building blocks of the adventure, containing an overall description, interactive objects, and items. Rooms should be constructed with a clear separation between static description and dynamic interactive elements.
1. Keep main room description short (1-2 sentences) that establish the overall mood and basic layout
2. Don't mention specific objects or items in the room description - these are automatically added from object/item roomDescription fields
3. Create objects for all important interactable elements that have at least an examine action
4. Use containers to organize items visually and logically
This room relies on examine, open, and go templates being available.
rooms: {
entrance: {
description: "You stand in a grand entrance hall illuminated by torchlight.",
image: "entrance.png",
items: ["rusty_key"],
objects: {
// Important interactive objects
chest: {
name: "chest",
roomDescription: "A massive oak [[]] stands by the north wall.",
description: "It is closed and looks very old.",
actions: ["examine", "open"]
},
table: {
name: "wooden table",
roomDescription: "A sturdy [[]] stands in the center of the room.",
description: "There are empty plates and mugs standing on the table.",
actions: ["examine"]
}
container: { // 'container' is the default container for items in room that did not find a better match
roomDescription: [ { condition: "roomItems.container", value: "\n\n"} ] // this conditional \n\n will make it so that items not in any specific container are in their own paragraph in the final room description
},
west: { // "exit" object should be last
name: "west",
roomDescription: "A door leads to the [[|kitchen]].",
actions: [
{
template: "go",
goto: "kitchen",
transition: "left"
}
]
}
}
}
}
Containers allow logical grouping and conditional visibility of items within rooms. Object key is considered the container name. In the above example of a well structured room, there are 4 objects, 3 of them are supposed to be containers. The 'container' object uses the special name 'container' which indicates that all items that were not placed in a more suitable container will be placed here.
items: {
gold_coin: {
name: "gold coin",
roomContainer: "chest", // Item prefers to be in the "chest" container
roomDescription: [
{
condition: "container.chest",
value: "A shiny [[]] lies in the chest.",
},
{
value: "A shiny [[]] glints in the light.", // fallback (not in chest)
}
],
actions: ["examine", "take"]
},
silver_key: {
name: "silver key",
roomContainer: ["chest", "table"], // Item can appear in multiple containers - order of preference is respected
roomDescription: "A delicate [[]] rests here.", // It is not necessary to have specific descriptions for each container, but if there are, they must be in the same order as the roomContainer list
actions: ["examine", "take"]
}
}
The final room description is assembled from:
1. Room description (static mood text)
2. Object roomDescription fields (in order)
3. Item roomDescription fields (grouped by container)
This creates dynamic, context-aware room descriptions without manual text management. If objects have conditional descriptions or visibility, the room description updates automatically when game state is updated - changes are highlighted.
Items are very similar to room objects, but they can be created, deleted, collected, or dropped. Items do not support any actions by themselves, just like room objects, but unlike room objects they are defined outside of rooms. Items do not have actions like take or drop natively, these actions need to be defined for each item. This allows for example creating an NPC (that is present in multiple rooms and has the same behavior) as an item without the (awkward) take/drop actions and have the NPC only defined in one place.
item: {
name: "iron key",
description: "A rusty iron key.",
roomDescription: "A [[]] is laying in the corner.",
actions: ["examine", "take", "drop"] // Use template-based examine, take, and drop actions
}
There are no built-in go or move commands, room transitions are now handled through object or item (think scroll of teleportation) actions.
objects: {
north_door: {
name: "north door",
roomDescription: "A door leads north",
actions: [
{
command: ["go north", "north", "enter door"],
message: "You go through the north door.",
goto: "dining_room",
transition: "fade"
}
]
}
}
objects: {
west_door: {
name: "west",
roomDescription: "A door leads [[]]",
actions: [
{
template: "go", // provides command names
outcomes: [
{
condition: "!door_locked",
goto: "library",
message: "You enter the library.",
},
{
message: "The door is locked.",
}
]
}
]
}
}
This is a simple template that just defines the commands for movement, but it can be enhanced by for example counting the number of room transitions (time => exhaustion), printing a message.
templates: {
go: {
command: ["go {object}", "move {object}", "{object}"],
icon: "move"
}
}
Objects are interactive elements within rooms that players can usually examine and manipulate.
### Example
object: {
name: ["wooden door", "door"],
description: "A sturdy wooden door.",
roomDescription: "A [[]] stands to the north.", // [[]] is replaced with clickable link to items/objects first action (typically examine)
visible: "!player_blind",
actions: { /* action definitions */ },
}
Actions define what players can do with objects and items.
actions: [
{
command: ["open {object}", "unlock {object}"],
condition: "door_locked && inventory.key",
message: "You unlock the door.",
sets: { door_locked: false }
}
]
Actions can define a prefix field to allow both direct commands and prefixed commands (like "say hello" vs just "hello").
actions: [
{
command: { en: "hello", cz: "ahoj" },
prefix: { en: "say", cz: "řekni" },
message: { en: "Hello there!", cz: "Ahoj!" }
}
]
This allows both:
When using hello in room descriptions, the generated clickable command will automatically include the prefix: "say hello" (localized based on current language).
Achievements provide recognition for player accomplishments and milestones. They are automatically unlocked when specific actions are performed and persist across game sessions.
Actions can include an achievement field to unlock achievements when the action is successfully performed.
- name: string - Unique identifier for the achievement (used internally)
- description: string|object - Achievement description (supports localization)
- hidden: boolean (optional) - Currently not used (default: false)
actions: [
{
command: ["open {object}", "unlock {object}"],
condition: "door_locked && inventory.key",
message: "You unlock the door with the key!",
sets: { door_locked: false },
achievement: {
name: "First unlock",
description: "Unlocked your first door",
}
},
{
command: "examine statue",
condition: "!statue_examined",
message: "You discover ancient runes on the statue!",
sets: { statue_examined: true },
achievement: {
name: "scholar",
description: "Discovered hidden knowledge"
}
}
]
The audio system allows adding sound effects and background music that enhances the game experience. Sounds play once, while music loops with smooth crossfading transitions.
Define audio resources in the adventure's audio object:
audio: {
// Simple format - just a file path
"stone_steps": "sounds/stone-steps.mp3",
// Advanced format with volume adjustment
"combat_music": {
url: "music/fighting-theme.mp3",
volume: 0.8 // 0.0 to 1.0, multiplied by master volume
},
}
Set music in the adventure object to play when the game starts, set music in room objects to override (can use conditions, so different music can play in the same room depending on game state):
music: "ambient_theme" // ID from audio object
Use the sound field to play sound effects when actions occur:
actions: [
{
command: "walk north",
message: "You walk north on the stone floor.",
sound: "stone_steps", // Play sound once
goto: "courtyard"
},
{
command: "drop key",
message: "You put the key down.",
sound: "drop", // Use stock sound (no stock sounds at this time)
takes: "rusty_key"
}
]
Use the music field in a room to set background music with crossfading:
dining_room: {
...
music: [
{
condition: "uncle_insulted",
value: "tense_theme" // Fade to new music
},
{
value: "cheerful_theme" // Fade to new music
}
]
...
}
The framework may provide some built-in sounds that don't need to be defined in your adventure:
Examples of potential additions:
Templates provide reusable action definitions that can be automatically expanded during game initialization.
templates: {
examine: {
command: ["examine {object}", "look {object}"],
icon: "look",
message: "{description}"
},
take: { // includes localizaion
command: { en: ["take {object}", "get {object}"], cz: ["vezmi {object}", "seber {object}", "zvedni {object}"] },
icon: "take",
outcomes: [
{
condition: "roomItem.{id}",
message: { en: "You take the {name}.", cz: "{Name} byl přidán do inventáře." },
takes: "{id}"
}
]
},
drop: {
command: { en: ["drop {object}", "put {object}", "place {object}"], cz: ["polož {object}", "odlož {object}", "vlož {object}", "zahoď {object}"] },
icon: "drop",
outcomes: [
{
condition: "inventory.{id}",
message: { en: "You drop the {name}.", cz: "{Name} byl odebrán z inventáře." },
drops: "{id}"
}
]
}
}
Templates can be used in actions through string references or template objects:
actions: ["examine", "take", "drop"]
actions: [
"examine", // Use template as-is
{
template: "take", // Base on template
outcomes: [ // Custom outcomes override template
{
condition: "player_strength < 10",
message: "You are not strong enough to pick {name} up.",
}
// if the above condition is not met, the outcome in template is executed and item is picked up
]
}
]
Templates support all standard action properties:
All usual text adventure built-in commands like take and drop have been removed in favor of template-based actions that support full localization and allow much more flexibility. You can customize for example pick up messages to fit your adventure language style.
templates: {
examine: {
command: { en: ["examine {object}", "look {object}", "look at {object}", "inspect {object}"], cz: ["prozkoumej {object}", "prohlédni {object}", "podívej {object}"] },
icon: "look",
message: "{description}"
},
go: {
command: { en: ["go {object}", "move {object}", "{object}"], cz: ["jdi {object}", "jdi na {object}", "jdi do {object}", "{object}"] },
icon: "move",
},
take: {
command: { en: ["take {object}", "get {object}"], cz: ["vezmi {object}", "seber {object}"] },
icon: "take",
outcomes: [
{
condition: "roomItem.{id}",
message: { en: "You take the {name}.", cz: "Vezmeš {name}." },
takes: "{id}"
}
]
},
drop: {
command: { en: ["drop {object}", "put {object}"], cz: ["polož {object}", "odlož {object}"] },
icon: "drop",
outcomes: [
{
condition: "inventory.{id}",
message: { en: "You drop the {name}.", cz: "Položíš {name}." },
drops: "{id}"
}
]
},
say: {
prefix: { en: "say", cz: "řekni" }
}
}
The framework supports global command handlers that can execute actions when specific commands are entered. This includes contextual error messages and help systems:
commands: [
// Hint command with conditional messages
{
command: "hint",
buttonType: "simple",
message: [
{
condition: "room.entrance && !visited.dungeon",
value: "Try examining the door and surrounding areas.",
},
{
condition: "inventory.lockpick",
value: "You might find a use for that lockpick somewhere.",
},
{
value: "Try examining everything you come across.",
}
]
},
// Help command
{
command: "help",
buttonType: "simple",
message: "Welcome to this text adventure! Type commands to explore, solve puzzles, and complete quests. Common commands include: 'go north/south/east/west', 'examine [object]', 'take [item]', 'use [item]'. Use the menu toggle button in the upper right corner to access settings.",
},
// Contextual error messages
{ template: "go", message: "You can't go that way." },
{ template: "examine", message: "You don't see that here." },
{ template: "take", message: "You don't see that here." },
{ template: "drop", message: "You don't have that item." },
// Last item = generic fallback
{ message: "I don't understand that command." }
]
`
Command Processing:
items: {
sword: {
name: ["sword", "blade"],
description: "A sharp steel sword.",
actions: ["take", "drop"] // Enables take/drop commands for this item, will work for both 'take sword' and 'take blade'
}
}
items: {
treasure_chest: {
name: "treasure chest",
description: "A heavy chest filled with gold.",
actions: [
{
template: "take", // note that if you did not include the "take" action at all, the pleyer would not be able to pick the chest up, but they will receive the default error message
outcomes: [
{
condition: "roomItem.treasure_chest", // override the standard outcome
message: "The treasure chest is too heavy to carry!",
}
]
},
"drop" // Normal drop behavior (using drop template)
]
},
potion: {
name: { en: ["healing potion", "potion"], cz: ["léčivý lektvar", "lektvar"] },
description: { en: "A red potion that glows faintly.", cz: "Červený lektvar, který slabě září." },
actions: [
"take",
{
template: "drop",
outcomes: [
{
condition: "inventory.potion && room.garden",
message: { en: "You carefully place the potion on the soft grass.", cz: "Opatrně položíš lektvar na měkkou trávu." },
drops: "potion"
},
{
condition: "inventory.potion", // important to use condition to take precedence over template
message: { en: "You drop the potion. The vial shatters on impact!", cz: "Položíš lektvar. Lahvička se při dopadu rozbije!" },
removes: "potion"
}
]
}
]
}
}
With the above setup, players can use these commands:
Take Commands:
Drop Commands:
items: {
guarded_key: {
name: "guarded key",
description: "A key guarded by a sleeping dragon.",
actions: [
{
template: "take", // customized take behavior
outcomes: [
{
condition: "!dragon_awake",
message: "You quietly take the key while the dragon sleeps.",
takes: "guarded_key"
},
{
condition: "dragon_awake",
message: "The dragon wakes and blocks your attempt!",
adjusts: { player_health: -20 }
}
]
},
"drop" // Normal drop behavior
]
}
}
Speech and dialogue interactions are handled through regular actions with the prefix field, providing full localization support.
objects: {
guard: {
name: { en: "guard", cz: "strážce" },
roomDescription: {
en: "A stern-looking guard stands watch.",
cz: "Přísně vypadající strážce stojí na stráži."
},
actions: [
{
command: { en: "hello", cz: "ahoj" },
prefix: { en: "say", cz: "řekni" },
condition: "!guard_defeated",
message: {
en: "The guard nods politely.",
cz: "Strážce zdvořile přikývne."
},
sets: { guard_greeted: true }
}
]
}
}
objects: {
shopkeeper: {
name: { en: "shopkeeper", cz: "obchodník" },
actions: [
{
command: { en: ["hello", "hi"], cz: ["ahoj", "čau"] },
prefix: { en: "say", cz: "řekni" },
message: {
en: "\"Welcome to my shop!\" the shopkeeper says.",
cz: "\"Vítejte v mém obchodě!\" řekne obchodník."
}
},
{
command: { en: ["buy sword", "purchase sword"], cz: ["koupit meč", "zakoupit meč"] },
prefix: { en: "say", cz: "řekni" },
condition: "coin >= 10",
message: {
en: "\"Here's your sword!\" You hand over 10 coins.",
cz: "\"Tady je tvůj meč!\" Předáš 10 mincí."
},
adjusts: { coin: -10 },
creates: "sword"
},
{
command: { en: "goodbye", cz: "na shledanou" },
prefix: { en: "say", cz: "řekni" },
message: {
en: "\"Come back anytime!\" the shopkeeper waves.",
cz: "\"Přijďte kdykoliv!\" obchodník zamává."
}
}
]
}
}
objects: {
mysterious_figure: {
name: "mysterious figure",
actions: [
{
template: "say",
command: "who are you",
outcomes: [
{
condition: "!figure_revealed",
message: "\"That information will cost you.\" The figure smirks.",
},
{
condition: "figure_revealed",
message: "\"I am the keeper of secrets.\" The figure bows deeply.",
}
]
}
]
}
}
roomDescription: {
en: "The guard eyes you suspiciously. Try [[hello]] or [[who are you|ask who he is]].",
cz: "Strážce vás podezřívavě pozoruje. Zkus [[ahoj]] nebo [[kdo jsi|zeptat se kdo je]]."
}
Languages supported in an adventure should be declared.
translations: {
en: {
name: "English",
},
cz: {
name: "Čeština",
diacritics: true,
inventory: "Inventář",
submit: "Odeslat",
placeholder: "Zadejte příkaz..."
}
},
Localization is available via a structured object format for all text fields.
message: {
en: "English text",
cz: "Czech text"
}
instead of the normal
message: "English text"
All text fields can be localized:
Conditions determine when features are available, visible, or active.
condition: "door_unlocked"
condition: "!guard_defeated"
condition: "player_health > 50"
condition: "roomItems >= 2"
condition: "inventory.sword == 1"
condition: "location == 'dungeon'" // String literals in single quotes
String values must be enclosed in single quotes:
condition: "room.entrance && player_name == 'hero'"
condition: "current_quest != 'completed'"
&& (logical AND) and || (logical OR) can be used; && has higher priority than || during evaluation; () are not supported.
condition: "door_unlocked && player_health > 0"
condition: "guard_defeated || guard_befriended"
condition: "location == 'dungeon' && inventory.key"
// Door is unlocked
condition: "!door1_locked"
// Player has sword in inventory
condition: "inventory.sword"
// In library and fire is lit
condition: "room.library && fire1_lit"
// Health is critical
condition: "player_health <= 10"
Game state tracks variables that change during gameplay.
gameState: {
player_health: 100,
coin: 30,
door1_locked: true,
guard1_defeated: false,
treasure_found: false
}
sets: {
door1_locked: false, // Fixed value
damage: [20, 30], // Random 20-30
treasure_found: true
}
adjusts: {
player_health: -20, // Fixed subtraction
enemy_health: "damage", // Add gameState.damage value
enemy_health: "-damage", // Subtract gameState.damage value
gold: 50 // Fixed addition
}
The framework supports random number generation for dynamic gameplay, allowing adventures to include variable combat, treasure, and other mechanics.
Use arrays [min, max] in the sets field to generate random numbers:
actions: [
{
command: "use {object}",
sets: {
club_damage: [20, 30], // Generate random 20-30
gold_found: [5, 15] // Generate random 5-15
},
adjusts: {
enemy_health: "club_damage", // Apply the random damage
player_gold: "gold_found" // Add the random gold
},
message: "You deal {club_damage} damage and find {gold_found} gold!"
}
]
Random values stored in gameState can be displayed in messages using {variableName} placeholders:
message: "You hit the enemy for {club_damage} damage!"
Use string values in adjusts to reference previously generated random values. Prefix with - to subtract instead of to add:
adjusts: {
enemy_health: "-club_damage", // Subtract stored random damage
player_gold: "gold_found" // Add stored random gold
}
actions: [
{
command: "attack",
triggers: [ // generate numbers first, then we can use them in outcome conditions
{
sets: {
base_damage: [15, 25], // Base attack damage
hit_roll: [1, 10] // Hit chance
}
}
]
outcomes: [
{
condition: "hit_roll >= 8",
adjusts: { enemy_health: "-base_damage" },
message: "You deal {base_damage} damage."
},
{
message: "You miss."
}
]
}
]
sets: { sword_damage: [8, 12] },
adjusts: { enemy_health: "sword_damage" },
message: "Your sword strikes for {sword_damage} damage!"
sets: { coins_found: [1, 10], gems_found: [0, 2] },
adjusts: { player_coins: "coins_found", player_gems: "gems_found" },
message: "You find {coins_found} coins and {gems_found} gems!"
sets: { event_chance: [1, 100] },
message: [
{ condition: "event_chance <= 25", value: "You find a hidden passage!" },
{ condition: "event_chance <= 50", value: "You hear strange noises." },
"Nothing unusual happens."
]
Note: you would have to use triggers+outcomes to actually "create" the hidden passage
and not just print a message that you found it.
Triggers allow multiple independent conditions to execute simultaneously within a single action. Triggers execute before outcomes.
Triggers use the same structure as outcomes but execute all matching conditions:
actions: [
{
command: "rest",
message: "You have finished resting.",
triggers: [
{
condition: "poisoned",
message: "The poison continues to weaken you.",
adjusts: { health: -3 }
},
{
condition: "bank_account > 100",
message: "Your savings earn interest.",
adjusts: { gold: 5 }
},
{
condition: "hungry",
message: "Resting eases your hunger.",
sets: { hungry: false }
}
]
}
]
Trigger Execution:
Combined Output:
The poison continues to weaken you.
Your savings earn interest.
Resting eases your hunger.
You have finished resting.
triggers: [
{
condition: "poisoned",
message: "Poison courses through your veins.",
adjusts: { health: -5 }
},
{
condition: "bleeding",
message: "Blood continues to seep from your wounds.",
adjusts: { health: -3 }
},
{
condition: "hungry",
message: "Your stomach growls with hunger.",
adjusts: { health: -1 }
}
]
triggers: [
{
condition: "room_temperature > 80",
message: "The heat saps your strength.",
adjusts: { health: -2 }
},
{
condition: "room_humidity > 90",
message: "The oppressive humidity makes breathing difficult.",
adjusts: { health: -1 }
},
{
condition: "hour > 20 || hour < 6",
message: "The darkness presses in around you.",
sets: { visibility: "poor" }
}
]
triggers: [
{
adjusts: { game_time: 1 }
},
{
condition: "game_time > 100",
message: "You feel the weight of time pressing upon you.",
adjusts: { morale: -1 }
}
]
Triggers support a repeat field for executing the same trigger multiple times, enabling complex command behaviors like buying multiple items.
triggers: [
{
condition: "inventory.food < 5 && credits >= price_food",
repeat: true, // Repeat until condition fails
message: "You buy 1 unit of food.",
adjusts: { credits: "-price_food", cargo_used: 1 },
creates: "food"
}
]
actions: [
{
command: "buy 5 food",
message: "Purchasing food...",
triggers: [
{
// setup
sets: { bought: 0, cost: 0 }
},
{
condition: "bought < 5 && credits >= price_food",
repeat: true,
adjusts: { credits: "-price_food", bought: 1, cost: "price_food" },
creates: "food",
takes: "food"
},
{
// report results
message: "You bought {bought} unit(s) of food for {cost} credits.",
},
{
// cleanup
sets: { bought: 0, cost: 0 }
}
]
}
]
// Repeat with step back (sleep for 8 hours, but wake up if ambush occurs)
triggers: [
{ sets: { counter: 0, ambush_chance: 0 } }
{ sets: { ambush_chance: [0, 100] }, adjusts { time: 1, hunger: 1 } }
{ condition: "ambush_chance < 90 && counter < 8", adjusts: { counter: 1 }, repeat: -1 }, // Go back 1 step
{ message: [ { condition: "counter >= 8, value: "You slept for 8 hours." }, "You were ambushed!" ] }
]
// Conditional repeat
triggers: [
{
condition: "health_potions_needed > 0 && credits >= 10",
repeat: 0, // Repeat current stepm sane as repeat: true
message: "You buy a health potion.",
adjusts: { credits: -10, health_potions_needed: -1 },
creates: "health_potion"
takes: "health_potion"
}
]
Define when the game ends (victory or defeat).
- condition: string - Condition that triggers the ending
- message: string|object - Ending message (supports localization)
ends: {
treasure: {
condition: "treasure_found && map_collected",
message: "You found the treasure!"
},
death: {
condition: "player_health <= 0",
message: "You have died!"
}
}
Many fields support conditional content using arrays with condition-value pairs.
description: [
{
condition: "treasure_found",
value: "The treasure chest is open!",
},
{
condition: "!treasure_found",
value: "An empty chest lies here.",
},
"A mysterious chest." // Fallback text
]
Text can include placeholders that are replaced with entity information:
Text can include interactive command links:
Note: there is no space between the [ [, but the same format is used by the web, so space was added to prevent web formatting.
roomDescription: "A {name} stands to the north."
// Results in: "A wooden door stands to the north." (no clickable text)
message: "You give the {name2} to the blacksmith."
// Results in: "You give the blade to the blacksmith."
// object has name field with ["sword", "blade"]
description: "A locked door. Try to [[unlock]] it!"
// Creates clickable "unlock" that finds the unlock action on the door (executes for example "unlock wooden door" if the action belongs to a "wooden door" object)
description: "You [[examine painting|look closely]] at the painting."
// Shows "look closely" but executes "examine painting"
description: "You see [[|a mysterious door]] leading north."
// Shows "a mysterious door" as clickable text with auto-generated command (like [[]])
description: "There is [[]] on the table."
// Automatically creates clickable spans for the entity's first action using object name
Rooms can display images based on conditions.
image: "entrance.png"
image: [
{
condition: "guard_defeated",
value: "guard_dead.png"
},
{
condition: "!guard_defeated",
value: "guard_alive.png"
}
]