aboutsummaryrefslogtreecommitdiff
path: root/building_guide
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-01 19:41:34 -0400
committerhistoria <[not public]>2026-07-01 19:41:34 -0400
commit8eec7b75ff9c8c368b252373db45fb891732d2ca (patch)
tree528ab3f235e3082a51b8bd6c8e5ae269c41b4f2b /building_guide
parent78b522d57a9ebc691e075523910d7cdaa06b6493 (diff)
downloadthehouseoficarus-8eec7b75ff9c8c368b252373db45fb891732d2ca.tar.gz
mob combat stats overhauled and standardized
Diffstat (limited to 'building_guide')
-rw-r--r--building_guide/behaviors.md45
-rw-r--r--building_guide/mobs.md377
2 files changed, 266 insertions, 156 deletions
diff --git a/building_guide/behaviors.md b/building_guide/behaviors.md
index c7b091d..ce266c1 100644
--- a/building_guide/behaviors.md
+++ b/building_guide/behaviors.md
@@ -185,7 +185,8 @@ name: Guard
talk:
nodes:
start:
- message: "\"Halt! This area is restricted.\" The guard eyes you suspiciously."
+ messages:
+ - "\"Halt! This area is restricted.\" The guard eyes you suspiciously."
options:
- text: "\"What's behind that gate?\""
goto: about_gate
@@ -202,7 +203,8 @@ talk:
# no goto = end conversation
about_gate:
- message: "\"Supplies. Weapons.\" He shifts his weight. \"Bring me some copper ore and I'll stamp you a pass.\""
+ messages:
+ - "\"Supplies. Weapons.\" He shifts his weight. \"Bring me some copper ore and I'll stamp you a pass.\""
action:
set_player_flags:
talked_to_guard: true
@@ -214,7 +216,8 @@ talk:
has_item: copper_ore
trade_ore:
- message: "\"Good quality ore.\" He stamps a pass and hands it to you."
+ messages:
+ - "\"Good quality ore.\" He stamps a pass and hands it to you."
action:
take_item: copper_ore
give_item: pass_stub
@@ -224,7 +227,8 @@ talk:
- text: "\"Thanks.\""
has_pass:
- message: "\"Alright, I'll open the gate.\""
+ messages:
+ - "\"Alright, I'll open the gate.\""
action:
set_flags:
gate_open: true
@@ -236,9 +240,8 @@ talk:
| Field | Type | Description |
|---|---|---|
-| `message` | string or list | NPC dialogue. May be a single string or a list of strings (pick one at random). |
-| `sequence` | []string | NPC monologue — each message shown one at a time, player presses enter to advance. Options appear after the last message. Takes priority over `message` if both present. |
-| `condition` | Condition | Optional. If the condition fails, the node is skipped entirely — its message, action, and options are not shown. Use with `goto` to auto-advance to a different node. |
+| `messages` | []string | NPC dialogue, shown one at a time. The player presses enter to advance through each message. Options appear after the last message. A single-element list works like a simple one-shot line. |
+| `condition` | Condition | Optional. If the condition fails, the node is skipped entirely — its messages, action, and options are not shown. Use with `goto` to auto-advance to a different node. |
| `action` | NodeAction | Fires when the node is entered (give items, set flags, etc). |
| `options` | []TalkOption | Player choices. If empty and `goto` is set, auto-advances. |
| `goto` | string | Node ID to auto-advance to when there are no visible options OR when the node condition fails. |
@@ -262,14 +265,12 @@ talk:
| `take_item` | Removes an item from the player's inventory. |
| `teleport` | Moves the player to a room ID. |
| `heal` | Restores that many hitpoints. |
-| `cost` | Credits charged for the action. |
-| `assign_task` | Assigns a random assassin task based on assassin level. |
-| `skip_task` | Cancels current assassin task, costs 30 reputation, resets streak. |
-| `extend_task` | Adds 50% more kills to current task, costs 30 reputation. |
+| `credits` | Credits charged (negative) or awarded (positive). |
| `reputation_cost` | Deducts reputation from `assassin_reputation` flag before other actions. |
-| `sawmill` | Opens sawmill plank conversion interface. |
| `aps_node` | Marks this room's APS node as unlocked. |
+> **Planned (not yet implemented):** `assign_task`, `skip_task`, `extend_task`, and `sawmill` are under design and will be added in a future update.
+
All fields in a single action are processed together — give an item, take an item, set flags,
and heal in one node.
@@ -294,11 +295,12 @@ simple transactions:
```yaml
nodes:
start:
- message: "Welcome! Houses are 10 credits. Interested?"
+ messages:
+ - "Welcome! Houses are 10 credits. Interested?"
options:
- text: "Yes, I'll buy one."
action:
- cost: 10
+ credits: -10
set_player_flags:
owns_house: true
goto: purchased
@@ -307,7 +309,8 @@ nodes:
not: true
- text: "No thanks."
purchased:
- message: "Excellent! The plot is yours."
+ messages:
+ - "Excellent! The plot is yours."
options:
- text: "Thanks!"
goto: start
@@ -316,9 +319,9 @@ nodes:
If an option has both an `action` and a `goto`, the action runs first, then the player
navigates to the target node (which may also have its own action, fired on entry).
-#### Sequences
+#### Multi-message nodes
-`sequence` is a flat list of NPC messages shown one at a time. The player presses
+`messages` is a list of NPC lines shown one at a time. The player presses
enter to advance through each message. After the last message, the node's options
(if any) are displayed. This is the preferred way to deliver NPC monologues — no
named nodes or `goto` chains needed:
@@ -326,7 +329,7 @@ named nodes or `goto` chains needed:
```yaml
nodes:
sign_reminder:
- sequence:
+ messages:
- "\"Yeah, just us two! Not a lot of people heading into the belt these days.\""
- "\"But we'll be landing shortly! Please look at the information sign.\""
options:
@@ -335,11 +338,11 @@ nodes:
```
Each `[enter to continue]` prompt is automatic. The node's action fires once on
-entry, before the first sequence message. Node conditions still work normally —
+entry, before the first message. Node conditions still work normally —
if the condition fails, the entire sequence is skipped via `goto`.
-If a sequence node has no options after the last message and `goto` is set, it
-auto-advances to the target node. Non-empty input during a sequence cancels the
+If a multi-message node has no options after the last message and `goto` is set, it
+auto-advances to the target node. Non-empty input during messages cancels the
conversation (just like invalid input does during option selection).
#### Randomized messages
diff --git a/building_guide/mobs.md b/building_guide/mobs.md
index 745fb49..31ff109 100644
--- a/building_guide/mobs.md
+++ b/building_guide/mobs.md
@@ -4,91 +4,143 @@
`goblin`), and the loader derives `MobDef.ID` from it. Do not put an `id:` field in the file
— it is ignored. Rooms reference mobs by that filename.
-Basic combat mob with per-type defense bonuses:
+All combat and stat fields are nested under a `combat:` block. Protected mobs (NPCs, shopkeepers,
+quest givers) do not have a `combat:` block at all — they cannot be attacked.
+
+Basic combat mob:
```yaml
name: "man"
description: "A shabby-looking man."
-attack: 1
-strength: 1
-defense: 1
-hp: 7
-speed: 5
-aggressive: false
-respawn_ticks: 30
-attack_type: crush # stab/slash/crush/ranged/science (default: crush)
-stab_defense: 0 # per-type defense bonuses
-slash_defense: 0
-crush_defense: 0
-science_defense: 0
-ranged_defense: 0
+idle_descriptions:
+ - "scribbles something in a small notebook"
+ - "gazes skyward at the clouds"
drops:
- remains: "bones" # always dropped on death
+ remains: "bones"
loot:
- item_id: "credits"
weight: 98
quantity: 10
- - item_id: "credits"
- weight: 2
- quantity: 150
-idle_descriptions:
- - "scribbles something in a small notebook"
- - "gazes skyward at the clouds"
-combat_descriptions:
- - "is engaged in a fight to the death with %s"
+combat:
+ kind: combat
+ stats:
+ hp: 7
+ attack: 1
+ strength: 1
+ defense: 1
+ attack_type: crush # stab/slash/crush/ranged/science (default: crush)
+ speed: 5
+ aggressive: false
+ respawn_ticks: 30
+ bonuses:
+ attack_bonus: 0
+ strength_bonus: 0
+ science_bonus: 0
+ science_percent_bonus: 0
+ ranged_bonus: 0
+ ranged_strength_bonus: 0
+ defenses:
+ stab_defense: 0 # per-type defense bonuses
+ slash_defense: 0
+ crush_defense: 0
+ science_defense: 0
+ ranged_defense: 0
+ size: small
+ combat_descriptions:
+ - "is engaged in a fight to the death with %s"
```
### Aggressive Mobs
-Mobs with `aggressive: true` auto-attack players entering their room (1-tick delay). Only aggros if the player's combat level is at most double the mob's combat level. Checked on room enter and login.
+Mobs with `combat.stats.aggressive: true` auto-attack players entering their room (1-tick delay). Only aggros if the player's combat level is at most double the mob's combat level. Checked on room enter and login.
```yaml
name: goblin
-aggressive: true # attacks players on sight
-attack: 1
-strength: 3
-defense: 3
-hp: 12
-attack_type: slash
-attack_bonus: 3 # equipment-equivalent attack bonus for mob's attack roll
-strength_bonus: 2 # equipment-equivalent strength bonus for mob's max hit
-stab_defense: 4
-slash_defense: 3
-crush_defense: -2 # weak to crush — negative defense is valid
-science_defense: 0
-ranged_defense: 3
+combat:
+ kind: combat
+ stats:
+ aggressive: true # attacks players on sight
+ hp: 12
+ attack: 1
+ strength: 3
+ defense: 3
+ attack_type: slash
+ speed: 5
+ respawn_ticks: 25
+ bonuses:
+ attack_bonus: 3 # equipment-equivalent attack bonus for mob's attack roll
+ strength_bonus: 2 # equipment-equivalent strength bonus for mob's max hit
+ science_bonus: 0
+ science_percent_bonus: 0
+ ranged_bonus: 0
+ ranged_strength_bonus: 0
+ defenses:
+ stab_defense: 4
+ slash_defense: 3
+ crush_defense: -2 # weak to crush — negative defense is valid
+ science_defense: 0
+ ranged_defense: 3
+ size: small
```
-### Mob Attack/Defense Fields
+### Combat Stats Fields
+
+All combat stats live under `combat.stats:`
| Field | Description |
|---|---|
+| `hp` | Hit points |
| `attack` | Attack level (base accuracy) |
| `strength` | Strength level (base max hit) |
| `defense` | Defense level (base defense) |
-| `hp` | Hit points |
| `ranged` | Ranged level (for ranged/science mobs) |
| `science` | Science level |
+| `attack_type` | Determines which player defense is used against this mob (stab/slash/crush/ranged/science) |
+| `speed` | Attack speed in ticks (default 5) |
+| `aggressive` | Auto-attacks players on sight (false if omitted) |
+| `respawn_ticks` | Ticks until respawn after death |
+
+#### Bonuses (`combat.stats.bonuses:`)
+
+| Field | Description |
+|---|---|
| `attack_bonus` | Equipment-equivalent attack bonus (added to attack roll) |
| `strength_bonus` | Equipment-equivalent strength bonus (added to max hit) |
-| `attack_type` | Determines which player defense is used against this mob |
-| `stab_defense` through `ranged_defense` | Per-type defense bonuses |
-| `weakness` | Elemental weakness (solar/hydro/eco/bio) — for future Science combat |
+| `science_bonus` | Equipment-equivalent science attack bonus |
+| `science_percent_bonus` | Percent boost to science damage |
+| `ranged_bonus` | Equipment-equivalent ranged attack bonus |
+| `ranged_strength_bonus` | Equipment-equivalent ranged strength bonus |
+
+#### Defenses (`combat.stats.defenses:`)
+
+| Field | Description |
+|---|---|
+| `stab_defense` | Stab defense bonus |
+| `slash_defense` | Slash defense bonus |
+| `crush_defense` | Crush defense bonus |
+| `science_defense` | Science defense bonus |
+| `ranged_defense` | Ranged defense bonus |
+| `weakness` | Elemental weakness (solar/hydro/eco/bio/chaos) — for future Science combat |
+
+#### Combat-level fields (under `combat:`)
+
+| Field | Description |
+|---|---|
+| `kind` | `combat` (default) or `task` — task mobs are worksites |
| `size` | Mob size for safespot blocking: small/medium/large/massive (default: medium) |
+| `assassin_level` | Required assassin skill level to attack this mob |
+| `finishing_blow` | Item ID required to kill (mob stays at 1 HP until used via `use <item> on <target>`) |
+| `damage_without` | Item ID for protection — mob deals 1.5x damage if player lacks it equipped |
+| `combat_descriptions` | List of strings shown when the mob is in combat. Use `%s` for the opponent's name |
### Protected/Unique Mobs
-Protected mobs cannot be attacked. All combat and stat fields (`attack`, `strength`,
-`defense`, `hp`, `speed`, `attack_type`, `attack_bonus`, `strength_bonus`,
-`*_defense`, `weakness`, `size`, `drops`, `aggressive`, `respawn_ticks`) are
-**optional for protected mobs** — omit them entirely. Protected mobs don't show
-a combat level in room listings or when examined.
+Protected mobs cannot be attacked. They have **no `combat:` block** at all. They don't show a combat level in room listings or when examined.
-Minimal protected mob (shopkeeper, quest giver, NPC):
```yaml
name: flight attendant
description: A smiling flight attendant in a crisp uniform.
unique: true # displays as "Flight attendant" not "a flight attendant"
-protected: true # cannot be attacked, combat stats are optional
+protected: true # cannot be attacked, no combat section
idle_descriptions:
- adjusts her name badge
- smiles politely at passengers
@@ -98,18 +150,20 @@ talk: # inline talk config
condition: # only show this node when flag is NOT set
player_flag: 1001_look_sign
not: true
- message: "\"How can I help you?\""
+ messages:
+ - "\"How can I help you?\""
goto: thanks # auto-advance here if condition fails
options:
- text: "\"Oh, nothing...\""
goto: sign_reminder
- text: "\"Goodbye.\""
thanks:
- message: "\"Thank you for flying with us. Be careful on the stairs.\""
+ messages:
+ - "\"Thank you for flying with us. Be careful on the stairs.\""
options:
- text: "\"Thanks!\""
sign_reminder:
- sequence:
+ messages:
- "\"Yeah, just us two! Not a lot of people heading into the belt these days.\""
- "\"But we'll be landing shortly! Look at the information sign so you'll know what to do.\""
options:
@@ -119,16 +173,16 @@ talk: # inline talk config
When the player has `1001_look_sign` set, the `start` node's condition fails, so
it skips directly to `thanks` ("Thank you for flying..."). When the flag is unset,
the player sees "How can I help you?" and can choose to be reminded about the sign
-or say goodbye. The `sign_reminder` node uses a `sequence` so the flight attendant
-delivers two lines, pausing for the player to press enter between them, before
-showing the "Okay" option.
+or say goodbye. The `sign_reminder` node uses a multi-message `messages` list so the
+flight attendant delivers two lines, pausing for the player to press enter between
+them, before showing the "Okay" option.
See the [Talk section of behaviors](behaviors.md#talk-dialog-trees) for the full
-dialog tree format, including sequences and conditional nodes.
+dialog tree format, including multi-message nodes and conditional nodes.
### Shops
-Give a mob a root-level `shop:` block to make it a shopkeeper. Players trade with
+Give a protected mob a root-level `shop:` block to make it a shopkeeper. Players trade with
it using the ordinary `list`, `buy`, and `sell` commands from the game prompt.
Prices derive from each item's `value:` field. See the
[Shops section of behaviors](behaviors.md#shops) for the full format, pricing
@@ -137,7 +191,6 @@ formula, and stock/restock rules.
```yaml
name: Shopkeeper
protected: true
-hp: 50
shop:
items:
- item_id: fishing_rod
@@ -146,57 +199,55 @@ shop:
stock: 10
```
-Protected mob with combat stats (for internal testing or debug purposes):
-```yaml
-name: "Guard"
-unique: true
-protected: true
-talk:
- nodes:
- start:
- message: "\"Halt! Who goes there?\""
- options:
- - text: "\"Just passing through.\""
-attack: 5
-strength: 5
-defense: 5
-hp: 30
-speed: 5
-aggressive: false
-respawn_ticks: 60
-attack_type: slash
-attack_bonus: 8
-strength_bonus: 6
-stab_defense: 12
-slash_defense: 15
-crush_defense: 10
-science_defense: -5
-ranged_defense: 12
-idle_descriptions:
- - "scans the area with a watchful eye"
-```
-
Note: `wander_rooms` and `wander_interval` are NOT set on the mob definition. Wander config
is per-instance in the room YAML (see Rooms > Mobs section above).
### Stealable Mobs
-Mobs can be stealable via the `steal` command. On failure, the mob turns hostile and attacks.
+Stealable mobs have a root-level `steal:` block. On failed steal, the mob turns hostile and attacks.
```yaml
name: Farmer
-steal_table: farmer_steal # Drop table for steal loot
-steal_level: 10 # Required thieving level
-steal_xp: 15 # XP per successful steal
-steal_speed: 4 # Ticks per steal attempt
+steal:
+ table: farmer_steal # Drop table for steal loot
+ level: 10 # Required thieving level
+ xp: 15 # XP per successful steal
+ speed: 4 # Ticks per steal attempt
+combat:
+ kind: combat
+ stats:
+ hp: 15
+ attack: 3
+ strength: 3
+ defense: 3
+ attack_type: crush
+ speed: 5
+ aggressive: false
+ respawn_ticks: 40
+ bonuses:
+ attack_bonus: 0
+ strength_bonus: 0
+ science_bonus: 0
+ science_percent_bonus: 0
+ ranged_bonus: 0
+ ranged_strength_bonus: 0
+ defenses:
+ stab_defense: 0
+ slash_defense: 0
+ crush_defense: 0
+ science_defense: 0
+ ranged_defense: 0
+ size: small
+ combat_descriptions:
+ - "swings a shovel at %s"
```
| 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) |
+| `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) |
### Assassin Mobs
@@ -204,13 +255,35 @@ Assassin (Slayer) mobs restrict combat by assassin level and introduce finishing
```yaml
name: slug
-assassin_level: 1 # required assassin level to attack
-finishing_blow: salt # item required to kill (mob stays at 1 HP otherwise)
-attack: 3
-strength: 3
-defense: 1
-hp: 15
-speed: 6
+combat:
+ kind: combat
+ stats:
+ hp: 15
+ attack: 3
+ strength: 3
+ defense: 1
+ attack_type: crush
+ speed: 6
+ aggressive: false
+ respawn_ticks: 25
+ bonuses:
+ attack_bonus: 0
+ strength_bonus: 0
+ science_bonus: 0
+ science_percent_bonus: 0
+ ranged_bonus: 0
+ ranged_strength_bonus: 0
+ defenses:
+ stab_defense: 0
+ slash_defense: 0
+ crush_defense: 0
+ science_defense: 0
+ ranged_defense: 0
+ assassin_level: 1 # required assassin level to attack
+ finishing_blow: salt # item required to kill (mob stays at 1 HP otherwise)
+ size: small
+ combat_descriptions:
+ - "lunges slimily at %s"
drops:
remains: slug_mucus
loot:
@@ -222,47 +295,83 @@ drops:
Damage-without mobs deal 1.5x damage unless the player has the specified item equipped:
```yaml
name: drone
-assassin_level: 15
-damage_without: insulated_gloves # 1.5x damage without this item equipped
-attack: 15
-strength: 14
-defense: 12
-hp: 45
-speed: 4
-aggressive: true
+combat:
+ kind: combat
+ stats:
+ hp: 45
+ attack: 15
+ strength: 14
+ defense: 12
+ attack_type: crush
+ speed: 4
+ aggressive: true
+ respawn_ticks: 35
+ bonuses:
+ attack_bonus: 0
+ strength_bonus: 0
+ science_bonus: 0
+ science_percent_bonus: 0
+ ranged_bonus: 0
+ ranged_strength_bonus: 0
+ defenses:
+ stab_defense: 0
+ slash_defense: 0
+ crush_defense: 0
+ science_defense: 0
+ ranged_defense: 0
+ assassin_level: 15
+ damage_without: insulated_gloves # 1.5x damage without this item equipped
+ size: small
```
| Field | Description |
|---|---|
-| `assassin_level` | Required assassin skill level to attack this mob |
-| `finishing_blow` | Item ID required to kill this mob (mob stays at 1 HP until used via `use <item> on <target>`) |
-| `damage_without` | Item ID for protection — mob deals 1.5x damage if player doesn't have it equipped |
+| `combat.assassin_level` | Required assassin skill level to attack this mob |
+| `combat.finishing_blow` | Item ID required to kill this mob (mob stays at 1 HP until used via `use <item> on <target>`) |
+| `combat.damage_without` | Item ID for protection — mob deals 1.5x damage if player doesn't have it equipped |
Mobs without these fields work normally (assassin_level defaults to 0, finishing_blow and damage_without default to empty).
### Task Mobs (Worksites)
-A task mob is a non-violent "worksite" — build a solar panel, survey flora, prospect a rock — that reuses the entire combat engine. Set `kind: task`. Mechanically its `hp` is a hidden work pool that drains to 0 to **complete** the work; the player sees a progress bar filling toward 100%. A task mob **never attacks back** — the danger (if any) comes from a room `hazard:` (see `hazards.md`). Use the `work` command (or `attack`) on it.
+A task mob is a non-violent "worksite" — build a solar panel, survey flora, prospect a rock — that reuses the entire combat engine. Set `combat.kind: task`. Mechanically `combat.stats.hp` is the work pool that drains to 0 to **complete** the work; the player sees a progress bar filling toward 100%. A task mob **never attacks back** — the danger (if any) comes from a room `hazard:` (see `hazards.md`). Use the `work` command (or `attack`) on it.
+
+Task-specific fields live in the root-level `task:` block:
```yaml
name: solar panel frame
-kind: task # "" / "combat" (default) or "task"
-verb: assemble # flavor verb shown in messages (default: "work on")
-progress_noun: assembly # flavor noun in the progress line (default: "work")
-complete_message: "You bolt the final panel into place and the array hums to life!"
-hp: 40 # units of work to complete (NOT a health bar)
-defense: 10 # base difficulty
-speed: 5
-aggressive: false # task mobs do not fight; this is ignored
-respawn_ticks: 40 # the worksite replenishes after this many ticks
-attack_type: crush
-# Per-type defenses become METHOD EFFICIENCY: low = that approach works well.
-crush_defense: 0 # hammering panels in: efficient
-stab_defense: 40 # stabbing a frame: useless
-slash_defense: 40
-ranged_defense: 10 # a rivet gun / scanner (ranged weapon): works
-science_defense: 10 # an analysis deck (science weapon): works
-drops: # the "payment" for the labour — same drop system as combat
+description: "A half-assembled photovoltaic array."
+task:
+ verb: assemble # flavor verb shown in messages (default: "work on")
+ progress_noun: assembly # flavor noun in the progress line (default: "work")
+ complete_message: "You bolt the final panel into place and the solar array hums to life!"
+idle_descriptions:
+ - "stands half-assembled, panels waiting to be fitted"
+combat:
+ kind: task # "task" triggers the work-engine mode
+ stats:
+ hp: 40 # units of work to complete (NOT a health bar)
+ defense: 10 # base difficulty
+ attack_type: crush
+ speed: 5
+ aggressive: false # task mobs do not fight; this is ignored
+ respawn_ticks: 40 # the worksite replenishes after this many ticks
+ bonuses:
+ attack_bonus: 0
+ strength_bonus: 0
+ science_bonus: 0
+ science_percent_bonus: 0
+ ranged_bonus: 0
+ ranged_strength_bonus: 0
+ # Per-type defenses become METHOD EFFICIENCY: low = that approach works well.
+ defenses:
+ stab_defense: 40 # stabbing a frame: useless
+ slash_defense: 40
+ crush_defense: 0 # hammering panels in: efficient
+ science_defense: 10 # an analysis deck (science weapon): works
+ ranged_defense: 10 # a rivet gun / scanner (ranged weapon): works
+ size: medium
+drops: # the "payment" for the labour — same drop system as combat
loot:
- item_id: credits
weight: 95
@@ -281,5 +390,3 @@ Key points:
- **One worker at a time**, the same 1-v-1 lock as combat.
---
-
-