World access
Lesson 12.8
world:get_block reads one block. Real mods ask bigger questions: what is the player looking at, what is this room made of, is it dark here, what is in that chest, which blocks just changed? This lesson answers them, for vanilla blocks and your own.
Look along a line
local eye = cl.vec(player:eye_position())
local look = cl.vec(player:look_vector())
local hit = world:raycast(eye, eye + look * 10, { blocks = "outline" })
if hit.type == "block" then
player:send_message("You look at " .. cl.registry.block_state_to_string(hit.state) .. " (" .. hit.face .. " face)")
end
A ray stops at the first thing in its way and tells you what it hit (type is "miss", "block" or "entity"), where (pos, block_pos, face) and how far (distance). The options choose what counts:
blocks = "collider"(the default) stops at what you bump into,"outline"at what the crosshair picks,"visual"lets glass, panes and bars through,"none"ignores blocks.fluids = "any"(or"source","water") stops at water and lava too.entities = truestops at mobs and players;ignore = playerleaves yourself out.
Many blocks in one call
local lo, hi = cl.pos(x - 4, y - 2, z - 4), cl.pos(x + 4, y + 2, z + 4)
local states = world:get_blocks(lo, hi) -- 9 * 5 * 9 = 405 numbers
local stone = 0
for _, s in ipairs(states) do
if cl.registry.block_state_is(s, "minecraft:stone") then stone = stone + 1 end
end
The list goes x first, then z, then y. One call is much faster than hundreds of get_block calls. world:snapshot(lo, hi) keeps such a copy for later: snap:get(pos) always answers from the moment you took it.
Doors, crops and other properties
Many blocks have properties: a door is open or not, wheat has an age, stairs face somewhere.
local door = world:get_block(pos)
if cl.registry.state_property(door, "open") then
world:set_block_property(pos, "open", false) -- close it
end
world:set_block(pos2, "minecraft:wheat[age=7]") -- fully grown
print(cl.registry.state_property("minecraft:wheat", "age")) -- 0
state_property gives true / false, a number or a string, and nil when the block has no such property.
What a block is like
local info = cl.registry.block_info(world:get_block(pos))
if info.sound_type == "wood" then
player:send_message("Creaky!")
end
print(info.hardness, info.light_emission, info.solid)
sound_type ("stone", "wood", "grass", "glass", "wool", …) is the material that decides footsteps and breaking sounds. There is also is_air, is_liquid, friction, map_color and more.
Light and height
local light = world:light(pos) -- { block = 0..15, sky = 0..15, combined = ... }
if light.combined < 8 then player:send_message("It is dark here") end
print(world:can_see_sky(pos))
local ground = world:heightmap(pos.x, pos.z) -- the y just above the ground
Chests and other containers
On the server you can look inside containers:
local chest = world:container(pos)
if chest then
chest.set(0, cl.stack.create("minecraft:diamond", 3))
local first = chest.get(1) -- nil for an empty slot
end
Slots start at 0. A double chest is one container with 54 slots. Block entity data comes as SNBT text, like /data get block: world:block_entity(pos).
Hear the world change
cl.on("level.block_changed", function(e)
if cl.registry.block_state_is(e.new_state, "minecraft:tnt") then
cl.log("TNT placed at %d %d %d", e.pos.x, e.pos.y, e.pos.z)
end
end, { filter = "minecraft:tnt" })
The event tells you about every block that changes (players, pistons, water, explosions, commands). The filter keeps only changes to one block, so your function runs rarely.
Exercise
Write a command /scan that counts the ores in a 16 x 16 x 16 box around the player with one get_blocks call and tells the player how many diamond and iron ores it found.
Show a solution
cl.on("server.command_register", function()
cl.cmd.register(cl.cmd.literal("scan"):executes(function(ctx)
local p = ctx:player()
if not p then return 0 end
local o = p.pos
local x, y, z = math.floor(o.x), math.floor(o.y), math.floor(o.z)
local states = ctx:level():get_blocks(cl.pos(x - 8, y - 8, z - 8), cl.pos(x + 7, y + 7, z + 7))
local diamonds, iron = 0, 0
for _, s in ipairs(states) do
if cl.registry.block_state_is(s, "minecraft:diamond_ore") or cl.registry.block_state_is(s, "minecraft:deepslate_diamond_ore") then
diamonds = diamonds + 1
elseif cl.registry.block_state_is(s, "minecraft:iron_ore") or cl.registry.block_state_is(s, "minecraft:deepslate_iron_ore") then
iron = iron + 1
end
end
p:send_message(("%d diamond and %d iron ores nearby"):format(diamonds, iron))
return 1
end))
end)
Recap: raycast looks along a line, get_blocks and snapshot read many blocks at once, state_property / state_with / set_block_property handle properties, block_info tells what a block is made of, light, can_see_sky and heightmap measure the world, container and block_entity look inside blocks, and level.block_changed tells you when anything changes. More in the guide World Access.