# The engine's mod API, 0.1

The reference of https://engine-modding.pages.dev/0/ as one file. Each item says where it runs: on the server, on a client, or both.

## Players

The people playing and the server's bots: who they are, where they are, what they hold and press, and the orders that act on them. NPCs are in `players` too; see [players, NPCs and entities](https://engine-modding.pages.dev/0/concepts/entities/).

A player's `name` is what they joined with, and is not unique. Key what you keep by `id` during a match, and by `identity` across joins. `identity` is the same on every join from one install of the game. Only the server reads it.

While a player's game loads the server's files, they are on the server but out of play. The mod hears `joined` once they can play, and `leaving` as they go.

### players

- On both sides: `players: {[number]: Player}`

The players here, people, bots and NPCs, by id: `players[id]`, and `for id, p in players do`.

### me

- On a client: `me: Player`

The player this game is of.

### Fields

What a player, `p`, or an entity, `e`, reads beside the mod's state.

| Name | Type | Side | What it is |
|---|---|---|---|
| `p.id` | `number` | both | Their id, as `players[id]` finds them, theirs while they are here. |
| `p.name` | `string?` | both | The name they joined with, up to 16 bytes, not unique; an NPC's its `set_name`. |
| `p.team` | `number` | both | Their team, a number; `set_team` changes it. |
| `p.bot` | `boolean` | both | Whether the server plays them, not a person: a bot or an NPC. |
| `p.exists` | `boolean` | both | Whether they are still here; after a `wait`, check it. |
| `p.identity` | `string?` | server | The same on every join from one install of the game, as their name is not: key what you keep across joins by it. 32 hex digits, `bot` for the server's bots, nil for an NPC. |
| `p.read_by` | `(Group \| ((Player?, Player) -> boolean))?` | server | Who is shown them; everyone for nil. Written, not read: keep what you gave it in state. |
| `p.spectating` | `boolean?` | client | Whether this player watches another, as a spectator or while dead. |

### net_usage

- On the server: `p:net_usage() → NetUsage?`

Gives what the server sent a player's game over the last second, or nil for a player with none, as a bot.

`rate` is the most the player's game takes and `sent` the bytes sent, both a second; `full` is the share of the rate
sent. `kinds` holds the bytes by what they carried: `own_player`, `players`, `npcs`, `props`, `parts`, `sounds`,
`effects`, `projectiles`, `damage`, `kills`, `hits_too_late`, `lines`, `file_names`, `rules`, `commands`,
`settings`, `channels`, `player_records`, `state_values`, `mod_events`, `netgraph`, `hidden_shapes` and `framing`;
and `declared` those each of the modules' declarations took, its values, its events and the state riding with
entities, by its name, an entity kind's state as `kind.name`; a state, a player state and an event of one name add
up under it. `packets` and `lost` count the packets sent and those found lost, and `left_out` the entities the rate
left out. The server rolls it over once a second, so read it in a handler or a timer. On an NPC it fails.

Returns `NetUsage?`.

#### To warn the players whose rate is nearly full

```luau server
for _, p in players do
    local usage = if p.npc then nil else p:net_usage()
    if usage and usage.full > 0.8 then
        say(`{math.floor(usage.full * 100)}% of your rate is used`, { to = { p } })
    end
end
```

### set_team

- On the server: `p:set_team(team)`

Puts a player on a team, 0 being none, as everyone starts.

The team is kept through death, and every player's game tints the player by it: 1 red, 2 blue, 3 yellow. Each
player's team reaches everyone, however far they are. On an NPC it fails.

| Name | Type | What it is |
|---|---|---|
| `team` | `number`, 0 to 3 | Needed. |

Fails on a value of the wrong type; a value outside its range.

#### To split the players into two teams

```luau server
local count = 0
for _, p in players do
    if not p.npc then
        count += 1
        p:set_team(if count % 2 == 1 then 1 else 2)
    end
end
```

### kick

- On the server: `p:kick(reason)`

Drops a player from the server, their game back at the main menu showing "Kicked:" and the reason.

The reason is cut to 100 bytes, without control characters. The player may join again at once, unlike after an
admin's `/kick`. On an NPC it fails: remove it instead.

| Name | Type | What it is |
|---|---|---|
| `reason` | `string` | Needed. |

Fails on a value of the wrong type.

#### To kick a player who idles

```luau server
p:kick("idle for too long")
```

### `on("connecting")`

- On the server: `on("connecting", fn)`

Someone asks to join. They wait until every handler ends, and are refused if one sets `e.refuse`. The server's bots skip it.

| Name | Type | What it is |
|---|---|---|
| `e.identity` | `string` | Theirs: the same on every join from one install of the game. |
| `e.name` | `string` | The name they join with. |
| `e.address` | `string` | Where they connect from. |
| `e.refuse` | `string?` | Set it to a reason to refuse them, cut to 100 bytes. |

### `on("joined")`

- On both sides: `on("joined", fn)`

A player can play: their game loaded the server's files. On a client, a player came into its list, and those here as it joins.

| Name | Type | What it is |
|---|---|---|
| `e.player` | `Player` | Who joined. |

### `on("leaving")`

- On the server: `on("leaving", fn)`

A player is going. They are out of `players`, but their object reads their values until every handler ends.

| Name | Type | What it is |
|---|---|---|
| `e.player` | `Player` | Who is leaving. |

### `on("left")`

- On a client: `on("left", fn)`

A player left the client's list of players.

| Name | Type | What it is |
|---|---|---|
| `e.player` | `Player` | Who left. |

### `on("screenshot")`

- On the server: `on("screenshot", fn)`

A player saved a screenshot, as their game says, which proves nothing. One a second at most.

| Name | Type | What it is |
|---|---|---|
| `e.player` | `Player` | Who saved it. |

### `on("kicked")`

- On the server: `on("kicked", fn)`

The engine's `/kick` or `/ban` drops a player. Heard before `leaving`.

| Name | Type | What it is |
|---|---|---|
| `e.player` | `Player` | Who is dropped. |
| `e.by` | `Player?` | Who ran it; nil for the server's console. |
| `e.duration` | `number?` | The seconds they are kept out; nil for a kick. |
| `e.reason` | `string?` | Why, if it said. |

## NPCs

Characters the server moves for the mod, such as zombies. `spawn` makes one on the navmesh, the map's walkable floor, and the mod tells it where to go: walk to a point, chase a player, attack. It stops with [`stop`](https://engine-modding.pages.dev/0/reference/entities-and-models/#stop) and goes with [`remove`](https://engine-modding.pages.dev/0/reference/entities-and-models/#remove), as a model does.

An NPC finds its own way around walls and closed doors. When a door opens or a crate moves, the navmesh changes with it within a tick or two, and every NPC finds its way again.

An NPC is listed among the players, so its orders are methods of a player's object; on a person or a bot they fail. NPCs have no health of the engine's. A mod that lets players kill them keeps its own count, and removes them.

### Fields

What a player, `p`, or an entity, `e`, reads beside the mod's state.

| Name | Type | Side | What it is |
|---|---|---|---|
| `p.npc` | `boolean` | both | Whether they are an NPC the mod spawned. |

### attack

- On the server: `x:attack({ animation?, length? }?)`

Plays an NPC's attack: a jab, or the clip it names, so each of an NPC's attacks may have its own.

It hurts no one: the mod hurts whom the attack reaches, as it likes. On a player it fails; on an NPC gone it does
nothing.

| Name | Type | What it is |
|---|---|---|
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.animation` | `animation?` | The `.anim` file it plays; `punch_jab` without. |
| `options.length` | `number?`, 0 to 3600 | How long it lasts, in seconds, above 0; the clip's own without. |

Fails on a value of the wrong type; an option it does not have; a value outside its range.

#### To have a zombie jab, then swing a cross

```luau server
local zombie = spawn({ at = vector.create(0, 0, 1) })
zombie:attack()
wait(0.5)
zombie:attack({ animation = "punch_cross", length = 0.6 })
```

### spawn

- On the server: `spawn(options?) → Player`

Spawns an NPC on the navmesh, standing still, and gives it as a player's object.

It is listed among the players, and takes the orders an NPC takes whether held from `players` or `entities`. It
has no health nor weapons of the engine's: a mod that lets players kill it keeps its own count, and removes it.

| Name | Type | What it is |
|---|---|---|
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.at` | `vector?` | Where it stands, on the navmesh's point nearest; the map's origin without. |
| `options.owner` | `(Player \| number)?` | The player it is about, whom `owner`, `team` and `spectators` pick by. |
| `options.read_by` | `(Group \| ((Player?, Player) -> boolean))?` | Who is shown it; everyone without. |
| `options.kind` | `string?` | Its kind, one `entity_kind` declared on its base; the base's own without. |

Returns `Player`.

Fails on a value of the wrong type; an option it does not have.

#### To spawn a zombie that hunts a player

```luau server
local zombie = spawn({ at = vector.create(0, 0, 20) })
zombie:set_name("Zombie")
zombie:chase(p)
```

#### To spawn a boss its target alone sees

```luau server
local boss = spawn({ at = vector.create(0, 0, 20), owner = p, read_by = owner })
boss:set_speed(2)
```

### walk_to

- On the server: `x:walk_to(point)`

Walks an NPC across the navmesh to the point nearest a point, finding its way around walls and closed doors.

It walks there and stands, until another order. On a player, a model or a part it fails; on an NPC gone it does
nothing.

| Name | Type | What it is |
|---|---|---|
| `point` | `vector` | Needed. |

Fails on a value of the wrong type.

#### To send a zombie to a window

```luau server
local zombie = spawn()
zombie:walk_to(vector.create(12, 0, 4))
```

### chase

- On the server: `x:chase(player)`

Makes an NPC chase a player across the navmesh, following them as they move, until another order.

| Name | Type | What it is |
|---|---|---|
| `player` | `Player \| number` | Needed. |

Fails on a value of the wrong type.

#### To set a zombie on a player

```luau server
local zombie = spawn({ at = vector.create(0, 0, 15) })
zombie:chase(p)
```

### set_speed

- On the server: `x:set_speed(speed)`

Sets how fast an NPC walks, in meters a second.

| Name | Type | What it is |
|---|---|---|
| `speed` | `number`, 0 or more | Needed. |

Fails on a value of the wrong type; a value outside its range.

#### To make a zombie sprint

```luau server
local zombie = spawn()
zombie:set_speed(6)
```

### set_name

- On the server: `x:set_name(name?)`

Names an NPC, so it passes as a player: every player's game lists it with the players, as a scoreboard does, and shows its name, as in chat and a kill feed.

The name is cleaned and cut as a player's is. Nil, or a name empty once cleaned, takes the name away. On a player
it fails: their name is their own.

| Name | Type | What it is |
|---|---|---|
| `name` | `string?` | May be left out. |

Fails on a value of the wrong type.

#### To name a boss

```luau server
local boss = spawn()
boss:set_name("The Butcher")
```

### bumps

- On the server: `bumps(kind, kinds)`

Declares which kinds of body a kind bumps into: a player's body, or an NPC's.

Bodies are round, stop a move as a wall does, can be stood on, and are never pushed. Without it nothing bumps, and
NPCs only steer clear of each other. Each kind's is declared by one module, at its top as it loads: a second
module's fails at load, naming both, and a module declaring it again replaces its own.

| Name | Type | What it is |
|---|---|---|
| `kind` | `"player" \| "npc"` | Needed. |
| `kinds` | `{"player" \| "npc"}` | Needed. |

Fails on a value of the wrong type.

#### To make players and zombies block each other

```luau server
bumps("player", { "player", "npc" })
bumps("npc", { "player", "npc" })
```

## Entities and models

Things in the world the mod places and moves: models, shapes, cameras, and the map's own parts. A model is a `.model` file of the mod's; a shape is a box, a ramp, a cylinder, a sphere or a capsule the mod sizes itself.

Each moves along a path it is given once, so a moving lift costs the network no more than a still one: move to a point, move by an offset, follow a curve, turn, or loop. It can ride another, as a turret rides a lift, and be solid to some things and not others. A solid one carries the players and NPCs standing on it, and pushes those it moves into. One that cannot push a body out of its way pauses, and goes on later by as long; an order to it ends the pause, with no `unblocked`. See [players, NPCs and entities](https://engine-modding.pages.dev/0/concepts/entities/) for ids and objects, and [who sees what](https://engine-modding.pages.dev/0/concepts/groups/) for `read_by`.

Each file a mod names is loaded by every player as the mod first names it. `preload` names files ahead, so players load them as they join.

### entities

- On both sides: `entities: {[number]: Entity}`

The entities by id, `entities[id]`: on the server those the mod made, alive; on a client those it draws.

### Fields

What a player, `p`, or an entity, `e`, reads beside the mod's state.

| Name | Type | Side | What it is |
|---|---|---|---|
| `p.camera` | `CameraView?` | server | Where they see from and which way: their eyes, or the camera `set_camera` gave them. |
| `e.id` | `number` | both | Its id, as `entities[id]` finds it. |
| `e.kind` | `string?` | both | What it is: `part` for the map's, else the kind the mod made it of. |
| `e.exists` | `boolean` | both | Whether it is still there; after a `wait`, check it. |
| `e.owner` | `Player?` | both | The player it is of, as it was made with `owner`. |
| `e.position` | `vector?` | both | Where it is. |
| `e.angles` | `Angles?` | both | How it is turned. |
| `e.move_seconds_left` | `number?` | server | The seconds its move has left; nil once it is still, `math.huge` for a loop. |
| `e.rotation_seconds_left` | `number?` | server | The seconds its turn has left; nil once it is still. |
| `e.solid` | `Solid?` | server | What it blocks: `true`, `false`, or a table of who by kind, of `players` (true or a group), `npcs`, the mod's body kinds, `shots`, `projectiles` and `one_way`. Read back as written. |
| `e.blocked_by` | `{Player}` | server | The players in its way as it last moved. |
| `e.read_by` | `(Group \| ((Player?, Player) -> boolean))?` | server | Who is shown it; everyone for nil. Written, not read: keep what you gave it in state. |

### animate

- On the server: `x:animate(animation, { length?, loop?, start? }?)`

Plays an animation, an `.anim` file, on a player's body, an NPC or a model.

It is told to each player's game once, as it starts, so a model walking a path with its walk looping costs no more
than one standing still, and a player who joins sees one under way. The one that started last shows: a later one
replaces it, and one looping goes on until another starts. A skinned model plays it whole; a body over its upper
half, its legs walking, crouching and jumping as it moves, its hands kept on a weapon it holds. The engine's own, as
a shot or a reload, neither end one nor are ended by one. Four are kept at a time, the one shown, one before it and
two to come: past them, the one starting last is dropped. On a part, a sound or an effect it fails; on what is gone
it does nothing.

| Name | Type | What it is |
|---|---|---|
| `animation` | `animation` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.length` | `number?`, 0 to 3600 | How long one play lasts, in seconds, above 0, at the pace that fits; the clip's own without. |
| `options.loop` | `boolean`, default false | Plays it again and again, until another starts. |
| `options.start` | `number`, -3600 to 5, default 0 | When it starts, in seconds from now: up to an hour ago, seen from part-way through, or up to 5 s ahead. |

Fails on a value of the wrong type; an option it does not have; a value outside its range.

#### To make a player dance

```luau server
p:animate("dance_loop", { loop = true })
```

#### To walk a model of a character along a path

```luau server
local guard = model("mannequin")
guard:animate("walk_loop", { loop = true })
guard:move_along({ vector.create(0, 0, 10), vector.create(10, 0, 10) }, 8, { face = true })
```

### preload

- On the server: `preload(kind, ...names)`

Names files of a kind that every player's game loads as they join, on their loading screen, not as the mod first uses them.

Each file named so far, by this or by any call, is in what a joining player loads before they play. A file first
named later is loaded in the background as it comes: until then a model or an image is not drawn, a sound not heard,
an animation not played, and a weapon neither held nor heard; a sound or an effect then starts from where it would
be by then. The server says once of each, `sound "hum" was not preloaded: clients load it as it is first used`.
Preload what plays in a hurry, at load or before it is needed, as a nuke's sound once it is bought. A name of no
file fails at the call, and a mod names 4096 files of each kind at most.

| Name | Type | What it is |
|---|---|---|
| `kind` | `"model" \| "sound" \| "effect" \| "image" \| "weapon" \| "animation" \| "material"` | Needed. |
| `...names` | `string` | Any number of them. |

Fails on a value of the wrong type.

#### To load a boss's sounds and model at load

```luau server
preload("sound", "zombie_groan1", "explosion_boom")
preload("model", "rocket")
```

### spawn_camera

- On the server: `spawn_camera({ at?, angles? }?) → Entity`

Makes a camera, an entity a player's view can be put on with `set_camera`, and gives it.

Its kind is `camera`. It moves, turns, rides and goes as a model does, but is never drawn nor solid, and only the
players looking through it are sent it, so its `solid` and `read_by` fail.

| Name | Type | What it is |
|---|---|---|
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.at` | `vector?` | Where it is; the map's origin without. |
| `options.angles` | `Angles?` | Which way it looks; unturned without. |

Returns `Entity`.

Fails on a value of the wrong type; an option it does not have.

#### To show a player the map from above

```luau server
local cam = spawn_camera({ at = vector.create(0, 30, -20), angles = { pitch = -0.9 } })
p:set_camera(cam, { look = "target" })
```

### set_camera

- On the server: `p:set_camera(target?, { offset?, look? }?)`

Puts a player's view on an entity, a player or a point; nil puts it back at their eyes.

On their own body it is third person. An offset from a body stays level, so an offset to the left and behind stays
over the shoulder. A point takes no offset and looks with the player's mouse. A player's shots leave from the
view, a bash and a throw from the body. The weapon in first person stays unless `hide_weapon` hides it; on the
player's own body none is drawn. A camera outlasts death and goes with a new match; one on what goes is back at
the eyes. It takes effect a ping after the call. On a player who left, or what is gone, it does nothing.

Whoever watches the player sees through their camera too; a dead player with a camera of their own watches no one.
Sounds are heard where the camera is, unless a client script hears them at the body (`view:set`). What the view is
on, and what that rides, is sent to the player whatever its `read_by`.

| Name | Type | What it is |
|---|---|---|
| `target` | `(Player \| Entity \| number \| vector)?` | May be left out. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.offset` | `vector?` | How far from what it is on, turned by it: from a body's eyes, by its view's yaw alone. |
| `options.look` | `("aim" \| "target")?` | `"aim"` turns the view with the player's mouse; `"target"` faces it as what it is on faces. Aim without. |

Fails on a value of the wrong type; an option it does not have.

#### To watch a player from over their shoulder

```luau server
p:set_camera(p, { offset = vector.create(0.5, 0.3, -2.5) })
```

#### To show a killcam from where the killer stood, and back

```luau server
victim:set_camera(killer.position + vector.create(0, 1.6, 0))
wait(3)
victim:set_camera(nil)
```

### remove

- On the server: `x:remove()`

Takes away an NPC, a model, a shape, a camera, a part, or a looping sound or effect.

Its handlers go with it, and any order on it after does nothing. On a player it fails: a player is kicked.

#### To remove a crate once it is broken

```luau server
local crate = model("rocket")
crate:remove()
```

#### To clear every zombie at the end of a round

```luau server
for _ = 1, 3 do
	spawn()
end
for _, e in entities do
	if e.kind == "body" then
		e:remove()
	end
end
```

### on

- On the server: `e:on(event: "move_finished" | "rotate_finished" | "blocked" | "unblocked", fn: (e: Moved) -> ())`
- On the server: `e:on(event: "damaged", fn: (e: Damaged) -> ())`

Hears a model's, a shape's or a part's move or turn arrive, or its pause for a body in its way and going on.

On the server, `e:on(event: "move_finished" | "rotate_finished" | "blocked" | "unblocked", fn: (e: Moved) -> ())`:

`move_finished` and `rotate_finished` are heard as its move or its turn arrives, never after a `stop` nor of a
loop; `blocked` and `unblocked` as it pauses for the players and NPCs in its way, which the event lists, and goes
on. The handler is given the object as `part` for a part, `entity` for what the mod made, and runs as a coroutine
that may `wait`. A part's handlers are given at a module's top, and again as the module loads again in place; an
entity's whenever the mod holds it, kept until it is removed. A module has one handler an event for each object in
each of its files: a second `on` replaces the first. On a player, an NPC, a sound or an effect it fails; on what is
gone it does nothing.

| Name | Type | What it is |
|---|---|---|
| `event` | `"move_finished" \| "rotate_finished" \| "blocked" \| "unblocked"` | Needed. |
| `fn` | `(e: Moved) -> ()` | Needed. |

Fails on a value of the wrong type.

On the server, `e:on(event: "damaged", fn: (e: Damaged) -> ())`:

Hears a model, a shape or a part struck by a shot, a bash or an explosion, which makes it damageable.

The event says who struck it, if anyone, `attacker`; for how much, `amount`; by what means and with which weapon;
where, `point`, and going which way, `dir`; and the projectile whose burst it was, if one. A shot strikes each wall
it goes into once, with the damage it carries there; an explosion strikes the point of its box nearest the burst, if
in its sight. The server works out no damage for what no handler hears. Its handlers are given and kept as the other
events' are.

| Name | Type | What it is |
|---|---|---|
| `event` | `"damaged"` | Needed. |
| `fn` | `(e: Damaged) -> ()` | Needed. |

Fails on a value of the wrong type.

#### To say when a lift arrives

```luau server
local lift = model("rocket")
local arrived = false
lift:on("move_finished", function(e)
	arrived = true
	say("the lift is up")
end)
lift:move_by(vector.create(0, 4, 0), 1)
```

#### To break a crate after three hits

```luau server
local crate = model("rocket", { at = vector.create(0, 0, 3) })
crate.solid = true
local hits = 0
crate:on("damaged", function(e)
	hits += 1
	if hits == 3 then
		crate:remove()
	end
end)
```

### model

- On the server: `model(name, options?) → Entity`

Places a model, a `.model` file of the mod's, and gives its entity.

It stands still until an order moves it, and is not solid until its `solid` is set. A model and a part of the map
take the same orders: `move_to`, `rotate_by`, `attach` and the rest.

| Name | Type | What it is |
|---|---|---|
| `name` | `model` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.at` | `vector?` | Where it stands; the map's origin without. |
| `options.angles` | `Angles?` | How it is turned; unturned without. |
| `options.owner` | `(Player \| number)?` | The player it is about, whom `owner`, `team` and `spectators` pick by. |
| `options.read_by` | `(Group \| ((Player?, Player) -> boolean))?` | Who is shown it; everyone without. |
| `options.kind` | `string?` | Its kind, one `entity_kind` declared on its base; the base's own without. |

Returns `Entity`.

Fails on a value of the wrong type; an option it does not have.

#### To place a solid crate, turned

```luau server
local crate = model("rocket", { at = vector.create(4, 0, 2), angles = { yaw = math.pi / 2 } })
crate.solid = true
```

#### To show a player alone the flag they carry

```luau server
local flag = model("rocket", { owner = p, read_by = owner })
flag:attach(p, { offset = vector.create(0, 1.2, -0.3) })
```

### move_to

- On the server: `e:move_to(point, seconds, { timing?, face?, loop? }?)`

Moves a model or a part to a point in a straight line, over some seconds.

The seconds are above 0 and up to an hour, rounded to whole ticks, one at least; `set_position` moves it at once. A
move and a turn go on together, and a new move starts from where it is then. `move_seconds_left` reads how long it
has to go, as of the last tick, so the call that orders a move reads the old one. `on("move_finished", fn)` hears it
arrive. On an NPC, a player, a sound or an effect it fails; on one gone it does nothing.

| Name | Type | What it is |
|---|---|---|
| `point` | `vector` | Needed. |
| `seconds` | `number` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.timing` | `Timing?` | How it eases, each lap if it loops; linear without. |
| `options.face` | `boolean`, default false | Points its nose the way it goes while it moves, its roll kept, until it is turned again. |
| `options.loop` | `boolean`, default false | Repeats it for good: it comes back to where it started, each lap taking its seconds. |

Fails on a value of the wrong type; an option it does not have.

#### To open a sliding door

```luau server
local door = model("rocket", { at = vector.create(0, 0, 4) })
door:move_to(vector.create(2, 0, 4), 1.5, { timing = "ease-in-out" })
```

#### To run a lift up and down for good

```luau server
local lift = model("rocket")
lift:move_to(vector.create(0, 6, 0), 4, { timing = "ease-in-out", loop = true })
```

### move_by

- On the server: `e:move_by(offset, seconds, { timing?, face?, loop? }?)`

Moves a model or a part by an offset from where it is, in a straight line, over some seconds.

It takes its seconds and options as `move_to` does. A move that would take it past the map's reach is dropped.

| Name | Type | What it is |
|---|---|---|
| `offset` | `vector` | Needed. |
| `seconds` | `number` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.timing` | `Timing?` | How it eases, each lap if it loops; linear without. |
| `options.face` | `boolean`, default false | Points its nose the way it goes while it moves, its roll kept, until it is turned again. |
| `options.loop` | `boolean`, default false | Repeats it for good: it comes back to where it started, each lap taking its seconds. |

Fails on a value of the wrong type; an option it does not have.

#### To raise a platform by 3 m

```luau server
local platform = model("rocket")
platform:move_by(vector.create(0, 3, 0), 2)
```

### move_along

- On the server: `e:move_along(points, seconds, options?)`

Moves a model or a part from where it is through each point in turn, over some seconds.

It takes 1 to 31 points, as markers' positions, and goes on a curve through them, or straight from one to the
next with `straight`. Each stretch takes its share of the seconds by its length. An easing that overshoots stops
at the path's ends.

| Name | Type | What it is |
|---|---|---|
| `points` | `{vector}` | Needed. |
| `seconds` | `number` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.timing` | `Timing?` | How it eases, each lap if it loops; linear without. |
| `options.straight` | `boolean`, default false | Goes straight from one point to the next, rather than on a curve through them. |
| `options.face` | `boolean`, default false | Points its nose the way it goes while it moves, its roll kept, until it is turned again. |
| `options.loop` | `boolean`, default false | Goes round for good, from its last point back to where it started, each lap taking its seconds. |

Fails on a value of the wrong type; an option it does not have.

#### To fly a camera along a path, facing its way

```luau server
local cam = spawn_camera({ at = vector.create(0, 5, 0) })
local path = { vector.create(10, 5, 0), vector.create(10, 5, 10), vector.create(0, 8, 10) }
cam:move_along(path, 8, { face = true, timing = "ease-in-out" })
```

#### To run a cart round a closed track

```luau server
local cart = model("rocket")
local track = { vector.create(8, 0, 0), vector.create(8, 0, 8), vector.create(0, 0, 8) }
cart:move_along(track, 12, { straight = true, face = true, loop = true })
```

### rotate_to

- On the server: `e:rotate_to(angles, seconds, { timing?, loop? }?)`

Turns a model or a part to angles, over some seconds.

Each angle turns the way it says, so a yaw from 0 to 2π turns once round, up to a thousand turns either way; an
angle left out is kept. Yaw turns left, pitch lifts the front, roll tips it to its right. The seconds are above 0
and up to an hour, rounded to whole ticks, one at least; `set_angles` turns it at once. `on("rotate_finished", fn)`
hears it arrive.

| Name | Type | What it is |
|---|---|---|
| `angles` | `Angles` | Needed. |
| `seconds` | `number` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.timing` | `Timing?` | How it eases, each lap if it loops; linear without. |
| `options.loop` | `boolean`, default false | Turns on for good, by as much again each lap, as a fan. |

Fails on a value of the wrong type; an option it does not have.

#### To swing a door open

```luau server
local door = model("rocket")
door:rotate_to({ yaw = math.pi / 2 }, 1, { timing = "ease-out" })
```

### rotate_by

- On the server: `e:rotate_by(angles, seconds, { timing?, loop? }?)`

Turns a model or a part by angles from how it is turned, over some seconds.

It takes its seconds and options as `rotate_to` does. A turn that would go past a thousand turns is dropped.

| Name | Type | What it is |
|---|---|---|
| `angles` | `Angles` | Needed. |
| `seconds` | `number` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.timing` | `Timing?` | How it eases, each lap if it loops; linear without. |
| `options.loop` | `boolean`, default false | Turns on for good, by as much again each lap, as a fan. |

Fails on a value of the wrong type; an option it does not have.

#### To spin a fan for good

```luau server
local fan = model("rocket")
fan:rotate_by({ yaw = 2 * math.pi }, 3, { loop = true })
```

### stop

- On the server: `x:stop(what?)`

Stops a model's or a part's move and turn where it is, or an NPC's walk.

`"move"` stops its move alone and `"rotate"` its turn; an NPC stops with neither. A stopped move or turn is never
heard as finished.

| Name | Type | What it is |
|---|---|---|
| `what` | `("move" \| "rotate")?` | May be left out. |

Fails on a value of the wrong type.

#### To stop a lift between floors

```luau server
local lift = model("rocket")
lift:move_to(vector.create(0, 6, 0), 4)
lift:rotate_by({ yaw = math.pi }, 4)
wait(1)
lift:stop("move")
```

#### To stop a zombie in its tracks

```luau server
local zombie = spawn({ at = vector.create(0, 0, 10) })
zombie:chase(p)
zombie:stop()
```

### turn_with

- On the server: `e:turn_with(player?, { rate? }?)`

Makes a model, a shape or a part face where a player looks, without moving it; nil stops it where it faces.

It turns level, in the world, whatever it rides, from how it faces as the call is made, as a mounted gun's barrel
follows its gunner. It costs no network a tick: each player's game turns it toward the player as it draws them.
`angles` reads it as of the last tick, and what collides with it, and `limit_view` about it, take it as it faced
when it began to turn. A turn, a stop of its turn, or its player leaving ends it, facing where it was.

| Name | Type | What it is |
|---|---|---|
| `player` | `(Player \| number)?` | May be left out. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.rate` | `number?` | The fastest it turns, in radians a second, above 0; at once without. |

Fails on a value of the wrong type; an option it does not have.

#### To mount a gun that follows its gunner

```luau server
local gun = model("ak47", { at = vector.create(0, 1, 2) })
gun:turn_with(p, { rate = math.pi })
```

#### To let the gun go

```luau server
local gun = model("ak47", { at = vector.create(0, 1, 2) })
gun:turn_with(p)
gun:turn_with(nil)
```

### attach

- On the server: `e:attach(to, { bone?, offset? }?)`

Makes a model, a part, a looping sound or a looping effect ride a player, an NPC, a model or a part.

It rides turned as what it rides is turned, and its moves and turns after are in that space: a flag on a
carrier's back, a turret on a lift, a charge stuck to a zombie. A sound or an effect rides by its offset alone. An
attach that would make a ring, or put anything more than 16 deep, is dropped; one to a player who left does
nothing. `detach`, or what it rides going, lets it go where it is.

| Name | Type | What it is |
|---|---|---|
| `to` | `Player \| Entity \| number` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.bone` | `string?` | The joint of a body it rides, by name, as `"DEF-spine.003"`; the body's origin without. |
| `options.offset` | `vector?` | How far from what it rides, or from the joint, in that one's space. |

Fails on a value of the wrong type; an option it does not have.

#### To put a turret on a lift

```luau server
local lift = model("rocket")
local turret = model("ak47")
turret:attach(lift, { offset = vector.create(0, 1, 0) })
lift:move_by(vector.create(0, 5, 0), 3)
```

#### To strap a charge to a player's back

```luau server
local charge = model("grenade")
charge:attach(p, { bone = "DEF-spine.003", offset = vector.create(0, 0, -0.15) })
```

### detach

- On the server: `e:detach()`

Lets a model, a part, a looping sound or a looping effect go of what it rides, where it is.

#### To drop a carried flag

```luau server
local flag = model("rocket")
flag:attach(p, { offset = vector.create(0, 1.2, -0.3) })
flag:detach()
```

### shape

- On the server: `shape(form, size, options?) → Entity`

Makes a shape of a form filling a size, or the convex hull of points, and gives its entity.

A form is `"box"`, `"wedge"` (a ramp rising from nothing at its front, -z, to its height at its back),
`"cylinder"`, `"sphere"` or `"capsule"`, filling `size`, standing on its origin, upright and round across x and z.
`"hull"` takes a list of up to 64 points about its origin in place of a size, spanning a volume. A size is above 0
each way. Its kind is `shape`: it moves, rides, is solid and goes as a model does.

| Name | Type | What it is |
|---|---|---|
| `form` | `"box" \| "wedge" \| "cylinder" \| "sphere" \| "capsule" \| "hull"` | Needed. |
| `size` | `vector \| {vector}` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.material` | `material?` | The `.material` file it is drawn with; not drawn without, a clip. |
| `options.solid` | `Solid?` | What it stops, as its `solid` says: true, false or a mask; everything without. |
| `options.at` | `vector?` | Where it stands; the map's origin without. |
| `options.angles` | `Angles?` | How it is turned; unturned without. |

Returns `Entity`.

Fails on a value of the wrong type; an option it does not have.

#### To build a wooden ramp

```luau server
local ramp = shape("wedge", vector.create(2, 1, 4), { material = "wood", at = vector.create(0, 0, 6) })
```

#### To block a doorway to players alone, unseen

```luau server
shape("box", vector.create(2, 3, 0.2), { solid = { players = true }, at = vector.create(0, 0, 3) })
```

#### To make a rock of a few points

```luau server
local points = {
	vector.create(0, 0, 0), vector.create(1, 0, 0), vector.create(0, 0, 1), vector.create(0.3, 0.8, 0.3),
}
shape("hull", points, { material = "wood" })
```

## Movement

How players move, and how the mod changes it: their speed, jump and gravity, a push, a jump to a point, holding them in place, and a limit on where they can look.

A player's game predicts their own movement, so it feels instant. The server's orders reach it marked with the moment they take effect, so the player sees each one a ping after the mod gave it, with no jump. An order that arrives too late for that is carried out at once and the player's game corrected. Speeds are in metres a second and angles in radians; see [units and axes](https://engine-modding.pages.dev/0/concepts/units-and-axes/).

A jump pad or a teleport that waits a ping feels late. So a client script gives the same orders on `me` in a handler of a trigger that fires on clients, and the player sees them at once; the server's handler of the same trigger gives them too, and the two agreeing show nothing. Put the code both handlers run in a `.shared.luau` file both sides `require`.

`p:control` hands a player's keys and view to a model, a shape, a camera or a part, as a vehicle's driver, in place of their body.

### Fields

What a player, `p`, or an entity, `e`, reads beside the mod's state.

| Name | Type | Side | What it is |
|---|---|---|---|
| `p.position` | `vector?` | both | Where they stand, at their feet. |
| `p.yaw` | `number?` | server | Which way they face, in radians: 0 faces +Z, more turns left. |
| `p.pitch` | `number?` | server | How far up they look, in radians. |
| `p.ground` | `(Entity \| Player)?` | server | What they stand on; nil in the air or on the map. |
| `p.move` | `Move?` | server | Their movement keys, each -1 to 1, as of their latest command. |

### set_velocity

- On the server: `p:set_velocity(velocity)`
- On a client: `p:set_velocity(velocity)`

Sets a player's velocity, in metres a second, as a jump pad throws them.

On the server:

The velocity is up to 100 m/s long. A dead player is not pushed. On an NPC it fails.

| Name | Type | What it is |
|---|---|---|
| `velocity` | `vector` | Needed. |

Fails on a value of the wrong type.

On a client:

Sets the player's own velocity, in metres a second, in a handler of a trigger that fires on clients, so a jump pad throws them at once.

The player's game carries it out from the next command on, and the server's handler of the same trigger gives the
same order, which the game then confirms: the two agreeing show nothing, and differing a correction, once. It is
checked as the server's is. Anywhere else, or on another player, it fails.

| Name | Type | What it is |
|---|---|---|
| `velocity` | `vector` | Needed. |

Fails on a value of the wrong type.

#### To throw a player up into the air

```luau server
p:set_velocity(vector.create(0, 8, 0))
```

#### To throw the player up off a jump pad at once

```luau client
triggers.jump_pad:on("entered", function()
    me:set_velocity(vector.create(0, 8, 0))
end)
```

### add_velocity

- On the server: `p:add_velocity(velocity)`
- On a client: `p:add_velocity(velocity)`

Adds to a player's velocity, in metres a second, as a blast pushes them.

On the server:

The push is up to 100 m/s long, and takes them no faster than 100 m/s or than they already went. A dead player is
not pushed. On an NPC it fails.

| Name | Type | What it is |
|---|---|---|
| `velocity` | `vector` | Needed. |

Fails on a value of the wrong type.

On a client:

Adds to the player's own velocity, in metres a second, in a handler of a trigger that fires on clients, as `me:set_velocity` sets it.

| Name | Type | What it is |
|---|---|---|
| `velocity` | `vector` | Needed. |

Fails on a value of the wrong type.

#### To knock a player back from a blast

```luau server
local blast = vector.create(0, 0, 0)
local away = vector.normalize(p.position - blast + vector.create(0, 1, 0))
p:add_velocity(away * 6)
```

#### To push the player along a conveyor at once

```luau client
triggers.conveyor:on("entered", function()
    me:add_velocity(vector.create(4, 0, 0))
end)
```

### tune

- On the server: `p:tune(scales)`
- On a client: `p:tune(scales)`

Scales how a player moves: their running speed, jump height and gravity, their view's bob and their weapon's sway.

On the server:

Each scale is 1 as the engine has it, and those left out are kept; the scales outlast death. `weapons` scales the
stats of every weapon the player holds, after the weapon's own tune, each by 0 to 10: `interval`'s scales a burst's
too, and each result stays within what a weapon's file may give. A weapon taken away is back to its own stats, and
the next player to hold it scales it by their own. Each scaled weapon counts as one of the 4096 a mod names: past
them, a weapon stays unscaled, which the server says as the mod's error. On an NPC it fails.

| Name | Type | What it is |
|---|---|---|
| `scales` | `table` | A table of the options below. Each may be left out. |
| `scales.speed` | `number?`, 0 to 10 | Their running speed. |
| `scales.jump` | `number?`, 0 to 10 | How high they jump. |
| `scales.gravity` | `number?`, 0 to 10 | Their gravity: at 0 a jump stays on the ground and a landing is no fall. |
| `scales.bob` | `number?`, 0 to 10 | How much their eyes bob with each stride. |
| `scales.sway` | `number?`, 0 to 10 | How much their weapon moves before their eyes: its bob, its lag behind the view's turns and its breathing. |
| `scales.weapons` | `({["damage" \| "interval" \| "range" \| "penetrate" \| "spread" \| "spread_moving" \| "spread_air" \| "reload" \| "drop" \| "raise" \| "sprint_out" \| "move_speed" \| "fire_delay" \| "aim_time" \| "aim_spread" \| "melee_damage" \| "melee_range" \| "melee_time" \| "melee_delay"]: number})?` | Every weapon they hold, after its own tune: each stat named, by its scale, as a perk such as Double Tap does. |

Fails on a value of the wrong type; an option it does not have; a value outside its range.

On a client:

Scales how the player's own body moves, in a handler of a trigger that fires on clients, as `me:set_velocity` pushes it; a player's game scales no weapons.

| Name | Type | What it is |
|---|---|---|
| `scales` | `table` | A table of the options below. Each may be left out. |
| `scales.speed` | `number?`, 0 to 10 | Their running speed. |
| `scales.jump` | `number?`, 0 to 10 | How high they jump. |
| `scales.gravity` | `number?`, 0 to 10 | Their gravity: at 0 a jump stays on the ground and a landing is no fall. |
| `scales.bob` | `number?`, 0 to 10 | How much their eyes bob with each stride. |
| `scales.sway` | `number?`, 0 to 10 | How much their weapon moves before their eyes: its bob, its lag behind the view's turns and its breathing. |

Fails on a value of the wrong type; an option it does not have; a value outside its range.

#### To slow a player wading through mud

```luau server
p:tune({ speed = 0.5, jump = 0.5 })
```

#### To give a player Double Tap

```luau server
p:tune({ weapons = { interval = 0.75, damage = 2 } })
```

#### To float in low gravity for ten seconds

```luau server
p:tune({ gravity = 0.2 })
wait(10)
p:tune({ gravity = 1 })
```

#### To slow the player at once as they step into mud

```luau client
triggers.mud:on("entered", function()
    me:tune({ speed = 0.5 })
end)
```

### limit_view

- On the server: `p:limit_view(limits?)`
- On a client: `p:limit_view(limits?)`

Keeps a player's view within ranges, or turning no faster than a speed, until `p:limit_view(nil)` lifts it.

On the server:

`yaw`, `pitch` and `cone` are measured from `around`, which they need. At an edge the view stops, and it moves off
it at once, never winding past it; the ranges win over the speed there. A limit about an entity removed keeps its
speed alone. A new limit replaces the last, and the limit outlasts death. On an NPC it fails.

| Name | Type | What it is |
|---|---|---|
| `limits` | `table?` | A table of the options below. It, and each option, may be left out. |
| `limits.around` | `(Angles \| Entity)?` | What `yaw`, `pitch` and `cone` are measured from: angles, a roll ignored, or a model, a shape or a part as it
turns. |
| `limits.yaw` | `AngleRange?` | How far the view turns left and right of `around`: radians either way, or `{min, max}` within half a turn,
positive turning left. Not with `cone`. |
| `limits.pitch` | `AngleRange?` | How far the view turns down and up from `around`, as `yaw`, positive looking up. Not with `cone`. |
| `limits.cone` | `number?`, 0 to 3.1415927410125732 | The most the view strays from `around`'s direction, in radians, in place of `yaw` and `pitch`. Not with `pitch` or `yaw`. |
| `limits.speed` | `number?`, 0 or more | The fastest the view turns, in radians a second; 0 freezes it. |

Fails on a value of the wrong type; an option it does not have; a value outside its range; `yaw` and `cone` both; `pitch` and `cone` both.

On a client:

Limits the player's own view, in a handler of a trigger that fires on clients, as `me:set_velocity` pushes them; `around` is angles or an entity, which the server checks.

| Name | Type | What it is |
|---|---|---|
| `limits` | `table?` | A table of the options below. It, and each option, may be left out. |
| `limits.around` | `(Angles \| Entity)?` | What `yaw`, `pitch` and `cone` are measured from: angles, a roll ignored, or a model, a shape or a part as it
turns. |
| `limits.yaw` | `AngleRange?` | How far the view turns left and right of `around`: radians either way, or `{min, max}` within half a turn,
positive turning left. Not with `cone`. |
| `limits.pitch` | `AngleRange?` | How far the view turns down and up from `around`, as `yaw`, positive looking up. Not with `cone`. |
| `limits.cone` | `number?`, 0 to 3.1415927410125732 | The most the view strays from `around`'s direction, in radians, in place of `yaw` and `pitch`. Not with `pitch` or `yaw`. |
| `limits.speed` | `number?`, 0 or more | The fastest the view turns, in radians a second; 0 freezes it. |

Fails on a value of the wrong type; an option it does not have; a value outside its range; `yaw` and `cone` both; `pitch` and `cone` both.

#### To keep a player in a turret looking ahead

```luau server
local turret = model("rocket")
p:limit_view({ around = turret, yaw = math.pi / 4, pitch = { min = -0.2, max = 0.5 } })
```

#### To freeze a player's view for a cutscene

```luau server
p:limit_view({ speed = 0 })
wait(3)
p:limit_view(nil)
```

#### To hold the player's view ahead as they sit in a turret

```luau client
triggers.turret_seat:on("entered", function()
    me:limit_view({ around = { yaw = 0 }, yaw = 0.8, pitch = 0.4 })
end)
```

### set_position

- On the server: `x:set_position(point)`
- On a client: `p:set_position(point)`

Moves a player, an NPC, a model, a shape, a camera or a part to a point at once.

On the server:

A player goes on as fast, in the same stance and looking the same way, off any ladder or ledge, as CoD's
`setOrigin`; a dead one is not moved. An NPC lands on the navmesh's point nearest it, and finds its way on from
there. The point is within 100 km of the map's origin. `move_to` moves a model or a part over some seconds. On a
looping sound or effect it fails; on what is gone it does nothing.

| Name | Type | What it is |
|---|---|---|
| `point` | `vector` | Needed. |

Fails on a value of the wrong type.

On a client:

Moves the player's own body to a point at once, in a handler of a trigger that fires on clients, so a teleport shows at once, as `me:set_velocity` pushes them.

| Name | Type | What it is |
|---|---|---|
| `point` | `vector` | Needed. |

Fails on a value of the wrong type.

#### To send a player back to where they started

```luau server
local start = p.position
wait(5)
p:set_position(start)
```

#### To put a crate on a ledge

```luau server
local crate = model("rocket")
crate:set_position(vector.create(3, 2, -4))
```

#### To teleport the player at once as they step onto a pad

```luau client
triggers.teleport:on("entered", function()
    me:set_position(vector.create(0, 1, 20))
end)
```

### set_angles

- On the server: `x:set_angles(angles)`
- On a client: `p:set_angles(angles)`

Turns a player's view, an NPC, a model, a shape, a camera or a part at once, each angle left out kept.

On the server:

A player turns by `yaw` and `pitch`, and an NPC by its `yaw` alone: a roll on either, or a pitch on an NPC, fails.
A dead player is not turned. `rotate_to` turns a model or a part over some seconds. On a looping sound or effect it
fails; on what is gone it does nothing.

| Name | Type | What it is |
|---|---|---|
| `angles` | `Angles` | Needed. |

Fails on a value of the wrong type.

On a client:

Turns the player's own view, by `yaw` and `pitch`, in a handler of a trigger that fires on clients, as `me:set_velocity` pushes them; a roll fails.

| Name | Type | What it is |
|---|---|---|
| `angles` | `Angles` | Needed. |

Fails on a value of the wrong type.

#### To turn a player to face the way they came

```luau server
p:set_angles({ yaw = p.yaw + math.pi })
```

#### To tip a crate onto its side

```luau server
local crate = model("rocket")
crate:set_angles({ roll = math.pi / 2 })
```

#### To turn the player round as they come out of a teleport

```luau client
triggers.teleport:on("entered", function()
    me:set_angles({ yaw = math.pi })
end)
```

### control

- On the server: `p:control(target?, { movement? }?)`

Hands a player's input to a model, a shape, a camera or a part in place of their body, until `p:control(nil)` gives it back.

Their movement keys, their view and their buttons then drive the entity. Without `movement` they drive nothing:
the mod moves the entity with its orders and reads `p.move` and `p.held` as it likes. `movement` names a file of
the mod's that moves a model or a camera at each of the player's commands: `<name>.server.luau`, run on the server
alone, so the player sees it move a ping late, or `<name>.shared.luau`, run on their game too, so they see it move
at once, as their body. A module's own `<name>.server.luau` is no movement, so name a movement apart from it.

A movement file returns `{ hull = { radius, height }, state = t.record { … }, step = function(car, input, dt,
world) … end }`. `step` changes `car` in place: its `position`, `velocity`, `angles`, `on_ground`, and `state`, a
record of up to 16 numbers, whole numbers, booleans, short strings, vectors and choices, starting at each field's
zero. `input` is `{ move = { forward, right }, angles = { yaw, pitch }, held }`, and `dt` a tick. `world:move(car,
by)` sweeps the hull, an upright capsule, by up to 3.9 m, sliding along what it meets, and gives `{ position,
on_ground }`; `world:ground(point)` gives the floor under a point, and `world:trace(from, to)` the first thing on a
line, `{ position, normal }`. A step is pure: it reads Luau's libraries, `vector`, `angles` and `t` alone, gives no
orders, and `math.random` and `now()` fail in it. The state goes whole to the player in every snapshot, so keep it
to a few numbers. The steps of a tick share 2 ms, past which the commands left are dropped.

The hull's radius and height are each 5 cm or more, up to 3 m and 4 m. It meets what stops players and never a body;
a hull starting inside something is moved out by an inch. A step leaves the entity riding nothing, and an order of
the mod's moving it is where the next step starts. A string in the state is 32 bytes at most. A step that fails
leaves the entity as it was, and the error is said once. Loaded again in place with a state of another type, a
movement starts its state afresh.

Meanwhile the body stands where it was, and can be hurt and killed there; its view turns with the input and its
weapon still fires, unless the mod disables it. Control outlasts death and a respawn. It ends as the entity is
removed, as the player leaves, or at a new match. On an NPC or a player it fails, and with a movement on a shape or
a part, which every player's game is told of; on what is gone it does nothing. A movement file that fails to load
fails `control`.

| Name | Type | What it is |
|---|---|---|
| `target` | `Entity?` | May be left out. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.movement` | `string?` | The movement file of the mod's that steps it, by its path without `.server.luau` or `.shared.luau`. |

Fails on a value of the wrong type; an option it does not have.

#### To fly a player's gunship along its path while they aim from it

```luau server
local plane = spawn_camera({ at = vector.create(0, 40, -30) })
plane:move_along({ vector.create(30, 40, 0), vector.create(0, 40, 30), vector.create(-30, 40, 0) }, 20, { loop = true })
p:set_camera(plane)
p:control(plane)
```

#### To give a player back their body

```luau server
p:control(nil)
p:set_camera(nil)
```

### hold

- On the server: `p:hold(held)`

Holds a player where they are, in the air too, until `p:hold(false)` lets them go.

A held player still looks around, fires and changes stance, as CoD2's `linkTo` holds them, and what they stand on
still carries them. The hold outlasts death. On an NPC it fails.

| Name | Type | What it is |
|---|---|---|
| `held` | `boolean` | Needed. |

Fails on a value of the wrong type.

#### To freeze everyone for a round's countdown

```luau server
for _, p in players do
    if not p.npc then
        p:hold(true)
    end
end
wait(5)
for _, p in players do
    if not p.npc then
        p:hold(false)
    end
end
```

## Health

Hurting, healing and killing players, and explosions. A shot or a fall takes no health by itself: the mod hears it and decides, with `hurt` or `set_health`. At 0 health a player is dead until the mod brings them back.

Each hurt shows as it happens. The attacker sees a hit mark. The one hurt sees a red bar pointing at the attacker. Everyone sees a death, with the weapon, as a kill feed needs. A shot at a player shows its shooter a mark only once the mod hurts for it; one at an NPC shows it as it lands. The dead neither move nor fire, and no one sees them.

### Fields

What a player, `p`, or an entity, `e`, reads beside the mod's state.

| Name | Type | Side | What it is |
|---|---|---|---|
| `p.health` | `number?` | both | Their health; on a client, the shown player's alone. |
| `p.max_health` | `number?` | both | The health they have at most; on a client, the shown player's alone. |

### explode

- On the server: `explode(at, options)`

Sets off an explosion at a point, hurting the players and NPCs around it as a grenade's does.

It plays no effect and no sound: play those with `play_effect` and `play_sound`. It goes off on the next tick, and
what stops shots shields from it. It hurts `by` too, if near. 16 go off a tick and the rest wait their turn; past
256 waiting, more are dropped.

| Name | Type | What it is |
|---|---|---|
| `at` | `vector` | Needed. |
| `options` | `table` | A table of the options below. Those marked needed may not be left out. |
| `options.radius` | `number`, 0 to 50 | How far it reaches, in metres, over 0. Needed. |
| `options.damage` | `number`, 0 or more | The damage at its middle. Needed. |
| `options.min_damage` | `number`, 0 or more, default 0 | The damage at its edge, `radius` away; in between it falls in a straight line from `damage`. |
| `options.by` | `(Player \| number)?` | Who set it off, a player or an NPC, as its hurts and kills name them. |

Fails on a value of the wrong type; an option it does not have; a key it needs left out; a value outside its range.

#### To drop a mortar where a player stands

```luau server
local at = p.position
explode(at, { radius = 6, damage = 150, min_damage = 10 })
play_effect("explosion", { at = at })
play_sound("explosion_boom", { at = at, range = 200 })
```

#### To blow up the charge a player planted, so its kills are theirs

```luau server
local charge = model("grenade", { at = p.position })
wait(3)
explode(charge.position, { radius = 8, damage = 300, by = p })
charge:remove()
```

### hurt

- On the server: `p:hurt(damage, by?, weapon?, { kind?, location? }?)`

Hurts a player, as a hit would, taking `damage` from their health; at 0 they die.

`by` is who did it, a player or an NPC, and `weapon` what with: any weapon's name, a file of `weapons/` or not.
The player hurt sees a red bar pointing at `by`, and `by` sees a hit mark, as do those watching either through their
eyes; a `by` not in play counts as no one. A death it causes is shown to everyone with who did it, the weapon, the
kind and the limb, as a kill feed needs. The dead stay dead. An NPC has no health of the engine's, so `hurt` on one
fails: the mod keeps its own.

| Name | Type | What it is |
|---|---|---|
| `damage` | `number`, 0 or more | Needed. |
| `by` | `(Player \| number)?` | May be left out. |
| `weapon` | `string?` | May be left out. |
| `how` | `table?` | A table of the options below. It, and each option, may be left out. |
| `how.kind` | `"bullet" \| "melee" \| "explosion" \| "fall" \| "mod"`, default "mod" | How it was done, as the kill feed and the `damaged` and `killed` events' `means` name it. |
| `how.location` | `("head" \| "neck" \| "chest" \| "stomach" \| "upper_arm" \| "lower_arm" \| "hand" \| "upper_leg" \| "lower_leg" \| "foot")?` | The limb it hit, one of a weapon's `locations`; none, a hurt of the whole body, without one. |

Fails on a value of the wrong type; an option it does not have; a value outside its range.

#### To hurt a player standing in fire

```luau server
p:hurt(5)
```

#### To deal a zombie's claw, so the kill feed names it

```luau server
local zombie = spawn()
p:hurt(40, zombie, "zombie_claw", { kind = "melee", location = "chest" })
```

### heal

- On the server: `p:heal(amount)`

Gives a living player back `amount` health, up to their maximum.

| Name | Type | What it is |
|---|---|---|
| `amount` | `number`, 0 or more | Needed. |

Fails on a value of the wrong type; a value outside its range.

#### To heal a player a little each second, for five seconds

```luau server
for _ = 1, 5 do
    p:heal(5)
    wait(1)
end
```

### set_health

- On the server: `p:set_health(health)`

Sets a living player's health, no higher than their maximum; at 0 they die, as by a hurt.

| Name | Type | What it is |
|---|---|---|
| `health` | `number`, 0 to 10000 | Needed. |

Fails on a value of the wrong type; a value outside its range.

#### To give a player full health

```luau server
p:set_health(p.max_health)
```

### set_max_health

- On the server: `p:set_max_health(max)`

Sets a player's maximum health; everyone starts at 100.

It is kept through death, and a respawn brings the player back at it. Health over it comes down to it.

| Name | Type | What it is |
|---|---|---|
| `max` | `number`, 1 to 10000 | Needed. |

Fails on a value of the wrong type; a value outside its range.

#### To give a player the Juggernog perk

```luau server
p:set_max_health(250)
p:set_health(250)
```

### respawn

- On the server: `p:respawn()`

Brings a dead player back at the next marker whose name starts with `spawn`, at full health, with the loadout.

Followed at once by `p:set_position` and `p:set_angles` on the same player, in the same handler, timer or tick, it
brings anyone back there instead, dead or alive, and leaves that spawn for the next. A player alive is left as they
are otherwise. A push, turn or move not yet carried out goes with the life before; `tune`, `hold` and `limit_view`
outlast it.

#### To bring every dead player back at the start of a round

```luau server
for _, p in players do
    if not p.npc and p.health == 0 then
        p:respawn()
    end
end
```

#### To bring a player back at a point of your choosing

```luau server
local at = vector.create(4, 0, 2)
p:respawn()
p:set_position(at)
p:set_angles({ yaw = math.pi })
```

### `on("hit")`

- On the server: `on("hit", fn)`

Something struck a player or an NPC, once for each pellet that hits. It takes no health itself: the mod hurts them, as with `p:hurt`.

| Name | Type | What it is |
|---|---|---|
| `e.victim` | `Player` | Who was struck. |
| `e.attacker` | `Player?` | Who struck them; nil for no one, or one no longer here. |
| `e.amount` | `number` | The damage it would do. |
| `e.means` | `string` | How: `bullet`, `melee`, `explosion`, `fall` or `mod`, the mod's own. |
| `e.location` | `string?` | The limb it struck, as `head`; nil for an explosion or a fall. |
| `e.weapon` | `string?` | The weapon it was done with; nil for none. |
| `e.point` | `{x: number, y: number, z: number}` | Where it struck. |
| `e.dir` | `{x: number, y: number, z: number}` | Which way it was going, a unit vector. |

### `on("fell")`

- On the server: `on("fell", fn)`

A player landed from over 6.5 m, a fall that would hurt, all their health from 12.2 m. It takes no health itself: the mod hurts them.

| Name | Type | What it is |
|---|---|---|
| `e.player` | `Player` | Who landed. |
| `e.amount` | `number` | The damage the fall would do. |
| `e.height` | `number` | How far they fell, in metres. |

### `on("killed")`

- On both sides: `on("killed", fn)`

A player or an NPC died, heard on the next mod tick.

| Name | Type | What it is |
|---|---|---|
| `e.victim` | `Player` | Who died: a player or an NPC. |
| `e.killer` | `Player?` | Who killed them; nil for no one, or one no longer here. |
| `e.weapon` | `string?` | The weapon it was done with; nil for none. |
| `e.means` | `string` | How: `bullet`, `melee`, `explosion`, `fall` or `mod`, the mod's own. |
| `e.location` | `string?` | The limb it struck, as `head`; nil for an explosion or a fall. |

### `on("damaged")`

- On both sides: `on("damaged", fn)`

A player or an NPC lost health.

| Name | Type | What it is |
|---|---|---|
| `e.victim` | `Player` | Who was hurt: a player or an NPC. |
| `e.attacker` | `Player?` | Who hurt them; nil for no one, or one no longer here. |
| `e.amount` | `number` | The health it took. |
| `e.weapon` | `string?` | The weapon it was done with; nil for none. |
| `e.means` | `string` | How: `bullet`, `melee`, `explosion`, `fall` or `mod`, the mod's own. |
| `e.location` | `string?` | The limb it struck, as `head`; nil for an explosion or a fall. |

### `on("hit_too_late")`

- On a client: `on("hit_too_late", fn)`

A shot of this player's reached its victim as they died or left, so it did nothing.

| Name | Type | What it is |
|---|---|---|
| `e.victim` | `Player` | Who the shot reached. |

## Weapons

What players carry and how it fires. A weapon is a file of the mod's, `<path>.weapon.luau`, which returns its numbers as a table: damage, rate, a burst, spread, range, its magazine and times, aiming, its bash on V, a projectile for a rocket or a grenade. The server and every player's game read the same file, so a shot feels the same on both.

To make one, copy one of the stock mod's, such as `weapons/ak47.weapon.luau`; [weapon files](https://engine-modding.pages.dev/0/concepts/weapon-files/) lists every key. The mod hands weapons out with `loadout` and `give`, tunes one a player carries with `tune_weapon`, fitting parts on it and naming its sounds, plays a clip on the one in hand with `animate_weapon`, scales all of a player's with `tune`, and can put one down, hide it or take it away.

A player carries up to four weapons, in slots 1, 2, 3 and 5 by the keys that take them, and grenades apart, which keys of their own throw. Each weapon carried is a value, `{weapon, rounds, reserve, tune}`, which `p.slots` lists, `p:take` returns and `p:give` gives back as it was, so a weapon handed from one player to another keeps its rounds and its tune. The orders name a weapon by its file, a tuned one by its base's.

### Fields

What a player, `p`, or an entity, `e`, reads beside the mod's state.

| Name | Type | Side | What it is |
|---|---|---|---|
| `p.weapon` | `string?` | both | The weapon in their hand; on a client, the shown player's alone. |
| `p.slots` | `{[number]: Carried}?` | both | The weapons they carry, by slot, as carried still while `killed` runs; on a client, the shown player's alone. |
| `p.ammo` | `number?` | client | The rounds in the shown player's weapon. |
| `p.reserve` | `number?` | client | The rounds the shown player carries for their weapon beside those. |

### give

- On the server: `p:give(weapon, slot?)`
- On the server: `p:give(carried, slot?)`

Gives a player a weapon by its file's name, its magazine full and its reserve the weapon's `reserve` stat.

On the server, `p:give(weapon, slot?)`:

It goes in `slot`, 1, 2, 3 or 5, or else where the player already carries it, or else in the first empty slot, or
else in hand, taking what was there. A player carries one weapon of a file at most, tuned or not, so one of the same
file in another slot is taken. It comes up when it is given in hand, to empty hands, or in place of the same weapon
in hand. A grenade is carried apart from the slots, in place of the one its key throws. A name of no weapon's file
fails at the call.

| Name | Type | What it is |
|---|---|---|
| `weapon` | `weapon` | Needed. |
| `slot` | `number?` | May be left out. |

Fails on a value of the wrong type.

On the server, `p:give(carried, slot?)`:

Gives a player back a weapon as it was carried: its rounds, its reserve and its tune.

`carried` is a value of `p.slots` or `p:take`, so a weapon taken from one player and given to another keeps its
rounds. It goes where a weapon given by name goes. Nothing stops one value being given twice. A reserve above 0 on a
melee weapon or a grenade, which reload nothing, fails at the call.

| Name | Type | What it is |
|---|---|---|
| `carried` | `table` | A table of the options below. Those marked needed may not be left out. |
| `carried.weapon` | `string` | The weapon's name, its base's if it is tuned. Needed. |
| `carried.rounds` | `number` | The rounds in its magazine; more than it holds are left out. Needed. |
| `carried.reserve` | `number`, 0 to 9999 | The rounds its reloads draw from. Needed. |
| `carried.tune` | `Tune?` | Its tune, if it has one. A table of the options below it. |
| `carried.tune.stats` | `({[string]: any})?` | Stats laid over the weapon's file, by name, each as the file writes it; `false` takes an optional one away. |
| `carried.tune.attach` | `({[string]: string})?` | The model drawn at each marker of the weapon's model, by the marker's name. |
| `carried.tune.sounds` | `string?` | The name the weapon's sounds take in place of its model's: `<sounds>_fire` and the rest. |
| `slot` | `number?` | May be left out. |

Fails on a value of the wrong type; an option it does not have; a key it needs left out; a value outside its range.

#### To give a player a pistol in their second slot

```luau server
p:give("weapons/usp", 2)
```

#### To hand a player's rifle to another, rounds and all

```luau server
local rifle = p:take("weapons/ak47")
if rifle then
    q:give(rifle, 1)
end
```

### take

- On the server: `p:take(weapon) → Carried?`
- On the server: `p:take() → {[number]: Carried}`

Takes a weapon away from a player and returns its value, which `p:give` gives back as it was; nil if they do not carry it.

On the server, `p:take(weapon) → Carried?`:

The value is the weapon as carried when the handler was called: orders are carried out after it, so `p:give(x)`
then `p:take(x)` in one handler returns nil. The weapon in hand taken, the first left comes up. A name of no
weapon's file fails at the call.

| Name | Type | What it is |
|---|---|---|
| `weapon` | `weapon` | Needed. |

Returns `Carried?`.

Fails on a value of the wrong type.

On the server, `p:take() → {[number]: Carried}`:

Takes every weapon away from a player, grenades too, and returns the values of those in slots, by slot number.

Each is as carried when the handler was called, as `p:take(weapon)` returns it; a grenade taken returns none.

Returns `{[number]: Carried}`.

#### To trade a player's rifle for a pistol with as many rounds in reserve

```luau server
local rifle = p:take("weapons/ak47")
if rifle then
    p:give("weapons/usp")
    p:set_ammo("weapons/usp", nil, rifle.reserve)
end
```

#### To take a player's weapons through a cutscene and give them back after

```luau server
local kept = p:take()
wait(5)
for slot, carried in kept do
    p:give(carried, slot)
end
```

### switch

- On the server: `p:switch(weapon)`

Switches a player to a weapon they carry, as its slot's key does; nothing happens if they carry none of it.

A name of no weapon's file fails at the call.

| Name | Type | What it is |
|---|---|---|
| `weapon` | `weapon` | Needed. |

Fails on a value of the wrong type.

#### To bring up a player's pistol as they climb a ladder

```luau server
p:switch("weapons/usp")
```

### set_ammo

- On the server: `p:set_ammo(weapon, rounds?, reserve?)`

Sets the rounds in a carried weapon's magazine and in the reserve its reloads draw from, each left as it is if nil.

More rounds than the magazine holds are left out. A `reserve` above 0 on a melee weapon or a grenade, which reload
nothing, fails at the call, as does a name of no weapon's file. Nothing happens for a weapon the player does not
carry. For an endless reserve, set it again every tick.

| Name | Type | What it is |
|---|---|---|
| `weapon` | `weapon` | Needed. |
| `rounds` | `number?` | May be left out. |
| `reserve` | `number?`, 0 to 9999 | May be left out. |

Fails on a value of the wrong type; a value outside its range.

#### To refill a player's rifle from an ammo box

```luau server
p:set_ammo("weapons/ak47", 30, 90)
```

#### To empty a player's pistol magazine

```luau server
p:set_ammo("weapons/usp", 0)
```

### disable_weapon

- On the server: `p:disable_weapon()`

Puts a player's weapon down, as for a sprint, until `p:enable_weapon`: no firing, aiming nor reloading.

It stays down through a respawn.

#### To keep players from firing through a countdown

```luau server
for _, p in players do
    p:disable_weapon()
end
wait(3)
for _, p in players do
    p:enable_weapon()
end
```

### enable_weapon

- On the server: `p:enable_weapon()`

Brings a player's weapon back up after `p:disable_weapon`.

#### To let a player fire again once a stun wears off

```luau server
p:disable_weapon()
wait(2)
p:enable_weapon()
```

### hide_weapon

- On the server: `p:hide_weapon()`

Draws the weapon in a player's hand nowhere until `p:show_weapon`: not before their eyes, not in their body's hands for everyone, and with no muzzle flash.

They keep it, its slot and its rounds, and it still fires and is heard, as a mounted gun's gunner fires while the
gun on the map shows. It stays hidden through a respawn.

#### To seat a player at a mounted gun

```luau server
p:hide_weapon()
```

### show_weapon

- On the server: `p:show_weapon()`

Draws a player's weapon again after `p:hide_weapon`.

#### To give a player their own weapon back as they leave a mounted gun

```luau server
p:hide_weapon()
wait(10)
p:show_weapon()
```

### loadout

- On the server: `loadout(weapons)`

Names the weapons a player carries on joining and after every respawn, in slot order: up to 4, but grenades, carried apart. Without it, none.

One module declares it, at its top as it loads: a second module's fails at load, naming both, and a module declaring
it again replaces its own. A weapon named twice, or one no file of the mod is, fails at load. `p:respawn()` gives
the loadout again, so give after it; a weapon disabled or hidden stays so.

| Name | Type | What it is |
|---|---|---|
| `weapons` | `{string}` | Needed. |

Fails on a value of the wrong type.

#### To start every player with a rifle, a pistol and a grenade

```luau server
loadout({ "weapons/ak47", "weapons/usp", "weapons/frag_grenade" })
```

### stats_of

- On both sides: `stats_of(weapon) → {[string]: any}`

Gives a weapon file's stats as it writes them, with the defaults it is read with filled in, its `model` too.

The table is frozen, nested tables kept. The file is read again only once it changes, and nothing is loaded by
players for it, so a mod may look up any weapon it has: a part adds to a stat,
`stats.damage = (stats.damage or stats_of(weapon).damage) - 3`, and a HUD shows a magazine's size. A name of no
weapon's file fails.

| Name | Type | What it is |
|---|---|---|
| `weapon` | `weapon` | Needed. |

Returns `{[string]: any}`.

Fails on a value of the wrong type.

### tune_weapon

- On the server: `p:tune_weapon(weapon, { stats?, attach?, sounds? }?, { clip? }?)`

Tunes a weapon a player carries, as a mod's parts or a burst switched on do: its stats, the parts on it and its sounds.

Each call replaces the weapon's whole set of changes; nil, or no changes, takes them all off. `changes.stats` takes
any of a weapon file's stats but `model` and `name`, laid over its file as written and read as the file is, so it fails as the
file would: `burst = false` takes a burst away, but `automatic` is true or false, and a `pattern` is only another of
the engine's. A tune may not set `offhand` nor `projectile`, nor move `magazine` to or from 0, and a grenade is not
tuned. `changes.attach` draws up to 8 models at markers of the weapon's model, on the weapon in hand in first person
alone, a shot's flash leaving from a part's own `muzzle` marker if it has one; the stock guns have `muzzle`, `rail`
on top and `under` below the handguard. `changes.sounds` names the weapon's sounds in place of its model's,
`<sounds>_fire` and its `_reload`, `_melee` and `_hold`, each the name lacks the model's own.

The tune belongs to the weapon carried: its rounds and reserve are kept, its name stays the base's in `p.weapon`,
`p.slots` and the events, and its value's `tune` is the changes, so `p:give` of the value tunes it again; a give by
name or a respawn gives it untuned. `options.clip` plays on the weapon if it is in hand, busy for the clip's length
as in a reload, which a sprint or a switch drops; the changes land at once, but its owner sees the parts only once
the clip ends. It fails for a weapon the player does not carry, this handler's `give`s and `take`s before it
counted, and for a model, a marker, a sound or a clip of no file.

| Name | Type | What it is |
|---|---|---|
| `weapon` | `weapon` | Needed. |
| `changes` | `table?` | A table of the options below. It, and each option, may be left out. |
| `changes.stats` | `({[string]: any})?` | Stats laid over the weapon's file, by name, each as the file writes it; `false` takes an optional one away. |
| `changes.attach` | `({[string]: string})?` | The model drawn at each marker of the weapon's model, by the marker's name. |
| `changes.sounds` | `string?` | The name the weapon's sounds take in place of its model's: `<sounds>_fire` and the rest. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.clip` | `string?` | A clip of the weapon's model played as the changes are fitted, `<model>_<clip>.anim`. |

Fails on a value of the wrong type; an option it does not have.

#### To switch a pistol to three-round bursts

```luau server
p:give("weapons/usp", 2)
p:tune_weapon("weapons/usp", { stats = { burst = { count = 3, interval = 0.05 }, interval = 0.35 } })
```

#### To take every change off a pistol

```luau server
p:tune_weapon("weapons/usp", nil)
```

### animate_weapon

- On the server: `p:animate_weapon(name)`

Plays a clip on the weapon in a player's hand, `<model>_<name>.anim` of the weapon's model: before their eyes, and in their body's hands for everyone.

It plays until it ends or the weapon fires, reloads, bashes, is switched or goes down for a sprint or a grenade;
the weapon stays free to do so meanwhile. It fails for a name of no file, and with no weapon in hand, this
handler's `give`s and `take`s before it counted: `p:animate_weapon("silencer_on")` plays `usp_silencer_on.anim` on
the stock pistol in hand.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |

Fails on a value of the wrong type.

### `on("fired")`

- On the server: `on("fired", fn)`

A player fired a shot or let a grenade go: once a shot, whatever its pellets.

| Name | Type | What it is |
|---|---|---|
| `e.player` | `Player` | Who fired. |
| `e.weapon` | `string` | What they fired. |
| `e.origin` | `{x: number, y: number, z: number}` | Where the shot started. |
| `e.dir` | `{x: number, y: number, z: number}` | Which way it went, a unit vector: the view, turned by a spray pattern; 0 for a grenade bursting in the hand. |

## Sounds

Sounds are `.sound` files your mod names by [path](https://engine-modding.pages.dev/0/concepts/mods/#files-and-paths). The server plays a sound for the players its [group](https://engine-modding.pages.dev/0/concepts/groups/) picks: from a point, following a player or an entity, or alike everywhere. A client script plays one for its own player alone, at once and at no network cost, as a click or a banner needs.

A sound played once ends by itself. One that loops is an [entity](https://engine-modding.pages.dev/0/concepts/entities/) until you stop it, or until what it follows goes.

Every sound plays in one part of each player's mix: `world`, `weapons`, `music` or `interface`. A client script turns a part down or muffles it with `sound_mix`, and each player sets their own volume of each part in Settings.

### sound_mix

- On a client: `sound_mix: SoundMix`

How loud each category of sounds plays, and how muffled, on top of the player's own volumes.

### play_sound

- On the server: `play_sound(name, options?) → Entity?`
- On a client: `play_sound(name, options?) → PlayingSound`

Plays a sound for the players who hear it, once, or again and again with `loop` until `stop_sound`.

On the server:

A sound at a point, or following a player or an entity, is heard by the players within its `range`; one with neither
by everyone, alike in both ears. Each sound costs network for every player who hears it, so on a busy server far
sounds wait for near ones. A player hears a sound where they stand, or while dead where the player they watch
stands. A player who joins while a loop plays hears it from where it is. A sound played once ends by itself; with
`loop`, `play_sound` gives the sound's entity, which `kind` names, so `kind` without `loop` fails. One played once
that follows something absent is not heard; a loop is silent while what it follows is absent. A player's game plays
16 sounds longer than 30 s at once, and not one past them.

| Name | Type | What it is |
|---|---|---|
| `name` | `sound` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.at` | `vector?` | The point it is heard from; alike everywhere, by everyone, without `at` or `follow`. Not with `follow`. |
| `options.follow` | `(Player \| Entity \| number)?` | A player or an entity it moves with; a loop stops once the entity is removed or the player leaves. Not with `at`. |
| `options.loop` | `boolean`, default false | Plays again and again until `stop_sound`, and `play_sound` gives the sound's entity. |
| `options.volume` | `number`, 0 to 1, default 1 | How loud, 1 as the file was made. |
| `options.speed` | `number`, 0.25 to 4, default 1 | How fast it plays, its pitch rising with it. |
| `options.range` | `number`, 1 to 1000, or math.huge, default 40 | How far from `at` or `follow` it is heard, in metres; at any distance, from its direction, with `math.huge`. |
| `options.category` | `"weapons" \| "world" \| "music" \| "interface"`, default "world" | The part of each player's mix it plays in, which a client script turns down and muffles (`sound_mix`). |
| `options.start` | `number`, -3600 to 5, default 0 | When it starts, in seconds from now: up to an hour ago, heard from part-way through, or up to 5 s ahead. |
| `options.owner` | `(Player \| number)?` | The player it is about, whom `owner`, `team` and `spectators` pick by. |
| `options.read_by` | `(Group \| ((Player?, Player) -> boolean))?` | Who is shown it; everyone without. |
| `options.kind` | `string?` | Its kind, one `entity_kind` declared on its base; the base's own without. |

Returns `Entity?`.

Fails on a value of the wrong type; an option it does not have; a value outside its range; `at` and `follow` both.

On a client:

Plays a sound for this player alone, as hover and click sounds, banners and a ringing ear need; gives its handle, whose `stop(transition)` fades it out.

No other player hears it, and it costs no network. It starts as the frame ends, 16 a frame at most, and 64 plays
and stops wait at most. A sound whose file still loads is not heard. The scripts' sounds play 32 at once, a new one
stopping the oldest, and 16 longer than 30 s, past which one is not heard. A name the server never named fails.

| Name | Type | What it is |
|---|---|---|
| `name` | `sound` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.volume` | `number`, 0 to 1, default 1 | How loud, 1 as the file was made. |
| `options.speed` | `number`, 0.25 to 4, default 1 | How fast it plays, its pitch rising with it. |
| `options.loop` | `boolean`, default false | Plays again and again, until its handle's `stop`. |
| `options.at` | `vector?` | The point in the world it is heard from; alike in both ears without. |
| `options.range` | `number`, 1 to 1000, or math.huge, default 40 | How far from `at` it is heard, in metres; at any distance, from its direction, with `math.huge`. |
| `options.category` | `"weapons" \| "world" \| "music" \| "interface"`, default "interface" | The part of the mix it plays in, which `sound_mix` turns down and muffles. |

Returns `PlayingSound`.

Fails on a value of the wrong type; an option it does not have; a value outside its range.

#### To play a shot where it was fired

```luau server
play_sound("awp_fire", { at = p.position, range = 200, category = "weapons" })
```

#### To blast every player's ears, wherever they are

```luau server
play_sound("explosion_boom", { volume = 0.8 })
```

#### To groan from a zombie as it walks, and stop the groan

```luau server
local zombie = spawn()
local groan = play_sound("zombie_groan1", { follow = zombie, loop = true, range = 25 })
stop_sound(groan)
```

#### To reload in this player's ears alone

```luau client
play_sound("ak47_reload", { volume = 0.5, category = "weapons" })
```

#### To ring in the ear after a blast, then fade it out

```luau client
local ring = play_sound("tinnitus", { loop = true, volume = 0.6 })
ring:stop({ seconds = 3 })
```

#### To hear a blast where it went off, for this player alone

```luau client
play_sound("explosion_boom", { at = vector.create(4, 1, -2), range = 100, category = "world" })
```

### stop_sound

- On the server: `stop_sound(sound)`

Stops a looping sound `play_sound` gave; one stopped already, or gone with what it followed, is left as it is.

| Name | Type | What it is |
|---|---|---|
| `sound` | `Entity` | Needed. |

Fails on a value of the wrong type.

#### To hold a grenade's hiss until it is thrown

```luau server
local hiss = play_sound("grenade_hold", { follow = p, loop = true, range = 10 })
stop_sound(hiss)
```

### PlayingSound:stop

- On a client: `PlayingSound:stop(transition?)`

Fades the sound out, or stops it at once without a transition; one stopped already is left as it is.

| Name | Type | What it is |
|---|---|---|
| `transition` | `Transition?` | May be left out. |

Fails on a value of the wrong type.

#### To ring in the ear after a blast, then fade it out

```luau client
local ring = play_sound("tinnitus", { loop = true, volume = 0.6 })
ring:stop({ seconds = 3 })
```

### SoundMix:set

- On a client: `SoundMix:set(categories, transition?)`

Turns down and muffles each category it names, eased over `transition`, at once without; `sound_mix.world` and the rest read back what was last set.

It changes this player's mix alone, the sounds the server plays among it. Each category reads `{volume = 1,
muffle = 0}` until set, and a new match sets them so again. A muffle is a low-pass from 20 kHz, clear, down to
200 Hz at 1.

| Name | Type | What it is |
|---|---|---|
| `categories` | `table` | A table of the options below. Each may be left out. |
| `categories.weapons` | `Mix?` | Shots, reloads and the rest of the weapons' sounds. A table of the options below it. |
| `categories.weapons.volume` | `number?`, 0 to 1 | How loud, 0 silent and 1 as made. |
| `categories.weapons.muffle` | `number?`, 0 to 1 | How muffled, 0 clear and 1 as through a wall. |
| `categories.world` | `Mix?` | The world's own sounds: steps, doors, blasts and voices. A table of the options below it. |
| `categories.world.volume` | `number?`, 0 to 1 | How loud, 0 silent and 1 as made. |
| `categories.world.muffle` | `number?`, 0 to 1 | How muffled, 0 clear and 1 as through a wall. |
| `categories.music` | `Mix?` | The music the mod plays. A table of the options below it. |
| `categories.music.volume` | `number?`, 0 to 1 | How loud, 0 silent and 1 as made. |
| `categories.music.muffle` | `number?`, 0 to 1 | How muffled, 0 clear and 1 as through a wall. |
| `categories.interface` | `Mix?` | Menus' and the HUD's clicks and banners. A table of the options below it. |
| `categories.interface.volume` | `number?`, 0 to 1 | How loud, 0 silent and 1 as made. |
| `categories.interface.muffle` | `number?`, 0 to 1 | How muffled, 0 clear and 1 as through a wall. |
| `transition` | `Transition?` | May be left out. |

Fails on a value of the wrong type; an option it does not have; a value outside its range.

#### To muffle the world after a blast, then bring it back

```luau client
sound_mix:set({ world = { volume = 0.3, muffle = 0.85 }, weapons = { muffle = 1 } }, { seconds = 0.3 })
sound_mix:set({ world = { volume = 1, muffle = 0 }, weapons = { muffle = 0 } }, { seconds = 4, timing = "ease-in" })
```

## Effects

Particle effects, such as an explosion or smoke: `.effect` files the mod plays at a point, along a direction, or riding something that moves. An effect played once ends by itself; one that loops is an [entity](https://engine-modding.pages.dev/0/concepts/entities/) until it is stopped.

Like sounds, effects start at the moment the server says, so an explosion shows as the grenade lands, however late a player's game hears of it.

### play_effect

- On the server: `play_effect(name, options?) → Entity?`

Sets off a particle effect for the players who see it, once, or again and again with `loop` until `stop_effect`.

An effect played once ends by itself; with `loop`, `play_effect` gives the effect's entity, which `kind` names, so
`kind` without `loop` fails. One that rides something absent is not set off. Once what it rides goes, a burst stays
where it was last; a loop goes with it.

| Name | Type | What it is |
|---|---|---|
| `name` | `effect` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.at` | `vector?` | The point it bursts at, the origin without; with `follow`, an offset from where what it rides stands. |
| `options.dir` | `vector?` | The way it points, up without. |
| `options.follow` | `(Player \| Entity \| number)?` | A player or an entity it rides as it is drawn. |
| `options.loop` | `boolean`, default false | Plays again each time it is over, until `stop_effect`, and `play_effect` gives the effect's entity. |
| `options.start` | `number`, -3600 to 5, default 0 | When it starts, in seconds from now: up to an hour ago, seen from part-way through, or up to 5 s ahead. |
| `options.owner` | `(Player \| number)?` | The player it is about, whom `owner`, `team` and `spectators` pick by. |
| `options.read_by` | `(Group \| ((Player?, Player) -> boolean))?` | Who is shown it; everyone without. |
| `options.kind` | `string?` | Its kind, one `entity_kind` declared on its base; the base's own without. |

Returns `Entity?`.

Fails on a value of the wrong type; an option it does not have; a value outside its range.

#### To burst a grenade where it lands

```luau server
play_effect("explosion", { at = p.position })
```

#### To puff smoke from a wall, pointing away from it

```luau server
play_effect("smoke", { at = vector.create(0, 1, 4), dir = vector.create(0, 0, -1) })
```

#### To keep a zombie smoking as it walks, and stop it

```luau server
local zombie = spawn()
local smoke = play_effect("smoke", { follow = zombie, at = vector.create(0, 1.6, 0), loop = true })
wait(5)
stop_effect(smoke)
```

### stop_effect

- On the server: `stop_effect(effect)`

Stops a looping effect `play_effect` gave; one stopped already, or gone with what it rode, is left as it is.

| Name | Type | What it is |
|---|---|---|
| `effect` | `Entity` | Needed. |

Fails on a value of the wrong type.

#### To stop a fire once it has burned for ten seconds

```luau server
local fire = play_effect("smoke", { at = vector.create(2, 0, 2), loop = true })
wait(10)
stop_effect(fire)
```

## Map

What the mapper put in the map, by the names they gave it:

- markers: points with a facing, such as spawns;
- parts: objects the mod can move, hide or make solid, such as doors and lifts;
- triggers: spaces that hear a player come in, look at them, use them or shoot them.

[Making a map](https://engine-modding.pages.dev/0/mapping/making-a-map/) says how a mapper marks each.

Each is reached by its name, `parts.door_a`, or by a pattern, `parts("barrier*")`, where `*` stands for any text. A marker or a part the map does not have fails, naming the map. A method called on a set calls it on each member, as `parts("barrier*"):remove()`.

A trigger's handlers are given at a module's top, by its name: `triggers.buy_door:on("activated", fn)`. A name the map does not have declares triggers the mod makes while it runs, with `trigger`, as a revive point where a player fell. The server tests every trigger each tick, and what it last found is what a trigger's `players` and a player's `is_inside` and the like read. A trigger that fires on clients is heard by their scripts too, so a jump pad pushes a player at once, as their game predicts it.

A trigger reads `name`, `position`, `properties`, frozen, `owner`, `exists` and `enabled`. Set `enabled` to false, and the bodies inside are heard leaving and nothing sets it off; set true again, it hears the bodies inside come in.

### markers

- On the server: `markers: Markers`

The map's markers, points with a facing, by name, `markers.spawn0`, or by a pattern, `markers("spawn*")`, in the map's order. A name the map has not fails.

### parts

- On the server: `parts: Parts`

The map's parts, by name, `parts.door_a`, or by a pattern, `parts("barrier*")`, whose methods call each. One removed is left out of the sets made after.

### triggers

- On both sides: `triggers: Triggers`

The map's triggers, and those the mod's handlers declare, by name, `triggers.buy_door`, or by a pattern, which lists those made at run time after the map's. One removed is left out of the sets made after.

### Fields

What a player, `p`, or an entity, `e`, reads beside the mod's state.

| Name | Type | Side | What it is |
|---|---|---|---|
| `p.use_target` | `(Trigger & {held: number?})?` | both | The use trigger they would use, with the seconds `held` its key so far; on a client, the shown player's alone. |
| `e.name` | `string?` | server | A part's name in the map; nil for what the mod made. |
| `e.properties` | `{[string]: Property}?` | server | A part's properties, as the mapper set them. |

### is_inside

- On the server: `p:is_inside(trigger) → boolean`

Whether the player stands inside `trigger`, a touch or a use trigger, as the server last found them.

It fails for a trigger of another type, and is false for one removed.

| Name | Type | What it is |
|---|---|---|
| `trigger` | `Trigger` | Needed. |

Returns `boolean`.

Fails on a value of the wrong type.

#### To heal a player while they stand in a spring

```luau server
local spring = trigger("spring", "touch", { box = vector.create(6, 3, 6) })
if p:is_inside(spring) then
    p:heal(5)
end
```

### is_looking_at

- On the server: `p:is_looking_at(trigger) → boolean`

Whether the player is looking at `trigger`, a look trigger, as the server last found them: their view meeting it before anything that stops shots.

It fails for a trigger of another type, and is false for one removed.

| Name | Type | What it is |
|---|---|---|
| `trigger` | `Trigger` | Needed. |

Returns `boolean`.

Fails on a value of the wrong type.

#### To tell a player what they look at

```luau server
local statue = trigger("statue", "look", { box = vector.create(1, 3, 1) })
if p:is_looking_at(statue) then
    say("The founder, 1921", { to = { p } })
end
```

### is_using

- On the server: `p:is_using(trigger) → boolean`

Whether the player holds the use key on `trigger`, a use trigger, as the server last found them.

It fails for a trigger of another type, and is false for one removed.

| Name | Type | What it is |
|---|---|---|
| `trigger` | `Trigger` | Needed. |

Returns `boolean`.

Fails on a value of the wrong type.

### time_inside

- On the server: `p:time_inside(trigger) → number?`

How many seconds the player has stood inside `trigger`, a touch or a use trigger, as the server last found them; nil when they are not.

It fails for a trigger of another type.

| Name | Type | What it is |
|---|---|---|
| `trigger` | `Trigger` | Needed. |

Returns `number?`.

Fails on a value of the wrong type.

### time_looking_at

- On the server: `p:time_looking_at(trigger) → number?`

How many seconds the player has looked at `trigger`, a look trigger, as the server last found them; nil when they do not.

It fails for a trigger of another type.

| Name | Type | What it is |
|---|---|---|
| `trigger` | `Trigger` | Needed. |

Returns `number?`.

Fails on a value of the wrong type.

### time_using

- On the server: `p:time_using(trigger) → number?`

How many seconds the player has held the use key on `trigger`, a use trigger, as the server last found them; nil when they do not.

It fails for a trigger of another type.

| Name | Type | What it is |
|---|---|---|
| `trigger` | `Trigger` | Needed. |

Returns `number?`.

Fails on a value of the wrong type.

#### To show how far a player is through opening a crate

```luau server
local crate = trigger("crate", "use", { box = vector.create(1, 1, 1) }, { activation_time = 4 })
local held = p:time_using(crate)
if held then
    say(`Opening: {math.floor(held / 4 * 100)}%`, { to = { p } })
end
```

### trigger

- On the server: `trigger(name, kind, { box?, cylinder?, sphere? }, options?) → Trigger`

Makes a trigger at run time of a name a handler declared, of `kind` and `shape`, and gives it.

A name the map has no trigger of is declared by its handlers, `triggers.revive:on(…)`, given at a module's top;
making one of a name no handler declared, or whose handlers hear an event `kind` does not, fails, as does one of a
name the map has. An option of another type than `kind`'s fails: `detect` is a touch trigger's, `look` a use
trigger's, `means` and `weapons` a damage trigger's, and `activation_time` that of every type but damage. Each shape
stands on its origin, a sphere and a box too. 1024 made at run time are alive at most.

One that `fires_on_client` is told each client as its player meets it, so their scripts hear it too: its name and
properties, and those of all such, within what a client joining is told at once.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `kind` | `"touch" \| "look" \| "use" \| "damage"` | Needed. |
| `shape` | `table` | A table of the options below. Those marked needed may not be left out. |
| `shape.box` | `vector?` | A box of this size along x, y and z, in metres, each 0.01 to 1000. Not with `cylinder` or `sphere`. |
| `shape.cylinder` | `TriggerCylinder?` | An upright cylinder. A table of the options below it. Not with `box` or `sphere`. |
| `shape.cylinder.radius` | `number`, 0.01 to 1000 | How far its side is from its upright axis. Needed. |
| `shape.cylinder.height` | `number`, 0.01 to 1000 | How tall it stands from its origin up. Needed. |
| `shape.sphere` | `number?`, 0.01 to 1000 | A ball of this radius, in metres. Not with `box` or `cylinder`. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.at` | `vector?` | Where its origin stands; at the world's origin without. |
| `options.angles` | `Angles?` | How it is turned about its origin. |
| `options.owner` | `(Player \| number)?` | The player it is about, whom `triggered_by` picks by. |
| `options.triggered_by` | `(Group \| ((Player?, Player) -> boolean))?` | Who sets it off, and no NPC; everyone, NPCs too, without. |
| `options.properties` | `({[string]: Property})?` | Its properties, as a mapper's: each a boolean, a number, a string or a list of them, 1024 at most. |
| `options.fires_on_client` | `boolean`, default false | Whether the scripts of each client hear what it hears of their own player too. |
| `options.detect` | `("overlap" \| "center" \| "contain")?` | A touch trigger's: what of a body counts as inside, any of its box, its middle or all of it. |
| `options.activation_time` | `number?`, 0 or more | A touch, look or use trigger's: the seconds its condition lasts before `activated`; at once without. |
| `options.look` | `boolean?` | A use trigger's: whether the use key counts only while the player's view meets it. |
| `options.means` | `{string}?` | A damage trigger's: the means of damage it hears, as `"bullet"`; any without. |
| `options.weapons` | `{string}?` | A damage trigger's: the weapons it hears, by name; any without. |

Returns `Trigger`.

Fails on a value of the wrong type; an option it does not have; a key it needs left out; a value outside its range; `box` and `cylinder` both; `box` and `sphere` both; `cylinder` and `sphere` both.

#### To mark where a player fell, for a teammate to revive them

```luau server
triggers.revive:on("activated", function(e)
    e.trigger:remove()
end)

local function revive_point(fallen)
    return trigger("revive", "use", { cylinder = { radius = 1, height = 2 } }, {
        at = fallen.position,
        owner = fallen,
        activation_time = 3,
        triggered_by = everyone - owner,
        properties = { hint = `Revive {fallen.name}` },
    })
end

on("killed", function(e)
    revive_point(e.victim)
end)
```

### DeclaredTrigger:on

- On the server: `DeclaredTrigger:on(event, fn)`

Runs `fn` each time a trigger of this name, made at run time, hears `event`, given `{trigger, player}` as `Trigger:on`'s handlers are.

A name the map has no trigger of reads as one declared: its handlers, given at a module's top, declare the name,
so `trigger` makes triggers of it. Their type is known only as one is made, which fails if its type does not hear
an event its handlers do.

| Name | Type | What it is |
|---|---|---|
| `event` | `TriggerHears` | Needed. |
| `fn` | `(e: TriggerEvent) -> ()` | Needed. |

Fails on a value of the wrong type.

#### To bring a player back where a teammate revived them

```luau server
triggers.revive:on("activated", function(e)
    local fallen = e.trigger.owner
    if fallen then
        fallen:respawn()
        fallen:set_position(e.trigger.position)
    end
    e.trigger:remove()
end)
```

### Trigger:on

- On the server: `Trigger:on(event, fn)`
- On a client: `Trigger:on(event, fn)`

Runs `fn` each time this trigger of the map hears `event`, given `{trigger, player}`.

On the server:

It is given at a module's top: a second for the same event in the same file replaces the first. A trigger hears
its type's events alone: a touch trigger `entered` and `left`, a look trigger `looked_at` and `looked_away`, a use
trigger `use_pressed`, `use_released` and `use_cancelled`, a hold ended early by letting go, looking away or
walking out. Each hears `activated` once its condition has lasted its `activation_time`: at once without one, once
a stay, a look or a hold. A damage trigger hears `activated` on each shot or blast that reaches it, given
`attacker`, as `player` too, `amount`, `means`, `weapon`, `point`, `dir` and `projectile`.

`player` is a person, a bot or an NPC: players and NPCs touch, players alone look and use. The dead and the gone
are heard leaving, as `left`, `looked_away` and `use_cancelled`, and so is whom it held as it is disabled. Removed
by a handler, it hears nothing more of what the server found that tick; disabled by one, nothing that starts or
sets it off, so of four players pressing the use key on one buy trigger in a tick, one buys it. A trigger made at
run time is listened to by its name before it is made, `triggers.revive:on` (`DeclaredTrigger:on`).

A body is followed along the way it went during the tick, so one crossing a thin trigger at a sprint is heard
entering and leaving, in order. A player standing in several use triggers uses the first `look` trigger their view
meets, else the one nearest their eyes. A damage trigger hears a shot only up to where it struck, with the damage it
does that far.

| Name | Type | What it is |
|---|---|---|
| `event` | `TriggerHears` | Needed. |
| `fn` | `(e: TriggerEvent) -> ()` | Needed. |

Fails on a value of the wrong type.

On a client:

Runs `fn` each time a trigger that fires on this client hears `event` of the client's own player, given `{trigger, player}`, as its game first predicts the player through it.

It is given at a script's top by the trigger's name, so one the server tells this client later, or makes at run
time, is heard too; a set, `triggers("pad*"):on(…)`, listens by its pattern. Only the server's triggers that fire
on clients are heard, and of them what a damage trigger hears is not. The server's handler of the same trigger
hears it too, a moment later.

While it runs, `me` reads the player as that moment left them, and `me:set_velocity`, `add_velocity`, `tune`,
`limit_view`, `set_position` and `set_angles` move them from the next moment on, as the server's handler orders
it: the two agreeing show nothing, and differing a correction, once. Anywhere else, or on another player, those
fail.

| Name | Type | What it is |
|---|---|---|
| `event` | `TriggerHears` | Needed. |
| `fn` | `(e: TriggerEvent) -> ()` | Needed. |

Fails on a value of the wrong type.

#### To open a door for the player who buys it

```luau server
triggers.buy_door:on("activated", function(e)
    say(`{e.player.name} opened the door`)
    e.trigger:remove()
end)
```

#### To launch a player off a jump pad as soon as they step on it

```luau client
triggers.jump_pad:on("entered", function(e)
    me:set_velocity(vector.create(0, e.trigger.properties.launch, 0))
end)
```

### Trigger:remove

- On the server: `Trigger:remove()`

Removes the trigger: it hears nothing more, reads `exists` false, and is left out of the sets made after. One removed already is left as it is.

#### To take away a revive point once it is used

```luau server
local revive = trigger("revive", "use", { sphere = 1 })
revive:remove()
```

### Trigger:players

- On the server: `Trigger:players() → {Player}`

The players and NPCs it holds, as the server last found them: those inside a touch trigger, looking at a look trigger or using a use trigger; none while it is disabled.

Returns `{Player}`.

#### To heal everyone standing in a spring

```luau server
local spring = trigger("spring", "touch", { box = vector.create(6, 3, 6) })
for _, p in spring:players() do
    p:heal(5)
end
```

### Trigger:attach

- On the server: `Trigger:attach(to, { offset? }?)`

Makes the trigger ride a player, an NPC, a model or a part, turning as it turns; once that goes, the trigger stays where it was.

One gone already is left as it is.

| Name | Type | What it is |
|---|---|---|
| `to` | `Player \| Entity \| number` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.offset` | `vector?` | Where its origin stays in the space of what it rides; at that origin without. |

Fails on a value of the wrong type; an option it does not have.

#### To make a zone that follows a player

```luau server
local aura = trigger("aura", "touch", { sphere = 3 }, { owner = p, triggered_by = everyone - owner })
aura:attach(p, { offset = vector.create(0, 1, 0) })
```

### Trigger:detach

- On the server: `Trigger:detach()`

Lets go of what the trigger rides, so it stays where it is.

### Trigger:set_position

- On the server: `Trigger:set_position(point)`

Moves the trigger's origin to `point` at once, letting go of what it rides; it sweeps nothing on the way.

| Name | Type | What it is |
|---|---|---|
| `point` | `vector` | Needed. |

Fails on a value of the wrong type.

#### To move a buy point to where the box now stands

```luau server
local box = trigger("mystery_box", "use", { box = vector.create(2, 1, 1) })
box:set_position(vector.create(12, 0, -4))
```

### Trigger:set_angles

- On the server: `Trigger:set_angles(angles)`

Turns the trigger about its origin to `angles` at once, letting go of what it rides; an angle left out is 0.

| Name | Type | What it is |
|---|---|---|
| `angles` | `Angles` | Needed. |

Fails on a value of the wrong type.

## Traces

Questions about lines through the world: what is the first thing between two points, is the way clear, where is the floor below a point. A trace meets the map, its parts, solid models and shapes, and with `bodies` the players and NPCs too.

In a client script, `aimed()` gives the player or NPC under the crosshair, as the player's game draws it.

### aimed

- On a client: `aimed() → Player?`

Gives the player or NPC under the crosshair as the player's game draws this frame, or nil for none.

It looks along the line from the eye through the middle of the screen, against the map and the bodies drawn.
Models, parts and shapes are never aimed at. A render that reads it draws again as it changes. The server checks
again anything that matters, as a player's game may lie.

Returns `Player?`.

#### To name whoever the player aims at

```luau client
mount(function()
    local aimed_at = aimed()
    if not aimed_at then
        return nil
    end
    return Box { aimed_at.name or "", style = { position = "absolute", top = 560, left = 0, right = 0, textAlign = "center" } }
end)
```

### trace

- On the server: `trace(from, to, { mask?, bodies?, ignore? }?) → Hit?`

Gives the first thing on the line from one point to another, or nil if the line is clear.

It meets the map and its parts, solid models and shapes where they stand at the call's tick, and with `bodies` the
players and NPCs too. What it gives is a table: `position` and `normal`, vectors; `player`, the player or NPC hit;
`entity`, the part, model or shape hit; `ladder`, whether the surface is a ladder's; and `mantle`, whether it may be
climbed over, false for a body. A line starting inside a body meets it where it starts, so put whoever traces in
`ignore`. A body is met by its capsule, not by its limbs as a shot is.

Traces run only in handlers, timers and ticks, 1024 a call at most, `is_trace_clear` and `ground` among them.

| Name | Type | What it is |
|---|---|---|
| `from` | `vector` | Needed. |
| `to` | `vector` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.mask` | `Mask?` | What of the map and its obstacles it meets; anything solid without. A table of the options below it. |
| `options.mask.players` | `boolean`, default false | What stops players. |
| `options.mask.npcs` | `boolean`, default false | What stops NPCs. |
| `options.mask.shots` | `boolean`, default false | What stops bullets: `{ shots = true }` meets what a shot does. |
| `options.mask.projectiles` | `boolean`, default false | What stops thrown grenades and rockets. |
| `options.bodies` | `boolean`, default false | Meets the players alive and the NPCs too, where they stood at the start of the call. |
| `options.ignore` | `({Player \| number})?` | Players and NPCs whose bodies it passes through, 1088 at most. |

Returns `Hit?`.

Fails on a value of the wrong type; an option it does not have.

#### To find what a player looks at

```luau server
local eye = p.position + vector.create(0, 1.6, 0)
local hit = trace(eye, eye + angles.forward(p) * 100, { bodies = true, ignore = { p } })
if hit and hit.player then
    say(`{p.name} looks at {hit.player.name}`)
end
```

#### To fire a bullet of your own, stopped by what stops shots

```luau server
local from = p.position + vector.create(0, 1.6, 0)
local hit = trace(from, from + angles.forward(p) * 50, { mask = { shots = true }, bodies = true, ignore = { p } })
if hit and hit.player then
    hit.player:hurt(30, p, "laser", { kind = "bullet" })
end
```

### is_trace_clear

- On the server: `is_trace_clear(a, b, { mask? }?) → boolean`

Whether the line between two points is clear: true if `trace` would meet nothing on it, the bodies aside.

| Name | Type | What it is |
|---|---|---|
| `a` | `vector` | Needed. |
| `b` | `vector` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.mask` | `Mask?` | What of the map and its obstacles it meets; anything solid without. A table of the options below it. |
| `options.mask.players` | `boolean`, default false | What stops players. |
| `options.mask.npcs` | `boolean`, default false | What stops NPCs. |
| `options.mask.shots` | `boolean`, default false | What stops bullets: `{ shots = true }` meets what a shot does. |
| `options.mask.projectiles` | `boolean`, default false | What stops thrown grenades and rockets. |

Returns `boolean`.

Fails on a value of the wrong type; an option it does not have.

#### To send each zombie that can see a player after them

```luau server
local eyes = vector.create(0, 1.6, 0)
for _, zombie in players do
    if zombie.npc and is_trace_clear(zombie.position + eyes, p.position + eyes) then
        zombie:chase(p)
    end
end
```

### ground

- On the server: `ground(point) → vector?`

Gives the floor below a point, the map's or a part's, a model's or a shape's, as a vector; nil if there is none.

A point resting on a floor, or a hair below it, finds that floor.

| Name | Type | What it is |
|---|---|---|
| `point` | `vector` | Needed. |

Returns `vector?`.

Fails on a value of the wrong type.

#### To drop a grenade onto the floor below a player

```luau server
local floor = ground(p.position + vector.create(0, 2, 0))
if floor then
    model("grenade", { at = floor })
end
```

## Chat

Lines in the chat box. The mod says lines to everyone or to some players, with a coloured label, and decides who sees each line a player says.

Every player has the channel `all`. The mod gives them more, such as `team` or a private reply channel, and Shift+Tab goes round them. A line is never the mod's to change: it routes it or stops it.

`print` writes to the server's console instead, which players never see.

### say

- On the server: `say(text, { to?, label?, color? }?)`

Writes a line of up to 150 bytes into the chat boxes of the players `to` lists, or of everyone.

Every line reaches each player it is for, in order with the rest of what they are told. A label is up to 16 bytes.

| Name | Type | What it is |
|---|---|---|
| `text` | `string \| number \| Secret` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.to` | `({Player \| number})?` | The players who see it; everyone without. |
| `options.label` | `string?` | A tag before the line, as a channel's. |
| `options.color` | `string?` | The colour of the label, or of the whole line without one, as CSS writes it. |

Fails on a value of the wrong type; an option it does not have.

#### To greet everyone

```luau server
say("Round 1: survive!")
```

#### To whisper to a player in red, labelled as the server

```luau server
say("You are out of lives", { to = { p }, label = "Server", color = "#ff4040" })
```

### set_channel

- On the server: `p:set_channel(name, { label, color? }?)`

Gives a player a channel to say lines on, or changes the one they have; with nil, takes it away.

A name is up to 16 lowercase letters, digits and `_`, and a label up to 16 bytes. A player has 8 channels at most,
`all` among them, which every player has first, labelled All, and the mod can neither change nor take away. They
go round them with Shift+Tab in the order given, and lose them when they leave. Who sees a line said on a channel is
the mod's to say, with the `said` event.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `channel` | `table?` | A table of the options below. It may be left out; given, it needs those marked needed. |
| `channel.label` | `string` | What lines on it are tagged with, before the speaker's name. Needed. |
| `channel.color` | `string?` | The label's colour, as CSS writes it. |

Fails on a value of the wrong type; an option it does not have; a key it needs left out.

#### To give a player a channel to their squad

```luau server
p:set_channel("squad", { label = "Squad", color = "#7cff4d" })
```

#### To take it away

```luau server
p:set_channel("squad", nil)
```

### print

- On the server: `print(...values)`

Prints a line to the server's console and log, each value as `tostring` shows it, a tab between them.

Players never see it: `say` writes to their chat. A secret's text is hidden as `<secret name>`, wherever it is in
the line.

| Name | Type | What it is |
|---|---|---|
| `...values` | `any` | Any number of them. |

Fails on a value of the wrong type.

#### To log each round as it starts

```luau server
print("round", 3, "starts with", p.name)
```

### `on("said")`

- On the server: `on("said", fn)`
- On a client: `on("said", fn)`

A line was said in the chat, one a second a player, cleaned and cut to 150 bytes. On the server, what its handlers leave in `to`, `label` and `color` routes it, never back to the speaker; on a client, it is shown.

On the server:

| Name | Type | What it is |
|---|---|---|
| `e.player` | `Player` | Who said it. |
| `e.text` | `string` | What they said. |
| `e.channel` | `string` | The channel they said it on. |
| `e.to` | `{Player \| number}?` | Who is shown it, as players; set it to players or ids. Everyone for nil. |
| `e.label` | `string?` | What it shows before the line. |
| `e.color` | `string?` | The label's colour, as CSS `#rrggbbaa`. |

On a client:

| Name | Type | What it is |
|---|---|---|
| `e.from` | `Player?` | Who said it; nil for the mod's own lines. |
| `e.text` | `string` | What was said. |
| `e.label` | `string?` | What it shows before the line. |
| `e.color` | `string?` | The label's colour, as CSS `#rrggbbaa`. |

## Commands

Commands players type in the chat box, as `/mute`, or bind to keys themselves, and the actions on the keys. A module declares each command at its top with its arguments, typed, and its handler. The chat box completes the names and checks the arguments before a line is sent, and the server checks them again. `run_by` says who may run one, as a [group](https://engine-modding.pages.dev/0/concepts/groups/).

Every server has the same actions, each on a key the player chooses once for all of them: moving, firing, the slots, crouching, and nine purpose actions that do nothing until a mod handles them, named by what they are for: `tactical`, `lethal`, `utility`, `ultimate`, `buy`, `drop`, `quick_menu`, `ping` and `map`. No mod binds a key. `action` handles a purpose action, with a label players see beside it, hearing its key go down and up; `p.held` says which actions a player holds. A client script reads the player's keys for an action with `keys_of`, as a hint does, and whether one is held with `is_held`.

A player binds keys in Settings → Controls, which lists every action, the server's label beside each purpose action its mod handles, or with `/bind`. On a server, Controls asks whether a change is for every server or this server alone, so a player may put `tactical` on F5 on your server and keep it on Q on the rest. The loading screen lists the actions your mod handles, with their labels and the player's keys.

Crouch, prone, sprint, aim and the scoreboard a player holds or toggles, as they choose in Controls for every server: crouch and prone toggle by default, the rest are held. A toggled one is on from one press to the next, and your mod sees it held all that while, in `p.held` and `is_held`. The engine turns a toggled sprint off when the sprint ends, a toggled aim on a weapon whose `aim_fov` is a list, which steps a level a press, and toggled aim, sprint and scoreboard when the player dies; crouch and prone stay, and no life starts aiming or sprinting. A purpose action is never toggled: its handler hears each press and release, and a mod that wants a toggle writes it.

| key | action | | key | action |
|---|---|---|---|---|
| W A S D | `forward`, `left`, `back`, `right` | | Q | `tactical` |
| Space | `jump` | | G | `lethal` |
| C | `crouch` | | E | `utility` |
| Ctrl | `prone` | | X | `ultimate` |
| Shift | `sprint` | | 4 | `equipment` |
| left, right mouse | `fire`, `aim` | | B | `buy` |
| R | `reload` | | H | `drop` |
| F | `use` | | Z | `quick_menu` |
| V | `melee` | | middle mouse | `ping` |
| 1, 2, 3, 5 | `slot1`, `slot2`, `slot3`, `slot5` | | M | `map` |
| wheel up, down | `slot_next`, `slot_prev` | | T, `` ` `` | `chat` |
| Tab | `scoreboard` | | F12 | `screenshot` |

These are the keys a player who never changed them has. An argument can take a word of a list, such as a map to vote for: `choices` fills the list, for everyone or for one player. `switch_off` turns stock actions off, such as sprinting.

### Fields

What a player, `p`, or an entity, `e`, reads beside the mod's state.

| Name | Type | Side | What it is |
|---|---|---|---|
| `p.held` | `{[string]: boolean}?` | server | The actions they held as of the last tick, as `p.held.fire` or `p.held.tactical`: the buttons, the movement keys and the purpose actions; a dead player's too. Crouch, prone, sprint and aim toggled by the player read held while on. |
| `p.pressed` | `{[string]: boolean}?` | server | The actions they held at any point since the tick before, as `held` names them. |

### is_held

- On a client: `is_held(action) → boolean`

Gives whether the player holds an action, as Tab for `scoreboard`, Shift for `sprint` or Q for `tactical`, on whatever keys the player put it. Crouch, prone, sprint, aim and the scoreboard a player may toggle in place of holding: a press turns one on and the next off, and it reads held while on.

A render that reads it draws again as it changes. A name of no action fails.

| Name | Type | What it is |
|---|---|---|
| `action` | `string` | Needed. |

Returns `boolean`.

Fails on a value of the wrong type.

#### To show the scoreboard while it is held, or toggled on

```luau client
mount(function()
    if not is_held("scoreboard") then
        return nil
    end
    return Box { "Scores", style = { position = "absolute", top = 100, left = 0, right = 0, textAlign = "center" } }
end)
```

### keys_of

- On a client: `keys_of(action) → {string}`

Gives the player's keys for an action, as `"tactical"`, or a command line they bound, as `"open shop"`: an array of the keys' names, in the order Controls shows them, a key bound for this server alone first, empty for none.

A render that reads it draws again as the player rebinds, so a hint names the key they press.

| Name | Type | What it is |
|---|---|---|
| `action` | `string` | Needed. |

Returns `{string}`.

Fails on a value of the wrong type.

#### To tell the player which key plants the bomb

```luau client
mount(function() return Box { `Hold {keys_of("use")[1] or "use"} to plant`, style = { position = "absolute", top =
700, left = 900 } } end)
```

### choices

- On the server: `choices(name, words?, { player? }?)`

Fills the list `name`, whose words a command's `t.choices(name)` argument takes, as maps to vote for; with nil words, empties it.

A list is everyone's, or with `player` that player's own, read over everyone's for them. It has any number of
words, each up to 32 bytes without spaces, as many as a player's game can be sent; a mod has 8 names of lists in
all, each 1 to 16 lowercase letters, digits and `_`. Each player's game is told a list again as it changes, and
completes its words as they are typed.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `words` | `{string}?` | May be left out. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.player` | `(Player \| number)?` | The player whose own list it is, read over everyone's; everyone's without. |

Fails on a value of the wrong type; an option it does not have.

#### To let players vote for a map of a list

```luau server
command("vote", { help = "Vote for the next map", args = { { "map", t.choices("maps") } } }, function(e)
	say(`{e.player.name} votes for {e.map}`)
end)
on("start", function()
	choices("maps", { "yard", "testbed" })
end)
```

#### To give one player a list of their own, and take it away

```luau server
choices("maps", { "yard" }, { player = p })
choices("maps", nil, { player = p })
```

### command

- On the server: `command(name, spec, run?)`

Declares one of the mod's commands, which players type in the chat box after a `/` or run by a key they bind, and its handler, `run`.

A module declares its commands at its top as it loads. The box reaches one by its name, an alias, or either as
`module.name` (`/admins.mute`, `main.` for `main.server.luau`'s). A name or an alias two modules declare fails at
load, naming both; a module declaring one again replaces its own. Names, aliases and argument names are 1 to 16
lowercase letters, digits and `_`, an argument's not a Luau keyword, as its handler reads `e.<name>`; none is named
`player`. No name or alias is one of the client's own (`bind`, `binds`, `clear`, `connect`, `disconnect`, `forget`,
`fov`, `help`, `maxfps`, `name`, `netgraph`, `quit`, `rate`, `request`, `screenshot`, `sensitivity`, `unbind`,
`volume`), nor the engine's (`set`, `reset`, `match`, `match_next`, `match_restart`, `match_rotation`, `kick`,
`ban`, `unban`), nor an action's, as `jump`, `slot1` or `map`. A mod has up to 256 commands.

Each argument of `args` is `{ name, type }`, its type one of `t`'s: `t.player`, typed by any part of a name or as
`#id` and given as the player; `t.int`, `t.number`, `t.string` (one word) or `t.one_of`, refused outside their
limits (`/mode: rounds: 1 to 9, got 10`); `t.duration`, a whole number and its unit, `30s`, `5m`, `12h` or `7d`, up
to 3650 days, given in seconds; `t.text`, the rest of the line, so last; or `t.choices(name)`, a word of the list
`choices` fills. Any of them `t.optional(…)` may be left out, after those needed, and reads nil.

Each player's game is told the commands they may run and the lists those read, again as what a `run_by` reads
changes, so a player's box never lists one they may not run. The box completes them and checks a line before it is
sent; the server checks it again, tells the player why one is dropped, and runs `run` with `e.player`, who ran it,
and each argument by its name. The server prints each command run, and each one dropped. A command typed counts as a
line said, one a second either kind; one run by a key, 10 a second after a burst of 5. No mod binds a key: a
player binds a command themselves, and a key held down is an action's (`action`).

What `run` returns before any `wait`, `{to, label, color}` as `say` takes them, shows the line typed as chat to the
players `to` lists, written from what the server took, players by name, never back to the player; without `to`, or
if `run` fails, no one sees it. A command declared without `run` is heard by `on_event("command", player, name,
args)`, `args` in order and players by id, whose answer routes it the same.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `spec` | `table` | A table of the options below. Each may be left out. |
| `spec.help` | `string?` | What `/help` shows of it, up to 64 bytes. |
| `spec.aliases` | `{string}?` | Other names the box runs it by, up to 4. |
| `spec.args` | `({{string \| Type}})?` | Its arguments in order, up to 8, each `{ name, type }` of a type of `t`; none without. |
| `spec.run_by` | `(Group \| ((Player?, Player) -> boolean))?` | Who may run it: any group but those that pick by an owner (`owner`, `team`, `spectators`); everyone without. |
| `run` | `((e: {player: Player, [string]: any}) -> Routing?)?` | May be left out. |

Fails on a value of the wrong type; an option it does not have.

#### To let players act in chat, as `/me waves`

```luau server
command("me", { help = "Say what you do", args = { { "action", t.text } } }, function(e)
	say(`* {e.player.name} {e.action}`)
end)
```

#### To give admins a command, shown to the player it is run on

```luau server
command("slay", {
	help = "Kill a player",
	aliases = { "sl" },
	args = { { "target", t.player }, { "reason", t.optional(t.text) } },
	run_by = admins,
}, function(e)
	e.target:hurt(e.target.health, e.player)
	return { to = { e.target }, label = "Admin", color = "#ff4040" }
end)
```

### action

- On the server: `action(name, { label }, handler)`

Handles a purpose action, hearing its key go down and up: `tactical`, `lethal`, `utility`, `ultimate`, `buy`, `drop`, `quick_menu`, `ping` or `map`, named by what it is for.

A player binds each action once, for every server, so a mod uses the keys they chose and binds none. Their Controls
and loading screen show `label` beside it. A module declares it at its top as it loads; one two modules declare
fails at load, naming both, and a module declaring it again replaces its own.

`handler` runs for every player as their key goes down and as it goes up, `e.down` saying which, on the player's
own time, as their requests do. When that time is spent, a press is dropped: a key still held is heard at the next
mod tick, and a key let go is heard only if its press was. A tap between mod ticks is heard down and up a tick
apart; one held as its player leaves is heard let go before `leaving`; `p.held.<action>` is true in between. A
player typing `/<action>` taps it. A tap or a hold is the mod's to tell apart: the engine never toggles one.

Until weapons go on these keys, `lethal` throws a frag grenade the player carries, and `tactical` a smoke grenade,
whether a mod handles them or not.

| Name | Type | What it is |
|---|---|---|
| `name` | `"tactical" \| "lethal" \| "utility" \| "ultimate" \| "buy" \| "drop" \| "quick_menu" \| "ping" \| "map"` | Needed. |
| `options` | `table` | A table of the options below. Those marked needed may not be left out. |
| `options.label` | `string` | What a player's Controls and loading screen show beside the action, up to 64 bytes. Needed. |
| `handler` | `(e: {player: Player, down: boolean}) -> ()` | Needed. |

Fails on a value of the wrong type; an option it does not have; a key it needs left out.

#### To call in a killstreak

```luau server
action("ultimate", { label = "Call in the airstrike" }, function(e)
	if e.down then
		say(`{e.player.name} calls in an airstrike`)
	end
end)
```

#### To walk slowly while a key is held

```luau server
action("utility", { label = "Walk slowly" }, function(e)
	e.player:tune({ speed = if e.down then 0.5 else 1 })
end)
```

### switch_off

- On the server: `switch_off(...actions)`

Switches stock actions off for the mod: their keys do nothing, and the server drops their buttons.

Any module may switch any off, at its top as it loads. `slots` is the keys taking the weapons in the slots, while
the wheel stays; `scoreboard` is the key showing the scoreboard, which client scripts draw while it is held.

| Name | Type | What it is |
|---|---|---|
| `...actions` | `"use" \| "slots" \| "scoreboard" \| "reload" \| "crouch" \| "prone" \| "sprint" \| "melee"` | Any number of them. |

Fails on a value of the wrong type.

#### To play without sprinting or going prone

```luau server
switch_off("sprint", "prone")
```

## State and events

How modules keep data and talk. A module declares its state at its top, typed: a value of the match, of the server, of each player, or of each entity of a kind. It declares its events the same way. Modules share nothing else but the files they `require`.

Each declaration says who is sent it with `read_by`: a value or an event without one stays on the server. One with a group reaches the players it picks, and their client scripts read it as `match.round` or `me.kills`. See [who sees what](https://engine-modding.pages.dev/0/concepts/groups/).

Handlers hear events, the engine's and the modules', with `on`, given at a module's top too. Requests go the other way: a client script asks the server with `request`, and the server's handler checks what it is asked.

### match

- On both sides: `match: table`

The match's state: the engine's, `match.map`, `match.gametype` and `match.number`, and each `match_state`.

### server

- On both sides: `server: table`

The server's state: its settings, as `server.hostname`, and each `server_state`.

### everyone

- On the server: `everyone: Group`

Every player.

### owner

- On the server: `owner: Group`

The owner of what it is given to: the player it is about, alone.

### team

- On the server: `team: Group`

The players on the owner's team, the owner too; no one when they are on none.

### spectators

- On the server: `spectators: Group`

The players watching the owner through their eyes while dead.

### admins

- On the server: `admins: Group`

The players the `admins` setting lists.

### on

- On the server: `on(name, fn, { after? }?)`
- On a client: `on(name, fn)`

Hears an event, the engine's or a module's, by its name or as `<module>.<name>`: `fn` is given its fields.

On the server:

A handler is given at a module's top, as it loads, and a module has one of a name in each of its files: a second
replaces the first. Handlers run in the order the modules loaded, those `after` once the rest have. Each runs as a
coroutine: before its first `wait` it may change the event's fields, which those after it and the clients get, or
return false, which stops the event there, and the clients hear nothing. After a `wait`, what it held may be gone:
check `exists`. A handler's error is said once and undoes what it did since it started or last waited, its changes
to the event among them; the other handlers run on. A name no module declares and the engine has not fails the load.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `fn` | `(e: {[string]: any}) -> any` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.after` | `boolean`, default false | Runs once the handlers without it have, as a module's default does after a gametype's own. |

Fails on a value of the wrong type; an option it does not have.

On a client:

Hears an event, the engine's or one of the modules' the server sends this player, by its name or as `<module>.<name>`: `fn` is given its fields, frozen.

A handler is given at a script's top, as it loads, one a name in each file of the script, its own and each it
requires: a second replaces the first. Several scripts may hear one name, in the order they listened. Each frame,
the players who joined and left come first, then what the triggers heard of the player, then the events in order,
then `frame`. A module's event with no `read_by`, which stays on the server, fails, as does a request. A value an
event reads may come after it: check for nil where that matters. A client has no `wait`: a handler that yields
fails, but where `open` waits for a dialog's answer.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `fn` | `(e: {[string]: any}) -> ()` | Needed. |

Fails on a value of the wrong type.

#### To greet each player who joins

```luau server
on("joined", function(e)
  say(`Welcome, {e.player.name}`)
end)
```

#### To halve fall damage

```luau server
on("fell", function(e)
  e.amount /= 2
end)
```

#### To keep a kill quiet once every other module has heard of it

```luau server
on("killed", function(e)
  return false
end, { after = true })
```

#### To count the frames drawn

```luau client
local frames = 0
on("frame", function(e)
  frames += 1
end)
```

#### To count this player's deaths

```luau client
local deaths = 0
on("killed", function(e)
  if e.victim == me then
    deaths += 1
  end
end)
```

### request

- On a client: `request(name, payload?)`

Sends the server one of its requests, by its name or as `<module>.<name>`, its payload checked against the declaration here and again there, as the frame ends.

The payload may be left out where none of its fields is needed. While the match loads, requests wait until it
plays, 64 at most; those past it are dropped. A request has no reply: what it does comes back as state or an
event.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `payload` | `({[string]: any})?` | May be left out. |

Fails on a value of the wrong type.

#### To buy armor at a click

```luau client
mount(function()
  return Box {
    "Buy armor",
    onClick = function()
      request("buy_armor")
    end,
  }
end)
```

### require

- On the server: `require(path) → any`
- On a client: `require(path) → any`

Runs another of the mod's server or shared files, by its path in its layer and its side without `.luau`, and gives what it returns.

On the server:

A file runs once a load, the first time it is required, with `main.server.luau`'s globals, and every module
requiring it gets the one value it gave: keep what changes in declared state, not in it. A path is lowercase
letters, digits and `_` between slashes, as `modules/teams/teams_lib.server` or `modules/ranks/ranks.shared`. A
client file is the clients' alone and a weapon's the engine's, so neither is required here, and a file requiring
itself fails. A mod of one file has no `require`.

| Name | Type | What it is |
|---|---|---|
| `path` | `string` | Needed. |

Returns `any`.

Fails on a value of the wrong type.

On a client:

Runs another of the mod's client or shared files, by its path in its layer and its side without `.luau`, and gives what it returns.

A path is a `.client` file (`modules/ui/button.client`), a `.shared` one the server runs too
(`modules/ranks/ranks.shared`) or another script (`modules/teams/teams.client`). A server file, even by name, or a
file the server did not name fails, as does a file requiring itself. Each script runs a file once a load, in an
environment of its own, and keeps what it gave: two scripts requiring one component get one each, each listening
and drawing for itself. Its time is the script's that required it.

| Name | Type | What it is |
|---|---|---|
| `path` | `string` | Needed. |

Returns `any`.

Fails on a value of the wrong type.

### group

- On the server: `group(picks) → Group`

Makes a group of a function, which `+` and `-` combine with the others: it picks the players it returns true for.

A `read_by` takes the function as it is too. The same function makes the same group. It is given each player's
object as `viewer`, and picks them for any value but nil and false; it may only read. It is asked again at once when
a declared value it read changes, or a player's name or team, or someone joins or leaves; else once a second.

| Name | Type | What it is |
|---|---|---|
| `picks` | `(owner: Player?, viewer: Player) -> boolean` | Needed. |

Returns `Group`.

Fails on a value of the wrong type.

#### To show a flag to the living and the admins

```luau server
local living = group(function(_, viewer)
  return viewer.health > 0
end)
local flag = model("rocket", { read_by = living + admins })
```

### emit

- On the server: `emit(name, payload?) → ({[any]: any})?`

Sends a module's event, its payload checked against its declaration: to its handlers here, then to the players its `read_by` picks.

The name may be another module's as `module.name`. The payload may be left out where none of its fields is needed.
It gives the event as its handlers left it, or nil if one stopped it. An event naming an entity, or an NPC as its
`player`, goes only to those shown it, and to no one once its handlers removed it. Its `read_by` is asked again at
each `emit`. A request is not emitted: clients send it.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `payload` | `({[string]: any})?` | May be left out. |

Returns `({[any]: any})?`.

Fails on a value of the wrong type.

#### To tell everyone a round was won, and say so here

```luau server
event("round_won", t.record { team = t.int }, { read_by = everyone })

on("round_won", function(e)
  say(`Team {e.team} wins the round`)
end)

on("start", function()
  emit("round_won", { team = 1 })
end)
```

### on_request

- On the server: `on_request(name, schema, fn)`

Declares a request clients send, its payload a `t.record`, and its one handler, given its sender as `player`.

It is declared at a module's top, as it loads, and named as an event is; a second handler of a name fails the load.
A client script sends it with `request`, a player with `/request` or a key bound to it. It arrives checked against
its declaration, the players it names here and the entities alive and shown to the sender, at most 10 a second after
a burst of 5; anything else is dropped. Any player may send any request at any time, so its handler checks what it
is asked and never takes a price from the client. It has no reply: the result goes back as state or an event. Its
handler runs on the sender's time, 2 ms a tick: one past it is dropped, and a player past it 20 ticks in a row is
kicked. 16 handlers of one player's requests wait at once at most; the rest are dropped.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `schema` | `Type` | Needed. |
| `fn` | `(e: {player: Player, [string]: any}) -> ()` | Needed. |

Fails on a value of the wrong type.

#### To sell armor to a player who can pay

```luau server
player_state("coins", t.int { min = 0 }, 0, { read_by = owner })

on_request("buy_armor", t.record {}, function(e)
  if e.player.coins >= 100 then
    e.player.coins -= 100
    e.player:set_max_health(150)
  end
end)
```

### match_state

- On the server: `match_state(name, type, default, { read_by?, help? }?)`

Declares a value of the match's, which `match.<name>` reads and writes, from `default` again each match.

A declaration is made at a module's top, as it loads. Its name is up to 32 lowercase letters, digits and `_`, not a
Luau word nor one of the engine's (`map`, `gametype`, `number`), and is the module's alone: another module's of the
name fails the load, naming both. It is read as `match["<module>.<name>"]` too, `main.` for `main.server.luau`'s and
`engine.` for the engine's (`match["engine.number"]`). A write is checked against its type at its line, and a value
outside its limits is refused, never clamped. A list, a record or a map read is frozen: change it by writing it
whole. Without a `read_by` it is the server's alone; one picking by an owner (`owner`, `team`, `spectators`) fails,
as the match has none.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `type` | `Type` | Needed. |
| `default` | `any` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.read_by` | `(Group \| ((Player?, Player) -> boolean))?` | The players sent it, whose client scripts read it; the server's alone without. |
| `options.help` | `string?` | What it is, in a line of up to 128 bytes, which the editor shows on hover. |

Fails on a value of the wrong type; an option it does not have.

#### To count the rounds, for everyone's screen

```luau server
match_state("round", t.int { min = 0 }, 0, { read_by = everyone, help = "The round being played" })

on("start", function()
  match.round += 1
end)
```

### server_state

- On the server: `server_state(name, type, default, { read_by?, set_by?, help? }?)`

Declares a value of the server's, which `server.<name>` reads and writes, kept from match to match until the server stops; with `set_by`, a setting.

It is checked as `match_state`'s is. A setting has its value as soon as it is declared, so the lines after it read
it as configured: its default, under the server's file, under the environment (`SET_SCORE_LIMIT`, or
`SET_TDM__SCORE_LIMIT` for module `tdm`'s), under `--set`, under `/set` or a write of the mod's. A setting is not
named as one of the engine's commands or the client's settings. One the server's `--lock` fixes refuses a write,
failing at its line (`server.score_limit is locked at 30`), and at a module's top failing the load. A secret,
`t.secret(t.string)`, is `set_by = config` and has no `read_by`.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `type` | `Type` | Needed. |
| `default` | `any` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.read_by` | `(Group \| ((Player?, Player) -> boolean))?` | The players sent it, whose client scripts read it; the server's alone without. |
| `options.set_by` | `(Group \| ((Player?, Player) -> boolean) \| Config)?` | Who may change it with `/set`, which makes it a setting, or `config` for the server's configuration alone. |
| `options.help` | `string?` | What it is, in a line of up to 128 bytes, which `/set` and the editor show. |

Fails on a value of the wrong type; an option it does not have.

#### To keep the best round played since the server started

```luau server
server_state("best_round", t.int { min = 0 }, 0, { read_by = everyone })
```

#### To let admins set the points that win a match

```luau server
server_state("score_limit", t.int { min = 1, max = 999 }, 50, {
  read_by = everyone,
  set_by = admins,
  help = "Team points that win the match",
})
```

#### To take a database's address from the server's configuration alone

```luau server
server_state("stats_db", t.secret(t.string), "sqlite:stats.sqlite3", { set_by = config })
```

### player_state

- On the server: `player_state(name, type, default, { read_by?, help? }?)`

Declares a value of each player's, the NPCs among them, which `p.<name>` reads and writes, from `default` as they join, until they leave or the match ends.

It is checked as `match_state`'s is, and is not named as a player's own field or method (`health`, `hurt`). Its
owner is the player, whom `owner`, `team` and `spectators` pick by. A person's reaches the players its `read_by`
picks wherever they are; an NPC's rides with its body, so it reaches only those shown the NPC.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `type` | `Type` | Needed. |
| `default` | `any` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.read_by` | `(Group \| ((Player?, Player) -> boolean))?` | The players sent it, whose client scripts read it; the server's alone without. |
| `options.help` | `string?` | What it is, in a line of up to 128 bytes, which the editor shows on hover. |

Fails on a value of the wrong type; an option it does not have.

#### To count each player's kills, shown to everyone

```luau server
player_state("kills", t.int { min = 0 }, 0, { read_by = everyone })

on("killed", function(e)
  if e.killer then
    e.killer.kills += 1
  end
end)
```

### entity_kind

- On the server: `entity_kind(name, base)`

Declares a kind of entity, on one of the engine's bases, which the calls making one take as their `kind`.

A `"prop"` is made by `model`, a `"body"` by `spawn`, a `"sound"` by `play_sound` and an `"effect"` by
`play_effect`, each a looping one. Its `entity_state` is each such entity's. A kind is named as state is, and is
its module's alone.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `base` | `"prop" \| "body" \| "sound" \| "effect"` | Needed. |

Fails on a value of the wrong type.

#### To give crates coins a player takes

```luau server
entity_kind("crate", "prop")
entity_state("crate", "coins", t.int { min = 0 }, 10)

on("start", function()
  local box = model("rocket", { kind = "crate" })
  box.coins -= 1
end)
```

### entity_state

- On the server: `entity_state(kind, name, type, default, { read_by?, help? }?)`

Declares a value of each entity of a kind `entity_kind` declared, which `e.<name>` reads and writes, from `default` as it is made, until it is removed.

It is checked as `match_state`'s is, and is not named as an entity's own field or method (`position`, `move_to`).
Its owner is the entity's, whom `owner`, `team` and `spectators` pick by. A client is sent it only while it is shown
the entity.

| Name | Type | What it is |
|---|---|---|
| `kind` | `string` | Needed. |
| `name` | `string` | Needed. |
| `type` | `Type` | Needed. |
| `default` | `any` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.read_by` | `(Group \| ((Player?, Player) -> boolean))?` | The players sent it, whose client scripts read it; the server's alone without. |
| `options.help` | `string?` | What it is, in a line of up to 128 bytes, which the editor shows on hover. |

Fails on a value of the wrong type; an option it does not have.

#### To show a turret's shells to its owner alone

```luau server
entity_kind("turret", "prop")
entity_state("turret", "shells", t.int { min = 0, max = 6 }, 6, { read_by = owner })
```

### event

- On the server: `event(name, schema, { read_by? }?)`

Declares an event of the module's, its payload a `t.record`, which `emit` sends and `on` hears, on the server and on the clients its `read_by` picks.

It is named as state is, and not as one of the engine's events. Its payload's `player`, of `t.player` or
`t.optional(t.player)`, names the player it is about, else its `entity` an entity, whose owner `owner`, `team` and
`spectators` pick by; one naming an entity reaches only those shown it. A `read_by` picking by an owner needs one of
the two.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `schema` | `Type` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.read_by` | `(Group \| ((Player?, Player) -> boolean))?` | The players sent it, whose client scripts hear it; the server's alone without. |

Fails on a value of the wrong type; an option it does not have.

#### To tell a player, and those watching them, that they bought something

```luau server
event("bought", t.record { player = t.player, item = t.string }, { read_by = owner + spectators })

on("start", function()
  emit("bought", { player = players[1], item = "armor" })
end)
```

### `on("start")`

- On the server: `on("start", fn)`

The match started: the first moment a module may give orders, before any other event. Heard once a match.

### `on("ending")`

- On the server: `on("ending", fn)`

The match is to switch to the next, which waits until every handler ends: the time to save what a mod keeps.

### `on("stopping")`

- On the server: `on("stopping", fn)`

The server is to stop, which waits until every handler ends and every call is answered.

## Timers

Doing something later, or again and again. A module names each timer at its top with what it runs, then starts it once or on a loop, for the whole match or for one player.

A handler may also `wait`, and go on that many seconds later. Whatever it held may be gone by then: check `exists`.

`now()` is the mod's clock. A client script reads the same clock, so a time the server keeps in state, such as when a round ends, counts down alike on every screen.

### now

- On the server: `now() → number`
- On a client: `now(step?) → number`

The mod's time, in seconds, as of its last tick.

On the server:

A client's `now()` reckons the same clock, so a time the mod keeps in state reads there as it does here.

Returns `number`.

On a client:

The server's mod clock as this client reckons it, in seconds, 0 before the first snapshot; with `step`, rounded down to a whole number of `step` seconds.

It reads the same clock as the server's `now()`, so a time the mod keeps in state reads here as there:
`now() - match.started`. Read in a render, `now()` draws it again every frame, but `now(step)` once a step: `now(1)`
for a countdown of seconds. `step` is above 0.

| Name | Type | What it is |
|---|---|---|
| `step` | `number?` | May be left out. |

Returns `number`.

Fails on a value of the wrong type.

#### To keep when the match started, for the clients' clocks

```luau server
match_state("started", t.number, 0, { read_by = everyone })

on("start", function()
  match.started = now()
end)
```

#### To count down the last minute

```luau client
local ends = now() + 60

mount(function()
  return Box { `{math.max(0, math.ceil(ends - now(1)))} s`, style = { right = 40, top = 40 } }
end)
```

### wait

- On the server: `wait(seconds)`

Pauses the handler, timer or command for `seconds`: it goes on with the next line at the first mod tick that much later.

What it held may be gone by then: check a player's or an entity's `exists`. A handler waits, never a module's
top, `on_tick`, `on_event`, a group or a function called outside a handler. A reload in place, or a new match,
drops the handlers waiting. A handler's error undoes what it did since it last waited, or since it started.

| Name | Type | What it is |
|---|---|---|
| `seconds` | `number`, 0 to 3600 | Needed. |

Fails on a value of the wrong type; a value outside its range.

#### To respawn a player after a countdown

```luau server
for left = 3, 1, -1 do
  say(`Respawning in {left}`, { to = { p } })
  wait(1)
end
if p.exists then
  p:respawn()
end
```

### timer

- On the server: `timer(name, fn)`

Names the function the module's timer `name` runs, given the timer's player if it has one.

A module names its timers at its top, as it loads, and starts them with `delay` or `loop`. A timer is its module's,
by its name and its player, so two modules' timers of one name never meet. It runs the function its name has as it
fires, so after a reload in place an edit takes effect at its next run. A module's names are those its last load
gave, or those before if that load failed; a timer whose name has no function as it fires is dropped, and said. A
timer goes with its player, and with its module.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `fn` | `(player: Player?) -> ()` | Needed. |

Fails on a value of the wrong type.

#### To end a round once its time is up

```luau server
timer("time_up", function()
  say("Time is up")
  start_match()
end)

on("start", function()
  delay("time_up", 300)
end)
```

### delay

- On the server: `delay(name, seconds, player?)`

Runs the module's timer `name` once, `seconds` from now, given `player` if one is given; given again before it runs, it starts over.

A timer given a player is separate from one of the same name with no player or another player, and goes as they
leave. It runs as a handler does, a coroutine that may `wait`, on the mod tick within half a tick of its time, after
that tick's handlers. Timers outlast a reload in place, and go as the map changes. A name the module gave no
`timer` fails.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `seconds` | `number`, 0 to 3600 | Needed. |
| `player` | `(Player \| number)?` | May be left out. |

Fails on a value of the wrong type; a value outside its range.

#### To bring a player back 5 seconds after they die

```luau server
timer("respawn", function(p)
  p:respawn()
end)

on("killed", function(e)
  delay("respawn", 5, e.victim)
end)
```

### loop

- On the server: `loop(name, seconds, player?)`

Runs the module's timer `name` every `seconds`, given `player` if one is given, until `cancel`; given again, it takes the new time and keeps its beat.

`seconds` is above 0 and up to an hour. A beat that ran late counts on from then, and one that fails is done for
that beat alone. Otherwise it is a timer as `delay`'s.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `seconds` | `number` | Needed. |
| `player` | `(Player \| number)?` | May be left out. |

Fails on a value of the wrong type.

#### To give every player 10 points every 5 seconds

```luau server
player_state("points", t.int { min = 0 }, 0, { read_by = everyone })

timer("income", function()
  for _, p in players do
    p.points += 10
  end
end)

on("start", function()
  loop("income", 5)
end)
```

### cancel

- On the server: `cancel(name, player?)`

Stops the module's timer `name`, of `player` if one is given; one not running is left as it is.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `player` | `(Player \| number)?` | May be left out. |

Fails on a value of the wrong type.

#### To bleed a player who is hit until they bandage

```luau server
timer("bleed", function(p)
  p:hurt(2)
end)

on("hit", function(e)
  loop("bleed", 1, e.victim)
end)

command("bandage", { help = "Stop your bleeding" }, function(e)
  cancel("bleed", e.player)
end)
```

### `on("tick")`

- On both sides: `on("tick", fn)`

The mod ticked: heard as often as the server's mod ticks.

| Name | Type | What it is |
|---|---|---|
| `e.delta` | `number` | The seconds since the last. |

### `on("frame")`

- On a client: `on("frame", fn)`

A frame is drawn: heard once a frame, as often as the player's game draws.

| Name | Type | What it is |
|---|---|---|
| `e.delta` | `number` | The seconds since the last frame. |

## Calls

The server's calls to the outside: HTTP requests, SQL databases (SQLite, Postgres, MySQL) and WebSockets, and `json` to read and write what they carry. Only the server makes them.

A call never holds up the game. It gives a task at once, and a handler that needs the answer `await`s it, going on at the first tick after it comes. A failed call never raises an error: its answer's `ok` is false and its `error` says what went wrong. A call sent always finishes, whatever becomes of the match.

Calls are made in handlers, timers, commands, `on_tick` and `on_event`, never at a module's top as it loads; only a pool may be made there. Each call waits 30 seconds for its answer unless its `timeout` says otherwise.

Keep passwords and keys out of the mod: declare them as secret settings, which the server owner gives, and pass them as a URL or a header's value. See [settings](https://engine-modding.pages.dev/0/hosting/settings/#secrets).

### await

- On the server: `await(...tasks) → ...Answer`

Pauses the handler until each task is answered, and gives their answers in order.

It goes on at the first tick after the last answer came, when what it held may be gone: check a player's or an
entity's `exists`. A task already answered, as its `done` says, gives its answer at once. A handler, a timer or a
command awaits, never a module's top, `on_tick` or `on_event`. A paused handler is undone, resumed and dropped as one
paused in `wait` is.

Every answer has `ok`, and `error` once it is false: no answer came, the call took longer than its `timeout`, or
the answers on their way together passed the memory the mod has left. A failed call never raises an error; one no
handler awaits is said once as the mod's error.

| Name | Type | What it is |
|---|---|---|
| `...tasks` | `Task` | Any number of them. |

Returns `...Answer`.

Fails on a value of the wrong type.

#### To fetch two things at once

```luau server
local rank, kills = await(
    http.request("GET", "https://stats.example/rank/" .. p.id),
    http.request("GET", "https://stats.example/kills/" .. p.id)
)
if rank.ok and kills.ok and p.exists then
    print(`{p.name}: rank {json.decode(rank.body).rank}, {json.decode(kills.body).kills} kills`)
end
```

### http.request

- On the server: `http.request(method, url, options?) → Task`

Sends an HTTP or HTTPS request, and gives its task at once, which `await` answers.

Any status, 500 too, is an answer: `ok`, with `status`, `headers` and `body`. A header's name is in lower case,
and a name sent several times has its values joined by `, `. No answer, as a refused connection or a `timeout`
passed, fails the call, its `error` saying why. Redirects are followed, never telling the next host where they came
from; a request carrying a secret, in its URL or a header, follows them only to the same scheme, host and port,
answering one elsewhere as it is. A call made at a module's top, or in a group, fails.

| Name | Type | What it is |
|---|---|---|
| `method` | `string` | Needed. |
| `url` | `string \| Secret` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.headers` | `({[string]: string \| Secret})?` | Headers to send, by name; a value may be a secret. |
| `options.body` | `(string \| buffer)?` | The body, a string's or a buffer's bytes as they are. Not with `json`. |
| `options.json` | `any?` | A value sent as the body, as `json.encode` writes it, with its content type unless `headers` names one. Not with `body`. |
| `options.timeout` | `number`, default 30 | How long it may take, in seconds, before it fails. |

Returns `Task`.

Fails on a value of the wrong type; an option it does not have; `body` and `json` both.

#### To fetch a player's rank

```luau server
local answer = await(http.request("GET", "https://stats.example/rank/" .. p.id))
if answer.ok and answer.status == 200 then
    local rank = json.decode(answer.body).rank
    print(`{p.name} is ranked {rank}`)
end
```

#### To post a kill to a stats site

```luau server
local answer = await(http.request("POST", "https://stats.example/kills", {
    headers = { ["x-server"] = "eu-1" },
    json = { player = p.name, weapon = "weapons/ak47" },
    timeout = 5,
}))
if not answer.ok then
    print("the stats site is down: " .. answer.error)
end
```

### json.encode

- On the server: `json.encode(value) → string`

Writes a value as JSON text.

A table of keys 1 to n is a list, an empty table too, and any other table an object of string keys, in sorted
order; nil is null. A whole number is written without a decimal point. A table holding itself fails, as does a
function, a key neither a string nor 1 to n, or text larger than the memory the mod has left.

| Name | Type | What it is |
|---|---|---|
| `value` | `any` | Needed. |

Returns `string`.

Fails on a value of the wrong type.

#### To save a player's stats as text

```luau server
local text = json.encode({ name = "ann", kills = 4, weapons = { "weapons/ak47", "weapons/usp" } })
```

### json.decode

- On the server: `json.decode(text) → (any, string?)`

Reads JSON text: gives its value, or nil and why it is not JSON.

An array is a table of keys 1 to n and an object a table of string keys; null is nil, leaving a hole in a list.

| Name | Type | What it is |
|---|---|---|
| `text` | `string` | Needed. |

Returns `(any, string?)`.

Fails on a value of the wrong type.

#### To read what a stats site answered

```luau server
local stats, why = json.decode('{"rank": 3, "best": null}')
if stats then
    print(`rank {stats.rank}`)
else
    print("not JSON: " .. why)
end
```

### sql.pool

- On the server: `sql.pool(url, options?) → Pool`

Makes a pool of connections to a SQLite, Postgres or MySQL database, which connects on its first call.

The URL starts `sqlite:`, `postgres://` or `mysql://`, and may be a secret. A SQLite file is in the server's
directory, made if missing, and its SQL opens no other file. The pool is made at once, so a module's top may make
one: keep it in a global, as a reload in place runs a module's top again. It closes once the mod no longer holds
it and its calls have finished.

| Name | Type | What it is |
|---|---|---|
| `url` | `string \| Secret` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.max_connections` | `number?`, 1 to 4294967295 | The most connections it opens at once, 10 without. |
| `options.min_connections` | `number`, default 0 | The connections it keeps open, even idle; kept within `max_connections`, refused above it when both are given. |
| `options.acquire_timeout` | `number`, default 30 | How long a call waits for a free connection, in seconds, before it fails. |
| `options.idle_timeout` | `number`, default 600 | How long a connection with no call on it stays open, in seconds. |
| `options.max_lifetime` | `number`, default 1800 | How long a connection stays open at most, in seconds, before a new one takes its place. |

Returns `Pool`.

Fails on a value of the wrong type; an option it does not have; a value outside its range.

#### To keep a SQLite file of stats

```luau server
db = db or sql.pool("sqlite:stats.db")
```

#### To share a Postgres database between servers

```luau server
db = db or sql.pool("postgres://game@db.example/stats", { max_connections = 4 })
```

### ws.connect

- On the server: `ws.connect(url, options?) → Task`

Connects a WebSocket, and gives its task at once, which answers `socket` once it is open.

The URL starts `ws://` or `wss://`, and it and a header's value may be secrets. `message` and `closed` run as
handlers of the module that connected, in the order the socket heard, never a player's; what they hear has secrets
hidden. The messages on their way, either way, count against the memory the mod has left, the socket closing past
it. A socket closes once the mod no longer holds it, unless one of its own callbacks holds it, and with its match:
reconnecting is the mod's. `socket.open` is false once either side closes it. A call made at a module's top, or in
a group, fails.

| Name | Type | What it is |
|---|---|---|
| `url` | `string \| Secret` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.headers` | `({[string]: string \| Secret})?` | Headers to send with the handshake, by name; a value may be a secret. |
| `options.protocols` | `{string}?` | The subprotocols offered, of which the host picks one, `socket.protocol`. |
| `options.timeout` | `number`, default 30 | How long it may take to connect, in seconds, before it fails. |
| `options.message` | `((data: string \| buffer) -> ())?` | Runs for each message the socket hears: a string for text, a buffer for binary. |
| `options.closed` | `((code: number, reason: string) -> ())?` | Runs once as the socket closes, whichever side closed it, with the close's code and reason. |

Returns `Task`.

Fails on a value of the wrong type; an option it does not have.

#### To follow a chat relay

```luau server
local answer = await(ws.connect("wss://chat.example/relay", {
    message = function(data)
        say(`[relay] {data}`)
    end,
    closed = function(code, reason)
        print(`the relay closed: {code} {reason}`)
    end,
}))
if answer.ok then
    relay = answer.socket
end
```

### Pool:query

- On the server: `Pool:query(sql, ...params) → Task`

Runs a query on one of the pool's connections, and gives its task at once, which answers `rows`.

The SQL is one statement in the database's own SQL, each `?` (`$1` and on in Postgres) a parameter after it, the
options last. Each row is a table of its values by column name, a database's null as nil and its bytes as a
buffer. A call made at a module's top, or in a group, fails.

| Name | Type | What it is |
|---|---|---|
| `sql` | `string` | Needed. |
| `...params` | `SqlParam` | Any number of them. |

Returns `Task`.

Fails on a value of the wrong type.

#### To list the best players

```luau server
db = db or sql.pool("sqlite:stats.db")
local answer = await(db:query("SELECT name, kills FROM players WHERE kills > ? ORDER BY kills DESC", 5))
if answer.ok then
    for i, row in answer.rows do
        print(`{i}. {row.name}, {row.kills} kills`)
    end
end
```

### Pool:execute

- On the server: `Pool:execute(sql, ...params) → Task`

Runs a statement that changes the database on one of the pool's connections, and gives its task at once, which answers the rows it `changed`.

Its SQL and parameters are as `Pool:query`'s. A call made at a module's top, or in a group, fails.

| Name | Type | What it is |
|---|---|---|
| `sql` | `string` | Needed. |
| `...params` | `SqlParam` | Any number of them. |

Returns `Task`.

Fails on a value of the wrong type.

#### To count a player's kill

```luau server
db = db or sql.pool("sqlite:stats.db")
local answer = await(db:execute("UPDATE players SET kills = kills + 1 WHERE name = ?", p.name, { timeout = 5 }))
if not answer.ok then
    print("not saved: " .. answer.error)
end
```

### Pool:transaction

- On the server: `Pool:transaction(fn, { timeout? }?) → Task`

Runs a function's calls on one connection as a transaction, and gives its task at once, which answers once it is committed or rolled back.

The function runs at once, as a handler of its own given the transaction, `tx`, so it may `await`. Its `tx:query`
and `tx:execute` go to the database in order. It commits as the function returns; it is rolled back if the
function returns false or fails, a call on `tx` fails, or `timeout` passes. A call made at a module's top, or in a
group, fails.

| Name | Type | What it is |
|---|---|---|
| `fn` | `(tx: Tx) -> ...any` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.timeout` | `number`, default 30 | How long it may take, in seconds, from its start to its commit, before it is rolled back. |

Returns `Task`.

Fails on a value of the wrong type; an option it does not have.

#### To move points from one wallet to another, or not at all

```luau server
db = db or sql.pool("sqlite:stats.db")
local moved = await(db:transaction(function(tx)
    local from = await(tx:query("SELECT points FROM wallets WHERE name = ?", "ann")).rows[1]
    if not from or from.points < 100 then
        return false
    end
    tx:execute("UPDATE wallets SET points = points - 100 WHERE name = ?", "ann")
    tx:execute("UPDATE wallets SET points = points + 100 WHERE name = ?", "bob")
end, { timeout = 10 }))
print(moved.ok and "moved" or "not moved: " .. moved.error)
```

### Socket:send

- On the server: `Socket:send(data, { timeout? }?) → Task`

Sends a message, after those sent before it, and gives its task at once, which answers once it is sent.

A string is sent as a text message and a buffer as a binary one. A message to a socket no longer open fails.

| Name | Type | What it is |
|---|---|---|
| `data` | `string \| buffer` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.timeout` | `number`, default 30 | How long it may take, in seconds, before it fails. |

Returns `Task`.

Fails on a value of the wrong type; an option it does not have.

#### To tell a relay a player joined

```luau server
local answer = await(ws.connect("wss://chat.example/relay"))
if answer.ok then
    answer.socket:send(json.encode({ joined = p.name }), { timeout = 5 })
end
```

### Socket:close

- On the server: `Socket:close(code?, reason?) → Task`

Closes the socket, after the messages sent before, and gives its task at once, which answers once the close is sent.

The code is 1000 without one, or 3000 to 4999 for the mod's own, and the reason up to 123 bytes. The socket is no
longer open at once, and its `closed` runs once the host closes too.

| Name | Type | What it is |
|---|---|---|
| `code` | `number?` | May be left out. |
| `reason` | `string?` | May be left out. |

Returns `Task`.

Fails on a value of the wrong type.

#### To close a relay as the match ends

```luau server
local answer = await(ws.connect("wss://chat.example/relay"))
if answer.ok then
    answer.socket:close(4000, "the match ended")
end
```

### Tx:query

- On the server: `Tx:query(sql, ...params) → Task`

Runs a query in the transaction, after the calls on it before, and gives its task at once, which answers `rows`.

Its SQL, parameters and rows are as `Pool:query`'s. One that fails rolls the transaction back, and those after it
fail. It fails once the transaction's function has ended.

| Name | Type | What it is |
|---|---|---|
| `sql` | `string` | Needed. |
| `...params` | `SqlParam` | Any number of them. |

Returns `Task`.

Fails on a value of the wrong type.

#### To read a wallet within a transaction

```luau server
db = db or sql.pool("sqlite:stats.db")
await(db:transaction(function(tx)
    local wallet = await(tx:query("SELECT points FROM wallets WHERE name = ?", "ann")).rows[1]
    print(`ann has {wallet and wallet.points or 0} points`)
end))
```

### Tx:execute

- On the server: `Tx:execute(sql, ...params) → Task`

Runs a statement that changes the database in the transaction, after the calls on it before, and gives its task at once, which answers the rows it `changed`.

Its SQL and parameters are as `Pool:query`'s. One that fails rolls the transaction back, and those after it fail.
It fails once the transaction's function has ended.

| Name | Type | What it is |
|---|---|---|
| `sql` | `string` | Needed. |
| `...params` | `SqlParam` | Any number of them. |

Returns `Task`.

Fails on a value of the wrong type.

#### To save a round's scores together

```luau server
db = db or sql.pool("sqlite:stats.db")
await(db:transaction(function(tx)
    for _, p in players do
        tx:execute("UPDATE players SET kills = kills + ? WHERE name = ?", 1, p.name)
    end
end))
```

## Settings

The values a server owner sets, and the matches a server plays: the engine's own and the mod's. A module makes one of its server state a setting by giving it `set_by`. That is the [group](https://engine-modding.pages.dev/0/concepts/groups/) who may change it with `/set`, or `config` for the server's configuration alone: its file, the environment and `--set`. See [settings](https://engine-modding.pages.dev/0/hosting/settings/) for where values come from.

A mod starts the next match itself with `start_match`, the rotation's or one it names, and tells players which comes next with `announce_next_match`, as a vote does once it ends. See [matches](https://engine-modding.pages.dev/0/hosting/matches/) for the rotation.

### config

- On the server: `config: Config`

As a `server_state`'s `set_by`: the server's configuration alone may set it, never a player's `/set`.

### announce_next_match

- On the server: `announce_next_match(map, gametype)`

Tells every player the match that comes next, so their game downloads its files while this one plays; it starts nothing.

It is checked as `start_match` is. A later call replaces it, and a player who joins is told the last. `start_match()`
still starts the rotation's next match, which the rotation announces itself as each match starts.

| Name | Type | What it is |
|---|---|---|
| `map` | `string` | Needed. |
| `gametype` | `string` | Needed. |

Fails on a value of the wrong type.

#### To announce the map a vote chose

```luau server
announce_next_match("yard", "tdm")
```

### start_match

- On the server: `start_match(map?, gametype?)`

Ends this match and starts the next: with no arguments the rotation's, else that map and gametype.

It fails at the call for a map the server has not, a gametype the map does not list, or one the mod has not. The
next map loads while this match plays on; then this match's `ending` handlers run, and the switch removes every
entity, timer and waiting handler and starts `match_state` and `player_state` from their defaults, while
`server_state` stays and `match.number` counts on. Without a rotation, `start_match()` plays this match again;
`start_match(match.map, match.gametype)` restarts it and leaves the rotation where it is.

| Name | Type | What it is |
|---|---|---|
| `map` | `string?` | May be left out. |
| `gametype` | `string?` | May be left out. |

Fails on a value of the wrong type.

#### To play team deathmatch on yard once a team wins

```luau server
event("team_won", t.record { team = t.int })

on("team_won", function()
  start_match("yard", "tdm")
end)
```

#### To go on to the rotation's next match

```luau server
start_match()
```

### snapshot_rate

- On the server: `snapshot_rate(rate)`

Asks for `rate` snapshots a second: 10, 12, 15, 20, 30 or 60, which divide the server's 60 ticks a second; 20 without it.

One module declares it, at its top as it loads: a second module's fails at load, naming both. Any other rate fails
at load, and on an edit is printed and the rate kept. The server sends at the most of those within it and its
`max_snapshot_rate` setting, 60 by default, and prints the rate it sends at. Each player's game gets the most of the
rates dividing that one whose snapshots each have 500 bytes of its `rate`: at 60 a second, a player at 10000 bytes a
second gets 20, and one at 25000 gets 30. A player's game draws the others two of its snapshots behind the newest,
100 ms at 20 and 33 ms at 60, easing into a new rate's delay. `on_tick` runs 20 times a second at any rate.

| Name | Type | What it is |
|---|---|---|
| `rate` | `number` | Needed. |

Fails on a value of the wrong type.

#### To send fast moves at once, for a mod of duels

```luau server
snapshot_rate(60)
```

## Types

The types state, events, requests and commands are declared with, in the global `t`, on the server and in client scripts. Most are a type themselves, `t.int` to `t.duration`, listed first below; `t`'s functions make a type of others. A name `t` lacks, as `t.innt`, fails where it is read, so at load.

Limits are part of a type: `t.int { min = 1, max = 999 }`, `t.number { min = 0 }`, `t.string { max = 32 }` in bytes. A value outside them is refused, never clamped, a default too. Types nest 32 deep at most.

Here too are the helpers for the engine's axes, `angles` and `vector.distance`, and the timings a change eases by, `timing`. A `transition` is `{seconds, timing}`, 0 seconds being at once; a style's `transition` stays CSS's text. See [units and axes](https://engine-modding.pages.dev/0/concepts/units-and-axes/).

### t.int

- On both sides: `t.int: Limited`

A whole number. Called with limits, `t.int { min = 1, max = 999 }`, it refuses one outside them, never clamping it.

### t.number

- On both sides: `t.number: Limited`

A finite number. Called with limits, `t.number { min = 0 }`, it refuses one outside them, never clamping it.

### t.string

- On both sides: `t.string: Sized`

Text. Called with its most bytes, `t.string { max = 32 }`, it refuses longer text; a command's argument is one word.

### t.bool

- On both sides: `t.bool: Type`

`true` or `false`.

### t.vector

- On both sides: `t.vector: Type`

A point or a direction: given as a vector or a table of `x`, `y` and `z`, read as a vector.

### t.player

- On both sides: `t.player: Type`

A player or an NPC: sent as its id, read as the player. A command's argument is typed as part of a name or `#id`.

### t.entity

- On both sides: `t.entity: Type`

An entity: sent as its id, read as the entity.

### t.model

- On both sides: `t.model: Type`

A model's file, by its name, checked as it is given.

### t.sound

- On both sides: `t.sound: Type`

A sound's file, by its name, checked as it is given.

### t.effect

- On both sides: `t.effect: Type`

An effect's file, by its name, checked as it is given.

### t.image

- On both sides: `t.image: Type`

An image's file, by its name, checked as it is given.

### t.weapon

- On both sides: `t.weapon: Type`

A weapon's file, by its name, checked as it is given.

### t.animation

- On both sides: `t.animation: Type`

An animation's file, by its name, checked as it is given.

### t.material

- On both sides: `t.material: Type`

A material's file, by its name, checked as it is given.

### t.text

- On both sides: `t.text: Type`

The rest of a command's line: its last argument's type alone.

### t.duration

- On both sides: `t.duration: Type`

A whole number of seconds typed with its unit, `30s`, `5m`, `12h` or `7d`, up to 3650 days: a command's argument's type alone.

### angles.forward

- On both sides: `angles.forward(a) → vector`

Gives the way angles face, a vector of length 1: yaw 0 faces +Z, positive yaw turns left, and pitch looks up.

It takes angles, `{ yaw, pitch, roll }` in radians, each 0 where it is left out, or anything with them: a
marker, a player, a model or a part.

| Name | Type | What it is |
|---|---|---|
| `a` | `Angled` | Needed. |

Returns `vector`.

Fails on a value of the wrong type.

#### To put a model 2 m in front of a player

```luau server
model("rocket", { at = p.position + angles.forward(p) * 2 })
```

### angles.right

- On both sides: `angles.right(a) → vector`

Gives the way to the right of angles, a vector of length 1, as roll tips it.

| Name | Type | What it is |
|---|---|---|
| `a` | `Angled` | Needed. |

Returns `vector`.

Fails on a value of the wrong type.

#### To put a model beside a player, on their right

```luau server
model("rocket", { at = p.position + angles.right(p) * 1.5 })
```

### angles.up

- On both sides: `angles.up(a) → vector`

Gives the way up from angles, a vector of length 1, as pitch and roll tip it.

| Name | Type | What it is |
|---|---|---|
| `a` | `Angled` | Needed. |

Returns `vector`.

Fails on a value of the wrong type.

#### To put a model on top of a leaning one

```luau server
local at, leaning = vector.create(0, 0, 5), { pitch = 0.3 }
model("rocket", { at = at, angles = leaning })
model("rocket", { at = at + angles.up(leaning) * 2, angles = leaning })
```

### angles.of

- On both sides: `angles.of(dir) → Angles`

Gives the yaw and pitch that face a direction; one straight up or down faces yaw 0.

| Name | Type | What it is |
|---|---|---|
| `dir` | `vector` | Needed. |

Returns `Angles`.

Fails on a value of the wrong type.

#### To turn a model to face a player

```luau server
local at = vector.create(0, 0, 5)
model("rocket", { at = at, angles = angles.of(p.position - at) })
```

### angles.rotate

- On both sides: `angles.rotate(v, a) → vector`

Gives an offset turned by angles: where it is from what has them, as `vector.create(0, 0, 1)` is 1 m in front.

| Name | Type | What it is |
|---|---|---|
| `v` | `vector` | Needed. |
| `a` | `Angled` | Needed. |

Returns `vector`.

Fails on a value of the wrong type.

#### To put a model at the tip of a player's gun

```luau server
model("rocket", { at = p.position + vector.create(0, 1.5, 0) + angles.rotate(vector.create(-0.2, 0, 0.8), p) })
```

### angles.delta

- On both sides: `angles.delta(a, b) → Angles`

Gives the shortest turn from one angles to another, each from -π to π: of the angles either has, the other's 0 where it has none.

| Name | Type | What it is |
|---|---|---|
| `a` | `Angled` | Needed. |
| `b` | `Angled` | Needed. |

Returns `Angles`.

Fails on a value of the wrong type.

#### To tell whether a player aims at a point

```luau server
local target = vector.create(5, 0, 5)
local turn = angles.delta(p, angles.of(target - p.position))
if math.abs(turn.yaw) < 0.1 then
	say(`{p.name} aims at it`)
end
```

### angles.lerp

- On both sides: `angles.lerp(a, b, t) → Angles`

Gives the angles `t` of the way from one to another, the short way round, 0.5 halfway: of the angles either has, the other's 0 where it has none.

| Name | Type | What it is |
|---|---|---|
| `a` | `Angled` | Needed. |
| `b` | `Angled` | Needed. |
| `t` | `number` | Needed. |

Returns `Angles`.

Fails on a value of the wrong type.

#### To aim a camera halfway from yaw 0 to where a player looks

```luau server
spawn_camera({ at = vector.create(0, 2, 0), angles = angles.lerp({ yaw = 0 }, p, 0.5) })
```

### vector.distance

- On both sides: `vector.distance(a, b) → number`

Gives the distance between two points, in metres.

| Name | Type | What it is |
|---|---|---|
| `a` | `vector` | Needed. |
| `b` | `vector` | Needed. |

Returns `number`.

Fails on a value of the wrong type.

#### To hurt the players within 10 m of a blast

```luau server
local at = vector.create(0, 0, 0)
for _, p in players do
	if vector.distance(p.position, at) < 10 then
		p:hurt(20)
	end
end
```

### timing.cubic_bezier

- On both sides: `timing.cubic_bezier(x1, y1, x2, y2) → TimingFunction`

Makes a timing of a cubic Bézier curve from (0, 0) to (1, 1), as CSS's `cubic-bezier()`: y1 and y2 past 0 or 1 overshoot.

A timing is how a change eases: one of the words `"linear"`, `"ease"`, `"ease-in"`, `"ease-out"` and
`"ease-in-out"`, or what `timing.cubic_bezier` and `timing.steps` make. A move order takes one, and a client
script's transition, `{ seconds, timing }`, linear without it. CSS's strings, as `"cubic-bezier(…)"`, are none.

| Name | Type | What it is |
|---|---|---|
| `x1` | `number`, 0 to 1 | Needed. |
| `y1` | `number`, -1000 to 1000 | Needed. |
| `x2` | `number`, 0 to 1 | Needed. |
| `y2` | `number`, -1000 to 1000 | Needed. |

Returns `TimingFunction`.

Fails on a value of the wrong type; a value outside its range.

#### To raise a platform, overshooting a little before it settles

```luau server
local platform = model("rocket")
platform:move_by(vector.create(0, 3, 0), 2, { timing = timing.cubic_bezier(0.3, 1.4, 0.6, 1) })
```

### timing.steps

- On both sides: `timing.steps(count, jump?) → TimingFunction`

Makes a timing of equal jumps, as CSS's `steps()`, `jump` saying where they fall: `jump-end` without it.

`jump-start` jumps at the start of each step and `jump-end` at its end; `jump-none` at neither end of the whole,
so it takes 2 steps at least, and `jump-both` at both.

| Name | Type | What it is |
|---|---|---|
| `count` | `number`, 1 to 65535 | Needed. |
| `jump` | `Jump?` | May be left out. |

Returns `TimingFunction`.

Fails on a value of the wrong type; a value outside its range.

#### To zoom a scope in four clicks

```luau client
view:set({ fov = 30 }, { seconds = 0.4, timing = timing.steps(4) })
```

### t.one_of

- On both sides: `t.one_of(values) → Type`

Makes a type of one of a list of words, or of numbers, at least one and none twice.

A value not in the list is refused. A command's argument of words takes one of them, which a player's game
completes as it is typed; a `one_of` of words keys a `t.map`.

| Name | Type | What it is |
|---|---|---|
| `values` | `{string} \| {number}` | Needed. |

Returns `Type`.

Fails on a value of the wrong type.

#### To let admins choose how hard the zombies are

```luau server
server_state("difficulty", t.one_of { "easy", "normal", "hard" }, "normal", { read_by = everyone, set_by = admins })
```

### t.choices

- On both sides: `t.choices(list) → Type`

Makes a command's argument's type of a word of the list `choices` fills, by the list's name.

A player's game completes the words as they are typed; a word not in the list is refused.

| Name | Type | What it is |
|---|---|---|
| `list` | `string` | Needed. |

Returns `Type`.

Fails on a value of the wrong type.

#### To let players vote for the next map

```luau server
command("nextmap", { help = "Vote for the next map", args = { { "map", t.choices("maps") } } }, function(e)
	say(`{e.player.name} votes for {e.map}`)
end)
on("start", function()
	choices("maps", { "yard", "testbed" })
end)
```

### t.optional

- On both sides: `t.optional(inner) → Type`

Makes a type of a value of another, or nil.

An optional type stays as it is. A command's optional argument may be left out, after those it needs, and reads
nil.

| Name | Type | What it is |
|---|---|---|
| `inner` | `Type` | Needed. |

Returns `Type`.

Fails on a value of the wrong type.

#### To keep whom each player marked, if anyone

```luau server
player_state("marked", t.optional(t.player), nil, { read_by = owner })
```

#### To give a kick an optional reason

```luau server
command("boot", { args = { { "target", t.player }, { "reason", t.optional(t.text) } }, run_by = admins }, function(e)
	e.target:kick(e.reason or "booted")
end)
```

### t.secret

- On both sides: `t.secret(inner) → Type`

Makes the type of a setting's secret, `t.secret(t.string)`: text the mod passes to a call but never reads.

A secret is a `server_state` of `set_by = config` without `read_by`, or it fails at load; `t.optional` of one may
be left unset, blank text making it nil. The mod holds a handle that shows as `<secret name>` wherever it is
printed, joins into another with `..`, and gives its text only where a call connects: `sql.pool` takes one as its
URL, and `http.request` as its URL or a header's value. Whatever the server prints of the mod and of what a call
answers has its text replaced so.

| Name | Type | What it is |
|---|---|---|
| `inner` | `Type` | Needed. |

Returns `Type`.

Fails on a value of the wrong type.

#### To keep the stats database's address out of the mod

```luau server
server_state("stats_db", t.secret(t.string), "sqlite::memory:", { set_by = config })
```

### t.list

- On both sides: `t.list(element) → Type`

Makes a type of a list of values of another: a table of keys 1 to n and no other.

| Name | Type | What it is |
|---|---|---|
| `element` | `Type` | Needed. |

Returns `Type`.

Fails on a value of the wrong type.

#### To keep the perks each player bought

```luau server
player_state("perks", t.list(t.string { max = 16 }), {}, { read_by = owner })
```

### t.record

- On both sides: `t.record(fields) → Type`

Makes a type of a table of named fields, each of its own type: a field's name is up to 32 lowercase letters, digits and `_`.

Every field is given, but an optional one, and no other; an event's or a request's payload is one.

| Name | Type | What it is |
|---|---|---|
| `fields` | `{[string]: Type}` | Needed. |

Returns `Type`.

Fails on a value of the wrong type.

#### To tell everyone what a player bought

```luau server
event("bought", t.record { player = t.player, weapon = t.weapon, price = t.int { min = 0 } }, { read_by = everyone })
```

### t.map

- On both sides: `t.map(key, value) → Type`

Makes a type of a table of keys of one type to values of another.

A key is an int, a string, a bool, a player, an entity, a file or a `one_of` of words, up to 32 bytes as sent.

| Name | Type | What it is |
|---|---|---|
| `key` | `Type` | Needed. |
| `value` | `Type` | Needed. |

Returns `Type`.

Fails on a value of the wrong type.

#### To count each player's kills by weapon

```luau server
player_state("kills_by_weapon", t.map(t.weapon, t.int { min = 0 }), {}, { read_by = owner })
```

## Boxes and styles

The HUD a client script draws: a tree of boxes, styled with CSS. A script mounts a function that returns its boxes; it draws again whenever something it read changes, and only what differs reaches the screen.

Styles are CSS as a browser has it, keys as JavaScript writes them: `fontSize = 32`, `color = "#ffcc33"`, `display = "flex"`, transitions and animations. A number is pixels of a 1080-line screen. The [style properties](https://engine-modding.pages.dev/0/reference/boxes-and-styles/#style-properties) are listed last. The [killstreak guide](https://engine-modding.pages.dev/0/guides/killstreak/) draws one line.

The rest of the screen is this player's too, at no network cost: `view:set` changes the field of view, the exposure, the fog and the crosshair, `shake` shakes the view or the aim, and `highlight` fills or outlines the bodies and props drawn.

### view

- On a client: `view: View`

What this player's view shows: its field of view, exposure, fog and crosshair.

### highlight

- On a client: `highlight(rule)`

Sets how the entities this player sees are drawn, at no network cost: `rule` gives each a highlight, or nil for none.

It is given at a script's top, one rule a script, and asked of each body, NPC, prop, shape and part drawn as it
appears, given its entity, and again only as what it read changes, as a render is. With several scripts' rules,
the last loaded that gives one wins. A highlight is `{fill, filter, outline, through_walls, transition}`: `fill` a
CSS colour mixed over the entity by its alpha, `"#ffffff"` a flat silhouette; `filter` CSS's functions but
`blur()`; `outline` a width of up to 4 pixels and a colour, `"2px #ffcc33"`; `through_walls` shows the fill and the
outline where the entity is hidden; `transition` eases it in and out, its seconds finite.

What rides an entity is drawn as it is, unless highlighted itself; the weapon before the eyes never is. Highlights
are drawn as the HUD's `zIndex` 0 starts, over a frame filter below it, so greying the world leaves highlighted
bodies as they are. 16 different ones show at once among what the camera sees, past which the rest are drawn
plain. A highlight shows only what the server sends this player.

A new highlight eases in by its own transition and the last out by its own; an entity that comes into view is
shown highlighted at once.

| Name | Type | What it is |
|---|---|---|
| `rule` | `(entity: Entity) -> Highlight?` | Needed. |

Fails on a value of the wrong type.

#### To outline the zombies through walls

```luau client
highlight(function(e)
    if e.kind == "npc" then
        return { outline = "2px #ff3333", through_walls = true, transition = { seconds = 0.2 } }
    end
    return nil
end)
```

#### To see in thermal

```luau client
view:set({ exposure = 0.5 })
mount(function()
    local filter = "grayscale(1) contrast(1.4)"
    return Box { style = { left = 0, top = 0, right = 0, bottom = 0, zIndex = -1, backdropFilter = filter } }
end)
highlight(function(e)
    if e.kind == "player" or e.kind == "npc" then
        return { filter = "grayscale(1) brightness(3)", fill = "rgba(255, 255, 255, 0.3)" }
    end
    return nil
end)
```

### keyframes

- On a client: `keyframes(name, frames?)`

Defines keyframes, as CSS's `@keyframes`, which a style's `animation` plays by name; nil frames remove them.

`frames` is keyed `"from"`, `"to"` or a percentage, a comma between several, each a style of the properties
`transition` eases and its own `animationTimingFunction`, 8 keyframes at most. A name is up to 16 lowercase
letters, digits and `_`, but no word `animation` takes, such as `linear`. An animation drawn again under its name
plays on; one whose name went away starts over. A style plays 4 animations at most.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `frames` | `({[string]: Style})?` | May be left out. |

Fails on a value of the wrong type.

#### To pulse a warning

```luau client
keyframes("pulse", {
    from = { opacity = 1 },
    ["50%"] = { opacity = 0.3, animationTimingFunction = "ease-in" },
    to = { opacity = 1 },
})
mount(function()
    return Box { "LOW AMMO", style = { left = 40, top = 80, color = "#ff3333", animation = "pulse 1s infinite" } }
end)
```

### shake

- On a client: `shake(target, { amplitude?, frequency }, transition) → Shake`

Shakes this player's view, `"view"`, the picture alone, or `"aim"`, where the player aims and shoots too, and gives its handle, whose `stop(transition)` fades it out.

`transition`'s seconds are how long it lasts, above 0, `math.huge` until stopped, and its timing how it fades.
Shakes add up, 32 at once, a new one past them ending the oldest. An aim shake reaches the server with the
player's aim, which a modified client could skip, as it can a weapon's kick.

| Name | Type | What it is |
|---|---|---|
| `target` | `"view" \| "aim"` | Needed. |
| `wave` | `table` | A table of the options below. Those marked needed may not be left out. |
| `wave.amplitude` | `Amplitude?` | How far it turns each way on each axis. A table of the options below it. |
| `wave.amplitude.yaw` | `number?`, 0 to 3.141592653589793 | About the vertical, left and right. |
| `wave.amplitude.pitch` | `number?`, 0 to 3.141592653589793 | Up and down. |
| `wave.amplitude.roll` | `number?`, 0 to 3.141592653589793 | About the line of sight: the view's alone, not the aim's. |
| `wave.frequency` | `number`, 0 to 60 | How often it turns, a second, above 0: a smooth noise rather than a sine. Needed. |
| `transition` | `Transition` | Needed. |

Returns `Shake`.

Fails on a value of the wrong type; an option it does not have; a key it needs left out; a value outside its range.

#### To shake the screen as a blast goes off

```luau client
local wave = { amplitude = { yaw = 0.02, pitch = 0.03, roll = 0.01 }, frequency = 8 }
shake("view", wave, { seconds = 1.5, timing = "ease-out" })
```

#### To sway the aim until the player steadies it

```luau client
local sway = shake("aim", { amplitude = { yaw = 0.01, pitch = 0.01 }, frequency = 0.5 }, { seconds = math.huge })
sway:stop({ seconds = 1 })
```

### merge

- On a client: `merge(defaults, props) → D & P`

Gives a component's defaults with its props over them, the caller's winning, to pass on to a `Box`.

A `style` is merged property by property, a function of `is` among them; content given replaces the default's.

| Name | Type | What it is |
|---|---|---|
| `defaults` | `D` | Needed. |
| `props` | `P` | Needed. |

Returns `D & P`.

Fails on a value of the wrong type.

#### To make a button its caller may restyle

```luau client
local Button = component(function(props)
    return Box(merge({ style = { padding = "4px 8px", color = "#ffffff" } }, props))
end)
mount(function() return Button { "Leave", style = { color = "#ff3333" } } end)
```

### omit

- On a client: `omit(props, ...keys) → P`

Gives a copy of props without the keys named, a component's own, which a `Box` refuses.

| Name | Type | What it is |
|---|---|---|
| `props` | `P` | Needed. |
| `...keys` | `string` | Any number of them. |

Returns `P`.

Fails on a value of the wrong type.

#### To pass on all but a component's own prop

```luau client
local Badge = component(function(props)
    return Box(merge({ if props.danger then "!" else "" }, omit(props, "danger")))
end)
mount(function() return Badge { danger = true, style = { color = "#ff3333" } } end)
```

### store

- On a client: `store(first?) → T`

Gives a table of the script's own data, which a render reads as it reads the game: one that read a key renders again as it changes.

It is read and written as a table, `banner.count += 1`, gone over with `for key, value in banner`, and keyed by any
key a table takes. Once a frame, after the handlers, each store of a script that ran is compared deeply with its
copy, on the script's time. A list goes in a field, `store { items = {} }`: `table.insert`, `table.remove` and
`table.sort` write past a store, and what they write there is dropped and said.

| Name | Type | What it is |
|---|---|---|
| `first` | `T?` | May be left out. |

Returns `T`.

Fails on a value of the wrong type.

#### To count this player's kills

```luau client
local banner = store { kills = 0 }
on("killed", function(e)
    if e.killer == me then banner.kills += 1 end
end)
mount(function() return Box { `{banner.kills} kills` } end)
```

### Box

- On a client: `Box(props) → Node`

A box: its content, unnamed and first, then its props, named.

Its content is its text, strings and numbers joined, and its children: Boxes, components' calls and lists of
them, flattened, `nil` and `false` skipped. Every element of a list needs a `key`, unique among its siblings; a
new key is a new box, its animations started again. A `style` table is read where it is written, so a property
the HUD lacks fails at that line; a function of `is` runs as its box renders, and again as `hovered`, `pressed`
or `disabled` flips. No style is inherited.

A root box is placed on the screen as CSS's `position: absolute`; its children flow in it. `at` and `follow` lay
a box out from a point in the world, keeping its size at any distance and showing through walls: `left` and
`top` measure from where the point shows, `right` and `bottom` back from it. It hides while the point is behind
the eye or what it follows is not drawn, unless `edge`. A text is up to 256 bytes, `\n` between lines, shown as
written, braces and all. A box under more than 8 parents is not drawn. Past 8 frames' worth of pictures, or 32
boxes reading what is behind them, a frame draws boxes without their effects, and masked ones not at all.

| Name | Type | What it is |
|---|---|---|
| `props` | `BoxProps` | Needed. |

Returns `Node`.

Fails on a value of the wrong type.

#### To show a player's health in a corner

```luau client
mount(function()
    return Box {
        `{me.health or 0} HP`,
        style = { right = 40, bottom = 40, fontSize = 32, color = "#ffcc33" },
    }
end)
```

#### To light a button up under the pointer

```luau client
mount(function()
    return Box {
        "Ready",
        style = function(is)
            return { padding = "4px 8px", background = if is.hovered then "#ffcc33" else "#333333" }
        end,
        onClick = function() print("ready") end,
    }
end)
```

#### To mark where a bomb was planted

```luau client
mount(function()
    return Box { "BOMB", at = vector.create(4, 1, -2), edge = true, style = { color = "#ff3333" } }
end)
```

### Each

- On a client: `Each(list?, render) → {Node}`

Renders each item of a list, `render(item, index)` of its 1 to `#list`, each its own redraw boundary.

Each element it gives needs a `key`, unique among them; an item giving nil shows nothing. An item renders again
only as what it read changes, so a long list costs only what changed.

| Name | Type | What it is |
|---|---|---|
| `list` | `{T}?` | May be left out. |
| `render` | `(item: T, index: number) -> Node?` | Needed. |

Returns `{Node}`.

Fails on a value of the wrong type.

#### To list the players by name

```luau client
mount(function()
    local listed = {}
    for _, p in players do
        table.insert(listed, p)
    end
    return Box {
        Each(listed, function(p) return Box { p.name, key = p.id } end),
        style = { display = "flex", flexDirection = "column" },
    }
end)
```

### component

- On a client: `component(render) → (props: P) -> Node`

Makes a component of a function of props: a call of it, `Row { key = id, … }`, renders as its own redraw boundary.

It renders again only as what it read changes, or as its parent gives it props that differ, functions all alike.
A component's props extend `BoxProps`, as `BoxProps & { danger: boolean? }`.

| Name | Type | What it is |
|---|---|---|
| `render` | `(props: P) -> Node?` | Needed. |

Returns `(props: P) -> Node`.

Fails on a value of the wrong type.

#### To make each row its own boundary

```luau client
local Row = component(function(props)
    return Box { props.name, style = { color = if props.dead then "#888888" else "#ffffff" } }
end)
mount(function()
    return Box { Row { key = 1, name = "Ann", dead = false }, Row { key = 2, name = "Bo", dead = true } }
end)
```

### mount

- On a client: `mount(component)`

Shows a component, or a function of no props, from the first frame its match plays until its script loads again or is stopped.

A render gives one `Box`, a component's call or nil, and renders again as what it read changes: `match`,
`server`, `me`, `players`, `entities`, a store and `now(step)`; only what differs reaches the screen. Mounts are
drawn in the order mounted, `zIndex` ordering them, and never take the pointer. The engine's own HUD is drawn at
`zIndex` 0: a box below it is drawn under it. For a HUD in several corners, mount each part, or give the root
`left = 0, top = 0, right = 0, bottom = 0` and its parts `position = "absolute"`.

What the scripts keep on screen, their boxes and keyframes, takes 8 MB a script and 64 MB in all, each counting
1 KB beside its bytes; past it, the script is stopped and its trees go. A frame makes 4096 changes to the screen
at most.

| Name | Type | What it is |
|---|---|---|
| `component` | `(props: P) -> Node?` | Needed. |

Fails on a value of the wrong type.

#### To show the player's name in a corner

```luau client
mount(function()
    return Box { me.name or "", style = { left = 40, top = 40 } }
end)
```

### Shake:stop

- On a client: `Shake:stop(transition?)`

Fades the shake out from where it is, at once without a transition, a later stop replacing an earlier.

| Name | Type | What it is |
|---|---|---|
| `transition` | `Transition?` | May be left out. |

Fails on a value of the wrong type.

#### To sway the aim until the player steadies it

```luau client
local sway = shake("aim", { amplitude = { yaw = 0.01, pitch = 0.01 }, frequency = 0.5 }, { seconds = math.huge })
sway:stop({ seconds = 1 })
```

### View:set

- On a client: `View:set(values, transition?)`

Changes how this player's view looks, at no network cost, each value given eased by `transition`, at once without one; the rest stay as they are, the last set winning.

`view.fov`, `view.exposure`, `view.fog`, `view.listener` and `view.crosshair` read back what was last set, `fov`
and `fog` nil while the player's and the map's own. A fog is exponential: `half_at` metres away, half of what lies
there is hidden; `{half_at = math.huge}` is none. A frame takes 64 changes to the view at most.

| Name | Type | What it is |
|---|---|---|
| `values` | `table` | A table of the options below. Each may be left out. |
| `values.fov` | `(number \| false)?` | The field of view, in degrees across a 4:3 screen as the player's `fov` setting has them, over the setting
and the weapon's sights; `false` for the player's own. |
| `values.exposure` | `number?`, 0 or more | How bright the scene is before tone mapping, 1 as without it; the HUD is as bright as ever. |
| `values.fog` | `(Fog \| false)?` | A fog over the map's, the sky fading into it; `false` for the map's own. |
| `values.listener` | `("camera" \| "body")?` | Where the sound is heard: at the view, wherever the mod's camera puts it, or at the eyes of the body the view
is of; at once, whatever the transition. |
| `values.crosshair` | `boolean?` | Whether the engine's crosshair shows, fading. |
| `transition` | `Transition?` | May be left out. |

Fails on a value of the wrong type; an option it does not have; a value outside its range.

#### To zoom in through a scope, then back

```luau client
view:set({ fov = 20 }, { seconds = 0.2, timing = "ease-out" })
view:set({ fov = false }, { seconds = 0.2 })
```

#### To fade into a thick fog

```luau client
view:set({ fog = { color = "#8a8f99", half_at = 12 }, crosshair = false }, { seconds = 3 })
```

### Style properties

What a `Box`'s `style` takes, on a client.

| Name | Takes | What it is |
|---|---|---|
| `left` | length | How far its left edge is from its parent's, or the screen's; or `auto`. |
| `top` | length | How far its top edge is from its parent's, or the screen's; or `auto`. |
| `right` | length | How far its right edge is from its parent's, or the screen's; or `auto`. |
| `bottom` | length | How far its bottom edge is from its parent's, or the screen's; or `auto`. |
| `width` | length | Its width, not negative; or `auto`, as its content needs. |
| `height` | length | Its height, not negative; or `auto`, as its content needs. |
| `padding` | lengths | The room inside its border: one to four lengths, as CSS's sides, no `%`. |
| `color` | colour | Its text's colour; white without it. |
| `fontSize` | length | Its text's size, 24 pixels without it; `em` and `%` are of the screen's 24. |
| `fontWeight` | number | `normal`, `bold` or 1 to 1000, bold from 700. |
| `textAlign` | `left`, `center`, `right`, `start`, `end` | Where its lines sit. |
| `lineHeight` | number | The height of a line: a multiple of the font size, a length, `%` or `normal`. |
| `letterSpacing` | length | The room between letters, or `normal`. |
| `textTransform` | `none`, `uppercase`, `lowercase`, `capitalize` | Its text's case. |
| `fontStyle` | `normal`, `italic`, `oblique` | Slanted, `oblique 10deg` under 90deg. |
| `overflow` | `visible`, `hidden`, `clip` | Whether what passes its box shows. |
| `textOverflow` | `clip`, `ellipsis` | How a line cut by `overflow` ends. |
| `background` | CSS | Its background colour, or image, position and size, as CSS's shorthand. |
| `backgroundColor` | colour | The colour behind its content. |
| `backgroundImage` | image | An image the server named, or a gradient, drawn behind its content. |
| `backgroundPosition` | position | Where its background image sits; at its top-left without it. |
| `backgroundSize` | size | How big its background image is; its own size without it. |
| `border` | CSS | A width, `solid` or `none`, and a colour, in any order. |
| `borderWidth` | length | Its border's width, one for all sides, no `%`. |
| `borderStyle` | `solid`, `none`, `hidden` | Whether its border is drawn. |
| `borderColor` | colour | Its border's colour. |
| `borderRadius` | lengths | How round its corners are: one to four lengths, as CSS's corners. |
| `boxShadow` | CSS | One shadow: x and y, a blur and a spread if given, and a colour; or `none`. |
| `transform` | CSS | CSS's transform functions, in order: `translate()`, `rotate()`, `scale()`… |
| `transformOrigin` | position | The point it turns and scales about; its middle without it. |
| `transition` | CSS | How a change to its properties eases: a property, seconds, a timing, a delay. It eases the place and the size, padding, colours, text sizes and spacing, backgrounds, borders, shadows, transforms, opacity, filters and masks, or `all`. |
| `animation` | CSS | Up to 4 animations of `keyframes` by name, as CSS's shorthand. |
| `opacity` | number | How opaque it and all inside it are, 0 to 1 or a percentage. |
| `filter` | CSS | Up to 8 of CSS's filter functions on it and all inside it, as one: blur, brightness, contrast, grayscale, hue-rotate, invert, opacity, saturate and sepia. |
| `backdropFilter` | CSS | Up to 8 of CSS's filter functions on what is behind it, within its border. |
| `mixBlendMode` | `normal`, `multiply`, `screen`, `overlay`, `darken`, `lighten`, `color-dodge`, `color-burn`, `hard-light`, `soft-light`, `difference`, `exclusion`, `hue`, `saturation`, `color`, `luminosity`, `plus-lighter` | How it and all inside it blend with what is behind it. |
| `isolation` | `auto`, `isolate` | Keeps what is inside it blending only with each other. |
| `maskImage` | image | An image or a gradient whose opacity shows it, nothing outside it; nothing while its image loads. |
| `maskSize` | size | How big its mask is. |
| `maskPosition` | position | Where its mask sits. |
| `zIndex` | whole number | Its order among its siblings, or `auto`; the engine's HUD is at 0. |
| `userSelect` | `none`, `auto`, `text` | Whether its text may be selected. |
| `display` | `block`, `flex`, `none` | How it lays its children out, or hides. |
| `position` | `static`, `relative`, `absolute` | Whether it flows in its parent. |
| `flexDirection` | `row`, `row-reverse`, `column`, `column-reverse` | Which way a flex box lays its children. |
| `flexWrap` | `nowrap`, `wrap`, `wrap-reverse` | Whether a flex box's children wrap. |
| `justifyContent` | `normal`, `flex-start`, `flex-end`, `start`, `end`, `center`, `space-between`, `space-around`, `space-evenly` | Where a flex box's children sit along its way. |
| `alignItems` | `normal`, `stretch`, `flex-start`, `flex-end`, `start`, `end`, `center` | Where a flex box's children sit across its way. |
| `alignSelf` | `auto`, `normal`, `stretch`, `flex-start`, `flex-end`, `start`, `end`, `center` | Where it sits across its flex parent's way, over its `alignItems`. |
| `gap` | lengths | The room between a flex box's rows and columns: one for both, or two. |
| `rowGap` | length | The room between a flex box's rows, or `normal`. |
| `columnGap` | length | The room between a flex box's columns, or `normal`. |
| `flex` | CSS | Grow and shrink factors and a basis, or `none`, `auto` or `initial`. |
| `flexGrow` | number | How much of a flex box's room to spare it takes. |
| `flexShrink` | number | How much it gives up when a flex box lacks room. |
| `flexBasis` | length | Its size along its flex parent's way before growing, or `auto` or `content`. |
| `order` | whole number | Its place among a flex box's children. |

## Menus

Screens a client script opens over the game: a buy menu, a vote, a dialog asking a question. A menu frees the mouse and takes keys; one opens at a time, and a dialog opens over it and gives back its answer. A menu may open on a purpose action's key, as a buy menu on `buy`, which the player chose for every server.

Here too is the loading screen a mod may draw in place of the stock one while players load a match. The stock one lists the purpose actions your mod handles, with their labels and the player's keys; your own gets them as `props.binds`.

### loading_screen

- On a client: `loading_screen(component)`

Draws the loading screen in place of the stock one while the match loads, once a mod, at a script's top.

The component is given `{map, title, image, gametype, stage, progress, bytes_done, bytes_total, bytes_per_second,
binds}` and draws again as they or what it read change. `title` and `image` are the map's, `image` nil for none.
While connecting, `map` and `gametype` are empty and `title` is the server's address. `stage` is `"connecting"`,
`"waiting"` (while the mod's `connecting` holds the join), `"downloading"`, `"map"`, `"files"` or
`"declarations"`; `progress` is the share of the match's files loaded, 0 to 1, or while downloading of the bytes,
`bytes_done` of `bytes_total` coming at `bytes_per_second`, all 0 at the other stages. `binds` are the purpose
actions the server handles, each `{action, label, keys}`: the action, as `"tactical"`, the label it has here, and
the keys the player put it on, the first as Controls shows it first, empty for none.

It takes no pointer and goes once the match plays. The stock screen shows while none is declared, and from when
its render fails until the match plays. Another script's call fails, naming both.

| Name | Type | What it is |
|---|---|---|
| `component` | `(props: LoadingProps) -> Node?` | Needed. |

Fails on a value of the wrong type.

#### To show the map and a bar

```luau client
loading_screen(function(props)
    return Box {
        `{props.title} ({props.stage})`,
        Box { style = { width = `{props.progress * 100}%`, height = 8, background = "#ffcc33" } },
        style = { left = 0, right = 0, bottom = 80, padding = 40, background = "rgba(0, 0, 0, 0.6)" },
    }
end)
```

#### To list the keys of what the server's mod does on them

```luau client
loading_screen(function(props)
    return Box {
        Each(props.binds, function(bind)
            return Box { `{bind.keys[1] or "unbound"}: {bind.label}`, key = bind.action }
        end),
        style = { left = 80, top = 80, fontSize = 28 },
    }
end)
```

### menu

- On a client: `menu(name, options?, render) → Menu`

Declares a menu, which `open(menu, props)` shows over the HUD, freeing the pointer; gives its handle.

It is declared at a script's top. Its name is up to 16 lowercase letters, digits and `_`, one script's: another
script declaring it fails, naming both. `render` is given the props `open` gave, copied, with `close(value)`,
which closes it. One menu is open at a time, `open` replacing it. A player opens one with its action's key, a key
they bind to `open <name>`, or `/open <name>`, with no props.

While a menu shows, the player's binds do nothing and the keys held are let go. A tap of Esc runs its
`keys.Escape`, `false` for a forced choice, or else opens its `back`, or else closes it. Holding Esc a second always
opens the engine's menu over the game, a ring filling from a quarter second: no script sees the hold.

`action` takes a purpose action, as `"buy"`, which no mod binds to a key: the player has, once for every server. Two
menus on one action fail at load. A menu on an action the server handles opens as its handler hears the key go
down.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.pointer` | `boolean`, default true | Frees the pointer, its tree taking hover and clicks, and takes every key; `false` keeps the player playing
but for its `keys`, which cannot then be Esc: the keys the player moves, jumps and chats with stay theirs. |
| `options.keys` | `({[string]: ((props: MenuProps) -> ()) \| false})?` | What its keys do, as `{ Q = fn, ["1"] = fn, Enter = fn, Escape = false }`: a key a player binds or Escape,
each given the props, `false` doing nothing; Mouse1 and Mouse2 only with `pointer = false`, as they click. |
| `options.onOpen` | `((props: MenuProps) -> ())?` | Runs as it opens, given the props. |
| `options.onClose` | `((props: MenuProps) -> ())?` | Runs as it closes, given the props. |
| `options.back` | `string?` | The menu a tap of Esc opens in its place, by name, rather than closing it. |
| `options.toggle` | `boolean`, default false | A key bound to open it, or to its action, closes it. |
| `options.action` | `("tactical" \| "lethal" \| "utility" \| "ultimate" \| "buy" \| "drop" \| "quick_menu" \| "ping" \| "map")?` | The purpose action whose keys open it, as `"buy"`; the server's handler of it runs too. |
| `options.hold` | `boolean`, default false | Closes as the key that opened it is let go. |
| `options.help` | `string?` | Its line on Settings → Controls, up to 64 bytes. |
| `render` | `(props: MenuProps) -> Node?` | Needed. |

Returns `Menu`.

Fails on a value of the wrong type; an option it does not have.

#### To open a shop on the player's buy key

```luau client
local Shop = menu("shop", { action = "buy", toggle = true, help = "Buy weapons" }, function(props)
    return Box {
        "Shop",
        Box { "Close", onClick = function() props.close() end },
        style = { left = "40%", top = "30%", padding = 24, background = "rgba(0, 0, 0, 0.8)" },
    }
end)
```

### dialog

- On a client: `dialog(name, options?, render) → Menu`

Declares a dialog, which `open(dialog, props)` shows over the menu, waiting for its answer; gives its handle.

It is declared at a script's top, as a menu is. One dialog is open at a time, over the menu if one is. `open` of
one waits, in a handler only, until it closes, and gives what `props.close(value)` passed, or nil if it closed
otherwise: by Esc, which closes it unanswered unless its `keys.Escape` says, another dialog, or its menu closing.

| Name | Type | What it is |
|---|---|---|
| `name` | `string` | Needed. |
| `options` | `table?` | A table of the options below. It, and each option, may be left out. |
| `options.pointer` | `boolean`, default true | Frees the pointer, its tree taking hover and clicks, and takes every key; `false` keeps the player playing
but for its `keys`, which cannot then be Esc: the keys the player moves, jumps and chats with stay theirs. |
| `options.keys` | `({[string]: ((props: MenuProps) -> ()) \| false})?` | What its keys do, as `{ Q = fn, ["1"] = fn, Enter = fn, Escape = false }`: a key a player binds or Escape,
each given the props, `false` doing nothing; Mouse1 and Mouse2 only with `pointer = false`, as they click. |
| `options.onOpen` | `((props: MenuProps) -> ())?` | Runs as it opens, given the props. |
| `options.onClose` | `((props: MenuProps) -> ())?` | Runs as it closes, given the props. |
| `render` | `(props: MenuProps) -> Node?` | Needed. |

Returns `Menu`.

Fails on a value of the wrong type; an option it does not have.

#### To ask before buying

```luau client
local Confirm = dialog("confirm", { keys = { Enter = function(props) props.close(true) end } }, function(props)
    return Box {
        props.question,
        Box { "Yes", onClick = function() props.close(true) end },
        Box { "No", onClick = function() props.close(false) end },
    }
end)
local Shop = menu("shop", nil, function()
    return Box {
        "Buy the MP40",
        onClick = function()
            if open(Confirm, { question = "Buy the MP40?" }) then request("buy") end
        end,
    }
end)
```

### open

- On a client: `open(menu, props?) → any`

Opens a menu, in place of the one open, or a dialog over it, its render given a copy of `props` with `close(value)`; a dialog's `open` gives its answer.

A dialog opens in a handler alone, `on`, `onClick`, a menu's key or `onOpen`, as it waits until it closes: then
it gives what `props.close(value)` passed, or nil if it closed otherwise.

| Name | Type | What it is |
|---|---|---|
| `menu` | `Menu` | Needed. |
| `props` | `({[string]: any})?` | May be left out. |

Returns `any`.

Fails on a value of the wrong type.

#### To open the shop when the player says so

```luau client
local Shop = menu("shop", nil, function(props)
    return Box { `Shop, opened by {props.by}`, onClick = function() props.close() end }
end)
on("said", function(e)
    if e.text == "shop" then open(Shop, { by = "chat" }) end
end)
```

### close

- On a client: `close(menu)`

Closes a menu, and a dialog over it, or a dialog, unanswered; one not open is left as it is.

| Name | Type | What it is |
|---|---|---|
| `menu` | `Menu` | Needed. |

Fails on a value of the wrong type.

#### To close the shop as the player dies

```luau client
local Shop = menu("shop", nil, function() return Box { "Shop" } end)
on("killed", function(e)
    if e.victim == me then close(Shop) end
end)
```

### is_open

- On a client: `is_open(menu) → boolean`

Whether a menu or a dialog is open: a read a render draws again on.

| Name | Type | What it is |
|---|---|---|
| `menu` | `Menu` | Needed. |

Returns `boolean`.

Fails on a value of the wrong type.

#### To dim the HUD while the shop is open

```luau client
local Shop = menu("shop", nil, function() return Box { "Shop" } end)
mount(function()
    return Box { "Round 1", style = { left = 40, top = 40, opacity = if is_open(Shop) then 0.3 else 1 } }
end)
```

### escape_menu

- On a client: `escape_menu(menu)`

Names what a tap of Esc opens while nothing is open, in place of the engine's menu: a menu, or a function asked at each tap, whose nil opens the engine's.

A mod calls it once, at a script's top; another script's call fails, naming both. Holding Esc a second always
opens the engine's menu.

| Name | Type | What it is |
|---|---|---|
| `menu` | `Menu \| () -> Menu?` | Needed. |

Fails on a value of the wrong type.

#### To open the mod's own pause menu

```luau client
local Pause = menu("pause", nil, function(props)
    return Box { "Paused", Box { "Resume", onClick = function() props.close() end } }
end)
escape_menu(Pause)
```
