aboutsummaryrefslogtreecommitdiff
path: root/building_guide/behaviors.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/behaviors.md
parentc705ae942573984784ef501bf8198f61f5206ddd (diff)
downloadthehouseoficarus-b8c90886ef1f3afb8d908aac89028bd177836cae.tar.gz
feat(admin): multi-select with shift+drag for common bulk operations
Diffstat (limited to 'building_guide/behaviors.md')
-rw-r--r--building_guide/behaviors.md605
1 files changed, 247 insertions, 358 deletions
diff --git a/building_guide/behaviors.md b/building_guide/behaviors.md
index 3b7d769..f358a33 100644
--- a/building_guide/behaviors.md
+++ b/building_guide/behaviors.md
@@ -1,45 +1,45 @@
## Behaviors
-Behaviors are **inline configs** placed directly inside object YAML (`gather:`, `talk:`,
-`use:`, `safespot:`) or mob YAML (`talk:`). Each section below shows the keys you can use under each behavior type.
-
-The examples below are object/mob files; remember the filename is the ID for these.
+Behaviors are **inline configs** placed directly inside object YAML (`gather:`, `talk:`, `use:`,
+`safespot:`) or mob YAML (`talk:`, `shop:`, `steal:`). Each section below shows the keys for
+each behavior type.
### Gather (mining, fishing, woodcutting)
-Mining — per-drop depletion on a rock object:
+Gather configs live under the `gather:` key on an object. The object's filename is its ID.
+
+Mining — per-drop depletion on a rock:
+
```yaml
name: copper rock
color: "B2"
gather:
skill: mining
- tools: # requires item with matching tool_type
+ tools:
- pickaxe
success:
- base: 0.40 # 40% base chance
- per_level: 0.01 # +1% per skill level
- cap: 0.95 # 95% max
+ base: 0.40
+ per_level: 0.01
+ cap: 0.95
gather_message: "You swing your pickaxe at the rock..."
fail_message: "You chip away but get nothing useful."
drops:
- item_id: copper_ore
- level: 1 # required skill level for this drop
- xp: 17 # XP awarded when this drop lands
- weight: 90 # 90% chance when roll succeeds
- depletes: true # rock becomes depleted after this drop
- message: "You manage to mine some {B2}copper ore{/}."
- - table: gem_table # reference a shared drop table
+ level: 1
+ xp: 17
+ weight: 90
+ depletes: true
+ success_message: "You manage to mine some {B2}copper ore{/}."
+ - table: gem_table
weight: 10
- depletes: false # gem drops don't deplete the rock
- message: "You spot a glint of something valuable!"
- respawn_timer: 50 # ticks until rock respawns
+ depletes: false
+ success_message: "You spot a glint of something valuable!"
+ respawn_timer: 50
respawn_broadcast: "A glint of copper catches your eye from some {name}."
```
-Drop messages support inline color tags: `{<00-FF>}text{/}`. Use the item's color
-index to match its display color. Gradients also work: `{g:C4,52}text{/}`.
+Fishing — non-depleting:
-Non-depleting gather (fishing on a fishing spot object):
```yaml
name: fishing spot
gather:
@@ -55,14 +55,15 @@ gather:
fail_message: "Nothing seems to bite."
drops:
- item_id: raw_trout
- level: 5 # required skill level for this drop
- xp: 20 # XP awarded when this drop lands
+ level: 5
+ xp: 20
weight: 100
- depletes: false # never depletes
- message: "You catch a {45}raw trout{/}!"
+ depletes: false
+ success_message: "You catch a {45}raw trout{/}!"
```
-Woodcutting with shared depletion and bird's nests:
+Woodcutting — shared depletion with bird's nests:
+
```yaml
name: oak tree
color: "71"
@@ -78,18 +79,19 @@ gather:
fail_message: "You swing but get no logs."
drops:
- item_id: oak_logs
- level: 15 # required skill level for this drop
- xp: 37 # XP awarded when this drop lands
+ level: 15
+ xp: 37
weight: 100
- depletes: false # depletion is timer-based (deplete_timer)
- message: "You get some {71}oak logs{/}."
- respawn_timer: 14 # ticks until tree respawns after being cut down
- deplete_timer: 45 # max ticks before next gather depletes (counts down while chopping)
- nest_chance: 256 # 1/256 chance for a bird's nest on each successful gather
+ depletes: false
+ success_message: "You get some {71}oak logs{/}."
+ respawn_timer: 14
+ deplete_timer: 45
+ nest_chance: 256
respawn_broadcast: "An {name} grows back."
```
-Regular tree — always depletes on first gather, no shared timer, no nests:
+Regular tree — always depletes on first gather:
+
```yaml
name: tree
gather:
@@ -104,11 +106,11 @@ gather:
fail_message: "You swing but get no logs."
drops:
- item_id: logs
- level: 1 # required skill level for this drop
- xp: 25 # XP awarded when this drop lands
+ level: 1
+ xp: 25
weight: 100
- depletes: true # regular tree depletes on first successful gather
- message: "You get some {6B}logs{/}."
+ depletes: true
+ success_message: "You get some {6B}logs{/}."
respawn_timer: 80
respawn_broadcast: "A {name} grows back."
```
@@ -116,46 +118,52 @@ gather:
#### GatherConfig Fields
| Field | Type | Description |
-|---|---|---|
+|-------|------|-------------|
| `skill` | string | Skill name for level check |
-| `tools` | []string | Required tool_type list (e.g. `[pickaxe]`) |
+| `tools` | []string | Required tool_type list (e.g. `[pickaxe]`, `[axe]`) |
+| `no_tool_speed` | float64 | Base wait in ticks when no tool is held |
| `bait` | string | Required bait item (for fishing) |
| `success` | SuccessFormula | Base + per_level, capped |
-| `gather_message` | string | Custom "You gather..." message |
-| `depleted_message` | string | Custom "The rock is depleted" message |
-| `exhausted_message` | string | Custom "The tree falls" message |
-| `fail_message` | string | Custom failure message |
+| `gather_message` | string | Message when gathering starts |
+| `depleted_message` | string | Message when resource is depleted |
+| `exhausted_message` | string | Message when tree falls |
+| `fail_message` | string | Message on failed gather |
+| `broadcast_message` | string | Broadcast to room on successful gather |
| `drops` | []DropEntry | Weighted drop entries |
| `respawn_timer` | float64 | Ticks until depleted object respawns |
-| `respawn_broadcast` | string | Broadcast message when object respawns |
+| `respawn_broadcast` | string | Broadcast when object respawns |
| `deplete_timer` | float64 | Ticks for shared depletion (trees) |
| `nest_chance` | int | 1/N chance for bird's nest alongside normal drop |
#### SuccessFormula
| Field | Type | Description |
-|---|---|---|
-| `base` | float64 | Base success chance (0.0-1.0) |
+|-------|------|-------------|
+| `base` | float64 | Base success chance (0.0–1.0) |
| `per_level` | float64 | Chance increase per skill level |
| `cap` | float64 | Maximum success chance |
#### DropEntry
| Field | Type | Description |
-|---|---|---|
+|-------|------|-------------|
| `item_id` | string | Item ID to drop |
| `table` | string | Reference to a shared drop table in `data/drops/` |
| `weight` | int | Relative drop weight |
| `level` | int | Skill level required for this drop |
| `xp` | int | XP awarded when this drop lands |
| `depletes` | bool | Resource depletes on this drop |
-| `quantity` | int | Amount to drop (1-3 for random) |
-| `message` | string | Player message on drop |
+| `quantity` | int | Amount to drop (1–3 for random) |
+| `success_message` | string | Player message on drop (supports color tags) |
+| `tool` | string | Only drops when this tool_type is active |
-#### Shared depletion explained
+Drop messages support inline color tags: `{<00–FF>}text{/}` and gradients `{g:C4,52}text{/}`.
+Use the item's color index to match its display color.
+
+#### Shared depletion
When `deplete_timer > 0`, the tree has a shared despawn timer:
-- The timer starts at `deplete_timer` max when the first player begins chopping.
+- Timer starts at `deplete_timer` max when the first player begins chopping.
- Each tick, if anyone is chopping, the timer counts down.
- When the timer reaches 0, the NEXT successful gather depletes the tree.
- If no one is chopping and the tree isn't depleted, the timer ticks back UP.
@@ -163,22 +171,27 @@ When `deplete_timer > 0`, the tree has a shared despawn timer:
Use `deplete_timer` for trees. Use `depletes: true` on individual drops for rocks.
-#### Bird's nests
+#### Bird's nests and the `search` command
-When `nest_chance > 0`, each successful gather has a 1/N independent chance to also drop
-a bird's nest. The nest goes to inventory (or to the ground if inventory is full).
-Use the `search` command to open nests — they roll on the `birds_nest_drop` table.
+When `nest_chance > 0`, each successful gather has a 1/N independent chance to also drop a
+bird's nest. The nest goes to inventory (or to the ground if inventory is full).
-#### XP drops
+Use the `search` command to open searchable items in your inventory:
-Every successful drop awards the XP defined on that drop entry. If the player's `xpdrops`
-toggle is on, the output includes the XP gain: `(+37xp wct)`.
+```
+search <item> e.g. search nest
+ e.g. search birds nest
+```
+
+Searches your inventory for the named item. If found, removes it and rolls on a drop table
+(currently `birds_nest_drop`). Only bird's nests are searchable at present.
+
+---
### Talk (dialog trees)
Talk configs go under the `talk:` key on objects or mobs. A conversation is a set of named
-**nodes**, each with a message, optional action, optional auto-advance target, and choices
-for the player.
+**nodes**, each with messages, optional action, and choices for the player.
```yaml
name: Guard
@@ -198,13 +211,11 @@ talk:
goto: has_pass
condition:
player_flag: got_pass
- value: true
- text: "\"Goodbye.\""
- # no goto = end conversation
about_gate:
messages:
- - "\"Supplies. Weapons.\" He shifts his weight. \"Bring me some copper ore and I'll stamp you a pass.\""
+ - "\"Supplies. Weapons. Bring me copper ore and I'll stamp you a pass.\""
action:
set_player_flags:
talked_to_guard: true
@@ -239,120 +250,41 @@ talk:
#### TalkNode fields
| Field | Type | Description |
-|---|---|---|
-| `messages` | []string | NPC dialogue, shown one at a time. The player presses enter to advance through each message. Options appear after the last message. A single-element list works like a simple one-shot line. |
-| `condition` | Condition | Optional. If the condition fails, the node is skipped entirely — its messages, action, and options are not shown. Use with `goto` to auto-advance to a different node. |
-| `action` | NodeAction | Fires when the node is entered (give items, set flags, etc). |
-| `options` | []TalkOption | Player choices. If empty and `goto` is set, auto-advances. |
-| `goto` | string | Node ID to auto-advance to when there are no visible options OR when the node condition fails. |
+|-------|------|-------------|
+| `messages` | []string | NPC dialogue, shown one at a time. Player presses enter to advance. Options appear after the last message. |
+| `message` | string/list | Single message or randomized list (picks one at random). |
+| `condition` | Condition | If condition fails, the node is skipped via `goto`. |
+| `action` | NodeAction | Fires when the node is entered. |
+| `options` | []TalkOption | Player choices. Empty + `goto` set = auto-advance. |
+| `goto` | string | Node ID to auto-advance to when no visible options or condition fails. |
#### TalkOption fields
| Field | Type | Description |
-|---|---|---|
-| `text` | string | Player-facing choice text. |
-| `goto` | string | Node ID to navigate to if this choice is selected. Omit to end the conversation. |
-| `condition` | Condition | Only show this option if the condition passes. |
-| `action` | NodeAction | Fires when this option is selected, BEFORE navigating to `goto`. |
-
-#### Node action reference
-
-| Field | Effect |
-|---|---|
-| `set_global_flags` | Sets global flags (global, shared by all players). |
-| `set_player_flags` | Sets player-local flags (per-character, quest progress). |
-| `give_item` | Gives an item to the player's inventory. |
-| `take_item` | Removes an item from the player's inventory. |
-| `teleport` | Moves the player to a room ID. |
-| `heal` | Restores that many hitpoints. |
-| `credits` | Credits charged (negative) or awarded (positive). |
-| `aps_node` | Marks this room's APS node as unlocked. |
-
-The full effect vocabulary (used by all interaction types and on_enter/trigger
-steps) additionally includes `message`, `broadcast`, `broadcast_global`,
-`spawn_mob`, `despawn_mob`, and `delay` — see [interaction
-reference](#interaction-reference) below.
-
-> **Planned (not yet implemented):** `assign_task`, `skip_task`, `extend_task`, and `sawmill` are under design and will be added in a future update.
-
-All fields in a single action are processed together — give an item, take an item, set flags,
-and heal in one node.
-
-Example — quest completion with multiple actions:
-```yaml
-action:
- take_item: dragon_head
- give_item: dragon_slayer_medal
- set_player_flags:
- dragon_quest: complete
- dragon_slain: true
- heal: 99
- teleport: 1
-```
-
-#### Option-level actions
-
-Actions can be placed directly on options. The action fires when the player selects that
-choice, BEFORE navigating to `goto`. This eliminates needing a separate "action node" for
-simple transactions:
-
-```yaml
-nodes:
- start:
- messages:
- - "Welcome! Houses are 10 credits. Interested?"
- options:
- - text: "Yes, I'll buy one."
- action:
- credits: -10
- set_player_flags:
- owns_house: true
- goto: purchased
- condition:
- player_flag: owns_house
- not: true
- - text: "No thanks."
- purchased:
- messages:
- - "Excellent! The plot is yours."
- options:
- - text: "Thanks!"
- goto: start
-```
-
-If an option has both an `action` and a `goto`, the action runs first, then the player
-navigates to the target node (which may also have its own action, fired on entry).
+|-------|------|-------------|
+| `text` | string | Player-facing choice text |
+| `goto` | string | Node ID to navigate to. Omit to end conversation. |
+| `condition` | Condition | Only show if condition passes |
+| `action` | NodeAction | Fires when selected, BEFORE navigating to `goto` |
#### Multi-message nodes
-`messages` is a list of NPC lines shown one at a time. The player presses
-enter to advance through each message. After the last message, the node's options
-(if any) are displayed. This is the preferred way to deliver NPC monologues — no
-named nodes or `goto` chains needed:
+`messages` is a list of NPC lines shown one at a time with `[enter to continue]` prompts:
```yaml
nodes:
sign_reminder:
messages:
- "\"Yeah, just us two! Not a lot of people heading into the belt these days.\""
- - "\"But we'll be landing shortly! Please look at the information sign.\""
+ - "\"But we'll be landing shortly! Look at the information sign.\""
options:
- text: "\"Okay, thanks.\""
- text: "\"Goodbye.\""
```
-Each `[enter to continue]` prompt is automatic. The node's action fires once on
-entry, before the first message. Node conditions still work normally —
-if the condition fails, the entire sequence is skipped via `goto`.
-
-If a multi-message node has no options after the last message and `goto` is set, it
-auto-advances to the target node. Non-empty input during messages cancels the
-conversation (just like invalid input does during option selection).
-
#### Randomized messages
-`message` accepts either a plain string or a list. A list picks one entry at random each
-time the node is visited:
+Use a list under `message` to pick one at random each time the node is visited:
```yaml
nodes:
@@ -368,38 +300,30 @@ nodes:
#### Linear auto-advance
-When a node has no options (or all its options are hidden by conditions) and `goto` is set,
-the conversation automatically advances to the target node. This creates dialogue chains
-without prompting the player:
+When a node has no visible options and `goto` is set, it auto-advances:
```yaml
nodes:
monologue_1:
- message: "The elder clears his throat and begins..."
+ message: "The elder clears his throat..."
goto: monologue_2
monologue_2:
message: "\"Long ago, before the asteroid was settled...\""
goto: monologue_3
monologue_3:
- message: "\"...a great darkness fell upon these halls.\" He pauses."
+ message: "\"...a great darkness fell upon these halls.\""
goto: choice_point
choice_point:
- message: "\"Do you understand the weight of what I'm telling you?\""
+ message: "\"Do you understand?\""
options:
- text: "\"I think so.\""
- text: "\"Not really.\""
```
-If a node has both `goto` and options, the options take priority — `goto` is only used
-when no options are visible.
-
#### Conditional nodes
-A node can have its own `condition` that gates the entire node — message, action,
-and options. When the condition fails, the node is skipped and the conversation
-automatically advances to `goto` (if set). This lets you build branching first
-messages or entire mutually-exclusive conversation paths based on player flags,
-items, or any other condition:
+A node with a `condition` gates the entire node. When the condition fails, the node is skipped
+and goes to `goto`. This lets you branch the entire conversation based on player state:
```yaml
nodes:
@@ -407,107 +331,134 @@ nodes:
condition:
player_flag: finished_tutorial
not: true
- message: "\"Welcome, newcomer! Need any help getting started?\""
+ messages:
+ - "\"Welcome, newcomer! Need help getting started?\""
goto: returning_player
options:
- text: "\"Yes, where do I go?\""
goto: directions
- - text: "\"I'll figure it out.\""
returning_player:
- message: "\"Ah, you're back! I heard you handled that raid beautifully.\""
+ messages:
+ - "\"Ah, you're back! I heard you handled that raid.\""
options:
- text: "\"The loot was worth it.\""
- - text: "\"Barely made it out alive.\""
- directions:
- message: "\"Head north through the gate and follow the road.\""
+```
+
+When `finished_tutorial` is set, `start` fails → jumps to `returning_player`. When unset,
+the player sees the newcomer message and options. Node conditions compose with option
+conditions: if the node condition passes, each option's condition is checked independently.
+
+#### Option-level actions
+
+Actions on options fire when selected, BEFORE navigating to `goto`:
+
+```yaml
+nodes:
+ start:
+ messages:
+ - "Houses are 10 credits. Interested?"
options:
- - text: "\"Thanks!\""
+ - text: "Yes, I'll buy one."
+ action:
+ credits: -10
+ set_player_flags:
+ owns_house: true
+ goto: purchased
+ condition:
+ player_flag: owns_house
+ not: true
+ - text: "No thanks."
+ purchased:
+ messages:
+ - "The plot is yours. Use the Estate Directory to enter."
+ options:
+ - text: "Thanks!"
+ goto: start
```
-When `finished_tutorial` is set, the `start` node's condition fails, so the player
-never sees "Welcome, newcomer" — it jumps straight to `returning_player`. When the
-flag is unset, the condition passes and the player sees the newcomer message and its
-options.
+If an option has both `action` and `goto`, action runs first, then navigation.
+
+#### Node/option action vocabulary
+
+| Field | Effect |
+|-------|--------|
+| `set_global_flags` | Sets global flags (shared by all players) |
+| `set_player_flags` | Sets player-local flags (per-character, saved to YAML) |
+| `give_item` | Gives an item to the player's inventory |
+| `take_item` | Removes an item from the player's inventory |
+| `teleport` | Moves the player to a room ID |
+| `heal` | Restores that many hitpoints (clamped to MaxHP) |
+| `credits` | Positive = award, negative = charge |
+| `aps_node` | Marks this room's APS node as unlocked |
+
+All fields in a single action are processed together:
+
+```yaml
+action:
+ take_item: dragon_head
+ give_item: dragon_slayer_medal
+ set_player_flags:
+ dragon_quest: complete
+ dragon_slain: true
+ heal: 99
+ teleport: 1
+```
-Node conditions compose cleanly with option conditions. When the node condition
-passes, each option's own condition is then checked independently to determine
-visibility — exactly as option conditions always work. When the node condition
-fails, the node (and all its options) are skipped entirely.
+---
### Shops
-A shop is a **root-level `shop:` property on a mob def** (not part of the talk tree).
-Any mob in a room that has a `shop:` block can be traded with directly from the game
-prompt — there is no separate shop mode. Players use the ordinary commands:
+A shop is a **root-level `shop:` property on a mob** (not part of the talk tree). Any mob
+with a `shop:` block can be traded with from the game prompt — no separate shop mode:
```
-list Show the shop inventory of the mob in the room
-buy <item> Buy an item (partial names work, e.g. "buy bait")
+list Show shop inventory
+buy <item> Buy an item (partial names work)
buy <N> <item> Buy a quantity
-buy all <item> Buy as many as you can afford and carry
-sell <item> Sell an item from your inventory
+buy all <item> Buy as many as you can afford/carry
+sell <item> Sell an item
sell <N> <item> Sell a quantity
sell all <item> Sell your entire held stack
```
-When a room has more than one shop mob, disambiguate with the mob name:
-
-```
-list <mob>
-buy <item> from <mob>
-sell <item> to <mob>
-```
-
-If several shops are present and no mob is named, the game replies "Which shop?"
-and lists the options.
+With multiple shops in a room, disambiguate: `list <mob>`, `buy <item> from <mob>`, `sell
+<item> to <mob>`.
#### Pricing
Shops never define per-item prices — prices derive from each item's `value:` field:
-
-- **Buy price** (player buying) is always **100% of the item's value**.
-- **Sell price** (player selling) uses the OSRS store formula:
-
- ```
- sell = value × max(10, buy_percentage − stock × change_percentage) / 100
- ```
-
- where `stock` is the shop's current holding of that item. With the defaults
- (`buy_percentage: 40`, `change_percentage: 3`), an item the shop has none of
- sells for 40% of value, dropping 3% per unit already in stock down to a floor
- of 10% of value (reached at 10 in stock). Selling multiple units in one command
- prices each unit at the rising stock level.
+- **Buy price** is always **100% of the item's value**.
+- **Sell price** uses the OSRS store formula: `value × max(10, buy_percent − stock ×
+ change_percent) / 100`. With defaults (buy_percent: 40, change_percent: 3), an item sells
+ for 40% of value at zero stock, dropping 3% per unit in stock, floor 10%.
#### Stock and restocking
-Each shop item carries a `stock` (its target/maximum, and its starting amount).
-Buying depletes stock — an item at 0 stock is out of stock. Selling raises stock,
-lowering subsequent sell prices. Every `restock_ticks` the current stock moves one
-step toward the configured target (refilling when low, decaying overstock when
-high). An item that omits `restock_ticks` uses the global default from
-`game_constants.shop_default_restock` in `config.yaml` (1000 ticks). Items a player
-sold that the shop didn't originally list are held as dynamic stock that decays
-back to 0.
+Each shop item carries a `stock` (target/max and starting amount). Buying depletes stock;
+selling raises stock, lowering sell prices. Every `restock_ticks` the current stock moves
+one step toward the target. Items with no `restock_ticks` use the global default (1000
+ticks). Items a player sold that the shop didn't list are held as dynamic stock that decays
+to 0.
#### Shop fields
| Field | Description |
-|---|---|
-| `shop.message` | Optional greeting shown at the top of `list`. |
-| `shop.buy_percentage` | Sell price at zero stock, as % of value (default 40). |
-| `shop.change_percentage` | Sell price drop per unit of stock, as % of value (default 3). |
-| `shop.buys_anything` | Whether the shop buys items not in its list (default true). |
-| `shop.items[].item_id` | Item definition ID. |
-| `shop.items[].stock` | Target/maximum and starting stock. |
-| `shop.items[].restock_ticks` | Per-item restock interval (default `game_constants.shop_default_restock`, 1000). |
-
-Full example — General Store:
+|-------|-------------|
+| `shop.message` | Optional greeting shown at top of `list` |
+| `shop.buy_percentage` | Sell price at zero stock, % of value (default 40) |
+| `shop.change_percentage` | Sell price drop per unit of stock, % of value (default 3) |
+| `shop.buys_anything` | Accept items not in its list (default true) |
+| `shop.items[].item_id` | Item definition ID |
+| `shop.items[].stock` | Target/maximum and starting stock |
+| `shop.items[].restock_ticks` | Per-item restock interval (default 1000) |
+
+Example — General Store:
+
```yaml
name: Shopkeeper
protected: true
-hp: 50
shop:
- message: "\"Welcome to the General Store! Have a look around.\""
+ message: "\"Welcome to the General Store!\""
items:
- item_id: fishing_rod
stock: 10
@@ -518,114 +469,78 @@ shop:
talk:
nodes:
start:
- message: "\"Welcome! Type 'list' to see my wares.\""
+ message: "\"Type 'list' to see my wares.\""
options:
- text: "\"Goodbye.\""
```
-A specialty shop that only buys back its own stock sets `buys_anything: false`.
+---
-### Object Interactions (levers, switches, gates) — `on_use` and `on_look`
+### Interaction Reference
-Interactions are the unified, conditional action system shared by objects
-(`on_use`, `on_look`), mobs (`on_kill`), exit traversal
-(`on_traverse`), on_enter steps, room/global triggers, talk nodes, and talk
-options. Each interaction has an optional condition, an optional message,
-and an optional `action` (the full effect vocabulary). When a list of
-interactions is evaluated, the first whose condition passes wins and fires.
+The `Interaction` struct is the shared shape used by `on_use`, `on_look`, `on_kill`,
+`on_traverse`, on_enter steps, and trigger steps. When a list of interactions is evaluated,
+the first whose condition passes wins and fires.
-On objects, `on_use` runs when the player types `use <obj>` (bare) or
-`use <item> on <obj>` (item-specific). Entries with no `item_id` are bare (fire when `use <obj>` is typed) —
-triggered by `use <obj>`:
+| Field | Type | Description |
+|-------|------|-------------|
+| `item_id` | string | `on_use`: required item (empty = bare `use <obj>`). `on_look`: only fires if carrying this item. `on_kill`: only fires if wielding this weapon. |
+| `condition` | Condition | Optional gate |
+| `message` | string | Message shown to the player when this entry fires |
+| `action` | StepAction | Optional effects (see below) |
-```yaml
-name: iron gate
-hidden: true
-description: "A heavy iron gate set into the north wall."
-on_use:
- - condition:
- global_flag: gate_open
- value: true
- not: true
- message: "You push the heavy iron gate open."
- action:
- set_global_flags:
- gate_open: true
-```
+#### StepAction / NodeAction vocabulary
-Item-specific interaction:
-```yaml
-name: bookshelf
-on_use:
- - item_id: dusty_tome
- message: "The bookshelf slides aside, revealing a secret passage!"
- action:
- set_global_flags:
- secret_passage_open: true
-```
+The universal effect vocabulary used by all interaction types, talk node actions, on_enter
+steps, and trigger steps:
-`on_look` runs after the description is shown when the player types
-`look <obj>`. It is a list of interactions (same shape), with `item_id`
-acting as a *has_item* gate: an entry with an `item_id` only fires if the
-player currently carries that item in their inventory (empty = always fires
-on look).
-typically empty:
+| Field | Type | Description |
+|-------|------|-------------|
+| `set_global_flags` | map[string]any | Set global flags (cascades other triggers) |
+| `set_player_flags` | map[string]any | Set player flags (per-character, saved to YAML) |
+| `give_item` | string | Give one unit of an item to inventory |
+| `take_item` | string | Remove one unit of an item from inventory |
+| `teleport` | int | Move player to a room ID |
+| `heal` | int | Restore hitpoints (clamped to MaxHP) |
+| `credits` | int | Add (positive) or deduct (negative) credits |
+| `aps_node` | bool | Mark current room as discovered APS node |
+| `message` | string | Direct message to the player (`%p`/`%v` supported) |
+| `broadcast` | string | Announce to all in the room |
+| `broadcast_global` | string | Announce to all online |
+| `spawn_mob` | string/map | Spawn a transient mob |
+| `despawn_mob` | string | Despawn all transient mobs of this id |
+| `delay` | int | Ticks to wait (on_enter/triggers only) |
+| `condition` | Condition | Per-step gate (on_enter/trigger steps only) |
+
+#### SpawnMobConfig
+
+For transient mob spawning, pass either a bare string (the mob ID) or a map:
```yaml
-name: sign
-on_look:
- - set_player_flags:
- read_sign: true
+spawn_mob: rat
+
+# Equivalent to:
+spawn_mob:
+ id: rat
```
+Full config:
+
+| Field | Description |
+|-------|-------------|
+| `id` | Mob ID to spawn (required) |
+| `owner_only` | Private to the triggering player |
+| `despawn_on_leave` | Fades when owner leaves allowed rooms |
+| `despawn_rooms` | Rooms where the spawn survives |
+| `despawn_ticks` | Despawn countdown in ticks |
+
---
-### Interaction Reference
+### On Kill — Mob Interactions
-The `Interaction` struct is the shared per-entry shape:
-
-| Field | Type | Description |
-| ----------- | ---------- | --------------------------------------------------------------- |
-| `item_id` | string | `on_use`: required item id (empty = bare `use <object>`). `on_look`: only fires if you carry this item. `on_kill`: only fires if you wield this weapon (main_hand or off_hand) when the mob is defeated. |
-| `condition` | Condition | Optional gate (see [conditions](conditions.md)) |
-| `message` | string | Message shown to the player when this entry fires |
-| `action` | StepAction | Optional effects — the full superset below |
-
-The `StepAction` superset is the universal effect container used by every
-interaction type, talk node actions, on_enter steps, and trigger steps. The
-inline-only `NodeAction` fields are the subset historically used by talk
-nodes; the additional sequence-only fields (`message`, `broadcast`,
-`broadcast_global`, `spawn_mob`, `despawn_mob`, `delay`) are available on
-all of them but primarily meaningful for on_enter/triggers (inline callers
-may use `broadcast` to announce to the room — handy for `on_kill`
-announcements).
-
-| Field | Type | Description |
-| ------------------ | -------------- | -------------------------------------------------------------------- |
-| `set_global_flags` | map[string]any | Set global flags (shared by all players; cascades other triggers) |
-| `set_player_flags` | map[string]any | Set player flags (per-character, saved to YAML; cascades) |
-| `give_item` | string | Give one unit of an item to inventory |
-| `take_item` | string | Remove one unit of an item from inventory |
-| `teleport` | int | Move player to a room ID; runs `look` + `on_enter` of the destination |
-| `heal` | int | Restore hitpoints (clamped to MaxHP) |
-| `credits` | int | Add (positive) or deduct (negative) credits (gated by affordability) |
-| `aps_node` | bool | Mark the current room as a discovered APS node on the datapad |
-| `message` | string | Direct message to the player (template vars `%p`/`%v` supported) |
-| `broadcast` | string | Announce to all in the room (`%p`/`%v` substituted per recipient) |
-| `broadcast_global` | string | Announce to all online (`%p`/`%v` substituted per recipient) |
-| `spawn_mob` | string/map | Spawn a transient mob — bare string (id) or full [config](#spawn-mob-config) |
-| `despawn_mob` | string | Despawn all transient mobs of this id (optionally owner-filtered) |
-| `delay` | int | Ticks to wait before this step fires (on_enter/triggers only) |
-| `condition` | Condition | Per-step gate (on_enter steps and trigger steps; evaluated before firing) |
-
-### Mob Interactions (On Kill)
-
-Mobs can define `on_kill` (fires when this mob is defeated — either by a
-combat kill or by a `kind: task` mob's HP draining to zero, the
-"completion" of the work). It is additive — standard loot `drops` still
-hit the ground first, then the first matching interaction fires. An
-entry with an `item_id` only fires if the player is wielding that item
-in a weapon-hand slot (main_hand or off_hand) at the moment of the kill.
+Mobs can define `on_kill` — fires when the mob is defeated. Standard loot `drops` hit the
+ground first, then the first matching interaction fires. An entry with `item_id` only fires
+if the player wields that weapon:
```yaml
name: boss
@@ -633,7 +548,7 @@ combat: ...
on_kill:
- condition:
global_flag: boss_quest_active
- message: "The boss crumbles to dust. The Guardian Stone reverberates!"
+ message: "The boss crumbles to dust!"
action:
set_global_flags:
boss_slain: true
@@ -646,42 +561,16 @@ on_kill:
dragon_slain: true
```
+Task mob `on_kill` works the same way — fires when the work is completed:
+
```yaml
name: reactor panel
task: ...
on_kill:
- - message: "With a final burst of effort, the panel snaps into place."
+ - message: "The panel snaps into place."
action:
set_global_flags:
reactor_repaired: true
set_player_flags:
repaired_reactor: true
```
-
-### Spawn Mob Config
-
-For transient mob spawning (in `spawn_mob:` fields), pass either a bare
-string (the mob ID) or a map with these fields:
-
-```yaml
-spawn_mob: rat
-```
-
-```yaml
-spawn_mob:
- id: boss_add
- owner_only: true
- despawn_on_leave: true
- despawn_rooms: [100, 101]
- despawn_ticks: 30.0
-```
-
-| Field | Description |
-| ----------------- | ------------------------------------------------------------------------ |
-| `id` | Mob ID to spawn (required when a map) |
-| `owner_only` | If true, the spawn is private to the triggering player |
-| `despawn_on_leave`| Trigger-spawned mob fades after leaving the owner's room (grace window) |
-| `despawn_rooms` | List of rooms where the spawn survives without ticking the despawn timer |
-| `despawn_ticks` | Despawn countdown in ticks (float OK; rounded probabilistically via `engine.ToTicks`) |
-
----