diff --git a/crates/valence/build/main.rs b/crates/valence/build/main.rs index dc8efdf..263c77b 100644 --- a/crates/valence/build/main.rs +++ b/crates/valence/build/main.rs @@ -9,7 +9,7 @@ mod entity; mod entity_event; pub fn main() -> anyhow::Result<()> { - println!("cargo:rerun-if-changed=extracted/"); + println!("cargo:rerun-if-changed=../../extracted/"); let generators = [ (entity::build as fn() -> _, "entity.rs"), diff --git a/crates/valence/src/client.rs b/crates/valence/src/client.rs index de8e6ef..298b191 100644 --- a/crates/valence/src/client.rs +++ b/crates/valence/src/client.rs @@ -24,7 +24,7 @@ use valence_protocol::packets::s2c::play::{ }; use valence_protocol::particle::{Particle, ParticleS2c}; use valence_protocol::types::{ - AttributeProperty, DisplayedSkinParts, GameMode, GameStateChangeReason, SyncPlayerPosLookFlags, + AttributeProperty, DisplayedSkinParts, GameEventKind, GameMode, SyncPlayerPosLookFlags, }; use valence_protocol::{ BlockPos, EncodePacket, Ident, ItemStack, RawBytes, Text, Username, VarInt, @@ -531,7 +531,7 @@ impl Client { if !self.created_this_tick() { self.queue_packet(&GameEvent { - reason: GameStateChangeReason::ChangeGameMode, + kind: GameEventKind::ChangeGameMode, value: game_mode as i32 as f32, }); } @@ -541,10 +541,10 @@ impl Client { /// Sets whether or not the client sees rain. pub fn set_raining(&mut self, raining: bool) { self.queue_packet(&GameEvent { - reason: if raining { - GameStateChangeReason::BeginRaining + kind: if raining { + GameEventKind::BeginRaining } else { - GameStateChangeReason::EndRaining + GameEventKind::EndRaining }, value: 0.0, }); @@ -556,7 +556,7 @@ impl Client { /// The rain level is clamped between `0.0.` and `1.0`. pub fn set_rain_level(&mut self, rain_level: f32) { self.queue_packet(&GameEvent { - reason: GameStateChangeReason::RainLevelChange, + kind: GameEventKind::RainLevelChange, value: rain_level.clamp(0.0, 1.0), }); } @@ -571,7 +571,7 @@ impl Client { /// The thunder level is clamped between `0.0` and `1.0`. pub fn set_thunder_level(&mut self, thunder_level: f32) { self.queue_packet(&GameEvent { - reason: GameStateChangeReason::ThunderLevelChange, + kind: GameEventKind::ThunderLevelChange, value: thunder_level.clamp(0.0, 1.0), }); } @@ -699,7 +699,7 @@ impl Client { /// Respawns client. Optionally can roll the credits before respawning. pub fn win_game(&mut self, show_credits: bool) { self.queue_packet(&GameEvent { - reason: GameStateChangeReason::WinGame, + kind: GameEventKind::WinGame, value: if show_credits { 1.0 } else { 0.0 }, }); } @@ -715,7 +715,7 @@ impl Client { if !self.created_this_tick() { self.queue_packet(&GameEvent { - reason: GameStateChangeReason::EnableRespawnScreen, + kind: GameEventKind::EnableRespawnScreen, value: if enable { 0.0 } else { 1.0 }, }); } diff --git a/crates/valence_derive/src/decode.rs b/crates/valence_derive/src/decode.rs index 1db410b..c400f8a 100644 --- a/crates/valence_derive/src/decode.rs +++ b/crates/valence_derive/src/decode.rs @@ -10,7 +10,7 @@ use crate::{ pub fn derive_decode(item: TokenStream) -> Result { let mut input = parse2::(item)?; - let name = input.ident; + let input_name = input.ident; if input.generics.lifetimes().count() > 1 { return Err(Error::new( @@ -34,7 +34,7 @@ pub fn derive_decode(item: TokenStream) -> Result { Fields::Named(fields) => { let init = fields.named.iter().map(|f| { let name = f.ident.as_ref().unwrap(); - let ctx = format!("failed to decode field `{name}`"); + let ctx = format!("failed to decode field `{name}` in `{input_name}`"); quote! { #name: Decode::decode(_r).context(#ctx)?, } @@ -49,7 +49,7 @@ pub fn derive_decode(item: TokenStream) -> Result { Fields::Unnamed(fields) => { let init = (0..fields.unnamed.len()) .map(|i| { - let ctx = format!("failed to decode field `{i}`"); + let ctx = format!("failed to decode field `{i}` in `{input_name}`"); quote! { Decode::decode(_r).context(#ctx)?, } @@ -73,7 +73,7 @@ pub fn derive_decode(item: TokenStream) -> Result { Ok(quote! { #[allow(unused_imports)] - impl #impl_generics ::valence_protocol::__private::Decode<#lifetime> for #name #ty_generics + impl #impl_generics ::valence_protocol::__private::Decode<#lifetime> for #input_name #ty_generics #where_clause { fn decode(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result { @@ -100,7 +100,8 @@ pub fn derive_decode(item: TokenStream) -> Result { .map(|f| { let field = f.ident.as_ref().unwrap(); let ctx = format!( - "failed to decode field `{field}` in variant `{name}`", + "failed to decode field `{field}` in variant `{name}` in \ + `{input_name}`", ); quote! { #field: Decode::decode(_r).context(#ctx)?, @@ -116,7 +117,8 @@ pub fn derive_decode(item: TokenStream) -> Result { let init = (0..fields.unnamed.len()) .map(|i| { let ctx = format!( - "failed to decode field `{i}` in variant `{name}`", + "failed to decode field `{i}` in variant `{name}` in \ + `{input_name}`", ); quote! { Decode::decode(_r).context(#ctx)?, @@ -143,16 +145,17 @@ pub fn derive_decode(item: TokenStream) -> Result { Ok(quote! { #[allow(unused_imports)] - impl #impl_generics ::valence_protocol::__private::Decode<#lifetime> for #name #ty_generics + impl #impl_generics ::valence_protocol::__private::Decode<#lifetime> for #input_name #ty_generics #where_clause { fn decode(_r: &mut &#lifetime [u8]) -> ::valence_protocol::__private::Result { use ::valence_protocol::__private::{Decode, Context, VarInt, bail}; - let disc = VarInt::decode(_r).context("failed to decode enum discriminant")?.0; + let ctx = concat!("failed to decode enum discriminant in `", stringify!(#input_name), "`"); + let disc = VarInt::decode(_r).context(ctx)?.0; match disc { #decode_arms - n => bail!("unexpected enum discriminant {}", disc), + n => bail!("unexpected enum discriminant {} in `{}`", disc, stringify!(#input_name)), } } } diff --git a/crates/valence_derive/src/encode.rs b/crates/valence_derive/src/encode.rs index aac3a57..7974bb6 100644 --- a/crates/valence_derive/src/encode.rs +++ b/crates/valence_derive/src/encode.rs @@ -8,7 +8,7 @@ use crate::{add_trait_bounds, find_packet_id_attr, pair_variants_with_discrimina pub fn derive_encode(item: TokenStream) -> Result { let mut input = parse2::(item)?; - let name = input.ident; + let input_name = input.ident; add_trait_bounds( &mut input.generics, @@ -25,7 +25,7 @@ pub fn derive_encode(item: TokenStream) -> Result { .iter() .map(|f| { let name = &f.ident.as_ref().unwrap(); - let ctx = format!("failed to encode field `{name}`"); + let ctx = format!("failed to encode field `{name}` in `{input_name}`"); quote! { self.#name.encode(&mut _w).context(#ctx)?; } @@ -34,7 +34,7 @@ pub fn derive_encode(item: TokenStream) -> Result { Fields::Unnamed(fields) => (0..fields.unnamed.len()) .map(|i| { let lit = LitInt::new(&i.to_string(), Span::call_site()); - let ctx = format!("failed to encode field `{lit}`"); + let ctx = format!("failed to encode field `{lit}` in `{input_name}`"); quote! { self.#lit.encode(&mut _w).context(#ctx)?; } @@ -45,7 +45,7 @@ pub fn derive_encode(item: TokenStream) -> Result { Ok(quote! { #[allow(unused_imports)] - impl #impl_generics ::valence_protocol::__private::Encode for #name #ty_generics + impl #impl_generics ::valence_protocol::__private::Encode for #input_name #ty_generics #where_clause { fn encode(&self, mut _w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> { @@ -67,7 +67,8 @@ pub fn derive_encode(item: TokenStream) -> Result { let variant_name = &variant.ident; let disc_ctx = format!( - "failed to encode enum discriminant {disc} for variant `{variant_name}`", + "failed to encode enum discriminant {disc} for variant `{variant_name}` \ + in `{input_name}`", ); match &variant.fields { @@ -83,7 +84,7 @@ pub fn derive_encode(item: TokenStream) -> Result { .map(|name| { let ctx = format!( "failed to encode field `{name}` in variant \ - `{variant_name}`", + `{variant_name}` in `{input_name}`", ); quote! { @@ -111,7 +112,7 @@ pub fn derive_encode(item: TokenStream) -> Result { .map(|name| { let ctx = format!( "failed to encode field `{name}` in variant \ - `{variant_name}`" + `{variant_name}` in `{input_name}`" ); quote! { @@ -142,7 +143,7 @@ pub fn derive_encode(item: TokenStream) -> Result { Ok(quote! { #[allow(unused_imports, unreachable_code)] - impl #impl_generics ::valence_protocol::Encode for #name #ty_generics + impl #impl_generics ::valence_protocol::Encode for #input_name #ty_generics #where_clause { fn encode(&self, mut _w: impl ::std::io::Write) -> ::valence_protocol::__private::Result<()> { diff --git a/crates/valence_protocol/build/block.rs b/crates/valence_protocol/build/block.rs index e1815d4..211ebdd 100644 --- a/crates/valence_protocol/build/block.rs +++ b/crates/valence_protocol/build/block.rs @@ -15,7 +15,6 @@ struct TopLevel { #[derive(Deserialize, Clone, Debug)] struct Block { - #[allow(unused)] id: u16, item_id: u16, translation_key: String, @@ -362,6 +361,18 @@ pub fn build() -> anyhow::Result { }) .collect::(); + let block_kind_from_raw_arms = blocks + .iter() + .map(|block| { + let name = ident(block.name.to_pascal_case()); + let id = block.id; + + quote! { + #id => Some(BlockKind::#name), + } + }) + .collect::(); + let block_kind_count = blocks.len(); let prop_names = blocks @@ -642,6 +653,23 @@ pub fn build() -> anyhow::Result { } } + /// Constructs a block kind from a raw block kind ID. + /// + /// If the given ID is invalid, `None` is returned. + pub const fn from_raw(id: u16) -> Option { + match id { + #block_kind_from_raw_arms + _ => None, + } + } + + /// Converts this block kind to its underlying raw block state ID. + /// + /// The original block kind can be recovered with [`BlockKind::from_raw`]. + pub const fn to_raw(self) -> u16 { + self as u16 + } + /// An array of all block kinds. pub const ALL: [Self; #block_kind_count] = [#(Self::#block_kind_variants,)*]; } diff --git a/crates/valence_protocol/build/main.rs b/crates/valence_protocol/build/main.rs index 6d1c6cf..ae75fe6 100644 --- a/crates/valence_protocol/build/main.rs +++ b/crates/valence_protocol/build/main.rs @@ -11,7 +11,7 @@ mod item; mod translation_key; pub fn main() -> anyhow::Result<()> { - println!("cargo:rerun-if-changed=../extracted/"); + println!("cargo:rerun-if-changed=../../extracted/"); let generators = [ (block::build as fn() -> _, "block.rs"), diff --git a/crates/valence_protocol/src/block.rs b/crates/valence_protocol/src/block.rs index 2690c88..4d3f9f1 100644 --- a/crates/valence_protocol/src/block.rs +++ b/crates/valence_protocol/src/block.rs @@ -51,7 +51,7 @@ fn fmt_block_state(bs: BlockState, f: &mut fmt::Formatter) -> fmt::Result { impl Encode for BlockState { fn encode(&self, w: impl Write) -> Result<()> { - VarInt(self.0 as i32).encode(w) + VarInt(self.to_raw() as i32).encode(w) } } @@ -64,6 +64,21 @@ impl Decode<'_> for BlockState { } } +impl Encode for BlockKind { + fn encode(&self, w: impl Write) -> Result<()> { + VarInt(self.to_raw() as i32).encode(w) + } +} + +impl Decode<'_> for BlockKind { + fn decode(r: &mut &[u8]) -> Result { + let id = VarInt::decode(r)?.0; + let errmsg = "invalid block kind ID"; + + BlockKind::from_raw(id.try_into().context(errmsg)?).context(errmsg) + } +} + #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] pub enum BlockFace { /// -Y diff --git a/crates/valence_protocol/src/impls.rs b/crates/valence_protocol/src/impls.rs index 40ac9a2..00d2e13 100644 --- a/crates/valence_protocol/src/impls.rs +++ b/crates/valence_protocol/src/impls.rs @@ -1,4 +1,6 @@ use std::borrow::Cow; +use std::collections::{BTreeSet, HashSet}; +use std::hash::{BuildHasher, Hash}; use std::io::Write; use std::mem::MaybeUninit; use std::rc::Rc; @@ -429,6 +431,92 @@ impl<'a, T: Decode<'a>> Decode<'a> for Box<[T]> { } } +impl Encode for HashSet { + fn encode(&self, mut w: impl Write) -> Result<()> { + let len = self.len(); + + ensure!( + len <= i32::MAX as usize, + "length of hash set ({len}) exceeds i32::MAX" + ); + + VarInt(len as i32).encode(&mut w)?; + + for val in self { + val.encode(&mut w)?; + } + + Ok(()) + } +} + +impl<'a, T, S> Decode<'a> for HashSet +where + T: Eq + Hash + Decode<'a>, + S: BuildHasher + Default, +{ + fn decode(r: &mut &'a [u8]) -> Result { + let len = VarInt::decode(r)?.0; + ensure!(len >= 0, "attempt to decode hash set with negative length"); + let len = len as usize; + + // Don't allocate more memory than what would roughly fit in a single packet in + // case we get a malicious array length. + let cap = (MAX_PACKET_SIZE as usize / mem::size_of::().max(1)).min(len); + let mut set = HashSet::with_capacity_and_hasher(cap, S::default()); + + for _ in 0..len { + ensure!( + set.insert(T::decode(r)?), + "encountered duplicate item while decoding hash set" + ); + } + + Ok(set) + } +} + +impl Encode for BTreeSet { + fn encode(&self, mut w: impl Write) -> Result<()> { + let len = self.len(); + + ensure!( + len <= i32::MAX as usize, + "length of b-tree set ({len}) exceeds i32::MAX" + ); + + VarInt(len as i32).encode(&mut w)?; + + for val in self { + val.encode(&mut w)?; + } + + Ok(()) + } +} + +impl<'a, T: Ord + Decode<'a>> Decode<'a> for BTreeSet { + fn decode(r: &mut &'a [u8]) -> Result { + let len = VarInt::decode(r)?.0; + ensure!( + len >= 0, + "attempt to decode b-tree set with negative length" + ); + let len = len as usize; + + let mut set = BTreeSet::new(); + + for _ in 0..len { + ensure!( + set.insert(T::decode(r)?), + "encountered duplicate item while decoding b-tree set" + ); + } + + Ok(set) + } +} + // ==== String ==== // impl Encode for str { diff --git a/crates/valence_protocol/src/lib.rs b/crates/valence_protocol/src/lib.rs index c2b5cae..3dfd3e9 100644 --- a/crates/valence_protocol/src/lib.rs +++ b/crates/valence_protocol/src/lib.rs @@ -112,6 +112,7 @@ pub mod packets; pub mod particle; pub mod player_list; mod raw_bytes; +pub mod recipe; pub mod text; pub mod translation_key; pub mod types; diff --git a/crates/valence_protocol/src/packets/s2c.rs b/crates/valence_protocol/src/packets/s2c.rs index 2909f20..3fbb8b9 100644 --- a/crates/valence_protocol/src/packets/s2c.rs +++ b/crates/valence_protocol/src/packets/s2c.rs @@ -7,11 +7,12 @@ use crate::byte_angle::ByteAngle; use crate::ident::Ident; use crate::item::ItemStack; use crate::raw_bytes::RawBytes; +use crate::recipe::DeclaredRecipe; use crate::text::Text; use crate::types::{ - AttributeProperty, BossBarAction, ChunkDataBlockEntity, Difficulty, GameMode, - GameStateChangeReason, GlobalPos, PlayerAbilitiesFlags, SignedProperty, SoundCategory, - SyncPlayerPosLookFlags, + AttributeProperty, BossBarAction, ChunkDataBlockEntity, Difficulty, GameEventKind, GameMode, + GlobalPos, PlayerAbilitiesFlags, SignedProperty, SoundCategory, SyncPlayerPosLookFlags, + TagGroup, }; use crate::username::Username; use crate::var_int::VarInt; @@ -276,7 +277,7 @@ pub mod play { #[derive(Copy, Clone, Debug, Encode, EncodePacket, Decode, DecodePacket)] #[packet_id = 0x1c] pub struct GameEvent { - pub reason: GameStateChangeReason, + pub kind: GameEventKind, pub value: f32, } @@ -673,6 +674,16 @@ pub mod play { pub features: Vec>, } + #[derive(Clone, Debug, Encode, Decode, EncodePacket, DecodePacket)] + #[packet_id = 0x69] + pub struct DeclareRecipes<'a> { + pub recipes: Vec>, + } + + #[derive(Clone, Debug, Encode, Decode, EncodePacket, DecodePacket)] + #[packet_id = 0x6a] + pub struct UpdateTags<'a>(pub Vec>); + packet_enum! { #[derive(Clone)] S2cPlayPacket<'a> { @@ -737,6 +748,8 @@ pub mod play { TeleportEntity, UpdateAttributes<'a>, FeatureFlags<'a>, + DeclareRecipes<'a>, + UpdateTags<'a>, } } } diff --git a/crates/valence_protocol/src/recipe.rs b/crates/valence_protocol/src/recipe.rs new file mode 100644 index 0000000..383f824 --- /dev/null +++ b/crates/valence_protocol/src/recipe.rs @@ -0,0 +1,392 @@ +use std::io::Write; + +use anyhow::{bail, ensure}; + +use crate::{Decode, Encode, Ident, ItemStack, VarInt}; + +#[derive(Clone, PartialEq, Debug)] +pub enum DeclaredRecipe<'a> { + CraftingShapeless { + recipe_id: Ident<&'a str>, + group: &'a str, + category: CraftingCategory, + ingredients: Vec, + result: Option, + }, + CraftingShaped { + recipe_id: Ident<&'a str>, + width: VarInt, + height: VarInt, + group: &'a str, + category: CraftingCategory, + ingredients: Vec, + result: Option, + }, + CraftingSpecial { + kind: SpecialCraftingKind, + recipe_id: Ident<&'a str>, + category: CraftingCategory, + }, + Smelting { + recipe_id: Ident<&'a str>, + group: &'a str, + category: SmeltCategory, + ingredient: Ingredient, + result: Option, + experience: f32, + cooking_time: VarInt, + }, + Blasting { + recipe_id: Ident<&'a str>, + group: &'a str, + category: SmeltCategory, + ingredient: Ingredient, + result: Option, + experience: f32, + cooking_time: VarInt, + }, + Smoking { + recipe_id: Ident<&'a str>, + group: &'a str, + category: SmeltCategory, + ingredient: Ingredient, + result: Option, + experience: f32, + cooking_time: VarInt, + }, + CampfireCooking { + recipe_id: Ident<&'a str>, + group: &'a str, + category: SmeltCategory, + ingredient: Ingredient, + result: Option, + experience: f32, + cooking_time: VarInt, + }, + Stonecutting { + recipe_id: Ident<&'a str>, + group: &'a str, + ingredient: Ingredient, + result: Option, + }, + Smithing { + recipe_id: Ident<&'a str>, + base: Ingredient, + addition: Ingredient, + result: Option, + }, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum SpecialCraftingKind { + ArmorDye, + BookCloning, + MapCloning, + MapExtending, + FireworkRocket, + FireworkStar, + FireworkStarFade, + RepairItem, + TippedArrow, + BannerDuplicate, + BannerAddPattern, + ShieldDecoration, + ShulkerBoxColoring, + SuspiciousStew, +} + +/// Any item in the Vec may be used for the recipe. +pub type Ingredient = Vec>; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] +pub enum CraftingCategory { + Building, + Redstone, + Equipment, + Misc, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] +pub enum SmeltCategory { + Food, + Blocks, + Misc, +} + +impl<'a> Encode for DeclaredRecipe<'a> { + fn encode(&self, mut w: impl Write) -> anyhow::Result<()> { + match self { + DeclaredRecipe::CraftingShapeless { + recipe_id, + group, + category, + ingredients, + result, + } => { + "crafting_shapeless".encode(&mut w)?; + recipe_id.encode(&mut w)?; + group.encode(&mut w)?; + category.encode(&mut w)?; + ingredients.encode(&mut w)?; + result.encode(w) + } + DeclaredRecipe::CraftingShaped { + recipe_id, + width, + height, + group, + category, + ingredients, + result, + } => { + "crafting_shaped".encode(&mut w)?; + recipe_id.encode(&mut w)?; + width.encode(&mut w)?; + height.encode(&mut w)?; + group.encode(&mut w)?; + category.encode(&mut w)?; + + ensure!( + (width.0 as usize).saturating_mul(height.0 as usize) == ingredients.len(), + "width * height must be equal to the number of ingredients" + ); + + for ing in ingredients { + ing.encode(&mut w)?; + } + + result.encode(w) + } + DeclaredRecipe::CraftingSpecial { + kind, + recipe_id, + category, + } => { + match kind { + SpecialCraftingKind::ArmorDye => "crafting_special_armordye", + SpecialCraftingKind::BookCloning => "crafting_special_bookcloning", + SpecialCraftingKind::MapCloning => "crafting_special_mapcloning", + SpecialCraftingKind::MapExtending => "crafting_special_mapextending", + SpecialCraftingKind::FireworkRocket => "crafting_special_firework_rocket", + SpecialCraftingKind::FireworkStar => "crafting_special_firework_star", + SpecialCraftingKind::FireworkStarFade => "crafting_special_firework_star_fade", + SpecialCraftingKind::RepairItem => "crafting_special_repairitem", + SpecialCraftingKind::TippedArrow => "crafting_special_tippedarrow", + SpecialCraftingKind::BannerDuplicate => "crafting_special_bannerduplicate", + SpecialCraftingKind::BannerAddPattern => "crafting_special_banneraddpattern", + SpecialCraftingKind::ShieldDecoration => "crafting_special_shielddecoration", + SpecialCraftingKind::ShulkerBoxColoring => { + "crafting_special_shulkerboxcoloring" + } + SpecialCraftingKind::SuspiciousStew => "crafting_special_suspiciousstew", + } + .encode(&mut w)?; + recipe_id.encode(&mut w)?; + category.encode(w) + } + DeclaredRecipe::Smelting { + recipe_id, + group, + category, + ingredient, + result, + experience, + cooking_time, + } => { + "smelting".encode(&mut w)?; + recipe_id.encode(&mut w)?; + group.encode(&mut w)?; + category.encode(&mut w)?; + ingredient.encode(&mut w)?; + result.encode(&mut w)?; + experience.encode(&mut w)?; + cooking_time.encode(w) + } + DeclaredRecipe::Blasting { + recipe_id, + group, + category, + ingredient, + result, + experience, + cooking_time, + } => { + "blasting".encode(&mut w)?; + recipe_id.encode(&mut w)?; + group.encode(&mut w)?; + category.encode(&mut w)?; + ingredient.encode(&mut w)?; + result.encode(&mut w)?; + experience.encode(&mut w)?; + cooking_time.encode(w) + } + DeclaredRecipe::Smoking { + recipe_id, + group, + category, + ingredient, + result, + experience, + cooking_time, + } => { + "smoking".encode(&mut w)?; + recipe_id.encode(&mut w)?; + group.encode(&mut w)?; + category.encode(&mut w)?; + ingredient.encode(&mut w)?; + result.encode(&mut w)?; + experience.encode(&mut w)?; + cooking_time.encode(w) + } + DeclaredRecipe::CampfireCooking { + recipe_id, + group, + category, + ingredient, + result, + experience, + cooking_time, + } => { + "campfire_cooking".encode(&mut w)?; + recipe_id.encode(&mut w)?; + group.encode(&mut w)?; + category.encode(&mut w)?; + ingredient.encode(&mut w)?; + result.encode(&mut w)?; + experience.encode(&mut w)?; + cooking_time.encode(w) + } + DeclaredRecipe::Stonecutting { + recipe_id, + group, + ingredient, + result, + } => { + "stonecutting".encode(&mut w)?; + recipe_id.encode(&mut w)?; + group.encode(&mut w)?; + ingredient.encode(&mut w)?; + result.encode(w) + } + DeclaredRecipe::Smithing { + recipe_id, + base, + addition, + result, + } => { + "smithing".encode(&mut w)?; + recipe_id.encode(&mut w)?; + base.encode(&mut w)?; + addition.encode(&mut w)?; + result.encode(w) + } + } + } +} + +impl<'a> Decode<'a> for DeclaredRecipe<'a> { + fn decode(r: &mut &'a [u8]) -> anyhow::Result { + Ok(match Ident::<&str>::decode(r)?.path() { + "crafting_shapeless" => Self::CraftingShapeless { + recipe_id: Decode::decode(r)?, + group: Decode::decode(r)?, + category: Decode::decode(r)?, + ingredients: Decode::decode(r)?, + result: Decode::decode(r)?, + }, + "crafting_shaped" => { + let recipe_id = Ident::<&str>::decode(r)?; + let width = VarInt::decode(r)?.0; + let height = VarInt::decode(r)?.0; + let group = <&str>::decode(r)?; + let category = CraftingCategory::decode(r)?; + + let mut ingredients = Vec::new(); + for _ in 0..width.saturating_mul(height) { + ingredients.push(Ingredient::decode(r)?); + } + + Self::CraftingShaped { + recipe_id, + width: VarInt(width), + height: VarInt(height), + group, + category, + ingredients, + result: Decode::decode(r)?, + } + } + "smelting" => Self::Smelting { + recipe_id: Decode::decode(r)?, + group: Decode::decode(r)?, + category: Decode::decode(r)?, + ingredient: Decode::decode(r)?, + result: Decode::decode(r)?, + experience: Decode::decode(r)?, + cooking_time: Decode::decode(r)?, + }, + "blasting" => Self::Blasting { + recipe_id: Decode::decode(r)?, + group: Decode::decode(r)?, + category: Decode::decode(r)?, + ingredient: Decode::decode(r)?, + result: Decode::decode(r)?, + experience: Decode::decode(r)?, + cooking_time: Decode::decode(r)?, + }, + "smoking" => Self::Smoking { + recipe_id: Decode::decode(r)?, + group: Decode::decode(r)?, + category: Decode::decode(r)?, + ingredient: Decode::decode(r)?, + result: Decode::decode(r)?, + experience: Decode::decode(r)?, + cooking_time: Decode::decode(r)?, + }, + "campfire_cooking" => Self::CampfireCooking { + recipe_id: Decode::decode(r)?, + group: Decode::decode(r)?, + category: Decode::decode(r)?, + ingredient: Decode::decode(r)?, + result: Decode::decode(r)?, + experience: Decode::decode(r)?, + cooking_time: Decode::decode(r)?, + }, + "stonecutting" => Self::Stonecutting { + recipe_id: Decode::decode(r)?, + group: Decode::decode(r)?, + ingredient: Decode::decode(r)?, + result: Decode::decode(r)?, + }, + "smithing" => Self::Smithing { + recipe_id: Decode::decode(r)?, + base: Decode::decode(r)?, + addition: Decode::decode(r)?, + result: Decode::decode(r)?, + }, + other => Self::CraftingSpecial { + kind: match other { + "crafting_special_armordye" => SpecialCraftingKind::ArmorDye, + "crafting_special_bookcloning" => SpecialCraftingKind::BookCloning, + "crafting_special_mapcloning" => SpecialCraftingKind::MapCloning, + "crafting_special_mapextending" => SpecialCraftingKind::MapExtending, + "crafting_special_firework_rocket" => SpecialCraftingKind::FireworkRocket, + "crafting_special_firework_star" => SpecialCraftingKind::FireworkStar, + "crafting_special_firework_star_fade" => SpecialCraftingKind::FireworkStarFade, + "crafting_special_repairitem" => SpecialCraftingKind::RepairItem, + "crafting_special_tippedarrow" => SpecialCraftingKind::TippedArrow, + "crafting_special_bannerduplicate" => SpecialCraftingKind::BannerDuplicate, + "crafting_special_banneraddpattern" => SpecialCraftingKind::BannerAddPattern, + "crafting_special_shielddecoration" => SpecialCraftingKind::ShieldDecoration, + "crafting_special_shulkerboxcoloring" => { + SpecialCraftingKind::ShulkerBoxColoring + } + "crafting_special_suspiciousstew" => SpecialCraftingKind::SuspiciousStew, + _ => bail!("unknown recipe type \"{other}\""), + }, + recipe_id: Decode::decode(r)?, + category: CraftingCategory::decode(r)?, + }, + }) + } +} diff --git a/crates/valence_protocol/src/types.rs b/crates/valence_protocol/src/types.rs index d1e55d4..23667ad 100644 --- a/crates/valence_protocol/src/types.rs +++ b/crates/valence_protocol/src/types.rs @@ -241,7 +241,7 @@ pub enum SoundCategory { } #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] -pub enum GameStateChangeReason { +pub enum GameEventKind { NoRespawnBlockAvailable, EndRaining, BeginRaining, @@ -358,3 +358,15 @@ pub struct PlayerAbilitiesFlags { #[bits(4)] _pad: u8, } + +#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] +pub struct TagGroup<'a> { + pub kind: Ident<&'a str>, + pub tags: Vec>, +} + +#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] +pub struct Tag<'a> { + pub name: Ident<&'a str>, + pub entries: Vec, +}