# Mobs & AI > ๐Ÿ“น **Demos:** [creating mobs & fleeing hunt-NPCs, plus combat and the admin plugin](https://makertube.net/w/tyn8JEMG3CaMbTXid8osdU) ยท [hunting a boar & butchering the carcass](https://makertube.net/w/oVJwYUhCKxA2ZvgvocdhwZ) The **mob & AI engine** ([`src/systems/Mobs.luau`](../src/systems/Mobs.luau)) is the shared, content-free creature substrate that combat (#12), animals (#13) and monsters (#14) all build on. The key idea: **a mob is a Humanoid.** Concretely, a mob is a `Model` tagged **`Mob`** that contains a `Humanoid` and a `PrimaryPart` (its `HumanoidRootPart`). Because of that, a mob is damaged, healed and killed *exactly* like a player โ€” `Humanoid:TakeDamage`, `Humanoid.Health`, `Humanoid.Died` โ€” so [combat](combat.md) has **one** code path for players and mobs, and there are no bespoke "mob HP" systems to keep in sync. > The engine ships **zero** creatures. You supply the rigged model (or let the engine build a blocky > placeholder), the `Mobs` def (stats), and the death/spawn juice via reactions. ## Defining a mob type A mob's stats come from a **`Mobs` registry def** โ€” in code or [no-code via the admin plugin](content-authoring.md): ```lua SurvivorCore.Mobs.register({ id = "husk", faction = "hostile", -- "hostile" | "passive" | "neutral" (picks the AI profile) health = 60, walkSpeed = 5, -- idle/wander speed runSpeed = 15, -- chase / flee speed aggroRange = 40, -- how close a player must be to be noticed (hostile) or fled from (passive) leashRange = 70, -- studs from spawn before a chaser gives up and returns attackRange = 6, -- melee reach of the mob's own attack (hostile) attackDamage = 8, attackCooldown = 1.5, }) ``` Anything you leave out falls back to the **`Mobs` Config** defaults (see Tuning). Every field also has a Config default, so a minimal def is just `{ id, faction }`. ## The behavior profile is data The FSM is one state machine; `faction` selects how it behaves โ€” you don't subclass or script it: | Faction | Behavior | |---|---| | `hostile` | idle/wander near spawn โ†’ **chase** a player in `aggroRange` (with line-of-sight) โ†’ **attack** within `attackRange` โ†’ **return** to spawn if it's dragged past `leashRange`. | | `passive` | idle/wander โ†’ **flee** from a player who comes within `aggroRange` (or who strikes it). | | `neutral` | idle/wander only; **flees briefly** when struck. | ## Placing a mob in the world Two ways, and they coexist: **1. Tag a model (no-code).** Build/import any rigged Model with a Humanoid + PrimaryPart, tag it **`Mob`** (CollectionService), and set one attribute: | Attribute | Meaning | |---|---| | `MobType` | the `Mobs` def id to inherit from (e.g. `"husk"`); blank = the model's `Name` | Per-instance attributes (`Faction`, `Health`, `WalkSpeed`, `RunSpeed`, `AggroRange`, `LeashRange`, `AttackRange`, `AttackDamage`, `AttackCooldown`, `WanderRadius`) **override** the def for that one mob. (The admin plugin's **Mobs** editor writes the def and its **+ Add to World** button drops a tagged placeholder rig for you.) **2. Spawn from code** (`SurvivorCore.Mobs`, available after `start()`): | Function | Behavior | |---|---| | `spawn(mobType, cframe, opts?) -> Model` | clone a creator template (`SurvivorCoreContent.MobModels.`) or build a placeholder, tag + adopt it. `opts.respawn = true` re-spawns it on death after `opts.respawnSeconds` (default Config). | | `adopt(model)` | bring an existing tagged Model under AI control (idempotent). | | `damage(model, amount, source?)` | apply damage to a mob (combat uses this; records the attacker + fires `mob:hit`). | | `getActive() -> { Model }` ยท `isMob(model) -> bool` | the live roster / a membership test. | ```lua SurvivorCore.Mobs.spawn("husk", CFrame.new(40, 5, 20), { respawn = true }) ``` ## Hunting & butchering Give a mob def **carcass fields** and slaying it leaves a **butcherable carcass** โ€” which is simply a tagged [`Gatherable`](harvesting.md) node, so butchering reuses the whole harvesting pipeline (tool gate, per-hit yields, floating HP bar, `gather:*` hooks, progression counters): ```lua SurvivorCore.Mobs.register({ id = "boar", faction = "passive", health = 40, carcassItem = "raw_meat", -- blank = no carcass on death carcassHp = 3, -- butcher interactions to deplete carcassTool = "knife", -- tool type required ("" = bare-hand) carcassYieldMin = 1, carcassYieldMax = 2, -- carcassSeconds = 120, -- lifetime before despawn (0 = stays until depleted) }) ``` The carcass uses a creator template (`SurvivorCoreContent.Carcasses.`) or a plain placeholder, and its prompt reads **"Butcher โ€” carcass"**. Per-type juice keys on `"_carcass"`: ```lua SurvivorCore.Gather.onReaction("boar_carcass", "depleted", function(ctx) scatterBones(ctx.position) end) ``` Butcher counters (`gathers_raw_meat`, โ€ฆ) feed [quests](quests.md) and [achievements](achievements.md) automatically. All six fields are editable in the admin plugin's **Mobs** editor. ## Reactions (the juice) Global `Hooks.on("mob:died", โ€ฆ)` fire for every mob. For behavior tied to **one** mob type, use the reaction API โ€” no core edits: ```lua SurvivorCore.Mobs.onReaction("husk", "died", function(ctx) -- ctx = { instance, mobType, killer?, position? } fadeCorpse(ctx.instance) -- creator content; the engine just removes the body after CorpseSeconds end) ``` Events: `"spawned"` ยท `"hit"` ยท `"attack"` (the mob hit a player) ยท `"died"`. Anims/sounds are content-free โ€” register them in `Assets` under `MobAnims` / `MobSounds`, keyed `mobType.."_"..state` (e.g. `husk_attack`); the FSM plays them if present. ## Tuning `Config.override("Mobs", { TickRate = 0.2, DefaultAggroRange = 40, DefaultLeashRange = 60, DefaultAttackRange = 6, DefaultAttackDamage = 8, DefaultAttackCooldown = 1.5, RequireLineOfSight = true, RespawnSeconds = 30, CorpseSeconds = 5 })` โ€” these are the fallbacks a def (or per-mob attribute) overrides. ## Out of scope (for now) Pathfinding (mobs walk straight via `Humanoid:MoveTo` โ€” open terrain), butchering carcasses into yields (#13), and spawn-zone scattering (#16) are follow-ups that build on this substrate. Combat (#12) is its first consumer โ€” see [combat.md](combat.md).