# Non-player characters (npc.* kits) People that walk, see, hear and react, as data: the base kit, the ambient crowd of a city, and the conversations. Readable offline: `gripforge docs npcs`, or the MCP tool `gripforge_docs` (topic `npcs`). ## What it is for | Kit | What it adds | | --- | --- | | `npc.core` | the `npcs` collection: spawning in zones, locomotion on the ground or in the air, a sight cone and hearing, memory of the last known player position, a behaviour state machine (idle, wander, patrol, flee, investigate) other kits extend | | `npc.pedestrian` | a crowd wandering the sidewalks of a road network, fleeing fast vehicles, knocked down when hit | | `npc.dialogue` | typed lines and choices when the player talks to an NPC (described in the Quests and dialogue guide) | Enemies are NPCs with health and attacks: see the Enemies guide. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "npc.core" } gripforge_gamekit_configure { project_id: "gkp_…", id: "npc.core", config: { "maxNpcs": 48 } } gripforge_gamekit_install { project_id: "gkp_…", id: "npc.pedestrian" } ``` `npc.core` requires `world.ground`, `spawn.entities`, `physics.port` and `game.engine`; `npc.pedestrian` requires `npc.core` and a road network (`world.roads`, from the district kit). When the game has `ai.navigation`, NPCs follow collision-derived routes (closed or moving colliders invalidate them; an unreachable goal stops the NPC instead of cutting straight through walls). ## The NPC document ```json { "schema": "gripforge.npc/1", "id": "guard", "faction": "town", "locomotion": { "type": "walk", "speed": 1.6, "runSpeed": 4 }, "perception": { "sight": { "range": 18, "fov": 110 }, "hearing": 4, "memory": 8 }, "behavior": { "initial": "patrol", "behaviors": ["patrol", "idle", "investigate", "flee"], "params": { "patrol": { "route": [[0,0,0],[10,0,0]], "wait": 2, "loop": true } } }, "assets": { "model": "guard" }, "spawn": { "zones": ["gate"], "count": 2 } } ``` `assets.model` names a model slot of the project; `spawn.zones` name zones of `world.zones`. `locomotion.type` is `walk`, `static` (it does not move) or `fly`; `fly` makes an NPC move freely in the air (it keeps the altitude it is sent to and never goes below the ground). A behaviour is registered by name: another kit adds its own (`chase`, `hunt`, `ship_attack`...), and the document lists which ones the NPC may use and which one it starts in. Placing: an archetype of kind `npc` with the component `npc { doc }`, or a `spawn` block in the documents of a scene (spawned on entering the scene, removed on leaving). ## Settings and defaults | Kit | Key | Default | Meaning | | --- | --- | --- | --- | | `npc.core` | `tickHz` | 10 | brain update rate (1 to 30); locomotion runs every tick | | `npc.core` | `maxNpcs` | 64 | live NPC cap | | `npc.core` | `debug` | `false` | emit `npc.state` on every transition (always on in tests) | | `npc.pedestrian` | `count` | 12 | pedestrians on the sidewalks (0 to 64) | | `npc.pedestrian` | `walkSpeed` / `fleeSpeed` | 1.5 / 4 | m/s | | `npc.pedestrian` | `fleeRadius` | 8 | a vehicle closer than this and faster than 5 m/s makes them flee | | `npc.pedestrian` | `vehicleImpacts` | `true` | web: continuous car and pedestrian hit detection, knockdown, recovery | | `npc.pedestrian` | `bloodEffects` | `false` | web: stylized droplets and temporary stains on impacts | | `npc.pedestrian` | `bodyLifetime` | 8 | seconds a lethally hit pedestrian stays visible | | `npc.pedestrian` | `localPopulation` | `false` | web: keep the crowd around the controlled player or car in a large world (`populationRadius` 100, `recycleRadius` 180) | ## Services, events, variables - `npc.locomotion`: `moveTo`, `stop`, path following (a flying NPC keeps its altitude). `npc.perception`: `canSee(entity, target)`, `heard(entity)`, `lastKnown(entity)`. `npc.behavior`: register a behaviour, spawn, switch. - `pedestrian.ai`: `pedestrians()`, `count()`, `setDensity(n)`. - Events: `npc.spawned`, `npc.state` (`{ entity, npc, from, to, target }`), `npc.despawned` (reason `despawn`, `died` or `removed`), and from pedestrians `npc.hit` (`{ npc, entity, by, speed, direction, lethal, blood }`), which a wanted-level kit counts as heat. - Condition `npc_state { npc, state }`, verb `npc_state`. - Slot `pedestrian` (a rigged character, several allowed); a capsule is drawn until a model is bound. ## Hooks and modding | Hook | Payload | What a tap may do | | --- | --- | --- | | `npc.spawn` | `{ npcId, faction, behavior }` | cancel one NPC (the others still spawn) | | `npc.switch` | `{ entity, npcId, faction, from, to }` | change `to`, cancel (the NPC stays in `from`) | | `npc.respawn` | `{ entity, npcId, faction, reason, delay }` | change `delay`, cancel (it does not come back) | | `pedestrian.spawn` | `{ reason, x, y, z }` | cancel (reason `crowd`, `replace` or `recycle`) | | `pedestrian.hit` | `{ npc, by, speed, damage }` | change `damage`, cancel (no damage, no knockdown, no panic) | ```json { "id": "brave_guards", "hook": "npc.switch", "match": { "faction": "town", "to": "flee" }, "set": { "to": "investigate" } } ``` Moddable: `tickHz`, `maxNpcs`, the pedestrian settings (`bloodEffects` aside), and the collection `npcs` (add, patch or remove documents). ## Engines The brains, perception and the built-in behaviours are the same Rust logic on the web and in the native player, and are also delivered to Godot and Unity as files and to Unreal through the shared kit library. The local population of the pedestrians and the blood effects are web options; the native adapters keep the world-wide crowd. The hooks run on the web and in the Rust player. ## When it does not do what you expect | Symptom | Cause and fix | | --- | --- | | An NPC never changes behaviour | the behaviour is not listed in its document's `behavior.behaviors` | | No NPC appears | the spawn zone does not exist, `maxNpcs` is reached, or a tap cancelled `npc.spawn` | | An NPC walks through walls | the game has no `ai.navigation`: add it for routes that follow the collisions | | No pedestrians | the world has no road network, or the `pedestrian` slot is unbound (a capsule is drawn but the project is not reported playable) | | A pedestrian is never hit | `vehicleImpacts` is off, or a `pedestrian.hit` tap cancels it |