Add 18w50a (451) multiprotocol support (#79)
Adds 18w50a (451) multiprotocol support, last snapshot of 2018 Reference: https://wiki.vg/index.php?title=Pre-release_protocol&oldid=14491 * Use v18w50a module for protocol * Add blasting, smoking, and suspicious stew recipe types * Add entity tags to tags packet * Add chunk data packet variant with height map * Add update light packet * Add chunk format parsing with block_count, without skylights, conditionalize on protocol_version >= 451 * Add villager data entity metadata type parsing https://wiki.vg/Pre-release_protocol#Entity_Metadata * Add open book and entity sound effect packets
This commit is contained in:
parent
98cc88df5c
commit
9adf589436
|
@ -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];
|
||||
|
|
|
@ -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<nbt::NamedTag> =,
|
||||
field data: LenPrefixedBytes<VarInt> =,
|
||||
field block_entities: LenPrefixed<VarInt, Option<nbt::NamedTag>> =,
|
||||
}
|
||||
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<VarInt, packet::Tags> =,
|
||||
field fluid_tags: LenPrefixed<VarInt, packet::Tags> =,
|
||||
}
|
||||
packet TagsWithEntities {
|
||||
field block_tags: LenPrefixed<VarInt, packet::Tags> =,
|
||||
field item_tags: LenPrefixed<VarInt, packet::Tags> =,
|
||||
field fluid_tags: LenPrefixed<VarInt, packet::Tags> =,
|
||||
field entity_tags: LenPrefixed<VarInt, packet::Tags> =,
|
||||
}
|
||||
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<u8> =,
|
||||
}
|
||||
}
|
||||
}
|
||||
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<item::Stack>,
|
||||
experience: f32,
|
||||
cooking_time: VarInt,
|
||||
},
|
||||
Smoking {
|
||||
group: String,
|
||||
ingredient: RecipeIngredient,
|
||||
result: Option<item::Stack>,
|
||||
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)
|
||||
};
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -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<Option<crate::nbt::NamedTag>>) {
|
||||
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,
|
||||
|
|
|
@ -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<R: io::Read>(buf: &mut R) -> Result<Self, protocol::Error> {
|
||||
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<W: io::Write>(&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::*;
|
||||
|
|
|
@ -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::<byteorder::LittleEndian>()?;
|
||||
// TODO: use block_count
|
||||
}
|
||||
|
||||
let mut bit_size = data.read_u8()?;
|
||||
let mut mappings: HashMap<usize, block::Block, BuildHasherDefault<FNVHash>> = 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 {
|
||||
|
|
Loading…
Reference in New Issue