Read the player and the world
This guide shows how to read the local player, the world state, the items the player carries and the blocks around it with xiao.game.
Where you can read
xiao.game is readable only during a frame: packet receivers, client signal callbacks (think, chat, damage...),
timers, on_frame and paint listeners. At the top level of a script, in on_unload and the unload signal, and inside
a packet hook or a cmd hook, it returns empty results (nil, empty lists, in_world = false). See
Execution contexts.
The client takes one snapshot per frame: every call during that frame returns the same values. Items are read the first
time a frame asks for them, then reused until the next frame. Nothing waits on the game: a value that cannot be read right
now is nil or absent, so always test before use.
The local player New
xiao.game.player() returns the full player state, or nil outside a world.
Every field except id, name, x, y, z and position is absent when the client could not read it this frame.
hunger, saturation, level and experience come from the server attribute packets. Each one is known after the
server sent it once (it sends a value when it changes), also when the client was injected in the middle of a game.
xiao.signal.add("think", "player_once", function()
local me = xiao.game.player()
if me == nil then return end
print(("hp %.0f/%.0f food %s level %s"):format(
me.health or 0, me.max_health or 0, tostring(me.hunger), tostring(me.level)))
xiao.signal.remove("think", "player_once")
end)
xiao.game.local_player() still exists and returns only the position as a vector3.
The world New
xiao.game.world() never returns nil: outside a world in_world is false.
Use the world_change signal to react to changes instead of comparing session every frame, see
Events.
Items New
All these functions return nil when unreadable.
An item has name ("minecraft:diamond_sword", "" for an empty slot), count (0 for
an empty slot), enchanted, and when relevant durability (0 to 1, items that wear). Armor slots also have damage
and max_damage. The aux value of a stack is not read.
local function describe(item)
if item.name == "" then return "empty" end
local text = ("%s x%d"):format(item.name:gsub("^minecraft:", ""), item.count)
if item.enchanted then text = text .. " (enchanted)" end
if item.durability then text = text .. (" %.0f%%"):format(item.durability * 100) end
return text
end
xiao.signal.add("think", "items_once", function()
local hotbar = xiao.game.hotbar()
local armor = xiao.game.armor()
if hotbar == nil or armor == nil then return end
for slot, item in ipairs(hotbar) do
print("hotbar", slot, describe(item))
end
local names = { "head", "chest", "legs", "feet" }
for index, piece in ipairs(armor) do
if piece.max_damage then
print(names[index], piece.name, piece.max_damage - piece.damage, "uses left")
end
end
xiao.signal.remove("think", "items_once")
end)
Blocks New
xiao.game.block(x, y, z) returns a block (name, x, y, z) with coordinates
rounded down. On failure it returns nil and a reason.
The player position is at eye height (feet plus 1.62), so the block under the feet of a standing player is
xiao.game.block(me.x, me.y - 2, me.z).
local function block_under(me)
local block, reason = xiao.game.block(me.x, me.y - 2, me.z)
if block == nil then return "? (" .. tostring(reason) .. ")" end
return block.name
end
A scan of many blocks hits the 1024 calls per frame budget. Spread large scans over several frames and handle "busy"
and "budget" by trying again on the next frame.
Entities around you
xiao.game.players() and xiao.game.entities() return the visible players and mobs, each an
entity with id, name, x, y, z and position.
New fields
New
: type ("minecraft:zombie", present when the client saw the spawn
packet), is_player, and distance to the local player when known. health and max_health are present when known.
local function nearest_mob(max_distance)
local best
for _, mob in ipairs(xiao.game.entities()) do
if mob.distance and mob.distance < max_distance and (best == nil or mob.distance < best.distance) then
best = mob
end
end
return best
end
Full example: status panel
local paint = xiao.paint
local white = paint.color.rgb(255, 255, 255)
local shadow = paint.color.rgb(0, 0, 0)
local cursor = paint.point.vector2(20, 0)
local function short(name)
return (name:gsub("^minecraft:", ""))
end
paint.add("status", function(width, height)
local me = xiao.game.player()
if me == nil then return end
local under = xiao.game.block(me.x, me.y - 2, me.z)
local held = xiao.game.held_item()
local mob = nil
for _, entity in ipairs(xiao.game.entities()) do
if entity.distance and (mob == nil or entity.distance < mob.distance) then mob = entity end
end
local lines = {
("hp %.0f / %.0f food %s lvl %s"):format(me.health or 0, me.max_health or 0,
tostring(me.hunger), tostring(me.level)),
("yaw %.0f pitch %.0f %s"):format(me.yaw or 0, me.pitch or 0, me.on_ground and "ground" or "air"),
("under %s hand %s"):format(under and short(under.name) or "?", held and short(held.name) or "empty"),
("dimension %s"):format(xiao.game.world().dimension_name or "?"),
mob and ("nearest mob %s %.1fm"):format(short(mob.type or "?"), mob.distance) or "no mob in view",
}
for index, line in ipairs(lines) do
cursor.y = 200 + index * 16
paint.text.left(cursor, line, white, shadow)
end
end)
Pitfalls and limits
-
Only
id,name,x,y,z,position(andis_playeron entities) are always present: useorfallbacks ornilchecks for the rest. -
Positions of
player()are at eye height, positions of other entities are the values the client has; test withxiao.game.world_to_screenwhen you draw. -
Do not keep entity tables across frames expecting them to update: read again each frame.
-
A packet hook cannot read
xiao.game: copy what it needs into an upvalue fromthink, see Modify packets.
xiao.game reads the game. The settings of the client itself (ESP, aim, HUD positions...) are read and changed by key
with xiao.config, which works everywhere except inside a packet hook or a cmd hook.
Related pages: xiao.game, xiao.config, Draw an overlay, React to events.