diff --git a/azalea-protocol/packet-macros/src/lib.rs b/azalea-protocol/packet-macros/src/lib.rs index 45df7e81..089d1594 100755 --- a/azalea-protocol/packet-macros/src/lib.rs +++ b/azalea-protocol/packet-macros/src/lib.rs @@ -6,6 +6,118 @@ use syn::{ parse_macro_input, DeriveInput, FieldsNamed, Ident, LitInt, Token, }; +#[proc_macro_derive(McBufReadable, attributes(varint))] +pub fn derive_mcbufreadable(input: TokenStream) -> TokenStream { + let DeriveInput { ident, data, .. } = parse_macro_input!(input); + + let fields = match data { + syn::Data::Struct(syn::DataStruct { fields, .. }) => fields, + _ => panic!("#[derive(*Packet)] can only be used on structs"), + }; + let FieldsNamed { named, .. } = match fields { + syn::Fields::Named(f) => f, + _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"), + }; + + let read_fields = named + .iter() + .map(|f| { + let field_name = &f.ident; + let field_type = &f.ty; + // do a different buf.write_* for each field depending on the type + // if it's a string, use buf.write_string + match field_type { + syn::Type::Path(_) => { + if f.attrs.iter().any(|a| a.path.is_ident("varint")) { + quote! { + let #field_name = crate::mc_buf::McBufVarintReadable::varint_read_into(buf).await?; + } + } else { + quote! { + let #field_name = crate::mc_buf::McBufReadable::read_into(buf).await?; + } + } + } + _ => panic!( + "Error reading field {}: {}", + field_name.clone().unwrap(), + field_type.to_token_stream() + ), + } + }) + .collect::>(); + let read_field_names = named.iter().map(|f| &f.ident).collect::>(); + + quote! { + #[async_trait::async_trait] + + impl crate::mc_buf::McBufReadable for #ident { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + #(#read_fields)* + Ok(#ident { + #(#read_field_names: #read_field_names),* + }) + } + } + } + .into() +} + +#[proc_macro_derive(McBufWritable, attributes(varint))] +pub fn derive_mcbufwritable(input: TokenStream) -> TokenStream { + let DeriveInput { ident, data, .. } = parse_macro_input!(input); + + let fields = match data { + syn::Data::Struct(syn::DataStruct { fields, .. }) => fields, + _ => panic!("#[derive(*Packet)] can only be used on structs"), + }; + let FieldsNamed { named, .. } = match fields { + syn::Fields::Named(f) => f, + _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"), + }; + + let write_fields = named + .iter() + .map(|f| { + let field_name = &f.ident; + let field_type = &f.ty; + // do a different buf.write_* for each field depending on the type + // if it's a string, use buf.write_string + match field_type { + syn::Type::Path(_) => { + if f.attrs.iter().any(|attr| attr.path.is_ident("varint")) { + quote! { + crate::mc_buf::McBufVarintWritable::varint_write_into(&self.#field_name, buf)?; + } + } else { + quote! { + crate::mc_buf::McBufWritable::write_into(&self.#field_name, buf)?; + } + } + } + _ => panic!( + "Error writing field {}: {}", + field_name.clone().unwrap(), + field_type.to_token_stream() + ), + } + }) + .collect::>(); + + quote! { + impl crate::mc_buf::McBufWritable for #ident { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + #(#write_fields)* + Ok(()) + } + } + } + .into() +} + fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); diff --git a/azalea-protocol/src/mc_buf/write.rs b/azalea-protocol/src/mc_buf/write.rs index e8845f25..2c46157b 100755 --- a/azalea-protocol/src/mc_buf/write.rs +++ b/azalea-protocol/src/mc_buf/write.rs @@ -2,7 +2,7 @@ use super::{UnsizedByteArray, MAX_STRING_LENGTH}; use async_trait::async_trait; use azalea_chat::component::Component; use azalea_core::{ - difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation, + difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation, Slot, }; use byteorder::{BigEndian, WriteBytesExt}; use std::io::Write; @@ -337,3 +337,19 @@ impl McBufWritable for Component { todo!() } } + +// Slot +impl McBufWritable for Slot { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + match self { + Slot::Empty => buf.write_byte(0)?, + Slot::Present(i) => { + buf.write_varint(i.id)?; + buf.write_byte(i.count)?; + buf.write_nbt(&i.nbt)?; + } + } + + Ok(()) + } +} diff --git a/azalea-protocol/src/packets/game/clientbound_update_recipes_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_recipes_packet.rs index 2e8532df..d9e6b262 100644 --- a/azalea-protocol/src/packets/game/clientbound_update_recipes_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_update_recipes_packet.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use azalea_chat::component::Component; use azalea_core::{resource_location::ResourceLocation, Slot}; -use packet_macros::GamePacket; +use packet_macros::{GamePacket, McBufReadable, McBufWritable}; use tokio::io::AsyncRead; use crate::mc_buf::{McBufReadable, McBufWritable, Readable, Writable}; @@ -17,19 +17,70 @@ pub struct Recipe { pub data: RecipeData, } -#[derive(Clone, Debug)] -pub enum RecipeData { - CraftingShapeless { - /// Used to group similar recipes together in the recipe book. - /// Tag is present in recipe JSON - group: String, - // ingredients - ingredients: Vec, - result: Slot, - }, +#[derive(Clone, Debug, McBufReadable, McBufWritable)] +pub struct ShapelessRecipe { + /// Used to group similar recipes together in the recipe book. + /// Tag is present in recipe JSON + group: String, + ingredients: Vec, + result: Slot, +} +#[derive(Clone, Debug, McBufReadable, McBufWritable)] +pub struct ShapedRecipe { + width: u32, + height: u32, + group: String, + ingredients: Vec, + result: Slot, +} +#[derive(Clone, Debug, McBufReadable, McBufWritable)] +pub struct CookingRecipe { + group: String, + ingredient: Ingredient, + result: Slot, + experience: f32, + #[varint] + cooking_time: u32, +} +#[derive(Clone, Debug, McBufReadable, McBufWritable)] +pub struct StoneCuttingRecipe { + group: String, + ingredient: Ingredient, + result: Slot, +} +#[derive(Clone, Debug, McBufReadable, McBufWritable)] +pub struct SmithingRecipe { + base: Ingredient, + addition: Ingredient, + result: Slot, } #[derive(Clone, Debug)] +pub enum RecipeData { + CraftingShapeless(ShapelessRecipe), + CraftingShaped(ShapedRecipe), + CraftingSpecialArmorDye, + CraftingSpecialBookCloning, + CraftingSpecialMapCloning, + CraftingSpecialMapExtending, + CraftingSpecialFireworkRocket, + CraftingSpecialFireworkStar, + CraftingSpecialFireworkStarFade, + CraftingSpecialRepairItem, + CraftingSpecialTippedArrow, + CraftingSpecialBannerDuplicate, + CraftingSpecialBannerAddPattern, + CraftingSpecialShieldDecoration, + CraftingSpecialShulkerBoxColoring, + CraftingSpecialSuspiciousStew, + Smelting(CookingRecipe), + Blasting(CookingRecipe), + Smoking(CookingRecipe), + CampfireCooking(CookingRecipe), + Stonecutting(StoneCuttingRecipe), +} + +#[derive(Clone, Debug, McBufReadable, McBufWritable)] pub struct Ingredient { pub allowed: Vec, } @@ -56,13 +107,13 @@ impl McBufReadable for Recipe { let ingredients = Vec::::read_into(buf).await?; let result = Slot::read_into(buf).await?; - RecipeData::CraftingShapeless { + RecipeData::CraftingShapeless(ShapelessRecipe { group, ingredients, result, - } + }) } else { - panic!(); + panic!("Unknown recipe type sent by server: {}", recipe_type); }; let recipe = Recipe { identifier, data }; @@ -70,21 +121,3 @@ impl McBufReadable for Recipe { Ok(recipe) } } - -impl McBufWritable for Ingredient { - fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { - todo!() - } -} -#[async_trait] -impl McBufReadable for Ingredient { - async fn read_into(buf: &mut R) -> Result - where - R: AsyncRead + std::marker::Unpin + std::marker::Send, - { - let ingredient = Ingredient { - allowed: Vec::::read_into(buf).await?, - }; - Ok(ingredient) - } -} diff --git a/bot/src/main.rs b/bot/src/main.rs index 7d129478..71e325c6 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -7,6 +7,7 @@ async fn main() { // let response = azalea_client::ping::ping_server(&address.try_into().unwrap()) // .await // .unwrap(); + // println!("{}", response.description.to_ansi(None)); let _response = azalea_client::connect::join_server(&address.try_into().unwrap()) .await