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
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
|
## 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
aggressive: false
attack_types: [crush] # list: exactly one melee (stab/slash/crush) + optional ranged/science
respawn_ticks: 30
stats:
hp: 7
attack: 1
strength: 1
defense: 1
speed: 5
max_melee_hit: 1 # max hit with melee (for melee-attacking mobs)
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.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
aggressive: true # attacks players on sight
attack_types: [slash]
respawn_ticks: 25
stats:
hp: 12
attack: 1
strength: 3
defense: 3
speed: 5
max_melee_hit: 1 # OSRS-derived: ((strength+9)*(strength_bonus+64)+320)/640
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 |
| `speed` | Attack speed in ticks (default 5) |
| `max_melee_hit` | Maximum melee hit (for melee-attacking mobs). Computed as ((strength+9)*(strength_bonus+64)+320)/640 |
| `max_ranged_hit` | Maximum ranged hit (for ranged-attacking mobs). Computed as ((ranged+9)*(ranged_bonus+64)+320)/640 |
| `max_science_hit` | Maximum science hit (for science-attacking mobs). A set value — magic max hits are not level-derived in OSRS |
#### 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 (used for science-attacking mobs' accuracy) |
| `science_percent_bonus` | Percent boost to science damage (applied to max_science_hit for science-attacking mobs) |
| `ranged_bonus` | Equipment-equivalent ranged attack bonus (used for ranged-attacking mobs' accuracy) |
| `ranged_strength_bonus` | Equipment-equivalent ranged strength bonus (used for max_ranged_hit derivation) |
#### 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) — matching science module element gives +accuracy% and +damage |
| `weakness_percent` | Percent bonus per point of elemental weakness (e.g. 50 = +50% accuracy and +50% base max hit) |
#### Combat-level fields (under `combat:`)
| Field | Description |
|---|---|
| `kind` | `combat` (default) or `task` — task mobs are worksites |
| `aggressive` | Auto-attacks players on sight (false if omitted). Only aggros if player's combat level ≤ mob level × 2 |
| `attack_types` | List of attack types this mob can use. Must contain **exactly one** melee type (`stab`/`slash`/`crush`) and may optionally add `ranged` and/or `science`. The mob uses its melee type in normal combat; when a player is safespotted from melee it switches to its strongest ranged/science type (by max hit). Written as a YAML list, e.g. `[crush]` or `[stab, ranged]`. |
| `respawn_ticks` | Ticks until respawn after death |
| `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).
### On Kill Interactions
Mobs can define `on_kill` — a `[]Trigger` list that fires when the mob is defeated. Standard
loot `drops` hit the ground first, then the first matching Trigger fires (first-match-wins).
An entry with `item_id` only fires if the player is wielding that weapon. See the
[On Kill section of behaviors](behaviors.md#on-kill--mob-interactions) for full examples,
and `triggers.md` for the complete Trigger/Step/Condition reference.
### Stealable Mobs
Stealable mobs have a root-level `steal:` block. On failed steal, the mob turns hostile and attacks.
```yaml
name: Farmer
steal:
level: 10
speed: 4
xp: 15
drops:
- table: farmer_steal
weight: 1
combat:
kind: combat
attack_types: [slash]
respawn_ticks: 25
stats:
hp: 12
attack: 1
strength: 3
defense: 3
speed: 5
max_melee_hit: 1
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.drops` | Array of drop entries: `item_id` vs `table` (mutually exclusive), `weight` (required for pity buckets), `quantity` (override; 0 = use sub-item's own for table / 1 for item) |
| `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
aggressive: false
attack_types: [crush]
respawn_ticks: 25
stats:
hp: 15
attack: 3
strength: 3
defense: 1
speed: 6
max_melee_hit: 1
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
aggressive: true
attack_types: [crush]
respawn_ticks: 35
stats:
hp: 45
attack: 15
strength: 14
defense: 12
speed: 4
max_melee_hit: 2 # drone: strength 14, bonuses 0
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
aggressive: false # task mobs do not fight; this is ignored
attack_types: [crush]
respawn_ticks: 40 # the worksite replenishes after this many ticks
stats:
hp: 40 # units of work to complete (NOT a health bar)
defense: 10 # base difficulty
speed: 5
max_melee_hit: 0 # task mobs do not attack; max hits are unused
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.
---
|