Skip to content

The world

Lesson 12.4

Mods often look at the world or change it: is there water here? Put a block there. This lesson covers worlds, positions and reading and placing blocks.

Worlds

The game calls a world a level. The overworld, the Nether and the End are three levels. You get one from:

  • an event: event.world (or event.level),
  • a player: player.world,
  • the server: cl.level.server_overworld().

A level can tell you a few things about itself. day_time keeps counting up from day to day, so % 24000 gives the time within the current day:

local world = player.world
print(world:dimension())   --> minecraft:overworld
print(world:day_time() % 24000)  --> 6000 (noon; a day is 24000 ticks)
print(world:game_time())         --> 183520 (ticks since the world was created)

Positions

A block position is three whole numbers. cl.pos(x, y, z) makes one:

local p = cl.pos(10, 64, -5)
print(p)              --> (10, 64, -5)
print(p:above())      --> (10, 65, -5)
print(p:below(3))     --> (10, 61, -5)
print(p:offset("north"))  --> (10, 64, -6)

offset takes a direction: "up", "down", "north" (towards negative z), "south", "west" (negative x) or "east", and an optional distance. Positions can be added and subtracted: p + cl.pos(1, 0, 0).

A player’s pos has decimals, because players stand anywhere inside a block. cl.pos rounds it down to the block they are in:

local feet = cl.pos(player.pos)        -- the block the player's feet are in
local below = feet:below()             -- the block they stand on

The other way round, p:center() gives the middle of a block as a position with decimals, which is what sounds and teleports want:

player:teleport(cl.pos(0, 100, 0):center())   -- (0.5, 100.5, 0.5)

Reading blocks

world:get_block(pos) returns the block state at a position. A state is a number that stands for one exact block, including details like which way stairs face. To ask what it is, use the registry helpers:

local state = world:get_block(below)

if cl.registry.block_state_is(state, "minecraft:grass_block") then
  player:send_message("You are standing on grass.")
end

-- the block's id as a string
local id = cl.registry.block_name(cl.registry.block_state_block(state))
print(id)   --> minecraft:grass_block

Reading a block in a part of the world that isn’t loaded raises a not_loaded error. Stick to blocks near players.

Placing blocks

world:set_block(pos, block) places a block. The block can be written as an id:

world:set_block(feet:below(), "minecraft:gold_block")

This is what you want nearly always: the neighbours are updated (so water flows and redstone reacts) and every player sees the new block straight away, like when a player places it.

An optional third argument, the flags, says who hears about the change. Leaving it out is the same as "all":

Flags Means
"all" (the default) Update neighbours and send to players.
"update_clients" Only send to players.
"update_neighbors" Only update neighbours. Players don’t see it until they reload the chunk.

To remove a block, place air: world:set_block(pos, "minecraft:air"). Your own blocks work too: "my_mod:ruby_block".

Blocks can only be placed on the server side, in server events like block.break or player.join, in commands, or in server timers.

Building something

Loops and positions together make little builders. A 5 x 5 glass floor under the player:

local function glass_floor(player)
  local world = player.world
  local centre = cl.pos(player.pos):below()
  for dx = -2, 2 do
    for dz = -2, 2 do
      world:set_block(centre + cl.pos(dx, 0, dz), "minecraft:glass")
    end
  end
end

Each set_block is quick, but thousands in one tick are not. For big builds, spread the work over several ticks with the timers from Timers and tasks.

Try it

When a player breaks a block of gold ore, replace the block below it with a gold block, and tell the player. Leave everything else alone.

Show a solution
cl.on("block.break", function(event)
  if event.block ~= "minecraft:gold_ore" then return end
  local below = event.pos:below()
  event.world:set_block(below, "minecraft:gold_block")
  event.player:send_action_bar("Lucky! There is gold below.")
end)

event.pos is already a block position, so below() works on it directly.

Recap: get a level from an event or player, make positions with cl.pos, read blocks with get_block and the registry helpers, and place them with set_block(pos, id).