Blocks and building
Examples: Glow Sphere Block (a new block with logic), Thruster Tuner (settings and limits), Block Mover (overlapping moves), Gear Works (gear blocks and a mechanism declared in data).
New blocks
A block is a JSON definition. List the files in mod.json:
"content": { "blocks": ["content/blocks/*.json"] },
"permissions": ["building"]{
"id": "crystal",
"name": "Power Crystal",
"description": "Glows. Heavy.",
"base": "Cube",
"category": "Advanced",
"model": "/models/crystal.glb",
"texture": "/textures/crystal.png",
"icon": "/icons/crystal.png",
"collider": "box",
"mass": 4,
"health": 400,
"sounds": { "place": "/sounds/crystal-place.wav" }
}| Field | Description |
|---|---|
id | Lower-case letters, digits, - and _. The block's full id is your.mod-id:id. |
name, description | Shown in the palette. |
base | The game block this copies (default Cube). It decides how the block connects, paints, collides and what it does. Based on Thruster it thrusts. Based on Wheel it rolls. |
category | Basic, Advanced, Movement, Weaponary or Logic. |
model | A .glb, .gltf, .obj or .fbx. See models. |
fit | true (default) scales and centres the model to fill one block (or its size). false keeps the file's size. Gear models are not fitted: see gears. |
modelScale | An extra scale on top of fitting. |
texture | A picture that replaces the model's own colour texture. |
tint | A "#rrggbb" colour multiplied over the material. |
paintable | true lets players paint it. |
icon | A .png for the block palette. |
collider | box (default), sphere (or round) or mesh (the first mesh of the model). |
mass | Kilograms, 0.001–100 000. |
health | 1–1 000 000. |
thrustRating | For thrust mechanisms: the part's rated maximum force, in newtons. |
scalable, minScale, maxScale | Whether players can stretch it, and how far (1–64 blocks, or [x, y, z]). |
logic | Replace the base's mechanism list, for example ["Thruster"]. |
mirror | Mirror symmetry for the building editor's mirror tool: detect (default, worked out from the model), AxisX, AxisY, AxisZ, DiagonalXY, DiagonalXYInverse, DiagonalXZ, DiagonalXZInverse, DiagonalYZ, DiagonalYZInverse. |
sounds | place and destroy, as .wav files. |
Paths are relative to the JSON file. Start with / for the mod root.
Redescribing the block. size, faces, spin (meshes that turn), footprint, scaleLink, connectableTo, gear (it becomes a gear that meshes with every other) and mechanism (its own settings, keys, wires and drive, run natively) are described in Gears and turning parts.
Models for blocks are limited to 16 meshes, 16 materials, 100 000 vertices and 60 000 triangles, because the building system draws thousands of blocks at once.
Ids are stable. A mod block's number in saves and network messages comes from its full id, so creations with your blocks save, load and synchronise with no code. Players need your mod to load them, and servers send it to joining players.
Finding blocks and reacting to them
building.onPlaced("crystal", function(block) -- placed, loaded, or received from the server
effects.burst("glow", block.position, { color = color("#80ffff") })
end)
building.onRemoved("crystal", function(block) log.info("a crystal was removed") end)
for _, thruster in ipairs(building.blocks("Thruster", 500)) do
log.info(thruster.id, thruster.creation, thruster.position, thruster.velocity:length())
end
local selected = building.selection() -- client: what the player has selected in the editor
building.onSelectionChanged(function(blocks) -- client: runs whenever that selection changes (no polling)
log.info(#blocks, "selected")
end)Types: your own block by its short id ("crystal"), another mod's block by full id ("a.mod:gizmo"), and the game's blocks by name ("Thruster"). building.blockTypes() lists them all.
Block fields: id, type, name, mechanism, creation, position, rotation, velocity. Blocks disappear, so check block:isValid() before using one you kept.
Settings on working parts
local settings = block:settings() -- { Force = { value = 25000, min = 0, max = 50000 }, Enabled = { value = true }, … }
local force = block:get("Force")
block:set("Force", 40000) -- client, needs building.edit
local ok, err = block:trySet("Force", 40000) -- false and the reason instead of an error when it is refused
if not ok then notify.toast(err) endBlock:set works exactly like the block's own panel. The request goes to the server, which checks that the player owns the creation and clamps the value to the current limit. Send changes a few times a second at most, not on every frame of a slider drag.
Raising part limits
A setting's maximum comes from its mechanism and the part's rating. A server script with building.limits can raise it for everyone on the server:
building.raiseLimit("Thruster", "Force", 250000)
building.raiseLimit("JetEngine", "Force", 8000000)
log.info(building.limit("Thruster", "Force")) -- 250000; nil when normal
building.resetLimit("Thruster", "Force")- Limits are keyed by mechanism (
Block.mechanism), so every block running that mechanism is affected. - Clients receive raised limits automatically, and the block panels show the new range.
- A limit belongs to the mod that raised it and returns to normal when that mod stops. If several mods raise the same limit, the highest wins.
- Never take the new limit from a client. Compute it on the server, as the Thruster Tuner does.
Moving blocks into other blocks
The editor refuses moves that make blocks overlap. A server script with building.overlap can make them:
net.handle("nudge", { blocks = "string[]" }, function(player, data)
local blocks = {}
for _, id in ipairs(data.blocks) do blocks[#blocks + 1] = building.block(id) end
local ok, err = building.tryMoveInto(player, blocks, vec3(0, 0.05, 0)) -- false, reason when refused
if not ok then return false, err end -- answered on "denied"
end, { rate = 20 })- The offset is in the creation's own directions, in metres, and snapped to the build lattice. One block is 0.2 m and a lattice step is 0.05 m.
- The game checks that
playerowns the creation. The move is saved and synchronised like any other edit. Only the placement check is skipped, and the move is not added to the player's undo history. - At most 2048 blocks and 1 km per move.
Pushing creations
A server script with building.physics can apply force to a moving creation: block:push(vec3(0, 5000, 0)), or block:push(force, true) for a one-off impulse.
Building tools
Add a button to the building editor's toolbar (needs only building):
local window
local tool = building.addTool({ name = "⚙ Tuner", tooltip = "Tune the selected parts" }, function()
if window then window:destroy(); window = nil else window = openWindow() end
tool:setActive(window ~= nil)
end)The button is removed when the mod stops. See the building tool template.