diff options
| author | historia <[not public]> | 2026-06-24 20:58:25 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-24 20:58:25 -0400 |
| commit | 9d4b799db4868bcab291b83599ff088763cd4ed6 (patch) | |
| tree | 252f0fcc779acf35243983d643d6399ee2e0cd0b /building_guide/objects.md | |
| parent | d25638d98fe63efdeab570ef77e6a6a97f2d7a60 (diff) | |
| download | thehouseoficarus-9d4b799db4868bcab291b83599ff088763cd4ed6.tar.gz | |
feat: new type of non-violent 'combat': work
Diffstat (limited to 'building_guide/objects.md')
| -rw-r--r-- | building_guide/objects.md | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/building_guide/objects.md b/building_guide/objects.md new file mode 100644 index 0000000..427968a --- /dev/null +++ b/building_guide/objects.md @@ -0,0 +1,392 @@ +## 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 <object>` 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 <name>`. + +### 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! **{/}"` + +--- + |
