diff options
| author | historia <[not public]> | 2026-07-09 16:09:35 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-09 16:09:35 -0400 |
| commit | b8c90886ef1f3afb8d908aac89028bd177836cae (patch) | |
| tree | 7e566a7e308e7531bbb7d6a3a39239e789f69a3c /building_guide/behaviors.md | |
| parent | c705ae942573984784ef501bf8198f61f5206ddd (diff) | |
| download | thehouseoficarus-b8c90886ef1f3afb8d908aac89028bd177836cae.tar.gz | |
feat(admin): multi-select with shift+drag for common bulk operations
Diffstat (limited to 'building_guide/behaviors.md')
| -rw-r--r-- | building_guide/behaviors.md | 605 |
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`) | - ---- |
