1.7.10 (5) multiprotocol support (#64)

Adds 1.7.10 protocol version 5 support, a major update with significant changes.
Expands https://github.com/iceiix/steven/issues/18 Enhance protocol support

* Add v1_7_10 protocol packet structures and IDs

* EncryptionRequest/Response i16 variant in login protocol

* 1.7.10 slot NBT data parsing

* Support both 1.7/1.8+ item::Stack in read_from, using if protocol_verson

* 1.7.10 chunk format support, ChunkDataBulk_17 and ChunkData_17

* Extract dirty_chunks_by_bitmask from load_chunks17/18/19

* Implement keepalive i32 handler

* Send PlayerPositionLook_HeadY

* Send PlayerBlockPlacement_u8_Item_u8y

* Handle JoinGame_i8_NoDebug

* Handle SpawnPlayer_i32_HeldItem_String

* BlockChange_u8, MultiBlockChange_i16, UpdateBlockEntity_Data, EntityMove_i8_i32_NoGround, EntityLook_i32_NoGround, EntityLookAndMove_i8_i32_NoGround

* UpdateSign_u16, PlayerInfo_String, EntityDestroy_u8, EntityTeleport_i32_i32_NoGround

* Send feet_y = head_y - 1.62, fixes Illegal stance

https://wiki.vg/index.php?title=Protocol&oldid=6003#Player_Position

> Updates the players XYZ position on the server. If HeadY - FeetY is less than 0.1 or greater than 1.65, the stance is illegal and the client will be kicked with the message “Illegal Stance”.

> Absolute feet position, normally HeadY - 1.62. Used to modify the players bounding box when going up stairs, crouching, etc…

* Set on_ground = true in entity teleport, fixes bouncing

* Implement block change, fix metadata/id packing, bounce _u8 through on_block_change

* Implement on_multi_block_change_u16, used with explosions
This commit is contained in:
iceiix 2018-12-15 19:56:54 -08:00 committed by ice_iix
parent 44dc7eedd5
commit 2b641c9af3
15 changed files with 996 additions and 97 deletions

View File

@ -43,11 +43,32 @@ impl Serializable for Option<Stack> {
if id == -1 {
return Ok(None);
}
let count = buf.read_u8()? as isize;
let damage = buf.read_i16::<BigEndian>()? as isize;
let protocol_version = unsafe { protocol::CURRENT_PROTOCOL_VERSION };
let tag: Option<nbt::NamedTag> = if protocol_version >= 47 {
Serializable::read_from(buf)?
} else {
// 1.7 uses a different slot data format described on https://wiki.vg/index.php?title=Slot_Data&diff=6056&oldid=4753
let tag_size = buf.read_i16::<BigEndian>()?;
if tag_size != -1 {
for _ in 0..tag_size {
let _ = buf.read_u8()?;
}
// TODO: decompress zlib NBT for 1.7
None
} else {
None
}
};
Ok(Some(Stack {
id: id as isize,
count: buf.read_u8()? as isize,
damage: buf.read_i16::<BigEndian>()? as isize,
tag: Serializable::read_from(buf)?,
count,
damage,
tag,
}))
}
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), protocol::Error> {
@ -56,6 +77,7 @@ impl Serializable for Option<Stack> {
buf.write_i16::<BigEndian>(val.id as i16)?;
buf.write_u8(val.count as u8)?;
buf.write_i16::<BigEndian>(val.damage as i16)?;
// TODO: compress zlib NBT if 1.7
val.tag.write_to(buf)?;
}
None => buf.write_i16::<BigEndian>(-1)?,

View File

@ -37,7 +37,7 @@ use flate2::Compression;
use std::time::{Instant, Duration};
use crate::shared::Position;
pub const SUPPORTED_PROTOCOLS: [i32; 8] = [340, 316, 315, 210, 109, 107, 74, 47];
pub const SUPPORTED_PROTOCOLS: [i32; 9] = [340, 316, 315, 210, 109, 107, 74, 47, 5];
// TODO: switch to using thread_local storage?, see https://doc.rust-lang.org/std/macro.thread_local.html
pub static mut CURRENT_PROTOCOL_VERSION: i32 = SUPPORTED_PROTOCOLS[0];
@ -553,6 +553,16 @@ impl <L: Lengthable> fmt::Debug for LenPrefixedBytes<L> {
}
}
impl Lengthable for u8 {
fn into(self) -> usize {
self as usize
}
fn from(u: usize) -> u8 {
u as u8
}
}
impl Lengthable for i16 {
fn into(self) -> usize {
self as usize

View File

@ -66,6 +66,9 @@ state_packets!(
field has_target: bool =,
field target: Option<Position> = when(|p: &TabComplete_NoAssume| p.has_target),
}
packet TabComplete_NoAssume_NoTarget {
field text: String =,
}
/// ChatMessage is sent by the client when it sends a chat message or
/// executes a command (prefixed by '/').
packet ChatMessage {
@ -75,6 +78,9 @@ state_packets!(
packet ClientStatus {
field action_id: VarInt =,
}
packet ClientStatus_u8 {
field action_id: u8=,
}
/// ClientSettings is sent by the client to update its current settings.
packet ClientSettings {
field locale: String =,
@ -99,6 +105,14 @@ state_packets!(
field chat_colors: bool =,
field displayed_skin_parts: u8 =,
}
packet ClientSettings_u8_Handsfree_Difficulty {
field locale: String =,
field view_distance: u8 =,
field chat_mode: u8 =,
field chat_colors: bool =,
field difficulty: u8 =,
field displayed_skin_parts: u8 =,
}
/// ConfirmTransactionServerbound is a reply to ConfirmTransaction.
packet ConfirmTransactionServerbound {
field id: u8 =,
@ -138,6 +152,10 @@ state_packets!(
field channel: String =,
field data: Vec<u8> =,
}
packet PluginMessageServerbound_i16 {
field channel: String =,
field data: LenPrefixedBytes<i16> =,
}
/// UseEntity is sent when the user interacts (right clicks) or attacks
/// (left clicks) an entity.
packet UseEntity {
@ -155,6 +173,10 @@ state_packets!(
field target_y: f32 = when(|p: &UseEntity_Handsfree| p.ty.0 == 2),
field target_z: f32 = when(|p: &UseEntity_Handsfree| p.ty.0 == 2),
}
packet UseEntity_Handsfree_i32 {
field target_id: i32 =,
field ty: u8 =,
}
/// KeepAliveServerbound is sent by a client as a response to a
/// KeepAliveClientbound. If the client doesn't reply the server
/// may disconnect the client.
@ -164,6 +186,9 @@ state_packets!(
packet KeepAliveServerbound_VarInt {
field id: VarInt =,
}
packet KeepAliveServerbound_i32 {
field id: i32 =,
}
/// PlayerPosition is used to update the player's position.
packet PlayerPosition {
field x: f64 =,
@ -171,6 +196,13 @@ state_packets!(
field z: f64 =,
field on_ground: bool =,
}
packet PlayerPosition_HeadY {
field x: f64 =,
field feet_y: f64 =,
field head_y: f64 =,
field z: f64 =,
field on_ground: bool =,
}
/// PlayerPositionLook is a combination of PlayerPosition and
/// PlayerLook.
packet PlayerPositionLook {
@ -181,6 +213,15 @@ state_packets!(
field pitch: f32 =,
field on_ground: bool =,
}
packet PlayerPositionLook_HeadY {
field x: f64 =,
field feet_y: f64 =,
field head_y: f64 =,
field z: f64 =,
field yaw: f32 =,
field pitch: f32 =,
field on_ground: bool =,
}
/// PlayerLook is used to update the player's rotation.
packet PlayerLook {
field yaw: f32 =,
@ -229,12 +270,24 @@ state_packets!(
field location: Position =,
field face: u8 =,
}
packet PlayerDigging_u8_u8y {
field status: u8 =,
field x: i32 =,
field y: u8 =,
field z: i32 =,
field face: u8 =,
}
/// PlayerAction is sent when a player preforms various actions.
packet PlayerAction {
field entity_id: VarInt =,
field action_id: VarInt =,
field jump_boost: VarInt =,
}
packet PlayerAction_i32 {
field entity_id: i32 =,
field action_id: i8 =,
field jump_boost: i32 =,
}
/// SteerVehicle is sent by the client when steers or preforms an action
/// on a vehicle.
packet SteerVehicle {
@ -242,6 +295,12 @@ state_packets!(
field forward: f32 =,
field flags: u8 =,
}
packet SteerVehicle_jump_unmount {
field sideways: f32 =,
field forward: f32 =,
field jump: bool =,
field unmount: bool =,
}
/// CraftingBookData is sent when the player interacts with the crafting book.
packet CraftingBookData {
field action: VarInt =,
@ -282,6 +341,15 @@ state_packets!(
field line3: String =,
field line4: String =,
}
packet SetSign_i16y {
field x: i32 =,
field y: i16 =,
field z: i32 =,
field line1: String =,
field line2: String =,
field line3: String =,
field line4: String =,
}
/// ArmSwing is sent by the client when the player left clicks (to swing their
/// arm).
packet ArmSwing {
@ -290,6 +358,10 @@ state_packets!(
packet ArmSwing_Handsfree {
field empty: () =,
}
packet ArmSwing_Handsfree_ID {
field entity_id: i32 =,
field animation: u8 =,
}
/// SpectateTeleport is sent by clients in spectator mode to teleport to a player.
packet SpectateTeleport {
field target: UUID =,
@ -319,6 +391,16 @@ state_packets!(
field cursor_y: u8 =,
field cursor_z: u8 =,
}
packet PlayerBlockPlacement_u8_Item_u8y {
field x: i32 =,
field y: u8 =,
field z: i32 =,
field face: u8 =,
field hand: Option<item::Stack> =,
field cursor_x: u8 =,
field cursor_y: u8 =,
field cursor_z: u8 =,
}
/// UseItem is sent when the client tries to use an item.
packet UseItem {
@ -478,6 +560,14 @@ state_packets!(
field location: Position =,
field direction: u8 =,
}
packet SpawnPainting_NoUUID_i32 {
field entity_id: VarInt =,
field title: String =,
field x: i32 =,
field y: i32 =,
field z: i32 =,
field direction: i32 =,
}
/// 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.
@ -512,6 +602,20 @@ state_packets!(
field current_item: u16 =,
field metadata: types::Metadata =,
}
packet SpawnPlayer_i32_HeldItem_String {
field entity_id: VarInt =,
field uuid: String =,
field name: String =,
field properties: LenPrefixed<VarInt, packet::SpawnProperty> =,
field x: i32 =,
field y: i32 =,
field z: i32 =,
field yaw: i8 =,
field pitch: i8 =,
field current_item: u16 =,
field metadata: types::Metadata =,
}
/// Animation is sent by the server to play an animation on a specific entity.
packet Animation {
field entity_id: VarInt =,
@ -528,6 +632,13 @@ state_packets!(
field location: Position =,
field stage: i8 =,
}
packet BlockBreakAnimation_i32 {
field entity_id: VarInt =,
field x: i32 =,
field y: i32 =,
field z: i32 =,
field stage: i8 =,
}
/// UpdateBlockEntity updates the nbt tag of a block entity in the
/// world.
packet UpdateBlockEntity {
@ -535,6 +646,14 @@ state_packets!(
field action: u8 =,
field nbt: Option<nbt::NamedTag> =,
}
packet UpdateBlockEntity_Data {
field x: i32 =,
field y: i16 =,
field z: i32 =,
field action: u8 =,
field data_length: i16 =,
field gzipped_nbt: Vec<u8> =,
}
/// BlockAction triggers different actions depending on the target block.
packet BlockAction {
field location: Position =,
@ -542,11 +661,26 @@ state_packets!(
field byte2: u8 =,
field block_type: VarInt =,
}
packet BlockAction_u16 {
field x: i32 =,
field y: u16 =,
field z: i32 =,
field byte1: u8 =,
field byte2: u8 =,
field block_type: VarInt =,
}
/// BlockChange is used to update a single block on the client.
packet BlockChange {
packet BlockChange_VarInt {
field location: Position =,
field block_id: VarInt =,
}
packet BlockChange_u8 {
field x: i32 =,
field y: u8 =,
field z: i32 =,
field block_id: VarInt =,
field block_metadata: u8 =,
}
/// 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.
@ -578,12 +712,22 @@ state_packets!(
/// 0 - Chat message, 1 - System message, 2 - Action bar message
field position: u8 =,
}
packet ServerMessage_NoPosition {
field message: format::Component =,
}
/// MultiBlockChange is used to update a batch of blocks in a single packet.
packet MultiBlockChange {
packet MultiBlockChange_VarInt {
field chunk_x: i32 =,
field chunk_z: i32 =,
field records: LenPrefixed<VarInt, packet::BlockChangeRecord> =,
}
packet MultiBlockChange_u16 {
field chunk_x: i32 =,
field chunk_z: i32 =,
field record_count: u16 =,
field data_size: i32 =,
field data: Vec<u8> =,
}
/// ConfirmTransaction notifies the client whether a transaction was successful
/// or failed (e.g. due to lag).
packet ConfirmTransaction {
@ -606,6 +750,14 @@ state_packets!(
field slot_count: u8 =,
field entity_id: i32 = when(|p: &WindowOpen| p.ty == "EntityHorse"),
}
packet WindowOpen_u8 {
field id: u8 =,
field ty: u8 =,
field title: format::Component =,
field slot_count: u8 =,
field use_provided_window_title: bool =,
field entity_id: i32 = when(|p: &WindowOpen_u8| p.ty == 11),
}
/// WindowItems sets every item in a window.
packet WindowItems {
field id: u8 =,
@ -636,6 +788,10 @@ state_packets!(
field channel: String =,
field data: Vec<u8> =,
}
packet PluginMessageClientbound_i16 {
field channel: String =,
field data: LenPrefixedBytes<i16> =,
}
/// Plays a sound by name on the client
packet NamedSoundEffect {
field name: String =,
@ -711,6 +867,9 @@ state_packets!(
packet KeepAliveClientbound_VarInt {
field id: VarInt =,
}
packet KeepAliveClientbound_i32 {
field id: i32 =,
}
/// ChunkData sends or updates a single chunk on the client. If New is set
/// then biome data should be sent too.
packet ChunkData {
@ -735,11 +894,25 @@ state_packets!(
field bitmask: u16 =,
field data: LenPrefixedBytes<VarInt> =,
}
packet ChunkData_17 {
field chunk_x: i32 =,
field chunk_z: i32 =,
field new: bool =,
field bitmask: u16 =,
field add_bitmask: u16 =,
field compressed_data: LenPrefixedBytes<i32> =,
}
packet ChunkDataBulk {
field skylight: bool =,
field chunk_meta: LenPrefixed<VarInt, packet::ChunkMeta> =,
field chunk_data: Vec<u8> =,
}
packet ChunkDataBulk_17 {
field chunk_column_count: u16 =,
field data_length: i32 =,
field skylight: bool =,
field chunk_data_and_meta: Vec<u8> =,
}
/// 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.
@ -749,6 +922,14 @@ state_packets!(
field data: i32 =,
field disable_relative: bool =,
}
packet Effect_u8y {
field effect_id: i32 =,
field x: i32 =,
field y: u8 =,
field z: i32 =,
field data: i32 =,
field disable_relative: bool =,
}
/// Particle spawns particles at the target location with the various
/// modifiers.
packet Particle {
@ -765,6 +946,17 @@ state_packets!(
field data1: VarInt = when(|p: &Particle| p.particle_id == 36 || p.particle_id == 37 || p.particle_id == 38 || p.particle_id == 46),
field data2: VarInt = when(|p: &Particle| p.particle_id == 36),
}
packet Particle_Named {
field particle_id: String =,
field x: f32 =,
field y: f32 =,
field z: f32 =,
field offset_x: f32 =,
field offset_y: f32 =,
field offset_z: f32 =,
field speed: f32 =,
field count: i32 =,
}
/// JoinGame is sent after completing the login process. This
/// sets the initial state for the client.
packet JoinGame_i32 {
@ -801,6 +993,14 @@ state_packets!(
/// information it displays in F3 mode
field reduced_debug_info: bool =,
}
packet JoinGame_i8_NoDebug {
field entity_id: i32 =,
field gamemode: u8 =,
field dimension: i8 =,
field difficulty: u8 =,
field max_players: u8 =,
field level_type: String =,
}
/// Maps updates a single map's contents
packet Maps {
field item_damage: VarInt =,
@ -823,6 +1023,10 @@ state_packets!(
field z: Option<u8> = when(|p: &Maps_NoTracking| p.columns > 0),
field data: Option<LenPrefixedBytes<VarInt>> = when(|p: &Maps_NoTracking| p.columns > 0),
}
packet Maps_NoTracking_Data {
field item_damage: VarInt =,
field data: LenPrefixedBytes<i16> =,
}
/// EntityMove moves the entity with the id by the offsets provided.
packet EntityMove_i16 {
field entity_id: VarInt =,
@ -838,6 +1042,12 @@ state_packets!(
field delta_z: i8 =,
field on_ground: bool =,
}
packet EntityMove_i8_i32_NoGround {
field entity_id: i32 =,
field delta_x: i8 =,
field delta_y: i8 =,
field delta_z: i8 =,
}
/// EntityLookAndMove is a combination of EntityMove and EntityLook.
packet EntityLookAndMove_i16 {
field entity_id: VarInt =,
@ -857,17 +1067,33 @@ state_packets!(
field pitch: i8 =,
field on_ground: bool =,
}
packet EntityLookAndMove_i8_i32_NoGround {
field entity_id: i32 =,
field delta_x: i8 =,
field delta_y: i8 =,
field delta_z: i8 =,
field yaw: i8 =,
field pitch: i8 =,
}
/// EntityLook rotates the entity to the new angles provided.
packet EntityLook {
packet EntityLook_VarInt {
field entity_id: VarInt =,
field yaw: i8 =,
field pitch: i8 =,
field on_ground: bool =,
}
packet EntityLook_i32_NoGround {
field entity_id: i32 =,
field yaw: i8 =,
field pitch: i8 =,
}
/// Entity does nothing. It is a result of subclassing used in Minecraft.
packet Entity {
field entity_id: VarInt =,
}
packet Entity_i32 {
field entity_id: i32 =,
}
/// EntityUpdateNBT updates the entity named binary tag.
packet EntityUpdateNBT {
field entity_id: VarInt =,
@ -886,6 +1112,11 @@ state_packets!(
packet SignEditorOpen {
field location: Position =,
}
packet SignEditorOpen_i32 {
field x: i32 =,
field y: i32 =,
field z: i32 =,
}
/// CraftRecipeResponse is a response to CraftRecipeRequest, notifies the UI.
packet CraftRecipeResponse {
field window_id: u8 =,
@ -912,6 +1143,11 @@ state_packets!(
packet PlayerInfo {
field inner: packet::PlayerInfoData =,
}
packet PlayerInfo_String {
field name: String =,
field online: bool =,
field ping: u16 =,
}
/// 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.
@ -937,6 +1173,12 @@ state_packets!(
field entity_id: VarInt =,
field location: Position =,
}
packet EntityUsedBed_i32 {
field entity_id: i32 =,
field x: i32 =,
field y: u8 =,
field z: i32 =,
}
packet UnlockRecipes {
field action: VarInt =,
field crafting_book_open: bool =,
@ -948,11 +1190,18 @@ state_packets!(
packet EntityDestroy {
field entity_ids: LenPrefixed<VarInt, VarInt> =,
}
packet EntityDestroy_u8 {
field entity_ids: LenPrefixed<u8, i32> =,
}
/// EntityRemoveEffect removes an effect from an entity.
packet EntityRemoveEffect {
field entity_id: VarInt =,
field effect_id: i8 =,
}
packet EntityRemoveEffect_i32 {
field entity_id: i32 =,
field 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.
@ -972,6 +1221,10 @@ state_packets!(
field entity_id: VarInt =,
field head_yaw: i8 =,
}
packet EntityHeadLook_i32 {
field entity_id: i32 =,
field head_yaw: i8 =,
}
packet EntityStatus {
field entity_id: i32 =,
field entity_status: i8 =,
@ -1012,6 +1265,10 @@ state_packets!(
field entity_id: VarInt =,
field metadata: types::Metadata =,
}
packet EntityMetadata_i32 {
field entity_id: i32 =,
field metadata: types::Metadata =,
}
/// EntityAttach attaches to entities together, either by mounting or leashing.
/// -1 can be used at the EntityID to deattach.
packet EntityAttach {
@ -1031,6 +1288,12 @@ state_packets!(
field velocity_y: i16 =,
field velocity_z: i16 =,
}
packet EntityVelocity_i32 {
field entity_id: i32 =,
field velocity_x: i16 =,
field velocity_y: i16 =,
field 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.
@ -1044,18 +1307,33 @@ state_packets!(
field slot: u16 =,
field item: Option<item::Stack> =,
}
packet EntityEquipment_u16_i32 {
field entity_id: i32 =,
field slot: u16 =,
field item: Option<item::Stack> =,
}
/// SetExperience updates the experience bar on the client.
packet SetExperience {
field experience_bar: f32 =,
field level: VarInt =,
field total_experience: VarInt =,
}
packet SetExperience_i16 {
field experience_bar: f32 =,
field level: i16 =,
field total_experience: i16 =,
}
/// UpdateHealth is sent by the server to update the player's health and food.
packet UpdateHealth {
field health: f32 =,
field food: VarInt =,
field food_saturation: f32 =,
}
packet UpdateHealth_u16 {
field health: f32 =,
field food: u16 =,
field food_saturation: f32 =,
}
/// ScoreboardObjective creates/updates a scoreboard objective.
packet ScoreboardObjective {
field name: String =,
@ -1063,6 +1341,11 @@ state_packets!(
field value: String = when(|p: &ScoreboardObjective| p.mode == 0 || p.mode == 2),
field ty: String = when(|p: &ScoreboardObjective| p.mode == 0 || p.mode == 2),
}
packet ScoreboardObjective_NoMode {
field name: String =,
field value: String =,
field ty: u8 =,
}
/// SetPassengers mounts entities to an entity
packet SetPassengers {
field entity_id: VarInt =,
@ -1081,6 +1364,15 @@ state_packets!(
field color: Option<i8> = when(|p: &Teams| p.mode == 0 || p.mode == 2),
field players: Option<LenPrefixed<VarInt, String>> = when(|p: &Teams| p.mode == 0 || p.mode == 3 || p.mode == 4),
}
packet Teams_NoVisColor {
field name: String =,
field mode: u8 =,
field display_name: Option<String> = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2),
field prefix: Option<String> = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2),
field suffix: Option<String> = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2),
field flags: Option<u8> = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2),
field players: Option<LenPrefixed<VarInt, String>> = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 3 || p.mode == 4),
}
/// UpdateScore is used to update or remove an item from a scoreboard
/// objective.
packet UpdateScore {
@ -1089,11 +1381,22 @@ state_packets!(
field object_name: String =,
field value: Option<VarInt> = when(|p: &UpdateScore| p.action != 1),
}
packet UpdateScore_i32 {
field name: String =,
field action: u8 =,
field object_name: String =,
field value: Option<i32 > = when(|p: &UpdateScore_i32| p.action != 1),
}
/// SpawnPosition is sent to change the player's current spawn point. Currently
/// only used by the client for the compass.
packet SpawnPosition {
field location: Position =,
}
packet SpawnPosition_i32 {
field x: i32 =,
field y: i32 =,
field z: i32 =,
}
/// 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
@ -1136,6 +1439,15 @@ state_packets!(
field line3: format::Component =,
field line4: format::Component =,
}
packet UpdateSign_u16 {
field x: i32 =,
field y: u16 =,
field z: i32 =,
field line1: format::Component =,
field line2: format::Component =,
field line3: format::Component =,
field line4: format::Component =,
}
/// SoundEffect plays the named sound at the target location.
packet SoundEffect {
field name: VarInt =,
@ -1171,6 +1483,10 @@ state_packets!(
field collected_entity_id: VarInt =,
field collector_entity_id: VarInt =,
}
packet CollectItem_nocount_i32 {
field collected_entity_id: i32 =,
field collector_entity_id: i32 =,
}
/// EntityTeleport teleports the entity to the target location. This is
/// sent if the entity moves further than EntityMove allows.
packet EntityTeleport_f64 {
@ -1191,6 +1507,14 @@ state_packets!(
field pitch: i8 =,
field on_ground: bool =,
}
packet EntityTeleport_i32_i32_NoGround {
field entity_id: i32 =,
field x: i32 =,
field y: i32 =,
field z: i32 =,
field yaw: i8 =,
field pitch: i8 =,
}
packet Advancements {
field reset_clear: bool =,
field mapping: LenPrefixed<VarInt, packet::Advancement> =,
@ -1202,6 +1526,10 @@ state_packets!(
field entity_id: VarInt =,
field properties: LenPrefixed<i32, packet::EntityProperty> =,
}
packet EntityProperties_i32 {
field entity_id: i32 =,
field properties: LenPrefixed<i32, packet::EntityProperty_i16> =,
}
/// EntityEffect applies a status effect to an entity for a given duration.
packet EntityEffect {
field entity_id: VarInt =,
@ -1210,6 +1538,12 @@ state_packets!(
field duration: VarInt =,
field hide_particles: bool =,
}
packet EntityEffect_i32 {
field entity_id: i32 =,
field effect_id: i8 =,
field amplifier: i8 =,
field duration: i16 =,
}
}
}
login Login {
@ -1231,6 +1565,10 @@ state_packets!(
/// public key
field verify_token: LenPrefixedBytes<VarInt> =,
}
packet EncryptionResponse_i16 {
field shared_secret: LenPrefixedBytes<i16> =,
field verify_token: LenPrefixedBytes<i16> =,
}
}
clientbound Clientbound {
/// LoginDisconnect is sent by the server if there was any issues
@ -1252,6 +1590,11 @@ state_packets!(
/// correctly
field verify_token: LenPrefixedBytes<VarInt> =,
}
packet EncryptionRequest_i16 {
field server_id: String =,
field public_key: LenPrefixedBytes<i16> =,
field verify_token: LenPrefixedBytes<i16> =,
}
/// LoginSuccess is sent by the server if the player successfully
/// authenicates with the session servers (online mode) or straight
/// after LoginStart (offline mode).
@ -1323,6 +1666,29 @@ state_packets!(
}
);
#[derive(Debug, Default)]
pub struct SpawnProperty {
pub name: String,
pub value: String,
pub signature: String,
}
impl Serializable for SpawnProperty {
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
Ok(SpawnProperty {
name: Serializable::read_from(buf)?,
value: Serializable::read_from(buf)?,
signature: Serializable::read_from(buf)?,
})
}
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
self.name.write_to(buf)?;
self.value.write_to(buf)?;
self.signature.write_to(buf)
}
}
#[derive(Debug, Default)]
pub struct Statistic {
pub name: String,
@ -1625,6 +1991,30 @@ impl Serializable for EntityProperty {
}
}
#[derive(Debug, Default)]
pub struct EntityProperty_i16 {
pub key: String,
pub value: f64,
pub modifiers: LenPrefixed<i16, PropertyModifier>,
}
impl Serializable for EntityProperty_i16 {
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
Ok(EntityProperty_i16 {
key: Serializable::read_from(buf)?,
value: Serializable::read_from(buf)?,
modifiers: Serializable::read_from(buf)?,
})
}
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
self.key.write_to(buf)?;
self.value.write_to(buf)?;
self.modifiers.write_to(buf)
}
}
#[derive(Debug, Default)]
pub struct PropertyModifier {
pub uuid: UUID,

View File

@ -7,6 +7,7 @@ mod v1_9_2;
mod v1_9;
mod v15w39c;
mod v1_8_9;
mod v1_7_10;
pub fn translate_internal_packet_id_for_version(version: i32, state: State, dir: Direction, id: i32, to_internal: bool) -> i32 {
match version {
@ -36,6 +37,9 @@ pub fn translate_internal_packet_id_for_version(version: i32, state: State, dir:
// 1.8.9 - 1.8
47 => v1_8_9::translate_internal_packet_id(state, dir, id, to_internal),
// 1.7.10 - 1.7.6
5 => v1_7_10::translate_internal_packet_id(state, dir, id, to_internal),
_ => panic!("unsupported protocol version"),
}
}

View File

@ -48,12 +48,12 @@ protocol_packet_ids!(
0x08 => BlockBreakAnimation
0x09 => UpdateBlockEntity
0x0a => BlockAction
0x0b => BlockChange
0x0b => BlockChange_VarInt
0x0c => BossBar
0x0d => ServerDifficulty
0x0e => TabCompleteReply
0x0f => ServerMessage
0x10 => MultiBlockChange
0x10 => MultiBlockChange_VarInt
0x11 => ConfirmTransaction
0x12 => WindowClose
0x13 => WindowOpen
@ -77,7 +77,7 @@ protocol_packet_ids!(
0x25 => Maps
0x26 => EntityMove_i8
0x27 => EntityLookAndMove_i8
0x28 => EntityLook
0x28 => EntityLook_VarInt
0x29 => Entity
0x2a => SignEditorOpen
0x2b => PlayerAbilities

View File

@ -51,12 +51,12 @@ protocol_packet_ids!(
0x08 => BlockBreakAnimation
0x09 => UpdateBlockEntity
0x0a => BlockAction
0x0b => BlockChange
0x0b => BlockChange_VarInt
0x0c => BossBar
0x0d => ServerDifficulty
0x0e => TabCompleteReply
0x0f => ServerMessage
0x10 => MultiBlockChange
0x10 => MultiBlockChange_VarInt
0x11 => ConfirmTransaction
0x12 => WindowClose
0x13 => WindowOpen
@ -79,7 +79,7 @@ protocol_packet_ids!(
0x24 => Maps
0x25 => EntityMove_i16
0x26 => EntityLookAndMove_i16
0x27 => EntityLook
0x27 => EntityLook_VarInt
0x28 => Entity
0x29 => VehicleTeleport
0x2a => SignEditorOpen

View File

@ -51,12 +51,12 @@ protocol_packet_ids!(
0x08 => BlockBreakAnimation
0x09 => UpdateBlockEntity
0x0a => BlockAction
0x0b => BlockChange
0x0b => BlockChange_VarInt
0x0c => BossBar
0x0d => ServerDifficulty
0x0e => TabCompleteReply
0x0f => ServerMessage
0x10 => MultiBlockChange
0x10 => MultiBlockChange_VarInt
0x11 => ConfirmTransaction
0x12 => WindowClose
0x13 => WindowOpen
@ -79,7 +79,7 @@ protocol_packet_ids!(
0x24 => Maps
0x25 => EntityMove_i16
0x26 => EntityLookAndMove_i16
0x27 => EntityLook
0x27 => EntityLook_VarInt
0x28 => Entity
0x29 => VehicleTeleport
0x2a => SignEditorOpen

View File

@ -54,12 +54,12 @@ protocol_packet_ids!(
0x08 => BlockBreakAnimation
0x09 => UpdateBlockEntity
0x0a => BlockAction
0x0b => BlockChange
0x0b => BlockChange_VarInt
0x0c => BossBar
0x0d => ServerDifficulty
0x0e => TabCompleteReply
0x0f => ServerMessage
0x10 => MultiBlockChange
0x10 => MultiBlockChange_VarInt
0x11 => ConfirmTransaction
0x12 => WindowClose
0x13 => WindowOpen
@ -83,7 +83,7 @@ protocol_packet_ids!(
0x25 => Entity
0x26 => EntityMove_i16
0x27 => EntityLookAndMove_i16
0x28 => EntityLook
0x28 => EntityLook_VarInt
0x29 => VehicleTeleport
0x2a => SignEditorOpen
0x2b => CraftRecipeResponse

View File

@ -0,0 +1,127 @@
protocol_packet_ids!(
handshake Handshaking {
serverbound Serverbound {
0x00 => Handshake
}
clientbound Clientbound {
}
}
play Play {
serverbound Serverbound {
0x00 => KeepAliveServerbound_i32
0x01 => ChatMessage
0x02 => UseEntity_Handsfree_i32
0x03 => Player
0x04 => PlayerPosition_HeadY
0x05 => PlayerLook
0x06 => PlayerPositionLook_HeadY
0x07 => PlayerDigging_u8_u8y
0x08 => PlayerBlockPlacement_u8_Item_u8y
0x09 => HeldItemChange
0x0a => ArmSwing_Handsfree_ID
0x0b => PlayerAction_i32
0x0c => SteerVehicle_jump_unmount
0x0d => CloseWindow
0x0e => ClickWindow_u8
0x0f => ConfirmTransactionServerbound
0x10 => CreativeInventoryAction
0x11 => EnchantItem
0x12 => SetSign_i16y
0x13 => ClientAbilities
0x14 => TabComplete_NoAssume_NoTarget
0x15 => ClientSettings_u8_Handsfree_Difficulty
0x16 => ClientStatus_u8
0x17 => PluginMessageServerbound_i16
}
clientbound Clientbound {
0x00 => KeepAliveClientbound_i32
0x01 => JoinGame_i8_NoDebug
0x02 => ServerMessage_NoPosition
0x03 => TimeUpdate
0x04 => EntityEquipment_u16_i32
0x05 => SpawnPosition_i32
0x06 => UpdateHealth_u16
0x07 => Respawn
0x08 => TeleportPlayer_NoConfirm
0x09 => SetCurrentHotbarSlot
0x0a => EntityUsedBed_i32
0x0b => Animation
0x0c => SpawnPlayer_i32_HeldItem_String
0x0d => CollectItem_nocount_i32
0x0e => SpawnObject_i32_NoUUID
0x0f => SpawnMob_u8_i32_NoUUID
0x10 => SpawnPainting_NoUUID_i32
0x11 => SpawnExperienceOrb_i32
0x12 => EntityVelocity_i32
0x13 => EntityDestroy_u8
0x14 => Entity_i32
0x15 => EntityMove_i8_i32_NoGround
0x16 => EntityLook_i32_NoGround
0x17 => EntityLookAndMove_i8_i32_NoGround
0x18 => EntityTeleport_i32_i32_NoGround
0x19 => EntityHeadLook_i32
0x1a => EntityStatus
0x1b => EntityAttach_leashed
0x1c => EntityMetadata_i32
0x1d => EntityEffect_i32
0x1e => EntityRemoveEffect_i32
0x1f => SetExperience_i16
0x20 => EntityProperties_i32
0x21 => ChunkData_17
0x22 => MultiBlockChange_u16
0x23 => BlockChange_u8
0x24 => BlockAction_u16
0x25 => BlockBreakAnimation_i32
0x26 => ChunkDataBulk_17
0x27 => Explosion
0x28 => Effect_u8y
0x29 => NamedSoundEffect_u8_NoCategory
0x2a => Particle_Named
0x2b => ChangeGameState
0x2c => SpawnGlobalEntity_i32
0x2d => WindowOpen_u8
0x2e => WindowClose
0x2f => WindowSetSlot
0x30 => WindowItems
0x31 => WindowProperty
0x32 => ConfirmTransaction
0x33 => UpdateSign_u16
0x34 => Maps_NoTracking_Data
0x35 => UpdateBlockEntity_Data
0x36 => SignEditorOpen_i32
0x37 => Statistics
0x38 => PlayerInfo_String
0x39 => PlayerAbilities
0x3a => TabCompleteReply
0x3b => ScoreboardObjective_NoMode
0x3c => UpdateScore_i32
0x3d => ScoreboardDisplay
0x3e => Teams_NoVisColor
0x3f => PluginMessageClientbound_i16
0x40 => Disconnect
}
}
login Login {
serverbound Serverbound {
0x00 => LoginStart
0x01 => EncryptionResponse_i16
}
clientbound Clientbound {
0x00 => LoginDisconnect
0x01 => EncryptionRequest_i16
0x02 => LoginSuccess
}
}
status Status {
serverbound Serverbound {
0x00 => StatusRequest
0x01 => StatusPing
}
clientbound Clientbound {
0x00 => StatusResponse
0x01 => StatusPong
}
}
);

View File

@ -58,7 +58,7 @@ protocol_packet_ids!(
0x13 => EntityDestroy
0x14 => Entity
0x15 => EntityMove_i8
0x16 => EntityLook
0x16 => EntityLook_VarInt
0x17 => EntityLookAndMove_i8
0x18 => EntityTeleport_i32
0x19 => EntityHeadLook
@ -70,8 +70,8 @@ protocol_packet_ids!(
0x1f => SetExperience
0x20 => EntityProperties
0x21 => ChunkData_NoEntities_u16
0x22 => MultiBlockChange
0x23 => BlockChange
0x22 => MultiBlockChange_VarInt
0x23 => BlockChange_VarInt
0x24 => BlockAction
0x25 => BlockBreakAnimation
0x26 => ChunkDataBulk

View File

@ -51,12 +51,12 @@ protocol_packet_ids!(
0x08 => BlockBreakAnimation
0x09 => UpdateBlockEntity
0x0a => BlockAction
0x0b => BlockChange
0x0b => BlockChange_VarInt
0x0c => BossBar
0x0d => ServerDifficulty
0x0e => TabCompleteReply
0x0f => ServerMessage
0x10 => MultiBlockChange
0x10 => MultiBlockChange_VarInt
0x11 => ConfirmTransaction
0x12 => WindowClose
0x13 => WindowOpen
@ -79,7 +79,7 @@ protocol_packet_ids!(
0x24 => Maps
0x25 => EntityMove_i16
0x26 => EntityLookAndMove_i16
0x27 => EntityLook
0x27 => EntityLook_VarInt
0x28 => Entity
0x29 => VehicleTeleport
0x2a => SignEditorOpen

View File

@ -51,12 +51,12 @@ protocol_packet_ids!(
0x08 => BlockBreakAnimation
0x09 => UpdateBlockEntity
0x0a => BlockAction
0x0b => BlockChange
0x0b => BlockChange_VarInt
0x0c => BossBar
0x0d => ServerDifficulty
0x0e => TabCompleteReply
0x0f => ServerMessage
0x10 => MultiBlockChange
0x10 => MultiBlockChange_VarInt
0x11 => ConfirmTransaction
0x12 => WindowClose
0x13 => WindowOpen
@ -79,7 +79,7 @@ protocol_packet_ids!(
0x24 => Maps
0x25 => EntityMove_i16
0x26 => EntityLookAndMove_i16
0x27 => EntityLook
0x27 => EntityLook_VarInt
0x28 => Entity
0x29 => VehicleTeleport
0x2a => SignEditorOpen

View File

@ -120,14 +120,23 @@ impl Server {
username: profile.username.clone(),
})?;
let packet;
use std::rc::Rc;
let (server_id, public_key, verify_token);
loop {
match conn.read_packet()? {
protocol::packet::Packet::SetInitialCompression(val) => {
conn.set_compresssion(val.threshold.0);
},
protocol::packet::Packet::EncryptionRequest(val) => {
packet = val;
server_id = Rc::new(val.server_id);
public_key = Rc::new(val.public_key.data);
verify_token = Rc::new(val.verify_token.data);
break;
},
protocol::packet::Packet::EncryptionRequest_i16(val) => {
server_id = Rc::new(val.server_id);
public_key = Rc::new(val.public_key.data);
verify_token = Rc::new(val.verify_token.data);
break;
},
protocol::packet::Packet::LoginSuccess(val) => {
@ -145,25 +154,26 @@ impl Server {
};
}
println!("packet.public_key.data = {:?}", &packet.public_key.data);
let mut shared = [0; 16];
// TODO: is this cryptographically secure enough?
rand::thread_rng().fill(&mut shared);
println!("shared ({:} bytes) = {:?}", shared.len(), &shared);
println!("packet.verify_token.data = {:?}", &packet.verify_token.data);
let shared_e = rsa_public_encrypt_pkcs1::encrypt(&public_key, &shared).unwrap();
let token_e = rsa_public_encrypt_pkcs1::encrypt(&public_key, &verify_token).unwrap();
let shared_e = rsa_public_encrypt_pkcs1::encrypt(&packet.public_key.data, &shared).unwrap();
let token_e = rsa_public_encrypt_pkcs1::encrypt(&packet.public_key.data, &packet.verify_token.data).unwrap();
println!("new shared_e({:}) = {:?}", shared_e.len(), &shared_e);
println!("new token_e({:}) = {:?}", token_e.len(), &token_e);
profile.join_server(&server_id, &shared, &public_key)?;
profile.join_server(&packet.server_id, &shared, &packet.public_key.data)?;
conn.write_packet(protocol::packet::login::serverbound::EncryptionResponse {
shared_secret: protocol::LenPrefixedBytes::new(shared_e),
verify_token: protocol::LenPrefixedBytes::new(token_e),
})?;
if protocol_version >= 47 {
conn.write_packet(protocol::packet::login::serverbound::EncryptionResponse {
shared_secret: protocol::LenPrefixedBytes::new(shared_e),
verify_token: protocol::LenPrefixedBytes::new(token_e),
})?;
} else {
conn.write_packet(protocol::packet::login::serverbound::EncryptionResponse_i16 {
shared_secret: protocol::LenPrefixedBytes::new(shared_e),
verify_token: protocol::LenPrefixedBytes::new(token_e),
})?;
}
let mut read = conn.clone();
let mut write = conn.clone();
@ -377,36 +387,51 @@ impl Server {
self pck {
JoinGame_i32 => on_game_join_i32,
JoinGame_i8 => on_game_join_i8,
JoinGame_i8_NoDebug => on_game_join_i8_nodebug,
Respawn => on_respawn,
KeepAliveClientbound_i64 => on_keep_alive_i64,
KeepAliveClientbound_VarInt => on_keep_alive_varint,
KeepAliveClientbound_i32 => on_keep_alive_i32,
ChunkData => on_chunk_data,
ChunkData_NoEntities => on_chunk_data_no_entities,
ChunkData_NoEntities_u16 => on_chunk_data_no_entities_u16,
ChunkData_17 => on_chunk_data_17,
ChunkDataBulk => on_chunk_data_bulk,
ChunkDataBulk_17 => on_chunk_data_bulk_17,
ChunkUnload => on_chunk_unload,
BlockChange => on_block_change,
MultiBlockChange => on_multi_block_change,
BlockChange_VarInt => on_block_change_varint,
BlockChange_u8 => on_block_change_u8,
MultiBlockChange_VarInt => on_multi_block_change_varint,
MultiBlockChange_u16 => on_multi_block_change_u16,
TeleportPlayer_WithConfirm => on_teleport_player_withconfirm,
TeleportPlayer_NoConfirm => on_teleport_player_noconfirm,
TimeUpdate => on_time_update,
ChangeGameState => on_game_state_change,
UpdateBlockEntity => on_block_entity_update,
UpdateBlockEntity_Data => on_block_entity_update_data,
UpdateSign => on_sign_update,
UpdateSign_u16 => on_sign_update_u16,
PlayerInfo => on_player_info,
PlayerInfo_String => on_player_info_string,
Disconnect => on_disconnect,
// Entities
EntityDestroy => on_entity_destroy,
EntityDestroy_u8 => on_entity_destroy_u8,
SpawnPlayer_f64 => on_player_spawn_f64,
SpawnPlayer_i32 => on_player_spawn_i32,
SpawnPlayer_i32_HeldItem => on_player_spawn_i32_helditem,
SpawnPlayer_i32_HeldItem_String => on_player_spawn_i32_helditem_string,
EntityTeleport_f64 => on_entity_teleport_f64,
EntityTeleport_i32 => on_entity_teleport_i32,
EntityTeleport_i32_i32_NoGround => on_entity_teleport_i32_i32_noground,
EntityMove_i16 => on_entity_move_i16,
EntityMove_i8 => on_entity_move_i8,
EntityLook => on_entity_look,
EntityMove_i8_i32_NoGround => on_entity_move_i8_i32_noground,
EntityLook_VarInt => on_entity_look_varint,
EntityLook_i32_NoGround => on_entity_look_i32_noground,
EntityLookAndMove_i16 => on_entity_look_and_move_i16,
EntityLookAndMove_i8 => on_entity_look_and_move_i8,
EntityLookAndMove_i8_i32_NoGround => on_entity_look_and_move_i8_i32_noground,
}
},
Err(err) => panic!("Err: {:?}", err),
@ -503,15 +528,28 @@ impl Server {
// Sync our position to the server
// Use the smaller packets when possible
let packet = packet::play::serverbound::PlayerPositionLook {
x: position.position.x,
y: position.position.y,
z: position.position.z,
yaw: -(rotation.yaw as f32) * (180.0 / PI),
pitch: (-rotation.pitch as f32) * (180.0 / PI) + 180.0,
on_ground,
};
self.write_packet(packet);
if self.protocol_version >= 47 {
let packet = packet::play::serverbound::PlayerPositionLook {
x: position.position.x,
y: position.position.y,
z: position.position.z,
yaw: -(rotation.yaw as f32) * (180.0 / PI),
pitch: (-rotation.pitch as f32) * (180.0 / PI) + 180.0,
on_ground,
};
self.write_packet(packet);
} else {
let packet = packet::play::serverbound::PlayerPositionLook_HeadY {
x: position.position.x,
feet_y: position.position.y - 1.62,
head_y: position.position.y,
z: position.position.z,
yaw: -(rotation.yaw as f32) * (180.0 / PI),
pitch: (-rotation.pitch as f32) * (180.0 / PI) + 180.0,
on_ground,
};
self.write_packet(packet);
}
}
}
@ -561,7 +599,7 @@ impl Server {
cursor_y: (at.y * 16.0) as u8,
cursor_z: (at.z * 16.0) as u8,
});
} else {
} else if self.protocol_version >= 47 {
self.write_packet(packet::play::serverbound::PlayerBlockPlacement_u8_Item {
location: pos,
face: match face {
@ -578,6 +616,25 @@ impl Server {
cursor_y: (at.y * 16.0) as u8,
cursor_z: (at.z * 16.0) as u8,
});
} else {
self.write_packet(packet::play::serverbound::PlayerBlockPlacement_u8_Item_u8y {
x: pos.x,
y: pos.y as u8,
z: pos.x,
face: match face {
Direction::Down => 0,
Direction::Up => 1,
Direction::North => 2,
Direction::South => 3,
Direction::West => 4,
Direction::East => 5,
_ => unreachable!(),
},
hand: None,
cursor_x: (at.x * 16.0) as u8,
cursor_y: (at.y * 16.0) as u8,
cursor_z: (at.z * 16.0) as u8,
});
}
}
}
@ -599,6 +656,12 @@ impl Server {
});
}
fn on_keep_alive_i32(&mut self, keep_alive: packet::play::clientbound::KeepAliveClientbound_i32) {
self.write_packet(packet::play::serverbound::KeepAliveServerbound_i32 {
id: keep_alive.id,
});
}
fn on_game_join_i32(&mut self, join: packet::play::clientbound::JoinGame_i32) {
self.on_game_join(join.gamemode, join.entity_id)
}
@ -607,6 +670,11 @@ impl Server {
self.on_game_join(join.gamemode, join.entity_id)
}
fn on_game_join_i8_nodebug(&mut self, join: packet::play::clientbound::JoinGame_i8_NoDebug) {
self.on_game_join(join.gamemode, join.entity_id)
}
fn on_game_join(&mut self, gamemode: u8, entity_id: i32) {
let gamemode = Gamemode::from_int((gamemode & 0x7) as i32);
let player = entity::player::create_local(&mut self.entities);
@ -622,9 +690,14 @@ impl Server {
self.player = Some(player);
// Let the server know who we are
self.write_packet(plugin_messages::Brand {
brand: "Steven".into(),
}.as_message());
let brand = plugin_messages::Brand {
brand: "Steven".into(),
};
if self.protocol_version >= 47 {
self.write_packet(brand.as_message());
} else {
self.write_packet(brand.as_message17());
}
}
fn on_respawn(&mut self, respawn: packet::play::clientbound::Respawn) {
@ -672,6 +745,14 @@ impl Server {
}
}
fn on_entity_destroy_u8(&mut self, entity_destroy: packet::play::clientbound::EntityDestroy_u8) {
for id in entity_destroy.entity_ids.data {
if let Some(entity) = self.entity_map.remove(&id) {
self.entities.remove_entity(entity);
}
}
}
fn on_entity_teleport_f64(&mut self, entity_telport: packet::play::clientbound::EntityTeleport_f64) {
self.on_entity_teleport(entity_telport.entity_id.0, entity_telport.x, entity_telport.y, entity_telport.z, entity_telport.yaw as f64, entity_telport.pitch as f64, entity_telport.on_ground)
}
@ -680,6 +761,12 @@ impl Server {
self.on_entity_teleport(entity_telport.entity_id.0, entity_telport.x as f64, entity_telport.y as f64, entity_telport.z as f64, entity_telport.yaw as f64, entity_telport.pitch as f64, entity_telport.on_ground)
}
fn on_entity_teleport_i32_i32_noground(&mut self, entity_telport: packet::play::clientbound::EntityTeleport_i32_i32_NoGround) {
let on_ground = true; // TODO: how is this supposed to be set? (for 1.7)
self.on_entity_teleport(entity_telport.entity_id, entity_telport.x as f64, entity_telport.y as f64, entity_telport.z as f64, entity_telport.yaw as f64, entity_telport.pitch as f64, on_ground)
}
fn on_entity_teleport(&mut self, entity_id: i32, x: f64, y: f64, z: f64, yaw: f64, pitch: f64, _on_ground: bool) {
use std::f64::consts::PI;
if let Some(entity) = self.entity_map.get(&entity_id) {
@ -701,6 +788,11 @@ impl Server {
self.on_entity_move(m.entity_id.0, m.delta_x as f64, m.delta_y as f64, m.delta_z as f64)
}
fn on_entity_move_i8_i32_noground(&mut self, m: packet::play::clientbound::EntityMove_i8_i32_NoGround) {
self.on_entity_move(m.entity_id, m.delta_x as f64, m.delta_y as f64, m.delta_z as f64)
}
fn on_entity_move(&mut self, entity_id: i32, delta_x: f64, delta_y: f64, delta_z: f64) {
if let Some(entity) = self.entity_map.get(&entity_id) {
let position = self.entities.get_component_mut(*entity, self.target_position).unwrap();
@ -710,15 +802,23 @@ impl Server {
}
}
fn on_entity_look(&mut self, look: packet::play::clientbound::EntityLook) {
fn on_entity_look(&mut self, entity_id: i32, yaw: f64, pitch: f64) {
use std::f64::consts::PI;
if let Some(entity) = self.entity_map.get(&look.entity_id.0) {
if let Some(entity) = self.entity_map.get(&entity_id) {
let rotation = self.entities.get_component_mut(*entity, self.target_rotation).unwrap();
rotation.yaw = -((look.yaw as f64) / 256.0) * PI * 2.0;
rotation.pitch = -((look.pitch as f64) / 256.0) * PI * 2.0;
rotation.yaw = -(yaw / 256.0) * PI * 2.0;
rotation.pitch = -(pitch / 256.0) * PI * 2.0;
}
}
fn on_entity_look_varint(&mut self, look: packet::play::clientbound::EntityLook_VarInt) {
self.on_entity_look(look.entity_id.0, look.yaw as f64, look.pitch as f64)
}
fn on_entity_look_i32_noground(&mut self, look: packet::play::clientbound::EntityLook_i32_NoGround) {
self.on_entity_look(look.entity_id, look.yaw as f64, look.pitch as f64)
}
fn on_entity_look_and_move_i16(&mut self, lookmove: packet::play::clientbound::EntityLookAndMove_i16) {
self.on_entity_look_and_move(lookmove.entity_id.0,
lookmove.delta_x as f64, lookmove.delta_y as f64, lookmove.delta_z as f64,
@ -731,6 +831,12 @@ impl Server {
lookmove.yaw as f64, lookmove.pitch as f64)
}
fn on_entity_look_and_move_i8_i32_noground(&mut self, lookmove: packet::play::clientbound::EntityLookAndMove_i8_i32_NoGround) {
self.on_entity_look_and_move(lookmove.entity_id,
lookmove.delta_x as f64, lookmove.delta_y as f64, lookmove.delta_z as f64,
lookmove.yaw as f64, lookmove.pitch as f64)
}
fn on_entity_look_and_move(&mut self, entity_id: i32, delta_x: f64, delta_y: f64, delta_z: f64, yaw: f64, pitch: f64) {
use std::f64::consts::PI;
if let Some(entity) = self.entity_map.get(&entity_id) {
@ -756,6 +862,10 @@ impl Server {
self.on_player_spawn(spawn.entity_id.0, spawn.uuid, spawn.x as f64, spawn.y as f64, spawn.z as f64, spawn.yaw as f64, spawn.pitch as f64)
}
fn on_player_spawn_i32_helditem_string(&mut self, spawn: packet::play::clientbound::SpawnPlayer_i32_HeldItem_String) {
self.on_player_spawn(spawn.entity_id.0, protocol::UUID::from_str(&spawn.uuid), spawn.x as f64, spawn.y as f64, spawn.z as f64, spawn.yaw as f64, spawn.pitch as f64)
}
fn on_player_spawn(&mut self, entity_id: i32, uuid: protocol::UUID, x: f64, y: f64, z: f64, pitch: f64, yaw: f64) {
use std::f64::consts::PI;
if let Some(entity) = self.entity_map.remove(&entity_id) {
@ -855,6 +965,10 @@ impl Server {
}
}
fn on_block_entity_update_data(&mut self, _block_update: packet::play::clientbound::UpdateBlockEntity_Data) {
// TODO: handle UpdateBlockEntity_Data for 1.7, decompress gzipped_nbt
}
fn on_sign_update(&mut self, mut update_sign: packet::play::clientbound::UpdateSign) {
use crate::format;
format::convert_legacy(&mut update_sign.line1);
@ -870,6 +984,26 @@ impl Server {
));
}
fn on_sign_update_u16(&mut self, mut update_sign: packet::play::clientbound::UpdateSign_u16) {
use crate::format;
format::convert_legacy(&mut update_sign.line1);
format::convert_legacy(&mut update_sign.line2);
format::convert_legacy(&mut update_sign.line3);
format::convert_legacy(&mut update_sign.line4);
self.world.add_block_entity_action(world::BlockEntityAction::UpdateSignText(
Position::new(update_sign.x, update_sign.y as i32, update_sign.z),
update_sign.line1,
update_sign.line2,
update_sign.line3,
update_sign.line4,
));
}
fn on_player_info_string(&mut self, _player_info: packet::play::clientbound::PlayerInfo_String) {
// TODO: support PlayerInfo_String for 1.7
}
fn on_player_info(&mut self, player_info: packet::play::clientbound::PlayerInfo) {
use crate::protocol::packet::PlayerDetail::*;
use base64;
@ -1003,23 +1137,39 @@ impl Server {
self.world.load_chunks18(chunk_data.new, skylight, &chunk_meta, chunk_data.data.data).unwrap();
}
fn on_chunk_data_17(&mut self, chunk_data: packet::play::clientbound::ChunkData_17) {
self.world.load_chunk17(chunk_data.chunk_x, chunk_data.chunk_z, chunk_data.new, chunk_data.bitmask, chunk_data.add_bitmask, chunk_data.compressed_data.data).unwrap();
}
fn on_chunk_data_bulk(&mut self, bulk: packet::play::clientbound::ChunkDataBulk) {
let new = true;
self.world.load_chunks18(new, bulk.skylight, &bulk.chunk_meta.data, bulk.chunk_data.to_vec()).unwrap();
}
fn on_chunk_data_bulk_17(&mut self, bulk: packet::play::clientbound::ChunkDataBulk_17) {
self.world.load_chunks17(bulk.chunk_column_count, bulk.data_length, bulk.skylight, &bulk.chunk_data_and_meta).unwrap();
}
fn on_chunk_unload(&mut self, chunk_unload: packet::play::clientbound::ChunkUnload) {
self.world.unload_chunk(chunk_unload.x, chunk_unload.z, &mut self.entities);
}
fn on_block_change(&mut self, block_change: packet::play::clientbound::BlockChange) {
self.world.set_block(
block_change.location,
block::Block::by_vanilla_id(block_change.block_id.0 as usize)
fn on_block_change(&mut self, location: Position, id: i32) {
self.world.set_block(location, block::Block::by_vanilla_id(id as usize))
}
fn on_block_change_varint(&mut self, block_change: packet::play::clientbound::BlockChange_VarInt) {
self.on_block_change(block_change.location, block_change.block_id.0)
}
fn on_block_change_u8(&mut self, block_change: packet::play::clientbound::BlockChange_u8) {
self.on_block_change(
crate::shared::Position::new(block_change.x, block_change.y as i32, block_change.z),
(block_change.block_id.0 << 4) | (block_change.block_metadata as i32)
);
}
fn on_multi_block_change(&mut self, block_change: packet::play::clientbound::MultiBlockChange) {
fn on_multi_block_change_varint(&mut self, block_change: packet::play::clientbound::MultiBlockChange_VarInt) {
let ox = block_change.chunk_x << 4;
let oz = block_change.chunk_z << 4;
for record in block_change.records.data {
@ -1033,6 +1183,30 @@ impl Server {
);
}
}
fn on_multi_block_change_u16(&mut self, block_change: packet::play::clientbound::MultiBlockChange_u16) {
let ox = block_change.chunk_x << 4;
let oz = block_change.chunk_z << 4;
let mut data = std::io::Cursor::new(block_change.data);
for _ in 0 .. block_change.record_count {
use byteorder::{BigEndian, ReadBytesExt};
let record = data.read_u32::<BigEndian>().unwrap();
let id = record & 0x0000_ffff;
let y = ((record & 0x00ff_0000) >> 16) as i32;
let z = oz + ((record & 0x0f00_0000) >> 24) as i32;
let x = ox + ((record & 0xf000_0000) >> 28) as i32;
self.world.set_block(
Position::new(x, y, z),
block::Block::by_vanilla_id(id as usize)
);
}
}
}
#[derive(Debug, Clone, Copy)]

View File

@ -1,6 +1,7 @@
use crate::protocol::Serializable;
use crate::protocol::packet::play::serverbound::PluginMessageServerbound;
use crate::protocol::packet::play::serverbound::PluginMessageServerbound_i16;
pub struct Brand {
pub brand: String,
@ -15,4 +16,15 @@ impl Brand {
data,
}
}
// TODO: cleanup this duplication for 1.7, return either message dynamically
pub fn as_message17(self) -> PluginMessageServerbound_i16 {
let mut data = vec![];
Serializable::write_to(&self.brand, &mut data).unwrap();
PluginMessageServerbound_i16 {
channel: "MC|Brand".into(),
data: crate::protocol::LenPrefixedBytes::<i16>::new(data),
}
}
}

View File

@ -28,6 +28,8 @@ use crate::chunk_builder;
use crate::ecs;
use crate::entity::block_entity;
use crate::format;
use flate2::read::ZlibDecoder;
use std::io::Read;
pub mod biome;
mod storage;
@ -558,6 +560,24 @@ impl World {
Ok(())
}
fn dirty_chunks_by_bitmask(&mut self, x: i32, z: i32, mask: u16) {
for i in 0 .. 16 {
if mask & (1 << i) == 0 {
continue;
}
for pos in [
(-1, 0, 0), (1, 0, 0),
(0, -1, 0), (0, 1, 0),
(0, 0, -1), (0, 0, 1)].into_iter() {
self.flag_section_dirty(x + pos.0, i as i32 + pos.1, z + pos.2);
}
self.update_range(
(x<<4) - 1, (i<<4) - 1, (z<<4) - 1,
(x<<4) + 17, (i<<4) + 17, (z<<4) + 17
);
}
}
pub fn load_chunk18(&mut self, x: i32, z: i32, new: bool, _skylight: bool, mask: u16, data: &mut std::io::Cursor<Vec<u8>>) -> Result<(), protocol::Error> {
use std::io::Read;
use byteorder::ReadBytesExt;
@ -635,21 +655,175 @@ impl World {
chunk.calculate_heightmap();
}
for i in 0 .. 16 {
if mask & (1 << i) == 0 {
continue;
}
for pos in [
(-1, 0, 0), (1, 0, 0),
(0, -1, 0), (0, 1, 0),
(0, 0, -1), (0, 0, 1)].into_iter() {
self.flag_section_dirty(x + pos.0, i as i32 + pos.1, z + pos.2);
}
self.update_range(
(x<<4) - 1, (i<<4) - 1, (z<<4) - 1,
(x<<4) + 17, (i<<4) + 17, (z<<4) + 17
);
self.dirty_chunks_by_bitmask(x, z, mask);
Ok(())
}
pub fn load_chunks17(&mut self, chunk_column_count: u16, data_length: i32, skylight: bool, data: &[u8]) -> Result<(), protocol::Error> {
let compressed_chunk_data = &data[0..data_length as usize];
let metadata = &data[data_length as usize..];
let mut zlib = ZlibDecoder::new(std::io::Cursor::new(compressed_chunk_data.to_vec()));
let mut chunk_data = Vec::new();
zlib.read_to_end(&mut chunk_data)?;
let mut chunk_data = std::io::Cursor::new(chunk_data);
// Chunk metadata
let mut metadata = std::io::Cursor::new(metadata);
for _i in 0..chunk_column_count {
use byteorder::ReadBytesExt;
let x = metadata.read_i32::<byteorder::BigEndian>()?;
let z = metadata.read_i32::<byteorder::BigEndian>()?;
let mask = metadata.read_u16::<byteorder::BigEndian>()?;
let mask_add = metadata.read_u16::<byteorder::BigEndian>()?;
let new = true;
self.load_uncompressed_chunk17(x, z, new, skylight, mask, mask_add, &mut chunk_data)?;
}
Ok(())
}
pub fn load_chunk17(&mut self, x: i32, z: i32, new: bool, mask: u16, mask_add: u16, compressed_data: Vec<u8>) -> Result<(), protocol::Error> {
let mut zlib = ZlibDecoder::new(std::io::Cursor::new(compressed_data.to_vec()));
let mut data = Vec::new();
zlib.read_to_end(&mut data)?;
let skylight = true;
self.load_uncompressed_chunk17(x, z, new, skylight, mask, mask_add, &mut std::io::Cursor::new(data))
}
fn load_uncompressed_chunk17(&mut self, x: i32, z: i32, new: bool, skylight: bool, mask: u16, mask_add: u16, data: &mut std::io::Cursor<Vec<u8>>) -> Result<(), protocol::Error> {
use std::io::Read;
use crate::types::nibble;
let cpos = CPos(x, z);
{
let chunk = if new {
self.chunks.insert(cpos, Chunk::new(cpos));
self.chunks.get_mut(&cpos).unwrap()
} else {
if !self.chunks.contains_key(&cpos) {
return Ok(());
}
self.chunks.get_mut(&cpos).unwrap()
};
// Block type array - whole byte per block
let mut block_types = [[0u8; 4096]; 16];
for i in 0 .. 16 {
if chunk.sections[i].is_none() {
let mut fill_sky = chunk.sections.iter()
.skip(i)
.all(|v| v.is_none());
fill_sky &= (mask & !((1 << i) | ((1 << i) - 1))) == 0;
if !fill_sky || mask & (1 << i) != 0 {
chunk.sections[i] = Some(Section::new(i as u8, fill_sky));
}
}
if mask & (1 << i) == 0 {
continue;
}
let section = chunk.sections[i as usize].as_mut().unwrap();
section.dirty = true;
data.read_exact(&mut block_types[i])?;
}
// Block metadata array - half byte per block
let mut block_meta: [nibble::Array; 16] = [
// TODO: cleanup this initialization
nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16),
nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16),
nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16),
nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16),
];
for i in 0 .. 16 {
if mask & (1 << i) == 0 {
continue;
}
data.read_exact(&mut block_meta[i].data)?;
}
// Block light array - half byte per block
for i in 0 .. 16 {
if mask & (1 << i) == 0 {
continue;
}
let section = chunk.sections[i as usize].as_mut().unwrap();
data.read_exact(&mut section.block_light.data)?;
}
// Sky light array - half byte per block - only if 'skylight' is true
if skylight {
for i in 0 .. 16 {
if mask & (1 << i) == 0 {
continue;
}
let section = chunk.sections[i as usize].as_mut().unwrap();
data.read_exact(&mut section.sky_light.data)?;
}
}
// Add array - half byte per block - uses secondary bitmask
let mut block_add: [nibble::Array; 16] = [
// TODO: cleanup this initialization
nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16),
nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16),
nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16),
nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16),
];
for i in 0 .. 16 {
if mask_add & (1 << i) == 0 {
continue;
}
data.read_exact(&mut block_add[i].data)?;
}
// Now that we have the block types, metadata, and add, combine to initialize the blocks
for i in 0 .. 16 {
if mask & (1 << i) == 0 {
continue;
}
let section = chunk.sections[i as usize].as_mut().unwrap();
for bi in 0 .. 4096 {
let id = ((block_add[i].get(bi) as u16) << 12) | ((block_types[i][bi] as u16) << 4) | (block_meta[i].get(bi) as u16);
section.blocks.set(bi, block::Block::by_vanilla_id(id as usize));
// Spawn block entities
let b = section.blocks.get(bi);
if block_entity::BlockEntityType::get_block_entity(b).is_some() {
let pos = Position::new(
(bi & 0xF) as i32,
(bi >> 8) as i32,
((bi >> 4) & 0xF) as i32
) + (chunk.position.0 << 4, (i << 4) as i32, chunk.position.1 << 4);
if chunk.block_entities.contains_key(&pos) {
self.block_entity_actions.push_back(BlockEntityAction::Remove(pos))
}
self.block_entity_actions.push_back(BlockEntityAction::Create(pos))
}
}
}
if new {
data.read_exact(&mut chunk.biomes)?;
}
chunk.calculate_heightmap();
}
self.dirty_chunks_by_bitmask(x, z, mask);
Ok(())
}
@ -733,21 +907,7 @@ impl World {
chunk.calculate_heightmap();
}
for i in 0 .. 16 {
if mask & (1 << i) == 0 {
continue;
}
for pos in [
(-1, 0, 0), (1, 0, 0),
(0, -1, 0), (0, 1, 0),
(0, 0, -1), (0, 0, 1)].into_iter() {
self.flag_section_dirty(x + pos.0, i as i32 + pos.1, z + pos.2);
}
self.update_range(
(x<<4) - 1, (i<<4) - 1, (z<<4) - 1,
(x<<4) + 17, (i<<4) + 17, (z<<4) + 17
);
}
self.dirty_chunks_by_bitmask(x, z, mask);
Ok(())
}