Send packets
This guide shows the three ways to send, and how to start from a captured packet. For every function see Sending.
Before you start
send, writer:send, send_raw and send_fields do not raise on a send failure, they return false, error. The most common one is no outgoing packet seen yet: join a world first: the client has not sent any packet yet, so it does not know the connection. Always check the result. A fields table that cannot be encoded (missing field runtime_id) is a script error: send_fields raises it, like encode.
The examples below send an Animate packet (a swing of the main hand). Its payload, as the decoder reads it, is action (byte, 1 is swing), runtime_id (unsigned varint 64), data (float), has_animation_source (bool) and hand (byte, 0 is the main hand). The runtime id comes from xiao.game.player(), which is readable only during a frame, so the sends run from a think callback.
1. Start from a capture: Copy as Lua New
The fastest way: open the packet log, pick a packet and use Copy as Lua. You get a script with named values on top and write_* calls below, see Copy as Lua. Edit the values, press Execute.
2. write_* on the current message
xiao.signal.add("think", "swing_once", function()
local me = xiao.game.player()
if me == nil then return end
xiao.signal.remove("think", "swing_once")
xiao.packet.start("Animate")
xiao.packet.write_byte(1)
xiao.packet.write_varuint64(me.id)
xiao.packet.write_float(0)
xiao.packet.write_bool(false)
xiao.packet.write_byte(0)
local ok, err = xiao.packet.send()
if not ok then print(err) end
end)
xiao.packet.cancel() drops a message you started and do not want. A write_* or send without start raises packet.write_byte called without packet.start (or packet.send called without packet.start).
3. A writer object
A writer is independent and reusable, and its methods chain:
xiao.signal.add("think", "swing_three", function()
local me = xiao.game.player()
if me == nil then return end
xiao.signal.remove("think", "swing_three")
local w = xiao.packet.writer("Animate")
w:write_byte(1):write_varuint64(me.id):write_float(0):write_bool(false):write_byte(0)
for i = 1, 3 do
local ok, err = w:send()
if not ok then print(err) break end
end
print(w:size(), "bytes")
end)
4. From a fields table New
When you know the packet by its field names, skip the byte layout. Decode a payload, edit it, send it back. Here the payload is the MovePlayer saved to disk in Read packets:
local f = xiao.packet.fields("MovePlayer", xiao.fs.read("saved_move.bin"))
if f ~= nil and f._error == nil then
f.position.y = f.position.y + 2
local ok, err = xiao.packet.send_fields("MovePlayer", f)
if not ok then print(err) end
end
The encoder fills what it can compute: has_* presence bytes, *_length and list counts. Enum values take the names of the game schema (action = "swing"); the old names still encode, see Renamed fields. Details and errors are in Encoding from fields.
The Animate of part 2, written with names instead of bytes:
xiao.signal.add("think", "swing_fields", function()
local me = xiao.game.player()
if me == nil then return end
xiao.signal.remove("think", "swing_fields")
local ok, err = xiao.packet.send_fields("Animate", {
action = "swing",
runtime_id = me.id,
data = 0,
hand = "mainhand",
})
if not ok then print(err) end
end)
Every field the packet reads must be given: without runtime_id, data or hand the call raises missing field runtime_id (then data, hand); only has_*, *_length and list counts are computed.
To get the bytes without sending, use xiao.packet.encode(packet, fields): it raises the same encoding errors and has no size limit.
To fill a writer from fields, use w:fields(fields). It replaces the payload and raises on an encoding error:
local f = xiao.packet.fields("MovePlayer", xiao.fs.read("saved_move.bin"))
local w = xiao.packet.writer("MovePlayer")
w:fields(f)
print(w:send())
5. The send queue New
A send takes one of 8 places and waits for the next packet the game sends on the same connection. Check the queue before a burst, and stop once the job is done:
local sent = 0
local f = xiao.packet.fields("MovePlayer", xiao.fs.read("saved_move.bin"))
xiao.signal.add("think", "burst", function()
if f == nil or sent >= 20 then
xiao.signal.remove("think", "burst")
return
end
while sent < 20 and xiao.packet.pending() < xiao.packet.capacity() do
local ok, err = xiao.packet.send_fields("MovePlayer", f)
if not ok then
print(err)
break
end
sent = sent + 1
end
end)
Sends leave in the order of the calls. A send not flushed within 2 seconds is dropped. Packet hooks never see the packets a script sends.
Limits to remember
All limits are in Limits.
Troubleshooting
Related pages: Sending, Read packets, Modify packets.