Appearance
world.blocks
The world's volume: cubic blocks on a one-metre grid, living alongside the terrain rather than instead of it.
luau
local world = require("world")
local stone = world.blocks:find("Stone")
world.blocks:set(4, 1, -7, stone)Coordinates are block coordinates: whole numbers, one block per metre.
A world where nobody defined a block type still has this table — it just answers air everywhere, which is true.
Reading
Reading is public: everybody in the region is already looking at the wall. These never fail, so you can use them straight in maths and conditions.
get
world.blocks:get(x, y, z) → type id
0 is air, and so is everything outside the world — a script probing past the edge is told "nothing there" rather than given an error.
solid
world.blocks:solid(x, y, z) → boolean
Is that cell something a body collides with?
Not the same as get() ~= 0. A tuft of grass is a block you walk through. Use solid for "can I stand here", get for "what is here".
find
world.blocks:find(name) → type id, or nil
The id the creator's palette gave that name. Case-insensitive.
Always look the id up by name; never write the number. The numbers belong to the creator's palette. Reordering it re-points every hard-coded id at a different material, silently, and the world starts building itself out of glass. A name is the one handle a script and a person share.
luau
local STONE = world.blocks:find("Stone")
if not STONE then
print("this world has no Stone in its palette")
return
endname
world.blocks:name(id) → name, or nil
The other direction. nil for air and for ids nobody defined.
This is what a block event needs: the id arrives as a number, and "they broke Stone" is the sentence you want to write.
raycast
world.blocks:raycast(ox, oy, oz, dx, dy, dz, dist) → hit, or nil
The first solid block along a ray. dist defaults to 32 metres, maximum 512.
This is the same walk the game highlights with and validates against, so a script's idea of "the block in front of you" is the world's.
luau
local eye, dir = shot.eye, shot.dir
local hit = world.blocks:raycast(eye.x, eye.y, eye.z, dir.x, dir.y, dir.z, 6)
if hit then
world.blocks:set(hit.x, hit.y, hit.z, nil) -- break it
end| Field | Meaning |
|---|---|
x, y, z | the block that was hit |
block | its type id — look the name up with name(id) |
dist | metres from the ray origin |
place_x, place_y, place_z | the empty cell against the face it entered through |
The place_* fields are where a new block goes if you are building on what you hit — the same cell a player's own right click would use.
luau
if hit then
world.blocks:set(hit.place_x, hit.place_y, hit.place_z, STONE)
endThis raycast takes loose numbers, not a table. It is the one call in the API that does — origin and direction are six arguments, not { from =, dir = }.
Writing
Both need the block permission and count against the eight-effects-per-event ceiling.
set
world.blocks:set(x, y, z, block) → true, or nil, reason
Places one block. nil or 0 breaks it.
Unlike a player's own edit, this has no reach limit: a script is the world's own, and it is already inside the region's budget.
fill
world.blocks:fill(x1, y1, z1, x2, y2, z2, block) → true, or nil, reason
Fills the box between two corners, inclusive. This is the verb a generated structure is built with.
luau
-- a hollow room, floor and walls
world.blocks:fill(0, 0, 0, 10, 0, 10, STONE) -- floor
world.blocks:fill(0, 1, 0, 10, 4, 0, STONE) -- one wallRefused whole if the box is too big — nil, "box too big" — and it says so now rather than doing half of it and stopping.
Refusals
| Reason | Meaning |
|---|---|
permission | this world has not granted block editing to scripts |
invalid position | outside the world |
box too big | fill covering too many cells |
too many commands | more than 8 world effects in this event |
A worked example: a pillar that grows
luau
--!strict
local self = require("self")
local world = require("world")
local STONE = world.blocks:find("Stone")
self:on_touch(function()
if not STONE then
print("no Stone in this world's palette")
return
end
local here = self:position()
local bx = math.floor(here.x)
local bz = math.floor(here.z)
local by = math.floor(here.y)
-- find the first free cell above us
for y = by, by + 12 do
if world.blocks:get(bx, y, bz) == 0 then
world.blocks:set(bx, y, bz, STONE)
return
end
end
end)The loop only reads, which is free — it places exactly one block, so the whole click costs one effect.
See also
- world.terrain — the ground underneath
- Terrain and blocks
