1.14 protocol support (477) (#132). Closes #72

Adds 1.14 (477) protocol support, based on:

https://wiki.vg/index.php?title=Pre-release_protocol&oldid=14723

* New packets: SetDifficulty, LockDifficulty, UpdateJigsawBlock, UpdateViewPosition, UpdateViewDistance

* New metadata: Optional VarInt (17) and Pose (18)

* Add new join game variant with view distance, without difficulty

* Add new server difficulty variant, with locked boolean

* Implement recipe parsing changes, add stonecutting recipe type
This commit is contained in:
ice_iix 2019-05-04 16:01:28 -07:00
parent c1692e950a
commit eca9b8ae00
7 changed files with 347 additions and 4 deletions

View File

@ -23,6 +23,7 @@ Join with your favorite IRC client or [Matrix](https://matrix.to/#/#_espernet_#s
| Game version | Protocol version | Supported? |
| ------ | --- | --- |
| 1.14 | 477 | ✓ |
| 19w02a | 452 | ✓ |
| 18w50a | 451 | ✓ |
| 1.13.2 | 404 | ✓ |

View File

@ -38,7 +38,7 @@ use flate2::Compression;
use std::time::{Instant, Duration};
use crate::shared::Position;
pub const SUPPORTED_PROTOCOLS: [i32; 12] = [404, 451, 452, 340, 316, 315, 210, 109, 107, 74, 47, 5];
pub const SUPPORTED_PROTOCOLS: [i32; 13] = [477, 452, 451, 404, 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];

View File

@ -57,6 +57,9 @@ state_packets!(
field transaction_id: VarInt =,
field location: Position =,
}
packet SetDifficulty {
field new_difficulty: u8 =,
}
/// TabComplete is sent by the client when the client presses tab in
/// the chat box.
packet TabComplete {
@ -202,6 +205,9 @@ state_packets!(
packet KeepAliveServerbound_i32 {
field id: i32 =,
}
packet LockDifficulty {
field locked: bool =,
}
/// PlayerPosition is used to update the player's position.
packet PlayerPosition {
field x: f64 =,
@ -370,6 +376,12 @@ state_packets!(
field slot: i16 =,
field clicked_item: Option<item::Stack> =,
}
packet UpdateJigsawBlock {
field location: Position =,
field attachment_type: String =,
field target_pool: String =,
field final_state: String =,
}
packet UpdateStructureBlock {
field location: Position =,
field action: VarInt =,
@ -753,6 +765,10 @@ state_packets!(
packet ServerDifficulty {
field difficulty: u8 =,
}
packet ServerDifficulty_Locked {
field difficulty: u8 =,
field locked: bool =,
}
/// TabCompleteReply is sent as a reply to a tab completion request.
/// The matches should be possible completions for the command/chat the
/// player sent.
@ -1055,6 +1071,23 @@ state_packets!(
}
/// JoinGame is sent after completing the login process. This
/// sets the initial state for the client.
packet JoinGame_i32_ViewDistance {
/// The entity id the client will be referenced by
field entity_id: i32 =,
/// The starting gamemode of the client
field gamemode: u8 =,
/// The dimension the client is starting in
field dimension: i32 =,
/// The max number of players on the server
field max_players: u8 =,
/// The level type of the server
field level_type: String =,
/// The render distance (2-32)
field view_distance: VarInt =,
/// Whether the client should reduce the amount of debug
/// information it displays in F3 mode
field reduced_debug_info: bool =,
}
packet JoinGame_i32 {
/// The entity id the client will be referenced by
field entity_id: i32 =,
@ -1377,6 +1410,15 @@ state_packets!(
packet SetCurrentHotbarSlot {
field slot: u8 =,
}
/// UpdateViewPosition is used to determine what chunks should be remain loaded.
packet UpdateViewPosition {
field chunk_x: VarInt =,
field chunk_z: VarInt =,
}
/// UpdateViewDistance is sent by the integrated server when changing render distance.
packet UpdateViewDistance {
field view_distance: VarInt =,
}
/// ScoreboardDisplay is used to set the display position of a scoreboard.
packet ScoreboardDisplay {
field position: u8 =,
@ -2396,6 +2438,11 @@ pub enum RecipeData {
experience: f32,
cooking_time: VarInt,
},
Stonecutting {
group: String,
ingredient: RecipeIngredient,
result: Option<item::Stack>,
},
}
impl Default for RecipeData {
@ -2413,8 +2460,35 @@ pub struct Recipe {
impl Serializable for Recipe {
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
let id = String::read_from(buf)?;
let ty = String::read_from(buf)?;
let (id, ty, namespace) = {
let a = String::read_from(buf)?;
let b = String::read_from(buf)?;
let protocol_version = unsafe { crate::protocol::CURRENT_PROTOCOL_VERSION };
// 1.14+ swaps recipe identifier and type, and adds namespace to type
if protocol_version >= 477 {
let ty = a;
let id = b;
if let Some(at) = ty.find(':') {
let (namespace, ty) = ty.split_at(at + 1);
let ty: String = ty.into();
let namespace: String = namespace.into();
(id, ty, namespace)
} else {
(id, ty, "minecraft:".to_string())
}
} else {
let ty = b;
let id = a;
(id, ty, "minecraft:".to_string())
}
};
if namespace != "minecraft:" {
panic!("unrecognized recipe type namespace: {}", namespace);
}
let data =
match ty.as_ref() {
@ -2473,13 +2547,18 @@ impl Serializable for Recipe {
experience: Serializable::read_from(buf)?,
cooking_time: Serializable::read_from(buf)?,
},
"campfire" => RecipeData::Campfire {
"campfire" | "campfire_cooking" => RecipeData::Campfire {
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)?,
},
"stonecutting" => RecipeData::Stonecutting {
group: Serializable::read_from(buf)?,
ingredient: Serializable::read_from(buf)?,
result: Serializable::read_from(buf)?,
},
_ => panic!("unrecognized recipe type: {}", ty)
};

View File

@ -1,5 +1,6 @@
use crate::protocol::*;
mod v1_14;
mod v19w02a;
mod v18w50a;
mod v1_13_2;
@ -16,6 +17,8 @@ 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
477 => v1_14::translate_internal_packet_id(state, dir, id, to_internal),
// 19w02a
452 => v19w02a::translate_internal_packet_id(state, dir, id, to_internal),

View File

@ -0,0 +1,180 @@
protocol_packet_ids!(
handshake Handshaking {
serverbound Serverbound {
0x00 => Handshake
}
clientbound Clientbound {
}
}
play Play {
serverbound Serverbound {
0x00 => TeleportConfirm
0x01 => QueryBlockNBT
0x02 => SetDifficulty
0x03 => ChatMessage
0x04 => ClientStatus
0x05 => ClientSettings
0x06 => TabComplete
0x07 => ConfirmTransactionServerbound
//0x08 => EnchantItem
//0x08 => ClickWindowButton
0x09 => ClickWindow
0x0a => CloseWindow
0x0b => PluginMessageServerbound
0x0c => EditBook
0x0d => QueryEntityNBT
0x0e => UseEntity
0x0f => KeepAliveServerbound_i64
0x10 => LockDifficulty
0x11 => PlayerPosition
0x12 => PlayerPositionLook
0x13 => PlayerLook
0x14 => Player
0x15 => VehicleMove
0x16 => SteerBoat
0x17 => PickItem
0x18 => CraftRecipeRequest
0x19 => ClientAbilities
0x1a => PlayerDigging
0x1b => PlayerAction
0x1c => SteerVehicle
0x1d => CraftingBookData
0x1e => NameItem
0x1f => ResourcePackStatus
0x20 => AdvancementTab
0x21 => SelectTrade
0x22 => SetBeaconEffect
0x23 => HeldItemChange
0x24 => UpdateCommandBlock
0x25 => UpdateCommandBlockMinecart
0x26 => CreativeInventoryAction
0x27 => UpdateJigsawBlock
0x28 => UpdateStructureBlock
0x29 => SetSign
0x2a => ArmSwing
0x2b => SpectateTeleport
0x2c => PlayerBlockPlacement_f32
0x2d => 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_Locked
0x0e => ServerMessage
0x0f => MultiBlockChange_VarInt
0x10 => TabCompleteReply
0x11 => DeclareCommands
0x12 => ConfirmTransaction
0x13 => WindowClose
0x14 => WindowItems
0x15 => WindowProperty
0x16 => WindowSetSlot
0x17 => SetCooldown
0x18 => PluginMessageClientbound
0x19 => NamedSoundEffect
0x1a => Disconnect
0x1b => EntityAction
0x1c => Explosion
0x1d => ChunkUnload
0x1e => ChangeGameState
0x1f => WindowOpenHorse
0x20 => KeepAliveClientbound_i64
0x21 => ChunkData_HeightMap
0x22 => Effect
0x23 => Particle_Data
0x24 => UpdateLight
0x25 => JoinGame_i32_ViewDistance
0x26 => Maps
0x27 => TradeList
0x28 => EntityMove_i16
0x29 => EntityLookAndMove_i16
0x2a => EntityLook_VarInt
0x2b => Entity
0x2c => VehicleTeleport
0x2d => OpenBook
0x2e => WindowOpen_VarInt
0x2f => SignEditorOpen
0x30 => CraftRecipeResponse
0x31 => PlayerAbilities
0x32 => CombatEvent
0x33 => PlayerInfo
0x34 => FacePlayer
0x35 => TeleportPlayer_WithConfirm
0x36 => UnlockRecipes_WithSmelting
0x37 => EntityDestroy
0x38 => EntityRemoveEffect
0x39 => ResourcePackSend
0x3a => Respawn
0x3b => EntityHeadLook
0x3c => SelectAdvancementTab
0x3d => WorldBorder
0x3e => Camera
0x3f => SetCurrentHotbarSlot
0x40 => UpdateViewPosition
0x41 => UpdateViewDistance
0x42 => ScoreboardDisplay
0x43 => EntityMetadata
0x44 => EntityAttach
0x45 => EntityVelocity
0x46 => EntityEquipment
0x47 => SetExperience
0x48 => UpdateHealth
0x49 => ScoreboardObjective
0x4a => SetPassengers
0x4b => Teams
0x4c => UpdateScore
0x4d => SpawnPosition
0x4e => TimeUpdate
0x4f => Title
0x50 => EntitySoundEffect
0x51 => SoundEffect
0x52 => StopSound
0x53 => PlayerListHeaderFooter
0x54 => NBTQueryResponse
0x55 => CollectItem
0x56 => EntityTeleport_f64
0x57 => Advancements
0x58 => EntityProperties
0x59 => EntityEffect
0x5a => DeclareRecipes
0x5b => TagsWithEntities
}
}
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
}
}
);

View File

@ -388,6 +388,7 @@ impl Server {
match pck {
Ok(pck) => handle_packet!{
self pck {
JoinGame_i32_ViewDistance => on_game_join_i32_viewdistance,
JoinGame_i32 => on_game_join_i32,
JoinGame_i8 => on_game_join_i8,
JoinGame_i8_NoDebug => on_game_join_i8_nodebug,
@ -666,6 +667,10 @@ impl Server {
});
}
fn on_game_join_i32_viewdistance(&mut self, join: packet::play::clientbound::JoinGame_i32_ViewDistance) {
self.on_game_join(join.gamemode, join.entity_id)
}
fn on_game_join_i32(&mut self, join: packet::play::clientbound::JoinGame_i32) {
self.on_game_join(join.gamemode, join.entity_id)
}

View File

@ -322,6 +322,14 @@ impl Metadata {
}
15 => panic!("TODO: particle"),
16 => m.put_raw(index, VillagerData::read_from(buf)?),
17 => {
if bool::read_from(buf)? {
m.put_raw(index, Option::<protocol::VarInt>::read_from(buf)?);
} else {
m.put_raw::<Option<protocol::VarInt>>(index, None);
}
},
18 => m.put_raw(index, PoseData::read_from(buf)?),
_ => return Err(protocol::Error::Err("unknown metadata type".to_owned())),
}
}
@ -405,6 +413,14 @@ impl Metadata {
u8::write_to(&16, buf)?;
val.write_to(buf)?;
}
Value::OptionalVarInt(ref val) => {
u8::write_to(&17, buf)?;
val.write_to(buf)?;
}
Value::Pose(ref val) => {
u8::write_to(&18, buf)?;
val.write_to(buf)?;
}
_ => panic!("unexpected metadata"),
}
}
@ -478,6 +494,8 @@ pub enum Value {
NBTTag(nbt::NamedTag),
Particle(ParticleData),
Villager(VillagerData),
OptionalVarInt(Option<protocol::VarInt>),
Pose(PoseData),
}
#[derive(Debug)]
@ -639,6 +657,38 @@ impl Serializable for VillagerData {
}
}
#[derive(Debug)]
pub enum PoseData {
Standing,
FallFlying,
Sleeping,
Swimming,
SpinAttack,
Sneaking,
Dying,
}
impl Serializable for PoseData {
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, protocol::Error> {
let n = protocol::VarInt::read_from(buf)?;
Ok(match n.0 {
0 => PoseData::Standing,
1 => PoseData::FallFlying,
2 => PoseData::Sleeping,
3 => PoseData::Swimming,
4 => PoseData::SpinAttack,
5 => PoseData::Sneaking,
6 => PoseData::Dying,
_ => panic!("unknown pose data: {}", n.0),
})
}
fn write_to<W: io::Write>(&self, _buf: &mut W) -> Result<(), protocol::Error> {
unimplemented!()
}
}
pub trait MetaValue {
fn unwrap(_: &Value) -> &Self;
@ -862,6 +912,31 @@ impl MetaValue for VillagerData {
}
}
impl MetaValue for Option<protocol::VarInt> {
fn unwrap(value: &Value) -> &Self {
match *value {
Value::OptionalVarInt(ref val) => val,
_ => panic!("incorrect key"),
}
}
fn wrap(self) -> Value {
Value::OptionalVarInt(self)
}
}
impl MetaValue for PoseData {
fn unwrap(value: &Value) -> &Self {
match *value {
Value::Pose(ref val) => val,
_ => panic!("incorrect key"),
}
}
fn wrap(self) -> Value {
Value::Pose(self)
}
}
#[cfg(test)]
mod test {
use super::*;