aboutsummaryrefslogtreecommitdiff
path: root/building_guide/rooms.md
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-09 16:09:35 -0400
committerhistoria <[not public]>2026-07-09 16:09:35 -0400
commitb8c90886ef1f3afb8d908aac89028bd177836cae (patch)
tree7e566a7e308e7531bbb7d6a3a39239e789f69a3c /building_guide/rooms.md
parentc705ae942573984784ef501bf8198f61f5206ddd (diff)
downloadthehouseoficarus-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.md462
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).