The pack
fpsEnemies
function fpsEnemies(): Pack<{ fpsEnemies: typeof kit; }>
Compose the first-person enemies under game.fpsEnemies.
Helpers
createHorde
function createHorde(config: HordeConfig, world: EnemyWorld): Horde
Make the enemies of a map, given its EnemyWorld (map.enemyWorld()). Throws, naming the field, on a config it cannot use.
Classes
Horde
class Horde
The enemies of a first-person game on any map that offers an EnemyWorld (cell-dungeon, sector-map): they
stand guard or patrol, see the player within their sight and field of view, react after a moment, hear shots
through open doors, chase along the map's routes opening doors on the way, aim and attack (a hitscan shot whose
chance falls with distance, a bite, or a projectile for the game to fly), flinch when hurt, turn on another kind
that hurts them, and fall with their drops, making their sounds as they go. Only a few aim at once. Time moves only
through tick. Get one from createHorde.
constructor(config: HordeConfig, world: EnemyWorld)get state(): HordeState— A copy of the state.list(): Enemy[]— Every enemy, dead ones too (copies).get alive(): number— Enemies still standing.def(id: string): EnemyDef | undefined— An enemy kind's definition.body(def: string): EnemyBody— The body an enemy kind moves as. Throws for an unknown kind.spawn(def: string, x: number, z: number, yaw = 0, mode: 'stand' | 'patrol' = 'stand', ambush = false): string— Put an enemy of kinddefatx, zfacingyaw, standing or patrolling, on the floor there (a flyer above it).ambushleaves it deaf to shots until it sees the player or is hurt. Returns its id.hear(x: number, z: number, radius: number): HordeEvent[]— A noise atx, zheardradiusunits away through open space and open doors: every enemy in reach that is not deaf starts hunting.hitTest(x: number, z: number, yaw: number, range: number, eye?: number, ignore?: string): { id: string; dist: number } | null— The nearest live enemy a ray fromx, zalongyawmeets withinrangeunits before a wall: its id and the distance, or null. What a hitscan pellet hits. With the shooter's eye heighteye, an enemy counts only when the shooter can see it (the shot aims up and down by itself, as the classic games did).ignoreskips one enemy (the shooter, when an enemy fires).hurt(id: string, damage: number, by?: string): HordeEvent[]— Hurt an enemy: it may flinch, and it falls at 0 hit points with its drops.byis who did it: another enemy's id, or nothing for the player. Hurt by the player, it hunts the player; hurt by another kind (with infighting on), it turns on that one.facing(id: string, x: number, z: number, frames = 8): number— Which offrames(1 to 16, default 8) sprite angles shows enemyidfrom a viewer atx, z: frame 0 when it faces the viewer, counting round as it turns away to its right. For eight-angle billboards.control(id: string, puppet: boolean): void— Hand an enemy to something else, or take it back: a puppet keeps its hit points, pain, death and sounds, but the horde neither moves it nor attacks with it.fps-bossesdrives its bosses this way.place(id: string, x: number, z: number, yaw: number, y?: number): void— Put a (puppet) enemy somewhere: position, facing, and feet height (default the floor there), counting the walk.heal(id: string, hp: number): void— Give an enemy back up tohphit points, never above its kind's full health.wake(id: string): HordeEvent[]— Wake an enemy, as if it had seen the player.tick(dt: number, player: EnemyTarget): HordeEvent[]— Run every enemy on bydtseconds (0 to 1) against the player.restore(state: HordeState): void— Put back a saved state. Throws on one that names unknown kinds.
Types
Enemy
interface Enemy
One enemy.
id: string— Unique id,<def>-<n>.def: string— Its kind.x: number— Position in world units.z: number— Position in world units.y: number— Height of its feet (a flyer's, in the air).yaw: number— The engine's yaw it faces.hp: number— Hit points left.mode: EnemyMode— What it is doing.timer: number— Seconds left in a timed mode (aim, pain) or before it reacts.cooldown: number— Seconds until it may attack again.alerted: boolean— Whether it knows where the player is.react: number— Seconds until it reacts to having seen the player, or −1 when it has not.route: EnemyWaypoint[]— The points it is walking to, next first.replan: number— Seconds until it plans its route again.walked: number— Distance walked, for the presenter's walk cycle.target: string | null— Who it is after: null for the player, or another enemy's id after that one hurt it.noise: number— Seconds until its next ambient sound.deaf: boolean— Placed in ambush: deaf to shots until it sees the player or is hurt.puppet: boolean— Driven by something else (a boss controller): the horde leaves its movement and attacks alone.
EnemyAttack
interface EnemyAttack
How an enemy attacks.
kind: 'hitscan' | 'melee' | 'projectile'—'hitscan'(a shot that hits or misses at once),'melee'(a bite or swing within reach) or'projectile'(something thrown that the game flies).damage: [number, number]— Damage,[min, max]whole numbers from 0 to 10000.range: number— Reach in world units, 0.5 to 128.cooldown: [number, number]— Seconds between attacks,[min, max]from 0.05 to 30.accuracy?: number— Hitscan: chance to hit at point blank, 0 to 1; default 0.8. It falls with distance to a third at full range.windup?: number— Seconds spent aiming before the attack lands, 0 to 5; default 0.3 (0.1 for melee).projectile?: { speed: number; radius: number }— Projectile: speed in units per second and radius in units.
EnemyBody
interface EnemyBody
The body the world moves: its size and how it climbs.
radius: number— Radius in world units.height: number— Height in world units.step: number— Highest step up, and deepest step down, it takes.fly: boolean— Whether it flies over steps and drops.
EnemyDef
interface EnemyDef
A kind of enemy.
id: string— Unique id.hp: number— Hit points, 1 to 100000.speed: number— Walking speed in units per second, 0 to 20.chaseSpeed?: number— Chasing speed; defaultspeed.radius?: number— Body radius in units, 0.1 to 4; default 0.3.height?: number— Body height in units, 0.1 to 20; default 1.6. Its eye is at 85% of it; it needs this much headroom.step?: number— Highest step it walks up, and the deepest drop it will walk off, in units, 0 to 20; default 0.5.fly?: number— A flyer: how high above the floor it hovers, in units; it crosses steps and drops freely. Default none.sight?: number— How far it sees, in units, 0 to 256; default 24.fov?: number— Half-angle it sees across, radians, 0 to π; default π/2 (all round its front).reaction?: [number, number]— Seconds from first seeing the player to reacting,[min, max]from 0 to 10; default [0.2, 0.7].attack: EnemyAttack— Its attack.pain?: number— Chance of flinching when hurt and not killed, 0 to 1; default 0.5.painTime?: number— Seconds a flinch lasts, 0 to 5; default 0.35.score?: number— Points for killing it, 0 to 1000000; default 0.drops?: string[]— Things it leaves where it falls, by name; default none.deaf?: boolean— Deaf to shots (it still sees); default false.sounds?: EnemySounds— Its sounds, by name; default none.ambient?: [number, number]— Seconds between itsidleandactivesounds,[min, max]from 0.5 to 600; default [4, 10].
EnemyMode
type EnemyMode = 'stand' | 'patrol' | 'chase' | 'aim' | 'pain' | 'dead'
What an enemy is doing.
EnemySounds
interface EnemySounds
The sounds an enemy makes, by name, for the game to play where the enemy is (HordeEvent sound). Any may be left
out. idle and active repeat at the ambient interval: idle while it has not noticed the player, active
while it hunts.
see?: string— When it notices the player (by sight, a shot heard or being hurt).attack?: string— When it attacks.pain?: string— When it flinches.death?: string— When it dies.idle?: string— Now and then while it waits, unaware.active?: string— Now and then while it hunts.
EnemyTarget
interface EnemyTarget
The player, as the enemies see them.
x: number— Position in world units.z: number— Position in world units.y?: number— Height of their feet, for line of sight over steps and through openings; default none (sight is flat).height?: number— Their height; default 1.6.alive: boolean— Whether there is anyone to attack.moving?: boolean— Whether they are moving (harder to hit); default false.
EnemyWaypoint
interface EnemyWaypoint
A point on an enemy's way, and the door to open before going on, if any.
x: number— Where, in world units.z: number— Where, in world units.door?: string— A door to open first, by the world's id for it.
EnemyWorld
interface EnemyWorld
What enemies need of the map they live on. cell-dungeon and sector-map each give one (map.enemyWorld());
any map that answers these can host a horde.
sees(ax: number, az: number, bx: number, bz: number, ay?: number, by?: number): boolean— An unbroken line from a to b; with heights, through openings in 2.5D maps.move(x: number, z: number, dx: number, dz: number, body: EnemyBody, y?: number): { x: number; z: number; y: number }— Move a body bydx, dz, sliding along walls; where it ends up and the floor under it.yis its feet now.route(fx: number, fz: number, tx: number, tz: number, body: EnemyBody): EnemyWaypoint[] | null— The way from one point to another for this body, next point first, opening unlocked doors; null when there is none.hearing(x: number, z: number, radius: number): (x: number, z: number) => boolean— Who hears a noise atx, zthat carriesradiusunits through open space and open doors.wallDistance(x: number, z: number, yaw: number, max: number, y?: number): number | null— How far a flat ray goes before a wall or closed door, or null withinmax.yis its height, for 2.5D maps.doorShut(door: string): boolean— Whether a door on a route is still shut.openDoor(door: string): boolean— Start a door opening; true when it was shut and now opens.floorAt(x: number, z: number): number— The floor height at a point.settle?(x: number, z: number): { x: number; z: number }— Where a patroller that has just turned should stand: a cell map's cell centre. Default where it is.
HordeConfig
interface HordeConfig
The enemies of one map.
defs: EnemyDef[]— The kinds of enemy, 1 to 64.maxAttackers?: number— Most enemies aiming at once, 1 to 64; default 3.damageScale?: number— Multiplies every enemy's damage, 0 to 10; default 1.infighting?: boolean— An enemy hurt by another of a different kind turns on it; default true.seed?: number— Seed, 0 to 4294967295; default 1.
HordeEvent
type HordeEvent = | { type: 'alerted'; id: string } | { type: 'aiming'; id: string } | { type: 'attack'; id: string; kind: 'hitscan' | 'melee'; hit: boolean; damage: number; target: string | null } | { type: 'projectile'; id: string; x: number; y: number; z: number; yaw: number; pitch: number; speed: number; radius: number; damage: number; target: string | null } | { type: 'pain'; id: string } | { type: 'died'; id: string; x: number; y: number; z: number; drops: string[]; score: number; by: string | null } | { type: 'door'; id: string; door: string; x: number; z: number } | { type: 'sound'; id: string; sound: string; x: number; y: number; z: number }
What the enemies did. target is null when the player is the target, else an enemy's id.
HordeState
interface HordeState
Everything that changes; a save holds exactly this.
enemies: Enemy[]— Every enemy, dead ones too.serial: number— Enemies spawned so far, for ids.rng: number[]— The random stream.


