aboutsummaryrefslogtreecommitdiff
path: root/building_guide/mobs.md
blob: 31ff109e40d1a82e97bfa81321c491f1523677d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
## Mobs

**The filename is the ID.** A mob is looked up by its filename stem (`goblin.yaml` →
`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.

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."
idle_descriptions:
  - "scribbles something in a small notebook"
  - "gazes skyward at the clouds"
drops:
  remains: "bones"
  loot:
    - item_id: "credits"
      weight: 98
      quantity: 10
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 `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
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
```

### 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) |
| `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) |
| `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. They have **no `combat:` block** at all. They don't show a combat level in room listings or when examined.

```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, no combat section
idle_descriptions:
  - adjusts her name badge
  - smiles politely at passengers
talk:                           # inline talk config
    nodes:
        start:
            condition:                    # only show this node when flag is NOT set
                player_flag: 1001_look_sign
                not: true
            messages:
                - "\"How can I help you?\""
            goto: thanks                  # auto-advance here if condition fails
            options:
                - text: "\"Oh, nothing...\""
                  goto: sign_reminder
                - text: "\"Goodbye.\""
        thanks:
            messages:
                - "\"Thank you for flying with us. Be careful on the stairs.\""
            options:
                - text: "\"Thanks!\""
        sign_reminder:
            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:
                - text: "\"Okay, I'll take a look.\""
```

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 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 multi-message nodes and conditional nodes.

### Shops

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
formula, and stock/restock rules.

```yaml
name: Shopkeeper
protected: true
shop:
    items:
        - item_id: fishing_rod
          stock: 10
        - item_id: hammer
          stock: 10
```

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

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
  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) |

### Assassin Mobs

Assassin (Slayer) mobs restrict combat by assassin level and introduce finishing blows and protective equipment:

```yaml
name: slug
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:
    - item_id: credits
      weight: 100
      quantity: 15
```

Damage-without mobs deal 1.5x damage unless the player has the specified item equipped:
```yaml
name: drone
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 |
|---|---|
| `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 `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
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
      quantity: 40
    - item_id: coal
      weight: 5
      quantity: 1
```

Key points:

- **All combat styles work.** Melee, ranged (consumes ammo as "supplies") and science decks (consume junk) all apply progress. The per-type `*_defense` values steer which method is *efficient* without forbidding any — a rock might have low `crush_defense` but high `stab_defense`.
- **XP and buffs are identical to combat** — driven by your attack style (accurate/aggressive/defensive/balanced) and split into the combat skills. This is a non-violent way to train Accuracy/Strength/Defense/Ranged/Science/Hitpoints.
- **No special equipment.** Task mobs are designed around the weapons and bonuses you already have. There is no separate "tool" equipment set.
- **Progress decays exactly like inverted mob regen.** A task you stop working on slowly loses progress over time (the same regen that heals combat mobs, displayed inverted).
- **One worker at a time**, the same 1-v-1 lock as combat.

---