1.12.2 protocol support (340) (#40)

* Add new 1.12.2 packets and shift IDs

CraftRecipeResponse
AdvancementTab
SelectAdvancementTab
Advancements
CraftingRecipeRequest
UnlockRecipes
CraftingBookData

* Fix unlock recipes packet, add length-prefixed arrays

https://wiki.vg/index.php?title=Protocol&oldid=14204#Unlock_Recipes

* Update resources to 1.12.2

* Handle NBTTag metadata (value 13), parsed as nbt::NamedTag

https://wiki.vg/index.php?title=Entity_metadata&oldid=14048#Entity_Metadata_Format
https://github.com/iceiix/steven/pull/40#issuecomment-443454757

* Fix entity packet IDs, 0x25 now is Entity https://wiki.vg/index.php?title=Protocol&oldid=14204#Entity

* Add NBT long array (type 12) support

https://wiki.vg/NBT#Specification

* Entity metadata type is now a VarInt, not u8: https://wiki.vg/index.php?title=Entity_metadata&oldid=14048#Entity_Metadata_Format

* Keep alives changed to longs, no longer VarInts

https://wiki.vg/index.php?title=Protocol&oldid=14204#Keep_Alive_.28serverbound.29

* Parse CraftRecipeResponse (0x2b)

* Add structs for advancements data

* Implement Serializable trait for Advancement and AdvancementDisplay

* Implement advancement progress parsing; advancement packet works

* Particle packet adds fallingdust (46) with length 1

https://wiki.vg/index.php?title=Protocol&oldid=14204#Particle_2
This commit is contained in:
iceiix 2018-12-02 19:32:52 -08:00 committed by ice_iix
parent 36d9c1383e
commit be6e1f79f1
5 changed files with 319 additions and 62 deletions

View File

@ -34,6 +34,7 @@ pub enum Tag {
List(Vec<Tag>),
Compound(HashMap<String, Tag>),
IntArray(Vec<i32>),
LongArray(Vec<i64>),
}
#[derive(Debug, Clone)]
@ -155,6 +156,14 @@ impl Tag {
}
}
pub fn as_long_array(&self) -> Option<&[i64]> {
match *self {
Tag::LongArray(ref val) => Some(&val[..]),
_ => None,
}
}
fn internal_id(&self) -> u8 {
match *self {
Tag::End => 0,
@ -169,6 +178,7 @@ impl Tag {
Tag::List(_) => 9,
Tag::Compound(_) => 10,
Tag::IntArray(_) => 11,
Tag::LongArray(_) => 12,
}
}
@ -217,6 +227,14 @@ impl Tag {
}
data
})),
12 => Ok(Tag::LongArray({
let len: i32 = Serializable::read_from(buf)?;
let mut data = Vec::with_capacity(len as usize);
for _ in 0..len {
data.push(buf.read_i64::<BigEndian>()?);
}
data
})),
_ => Err(protocol::Error::Err("invalid tag".to_owned())),
}
}
@ -267,6 +285,12 @@ impl Serializable for Tag {
v.write_to(buf)?;
}
}
Tag::LongArray(ref val) => {
(val.len() as i32).write_to(buf)?;
for v in val {
v.write_to(buf)?;
}
}
}
Result::Ok(())
}

View File

@ -36,7 +36,7 @@ use flate2::Compression;
use std::time::{Instant, Duration};
use crate::shared::Position;
pub const SUPPORTED_PROTOCOL: i32 = 316;
pub const SUPPORTED_PROTOCOL: i32 = 340;
/// Helper macro for defining packets
@ -96,24 +96,27 @@ pub const CloseWindow: i32 = 0x08;
pub const PluginMessageServerbound: i32 = 0x09;
pub const UseEntity: i32 = 0x0a;
pub const KeepAliveServerbound: i32 = 0x0b;
pub const PlayerPosition: i32 = 0x0c;
pub const PlayerPositionLook: i32 = 0x0d;
pub const PlayerLook: i32 = 0x0e;
pub const Player: i32 = 0x0f;
pub const Player: i32 = 0x0c;
pub const PlayerPosition: i32 = 0x0d;
pub const PlayerPositionLook: i32 = 0x0e;
pub const PlayerLook: i32 = 0x0f;
pub const VehicleMove: i32 = 0x10;
pub const SteerBoat: i32 = 0x11;
pub const ClientAbilities: i32 = 0x12;
pub const PlayerDigging: i32 = 0x13;
pub const PlayerAction: i32 = 0x14;
pub const SteerVehicle: i32 = 0x15;
pub const ResourcePackStatus: i32 = 0x16;
pub const HeldItemChange: i32 = 0x17;
pub const CreativeInventoryAction: i32 = 0x18;
pub const SetSign: i32 = 0x19;
pub const ArmSwing: i32 = 0x1a;
pub const SpectateTeleport: i32 = 0x1b;
pub const PlayerBlockPlacement: i32 = 0x1c;
pub const UseItem: i32 = 0x1d;
pub const CraftRecipeRequest: i32 = 0x12;
pub const ClientAbilities: i32 = 0x13;
pub const PlayerDigging: i32 = 0x14;
pub const PlayerAction: i32 = 0x15;
pub const SteerVehicle: i32 = 0x16;
pub const CraftingBookData: i32 = 0x17;
pub const ResourcePackStatus: i32 = 0x18;
pub const AdvancementTab: i32 = 0x19;
pub const HeldItemChange: i32 = 0x1a;
pub const CreativeInventoryAction: i32 = 0x1b;
pub const SetSign: i32 = 0x1c;
pub const ArmSwing: i32 = 0x1d;
pub const SpectateTeleport: i32 = 0x1e;
pub const PlayerBlockPlacement: i32 = 0x1f;
pub const UseItem: i32 = 0x20;
pub const SpawnObject: i32 = 0x00;
pub const SpawnExperienceOrb: i32 = 0x01;
pub const SpawnGlobalEntity: i32 = 0x02;
@ -151,45 +154,49 @@ pub const Effect: i32 = 0x21;
pub const Particle: i32 = 0x22;
pub const JoinGame: i32 = 0x23;
pub const Maps: i32 = 0x24;
pub const EntityMove: i32 = 0x25;
pub const EntityLookAndMove: i32 = 0x26;
pub const EntityLook: i32 = 0x27;
pub const Entity: i32 = 0x28;
pub const Entity: i32 = 0x25;
pub const EntityMove: i32 = 0x26;
pub const EntityLookAndMove: i32 = 0x27;
pub const EntityLook: i32 = 0x28;
pub const VehicleTeleport: i32 = 0x29;
pub const SignEditorOpen: i32 = 0x2a;
pub const PlayerAbilities: i32 = 0x2b;
pub const CombatEvent: i32 = 0x2c;
pub const PlayerInfo: i32 = 0x2d;
pub const TeleportPlayer: i32 = 0x2e;
pub const EntityUsedBed: i32 = 0x2f;
pub const EntityDestroy: i32 = 0x30;
pub const EntityRemoveEffect: i32 = 0x31;
pub const ResourcePackSend: i32 = 0x32;
pub const Respawn: i32 = 0x33;
pub const EntityHeadLook: i32 = 0x34;
pub const WorldBorder: i32 = 0x35;
pub const Camera: i32 = 0x36;
pub const SetCurrentHotbarSlot: i32 = 0x37;
pub const ScoreboardDisplay: i32 = 0x38;
pub const EntityMetadata: i32 = 0x39;
pub const EntityAttach: i32 = 0x3a;
pub const EntityVelocity: i32 = 0x3b;
pub const EntityEquipment: i32 = 0x3c;
pub const SetExperience: i32 = 0x3d;
pub const UpdateHealth: i32 = 0x3e;
pub const ScoreboardObjective: i32 = 0x3f;
pub const SetPassengers: i32 = 0x40;
pub const Teams: i32 = 0x41;
pub const UpdateScore: i32 = 0x42;
pub const SpawnPosition: i32 = 0x43;
pub const TimeUpdate: i32 = 0x44;
pub const Title: i32 = 0x45;
pub const SoundEffect: i32 = 0x46;
pub const PlayerListHeaderFooter: i32 = 0x47;
pub const CollectItem: i32 = 0x48;
pub const EntityTeleport: i32 = 0x49;
pub const EntityProperties: i32 = 0x4a;
pub const EntityEffect: i32 = 0x4b;
pub const CraftRecipeResponse: i32 = 0x2b;
pub const PlayerAbilities: i32 = 0x2c;
pub const CombatEvent: i32 = 0x2d;
pub const PlayerInfo: i32 = 0x2e;
pub const TeleportPlayer: i32 = 0x2f;
pub const EntityUsedBed: i32 = 0x30;
pub const UnlockRecipes: i32 = 0x31;
pub const EntityDestroy: i32 = 0x32;
pub const EntityRemoveEffect: i32 = 0x33;
pub const ResourcePackSend: i32 = 0x34;
pub const Respawn: i32 = 0x35;
pub const EntityHeadLook: i32 = 0x36;
pub const SelectAdvancementTab: i32 = 0x37;
pub const WorldBorder: i32 = 0x38;
pub const Camera: i32 = 0x39;
pub const SetCurrentHotbarSlot: i32 = 0x3a;
pub const ScoreboardDisplay: i32 = 0x3b;
pub const EntityMetadata: i32 = 0x3c;
pub const EntityAttach: i32 = 0x3d;
pub const EntityVelocity: i32 = 0x3e;
pub const EntityEquipment: i32 = 0x3f;
pub const SetExperience: i32 = 0x40;
pub const UpdateHealth: i32 = 0x41;
pub const ScoreboardObjective: i32 = 0x42;
pub const SetPassengers: i32 = 0x43;
pub const Teams: i32 = 0x44;
pub const UpdateScore: i32 = 0x45;
pub const SpawnPosition: i32 = 0x46;
pub const TimeUpdate: i32 = 0x47;
pub const Title: i32 = 0x48;
pub const SoundEffect: i32 = 0x49;
pub const PlayerListHeaderFooter: i32 = 0x4a;
pub const CollectItem: i32 = 0x4b;
pub const EntityTeleport: i32 = 0x4c;
pub const Advancements: i32 = 0x4d;
pub const EntityProperties: i32 = 0x4e;
pub const EntityEffect: i32 = 0x4f;
pub const LoginStart: i32 = 0x00;
pub const EncryptionResponse: i32 = 0x01;
pub const LoginDisconnect: i32 = 0x00;

View File

@ -124,7 +124,7 @@ state_packets!(
/// KeepAliveClientbound. If the client doesn't reply the server
/// may disconnect the client.
packet KeepAliveServerbound {
field id: VarInt =,
field id: i64 =,
}
/// PlayerPosition is used to update the player's position.
packet PlayerPosition {
@ -166,6 +166,12 @@ state_packets!(
field unknown: bool =,
field unknown2: bool =,
}
/// CraftRecipeRequest is sent when player clicks a recipe in the crafting book.
packet CraftRecipeRequest {
field window_id: u8 =,
field recipe: VarInt =,
field make_all: bool =,
}
/// ClientAbilities is used to modify the players current abilities.
/// Currently flying is the only one
packet ClientAbilities {
@ -193,11 +199,23 @@ state_packets!(
field forward: f32 =,
field flags: u8 =,
}
/// CraftingBookData is sent when the player interacts with the crafting book.
packet CraftingBookData {
field action: VarInt =,
field recipe_id: i32 = when(|p: &CraftingBookData| p.action.0 == 0),
field crafting_book_open: bool = when(|p: &CraftingBookData| p.action.0 == 1),
field crafting_filter: bool = when(|p: &CraftingBookData| p.action.0 == 1),
}
/// ResourcePackStatus informs the server of the client's current progress
/// in activating the requested resource pack
packet ResourcePackStatus {
field result: VarInt =,
}
// TODO: Document
packet AdvancementTab {
field action: VarInt =,
field tab_id: String = when(|p: &AdvancementTab| p.action.0 == 0),
}
/// HeldItemChange is sent when the player changes the currently active
/// hotbar slot.
packet HeldItemChange {
@ -488,7 +506,7 @@ state_packets!(
/// The client should reply with the KeepAliveServerbound
/// packet setting ID to the same as this one.
packet KeepAliveClientbound {
field id: VarInt =,
field id: i64 =,
}
/// ChunkData sends or updates a single chunk on the client. If New is set
/// then biome data should be sent too.
@ -522,7 +540,7 @@ state_packets!(
field offset_z: f32 =,
field speed: f32 =,
field count: i32 =,
field data1: VarInt = when(|p: &Particle| p.particle_id == 36 || p.particle_id == 37 || p.particle_id == 38),
field data1: VarInt = when(|p: &Particle| p.particle_id == 36 || p.particle_id == 37 || p.particle_id == 38 || p.particle_id == 46),
field data2: VarInt = when(|p: &Particle| p.particle_id == 36),
}
/// JoinGame is sent after completing the login process. This
@ -598,6 +616,11 @@ state_packets!(
packet SignEditorOpen {
field location: Position =,
}
/// CraftRecipeResponse is a response to CraftRecipeRequest, notifies the UI.
packet CraftRecipeResponse {
field window_id: u8 =,
field recipe: VarInt =,
}
/// PlayerAbilities is used to modify the players current abilities. Flying,
/// creative, god mode etc.
packet PlayerAbilities {
@ -636,6 +659,13 @@ state_packets!(
field entity_id: VarInt =,
field location: Position =,
}
packet UnlockRecipes {
field action: VarInt =,
field crafting_book_open: bool =,
field filtering_craftable: bool =,
field recipe_ids: LenPrefixed<VarInt, VarInt> =,
field recipe_ids2: LenPrefixed<VarInt, VarInt> = when(|p: &UnlockRecipes| p.action.0 == 0),
}
/// EntityDestroy destroys the entities with the ids in the provided slice.
packet EntityDestroy {
field entity_ids: LenPrefixed<VarInt, VarInt> =,
@ -664,6 +694,11 @@ state_packets!(
field entity_id: VarInt =,
field head_yaw: i8 =,
}
/// SelectAdvancementTab indicates the client should switch the advancement tab.
packet SelectAdvancementTab {
field has_id: bool =,
field tab_id: String = when(|p: &SelectAdvancementTab| p.has_id),
}
/// WorldBorder configures the world's border.
packet WorldBorder {
field action: VarInt =,
@ -818,6 +853,12 @@ state_packets!(
field pitch: i8 =,
field on_ground: bool =,
}
packet Advancements {
field reset_clear: bool =,
field mapping: LenPrefixed<VarInt, packet::Advancement> =,
field identifiers: LenPrefixed<VarInt, String> =,
field progress: LenPrefixed<VarInt, packet::AdvancementProgress> =,
}
/// EntityProperties updates the properties for an entity.
packet EntityProperties {
field entity_id: VarInt =,
@ -1043,6 +1084,163 @@ impl Default for MapIcon {
}
}
#[derive(Debug, Default)]
pub struct Advancement {
pub id: String,
pub parent_id: Option<String>,
pub display_data: Option<AdvancementDisplay>,
pub criteria: LenPrefixed<VarInt, String>,
pub requirements: LenPrefixed<VarInt, LenPrefixed<VarInt, String>>,
}
impl Serializable for Advancement {
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
let id: String = Serializable::read_from(buf)?;
let parent_id = {
let has_parent: u8 = Serializable::read_from(buf)?;
if has_parent != 0 {
let parent_id: String = Serializable::read_from(buf)?;
Some(parent_id)
} else {
None
}
};
let has_display: u8 = Serializable::read_from(buf)?;
let display_data = {
if has_display != 0 {
let display_data: AdvancementDisplay = Serializable::read_from(buf)?;
Some(display_data)
} else {
None
}
};
let criteria: LenPrefixed<VarInt, String> = Serializable::read_from(buf)?;
let requirements: LenPrefixed<VarInt, LenPrefixed<VarInt, String>> = Serializable::read_from(buf)?;
Ok(Advancement {
id,
parent_id,
display_data,
criteria,
requirements,
})
}
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
self.id.write_to(buf)?;
self.parent_id.write_to(buf)?;
self.display_data.write_to(buf)?;
self.criteria.write_to(buf)?;
self.requirements.write_to(buf)
}
}
#[derive(Debug, Default)]
pub struct AdvancementDisplay {
pub title: String,
pub description: String,
pub icon: Option<crate::item::Stack>,
pub frame_type: VarInt,
pub flags: i32,
pub background_texture: Option<String>,
pub x_coord: f32,
pub y_coord: f32,
}
impl Serializable for AdvancementDisplay {
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
let title: String = Serializable::read_from(buf)?;
let description: String = Serializable::read_from(buf)?;
let icon: Option<crate::item::Stack> = Serializable::read_from(buf)?;
let frame_type: VarInt = Serializable::read_from(buf)?;
let flags: i32 = Serializable::read_from(buf)?;
let background_texture: Option<String> = if flags & 1 != 0 {
Serializable::read_from(buf)?
} else {
None
};
let x_coord: f32 = Serializable::read_from(buf)?;
let y_coord: f32 = Serializable::read_from(buf)?;
Ok(AdvancementDisplay {
title,
description,
icon,
frame_type,
flags,
background_texture,
x_coord,
y_coord,
})
}
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
self.title.write_to(buf)?;
self.description.write_to(buf)?;
self.icon.write_to(buf)?;
self.frame_type.write_to(buf)?;
self.flags.write_to(buf)?;
if self.flags & 1 != 0 {
self.background_texture.write_to(buf)?;
}
self.x_coord.write_to(buf)?;
self.y_coord.write_to(buf)
}
}
#[derive(Debug, Default)]
pub struct AdvancementProgress {
pub id: String,
pub criteria: LenPrefixed<VarInt, CriterionProgress>,
}
impl Serializable for AdvancementProgress {
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
Ok(AdvancementProgress {
id: Serializable::read_from(buf)?,
criteria: Serializable::read_from(buf)?,
})
}
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
self.id.write_to(buf)?;
self.criteria.write_to(buf)
}
}
#[derive(Debug, Default)]
pub struct CriterionProgress {
pub id: String,
pub date_of_achieving: Option<i64>,
}
impl Serializable for CriterionProgress {
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
let id = Serializable::read_from(buf)?;
let achieved: u8 = Serializable::read_from(buf)?;
let date_of_achieving: Option<i64> = if achieved != 0 {
Serializable::read_from(buf)?
} else {
None
};
Ok(CriterionProgress {
id,
date_of_achieving,
})
}
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
self.id.write_to(buf)?;
self.date_of_achieving.write_to(buf)
}
}
#[derive(Debug, Default)]
pub struct EntityProperty {
pub key: String,

View File

@ -30,10 +30,10 @@ use zip;
use crate::types::hash::FNVHash;
use crate::ui;
const RESOURCES_VERSION: &str = "1.11.2";
const VANILLA_CLIENT_URL: &str = "https://launcher.mojang.com/mc/game/1.11.2/client/db5aa600f0b0bf508aaf579509b345c4e34087be/client.jar";
const ASSET_VERSION: &str = "1.11";
const ASSET_INDEX_URL: &str = "https://launchermeta.mojang.com/mc/assets/1.11/e02b8fba4390e173057895c56ecc91e3ce3bbd40/1.11.json";
const RESOURCES_VERSION: &str = "1.12.2";
const VANILLA_CLIENT_URL: &str = "https://launcher.mojang.com/v1/objects/0f275bc1547d01fa5f56ba34bdc87d981ee12daf/client.jar";
const ASSET_VERSION: &str = "1.12";
const ASSET_INDEX_URL: &str = "https://launchermeta.mojang.com/mc/assets/1.12/67e29e024e664064c1f04c728604f83c24cbc218/1.12.json";
pub trait Pack: Sync + Send {
fn open(&self, name: &str) -> Option<Box<io::Read>>;

View File

@ -21,6 +21,7 @@ use crate::protocol::Serializable;
use crate::format;
use crate::item;
use crate::shared::Position;
use crate::nbt;
pub struct MetadataKey<T: MetaValue> {
index: i32,
@ -68,7 +69,7 @@ impl Serializable for Metadata {
if index == 0xFF {
break;
}
let ty = u8::read_from(buf)?;
let ty = protocol::VarInt::read_from(buf)?.0;
match ty {
0 => m.put_raw(index, i8::read_from(buf)?),
1 => m.put_raw(index, protocol::VarInt::read_from(buf)?.0),
@ -98,6 +99,15 @@ impl Serializable for Metadata {
}
}
12 => m.put_raw(index, protocol::VarInt::read_from(buf)?.0 as u16),
13 => {
let ty = u8::read_from(buf)?;
if ty != 0 {
let name = nbt::read_string(buf)?;
let tag = nbt::Tag::read_from(buf)?;
m.put_raw(index, nbt::NamedTag(name, tag));
}
}
_ => return Err(protocol::Error::Err("unknown metadata type".to_owned())),
}
}
@ -164,6 +174,11 @@ impl Serializable for Metadata {
u8::write_to(&11, buf)?;
protocol::VarInt(*val as i32).write_to(buf)?;
}
Value::NBTTag(ref _val) => {
u8::write_to(&13, buf)?;
// TODO: write NBT tags metadata
//nbt::Tag(*val).write_to(buf)?;
}
}
}
u8::write_to(&0xFF, buf)?;
@ -202,6 +217,7 @@ pub enum Value {
Direction(protocol::VarInt), // TODO: Proper type
OptionalUUID(Option<protocol::UUID>),
Block(u16), // TODO: Proper type
NBTTag(nbt::NamedTag),
}
pub trait MetaValue {
@ -365,6 +381,18 @@ impl MetaValue for u16 {
}
}
impl MetaValue for nbt::NamedTag {
fn unwrap(value: &Value) -> &Self {
match *value {
Value::NBTTag(ref val) => val,
_ => panic!("incorrect key"),
}
}
fn wrap(self) -> Value {
Value::NBTTag(self)
}
}
#[cfg(test)]
mod test {
use super::*;