xiao.game
Read the visible players and mobs, the local player, its items, the world state and the blocks around, and project world positions to the screen.
At a glance
Frame only
These functions read the snapshot of the current frame: on_frame, client signal callbacks (think, chat, damage...), timers and paint listeners. Anywhere else, including the main chunk, a packet hook and a cmd hook, there is no frame: lists are empty, player() and the items are nil, state() is nil, world().in_world is false and block returns nil, "unavailable". Read what you need in think and keep it in an upvalue. set_state and requested work in the frame and in the main chunk; set_state raises an error in a hook. See Execution contexts and xiao.signal.
The client takes one snapshot per frame: every call during that frame returns the same values. Items are read from the game the first time a frame asks for them (hotbar, inventory, armor, offhand, held_item), then reused until the next frame. Nothing here waits on the game: a value that cannot be read right now is nil or absent, so always test before use.
Always on
New
players() and entities() are filled whenever a Lua VM is running, even when the ESP is switched off in the menu.
Entities
xiao.game.players(): entity[]
xiao.game.players(): entity[]
Returns an array of entity
Returns: an array, empty when nothing is visible or outside a frame.
for _, p in ipairs(xiao.game.players()) do
print(p.name, p.distance)
end
xiao.game.entities(): entity[]
xiao.game.entities(): entity[]
Returns an array of entity
Returns: an array, empty when nothing is visible or outside a frame.
for _, mob in ipairs(xiao.game.entities()) do
if mob.type == "minecraft:creeper" and mob.distance and mob.distance < 6 then
print("creeper close", mob.distance)
end
end
Local player
xiao.game.player(): player | nil New
xiao.game.player(): player | nil
Returns the local player state.
Returns: a playernil outside a world. A field the client could not read this frame is absent.
local me = xiao.game.player()
if me then print(me.health, me.dimension_name) end
xiao.game.local_player(): vector3 | nil
xiao.game.local_player(): vector3 | nil
Returns the local player position at eye height. Kept for older scripts, xiao.game.player() returns the full state.
Returns: a vector3, or nil when the local player is unknown.
local position = xiao.game.local_player()
if position then print(position.x, position.y, position.z) end
xiao.game.world(): world New
xiao.game.world(): world
Returns the world
Returns: a table, never nil. Outside a world, or outside a frame, in_world is false.
if not xiao.game.world().in_world then return end
State New
Seven movement states of the local player can be read and forced:
The actor flags are the ones the game itself reads for query.is_sprinting, query.is_swimming... They are the state
the client simulates, which can differ for a moment from what the server decided.
xiao.game.state(name: string): boolean | nil New
xiao.game.state(name: string): boolean | nil
Returns one state of the local player in this frame. xiao.game.player() carries the same values as fields.
Returns: true or false, or nil outside a frame, outside a world or when the value could not be read.
Errors: bad argument #1 to 'state' (state name expected (sprinting, sneaking, swimming, gliding, crawling, flying, jumping)).
xiao.signal.add("think", "swim", function()
if xiao.game.state("swimming") then print("swimming") end
end)
xiao.game.set_state(name: string, enabled: boolean | nil) New
xiao.game.set_state(name: string, enabled: boolean | nil)
Forces a state on or off until it is called again. nil (or no second argument) gives the state back to the game.
The request is applied at every tick, by the two layers of xiao.cmd, without any cmd callback:
-
client (
createmove): forsprinting,sneakingandjumping, the key and the state bit of the input are written before the tick is simulated, so the client moves with them and builds its packet from them; -
server (
authinput): the first packet after the request carriesstart_<state>(orstop_<state>forfalse) when the server was not told yet, every packet keeps the held buttons (sprinting,sneaking,jumpingand their keys) and removes the opposite transition the game would send. The first packet after the release sends the transition that brings the server back to the client state (stop_swimmingafter a forced swim the client does not do, for example).jumpinghas no transition:start_jumpingstays the game's own, one per real jump.
Errors: an unknown name (same message as state), bad argument #2 to 'set_state' (boolean or nil expected),
and xiao.game.set_state is not available inside a packet hook or a cmd hook (use cmd:set_button there).
xiao.game.set_state("sprinting", true)
xiao.timer.after(3, function() xiao.game.set_state("sprinting", nil) end)
xiao.game.requested(name: string): boolean | nil New
xiao.game.requested(name: string): boolean | nil
Returns: the current request of set_state: true, false, or nil when the game has the state. Works in any
context.
Priority
-
A
createmovecallback sees the forced state (cmd:has_button("sprint_down")istrue) and can still change it for that tick:xiao.cmdkeeps the last word,set_stateis the default it starts from. -
The pathfinder and Fly run before the request in the same tick: a forced
sneakingorjumpingis not seen by Fly (it reads the jump and sneak keys of the game), and it replaces the keys the pathfinder pressed for that state only. -
Only the requested states are touched:
set_state("sneaking", true)leaves sprinting to the game.
Limits
-
The simulation decides, not the request. Sprinting needs a forward movement and enough food; sneaking stops sprinting; a sneak toggle of the game can keep the player sneaking after a forced
false. Readstate()to see what the client actually does. -
swimming,gliding,crawlingandflyingare only told to the server: the client keeps simulating what it decides (swim in water while sprinting, glide with an elytra after a jump in the air, crawl under one block, fly with theflyingability). A server that disagrees corrects the position (rubberband) or ignores the transition. Paired with a client that does the same thing (in water, with an elytra), the transition is just sent earlier or kept. -
flyingon the server without themayflypermission is refused by a vanilla server. -
Clear VM, the unload of the client and a new script release every request. The client input is the game's again at the next tick; the next packet carries the
stop_*the server still needs. If no packet leaves before the client unloads, the game's next transition repairs it. -
Without the
createmovelayer (xiao.cmd.available("createmove")isfalse), only the server layer works.
Items
An empty slot is an itemname = "" and count = 0.
xiao.game.hotbar(): item[] | nil New
xiao.game.hotbar(): item[] | nil
Returns the 9 hotbar slots, index 1 is the leftmost slot.
Returns: an array of 9 items, or nil when unreadable.
local hotbar = xiao.game.hotbar()
if hotbar then print(hotbar[1].name, hotbar[1].count) end
xiao.game.inventory(): item[] | nil New
xiao.game.inventory(): item[] | nil
Returns the 36 inventory slots. Slots 1 to 9 are the hotbar.
Returns: an array of 36 items, or nil when unreadable.
local free = 0
for _, item in ipairs(xiao.game.inventory() or {}) do
if item.count == 0 then free = free + 1 end
end
print(free .. " free slots")
xiao.game.armor(): item[] | nil New
xiao.game.armor(): item[] | nil
Returns the 4 armor slots in order head, chest, legs, feet.
Returns: an array of 4 items, or nil when unreadable. Armor items carry damage and max_damage.
local armor = xiao.game.armor()
if armor then print("helmet", armor[1].name) end
xiao.game.offhand(): item | nil New
xiao.game.offhand(): item | nil
Returns the off hand slot.
Returns: an item (with damage and max_damage for an item that wears, a shield for example), or nil when unreadable.
local off = xiao.game.offhand()
print(off and off.name or "unreadable")
xiao.game.selected_slot(): number | nil New
xiao.game.selected_slot(): number | nil
Returns the selected hotbar slot.
Returns: 1 to 9, or nil when unknown.
print("slot", xiao.game.selected_slot())
xiao.game.held_item(): item | nil New
xiao.game.held_item(): item | nil
Returns the item in the selected hotbar slot, with an extra slot field.
Returns: an item (slot is 1 to 9), or nil when the hand is empty or unreadable.
local held = xiao.game.held_item()
print(held and held.name or "empty hand")
Blocks
xiao.game.block(x: number, y: number, z: number): block | nil, string New
xiao.game.block(x: number, y: number, z: number): block | nil, string
Returns the block at the given position. Coordinates are rounded down.
Returns: a blocknil and a reason.
Errors: bad argument #1 to 'block' (block coordinate out of range) for a coordinate that is not finite or past 33554432.
The player position is at eye height (feet + 1.62), so xiao.game.block(p.x, p.y - 2, p.z) is the block under the feet of a standing player.
local me = xiao.game.player()
if me then
local under, why = xiao.game.block(me.x, me.y - 2, me.z)
print(under and under.name or why)
end
Screen and time
xiao.game.world_to_screen(point: vector3): vector2 | nil
xiao.game.world_to_screen(point: vector3): vector2 | nil
xiao.game.world_to_screen(x: number, y: number, z: number): vector2 | nil
Projects a world position to the screen. Both forms give the same result; point can also be a paint point.
Returns: a vector2 on screen, or nil when the point is behind the camera.
Errors: bad argument #1 to 'world_to_screen' (finite number expected) for a coordinate that is NaN or infinite.
local me = xiao.game.player()
if me then
local screen = xiao.game.world_to_screen(me.x, me.y + 1, me.z)
if screen then print(screen.x, screen.y) end
end
xiao.game.time(): number
xiao.game.time(): number
Returns: the seconds since the system started, with millisecond steps.
local started = xiao.game.time()
print("uptime", started)
Types
entity
player New
Every field except id, name and the position can be absent when the client could not read it this frame.
Server attributes
hunger, saturation, level and experience come from the server attribute packets (UpdateAttributes): each one is known after the server sent it once, which happens when it changes. An injection in the middle of a game works: the packet is matched to the local player by its runtime id (id), read in memory.
world New
The world_change signal fires when one of these fields changes.
item New
The aux value of a stack is not read.
block New
Complete example
A status panel with the player state, the block under the feet and the held item:
local paint = xiao.paint
local white = paint.color.rgb(255, 255, 255)
paint.add("status", function(w, h)
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 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 x%d"):format(under and under.name or "?", held and held.name or "empty", held and held.count or 0),
("dimension %s"):format(xiao.game.world().dimension_name or "?"),
}
for index, line in ipairs(lines) do
paint.text.left(paint.point.vector2(20, 200 + index * 16), line, white)
end
end)
Names above the players' heads:
local paint = xiao.paint
local gold = paint.color.hex("#F0C250")
paint.add("names", function(w, h)
for _, player in ipairs(xiao.game.players()) do
local head = paint.point.vector3(player.x, player.y + 2, player.z)
paint.text.center(head, player.name, gold, paint.color.rgb(0, 0, 0))
end
end)
Armor wear, printed once:
xiao.signal.add("think", "armor_once", function()
local armor = xiao.game.armor()
if armor == nil then return end
for index, piece in ipairs(armor) do
if piece.max_damage then
print(index, piece.name, piece.max_damage - piece.damage, "uses left")
end
end
xiao.signal.remove("think", "armor_once")
end)
New Auto sprint while moving (Clear VM gives sprinting back to the game):
xiao.signal.add("think", "autosprint", function()
local me = xiao.game.player()
if me == nil or me.velocity == nil then return end
local moving = math.abs(me.velocity.x) + math.abs(me.velocity.z) > 0.05
if moving ~= (xiao.game.requested("sprinting") == true) then
xiao.game.set_state("sprinting", moving or nil)
end
end)
xiao.signal.add("unload", "autosprint", function() print("sprint given back to the game") end)