world.blocks#
The world's volume: cubic blocks on a one-metre grid, living alongside the terrain rather than instead of it.
Use it to
- read which block is at a spot, and whether it is solid
- find the first block along a line with raycast
- place, break and fill blocks from a script
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, so 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.
Careful
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.
local STONE = world.blocks:find("Stone")if not STONE then print("this world has no Stone in its palette") returnendname#
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.
local eye, dir = shot.eye, shot.dirlocal 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 itend| 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.
if hit then world.blocks:set(hit.place_x, hit.place_y, hit.place_z, STONE)endCareful
This 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.
-- a hollow room, floor and wallsworld.blocks:fill(0, 0, 0, 10, 0, 10, STONE) -- floorworld.blocks:fill(0, 1, 0, 10, 4, 0, STONE) -- one wallRefused whole if the box is too big, answering nil, "box too big", and it says so now rather than doing half of it and stopping.
Refusals#
| Reason | Meaning |
|---|---|
permission | the script's owner does not hold Dig and build with blocks in this world |
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#
--!strictlocal 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 endend)The loop only reads, which is free. It places exactly one block, so the whole click costs one effect.
See also#
- world.terrain is the ground underneath
Updated 17 October 2026