xiao.game

Players, mobs, the local player, items, world state and blocks, read from the game each frame.

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

Function Returns What it gives
players() entity[] visible players
entities() entity[] visible mobs
player() New player \| nil full local player state
world() New world world and session state
hotbar() New item[] \| nil the 9 hotbar slots
inventory() New item[] \| nil the 36 inventory slots
armor() New item[] \| nil the 4 armor slots
offhand() New item \| nil the off hand slot
selected_slot() New number \| nil selected hotbar slot
held_item() New item \| nil item in hand
block(x, y, z) New block \| nil, string block at a position
state(name) New boolean \| nil a movement state of the local player
set_state(name, enabled) New nothing force a movement state, nil gives it back
requested(name) New boolean \| nil the request of set_state
local_player() vector3 \| nil local position only (older scripts)
world_to_screen(...) vector2 \| nil project a world point
time() number seconds since system start

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.


Entities

xiao.game.players(): entity[]

xiao.game.players(): entity[]

Returns an array of entity for the visible players.

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 for the visible mobs.

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 player, or nil 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 state.

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:

Name Read from (local player, this frame) Forced on the client (createmove) Forced on the server (authinput)
"sprinting" actor flag 3 sprint_down key and sprint state sprinting, sprint_down, start_sprinting / stop_sprinting
"sneaking" actor flag 1 sneak_down key and sneak state sneaking, sneak_down, start_sneaking / stop_sneaking
"swimming" actor flag 57 no start_swimming / stop_swimming
"gliding" actor flag 32 no start_gliding / stop_gliding
"crawling" actor flag 114 no start_crawling / stop_crawling
"flying" flying ability (what the game uses, including the Fly module) no start_flying / stop_flying
"jumping" the game's "is jumping" marker (query.is_jumping) jump_down key and jump state jumping, jump_down (no start or stop button exists)

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.

Name Type Description
name string one of the seven names above
enabled boolean or nil true forces on, false forces off, nil releases

The request is applied at every tick, by the two layers of xiao.cmd, without any cmd callback:

  • client (createmove): for sprinting, sneaking and jumping, 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 carries start_<state> (or stop_<state> for false) when the server was not told yet, every packet keeps the held buttons (sprinting, sneaking, jumping and 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_swimming after a forced swim the client does not do, for example). jumping has no transition: start_jumping stays 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

Layer Order, last one wins
client input of a tick keyboard, pathfinder keys (Fly reads the input at this point), set_state, xiao.cmd createmove callbacks
PlayerAuthInputPacket the game, native aim, set_state, silent angles of lock_angles, xiao.cmd authinput callbacks, xiao.packet.hook
  • A createmove callback sees the forced state (cmd:has_button("sprint_down") is true) and can still change it for that tick: xiao.cmd keeps the last word, set_state is the default it starts from.

  • The pathfinder and Fly run before the request in the same tick: a forced sneaking or jumping is 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. Read state() to see what the client actually does.

  • swimming, gliding, crawling and flying are 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 the flying ability). 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.

  • flying on the server without the mayfly permission 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 createmove layer (xiao.cmd.available("createmove") is false), only the server layer works.


Items

An empty slot is an item with name = "" 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.

Name Type Description
x, y, z number block coordinates, from -33554432 to 33554432

Returns: a block, or nil and a reason.

Errors: bad argument #1 to 'block' (block coordinate out of range) for a coordinate that is not finite or past 33554432.

Reason Meaning
"unloaded" the chunk or the height is not loaded on the client
"busy" the block scanner holds the chunk data right now, try again next frame
"no_world" no local player
"budget" more than 1024 calls in this frame
"failed" the chunk changed while it was read
"unavailable" called outside a frame
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.

Name Type Description
point vector3 world position
x, y, z number world position, finite numbers

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

Field Type Description
id number runtime id
name string entity name
x, y, z number position
position vector3 position as a vector3
is_player New boolean true for a player
type New string type identifier ("minecraft:zombie"), present when the client saw its spawn packet
distance New number distance to the local player, present when the local player is known
health New number present when known
max_health New number present when known

player New

Field Type Description
id number runtime id
name string player name, empty when unknown
x, y, z number position (eyes height, like local_player())
position vector3 position as a vector3
pitch number degrees, negative looks up
yaw number degrees
velocity vector3 blocks per tick (20 ticks per second)
on_ground boolean standing on a block
health, max_health number health points
hunger number food points, 0 to 20
saturation number saturation points
level number experience level
experience number progress to the next level, 0 to 1
selected_slot number selected hotbar slot, 1 to 9
sprinting, sneaking, swimming, gliding, crawling, flying, jumping New boolean movement states, same values as state(name)
dimension number 0 overworld, 1 nether, 2 the end
dimension_name string "overworld", "nether" or "the_end"

Every field except id, name and the position can be absent when the client could not read it this frame.

world New

Field Type Description
in_world boolean the local player is bound
session number network session counter, changes on StartGame, ChangeDimension, disconnect and server change
dimension number 0, 1 or 2, absent when unknown
dimension_name string "overworld", "nether" or "the_end", absent when unknown

The world_change signal fires when one of these fields changes.

item New

Field Type Description
name string item identifier ("minecraft:diamond_sword"), "" for an empty slot
count number stack size, 0 for an empty slot
enchanted boolean the item has enchantments
durability number remaining durability from 0 to 1, present for items that wear
damage number damage taken, armor and off hand slots only
max_damage number maximum damage, armor and off hand slots only
slot number held_item() only, 1 to 9

The aux value of a stack is not read.

block New

Field Type Description
name string block identifier ("minecraft:stone", "minecraft:air")
x, y, z number block coordinates, rounded down

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)