## Rooms **The filename is the ID.** A room's numeric filename is its ID (`1.yaml` → room `1`); the loader derives it from the filename. Do not put an `id:` field in the file — it is ignored. Exits and other rooms reference a room by that number. (A nested `- id:` under `objects:` or `mobs:` is a *reference* to an object/mob by its filename, and is still required.) Minimal room: ```yaml name: "Town Square" description: "Cobblestone paths lead in all directions. A fountain gurgles peacefully." exits: north: 2 west: 7 east: 3 ``` ### Map color — default symbol color on the map A room may set a default `color` that tints its node on the map and the links connecting it to neighbouring nodes. A player's per-room `symbol` color (set via the `symbol` command) overrides it. ```yaml color: "5E" ``` ### Inline Color Tags Room descriptions support inline color tags using `{spec}text{/}` syntax. Untagged text uses the `room_desc` color. ```yaml description: "On the table lies a {B6 bold}mysterious vase{/} with a rose in it." ``` Tag spec format: `{<00-FF> [bold] [dim] [underline]}text{/}` Gradients: `{g:C4,52}gradient text{/}`. Multi-stop: `{g:2D,27,3B}three stops{/}`. ### Exits — simple vs conditional Simple exit — always passable: ```yaml exits: north: 2 ne: 3 # ne/nw/se/sw also work southeast: 4 ``` Conditional exit — blocked until a world flag is set: ```yaml exits: north: room: 11 condition: flag: gate_open 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 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 - player_flag: paid_toll blocked_message: "The bridge is out, and the toll collector blocks the path." ``` Exit that sets flags when used — `set_flags` / `set_player_flags` are applied only when the player actually moves through the exit (not when it's blocked): ```yaml exits: north: room: 21 condition: player_flag: lined_up set_player_flags: boarded_shuttle: true # marks "left this area" on the way out ``` ### Map grid & one-way exits All exits (north/south/east/west/ne/nw/se/sw) must form a consistent 3D grid: from any starting room, walking horizontal directions should never land two different rooms on the same 3D coordinate, and a given room must always resolve to the same spot. Up/down move on the z-axis within this same grid (different floors share the same x/y coordinate space). Startup validation enforces this, starting from `startup_validation.root_rooms` (see config.yaml). It reports an **overlap** (two rooms on one grid cell) or a **twist** (one room on two cells) as an ERROR so you can fix the wiring. Exits don't have to be reciprocal. A one-way link (e.g. room 2001 has `east: 2006` but 2006 has no `west` back) renders on the map as a directional arrow (`← ↑ → ↓`, or `< ^ > v` in ASCII, with `↗↖↘↙` for diagonal) instead of a two-way bar. If a link is open in one direction but blocked (failing condition) in the other, the open direction's arrow is shown; only a link with no traversable direction shows the blocked `X`. Blocked up/down exits also show the blocked `X`. ### Item Spawns — ground items that respawn ```yaml item_spawns: - id: bronze_pickaxe quantity: 1 respawn_ticks: 30 # reappears 30 ticks (18 seconds) after being picked up - 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 and timed sequences when a player arrives Each `on_enter` step shows a `message`, optionally gated by a `condition`. Steps whose condition fails are skipped. ```yaml on_enter: - message: "The guard barks: \"State your business!\"" condition: player_flag: talked_to_guard not: true # only the first visit - message: "The guard nods. \"Back again?\"" condition: player_flag: talked_to_guard # subsequent visits ``` **Timed sequences.** A step may also carry a `delay` (ticks to wait before it fires) and/or set flags (`set_flags` / `set_player_flags`). If any surviving step has a delay or sets a flag, the whole sequence runs as a scheduled enter sequence; plain message-only scripts still print instantly. ```yaml on_enter: - condition: { player_flag: boarded, not: true } delay: 5 message: "Some of the crowd look you up and down." - condition: { player_flag: boarded, not: true } delay: 5 message: "The pilot calls out: \"Tickets, please! Nice and orderly!\"" set_player_flags: lined_up: true # opens an exit, flips a description, etc. - condition: { player_flag: boarded, not: true } message: "The crowd forms a single-file line." ``` **Step actions.** On-enter steps support all the same actions as trigger steps: `broadcast`, `broadcast_global`, `spawn_mob`, `despawn_mob`, `give_item`, `take_item`, `teleport`, and `heal`. See the [trigger step actions](triggers.md#step-actions) table for details. ```yaml on_enter: - condition: { player_flag: boss_summoned } delay: 10 broadcast: "The ground trembles..." - condition: { player_flag: boss_summoned } delay: 20 broadcast: "A massive guardian emerges from the shadows!" spawn_mob: id: altar_guardian owner_only: true despawn_on_leave: true ``` Notes: - `delay` counts ticks before the step fires; `delay: 0` (or omitted) fires on the next tick. - Conditions are evaluated **once** on entry, so a flag a step sets won't cancel a later step in the same sequence. - Gate a sequence on a flag the sequence itself sets (above, `lined_up`) so it doesn't replay on a return visit. - If a player disconnects mid-sequence it resumes on reconnect, so they can't get stuck behind an exit the sequence was meant to open. Plain `on_enter` messages never replay on login (except the first room for new characters). - Setting player flags from on_enter steps fires room triggers watching those flags — this is how room 1001's touchdown sequence works. ### Conditional room descriptions A room's `description` can be a plain string or a list of conditional variants. Entries are checked top-to-bottom; the first whose condition passes wins. An entry with no condition always matches — put it last as the fallback. ```yaml description: - condition: player_flag: boarded not: true text: "A concrete pad swarming with a couple dozen anxious passengers." - text: "A large, empty concrete pad in the middle of the ocean." ``` ### Hazardous rooms — environmental danger A room can reference a shared hazard by ID. The hazard rolls an attack against everyone in the room every few ticks using the combat formulas (see `hazards.md`). ```yaml name: "Exposed Solar Array" hazard: solar_radiation # ID of a data/hazards/.yaml definition exits: south: 99999180 objects: - id: rock_outcrop # a safespot object shields players from the hazard mobs: - solar_panel_frame # a task worksite (see mobs.md > Task Mobs) ``` - Players are warned with a `[Y/n]` prompt before walking from a **safe** room into a **hazardous** one (unless they disable the `danger_warning` option). Moving between two hazardous rooms does not re-prompt. - A safespot object (an overhang, alcove, rock outcrop, …) shields a hidden player from **all** hazard damage — but performing a melee attack/work step forces you out of cover. - Hazards stack with mobs: an aggressive mob in a hazardous room hits you while the room hazard also rolls against you. ### Room triggers — scripted events when flags change A room can carry a `triggers:` block. Each trigger watches a player or world flag and fires a sequence of timed steps when that flag's value changes. See `triggers.md` for the full reference. ```yaml triggers: - on_player_flag: 1001_look_sign steps: - delay: 5 message: "The cabin shakes as the small craft touches down" - delay: 5 message: "The pistons hiss as the rear staircase opens" - set_player_flags: 1001_touchdown: true ``` Room triggers are scoped to the room — broadcasts go to that room, mobs spawn there, and the trigger only fires when the flag-setting player is in that room. For server-wide events, use global triggers in `data/triggers/` instead. ---