The world from Lua
Blocks, sections, block entities, entity NBT, biomes and light in the native API.
worlds.default(), worlds.by_name(name) and p.pos.world give you a world object. Single blocks use the calls
below; edits of many blocks have a bulk form that is orders of magnitude cheaper.
local world = worlds.default()
world:set_block(0, 64, 0, "minecraft:oak_stairs[facing=east]")
print(world:block_state(0, 64, 0))A position can be given as three integers or as a pos.
Sections#
A section is 16 by 16 by 16 blocks. It travels as a list of 4096 block state ids, ordered x first, then z, then y
(index y * 256 + z * 16 + x + 1 for local coordinates from 0). A state id is an integer; worlds.state_id and
worlds.state_text convert to and from the text form.
local stone = worlds.state_id("minecraft:stone")
local states = world:section(0, 4, 0) -- chunk_x, section_y, chunk_z
for i = 1, 4096 do states[i] = stone end
world:set_section(0, 4, 0, states)| Call | Does |
|---|---|
world:section(cx, sy, cz, load?) |
The 4096 ids, or nil when the chunk is not loaded and load is false |
world:set_section(cx, sy, cz, states, notify?) |
Writes a section; notify (default true) shows it to the players who see it |
world:apply_chunk(cx, cz, sections, options?) |
Writes several sections of one chunk |
world:apply_chunks(chunks, options?) |
Writes many chunks at once; chunks is a list of { x, z, sections } and the options are the same |
world.block_version |
Changes whenever a block changes or a chunk loads or unloads |
In apply_chunk and apply_chunks, sections maps a section y to its 4096 ids, and a negative id keeps the block
that is there. options.resend (default true) shows the result to the players; options.previous = true returns a
second value with what each section held before, which is what an undo needs. They return the number of blocks that
changed, or nil when something was out of range (everything else is still applied).
local changed, before = world:apply_chunk(0, 0, { [4] = states }, { previous = true })A copy of a section stays good while world.block_version has not moved.
world:resend_chunks({ {0, 0}, {1, 0} }) sends chunks again to the players near them, and world:loaded_chunks()
lists the loaded ones as { cx, cz } pairs.
Many positions at once#
world:read_states(positions, load?) returns the state id at each { x, y, z } (-1 where the chunk is not loaded and
load is false), and world:write_states(positions, states, notify?) writes them, returning how many changed.
A change of blocks can set off more than the blocks. world:apply_changes(positions, states, effects?) writes the
states and then runs effects for the blocks that changed, the way Level.setBlock does in the official server;
world:update_neighbors(positions, effects?, previous?) runs them for blocks that are already written, and previous
(a state id per position) says what each held before, which the departure of a block needs. effects is a list of
names, or their bits added up; without it the effects are neighbors, shapes and clients:
| Effect | Does |
|---|---|
departure |
The block that left sets off its own hooks on the way out: a piston head takes its base, a dust or a container tells what it fed |
place |
The block that arrived sets off its own hooks: a pumpkin builds its golem, dust settles its power, a piston checks its signal, a sponge drinks |
ticks |
The block that arrived books what it needs: sand its fall, water its flow, fire its spread, frogspawn its hatch |
neighbors |
Tells the neighbors that one of theirs changed, in the official order, so a torch falls off a broken wall and a lamp lights beside a redstone block |
shapes |
Reshapes the blocks around to fit, like fences joining and unsupported blocks breaking |
light |
Works the light out again |
entities |
A container that is replaced spills what it held as items |
poi |
Points of interest (beds, bells, job blocks) follow the change; without it they keep their old state |
clients |
Shows the new state to the players who see the blocks |
local at = { {10, 64, 10}, {10, 65, 10} }
local changed = world:apply_changes(at, { stone, stone }, { "neighbors", "shapes", "clients" })
-- sand put down this way falls, water flows, a redstone block lights the lamp next to it
world:apply_changes({ {0, 70, 0} }, { worlds.state_id("minecraft:sand") }, { "place", "ticks", "neighbors" })The pieces of that aftermath can also be asked for on their own, for blocks already written:
| Call | Does |
|---|---|
world:neighbor_changed(positions, sources?) |
The block at each position is told that a neighbor changed, and answers as it would in the official server |
world:update_shapes(positions, states?) |
The blocks around each position reshape to fit it; with states, only along dust lines for a block that is or was that state |
world:valid_state(where, state) |
The state a fence, pane, wall or stair would take if it were put there, joined to what is around it; nothing is written |
world:schedule_tick(where, delay, kind?) |
Books a tick of the block there, or of its fluid when kind is "fluid"; false when there is nothing to tick |
world:use_item_on(where, face, item, yaw?, pitch?) |
Uses a block item on a face (down, up, north, south, west, east) as a player holding it would; returns how many blocks were put down |
Chunks#
world:chunk_sections(cx, cz, load?) returns every section of a chunk at once, a table from section y to the 4096
ids, which is one call instead of one per section. world:chunk_status(cx, cz) says where a chunk stands: unloaded,
queued (asked for and on its way), loaded or ticking (loaded near a player).
request_chunk(world, cx, cz, options?) waits until a chunk is loaded, see
Scheduler and coroutines. Look at chunk_status afterwards.
request_chunk("world", 40, -12, { urgent = true })
if world:chunk_status(40, -12) ~= "unloaded" then
local sections = world:chunk_sections(40, -12, false)
endA chunk another plugin needs can be kept loaded with world:hold_chunks(token, { {40, -12} }). The chunks stay until
world:release_chunks(token) is called with the same token, whether or not a player is near.
Block entities#
Chests, signs, banners and the like carry data besides their state. It is read and written as SNBT text, the same
notation /data uses.
local chest = world:block_entity(10, 64, 10) -- nil when the block holds none
world:set_block_entity(10, 64, 10, chest) -- id, x, y and z in the text are ignored
for _, at in ipairs(world:block_entities(0, 0)) do -- { x, y, z } for every one in a loaded chunk
print(at[1], at[2], at[3])
endset_block_entity returns false when the block there owns no block entity, and shows the change to the players who
see it. create_block_entity(x, y, z, snbt?) gives the block a block entity (the block's defaults when the text is left
out) in place of the one there, and remove_block_entity(x, y, z) takes it away and leaves the block; both return
false when they have nothing to do.
Entities as NBT#
local snbt = world:entity_nbt(entity) -- an entity or its id
local copy = world:spawn_nbt(snbt) -- edit Pos in the text first to place it elsewhere
world:remove_entity(copy.uuid) -- takes an entity out by its uuidspawn_nbt(snbt, cause?) asks the entity.spawn event first, with cause as its reason (plugin unless given), and
returns nil when a handler cancels it.
Entities also expose uuid, yaw, pitch, velocity, on_ground, age, width, height and custom_name.
world:entity(id_or_uuid) finds one and world:entities_in(cx, cz) lists the entities a chunk would save.
Biomes and light#
Biomes are stored per 4 by 4 by 4 cell. A chunk column has world.height / 4 * 16 of them, ordered
(cell_y * 4 + cell_z) * 4 + cell_x from the lowest block up.
local biomes = world:biomes(0, 0) -- one name per cell, nil when not loaded
biomes[1] = "minecraft:desert"
world:set_biomes(0, 0, biomes) -- shows the chunk againA cell in set_biomes that is not a string keeps its biome. worlds.biomes() lists every biome name.
The server works the light out itself when blocks change, off the tick thread. These calls are for tools that edit many blocks and want to read the result or take control of it.
| Call | Does |
|---|---|
world:light_at(where, layer) |
The level (0 to 15) of "block" or "sky" light at a position, up to date; nil when the chunk is not loaded |
world:light_section(cx, sy, cz, layer, load?, fresh?) |
The light of a section as 2048 bytes, two levels each (index y * 256 + z * 16 + x, low nibble first), or nil; fresh (default true) works the light out first when blocks around changed |
world:set_light_section(cx, sy, cz, layer, bytes) |
Replaces a section's light; it stands until blocks around change and the light is worked out again |
world:check_light(positions) |
Queues the light around those blocks to be worked out again |
world:relight_chunks(chunks) |
Works the light of those chunks out again off the tick thread, then shows them again to the players near them |
world:relight_chunks({ {0, 0} })
print(world:light_at(0, 64, 0, "sky"))What blocks are#
The tables the server uses for blocks are readable, so a plugin does not need its own copy.
| Call | Gives |
|---|---|
worlds.blocks() |
{ name, first_state, state_count } for every block |
worlds.block_properties(block) |
{ name, values } for each property, in the order their values step the state id |
worlds.state_flags(id) |
blocks_motion, occludes, full_cube, replaceable, has_fluid, has_collision, conducts_redstone, ticks_randomly, air, has_block_entity, light_emission, light_opacity |
worlds.block_hardness(block) |
destroy_time, explosion_resistance, needs_tool |
worlds.block_info(id) |
friction, speed_factor, jump_factor, push_reaction, map_color, ignite_odds, burn_odds |
worlds.block_entity_types() |
Block id to the id of its block entity type, for every block that owns one |
worlds.items(), worlds.entity_types() |
The ids, in registry order |
worlds.registry(name) |
The ids of a registry by name: damage_type, particle_type, worldgen/biome and the other synchronized ones |
worlds.registry_data(registry, id) |
The data a synchronized registry holds for one entry as SNBT, or nil: damage_type, jukebox_song, instrument, wolf_variant and the rest |
worlds.tags(registry) |
Tag id to member ids, for blocks, items, fluids or entity_types |
local info = worlds.block_info(worlds.state_id("minecraft:ice"))
print(info.friction, info.push_reaction) -- 0.98 normalOld saved data#
Data written by an older version can be brought up to date with the same fixes the server applies to its own worlds. Block states and biome ids are only fixed from data version 2832 (1.18) on; older text comes back unchanged.
| Call | Gives |
|---|---|
worlds.data_version() |
The data version the server writes |
worlds.fix_nbt(kind, snbt, from_version) |
SNBT upgraded from from_version; kind is chunk, block_entity, entity or item |
worlds.fix_name(kind, text, from_version) |
A block_state (minecraft:oak_stairs[facing=east]), item id or biome id upgraded |
print(worlds.fix_name("block_state", "minecraft:grass", 3000)) -- minecraft:short_grassThreads#
These calls follow the rules of the rest of the API: a plugin runs in its own lane, and the world hands the work to the thread that owns it. Reading a loaded section is cheap from any lane; a write is applied before the call returns.