aboutsummaryrefslogtreecommitdiff
path: root/building_guide/objects.md
diff options
context:
space:
mode:
Diffstat (limited to 'building_guide/objects.md')
-rw-r--r--building_guide/objects.md392
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! **{/}"`
+
+---
+