# World Building Guide Third Collapse is data-driven. Everything — rooms, items, mobs, objects, behaviors — is defined in YAML files under `data/`. No code changes needed to build a world. ## Quick Reference | What you want | Where to put it | |---|---| | A room | `data/rooms/.yaml` | | An item (sword, ore, key) | `data/items/.yaml` | | A mob (NPC, monster) | `data/mobs/.yaml` | | An interactive object (rock, lever, door) | `data/objects/.yaml` | | A behavior (mining, dialog, toggle) | `data/behaviors/.yaml` | | A shared drop table | `data/drops/.yaml` | ## State: World vs Player **World flags** (`set_flags`, checked with `flag`) are shared by every player on the server. A door opened by one player is open for everyone. A lever pulled once changes the world for all. **Player flags** (`set_player_flags`, checked with `player_flag`) are per-character. Quest progress, "has read the sign," "paid the toll" — these are different for each player. Saved to the character YAML and persist across logins. --- ## Rooms Minimal room: ```yaml id: 1 name: "Town Square" description: "Cobblestone paths lead in all directions. A fountain gurgles peacefully." exits: north: 2 west: 7 east: 3 ``` ### Exits — simple vs conditional Simple exit — always passable: ```yaml exits: north: 2 ``` Conditional exit — blocked until a world flag is set: ```yaml exits: north: room: 11 condition: flag: gate_open value: true blocked_message: "A heavy iron gate blocks the way north." ``` Conditional exit — blocked unless the PLAYER has a flag (key, permission, quest state): ```yaml exits: east: room: 12 condition: player_flag: has_vault_key value: true blocked_message: "The vault door is locked. You need a key." ``` Conditional exit with compound condition — requires both a world flag AND a player flag: ```yaml exits: north: room: 20 condition: all_of: - flag: bridge_repaired value: true - player_flag: paid_toll value: true blocked_message: "The bridge is out, and the toll collector blocks the path." ``` ### Spawns — ground items that respawn ```yaml spawns: - item_id: bronze_pickaxe quantity: 1 respawn_ticks: 30 # reappears 30 ticks (18 seconds) after being picked up - item_id: copper_ore quantity: 3 respawn_ticks: 50 ``` ### Mobs — NPCs placed in the room ```yaml mobs: - "man" # spawns a "man" mob - "man" # second "man" — duplicate IDs create multiple instances - "guard" # unique named mob ``` ### Objects — interactive fixtures ```yaml objects: - id: copper_rock # simple placement - id: copper_rock # second instance - id: fishing_spot wander_rooms: [7, 8, 9] # moves between these rooms wander_interval: 12 # every 12 ticks - id: iron_gate # hidden object (see below) ``` ### On-enter scripts — messages when a player arrives ```yaml on_enter: - message: "The guard barks: \"State your business!\"" condition: player_flag: talked_to_guard not: true # only first visit - message: "The guard nods. \"Back again?\"" condition: player_flag: talked_to_guard value: true # subsequent visits ``` --- ## Items ```yaml id: bronze_pickaxe name: bronze pickaxe aliases: ["pick", "pickaxe"] description: "A sturdy bronze pickaxe." value: 10 stackable: false equip_slot: main_hand # optional — where it equips weapon_type: melee # optional — melee or ranged stats: # optional — combat bonuses attack_bonus: 2 strength_bonus: 1 speed: 5 # ticks between attacks toolbelt: true # usable from toolbelt (doesn't need to be in inventory) tool_type: pickaxe # used by gather behaviors that require "tool: pickaxe" tool_speed: 2 # reduces gather wait time ``` Key item (quest token, not equippable): ```yaml id: pass_stub name: pass stub description: "A crumpled slip of paper stamped with the guard's seal." value: 0 stackable: false ``` --- ## Mobs Basic combat mob: ```yaml id: "man" name: "man" description: "A shabby-looking man." attack: 1 strength: 1 defense: 1 hp: 7 speed: 5 aggressive: false respawn_ticks: 30 wander_rooms: [1, 2, 3] wander_interval: 10 drops: remains: "bones" # always dropped on death loot: - item_id: "credits" weight: 98 quantity: 10 - item_id: "credits" weight: 2 quantity: 150 idle_descriptions: - "scribbles something in a small notebook" - "gazes skyward at the clouds" combat_descriptions: - "is engaged in a fight to the death with %s" ``` Mob with a behavior — can be talked to, toggled, etc: ```yaml id: "guard" name: "Guard" behavior: guard_talk # links to data/behaviors/guard_talk.yaml unique: true # displays as "Guard" not "a guard" protected: true # cannot be attacked attack: 5 strength: 5 defense: 5 hp: 30 speed: 5 aggressive: false respawn_ticks: 60 idle_descriptions: - "scans the area with a watchful eye" - "adjusts the grip on his weapon" ``` --- ## Objects Object defined — links to a behavior: ```yaml id: copper_rock name: copper rock behavior: mine_copper ``` Hidden object — doesn't appear in room's object list, only discoverable via description or experimentation: ```yaml id: iron_gate name: iron gate behavior: iron_gate_toggle hidden: true props: description: "A heavy iron gate set into the north wall." ``` Decorative object (no behavior): ```yaml id: lumby_fountain name: town fountain behavior: "" props: description: "Clear water sparkles in the sunlight." ``` --- ## Behaviors ### Gather (mining, fishing, woodcutting) ```yaml id: mine_copper type: gather skill: mining level: 1 base_wait: 8 # ticks between attempts tool: pickaxe # requires item with tool_type: pickaxe success: base: 0.40 # 40% base chance per_level: 0.01 # +1% per level above requirement cap: 0.95 # 95% max gather_message: "You swing your pickaxe at the rock..." fail_message: "You chip away but get nothing useful." drops: - item_id: copper_ore weight: 90 # 90% chance when roll succeeds depletes: true # rock becomes depleted after this drop message: "You manage to mine some copper ore." - table: gem_table # reference a shared drop table weight: 10 depletes: false # gem drops don't deplete the rock message: "You spot a glint of something valuable!" deplete_delay: 50 # ticks until rock respawns respawn_message: "You see more ore in the rock." respawn_broadcast: "A glint of copper catches your eye from some {name}." ``` Non-depleting gather (fishing): ```yaml id: fish_trout type: gather skill: fishing level: 1 base_wait: 4 tool: fishing_rod success: base: 0.30 per_level: 0.01 cap: 0.90 gather_message: "You cast your line into the water..." fail_message: "Nothing seems to bite." drops: - item_id: raw_trout weight: 100 depletes: false # never depletes message: "You catch a trout!" ``` ### Talk (dialog trees) Full conversation with conditions, actions, and player flag tracking: ```yaml id: guard_talk type: talk nodes: start: message: "\"Halt! This area is restricted.\"" options: - text: "\"What's behind that gate?\"" goto: about_gate - text: "\"I have copper ore.\"" # only shows if player has ore goto: trade_ore condition: has_item: copper_ore - text: "\"I have a pass.\"" # only shows if player earned a pass goto: has_pass condition: player_flag: got_pass value: true - text: "\"Goodbye.\"" end: true about_gate: message: "\"Bring me some copper ore and I'll stamp you a pass.\"" action: set_player_flags: # player-local: only this player talked_to_guard: true options: - text: "\"I'll be back.\"" end: true - text: "\"I have some right here.\"" goto: trade_ore condition: has_item: copper_ore trade_ore: message: "\"Good quality ore.\" He stamps a pass and hands it to you." action: take_item: copper_ore # removes 1 copper ore give_item: pass_stub # gives pass stub set_player_flags: got_pass: true # player now "has a pass" options: - text: "\"Thanks.\"" end: true has_pass: message: "\"Alright, I'll open the gate for you.\"" action: set_flags: # WORLD flag: gate opens for everyone gate_open: true options: - text: "\"Thanks.\"" end: true ``` #### Node action reference | Field | Effect | |---|---| | `set_flags` | Sets world flags (global, shared by all players) | | `set_player_flags` | Sets player-local flags (per-character, quest progress) | | `give_item` | Gives an item to the player's inventory | | `take_item` | Removes an item from the player's inventory | | `teleport` | Moves the player to a room ID | | `heal` | Restores that many hitpoints | All fields in a single action are processed together — you can give an item, take an item, set flags, and heal all in one node. Example — quest completion: ```yaml action: take_item: dragon_head give_item: dragon_slayer_medal set_player_flags: dragon_quest: complete dragon_slain: true heal: 99 teleport: 1 # return to town ``` ### Toggle (levers, switches, gates) Simple toggle that sets a world flag: ```yaml id: iron_gate_toggle type: toggle message: "You push the heavy iron gate open." set_flags: gate_open: true check: # only works when gate is closed flag: gate_open value: true not: true ``` Lever that toggles between two states: ```yaml id: bridge_lever type: toggle message: "You pull the lever. Mechanisms groan somewhere in the distance." set_flags: bridge_extended: true check: flag: bridge_extended value: true not: true ``` ### Use (crafting stations) ```yaml id: smelt_copper type: use message: "You place the ore in the furnace..." wait: 4 # ticks between crafts consume: # items consumed per craft copper_ore: 1 reward: # item produced item_id: copper_bar quantity: 1 fail_message: "The ore crumbles to dust." success: base: 0.60 per_level: 0.01 cap: 0.95 skill: smithing level: 1 ``` --- ## Conditions Reference Conditions are used in talk options, exit gates, on-enter scripts, and toggle checks. ### Simple conditions ```yaml # Check a world flag condition: flag: gate_open value: true # Check a world flag is NOT set condition: flag: gate_open not: true # Check a player flag condition: player_flag: finished_tutorial value: true # Check if player has an item condition: has_item: bronze_key # Check if player does NOT have an item condition: has_item: bronze_key not: true ``` ### Compound conditions All must pass: ```yaml condition: all_of: - flag: gate_open value: true - has_item: pass_stub ``` Any one must pass: ```yaml condition: any_of: - has_item: bronze_key - has_item: iron_key - player_flag: master_of_unlocking value: true ``` Nested compounds: ```yaml condition: all_of: - player_flag: quest_started value: true - any_of: - has_item: wolf_pelt - has_item: bear_pelt ``` --- ## Drop Tables Shared drop tables can be referenced by multiple behaviors: ```yaml # data/drops/gem_table.yaml id: gem_table drops: - item_id: uncut_sapphire weight: 47 - item_id: uncut_emerald weight: 16 - item_id: uncut_ruby weight: 4 - item_id: uncut_diamond weight: 1 ``` Referenced from a gather behavior: ```yaml drops: - item_id: copper_ore weight: 90 depletes: true - table: gem_table # pulls from data/drops/gem_table.yaml weight: 10 depletes: false ``` --- ## Global vs Player State: Door Examples ### Example A: Door with a button in another room (GLOBAL) A button in room 3 opens a door in room 7. Anyone can press it. Once pressed, the door is open for everyone. **Button object** (`data/objects/door_button.yaml`): ```yaml id: door_button name: stone button behavior: button_toggle hidden: true ``` **Button behavior** (`data/behaviors/button_toggle.yaml`): ```yaml id: button_toggle type: toggle message: "You press the stone button. You hear grinding stone in the distance." set_flags: secret_door_open: true # WORLD flag check: flag: secret_door_open not: true # only works when door is closed ``` **Room 3** — contains the button: ```yaml id: 3 name: "Button Chamber" description: "A small stone chamber. A button protrudes from the east wall." exits: south: 1 objects: - id: door_button ``` **Room 7** — contains the door: ```yaml id: 7 name: "Hidden Passage" description: "A dusty corridor. A heavy stone door blocks the way north." exits: south: 2 north: room: 8 condition: flag: secret_door_open # checks WORLD flag value: true blocked_message: "A heavy stone door blocks the way." objects: - id: stone_door hidden: true ``` ### Example B: Key-locked door (PLAYER-LOCAL) A locked door that only opens for a player carrying the key. Each player must find their own key. **Key item** (`data/items/rusty_key.yaml`): ```yaml id: rusty_key name: rusty key description: "An old iron key, still functional." value: 0 stackable: false ``` **Room 5** — locked door: ```yaml id: 5 name: "Locked Storage" description: "A small storage room. The way east is blocked by a locked iron door." exits: west: 2 east: room: 6 condition: has_item: rusty_key # checks PLAYER inventory blocked_message: "The iron door is locked. You need a key." objects: - id: iron_door hidden: true ``` **Room 6** — the other side (no key needed to exit): ```yaml id: 6 name: "Storage Closet" description: "Shelves of dusty crates." exits: west: 5 # exit back — no condition spawns: - item_id: uncut_ruby quantity: 1 respawn_ticks: 500 ``` Key difference: the button door uses `flag` (shared state — one player presses, everyone benefits), the key door uses `has_item` (per-player inventory check — each player needs their own key). --- ## Complete Quest Example: "Clear the Rats" ### 1. Quest giver mob (`data/mobs/quest_giver.yaml`) ```yaml id: "quest_giver" name: "Elder" behavior: rat_quest_talk unique: true protected: true attack: 1 strength: 1 defense: 1 hp: 20 speed: 5 aggressive: false idle_descriptions: - "mutters about the rat infestation" ``` ### 2. Dialog behavior (`data/behaviors/rat_quest_talk.yaml`) ```yaml id: rat_quest_talk type: talk nodes: start: message: "\"Rats! Rats everywhere in the cellar. Clear them out and I'll reward you.\"" options: - text: "\"I'll handle it.\"" goto: accept_quest condition: player_flag: rat_quest not: true - text: "\"I killed the rats.\"" goto: turn_in condition: player_flag: rat_quest value: started - text: "\"Goodbye.\"" end: true accept_quest: message: "\"Good lad. The cellar is west of here. Come back when they're dead.\"" action: set_player_flags: rat_quest: started options: - text: "\"On my way.\"" end: true turn_in: message: "\"You did it! The village owes you a debt. Here — take this.\"" action: give_item: rusty_sword set_player_flags: rat_quest: complete heal: 10 options: - text: "\"Thanks!\"" end: true ``` ### 3. Rat mobs (`data/mobs/rat.yaml`) ```yaml id: "rat" name: "giant rat" attack: 2 strength: 1 defense: 1 hp: 3 speed: 4 aggressive: true respawn_ticks: 60 drops: remains: "rat bones" ``` ### 4. Cellar room (`data/rooms/20.yaml`) ```yaml id: 20 name: "Cellar" description: "A damp, dark cellar. The floor scuttles with movement." exits: east: 1 mobs: - "rat" - "rat" - "rat" - "rat" - "rat" ``` ### 5. Room 1 with quest giver (`data/rooms/1.yaml`) ```yaml id: 1 name: "Town Square" description: "Cobblestone paths lead in all directions. The Elder stands near the fountain." exits: west: 20 north: 2 mobs: - "quest_giver" objects: - id: lumby_fountain ``` --- ## Wandering Objects Fishing spots that move between rooms: ```yaml # data/objects/fishing_spot.yaml id: fishing_spot name: fishing spot behavior: fish_trout ``` ```yaml # In rooms 7, 8, and 9: objects: - id: fishing_spot wander_rooms: [7, 8, 9] wander_interval: 12 ``` Players gathering from a wandering object are silently interrupted when it moves. --- ## Wandering Mobs ```yaml # mobs that roam between rooms: wander_rooms: [1, 2, 3, 4] wander_interval: 10 # moves every 10 ticks ``` Mobs stop wandering while in combat. Dead mobs respawn at their home room after `respawn_ticks`. --- ## Hidden Objects Objects with `hidden: true` don't appear in the room's object listing. Players discover them by reading room descriptions or trying commands. The object is still fully interactable — `push gate`, `look gate`, etc. ```yaml # data/objects/secret_lever.yaml id: secret_lever name: stone lever behavior: secret_toggle hidden: true props: description: "A cleverly concealed lever behind a loose stone." ``` Room description hints at it: ```yaml description: "A dusty corridor. One of the wall stones looks slightly out of place." ``` --- ## Tips 1. **Use player flags for quest progress, world flags for environmental state.** If a bridge is repaired, that's world state. If a player has read a sign, that's player state. 2. **Conditions on enter scripts** make rooms feel alive. A guard who only barks the first time, a room that changes after a quest completes. 3. **Hidden objects** keep room descriptions clean. Mention them in the room's description text instead of auto-listing them. 4. **Drop tables** are shareable. The `gem_table` is used by copper rocks AND mob loot. Define once, reference everywhere. 5. **Objects are for fixtures, mobs are for living things.** If it has HP and can die, it's a mob. If it's a rock, lever, door, or crafting station, it's an object. Both can have behaviors. 6. **The `behavior` field on mobs** lets you talk to them directly — `talk guard` finds the guard mob, no duplicate object entry needed. 7. **Exits accept both `int` and `map` formats.** `north: 2` is shorthand for `north: {room: 2}`. Add `condition` and `blocked_message` only when needed. 8. **Live editing works.** Room, item, mob, object, and behavior YAML files are read from disk on each access. Change a room description or dialog and it takes effect immediately — no restart needed.