Hungrit Docs

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

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, 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.

Luau
local STONE = world.blocks:find("Stone")if not STONE then    print("this world has no Stone in its palette")    returnend

name#

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.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
FieldMeaning
x, y, zthe block that was hit
blockits type id. Look the name up with name(id)
distmetres from the ray origin
place_x, place_y, place_zthe 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)end

Careful

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.

Luau
-- 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 wall

Refused 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#

ReasonMeaning
permissionthe script's owner does not hold Dig and build with blocks in this world
invalid positionoutside the world
box too bigfill covering too many cells
too many commandsmore than 8 world effects in this event

A worked example: a pillar that grows#

Luau
--!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#

Updated 17 October 2026