## 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 on `) | | `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 on `) | | `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. ---