Server World API
Reading and placing blocks from server-side Lua, and the per-block interact and update hooks.
Weather hooks and block hooks receive a world object representing the world they run in. It lets scripts inspect and change blocks; every change is saved and broadcast to clients automatically.
All coordinates are block coordinates (integers). layer selects the block layer: 0 = background walls, 1 = foreground (the colliding layer), 2+ = overlay (fluids, effects).
Fields
| Field | Type | Description |
|---|---|---|
world_name | string | The world’s name (read-only). |
Reading Blocks
get_block_at(x, y, layer)
Returns a block table describing the block at that position, or an empty table if there is no block there — check for id before using the result:
local block = world:get_block_at(x, y, 1)
if block.id ~= nil then
log_info("found " .. block.name)
end
Block table keys:
| Key | Type | Description |
|---|---|---|
name | string | The block’s registered name. |
id | number | The block’s numeric ID. |
facing | string | "Up", "Down", "Left", or "Right". |
state | number | The block’s state value (drives state-based block textures). |
biome_id | number | ID of the biome this block belongs to. |
block_light_level | number | Light emitted/held from block light sources (0–255). |
sky_light_level | number | Light from the sky (0–255). |
fluid_level | number | Fluid fill amount for fluid blocks. |
is_surface_block | boolean | Whether this is the top-of-world surface block in its column. |
block_matter_type | string | The block’s matter type (solid, liquid, …). |
block_shape | table | { type = "Full" }, or { type = "Slab", divisions = n, side = "...", count = n } for partial blocks. |
Placing Blocks
set_block_at_from_name(x, y, block_name)
Places a fresh block by name (e.g. "STONE"). This is the simplest and usually best way to place blocks. The layer is determined by the block type itself; the biome is inherited from the block previously at that position. Raises an error if the name is unknown.
set_block_at_from_block_id(x, y, block_id)
Same as above, but takes a numeric block ID — useful together with get_block_variation (see lua-server-globals.md).
set_block_at(x, y, block_table)
Places a fully specified block. Use this only when you need control over facing, state, fluid level, or shape — otherwise prefer the two functions above.
Required keys: id, facing, biome_id, block_light_level, sky_light_level, fluid_level, state, collidable (boolean), and block_shape.
Note: a table returned by get_block_at cannot be passed straight back into set_block_at — it lacks the required collidable key. Copy the fields you want and add collidable yourself:
local old = world:get_block_at(x, y, 1)
if old.id ~= nil then
world:set_block_at(x, y, {
id = old.id,
facing = "Left", -- the change we wanted
biome_id = old.biome_id,
block_light_level = old.block_light_level,
sky_light_level = old.sky_light_level,
fluid_level = old.fluid_level,
state = old.state,
collidable = true,
block_shape = old.block_shape,
})
end
Damaging Entities
damage_entities_in_radius(x, y, radius, amount, damage_type, attacker_id)
Deals amount damage to every player, mob, and mount whose hitbox overlaps a circle of radius blocks centered on (x, y), and returns the number of entities hit. This is the area counterpart to entity:damage — the delivery mechanism for scripted hazards such as damaging weather, without spawning an AoE zone.
Unlike the block functions above, x, y, and radius are floating-point world positions in blocks (entity positions are not grid-aligned), not integer block coordinates.
damage_typeis optional —"physical"(the default when omitted),"fire", or"blast"(case-insensitive); an unknown name raises a Lua error.attacker_idis optional. When given, that entity is excluded from the hit and is credited with any kills (pass an attacker’sget_id()); omit it for an ownerless environmental hit.- Each hit routes through the target’s normal mitigation (general armor, per-type resistance, the minimum-damage floor) and is not blocked by per-attacker invincibility frames but still respects each victim’s global post-hit window. Kills (drops,
on_deathhooks) resolve on the same tick.
-- In a weather server-update hook: a Swelter heat pulse that burns everyone near a hot spot.
return function(world, weather_region, delta_ns)
for _, loc in ipairs(weather_region:select_n_random_locations(3)) do
local hit = world:damage_entities_in_radius(loc.x, loc.y, 6.0, 2.0, "fire")
if hit > 0 then log_info("swelter scorched " .. hit .. " target(s)") end
end
end
Reaching Players
get_players_in_radius(x, y, radius)
Returns a list (1-indexed) of entity handles for every player whose hitbox overlaps a circle of radius blocks centered on (x, y). Mobs and mounts are excluded — for those, damage them with damage_entities_in_radius above.
This is the world-level counterpart to entity:get_closest_player: where damage_entities_in_radius can only hurt an area, this hands you the players themselves, so a hook can push, buff, or check them with any entity method — apply_impulse, apply_effect, has_capability, and so on. It returns every player in range (not just the closest), so effects behave correctly in multiplayer.
As with the damage reach, x, y, and radius are floating-point world positions in blocks (entity positions are not grid-aligned), not integer block coordinates. Returns an empty list if no player is in range.
-- A gust that shoves nearby players away from a point, sparing anyone holding the ward.
return function(world, weather_region, delta_ns)
for _, loc in ipairs(weather_region:select_n_random_locations(2)) do
for _, player in ipairs(world:get_players_in_radius(loc.x, loc.y, 8.0)) do
if not player:has_capability("stormward") then
player:apply_impulse(18.0, -4.0)
end
end
end
end
Block Hooks
Blocks can run server-side Lua of their own. Both hooks are declared per block entry in your block-config.md file and receive the world object as their first argument.
blocks:
- name: ritual_stone
# ...
scripts:
server-on-interact: /blocks/ritual_stone_interact.lua
server-update: /blocks/ritual_stone_update.lua
interact-cooldown-ms: 500 # optional, default 0
Each value is a bare path to a .lua file that returns its handler function.
scripts: key | When Called | Signature |
|---|---|---|
server-on-interact | A player interacts with a block of this type | function(world, x, y, layer, player_entity_id) |
server-update | Every server tick, for each placed block of this type | function(world, x, y, layer, delta_ns) |
interact-cooldown-mssets the minimum time between interactions on a single block cell (0= no cooldown).- Update hooks only run for blocks of types that declare one, so ordinary blocks cost nothing. A block type with an update hook ticks for every placed block of that type in loaded chunks — keep the handler light, and use block
stateplus early returns to skip idle blocks. delta_nsis the tick delta in nanoseconds (delta_ns * 1e-9= seconds).
-- Turns the block into stone when a player interacts with it.
return function(world, x, y, layer, player_entity_id)
world:set_block_at_from_name(x, y, "STONE")
end
See Also
- lua-server-globals.md —
get_block_variation, spawning, locations - lua-server-weather.md — weather hooks, which also receive the world object
Last updated