diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index c247dba..3fcc23d 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; 7] = [340, 316, 315, 210, 109, 107, 74]; +pub const SUPPORTED_PROTOCOLS: [i32; 8] = [340, 316, 315, 210, 109, 107, 74, 47]; /// Helper macro for defining packets diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index aea455f..bf87440 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -92,6 +92,13 @@ state_packets!( field displayed_skin_parts: u8 =, field main_hand: VarInt =, } + packet ClientSettings_u8_Handsfree { + field locale: String =, + field view_distance: u8 =, + field chat_mode: u8 =, + field chat_colors: bool =, + field displayed_skin_parts: u8 =, + } /// ConfirmTransactionServerbound is a reply to ConfirmTransaction. packet ConfirmTransactionServerbound { field id: u8 =, @@ -141,6 +148,13 @@ state_packets!( field target_z: f32 = when(|p: &UseEntity| p.ty.0 == 2), field hand: VarInt = when(|p: &UseEntity| p.ty.0 == 0 || p.ty.0 == 2), } + packet UseEntity_Handsfree { + field target_id: VarInt =, + field ty: VarInt =, + field target_x: f32 = when(|p: &UseEntity_Handsfree| p.ty.0 == 2), + field target_y: f32 = when(|p: &UseEntity_Handsfree| p.ty.0 == 2), + field target_z: f32 = when(|p: &UseEntity_Handsfree| 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. @@ -273,6 +287,9 @@ state_packets!( packet ArmSwing { field hand: VarInt =, } + packet ArmSwing_Handsfree { + field empty: () =, + } /// SpectateTeleport is sent by clients in spectator mode to teleport to a player. packet SpectateTeleport { field target: UUID =, @@ -294,6 +311,15 @@ state_packets!( field cursor_y: u8 =, field cursor_z: u8 =, } + packet PlayerBlockPlacement_u8_Item { + field location: Position =, + field face: u8 =, + field hand: Option =, + 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 { field hand: VarInt =, @@ -330,6 +356,19 @@ state_packets!( field velocity_y: i16 =, field velocity_z: i16 =, } + packet SpawnObject_i32_NoUUID { + field entity_id: VarInt =, + field ty: u8 =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field pitch: i8 =, + field yaw: i8 =, + field data: i32 =, + field velocity_x: i16 = when(|p: &SpawnObject_i32_NoUUID| p.data != 0), + field velocity_y: i16 = when(|p: &SpawnObject_i32_NoUUID| p.data != 0), + field velocity_z: i16 = when(|p: &SpawnObject_i32_NoUUID| p.data != 0), + } /// 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. @@ -378,7 +417,7 @@ state_packets!( field velocity_x: i16 =, field velocity_y: i16 =, field velocity_z: i16 =, - field metadata: types::Metadata =, + field metadata: types::Metadata19 =, } packet SpawnMob_u8 { field entity_id: VarInt =, @@ -393,7 +432,7 @@ state_packets!( field velocity_x: i16 =, field velocity_y: i16 =, field velocity_z: i16 =, - field metadata: types::Metadata =, + field metadata: types::Metadata19 =, } packet SpawnMob_u8_i32 { field entity_id: VarInt =, @@ -408,7 +447,21 @@ state_packets!( field velocity_x: i16 =, field velocity_y: i16 =, field velocity_z: i16 =, - field metadata: types::Metadata =, + field metadata: types::Metadata19 =, + } + packet SpawnMob_u8_i32_NoUUID_18 { + field entity_id: VarInt =, + field ty: u8 =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field yaw: i8 =, + field pitch: i8 =, + field head_pitch: i8 =, + field velocity_x: i16 =, + field velocity_y: i16 =, + field velocity_z: i16 =, + field metadata: types::Metadata18 =, } /// 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. @@ -436,7 +489,7 @@ state_packets!( field z: f64 =, field yaw: i8 =, field pitch: i8 =, - field metadata: types::Metadata =, + field metadata: types::Metadata19 =, } packet SpawnPlayer_i32 { field entity_id: VarInt =, @@ -446,7 +499,18 @@ state_packets!( field z: i32 =, field yaw: i8 =, field pitch: i8 =, - field metadata: types::Metadata =, + field metadata: types::Metadata19 =, + } + packet SpawnPlayer_i32_HeldItem_18 { + field entity_id: VarInt =, + field uuid: UUID =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field yaw: i8 =, + field pitch: i8 =, + field current_item: u16 =, + field metadata: types::Metadata18 =, } /// Animation is sent by the server to play an animation on a specific entity. packet Animation { @@ -664,6 +728,18 @@ state_packets!( field bitmask: VarInt =, field data: LenPrefixedBytes =, } + packet ChunkData_NoEntities_u16 { + field chunk_x: i32 =, + field chunk_z: i32 =, + field new: bool =, + field bitmask: u16 =, + field data: LenPrefixedBytes =, + } + packet ChunkDataBulk { + field skylight: bool =, + field chunk_meta: LenPrefixed =, + field chunk_data: Vec =, + } /// 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. @@ -737,6 +813,16 @@ state_packets!( field z: Option = when(|p: &Maps| p.columns > 0), field data: Option> = when(|p: &Maps| p.columns > 0), } + packet Maps_NoTracking { + field item_damage: VarInt =, + field scale: i8 =, + field icons: LenPrefixed =, + field columns: u8 =, + field rows: Option = when(|p: &Maps_NoTracking| p.columns > 0), + field x: Option = when(|p: &Maps_NoTracking| p.columns > 0), + field z: Option = when(|p: &Maps_NoTracking| p.columns > 0), + field data: Option> = when(|p: &Maps_NoTracking| p.columns > 0), + } /// EntityMove moves the entity with the id by the offsets provided. packet EntityMove_i16 { field entity_id: VarInt =, @@ -782,6 +868,11 @@ state_packets!( packet Entity { field entity_id: VarInt =, } + /// EntityUpdateNBT updates the entity named binary tag. + packet EntityUpdateNBT { + field entity_id: VarInt =, + field nbt: Option =, + } /// Teleports the player's vehicle packet VehicleTeleport { field x: f64 =, @@ -881,6 +972,10 @@ state_packets!( field entity_id: VarInt =, field head_yaw: i8 =, } + packet EntityStatus { + field entity_id: i32 =, + field entity_status: i8 =, + } /// SelectAdvancementTab indicates the client should switch the advancement tab. packet SelectAdvancementTab { field has_id: bool =, @@ -915,7 +1010,11 @@ state_packets!( /// EntityMetadata updates the metadata for an entity. packet EntityMetadata { field entity_id: VarInt =, - field metadata: types::Metadata =, + field metadata: types::Metadata19 =, + } + packet EntityMetadata_18 { + field entity_id: VarInt =, + field metadata: types::Metadata18 =, } /// EntityAttach attaches to entities together, either by mounting or leashing. /// -1 can be used at the EntityID to deattach. @@ -944,6 +1043,11 @@ state_packets!( field slot: VarInt =, field item: Option =, } + packet EntityEquipment_u16 { + field entity_id: VarInt =, + field slot: u16 =, + field item: Option =, + } /// SetExperience updates the experience bar on the client. packet SetExperience { field experience_bar: f32 =, @@ -1266,6 +1370,29 @@ impl Serializable for BlockChangeRecord { } } +#[derive(Debug, Default)] +pub struct ChunkMeta { + pub x: i32, + pub z: i32, + pub bitmask: u16, +} + +impl Serializable for ChunkMeta { + fn read_from(buf: &mut R) -> Result { + Ok(ChunkMeta { + x: Serializable::read_from(buf)?, + z: Serializable::read_from(buf)?, + bitmask: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.x.write_to(buf)?; + self.z.write_to(buf)?; + self.bitmask.write_to(buf) + } +} + #[derive(Debug, Default)] pub struct ExplosionRecord { pub x: i8, diff --git a/src/protocol/versions.rs b/src/protocol/versions.rs index 259c5af..3e71b28 100644 --- a/src/protocol/versions.rs +++ b/src/protocol/versions.rs @@ -6,6 +6,7 @@ mod v1_10_2; mod v1_9_2; mod v1_9; mod v15w39c; +mod v1_8_9; pub fn translate_internal_packet_id_for_version(version: i32, state: State, dir: Direction, id: i32, to_internal: bool) -> i32 { match version { @@ -32,6 +33,9 @@ pub fn translate_internal_packet_id_for_version(version: i32, state: State, dir: // 15w39a/b/c 74 => v15w39c::translate_internal_packet_id(state, dir, id, to_internal), + // 1.8.9 - 1.8 + 47 => v1_8_9::translate_internal_packet_id(state, dir, id, to_internal), + _ => panic!("unsupported protocol version"), } } diff --git a/src/protocol/versions/v1_8_9.rs b/src/protocol/versions/v1_8_9.rs new file mode 100644 index 0000000..72d55ff --- /dev/null +++ b/src/protocol/versions/v1_8_9.rs @@ -0,0 +1,139 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => KeepAliveServerbound_VarInt + 0x01 => ChatMessage + 0x02 => UseEntity_Handsfree + 0x03 => Player + 0x04 => PlayerPosition + 0x05 => PlayerLook + 0x06 => PlayerPositionLook + 0x07 => PlayerDigging_u8 + 0x08 => PlayerBlockPlacement_u8_Item + 0x09 => HeldItemChange + 0x0a => ArmSwing_Handsfree + 0x0b => PlayerAction + 0x0c => SteerVehicle + 0x0d => CloseWindow + 0x0e => ClickWindow_u8 + 0x0f => ConfirmTransactionServerbound + 0x10 => CreativeInventoryAction + 0x11 => EnchantItem + 0x12 => SetSign + 0x13 => ClientAbilities + 0x14 => TabComplete_NoAssume + 0x15 => ClientSettings_u8_Handsfree + 0x16 => ClientStatus + 0x17 => PluginMessageServerbound + 0x18 => SpectateTeleport + 0x19 => ResourcePackStatus + } + clientbound Clientbound { + 0x00 => KeepAliveClientbound_VarInt + 0x01 => JoinGame_i8 + 0x02 => ServerMessage + 0x03 => TimeUpdate + 0x04 => EntityEquipment_u16 + 0x05 => SpawnPosition + 0x06 => UpdateHealth + 0x07 => Respawn + 0x08 => TeleportPlayer_NoConfirm + 0x09 => SetCurrentHotbarSlot + 0x0a => EntityUsedBed + 0x0b => Animation + 0x0c => SpawnPlayer_i32_HeldItem_18 + 0x0d => CollectItem_nocount + 0x0e => SpawnObject_i32_NoUUID + 0x0f => SpawnMob_u8_i32_NoUUID_18 + 0x10 => SpawnPainting_NoUUID + 0x11 => SpawnExperienceOrb_i32 + 0x12 => EntityVelocity + 0x13 => EntityDestroy + 0x14 => Entity + 0x15 => EntityMove_i8 + 0x16 => EntityLook + 0x17 => EntityLookAndMove_i8 + 0x18 => EntityTeleport_i32 + 0x19 => EntityHeadLook + 0x1a => EntityStatus + 0x1b => EntityAttach_leashed + 0x1c => EntityMetadata_18 + 0x1d => EntityEffect + 0x1e => EntityRemoveEffect + 0x1f => SetExperience + 0x20 => EntityProperties + 0x21 => ChunkData_NoEntities_u16 + 0x22 => MultiBlockChange + 0x23 => BlockChange + 0x24 => BlockAction + 0x25 => BlockBreakAnimation + 0x26 => ChunkDataBulk + 0x27 => Explosion + 0x28 => Effect + 0x29 => NamedSoundEffect_u8_NoCategory + 0x2a => Particle + 0x2b => ChangeGameState + 0x2c => SpawnGlobalEntity_i32 + 0x2d => WindowOpen + 0x2e => WindowClose + 0x2f => WindowSetSlot + 0x30 => WindowItems + 0x31 => WindowProperty + 0x32 => ConfirmTransaction + 0x33 => UpdateSign + 0x34 => Maps_NoTracking + 0x35 => UpdateBlockEntity + 0x36 => SignEditorOpen + 0x37 => Statistics + 0x38 => PlayerInfo + 0x39 => PlayerAbilities + 0x3a => TabCompleteReply + 0x3b => ScoreboardObjective + 0x3c => UpdateScore + 0x3d => ScoreboardDisplay + 0x3e => Teams + 0x3f => PluginMessageClientbound + 0x40 => Disconnect + 0x41 => ServerDifficulty + 0x42 => CombatEvent + 0x43 => Camera + 0x44 => WorldBorder + 0x45 => Title_notext_component + 0x46 => SetCompression + 0x47 => PlayerListHeaderFooter + 0x48 => ResourcePackSend + 0x49 => EntityUpdateNBT + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + } + } + 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 87e695c..f6d4f28 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -382,6 +382,8 @@ impl Server { KeepAliveClientbound_VarInt => on_keep_alive_varint, ChunkData => on_chunk_data, ChunkData_NoEntities => on_chunk_data_no_entities, + ChunkData_NoEntities_u16 => on_chunk_data_no_entities_u16, + ChunkDataBulk => on_chunk_data_bulk, ChunkUnload => on_chunk_unload, BlockChange => on_block_change, MultiBlockChange => on_multi_block_change, @@ -397,6 +399,7 @@ impl Server { EntityDestroy => on_entity_destroy, SpawnPlayer_f64 => on_player_spawn_f64, SpawnPlayer_i32 => on_player_spawn_i32, + SpawnPlayer_i32_HeldItem_18 => on_player_spawn_i32_helditem_18, EntityTeleport_f64 => on_entity_teleport_f64, EntityTeleport_i32 => on_entity_teleport_i32, EntityMove_i16 => on_entity_move_i16, @@ -541,7 +544,7 @@ impl Server { cursor_y: at.y as f32, cursor_z: at.z as f32, }); - } else { + } else if self.protocol_version >= 49 { self.write_packet(packet::play::serverbound::PlayerBlockPlacement_u8 { location: pos, face: protocol::VarInt(match face { @@ -558,6 +561,23 @@ 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 { + location: pos, + 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, + }); } } } @@ -732,6 +752,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_18(&mut self, spawn: packet::play::clientbound::SpawnPlayer_i32_HeldItem_18) { + 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(&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) { @@ -930,7 +954,7 @@ impl Server { } fn on_chunk_data(&mut self, chunk_data: packet::play::clientbound::ChunkData) { - self.world.load_chunk( + self.world.load_chunk19( chunk_data.chunk_x, chunk_data.chunk_z, chunk_data.new, @@ -960,7 +984,7 @@ impl Server { } fn on_chunk_data_no_entities(&mut self, chunk_data: packet::play::clientbound::ChunkData_NoEntities) { - self.world.load_chunk( + self.world.load_chunk19( chunk_data.chunk_x, chunk_data.chunk_z, chunk_data.new, @@ -969,6 +993,21 @@ impl Server { ).unwrap(); } + fn on_chunk_data_no_entities_u16(&mut self, chunk_data: packet::play::clientbound::ChunkData_NoEntities_u16) { + let chunk_meta = vec![crate::protocol::packet::ChunkMeta { + x: chunk_data.chunk_x, + z: chunk_data.chunk_z, + bitmask: chunk_data.bitmask, + }]; + let skylight = false; + self.world.load_chunks18(chunk_data.new, skylight, &chunk_meta, chunk_data.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_unload(&mut self, chunk_unload: packet::play::clientbound::ChunkUnload) { self.world.unload_chunk(chunk_unload.x, chunk_unload.z, &mut self.entities); } diff --git a/src/types/metadata.rs b/src/types/metadata.rs index 396b983..498cd01 100644 --- a/src/types/metadata.rs +++ b/src/types/metadata.rs @@ -38,32 +38,161 @@ impl MetadataKey { } } -pub struct Metadata { +pub struct Metadata18 { map: HashMap, } -impl Metadata { - pub fn new() -> Metadata { - Metadata { map: HashMap::new() } +pub struct Metadata19 { + map: HashMap, +} + +trait MetadataBase: fmt::Debug + Default { + fn map(&self) -> &HashMap; + fn map_mut(&mut self) -> &mut HashMap; + + fn get(&self, key: &MetadataKey) -> Option<&T> { + self.map().get(&key.index).map(T::unwrap) } - pub fn get(&self, key: &MetadataKey) -> Option<&T> { - self.map.get(&key.index).map(T::unwrap) - } - - pub fn put(&mut self, key: &MetadataKey, val: T) { - self.map.insert(key.index, val.wrap()); + fn put(&mut self, key: &MetadataKey, val: T) { + self.map_mut().insert(key.index, val.wrap()); } fn put_raw(&mut self, index: i32, val: T) { - self.map.insert(index, val.wrap()); + self.map_mut().insert(index, val.wrap()); + } + + fn fmt2(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Metadata[ ")?; + for (k, v) in self.map() { + write!(f, "{:?}={:?}, ", k, v)?; + } + write!(f, "]") } } -impl Serializable for Metadata { +impl MetadataBase for Metadata18 { + fn map(&self) -> &HashMap { &self.map } + fn map_mut(&mut self) -> &mut HashMap { &mut self.map } +} + +impl MetadataBase for Metadata19 { + fn map(&self) -> &HashMap { &self.map } + fn map_mut(&mut self) -> &mut HashMap { &mut self.map } +} + +impl Metadata18 { + pub fn new() -> Self { + Self { map: HashMap::new() } + } +} + +impl Metadata19 { + pub fn new() -> Self { + Self { map: HashMap::new() } + } +} + + + +impl Serializable for Metadata18 { fn read_from(buf: &mut R) -> Result { - let mut m = Metadata::new(); + let mut m = Self::new(); + loop { + let ty_index = u8::read_from(buf)? as i32; + if ty_index == 0x7f { + break; + } + let index = ty_index & 0x1f; + let ty = ty_index >> 5; + + match ty { + 0 => m.put_raw(index, i8::read_from(buf)?), + 1 => m.put_raw(index, i16::read_from(buf)?), + 2 => m.put_raw(index, i32::read_from(buf)?), + 3 => m.put_raw(index, f32::read_from(buf)?), + 4 => m.put_raw(index, String::read_from(buf)?), + 5 => m.put_raw(index, Option::::read_from(buf)?), + 6 => m.put_raw(index, + [i32::read_from(buf)?, + i32::read_from(buf)?, + i32::read_from(buf)?]), + 7 => m.put_raw(index, + [f32::read_from(buf)?, + f32::read_from(buf)?, + f32::read_from(buf)?]), + _ => return Err(protocol::Error::Err("unknown metadata type".to_owned())), + } + } + Ok(m) + } + + fn write_to(&self, buf: &mut W) -> Result<(), protocol::Error> { + for (k, v) in &self.map { + if (*k as u8) > 0x1f { + panic!("write metadata index {:x} > 0x1f", *k as u8); + } + + let ty_index: u8 = *k as u8; + const TYPE_SHIFT: usize = 5; + + match *v + { + Value::Byte(ref val) => { + u8::write_to(&(ty_index | (0 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + Value::Short(ref val) => { + u8::write_to(&(ty_index | (1 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + + Value::Int(ref val) => { + u8::write_to(&(ty_index | (2 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + Value::Float(ref val) => { + u8::write_to(&(ty_index | (3 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + Value::String(ref val) => { + u8::write_to(&(ty_index | (4 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + Value::OptionalItemStack(ref val) => { + u8::write_to(&(ty_index | (5 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + Value::Vector(ref val) => { + u8::write_to(&(ty_index | (6 << TYPE_SHIFT)), buf)?; + val[0].write_to(buf)?; + val[1].write_to(buf)?; + val[2].write_to(buf)?; + } + Value::Rotation(ref val) => { + u8::write_to(&(ty_index | (7 << TYPE_SHIFT)), buf)?; + val[0].write_to(buf)?; + val[1].write_to(buf)?; + val[2].write_to(buf)?; + } + + Value::FormatComponent(_) | Value::Bool(_) | Value::Position(_) | + Value::OptionalPosition(_) | Value::Direction(_) | Value::OptionalUUID(_) | + Value::Block(_) | Value::NBTTag(_) => { + panic!("attempted to write 1.9+ metadata to 1.8"); + } + } + } + u8::write_to(&0x7f, buf)?; + Ok(()) + } +} + +impl Serializable for Metadata19 { + + fn read_from(buf: &mut R) -> Result { + let mut m = Self::new(); loop { let index = u8::read_from(buf)? as i32; if index == 0xFF { @@ -179,6 +308,7 @@ impl Serializable for Metadata { // TODO: write NBT tags metadata //nbt::Tag(*val).write_to(buf)?; } + _ => panic!("unexpected metadata"), } } u8::write_to(&0xFF, buf)?; @@ -186,25 +316,25 @@ impl Serializable for Metadata { } } -impl fmt::Debug for Metadata { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Metadata[ ")?; - for (k, v) in &self.map { - write!(f, "{:?}={:?}, ", k, v)?; - } - write!(f, "]") +// TODO: is it possible to implement these traits on MetadataBase instead? +impl fmt::Debug for Metadata19 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt2(f) } } +impl fmt::Debug for Metadata18 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt2(f) } } + +impl Default for Metadata19 { + fn default() -> Self { + Self::new() } } - -impl Default for Metadata { - fn default() -> Metadata { - Metadata::new() +impl Default for Metadata18 { + fn default() -> Self { + Self::new() } } #[derive(Debug)] pub enum Value { Byte(i8), + Short(i16), Int(i32), Float(f32), String(String), @@ -212,6 +342,7 @@ pub enum Value { OptionalItemStack(Option), Bool(bool), Vector([f32; 3]), + Rotation([i32; 3]), Position(Position), OptionalPosition(Option), Direction(protocol::VarInt), // TODO: Proper type @@ -237,6 +368,18 @@ impl MetaValue for i8 { } } +impl MetaValue for i16 { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Short(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Short(self) + } +} + impl MetaValue for i32 { fn unwrap(value: &Value) -> &Self { match *value { @@ -309,6 +452,18 @@ impl MetaValue for bool { } } +impl MetaValue for [i32; 3] { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Rotation(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Rotation(self) + } +} + impl MetaValue for [f32; 3] { fn unwrap(value: &Value) -> &Self { match *value { @@ -406,7 +561,7 @@ mod test { #[test] fn basic() { - let mut m = Metadata::new(); + let mut m = Metadata19::new(); m.put(&TEST, "Hello world".to_owned()); diff --git a/src/world/mod.rs b/src/world/mod.rs index ea0179d..537392c 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -545,7 +545,115 @@ impl World { } } - pub fn load_chunk(&mut self, x: i32, z: i32, new: bool, mask: u16, data: Vec) -> Result<(), protocol::Error> { + pub fn load_chunks18(&mut self, new: bool, skylight: bool, chunk_metas: &[crate::protocol::packet::ChunkMeta], data: Vec) -> Result<(), protocol::Error> { + let mut data = std::io::Cursor::new(data); + + for chunk_meta in chunk_metas { + let x = chunk_meta.x; + let z = chunk_meta.z; + let mask = chunk_meta.bitmask; + + self.load_chunk18(x, z, new, skylight, mask, &mut data)?; + } + Ok(()) + } + + pub fn load_chunk18(&mut self, x: i32, z: i32, new: bool, _skylight: bool, mask: u16, data: &mut std::io::Cursor>) -> Result<(), protocol::Error> { + use std::io::Read; + use byteorder::ReadBytesExt; + + 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() + }; + + 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; + + for bi in 0 .. 4096 { + let id = data.read_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)) + } + } + } + + 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)?; + } + + 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)?; + } + + if new { + data.read_exact(&mut chunk.biomes)?; + } + + 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 + ); + } + Ok(()) + } + + pub fn load_chunk19(&mut self, x: i32, z: i32, new: bool, mask: u16, data: Vec) -> Result<(), protocol::Error> { use std::io::{Cursor, Read}; use byteorder::ReadBytesExt; use crate::protocol::{VarInt, Serializable, LenPrefixed};