diff options
| author | historia <[not public]> | 2026-07-09 16:09:35 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-09 16:09:35 -0400 |
| commit | b8c90886ef1f3afb8d908aac89028bd177836cae (patch) | |
| tree | 7e566a7e308e7531bbb7d6a3a39239e789f69a3c /building_guide/rooms.md | |
| parent | c705ae942573984784ef501bf8198f61f5206ddd (diff) | |
| download | thehouseoficarus-b8c90886ef1f3afb8d908aac89028bd177836cae.tar.gz | |
feat(admin): multi-select with shift+drag for common bulk operations
Diffstat (limited to 'building_guide/rooms.md')
| -rw-r--r-- | building_guide/rooms.md | 462 |
1 files changed, 357 insertions, 105 deletions
diff --git a/building_guide/rooms.md b/building_guide/rooms.md index f77aa42..3834a56 100644 --- a/building_guide/rooms.md +++ b/building_guide/rooms.md @@ -15,48 +15,65 @@ exits: east: 3 ``` -### Map color — default symbol color on the map +### Map color -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. +A room may set a `color` that tints its node on the map and the links connecting it: ```yaml color: "5E" ``` +A player's per-room `symbol` (set via the `symbol` command) overrides the room color. + ### Inline Color Tags -Room descriptions support inline color tags using `{spec}text{/}` syntax. Untagged text uses the `room_desc` color. +Room descriptions support inline color tags using `{spec}text{/}` syntax: ```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{/}` +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 +Untagged text uses the `room_desc` theme color. + +--- + +### Exits + +#### Simple exits + +Always passable, any direction: -Simple exit — always passable: ```yaml exits: north: 2 - ne: 3 # ne/nw/se/sw also work + ne: 3 southeast: 4 ``` -Conditional exit — blocked until a global flag is set: +#### Conditional exits + +Blocked until a condition passes: + ```yaml exits: north: room: 11 condition: - flag: gate_open + global_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): +The `condition:` field uses the same condition vocabulary as everywhere else — see the +[Conditions Reference](#conditions-reference) below. + +#### Player-flag exits + +Blocked unless the PLAYER has a flag: + ```yaml exits: east: @@ -66,39 +83,57 @@ exits: blocked_message: "The vault door is locked. You need a key." ``` -Conditional exit with compound condition — requires both a global flag AND a player flag: +#### Inventory-check exit + +Blocked unless the player has an item: + +```yaml +exits: + east: + room: 6 + condition: + has_item: rusty_key + blocked_message: "The iron door is locked. You need a key." +``` + +#### Compound conditions + ```yaml exits: north: room: 20 condition: all_of: - - flag: bridge_repaired + - global_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_global_flags` / `set_player_flags` are applied -only when the player actually moves through the exit (not when it's blocked): +#### Exit on_traverse — actions when walked through + +An exit can carry an `on_traverse` list. Interactions fire when the player moves through the +exit (not when it's blocked). The first whose condition passes wins. Supports the full +`Interaction` shape — condition, message, and action: + ```yaml exits: north: room: 21 condition: player_flag: lined_up - set_player_flags: - boarded_shuttle: true # marks "left this area" on the way out + blocked_message: "You're not in line yet." + on_traverse: + - set_player_flags: + boarded_shuttle: true ``` -### Hidden exits +#### Hidden exits -Hidden exits are invisible in `look` and `verbs` output until the player -discovers them by traversing them once. Even hidden, the exit is still -traversable — just not listed. Once a player moves through a hidden exit, it -becomes "discovered" for that character and appears in listing with a -`(HIDDEN)` tag. On the map, hidden exits (and rooms only reachable via them) -do not appear until the character discovers them. Gods in `god` mode see all -hidden exits regardless of discovery. +Hidden exits are invisible in `look` and `verbs` output until the player discovers them by +traversing them once. Even hidden, the exit is traversable — just not listed. Once a player +moves through a hidden exit, it becomes "discovered" for that character and appears with a +`(HIDDEN)` tag. On the map, hidden exits (and rooms only reachable via them) do not appear +until the character discovers them. Gods in `god` mode see all hidden exits. ```yaml exits: @@ -107,14 +142,12 @@ exits: hidden: true ``` -### Always-blocked exits +#### Always-blocked exits -Always-blocked exits are blocked to players but always visible. They appear in -`look`/`verbs`/`exits` tagged `(blocked)` (same as a conditionally-blocked exit), -refuse movement with the standard "The way <dir> is blocked." message, and -render on the map as blocked `X` connectors. Gods in `god` mode can traverse -them (and see no `(blocked)` tag). Primarily used for agility course -room-to-room links so the map can lay the course out visually. +Always-blocked exits are blocked to players but always visible. They show `(blocked)` in +`look`/`verbs`/`exits`, refuse movement, and render on the map as blocked `X` connectors. +Gods in `god` mode can traverse them. Used for agility course room-to-room links so the map +can lay the course out visually. ```yaml exits: @@ -123,47 +156,43 @@ exits: always_blocked: true ``` -`always_blocked` is absolute: when set, a `condition:` on the same exit is not -evaluated (the exit is blocked unconditionally). Don't combine the two — if you -need a conditionally-gated exit, use `condition:` alone. +`always_blocked` is absolute — a `condition:` on the same exit is not evaluated. Don't +combine them. Hidden and always_blocked can be combined: the exit is invisible until +discovered, then shows as blocked. -Hidden and always_blocked can be combined: the exit is invisible until -discovered (or seen by a god), and then shows as blocked. +--- ### 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). +All horizontal exits (n/s/e/w/ne/nw/se/sw) must form a consistent 3D grid. Startup +validation enforces this from `startup_validation.root_rooms` in config.yaml — it reports +**overlap** (two rooms on one cell) or **twist** (one room on two cells) as errors. -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 renders on the map as a directional arrow +instead of a two-way bar. -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 -### Item Spawns — ground items that respawn +Ground items that respawn after being picked up: ```yaml item_spawns: - id: bronze_pickaxe quantity: 1 - respawn_ticks: 30 # reappears 30 ticks (18 seconds) after being picked up + respawn_ticks: 30 # reappears 30 ticks (18s) after pickup - id: copper_ore quantity: 3 respawn_ticks: 50 ``` +--- + ### Mobs — NPCs placed in the room -Simple string (no wandering): +Simple placement (no wandering): + ```yaml mobs: - "newbie_trainer" @@ -171,6 +200,7 @@ mobs: ``` With wander config per-instance: + ```yaml mobs: - id: man @@ -180,27 +210,61 @@ mobs: 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. +Wander config lives in the room YAML, not the mob definition. The same `man` can wander +differently depending on where it's placed. Mobs wander through un-conditioned room exits. +If no legal exits exist, the mob stays still. Mobs with no `wander_interval` never wander. +Mobs stop wandering while in combat. Dead mobs respawn at their home room. + +--- ### Objects — interactive fixtures +Simple placement: + +```yaml +objects: + - id: copper_rock + - id: copper_rock # second instance +``` + +With wandering config: + ```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) + wander_rooms: [7, 8, 9] # teleports between these rooms + wander_interval: 12 # every 12 ticks +``` + +Wandering objects teleport between rooms in their `wander_rooms` list. Players gathering +from a wandering object are silently interrupted when it moves. + +Local objects (defined directly in the room, no file needed): + +```yaml +objects: + - id: workbench # reference: resolves to data/objects/workbench.yaml + - name: window # local: no file, identity comes from name + hidden: true + description: "A thick trim with rounded bolt heads frames the tiny window." + - name: sign + aliases: [safety card, frame] + inroom_description: "A large framed sign is mounted near the cockpit door." + description: "Safety instructions are printed in bold lettering." + on_look: + - set_player_flags: + 1001_look_sign: true ``` -### On-enter scripts — messages and timed sequences when a player arrives +See `objects.md` for the full object reference — local objects, file objects, behaviors, +interactions, safespots, stealing, and hidden objects. + +--- + +### On-enter scripts -Each `on_enter` step shows a `message`, optionally gated by a `condition`. Steps -whose condition fails are skipped. +Each `on_enter` step shows a `message`, optionally gated by a `condition`. Steps whose +condition fails are skipped. ```yaml on_enter: @@ -214,10 +278,11 @@ on_enter: 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_global_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. +#### Timed sequences + +A step may carry a `delay` (ticks to wait before firing) and/or set flags. If any step has a +delay or sets a flag, the whole sequence runs as a scheduled enter sequence; plain +message-only scripts print instantly. ```yaml on_enter: @@ -226,17 +291,19 @@ on_enter: 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!\"" + message: "The pilot calls out: \"Tickets, please!\"" set_player_flags: - lined_up: true # opens an exit, flips a description, etc. + 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. +#### Step actions + +On-enter steps support all the same actions as triggers: `broadcast`, `broadcast_global`, +`spawn_mob`, `despawn_mob`, `give_item`, `take_item`, `teleport`, `heal`, `credits`, +`aps_node`, `set_global_flags`, and `set_player_flags`. See the +[trigger step actions](triggers.md#step-actions) table. ```yaml on_enter: @@ -252,57 +319,76 @@ on_enter: despawn_on_leave: true ``` +#### Step-level conditions + +Each step can have a `condition:` that gates it individually. Unlike the step's action +fields, the condition is evaluated **once** on entry — a flag set by a later step won't +cancel an earlier step. + +```yaml +on_enter: + - condition: + player_flag: 1001_welcome + not: true + delay: 5 + message: "Welcome to The House of Icarus" +``` + 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. +- `delay: 0` (or omitted) fires on the next tick. +- Gate a sequence on a flag the sequence itself sets so it doesn't replay on return visits. +- If a player disconnects mid-sequence it resumes on reconnect. +- Setting player flags from on_enter steps fires room triggers watching those flags. + +--- ### 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. +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 fallback. ```yaml description: - condition: player_flag: boarded not: true - text: "A concrete pad swarming with a couple dozen anxious passengers." + text: "A concrete pad swarming with anxious passengers." - text: "A large, empty concrete pad in the middle of the ocean." ``` -### Hazardous rooms — environmental danger +--- + +### Hazardous rooms -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`). +A room can reference a shared hazard by ID. The hazard rolls an attack against everyone in +the room every few ticks using combat formulas. ```yaml name: "Exposed Solar Array" -hazard: solar_radiation # ID of a data/hazards/<id>.yaml definition +hazard: solar_radiation # ID of data/hazards/solar_radiation.yaml exits: south: 99999180 objects: - - id: rock_outcrop # a safespot object shields players from the hazard + - id: rock_outcrop # safespot object shields players from the hazard mobs: - - solar_panel_frame # a task worksite (see mobs.md > Task Mobs) + - solar_panel_frame # task worksite (see mobs.md) ``` -- 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. +- Players get a `[Y/n]` confirmation when walking from a safe room into a hazardous one + (unless `danger_warning` option is disabled). Moving between two hazardous rooms does not + re-prompt. +- Safespot objects shield hidden players from ALL hazard damage. +- Hazards stack with mobs: an aggressive mob in a hazardous room hits you while the hazard + also rolls. See `hazards.md` for the full hazard definition reference. -### Room triggers — scripted events when flags change +--- + +### Room triggers -A room can carry a `triggers:` block. Each trigger watches a player or global flag -and fires a sequence of timed steps when that flag's value changes. See `triggers.md` -for the full reference. +A room can carry a `triggers:` block. Each trigger watches a player or global flag and fires +a sequence of timed steps when the flag's value changes. See `triggers.md` for the full +reference. ```yaml triggers: @@ -316,9 +402,175 @@ triggers: 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. +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. + +--- + +### Conditions Reference + +Conditions are used in exit gates, on-enter steps, room descriptions, on_use/on_look/on_kill +interactions, talk option guards, talk node conditions, and trigger value matching. + +#### Simple conditions + +A bare `global_flag:` / `player_flag:` check passes when the flag is **set to a truthy +value** (`true`, a non-zero number, a non-empty string). Add `value:` to match a specific +value. `not: true` inverts any check. + +```yaml +# Global flag is set +condition: + global_flag: gate_open + +# Global flag is NOT set +condition: + global_flag: gate_open + not: true + +# Player flag is set +condition: + player_flag: finished_tutorial + +# Player flag is NOT set (e.g. only on first visit) +condition: + player_flag: finished_tutorial + not: true + +# Match a specific (non-boolean) value +condition: + player_flag: quest_stage + value: 3 + +# Player has an item +condition: + has_item: bronze_key + +# Player does NOT have an item +condition: + has_item: bronze_key + not: true + +# Player has enough credits +condition: + min_credits: 50 +``` + +#### Compound conditions + +All must pass: + +```yaml +condition: + all_of: + - global_flag: gate_open + - 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 +``` + +Nested compounds: + +```yaml +condition: + all_of: + - player_flag: quest_started + - any_of: + - has_item: wolf_pelt + - has_item: bear_pelt +``` --- +### Door Pattern Examples + +#### Global door: Button in another room (shared state) + +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 +name: stone button +hidden: true +on_use: + - condition: + global_flag: secret_door_open + not: true + message: "You press the stone button. You hear grinding stone in the distance." + action: + set_global_flags: + secret_door_open: true +``` + +**Room 7** — contains the door: + +```yaml +name: "Hidden Passage" +exits: + south: 2 + north: + room: 8 + condition: + global_flag: secret_door_open + blocked_message: "A heavy stone door blocks the way." +objects: + - id: stone_door + hidden: true +``` + +#### Player door: Key-locked (per-player state) + +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 +name: rusty key +description: "An old iron key, still functional." +value: 0 +stackable: false +``` + +**Room 5** — locked door: + +```yaml +name: "Locked Storage" +exits: + west: 2 + east: + room: 6 + condition: + has_item: rusty_key + 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 +name: "Storage Closet" +exits: + west: 5 # exit back — no condition +item_spawns: + - id: uncut_ruby + quantity: 1 + respawn_ticks: 500 +``` + +The key difference: the button door uses `global_flag` (shared — one player presses, +everyone benefits); the key door uses `has_item` (per-player inventory — each player needs +their own key). |
