aboutsummaryrefslogtreecommitdiff
path: root/building_guide
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-19 03:15:59 -0400
committerhistoria <[not public]>2026-09-19 03:15:59 -0400
commit0e60567a7a0addb9421769ee7be7853bfa8788cb (patch)
tree7645112db0e4eae95b8a83c1e4f9aab38bede655 /building_guide
parentcc33072eced3d5460512c09c42ec4531c807d7af (diff)
downloadthehouseoficarus-0e60567a7a0addb9421769ee7be7853bfa8788cb.tar.gz
refactor: simplifcation, change how rooms are stored in player yaml, remove legacy code
Diffstat (limited to 'building_guide')
-rw-r--r--building_guide/behaviors.md12
-rw-r--r--building_guide/rooms.md53
-rw-r--r--building_guide/triggers.md308
3 files changed, 191 insertions, 182 deletions
diff --git a/building_guide/behaviors.md b/building_guide/behaviors.md
index 3063677..26c12ec 100644
--- a/building_guide/behaviors.md
+++ b/building_guide/behaviors.md
@@ -479,10 +479,10 @@ talk:
### Interaction Reference
The unified **Trigger** shape is the shared wrapper used by every event block: `on_use`,
-`on_look`, `on_kill`, `on_traverse`, `on_enter`, `on_exit`, `on_flag_change`,
-`on_global_flag_change`, and global trigger files (`data/triggers/*.yaml`). When a block's
-list of Triggers is evaluated, the first whose filters pass fires and its `steps` run as a
-scripted sequence (first-match-wins).
+`on_look`, `on_kill`, `on_traverse`, `on_enter`, `on_exit`, and flag-trigger files under
+`data/triggers/player_flag/` or `data/triggers/global_flag/` (`on_player_flag` /
+`on_global_flag`). When a block's list of Triggers is evaluated, the first whose filters
+pass fires and its `steps` run as a scripted sequence (first-match-wins).
| Field | Scope | Description |
|-------|-------|-------------|
@@ -490,8 +490,8 @@ scripted sequence (first-match-wins).
| `item_id` | on_use/on_look/on_kill | `on_use`: required item (`use <item> on <obj>`); empty = bare `use <obj>`. `on_look`: only fires if carrying this item. `on_kill`: only fires if wielding this weapon. |
| `condition` | all | Optional Trigger-level gate (Condition struct). |
| `steps` | all | Ordered list of Step entries run as a scripted sequence. |
-| `on_player_flag` | on_flag_change only | Player flag this trigger watches. |
-| `on_global_flag` | on_global_flag_change only | Global flag this trigger watches. |
+| `on_player_flag` | flag-trigger files only | Player flag this trigger watches. |
+| `on_global_flag` | flag-trigger files only | Global flag this trigger watches. |
| `value` | flag triggers | Optional value-match filter. |
Talk node/option actions still use the NodeAction shape (`action:` with the effect fields
diff --git a/building_guide/rooms.md b/building_guide/rooms.md
index 719c09e..25c6f5f 100644
--- a/building_guide/rooms.md
+++ b/building_guide/rooms.md
@@ -355,14 +355,15 @@ validation reports invalid definitions, and game lookup refuses invalid room
casino configurations. Raw file editing is not a validated save endpoint.
- IDs must be nonempty, have no surrounding whitespace, and be unique across
- the room's tables and machines (including legacy slots table entries).
-- Supported table games are `blackjack` and `baccarat`; machines use `slots`
+ the room's tables and machines.
+- Supported table games are `blackjack` and `baccarat`; slots are machines only
+ and must be declared under `casino_machines`. A `slots` entry under
+ `casino_tables` is rejected. Machines use `slots`
with the optional variant `huff_and_even_more_puff`. A machine variant is
registered in Go (`slotVariants` in `internal/casino/registry.go`); naming an
unregistered variant is a validation error.
- A multiplayer table is addressed by its game name (`play blackjack`), so a
- room may host only one table per game; a legacy slots table still collides
- with a slots machine.
+ room may host only one table per game.
- A machine is addressed by its `variant` when one is declared, so a room may
host several variants of one game, each reached by its own variant name. A
bare game name addresses a machine only when it is the sole machine of that
@@ -607,31 +608,33 @@ mobs:
---
-### Room flag triggers
+### Flag triggers
-A room that needs to react when a flag changes uses the `on_flag_change:` / `on_global_flag_change:`
-blocks (these replace the old `triggers:` block). Each entry watches one flag, runs a `steps`
-sequence when that flag changes, and resolves broadcasts/spawns against the room. See
+A trigger that reacts to a flag change is a **file-backed trigger**, not a room field. Put it
+under `data/triggers/player_flag/` (watch a player flag with `on_player_flag:`) or
+`data/triggers/global_flag/` (watch a global flag with `on_global_flag:`). Each file holds one
+trigger, the filename stem is its id, and the trigger must set exactly one of those two keys —
+a file with neither is a startup ERROR because it is loaded but can never fire. See
`triggers.md` for the full reference.
```yaml
-on_flag_change:
- - on_player_flag: lever_pulled
- condition:
- room: 1001 # opt-in: only fires if the player is in this room
- steps:
- - wait: 5
- messages: ["The cabin shakes as the small craft touches down"]
- - wait: 5
- messages: ["The pistons hiss as the rear staircase opens"]
- - set_player_flags:
- 1001_touchdown: true
-```
-
-Flag triggers listen **globally** — they fire regardless of where the flag is set. The old
-room `triggers:` block was implicitly room-scoped; migration injects `condition: { room:
-<id> }` to preserve that. Delete the `room` condition to make the trigger fire wherever the
-player is. For server-wide events use global trigger files in `data/triggers/` instead.
+# data/triggers/player_flag/1001_touchdown.yaml
+on_player_flag: lever_pulled
+condition:
+ room: 1001 # opt-in: only fires if the player is in this room
+steps:
+ - wait: 5
+ messages: ["The cabin shakes as the small craft touches down"]
+ - wait: 5
+ messages: ["The pistons hiss as the rear staircase opens"]
+ - set_player_flags:
+ 1001_touchdown: true
+```
+
+Flag triggers listen **globally** — they fire regardless of where the flag is set. Add
+`condition: { room: <id> }` to scope one to the room it belongs to; delete that condition to
+let it fire wherever the player is. Room files support only `on_enter`, `on_exit`, and exit
+`on_traverse` trigger blocks; there is no room-level `on_flag_change:` field.
---
diff --git a/building_guide/triggers.md b/building_guide/triggers.md
index c32830d..ba44c40 100644
--- a/building_guide/triggers.md
+++ b/building_guide/triggers.md
@@ -181,8 +181,8 @@ entity:
| Object | `on_use`, `on_look` |
| Mob | `on_kill` |
| Exit (room YAML) | `on_traverse` |
-| Room | `on_enter`, `on_exit` (NEW), `on_flag_change`, `on_global_flag_change` |
-| Global file (`data/triggers/*.yaml`) | one Trigger per file (flag triggers) |
+| Room | `on_enter`, `on_exit` (NEW) |
+| Flag trigger file (`data/triggers/player_flag/`, `data/triggers/global_flag/`) | one Trigger per file (`on_player_flag` / `on_global_flag`) |
- **on_use / on_look** — see `objects.md`. `item_id` filters which item triggers the entry.
- **on_kill** — see `mobs.md`. `item_id` filters by wielded weapon.
@@ -193,8 +193,7 @@ entity:
- **on_exit** (NEW) — fires when a player leaves the room, **before** `RoomID` is updated
to the destination. Broadcasts/spawn resolve against the old (departed) room. Mirror of
on_enter, useful for parting messages, closing spawns, or recording departures.
-- **on_flag_change** / **on_global_flag_change** — flag-watch triggers, replacing the old
- room `triggers:` block. See Flag triggers below.
+- **on_flag_change** / **on_global_flag_change** — flag-watch triggers are **file-backed**, one per file under `data/triggers/player_flag/` or `data/triggers/global_flag/`. They are not room fields. See Flag triggers below.
### Quick Start: Landing Sequence
@@ -259,91 +258,99 @@ triggers watching that flag.
### Flag triggers
-`on_flag_change` and `on_global_flag_change` replace the old room `triggers:` block. Each
-entry carries the flag it watches (`on_player_flag` or `on_global_flag`), an optional
-`condition` (use `player_flag`/`global_flag` + `value` to match a specific flag value), and
-a `steps` list.
+A flag trigger is a **standalone trigger file**, one per flag, under
+`data/triggers/player_flag/` (set `on_player_flag:`) or `data/triggers/global_flag/` (set
+`on_global_flag:`). The filename stem is the trigger id. Each file carries the flag it
+watches, an optional `condition` (use `player_flag`/`global_flag` + `value` to match a
+specific flag value), and a `steps` list. The block sits at the top level of the file —
+there is no `on_flag_change:` wrapper and no room-file form.
Key behaviors:
- **Listen globally.** A flag-change trigger fires regardless of where the flag is set.
- The event block lives in a room (for broadcasts/spawns to use that room) or in a global
- file (see Global trigger files).
+ Add `condition: { room: <id> }` when a broadcast/spawn should resolve against a specific
+ room, or use a global trigger file for server-wide events (see Global trigger files).
- **Room scoping is opt-in.** Use `condition: { room: <id> }` to restrict the trigger to
- players currently in that room. Migration injects `{ room: <roomID> }` into migrated
- room triggers to preserve the old room-scoped behavior; authors can delete it to make the
- trigger fire globally.
+ players currently in that room.
- **Fires on the non-existent → existent transition.** Setting a flag that didn't exist
before counts as a change and fires the trigger. Setting a flag to the same value it
already has does **not** re-fire.
- **Triggers activate on becoming truthy.** Setting `true → false` does **not** fire.
```yaml
-# A room watches a player flag and plays a sequence for that player
-on_flag_change:
- - on_player_flag: lever_pulled
- steps:
- - broadcast: "The lever snaps back into place with a loud clunk."
- - set_player_flags:
- lever_pulled: false # reset so next pull re-fires
+# data/triggers/player_flag/lever_pulled.yaml
+on_player_flag: lever_pulled
+steps:
+ - broadcast: "The lever snaps back into place with a loud clunk."
+ - set_player_flags:
+ lever_pulled: false # reset so next pull re-fires
```
-To migrate the old room `triggers:` block, replace `triggers:` with `on_flag_change:` (for
-`on_player_flag`) or `on_global_flag_change:` (for `on_global_flag`) and add
-`condition: { room: <id> }` if you want the old room-scoped behavior.
+A trigger file that sets neither `on_player_flag` nor `on_global_flag` is a startup ERROR:
+it is loaded but indexed nowhere and can never fire.
### Value-matching flag triggers
By default a flag trigger fires when the watched flag becomes truthy. Use a `condition` with
a `player_flag`/`global_flag` + `value` to require a specific value — enabling multi-stage
-quests off one numeric flag:
+quests off one numeric flag. Because each file watches exactly one flag, a multi-stage flag
+uses one file per stage:
```yaml
-on_flag_change:
- - on_player_flag: quest_stage
- condition:
- player_flag: quest_stage
- value: 1
- steps:
- - messages: ["Quest started — find the crystal shard."]
- - on_player_flag: quest_stage
- condition:
- player_flag: quest_stage
- value: 2
- steps:
- - messages: ["You found the shard — return to the elder."]
- - on_player_flag: quest_stage
- condition:
- player_flag: quest_stage
- value: 3
- steps:
- - messages: ["The ritual begins..."]
- - wait: 10
- broadcast: "The temple hums with ancient power!"
- - spawn_mob: crystal_guardian
+# data/triggers/player_flag/quest_stage_1.yaml
+on_player_flag: quest_stage
+condition:
+ player_flag: quest_stage
+ value: 1
+steps:
+ - messages: ["Quest started — find the crystal shard."]
+```
+
+```yaml
+# data/triggers/player_flag/quest_stage_2.yaml
+on_player_flag: quest_stage
+condition:
+ player_flag: quest_stage
+ value: 2
+steps:
+ - messages: ["You found the shard — return to the elder."]
+```
+
+```yaml
+# data/triggers/global_flag/ritual.yaml
+on_global_flag: ritual_stage
+condition:
+ global_flag: ritual_stage
+ value: 3
+steps:
+ - messages: ["The ritual begins..."]
+ - wait: 10
+ broadcast: "The temple hums with ancient power!"
+ - spawn_mob: crystal_guardian
```
### Global flag trigger
-A room can watch a global flag and fire once globally (not per-player). Everyone in the
-room sees the broadcast:
+A global-flag trigger file fires once globally (not per-player), and its broadcast is seen
+by every connected player. (`broadcast` inside a player-flag trigger is seen by that player.)
```yaml
-on_global_flag_change:
- - on_global_flag: floodgate_open
- steps:
- - broadcast: "Ancient gears grind as the floodgate slowly opens..."
- - wait: 15
- broadcast: "Water thunders through the opening!"
- - set_global_flags:
- valley_flooded: true
+# data/triggers/global_flag/floodgate.yaml
+on_global_flag: floodgate_open
+steps:
+ - broadcast: "Ancient gears grind as the floodgate slowly opens..."
+ - wait: 15
+ broadcast: "Water thunders through the opening!"
+ - set_global_flags:
+ valley_flooded: true
```
### Global trigger files
-Global triggers live one-per-file in `data/triggers/`. They fire regardless of where the
-flag-setting player is. Use them for server-wide events. The filename stem is the trigger
-ID (must be globally unique). One Trigger per file — the block fields sit at the top level:
+Global triggers live one-per-file in `data/triggers/player_flag/` or
+`data/triggers/global_flag/`. They fire regardless of where the flag-setting player is. Use
+them for server-wide events. The filename stem is the trigger ID (must be globally unique).
+One Trigger per file — the block fields sit at the top level:
```yaml
# data/triggers/announce_99.yaml
@@ -374,24 +381,23 @@ steps:
### Boss arena — puzzle unlocks a boss
-Player activates an altar (on a separate object) setting a player flag, and this room's
-`on_flag_change` triggers the boss spawn:
+Player activates an altar (on a separate object) setting a player flag, and a flag trigger
+file spawns the boss:
```yaml
-# data/rooms/dungeon/9001.yaml
-on_flag_change:
- - on_player_flag: activated_altar
- steps:
- - broadcast: "The altar glows with an eerie light..."
- - wait: 10
- broadcast: "The ground trembles beneath your feet."
- - wait: 20
- broadcast: "A massive guardian emerges from the shadows!"
- - spawn_mob:
- id: altar_guardian
- owner_only: true
- despawn_on_leave: true
- despawn_rooms: [450, 451, 452]
+# data/triggers/player_flag/activated_altar.yaml
+on_player_flag: activated_altar
+steps:
+ - broadcast: "The altar glows with an eerie light..."
+ - wait: 10
+ broadcast: "The ground trembles beneath your feet."
+ - wait: 20
+ broadcast: "A massive guardian emerges from the shadows!"
+ - spawn_mob:
+ id: altar_guardian
+ owner_only: true
+ despawn_on_leave: true
+ despawn_rooms: [450, 451, 452]
```
The boss is `owner_only` — only the player who triggered it can interact with it. It
@@ -400,23 +406,23 @@ When the owner leaves those rooms, the boss begins despawning.
### Story beat — timed cutscene after NPC conversation
-An NPC conversation node sets `quest_ritual: started`. A room trigger plays a dramatic
-sequence for that player:
+An NPC conversation node sets `quest_ritual: started`. A flag trigger file plays a
+dramatic sequence for that player:
```yaml
-on_flag_change:
- - on_player_flag: quest_ritual
- steps:
- - wait: 8
- messages: ["The elder begins to chant in a language you don't recognize."]
- - wait: 6
- messages: ["Wisps of light swirl around the altar."]
- - wait: 4
- messages: ["The ground beneath you shudders as the ritual reaches its peak."]
- - wait: 6
- broadcast: "A blinding flash fills the chamber!"
- - teleport: 601
- messages: ["You open your eyes. You're somewhere else entirely."]
+# data/triggers/player_flag/quest_ritual.yaml
+on_player_flag: quest_ritual
+steps:
+ - wait: 8
+ messages: ["The elder begins to chant in a language you don't recognize."]
+ - wait: 6
+ messages: ["Wisps of light swirl around the altar."]
+ - wait: 4
+ messages: ["The ground beneath you shudders as the ritual reaches its peak."]
+ - wait: 6
+ broadcast: "A blinding flash fills the chamber!"
+ - teleport: 601
+ messages: ["You open your eyes. You're somewhere else entirely."]
```
The final step uses `teleport` and `messages` (the triggering player is moved and sees a
@@ -430,19 +436,22 @@ When a step sets a flag, any triggers watching that flag fire immediately (on th
tick). This lets you chain sequences:
```yaml
-on_flag_change:
- - on_player_flag: phase_1_done
- steps:
- - broadcast: "The first seal cracks."
- - set_player_flags:
- phase_2_started: true # triggers the next trigger
+# data/triggers/player_flag/phase_1_done.yaml
+on_player_flag: phase_1_done
+steps:
+ - broadcast: "The first seal cracks."
+ - set_player_flags:
+ phase_2_started: true # triggers the next trigger
+```
- - on_player_flag: phase_2_started
- steps:
- - wait: 10
- broadcast: "The second seal glows brighter..."
- - wait: 10
- spawn_mob: phase_2_adds
+```yaml
+# data/triggers/player_flag/phase_2_started.yaml
+on_player_flag: phase_2_started
+steps:
+ - wait: 10
+ broadcast: "The second seal glows brighter..."
+ - wait: 10
+ spawn_mob: phase_2_adds
```
Both sequences run concurrently — phase 2's wait countdown starts on the same tick phase
@@ -506,13 +515,13 @@ spawn_mob:
Use `despawn_mob` in a step to remove previously spawned mobs:
```yaml
-on_flag_change:
- - on_player_flag: puzzle_solved
- steps:
- - despawn_mob: puzzle_guardian # remove the puzzle mob
- - wait: 10
- broadcast: "The guardian dissolves into mist."
- - spawn_mob: boss_guardian # spawn the real boss
+# data/triggers/player_flag/puzzle_solved.yaml
+on_player_flag: puzzle_solved
+steps:
+ - despawn_mob: puzzle_guardian # remove the puzzle mob
+ - wait: 10
+ broadcast: "The guardian dissolves into mist."
+ - spawn_mob: boss_guardian # spawn the real boss
```
`despawn_mob` with an owner only removes mobs spawned by that player. Without an owner (in
@@ -613,59 +622,56 @@ on_look:
**Step 2: Blood trail in room 5002** — flag trigger fires on value 1
```yaml
-# data/rooms/city/5002.yaml
-on_flag_change:
- - on_player_flag: investigation
- condition:
- all_of:
- - player_flag: investigation
- value: 1
- - room: 5002
- steps:
- - wait: 3
- messages: ["You notice a trail of blood leading east..."]
- - set_player_flags:
- investigation: 2
+# data/triggers/player_flag/investigation_1.yaml
+on_player_flag: investigation
+condition:
+ all_of:
+ - player_flag: investigation
+ value: 1
+ - room: 5002
+steps:
+ - wait: 3
+ messages: ["You notice a trail of blood leading east..."]
+ - set_player_flags:
+ investigation: 2
```
**Step 3: Broken dagger in room 5003** — flag trigger fires on value 2
```yaml
-# data/rooms/city/5003.yaml
-on_flag_change:
- - on_player_flag: investigation
- condition:
- all_of:
- - player_flag: investigation
- value: 2
- - room: 5003
- steps:
- - wait: 3
- messages: ["A glint of metal catches your eye under a crate."]
+# data/triggers/player_flag/investigation_2.yaml
+on_player_flag: investigation
+condition:
+ all_of:
+ - player_flag: investigation
+ value: 2
+ - room: 5003
+steps:
+ - wait: 3
+ messages: ["A glint of metal catches your eye under a crate."]
```
**Step 4: Confrontation** — flag trigger fires on value 3 (set by a separate talk node),
spawns boss
```yaml
-# data/rooms/city/5004.yaml
-on_flag_change:
- - on_player_flag: investigation
- condition:
- all_of:
- - player_flag: investigation
- value: 3
- - room: 5004
- steps:
- - wait: 5
- broadcast: "A shadow detaches itself from the warehouse wall..."
- - wait: 5
- broadcast: "The assassin steps into the light, blade drawn."
- - spawn_mob:
- id: assassin_boss
- owner_only: true
- despawn_on_leave: true
- despawn_rooms: [5004]
+# data/triggers/player_flag/investigation_3.yaml
+on_player_flag: investigation
+condition:
+ all_of:
+ - player_flag: investigation
+ value: 3
+ - room: 5004
+steps:
+ - wait: 5
+ broadcast: "A shadow detaches itself from the warehouse wall..."
+ - wait: 5
+ broadcast: "The assassin steps into the light, blade drawn."
+ - spawn_mob:
+ id: assassin_boss
+ owner_only: true
+ despawn_on_leave: true
+ despawn_rooms: [5004]
```
The player must stay in room 5004 to fight the boss. If they flee, the boss despawns and