aboutsummaryrefslogtreecommitdiff
path: root/building_guide/mobs.md
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/mobs.md
parent78b522d57a9ebc691e075523910d7cdaa06b6493 (diff)
downloadthehouseoficarus-8eec7b75ff9c8c368b252373db45fb891732d2ca.tar.gz
mob combat stats overhauled and standardized
Diffstat (limited to 'building_guide/mobs.md')
-rw-r--r--building_guide/mobs.md377
1 files changed, 242 insertions, 135 deletions
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.
---
-
-