Land of Dran Server Lua API

This documents every function the server-side Lua environment exposes to scripts (the add-ons' server.lua files, serverstart.lua and anything dofile'd from them, or run through the eval console), along with the object types, events, fields, and constants they use. Generated from the current LuaFunctions/*.cpp and Networking/PacketsFromClient/*.cpp source - if you add or change a binding, update this file too.

How the engine behaves beyond what each function does - what players see and do, how things are simulated and drawn, what it costs - and the add-ons the game comes with are in Engine.md.

Conventions


Logging / misc

Function Arguments Description
info(...) any number of values Logs a line to the server's info log. Values are stringified (tables holding a Dynamic/Static/Client/Brick/Light/Emitter print as [Dynamic N]/[Static N]/[Client N]/[Brick N]/[Light N]/[Emitter N]).
error(...) any number of values Same as info, but logged as an error and prefixed accordingly.
debug(...) any number of values Same as info, but only logged when the logger/verbose setting is on.
shutdown() none Stops the main program loop (shuts the whole process down, not just the server).

Time of day, sky, and water

The server owns the time of day, the look of the day/night cycle, and the water level. It sends them to every client once a second, and right away when one of these functions changes them or a client finishes joining.

Function Arguments Returns Description
setTimeOfDay(fraction) fraction: 0 = midnight, 0.25 = sunrise, 0.5 = noon, 0.75 = sunset. Values outside 0-1 wrap around. none Jumps to that time of day. The server starts at noon.
getTimeOfDay() none number, 0-1 Current time of day, on the same scale as setTimeOfDay.
setTimeScale(scale) scale: in-game seconds that pass per real second none A full day is 1000 in-game seconds (DAY_LENGTH_SECONDS in LandOfDran.h), so the default of 1 is a ~16.7 minute day. 0 freezes time, negative values run it backwards.
getTimeScale() none number Current time scale.
setWaterLevel([y]) y: world height of the water surface, or no argument / nil none Puts a water surface at height y across the whole world, or removes it when called with no argument. Off by default. Dynamics in the water float or sink depending on their buoyancy (see dynamic:setBuoyancy) and are slowed by drag. Vehicles float on their wheels, or on their hulls if they're boats (see Boats). Players at least half under water swim: W and S follow the camera up and down, A and D stay level, holding jump swims up, and pressing jump with their head above the surface jumps out. The server plays the Splash sound where a dynamic falls in fast and ExitWater where one comes out fast, if they're registered; a vehicle splashes too, per wheel as each goes under, or once for its hull if it hasn't any wheels. Clients draw ripples spreading across the surface where dynamics go in, come out, or move along it.
getWaterLevel() none number, or nil if there's no water Current water height, which is the level the waves roll about rather than the height of the water at any one spot, see setWind.
setWind(strength[, degrees]) strength: 0 for a dead calm up to 1 for a gale, clamped; degrees: the way it blows, 0 along +x, keeping what it was if left out none The wind over the water, which is what makes waves. It starts at 0.25, a light breeze. The whole surface is a sum of four sines rolling along the wind, worked out the same way on the server and on every client (Physics/WaterWaves.h), so what a boat floats on is exactly what's drawn: the waves lift and tilt everything in the water, and a dead calm is a flat mirror. A gale runs about three studs from a trough to a crest. The wind also pushes whatever is floating downwind, a swimmer harder than a hull, so a boat nobody rows drifts, and it slants the rain. See Boats for what floats on it, and the graphics/watertessellation setting below for waves that are really in the surface rather than only in the mesh it's drawn with.
getWind() none strength, degrees What the wind is doing, as setWind takes it.
setWeather(kind[, intensity]) kind: "none", "rain", or "snow", ignoring case; intensity: 0 for none up to 1 for a downpour or a blizzard, clamped, 1 if left out none What falls from the sky everywhere. Clear by default, and only one kind falls at a time: setting one stops the other, which fades out over a few seconds as the new one fades in. "none", or an intensity of 0, stops the weather. Rain plays a rain loop that gets quieter and muffled the less open sky is above the camera, and, unless the client's graphics/rainquality is off, draws falling drops and splashes only where nothing is overhead, and darkens and adds shine to the surfaces it reaches; those dry off slowly after it stops. Past 0.5 distant thunder rolls under it, from nothing at 0.5 to full at 1, muffled the same way; past 0.8 lightning flashes every so often (about every 40 seconds at 0.8, every 15 at 1), each strike a few flickers that light the whole world and, under the simulated sky, the clouds. Each client times its own strikes. Snow is silent, and draws flakes drifting down wherever nothing is overhead, settling as white cover on surfaces that face up and out into the open, deepest in the hollows; it melts off slowly after the snow stops, and much faster if it starts raining. Both are slanted by the wind, snow a good deal further than rain, see setWind. Neither changes the sky or sun, pair them with setSunColor / setFogColor / setFogDistance for an overcast look.
getWeather() none string, number What's falling ("none", "rain", or "snow") and how hard, 0 while it's clear.
setRain(intensity) intensity: 0 for none up to 1 for a downpour, clamped to that range none The older way of saying setWeather("rain", intensity); 0 clears the weather.
getRain() none number, 0-1 Current rain intensity, 0 while it's clear or snowing.

How the waves are drawn, the graphics/watertessellation setting, and what they cost are in Engine.md.

All of these except the getters use the strict Expected 1 number argument check described above (setWaterLevel also accepts no arguments).

The day/night cycle blends between four phases: "night", "dawn", "day", and "dusk" (phase names ignore case). Each has its own sky, fog, and sun color, and the colors you see at any moment are a mix of the phases on either side of the current time. Everything is lit by the ambient color, light from the sky itself, and the sun adds its light on top wherever it reaches, so shadows are the ambient color with the sun taken away. At night the "sun" is the moon, shining from the opposite side of the sky.

Function Arguments Returns Description
setSkyColor(phase, r, g, b) phase name, color none Color of the sky straight up during that phase. It fades into the fog color toward the horizon.
getSkyColor(phase) phase name r, g, b That phase's sky color.
setFogColor(phase, r, g, b) phase name, color none Color of the fog, and of the sky near the horizon, during that phase.
getFogColor(phase) phase name r, g, b That phase's fog color.
setSunColor(phase, r, g, b[, brightness]) phase name, color, optional brightness none Color of the sunlight (moonlight for "night") during that phase. Sunlight is much brighter than a screen color, so the color is multiplied by brightness; leave it out to keep the phase's current brightness. Defaults: day is 1, 0.7, 0.5 at brightness 15, dawn and dusk are about 6, night is 0.5, 0.6, 1 at 0.35.
getSunColor(phase) phase name r, g, b, brightness That phase's sunlight, split into a color whose brightest channel is 1 and its brightness.
setAmbientColor(phase, r, g, b) phase name, color none Light from the sky that reaches everything, shadows included, during that phase. It's dim next to sunlight, so small values go a long way. Defaults: day is about 0.45, 0.32, 0.22, dawn and dusk about 0.25, 0.2, 0.2, night about 0.015, 0.018, 0.03. Values above 1 are allowed.
getAmbientColor(phase) phase name r, g, b That phase's ambient color.
setFogDistance(start, end) distances from the camera in world units none Fog begins at start and completely hides everything past end. Needs 0 <= start < end <= 1000. Defaults to 150, 290. A player whose draw distance is shorter than end sees the whole fog shrunk to fit it. Grass and water always reach past end, and shadows cover the view out to end, so a longer fog distance spreads the same shadow detail over more ground.
getFogDistance() none start, end Current fog distances.
setFogHeight(height) height: world height the fog is thickest up to. Needs 0 <= height <= 2000. none The fog is thickest below height and thins out gradually above it, over a height that grows with height (half of it, at least 20). How fogged anything looks, the sky included, comes from how much fog the view ray crosses, so raising it drags the fog further up the sky and lowering it pulls it down toward the horizon. The horizon itself is always hidden, since the world is fully fogged there. Defaults to 40. Something past the start of the fog also fades into the sky behind it rather than into the fog color, so a tall tower in the distance disappears into the sky instead of standing out against it. See Fog.
getFogHeight() none number Current fog height.
setFogginess(amount) amount: 0 for clear air up to 1 for thick fog, clamped none Fog for the simulated sky (setSkybox("simulated")), which ignores setFogDistance and setFogHeight: its clouds come down and spread into the fog, the fog closes in from 600 studs on a clear day to 60 at 1, and its color is the sky's own light, so it's lit right at noon, dusk, and night. Rain makes it a little hazier on its own. Does nothing under any other sky. Defaults to 0.
getFogginess() none number Current fogginess.
resetDayCycle() none none Puts every phase's sky, fog, sun, and ambient color, the fog distances, and the fog height back to their defaults. Doesn't change the time of day or time scale.

The setters log an error and do nothing if the phase name is unknown or the arguments are the wrong count or type. Negative colors and brightness are treated as 0.

Skyboxes

By default the sky is the gradient from the colors above. A server can replace it with a day skybox and a night skybox instead. They cross fade while the sun is near the horizon (starting a little before sunrise, done a little after). Leaving one out keeps the gradient for that part of the day. Clients load skyboxes from their own copy of the game folder. A sky an add-on registers with registerSkybox is sent to players who haven't got it, see Sky add-ons; any other sky can be offered with addServerFile like any other file, see Sending add-on files to clients.

A skybox path is one of:

Every skybox fades into the fog color toward the horizon, so the fogged edge of the world blends into it. How far up the sky that fade reaches is setFogHeight above: a skybox with trees or buildings along its horizon usually wants a height that covers all of them, and setFogHeight(0) leaves only a thin haze just above the horizon.

Function Arguments Returns Description
setSkybox([day[, night]]) day, night: skybox paths or names registerSkybox was given (ignoring case), or nil for the gradient none Sets both skyboxes at once and sends them to every client. setSkybox() goes back to the gradient all day. Logs an error and changes nothing if a path isn't relative, reaches outside the game folder, is longer than 255 characters, or the server doesn't have the file(s). The first time a registered sky is used, its files are offered to clients from then on, see below. A registered sky given by its path rather than its name counts as that sky, files offered and all.
getSkybox() none day, night Current skybox paths, nil for the gradient. Paths even for a sky picked by name, which setSkybox takes back just the same.
registerSkybox(name, path) a name of up to 64 characters; a skybox path, as for setSkybox true if it's registered Makes a sky a choice setSkybox and an admin's /sky can pick by name, without changing the sky. Registering a name again points it at the new path.
getSkyboxes() none table of names Every name registerSkybox was given, in the order it was.
setSkybox("Assets/skyboxes/bluecloud", "Assets/skyboxes/space")  -- clouds by day, stars by night
setSkybox("Country Road", "Qwantani")                             -- two Sky_ add-ons' skies, which also light the world

Terrain

A server can have rolling ground made from a heightmap in place of the flat ground, repeating forever in every direction. It's picked before the server starts, from the Terrain_ add-ons in Add-ons, and can't change while it runs: the Start Game dialog's Terrain box, or hosting/terrain in Config/settings.txt for a dedicated server. How a terrain is made and how it behaves is in Terrain.

The terrain is the ground: raycast and ProjectileHit report hitting it the way they report the flat ground, with nil for the object, and nothing can end up under it. Players can't plant a brick under it, but addBrick and loaded saves can put bricks anywhere, the flat ground's y of 0 still being the lowest a brick goes.

Function Arguments Returns Description
getTerrain() none string or nil The terrain's add-on folder name, like "Terrain_Hills", or nil on the flat ground.
getTerrainHeight(x, z) a world position across the ground number How high the ground is there, exactly where things collide with it, or 0 on the flat ground. For putting things on the ground: System_Players drops players from getTerrainHeight(0, 0) + 5 when that's higher than its usual 50.
local x, z = 120, -40
local bot = createDynamic(botType, x, getTerrainHeight(x, z) + 3, z)

Scheduling

Function Arguments Returns Description
schedule(delayMS, functionName, ...) delayMS: milliseconds from now to run; functionName: string name of a global function; any further arguments are passed through to that function when it runs schedule ID (number) Calls the named global function once, after delayMS milliseconds. Extra arguments after functionName are forwarded to it. Runs are one-shot - call schedule again inside the callback for a repeating timer.
cancel(scheduleID) scheduleID: value returned by schedule none Cancels a pending scheduled call before it fires. No-op if it already fired or was already cancelled.

Events

Function Arguments Returns Description
registerEventListener(eventName, functionName) both strings none Binds a global function as a listener for a built-in event. Multiple listeners can be bound to the same event; they run in registration order, each receiving whatever the previous one returned (see below), so every listener for a given event must accept and return the same argument list.
unregisterEventListener(eventName, functionName) both strings none Removes a previously-registered listener.
getNumListeners(eventName) string count How many functions are bound to an event.
getListenerIdx(eventName, index) string, 0-based index function name (string) Name of the listener at that index.

Built-in events

Event Listener signature Notes
ClientJoin function(client) ... return client end Fires once a client finishes phase-1 loading (right after connecting).
ClientLeave function(client) ... return client end Fires when a client disconnects, before it's removed from the client list. Use this to clean up anything the client owned.
ClientChat function(client, message) ... return client, message end Fires when a client sends a chat message, before it's broadcast. message is "<name>: <text>". Return a modified message to alter it, or an empty string to suppress it. Slash commands are case-insensitive: when the text starts with /, the command word (up to the first space) is lowercased before listeners see it, so compare against lowercase names; arguments after the space keep their case.
ClientPlantBrick function(client, brick) ... return client, brick end Fires after a client plants its ghost brick and the server accepts it. The brick is already placed and sent to clients; call brick:remove() to take it back out. A listener that does can return client, nil, so listeners after it have to expect no brick.
ClientAdminLogin function(client) ... return client end Fires when a client enters the right eval console password. Not fired for the single player host, who is made admin automatically.
ClientClick function(client, posX, posY, posZ, dirX, dirY, dirZ, mask) ... return client, posX, posY, posZ, dirX, dirY, dirZ, mask end Fires on every mouse click. posX/Y/Z and dirX/Y/Z are the camera's position and look direction at the moment of the click; mask is the SDL mouse button mask (see Conventions).
ClientStartTalking function(client) ... return client end Fires when a client starts sending voice chat. Calling client:setVoiceMuted(true) here cuts them off before anyone hears them.
ClientStopTalking function(client) ... return client end Fires when a client lets go of push to talk, or half a second after their voice stops arriving (a lost last packet, or muted while talking). Not fired for a client who leaves while talking.
ClientWrenchBrick function(client, brick) ... return client, brick end Fires when a client holds Insert and left clicks a brick within 100 studs of their camera, before its wrench dialog opens. Return client, nil to keep the dialog closed, or another brick to open that one's dialog instead. Not fired by client:openWrenchDialog.
ClientClickRelease function(client, posX, posY, posZ, dirX, dirY, dirZ, mask) ... return client, posX, posY, posZ, dirX, dirY, dirZ, mask end Fires when a client lets go of a mouse button in game, even over a window. Same arguments as ClientClick, except mask is only the button let go.
ClientSliceBricks function(client, brickCount) ... return client, brickCount end Fires when a client's selection box would make a vehicle, after every rule it has to follow checks out, with how many bricks (wheels included) would be sliced. Return client, nil to stop it, which leaves the bricks where they are and tells the client nothing. See Vehicles.
VehicleCreated function(vehicle, builder) ... return vehicle, builder end Fires once a vehicle is finished being made, by slicing, loading a save (by a client or loadVehicleFile), or sliceBricks, with the client who made it or nil for Lua. Return values are ignored.
ClientEnterVehicle function(client, vehicle, seat) ... return client, vehicle, seat end Fires when a client right clicks a vehicle, before they get in: to drive it (seat is nil) when nobody is, otherwise onto its free passenger seat nearest where they clicked (seat 0 or more). Also fires when a client already in the vehicle presses their next seat key (comma) to move to another of its seats, with the seat they'd move to. Return client, nil, seat to keep them out, or where they are. Not fired by client:enterVehicle.
ClientExitVehicle function(client, vehicle, seat) ... return client, vehicle, seat end Fires after a client gets out of a vehicle (seat is nil for the driver, else the passenger seat they were on) by right clicking, or because their player was destroyed or given to someone else while in it. Not fired by client:exitVehicle, vehicle:ejectDriver, removing the vehicle, or leaving the server.
ClientWrenchVehicle function(client, vehicle) ... return client, vehicle end Fires when a client holds Insert and left clicks a vehicle, before its wrench dialog opens. Return client, nil to keep the dialog closed. Not fired by client:openWrenchDialog.
ClientLoadVehicle function(client, brickCount, asVehicle) ... return client, brickCount, asVehicle end Fires when a vehicle save a client uploaded from their Saved Vehicles window is about to be placed, with how many bricks it has (wheels included) and whether it's loading as a vehicle or as bricks. Return client, nil to stop it, which tells the client nothing. Not fired by loadVehicleFile. See Vehicles.
ClientRemoveVehicle function(client, vehicle) ... return client, vehicle end Fires when a client confirms Remove vehicle in a vehicle's wrench dialog, before it's removed. Return client, nil to keep it. Not fired by vehicle:destroy or clearAllVehicles.
ClientSaveBricks function(client) ... return client end Fires when an admin clicks Save in their Saved Bricks window (escape menu), just before the server sends them a copy of every brick to write to their own Saves folder, with their name and the time in it (their game adds a picture of the build). Return client, nil to stop it, which tells the client nothing. Not fired for anyone without admin: the server refuses them, and their game saves what it can see of the bricks on its own (no names, owners, or attachments). Not fired by saveBuild.
ClientLoadBricks function(client, fileName, clearFirst, x, y, z) ... return client, fileName, clearFirst, x, y, z end Fires once an admin's upload of a .lod or .bls save from their own computer has fully arrived and is about to be loaded, with the name they gave it, whether they asked for every brick to be taken away first, and the offset they typed (studs, plates, studs, 0 by default). Return client, nil to stop it, which tells the client nothing. The server refuses uploads from clients without admin before any of it is kept. Loads go through loadLodSave / loadBlocklandSave's code with that offset. Not fired by those functions.
ClientPaintCan function(client, out) ... return client, out end Fires when a client's paint palette wants a paint can in their hand (out is true), which happens as the palette comes out, and again when their item bar or brick bar takes it back (out is false). Nothing happens unless a listener does it, usually by putting an item in their hand with client:setHandItem and taking it away again.
ClientDropItem function(client, slot) ... return client, slot end Fires when a client presses their drop item key with Ctrl (Ctrl+W by default), with the slot their item bar has picked (0-4), whether or not there's an item in it or their items are out. Nothing is dropped unless a listener does it.
ClientKey function(client, name) ... return client, name end Fires when a client presses one of the key binds that mean nothing to the engine itself and are only there for Lua: "reload" (the Reload bind, R by default) and "firemode" (the Switch Fire Mode bind, X by default). Nothing happens unless a listener does something. A client older than this event never sends it, and a server older than it logs the packet as invalid.
ProjectileHit function(projectile, hit, x, y, z, tag, normalX, normalY, normalZ) ... return projectile, hit, x, y, z, tag, normalX, normalY, normalZ end Fires the first time a projectile from addProjectile touches something that collides: a Dynamic, Static, Brick, or Vehicle as hit, or nil for the ground. hit is also nil when a listener for another projectile landing in the same step already removed what this one touched, like a player killed by the round before it and swapped for a corpse. x, y, z is where on hit they touched, which for anything but a tiny projectile is a corner of the box it collides as rather than its middle, tag is the tag it was fired with, and the normal is that of the surface it hit, pointing out of it, like raycast's. It's removed right after its listeners run, unless one already removed it, so projectile:getPosition() still works in one. Return values are ignored, but like every event all nine have to be returned or the listeners after this one aren't called.
RadiusImpulseHit function(pushed, x, y, z, strength) ... return pushed, x, y, z, strength end Fires from radiusImpulse for each thing it pushes: every Vehicle first, then every Dynamic (players, items on the ground, projectiles, the rest), with the middle of the impulse and the impulse that reached it where it stood: strength * (1 - distance / reach), before its mass, negative for a pull. Check pushed.type (see Conventions) for which kind it is. Fired as it's pushed, so a listener can move or destroy it, and a vehicle's listener runs after any bricks that blast broke off it are gone. Return values are ignored.

Dynamics

Dynamics are physics-simulated objects (players, projectiles, pickups, etc).

Global functions

Function Arguments Returns Description
createDynamic(typeID, x, y, z) typeID from newDynamicType/getDynamicType; spawn position Dynamic Spawns a new dynamic of the given type at the given position.
getDynamicId(netId) net ID Dynamic Looks up a dynamic by its net ID. Errors if it doesn't exist.
dynamicExists(netId) net ID boolean Whether there's still a dynamic with that net ID, without the error getDynamicId logs for one that's gone. For a script that keeps IDs of things that are removed out from under it, like rounds in the air.
getDynamicIdx(index) 0-based index Dynamic Looks up a dynamic by its position in the internal list (see getNumDynamics).
getNumDynamics() none count How many dynamics currently exist.
newDynamicType(scriptName, modelFilePath, scaleX, scaleY, scaleZ) scriptName: unique name used to refer to this type later; modelFilePath: path to the model file; scale on each axis typeID Registers a new kind of dynamic (model + scale). Call once at startup per type. modelFilePath is normally a .txt descriptor, but a .dts (the shapes Blockland add-ons ship their models in) can be given straight to it with no descriptor next to it, see DTS models.
getDynamicType(scriptName) string typeID Looks up a previously-registered type's ID by its script name.
getTypeNodePosition(typeID, nodeName) a dynamic or item type; the name of a node in its model, case insensitive x, y, z, or nothing Where that node sits in the model's own space with nothing animating, the type's scale applied. Nothing at all if the model has no node by that name. Shapes name the spots an add-on cares about, so this is how a script finds them without writing the numbers down: a Blockland jeep hangs its wheels from hub0 to hub3 and seats its riders on Mount0 and up, and a weapon's hand goes on its mountPoint. See Model vehicles and DTS models.
getTypeMeshBounds(typeID, meshName) dynamic type ID; mesh name within its model, case doesn't matter lowX, lowY, lowZ, highX, highY, highZ; or nil The box one mesh of a type's model fills, in the model's own space with its scale applied, in the pose it was loaded in. So for a dynamic standing upright these are studs from its position: the player model's Head is about 3.55 to 5.15 up. nil if the model has no such mesh.
addAnimation(typeID, animationName, startFrame, endFrame, speed, fadeInMS, fadeOutMS) type to attach the animation to; frame range (the model file's animation ticks, which for an FBX are its frame numbers minus 1); playback speed in ticks per ms; fade in/out durations in ms none Adds a named animation clip to a dynamic type, which dynamic:playAnimation plays by name. The first animation added to a type is used as its walk cycle. One named grab plays on a player whenever its client left clicks in game, for everyone, and one named sit loops on a player while they ride in a model vehicle, see Model vehicles. While several play at once, animations added later play over earlier ones, but only on the parts of the model they actually move (a grab only takes over the arm it swings, the legs keep walking). Players' heads also turn to show where their camera looks, if the model has a node named Head. A .dts model needs none of these lines: it registers every sequence it came with under its own name, see DTS models.
raycast(startX, startY, startZ, endX, endY, endZ[, dynamicToIgnore]) ray start/end points; optionally a Dynamic to exclude from the hit test hit object, x, y, z, normalX, normalY, normalZ, distance; or nil Casts a ray through the physics world. Returns the Dynamic, Static, or Brick it hit first, then the world position of the hit, the normal of the surface it hit (pointing out of it), and the distance from the start point. If it hit the ground, which has no object, the hit object is nil and the rest still follow. Returns just nil if it hit nothing. local hit = raycast(...) still works if you only need the object.
addProjectile(typeID, x, y, z, velX, velY, velZ[, tag[, shooter]]) dynamic type ID; position; velocity in studs per second; any string, "" by default, or nil; a Dynamic, or nil Dynamic Fires a dynamic that falls with gravity and is turned every tick so its model's +Y points the way it's going (while faster than 8 studs a second). A .dts model is turned along its -Z instead, which is the +Y forward Torque built it with, see DTS models. It never falls asleep, and is swept along what each physics substep is about to move it before the substep runs, and stopped on the first thing in the way, so it doesn't skip through thin bricks however fast it goes. It passes through shooter, usually the player who fired it, and through every other projectile, so a shotgun's pellets can all leave one spot at once. Clients only draw it where the server has it, it never bumps into their own player. The first time it touches anything that collides, the ground included, ProjectileHit fires with tag and it's removed. Bricks and statics with collision off don't count.

dynamic: methods

Method Arguments Returns Description
dynamic:destroy() none none Removes the dynamic from the world and un-controls it for any client controlling it.
dynamic:getPosition() none x, y, z Current world position.
dynamic:setPosition(x, y, z) position none Teleports the dynamic.
dynamic:getVelocity() none x, y, z Current linear velocity.
dynamic:setVelocity(x, y, z) velocity none Sets linear velocity directly. On a player it reaches their own game as a velocity-only correction: they take the new velocity but keep the position their game has, rather than being moved back to where the server last heard they were, so it can be called every tick (the way the grapple rope swings someone) without rubber-banding. setPosition and setRotation do move them. radiusImpulse pushes players the same way.
dynamic:getAngularVelocity() none x, y, z Current angular velocity.
dynamic:setAngularVelocity(x, y, z) angular velocity none Sets angular velocity directly.
dynamic:setAngularFactor(x, y, z) per-axis multiplier (0 = locked) none Restricts which axes the physics engine is allowed to rotate the object around, e.g. (0,0,0) to stop it tipping over.
dynamic:activate() none none Wakes the physics body up if it was asleep.
dynamic:isActive() none bool Whether the physics body is currently active (not asleep).
dynamic:getGravity() none x, y, z Current per-object gravity vector.
dynamic:setGravity(x, y, z) gravity vector none Overrides gravity for just this object.
dynamic:getFriction() none value Current friction coefficient.
dynamic:setFriction(friction) 0-10, clamped none Sets friction.
dynamic:getRestitution() none value Current restitution (bounciness).
dynamic:setRestitution(restitution) 0-10, clamped none Sets restitution.
dynamic:getRotation() none w, x, y, z Current orientation as a quaternion.
dynamic:setRotation(w, x, y, z) full quaternion none Sets orientation from an explicit quaternion.
dynamic:setRotation(yaw, pitch, roll) 3 args instead of 4 none Alternate overload: sets orientation from Euler angles instead of a quaternion.
dynamic:getMass() none value Current mass.
dynamic:setMassProps(mass, centerX, centerY, centerZ) mass and local center of mass none Sets mass and center of mass together.
dynamic:setMeshColor(meshName, r, g, b, a) mesh name within the model, color none Recolors one mesh of the model and broadcasts the change to clients. Alpha is how much of this color is blended over the mesh's own material, not see-through: use hideMesh to make a mesh disappear.
dynamic:hideMesh(meshName) mesh name within the model none Stops drawing one mesh of the model, shadow and outline included, and broadcasts the change. Anything worn on that mesh with setPart, like a hat on a hidden Head, is hidden with it. Remembered, so a client that joins later sees it hidden too. Logs an error for a name the model has no mesh by.
dynamic:unhideMesh(meshName) mesh name within the model none Draws a mesh hideMesh hid again.
dynamic:getMeshAt(x, y, z) a world position, like one raycast() or client:getCursorItem() gave mesh name, or nil Which mesh of the model a spot is on, for painting the body part someone was sprayed or shot on. The spot is moved into the model's own space and matched against the bounding box each mesh was loaded with, so the nearest mesh wins, the smaller of two boxes wins a tie, and a spot outside the model still gives the mesh it's nearest. Meshes that are never drawn (Collision) and the see-through face plate over a head (Face1) are skipped. Animations aren't taken into account, so a limb the model is playing an animation on is matched where it sits in the pose the model was loaded in. nil for a model with nothing paintable.
dynamic:playAnimation(name[, loop]) the name an addAnimation line gave its type; loop defaults to false none Plays an animation for everyone: once from its start, over the walk cycle and anything else playing, or looped until stopAnimation. Looping ones are remembered, so a client that joins later sees them too, and as many can loop at once as the model has animations. A player's own client plays its grab itself on the click, everything else reaches it from here like everyone else. An item in someone's hand plays through item:playAnimation instead, which its carrier's item bar knows about. Logs an error for a name the type has no animation by.
dynamic:stopAnimation([name]) animation name, or nothing none Stops that looping animation, fading it out over the fade its addAnimation line gave it, or every one looping without a name. One playing once finishes on its own.
dynamic:setMeshDecal(meshName, decalName) mesh name within the model; file name of an image in Assets/faces or Assets/shirts (e.g. "smiley.png" or "Mod-Police.png", up to 64 characters), or "" to remove it none Shows a face or shirt on one mesh, drawn over its color, and broadcasts the change. The image covers the mesh's texture coordinates from 0 to 1, or only the rectangle a decalarea line in the model's .txt gives that mesh (decalarea, the mesh name, then the texture coordinates of the image's top left and bottom right corners, all tab separated), with nothing outside it; the default player's Torso has one covering its front. A model's face plate (a mesh named Face1, or Face without one) is see-through except for the face, so without a face it isn't drawn at all, and it casts no shadow or outline. Clients look the name up in their own Assets/faces folder, then Assets/shirts, so one they don't have isn't shown. A player's own image goes by custom: and a number (see setCustomDecalsAllowed), which can be passed here too, like "custom:12", to put it on something else: clients forget it when that player changes it or leaves, after which it shows as nothing.
dynamic:setPart(slot, partName[, r, g, b, a[, scale]]) slot name, up to 32 characters, "hat" is the one the appearance editor fills; file name of a model descriptor in Assets/brickhead/parts (like "top_hat.txt"), or "" to take off what's in the slot; color 0-1, alpha 0 (the default) leaves the part its own look; size from 0.5 to 1.5 times the descriptor's (default 1) none Wears a model on the dynamic, the way client:applyAppearance puts a player's hat on, and broadcasts the change. The descriptor's attach lines say which mesh it's worn on and how it sits there, so the dynamic's model needs a mesh by that name (Head for the hats), though it doesn't have to be one that's drawn. Clients look the file up in their own parts folder, so one they don't have isn't shown.
dynamic:getPart(slot) slot name partName, r, g, b, a, scale; or nil What's worn in a slot, as setPart takes it, nil for nothing.
dynamic:setHighlight(r, g, b, a, thickness[, client]) color; thickness is how far (in world units) the outline extends past the model's surface; optionally one client none Applies an outline/highlight effect around the whole object, parts it wears (dynamic:setPart) included. Without a client it's sent to everyone and kept, so clients joining later see it too. With a client only they see it, it isn't kept for anyone else, and it replaces what everyone sees on their screen until clearHighlight(client) or another highlight for everyone.
dynamic:clearHighlight([client]) optionally one client none Removes the outline/highlight effect for everyone. With a client, takes back only a highlight sent to them alone, putting back on their screen whatever everyone else sees (which may be a highlight of its own).
dynamic:setNameTag(text, r, g, b) text up to 64 characters, "" for none; color, 0-1 each none Puts floating text over the object for every client, drawn over the world above its collision box, and broadcasts it. Clients don't draw the tag on the object they control, so you never see your own, a tag fades out past 150 world units and is left off past 256, and one is only drawn while the camera has a clear line to the object (its middle or the spot the tag floats at), so a player behind a wall doesn't show a name over it.
dynamic:getNumControllers() none count How many clients currently control this dynamic (usually 0 or 1; 0 means it's a normal server-simulated object, not a player).
dynamic:getControllerIdx(index) 0-based index Client The client controlling this dynamic at that index.
dynamic:snapToCursor(client, xOffset, yOffset, zOffset) client to attach to; view-space offset: x = right, y = up, z = distance in front of the camera none Attaches the dynamic to a client's cursor: every physics tick its position is recomputed from that client's live camera position/direction plus this offset, and its gravity is disabled. Calling this again while already snapped just updates the client/offset.
dynamic:unsnap() none none Detaches from the cursor (if snapped) and restores the gravity it had before snapping. No-op if not snapped.
dynamic:isSnapped() none bool Whether the dynamic is currently snapped to any client's cursor.
dynamic:getSnapClient() none Client or nil The client it's snapped to, or nil if not snapped.
dynamic:playSound(name[, pitch, volume]) sound type name; see Sounds none Plays a sound once for everyone, following the dynamic as it moves.
dynamic:startSoundLoop(name[, pitch, volume]) sound type name; see Sounds loop ID Starts a looping sound that follows the dynamic. It stops by itself when the dynamic is destroyed.
dynamic:setBuoyancy(buoyancy) 0-10, clamped; default 1.3 none How hard water pushes the dynamic up, as a multiple of its weight when it's fully under. 0 sinks (slowed by drag), 1 hangs wherever it is, higher values float with less of it under. Sent to clients too, since they simulate the dynamics they control (players) in water themselves. A swimming player holds their depth while moving, so buoyancy only decides whether they sink or float up while they aren't swimming.
dynamic:getBuoyancy() none number Current buoyancy.
dynamic:setScale(x, y, z) how many times its type's size along each of its model's own axes, 0.2-5 each, clamped none Resizes the dynamic, both how it's drawn and its collision box, and broadcasts the change. Remembered, so a client that joins later sees it at that size too. Its mass stays the same. A dynamic in the world is moved so the bottom of its box stays at the same height, so growing doesn't push it into the floor. Logs an error and does nothing for a number that isn't finite. See Resized dynamics for what else follows the size.
dynamic:setScale(size) one number, 0.2-5, clamped none Alternate overload: the same size along all three axes.
dynamic:getScale() none x, y, z The size setScale last gave it, after clamping. 1, 1, 1 for its type's own size.
dynamic:isItem() none bool Whether it's an item, which has the item: methods below too.
dynamic:isProjectile() none bool Whether addProjectile made it.
dynamic:setBotInput(dirX, dirY, dirZ[, forward, backward, left, right, jump, jet, crawl]) where it's looking, which needn't be normalized; the movement keys, each false by default none Holds a set of movement keys down on a dynamic nobody is playing, so the server walks it with the same code a client's player gets: it walks at the same speed, steps onto ledges, jumps, jets, swims, lies down, plays the walk cycle, and turns to face where it's going. The keys stay held until this is called again, so a script only has to call it when something changes, and the look direction is where its head turns and where anything aiming from it should aim. Logs an error and does nothing for a dynamic a client controls, since that client's own game is already walking it. See Bots.
dynamic:clearBotInput() none none Stops walking it and stops its walk cycle, leaving it an ordinary object again. Does nothing to one that wasn't being walked. A bot's keys are also forgotten when it's destroyed.
dynamic:setHeldItem([item]) an item on the ground, or nothing/nil to empty its hand the item it was holding, or nil Puts one item in the hand of a dynamic nobody is playing, the way client:setHandItem puts one in a player's: it comes out of the physics world, goes along wherever the dynamic walks, and everyone draws it in that model's Right_Hand (or in front of its middle for a model without one), since what draws a held item goes by the dynamic holding it rather than by whose it is. Its animations (item:playAnimation) work as they do in anyone's hands. Nothing, or nil, drops what it was holding back into the world where it stands, which also happens by itself when its holder is destroyed. There is no inventory behind it: a bot has one hand and no slots, so this replaces whatever was in it. Logs an error and does nothing for a dynamic a client controls (use client:addItem or client:setHandItem), for an item somebody else is already holding, or for a display item.
dynamic:getHeldItem() none Item or nil The item setHeldItem put in its hand.

Bots

A bot is an ordinary dynamic that nobody is playing, walked by dynamic:setBotInput instead of by a client, with the same movement code a player's dynamic gets. What that gives it and what it doesn't is in Engine.md, along with System_Bots, the add-on that gives bots health and weapons.

--Walk a brickhead toward a spot, and shoot from its chest at whatever it's facing
local bot = createDynamic(brickhead, x, y, z)
bot:setAngularFactor(0, 0, 0)     --like a player, nothing tips it over

local dx, dz = targetX - x, targetZ - z
local length = math.sqrt(dx * dx + dz * dz)
bot:setBotInput(dx / length, 0, dz / length, true)

setBotInput holds keys down rather than taking a step, so call it again when the bot should turn or stop, not every tick. jump is held too: it jumps whenever it's on the ground with that set, so for a single jump set it for one call and clear it on the next.

Finding a path

Function Arguments Returns Description
findPath(bot, target[, options]) the dynamic that will walk, the dynamic to get to, optional table list of {x, y, z} tables, or nil; whether it gets there A way for bot to walk to target around bricks. Each point is where to head next, at the height of its feet there, with the start left out and the last point where target is standing. Walk to each in turn with setBotInput. A point it has to jump to from the one before (only with the jump option) also has jump = true. An empty list means it's already there, nil means it can't walk there (or target isn't anywhere anything can stand, like floating in the air or inside a wall). The second value is true when the path reaches target and false when, with partial, it only gets as close as it could.
findPath(bot, x, y, z[, options]) the dynamic that will walk, a spot, optional table same The same, to a spot. y is the height of the feet there, and the highest floor no more than a plate above it is the one used.

The options table's fields are all optional:

Field Default What it does
maxNodes 10000 How many spots the search may finish looking at before it gives up. This caps how long one call can take, see below.
maxDrop 10 Farthest it will drop off a ledge, in studs.
partial false When there's no way there (or no way within maxNodes), return a path to the place it reached that's closest to the goal instead of nil. Good for a bot chasing someone it can't reach yet.
jump false Also jump: up onto a ledge taller than a step, and across a gap to a floor no more than a step higher. Each jump counts as 3 studs more than the ground it covers, so of two ways about as long the one on foot wins.
jumpHeight 7.6 With jump, the tallest ledge it jumps onto, in studs. The same as a player: a jump rises 6.2, and walking into the wall as it does gets onto a ledge up to 7.6 (19 plates), since stepping up lifts the last few plates.
jumpDistance 8 With jump, the farthest it jumps across a gap, in studs from the middle of where it takes off to the middle of where it lands, straight or diagonally: a 7 stud gap. A bot takes off with its middle over the floor, so that's a stud or so short of what a player running off the very edge clears.

How it sees the world (bricks only, each as its whole box, walking and dropping, and jumping only when asked) and what a call costs are in Engine.md. In short, a call is worked out on the server's tick, so work out a new path now and again, not every tick for every bot.

On a server with hosting/threadedpathfinding on, paths can also be worked out on other threads while the bricks are locked, see Threaded pathfinding:

Function Arguments Returns Description
requestPath(bot, target[, options]) or requestPath(bot, x, y, z[, options]) the same as findPath request ID The same search as findPath, done on the pathfinding threads while the bricks are locked, and on the spot otherwise, when its result is ready at once. Where the bot and target are is taken now. Works whether or not the server can lock its bricks.
pathResult(id) a request ID false, or true and what findPath would have returned false while it's still being worked out. Once it's done, true, then the path (or nil) and whether it gets there. That forgets the request, so ask for it once. An ID that's unknown, cancelled, or already taken is done with no path.
cancelPath(id) a request ID none Throws a request away, whether it's waiting, being worked out, or done, like one for a bot that's gone. A result nobody takes is kept until the server shuts down, so cancel what you won't take.
canLockBricks() none bool Whether this server was started with hosting/threadedpathfinding on.
setBricksLocked(locked) bool bool, whether they're locked now Locks or unlocks the bricks. While locked, requestPath goes to the threads and no brick can be added, removed, or start or stop colliding, by players or Lua. Locking with hosting/threadedpathfinding off logs an error and returns false. Unlocking waits for every request still being worked out. The same as an admin's /lockBricks [on|off], which only exists on such a server.
getBricksLocked() none bool Whether they're locked. Always false with hosting/threadedpathfinding off.
--Every tick, on a server where setBricksLocked(true) was called for the round
if bot.request == nil then
	bot.request = requestPath(bot, target, { partial = true })
else
	local done, path, reached = pathResult(bot.request)
	if done then
		bot.request = nil
		bot.path, bot.step = path, 1
	end
end
--Walk a bot to a player, around whatever's in the way
function chase(bot, target)
	bot.path = findPath(bot, target, { partial = true })
	bot.step = 1
end

--Every tick
function walkPath(bot)
	local point = bot.path and bot.path[bot.step]
	if not point then
		bot:setBotInput(1, 0, 0, false)
		return
	end
	local x, y, z = bot:getPosition()
	local dx, dz = point.x - x, point.z - z
	local distance = math.sqrt(dx * dx + dz * dz)
	if distance < 0.4 then
		bot.step = bot.step + 1
	else
		bot:setBotInput(dx / distance, 0, dz / distance, true)
	end
end

Add-ons

Everything a game is made of lives in an add-on: a folder in Add-ons with a server.lua in it. How they're found, turned on and off, unzipped, and which ones the game comes with is in Engine.md.

Function Arguments Description
getAddOns() none The names of every add-on this server has on, so a script can go through them rather than naming each by hand. The ones list.txt enables come first, in the order it loads them, then any folder the list says nothing about, like a print pack, which are always on. A brick pack has no server.lua to offer its own files, so this is how a start script reaches them.
requireAddOn(name) add-on folder name Loads that add-on's server.lua now if it hasn't been loaded yet, for one that can't work without another. The same as Torque's ForceRequiredAddOn: it loads whether or not list.txt has it enabled, since whatever asked for it needs it. Loading twice is impossible, and two add-ons that require each other each load once. Returns true if the add-on is loaded by the time it returns. The game also reads these calls out of an add-on's .lua files before anything runs (comments left out, and only a name written in quotes), so turning an add-on on turns on what it requires too, see Requirements.
--At the top of Weapon_Package_Tier1's server.lua: its guns fire the plain Gun's bullet
requireAddOn("Weapon_Gun")

Sending add-on files to clients

Someone joining a server hasn't usually got the add-ons that server runs, so the models, textures and sounds those add-ons name aren't on their computer to load. A server can offer them the files it uses:

Function Arguments Description
addServerFile(path) file path Offers one file to clients as they join. Returns true if it's on offer.
addServerFolder(folder[, extensions]) folder path; optionally a table of extensions like {"blb", "txt"} The same for every file in a folder, and the folders inside it, that clients take. Anything else in there, its scripts above all, is passed over. Given a table of extensions it keeps to those, for a server that wants a brick pack's shapes and names without its 28 icons per pack. Returns how many files of it are on offer.
--Everything in this add-on a client could need, which is its models, textures and sounds
addServerFolder("Add-ons/Weapon_Gun")

--A brick pack is the same call: its bricks.txt, .blb shapes and .png icons, never its server.cs
addServerFolder("Add-ons/Brick_SmallRampsPack")

--Every brick pack at once, shapes and names but not the icons, which only the brick selector wants
for _, name in ipairs(getAddOns()) do
	if name:sub(1, 6) == "Brick_" then
		addServerFolder("Add-ons/" .. name, {"blb", "txt"})
	end
end

A server offers at most 4096 files, and a brick pack is around 60 of them with its icons or half that without, so a server running dozens of packs wants the extensions argument.

What happens as someone joins (the file list, the download window, TCP on the game port, where downloads go, and which kinds of file are ever sent) is in Engine.md.


Items

Items are tools like the hammer: dynamics that players can carry in their inventory. On the ground an item is an ordinary dynamic. It falls, collides, and floats, every dynamic: method works on it, and raycast() and client:getCursorItem() can hit it. Item tables are Dynamic tables (type is 1) that have the item: methods below as well, so check with dynamic:isItem().

Each client can carry 5 items, in slots 0 to 4, plus one client:setHandItem put in their hand outside those slots, which is held instead of whatever their item bar has picked and which their item bar can't reach. While an item is carried its body is out of the physics world. It doesn't collide, fall, or float, and setPosition, setRotation, setVelocity, setAngularVelocity, activate, and snapToCursor do nothing. getPosition gives the position of the player carrying it. Settings like gravity, friction, and buoyancy are kept for when it's back on the ground. Items a leaving client still carries go back into the world where they were, after ClientLeave listeners run.

A dynamic nobody plays can hold one item too, with dynamic:setHeldItem: no inventory, no slots and no item bar, just the one in its hand until something takes it back. Everyone draws it the same way they draw a player's, in the holder's Right_Hand, because what an item is drawn on is the dynamic holding it rather than the client behind it. item:getHolder is about clients, so it gives nil for one of these; the dynamic's own getHeldItem is what finds it.

The keys players use with items, and what the tools System_Inventory hands out do, are in Engine.md.

Global functions

Function Arguments Returns Description
newItemType(scriptName, modelFilePath, scaleX, scaleY, scaleZ[, uiName[, iconPath]]) same as newDynamicType; the name shown in the item bar, "" by default; an image for its slot, relative to the game folder, or "" (the default) for none typeID Registers a kind of item. A type with no uiName is left out of the wrench dialog's list of items a brick can offer, and its slot in the item bar shows its script name. The type ID works anywhere a dynamic type's does, like addAnimation and getDynamicType. Call it at startup, before anyone joins. An icon that isn't a file in the game folder logs an error and the type gets none. Clients load the icon from their own game folder. A model with no Collision mesh collides as a box around the whole model. A .dts model works here too, see DTS models.
setItemHand(typeID, gripX, gripY, gripZ, pitch, yaw, roll) item type ID; the point on the model that goes in the hand, in world units after scaling; degrees around the x, y, and z axes none How items of a type sit in a hand. Unturned, the model's +Y points up out of the hand and its -Z the way its holder faces, and a negative pitch leans its top forward. Call it at startup, before anyone joins. By default the model's origin is in the hand, unturned.
createItem(typeID, x, y, z) item type ID from newItemType; position Item Spawns an item on the ground. Logs an error for a type that isn't an item type.
getNumItems() none count How many items exist, carried or not.
getItemIdx(index) 0-based index Item The item at that position among all items.

item: methods

Along with every dynamic: method.

Method Arguments Returns Description
item:isHeld() none bool Whether it's in someone's inventory, or in the hand of a dynamic dynamic:setHeldItem gave it to.
item:getHolder() none Client or nil The client carrying it, nil for one held by a dynamic nobody plays.
item:getSlot() none slot or nil Which of its carrier's slots it's in, 0-4.
item:isEquipped() none bool Whether it's in its carrier's hand: their items are out with its slot picked.
item:playAnimation(name[, loop]) "swing", "kick", or the name of an animation addAnimation gave its type; loop defaults to false none Plays the animation for everyone, once or over and over. Every item can "swing", tipping forward around its grip until its top points 90 degrees further toward the ground and back, a bit over a fifth of a second each time, and "kick", the jolt of a gun going off: shoved 0.3 studs back toward its holder with its barrel tipped 7 degrees up in under 20 ms, then easing home, 90 ms in all, for a gun whose model has no fire animation of its own. A kick played again starts over, so an automatic weapon kicks once a shot. Games from before the kick ignore it. Only one animation loops at a time, starting a loop replaces the last. Logs an error for an animation it doesn't have.
item:stopAnimation([name]) animation name, or nothing none Stops the looping animation if it's the one named, or whatever loops without a name. A swing finishes the one it's partway through.
item:getItemName() none string Its type's name in the item bar, like "Hammer", or "" for a type that was given none.
item:getTypeName() none string Its type's script name, like "hammer".
item:isDisplay() none bool Whether it's a display item: the copy floating over a brick wrenched to offer an item, see Wrench dialog. It spins in place (its body too, so dynamic:getRotation turns with it and rays hit it where it's drawn), never falls or moves, collides with nothing (rays and clicks still hit it, and System_Inventory outlines it on a player's screen while their crosshair is on it within 10 studs), and client:addItem refuses it. radiusImpulse and water leave it alone.
item:getDisplayBrick() none Brick or nil The brick a display item floats over.

Click prediction

A click normally has to reach the server before anything happens, so a shot is heard and seen a round trip after the button goes down. client:setClickAction gets ahead of that: it tells one client's game what their next click with an item will look like, and their game plays it the moment they click.

Only the look of it is predicted. The shot, what it hits, and the ammo are all still worked out by the server, so the worst a wrong guess can do is show a flash that shouldn't have happened.

Because the server says what the next click does, the client never needs to know any rules: an empty gun is simply sent the dry click track instead of the firing one.

Function Arguments Returns Description
client:setClickAction(item, action) the item it applies to; a table, see below none What that client's game plays the instant they click while holding that item. Replaces whatever was set before.
client:setClickAction() none none Stop predicting anything, for a client holding something that isn't a weapon.

The action table:

Field Default Description
steps required The things that happen, in a list.
repeatMS 0 While the button stays down, the track may play again this often without asking the server, for an automatic weapon. 0 plays once per click.
repeatLimit 0 How many more times the track may play before you send another, which is what keeps a client from showing more shots than the magazine holds. It covers an automatic weapon carrying on while held and someone clicking faster than the round trip. Send a fresh action after every shot; the client subtracts the plays it has made that you haven't answered for yet, so a refresh never hands back rounds already spent.

Each step happens at milliseconds after the click, and is one of a sound, an animation, an emitter, or a light, by which of those fields it has:

Field Description
at Milliseconds after the click, 0 for right away.
sound A sound type name, with optional pitch and volume. It follows the item.
animation The name of one of the item model's animations, or "kick", played once.
emitter An emitter type name. Ejects for forMS, with an optional scale (0.2-5, default 1) that sizes its effect like emitter:setScale.
light {r, g, b}, with brightness, coronaWidth and forMS. Lights and casts shadows like any other light, or with a negative brightness (-500 or more) is a darkness like one.
forMS How long an emitter or light lasts.
offset Where an emitter or light sits in the item's own space, so it stays on the end of the barrel as the item moves. This is the model's own muzzle point, not an offset from the player.
client:setClickAction(pistol, {
	repeatMS = 96, repeatLimit = 34,
	steps = {
		{ at = 0,   sound = "PistolFire" },
		{ at = 0,   animation = "fire" },
		{ at = 0,   emitter = "MuzzleFlash", forMS = 60, offset = {0, 0.6, -2.2} },
		{ at = 0,   light = {1, 0.9, 0.5}, brightness = 35, coronaWidth = 0.35, forMS = 60, offset = {0, 0.6, -2.2} },
		{ at = 115, sound = "PistolClick" }
	}
})

Anything the server plays itself when the shot happens is seen by the shooter as well as their predicted copy, so a sound is best sent to everyone else with client:playSound rather than from the item, and a light the server makes is worth turning down. There's no way yet to broadcast an emitter or an animation to everyone except one client.

The client is only told about the item it's holding, so a predicted action stops mattering as soon as they put it away. An action is dropped if the item it names isn't what they click with. Only an item picked from the item bar counts as what they click with: one put in the hand by client:setHandItem plays nothing ahead of the server, since their game doesn't know it's there.


Statics

Statics are non-moving objects that still have a mesh and physics presence (walls, floor tiles, buttons, etc). They reuse the same "dynamic type" definitions (model + scale) as dynamics.

Global functions

Function Arguments Returns Description
createStatic(typeID, x, y, z) type ID (from newDynamicType/getDynamicType); position Static Spawns a static object of the given type.
getStaticId(netId) net ID Static Looks up a static by its net ID.
getStaticIdx(index) 0-based index Static Looks up a static by its position in the internal list.
getNumStatics() none count How many statics currently exist.

static: methods

Method Arguments Returns Description
static:destroy() none none Removes the static from the world.
static:getPosition() none x, y, z Current position.
static:getRotation() none w, x, y, z Current orientation as a quaternion.
static:getFriction() none value Current friction coefficient.
static:setFriction(friction) 0-10, clamped none Sets friction.
static:getRestitution() none value Current restitution.
static:setRestitution(restitution) 0-10, clamped none Sets restitution.
static:setMeshColor(meshName, r, g, b, a) mesh name, color none Recolors one mesh and broadcasts the change.
static:setHighlight(r, g, b, a, thickness[, client]) color; thickness is how far (in world units) the outline extends past the model's surface; optionally one client none Applies an outline/highlight effect around the whole object. Without a client it's sent to everyone and kept, so clients joining later see it too. With a client only they see it, it isn't kept for anyone else, and it replaces what everyone sees on their screen until clearHighlight(client) or another highlight for everyone.
static:clearHighlight([client]) optionally one client none Removes the outline/highlight effect for everyone. With a client, takes back only a highlight sent to them alone, putting back on their screen whatever everyone else sees (which may be a highlight of its own).
static:setColliding(bool) true/false none Enables or disables collision for the object without removing it.
static:setHidden(bool) true/false none Shows or hides the object client-side.

Lights

Lights that shine from a spot in the world, either in every direction or, as spotlights, in a cone (see light:setConeAngle). They have no model or collision. Clients light everything around them (bricks, models, grass, and the water surface, which also shows glints of them on its waves) with inverse square falloff, draw a glowing corona where they are, and give the lights nearest the camera shadows. How many get shadows is each player's graphics/pointshadows setting (0 to 8, default 4). A light with shadows turned off (light:setShadows(false), or No Shadows in the wrench dialog) never takes one, so it shines through walls and the slot goes to the next nearest light. Up to 32 lights light the view at once, the nearest ones win, or only 8 for a player with graphics/extrapointlights (Extra non-shadow lights) off. A brick a light is inside doesn't cast that light's shadows, so lights in the middle of bricks (like the ones on bricks, see brick:setLight) shine out of them. A light with a negative brightness is a darkness instead: it takes ambient light away out to a radius of its brightness's size, see Negative lights.

A light reaches until it's too dim to see: roughly sqrt(brightness * 50) studs for a light whose brightest color channel is 1, up to 500 (light:getRange() gives the exact value). As a guide, brightness 50 at 5 studs is about an eighth of noon sunlight, so a lamp is around 20-100 and a floodlight a few thousand.

Players' flashlights (see client:setFlashlightEnabled) are lights too: an 80 degree spotlight with brightness 150 and a 0.8 stud corona, which shows up in getNumLights and getLightIdx while it's on. Each client shines it from just past the holding player's Right_Hand mesh as it's drawn (or in front of their eyes if their model has no such mesh) toward where that player looks. Like any spotlight's, its corona only shows to people inside the beam, so its owner doesn't see their own. light:getPosition() gives the player's position, and the server keeps pointing it with setDirection. light:setPosition takes it out of the player's hand, and light:destroy() switches it off without the LightOff sound. A vehicle's headlight (see Vehicles) is a light the same way while it's on, kept in the vehicle's own space like the lights carried over from its bricks; light:destroy() switches it off too, and the vehicle makes a new one when it's switched on again.

Global functions

Function Arguments Returns Description
createLight(x, y, z, r, g, b, brightness, flicker, coronaWidth) position; color 0-1 per channel; brightness -500 to 100000 (clamped), negative makes a darkness of that radius; flicker in world units (0-16); coronaWidth in world units (0-256, 0 for no corona) Light Places a point light. flicker is how far the light wanders around its position: every 40-160 ms it picks a new random spot within that distance and glides there, easing in and out, which makes its lighting and shadows waver like a flame. Each client picks its own spots. A flickering light with shadows redraws them every frame.
getLightId(netId) net ID Light Looks up a light by its net ID.
getLightIdx(index) 0-based index Light Looks up a light by its position in the internal list.
getNumLights() none count How many lights currently exist.

light: methods

All of these use the strict argument count check, and setters log an error and do nothing if an argument isn't a finite number.

Method Arguments Returns Description
light:destroy() none none Removes the light.
light:getPosition() none x, y, z Position, without flicker.
light:setPosition(x, y, z) position none Moves the light.
light:getColor() none r, g, b Color.
light:setColor(r, g, b) 0-1, clamped none Sets the color.
light:getBrightness() none number Brightness.
light:setBrightness(brightness) -500 to 100000, clamped none Sets the brightness. 0 turns the light and its corona off. Negative makes it a darkness reaching that many studs, with no corona, see Negative lights.
light:getFlicker() none number Flicker distance.
light:setFlicker(distance) world units, 0-16, clamped none How far the light wanders around its position.
light:getBlink() none speed, strength Blink cycle time and strength. A new light has speed 0 and strength 1.
light:setBlink(speed, strength) speed seconds, 0-60, clamped; strength 0-1, clamped none Makes the light and its corona dim and brighten again smoothly. speed is how long one whole cycle takes, 0 for no blinking. strength is how much of its brightness it loses at the dimmest point, halfway through each cycle: 1 goes fully dark, 0.5 drops to half. How far it reaches (getRange) doesn't change. Each client runs the cycle on its own clock, so players see it at different points, but lights with the same speed stay in step for any one player.
light:getCoronaWidth() none number Corona width.
light:setCoronaWidth(width) world units, 0-256, clamped none Width of the glow drawn at the light, 0 for none.
light:getRange() none number How far the light reaches, from its brightness and color. A darkness's is its brightness's size, or 0 if its color is black.
light:getDirection() none x, y, z Which way a spotlight points, normalized, not counting spin. Defaults to straight down.
light:setDirection(x, y, z) any length but zero none Points the spotlight. Also resets how far it has spun. Does nothing for a light with a cone angle of 0.
light:getConeAngle() none degrees Full width of the beam, 0 for a light that shines every way.
light:setConeAngle(degrees) 0, or 1-179 (clamped) none Turns the light into a spotlight with a beam this many degrees wide, softening toward its edge. 0 (the default) makes it shine every way again. A spotlight's corona only shows from inside its beam, and its shadows only draw the directions the beam can reach, so they cost less.
light:getSpin() none degrees per second How fast the direction turns.
light:setSpin(degreesPerSecond) -3600 to 3600, clamped none Turns the spotlight's direction around the vertical axis, like a lighthouse. Negative spins the other way. Each client spins it on its own, so players can see it at slightly different angles. A spinning spotlight redraws its shadows every frame.
light:getShadows() none bool Whether it casts shadows. A new light does.
light:setShadows(shadows) true or false none false keeps it from ever taking one of the shadow slots, so it shines (or a darkness darkens) through walls, and the slot and the cost of drawing its shadows go to other lights.
light:attachToDynamic(dynamic[, meshName][, offsetX, offsetY, offsetZ]) dynamic; name of one of its model's meshes; studs along the dynamic's (or mesh's) own x, y, and z axes none Follows the dynamic, or the middle of that mesh as it animates, the way emitter:attachToDynamic does, and a spotlight's direction turns with it. Each client puts it on the dynamic as that client draws it, so on a held item it's where the item is drawn: in its holder's hand, or in front of the camera for the holder's own game in first person. That's how System_Weapons hangs a muzzle light on the end of the barrel. light:getPosition gives the server's idea of it, which for a held item is where the item was last dropped. setPosition takes it off again. It's destroyed along with the dynamic, and calls on it after that log an error.

Emitters

Emitters eject particles: small camera-facing images that move, spin, and change color and size over their short lives, like Blockland's. A particle type says what one particle looks like and how it moves, an emitter type says how particles are ejected, and an emitter is one of those placed in the world, at a spot or following a dynamic. Only the types and emitters are sent to clients, each client ejects, moves, and draws the particles on its own, so two players never see exactly the same ones.

Types are made by name, and adding one with a name that's taken replaces it: clients already in the game get the change right away and existing emitters of that type carry on with it. The server puts a playerJetEmitter under each foot of a jetting player (see client:setJetsEnabled), and makes a playerBubbleEmitter wherever a dynamic splashes into the water, if a type by that name exists. The types the game comes with are the System_Emitters add-on's, see Engine.md.

Units are Blockland's: angles in degrees, speeds in studs per second, times in milliseconds. Ejection directions are relative to the emitter: world space for one at a spot or on a brick, and turning with the dynamic (or the mesh) for one that follows a dynamic. Gravity is always world space. Each client keeps at most graphics/maxparticles particles alive (0 to 100000, default 20000), and emitters much further from its camera than the end of the fog don't eject any. Particles fade into the fog and show in water reflections. Particle types with lit set are lit and shadowed like smoke or bubbles would be, the rest keep their colors day and night like fire and sparks.

Particle type fields

The table passed to addParticleType. Anything left out keeps its default. Vectors can be tables like {1, 0.5, 0, 1} or, like the old game's scripts, strings like "1 0.5 0 1". An unknown field name or a value of the wrong kind logs an error and the type isn't added. Numbers out of range are clamped.

Field Default Description
texture required Image path relative to the game folder, like "Assets/particles/cloud.png". Clients load it from their own copy, so it has to exist there too.
color0 - color3 {1, 1, 1, 1} RGBA, 0-1, multiplied by the texture.
size0 - size3 1 Width in studs, 0-256.
time0 - time3 0, 0.33, 0.66, 1 When over a particle's life (0 to 1) each color and size key applies, blended in between. Kept in order.
drag 0 Fraction of its velocity a particle loses per second, one number or {x, y, z}, 0-1000.
gravity {0, 0, 0} Acceleration in studs per second squared. {0, -20, 0} falls, {0, 15, 0} rises like smoke.
inheritedVelFactor 0 How much of the velocity of the dynamic the emitter follows particles start with.
lifetimeMS 1000 How long each particle lives, 1-60000.
lifetimeVarianceMS 0 Each particle lives up to this much longer or shorter, kept under lifetimeMS.
spinSpeed 0 Degrees per second the image turns.
useInvAlpha false true blends by alpha, for smoke and anything that should cover what's behind it. false adds its color on top, for glows, sparks, and fire.
needsSorting false Draws this type's particles back to front each frame. Alpha blended particles that overlap need it to look right.
lit false Lights the particle like a surface of its color would be: by the sun or moon, ambient light, and point lights, darker in sun and point light shadows. For smoke, dust, and bubbles. Leave it off for anything that glows, like fire, sparks, and jets. The whole particle gets the light at its middle, and shadows don't pick up colors from transparent bricks.

Emitter type fields

The table passed to addEmitterType.

Field Default Description
particles required Names of 1-16 particle types, separated by spaces ("a b") or as a table ({"a", "b"}). Each particle is one of them picked at random. They have to be added first.
ejectionPeriodMS 100 Milliseconds between particles, at least 1. Each frame an emitter ejects every particle it owes, spread along the way it moved.
periodVarianceMS 0 Each gap is up to this much longer or shorter, kept under ejectionPeriodMS.
ejectionVelocity 2 Studs per second away from the emitter.
velocityVariance 1 Up to this much faster or slower, at most ejectionVelocity.
ejectionOffset 0 How far out from the emitter, along the way they go, particles start.
thetaMin, thetaMax 0, 90 Degrees down from the emitter's up each particle goes out at, picked between these (0-180). 0, 0 shoots straight up, 90, 90 flat outward, 180, 180 straight down.
phiReferenceVel 0 Degrees per second the direction particles go out in turns around the vertical, for spirals.
phiVariance 360 Degrees around the vertical past that direction a particle can go, 360 for every way.
lifetimeMS 0 Emitters of this type remove themselves this long after they're made, for one-off bursts. 0 lasts until removed. Emitters on a brick ignore this and last as long as the brick, though their particles still use their own lifetimeMS.
useEmitterColors false Blockland's flag of the same name: an emitter of this type placed on a brick starts tinted with that brick's color, as if emitter:setColor had been called with it, so its particles come out the color of the brick rather than their own. Particle colors are multiplied by the tint, so a type meant to wear a brick's color should have white particles. It's applied once, when the emitter is made, so repainting the brick afterwards doesn't change it, and neither this nor a later emitter:setColor affects particles already out. An emitter that isn't on a brick is unaffected.
uiName "" Blockland's name for it: loadBlocklandSave puts an emitter of this type on bricks whose saved emitter has this name, ignoring case, the last added type winning if several share it, unless addBlocklandEmitter gave that name a type.

Global functions

Function Arguments Returns Description
addParticleType(name, table) name of 1-255 characters; fields above none Adds a particle type, or replaces the one with that name. Does nothing if its texture doesn't exist on the server.
addEmitterType(name, table) name of 1-255 characters; fields above none Adds an emitter type, or replaces the one with that name.
getParticleTable(name) particle type name table A particle type's fields, with vectors as tables.
getEmitterTable(name) emitter type name table An emitter type's fields, with particles as a string of names.
addEmitter(typeName[, x, y, z]) emitter type name; position, default 0, 0, 0 Emitter Places an emitter.
getEmitterId(netId) net ID Emitter Looks up an emitter by its net ID.
getEmitterIdx(index) 0-based index Emitter Looks up an emitter by its position in the internal list.
getNumEmitters() none count How many emitters currently exist.

emitter: methods

These use the strict argument count check.

Method Arguments Returns Description
emitter:destroy() none none Removes the emitter. Particles it already ejected live out their lifetimes. emitter:remove() does the same, it's the old game's name.
emitter:getPosition() none x, y, z Where it is, or where the dynamic it follows is.
emitter:setPosition(x, y, z) position none Moves it there, no longer following a dynamic or on a brick.
emitter:getTypeName() none string Its emitter type's name.
emitter:setType(typeName) emitter type name none Switches it to another emitter type.
emitter:attachToDynamic(dynamic[, meshName][, offsetX, offsetY, offsetZ]) dynamic; name of one of its model's meshes; studs along the dynamic's (or mesh's) own x, y, and z axes none Follows the dynamic, or the middle of that mesh as it animates, ejecting particles turned the way the dynamic (or mesh) is turned. An offset moves it that far from the middle, turning with it, like to the end of a gun's barrel. Removed along with the dynamic.
emitter:attachToBrick(brick) brick, or nil none Moves it to the middle of the brick, and it's removed along with the brick instead of after its type's lifetimeMS. nil leaves it where it is, no longer on or following anything.
emitter:attachToVehicle(vehicle[, offsetX, offsetY, offsetZ]) Vehicle; studs along the vehicle body's own x, y, and z axes none Goes along with the vehicle, at a spot in its body's space so it turns with it, like the wingtips of a plane. Removed along with the vehicle rather than after its type's lifetimeMS, like one on a brick.
emitter:setColor(r, g, b[, a]) 0-1, clamped; a defaults to 1 none Multiplies its particles' colors and opacity by this, white by default. Only sent to clients if it changed, so it's cheap to call often. Particles already out keep the color they left with.
emitter:getColor() none r, g, b, a Its color.
emitter:setScale(size) 0.2-5, clamped; default 1 none Makes its whole effect that many times its type's size: particles are that much bigger, start that much further out, fly that much faster, and fall that much harder, so they trace the same shapes at that size over the same lifetimes. How often it ejects stays the same, and so does the velocity particles take from what it follows. Only sent to clients if it changed. Logs an error for a number that isn't finite.
emitter:getScale() none number Its scale, 1 unless setScale changed it.
emitter:aimWith(dynamic, range) / emitter:aimWith(nil) a dynamic, usually a player; how far its aim reaches in studs, 0-1000 none Sends particles toward whatever the dynamic looks at, up to range studs from its eyes (its client's crosshair for a player's own game, where a third person camera reaches that much further), and they only last until they get there. The type's thetaMin and thetaMax spread particles around that direction instead of around up. Other clients use the way the player's head turns. nil ejects normally again.

Ropes

A rope is tied between two things and keeps them from getting further apart than it is long. It hangs slack until it's pulled out to its length, then it holds, and it never pushes. Each end is tied to a dynamic or a vehicle (a spot on it, which goes along with it), a brick (a spot in the world, the rope is removed along with the brick), or just a spot in the world. A rope is also removed along with a dynamic or vehicle it's tied to.

The pull is one physics constraint that the server and every client each put in their own physics world, and the sagging look is worked out by each client, see Engine.md. Nothing is sent but what the rope is tied to, its length, and how it's drawn, and only when one of those changes.

Bends give a rope more than one straight section: fixed spots in the world it runs over between its ends, like the edge of a ledge or a pulley. Its length is measured along them, each end is pulled toward the bend nearest it, and lengthening one side shortens the other, so a weight hung over a bend can be hauled up from the other side.

Global functions

Function Arguments Returns Description
createRope(endA, endB[, length[, links]]) each end a Dynamic, Vehicle, or Brick (tied at its middle) or a table {x, y, z} (a spot in the world); studs, nil or 0 for as far apart as the ends are right now; how many sections it's drawn with, 1-50, default 15 Rope, or nil Ties a rope. Use rope:setEnd afterward to tie an end somewhere other than the middle of something.
getRopeId(id) net ID Rope or nil Looks up a rope by net ID. nil without an error if there's none, which is how a script finds out a rope went away along with what it was tied to.
getRopeIdx(index) 0-based index Rope Looks up a rope by its position in the internal list.
getNumRopes() none integer Number of ropes.

The old game made ropes from the dynamic they were tied to, and those still work, on items too:

Method Arguments Returns Description
dynamic:attachByRope(otherDynamic[, links]) a Dynamic; 1-50, default 15 Rope or nil A rope between the middles of the two, as long as they are apart right now. The old game returned nothing and also took where in the world each end started.
dynamic:attachByRopeBrick(brick[, links]) a Brick; 1-50, default 15 Rope or nil Same, to the middle of a brick.
dynamic:clearRopes() none integer Removes every rope tied to the dynamic, and returns how many there were.

rope: methods

which is 1 for the rope's first end (endA) and 2 for its second.

Method Arguments Returns Description
rope:destroy() none none Removes it.
rope:getLength() / rope:setLength(studs) 0-2000 studs / none As far apart along the rope as its ends can get. Shortening a tight rope hauls its ends together, over about a fifth of a second rather than at once.
rope:getSpan() none studs How far apart its ends are right now, along its bends. Less than its length while it hangs slack, and a hair over while something heavy swings on it.
rope:getEnd(which) 1 or 2 x, y, z Where that end is tied right now, in the world.
rope:setEnd(which, target[, x, y, z]) 1 or 2; a Dynamic, Vehicle, Brick, or {x, y, z} table; a spot none Ties that end to something else. For a dynamic or vehicle x, y, z is how far from the middle of its body along its own axes, so it turns with it (default its middle). For a brick it's a spot in the world (default the brick's middle).
rope:drawOn(which, dynamic[, x, y, z]) / rope:drawOn(which, nil) 1 or 2; a Dynamic; how far from its middle along its own axes none Draws that end on another dynamic than the one it pulls on, wherever each client draws that dynamic. For an item in someone's hand, which has no body in the world to pull on: the Grapple Rope holds a player by their body and is drawn from the tool's muzzlePoint. nil draws it where it's tied again. The slack it's drawn with is still the slack it really has.
rope:getBends() / rope:setBends({{x, y, z}, ...}) up to 8 spots in the world, in order from the first end; {} for none table of {x, y, z} / none The fixed spots it runs over, see above.
rope:getLinks() / rope:setLinks(links) 1-50 integer / none How many sections it's drawn with, the more there are the rounder it hangs. Only the look, and at least one per straight section.
rope:getWidth() / rope:setWidth(studs) 0.01-10, default 0.15 studs / none How thick it's drawn.
rope:getColor() / rope:setColor(r, g, b) each 0-1 r, g, b / none Its color, hemp brown by default. It's lit and shadowed by the sun and point lights like a lit particle, and doesn't cast a shadow.

Bricks

Bricks on a grid of 1 stud (1 world unit) horizontally by 1 plate (0.4 world units) vertically. Positions are a brick's min corner in whole studs/plates, not its center. Bricks can never overlap. A table for a brick that has since been removed stays valid Lua, but its methods log an error and do nothing.

Besides basic boxes of any size there are special bricks, like ramps, with their own shapes from Blockland .blb files. Their types come from Assets/brick/types: fxDTSBrickData datablocks in bricks.txt files there (Blockland add-on syntax, with brickFile, uiName, iconName, category, and subCategory) and any .blb not named elsewhere, by its file name. A special brick fills its type's size on the grid like a basic brick and turns around its middle. It collides using the collision boxes listed in its .blb, or a convex hull of its shape if it lists none. Clients match the server's types by name as they join, and draw bricks of types they don't have as plain boxes.

Global functions

Function Arguments Returns Description
addBrick(x, y, z, width, height, length, r, g, b, a[, angleID]) min corner in studs/plates; width and length in studs, height in plates, each 1-255; color; angleID is 0-3 quarter turns (default 0), where 1 and 3 swap width and length Brick, or nil Adds a brick. Returns nil without an error if it would overlap another brick or be out of bounds (y below 0). Also nil, with an error, while the bricks are locked, see setBricksLocked. Only cells both bricks really fill count as an overlap, see the note under the save and load functions below.
addSpecialBrick(x, y, z, typeName, r, g, b, a[, angleID]) min corner in studs/plates; a special brick type's name (like "45° Ramp 2x", case-insensitive, or its .blb file name); color; angleID 0-3 Brick, or nil Adds a special brick, which takes its type's size. Logs an error if there's no type by that name. Returns nil without an error if it would overlap another brick or be out of bounds. The shape's color faces (like a pine tree's green) keep their own color.
getNumBricks() none count How many bricks exist.
getBrickIdx(index) 0-based index Brick Looks up a brick by its position in the internal list. Removing bricks changes the order.
getNumNamedBricks(name) a brick's name count How many bricks are named that, matching case exactly, so "Door" and "door" are different names. Bricks with no name aren't counted under any name, so an empty name is always 0.
getNamedBrickIdx(name, index) the name, matching case exactly; 0-based index Brick Looks up one of the bricks with that name. Logs an error and returns nothing past the last one. Removing or renaming a brick with that name changes the order of the rest, like getBrickIdx. Both of these are constant time lookups, unlike scanning every brick with getNumBricks.
getBrickId(id) net ID Brick or nil Looks up a brick by its net ID.
getBrickAt(x, y, z) one stud/plate grid cell Brick or nil The brick filling that cell, if any.
clearAllBricks() none none Removes every brick.
saveBuildPicture(fileName) file name inside the Saves folder, like "preview.jpg" bool Draws every brick from above into a 256 by 256 JPEG, each pixel the color of the highest brick under it shaded by height, on grass, and writes it there. The same picture a player's game draws for a save without one, meant for a server's preview on a server list. Logs an error and returns false with no bricks or if the file can't be written.
saveBuild(fileName[, omitOwnership]) file name inside the Saves folder; omitOwnership writes every owner as -1 bool Saves every brick, with its name, material, collision, music, light, emitter, print, and what it spawns, in the Land of Dran binary format. Saves are written under a newer version number than the old game's, so the old game can't load them.
loadLodSave(fileName[, x, y, z]) file name inside Saves; optional offset in studs/plates count, or nil Loads a Land of Dran binary save (either of the old game's versions, or ours) on top of the current bricks, returning how many were added. Special bricks of types in Assets/brick/types are loaded, and so are names, collision, materials, prints, and our saves' music, lights, and emitters. The old game let undulo or bouncy go on top of another material; those bricks keep only the undulo or bouncy. Prints come by name, from the old game's saves too, however many faces its print mask covered; ones the server doesn't have are dropped and listed in the log. Other special types, and the old game's lights and music, are skipped. A brick's music or emitter of a type the server doesn't have is kept (and saved again) but doesn't play.
loadBlocklandSave(fileName[, x, y, z]) file name inside Saves; optional offset in studs/plates count, or nil Imports a Blockland .bls save using its own color palette, offset like loadLodSave, returning how many bricks were added. Brick names are matched against Assets/brick/types, special bricks included; unrecognized names are skipped and listed in the log. Pearl, chrome, glow, blink, swirl (as Hologram), rainbow, and undulo effects become materials, undulo winning on a brick that has a color effect too; water effects are dropped. Brick names, collision, prints, lights, emitters, and music come along, the last three as the brick's own like the wrench dialog's (saved by saveBuild). Prints are matched by the name in the save, like Letters/X; ones the server doesn't have are dropped and listed in the log. Lights become the light addBlocklandLight gave their Blockland type. Emitters use the emitter type addBlocklandEmitter gave their name, or else the one whose uiName matches, ignoring case, and always point up. Music uses a music sound type (see newSoundType) with the same name, ignoring case and with underscores as spaces. Anything without a match is skipped and listed in the log. Blockland is z-up where we're y-up, so a build comes in turned rather than mirrored: a brick lands at minus its saved x (in studs, two per Blockland unit), which is what keeps a word spelled out in print bricks reading the right way round.
addBlocklandLight(uiName, table) / addBlocklandLight(uiName, nil) a Blockland light type's name, like "Red Light", 1-255 characters, case-insensitive; light fields as for brick:setLight none Sets the light loadBlocklandSave puts on bricks that had this Blockland light type, replacing any set before. Fields left out get a new light's defaults, so without an offset the light sits in the middle of its brick, like Blockland's. An unknown field or a value of the wrong kind logs an error and changes nothing. nil forgets the type, so its lights are skipped. Bricks already loaded keep their lights.
addBlocklandEmitter(uiName, typeName) / addBlocklandEmitter(uiName, nil) a Blockland emitter's name, like "Fog A", 1-255 characters, case-insensitive; an emitter type's name none Makes loadBlocklandSave put an emitter of that type on bricks that had this Blockland emitter, instead of looking for a type with that uiName. Logs an error if there's no emitter type by that name. nil goes back to matching by uiName.

Save and load functions only accept a plain file name, with no folders, since saves always live directly in Saves/. Bricks that would overlap an existing brick are skipped when loading. A special brick only holds the cells its shape really fills, from the placement grid its .blb starts with, so bricks can be planted in the open part of one's box: the space around a pine tree's trunk, or over the low side of a ramp corner. A special brick whose file has no grid holds its whole box, as basic bricks always do.

Players' own Saved Bricks window is described in Engine.md.

Brick materials

Every brick has one material, painted on like its color. Players pick theirs next to the color in the brick selector. Shape effects are only drawn: a brick always collides as its plain shape.

Material Effect
None Plain brick.
Undulo Its corners wiggle and dance around, up to 0.3 studs along each axis.
Bouncy Stretches up to 35% taller and back down, once every 1.25 seconds. Anything landing on it bounces back up as fast as it came down (100% restitution), like a trampoline.
Pearl Mostly metallic and fairly smooth, a soft blurred sheen.
Chrome Almost fully metallic and very smooth, a mirror of a .hdr sky when clients have image based lighting on.
Blink Pulses once a second, like the part under the mouse in the appearance editor.
Hologram See-through bars walk around its sides.
Glow Never drawn darker than its own color, however dark it is.
Slippery Perfectly smooth to look at, and has a friction of 0.01 with whatever touches it.
Foil Metallic, with crinkled rainbow highlights that shift as you look at it from different directions.
Rainbow Its color is replaced by one that cycles through the rainbow every 5 seconds, in bands that flow diagonally across a build.

Prints

A print is a picture drawn over the paint of a print brick's printed faces, the ones a .blb marks TEX:PRINT, like 1x1 Print or 2x2F Print. Prints load from Blockland style folders under Assets/brick/prints (Print_<group>[_Default]/prints/<image>.png) and are named <group>/<image>, so Assets/brick/prints/Print_Letters_Default/prints/X.png is Letters/X, the same name Blockland saves use. Each brick wears one print, on every printed face it has. Players pick one in the print menu the print gun opens, Lua with brick:setPrint, and a print's see-through parts show the brick's own color.

Print packs in Add-ons load the same way (Add-ons/Print_MyPack/prints/whatever.png is MyPack/whatever). How clients match prints up and download ones they don't have is in Engine.md.

A print can also be a .webm video in the same folders, named the same way, which plays on the brick and loops, see Engine.md.

brick: methods

Method Arguments Returns Description
brick:getPosition() none x, y, z Min corner, in studs/plates.
brick:getDimensions() none width, height, length Size before rotation.
brick:getAngleID() none 0-3 Quarter turns around the vertical axis.
brick:getColor() none r, g, b, a Color, 0-1.
brick:setColor(r, g, b, a) color none Recolors the brick.
brick:getMaterial() none material name The brick's material, like "Chrome", or "None".
brick:setMaterial(name) a material name, case-insensitive none Paints a material onto the brick, see Brick materials above. "None" takes it off. Logs an error listing the materials for an unknown name.
brick:isColliding() none bool Whether players and objects collide with it.
brick:setColliding(collides) bool none Turns collision on or off. Non-colliding bricks can still be hit by raycast().
brick:getOwner() none client net ID, or -1 Who planted it. -1 for bricks added by Lua or loaded from a save.
brick:getName() none string The brick's name, empty by default.
brick:setName(name) string none Sets the brick's name, which getNumNamedBricks and getNamedBrickIdx find it by. "" takes its name away. Names are only kept on the server (and in saves); clients never learn them.
brick:remove([showEffect]) optional bool none Removes the brick. With true, clients show it popping loose and flying off like an undone brick. Leave it off when removing many bricks at once.
brick:isSpecial() none bool Whether it's a special brick with its own shape, rather than a basic box.
brick:getTypeName() none string A special brick's type name, like "45° Ramp 2x". Empty for basic bricks.
brick:getMusic() none sound name, volume, pitch; or nil The music loop playing from the brick.
brick:setMusic(soundName[, volume, pitch]) / brick:setMusic(nil) a sound type's name (any sound, not just ones marked as music); volume 0-2 (1 is the file as recorded, the wrench dialog's slider covers the same range) and pitch 0.05-10, clamped none Plays the sound on a loop from the middle of the brick for everyone, like startSoundLoop, until it's changed or the brick is removed. Leaving out volume and pitch keeps the brick's current ones (1 and 1 at first). Changing anything starts the loop over. nil or "" stops it.
brick:getLight() none table, or nil The brick's light settings, with every field below.
brick:setLight(table) / brick:setLight(nil) light fields, see below none Puts a light on the brick, or changes it. Fields left out keep the brick's current values, or a new light's defaults. An unknown field or a value of the wrong kind logs an error and changes nothing. nil takes the light off.
brick:getEmitter() none emitter type name, or nil The emitter on the brick.
brick:setEmitter(typeName) / brick:setEmitter(nil) an emitter type's name none Puts an emitter of that type in the middle of the brick, replacing any it had, tinted with the brick's color if the type has useEmitterColors. nil takes it off.
brick:canPrint() none bool Whether the brick's type has printed faces, so a print put on it would actually show.
brick:getPrint() none print name, or "" The print on the brick, like "Letters/X".
brick:setPrint(name) / brick:setPrint("") a print's name, case-insensitive none Puts a print on the brick, drawn on the printed faces of a print brick type. A .webm print plays there, see Prints above. Logs an error and changes nothing for a name no print has. "" takes it off. Prints on a brick whose type has no printed face are kept but never drawn.
brick:getItemSpawn() none item type script name, or nil The item the brick offers, like "hammer", see the Item section of the wrench dialog.
brick:setItemSpawn(typeName) / brick:setItemSpawn(nil) an item type's script name from newItemType none Floats a display item of that type over the brick (item:isDisplay), replacing any it had, for players to click and take copies of. Logs an error for a name no item type has. nil takes it away. Any brick can offer an item.
brick:getDisplayItem() none Item or nil The display item floating over the brick, if it has one. Destroying it leaves the brick without one until its settings change again, like a light.
brick:isVehicleSpawn() none bool Whether its type is a Vehicle Spawn brick (the vehicleSpawn datablock field), the only kind that can keep a vehicle spawned.
brick:getVehicleSpawn() none vehicle spawn name, or nil Which registered vehicle the brick keeps spawned, like "Jeep", see Vehicle spawn bricks.
brick:setVehicleSpawn(name) / brick:setVehicleSpawn(nil) a name from registerVehicleSpawn none Has the brick keep that vehicle spawned above it, spawning one right away if it has none. Setting a different vehicle removes the one already out, driver and all, and spawns the new kind in its place; setting the same one again leaves it alone. Logs an error for a name nothing registered. Works on any brick from Lua, though only a Vehicle Spawn brick's wrench dialog offers it. nil removes the vehicle along with the setting.
brick:getSpawnedVehicle() none Vehicle or nil The vehicle the brick spawned, while it's around.

Wrench dialog and brick attachments

Players wrench a brick to change whether it collides, its name, its music loop, light, emitter, and the item it offers; what the dialog does is in Engine.md. Lua can veto or redirect it with ClientWrenchBrick, or open one itself with client:openWrenchDialog.

The music loop, light, and emitter are real sound loops, lights, and emitters: they show up in getNumLights/getLightIdx and getNumEmitters/getEmitterIdx, are sent to players who join later, and are removed along with the brick. If Lua destroys one, the brick makes it again the next time its settings are changed. They're saved with the brick by saveBuild.

Light fields for brick:setLight and brick:getLight (see Lights for what each does):

Field Default Description
color {1, 1, 1} RGB, 0-1.
brightness 50 -500 to 100000. Negative makes a darkness with a radius of that many studs, see Negative lights. The wrench dialog shows that as Invert ticked and a Radius.
flicker 0 World units, 0-16.
blinkSpeed 0 Seconds for one full blink cycle, 0-60, 0 for no blinking.
blinkStrength 1 0-1, how much it dims at the low point of each blink.
coronaWidth 0 World units, 0-256.
coneAngle 0 0 shines every way, 1-179 makes a spotlight this many degrees wide.
direction {0, -1, 0} Which way a spotlight points, any length but zero.
spin 0 Degrees per second, -3600 to 3600.
shadows true Whether it casts shadows, see light:setShadows. false is the wrench dialog's No Shadows.
offset {0, 0, 0} Where the light is from the middle of the brick, in world units, -32 to 32 on each axis. Point light shadows leave out any brick a light is inside, so a light in the middle of its brick shines out through it, though bricks right next to it still cast shadows.

Decals

Marks left on the world, like the bullet holes the add-on guns leave in bricks. A decal is a flat square lying on a surface, drawn with a material of its own through the same lighting as everything else, so its normal map is what makes a hole look dented in, and it's lit, shadowed, and rained on like the brick under it. Decals never fade with time. Instead only so many exist at once, 256 unless setMaxDecals says otherwise, and adding one past that takes away the oldest. One put on a brick goes when the brick does. Clients who join later see the ones already there.

Function Arguments Returns Description
addDecalType(name, materialPath) a name to add decals by; a material descriptor relative to the game folder, like "Assets/decals/bulletHole/bulletHole.txt" type ID Registers a kind of decal. Clients load the material from their own copy of the same path, one they don't have isn't drawn. The albedo image's alpha is how much of the decal shows, so it should fade to nothing well inside its edges: everything that shows ought to fit the circle touching the square's sides, since the square is turned at random. The albedo is multiplied by the color of the brick the decal is on, so white in it is "the brick's own plastic". The images of a material all have to be the same size, see Assets/decals/bulletHole and the script there that makes its four images from one height field. Adding a name again replaces its material.
addDecal(type, x, y, z, normalX, normalY, normalZ[, size[, brick[, roll]]]) a type's name or ID; where, and the normal of the surface there pointing out of it, as raycast and ProjectileHit give them; studs along each side (default 1, up to 64); the Brick it's on, or nil; radians it's turned about the normal, random if left out none Leaves a decal. With a brick it takes the brick's color, goes when the brick does, is slid along the face so it doesn't hang off the brick's edge (by half its size at most), and whatever still doesn't fit a small face is cut off at the brick's sides. Without one it's left exactly where it was put until it's the oldest. It goes out to clients at the end of the tick, after any bricks made in the same tick.
clearDecals([type]) a type's name or ID, or nothing none Removes every decal, or with a type only the decals of that type, leaving the rest where they are.
setMaxDecals(amount) 0 to 65535 none How many decals can exist at once, 256 to start with. Lowering it takes away the oldest right away, and 0 turns decals off.
getMaxDecals() none count The limit above.
getNumDecals() none count How many decals there are now.

Vehicles

A vehicle is either bricks sliced out of the world into one body that drives on wheels, like the old game's brick cars, or one model (see Model vehicles). How players slice, drive, ride, wrench, save, and load them is in Engine.md. Lua slices with sliceBricks.

A vehicle needs exactly one steering wheel brick and at least one wheel brick (at most 24), up to 10000 other bricks, and can reach at most 40 studs along any axis. Special brick types are vehicle parts through our own vehiclePart datablock field in bricks.txt ("wheel", "steering", or "seat"); Assets/brick/types/vehicle has the old game's wheels and steering wheel, plus our own 2x4 Seat (a plate that looks like any 2x4 plate), listed under Special, Vehicle in the brick selector. A vehicle can have up to 32 seats, which are part of its body. The steering wheel decides which way the vehicle drives (the way its rim faces from its column, +x unturned, with the driver standing on its column's side), and every wheel has to roll that way: a wheel rolls along its longer side. Wheel bricks become wheels drawn with Assets/tire/tire.txt, half as tall as the brick in radius, and everything else, the steering wheel included, becomes the body. Bricks that don't collide are drawn but aren't part of the body. Lights and emitters on the bricks carry over and move with the vehicle. Music on the steering wheel brick becomes the vehicle's music; music on other bricks doesn't carry over. Like the old game, the body weighs one per colliding brick, so a very small vehicle is light enough to wheelie and flip easily.

Wheel and steering wheel bricks are set up in their wrench dialogs before slicing, with these settings. A model vehicle's wheels take the same ones as fields, see Model vehicles.

Wheel setting Default Range Description
Engine force 200 -2000 to 2000 How hard the wheel drives the vehicle forward, negative backward.
Brake force 400 0 to 2000 How hard it stops while the driver holds jump, and while nobody drives.
Steering 0.5 radians -pi to pi How far it turns while steering, 0 doesn't steer, negative turns the other way. A positive angle steers the way the driver asks for; this was backwards until 2026-09-17, so a vehicle saved before then was probably given a negative angle to make up for it and now wants a positive one.
Suspension length 0.7 0.1 to 5 World units the wheel hangs down when resting.
Stiffness 100 1 to 1000 How hard the suspension pushes back.
Compression / relaxation damping 6 / 10 1 to 100 How much the suspension resists moving in and out. The pair matters as much as the size: at the default stiffness, 6/10, 8/8 and 10/10 put a light vehicle on small wheels into a rock between its axles, where all of its weight, and so all of its grip, sits on one axle at a time and it drives on half its wheels. 6/8 and below rest evenly. Less damping lands worse, though, since damping is what soaks up a drop. Wheels the size of the jeep's are steady at any of them.
Grip 1.2 0.1 to 10 Friction slip, higher slides less.
Roll influence 0.6 0.1 to 10 How much cornering tips the vehicle, lower is steadier.
Steering wheel setting Default Range Description
Mass 1.5 1.5 to 30 How heavy each brick is to turn or tip over.
Spin damping 0.03 0 to 1 How quickly spinning slows down.
Realistic center of mass off Off, the vehicle turns around a point down near its wheels, which keeps it from flipping. On, around the middle of its bricks.
Horn Honk any sound type that isn't music, or none What the driver honks with left click, see vehicle:setHorn.
Vehicle's headlight off In the Light section: the brick's light becomes the vehicle's headlight once sliced, see vehicle:setHeadlight.

Global functions

Function Arguments Returns Description
sliceBricks(x1, y1, z1, x2, y2, z2) two opposite grid corners in studs/plates, both inclusive Vehicle, or nil and why Slices every brick with any part in that box into a vehicle, without firing ClientSliceBricks. The box can be at most 64 by 160 by 64.
getNumVehicles() none count How many vehicles exist.
getVehicleIdx(index) 0-based index Vehicle Looks up a vehicle by its position in the internal list.
getVehicleId(id) net ID Vehicle or nil Looks up a vehicle by its net ID.
clearAllVehicles() none none Removes every vehicle, letting their drivers out.
radiusImpulse(x, y, z, strength) world position; impulse, positive pushes away and negative pulls in objects pushed, bricks broken Pushes every dynamic in the world (players, items on the ground, and the rest) and every vehicle whose bounding box is within reach, which is 2.5 * sqrt(abs(strength)) studs (at most 200), along the line from the position to its center, fading to nothing at the edge of its reach. It's an impulse: something weighing 1, like a player or an item, gets strength studs a second right at the middle. A vehicle weighs one per brick (what it weighed before the same impulse broke any off), or its impulseMass for a model vehicle, and is pushed 5 times harder than that weight alone would say, with at least 0.6 of lift mixed into its direction: a car shoved only sideways goes nowhere, its tires' grip eats the push within a frame, so the lift hops its wheels off the ground first. A launcher shell beside a 22 brick car throws it a dozen studs. Carried items and players in vehicles aren't pushed themselves. RadiusImpulseHit fires for each vehicle and each dynamic pushed, see Built-in events. Destructable vehicles (see vehicle:setDestructable) also lose every brick, except the steering wheel, where abs(strength) * vehicleBrickBreakScale / (1 + distance^2) is at least its volume in cubic studs (a plate is 0.4 tall), with the distance to the nearest part of the brick; they fly off for everyone like hammered bricks, pushed the same way, taking their lights and emitters with them, and a broken seat lets its rider off and can't be used again. The tuning constants are in Physics/RadiusImpulse.h.
setVehicleDirtEmitter(typeName) / setVehicleDirtEmitter(nil) an emitter type's name none The emitter type wheels of vehicles sliced from now on throw dirt with. nil for none.
loadVehicleFile(fileName, x, y, z[, asBricks]) a name in the server's Saves/Vehicles without .lod; a grid spot in studs/plates; asBricks Vehicle (or true as bricks) and a message, or nil and why Places a vehicle save, from vehicle:saveToFile or one a player saved, with the middle of its bottom at the spot. As a vehicle it follows the same rules as slicing; as bricks, ones in the way of other bricks are left out. Doesn't fire ClientLoadVehicle.
spawnModelVehicle(settings) one table, see Model vehicles Vehicle, or nil and why Makes a vehicle whose body is a model rather than bricks, and fires VehicleCreated.
registerVehicleSpawn(name, functionName) / registerVehicleSpawn(name, nil) a name for wrench dialogs, 1-255 characters; the name of a global function none Lists a vehicle for Vehicle Spawn bricks to keep spawned, see Vehicle spawn bricks. The function is called as functionName(x, y, z, brick) and returns the vehicle it made (or nil). Registering a name again changes its function, nil takes it off the list; bricks set to it keep the name and spawn nothing until it's registered again.

vehicle: methods

Method Arguments Returns Description
vehicle:destroy() / vehicle:remove() none none Lets out its driver and passengers, removes its lights, emitters, and music, and removes it. Its bricks don't come back.
vehicle:saveToFile(fileName) a name without .lod bool Saves it to Saves/Vehicles/<fileName>.lod on the server, for loadVehicleFile. Names can't have folders in them. A vehicle save is a save of bricks, so this fails for a model vehicle.
vehicle:isModelVehicle() none bool Whether its body is a model rather than bricks, see Model vehicles.
vehicle:getNumBricks() none count Bricks in its body, the steering wheel included. 0 for a model vehicle.
vehicle:getNumWheels() none count
vehicle:getPosition() / vehicle:setPosition(x, y, z) world position x, y, z / none Its body's origin, which is near its wheels' tops unless its steering wheel asks for a realistic center of mass.
vehicle:getRotation() / vehicle:setRotation(w, x, y, z) quaternion w, x, y, z / none
vehicle:getVelocity() / vehicle:setVelocity(x, y, z) studs per second x, y, z / none
vehicle:getAngularVelocity() / vehicle:setAngularVelocity(x, y, z) radians per second x, y, z / none
vehicle:setGravity(x, y, z) acceleration none
vehicle:getDriver() none Client or nil Who's driving it.
vehicle:ejectDriver() none none Lets its driver out, without ClientExitVehicle.
vehicle:drive(forward, backward, left, right, brake) the driver's keys, each false by default none Holds a set of a driver's keys down on a vehicle nobody is in, the same call their own keys make every tick, so it accelerates, steers, leans on its suspension and throws dirt off its wheels exactly like a driven one. The keys stay held until this is called again. Logs an error and does nothing for a vehicle that has a driver, whose keys would overwrite these next tick; a driver getting in takes it back over, and letting them out leaves the script driving again. Steering is a key rather than a wheel, so left throws the wheels to full lock: something following a path wants a dead zone it holds its last choice through, or it flips lock to lock every tick and scrubs off all its speed.
vehicle:stopDriving() none none Lets go of every key, leaving it to roll to a stop and park like any other empty vehicle.
vehicle:isDriving() none bool Whether Lua is holding its keys down.
vehicle:setFlight(table) / vehicle:setFlight(nil) flight fields, see Flying none Makes it a plane, or changes how it flies. Fields left out keep what they were, so one number can be handed over on its own while it's in the air, which is how these were tuned. nil takes flight away and leaves an ordinary vehicle.
vehicle:getFlight() none table, or nil How it flies, nil for one that doesn't.
vehicle:setBuoyancy(buoyancy) 0-10, clamped; 0 by default none How hard the water holds its hull up, as a multiple of its weight with the whole of it under, the way dynamic:setBuoyancy works. 0 leaves it floating on its wheels like a car, see Boats.
vehicle:getBuoyancy() none number What setBuoyancy last gave it.
vehicle:setRowing(table) / vehicle:setRowing(nil) rowing fields, see Boats none Makes it a boat, or changes how it rows. Fields left out keep what they were, so one number can be handed over on its own while it's out on the water. nil takes the oars away and leaves an ordinary vehicle.
vehicle:getRowing() none table, or nil How it rows, nil for one that isn't a boat.
vehicle:playAnimation(name[, loop]) an animation of its body model, like a .dts shape's own sequences; loop defaults to false none Plays one of a model vehicle's animations for everyone, once or on a loop, like a plane's propeller. Looping ones are remembered, so a client that joins later sees them too. Logs an error for a vehicle made of bricks, which has no model to play anything on, or for a name its model has no animation by.
vehicle:stopAnimation([name]) animation name, or nothing none Stops that looping animation, or every one looping without a name. One playing once finishes on its own.
vehicle:setDriver(dynamic) a Dynamic nobody is playing none Sits a dynamic in the driver's seat, the way a client getting in seats their player: it comes out of the physics world and rides on the seat from then on, and everyone sees it sitting there with a model vehicle's sit animation playing, since what draws a driver goes by the seated dynamic rather than by whose it is. PlayerMount plays from it. Logs an error and does nothing if the seat is taken, if the dynamic is an item, a projectile, or already riding something, or if a client controls it (use client:enterVehicle for those). It stays there until clearDriver, or until its dynamic is destroyed. vehicle:getDriver() is still about clients, so it gives nil for one of these.
vehicle:clearDriver() none none Lets a dynamic setDriver sat there back out, standing above the seat like a player getting out, and parks the vehicle. Logs an error if nothing Lua sat there is driving.
vehicle:setColor(r, g, b[, a]) each 0 to 1, a defaults to 1 none Paints the blank material of a model vehicle's shape, see Painting a vehicle. a is how much of the paint shows over the material's own color, so 0 leaves the shape looking exactly as its files do. Does nothing visible to a vehicle made of bricks, which carry their own colors. Painting one that came from a Vehicle Spawn brick takes it out of that brick's hands: repainting the brick leaves it alone from then on.
vehicle:getColor() none r, g, b, a What it's painted, all four 0 for a vehicle nothing has painted.
vehicle:setDestructable(bool) / vehicle:isDestructable() bool none / bool Whether radiusImpulse breaks its bricks off. Off for a new vehicle until a script turns it on. A vehicle's own health isn't the engine's, see Vehicle damage and explosions.
vehicle:getNumSeats() none count How many passenger seats (seat bricks, or the ones spawnModelVehicle was given) it has, not counting the driver's, including seats that were broken off.
vehicle:getPassenger(seat) 0 to getNumSeats() - 1 Client or nil Who's riding on that seat. Use client:exitVehicle to get them off.
vehicle:getBuilder() none Client or nil Who sliced or loaded it, or the builder spawnModelVehicle was given, nil if Lua made it or they left.
vehicle:getBuilderID() none client net ID, or -1
vehicle:getSpawnBrick() none Brick or nil The Vehicle Spawn brick that spawned it, which spawns another once it's gone, see Vehicle spawn bricks.
vehicle:getMusic() none sound name, volume, pitch; or nil The loop playing from it.
vehicle:setMusicPitch(pitch) 0.05-10, clamped none Re-pitches the loop it's already playing without starting it over, see setSoundLoopPitch. This is how an engine note revs with the speed; setMusic with a new pitch would restart the sound several times a second. Nothing is playing yet, or it stopped: the pitch is still remembered for the next setMusic.
vehicle:setMusic(soundName[, volume, pitch]) / vehicle:setMusic(nil) a sound type's name; volume 0-2 (1 is the file as recorded), pitch 0.05-10 none Plays the sound on a loop from the vehicle for everyone, following it, until it's changed or the vehicle is removed. Changing anything starts the loop over.
vehicle:getHorn() none sound name, or nil What its driver honks with left click. A new vehicle's is Honk if there's a sound by that name, or whatever its steering wheel brick was wrenched to.
vehicle:setHorn(soundName) / vehicle:setHorn(nil) a sound type's name none Changes its horn, nil for no horn. Logs an error for a name no sound type has.
vehicle:getHeadlight() none table, or nil Its headlight's settings, the same fields as brick:getLight, with direction and offset in the vehicle's own space: offset from the middle of its front, direction where {0, 0, -1} on a model vehicle whose forward is that shines straight ahead.
vehicle:setHeadlight(table) / vehicle:setHeadlight(nil) light fields as for brick:setLight none Gives it a headlight, or changes it, and switches it on. Fields left out keep the current values, or for a new headlight a white 70 degree spotlight of brightness 150 with a 0.5 stud corona shining the way it drives from the middle of its front. nil takes it off.
vehicle:setHeadlightOn(bool) / vehicle:isHeadlightOn() bool none / bool Switches the headlight on or off, playing LightOn or LightOff from the vehicle, like the driver's flashlight key does. Logs an error switching on a vehicle with no headlight. The light shows up in getNumLights while it's on and is gone while it's off.

Model vehicles

A vehicle can be one model instead of a pile of bricks, for an add-on that ships its car as a shape rather than as a build. spawnModelVehicle makes one out of a dynamic type, which can be a .dts straight out of a Blockland add-on (see DTS models). The two kinds of vehicle sit side by side in the same world: a model vehicle is driven, ridden, flipped upright, wrenched for its music, horn, and headlight, and pushed by radiusImpulse exactly like a sliced one. What it doesn't have is bricks, so vehicle:getNumBricks is 0, vehicle:saveToFile refuses it, and radiusImpulse has nothing to break off it no matter what vehicle:setDestructable says. vehicle:isModelVehicle tells the two apart.

Everything about one is measured in its model's own space, with the model's origin at the vehicle's origin, which is also what it turns around. getTypeNodePosition reads the spots the shape itself names, so an add-on's own nodes can place the wheels and seats rather than a script guessing at numbers.

Whoever rides in one sits down with their player's sit animation, see Engine.md.

spawnModelVehicle(settings) takes one table and returns the Vehicle, or nil and why not:

Field Default Description
model required A type ID from newDynamicType, whose model is drawn as the body.
position required {x, y, z}, where the model's origin goes. Its wheels hang below that, so it wants a little height to drop from.
wheels required A list of at least one wheel, at most 24, each its own table, see below. Only a boat (buoyancy or rowing) can be given none.
wheelModel the client's own tire A type ID whose model every wheel is drawn with, scaled to the wheel's radius.
forward {0, 0, -1} Which way it drives in the model's space, along x or z.
box the model's collision box {x, y, z} half sizes of the one box it collides as, in world units.
boxOffset the model's collision box {x, y, z} middle of that box in the model's space.
mass 40 What the whole thing weighs, 1 to 100000. A brick vehicle weighs one per colliding brick.
impulseMass 30 What radiusImpulse pushes it as if it weighed, 1 to 100000, since mass is picked for how it drives: at the jeep's 150 a launcher shell that sends a 30 brick car flying would barely nudge it. The default is about a small brick car.
angularDamping 0.03 The same setting a steering wheel brick has, 0 to 1.
color the shape's own look {r, g, b} or {r, g, b, a}, each 0 to 1, what its blank material is painted, see Painting a vehicle. Giving one here also stops a Vehicle Spawn brick's color being put on it.
seat {0, 0, 0} Where the driver's model goes, which for a player model is their feet.
seats none A list of at most 32 passenger seats, each {x, y, z} (or a table with a position), where that passenger stands.
builder none A Client the vehicle counts as built by: vehicle:getBuilder() returns them and they're passed to VehicleCreated, like the client who sliced a brick vehicle.
horn "Honk" if registered The sound type its driver honks with, "" for none, see vehicle:setHorn.
headlight none A table of light fields as for vehicle:setHeadlight, which gives it a headlight switched on.
flight none A table of flight fields, see Flying, which makes it a plane rather than a car.
buoyancy 0 How hard the water holds its hull up, 0 to 10, see Boats. Anything above 0 floats the hull itself rather than its wheels.
rowing none A table of rowing fields, see Boats, which makes it a boat. A vehicle that floats or rows is the one kind that needs no wheels at all.
standing false true has whoever rides it stand on it rather than sit in it, like a skier on skis, and its driver's hint says W/S push rather than drive, with no horn. Sent to clients in the flags byte, so a game from before it was added still sits them down.
steeringAnimation none The name of one of the model's animations that its steering holds at a spot rather than plays, the way Torque held a vehicle's steering sequence: its first frame is full right, its middle straight ahead. Torque's own sum places it, 0.5 - 0.5 * angle * abs(angle) / lock with the angle in radians positive to the right and the lock the furthest any wheel's steerAngle turns, so at full lock a vehicle only gets as far along as its lock is in radians: six tenths of the way for a lock of 0.6. Each player's own game works the spot out from the wheels' steering, eased so it tips over in about a quarter of a second rather than snapping, so nothing more is sent while it's driven. Only drawn: the vehicle's physics never leans. The Lil' Rascal's fork turns and the whole bike leans into a corner this way. Logs an error for a name the model has no animation by.

A wheel's table takes position, {x, y, z} where its middle rests, and radius and width in world units (1 each by default). It also takes any of the wheel settings in the table above under their own names, clamped to the same ranges: engineForce, brakeForce, steerAngle, suspensionLength, suspensionStiffness, dampingCompression, dampingRelaxation, frictionSlip, and rollInfluence. The wheel's suspension hangs it suspensionLength below where it's bolted to the body, so position is where it sits with the vehicle standing still.

A Blockland shape's hub nodes aren't where the wheel rests, see Engine.md.

A model vehicle fires VehicleCreated like any other, and is not put in the world with the upward shove a sliced vehicle gets to free its bricks from the ground.

Flying

A vehicle given a flight table is a plane: the same body on the same wheels, with wings, a throttle and controls on top of the driving it already does. spawnModelVehicle's flight field or vehicle:setFlight switches it on, vehicle:setFlight(nil) takes it off again, and it works on a vehicle sliced out of bricks just as well as on a model one. Nothing about it is sent to clients beyond one bit that tells its driver what its controls do: the server pushes the body around and everyone draws it where the server says it is.

Whoever drives it has W and S for the throttle, A and D to roll, and it turns its nose toward wherever they look. How it handles, glides, and where its camera sits are in Engine.md.

Every field is in studs, seconds and radians, and the ones that fight gravity are given as speeds rather than forces, so they mean the same thing whatever the plane weighs and whatever gravity it's in. Anything left out keeps its default, and numbers out of range are clamped.

Field Default Description
thrust 0 Studs a second squared along its nose with the throttle forward.
reverseThrust 0 The same with the throttle back.
maxSpeed / maxReverseSpeed 120 / 30 Studs a second along its nose the throttle stops pushing past.
liftSpeed 60 The airspeed where its wings hold up exactly its own weight with its nose level. Lift goes with the square of the airspeed from there, so this is the one number that decides where it flies level: a little under maxSpeed gives a plane that holds its height at full throttle and sinks as it slows.
maxLift 8 The most lift it can make, as a multiple of its weight, so a dive doesn't turn into a slingshot.
angleLift 2.5 How much more lift its wings make per radian of angle of attack, which is how far its nose is above the way it's actually going. This is what makes pulling the nose up climb rather than just slow down.
stallAngle 0.35 Radians of angle of attack its wings work up to. Past it they start to give up and twice that far they do nothing at all, which is a stall: the nose drops until the air is over them again. Its elevator also stops answering as the angle comes up on this, so yanking the view around at speed groans the plane around the corner instead of snapping it into a stall.
stallSpeed 20 The airspeed where its wings and controls reach their full effect. At a standstill they do nothing at all, so a parked plane is an ordinary vehicle on wheels.
wingDamping / finDamping 1.2 / 0.9 Per second, how quickly the wings and the tail fin bleed off the part of its velocity that isn't along its nose: the wing catches it moving up or down through the air and the fin catches it sliding sideways, which together are what make a banked turn carry it around rather than skid.
dragSpeed 150 The airspeed where drag holds up exactly its own weight, which is how fast it ends up going in a vertical dive.
pitchRate / yawRate / rollRate 1.4 / 0.7 / 2.2 Radians a second its controls ask for. Lift makes a turn, so a plane wants far more pitch than yaw.
response 0.25 Seconds it takes to reach those rates. Also how quickly it stops turning when its controls are let go, which is its rotational drag.
levelRate 0.8 Radians a second of roll toward the bank it wants while no roll key is held, 0 to leave it rolled wherever it is. Upside down it wants nothing: a plane rolled onto its back stays there until its pilot rolls it out.
turnBank 0.7 Radians it banks into however hard it's turning, and level when it isn't, which is what makes a turn a banked one rather than a flat skid.

Boats

A vehicle can float on its own hull and be rowed along, which is what a vehicle with no wheels at all needs: its wheels are the only other thing that holds it up in the water and the only thing that moves it, and neither is any use out in the middle of a lake. vehicle:setBuoyancy is the floating and vehicle:setRowing is the moving, and the two are separate: a brick car given buoyancy bobs about as a raft, and a boat given rowing but no buoyancy rows about on whatever holds it up. Both work on a vehicle sliced out of bricks as well as on a model one, and like flying, none of it is sent to clients beyond one bit that tells the driver what its controls do. There has to be water in the world (setWaterLevel) for any of it to happen.

Its driver has W and S to row forward and back, and A and D to turn. How the hull floats on the waves and how rowing handles are in Engine.md.

Every field is in studs, seconds and radians. Anything left out keeps its default, and numbers out of range are clamped.

Field Default Description
thrust 10 Studs a second squared along its nose with the oars forward.
reverseThrust 5 The same backing up.
maxSpeed / maxReverseSpeed 12 / 5 Studs a second along its nose the oars stop pushing past. The water's drag settles it at about this anyway.
turnRate 1 Radians a second it comes around at with a turn key held.
response 0.4 Seconds it takes to reach that rate, and how quickly it stops turning again.
keelDamping 1.5 Per second, how quickly its hull bleeds off the part of its velocity that isn't along its nose, which is what makes a turn carry it around rather than slide it sideways.

Vehicles also come back from raycast() and client:getCursorItem, with type 7.


Sounds

Sounds are registered by name with newSoundType, then played by that name. Clients load the file from their own copy of the game folder when they join (or right away if they're already connected), so the file has to exist on the clients too, or be offered to them with addServerFile. .wav (any bit depth), .ogg (Vorbis), and .mp3 files work, mono or stereo.

How positioned sounds fade, bend, echo, and muffle, and which sound names the engine and the add-ons play on their own, are in Engine.md.

In the functions below, pitch is a playback speed multiplier (default 1, clamped to 0.05-10) and volume is 0-1 (default 1). They can only be given together.

Function Arguments Returns Description
newSoundType(name, filePath[, isMusic[, fullVolumeDistance]]) unique name and a path relative to the game folder, each 1-255 characters; isMusic marks it as music; studs, 1-500 (clamped), default 5 none Registers a sound. fullVolumeDistance is how far from it the sound still plays at full volume when it has a position, past which it loses about 10 dB each time the distance doubles: the default 5 is right for clicks, tools, and footsteps, and close to silent 50 studs off, so give something meant to carry more, like the Rocket Launcher add-on's explosion (40), which is then as loud 100 studs away as a default sound is at 12. Logs an error and skips it if the name is taken or the file doesn't exist on the server. Sounds marked as music are the ones players can pick for a brick in the wrench dialog.
playSound(name[, x, y, z][, pitch, volume]) sound type name; optional world position none Plays a sound once for every client, with no position or at x, y, z. Sent unreliably, so a client can occasionally miss one.
startSoundLoop(name[, x, y, z][, pitch, volume]) sound type name; optional world position loop ID Starts a sound that repeats until stopSoundLoop, with no position or at x, y, z. Clients who join later hear it too. Each client only plays the 16 loops closest to them at once; farther ones pause and pick up where they left off. Loops use the music volume setting on top of volume.
stopSoundLoop(loopID) ID from startSoundLoop or dynamic:startSoundLoop none Stops a loop. Does nothing if it already ended.
setSoundLoopPitch(loopID, pitch) ID from startSoundLoop or dynamic:startSoundLoop; 0.05-10, clamped none Changes a playing loop's pitch where it is, keeping its place in the sound rather than starting it over the way starting a new loop would. For a note that follows something, like an engine revving with a vehicle's speed. Does nothing if the loop already ended, and a client who joins afterwards starts it at the pitch it's playing at now.
setAudioEffect(preset) preset name, case insensitive none Puts a reverb effect on every sound for every client, including ones who join later. auto, the default, has each client's reverb follow the space around them (see above). none turns reverb off. Muffling underwater and behind walls happens either way. Presets: generic, paddedcell, auditorium, concerthall, cave, forest, plain, underwater, drugged, dizzy, psychotic, outhouse, heaven, hell, memory, dustyroom, waterroom, racer, tunnel.

See also dynamic:playSound, dynamic:startSoundLoop, client:playSound, and client:setAudioEffect.


Voice chat

Players hold push to talk (V by default) to talk. Their voice is only sent to clients whose camera is within the voice range, and plays for them like a positioned sound, see Engine.md.

Function Arguments Returns Description
setVoiceRange(studs) distance, at least 0; default 128 none How far a talker can be from a client's camera and still be heard. 0 turns voice chat off for everyone.
getVoiceRange() none studs The current voice range.

See also client:setVoiceMuted, client:isVoiceMuted, client:isTalking, and the ClientStartTalking and ClientStopTalking events.


Clients

A "client" represents one connected player/connection.

Global functions

Function Arguments Returns Description
getNumClients() none count How many clients have finished joining. Someone still being let in (or being turned away for a wrong password or a name already taken) isn't counted.
getClientIdx(index) 0-based index Client Looks up a joined client by index, counting the same ones getNumClients does.
messageAll(text) text (max 255 chars) none Broadcasts a chat message from the server to every connected client, as a single packet. Empty strings are silently ignored, same as client:message.
centerPrintAll(text) / centerPrintAll(text, durationMS) / centerPrintAll(text, durationMS, red, green, blue) text (max 255 chars); duration in ms (default 3000, clamped to 60000); color 0-1 (default white) none Broadcasts a temporary message to the center of every connected client's screen, as a single packet. A duration of 0 clears what's showing instead, like client:centerPrint.
bottomPrintAll(text) / bottomPrintAll(text, durationMS) / bottomPrintAll(text, durationMS, red, green, blue) same as centerPrintAll none Broadcasts client:bottomPrint to every connected client, as a single packet.
setCustomDecalsAllowed(allowed) boolean none Whether players can wear a face or shirt image of their own, which their game sends as they join (scaled down to 128x128 at most) and the server passes on to everyone else, keeping it in memory only until they leave. Starts as the hosting/customdecals setting ("Custom faces and shirts" in the Start Game dialog), on by default. Turning it off puts everyone back in the face and shirt they'd have without one and forgets every image; turning it on again has every game send its own again. Each player can still hide other players' images for themselves, which shows them the face or shirt from Assets instead.
getCustomDecalsAllowed() none boolean What setCustomDecalsAllowed last said, or the hosting/customdecals setting.
setServerName(name) string boolean, whether it was taken What the server list calls this server. Control characters are dropped and spaces trimmed from the ends, and what's left has to be 1 to 64 characters, what the master server takes, or it logs an error and returns false. A listed server sends it straight away rather than waiting for its next once-a-minute heartbeat. It lasts until the server stops: the hosting/servername setting (the Start Game dialog's Name box) isn't changed, and is what the next start uses. The same as an admin's /setServerName <name>.
getServerName() none string What setServerName last said, or the hosting/servername setting, or "Land of Dran Server" if that's empty.
registerChatSuggestion(commandName, suggestionText) command name (with or without the leading /, no spaces, max 64 chars, lowercased); text (max 255 chars, defaults to /commandName if empty) none Tells every client, now and as they join, about a slash command so their chat window lists it while they type one. Typing / plus the start of a name lists every matching command's suggestionText (the full name with its arguments, e.g. "/kick <player> [reason]"), and Up/Down write the picked command into the message bar ready for arguments. Once a space follows the command only its own line stays listed. This is only the hint: the command itself is still handled by a ClientChat listener. Registering a name again replaces its text.

Player list

Players press F2 for a window listing everyone on the server with the score text Lua gave them (client:setScoreText), how long they've been connected, and their ping, see Engine.md.

client: methods

Method Arguments Returns Description
client:message(text) string (max 255 chars) none Sends a chat message from the server to just this client.
client:kick() none none Disconnects the client.
client:getName() none string The client's display name.
client:getIP() none string The client's IP address.
client:getID() none net ID The client's unique net ID.
client:getPing() none ms Round-trip ping.
client:getPacketLoss() none value Current packet loss.
client:getTimeConnected() none seconds How long they've been connected, counted from the moment the connection opened rather than from when they finished loading. The same number the Time column of everyone's player list shows, see Player list.
client:isAdmin() none bool Whether the client logged into the eval console as admin.
client:getAccountId() none number The master server's id for the account this client logged in with, the same number every time that person plays anywhere, or 0 for a guest. Key your own records on this rather than on their name. See MasterServerAPI.md.
client:isGuest() none bool True when they have no account behind them, which is everyone on a server that isn't using a master server.
client:giveControl(dynamic) Dynamic none Gives the client physics-simulation authority over the dynamic (e.g. their player).
client:removeControl(dynamic) Dynamic none Takes physics-simulation authority for the dynamic back from the client.
client:getNumControlled() none count How many dynamics this client currently controls.
client:getControlledIdx(index) 0-based index Dynamic The controlled dynamic at that index (index 0 is typically their player).
client:setDefaultController(dynamic) Dynamic none Sets up movement-key/camera-direction input handling for this dynamic (walking, jumping). Currently the only way to stop this is to destroy the dynamic, which both sides then forget: whatever reads "the client's player" (the item in their hand, their flashlight, getCursorItem, vehicle seats) goes by the first dynamic given here that still exists, so a client whose player was destroyed has none until this is called with a new one. Also required before getCursorItem/snapToCursor will have live camera data for this client.
client:bindCamera(dynamic, fixUpVector, maxFollowDistance) Dynamic to follow; whether to lock the camera's up vector; max third-person follow distance none Binds the client's camera to follow a dynamic.
client:staticCamera(posX, posY, posZ) fixed camera position none Detaches the camera and locks it to a fixed position (direction stays free/mouse-controlled).
client:staticCamera(posX, posY, posZ, dirX, dirY, dirZ) fixed camera position and direction none Same, but also locks the look direction.
client:getCursorItem(maxDistance) max ray distance hit object, x, y, z, normalX, normalY, normalZ, distance; or nil Same return values as raycast(). Raycasts from the client's live camera position/direction (updated continuously, not just on click) out to maxDistance, ignoring the client's own first controlled object. Requires setDefaultController to have been called for this client.
client:centerPrint(text) / client:centerPrint(text, durationMS) / client:centerPrint(text, durationMS, red, green, blue) text (max 255 chars); duration in ms (default 3000, clamped to 60000); color 0-1 (default white) none Shows a temporary message in the center of just this client's screen. Messages showing at the same time stack up in lines. Their crosshair is hidden while any is showing, since the text covers it. A duration of 0 shows nothing and takes away every message showing instead, so a script can replace one message with another, like a countdown clearing the last second's line before printing the next.
client:bottomPrint(text) / client:bottomPrint(text, durationMS) / client:bottomPrint(text, durationMS, red, green, blue) text (max 255 chars); duration in ms (default 3000, clamped to 60000); color 0-1 (default white) none Shows a temporary message three quarters of the way down just this client's screen, between the center prints and the brick bar, apart from them. Only one shows at a time: a new one replaces the one showing. A duration of 0 takes it away.
client:playSound(name[, x, y, z][, pitch, volume]) same as playSound none Plays a sound once for just this client.
client:setVignette(red, green, blue, alpha, strength, durationMS) color 0-1; alpha 0-10, how opaque the color is at the very edges of the screen as it starts, over 1 it comes in further; strength 0-10, how hard the picture wobbles, 0 for none, 1 about as much as being underwater; how long it lasts in milliseconds none Draws the color in from the edges of the client's screen, clear in the middle, with the whole picture wobbling like it does under the water, both dying away together as the duration runs out. The old game's setVignette, with the wobble and duration added. A new one replaces the one showing, and a duration of 0 clears it. Drawn along with the underwater effect when the camera is under the water too.
client:setScoreText(text) any one line of text, or a number, up to 255 characters; "" for nothing none What's shown in the Score column next to the client's name in everyone's player list, see Player list. It's only text to the engine, so it can be a score, a team, a rank, or all of them. Only a change is sent, so it's cheap to call with the same text.
client:getScoreText() none string The text setScoreText last gave them, "" to start with.
client:setAudioEffect(preset) same as setAudioEffect none Sets the reverb effect for just this client, until something sets it again. Not remembered: setAudioEffect's preset is what a client gets when they join.
client:setVoiceMuted(muted) bool none Mutes or unmutes the client's voice chat. The server drops their voice while they're muted, and their game shows "Voice Muted" and stops sending. If they were talking, ClientStopTalking fires half a second later. Not remembered if they reconnect.
client:isVoiceMuted() none bool Whether setVoiceMuted muted the client.
client:isTalking() none bool Whether the client is talking right now, between ClientStartTalking and ClientStopTalking.
client:setJetsEnabled(enabled) bool none Whether the client can jet, on by default. Holding right mouse cancels gravity and lifts the player from setDefaultController up to 30 studs a second, moving at twice walking speed while they walk, not while swimming. A player lying down (the Crawl key bind, Left Shift) is pushed along the way they face at up to 30 studs a second instead of being lifted, which is faster than jetting upright - crawling slows the legs, not the jets. Each foot (Left_Foot and Right_Foot meshes, or the middle of a model without them) gets a playerJetEmitter while they jet, if Lua added that emitter type. Turning it off mid-jet drops them and removes the flames. Not remembered if they reconnect.
client:getJetsEnabled() none bool Whether setJetsEnabled lets the client jet.
client:setWalkSpeedScale(scale) how fast they move compared to an ordinary player, 0.01 to 2.55, clamped none Scales how fast the client's player walks, crawls and is pushed along by its jets, 1 by default. Their own game is told as well, so it predicts the same walk the server does and nothing snaps back; an older client, which isn't told, walks them at the ordinary speed. It doesn't change how high they jump or how fast their jets lift them. Sent as hundredths, so the speed is rounded to a hundredth of a player's. Not remembered if they reconnect.
client:getWalkSpeedScale() none number What setWalkSpeedScale set, 1 unless something changed it.
client:setFlashlightEnabled(enabled) bool none Whether the client can use their flashlight, on by default. Players tap their flashlight key (] by default) to switch it on or off, and hold it to cycle through colors starting from white. The LightOn and LightOff sounds play from their player. Each new player they're given (as they join, and every respawn) starts with it on if it's dark (getTimeOfDay() before 0.22 or after 0.78) and this allows it at that moment; their own game switches it on, the same as a tap of the key. Turning it off switches off a flashlight that's on. Needs a player from setDefaultController to hold it (see Lights). Not remembered if they reconnect. While they drive a vehicle with a headlight, the key switches that instead, whatever this is set to, see Vehicles.
client:getFlashlightEnabled() none bool Whether setFlashlightEnabled lets the client use a flashlight.
client:setFreeCameraEnabled(enabled) bool none Whether the client can drop their camera off their player and fly it around, off by default for everyone but admins, who are given it as they log in (single player's host included). With it on, their Drop Camera At Player key (F7) leaves their player standing where it is and flies the camera with the walking keys at 60 studs a second, through anything, and Drop Player At Camera (F8) teleports their player to the camera and puts the camera back on it. If they're driving or riding a vehicle, the vehicle is moved to the camera instead, keeping the way it faces and stopped, with them still in it. A yellow light with a wide corona follows the loose camera so everyone can see where it is, and their updates follow the camera rather than the player they left behind. Turning it off puts a camera that's already flying back on its player, where it is. client:bindCamera or client:staticCamera also takes the camera back. Not remembered if they reconnect.
client:getFreeCameraEnabled() none bool Whether setFreeCameraEnabled lets the client fly their camera.
client:getFreeCamera() none bool Whether their camera is off flying right now. Their player stays where they left it, so dynamic:getPosition on their player is not where they're watching from.
client:addItem(item[, slot]) an item on the ground; slot 0-4, or the first empty one slot or nil Puts the item in the client's inventory. Returns nil if that slot is taken or none are free, and logs an error too for an item someone already carries. See Items.
client:removeItem(slot) 0-4 Item or nil Takes the item out of the slot and puts it back into the world just in front of the client's player, not moving, or where it was without a player. nil for an empty slot.
client:getItem(slot) 0-4 Item or nil The item in that slot.
client:getSelectedSlot() none slot, open The slot the client's item bar has picked (0-4, kept while it's put away), and whether their items are out.
client:getHeldItem() none Item or nil The item in the client's hand: their setHandItem one if they have one, otherwise the one in the picked slot while their items are out.
client:setHandItem(item or nil) an item on the ground, or nil/nothing to empty their hand Item or nil Puts an item in the client's hand without using a slot, so their item bar can't reach it and it's held whatever their bar has picked. Whatever was in their hand before goes back into the world in front of their player and is returned, as does the one there when this is called with nil. Logs an error for an item someone already carries.
client:getHandItem() none Item or nil The item setHandItem put in their hand, nil if there isn't one.
client:getCameraPosition() none x, y, z Where the client's camera was as of their last movement update, which comes about every 100 ms. Needs setDefaultController.
client:getCameraDirection() none x, y, z Which way their camera looked then, normalized. Needs setDefaultController. While they hold left mouse, their camera is sent about every 30 ms instead.
client:getPaintColor() none r, g, b, a The color their paint palette (E, Right Shift's custom color, or a middle click on a brick with the paint can out) has picked, 0-1. Their game sends it as they connect and whenever it changes. White until then.
client:getPaintMaterial() none material name The brick material their paint palette has picked, like "Chrome", see Brick materials.
client:openPrintMenu(brick) Brick none Opens the print menu for the brick on the client's screen, see Print menu. The print they pick only reaches the brick in the last print menu they were sent, once, and only if that brick's type has printed faces.
client:openWrenchDialog(brick) / client:openWrenchDialog(vehicle) Brick or Vehicle none Opens the wrench dialog for the brick (or vehicle, with just its music) on the client's screen, as if they'd wrenched it, without firing ClientWrenchBrick or ClientWrenchVehicle. What they apply only reaches the brick or vehicle in the last dialog of that kind they were sent, once. See Wrench dialog and brick attachments and Vehicles.
client:getVehicle() none Vehicle and seat, or nil The vehicle the client is driving (seat nil) or riding on (the passenger seat, 0 or more).
client:enterVehicle(vehicle[, seat]) Vehicle; a passenger seat, 0 to getNumSeats() - 1, or none to drive bool Puts the player from setDefaultController in the vehicle's driver's seat, or on that passenger seat, without firing ClientEnterVehicle. false if someone's already in that seat, the seat doesn't exist, the client is already in a vehicle, or their player isn't standing in the world (like one Lua took out of it).
client:exitVehicle() none none Lets the client out of whatever they're driving (just above its seat) or riding (just above where they stood), without firing ClientExitVehicle.
client:applyAppearance(dynamic) Dynamic none Puts the colors, face, and shirt the client picked in their appearance editor on the dynamic, usually their player in ClientJoin. Their game sends their appearance as they connect: each painted part is matched to a mesh by name ignoring case (parts the model doesn't have are skipped), the face goes on the Face1 mesh, or Face or Head if there's no Face1, and the shirt goes on the Torso mesh, like dynamic:setMeshDecal. Their hat, a model descriptor from Assets/brickhead/parts painted its own color and sized by their slider (50% to 150%), is worn on the Head mesh (the "hat" slot of the dynamic's parts, sent to clients by file name like a face). If they save a change while connected, it's put on the last dynamic this was called with, and parts they no longer paint go back to the model's own look. A face or shirt image of their own (see setCustomDecalsAllowed) is put on in place of the one from Assets whenever the server has one from them, and taken back off again when it goes.