## Objects Objects interact with the world through **inline behavior configs** under `gather:`, `talk:`, or `use:` keys. There is no separate behavior directory — everything goes directly in the object's YAML. Gathering object (mining): ```yaml id: copper_rock name: copper rock color: "178" # xterm-256 color index (0-255) gather: skill: mining level: 1 xp: 17 base_wait: 8 tools: - pickaxe success: 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 weight: 90 depletes: true message: "You manage to mine some {178}copper ore{/}." - table: gem_table weight: 10 depletes: false message: "You spot a glint of something valuable!" respawn_timer: 50 respawn_broadcast: "A glint of copper catches your eye from some {name}." ``` Tree object (woodcutting with shared depletion): ```yaml id: oak_tree name: oak tree color: "113" 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 message: "You get some {113}oak logs{/}." respawn_timer: 14 deplete_timer: 45 nest_chance: 256 respawn_broadcast: "An {name} grows back." ``` Color accepts xterm-256 indices with optional modifiers (`bold`, `dim`, `underline`) and gradients (`g:196,82`). In ANSI mode, extended colors downgrade to the nearest ANSI color. Object interaction (gate, lever — uses `use_interactions:` key): ```yaml id: iron_gate 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 ``` Decorative object (no behavior keys — just a name/description): ```yaml id: lumby_fountain name: town fountain description: "Clear water sparkles in the sunlight." ``` Talk object (NPC conversations — uses `talk:` key): ```yaml id: tool_shed name: Tool Shed description: "A small shed with an open window." talk: nodes: start: message: "\"Welcome to the tool shed!\"" options: - text: "\"What do you have?\"" goto: shop - text: "\"Goodbye.\"" end: true ``` See the [Talk section of behaviors](behaviors.md#talk-dialog-trees) for the full dialog tree format. See [Stealable Objects](#stealable-objects) for theft mechanics. --- ## Use Interactions Objects can define `use_interactions` to handle when a player uses a specific item on the object. Each interaction can check conditions, show a message, and execute actions — using the same condition and action primitives as the talk system. Entries are checked top-to-bottom; the first match with a passing condition wins. Simple message (no action): ```yaml id: anvil name: anvil use_interactions: - item_id: silver_bar message: "You should use this with a mold at a furnace." - item_id: gold_bar message: "You should use this with a mold at a furnace." ``` Puzzle interaction (take item, set flag): ```yaml id: crystal_slot name: crystal slot use_interactions: - item_id: crystal_key condition: flag: crystal_inserted message: "The crystal key is already in the slot." - item_id: crystal_key message: "You insert the crystal key into the slot. It clicks into place." action: take_item: crystal_key set_flags: crystal_inserted: true ``` Quest item exchange: ```yaml use_interactions: - item_id: ancient_scroll condition: player_flag: quest_started message: "You place the scroll on the pedestal. The door rumbles open!" action: take_item: ancient_scroll set_player_flags: quest_complete: true set_flags: temple_door_open: true ``` ### UseInteraction Fields | Field | Type | Description | | ----------- | ---------- | ------------------------------------------------ | | `item_id` | string | Item ID that triggers this interaction | | `condition` | Condition | Optional condition (same as exits/talk/use_interactions) | | `message` | string | Message shown to the player | | `action` | NodeAction | Optional actions (same as talk node actions) | ### Available Actions (same as talk) | Field | Type | Description | | ------------------ | -------------- | ---------------------------------- | | `set_flags` | map[string]any | Set world flags (shared) | | `set_player_flags` | map[string]any | Set player flags (per-character) | | `give_item` | string | Give an item to inventory | | `take_item` | string | Remove an item from inventory | | `teleport` | int | Move player to a room ID | | `heal` | int | Restore hitpoints | ### Available Conditions (same as exits/talk) | Field | Description | | ------------- | -------------------------------- | | `flag` | World flag check | | `player_flag` | Per-character flag check | | `has_item` | Inventory item check | | `value` | Expected value for flag checks | | `not` | Invert the condition | | `all_of` | All sub-conditions must pass | | `any_of` | Any sub-condition must pass | --- ## Stealable Objects Objects can be stealable via the `steal` command. These use the same drop table system as mobs and searches. ```yaml id: market_stall name: Market Stall description: "A wooden stall piled with food and sundries." steal_table: market_stall_steal steal_level: 5 steal_xp: 12 steal_speed: 5 guard_mob: guard ``` | Field | Description | | ------------- | ------------------------------------------------ | | `steal_table` | Drop table ID for loot when stealing | | `steal_level` | Required thieving level | | `steal_xp` | XP awarded per successful steal | | `steal_speed` | Ticks per steal attempt (base wait) | | `guard_mob` | Mob def ID that guards this object (watches it) | When `guard_mob` is set, a mob with that def ID in the same room watches the object on a tick-based cycle (8 ticks watching, 4 ticks looking away). While the guard is watching, steal success chance is halved and failures trigger a confrontation dialog. Use the `sneak` command to see guard watch state changes in real time. --- ## Safespots Safespot objects provide cover that blocks melee attacks. Players use `hide ` to crouch behind them and attack with ranged or science weapons. Melee attacks force the player out of cover. ```yaml id: rock_outcrop name: rock outcrop color: "248" hidden: true inroom_description: "A jagged rock outcrop juts from the floor." description: "A large, jagged rock formation providing natural cover." safespot: tier: 2 max_block_size: large max_occupants: 3 unsafe_chance: 0.008 decay_ticks: 500 decay_chance: 0.01 respawn_on_hide: false respawn_ticks: 300 levels: - message: "The rock formation stands solid." degrade_message: "The rock structure begins crumbling!" - message: "The rock outcrop is cracked and worn." degrade_message: "A few jagged bits are all that's left of the rock outcrop!" - message: "Only a few jagged rocks remain." degrade_message: "The rock outcrop crumbles to nothing!" ``` Safespot objects should be `hidden: true`. They never appear in room descriptions — players discover them through quest guidance, `inroom_description` text in the room's YAML, or by examining objects directly with `look `. ### SafespotConfig Fields | Field | Type | Default | Description | |---|---|---|---| | `tier` | int | 0 | Required safespot tier to use this object | | `max_block_size` | string | `""` (all) | Largest mob size blocked: small/medium/large/massive | | `max_occupants` | int | 0 | Max players that can hide behind this object (0 = unlimited) | | `unsafe_chance` | float64 | 0 | Per-tick chance (0.0-1.0) to be forced out of cover | | `decay_ticks` | float64 | 0 | Guaranteed ticks before object degrades one level | | `decay_chance` | float64 | 0 | Per-tick random chance (0.0-1.0) to degrade one level | | `respawn_on_hide` | bool | false | If true, hiding behind an empty safespot resets it to max level. Only triggers when no occupants are present. | | `respawn_ticks` | float64 | 0 | Ticks until a destroyed safespot respawns (0 = never respawns — object is deleted from the room) | | `levels` | []SafespotLevel | required | At least one level entry | ### SafespotLevel Fields | Field | Type | Description | |---|---|---| | `message` | string | Shown to the player when the safespot is at this level | | `degrade_message` | string | Broadcast to the room when the safespot degrades FROM this level | Levels are ordered from highest to lowest. The first entry is the safespot's best state; the last entry is its weakest state before destruction. When the last level degrades, the safespot is destroyed. ### Three Safespot Patterns The combination of `respawn_on_hide` and `respawn_ticks` creates three distinct use cases: **1. Standard Safespot (always available)** ```yaml safespot: respawn_on_hide: true respawn_ticks: 0 levels: - message: "You crouch behind the rock formation, using it as cover." ``` The safespot starts at full health each time a player hides behind it (first occupant only — subsequent players don't reset the level). Ideal for skilling areas and general exploration. Since `respawn_ticks: 0`, a destroyed safespot would be deleted, but with a single level and no decay, it never degrades. Simple and permanent. **2. Regenerating Safespot (timer-based respawn)** ```yaml safespot: respawn_on_hide: false respawn_ticks: 300 levels: - message: "The rock formation stands solid." degrade_message: "The rock structure begins crumbling!" - message: "The rock outcrop is cracked and worn." degrade_message: "A few jagged bits are all that's left!" - message: "Only a few jagged rocks remain." degrade_message: "The rock outcrop crumbles to nothing!" ``` Degradation persists between hides — a player who leaves and re-hides finds the safespot at its previous level. After total destruction, a timer (`respawn_ticks`) counts down, then the safespot reforms at full health. Suits areas with heavy use where a temporary respite is valuable. **3. Disposable Safespot (boss fight cover)** ```yaml safespot: respawn_on_hide: false respawn_ticks: 0 levels: - message: "The barricade offers solid cover." degrade_message: "The barricade splinters under the assault!" - message: "The barricade is splintering badly." degrade_message: "The barricade shatters completely!" ``` Degradation persists between hides. When the last level degrades, the safespot is **deleted from the room entirely** — it does not respawn. Perfect for boss encounters where a room script places temporary cover that the party consumes during the fight. Once destroyed, it's gone for good (until the boss room re-instantiates it). ### Mob Size Mobs must have a `size` field for safespot blocking to work: ```yaml # data/mobs/cow.yaml size: small # data/mobs/moss_giant.yaml size: large ``` Size ordering: `small` < `medium` < `large` < `massive`. Defaults to `medium` when absent. A safespot with `max_block_size: large` blocks mobs of size small, medium, and large, but not massive. ### Progression Players gain safespot tier by completing quests and combat achievements, which set player flags via talk node actions: | Flag | Source | Tier bonus | |---|---|---| | `quest_animal_magnetism` | Quest completion | +1 | | `quest_dragon_slayer` | Quest completion | +1 | | `achieve_medium_combat` | Combat achievement | +1 | | `achieve_hard_combat` | Combat achievement | +2 | ### Combat Integration - Melee mobs (attack_type: stab/slash/crush) cannot initiate aggro or land hits on safespotted players if the mob's size is within the safespot's `max_block_size`. - Ranged and science mobs (attack_type: ranged/science) ignore safespots entirely. - If a safespotted player attacks with melee, they automatically leave cover. - Hiding in combat takes 4 ticks; if the mob hits during this time, the hide fails. - Hiding out of combat takes 1 tick. ### Option: safespot_alert Players can customize the message shown when forced out of a safespot: option safespot_alert "{196 bold}** DANGER **{/} Cover blown!" Default: `"{196 bold}** Your safespot has been compromised! **{/}"` ---