From b8c90886ef1f3afb8d908aac89028bd177836cae Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Thu, 9 Jul 2026 16:09:35 -0400 Subject: feat(admin): multi-select with shift+drag for common bulk operations --- building_guide/objects.md | 531 ++++++++++++++++++++-------------------------- 1 file changed, 235 insertions(+), 296 deletions(-) (limited to 'building_guide/objects.md') diff --git a/building_guide/objects.md b/building_guide/objects.md index db72cf3..cb38e0c 100644 --- a/building_guide/objects.md +++ b/building_guide/objects.md @@ -1,90 +1,156 @@ ## 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. +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`), and the loader derives `ObjectDef.ID` from that -filename. Rooms reference the object by this bare ID regardless of which folder the file -lives in. **Do not put an `id:` field in the file** — it is ignored. (A nested `- id:` under -a room's `objects:` list is a different thing: a *reference* to an object by its filename, -and is still required there.) - -- **Generic, shared objects** (rocks, trees, altars, stations) use a semantic name and live - flat in `data/objects/` — e.g. `data/objects/copper_rock.yaml`. -- **Room-specific one-offs** (signs, set-dressing, scenery for a single room): if they are - description-only (the passive subset), prefer defining them **locally in the room** (see - [Local objects](#local-objects-defined-in-the-room) below). If a one-off needs - interactable behavior (`gather`/`talk`/`use`/etc.) it must be a file — prefix it with the - room number and place it in `data/objects/unique/`, e.g. `data/objects/unique/1002_sign.yaml` - (ID `1002_sign`, referenced from room `1002`). - -Loading walks `data/objects/` recursively, so subdirectories are purely organizational and -need no changes to room references when a file is moved. They do **not** namespace IDs: every -filename stem must be globally unique across all of `data/objects/`. Use `hidden: true` for -signage/scenery so it doesn't appear in room listings. +**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 in `data/objects/unique/`. An entry in the list -is treated as **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** to a file object, as before. `id:` is reserved -for references; **local objects do not take an `id:`** — their identity comes from the name. +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 derived from the name, no file + - 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: |- - Conditional or multi-line descriptions work exactly like file objects. + 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, surrounding/redundant spaces trimmed, spaces kept — `"Instrument Panel"` → - `instrument panel`). You never write an `id:`; doing so on a local entry is ignored and - warned about at startup. -- **Identity is room-scoped.** Two different rooms may each have an object named `sign` - without conflict — no room-number prefixing needed (unlike the old `data/objects/unique/` - files). -- **Each object in a room must have a unique name.** Two *distinct* objects in the same room - with the same exact name — whether both local, both file references, or one of each — is a - startup **error** (the player could not disambiguate them). Multiple instances of the *same* - object are still fine via repeated references (e.g. two `- id: copper_rock`). -- **Partial-name siblings are fine.** `rusty sign` and `shiny sign` can coexist; `look sign` - matches both and prompts *"which one?"*, while `look rusty sign` resolves directly. + (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 / stateful behavior (`gather`, `talk`, `use`, `safespot`, `steal`, `guard_mob`, - `removal_item`, `on_use`, craft stations) **must** be a standalone object file; - startup validation errors if those appear locally. +- 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): -Gathering object (mining): ```yaml name: copper rock -color: "B2" # xterm-256 color index (00-FF) +color: "B2" gather: skill: mining - tools: - - pickaxe + tools: [pickaxe] success: base: 0.40 per_level: 0.01 @@ -93,51 +159,42 @@ gather: fail_message: "You chip away but get nothing useful." drops: - item_id: copper_ore - level: 1 # required skill level for this drop - xp: 17 # XP awarded when this drop lands + level: 1 + xp: 17 weight: 90 depletes: true - message: "You manage to mine some {B2}copper ore{/}." + success_message: "You manage to mine some {B2}copper ore{/}." - table: gem_table weight: 10 depletes: false - message: "You spot a glint of something valuable!" + success_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 -name: oak tree -color: "71" -gather: - skill: woodcutting - 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 - level: 15 - xp: 37 - weight: 100 - depletes: false - message: "You get some {71}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:C4,52`). In ANSI mode, extended colors downgrade to the nearest -ANSI color. +### 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: -Object interaction (gate, lever — uses `on_use:` key): ```yaml name: iron gate hidden: true @@ -145,7 +202,6 @@ description: "A heavy iron gate set into the north wall." on_use: - condition: global_flag: gate_open - value: true not: true message: "You push the heavy iron gate open." action: @@ -153,67 +209,20 @@ on_use: gate_open: true ``` -Decorative object (no behavior keys — just a name/description): -```yaml -name: town fountain -description: "Clear water sparkles in the sunlight." -``` - -Talk object (NPC conversations — uses `talk:` key): -```yaml -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.\"" -``` - -On-look interaction (runs when a player examines an object with `look `): -```yaml -name: sign -aliases: [notice, board] -description: "A wooden signpost with faded writing." -on_look: - - set_player_flags: - read_sign: true -``` -`on_look` is a list of interactions (same shape as `on_use`). The first entry -whose `item_id` and `condition` pass wins, and fires AFTER the object's -description is shown. An entry with an `item_id` only fires if the player -currently carries that item in their inventory (empty `item_id` = always -fires on look). It uses the same `interaction` shape as `on_use` — supports -`condition`, `message`, and a full `action` (`set_global_flags`, -`set_player_flags`, `give_item`, `take_item`, `teleport`, `heal`, `credits`, -`aps_node`, `broadcast`, `broadcast_global`, `spawn_mob`, `despawn_mob`, -`delay`), per the [interaction reference](behaviors.md#interaction-reference). - -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. - ---- - -## 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 — one fires per use/look). +Item-specific: -Simple message (no action): ```yaml -name: anvil +name: bookshelf on_use: - - 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." + - item_id: dusty_tome + message: "The bookshelf slides aside, revealing a secret passage!" + action: + set_global_flags: + secret_passage_open: true ``` -Puzzle interaction (take item, set flag): +Puzzle interaction: + ```yaml name: crystal slot on_use: @@ -222,7 +231,7 @@ on_use: global_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." + message: "You insert the crystal key. It clicks into place." action: take_item: crystal_key set_global_flags: @@ -230,6 +239,7 @@ on_use: ``` Quest item exchange: + ```yaml on_use: - item_id: ancient_scroll @@ -244,55 +254,24 @@ on_use: temple_door_open: true ``` -### Interaction Fields - -| Field | Type | Description | -| ----------- | ---------- | --------------------------------------------------------------- | -| `item_id` | string | `on_use`: required item id (empty = bare `use `). `on_look`: only fires if you carry this item. (Mobs' `on_kill`: only fires if you wield this weapon in main_hand or off_hand.) | -| `condition` | Condition | Optional condition (same as exits/talk/on_enter/triggers) | -| `message` | string | Message shown to the player when this entry fires | -| `action` | StepAction | Optional actions (see [interaction reference](behaviors.md#interaction-reference)) | - -### Available Actions (same superset as on_enter steps/triggers/talk) - -| Field | Type | Description | -| ------------------ | -------------- | ----------------------------------------------------- | -| `set_global_flags` | map[string]any | Set global flags (shared by all players) | -| `set_player_flags` | map[string]any | Set player flags (per-character, saved to YAML) | -| `give_item` | string | Give an item to inventory (1 unit) | -| `take_item` | string | Remove an item from inventory (1 unit) | -| `teleport` | int | Move player to a room ID | -| `heal` | int | Restore hitpoints (clamped to MaxHP) | -| `credits` | int | Add (positive) or deduct (negative) credits | -| `aps_node` | bool | Mark current room as a discovered APS node | -| `broadcast` | string | Announce to all in the room (uses `%p`, `%v`) | -| `broadcast_global` | string | Announce to all online (uses `%p`, `%v`) | -| `spawn_mob` | string/map | Spawn a transient mob (`id` or full [config](behaviors.md#spawn-mob-config)) | -| `despawn_mob` | string | Despawn all transient mobs of this id | -| `message` | string | Direct message to the player (also valid here) | -| `delay` | int | Ticks to wait before this step (only meaningful in on_enter/triggers) | - -### Available Conditions (same as exits/talk/on_enter/triggers) - -| Field | Description | -| ------------- | ------------------------------------------------------- | -| `global_flag` | Global flag check | -| `player_flag` | Per-character flag check | -| `has_item` | Inventory item check | -| `min_credits` | Minimum credits | -| `value` | Expected value for flag checks | -| `not` | Invert the condition | -| `all_of` | All sub-conditions must pass | -| `any_of` | Any sub-condition must pass | +#### 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 +### Stealable objects -Objects can be stealable via the `steal` command. These use drop entries — either -item IDs or drop table references — resolved by weighted pick. The `steal` command -supports auto-selection when only one target is available, or `steal ` for -explicit targeting, including `steal .` to disambiguate identical defs. +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 @@ -305,33 +284,28 @@ steal: 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 (watches it) | - -Each drop entry: -| Field | Description | -| ------------ | ------------------------------------------------ | -| `item_id` | Item ID (mutually exclusive with `table`) | -| `table` | Drop table ID to sub-resolve (mutually exclusive with `item_id`) | -| `weight` | Weight in the weighted-pick pool | -| `quantity` | Override quantity (0 = use sub-item's own qty for tables, or 1 for items) | +| 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. +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. -Use the `sneak` command to see guard watch state changes in real time. +Mobs can also be stealable (see `mobs.md`). --- -## Safespots +### 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 @@ -344,7 +318,7 @@ hidden: true inroom_description: "A jagged rock outcrop juts from the floor." description: "A large, jagged rock formation providing natural cover." safespot: - tier: 2 + tier: 1 max_block_size: large max_occupants: 3 unsafe_chance: 0.008 @@ -356,65 +330,67 @@ safespot: - 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!" + 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 quest guidance, `inroom_description` text in the room's YAML, or by -examining objects directly with `look `. +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: -### 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 | +| Flag | Source | Tier bonus | +|------|--------|------------| +| `quest_animal_magnetism` | Quest | +1 | +| `quest_dragon_slayer` | Quest | +1 | +| `achieve_medium_combat` | Achievement | +1 | +| `achieve_hard_combat` | Achievement | +2 | -### SafespotLevel Fields +#### SafespotConfig 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 | +| 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 | -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. +#### SafespotLevel fields -### Three Safespot Patterns +| Field | Description | +|-------|-------------| +| `message` | Shown to the player when safespot is at this level | +| `degrade_message` | Broadcast to room when safespot degrades FROM this level | -The combination of `respawn_on_hide` and `respawn_ticks` creates three distinct use cases: +#### Three safespot patterns -**1. Standard Safespot (always available)** +**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 rock formation, using it as cover." + - message: "You crouch behind the fence post, 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)** +**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!" @@ -424,12 +400,7 @@ safespot: 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)** +**Disposable safespot** — permanent degradation, deleted on destruction: ```yaml safespot: @@ -442,57 +413,25 @@ safespot: 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 -### 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. -Mobs must have a `size` field for safespot blocking to work: +#### Combat integration -```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 - -- A safespot blocks a mob's **melee** attacks if the mob's size is within the - safespot's `max_block_size`. -- When a mob's melee is blocked, if it also has a `ranged`/`science` attack type it - switches to its strongest ranged/science attack (by max hit) and keeps attacking. - A mob with only melee is fully blocked (cannot aggro or land hits). -- Mobs whose size exceeds `max_block_size` are never blocked and always use melee. -- 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. +- 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 +#### Option: safespot_alert -Players can customize the message shown when forced out of a safespot: +Players can customize the alert message when forced out of cover: option safespot_alert "{C4 bold}** DANGER **{/} Cover blown!" -Default: `"{C4 bold}** Your safespot has been compromised! **{/}"` - ---- - -- cgit v1.2.3