diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index b54cea6..5062d12 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -37,7 +37,7 @@ use flate2::Compression; use std::time::{Instant, Duration}; use crate::shared::Position; -pub const SUPPORTED_PROTOCOLS: [i32; 10] = [404, 340, 316, 315, 210, 109, 107, 74, 47, 5]; +pub const SUPPORTED_PROTOCOLS: [i32; 11] = [404, 451, 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]; diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index 9f5c2e1..5a0cf30 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -931,6 +931,15 @@ state_packets!( } /// ChunkData sends or updates a single chunk on the client. If New is set /// then biome data should be sent too. + packet ChunkData_HeightMap { + field chunk_x: i32 =, + field chunk_z: i32 =, + field new: bool =, + field bitmask: VarInt =, + field heightmaps: Option =, + field data: LenPrefixedBytes =, + field block_entities: LenPrefixed> =, + } packet ChunkData { field chunk_x: i32 =, field chunk_z: i32 =, @@ -1166,6 +1175,10 @@ state_packets!( field yaw: f32 =, field pitch: f32 =, } + /// Opens the book GUI. + packet OpenBook { + field hand: 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. packet SignEditorOpen { @@ -1553,6 +1566,14 @@ state_packets!( field volume: f32 =, field pitch: u8 =, } + /// Plays a sound effect from an entity. + packet EntitySoundEffect { + field sound_id: VarInt =, + field sound_category: VarInt =, + field entity_id: VarInt =, + field volume: f32 =, + field pitch: f32 =, + } /// PlayerListHeaderFooter updates the header/footer of the player list. packet PlayerListHeaderFooter { field header: format::Component =, @@ -1638,6 +1659,20 @@ state_packets!( field item_tags: LenPrefixed =, field fluid_tags: LenPrefixed =, } + packet TagsWithEntities { + field block_tags: LenPrefixed =, + field item_tags: LenPrefixed =, + field fluid_tags: LenPrefixed =, + field entity_tags: LenPrefixed =, + } + packet UpdateLight { + field chunk_x: VarInt =, + field chunk_z: VarInt =, + field sky_light_mask: VarInt =, + field block_light_mask: VarInt =, + field empty_sky_light_mask: VarInt =, + field light_arrays: Vec =, + } } } login Login { @@ -2300,6 +2335,7 @@ pub enum RecipeData { BannerAddPattern, ShieldDecoration, ShulkerBoxColoring, + SuspiciousStew, Smelting { group: String, ingredient: RecipeIngredient, @@ -2307,6 +2343,20 @@ pub enum RecipeData { experience: f32, cooking_time: VarInt, }, + Blasting { + group: String, + ingredient: RecipeIngredient, + result: Option, + experience: f32, + cooking_time: VarInt, + }, + Smoking { + group: String, + ingredient: RecipeIngredient, + result: Option, + experience: f32, + cooking_time: VarInt, + }, } impl Default for RecipeData { @@ -2362,6 +2412,7 @@ impl Serializable for Recipe { "crafting_special_banneraddpattern" => RecipeData::BannerAddPattern, "crafting_special_shielddecoration" => RecipeData::ShieldDecoration, "crafting_special_shulkerboxcoloring" => RecipeData::ShulkerBoxColoring, + "crafting_special_suspiciousstew" => RecipeData::SuspiciousStew, "smelting" => RecipeData::Smelting { group: Serializable::read_from(buf)?, ingredient: Serializable::read_from(buf)?, @@ -2369,6 +2420,20 @@ impl Serializable for Recipe { experience: Serializable::read_from(buf)?, cooking_time: Serializable::read_from(buf)?, }, + "blasting" => RecipeData::Blasting { + group: Serializable::read_from(buf)?, + ingredient: Serializable::read_from(buf)?, + result: Serializable::read_from(buf)?, + experience: Serializable::read_from(buf)?, + cooking_time: Serializable::read_from(buf)?, + }, + "smoking" => RecipeData::Smoking { + group: Serializable::read_from(buf)?, + ingredient: Serializable::read_from(buf)?, + result: Serializable::read_from(buf)?, + experience: Serializable::read_from(buf)?, + cooking_time: Serializable::read_from(buf)?, + }, _ => panic!("unrecognized recipe type: {}", ty) }; diff --git a/src/protocol/versions.rs b/src/protocol/versions.rs index 5435189..1ba9dae 100644 --- a/src/protocol/versions.rs +++ b/src/protocol/versions.rs @@ -1,5 +1,6 @@ use crate::protocol::*; +mod v18w50a; mod v1_13_2; mod v1_12_2; mod v1_11_2; @@ -14,6 +15,10 @@ pub fn translate_internal_packet_id_for_version(version: i32, state: State, dir: match version { // https://wiki.vg/Protocol_History // https://wiki.vg/Protocol_version_numbers#Versions_after_the_Netty_rewrite + + // 18w50a + 451 => v18w50a::translate_internal_packet_id(state, dir, id, to_internal), + // 1.13.2 404 => v1_13_2::translate_internal_packet_id(state, dir, id, to_internal), diff --git a/src/protocol/versions/v18w50a.rs b/src/protocol/versions/v18w50a.rs new file mode 100644 index 0000000..836353e --- /dev/null +++ b/src/protocol/versions/v18w50a.rs @@ -0,0 +1,172 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => ChatMessage + 0x03 => ClientStatus + 0x04 => ClientSettings + 0x05 => TabComplete + 0x06 => ConfirmTransactionServerbound + 0x07 => EnchantItem + 0x08 => ClickWindow + 0x09 => CloseWindow + 0x0a => PluginMessageServerbound + 0x0b => EditBook + 0x0c => QueryEntityNBT + 0x0d => UseEntity + 0x0e => KeepAliveServerbound_i64 + 0x0f => Player + 0x10 => PlayerPosition + 0x11 => PlayerPositionLook + 0x12 => PlayerLook + 0x13 => VehicleMove + 0x14 => SteerBoat + 0x15 => PickItem + 0x16 => CraftRecipeRequest + 0x17 => ClientAbilities + 0x18 => PlayerDigging + 0x19 => PlayerAction + 0x1a => SteerVehicle + 0x1b => CraftingBookData + 0x1c => NameItem + 0x1d => ResourcePackStatus + 0x1e => AdvancementTab + 0x1f => SelectTrade + 0x20 => SetBeaconEffect + 0x21 => HeldItemChange + 0x22 => UpdateCommandBlock + 0x23 => UpdateCommandBlockMinecart + 0x24 => CreativeInventoryAction + 0x25 => UpdateStructureBlock + 0x26 => SetSign + 0x27 => ArmSwing + 0x28 => SpectateTeleport + 0x29 => PlayerBlockPlacement_f32 + 0x2a => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob + 0x04 => SpawnPainting + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => ServerMessage + 0x0f => MultiBlockChange_VarInt + 0x10 => TabCompleteReply + 0x11 => DeclareCommands + 0x12 => ConfirmTransaction + 0x13 => WindowClose + 0x14 => WindowOpen + 0x15 => WindowItems + 0x16 => WindowProperty + 0x17 => WindowSetSlot + 0x18 => SetCooldown + 0x19 => PluginMessageClientbound + 0x1a => NamedSoundEffect + 0x1b => Disconnect + 0x1c => EntityAction + 0x1d => NBTQueryResponse + 0x1e => Explosion + 0x1f => ChunkUnload + 0x20 => ChangeGameState + 0x21 => KeepAliveClientbound_i64 + 0x22 => ChunkData_HeightMap + 0x23 => Effect + 0x24 => Particle + 0x25 => JoinGame_i32 + 0x26 => Maps + 0x27 => Entity + 0x28 => EntityMove_i16 + 0x29 => EntityLookAndMove_i16 + 0x2a => EntityLook_VarInt + 0x2b => VehicleTeleport + 0x2c => OpenBook + 0x2d => SignEditorOpen + 0x2e => CraftRecipeResponse + 0x2f => PlayerAbilities + 0x30 => CombatEvent + 0x31 => PlayerInfo + 0x32 => FacePlayer + 0x33 => TeleportPlayer_WithConfirm + 0x34 => EntityUsedBed + 0x35 => UnlockRecipes_WithSmelting + 0x36 => EntityDestroy + 0x37 => EntityRemoveEffect + 0x38 => ResourcePackSend + 0x39 => Respawn + 0x3a => EntityHeadLook + 0x3b => SelectAdvancementTab + 0x3c => WorldBorder + 0x3d => Camera + 0x3e => SetCurrentHotbarSlot + 0x3f => ScoreboardDisplay + 0x40 => EntityMetadata + 0x41 => EntityAttach + 0x42 => EntityVelocity + 0x43 => EntityEquipment + 0x44 => SetExperience + 0x45 => UpdateHealth + 0x46 => ScoreboardObjective + 0x47 => SetPassengers + 0x48 => Teams + 0x49 => UpdateScore + 0x4a => SpawnPosition + 0x4b => TimeUpdate + 0x4d => StopSound + 0x4e => SoundEffect + 0x4f => EntitySoundEffect + 0x50 => PlayerListHeaderFooter + 0x51 => CollectItem + 0x52 => EntityTeleport_f64 + 0x53 => Advancements + 0x54 => EntityProperties + 0x55 => EntityEffect + 0x56 => DeclareRecipes + 0x57 => TagsWithEntities + 0x58 => UpdateLight + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/src/server/mod.rs b/src/server/mod.rs index 1a7cb20..a176322 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -393,6 +393,7 @@ impl Server { KeepAliveClientbound_VarInt => on_keep_alive_varint, KeepAliveClientbound_i32 => on_keep_alive_i32, ChunkData => on_chunk_data, + ChunkData_HeightMap => on_chunk_data_heightmap, ChunkData_NoEntities => on_chunk_data_no_entities, ChunkData_NoEntities_u16 => on_chunk_data_no_entities_u16, ChunkData_17 => on_chunk_data_17, @@ -1087,15 +1088,8 @@ impl Server { } } - fn on_chunk_data(&mut self, chunk_data: packet::play::clientbound::ChunkData) { - self.world.load_chunk19( - chunk_data.chunk_x, - chunk_data.chunk_z, - chunk_data.new, - chunk_data.bitmask.0 as u16, - chunk_data.data.data - ).unwrap(); - for optional_block_entity in chunk_data.block_entities.data { + fn load_block_entities(&mut self, block_entities: Vec>) { + for optional_block_entity in block_entities { if let Some(block_entity) = optional_block_entity { let x = block_entity.1.get("x").unwrap().as_int().unwrap(); let y = block_entity.1.get("y").unwrap().as_int().unwrap(); @@ -1117,6 +1111,28 @@ impl Server { } } + fn on_chunk_data(&mut self, chunk_data: packet::play::clientbound::ChunkData) { + self.world.load_chunk19( + chunk_data.chunk_x, + chunk_data.chunk_z, + chunk_data.new, + chunk_data.bitmask.0 as u16, + chunk_data.data.data + ).unwrap(); + self.load_block_entities(chunk_data.block_entities.data); + } + + fn on_chunk_data_heightmap(&mut self, chunk_data: packet::play::clientbound::ChunkData_HeightMap) { + self.world.load_chunk19( + chunk_data.chunk_x, + chunk_data.chunk_z, + chunk_data.new, + chunk_data.bitmask.0 as u16, + chunk_data.data.data + ).unwrap(); + self.load_block_entities(chunk_data.block_entities.data); + } + fn on_chunk_data_no_entities(&mut self, chunk_data: packet::play::clientbound::ChunkData_NoEntities) { self.world.load_chunk19( chunk_data.chunk_x, diff --git a/src/types/metadata.rs b/src/types/metadata.rs index a5e8a3e..cb390ff 100644 --- a/src/types/metadata.rs +++ b/src/types/metadata.rs @@ -321,6 +321,7 @@ impl Metadata { } } 15 => panic!("TODO: particle"), + 16 => m.put_raw(index, VillagerData::read_from(buf)?), _ => return Err(protocol::Error::Err("unknown metadata type".to_owned())), } } @@ -400,6 +401,10 @@ impl Metadata { u8::write_to(&15, buf)?; val.write_to(buf)?; } + Value::Villager(ref val) => { + u8::write_to(&16, buf)?; + val.write_to(buf)?; + } _ => panic!("unexpected metadata"), } } @@ -472,6 +477,7 @@ pub enum Value { Block(u16), // TODO: Proper type NBTTag(nbt::NamedTag), Particle(ParticleData), + Villager(VillagerData), } #[derive(Debug)] @@ -613,6 +619,27 @@ impl Serializable for ParticleData { } } +#[derive(Debug)] +pub struct VillagerData { + villager_type: protocol::VarInt, + profession: protocol::VarInt, + level: protocol::VarInt, +} + +impl Serializable for VillagerData { + fn read_from(buf: &mut R) -> Result { + let villager_type = protocol::VarInt::read_from(buf)?; + let profession = protocol::VarInt::read_from(buf)?; + let level = protocol::VarInt::read_from(buf)?; + Ok(VillagerData { villager_type, profession, level }) + } + + fn write_to(&self, _buf: &mut W) -> Result<(), protocol::Error> { + unimplemented!() + } +} + + pub trait MetaValue { fn unwrap(_: &Value) -> &Self; fn wrap(self) -> Value; @@ -823,6 +850,18 @@ impl MetaValue for nbt::NamedTag { } } +impl MetaValue for VillagerData { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Villager(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Villager(self) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/world/mod.rs b/src/world/mod.rs index 9900846..87f4c28 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -868,6 +868,11 @@ impl World { } let section = chunk.sections[i as usize].as_mut().unwrap(); section.dirty = true; + + if self.protocol_version >= 451 { + let _block_count = data.read_u16::()?; + // TODO: use block_count + } let mut bit_size = data.read_u8()?; let mut mappings: HashMap> = HashMap::with_hasher(BuildHasherDefault::default()); @@ -903,8 +908,12 @@ impl World { } } - data.read_exact(&mut section.block_light.data)?; - data.read_exact(&mut section.sky_light.data)?; + if self.protocol_version >= 451 { + // Skylight in update skylight packet for 1.14+ + } else { + data.read_exact(&mut section.block_light.data)?; + data.read_exact(&mut section.sky_light.data)?; + } } if new {