## 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. ### Gather (mining, fishing, woodcutting) Mining — per-drop depletion on a rock object: ```yaml name: copper rock color: "B2" gather: skill: mining level: 1 xp: 17 # XP awarded per successful gather base_wait: 8 # ticks between attempts tools: # requires item with matching tool_type - pickaxe success: base: 0.40 # 40% base chance per_level: 0.01 # +1% per level above requirement cap: 0.95 # 95% max gather_message: "You swing your pickaxe at the rock..." fail_message: "You chip away but get nothing useful." drops: - item_id: copper_ore 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 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 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{/}`. Non-depleting gather (fishing on a fishing spot object): ```yaml name: fishing spot gather: skill: fishing level: 1 xp: 10 base_wait: 4 tools: - fishing_rod bait: fishing_bait success: base: 0.30 per_level: 0.01 cap: 0.90 gather_message: "You cast your line into the water..." fail_message: "Nothing seems to bite." drops: - item_id: raw_trout weight: 100 depletes: false # never depletes message: "You catch a {45}raw trout{/}!" ``` Woodcutting with shared depletion and bird's nests: ```yaml name: oak tree color: "71" gather: skill: woodcutting level: 15 xp: 37 base_wait: 6 tools: - axe success: base: 0.40 per_level: 0.01 cap: 0.90 gather_message: "You swing your axe at the oak tree..." fail_message: "You swing but get no logs." drops: - item_id: oak_logs 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 respawn_broadcast: "An {name} grows back." ``` Regular tree — always depletes on first gather, no shared timer, no nests: ```yaml name: tree gather: skill: woodcutting level: 1 xp: 25 base_wait: 4 tools: - axe success: base: 0.50 per_level: 0.01 cap: 0.95 gather_message: "You swing your axe at the tree..." fail_message: "You swing but get no logs." drops: - item_id: logs weight: 100 depletes: true # regular tree depletes on first successful gather message: "You get some {6B}logs{/}." respawn_timer: 80 respawn_broadcast: "A {name} grows back." ``` #### GatherConfig Fields | Field | Type | Description | |---|---|---| | `skill` | string | Skill name for level check | | `level` | int | Required level to gather | | `xp` | int | XP per successful gather | | `base_wait` | float64 | Base ticks per action cycle | | `tools` | []string | Required tool_type list (e.g. `[pickaxe]`) | | `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 | | `drops` | []DropEntry | Weighted drop entries | | `respawn_timer` | float64 | Ticks until depleted object respawns | | `respawn_broadcast` | string | Broadcast message 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) | | `per_level` | float64 | Chance increase per level above requirement | | `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 | | `depletes` | bool | Resource depletes on this drop | | `quantity` | int | Amount to drop (1-3 for random) | | `message` | string | Player message on drop | | `level` | int | Skill level required for this drop | | `xp` | int | Bonus XP on this drop | #### Shared depletion explained When `deplete_timer > 0`, the tree has a shared despawn timer: - The 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. - All players chopping the same tree are interrupted when it depletes. Use `deplete_timer` for trees. Use `depletes: true` on individual drops for rocks. #### Bird's nests 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. #### XP drops When `xp > 0`, the gather awards XP on each successful drop. If the player's `xpdrops` toggle is on, the output includes the XP gain: `(+37xp wct)`. ### 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. ```yaml name: Guard talk: nodes: start: message: "\"Halt! This area is restricted.\" The guard eyes you suspiciously." options: - text: "\"What's behind that gate?\"" goto: about_gate - text: "\"I have copper ore.\"" goto: trade_ore condition: has_item: copper_ore - text: "\"I have a pass.\"" goto: has_pass condition: player_flag: got_pass value: true - text: "\"Goodbye.\"" # no goto = end conversation about_gate: message: "\"Supplies. Weapons.\" He shifts his weight. \"Bring me some copper ore and I'll stamp you a pass.\"" action: set_player_flags: talked_to_guard: true options: - text: "\"I'll be back.\"" - text: "\"I have some right here.\"" goto: trade_ore condition: has_item: copper_ore trade_ore: message: "\"Good quality ore.\" He stamps a pass and hands it to you." action: take_item: copper_ore give_item: pass_stub set_player_flags: got_pass: true options: - text: "\"Thanks.\"" has_pass: message: "\"Alright, I'll open the gate.\"" action: set_flags: gate_open: true options: - text: "\"Thanks.\"" ``` #### TalkNode fields | Field | Type | Description | |---|---|---| | `message` | string or list | NPC dialogue. May be a single string or a list of strings (pick one at random). | | `sequence` | []string | NPC monologue — each message shown one at a time, player presses enter to advance. Options appear after the last message. Takes priority over `message` if both present. | | `condition` | Condition | Optional. If the condition fails, the node is skipped entirely — its message, 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. | #### 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_flags` | Sets world 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. | | `cost` | Credits charged for the action. | | `shop` | Opens a buy/sell shop interface. See Shop section below. | | `assign_task` | Assigns a random assassin task based on assassin level. | | `skip_task` | Cancels current assassin task, costs 30 reputation, resets streak. | | `extend_task` | Adds 50% more kills to current task, costs 30 reputation. | | `reputation_cost` | Deducts reputation from `assassin_reputation` flag before other actions. | | `sawmill` | Opens sawmill plank conversion interface. | | `aps_node` | Marks this room's APS node as unlocked. | 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: message: "Welcome! Houses are 10 credits. Interested?" options: - text: "Yes, I'll buy one." action: cost: 10 set_player_flags: owns_house: true goto: purchased condition: player_flag: owns_house not: true - text: "No thanks." purchased: message: "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). #### Sequences `sequence` is a flat list of NPC messages 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: ```yaml nodes: sign_reminder: sequence: - "\"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.\"" options: - text: "\"Okay, thanks.\"" - text: "\"Goodbye.\"" ``` Each `[enter to continue]` prompt is automatic. The node's action fires once on entry, before the first sequence message. Node conditions still work normally — if the condition fails, the entire sequence is skipped via `goto`. If a sequence node has no options after the last message and `goto` is set, it auto-advances to the target node. Non-empty input during a sequence 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: ```yaml nodes: greeting: message: - "\"Hello, traveller.\"" - "\"Ah, you again.\"" - "\"A visitor! Pull up a chair.\"" - "\"Back so soon?\"" options: - text: "\"Hello.\"" ``` #### 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: ```yaml nodes: monologue_1: message: "The elder clears his throat and begins..." 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." goto: choice_point choice_point: message: "\"Do you understand the weight of what I'm telling you?\"" 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. A node with a shop action ignores `goto` entirely. #### 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: ```yaml nodes: start: condition: player_flag: finished_tutorial not: true message: "\"Welcome, newcomer! Need any 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.\"" options: - text: "\"The loot was worth it.\"" - text: "\"Barely made it out alive.\"" directions: message: "\"Head north through the gate and follow the road.\"" options: - text: "\"Thanks!\"" ``` 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. 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. #### Shop (buy/sell interface) The `shop` node action opens a dedicated buy/sell interface. The player can browse items, buy with credits, and sell items back. Define a shop on a talk node: ```yaml action: shop: message: "What would you like to buy or sell?" items: - item_id: fishing_rod buy_price: 10 sell_price: 2 - item_id: fishing_bait buy_price: 2 sell_price: 0 ``` | Field | Description | |---|---| | `shop.message` | Greeting shown when entering the shop. | | `shop.items` | List of items for sale. | | `item_id` | Item definition ID. | | `buy_price` | Credits to buy from shop. | | `sell_price` | Credits shop pays (0 = won't buy). | Full example — General Store: ```yaml name: General Store Clerk talk: nodes: start: message: "\"Welcome to the General Store!\"" options: - text: "\"I'd like to browse.\"" goto: shop - text: "\"Goodbye.\"" shop: message: "\"Take your time.\"" action: shop: message: "What would you like to buy or sell?" items: - item_id: fishing_rod buy_price: 10 sell_price: 2 - item_id: hammer buy_price: 20 sell_price: 5 options: - text: "\"I'm done.\"" goto: start ``` When the player leaves the shop, the current node's options are shown (or auto-advance fires if no options are visible). Use `goto: start` to loop back to the main greeting. ### Object Interactions (levers, switches, gates) Interaction configs go under the `use_interactions:` key on objects. Entries with no `item` field are bare interactions triggered by "use" or "push"/"pull": ```yaml name: iron gate hidden: true description: "A heavy iron gate set into the north wall." use_interactions: - condition: flag: gate_open value: true not: true message: "You push the heavy iron gate open." action: set_flags: gate_open: true ``` Item-specific interaction: ```yaml name: bookshelf use_interactions: - item: dusty_tome message: "The bookshelf slides aside, revealing a secret passage!" action: set_flags: secret_passage_open: true ``` ### Use (crafting stations) Use configs go under the `use:` key on objects (e.g. furnaces, ranges): ```yaml name: furnace color: "D0" use: message: "You place the ore in the furnace..." wait: 4 consume: copper_ore: 1 reward: item_id: copper_bar quantity: 1 fail_message: "The ore crumbles to dust." success: base: 0.60 per_level: 0.01 cap: 0.95 skill: smithing level: 1 xp: 15 ``` ---