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>), List(Vec<Tag>),
Compound(HashMap<String, Tag>), Compound(HashMap<String, Tag>),
IntArray(Vec<i32>), IntArray(Vec<i32>),
LongArray(Vec<i64>),
} }
#[derive(Debug, Clone)] #[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 { fn internal_id(&self) -> u8 {
match *self { match *self {
Tag::End => 0, Tag::End => 0,
@ -169,6 +178,7 @@ impl Tag {
Tag::List(_) => 9, Tag::List(_) => 9,
Tag::Compound(_) => 10, Tag::Compound(_) => 10,
Tag::IntArray(_) => 11, Tag::IntArray(_) => 11,
Tag::LongArray(_) => 12,
} }
} }
@ -217,6 +227,14 @@ impl Tag {
} }
data 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())), _ => Err(protocol::Error::Err("invalid tag".to_owned())),
} }
} }
@ -267,6 +285,12 @@ impl Serializable for Tag {
v.write_to(buf)?; 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(()) Result::Ok(())
} }

View File

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

View File

@ -124,7 +124,7 @@ state_packets!(
/// KeepAliveClientbound. If the client doesn't reply the server /// KeepAliveClientbound. If the client doesn't reply the server
/// may disconnect the client. /// may disconnect the client.
packet KeepAliveServerbound { packet KeepAliveServerbound {
field id: VarInt =, field id: i64 =,
} }
/// PlayerPosition is used to update the player's position. /// PlayerPosition is used to update the player's position.
packet PlayerPosition { packet PlayerPosition {
@ -166,6 +166,12 @@ state_packets!(
field unknown: bool =, field unknown: bool =,
field unknown2: 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. /// ClientAbilities is used to modify the players current abilities.
/// Currently flying is the only one /// Currently flying is the only one
packet ClientAbilities { packet ClientAbilities {
@ -193,11 +199,23 @@ state_packets!(
field forward: f32 =, field forward: f32 =,
field flags: u8 =, 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 /// ResourcePackStatus informs the server of the client's current progress
/// in activating the requested resource pack /// in activating the requested resource pack
packet ResourcePackStatus { packet ResourcePackStatus {
field result: VarInt =, 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 /// HeldItemChange is sent when the player changes the currently active
/// hotbar slot. /// hotbar slot.
packet HeldItemChange { packet HeldItemChange {
@ -488,7 +506,7 @@ state_packets!(
/// The client should reply with the KeepAliveServerbound /// The client should reply with the KeepAliveServerbound
/// packet setting ID to the same as this one. /// packet setting ID to the same as this one.
packet KeepAliveClientbound { packet KeepAliveClientbound {
field id: VarInt =, field id: i64 =,
} }
/// ChunkData sends or updates a single chunk on the client. If New is set /// ChunkData sends or updates a single chunk on the client. If New is set
/// then biome data should be sent too. /// then biome data should be sent too.
@ -522,7 +540,7 @@ state_packets!(
field offset_z: f32 =, field offset_z: f32 =,
field speed: f32 =, field speed: f32 =,
field count: i32 =, 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), field data2: VarInt = when(|p: &Particle| p.particle_id == 36),
} }
/// JoinGame is sent after completing the login process. This /// JoinGame is sent after completing the login process. This
@ -598,6 +616,11 @@ state_packets!(
packet SignEditorOpen { packet SignEditorOpen {
field location: Position =, 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, /// PlayerAbilities is used to modify the players current abilities. Flying,
/// creative, god mode etc. /// creative, god mode etc.
packet PlayerAbilities { packet PlayerAbilities {
@ -636,6 +659,13 @@ state_packets!(
field entity_id: VarInt =, field entity_id: VarInt =,
field location: Position =, 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. /// EntityDestroy destroys the entities with the ids in the provided slice.
packet EntityDestroy { packet EntityDestroy {
field entity_ids: LenPrefixed<VarInt, VarInt> =, field entity_ids: LenPrefixed<VarInt, VarInt> =,
@ -664,6 +694,11 @@ state_packets!(
field entity_id: VarInt =, field entity_id: VarInt =,
field head_yaw: i8 =, 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. /// WorldBorder configures the world's border.
packet WorldBorder { packet WorldBorder {
field action: VarInt =, field action: VarInt =,
@ -818,6 +853,12 @@ state_packets!(
field pitch: i8 =, field pitch: i8 =,
field on_ground: bool =, 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. /// EntityProperties updates the properties for an entity.
packet EntityProperties { packet EntityProperties {
field entity_id: VarInt =, 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)] #[derive(Debug, Default)]
pub struct EntityProperty { pub struct EntityProperty {
pub key: String, pub key: String,

View File

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

View File

@ -21,6 +21,7 @@ use crate::protocol::Serializable;
use crate::format; use crate::format;
use crate::item; use crate::item;
use crate::shared::Position; use crate::shared::Position;
use crate::nbt;
pub struct MetadataKey<T: MetaValue> { pub struct MetadataKey<T: MetaValue> {
index: i32, index: i32,
@ -68,7 +69,7 @@ impl Serializable for Metadata {
if index == 0xFF { if index == 0xFF {
break; break;
} }
let ty = u8::read_from(buf)?; let ty = protocol::VarInt::read_from(buf)?.0;
match ty { match ty {
0 => m.put_raw(index, i8::read_from(buf)?), 0 => m.put_raw(index, i8::read_from(buf)?),
1 => m.put_raw(index, protocol::VarInt::read_from(buf)?.0), 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), 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())), _ => return Err(protocol::Error::Err("unknown metadata type".to_owned())),
} }
} }
@ -164,6 +174,11 @@ impl Serializable for Metadata {
u8::write_to(&11, buf)?; u8::write_to(&11, buf)?;
protocol::VarInt(*val as i32).write_to(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)?; u8::write_to(&0xFF, buf)?;
@ -202,6 +217,7 @@ pub enum Value {
Direction(protocol::VarInt), // TODO: Proper type Direction(protocol::VarInt), // TODO: Proper type
OptionalUUID(Option<protocol::UUID>), OptionalUUID(Option<protocol::UUID>),
Block(u16), // TODO: Proper type Block(u16), // TODO: Proper type
NBTTag(nbt::NamedTag),
} }
pub trait MetaValue { 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)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;