Packets
Listen to, change, cancel and send packets of any connection state from Lua.
A plugin can see every packet of every connection state: handshake, status, login, configuration and play, in both directions. It needs the capability in its manifest, and the operator has to grant it, see Manifest and layout.
[capabilities]
packets = trueListening#
packet.on("clientbound.set_title_text", function(p)
p.cancelled = true -- the player never sees it
end)
packet.watch("serverbound.play.*", function(p)
log.info(p.name .. " from " .. p.player_name)
end)packet.on(name, [options,] handler) can cancel or rewrite a packet. packet.watch has the same shape but only
observes. options.priority is first, early, normal, late or last.
A name looks like clientbound.set_title_text. Add the state for packets that exist in more than one,
serverbound.play.chat, or give the id in hex, clientbound.configuration.0x07. Wildcards listen to groups:
serverbound.*, clientbound.play.* and *. /plugins packets lists the names.
| Field | Meaning |
|---|---|
name, phase, id, serverbound |
Which packet it is |
player, player_name, player_uuid |
Who it belongs to, once known |
connection |
An id for the connection, stable while it lasts |
protocol, translated |
The version the client announced, and whether it is older than the server |
bytes, size |
The body without the id, as a list of numbers from 0 to 255 |
cancelled |
Set to true to drop the packet |
Assigning a list to p.bytes replaces the body. The bytes are in the server's protocol; a client that is older gets
them translated on the way out, and what it sends arrives already translated.
Cost
A packet nobody listens to costs one atomic read. Listen to the narrowest name that does the job.
Sending#
packet.send(player, "clientbound.set_title_text", bytes) -- to one player
packet.receive(player, "serverbound.play.chat", bytes) -- as if the client had sent itInside a packet listener, the packet object can do the same on the connection it travels on:
packet.on("serverbound.play.chat", function(p)
p:send("clientbound.set_title_text", bytes)
p:receive("serverbound.play.chat", other_bytes)
p:cancel() -- the same as p.cancelled = true
end)bytes is a list of numbers from 0 to 255, the body of the packet without its id. The last argument, intercept,
defaults to false: the packet goes past the listeners. Pass true to run them on it too. All of these return false
when the player has no Java connection, which is the case for Bedrock players, and raise an error when the name is not a
packet of that direction.
Plugin channels#
Plugin messages are custom payloads on named channels, the way mods and proxies talk to a server. A plugin lists the
channels it uses in its manifest and the operator grants network, see
Manifest and layout:
[capabilities]
network = ["arena:lobby"]channel.on("arena:lobby", function(player, bytes)
-- bytes is a list of numbers from 0 to 255; player is nil when the sender is unknown
end)
channel.send(player, "arena:lobby", { 1, 2, 3 }) -- returns whether it was sentA channel that is not in the manifest raises an error that lists the ones that are. The handler answers at once and cannot
wait.