diff options
Diffstat (limited to 'building_guide')
| -rw-r--r-- | building_guide/admin.md | 12 | ||||
| -rw-r--r-- | building_guide/behaviors.md | 10 | ||||
| -rw-r--r-- | building_guide/construction.md | 5 | ||||
| -rw-r--r-- | building_guide/courses.md | 7 | ||||
| -rw-r--r-- | building_guide/hacking.md | 40 | ||||
| -rw-r--r-- | building_guide/hazards.md | 45 | ||||
| -rw-r--r-- | building_guide/mobs.md | 21 | ||||
| -rw-r--r-- | building_guide/objects.md | 15 | ||||
| -rw-r--r-- | building_guide/rooms.md | 46 | ||||
| -rw-r--r-- | building_guide/triggers.md | 16 |
10 files changed, 86 insertions, 131 deletions
diff --git a/building_guide/admin.md b/building_guide/admin.md index a99535a..3167147 100644 --- a/building_guide/admin.md +++ b/building_guide/admin.md @@ -273,11 +273,9 @@ even across sessions. #### Live editing and maintenance -Editor API requests (including undo/redo) execute as one game-owner task: -file writes and their runtime callbacks/reloads cannot interleave with a tick -or player command. Large edits briefly pause gameplay; slow browser uploads -and downloads do not. Requests are limited to 8 MiB and rejected during game -shutdown. +Editor API requests (including undo/redo) execute as a single serialized task, so +large edits briefly pause gameplay while slow browser uploads and downloads do +not. Requests are limited to 8 MiB and rejected during game shutdown. This does not make every edit automatically hot-reloadable. Existing reload requirements still apply to definition editors and raw Files edits. Use the @@ -308,9 +306,7 @@ The colors page is the single source of truth for everything the game paints wit **Constants** — colors the engine renders conditionally that players can **never** override: HP-bar tiers (green/yellow/red by remaining %), level-difference bands (white/green/red by how far a target's level is from the viewer's), skill-level tier colors on the datapad, battery/run-energy bars, tech status indicators (`ON`/`off`/`locked`), tech/buff `[ON]`/`[BUFF]` tags and stat names, the score panel "max value" trailing numbers, shared table-header chrome (`Skill`, `Level`, `XP`, `Option`, `Value`, `Description` etc.), the production-menu locked-row tint, the APS datapad "here" highlight, the mob inline `n/maxhp` value, the `(protected)` tag, and the god-mode `[LOCAL]`/`[GLOBAL]`/`(HIDDEN)` tags. These are stored in a separate `constant_colors` block in `config.yaml`. -**Why constants are separate from player colors.** The in-game `colors` command only knows about names listed in `config.DefaultColors()`. Constants live in a different map (`constant_colors`), which that command never reads. So if a player types `colors hp_bar_high FF`, the game answers "Unknown color target: hp_bar_high" and refuses — there is no path for a player to set one. Editing a constant here is the only way to change it short of editing `config.yaml` directly. - -**Save / Reset.** One shared **Save Colors** button persists both sections to `config.yaml` (a backup snapshot of the previous file is written first). **Reset all to defaults** restores both `default_colors` and `constant_colors` to the built-in values from `internal/config/config.go` and saves immediately. **Export JSON** / **Import JSON** round-trip both maps; the import payload may be either the layered `{colors:{...}, constant_colors:{...}}` shape (the format Export produces) or a legacy flat `{category: spec}` object, in which case only player colors are applied. +**Save / Reset.** One shared **Save Colors** button persists both sections to `config.yaml` (a backup snapshot of the previous file is written first). **Reset all to defaults** restores both `default_colors` and `constant_colors` to their built-in values and saves immediately. **Export JSON** / **Import JSON** round-trip both maps; the import payload may be either the layered `{colors:{...}, constant_colors:{...}}` shape (the format Export produces) or a flat `{category: spec}` object, in which case only player colors are applied. **Preview.** The right pane renders example output (a full room look, two combat rounds, chat, drops, ticks, assassin/safespot, and a Constants demo block) using the live specs you have staged. The combat HP bars in the preview paint with the real `hp_bar_high` / `hp_bar_med` / `hp_bar_empty` constants, and the `(level …)` annotations in the Room Look paint with `level_diff_*` — so editing a constant is reflected immediately in the same example lines an admin would recognize from gameplay. diff --git a/building_guide/behaviors.md b/building_guide/behaviors.md index 26c12ec..976ba3d 100644 --- a/building_guide/behaviors.md +++ b/building_guide/behaviors.md @@ -488,20 +488,20 @@ pass fires and its `steps` run as a scripted sequence (first-match-wins). |-------|-------|-------------| | `lock` | all | `true` = atomic (player can only `quit`) + resumable on reconnect. Default `false` = interruptable, not persisted. | | `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). | +| `condition` | all | Optional Trigger-level gate (Condition). | | `steps` | all | Ordered list of Step entries run as a scripted sequence. | | `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 -inline) — those have not changed. +Talk node/option actions use the NodeAction shape (`action:` with the effect fields +inline). #### Step vocabulary Each Step can carry a `wait` (ticks to pause before the step fires) and any combination of the effects below. All effects on a step fire together when the wait elapses. Messages are -plain strings — neither `{message, delay}` maps nor the old `delay` field exist. +plain strings. | Field | Type | Description | |-------|------|-------------| @@ -535,7 +535,7 @@ Conditions gate a Trigger (Trigger-level `condition`) or an individual step. A b | `not` | `true` inverts the entire condition | | `has_item` | Passes when the player carries this item | | `min_credits` | Passes when the player has at least this many credits | -| `room` | **New.** Passes when the triggering player is currently in this room — use to scope flag-change triggers | +| `room` | Passes when the triggering player is currently in this room — use to scope flag-change triggers | | `all_of` | List of sub-conditions; all must pass | | `any_of` | List of sub-conditions; any one must pass | diff --git a/building_guide/construction.md b/building_guide/construction.md index 07b8f7f..5e2c898 100644 --- a/building_guide/construction.md +++ b/building_guide/construction.md @@ -26,9 +26,8 @@ description: inroom_description: "An estate directory terminal stands in the center of the neighborhood." ``` -The directory is handled by Go code: when a player looks at or uses it, it scans character -YAML files for the `owns_house_local_neighborhood` player flag and lets homeowners teleport -into their house. +The directory scans character YAML files for the `owns_house_local_neighborhood` +player flag and lets homeowners teleport into their house. ### Tools diff --git a/building_guide/courses.md b/building_guide/courses.md index 3f571e0..eecfb00 100644 --- a/building_guide/courses.md +++ b/building_guide/courses.md @@ -4,7 +4,7 @@ Agility courses are defined in `data/courses/<id>.yaml`. Each course defines a s **The filename is the ID** (`vent_shaft.yaml` → `vent_shaft`); the loader derives it from the filename. Do not put an `id:` field in the file — it is ignored. -> Editing courses is done from the **Map screen** in the admin web GUI: select an obstacle room and use its **Course** side-panel tab to create a course, add the room as a step, edit all of its parameters, or remove it. The legacy standalone "Courses" editor tab has been removed. +> Editing courses is done from the **Map screen** in the admin web GUI: select an obstacle room and use its **Course** side-panel tab to create a course, add the room as a step, edit all of its parameters, or remove it. > > When a room is added as a course step, the admin GUI auto-creates **always-blocked one-way exits** between consecutive obstacle rooms so the in-game map can lay them out correctly. The direction of these exits is chosen by the builder. Always-blocked exits are blocked to players (movement is refused with the standard "blocked" message and they show "(blocked)" in `look`) but visible on the map — they exist solely for map rendering. On the map, course links appear as blue dashed one-way arrows (colored via the `map_agility` theme token). @@ -66,7 +66,7 @@ obstacles: | Field | Type | Description | | ------------ | -------- | -------------------------------------------------------------------------- | | `message` | string | Text printed when this phase advances | -| `delay` | float64 | Ticks to wait before printing this phase (supports fractional via `engine.ToTicks`) | +| `delay` | float64 | Ticks to wait before printing this phase (fractional values allowed) | | `fail_check` | bool | If true, the obstacle's failure roll happens on this phase (set on at most one phase) | ### Phase Advancement @@ -128,4 +128,5 @@ exits: | `leap` | leaping | | `slide` | sliding | -Obstacle verbs are auto-registered at startup from all course YAML files. No changes to `cmd_registry.go` needed when adding new courses — any verb in a `data/courses/` file will work.
\ No newline at end of file +Obstacle verbs are auto-registered at startup from all course YAML files — any verb in a +`data/courses/` file will work without additional configuration. diff --git a/building_guide/hacking.md b/building_guide/hacking.md index dc4372b..b45363f 100644 --- a/building_guide/hacking.md +++ b/building_guide/hacking.md @@ -1,8 +1,8 @@ ## Hacking -Hacking minigames are played by jacking into terminal objects. Terminals are YAML objects that are registered in Go's `terminalDefs` map. - -**The filename is the ID** (`terminal_basic.yaml` → `terminal_basic`); like all objects, the loader derives the ID from the filename — don't add an `id:` field (see `objects.md`). +Hacking minigames are played by jacking into terminal objects. Terminals are YAML +objects; the filename is the ID (`terminal_basic.yaml` → `terminal_basic`), and +like all objects you should not add an `id:` field (see `objects.md`). ### Terminal Object YAML @@ -13,38 +13,20 @@ description: "A battered terminal with a cracked screen. {28}[Level 1 Hacking]{/ inroom_description: "A {28}basic terminal{/} hums quietly against the wall." ``` -- `id` must match a key in `terminalDefs` (Go code in `internal/game/hacking.go`) - `name` is used for matching in the `jack` command - `color` controls the display color - `description` is shown when examining the terminal - `inroom_description` is shown in room listings -- No `behavior` field — terminals are registered in Go, not YAML behaviors - -### Adding a New Terminal - -1. Create an object YAML file in `data/objects/terminal_<name>.yaml` -2. Add an entry to `terminalDefs` in `internal/game/hacking.go` -3. Optionally create a new minigame implementation in `internal/game/hacking_<name>.go` -4. Place the terminal object in a room's `objects:` list - -### Adding a New Minigame +- Terminals have no behavior block; each terminal corresponds to a minigame + registered in the game code -New minigames implement the `HackingMinigame` interface: +### Placing a Terminal -```go -type HackingMinigame interface { - Init(level int) string - HandleInput(input string) (output string, done bool, won bool) - Name() string -} -``` - -Optionally implement `XPBonuser` for bonus XP: +Add the terminal object to a room's `objects:` list: -```go -type XPBonuser interface { - BonusXP() int -} +```yaml +objects: + - id: terminal_basic ``` - +Adding a brand-new hacking minigame is an engine change, not a world-data change. diff --git a/building_guide/hazards.md b/building_guide/hazards.md index 04333c9..3b72840 100644 --- a/building_guide/hazards.md +++ b/building_guide/hazards.md @@ -1,17 +1,9 @@ ## Hazards A **hazard** is an environmental threat attached to a room — a sandstorm, solar -radiation, falling rocks, toxic spores. It rolls an "attack" against everyone in -the room every few ticks using the **same combat formulas as a mob**: the hazard -supplies the accuracy and max hit, and the player defends with their Defense -level plus the equipment defense bonus selected by the hazard's attack type. No -new combat math, no special gear — your existing armour and Defense skill are -what keep you alive. - -Hazards are **shared, ID-referenced definitions** (like drop tables and modules): -define a hazard once in `data/hazards/<id>.yaml`, then reference it from any number -of rooms with `hazard: <id>` (see `rooms.md`). This makes one hazard reusable -across a whole area and lets a future `examine` command resolve it by name. +radiation, falling rocks, toxic spores. Define it once in +`data/hazards/<id>.yaml`, then reference it from any number of rooms with +`hazard: <id>` (see `rooms.md`). **The filename is the ID** (`solar_radiation.yaml` → reference it as `hazard: solar_radiation`); the loader derives it from the filename. Do not put an `id:` field in the file — it is ignored. @@ -76,37 +68,8 @@ attacked — they only attack the player. | Field | Description | |---|---| | `name` | Display name used in warnings and default messages | -| `description` | Flavour text (for a future `examine`) | +| `description` | Flavour text for the hazard | | `warn_message` | Shown when warning the player about the area | | `hit_message` | Message on a hit; include a single `%d` to show the damage | | `miss_message` | Message on a miss | | `required_item` | If set and equipped, the hazard is fully negated for that player | - -### How it behaves - -- **Applies to everyone in the room, always** — idle, passing through, working a task, - or fighting a mob. Strong incentive to work fast, take cover, or leave. -- **Tech protection applies automatically.** Because the hazard has an attack type, the - matching protection tech reduces its damage: Kinetic Barrier (melee types), - Projectile Screen (`ranged`), Neural Firewall (`science`). -- **Safespots block all hazard damage.** A hidden player behind a safespot object takes - no hazard damage, regardless of hazard type. Note that doing a *melee* attack/work step - forces you out of cover — so ranged/science work lets you stay sheltered, melee does not. -- **Right gear negates it.** If `required_item` is set and equipped, the hazard is skipped - for that player entirely. The item must exist (validated at startup). -- **Hazards never interrupt movement.** Unlike a mob hit (which aborts a flee with - "Can't escape!"), a hazard hit does not touch your walk — you can keep moving out of - the danger zone while taking hits. - -### Entry warning - -Players walking from a **safe** room into a **hazardous** one get a `[Y/n]` confirmation -(pressing return defaults to yes; anything other than empty/`y`/`yes` cancels). Moving -between two hazardous rooms does not re-prompt. Players can disable the prompt with the -account option `danger_warning` (`option danger_warning false`). - -### Examine (forward-compatible) - -Because the hazard is a shared def referenced by ID, a future `examine sandstorm` can -resolve the current room's `hazard` ID, load the def, and render its attack type, accuracy, -max hit and description — no hidden objects required. diff --git a/building_guide/mobs.md b/building_guide/mobs.md index 833fbfd..cbcd728 100644 --- a/building_guide/mobs.md +++ b/building_guide/mobs.md @@ -67,7 +67,7 @@ combat: strength: 3 defense: 3 speed: 5 - max_melee_hit: 1 # OSRS-derived: ((strength+9)*(strength_bonus+64)+320)/640 + max_melee_hit: 1 # max hit with melee (for melee-attacking mobs) bonuses: attack_bonus: 3 # equipment-equivalent attack bonus for mob's attack roll strength_bonus: 2 # equipment-equivalent strength bonus for mob's max hit @@ -97,9 +97,9 @@ All combat stats live under `combat.stats:` | `ranged` | Ranged level (for ranged/science mobs) | | `science` | Science level | | `speed` | Attack speed in ticks (default 5) | -| `max_melee_hit` | Maximum melee hit (for melee-attacking mobs). Computed as ((strength+9)*(strength_bonus+64)+320)/640 | -| `max_ranged_hit` | Maximum ranged hit (for ranged-attacking mobs). Computed as ((ranged+9)*(ranged_bonus+64)+320)/640 | -| `max_science_hit` | Maximum science hit (for science-attacking mobs). A set value — magic max hits are not level-derived in OSRS | +| `max_melee_hit` | Maximum melee hit (for melee-attacking mobs) | +| `max_ranged_hit` | Maximum ranged hit (for ranged-attacking mobs) | +| `max_science_hit` | Maximum science hit (for science-attacking mobs). A set value, not level-derived | #### Bonuses (`combat.stats.bonuses:`) @@ -352,7 +352,7 @@ Mobs without these fields work normally (assassin_level defaults to 0, finishing ### Task Mobs (Worksites) -A task mob is a non-violent "worksite" — build a solar panel, survey flora, prospect a rock — that reuses the entire combat engine. Set `combat.kind: task`. Mechanically `combat.stats.hp` is the work pool that drains to 0 to **complete** the work; the player sees a progress bar filling toward 100%. A task mob **never attacks back** — the danger (if any) comes from a room `hazard:` (see `hazards.md`). Use the `work` command (or `attack`) on it. +A task mob is a non-violent "worksite" — build a solar panel, survey flora, prospect a rock. Set `combat.kind: task` and treat `combat.stats.hp` as the amount of work required to complete it. A task mob **never attacks back** — the danger (if any) comes from a room `hazard:` (see `hazards.md`). Use the `work` command (or `attack`) on it. Task-specific fields live in the root-level `task:` block: @@ -402,10 +402,11 @@ drops: # the "payment" for the labour — same drop sy Key points: -- **All combat styles work.** Melee, ranged (consumes ammo as "supplies") and science decks (consume junk) all apply progress. The per-type `*_defense` values steer which method is *efficient* without forbidding any — a rock might have low `crush_defense` but high `stab_defense`. -- **XP and buffs are identical to combat** — driven by your attack style (accurate/aggressive/defensive/balanced) and split into the combat skills. This is a non-violent way to train Accuracy/Strength/Defense/Ranged/Science/Hitpoints. -- **No special equipment.** Task mobs are designed around the weapons and bonuses you already have. There is no separate "tool" equipment set. -- **Progress decays exactly like inverted mob regen.** A task you stop working on slowly loses progress over time (the same regen that heals combat mobs, displayed inverted). -- **One worker at a time**, the same 1-v-1 lock as combat. +- All combat styles apply progress: melee, ranged (consumes ammo), and science decks + (consume junk). The per-type `*_defense` values steer which method is *efficient* + without forbidding any — a rock might have low `crush_defense` but high `stab_defense`. +- `combat.stats.hp` is the amount of work required, not a health bar. Task mobs never + attack back; danger comes from a room `hazard:` if you want any. +- `drops` are the payment for completing the work, using the same drop system as combat. --- diff --git a/building_guide/objects.md b/building_guide/objects.md index 958938d..10d50c1 100644 --- a/building_guide/objects.md +++ b/building_guide/objects.md @@ -449,15 +449,12 @@ Mobs must have a `size` field for safespot blocking to work (small < medium < la massive, default medium). A safespot with `max_block_size: large` blocks mobs of size small, medium, and large, but not massive. -#### Combat integration - -- A safespot blocks melee attacks from mobs within `max_block_size`. -- When a mob's melee is blocked but it has a ranged/science attack type, it switches to its - strongest ranged/science attack. -- Mobs exceeding `max_block_size` are never blocked and always use melee. -- Melee attacks from a safespotted player force them out of cover. -- Hiding in combat takes 4 ticks; taking a hit during this cancels the hide. -- Hiding out of combat takes 1 tick. +#### Interaction notes + +- A safespot blocks melee attacks from mobs up to `max_block_size`; larger mobs always use + melee. +- A blocked mob with a ranged/science attack type switches to its strongest ranged/science + attack. - Safespots block all hazard damage (see `hazards.md`). #### Option: safespot_alert diff --git a/building_guide/rooms.md b/building_guide/rooms.md index 9cb93b1..f1e1712 100644 --- a/building_guide/rooms.md +++ b/building_guide/rooms.md @@ -246,7 +246,7 @@ Multiplayer table fields (`casino_tables`): | Field | Type | Default | Notes | |---|---|---|---| | `id` | string | — | Required. Nonempty, no surrounding whitespace, unique among the room's tables and machines. | -| `game` | string | — | Required. `blackjack`, `baccarat`, `roulette`, `craps`, `paigow`, or `video_poker`. | +| `game` | string | — | Required. `blackjack`, `baccarat`, `roulette`, `craps`, `paigow`, `video_poker`, or `plinko`. | | `max_players` | int | 8 | 0–64. Video poker is forced to 1; Pai Gow caps at 6. | | `betting_ticks` | int | 20 | 0–3600. | | `min_bet` | int | 1 | Must not exceed `max_bet`. | @@ -255,6 +255,27 @@ Multiplayer table fields (`casino_tables`): Game rules are nested in a block named after the game. `id` and `game` are the only required keys; omitted numeric fields keep their defaults. +#### Plinko + +Plinko is a single-seat fixture under `casino_tables`, addressed with `play plinko`: + +```yaml +casino_tables: + - id: plinko + game: plinko + min_bet: 1 + max_bet: 10000 + plinko: + rtp: 98 +``` + +| Field | Type | Default | Notes | +|---|---|---|---| +| `rtp` | int | 98 | Target return-to-player percentage, 31–100. | + +`max_players` may only be omitted, `0`, or `1`. Bet limits are per ball, and +`max_bet` cannot exceed 10000000. + #### Blackjack ```yaml @@ -402,7 +423,7 @@ casino_machines: |---|---|---|---| | `id` | string | — | Required; same rules as table IDs. | | `game` | string | — | Required; only `slots`. | -| `variant` | string | `huff_and_even_more_puff` | Registered in Go (`slotVariants` in `internal/casino/registry.go`); an unregistered name is rejected. | +| `variant` | string | `huff_and_even_more_puff` | Must be a registered slot variant; an unregistered name is rejected. | | `min_bet` | int | 1 | | | `max_bet` | int | 1000000 | Must be at least `min_bet`. | | `spin_ticks` | int | 2 | 0–3600. | @@ -422,8 +443,7 @@ invalid room casino configurations. Unknown fields are rejected. seat counts, split counts, decks, and payouts are rejected. - Payouts and shuffle fractions must be finite (no NaN or infinity). - Editing a room hot-reconfigures an **idle** table or machine on the next - lookup; an occupied game keeps its live rules until it becomes idle, so a - committed wager is never discarded. + lookup. ### Objects — interactive fixtures @@ -532,9 +552,9 @@ on_enter: #### Timed sequences -`wait: N` on a step is the number of ticks to pause **before** that step fires (replaces the -old `delay`). Messages are plain strings; effects on a step fire together when the wait -elapses. A step with no `wait` fires on the next tick. +`wait: N` on a step is the number of ticks to pause **before** that step fires. Messages +are plain strings; effects on a step fire together when the wait elapses. A step with no +`wait` fires on the next tick. #### Step actions @@ -558,12 +578,11 @@ on_enter: despawn_on_leave: true ``` -#### First-match-wins and migration +#### First-match-wins -Entries in `on_enter` are walked top-to-bottom; the first whose `condition` passes fires -and its steps run. The old on_enter ran **every** matching step, so migration wraps the old -step list into a single Trigger entry to preserve behavior. For new content where steps -are mutually exclusive, use multiple entries with distinct conditions. +Entries in `on_enter` are walked top-to-bottom; the first whose `condition` passes +fires and its steps run. For new content where steps are mutually exclusive, use +multiple entries with distinct conditions. Notes: - `wait: 0` (or omitted) fires on the next tick. @@ -592,8 +611,7 @@ on_exit: Use on_exit for parting messages, closing out a spawn when a player departs, or recording that the player has passed through an area. It obeys the same first-match-wins rule and the -same Step vocabulary as on_enter. (New event block — did not exist before the Trigger -unification.) +same Step vocabulary as on_enter. --- diff --git a/building_guide/triggers.md b/building_guide/triggers.md index ba44c40..d99230b 100644 --- a/building_guide/triggers.md +++ b/building_guide/triggers.md @@ -92,8 +92,7 @@ wait elapses. | `aps_node` | Marks this room's APS node as unlocked. | | `condition` | Per-step gate (evaluated once when the sequence reaches this step). | -Messages are **plain strings**, no longer `{message, delay}` maps. The `delay` field is -gone — use `wait` instead. +Messages are plain strings. Use `wait` to pause before a step fires. ### Condition vocabulary @@ -108,7 +107,7 @@ check passes when the flag is **set to a truthy value**. `not: true` inverts any | `not` | `true` inverts the entire condition. | | `has_item` | Passes when the player carries this item. | | `min_credits` | Passes when the player has at least this many credits. | -| `room` | **New.** Passes when the triggering player is currently in this room. Use it to scope flag-change triggers to a location. | +| `room` | Passes when the triggering player is currently in this room. Use it to scope flag-change triggers to a location. | | `all_of` | List of sub-conditions; all must pass. | | `any_of` | List of sub-conditions; any one must pass. | @@ -144,10 +143,9 @@ on_use: crystal_inserted: true ``` -For `on_enter` specifically, the old flat step list ran **every** matching step. Migration -wraps the old steps into a single Trigger entry, so all the old steps still run as one -sequence. New `on_enter` content should use multiple entries with distinct conditions when -the steps are mutually exclusive. +For `on_enter` specifically, entries are walked top-to-bottom and the first whose +condition passes runs. Use multiple entries with distinct conditions when the steps +are mutually exclusive. ### Lock @@ -181,7 +179,7 @@ entity: | Object | `on_use`, `on_look` | | Mob | `on_kill` | | Exit (room YAML) | `on_traverse` | -| Room | `on_enter`, `on_exit` (NEW) | +| Room | `on_enter`, `on_exit` | | 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. @@ -190,7 +188,7 @@ entity: (not when it's blocked). The exit's own `condition:` / `blocked_message:` belong to the exit gate, not the Trigger list. - **on_enter** — fires after the player enters the room. -- **on_exit** (NEW) — fires when a player leaves the room, **before** `RoomID` is updated +- **on_exit** — 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 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. |
