KKAWAKIDOCSBack to site
Documentation menu

KAWAKI DOCS / LUA · GAME

Markdown

world

Two globals hold everything live in the game:

Global Is
player Your player as an entity handle, or nil outside a world
world The loaded world handle, or nil outside a world

Both re-resolve on every access, so a respawn or dimension change already shows in the next read. Guard for nil on the title screen:

module:event("tick", function()
    if not player then return end
    if player:health() < 8 then
        hud.notify("Low HP", ("%.1f left"):format(player:health()), "error")
    end
end)

Handles are live: every colon-call re-reads the game, so one kept from last tick answers with this tick's state. The exception is text, which always comes as a snapshot.

Handle Comes from
Entity player, world:players(), world:entities(), world:entity(id)
Item e:held_item(), e:equipped(slot), inventory.item, game.item
Block state world:block_state(pos), a block hit's state
Raycast hit world:raycast(...), world:raycast_entity(...), player:raycast()
Chunk world:chunk(x, z), world:loaded_chunks()

Positions are vec3 everywhere. Data that never changes at runtime (items, translations) lives on game.

Entities

Method Returns
world:players() Every player as an entity handle, yourself included
world:player(name) One player by name, case-insensitive, or nil
world:entities(type?) Every entity, optionally filtered by exact type id ("minecraft:item")
world:entity(id) Entity by numeric id, or nil
for _, p in ipairs(world:players()) do
    if not p:is_self() and p:distance() < 8 then
        print(p:name() .. " is close")
    end
end

Blocks

Method Returns
world:block(pos) Block id at a vec3, e.g. "minecraft:air"
world:block_state(pos) Full block state handle
world:find_blocks(ids, center, radius) Positions of matching blocks in a cube around center
world:sign_text(pos) A sign's four front lines, four back lines and waxed flag, or nil
world:top_y(x, z[, heightmap]) Surface y of a column

Heightmaps: "motion_blocking" (default) · "motion_blocking_no_leaves" · "world_surface" · "ocean_floor".

find_blocks takes one block id or a list, scans radius blocks out along each axis (capped at 64) and returns the matches as vec3 block positions:

local chests = world:find_blocks({ "minecraft:chest", "minecraft:trapped_chest" },
    player:position(), 6)
print(#chests .. " chests nearby")

Raycasts

Method Returns
world:raycast(from, to[, fluids[, shape]]) First block hit between two points, or nil
world:raycast_entity(from, to) Closest entity hit, or nil. Skips spectators and yourself

fluids = true also hits fluids. shape picks the collision shape to test: "outline" (default, what the crosshair uses) · "collider" (physics shape) · "visual".

Both need a local player and return nil without one. For the crosshair ray use player:raycast(max_distance?), which tests blocks and entities at once.

Environment

Method Returns
world:time() Time of day in ticks
world:dimension() Dimension id, e.g. "minecraft:overworld"
world:raining(), world:thundering() Weather flags
world:rain_gradient(), world:thunder_gradient() Weather strength, 0..1
world:biome(pos) Biome id, e.g. "minecraft:plains"
world:light(pos) Block light and sky light, 0–15, as two returns
local pos = player:position()
print(world:biome(pos), world:light(pos))
if world:raining() then
    print("rain strength", world:rain_gradient())
end

Chunks

Method Returns
world:chunk(x, z) Loaded chunk by chunk coordinates, or nil
world:is_chunk_loaded(x, z) Whether that chunk is loaded
world:loaded_chunks() Every loaded chunk
world:min_section() Section y of the lowest section, -4 in the overworld
world:section_count() Sections per chunk column, 24 in the overworld

Chunk and section coordinates are block coordinates divided by 16, rounded down. A chunk handle has x(), z() and section(y), which takes a section y from world:min_section() to world:min_section() + world:section_count() - 1 and returns nil outside it.

A section is a snapshot: the 16×16×16 blocks copied at the section(y) call.

Method Returns
s:y() Section y; its blocks run from y * 16 to y * 16 + 15
s:empty() true when every block is air
s:palette() The stored palette as {id, properties, count} entries
s:blocks() 4096 palette indices; local block x, y, z sits at y * 256 + z * 16 + x + 1

The palette is the one the server sent, so it can hold states no block uses (count is 0). A section whose palette overflowed (over 256 states) lists only the states in use. Reading the palette is cheap; check it before walking the blocks:

local function full_hives(chunk)
    local found = {}
    local bottom = world:min_section()
    for sy = bottom, bottom + world:section_count() - 1 do
        local section = chunk:section(sy)
        local wanted = {}
        for i, entry in ipairs(section:palette()) do
            if entry.count > 0 and entry.properties.honey_level == "5" then wanted[i] = true end
        end
        if next(wanted) then
            local blocks = section:blocks()
            for i = 0, 4095 do
                if wanted[blocks[i + 1]] then
                    found[#found + 1] = vec3(chunk:x() * 16 + i % 16, sy * 16 + math.floor(i / 256),
                        chunk:z() * 16 + math.floor(i / 16) % 16)
                end
            end
        end
    end
    return found
end

module:event("chunk_load", function(e)
    local chunk = world:chunk(e.x, e.z)
    if chunk then
        for _, pos in ipairs(full_hives(chunk)) do print(tostring(pos)) end
    end
end)

Light sections stretch one past each end of the column, so bit i of a light mask from the light_update and level_chunk_with_light packets is section world:min_section() - 1 + i.