## Objects Objects interact with the world through **inline behavior configs** under `gather:`, `talk:`, `use:`, or `safespot:` keys. There is no separate behavior directory — everything goes directly in the object's YAML. ### Naming & file organization **The filename is the ID.** An object is looked up by its filename stem (`copper_rock.yaml` → `copper_rock`). Rooms reference the object by this bare ID. Do not put an `id:` field in the file — it is ignored. (A nested `- id:` under a room's `objects:` list is a *reference* to an object by filename, and is still required.) - **Generic, shared objects** (rocks, trees, altars, stations) live flat in `data/objects/`. - **Room-specific one-offs** (signs, set-dressing) that are description-only should be defined as **local objects** in the room (see below). If a one-off needs interactable behavior (`gather`/`talk`/`use`/etc.) it must be a file — place it in `data/objects/unique/`, e.g. `data/objects/unique/1002_sign.yaml`. Loading walks `data/objects/` recursively, so subdirectories are purely organizational. All filename stems must be globally unique across all of `data/objects/`. ### Local objects (defined in the room) Room-specific description/scenery objects can be defined directly inside the room's `objects:` list instead of as separate files. An entry is **local** when it carries a `name:` (or any other content field — `description`, `aliases`, `inroom_description`, `color`, `hidden`, `on_look`). An entry with only `- id: ` is a plain **reference**: ```yaml # in data/rooms/intro/1001.yaml objects: - id: workbench # reference: resolves to data/objects/workbench.yaml - name: window # local: identity from name, no file hidden: true description: "A thick trim with rounded bolt heads frames the tiny window." - name: sign aliases: [safety card, frame] inroom_description: "A large framed sign is mounted near the cockpit door." description: "Safety instructions are printed in bold lettering." on_look: - set_player_flags: 1001_look_sign: true ``` - **Identity comes from `name`.** Internally the object's id is the name, normalized (lowercased, spaces kept — `"Instrument Panel"` → `instrument panel`). - **Identity is room-scoped.** Two rooms may each have an object named `sign` without conflict. - **Each object in a room must have a unique name.** Two distinct objects in the same room with the same name is a startup error. - **Partial-name siblings are fine.** `rusty sign` and `shiny sign` can coexist; `look sign` matches both with "which one?", while `look rusty sign` resolves directly. - Local objects support only the **passive subset**: `name`, `aliases`, `color`, `hidden`, `inroom_description`, `description` (including conditional variants), and `on_look`. - Interactable behavior (`gather`, `talk`, `use`, `safespot`, `steal`, `guard_mob`, `removal_item`, `on_use`, craft stations) **must** be standalone files; startup validation errors if those appear locally. - Local objects are re-read from the room file on every access, so edits take effect immediately (file objects are cached after first load). Prefer local for one-room flavor; keep generic/reusable/interactable objects as files. --- ### Hidden objects Objects with `hidden: true` don't appear in the room's object listing. Players discover them by reading room descriptions or trying commands. The object is still fully interactable — `push gate`, `look gate`, `hide outcrop` all work. ```yaml name: stone lever hidden: true description: "A cleverly concealed lever behind a loose stone." on_use: - condition: global_flag: secret_passage_open not: true message: "You pull the lever. Grinding echoes from the east." action: set_global_flags: secret_passage_open: true ``` Room description hints at it: ```yaml description: "A dusty corridor. One of the wall stones looks slightly out of place." ``` #### Aliases By default an object matches its `name` (word-prefix matching). Add `aliases` for extra names players can type: ```yaml name: landing craft # matches "landing", "craft", "landing craft" aliases: [shuttle, ship] # also matches "shuttle" and "ship" hidden: true ``` If a player's input matches more than one distinct object, `look` lists the candidates ("That's ambiguous, which one?") — keep aliases specific enough to avoid overlap. #### Conditional object descriptions An object's `description` can be a plain string or a list of conditional variants (first matching condition wins). If **none** of the variants match, the object is treated as absent for that player — `look ` reports nothing is there. Combined with `hidden: true`, the same object can appear only while a player's flags warrant it (e.g. during an intro) and vanish on return visits: ```yaml name: crowd of people aliases: [people, crowd, passengers] hidden: true description: - condition: # after the pilot arrives all_of: - player_flag: boarded not: true - player_flag: lined_up text: "The passengers have formed a single-file line." - condition: # before the pilot arrives all_of: - player_flag: boarded not: true - player_flag: lined_up not: true text: "A couple dozen anxious passengers mill about the pad." # once `boarded` is set, no variant matches → the crowd is gone ``` A bare string in `description:` maps to a single unconditional entry, so a plain object is always present. --- ### Gather behavior Gather behaviors (`gather:`) define mining, fishing, and woodcutting object interactions. See the [Gather section of behaviors](behaviors.md#gather-mining-fishing-woodcutting) for the complete reference — SuccessFormula, DropEntry, shared depletion, bird's nests, and all GatherConfig fields. Quick example (copper rock): ```yaml name: copper rock color: "B2" gather: skill: mining 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 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 success_message: "You spot a glint of something valuable!" respawn_timer: 50 respawn_broadcast: "A glint of copper catches your eye from some {name}." ``` --- ### Talk behavior (conversations) Objects can host dialog trees via the `talk:` key. This is the same format as talk configs on mobs. See the [Talk section of behaviors](behaviors.md#talk-dialog-trees) for the full reference — multi-message nodes, randomized messages, conditional nodes, option-level actions, and linear auto-advance. --- ### Interactions — on_use / on_look Objects can define `on_use` and/or `on_look` to react to player actions. Both take a list of interactions; entries are checked top-to-bottom, the first whose `condition` passes wins. The full `Interaction` shape (condition, message, action with the entire StepAction vocabulary) is documented in the [Interaction Reference](behaviors.md#interaction-reference). #### on_use — using items on objects `on_use` runs when the player types `use ` (bare) or `use on ` (item-specific). Entries with no `item_id` are bare: ```yaml name: iron gate hidden: true description: "A heavy iron gate set into the north wall." on_use: - condition: global_flag: gate_open not: true message: "You push the heavy iron gate open." action: set_global_flags: gate_open: true ``` Item-specific: ```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 ``` Puzzle interaction: ```yaml name: crystal slot on_use: - item_id: crystal_key condition: global_flag: crystal_inserted message: "The crystal key is already in the slot." - item_id: crystal_key message: "You insert the crystal key. It clicks into place." action: take_item: crystal_key set_global_flags: crystal_inserted: true ``` Quest item exchange: ```yaml on_use: - 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_global_flags: temple_door_open: true ``` #### on_look — actions when examining an object `on_look` fires AFTER the object's description is shown. An entry with `item_id` only fires if the player carries that item. Supports the full Interaction shape: ```yaml name: sign on_look: - set_player_flags: read_sign: true ``` --- ### Stealable objects Objects can be stealable via the `steal` command. These use drop entries — item IDs or drop table references — resolved by weighted pick: ```yaml name: Market Stall description: "A wooden stall piled with food and sundries." steal: drops: - table: market_stall_steal weight: 1 level: 5 xp: 12 speed: 5 guard_mob: guard guard_range: 2 ``` | Field | Description | |-------|-------------| | `steal.drops` | Array of drop entries (item_id + weight, or table + weight) | | `steal.level` | Required thieving level | | `steal.xp` | XP awarded per successful steal | | `steal.speed` | Ticks per steal attempt (base wait) | | `steal.guard_mob` | Mob def ID that guards this object | | `steal.guard_range` | Room range within which the guard watches (default 0 = same room only) | 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. Mobs can also be stealable (see `mobs.md`). --- ### 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 name: rock outcrop color: "F8" hidden: true inroom_description: "A jagged rock outcrop juts from the floor." description: "A large, jagged rock formation providing natural cover." safespot: tier: 1 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!" - 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 room text hints, quest guidance, or by trying `look `. Safespots only become visible in `look` when the player's effective safespot tier meets the object's `safespot.tier` requirement. Gaining tier through quest completion and achievements gradually reveals new coverage: | Flag | Source | Tier bonus | |------|--------|------------| | `quest_animal_magnetism` | Quest | +1 | | `quest_dragon_slayer` | Quest | +1 | | `achieve_medium_combat` | Achievement | +1 | | `achieve_hard_combat` | Achievement | +2 | #### SafespotConfig fields | Field | Default | Description | |-------|---------|-------------| | `tier` | 0 | Required safespot tier to use this object | | `max_block_size` | `""` (all) | Largest mob size blocked: small/medium/large/massive | | `max_occupants` | 0 (unlimited) | Max players behind this object | | `unsafe_chance` | 0 | Per-tick chance (0.0–1.0) to be forced out of cover | | `decay_ticks` | 0 | Guaranteed ticks before degrading one level | | `decay_chance` | 0 | Per-tick random chance (0.0–1.0) to degrade | | `respawn_on_hide` | false | First occupant resets safespot to max level | | `respawn_ticks` | 0 | Ticks until destroyed safespot respawns (0 = deleted) | | `levels` | required | At least one SafespotLevel entry | #### SafespotLevel fields | Field | Description | |-------|-------------| | `message` | Shown to the player when safespot is at this level | | `degrade_message` | Broadcast to room when safespot degrades FROM this level | #### Three safespot patterns **Standard safespot** — always available, permanent: ```yaml safespot: tier: 1 max_block_size: small respawn_on_hide: true respawn_ticks: 0 levels: - message: "You crouch behind the fence post, using it as cover." ``` **Regenerating safespot** — degrades across hides, respawns after destruction: ```yaml safespot: respawn_on_hide: false respawn_ticks: 300 decay_ticks: 500 decay_chance: 0.01 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!" ``` **Disposable safespot** — permanent degradation, deleted on destruction: ```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!" ``` #### Mob Size Mobs must have a `size` field for safespot blocking to work (small < medium < large < massive, default medium). A safespot with `max_block_size: large` blocks mobs of size small, medium, and large, but not massive. #### Combat integration - A safespot blocks melee attacks from mobs within `max_block_size`. - When a mob's melee is blocked but it has a ranged/science attack type, it switches to its strongest ranged/science attack. - Mobs exceeding `max_block_size` are never blocked and always use melee. - Melee attacks from a safespotted player force them out of cover. - Hiding in combat takes 4 ticks; taking a hit during this cancels the hide. - Hiding out of combat takes 1 tick. - Safespots block all hazard damage (see `hazards.md`). #### Option: safespot_alert Players can customize the alert message when forced out of cover: option safespot_alert "{C4 bold}** DANGER **{/} Cover blown!"