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:
parent
c1692e950a
commit
eca9b8ae00
|
@ -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 | ✓ |
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::protocol::*;
|
||||
|
||||
mod v1_14;
|
||||
mod v19w02a;
|
||||
mod v18w50a;
|
||||
mod v1_13_2;
|
||||
|
@ -17,6 +18,8 @@ pub fn translate_internal_packet_id_for_version(version: i32, state: State, dir:
|
|||
// 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),
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
|
Loading…
Reference in New Issue