# 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 Simple string (no wandering): ```yaml mobs: - "newbie_trainer" - "man" ``` With wander config per-instance: ```yaml mobs: - id: man wander_interval: 10 # attempts to wander every 10 ticks - id: man wander_interval: 15 wander_rooms: [1, 4, 5] # optional — only exit to these rooms ``` Mob wander config lives in the room YAML, not in the mob definition. This keeps mobs generic so the same `man` can wander differently depending on where it's placed. Mobs wander through legal (unconditioned) room exits. If no legal exits exist, the mob stays still. Mobs with no `wander_interval` never wander. ### Objects — interactive fixtures ```yaml objects: - id: copper_rock # simple placement - id: copper_rock # second instance - id: fishing_spot wander_rooms: [7, 8, 9] # teleports 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 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 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" ``` Note: `wander_rooms` and `wander_interval` are NOT set on the mob definition. Wander config is per-instance in the room YAML (see Rooms > Mobs section above). --- ## Objects Object defined — links to a behavior: ```yaml id: copper_rock name: copper rock behavior: mine_copper ``` Tree object: ```yaml id: oak_tree name: oak tree behavior: chop_oak ``` 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) Mining — per-drop depletion: ```yaml id: mine_copper type: gather skill: mining level: 1 xp: 17 # XP awarded per successful gather 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 xp: 10 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!" ``` Woodcutting with shared depletion and bird's nests: ```yaml id: chop_oak type: gather skill: woodcutting level: 15 xp: 37 base_wait: 6 tool: axe success: base: 0.40 per_level: 0.01 cap: 0.90 gather_message: "You swing your axe at the oak tree..." fail_message: "You swing but get no logs." drops: - item_id: oak_logs weight: 100 depletes: false # depletion is timer-based (shared_deplete) message: "You get some oak logs." deplete_delay: 14 # ticks until tree respawns after being cut down shared_deplete: 45 # max ticks before next gather depletes (counts down while chopping) nest_chance: 256 # 1/256 chance for a bird's nest on each successful gather respawn_message: "A new oak sapling grows in its place." respawn_broadcast: "An {name} grows back." ``` Regular tree — always depletes on first gather, no shared timer, no nests: ```yaml id: chop_tree type: gather skill: woodcutting level: 1 xp: 25 base_wait: 4 tool: axe success: base: 0.50 per_level: 0.01 cap: 0.95 gather_message: "You swing your axe at the tree..." fail_message: "You swing but get no logs." drops: - item_id: logs weight: 100 depletes: true # regular tree depletes on first successful gather message: "You get some logs." deplete_delay: 80 respawn_message: "A new tree grows in its place." respawn_broadcast: "A {name} grows back." ``` #### Shared depletion explained When `shared_deplete > 0`, the tree has a shared despawn timer: - The timer starts at `shared_deplete` max when the first player begins chopping. - Each tick, if anyone is chopping, the timer counts down. - When the timer reaches 0, the NEXT successful gather depletes the tree. - If no one is chopping and the tree isn't depleted, the timer ticks back UP. - All players chopping the same tree are interrupted when it depletes. Use `shared_deplete` for trees. Use `depletes: true` on individual drops for rocks. #### Bird's nests When `nest_chance > 0`, each successful gather has a 1/N independent chance to also drop a bird's nest. The nest goes to inventory (or to the ground if inventory is full). Use the `search` command to open nests — they roll on the `birds_nest_drop` table. #### XP drops When `xp > 0`, the gather awards XP on each successful drop. If the player's `xpdrops` toggle is on, the output includes the XP gain: `(+37xp wct)`. ### 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 xp: 15 # XP awarded per successful craft ``` --- ## 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 ``` Bird's nest drop table: ```yaml # data/drops/birds_nest_drop.yaml id: birds_nest_drop drops: - item_id: credits weight: 50 quantity: 200 - item_id: credits weight: 30 quantity: 500 - item_id: credits weight: 15 quantity: 1000 - item_id: credits weight: 4 quantity: 3000 - item_id: credits weight: 1 quantity: 10000 ``` 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 a room: objects: - id: fishing_spot wander_rooms: [7, 8, 9] wander_interval: 12 ``` Objects teleport between rooms in their `wander_rooms` list. Players gathering from a wandering object are silently interrupted when it moves. --- ## Wandering Mobs Mob wandering is configured per-instance in the room YAML, not on the mob definition: ```yaml # In a room: mobs: - id: man wander_interval: 10 # attempt to wander every 10 ticks - id: man wander_interval: 15 wander_rooms: [5, 6, 7] # optional — restrict which rooms via exits ``` Mobs wander through legal (unconditioned) room exits. When the wander interval expires, the mob picks a random exit with no conditions and moves through it. If `wander_rooms` is set, only exits leading to those room IDs are legal. Mobs without `wander_interval` never wander. Mobs stop wandering while in combat. Dead mobs respawn at their home room. --- ## Player Toggles Players can toggle personal settings with the `toggle` command: | Toggle | Effect | |---|---| | `description` | Show full room description when moving | | `tinymap` | Mini-map display (not yet implemented) | | `xpdrops` | Show XP gained in gather/craft/combat messages | | `exits` | Show exit destinations inline in look output | | `mobenter` | Notify when a mob enters the room | | `mobleave` | Notify when a mob leaves the room | | `mobspawn` | Notify when a mob spawns in the area | | `reserve` | Show full reserved item details in look | | `depletion` | Show depletion and despawn timers on objects | --- ## The `search` Command Used to open searchable items in your inventory (bird's nests, etc.): ``` Usage: search Example: search nest search birds nest ``` Searches your inventory for the named item. If found, removes it and rolls on a drop table. Currently only bird's nests are searchable. --- ## 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. Mob room entries accept both `"man"` and `{id: man, ...}`. 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. 9. **Shared depletion vs per-drop depletion.** Use `shared_deplete` for trees — the timer counts down while being chopped and regens when left alone. Use `depletes: true` on individual drops for rocks — they deplete on first successful gather. 10. **Mob wander config goes in the room YAML**, not the mob definition. This lets the same `man` wander differently in different rooms.