Skip to content

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 for ids and objects, and who sees what 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.

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.

What a player, p, or an entity, e, reads beside the mod’s state. A field of the other side’s fails.

NameTypeSideWhat it is
p.cameraCameraView?On the serverWhere they see from and which way: their eyes, or the camera set_camera gave them.
e.idnumberOn both sidesIts id, as entities[id] finds it.
e.kindstring?On both sidesWhat it is: part for the map's, else the kind the mod made it of.
e.existsbooleanOn both sidesWhether it is still there; after a wait, check it.
e.ownerPlayer?On both sidesThe player it is of, as it was made with owner.
e.positionvector?On both sidesWhere it is.
e.anglesAngles?On both sidesHow it is turned.
e.move_seconds_leftnumber?On the serverThe seconds its move has left; nil once it is still, math.huge for a loop.
e.rotation_seconds_leftnumber?On the serverThe seconds its turn has left; nil once it is still.
e.solidSolid?On the serverWhat 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}On the serverThe players in its way as it last moved.
e.read_by(Group | ((Player?, Player) -> boolean))?On the serverWho is shown it; everyone for nil. Written, not read: keep what you gave it in state.
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.

NameTypeWhat it is
animationanimationNeeded.
optionstable?A table of the options below. It, and each option, may be left out.
lengthnumber?0 to 3600How long one play lasts, in seconds, above 0, at the pace that fits; the clip's own without.
loopbooleandefault falsePlays it again and again, until another starts.
startnumber-3600 to 5, default 0When it starts, in seconds from now: up to an hour ago, seen from part-way through, or up to 5 s ahead.
Fails
  • a value of the wrong type
  • an option it does not have
  • a value outside its range
p:animate("dance_loop", { loop = true })

To walk a model of a character along a path

Section titled “To walk a model of a character along a path”
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 })
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.

NameTypeWhat it is
kind"model" | "sound" | "effect" | "image" | "weapon" | "animation" | "material"Needed.
...namesstringAny number of them.
Fails
  • a value of the wrong type

To load a boss’s sounds and model at load

Section titled “To load a boss’s sounds and model at load”
preload("sound", "zombie_groan1", "explosion_boom")
preload("model", "rocket")
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.

NameTypeWhat it is
optionstable?A table of the options below. It, and each option, may be left out.
atvector?Where it is; the map's origin without.
anglesAngles?Which way it looks; unturned without.
Returns
Entity
Fails
  • a value of the wrong type
  • an option it does not have
local cam = spawn_camera({ at = vector.create(0, 30, -20), angles = { pitch = -0.9 } })
p:set_camera(cam, { look = "target" })
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.

NameTypeWhat it is
target(Player | Entity | number | vector)?May be left out.
optionstable?A table of the options below. It, and each option, may be left out.
offsetvector?How far from what it is on, turned by it: from a body's eyes, by its view's yaw alone.
look("aim" | "target")?"aim" turns the view with the player's mouse; "target" faces it as what it is on faces. Aim without.
Fails
  • a value of the wrong type
  • an option it does not have

To watch a player from over their shoulder

Section titled “To watch a player from over their shoulder”
p:set_camera(p, { offset = vector.create(0.5, 0.3, -2.5) })

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

Section titled “To show a killcam from where the killer stood, and back”
victim:set_camera(killer.position + vector.create(0, 1.6, 0))
wait(3)
victim:set_camera(nil)
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.

local crate = model("rocket")
crate:remove()

To clear every zombie at the end of a round

Section titled “To clear every zombie at the end of a round”
for _ = 1, 3 do
spawn()
end
for _, e in entities do
if e.kind == "body" then
e:remove()
end
end
On the server e:on(event: "move_finished" | "rotate_finished" | "blocked" | "unblocked", fn: (e: Moved) -> ())
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.

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.

NameTypeWhat it is
event"move_finished" | "rotate_finished" | "blocked" | "unblocked"Needed.
fn(e: Moved) -> ()Needed.
Fails
  • a value of the wrong type

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.

NameTypeWhat it is
event"damaged"Needed.
fn(e: Damaged) -> ()Needed.
Fails
  • a value of the wrong type
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)
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)
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.

NameTypeWhat it is
namemodelNeeded.
optionstable?A table of the options below. It, and each option, may be left out.
atvector?Where it stands; the map's origin without.
anglesAngles?How it is turned; unturned without.
owner(Player | number)?The player it is about, whom owner, team and spectators pick by.
read_by(Group | ((Player?, Player) -> boolean))?Who is shown it; everyone without.
kindstring?Its kind, one entity_kind declared on its base; the base's own without.
Returns
Entity
Fails
  • a value of the wrong type
  • an option it does not have
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

Section titled “To show a player alone the flag they carry”
local flag = model("rocket", { owner = p, read_by = owner })
flag:attach(p, { offset = vector.create(0, 1.2, -0.3) })
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.

NameTypeWhat it is
pointvectorNeeded.
secondsnumberNeeded.
optionstable?A table of the options below. It, and each option, may be left out.
timingTiming?How it eases, each lap if it loops; linear without.
facebooleandefault falsePoints its nose the way it goes while it moves, its roll kept, until it is turned again.
loopbooleandefault falseRepeats it for good: it comes back to where it started, each lap taking its seconds.
Fails
  • a value of the wrong type
  • an option it does not have
local door = model("rocket", { at = vector.create(0, 0, 4) })
door:move_to(vector.create(2, 0, 4), 1.5, { timing = "ease-in-out" })
local lift = model("rocket")
lift:move_to(vector.create(0, 6, 0), 4, { timing = "ease-in-out", loop = true })
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.

NameTypeWhat it is
offsetvectorNeeded.
secondsnumberNeeded.
optionstable?A table of the options below. It, and each option, may be left out.
timingTiming?How it eases, each lap if it loops; linear without.
facebooleandefault falsePoints its nose the way it goes while it moves, its roll kept, until it is turned again.
loopbooleandefault falseRepeats it for good: it comes back to where it started, each lap taking its seconds.
Fails
  • a value of the wrong type
  • an option it does not have
local platform = model("rocket")
platform:move_by(vector.create(0, 3, 0), 2)
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.

NameTypeWhat it is
points{vector}Needed.
secondsnumberNeeded.
optionstable?A table of the options below. It, and each option, may be left out.
timingTiming?How it eases, each lap if it loops; linear without.
straightbooleandefault falseGoes straight from one point to the next, rather than on a curve through them.
facebooleandefault falsePoints its nose the way it goes while it moves, its roll kept, until it is turned again.
loopbooleandefault falseGoes round for good, from its last point back to where it started, each lap taking its seconds.
Fails
  • a value of the wrong type
  • an option it does not have

To fly a camera along a path, facing its way

Section titled “To fly a camera along a path, facing its way”
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" })
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 })
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.

NameTypeWhat it is
anglesAnglesNeeded.
secondsnumberNeeded.
optionstable?A table of the options below. It, and each option, may be left out.
timingTiming?How it eases, each lap if it loops; linear without.
loopbooleandefault falseTurns on for good, by as much again each lap, as a fan.
Fails
  • a value of the wrong type
  • an option it does not have
local door = model("rocket")
door:rotate_to({ yaw = math.pi / 2 }, 1, { timing = "ease-out" })
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.

NameTypeWhat it is
anglesAnglesNeeded.
secondsnumberNeeded.
optionstable?A table of the options below. It, and each option, may be left out.
timingTiming?How it eases, each lap if it loops; linear without.
loopbooleandefault falseTurns on for good, by as much again each lap, as a fan.
Fails
  • a value of the wrong type
  • an option it does not have
local fan = model("rocket")
fan:rotate_by({ yaw = 2 * math.pi }, 3, { loop = true })
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.

NameTypeWhat it is
what("move" | "rotate")?May be left out.
Fails
  • a value of the wrong type
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")
local zombie = spawn({ at = vector.create(0, 0, 10) })
zombie:chase(p)
zombie:stop()
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.

NameTypeWhat it is
player(Player | number)?May be left out.
optionstable?A table of the options below. It, and each option, may be left out.
ratenumber?The fastest it turns, in radians a second, above 0; at once without.
Fails
  • a value of the wrong type
  • an option it does not have
local gun = model("ak47", { at = vector.create(0, 1, 2) })
gun:turn_with(p, { rate = math.pi })
local gun = model("ak47", { at = vector.create(0, 1, 2) })
gun:turn_with(p)
gun:turn_with(nil)
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.

NameTypeWhat it is
toPlayer | Entity | numberNeeded.
optionstable?A table of the options below. It, and each option, may be left out.
bonestring?The joint of a body it rides, by name, as "DEF-spine.003"; the body's origin without.
offsetvector?How far from what it rides, or from the joint, in that one's space.
Fails
  • a value of the wrong type
  • an option it does not have
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)
local charge = model("grenade")
charge:attach(p, { bone = "DEF-spine.003", offset = vector.create(0, 0, -0.15) })
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.

local flag = model("rocket")
flag:attach(p, { offset = vector.create(0, 1.2, -0.3) })
flag:detach()
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.

NameTypeWhat it is
form"box" | "wedge" | "cylinder" | "sphere" | "capsule" | "hull"Needed.
sizevector | {vector}Needed.
optionstable?A table of the options below. It, and each option, may be left out.
materialmaterial?The .material file it is drawn with; not drawn without, a clip.
solidSolid?What it stops, as its solid says: true, false or a mask; everything without.
atvector?Where it stands; the map's origin without.
anglesAngles?How it is turned; unturned without.
Returns
Entity
Fails
  • a value of the wrong type
  • an option it does not have
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

Section titled “To block a doorway to players alone, unseen”
shape("box", vector.create(2, 3, 0.2), { solid = { players = true }, at = vector.create(0, 0, 3) })
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" })