Packets
The packets global sends; the packet_send and packet_receive events
observe. Fields mirror the wire format, so coordinates arrive as raw x, y, z scalars rather
than vec3.
module:event("packet_receive", function(e)
if e.name == "sound" then e:cancel() end -- mute server-played sounds
end)
Packets are matched by stable snake_case names, the same strings e.name reports and
packets.send accepts. Every vanilla packet is mapped, in every protocol phase (handshake,
status, login, configuration, play); only a packet a mod adds arrives under its protocol id
(a name with a colon) carrying no decoded fields, still cancellable. Ids are strings
("minecraft:stone"), uuids are strings, enums are lowercase names, text components arrive
as plain strings and lists are 1-based arrays.
| Function | Does |
|---|---|
packets.list() |
Sorted names packets.send can build |
packets.send(name, fields) |
Builds and sends a C2S packet, true on success |
packets.send raises on an unknown name and returns false when a required game object is
missing. It goes through the normal client pipeline, so it fires packet_send as well: a
module that cancels its own scripted packets swallows them.
local pos = player:position()
packets.send("move_position", { x = pos.x, y = pos.y + 0.05, z = pos.z, on_ground = false })
packets.send("client_command", { mode = "start_sprinting" })
packets.send("player_action", { action = "swap_item_with_offhand", x = 0, y = 0, z = 0 })
local target = world:player("Notch")
if target then
packets.send("attack_entity", { entity_id = target:id() })
end
For attacking, breaking blocks and clicking slots, interaction is the
better tool: it gets swing timers and sequence numbers right. Raw packets are for when you
want the wire format yourself.
Sendable packets
Optional fields show their defaults. hand is "main" / "off" (default "main"), side
is "up" · "down" · "north" · "south" · "east" · "west" (default "up"), and
sequence defaults to 0.
| Name | Fields |
|---|---|
move_full |
x, y, z, yaw, pitch, on_ground = true, horizontal_collision = false |
move_position |
x, y, z, on_ground = true, horizontal_collision = false |
move_look |
yaw, pitch, on_ground = true, horizontal_collision = false |
move_on_ground |
on_ground = true, horizontal_collision = false |
client_command |
mode — "stop_sleeping" · "start_sprinting" · "stop_sprinting" · "start_riding_jump" · "stop_riding_jump" · "open_inventory" · "start_fall_flying"; jump_height = 0 |
client_status |
mode — "perform_respawn" · "request_stats" |
hand_swing |
hand |
player_action |
action — "start_destroy_block" · "abort_destroy_block" · "stop_destroy_block" · "drop_all_items" · "drop_item" · "release_use_item" · "swap_item_with_offhand" · "stab"; x, y, z, side, sequence |
attack_entity |
entity_id, sneaking = false |
interact_entity |
entity_id, hand, sneaking = false |
interact_entity_at |
entity_id, x, y, z (hit point), hand, sneaking = false |
interact_block |
x, y, z, side, hit_x/hit_y/hit_z (default block center), inside = false, hand, sequence |
interact_item |
hand, yaw, pitch (default current rotation), sequence |
select_slot |
slot — hotbar 0–8 |
close_screen |
sync_id |
teleport_confirm |
id |
keep_alive |
id |
command |
command — without the leading / |
player_input |
forward, backward, left, right, jump, sneak, sprint — all false |
sign_update |
x, y, z, front = true, line1..line4 = "" — the text a sign editor would submit |
pick_item_from_block |
x, y, z, include_data = false |
pick_item_from_entity |
entity_id, include_data = false |
block_entity_tag_query |
x, y, z, transaction_id = 0 |
bundle_item_selected |
slot, index = -1 |
change_difficulty |
difficulty — "peaceful" · "easy" · "normal" · "hard" |
change_game_mode |
mode — "survival" · "creative" · "adventure" · "spectator" |
chat_ack |
offset |
chunk_batch_received |
chunks_per_tick = 9 |
client_tick_end |
— |
command_suggestion |
command, id = 0 |
configuration_acknowledged |
— (tells the server you left the play phase: it will reconfigure you) |
container_button_click |
sync_id, button |
container_click |
sync_id, slot, button = 0, mode = "pickup", revision = 0 — mode is "pickup" · "quick_move" · "swap" · "clone" · "throw" · "quick_craft" · "pickup_all"; sent without predicted slot changes, so the server resyncs the container |
container_slot_state_changed |
slot, sync_id, state = true |
debug_subscription_request |
subscriptions — list of subscription ids |
edit_book |
slot, pages (list of strings), title (signs the book when present) |
entity_tag_query |
entity_id, transaction_id = 0 |
jigsaw_generate |
x, y, z, levels = 1, keep_jigsaws = false |
lock_difficulty |
locked = true |
move_vehicle |
x, y, z, yaw = 0, pitch = 0, on_ground = false |
paddle_boat |
left = false, right = false |
place_recipe |
sync_id, recipe, use_max_items = false — recipe is the display index from recipe_book_add |
player_abilities |
flying = false |
player_loaded |
— |
recipe_book_change_settings |
book — "crafting" · "furnace" · "blast_furnace" · "smoker"; open = false, filtering = false |
recipe_book_seen_recipe |
recipe |
rename_item |
name |
seen_advancements |
tab — advancement tab id; omit it to report the screen closed |
select_trade |
index |
set_beacon |
primary, secondary — effect ids, both optional |
set_command_block |
x, y, z, command, mode = "redstone" ("sequence" · "auto" · "redstone"), track_output = true, conditional = false, automatic = false |
set_command_minecart |
entity_id, command, track_output = true |
set_creative_mode_slot |
slot, item, count = 1 — omit item to clear the slot |
set_game_rule |
entries — list of { rule, value } |
set_jigsaw_block |
x, y, z, name, target, pool, final_state = "minecraft:air", joint = "rollable", selection_priority = 0, placement_priority = 0 |
set_structure_block |
x, y, z, update = "update_data", mode = "data", name = "", offset_x/y/z = 0, size_x/y/z = 0, mirror = "none", rotation = "none", data = "", ignore_entities = true, strict = false, show_air = false, show_bounding_box = true, integrity = 1, seed = 0 |
set_test_block |
x, y, z, mode = "start", message = "" |
spectator_action |
entity_id — omit it to stop spectating |
teleport_to_entity |
uuid |
test_instance_block_action |
x, y, z, action — "init" · "query" · "set" · "reset" · "save" · "export" · "run"; test, size_x/y/z = 0, rotation = "none", ignore_entities = false |
client_information |
language, view_distance, chat_visibility, chat_colors, model_customisation, main_hand, text_filtering, allows_listing, particle_status — each defaults to your current options |
custom_click_action |
id, payload — optional SNBT compound |
custom_payload |
brand — only the brand payload can be built |
pong |
id |
resource_pack |
id (pack uuid), action — "successfully_loaded" · "declined" · "failed_download" · "accepted" · "downloaded" · "invalid_url" · "failed_reload" · "discarded" |
cookie_response |
key, payload — optional string |
ping_request |
time — default now |
Decoded event fields
Both events always carry name. A ? marks a field that is only present when the packet
carries it; x/y/z stands for the three scalars (vel_x/y/z for vel_x, vel_y, vel_z).
Outgoing. Sendable packets decode with the same fields they take, except that move_*
carry x/y/z only when the position changed and yaw/pitch only when the look changed,
interact_entity_at arrives as interact_entity (entity_id, hand, x/y/z, sneaking),
container_click adds changed (predicted slot count) and carried_item?/carried_count?,
custom_payload carries channel and brand?, and cookie_response carries key, size?.
These go out too but cannot be built, because they are signed or belong to a phase without
a game connection:
| Name | Fields |
|---|---|
chat |
message, timestamp, salt, signed, offset |
command_signed |
command, timestamp, salt, signatures, offset |
chat_session_update |
session_id, expires_at |
accept_code_of_conduct |
— |
finish_configuration |
— |
select_known_packs |
packs (list) |
intention |
protocol_version, host, port, intention |
hello |
name, uuid |
key |
— |
custom_query_answer |
transaction_id, understood |
login_acknowledged |
— |
status_request |
— |
Incoming.
<details open> <summary>All 154 incoming packets</summary>| Name | Fields |
|---|---|
keep_alive |
id |
disconnect |
reason |
player_position_look |
teleport_id, x, y, z, vel_x, vel_y, vel_z, yaw, pitch |
entity_velocity |
entity_id, vel_x, vel_y, vel_z |
entity_position |
entity_id, x, y, z, yaw, pitch, on_ground |
entity_position_sync |
entity_id, x, y, z, yaw, pitch, on_ground |
entity_damage |
entity_id, source_cause_id, source_direct_id, source_type, source_x/y/z? — source_type is a damage type id |
damage_tilt |
entity_id, yaw |
health_update |
health, food, saturation |
explosion |
x, y, z, radius, block_count, knockback_x/y/z? — knockback only when you were pushed |
block_update |
x, y, z, block — block id |
select_slot |
slot |
open_screen |
sync_id, kind, title — kind is a menu type id like "minecraft:generic_9x6" |
container_content |
sync_id, revision, size, items (list of {item, count}), carried_item, carried_count — the server syncing every slot of a handler |
container_slot |
sync_id, revision, slot, item, count — a single slot update |
open_sign_editor |
x, y, z, front — cancel it and answer with sign_update to write a sign without the editor |
add_entity |
entity_id, uuid, kind, x, y, z, vel_x/y/z, yaw, pitch, head_yaw, data |
animate |
entity_id, action |
award_stats |
stats (list of {stat, value}) |
block_changed_ack |
sequence |
block_destruction |
breaker_id, x/y/z, progress |
block_entity_data |
x/y/z, kind, tag — tag is SNBT |
block_event |
x/y/z, type, data, block |
boss_event |
operation, id, title, progress, color, overlay, darken_screen, play_music, world_fog — operation is "add" · "remove" · "update_progress" · "update_name" · "update_style" · "update_properties"; the other fields follow the operation |
bundle |
packets (list of {name, ...}) — every packet of the bundle, decoded like a top-level one. Sub-packets do not fire on their own: match them here |
bundle_delimiter |
— |
change_difficulty |
difficulty, locked |
chunks_biomes |
chunks (list of {x, z}) |
chunk_batch_finished |
size |
chunk_batch_start |
— |
clear_titles |
reset |
commands |
— |
command_suggestions |
id, start, length, suggestions (list) |
close_screen |
sync_id |
container_data |
sync_id, property, value |
cooldown |
group, duration |
custom_chat_completions |
action, entries (list) |
debug_block_value |
x/y/z, subscription |
debug_chunk_value |
x, z, subscription |
debug_entity_value |
entity_id, subscription |
debug_event |
subscription |
debug_sample |
type, sample (list) |
delete_chat |
id |
disguised_chat |
message, chat_type, sender_name, target_name? |
entity_event |
entity_id, event |
forget_level_chunk |
x, z |
game_event |
event, value — event like "start_raining", "change_game_mode", "win_game" |
game_rule_values |
values (map) — rule id → value |
game_test_highlight_pos |
x/y/z, relative_x/y/z |
initialize_border |
center_x, center_z, old_size, size, lerp_time, max_size, warning_time, warning_blocks |
level_chunk_with_light |
x, z, plus the light fields |
level_event |
type, x/y/z, data, global |
level_particles |
particle, x, y, z, spread_x, spread_y, spread_z, speed, count, override_limiter, always_show |
light_update |
x, z, plus the light fields |
login |
entity_id, hardcore, dimensions (list), max_players, view_distance, simulation_distance, reduced_debug_info, show_death_screen, limited_crafting, online_mode, enforces_secure_chat, dimension, dimension_type, seed, game_mode, previous_game_mode?, debug, flat, portal_cooldown, sea_level, death_dimension?, death_x/y/z? — entity_id is your own |
low_disk_space_warning |
— |
map_item_data |
map_id, scale, locked, decorations? (list of {type, x, y, rotation, name?}), patch_x?, patch_y?, patch_width?, patch_height? |
merchant_offers |
sync_id, level, xp, show_progress, can_restock, offers (list of {item, count, cost_item, cost_count, cost_b_item, cost_b_count, uses, max_uses}) |
mount_screen_open |
sync_id, columns, entity_id |
move_entity_pos |
entity_id, dx?, dy?, dz?, yaw?, pitch?, on_ground — dx/dy/dz are block deltas |
move_entity_pos_rot |
entity_id, dx?, dy?, dz?, yaw?, pitch?, on_ground — dx/dy/dz are block deltas |
move_entity_rot |
entity_id, dx?, dy?, dz?, yaw?, pitch?, on_ground |
move_minecart_along_track |
entity_id, steps (list of {x/y/z, vel_x/y/z, yaw, pitch, weight}) |
move_vehicle |
x/y/z, yaw, pitch |
open_book |
hand |
place_ghost_recipe |
sync_id |
player_abilities |
invulnerable, flying, can_fly, instabuild, fly_speed, walk_speed |
player_chat |
sender, index, global_index, message, timestamp, salt, signed, unsigned_content?, chat_type, sender_name, target_name? |
player_combat_end |
duration |
player_combat_enter |
— |
player_combat_kill |
entity_id, message |
player_info_remove |
uuids (list) |
player_info_update |
actions (list), entries (list of {uuid, name?, listed, latency, game_mode?, display_name?, show_hat, list_order}) |
player_look_at |
from_anchor, x, y, z, entity_id? |
player_rotation |
yaw, pitch, relative_yaw, relative_pitch |
projectile_power |
entity_id, power |
recipe_book_add |
replace, recipes (list of {recipe, notification, highlight}) |
recipe_book_remove |
recipes (list) |
recipe_book_settings |
<book>_open, <book>_filtering — one pair per book: crafting, furnace, blast_furnace, smoker |
remove_entities |
entity_ids (list) |
remove_mob_effect |
entity_id, effect |
reset_score |
owner, objective? |
respawn |
keep_attributes, keep_entity_data, dimension, dimension_type, seed, game_mode, previous_game_mode?, debug, flat, portal_cooldown, sea_level, death_dimension?, death_x/y/z? |
rotate_head |
entity_id, head_yaw |
section_blocks_update |
blocks (list of {x/y/z, block}) |
select_advancements_tab |
tab? |
server_data |
motd, has_icon |
set_action_bar_text |
text |
set_border_center |
x, z |
set_border_lerp_size |
old_size, size, lerp_time |
set_border_size |
size |
set_border_warning_delay |
delay |
set_border_warning_distance |
blocks |
set_camera |
entity_id |
set_chunk_cache_center |
x, z |
set_chunk_cache_radius |
radius |
set_cursor_item |
item, count |
set_default_spawn_position |
dimension, x/y/z, yaw, pitch |
set_display_objective |
slot, objective? |
set_entity_data |
entity_id, values (list of {id, value}) — value is the tracked value as a string |
set_entity_link |
entity_id, holder_id |
set_equipment |
entity_id, slots (list of {slot, item, count}) |
set_experience |
progress, total, level |
set_objective |
objective, method, display_name?, render_type? — method is 0 add · 1 remove · 2 change |
set_passengers |
entity_id, passengers (list) |
set_player_inventory |
slot, item, count |
set_player_team |
team, action?, player_action?, players (list), display_name?, prefix?, suffix?, name_tag_visibility?, collision_rule?, color?, options? |
set_score |
owner, objective, score, display? |
set_simulation_distance |
distance |
set_subtitle_text |
text |
set_time |
game_time, clocks (list of {clock, total_ticks, rate}) |
set_titles_animation |
fade_in, stay, fade_out |
set_title_text |
text |
sound |
sound, category, x, y, z, volume, pitch, seed |
sound_entity |
sound, category, entity_id, volume, pitch, seed |
start_configuration |
— |
stop_sound |
sound?, category? |
system_chat |
text, overlay |
tab_list |
header, footer |
tag_query |
transaction_id, tag? — tag is SNBT |
take_item_entity |
item_entity_id, collector_id, amount |
test_instance_block_status |
status, size_x/y/z? |
ticking_state |
tick_rate, frozen |
ticking_step |
steps |
update_advancements |
reset, show, added (list), removed (list), progress (list) |
update_attributes |
entity_id, attributes (list of {id, base, modifiers}) |
update_mob_effect |
entity_id, effect, amplifier, duration, visible, ambient, show_icon |
update_recipes |
item_sets |
waypoint |
operation, id — operation is "track" · "untrack" · "update" |
clear_dialog |
— |
custom_payload |
channel, brand? |
custom_report_details |
details (map) |
ping |
id |
resource_pack_pop |
id? |
resource_pack_push |
id, url, hash, required, prompt? |
server_links |
links (list of {type, url}) |
show_dialog |
dialog |
store_cookie |
key, size |
transfer |
host, port |
update_tags |
registries (list) |
cookie_request |
key |
code_of_conduct |
text |
finish_configuration |
— |
registry_data |
registry, entries (list) |
reset_chat |
— |
select_known_packs |
packs (list) |
update_enabled_features |
features (list) |
custom_query |
transaction_id, channel |
hello |
server_id, should_authenticate |
login_compression |
threshold |
login_disconnect |
reason |
login_finished |
name, uuid |
pong_response |
time |
status_response |
description, online?, max?, version?, protocol?, enforces_secure_chat |
Light
light_update and level_chunk_with_light carry the chunk's light the way the server sent it.
| Field | Holds |
|---|---|
sky_mask, block_mask |
Section bits that come with light data |
empty_sky_mask, empty_block_mask |
Section bits whose light is all zero |
sky_light, block_light |
One 2048-byte string per bit of the matching mask, in the same order |
A mask is a list of bit numbers, and bit i is section world:min_section() - 1 + i: light
sections reach one past each end of the chunk column. sky_light[k] is the
light of section bit sky_mask[k]. Each string packs one 0–15 level per block, local block
x, y, z at nibble y * 256 + z * 16 + x, low half of the byte first:
local function light_at(data, x, y, z)
local nibble = y * 256 + z * 16 + x
local byte = data:byte(bit32.rshift(nibble, 1) + 1)
return bit32.band(bit32.rshift(byte, bit32.band(nibble, 1) * 4), 15)
end
local lit = {}
module:event("packet_receive", function(e)
if e.name ~= "light_update" and e.name ~= "level_chunk_with_light" then return end
for k, bit in ipairs(e.block_mask) do
if light_at(e.block_light[k], 8, 8, 8) > 0 then
lit[#lit + 1] = { x = e.x, z = e.z, section_bit = bit }
end
end
end)
:::caution
Packet events run on network threads. Collect data there and act in tick; never draw or
call projection from them.
:::