// Copyright 2015 Matthew Collins // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use format; state_packets!( handshake Handshaking { serverbound Serverbound { // Handshake is the first packet sent in the protocol. // Its used for deciding if the request is a client // is requesting status information about the server // (MOTD, players etc) or trying to login to the server. // // The host and port fields are not used by the vanilla // server but are there for virtual server hosting to // be able to redirect a client to a target server with // a single address + port. // // Some modified servers/proxies use the handshake field // differently, packing information into the field other // than the hostname due to the protocol not providing // any system for custom information to be transfered // by the client to the server until after login. Handshake => 0x00 { // The protocol version of the connecting client protocol_version: VarInt =, // The hostname the client connected to host: String =, // The port the client connected to port: u16 =, // The next protocol state the client wants next: VarInt =, } } clientbound Clientbound { } } play Play { serverbound Serverbound { // TabComplete is sent by the client when the client presses tab in // the chat box. TabComplete => 0x00 { text: String =, has_target: bool =, target: Option = when(|p: &TabComplete| p.has_target == true), } // ChatMessage is sent by the client when it sends a chat message or // executes a command (prefixed by '/'). ChatMessage => 0x01 { message: String =, } // ClientStatus is sent to update the client's status ClientStatus => 0x02 { action_id: VarInt =, } // ClientSettings is sent by the client to update its current settings. ClientSettings => 0x03 { locale: String =, view_distance: u8 =, chat_mode: u8 =, chat_colors: bool =, displayed_skin_parts: u8 =, main_hand: VarInt =, } // ConfirmTransactionServerbound is a reply to ConfirmTransaction. ConfirmTransactionServerbound => 0x04 { id: u8 =, action_number: i16 =, accepted: bool =, } // EnchantItem is sent when the client enchants an item. EnchantItem => 0x05 { id: u8 =, enchantment: u8 =, } // ClickWindow is sent when the client clicks in a window. ClickWindow => 0x06 { id: u8 =, slot: i16 =, button: u8 =, action_number: u16 =, mode: u8 =, clicked_item: Option =, } // CloseWindow is sent when the client closes a window. CloseWindow => 0x07 { id: u8 =, } // PluginMessageServerbound is used for custom messages between the client // and server. This is mainly for plugins/mods but vanilla has a few channels // registered too. PluginMessageServerbound => 0x08 { channel: String =, data: Vec =, } // UseEntity is sent when the user interacts (right clicks) or attacks // (left clicks) an entity. UseEntity => 0x09 { target_id: VarInt =, ty: VarInt =, target_x: f32 = when(|p: &UseEntity| p.ty.0 == 2), target_y: f32 = when(|p: &UseEntity| p.ty.0 == 2), target_z: f32 = when(|p: &UseEntity| p.ty.0 == 2), hand: VarInt = when(|p: &UseEntity| p.ty.0 == 0 || p.ty.0 == 2), } // KeepAliveServerbound is sent by a client as a response to a // KeepAliveClientbound. If the client doesn't reply the server // may disconnect the client. KeepAliveServerbound => 0x0A { id: VarInt =, } // PlayerPosition is used to update the player's position. PlayerPosition => 0x0B { x: f64 =, y: f64 =, z: f64 =, on_ground: bool =, } // PlayerPositionLook is a combination of PlayerPosition and // PlayerLook. PlayerPositionLook => 0x0C { x: f64 =, y: f64 =, z: f64 =, yaw: f32 =, pitch: f32 =, on_ground: bool =, } // PlayerLook is used to update the player's rotation. PlayerLook => 0x0D { yaw: f32 =, pitch: f32 =, on_ground: bool =, } // Player is used to update whether the player is on the ground or not. Player => 0x0E { on_ground: bool =, } // ClientAbilities is used to modify the players current abilities. // Currently flying is the only one ClientAbilities => 0x0F { flags: u8 =, flying_speed: f32 =, walking_speed: f32 =, } // PlayerDigging is sent when the client starts/stops digging a block. // It also can be sent for droppping items and eating/shooting. PlayerDigging => 0x10 { status: u8 =, location: types::Position =, face: u8 =, } // PlayerAction is sent when a player preforms various actions. PlayerAction => 0x11 { entity_id: VarInt =, action_id: VarInt =, jump_boost: VarInt =, } // SteerVehicle is sent by the client when steers or preforms an action // on a vehicle. SteerVehicle => 0x12 { sideways: f32 =, forward: f32 =, flags: u8 =, } // ResourcePackStatus informs the server of the client's current progress // in activating the requested resource pack ResourcePackStatus => 0x13 { hash: String =, result: VarInt =, } // HeldItemChange is sent when the player changes the currently active // hotbar slot. HeldItemChange => 0x14 { slot: i16 =, } // CreativeInventoryAction is sent when the client clicks in the creative // inventory. This is used to spawn items in creative. CreativeInventoryAction => 0x15 { slot: i16 =, clicked_item: Option =, } // SetSign sets the text on a sign after placing it. SetSign => 0x16 { location: types::Position =, line1: String =, line2: String =, line3: String =, line4: String =, } // ArmSwing is sent by the client when the player left clicks (to swing their // arm). ArmSwing => 0x17 { hand: VarInt =, } // SpectateTeleport is sent by clients in spectator mode to teleport to a player. SpectateTeleport => 0x18 { target: UUID =, } // PlayerBlockPlacement is sent when the client tries to place a block. PlayerBlockPlacement => 0x19 { location: types::Position =, face: VarInt =, hand: VarInt =, cursor_x: u8 =, cursor_y: u8 =, cursor_z: u8 =, } // UseItem is sent when the client tries to use an item. UseItem => 0x1A { hand: VarInt =, } } clientbound Clientbound { // SpawnObject is used to spawn an object or vehicle into the world when it // is in range of the client. SpawnObject => 0x00 { entity_id: VarInt =, uuid: UUID =, ty: u8 =, x: i32 =, y: i32 =, z: i32 =, pitch: i8 =, yaw: i8 =, data: i32 =, velocity_x: i16 =, velocity_y: i16 =, velocity_z: i16 =, } // SpawnExperienceOrb spawns a single experience orb into the world when // it is in range of the client. The count controls the amount of experience // gained when collected. SpawnExperienceOrb => 0x01 { entity_id: VarInt =, x: i32 =, y: i32 =, z: i32 =, count: i16 =, } // SpawnGlobalEntity spawns an entity which is visible from anywhere in the // world. Currently only used for lightning. SpawnGlobalEntity => 0x02 { entity_id: VarInt =, ty: u8 =, x: i32 =, y: i32 =, z: i32 =, } // SpawnMob is used to spawn a living entity into the world when it is in // range of the client. SpawnMob => 0x03 { entity_id: VarInt =, uuid: UUID =, ty: u8 =, x: i32 =, y: i32 =, z: i32 =, yaw: i8 =, pitch: i8 =, head_pitch: i8 =, velocity_x: i16 =, velocity_y: i16 =, velocity_z: i16 =, metadata: types::Metadata =, } // SpawnPainting spawns a painting into the world when it is in range of // the client. The title effects the size and the texture of the painting. SpawnPainting => 0x04 { entity_id: VarInt =, title: String =, location: types::Position =, direction: u8 =, } // SpawnPlayer is used to spawn a player when they are in range of the client. // This packet alone isn't enough to display the player as the skin and username // information is in the player information packet. SpawnPlayer => 0x05 { entity_id: VarInt =, uuid: UUID =, x: i32 =, y: i32 =, z: i32 =, yaw: i8 =, pitch: i8 =, metadata: types::Metadata =, } // Animation is sent by the server to play an animation on a specific entity. Animation => 0x06 { entity_id: VarInt =, animation_id: u8 =, } // Statistics is used to update the statistics screen for the client. Statistics => 0x07 { statistices: LenPrefixed =, } // BlockBreakAnimation is used to create and update the block breaking // animation played when a player starts digging a block. BlockBreakAnimation => 0x08 { entity_id: VarInt =, location: types::Position =, stage: i8 =, } // UpdateBlockEntity updates the nbt tag of a block entity in the // world. UpdateBlockEntity => 0x09 { location: types::Position =, action: u8 =, nbt: Option =, } // BlockAction triggers different actions depending on the target block. BlockAction => 0x0A { location: types::Position =, byte1: u8 =, byte2: u8 =, block_type: VarInt =, } // BlockChange is used to update a single block on the client. BlockChange => 0x0B { location: types::Position =, block_id: VarInt =, } // BossBar displays and/or changes a boss bar that is displayed on the // top of the client's screen. This is normally used for bosses such as // the ender dragon or the wither. BossBar => 0x0C { uuid: UUID =, action: VarInt =, title: format::Component = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 3), health: f32 = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 2), color: VarInt = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 4), style: VarInt = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 4), flags: u8 = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 5), } // ServerDifficulty changes the displayed difficulty in the client's menu // as well as some ui changes for hardcore. ServerDifficulty => 0x0D { difficulty: u8 =, } // TabCompleteReply is sent as a reply to a tab completion request. // The matches should be possible completions for the command/chat the // player sent. TabCompleteReply => 0x0E { matches: LenPrefixed =, } // ServerMessage is a message sent by the server. It could be from a player // or just a system message. The Type field controls the location the // message is displayed at and when the message is displayed. ServerMessage => 0x0F { message: format::Component =, // 0 - Chat message, 1 - System message, 2 - Action bar message position: u8 =, } // MultiBlockChange is used to update a batch of blocks in a single packet. MultiBlockChange => 0x10 { chunk_x: i32 =, chunk_y: i32 =, records: LenPrefixed =, } // ConfirmTransaction notifies the client whether a transaction was successful // or failed (e.g. due to lag). ConfirmTransaction => 0x11 { id: u8 =, action_number: i16 =, accepted: bool =, } // WindowClose forces the client to close the window with the given id, // e.g. a chest getting destroyed. WindowClose => 0x12 { id: u8 =, } // WindowOpen tells the client to open the inventory window of the given // type. The ID is used to reference the instance of the window in // other packets. WindowOpen => 0x13 { id: u8 =, ty: String =, title: format::Component =, slot_count: u8 =, entity_id: i32 = when(|p: &WindowOpen| p.ty == "EntityHorse"), } // WindowItems sets every item in a window. WindowItems => 0x14 { id: u8 =, items: LenPrefixed> =, } // WindowProperty changes the value of a property of a window. Properties // vary depending on the window type. WindowProperty => 0x15 { id: u8 =, property: i16 =, value: i16 =, } // WindowSetSlot changes an itemstack in one of the slots in a window. WindowSetSlot => 0x16 { id: u8 =, property: i16 =, item: Option =, } // SetCooldown disables a set item (by id) for the set number of ticks SetCooldown => 0x17 { item_id: VarInt =, ticks: VarInt =, } // PluginMessageClientbound is used for custom messages between the client // and server. This is mainly for plugins/mods but vanilla has a few channels // registered too. PluginMessageClientbound => 0x18 { channel: String =, data: Vec =, } // Disconnect causes the client to disconnect displaying the passed reason. Disconnect => 0x19 { reason: format::Component =, } // EntityAction causes an entity to preform an action based on the passed // id. EntityAction => 0x1A { entity_id: i32 =, action_id: u8 =, } // Explosion is sent when an explosion is triggered (tnt, creeper etc). // This plays the effect and removes the effected blocks. Explosion => 0x1B { x: f32 =, y: f32 =, z: f32 =, radius: f32 =, records: LenPrefixed =, velocity_x: f32 =, velocity_y: f32 =, velocity_z: f32 =, } // ChunkUnload tells the client to unload the chunk at the specified // position. ChunkUnload => 0x1C { x: i32 =, z: i32 =, } // SetCompression updates the compression threshold. SetCompression => 0x1D { threshold: VarInt =, } // ChangeGameState is used to modify the game's state like gamemode or // weather. ChangeGameState => 0x1E { reason: u8 =, value: f32 =, } // KeepAliveClientbound is sent by a server to check if the // client is still responding and keep the connection open. // The client should reply with the KeepAliveServerbound // packet setting ID to the same as this one. KeepAliveClientbound => 0x1F { id: VarInt =, } // ChunkData sends or updates a single chunk on the client. If New is set // then biome data should be sent too. ChunkData => 0x20 { chunk_x: i32 =, chunk_z: i32 =, new: bool =, bitmask: VarInt =, data: LenPrefixedBytes =, } // Effect plays a sound effect or particle at the target location with the // volume (of sounds) being relative to the player's position unless // DisableRelative is set to true. Effect => 0x21 { effect_id: i32 =, location: types::Position =, data: i32 =, disable_relative: bool =, } // Particle spawns particles at the target location with the various // modifiers. Particle => 0x22 { particle_id: i32 =, long_distance: bool =, x: f32 =, y: f32 =, z: f32 =, offset_x: f32 =, offset_y: f32 =, offset_z: f32 =, speed: f32 =, count: i32 =, data1: VarInt = when(|p: &Particle| p.particle_id == 36 || p.particle_id == 37 || p.particle_id == 38), data2: VarInt = when(|p: &Particle| p.particle_id == 36), } // SoundEffect plays the named sound at the target location. SoundEffect => 0x23 { name: String =, x: i32 =, y: i32 =, z: i32 =, volume: f32 =, pitch: u8 =, } // JoinGame is sent after completing the login process. This // sets the initial state for the client. JoinGame => 0x24 { // The entity id the client will be referenced by entity_id: i32 =, // The starting gamemode of the client gamemode: u8 =, // The dimension the client is starting in dimension: i8 =, // The difficuilty setting for the server difficulty: u8 =, // The max number of players on the server max_players: u8 =, // The level type of the server level_type: String =, // Whether the client should reduce the amount of debug // information it displays in F3 mode reduced_debug_info: bool =, } // Maps updates a single map's contents Maps => 0x25 { item_damage: VarInt =, scale: i8 =, tracking_position: bool =, icons: LenPrefixed =, columns: u8 =, rows: Option = when(|p: &Maps| p.columns > 0), x: Option = when(|p: &Maps| p.columns > 0), z: Option = when(|p: &Maps| p.columns > 0), data: Option> = when(|p: &Maps| p.columns > 0), } // EntityMove moves the entity with the id by the offsets provided. EntityMove => 0x26 { entity_id: VarInt =, delta_x: i8 =, delta_y: i8 =, delta_z: i8 =, on_ground: bool =, } // EntityLookAndMove is a combination of EntityMove and EntityLook. EntityLookAndMove => 0x27 { entity_id: VarInt =, delta_x: i8 =, delta_y: i8 =, delta_z: i8 =, yaw: i8 =, pitch: i8 =, on_ground: bool =, } // EntityLook rotates the entity to the new angles provided. EntityLook => 0x28 { entity_id: VarInt =, yaw: i8 =, pitch: i8 =, on_ground: bool =, } // Entity does nothing. It is a result of subclassing used in Minecraft. Entity => 0x29 { entity_id: VarInt =, } // SignEditorOpen causes the client to open the editor for a sign so that // it can write to it. Only sent in vanilla when the player places a sign. SignEditorOpen => 0x2A { location: types::Position =, } // PlayerAbilities is used to modify the players current abilities. Flying, // creative, god mode etc. PlayerAbilities => 0x2B { flags: u8 =, flying_speed: f32 =, walking_speed: f32 =, } // CombatEvent is used for... you know, I never checked. I have no // clue. CombatEvent => 0x2C { event: VarInt =, direction: Option = when(|p: &CombatEvent| p.event.0 == 1), player_id: Option = when(|p: &CombatEvent| p.event.0 == 2), entity_id: Option = when(|p: &CombatEvent| p.event.0 == 1 || p.event.0 == 2), message: Option = when(|p: &CombatEvent| p.event.0 == 2), } // PlayerInfo is sent by the server for every player connected to the server // to provide skin and username information as well as ping and gamemode info. PlayerInfo => 0x2D { inner: packet::PlayerInfoData =, // Ew but watcha gonna do? } // TeleportPlayer is sent to change the player's position. The client is expected // to reply to the server with the same positions as contained in this packet // otherwise will reject future packets. TeleportPlayer => 0x2E { x: f64 =, y: f64 =, z: f64 =, yaw: f32 =, pitch: f32 =, flags: u8 =, } // EntityUsedBed is sent by the server when a player goes to bed. EntityUsedBed => 0x2F { entity_id: VarInt =, location: types::Position =, } // EntityDestroy destroys the entities with the ids in the provided slice. EntityDestroy => 0x30 { entity_ids: LenPrefixed =, } // EntityRemoveEffect removes an effect from an entity. EntityRemoveEffect => 0x31 { entity_id: VarInt =, effect_id: i8 =, } // ResourcePackSend causes the client to check its cache for the requested // resource packet and download it if its missing. Once the resource pack // is obtained the client will use it. ResourcePackSend => 0x32 { url: String =, hash: String =, } // Respawn is sent to respawn the player after death or when they move worlds. Respawn => 0x33 { dimension: i32 =, difficulty: u8 =, gamemode: u8 =, level_type: String =, } // EntityHeadLook rotates an entity's head to the new angle. EntityHeadLook => 0x34 { entity_id: VarInt =, head_yaw: i8 =, } // WorldBorder configures the world's border. WorldBorder => 0x35 { action: VarInt =, old_radius: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 1), new_radius: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 1 || p.action.0 == 0), speed: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 1), x: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 2), z: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 2), portal_boundary: Option = when(|p: &WorldBorder| p.action.0 == 3), warning_time: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 4), warning_blocks: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 5), } // Camera causes the client to spectate the entity with the passed id. // Use the player's id to de-spectate. Camera => 0x36 { target_id: VarInt =, } // SetCurrentHotbarSlot changes the player's currently selected hotbar item. SetCurrentHotbarSlot => 0x37 { slot: u8 =, } // ScoreboardDisplay is used to set the display position of a scoreboard. ScoreboardDisplay => 0x38 { position: u8 =, name: String =, } // EntityMetadata updates the metadata for an entity. EntityMetadata => 0x39 { entity_id: VarInt =, metadata: types::Metadata =, } // EntityAttach attaches to entities together, either by mounting or leashing. // -1 can be used at the EntityID to deattach. EntityAttach => 0x3A { entity_id: i32 =, vehicle: i32 =, leash: bool =, } // EntityVelocity sets the velocity of an entity in 1/8000 of a block // per a tick. EntityVelocity => 0x3B { entity_id: VarInt =, velocity_x: i16 =, velocity_y: i16 =, velocity_z: i16 =, } // EntityEquipment is sent to display an item on an entity, like a sword // or armor. Slot 0 is the held item and slots 1 to 4 are boots, leggings // chestplate and helmet respectively. EntityEquipment => 0x3C { entity_id: VarInt =, slot: VarInt =, item: Option =, } // SetExperience updates the experience bar on the client. SetExperience => 0x3D { experience_bar: f32 =, level: VarInt =, total_experience: VarInt =, } // UpdateHealth is sent by the server to update the player's health and food. UpdateHealth => 0x3E { health: f32 =, food: VarInt =, food_saturation: f32 =, } // ScoreboardObjective creates/updates a scoreboard objective. ScoreboardObjective => 0x3F { name: String =, mode: u8 =, value: String = when(|p: &ScoreboardObjective| p.mode == 0 || p.mode == 2), ty: String = when(|p: &ScoreboardObjective| p.mode == 0 || p.mode == 2), } // Teams creates and updates teams Teams => 0x40 { name: String =, mode: u8 =, display_name: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), prefix: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), suffix: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), flags: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), name_tag_visibility: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), collision_rule: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), color: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), players: Option> = when(|p: &Teams| p.mode == 0 || p.mode == 3 || p.mode == 4), } // UpdateScore is used to update or remove an item from a scoreboard // objective. UpdateScore => 0x41 { name: String =, action: u8 =, object_name: String =, value: Option = when(|p: &UpdateScore| p.action != 1), } // SpawnPosition is sent to change the player's current spawn point. Currently // only used by the client for the compass. SpawnPosition => 0x42 { location: types::Position =, } // TimeUpdate is sent to sync the world's time to the client, the client // will manually tick the time itself so this doesn't need to sent repeatedly // but if the server or client has issues keeping up this can fall out of sync // so it is a good idea to sent this now and again TimeUpdate => 0x43 { world_age: i64 =, time_of_day: i64 =, } // Title configures an on-screen title. Title => 0x44 { action: VarInt =, title: Option = when(|p: &Title| p.action.0 == 0), sub_title: Option = when(|p: &Title| p.action.0 == 1), fade_in: Option = when(|p: &Title| p.action.0 == 2), fade_stay: Option = when(|p: &Title| p.action.0 == 2), fade_out: Option = when(|p: &Title| p.action.0 == 2), } // UpdateSign sets or changes the text on a sign. UpdateSign => 0x45 { location: types::Position =, line1: format::Component =, line2: format::Component =, line3: format::Component =, line4: format::Component =, } // PlayerListHeaderFooter updates the header/footer of the player list. PlayerListHeaderFooter => 0x46 { header: format::Component =, footer: format::Component =, } // CollectItem causes the collected item to fly towards the collector. This // does not destroy the entity. CollectItem => 0x47 { collected_entity_id: VarInt =, collector_entity_id: VarInt =, } // EntityTeleport teleports the entity to the target location. This is // sent if the entity moves further than EntityMove allows. EntityTeleport => 0x48 { entity_id: VarInt =, x: i32 =, y: i32 =, z: i32 =, yaw: i8 =, pitch: i8 =, on_ground: bool =, } // EntityProperties updates the properties for an entity. EntityProperties => 0x49 { entity_id: VarInt =, properties: LenPrefixed =, } // EntityEffect applies a status effect to an entity for a given duration. EntityEffect => 0x4A { entity_id: VarInt =, effect_id: i8 =, amplifier: i8 =, duration: VarInt =, hide_particles: bool =, } } } login Login { serverbound Serverbound { // LoginStart is sent immeditately after switching into the login // state. The passed username is used by the server to authenticate // the player in online mode. LoginStart => 0x00 { username: String =, } // EncryptionResponse is sent as a reply to EncryptionRequest. All // packets following this one must be encrypted with AES/CFB8 // encryption. EncryptionResponse => 0x01 { // The key for the AES/CFB8 cipher encrypted with the // public key shared_secret: LenPrefixedBytes =, // The verify token from the request encrypted with the // public key verify_token: LenPrefixedBytes =, } } clientbound Clientbound { // LoginDisconnect is sent by the server if there was any issues // authenticating the player during login or the general server // issues (e.g. too many players). LoginDisconnect => 0x00 { reason: format::Component =, } // EncryptionRequest is sent by the server if the server is in // online mode. If it is not sent then its assumed the server is // in offline mode. EncryptionRequest => 0x01 { // Generally empty, left in from legacy auth // but is still used by the client if provided server_id: String =, // A RSA Public key serialized in x.509 PRIX format public_key: LenPrefixedBytes =, // Token used by the server to verify encryption is working // correctly verify_token: LenPrefixedBytes =, } // LoginSuccess is sent by the server if the player successfully // authenicates with the session servers (online mode) or straight // after LoginStart (offline mode). LoginSuccess => 0x02 { // String encoding of a uuid (with hyphens) uuid: String =, username: String =, } // SetInitialCompression sets the compression threshold during the // login state. SetInitialCompression => 0x03 { // Threshold where a packet should be sent compressed threshold: VarInt =, } } } status Status { serverbound Serverbound { // StatusRequest is sent by the client instantly after // switching to the Status protocol state and is used // to signal the server to send a StatusResponse to the // client StatusRequest => 0x00 { empty: () =, } // StatusPing is sent by the client after recieving a // StatusResponse. The client uses the time from sending // the ping until the time of recieving a pong to measure // the latency between the client and the server. StatusPing => 0x01 { ping: i64 =, } } clientbound Clientbound { // StatusResponse is sent as a reply to a StatusRequest. // The Status should contain a json encoded structure with // version information, a player sample, a description/MOTD // and optionally a favicon. // // The structure is as follows // { // "version": { // "name": "1.8.3", // "protocol": 47, // }, // "players": { // "max": 20, // "online": 1, // "sample": [ // {"name": "Thinkofdeath", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"} // ] // }, // "description": "Hello world", // "favicon": "data:image/png;base64," // } StatusResponse => 0x00 { status: String =, } // StatusPong is sent as a reply to a StatusPing. // The Time field should be exactly the same as the // one sent by the client. StatusPong => 0x01 { ping: i64 =, } } } ); #[derive(Debug, Default)] pub struct Statistic { pub name: String, pub value: VarInt, } impl Serializable for Statistic { fn read_from(buf: &mut io::Read) -> Result { Ok(Statistic { name: try!(Serializable::read_from(buf)), value: try!(Serializable::read_from(buf)), }) } fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { try!(self.name.write_to(buf)); self.value.write_to(buf) } } #[derive(Debug, Default)] pub struct BlockChangeRecord { pub xz: u8, pub y: u8, pub block_id: VarInt, } impl Serializable for BlockChangeRecord { fn read_from(buf: &mut io::Read) -> Result { Ok(BlockChangeRecord { xz: try!(Serializable::read_from(buf)), y: try!(Serializable::read_from(buf)), block_id: try!(Serializable::read_from(buf)), }) } fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { try!(self.xz.write_to(buf)); try!(self.y.write_to(buf)); self.block_id.write_to(buf) } } #[derive(Debug, Default)] pub struct ExplosionRecord { pub x: i8, pub y: i8, pub z: i8, } impl Serializable for ExplosionRecord { fn read_from(buf: &mut io::Read) -> Result { Ok(ExplosionRecord { x: try!(Serializable::read_from(buf)), y: try!(Serializable::read_from(buf)), z: try!(Serializable::read_from(buf)), }) } fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { try!(self.x.write_to(buf)); try!(self.y.write_to(buf)); self.z.write_to(buf) } } #[derive(Debug)] pub struct MapIcon { pub direction_type: i8, pub x: i8, pub z: i8, } impl Serializable for MapIcon { fn read_from(buf: &mut io::Read) -> Result { Ok(MapIcon { direction_type: try!(Serializable::read_from(buf)), x: try!(Serializable::read_from(buf)), z: try!(Serializable::read_from(buf)), }) } fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { try!(self.direction_type.write_to(buf)); try!(self.x.write_to(buf)); self.z.write_to(buf) } } impl Default for MapIcon { fn default() -> Self { MapIcon { direction_type: 0, x: 0, z: 0, } } } #[derive(Debug, Default)] pub struct EntityProperty { pub key: String, pub value: f64, pub modifiers: LenPrefixed, } impl Serializable for EntityProperty { fn read_from(buf: &mut io::Read) -> Result { Ok(EntityProperty { key: try!(Serializable::read_from(buf)), value: try!(Serializable::read_from(buf)), modifiers: try!(Serializable::read_from(buf)), }) } fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { try!(self.key.write_to(buf)); try!(self.value.write_to(buf)); self.modifiers.write_to(buf) } } #[derive(Debug, Default)] pub struct PropertyModifier { pub uuid: UUID, pub amount: f64, pub operation: i8, } impl Serializable for PropertyModifier { fn read_from(buf: &mut io::Read) -> Result { Ok(PropertyModifier { uuid: try!(Serializable::read_from(buf)), amount: try!(Serializable::read_from(buf)), operation: try!(Serializable::read_from(buf)), }) } fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { try!(self.uuid.write_to(buf)); try!(self.amount.write_to(buf)); self.operation.write_to(buf) } } #[derive(Debug)] pub struct PlayerInfoData { pub action: VarInt, pub players: Vec, } impl Serializable for PlayerInfoData { fn read_from(buf: &mut io::Read) -> Result { let mut m = PlayerInfoData { action: try!(Serializable::read_from(buf)), players: Vec::new(), }; let len = try!(VarInt::read_from(buf)); for _ in 0..len.0 { let uuid = try!(UUID::read_from(buf)); match m.action.0 { 0 => { let name = try!(String::read_from(buf)); let mut props = Vec::new(); let plen = try!(VarInt::read_from(buf)).0; for _ in 0..plen { let mut prop = PlayerProperty { name: try!(String::read_from(buf)), value: try!(String::read_from(buf)), signature: Default::default(), }; if try!(bool::read_from(buf)) { prop.signature = Some(try!(String::read_from(buf))); } props.push(prop); } let p = PlayerDetail::Add { uuid: uuid, name: name, properties: props, gamemode: try!(Serializable::read_from(buf)), ping: try!(Serializable::read_from(buf)), display: { if try!(bool::read_from(buf)) { Some(try!(Serializable::read_from(buf))) } else { None } }, }; m.players.push(p); } 1 => { m.players.push(PlayerDetail::UpdateGamemode { uuid: uuid, gamemode: try!(Serializable::read_from(buf)), }) } 2 => { m.players.push(PlayerDetail::UpdateLatency { uuid: uuid, ping: try!(Serializable::read_from(buf)), }) } 3 => { m.players.push(PlayerDetail::UpdateDisplayName { uuid: uuid, display: { if try!(bool::read_from(buf)) { Some(try!(Serializable::read_from(buf))) } else { None } }, }) } 4 => { m.players.push(PlayerDetail::Remove { uuid: uuid }) } _ => panic!(), } } Ok(m) } fn write_to(&self, _: &mut io::Write) -> Result<(), io::Error> { unimplemented!() // I'm lazy } } impl Default for PlayerInfoData { fn default() -> Self { PlayerInfoData { action: VarInt(0), players: Vec::new(), } } } #[derive(Debug)] pub enum PlayerDetail { Add { uuid: UUID, name: String, properties: Vec, gamemode: VarInt, ping: VarInt, display: Option, }, UpdateGamemode { uuid: UUID, gamemode: VarInt, }, UpdateLatency { uuid: UUID, ping: VarInt, }, UpdateDisplayName { uuid: UUID, display: Option, }, Remove { uuid: UUID, }, } #[derive(Debug)] pub struct PlayerProperty { pub name: String, pub value: String, pub signature: Option, }