Hungrit Docs

Terrain and blocks#

Two separate systems that live alongside each other.

world.terrainworld.blocks
Shapesmooth reliefone-metre cubes
Good forhills, valleys, riverbeds, building padswalls, mines, structures, anything with a face
Coordinatesworld metreswhole block coordinates
Readingfree, publicfree, public

Putting something on the ground#

Luau
local ground = world.terrain:height(x, z)world:spawn({ model = "crate", pos = { x = x, y = ground + 0.5, z = z } })

height never fails: a flat world reads 0, and so does nonsense input, so you can use it straight in maths.

It answers for the terrain, not for whatever is standing on it. For "what is under this point, including objects", cast a ray downward:

Luau
local hit = world:raycast({    from = { x = x, y = 60, z = z },    dir = { x = 0, y = -1, z = 0 },})

Digging with the brushes#

Four brushes, all with the same shape:

Luau
world.terrain:raise(x, z, radius, strength)world.terrain:lower(x, z, radius, strength)world.terrain:flatten(x, z, radius, strength)world.terrain:smooth(x, z, radius, strength)

radius defaults to 4 (range 0.5 to 64), strength to 1 (range 0 to 8).

flatten pulls the area toward the height at the brush centre, not toward zero, so put the centre where you want the floor.

A crater#

Luau
local function crater(cx: number, cz: number)    world.terrain:lower(cx, cz, 8, 4)    world.terrain:raise(cx, cz, 12, 0.6)     -- the rim thrown up    world.terrain:smooth(cx, cz, 14, 1)      -- soften the joinend

Three strokes is three effects. A hundred strokes is not, so spread those over a timer:

Luau
local queue = {}local i = 0 self:on_timer(0.1, function()    for _ = 1, 4 do        i += 1        local s = queue[i]        if not s then return end        world.terrain:raise(s.x, s.z, s.r, s.h)    endend)

Blocks: always look the id up by name#

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

Careful

Never write the number. The ids 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.

Reading a cell#

Luau
world.blocks:get(x, y, z)      -- the type id; 0 is air, and so is outside the worldworld.blocks:solid(x, y, z)    -- can a body walk through it?world.blocks:name(id)          -- the other direction

solid is 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".

Breaking and placing what you are looking at#

Luau
self:on_action("mine", { key = "e" }, function(p, shot)    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 not hit then return end     local kind = world.blocks:name(hit.block) or "something"    world.blocks:set(hit.x, hit.y, hit.z, nil)     print(p.name .. " broke " .. kind)end)

Building on the face you hit uses the place_* fields, the same cell a player's own right click would use:

Luau
world.blocks:set(hit.place_x, hit.place_y, hit.place_z, STONE)

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 = }.

Building a structure#

Luau
--!strictlocal self = require("self")local world = require("world") local function room(x: number, y: number, z: number, w: number, h: number, d: number, b: number)    world.blocks:fill(x, y, z, x + w, y, z + d, b)                  -- floor    world.blocks:fill(x, y + h, z, x + w, y + h, z + d, b)          -- roof    world.blocks:fill(x, y, z, x + w, y + h, z, b)                  -- walls    world.blocks:fill(x, y, z + d, x + w, y + h, z + d, b)    world.blocks:fill(x, y, z, x, y + h, z + d, b)    world.blocks:fill(x + w, y, z, x + w, y + h, z + d, b)end world:on_start(function()    local STONE = world.blocks:find("Stone")    if STONE then        room(0, 0, 0, 8, 4, 10, STONE)    endend)

Six fills is six effects, which is inside the ceiling. A fill that covers too many cells is refused whole, with nil, "box too big", said now rather than done halfway.

A pillar that grows one block per click#

Luau
self:on_touch(function()    local here = self:position()    local bx, bz = math.floor(here.x), math.floor(here.z)     for y = math.floor(here.y), math.floor(here.y) + 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.

Reacting to what players do#

Luau
local events = require("events") events:listen("world.block", function(e)    -- somebody placed or broke a blockend)

previous arrives as a number, and world.blocks:name(previous) is what turns it into a sentence.

Permissions#

Writing terrain needs Terraform and writing blocks needs Dig and build with blocks. The world's owner holds both; anybody else holds what the owner granted them. See Permissions.

Reading both is always free and always public, because everyone standing there is already looking at it.

See also#

Updated 11 October 2026