xiao.cmd New
The Bedrock version of the Source engine usercmd / CreateMove: a callback receives a cmd object once per game tick and can read or change the view angles, the movement and the input buttons of the local player.
At a glance
Bedrock has two places where this input exists, so there are two events with the same cmd object:
The overview explains the two layers, when to use which, the order of one tick and the time budget.
The cmd object:
Page map
A cmd callback runs outside the render thread, with the same restrictions as a packet hook: see Execution contexts.
Functions
xiao.cmd.hook(event, identity, callback)
xiao.cmd.hook(event: "createmove" | "authinput", identity: any, callback: function(cmd: cmd))
Installs a callback. Callbacks of one event run in the order they were added, all on the same cmd: the second one sees the changes of the first. A new callback takes effect at the next frame.
Returns: nothing.
Errors:
xiao.cmd.hook("createmove", "sprint", function(cmd)
if cmd:get_forward_move() > 0 then cmd:set_button("sprint_down") end
end)
xiao.cmd.unhook(event, identity): boolean
xiao.cmd.unhook(event: "createmove" | "authinput", identity: any): boolean
Removes a callback. When the last callback of an event is removed the layer costs nothing again.
Returns: true if the callback existed, false otherwise.
Errors: bad argument #1 to 'unhook' ('createmove' or 'authinput' expected).
xiao.cmd.unhook("createmove", "sprint")
xiao.cmd.current(): cmd | nil
xiao.cmd.current(): cmd | nil
Returns: the cmd of the running callback, nil anywhere else. Useful in a helper function that does not receive the cmd as an argument.
local function jump()
local cmd = xiao.cmd.current()
if cmd then cmd:set_button("jump_down") end
end
xiao.cmd.buttons: table
Every input button of the game build, name to index (xiao.cmd.buttons.start_sprinting == 25). The full table is in buttons. A button can be given by name or by index to every button method.
xiao.cmd.button_name(index): string | nil
Returns: the name of a button index (0 to 65), nil for an index the schema does not name (66 to 127).
Errors: bad argument #1 to 'button_name' (integer out of range) for a value that is not an integer from 0 to 127.
xiao.cmd.available(event): boolean
xiao.cmd.available(event: "createmove" | "authinput"): boolean
Returns: true when the layer is installed. createmove needs the client tick hook of the pathfinder (ClientInputUpdateSystem), authinput needs the network capture. Updated once per frame. A layer that is not available never calls its callbacks; the other one keeps working.
Errors: bad argument #1 to 'available' ('createmove' or 'authinput' expected).
if not xiao.cmd.available("createmove") then print("createmove is not installed") end
xiao.cmd.stats(): table
xiao.cmd.stats(): table
Returns: the counters since the VM started.
local s = xiao.cmd.stats()
print(s.createmove.calls, s.authinput.avg_us)
Methods shared by both layers
cmd:valid(): boolean
cmd:valid(): boolean
Returns: true only inside the callback that received the cmd. Every other method raises cmd is only valid inside its createmove or authinput callback once the callback returned, so do not keep a cmd for later: copy the numbers you need. tostring(cmd) gives xiao.cmd(createmove, 1234), or xiao.cmd(expired).
cmd:layer(): string
cmd:layer(): "createmove" | "authinput"
Returns: the event of the callback that received the cmd.
cmd:command_number(): number
cmd:command_number(): number | nil
Returns: the Client Tick of the PlayerAuthInputPacket of this tick (the tick the server uses to order inputs). In authinput it is read in the packet. In createmove it is the tick of the packet that will leave with this input: the previous packet tick plus one (one packet per tick, checked on 854 consecutive real packets), so a createmove and the authinput of the same tick give the same number. nil in createmove before the first packet of the session was seen. stats().createmove.matched counts the packets that carried the announced number.
cmd:native_aim(): boolean
cmd:native_aim(): boolean
Returns: true in authinput when the client aim (aura, aim assist on the server packet) already rewrote the angles of this packet, always false in createmove. See priority.
cmd:get_position(): number, number, number
cmd:get_position(): number, number, number
Returns: x, y, z, at eye height like xiao.game.local_player(). In authinput, the position written in the packet (read only). In createmove, the position of the player before the tick is simulated.
xiao.cmd.hook("authinput", "where", function(cmd)
local x, y, z = cmd:get_position()
if y < 0 then print("below zero", x, z) end
end)
cmd:get_mouse_delta(), cmd:set_mouse_delta(dx, dy)
Errors: always: mouse_delta is not available: Bedrock applies mouse look per frame (CameraInjectInputLookDeltaSystem), not in the 20 Hz input tick; use set_view_angles. A delta set in a tick would only move the camera frames later and never in this tick. Use set_view_angles instead.
Complete example
Walk forward and jump while a toggle is on, and print the cost of the layer every 5 seconds. More scripts are in examples.
local walking = true
local elapsed = 0
xiao.cmd.hook("createmove", "walker", function(cmd)
if not walking then return end
cmd:set_move(1, 0, 1)
cmd:set_button("sprint_down")
end)
xiao.signal.add("think", "walker_cost", function(dt)
elapsed = elapsed + dt
if elapsed < 5 then return end
elapsed = 0
local s = xiao.cmd.stats().createmove
print(("walker: %d ticks, %.1f us average, %d busy"):format(s.calls, s.avg_us, s.busy))
end)