The pack
fpsWeapons
function fpsWeapons(): Pack<{ fpsWeapons: typeof kit; }>
Compose the weapons and ammunition rules under game.fpsWeapons.
Helpers
createArsenal
function createArsenal(config: ArsenalConfig): Arsenal
Make an arsenal. Throws, naming the field, on a config it cannot use.
scaleDamage
function scaleDamage(damage: number, dist: number, falloff?: { from: number; to: number; min: number }): number
Damage after falloff: full up to falloff.from cells, falling linearly to falloff.min of it at falloff.to
cells and beyond. Without a falloff, the damage unchanged. Rounded to a whole number.
Classes
Arsenal
class Arsenal
A player's weapons and ammunition: which are carried, which is in hand, the trigger, rates of fire, raising and
lowering, automatic switching when a weapon runs dry, and pickups. It decides when a shot goes off and with what
pellets and damage; the game resolves each pellet against its world (cell-dungeon's ray, its enemies). Time moves
only through tick. Get one from createArsenal.
constructor(config: ArsenalConfig)get state(): ArsenalState— A copy of the state.get current(): WeaponDef— The weapon in hand.get owned(): string[]— The weapons carried, best last.ammo(type: string): number— Ammunition carried of a type (0 for an unknown type).get rounds(): number | null— Ammunition for the weapon in hand, or null when it needs none.weapon(id: string): WeaponDef | undefined— A weapon's definition, or undefined.get view(): { phase: ArsenalState['phase']; raised: number; recoil: number }— How far the weapon in hand is through raising (0 to 1, 1 when ready), for drawing it coming up or going down.trigger(held: boolean): void— Hold or release the trigger.select(id: string): boolean— Switch to a carried weapon: the one in hand lowers, then this one rises. False when it is not carried or already in hand.cycle(direction: 1 | -1): boolean— Switch to the next (1) or previous (−1) carried weapon that can fire.give(id: string, ammo = 0): ArsenalEvent[]— Pick up a weapon (withammorounds of its type, default 0): it is carried from now on. Switches to it when it is better than the one in hand.addAmmo(type: string, amount: number): number— Add ammunition, up to the type's maximum. Returns how much was taken (0 when full or unknown).max(type: string): number— The most of an ammunition type that can be carried now; 0 for an unknown type.setMax(type: string, max: number): void— Carry more of an ammunition type (a backpack): the maximum becomesmax, 1 to 100000. Unknown types are ignored.tick(dt: number): { shots: WeaponShot[]; events: ArsenalEvent[] }— Run the arsenal on bydtseconds (0 to 10): raising and lowering, cooldowns, and shots while the trigger allows.restore(state: ArsenalState): void— Put back a saved state. Throws on one that names weapons this arsenal lacks.
Types
AmmoDef
interface AmmoDef
An ammunition type.
id: string— Unique id.max: number— Most that can be carried, 1 to 100000.start?: number— How much a new arsenal has; default 0.
ArsenalConfig
interface ArsenalConfig
A player's arsenal.
weapons: WeaponDef[]— Every weapon there is, in order of preference (later is better when switching automatically). 1 to 32.ammo: AmmoDef[]— Every ammunition type.owned?: string[]— Weapons carried at the start; default the first.current?: string— The weapon in hand at the start; default the best owned one with ammunition.autoSwitch?: boolean— Switch to the best owned weapon with ammunition when the one in hand runs dry; default true.seed?: number— Seed for damage and spread, 0 to 4294967295; default 1.
ArsenalEvent
type ArsenalEvent = | { type: 'switched'; weapon: string } | { type: 'empty'; weapon: string } | { type: 'picked-up'; weapon: string }
What a step did besides firing.
ArsenalState
interface ArsenalState
Everything that changes; a save holds exactly this.
owned: string[]— Weapons carried, by id, in the config's order.ammo: Record<string, number>— Ammunition by type.current: string— The weapon in hand (or being raised).phase: 'ready' | 'cooldown' | 'lowering' | 'raising'—'ready','cooldown'(between shots),'lowering'or'raising'.timer: number— Seconds left in the phase.next: string | null— The weapon to raise once this one is lowered, or null.held: boolean— Whether the trigger is held.spent: boolean— Whether this press has already fired (one-shot weapons).shots: number— Shots fired.rng: number[]— The random stream.maxes?: Record<string, number>— Ammunition maximums raised above the config's (a backpack), by type.
WeaponDef
interface WeaponDef
A weapon.
id: string— Unique id, 1 to 100 characters.kind: WeaponKind—'hitscan'(an instant ray per pellet),'projectile'(something that flies) or'melee'(a short reach).damage: [number, number]— Damage per pellet,[min, max]whole numbers from 0 to 10000, drawn evenly.rate: number— Seconds between shots, 0.03 to 10.auto?: boolean— True: fires again while the trigger is held. False (default): one shot per press.ammo?: string— The ammunition type it uses; none (the default) for a weapon that needs none.cost?: number— Ammunition per shot, 1 to 100; default 1.pellets?: number— Pellets (rays or projectiles) per shot, 1 to 64; default 1.spread?: number— Half-angle of the cone pellets scatter in, radians, 0 to 1; default 0.range?: number— Reach in cells, 0.5 to 512; default 64, or 1.5 for melee.falloff?: { from: number; to: number; min: number }— Damage falls from full atfromcells tomin(0 to 1) attocells; default none.projectile?: { speed: number; radius: number; splash?: { radius: number; damage: [number, number]; knock?: number } }— Projectile weapons: speed in units per second, radius in units, and splash damage around where it lands, withknock, how hard the blast pushes bodies away (units per second at its centre; default 0).raise?: number— Seconds to raise it when switched to, 0 to 5; default 0.2.lower?: number— Seconds to lower it when switching away, 0 to 5; default 0.2.noise?: number— How many units away a shot is heard, 0 to 256; default 16, or 0 for melee.sound?: string— The sound of a shot, by name, for the game to play; default none.
WeaponKind
type WeaponKind = 'hitscan' | 'projectile' | 'melee'
How a weapon delivers its damage.
WeaponShot
interface WeaponShot
One shot: what the game resolves against the world.
weapon: string— The weapon's id.kind: WeaponKind— How it delivers damage.pellets: { yaw: number; damage: number }[]— Each pellet: its angle off the aim (radians, positive turns right like the engine's yaw) and its damage before falloff.range: number— Reach in cells.noise: number— Units away it is heard.sound?: string— The weapon'ssound, if it has one.falloff?: { from: number; to: number; min: number }— The falloff to apply by distance withscaleDamage, if any.projectile?: { speed: number; radius: number; splash?: { radius: number; damage: [number, number]; knock?: number } }— Projectile settings, for projectile weapons.


