diff options
40 files changed, 2349 insertions, 884 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d2194a3..0484dc69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ is breaking anyways, semantic versioning is not followed. - Movement code was updated with the changes from 1.21.5, so it no longer flags Grim. - `azalea-chat` now correctly handles arrays of integers in the `with` field. (@qwqawawow) - Inventories now use the correct max stack sizes. +- Clients now send the correct data component checksums when interacting with items. ## [0.13.0+mc1.21.5] - 2025-06-15 @@ -302,6 +302,7 @@ version = "0.13.0+mc1.21.8" dependencies = [ "azalea-buf-macros", "byteorder", + "serde", "serde_json", "simdnbt", "thiserror 2.0.12", @@ -376,12 +377,15 @@ dependencies = [ "azalea-chat", "azalea-registry", "bevy_ecs", + "crc32c", "indexmap", "nohash-hasher", "num-traits", "serde", "simdnbt", + "thiserror 2.0.12", "tracing", + "uuid", ] [[package]] @@ -434,6 +438,8 @@ dependencies = [ "azalea-inventory-macros", "azalea-registry", "indexmap", + "serde", + "serde_json", "simdnbt", "tracing", "uuid", @@ -494,7 +500,6 @@ dependencies = [ "azalea-registry", "azalea-world", "bevy_ecs", - "crc32fast", "flate2", "futures", "futures-lite", @@ -1065,6 +1070,15 @@ dependencies = [ ] [[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3066,10 +3080,11 @@ dependencies = [ [[package]] name = "simdnbt" version = "0.7.2" -source = "git+https://github.com/azalea-rs/simdnbt#1505d233fd277353f1dba9845360880b74ff3a71" +source = "git+https://github.com/azalea-rs/simdnbt#6c1100f38262abab2f080c454351c6c594921a95" dependencies = [ "byteorder", "flate2", + "serde", "simd_cesu8", "simdnbt-derive", "thiserror 2.0.12", @@ -3078,7 +3093,7 @@ dependencies = [ [[package]] name = "simdnbt-derive" version = "0.7.0" -source = "git+https://github.com/azalea-rs/simdnbt#1505d233fd277353f1dba9845360880b74ff3a71" +source = "git+https://github.com/azalea-rs/simdnbt#6c1100f38262abab2f080c454351c6c594921a95" dependencies = [ "proc-macro2", "quote", @@ -3798,7 +3813,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -80,7 +80,7 @@ num-format = "0.4.4" indexmap = "2.10.0" paste = "1.0.15" compact_str = "0.9.0" -crc32fast = "1.5.0" +crc32c = "0.6.8" async-compat = "0.2.4" azalea-block-macros = { path = "azalea-block/azalea-block-macros", version = "0.13.0" } diff --git a/azalea-buf/Cargo.toml b/azalea-buf/Cargo.toml index e5fc7f07..793332b7 100644 --- a/azalea-buf/Cargo.toml +++ b/azalea-buf/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true [dependencies] azalea-buf-macros.workspace = true byteorder.workspace = true +serde.workspace = true serde_json = { workspace = true, optional = true } simdnbt.workspace = true thiserror.workspace = true diff --git a/azalea-buf/azalea-buf-macros/src/read.rs b/azalea-buf/azalea-buf-macros/src/read.rs index 5b4518bb..3ec6133e 100644 --- a/azalea-buf/azalea-buf-macros/src/read.rs +++ b/azalea-buf/azalea-buf-macros/src/read.rs @@ -9,7 +9,7 @@ pub fn create_impl_azalearead(ident: &Ident, data: &Data) -> proc_macro2::TokenS quote! { impl azalea_buf::AzaleaRead for #ident { - fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> { + fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> std::result::Result<Self, azalea_buf::BufReadError> { #(#read_fields)* Ok(Self { #(#read_field_names: #read_field_names),* @@ -21,7 +21,7 @@ pub fn create_impl_azalearead(ident: &Ident, data: &Data) -> proc_macro2::TokenS syn::Fields::Unit => { quote! { impl azalea_buf::AzaleaRead for #ident { - fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> { + fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> std::result::Result<Self, azalea_buf::BufReadError> { Ok(Self) } } @@ -32,7 +32,7 @@ pub fn create_impl_azalearead(ident: &Ident, data: &Data) -> proc_macro2::TokenS quote! { impl azalea_buf::AzaleaRead for #ident { - fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> { + fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> std::result::Result<Self, azalea_buf::BufReadError> { Ok(Self( #(#read_fields),* )) @@ -136,14 +136,14 @@ pub fn create_impl_azalearead(ident: &Ident, data: &Data) -> proc_macro2::TokenS quote! { impl azalea_buf::AzaleaRead for #ident { - fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> Result<Self, azalea_buf::BufReadError> { + fn azalea_read(buf: &mut std::io::Cursor<&[u8]>) -> std::result::Result<Self, azalea_buf::BufReadError> { let id = azalea_buf::AzaleaReadVar::azalea_read_var(buf)?; Self::azalea_read_id(buf, id) } } impl #ident { - pub fn azalea_read_id(buf: &mut std::io::Cursor<&[u8]>, id: u32) -> Result<Self, azalea_buf::BufReadError> { + pub fn azalea_read_id(buf: &mut std::io::Cursor<&[u8]>, id: u32) -> std::result::Result<Self, azalea_buf::BufReadError> { match id { #match_contents // you'd THINK this throws an error, but mojang decided to make it default for some reason diff --git a/azalea-buf/azalea-buf-macros/src/write.rs b/azalea-buf/azalea-buf-macros/src/write.rs index d433d1d7..12739eb5 100644 --- a/azalea-buf/azalea-buf-macros/src/write.rs +++ b/azalea-buf/azalea-buf-macros/src/write.rs @@ -11,7 +11,7 @@ pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::Token quote! { impl azalea_buf::AzaleaWrite for #ident { - fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl std::io::Write) -> std::result::Result<(), std::io::Error> { #write_fields Ok(()) } @@ -21,7 +21,7 @@ pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::Token syn::Fields::Unit => { quote! { impl azalea_buf::AzaleaWrite for #ident { - fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl std::io::Write) -> std::result::Result<(), std::io::Error> { Ok(()) } } @@ -32,7 +32,7 @@ pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::Token quote! { impl azalea_buf::AzaleaWrite for #ident { - fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl std::io::Write) -> std::result::Result<(), std::io::Error> { #write_fields Ok(()) } @@ -144,7 +144,7 @@ pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::Token if is_data_enum { quote! { impl azalea_buf::AzaleaWrite for #ident { - fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl std::io::Write) -> std::result::Result<(), std::io::Error> { match self { #match_arms } @@ -152,7 +152,7 @@ pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::Token } } impl #ident { - pub fn write_without_id(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + pub fn write_without_id(&self, buf: &mut impl std::io::Write) -> std::result::Result<(), std::io::Error> { match self { #match_arms_without_id } @@ -164,7 +164,7 @@ pub fn create_impl_azaleawrite(ident: &Ident, data: &Data) -> proc_macro2::Token // optimization: if it doesn't have data we can just do `as u32` quote! { impl azalea_buf::AzaleaWrite for #ident { - fn azalea_write(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> { + fn azalea_write(&self, buf: &mut impl std::io::Write) -> std::result::Result<(), std::io::Error> { azalea_buf::AzaleaWriteVar::azalea_write_var(&(*self as u32), buf) } } diff --git a/azalea-chat/src/base_component.rs b/azalea-chat/src/base_component.rs index cbc5ae8b..366904c7 100644 --- a/azalea-chat/src/base_component.rs +++ b/azalea-chat/src/base_component.rs @@ -2,20 +2,26 @@ use serde::Serialize; use crate::{FormattedText, style::Style}; -#[derive(Clone, Debug, PartialEq, Serialize, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Serialize)] pub struct BaseComponent { // implements mutablecomponent #[serde(skip_serializing_if = "Vec::is_empty")] pub siblings: Vec<FormattedText>, #[serde(flatten)] - pub style: Style, + pub style: Box<Style>, } impl BaseComponent { pub fn new() -> Self { Self { siblings: Vec::new(), - style: Style::default(), + style: Default::default(), + } + } + pub fn with_style(self, style: Style) -> Self { + Self { + style: Box::new(style), + ..self } } } diff --git a/azalea-chat/src/click_event.rs b/azalea-chat/src/click_event.rs new file mode 100644 index 00000000..765ef3ef --- /dev/null +++ b/azalea-chat/src/click_event.rs @@ -0,0 +1,16 @@ +use serde::Serialize; +use simdnbt::owned::Nbt; + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "snake_case", tag = "action")] +pub enum ClickEvent { + OpenUrl { url: String }, + OpenFile { path: String }, + RunCommand { command: String }, + SuggestCommand { command: String }, + // TODO: this uses Dialog.CODEC + ShowDialog, + ChangePage { page: i32 }, + CopyToClipboard { value: String }, + Custom { id: String, payload: Nbt }, +} diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs index 2a7e588e..e1652cf1 100644 --- a/azalea-chat/src/component.rs +++ b/azalea-chat/src/component.rs @@ -21,7 +21,7 @@ use crate::{ }; /// A chat component, basically anything you can see in chat. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)] +#[derive(Clone, Debug, PartialEq, Serialize)] #[serde(untagged)] pub enum FormattedText { Text(TextComponent), @@ -348,7 +348,7 @@ impl<'de> Deserialize<'de> for FormattedText { } let style = Style::deserialize(&json); - component.get_base_mut().style = style; + component.get_base_mut().style = Box::new(style); return Ok(component); } @@ -539,7 +539,7 @@ impl FormattedText { let base_style = Style::from_compound(compound).ok()?; let new_style = &mut component.get_base_mut().style; - *new_style = new_style.merged_with(&base_style); + *new_style = Box::new(new_style.merged_with(&base_style)); Some(component) } diff --git a/azalea-chat/src/hover_event.rs b/azalea-chat/src/hover_event.rs new file mode 100644 index 00000000..a18a3047 --- /dev/null +++ b/azalea-chat/src/hover_event.rs @@ -0,0 +1,20 @@ +use serde::Serialize; + +use crate::FormattedText; + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "snake_case", tag = "action")] +pub enum HoverEvent { + ShowText { + value: Box<FormattedText>, + }, + // TODO + ShowItem { + // item: ItemStack, + }, + ShowEntity { + id: i32, + // uuid: Uuid, + name: Box<FormattedText>, + }, +} diff --git a/azalea-chat/src/lib.rs b/azalea-chat/src/lib.rs index f01d8835..a0361c86 100644 --- a/azalea-chat/src/lib.rs +++ b/azalea-chat/src/lib.rs @@ -1,7 +1,9 @@ #![doc = include_str!("../README.md")] pub mod base_component; +mod click_event; mod component; +pub mod hover_event; #[cfg(feature = "numbers")] pub mod numbers; pub mod style; diff --git a/azalea-chat/src/style.rs b/azalea-chat/src/style.rs index 18176a3d..ebab6874 100644 --- a/azalea-chat/src/style.rs +++ b/azalea-chat/src/style.rs @@ -7,6 +7,8 @@ use serde_json::Value; #[cfg(feature = "simdnbt")] use simdnbt::owned::{NbtCompound, NbtTag}; +use crate::{click_event::ClickEvent, hover_event::HoverEvent}; + #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct TextColor { pub value: u32, @@ -30,13 +32,16 @@ impl simdnbt::ToNbtTag for TextColor { } impl TextColor { - pub fn parse(value: String) -> Option<TextColor> { + /// Parse a text component in the same way that Minecraft does. + /// + /// This supports named colors and hex codes. + pub fn parse(value: &str) -> Option<TextColor> { if value.starts_with('#') { let n = value.chars().skip(1).collect::<String>(); let n = u32::from_str_radix(&n, 16).ok()?; return Some(TextColor::from_rgb(n)); } - let color_option = NAMED_COLORS.get(&value.to_ascii_uppercase()); + let color_option = NAMED_COLORS.get(&value.to_ascii_lowercase()); if let Some(color) = color_option { return Some(color.clone()); } @@ -146,28 +151,28 @@ impl ChatFormatting { pub fn name(&self) -> &'static str { match self { - ChatFormatting::Black => "BLACK", - ChatFormatting::DarkBlue => "DARK_BLUE", - ChatFormatting::DarkGreen => "DARK_GREEN", - ChatFormatting::DarkAqua => "DARK_AQUA", - ChatFormatting::DarkRed => "DARK_RED", - ChatFormatting::DarkPurple => "DARK_PURPLE", - ChatFormatting::Gold => "GOLD", - ChatFormatting::Gray => "GRAY", - ChatFormatting::DarkGray => "DARK_GRAY", - ChatFormatting::Blue => "BLUE", - ChatFormatting::Green => "GREEN", - ChatFormatting::Aqua => "AQUA", - ChatFormatting::Red => "RED", - ChatFormatting::LightPurple => "LIGHT_PURPLE", - ChatFormatting::Yellow => "YELLOW", - ChatFormatting::White => "WHITE", - ChatFormatting::Obfuscated => "OBFUSCATED", - ChatFormatting::Strikethrough => "STRIKETHROUGH", - ChatFormatting::Bold => "BOLD", - ChatFormatting::Underline => "UNDERLINE", - ChatFormatting::Italic => "ITALIC", - ChatFormatting::Reset => "RESET", + ChatFormatting::Black => "black", + ChatFormatting::DarkBlue => "dark_blue", + ChatFormatting::DarkGreen => "dark_green", + ChatFormatting::DarkAqua => "dark_aqua", + ChatFormatting::DarkRed => "dark_red", + ChatFormatting::DarkPurple => "dark_purple", + ChatFormatting::Gold => "gold", + ChatFormatting::Gray => "gray", + ChatFormatting::DarkGray => "dark_gray", + ChatFormatting::Blue => "blue", + ChatFormatting::Green => "green", + ChatFormatting::Aqua => "aqua", + ChatFormatting::Red => "red", + ChatFormatting::LightPurple => "light_purple", + ChatFormatting::Yellow => "yellow", + ChatFormatting::White => "white", + ChatFormatting::Obfuscated => "obfuscated", + ChatFormatting::Strikethrough => "strikethrough", + ChatFormatting::Bold => "bold", + ChatFormatting::Underline => "underline", + ChatFormatting::Italic => "italic", + ChatFormatting::Reset => "reset", } } @@ -298,18 +303,77 @@ impl TryFrom<ChatFormatting> for TextColor { } } -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Default, PartialEq)] +#[non_exhaustive] pub struct Style { - // These are options instead of just bools because None is different than false in this case pub color: Option<TextColor>, + pub shadow_color: Option<u32>, pub bold: Option<bool>, pub italic: Option<bool>, pub underlined: Option<bool>, pub strikethrough: Option<bool>, pub obfuscated: Option<bool>, + pub click_event: Option<ClickEvent>, + pub hover_event: Option<HoverEvent>, + pub insertion: Option<String>, + /// Represented as a `ResourceLocation`. + pub font: Option<String>, /// Whether formatting should be reset before applying these styles pub reset: bool, } +impl Style { + pub fn new() -> Self { + Self::default() + } + pub fn color(mut self, color: impl Into<Option<TextColor>>) -> Self { + self.color = color.into(); + self + } + pub fn shadow_color(mut self, color: impl Into<Option<u32>>) -> Self { + self.shadow_color = color.into(); + self + } + pub fn bold(mut self, bold: impl Into<Option<bool>>) -> Self { + self.bold = bold.into(); + self + } + pub fn italic(mut self, italic: impl Into<Option<bool>>) -> Self { + self.italic = italic.into(); + self + } + pub fn underlined(mut self, underlined: impl Into<Option<bool>>) -> Self { + self.underlined = underlined.into(); + self + } + pub fn strikethrough(mut self, strikethrough: impl Into<Option<bool>>) -> Self { + self.strikethrough = strikethrough.into(); + self + } + pub fn obfuscated(mut self, obfuscated: impl Into<Option<bool>>) -> Self { + self.obfuscated = obfuscated.into(); + self + } + pub fn click_event(mut self, click_event: impl Into<Option<ClickEvent>>) -> Self { + self.click_event = click_event.into(); + self + } + pub fn hover_event(mut self, hover_event: impl Into<Option<HoverEvent>>) -> Self { + self.hover_event = hover_event.into(); + self + } + pub fn insertion(mut self, insertion: impl Into<Option<String>>) -> Self { + self.insertion = insertion.into(); + self + } + pub fn font(mut self, font: impl Into<Option<String>>) -> Self { + self.font = font.into(); + self + } + pub fn reset(mut self, reset: bool) -> Self { + self.reset = reset; + self + } +} fn serde_serialize_field<S: serde::ser::SerializeStruct>( state: &mut S, @@ -443,7 +507,7 @@ impl Style { let color: Option<TextColor> = json_object .get("color") .and_then(|v| v.as_str()) - .and_then(|v| TextColor::parse(v.to_string())); + .and_then(TextColor::parse); Style { color, bold, @@ -565,11 +629,16 @@ impl Style { pub fn merged_with(&self, other: &Style) -> Style { Style { color: other.color.clone().or(self.color.clone()), + shadow_color: other.shadow_color.or(self.shadow_color), bold: other.bold.or(self.bold), italic: other.italic.or(self.italic), underlined: other.underlined.or(self.underlined), strikethrough: other.strikethrough.or(self.strikethrough), obfuscated: other.obfuscated.or(self.obfuscated), + click_event: other.click_event.clone().or(self.click_event.clone()), + hover_event: other.hover_event.clone().or(self.hover_event.clone()), + insertion: other.insertion.clone().or(self.insertion.clone()), + font: other.font.clone().or(self.font.clone()), reset: other.reset, // if reset is true in the new style, that takes precedence } } @@ -647,7 +716,7 @@ impl simdnbt::Deserialize for Style { let obfuscated = compound.byte("obfuscated").map(|v| v != 0); let color: Option<TextColor> = compound .string("color") - .and_then(|v| TextColor::parse(v.to_string())); + .and_then(|v| TextColor::parse(&v.to_str())); Ok(Style { color, bold, @@ -667,14 +736,11 @@ mod tests { #[test] fn text_color_named_colors() { - assert_eq!(TextColor::parse("red".to_string()).unwrap().value, 16733525); + assert_eq!(TextColor::parse("red").unwrap().value, 16733525); } #[test] fn text_color_hex_colors() { - assert_eq!( - TextColor::parse("#a1b2c3".to_string()).unwrap().value, - 10597059 - ); + assert_eq!(TextColor::parse("#a1b2c3").unwrap().value, 10597059); } #[test] diff --git a/azalea-chat/src/text_component.rs b/azalea-chat/src/text_component.rs index bd598e16..eab7ee79 100644 --- a/azalea-chat/src/text_component.rs +++ b/azalea-chat/src/text_component.rs @@ -5,21 +5,24 @@ use serde::{__private::ser::FlatMapSerializer, Serialize, Serializer, ser::Seria use crate::{ FormattedText, base_component::BaseComponent, - style::{ChatFormatting, TextColor}, + style::{ChatFormatting, Style, TextColor}, }; /// A component that contains text that's the same in all locales. -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct TextComponent { pub base: BaseComponent, pub text: String, } - impl Serialize for TextComponent { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { + if self.base == BaseComponent::default() { + return serializer.serialize_str(&self.text); + } + let mut state = serializer.serialize_map(None)?; state.serialize_entry("text", &self.text)?; Serialize::serialize(&self.base, FlatMapSerializer(&mut state))?; @@ -85,7 +88,7 @@ pub fn legacy_color_code_to_text_component(legacy_color_code: &str) -> TextCompo components.push(TextComponent::new("".to_string())); } let style = &mut components.last_mut().unwrap().base.style; - style.color = TextColor::parse(color); + style.color = TextColor::parse(&color); i += 6; } else if let Some(formatter) = ChatFormatting::from_code(formatting_code) { @@ -124,7 +127,8 @@ pub fn legacy_color_code_to_text_component(legacy_color_code: &str) -> TextCompo } impl TextComponent { - pub fn new(text: String) -> Self { + pub fn new(text: impl Into<String>) -> Self { + let text = text.into(); // if it contains a LEGACY_FORMATTING_CODE_SYMBOL, format it if text.contains(LEGACY_FORMATTING_CODE_SYMBOL) { legacy_color_code_to_text_component(&text) @@ -139,6 +143,10 @@ impl TextComponent { fn get(self) -> FormattedText { FormattedText::Text(self) } + pub fn with_style(mut self, style: Style) -> Self { + self.base.style = Box::new(style); + self + } } impl Display for TextComponent { diff --git a/azalea-chat/src/translatable_component.rs b/azalea-chat/src/translatable_component.rs index 80d5e8e2..10c502a8 100644 --- a/azalea-chat/src/translatable_component.rs +++ b/azalea-chat/src/translatable_component.rs @@ -4,11 +4,9 @@ use serde::{__private::ser::FlatMapSerializer, Serialize, Serializer, ser::Seria #[cfg(feature = "simdnbt")] use simdnbt::Serialize as _; -use crate::{ - FormattedText, base_component::BaseComponent, style::Style, text_component::TextComponent, -}; +use crate::{FormattedText, base_component::BaseComponent, text_component::TextComponent}; -#[derive(Clone, Debug, PartialEq, Serialize, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Serialize)] #[serde(untagged)] pub enum StringOrComponent { String(String), @@ -26,7 +24,7 @@ impl simdnbt::ToNbtTag for StringOrComponent { } /// A message whose content depends on the client's language. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq)] pub struct TranslatableComponent { pub base: BaseComponent, pub key: String, @@ -181,7 +179,7 @@ impl TranslatableComponent { Ok(TextComponent { base: BaseComponent { siblings: components.into_iter().map(FormattedText::Text).collect(), - style: Style::default(), + style: Default::default(), }, text: "".to_string(), }) diff --git a/azalea-chat/tests/integration_test.rs b/azalea-chat/tests/integration_test.rs index ec305d49..37e4620f 100644 --- a/azalea-chat/tests/integration_test.rs +++ b/azalea-chat/tests/integration_test.rs @@ -62,7 +62,7 @@ fn complex_ansi_test() { red = Ansi::rgb(ChatFormatting::Red.color().unwrap()), reset = Ansi::RESET, strikethrough = Ansi::STRIKETHROUGH, - abcdef = Ansi::rgb(TextColor::parse("#abcdef".to_string()).unwrap().value), + abcdef = Ansi::rgb(TextColor::parse("#abcdef").unwrap().value), ) ); } diff --git a/azalea-client/src/plugins/inventory.rs b/azalea-client/src/plugins/inventory.rs index 1662be9f..732e1154 100644 --- a/azalea-client/src/plugins/inventory.rs +++ b/azalea-client/src/plugins/inventory.rs @@ -18,6 +18,7 @@ use azalea_protocol::packets::game::{ s_set_carried_item::ServerboundSetCarriedItem, }; use azalea_registry::MenuKind; +use azalea_world::{InstanceContainer, InstanceName}; use bevy_app::{App, Plugin, Update}; use bevy_ecs::prelude::*; use tracing::{error, warn}; @@ -835,12 +836,19 @@ pub struct ContainerClickEvent { pub operation: ClickOperation, } pub fn handle_container_click_event( - mut query: Query<(Entity, &mut Inventory, Option<&PlayerAbilities>)>, + mut query: Query<( + Entity, + &mut Inventory, + Option<&PlayerAbilities>, + &InstanceName, + )>, mut events: EventReader<ContainerClickEvent>, mut commands: Commands, + instance_container: Res<InstanceContainer>, ) { for event in events.read() { - let (entity, mut inventory, player_abilities) = query.get_mut(event.entity).unwrap(); + let (entity, mut inventory, player_abilities, instance_name) = + query.get_mut(event.entity).unwrap(); if inventory.id != event.window_id { error!( "Tried to click container with ID {}, but the current container ID is {}. Click packet won't be sent.", @@ -849,6 +857,10 @@ pub fn handle_container_click_event( continue; } + let Some(instance) = instance_container.get(instance_name) else { + continue; + }; + let old_slots = inventory.menu().slots(); inventory.simulate_click( &event.operation, @@ -856,13 +868,18 @@ pub fn handle_container_click_event( ); let new_slots = inventory.menu().slots(); + let registry_holder = &instance.read().registries; + // see which slots changed after clicking and put them in the hashmap // the server uses this to check if we desynced let mut changed_slots: HashMap<u16, HashedStack> = HashMap::new(); for (slot_index, old_slot) in old_slots.iter().enumerate() { let new_slot = &new_slots[slot_index]; if old_slot != new_slot { - changed_slots.insert(slot_index as u16, HashedStack::from(new_slot)); + changed_slots.insert( + slot_index as u16, + HashedStack::from_item_stack(new_slot, registry_holder), + ); } } @@ -875,7 +892,7 @@ pub fn handle_container_click_event( button_num: event.operation.button_num(), click_type: event.operation.click_type(), changed_slots, - carried_item: (&inventory.carried).into(), + carried_item: HashedStack::from_item_stack(&inventory.carried, registry_holder), }, )); } diff --git a/azalea-core/Cargo.toml b/azalea-core/Cargo.toml index 706964f3..d36ac038 100644 --- a/azalea-core/Cargo.toml +++ b/azalea-core/Cargo.toml @@ -12,13 +12,15 @@ azalea-registry.workspace = true bevy_ecs = { workspace = true, optional = true } nohash-hasher.workspace = true num-traits.workspace = true -serde = { workspace = true, optional = true } +serde.workspace = true simdnbt.workspace = true tracing.workspace = true azalea-chat.workspace = true indexmap.workspace = true +crc32c.workspace = true +thiserror.workspace = true +uuid.workspace = true [features] bevy_ecs = ["dep:bevy_ecs"] -serde = ["dep:serde"] strict_registry = [] diff --git a/azalea-core/src/checksum.rs b/azalea-core/src/checksum.rs new file mode 100644 index 00000000..8265906f --- /dev/null +++ b/azalea-core/src/checksum.rs @@ -0,0 +1,823 @@ +use std::{cmp::Ordering, fmt, hash::Hasher}; + +use azalea_buf::AzBuf; +use crc32c::Crc32cHasher; +use serde::{Serialize, ser}; +use thiserror::Error; +use tracing::error; + +use crate::{registry_holder::RegistryHolder, resource_location::ResourceLocation}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, AzBuf)] +pub struct Checksum(pub u32); + +pub struct ChecksumSerializer<'a, 'r> { + hasher: &'a mut Crc32cHasher, + registries: &'r RegistryHolder, +} +impl<'a, 'r> ChecksumSerializer<'a, 'r> { + pub fn checksum(&mut self) -> Checksum { + Checksum(self.hasher.finish() as u32) + } +} + +impl<'a, 'r> ser::Serializer for ChecksumSerializer<'a, 'r> { + type Ok = (); + + // The error type when some error occurs during serialization. + type Error = ChecksumError; + + type SerializeSeq = ChecksumListSerializer<'a, 'r>; + type SerializeTuple = ChecksumListSerializer<'a, 'r>; + type SerializeTupleStruct = ChecksumListSerializer<'a, 'r>; + type SerializeTupleVariant = ChecksumMapSerializer<'a, 'r>; + type SerializeMap = ChecksumMapSerializer<'a, 'r>; + type SerializeStruct = ChecksumMapSerializer<'a, 'r>; + type SerializeStructVariant = ChecksumMapSerializer<'a, 'r>; + + fn serialize_bool(self, v: bool) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(13); + self.hasher.write(&[v as u8]); + Ok(()) + } + + fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> { + assert!(self.hasher.finish() == 0); + Ok(ChecksumMapSerializer { + hasher: self.hasher, + registries: self.registries, + entries: Vec::new(), + }) + } + fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> { + assert!(self.hasher.finish() == 0); + self.serialize_map(Some(len)) + } + + fn serialize_i8(self, v: i8) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(6); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_i16(self, v: i16) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(7); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_i32(self, v: i32) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(8); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_i64(self, v: i64) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(9); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_u8(self, v: u8) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.serialize_i8(v as i8) + } + + fn serialize_u16(self, v: u16) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.serialize_i16(v as i16) + } + + fn serialize_u32(self, v: u32) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.serialize_i32(v as i32) + } + + fn serialize_u64(self, v: u64) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.serialize_i64(v as i64) + } + + fn serialize_f32(self, v: f32) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(10); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_f64(self, v: f64) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(11); + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + + fn serialize_char(self, v: char) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.serialize_u32(v as u32) + } + + fn serialize_str(self, v: &str) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(12); + let utf16 = v.encode_utf16().collect::<Vec<_>>(); + self.hasher.write(&(utf16.len() as u32).to_le_bytes()); + for c in utf16 { + self.hasher.write(&c.to_le_bytes()); + } + Ok(()) + } + + fn serialize_bytes(self, v: &[u8]) -> Result<()> { + assert!(self.hasher.finish() == 0); + self.hasher.write_u8(14); + self.hasher.write(v); + self.hasher.write_u8(15); + Ok(()) + } + + fn serialize_none(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + println!("serialize none"); + self.hasher.write_u8(1); + Ok(()) + } + + fn serialize_some<T>(self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + // check if t + + value.serialize(self)?; + Ok(()) + } + + fn serialize_unit(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + Ok(()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { + assert!(self.hasher.finish() == 0); + update_hasher_for_map(self.hasher, &[]); + Ok(()) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result<()> { + self.serialize_str(variant) + } + + fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant<T>( + self, + name: &'static str, + variant_index: u32, + _variant: &'static str, + value: &T, + ) -> Result<()> + where + T: ?Sized + Serialize, + { + // we can't have custom handlers with serde's traits, so we use this silly hack + // to make serializing data-driven registries work + if name.starts_with("minecraft:") { + let value = self + .registries + .map + .get(&ResourceLocation::from(name)) + .and_then(|r| r.get_index(variant_index as usize)) + .map(|r| r.0.to_string()) + .unwrap_or_default(); + self.serialize_str(&value)?; + return Ok(()); + } + + value.serialize(ChecksumSerializer { + hasher: self.hasher, + registries: self.registries, + }) + } + + fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> { + assert!(self.hasher.finish() == 0); + println!("serialize seq with len: {:?}", len); + Ok(ChecksumListSerializer { + hasher: self.hasher, + registries: self.registries, + values: Vec::with_capacity(len.unwrap_or_default()), + list_kind: ListKind::Normal, + }) + } + + fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> { + assert!(self.hasher.finish() == 0); + Ok(ChecksumListSerializer { + hasher: self.hasher, + registries: self.registries, + values: Vec::with_capacity(len), + list_kind: ListKind::Normal, + }) + } + + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result<Self::SerializeTupleStruct> { + assert!(self.hasher.finish() == 0); + let list_kind = if name == "azalea:int_array" { + self.hasher.write_u8(16); + ListKind::Int + } else if name == "azalea:long_array" { + self.hasher.write_u8(18); + ListKind::Long + } else { + ListKind::Normal + }; + Ok(ChecksumListSerializer { + hasher: self.hasher, + registries: self.registries, + values: Vec::with_capacity(len), + list_kind, + }) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + len: usize, + ) -> Result<Self::SerializeTupleVariant> { + assert!(self.hasher.finish() == 0); + Ok(ChecksumMapSerializer { + hasher: self.hasher, + registries: self.registries, + entries: Vec::with_capacity(len), + }) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + len: usize, + ) -> Result<Self::SerializeStructVariant> { + Ok(ChecksumMapSerializer { + hasher: self.hasher, + registries: self.registries, + entries: Vec::with_capacity(len), + }) + } +} + +pub struct ChecksumListSerializer<'a, 'r> { + hasher: &'a mut Crc32cHasher, + registries: &'r RegistryHolder, + values: Vec<Checksum>, + /// If you set this to not be the default, you should also update the hasher + /// before creating the list serializer. + list_kind: ListKind, +} +impl<'a, 'r> ser::SerializeSeq for ChecksumListSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_element<T>(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + if self.list_kind == ListKind::Normal { + // elements are hashed individually + self.values.push(get_checksum(value, self.registries)?); + } else { + value.serialize(IntOrLongArrayChecksumSerializer { + hasher: self.hasher, + })?; + } + + Ok(()) + } + + fn end(self) -> Result<()> { + match self.list_kind { + ListKind::Normal => { + assert!(self.hasher.finish() == 0); + update_hasher_for_list(self.hasher, &self.values); + } + ListKind::Int => { + self.hasher.write_u8(17); + } + ListKind::Long => { + self.hasher.write_u8(19); + } + } + + Ok(()) + } +} +/// Minecraft sometimes serializes u8/i32/i64 lists differently, so we have to +/// keep track of that when serializing the arrays. +/// +/// Byte arrays aren't included here as they're handled with `serialize_bytes`. +#[derive(Default, PartialEq, Eq)] +enum ListKind { + #[default] + Normal, + Int, + Long, +} + +impl<'a, 'r> ser::SerializeTuple for ChecksumListSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_element<T>(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result<()> { + ser::SerializeSeq::end(self) + } +} +impl<'a, 'r> ser::SerializeTupleStruct for ChecksumListSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_field<T>(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result<()> { + ser::SerializeSeq::end(self) + } +} + +pub struct ChecksumMapSerializer<'a, 'r> { + // this is only written to at the end + hasher: &'a mut Crc32cHasher, + registries: &'r RegistryHolder, + // we have to keep track of the elements like this because they're sorted at the end + entries: Vec<(Checksum, Checksum)>, +} +impl<'a, 'r> ser::SerializeMap for ChecksumMapSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_key<T>(&mut self, key: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + // this 0 is a placeholder + self.entries + .push((get_checksum(key, self.registries)?, Checksum(0))); + Ok(()) + } + + // It doesn't make a difference whether the colon is printed at the end of + // `serialize_key` or at the beginning of `serialize_value`. In this case + // the code is a bit simpler having it here. + fn serialize_value<T>(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + // placeholder gets replaced here + self.entries + .last_mut() + .expect("entry should've already been added") + .1 = get_checksum(value, self.registries)?; + Ok(()) + } + + fn end(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + update_hasher_for_map(self.hasher, &self.entries); + Ok(()) + } +} +impl<'a, 'r> ser::SerializeTupleVariant for ChecksumMapSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_field<T>(&mut self, _value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + // TODO + error!("tuple variants are not supported when serializing checksums"); + Ok(()) + } + + fn end(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + Ok(()) + } +} +impl<'a, 'r> ser::SerializeStruct for ChecksumMapSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.entries.push(( + get_checksum(key, self.registries)?, + get_checksum(value, self.registries)?, + )); + Ok(()) + } + + fn end(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + update_hasher_for_map(self.hasher, &self.entries); + Ok(()) + } +} +impl<'a, 'r> ser::SerializeStructVariant for ChecksumMapSerializer<'a, 'r> { + type Ok = (); + type Error = ChecksumError; + + fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.entries.push(( + get_checksum(key, self.registries)?, + get_checksum(value, self.registries)?, + )); + Ok(()) + } + + fn end(self) -> Result<()> { + assert!(self.hasher.finish() == 0); + update_hasher_for_map(self.hasher, &self.entries); + Ok(()) + } +} + +/// A hasher that can only serialize i32 and i64. +struct IntOrLongArrayChecksumSerializer<'a> { + hasher: &'a mut Crc32cHasher, +} +impl<'a> ser::Serializer for IntOrLongArrayChecksumSerializer<'a> { + type Ok = (); + type Error = ChecksumError; + // unused + type SerializeSeq = ChecksumListSerializer<'a, 'a>; + type SerializeTuple = ChecksumListSerializer<'a, 'a>; + type SerializeTupleStruct = ChecksumListSerializer<'a, 'a>; + type SerializeTupleVariant = ChecksumMapSerializer<'a, 'a>; + type SerializeMap = ChecksumMapSerializer<'a, 'a>; + type SerializeStruct = ChecksumMapSerializer<'a, 'a>; + type SerializeStructVariant = ChecksumMapSerializer<'a, 'a>; + + fn serialize_bool(self, _v: bool) -> Result<()> { + unimplemented!() + } + fn serialize_i8(self, _v: i8) -> Result<()> { + unimplemented!() + } + fn serialize_i16(self, _v: i16) -> Result<()> { + unimplemented!() + } + fn serialize_i32(self, v: i32) -> Result<()> { + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + fn serialize_i64(self, v: i64) -> Result<()> { + self.hasher.write(&v.to_le_bytes()); + Ok(()) + } + fn serialize_u8(self, _v: u8) -> Result<()> { + unimplemented!() + } + fn serialize_u16(self, _v: u16) -> Result<()> { + unimplemented!() + } + fn serialize_u32(self, v: u32) -> Result<()> { + self.serialize_i32(v as i32) + } + fn serialize_u64(self, v: u64) -> Result<()> { + self.serialize_i64(v as i64) + } + fn serialize_f32(self, _v: f32) -> Result<()> { + unimplemented!() + } + fn serialize_f64(self, _v: f64) -> Result<()> { + unimplemented!() + } + fn serialize_char(self, _v: char) -> Result<()> { + unimplemented!() + } + fn serialize_str(self, _v: &str) -> Result<()> { + unimplemented!() + } + fn serialize_bytes(self, _v: &[u8]) -> Result<()> { + unimplemented!() + } + fn serialize_none(self) -> Result<()> { + unimplemented!() + } + fn serialize_some<T>(self, _v: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + unimplemented!() + } + fn serialize_unit(self) -> Result<()> { + unimplemented!() + } + fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { + unimplemented!() + } + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + ) -> Result<()> { + unimplemented!() + } + fn serialize_newtype_struct<T>(self, _name: &'static str, _value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + unimplemented!() + } + fn serialize_newtype_variant<T>( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> Result<()> + where + T: ?Sized + Serialize, + { + unimplemented!() + } + fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> { + unimplemented!() + } + fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> { + unimplemented!() + } + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result<Self::SerializeTupleStruct> { + unimplemented!() + } + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result<Self::SerializeTupleVariant> { + unimplemented!() + } + fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> { + unimplemented!() + } + fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> { + unimplemented!() + } + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result<Self::SerializeStructVariant> { + unimplemented!() + } +} + +#[derive(Error, Debug)] +#[error("Checksum serialization error")] +pub struct ChecksumError; +impl ser::Error for ChecksumError { + fn custom<T>(msg: T) -> Self + where + T: fmt::Display, + { + eprintln!("Serialization error: {msg}"); + ChecksumError + } +} +type Result<T> = std::result::Result<T, ChecksumError>; + +pub fn get_checksum<T: Serialize + ?Sized>( + value: &T, + registries: &RegistryHolder, +) -> Result<Checksum> { + let mut hasher = Crc32cHasher::default(); + value.serialize(ChecksumSerializer { + hasher: &mut hasher, + registries, + })?; + Ok(Checksum(hasher.finish() as u32)) +} + +fn update_hasher_for_list(h: &mut Crc32cHasher, values: &[Checksum]) { + h.write_u8(4); + for v in values { + h.write(&v.0.to_le_bytes()); + } + h.write_u8(5); +} +fn update_hasher_for_map(h: &mut Crc32cHasher, entries: &[(Checksum, Checksum)]) { + println!("getting checksum for map with {} entries", entries.len()); + h.write_u8(2); + let mut entries = entries.to_vec(); + entries.sort_by(|a, b| match a.0.cmp(&b.0) { + Ordering::Equal => a.1.cmp(&b.1), + other => other, + }); + for (k, v) in entries { + h.write(&k.0.to_le_bytes()); + h.write(&v.0.to_le_bytes()); + } + h.write_u8(3); +} + +// impl AzaleaChecksum for i8 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(6); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for i16 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(7); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for i32 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(8); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for i64 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(9); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for f32 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(10); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for f64 { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(11); +// h.write(&self.to_le_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for &str { +// fn azalea_checksum(&self) -> HashCode { +// println!("doing checksum for str: {self:?}"); +// let mut h = Crc32cHasher::default(); +// h.write_u8(12); +// h.write(&(self.len() as u32).to_le_bytes()); +// h.write(&self.as_bytes()); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for String { +// fn azalea_checksum(&self) -> HashCode { +// println!("doing checksum for String: {self:?}"); +// let mut h = Crc32cHasher::default(); +// h.write_u8(12); + +// let utf16 = self.encode_utf16().collect::<Vec<_>>(); +// h.write(&(utf16.len() as u32).to_le_bytes()); +// for c in utf16 { +// h.write(&c.to_le_bytes()); +// } + +// println!("doing checksum for string: {self:?}"); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for bool { +// fn azalea_checksum(&self) -> HashCode { +// println!("doing checksum for bool: {self:?}"); +// let mut h = Crc32cHasher::default(); +// h.write_u8(13); +// h.write_u8(*self as u8); +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<u8> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(14); +// h.write(self); +// h.write_u8(15); + +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<i8> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(14); +// for item in self { +// h.write(&[*item as u8]); +// } +// h.write_u8(15); + +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<u32> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(16); +// for item in self { +// h.write(&item.to_le_bytes()); +// } +// h.write_u8(17); + +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<i32> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(16); +// for item in self { +// h.write(&item.to_le_bytes()); +// } +// h.write_u8(17); + +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<u64> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(18); +// for item in self { +// h.write(&item.to_le_bytes()); +// } +// h.write_u8(19); + +// HashCode(h.finish() as u32) +// } +// } +// impl AzaleaChecksum for Vec<i64> { +// fn azalea_checksum(&self) -> HashCode { +// let mut h = Crc32cHasher::default(); +// h.write_u8(18); +// for item in self { +// h.write(&item.to_le_bytes()); +// } +// h.write_u8(19); + +// HashCode(h.finish() as u32) +// } +// } diff --git a/azalea-core/src/codec_utils.rs b/azalea-core/src/codec_utils.rs new file mode 100644 index 00000000..0014f86d --- /dev/null +++ b/azalea-core/src/codec_utils.rs @@ -0,0 +1,83 @@ +//! Some functions that are useful to have when implementing +//! `Serialize`/`Deserialize`, which Azalea uses to imitate Minecraft codecs. + +use azalea_buf::SerializableUuid; +use serde::{Serialize, Serializer, ser::SerializeTupleStruct}; +use uuid::Uuid; + +/// Intended to be used for skipping serialization if the value is the default. +/// +/// ```no_run +/// #[serde(skip_serializing_if = "is_default")] +/// ``` +pub fn is_default<T: Default + PartialEq>(t: &T) -> bool { + *t == Default::default() +} + +/// Intended to be used for skipping serialization if the value is `true`. +/// +/// ```no_run +/// #[serde(skip_serializing_if = "is_true")] +/// ``` +pub fn is_true(t: &bool) -> bool { + *t +} + +/// If the array has a single item, don't serialize as an array +/// +/// ```no_run +/// #[serde(serialize_with = "flatten_array")] +/// ``` +pub fn flatten_array<S: Serializer, T: Serialize>(x: &Vec<T>, s: S) -> Result<S::Ok, S::Error> { + if x.len() == 1 { + x[0].serialize(s) + } else { + x.serialize(s) + } +} + +/// Minecraft writes UUIDs as an IntArray<4> +pub fn uuid<'a, S: Serializer>( + uuid: impl Into<&'a Option<Uuid>>, + serializer: S, +) -> Result<S::Ok, S::Error> { + if let Some(uuid) = uuid.into() { + let arr: [u32; 4] = uuid.to_int_array(); + let arr: [i32; 4] = [arr[0] as i32, arr[1] as i32, arr[2] as i32, arr[3] as i32]; + IntArray(arr).serialize(serializer) + } else { + serializer.serialize_unit() + } +} + +/// An internal type that makes the i32 array be serialized differently. +/// +/// Azalea currently only uses this when writing checksums, but Minecraft also +/// uses this internally when converting types to NBT. +pub struct IntArray<const N: usize>(pub [i32; N]); +/// An internal type that makes the i64 array be serialized differently. +/// +/// Azalea currently only uses this when writing checksums, but Minecraft also +/// uses this internally when converting types to NBT. +pub struct LongArray<const N: usize>(pub [i64; N]); + +impl<const N: usize> Serialize for IntArray<N> { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + // see checksum::serialize_tuple_struct + let mut seq = serializer.serialize_tuple_struct("azalea:int_array", N)?; + for &item in &self.0 { + seq.serialize_field(&item)?; + } + seq.end() + } +} +impl<const N: usize> Serialize for LongArray<N> { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + // see checksum::serialize_tuple_struct + let mut seq = serializer.serialize_tuple_struct("azalea:long_array", N)?; + for &item in &self.0 { + seq.serialize_field(&item)?; + } + seq.end() + } +} diff --git a/azalea-core/src/direction.rs b/azalea-core/src/direction.rs index 9f51ceaf..c794c79a 100644 --- a/azalea-core/src/direction.rs +++ b/azalea-core/src/direction.rs @@ -2,8 +2,9 @@ use azalea_buf::AzBuf; use crate::position::{BlockPos, Vec3}; -#[derive(Clone, Copy, Debug, AzBuf, Default, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive( + Clone, Copy, Debug, AzBuf, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, +)] pub enum Direction { #[default] Down = 0, @@ -90,8 +91,7 @@ impl Direction { /// /// Note that azalea_block has a similar enum named `FacingCardinal` that is /// used for block states. -#[derive(Clone, Copy, Debug, AzBuf, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone, Copy, Debug, AzBuf, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)] pub enum CardinalDirection { North, South, diff --git a/azalea-core/src/filterable.rs b/azalea-core/src/filterable.rs index 1c432b28..893ebc3e 100644 --- a/azalea-core/src/filterable.rs +++ b/azalea-core/src/filterable.rs @@ -1,10 +1,13 @@ use std::io::{self, Cursor, Write}; use azalea_buf::{AzaleaRead, AzaleaReadLimited, AzaleaReadVar, AzaleaWrite}; +use serde::Serialize; /// Used for written books. +#[derive(Serialize)] pub struct Filterable<T> { pub raw: T, + #[serde(skip_serializing_if = "Option::is_none")] pub filtered: Option<T>, } diff --git a/azalea-core/src/hit_result.rs b/azalea-core/src/hit_result.rs index fe759d43..96ff32c9 100644 --- a/azalea-core/src/hit_result.rs +++ b/azalea-core/src/hit_result.rs @@ -1,5 +1,3 @@ -use bevy_ecs::entity::Entity; - use crate::{ direction::Direction, position::{BlockPos, Vec3}, @@ -8,12 +6,14 @@ use crate::{ /// The block or entity that our player is looking at and can interact with. /// /// If there's nothing, it'll be a [`BlockHitResult`] with `miss` set to true. +#[cfg(feature = "bevy_ecs")] #[derive(Debug, Clone, PartialEq)] pub enum HitResult { Block(BlockHitResult), Entity(EntityHitResult), } +#[cfg(feature = "bevy_ecs")] impl HitResult { pub fn miss(&self) -> bool { match self { @@ -86,18 +86,21 @@ impl BlockHitResult { Self { block_pos, ..*self } } } +#[cfg(feature = "bevy_ecs")] +impl From<BlockHitResult> for HitResult { + fn from(value: BlockHitResult) -> Self { + HitResult::Block(value) + } +} +#[cfg(feature = "bevy_ecs")] #[derive(Debug, Clone, PartialEq)] pub struct EntityHitResult { pub location: Vec3, - pub entity: Entity, + pub entity: bevy_ecs::entity::Entity, } -impl From<BlockHitResult> for HitResult { - fn from(value: BlockHitResult) -> Self { - HitResult::Block(value) - } -} +#[cfg(feature = "bevy_ecs")] impl From<EntityHitResult> for HitResult { fn from(value: EntityHitResult) -> Self { HitResult::Entity(value) diff --git a/azalea-core/src/lib.rs b/azalea-core/src/lib.rs index 9f6e9386..9fdf4b6c 100644 --- a/azalea-core/src/lib.rs +++ b/azalea-core/src/lib.rs @@ -4,6 +4,8 @@ pub mod aabb; pub mod bitset; +pub mod checksum; +pub mod codec_utils; pub mod color; pub mod cursor3d; pub mod data_registry; diff --git a/azalea-core/src/position.rs b/azalea-core/src/position.rs index dd6a37e0..8ba381a5 100644 --- a/azalea-core/src/position.rs +++ b/azalea-core/src/position.rs @@ -13,8 +13,12 @@ use std::{ }; use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError}; +use serde::{Serialize, Serializer}; +use simdnbt::Deserialize; -use crate::{direction::Direction, math, resource_location::ResourceLocation}; +use crate::{ + codec_utils::IntArray, direction::Direction, math, resource_location::ResourceLocation, +}; macro_rules! vec3_impl { ($name:ident, $type:ty) => { @@ -295,8 +299,7 @@ macro_rules! vec3_impl { /// Used to represent an exact position in the world where an entity could be. /// /// For blocks, [`BlockPos`] is used instead. -#[derive(Clone, Copy, Debug, Default, PartialEq, AzBuf)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone, Copy, Debug, Default, PartialEq, AzBuf, serde::Deserialize, serde::Serialize)] pub struct Vec3 { pub x: f64, pub y: f64, @@ -362,7 +365,6 @@ impl Vec3 { /// /// For entities (if the coordinates are floating-point), use [`Vec3`] instead. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct BlockPos { pub x: i32, pub y: i32, @@ -425,6 +427,24 @@ impl BlockPos { (self - other).length() } } +impl serde::Serialize for BlockPos { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + // makes sure it gets serialized correctly for the checksum + IntArray([self.x, self.y, self.z]).serialize(serializer) + } +} +impl<'de> serde::Deserialize<'de> for BlockPos { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let [x, y, z] = <[i32; 3]>::deserialize(deserializer)?; + Ok(BlockPos { x, y, z }) + } +} /// Similar to [`BlockPos`] but it's serialized as 3 varints instead of one /// 64-bit integer, so it can represent a bigger range of numbers. @@ -661,10 +681,10 @@ impl From<ChunkSectionBlockPos> for u16 { impl nohash_hasher::IsEnabled for ChunkSectionBlockPos {} /// A block pos with an attached world -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize)] pub struct GlobalPos { // this is actually a ResourceKey in Minecraft, but i don't think it matters? - pub world: ResourceLocation, + pub dimension: ResourceLocation, pub pos: BlockPos, } @@ -819,8 +839,7 @@ impl fmt::Display for Vec3 { } /// A 2D vector. -#[derive(Clone, Copy, Debug, Default, PartialEq, AzBuf)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone, Copy, Debug, Default, PartialEq, AzBuf, Deserialize, Serialize)] pub struct Vec2 { pub x: f32, pub y: f32, @@ -900,7 +919,7 @@ impl AzaleaRead for BlockPos { impl AzaleaRead for GlobalPos { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { Ok(GlobalPos { - world: ResourceLocation::azalea_read(buf)?, + dimension: ResourceLocation::azalea_read(buf)?, pos: BlockPos::azalea_read(buf)?, }) } @@ -929,7 +948,7 @@ impl AzaleaWrite for BlockPos { impl AzaleaWrite for GlobalPos { fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> { - ResourceLocation::azalea_write(&self.world, buf)?; + ResourceLocation::azalea_write(&self.dimension, buf)?; BlockPos::azalea_write(&self.pos, buf)?; Ok(()) diff --git a/azalea-core/src/resource_location.rs b/azalea-core/src/resource_location.rs index c6e39150..1591f678 100644 --- a/azalea-core/src/resource_location.rs +++ b/azalea-core/src/resource_location.rs @@ -7,7 +7,6 @@ use std::{ }; use azalea_buf::{AzaleaRead, AzaleaWrite, BufReadError}; -#[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use simdnbt::{FromNbtTag, ToNbtTag, owned::NbtTag}; @@ -77,7 +76,6 @@ impl AzaleaWrite for ResourceLocation { } } -#[cfg(feature = "serde")] impl Serialize for ResourceLocation { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where @@ -87,7 +85,6 @@ impl Serialize for ResourceLocation { } } -#[cfg(feature = "serde")] impl<'de> Deserialize<'de> for ResourceLocation { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where diff --git a/azalea-core/src/sound.rs b/azalea-core/src/sound.rs index f12205e0..3f7b86c3 100644 --- a/azalea-core/src/sound.rs +++ b/azalea-core/src/sound.rs @@ -1,8 +1,9 @@ use azalea_buf::AzBuf; +use serde::Serialize; use crate::resource_location::ResourceLocation; -#[derive(Clone, Debug, PartialEq, AzBuf)] +#[derive(Clone, Debug, PartialEq, AzBuf, Serialize)] pub struct CustomSound { pub location: ResourceLocation, pub fixed_range: Option<f32>, diff --git a/azalea-inventory/Cargo.toml b/azalea-inventory/Cargo.toml index 018dd8b3..12dc5afe 100644 --- a/azalea-inventory/Cargo.toml +++ b/azalea-inventory/Cargo.toml @@ -11,8 +11,10 @@ azalea-buf.workspace = true azalea-chat = { workspace = true, features = ["azalea-buf"] } azalea-core.workspace = true azalea-inventory-macros.workspace = true -azalea-registry.workspace = true +azalea-registry = { workspace = true, features = ["serde"] } indexmap.workspace = true +serde.workspace = true +serde_json.workspace = true simdnbt.workspace = true tracing.workspace = true diff --git a/azalea-inventory/src/components.rs b/azalea-inventory/src/components.rs index e11a4491..09923af0 100644 --- a/azalea-inventory/src/components.rs +++ b/azalea-inventory/src/components.rs @@ -3,30 +3,40 @@ use std::{ any::Any, collections::HashMap, io::{self, Cursor}, + mem::ManuallyDrop, }; use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError}; use azalea_chat::FormattedText; use azalea_core::{ - filterable::Filterable, position::GlobalPos, resource_location::ResourceLocation, + checksum::{Checksum, get_checksum}, + codec_utils::*, + filterable::Filterable, + position::GlobalPos, + registry_holder::RegistryHolder, + resource_location::ResourceLocation, sound::CustomSound, }; use azalea_registry::{ self as registry, Attribute, Block, DamageKind, DataComponentKind, Enchantment, EntityKind, Holder, HolderSet, Item, MobEffect, Potion, SoundEvent, TrimMaterial, TrimPattern, }; +use serde::{Serialize, ser::SerializeMap}; use simdnbt::owned::{Nbt, NbtCompound}; use tracing::trace; use uuid::Uuid; use crate::{ItemStack, item::consume_effect::ConsumeEffect}; -pub trait DataComponent: Send + Sync + Any + Clone { +pub trait DataComponentTrait: + Send + Sync + Any + Clone + Serialize + Into<DataComponentUnion> +{ const KIND: DataComponentKind; } pub trait EncodableDataComponent: Send + Sync + Any { fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()>; + fn crc_hash(&self, registries: &RegistryHolder) -> Checksum; // using the Clone trait makes it not be object-safe, so we have our own clone // function instead fn clone(&self) -> Box<dyn EncodableDataComponent>; @@ -36,11 +46,14 @@ pub trait EncodableDataComponent: Send + Sync + Any { impl<T> EncodableDataComponent for T where - T: DataComponent + Clone + AzaleaWrite + AzaleaRead + PartialEq, + T: DataComponentTrait + Clone + AzaleaWrite + AzaleaRead + PartialEq, { fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> { self.azalea_write(buf) } + fn crc_hash(&self, registries: &RegistryHolder) -> Checksum { + get_checksum(self, registries).expect("serializing data components should always succeed") + } fn clone(&self) -> Box<dyn EncodableDataComponent> { let cloned = self.clone(); Box::new(cloned) @@ -54,220 +67,286 @@ where } } -pub fn from_kind( - kind: registry::DataComponentKind, - buf: &mut Cursor<&[u8]>, -) -> Result<Box<dyn EncodableDataComponent>, BufReadError> { - // if this is causing a compile-time error, look at DataComponents.java in the - // decompiled vanilla code to see how to implement new components - - trace!("Reading data component {kind}"); - - // note that this match statement is updated by genitemcomponents.py - Ok(match kind { - DataComponentKind::CustomData => Box::new(CustomData::azalea_read(buf)?), - DataComponentKind::MaxStackSize => Box::new(MaxStackSize::azalea_read(buf)?), - DataComponentKind::MaxDamage => Box::new(MaxDamage::azalea_read(buf)?), - DataComponentKind::Damage => Box::new(Damage::azalea_read(buf)?), - DataComponentKind::Unbreakable => Box::new(Unbreakable::azalea_read(buf)?), - DataComponentKind::CustomName => Box::new(CustomName::azalea_read(buf)?), - DataComponentKind::ItemName => Box::new(ItemName::azalea_read(buf)?), - DataComponentKind::Lore => Box::new(Lore::azalea_read(buf)?), - DataComponentKind::Rarity => Box::new(Rarity::azalea_read(buf)?), - DataComponentKind::Enchantments => Box::new(Enchantments::azalea_read(buf)?), - DataComponentKind::CanPlaceOn => Box::new(CanPlaceOn::azalea_read(buf)?), - DataComponentKind::CanBreak => Box::new(CanBreak::azalea_read(buf)?), - DataComponentKind::AttributeModifiers => Box::new(AttributeModifiers::azalea_read(buf)?), - DataComponentKind::CustomModelData => Box::new(CustomModelData::azalea_read(buf)?), - DataComponentKind::RepairCost => Box::new(RepairCost::azalea_read(buf)?), - DataComponentKind::CreativeSlotLock => Box::new(CreativeSlotLock::azalea_read(buf)?), - DataComponentKind::EnchantmentGlintOverride => { - Box::new(EnchantmentGlintOverride::azalea_read(buf)?) - } - DataComponentKind::IntangibleProjectile => { - Box::new(IntangibleProjectile::azalea_read(buf)?) - } - DataComponentKind::Food => Box::new(Food::azalea_read(buf)?), - DataComponentKind::Tool => Box::new(Tool::azalea_read(buf)?), - DataComponentKind::StoredEnchantments => Box::new(StoredEnchantments::azalea_read(buf)?), - DataComponentKind::DyedColor => Box::new(DyedColor::azalea_read(buf)?), - DataComponentKind::MapColor => Box::new(MapColor::azalea_read(buf)?), - DataComponentKind::MapId => Box::new(MapId::azalea_read(buf)?), - DataComponentKind::MapDecorations => Box::new(MapDecorations::azalea_read(buf)?), - DataComponentKind::MapPostProcessing => Box::new(MapPostProcessing::azalea_read(buf)?), - DataComponentKind::ChargedProjectiles => Box::new(ChargedProjectiles::azalea_read(buf)?), - DataComponentKind::BundleContents => Box::new(BundleContents::azalea_read(buf)?), - DataComponentKind::PotionContents => Box::new(PotionContents::azalea_read(buf)?), - DataComponentKind::SuspiciousStewEffects => { - Box::new(SuspiciousStewEffects::azalea_read(buf)?) - } - DataComponentKind::WritableBookContent => Box::new(WritableBookContent::azalea_read(buf)?), - DataComponentKind::WrittenBookContent => Box::new(WrittenBookContent::azalea_read(buf)?), - DataComponentKind::Trim => Box::new(Trim::azalea_read(buf)?), - DataComponentKind::DebugStickState => Box::new(DebugStickState::azalea_read(buf)?), - DataComponentKind::EntityData => Box::new(EntityData::azalea_read(buf)?), - DataComponentKind::BucketEntityData => Box::new(BucketEntityData::azalea_read(buf)?), - DataComponentKind::BlockEntityData => Box::new(BlockEntityData::azalea_read(buf)?), - DataComponentKind::Instrument => Box::new(Instrument::azalea_read(buf)?), - DataComponentKind::OminousBottleAmplifier => { - Box::new(OminousBottleAmplifier::azalea_read(buf)?) +#[macro_export] +macro_rules! define_data_components { + ( $( $x:ident ),* $(,)? ) => { + /// A union of all data components. + /// + /// You probably don't want to use this directly. Consider [`DataComponentPatch`] instead. + /// + /// This type does not know its own value, and as such every function for it requires the + /// `DataComponentKind` to be passed in. Passing the wrong `DataComponentKind` will result + /// in undefined behavior. Also, all of the values are `ManuallyDrop`. + /// + /// [`DataComponentPatch`]: crate::DataComponentPatch + #[allow(non_snake_case)] + pub union DataComponentUnion { + $( $x: ManuallyDrop<$x>, )* } - DataComponentKind::Recipes => Box::new(Recipes::azalea_read(buf)?), - DataComponentKind::LodestoneTracker => Box::new(LodestoneTracker::azalea_read(buf)?), - DataComponentKind::FireworkExplosion => Box::new(FireworkExplosion::azalea_read(buf)?), - DataComponentKind::Fireworks => Box::new(Fireworks::azalea_read(buf)?), - DataComponentKind::Profile => Box::new(Profile::azalea_read(buf)?), - DataComponentKind::NoteBlockSound => Box::new(NoteBlockSound::azalea_read(buf)?), - DataComponentKind::BannerPatterns => Box::new(BannerPatterns::azalea_read(buf)?), - DataComponentKind::BaseColor => Box::new(BaseColor::azalea_read(buf)?), - DataComponentKind::PotDecorations => Box::new(PotDecorations::azalea_read(buf)?), - DataComponentKind::Container => Box::new(Container::azalea_read(buf)?), - DataComponentKind::BlockState => Box::new(BlockState::azalea_read(buf)?), - DataComponentKind::Bees => Box::new(Bees::azalea_read(buf)?), - DataComponentKind::Lock => Box::new(Lock::azalea_read(buf)?), - DataComponentKind::ContainerLoot => Box::new(ContainerLoot::azalea_read(buf)?), - DataComponentKind::JukeboxPlayable => Box::new(JukeboxPlayable::azalea_read(buf)?), - DataComponentKind::Consumable => Box::new(Consumable::azalea_read(buf)?), - DataComponentKind::UseRemainder => Box::new(UseRemainder::azalea_read(buf)?), - DataComponentKind::UseCooldown => Box::new(UseCooldown::azalea_read(buf)?), - DataComponentKind::Enchantable => Box::new(Enchantable::azalea_read(buf)?), - DataComponentKind::Repairable => Box::new(Repairable::azalea_read(buf)?), - DataComponentKind::ItemModel => Box::new(ItemModel::azalea_read(buf)?), - DataComponentKind::DamageResistant => Box::new(DamageResistant::azalea_read(buf)?), - DataComponentKind::Equippable => Box::new(Equippable::azalea_read(buf)?), - DataComponentKind::Glider => Box::new(Glider::azalea_read(buf)?), - DataComponentKind::TooltipStyle => Box::new(TooltipStyle::azalea_read(buf)?), - DataComponentKind::DeathProtection => Box::new(DeathProtection::azalea_read(buf)?), - DataComponentKind::Weapon => Box::new(Weapon::azalea_read(buf)?), - DataComponentKind::PotionDurationScale => Box::new(PotionDurationScale::azalea_read(buf)?), - DataComponentKind::VillagerVariant => Box::new(VillagerVariant::azalea_read(buf)?), - DataComponentKind::WolfVariant => Box::new(WolfVariant::azalea_read(buf)?), - DataComponentKind::WolfCollar => Box::new(WolfCollar::azalea_read(buf)?), - DataComponentKind::FoxVariant => Box::new(FoxVariant::azalea_read(buf)?), - DataComponentKind::SalmonSize => Box::new(SalmonSize::azalea_read(buf)?), - DataComponentKind::ParrotVariant => Box::new(ParrotVariant::azalea_read(buf)?), - DataComponentKind::TropicalFishPattern => Box::new(TropicalFishPattern::azalea_read(buf)?), - DataComponentKind::TropicalFishBaseColor => { - Box::new(TropicalFishBaseColor::azalea_read(buf)?) + impl DataComponentUnion { + /// # Safety + /// + /// `kind` must be the correct value for this union. + pub unsafe fn serialize_entry_as<S: SerializeMap>( + &self, + serializer: &mut S, + kind: DataComponentKind, + ) -> Result<(), S::Error> { + match kind { + $( DataComponentKind::$x => { unsafe { serializer.serialize_entry(&kind, &*self.$x) } }, )* + } + } + /// # Safety + /// + /// `kind` must be the correct value for this union. + pub unsafe fn drop_as(&mut self, kind: DataComponentKind) { + match kind { + $( DataComponentKind::$x => { unsafe { ManuallyDrop::drop(&mut self.$x) } }, )* + } + } + /// # Safety + /// + /// `kind` must be the correct value for this union. + pub unsafe fn as_kind(&self, kind: DataComponentKind) -> &dyn EncodableDataComponent { + match kind { + $( DataComponentKind::$x => { unsafe { &**(&self.$x as &ManuallyDrop<dyn EncodableDataComponent>) } }, )* + } + } + pub fn azalea_read_as( + kind: registry::DataComponentKind, + buf: &mut Cursor<&[u8]>, + ) -> Result<Self, BufReadError> { + trace!("Reading data component {kind}"); + + Ok(match kind { + $( DataComponentKind::$x => { + Self { $x: ManuallyDrop::new($x::azalea_read(buf)?) } + }, )* + }) + } + /// # Safety + /// + /// `kind` must be the correct value for this union. + pub unsafe fn azalea_write_as( + &self, + kind: registry::DataComponentKind, + buf: &mut Vec<u8>, + ) -> io::Result<()> { + match kind { + $( DataComponentKind::$x => unsafe { self.$x.encode(buf) }, )* + } + } + /// # Safety + /// + /// `kind` must be the correct value for this union. + pub unsafe fn clone_as( + &self, + kind: registry::DataComponentKind, + ) -> Self { + match kind { + $( DataComponentKind::$x => { + Self { $x: unsafe { self.$x.clone() } } + }, )* + } + } + /// # Safety + /// + /// `kind` must be the correct value for this union. + pub unsafe fn eq_as( + &self, + other: &Self, + kind: registry::DataComponentKind, + ) -> bool { + match kind { + $( DataComponentKind::$x => unsafe { self.$x.eq(&other.$x) }, )* + } + } } - DataComponentKind::TropicalFishPatternColor => { - Box::new(TropicalFishPatternColor::azalea_read(buf)?) - } - DataComponentKind::MooshroomVariant => Box::new(MooshroomVariant::azalea_read(buf)?), - DataComponentKind::RabbitVariant => Box::new(RabbitVariant::azalea_read(buf)?), - DataComponentKind::PigVariant => Box::new(PigVariant::azalea_read(buf)?), - DataComponentKind::FrogVariant => Box::new(FrogVariant::azalea_read(buf)?), - DataComponentKind::HorseVariant => Box::new(HorseVariant::azalea_read(buf)?), - DataComponentKind::PaintingVariant => Box::new(PaintingVariant::azalea_read(buf)?), - DataComponentKind::LlamaVariant => Box::new(LlamaVariant::azalea_read(buf)?), - DataComponentKind::AxolotlVariant => Box::new(AxolotlVariant::azalea_read(buf)?), - DataComponentKind::CatVariant => Box::new(CatVariant::azalea_read(buf)?), - DataComponentKind::CatCollar => Box::new(CatCollar::azalea_read(buf)?), - DataComponentKind::SheepColor => Box::new(SheepColor::azalea_read(buf)?), - DataComponentKind::ShulkerColor => Box::new(ShulkerColor::azalea_read(buf)?), - DataComponentKind::TooltipDisplay => Box::new(TooltipDisplay::azalea_read(buf)?), - DataComponentKind::BlocksAttacks => Box::new(BlocksAttacks::azalea_read(buf)?), - DataComponentKind::ProvidesTrimMaterial => { - Box::new(ProvidesTrimMaterial::azalea_read(buf)?) - } - DataComponentKind::ProvidesBannerPatterns => { - Box::new(ProvidesBannerPatterns::azalea_read(buf)?) - } - DataComponentKind::BreakSound => Box::new(BreakSound::azalea_read(buf)?), - DataComponentKind::WolfSoundVariant => Box::new(WolfSoundVariant::azalea_read(buf)?), - DataComponentKind::CowVariant => Box::new(CowVariant::azalea_read(buf)?), - DataComponentKind::ChickenVariant => Box::new(ChickenVariant::azalea_read(buf)?), - }) -} - -#[derive(Clone, PartialEq, AzBuf)] + $( + impl From<$x> for DataComponentUnion { + fn from(value: $x) -> Self { + DataComponentUnion { $x: ManuallyDrop::new(value) } + } + } + )* + + $( + impl DataComponentTrait for $x { + const KIND: DataComponentKind = DataComponentKind::$x; + } + )* + }; +} + +// if this is causing a compile-time error, look at DataComponents.java in the +// decompiled vanilla code to see how to implement new components + +// note that this statement is updated by genitemcomponents.py +define_data_components!( + CustomData, + MaxStackSize, + MaxDamage, + Damage, + Unbreakable, + CustomName, + ItemName, + ItemModel, + Lore, + Rarity, + Enchantments, + CanPlaceOn, + CanBreak, + AttributeModifiers, + CustomModelData, + TooltipDisplay, + RepairCost, + CreativeSlotLock, + EnchantmentGlintOverride, + IntangibleProjectile, + Food, + Consumable, + UseRemainder, + UseCooldown, + DamageResistant, + Tool, + Weapon, + Enchantable, + Equippable, + Repairable, + Glider, + TooltipStyle, + DeathProtection, + BlocksAttacks, + StoredEnchantments, + DyedColor, + MapColor, + MapId, + MapDecorations, + MapPostProcessing, + ChargedProjectiles, + BundleContents, + PotionContents, + PotionDurationScale, + SuspiciousStewEffects, + WritableBookContent, + WrittenBookContent, + Trim, + DebugStickState, + EntityData, + BucketEntityData, + BlockEntityData, + Instrument, + ProvidesTrimMaterial, + OminousBottleAmplifier, + JukeboxPlayable, + ProvidesBannerPatterns, + Recipes, + LodestoneTracker, + FireworkExplosion, + Fireworks, + Profile, + NoteBlockSound, + BannerPatterns, + BaseColor, + PotDecorations, + Container, + BlockState, + Bees, + Lock, + ContainerLoot, + BreakSound, + VillagerVariant, + WolfVariant, + WolfSoundVariant, + WolfCollar, + FoxVariant, + SalmonSize, + ParrotVariant, + TropicalFishPattern, + TropicalFishBaseColor, + TropicalFishPatternColor, + MooshroomVariant, + RabbitVariant, + PigVariant, + CowVariant, + ChickenVariant, + FrogVariant, + HorseVariant, + PaintingVariant, + LlamaVariant, + AxolotlVariant, + CatVariant, + CatCollar, + SheepColor, + ShulkerColor, +); + +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct CustomData { pub nbt: Nbt, } -impl DataComponent for CustomData { - const KIND: DataComponentKind = DataComponentKind::CustomData; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct MaxStackSize { #[var] pub count: i32, } -impl DataComponent for MaxStackSize { - const KIND: DataComponentKind = DataComponentKind::MaxStackSize; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct MaxDamage { #[var] pub amount: i32, } -impl DataComponent for MaxDamage { - const KIND: DataComponentKind = DataComponentKind::MaxDamage; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct Damage { #[var] pub amount: i32, } -impl DataComponent for Damage { - const KIND: DataComponentKind = DataComponentKind::Damage; -} - -#[derive(Clone, PartialEq, Default, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Unbreakable; -impl DataComponent for Unbreakable { - const KIND: DataComponentKind = DataComponentKind::Unbreakable; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct CustomName { pub name: FormattedText, } -impl DataComponent for CustomName { - const KIND: DataComponentKind = DataComponentKind::CustomName; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct ItemName { pub name: FormattedText, } -impl DataComponent for ItemName { - const KIND: DataComponentKind = DataComponentKind::ItemName; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct Lore { pub lines: Vec<FormattedText>, // vanilla also has styled_lines here but it doesn't appear to be used for the protocol } -impl DataComponent for Lore { - const KIND: DataComponentKind = DataComponentKind::Lore; -} -#[derive(Clone, PartialEq, Copy, AzBuf)] +#[derive(Clone, Copy, PartialEq, AzBuf, Debug, Serialize)] +#[serde(rename_all = "snake_case")] pub enum Rarity { Common, Uncommon, Rare, Epic, } -impl DataComponent for Rarity { - const KIND: DataComponentKind = DataComponentKind::Rarity; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Serialize)] +#[serde(transparent)] pub struct Enchantments { #[var] pub levels: HashMap<Enchantment, u32>, } -impl DataComponent for Enchantments { - const KIND: DataComponentKind = DataComponentKind::Enchantments; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub enum BlockStateValueMatcher { Exact { value: String, @@ -278,41 +357,43 @@ pub enum BlockStateValueMatcher { }, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct BlockStatePropertyMatcher { pub name: String, pub value_matcher: BlockStateValueMatcher, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct BlockPredicate { + #[serde(skip_serializing_if = "is_default")] pub blocks: Option<HolderSet<Block, ResourceLocation>>, + #[serde(skip_serializing_if = "is_default")] pub properties: Option<Vec<BlockStatePropertyMatcher>>, + #[serde(skip_serializing_if = "is_default")] pub nbt: Option<NbtCompound>, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct AdventureModePredicate { + #[serde(serialize_with = "flatten_array")] pub predicates: Vec<BlockPredicate>, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct CanPlaceOn { pub predicate: AdventureModePredicate, } -impl DataComponent for CanPlaceOn { - const KIND: DataComponentKind = DataComponentKind::CanPlaceOn; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct CanBreak { pub predicate: AdventureModePredicate, } -impl DataComponent for CanBreak { - const KIND: DataComponentKind = DataComponentKind::CanBreak; -} -#[derive(Clone, Copy, PartialEq, AzBuf)] +#[derive(Clone, Copy, PartialEq, AzBuf, Debug, Serialize)] +#[serde(rename_all = "snake_case")] pub enum EquipmentSlotGroup { Any, Mainhand, @@ -326,7 +407,8 @@ pub enum EquipmentSlotGroup { Body, } -#[derive(Clone, Copy, PartialEq, AzBuf)] +#[derive(Clone, Copy, PartialEq, AzBuf, Debug, Serialize)] +#[serde(rename_all = "snake_case")] pub enum AttributeModifierOperation { AddValue, AddMultipliedBase, @@ -336,85 +418,86 @@ pub enum AttributeModifierOperation { // this is duplicated in azalea-entity, BUT the one there has a different // protocol format (and we can't use it anyways because it would cause a // circular dependency) -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct AttributeModifier { pub id: ResourceLocation, pub amount: f64, pub operation: AttributeModifierOperation, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct AttributeModifiersEntry { - pub attribute: Attribute, + #[serde(rename = "type")] + pub kind: Attribute, + #[serde(flatten)] pub modifier: AttributeModifier, pub slot: EquipmentSlotGroup, + #[serde(skip_serializing_if = "is_default")] pub display: AttributeModifierDisplay, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct AttributeModifiers { pub modifiers: Vec<AttributeModifiersEntry>, } -impl DataComponent for AttributeModifiers { - const KIND: DataComponentKind = DataComponentKind::AttributeModifiers; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Default, Serialize)] +#[serde(rename_all = "snake_case")] pub enum AttributeModifierDisplay { + #[default] Default, Hidden, - Override { text: FormattedText }, + Override { + text: FormattedText, + }, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct CustomModelData { + #[serde(skip_serializing_if = "Vec::is_empty")] pub floats: Vec<f32>, + #[serde(skip_serializing_if = "Vec::is_empty")] pub flags: Vec<bool>, + #[serde(skip_serializing_if = "Vec::is_empty")] pub strings: Vec<String>, + #[serde(skip_serializing_if = "Vec::is_empty")] pub colors: Vec<i32>, } -impl DataComponent for CustomModelData { - const KIND: DataComponentKind = DataComponentKind::CustomModelData; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct RepairCost { #[var] pub cost: u32, } -impl DataComponent for RepairCost { - const KIND: DataComponentKind = DataComponentKind::RepairCost; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct CreativeSlotLock; -impl DataComponent for CreativeSlotLock { - const KIND: DataComponentKind = DataComponentKind::CreativeSlotLock; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct EnchantmentGlintOverride { pub show_glint: bool, } -impl DataComponent for EnchantmentGlintOverride { - const KIND: DataComponentKind = DataComponentKind::EnchantmentGlintOverride; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct IntangibleProjectile; -impl DataComponent for IntangibleProjectile { - const KIND: DataComponentKind = DataComponentKind::IntangibleProjectile; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct MobEffectDetails { #[var] + #[serde(skip_serializing_if = "is_default")] pub amplifier: i32, #[var] + #[serde(skip_serializing_if = "is_default")] pub duration: i32, + #[serde(skip_serializing_if = "is_default")] pub ambient: bool, + #[serde(skip_serializing_if = "is_default")] pub show_particles: bool, pub show_icon: bool, + #[serde(skip_serializing_if = "is_default")] pub hidden_effect: Option<Box<MobEffectDetails>>, } impl MobEffectDetails { @@ -424,7 +507,7 @@ impl MobEffectDetails { duration: 0, ambient: false, show_particles: true, - show_icon: false, + show_icon: true, hidden_effect: None, } } @@ -435,28 +518,28 @@ impl Default for MobEffectDetails { } } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct MobEffectInstance { pub id: MobEffect, + #[serde(flatten)] pub details: MobEffectDetails, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct PossibleEffect { pub effect: MobEffectInstance, pub probability: f32, } -#[derive(Clone, PartialEq, Default, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Default, Serialize)] pub struct Food { #[var] pub nutrition: i32, pub saturation: f32, + #[serde(skip_serializing_if = "is_default")] pub can_always_eat: bool, } -impl DataComponent for Food { - const KIND: DataComponentKind = DataComponentKind::Food; -} + impl Food { pub const fn new() -> Self { Food { @@ -467,10 +550,12 @@ impl Food { } } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct ToolRule { pub blocks: HolderSet<Block, ResourceLocation>, + #[serde(skip_serializing_if = "is_default")] pub speed: Option<f32>, + #[serde(skip_serializing_if = "is_default")] pub correct_for_drops: Option<bool>, } impl ToolRule { @@ -488,17 +573,19 @@ impl Default for ToolRule { } } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Tool { + #[serde(serialize_with = "flatten_array")] pub rules: Vec<ToolRule>, + #[serde(skip_serializing_if = "is_default")] pub default_mining_speed: f32, #[var] + #[serde(skip_serializing_if = "is_default")] pub damage_per_block: i32, + #[serde(skip_serializing_if = "is_default")] pub can_destroy_blocks_in_creative: bool, } -impl DataComponent for Tool { - const KIND: DataComponentKind = DataComponentKind::Tool; -} + impl Tool { pub const fn new() -> Self { Tool { @@ -515,83 +602,68 @@ impl Default for Tool { } } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct StoredEnchantments { #[var] pub enchantments: HashMap<Enchantment, i32>, } -impl DataComponent for StoredEnchantments { - const KIND: DataComponentKind = DataComponentKind::StoredEnchantments; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct DyedColor { pub rgb: i32, } -impl DataComponent for DyedColor { - const KIND: DataComponentKind = DataComponentKind::DyedColor; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct MapColor { pub color: i32, } -impl DataComponent for MapColor { - const KIND: DataComponentKind = DataComponentKind::MapColor; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct MapId { #[var] pub id: i32, } -impl DataComponent for MapId { - const KIND: DataComponentKind = DataComponentKind::MapId; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct MapDecorations { pub decorations: NbtCompound, } -impl DataComponent for MapDecorations { - const KIND: DataComponentKind = DataComponentKind::MapDecorations; -} -#[derive(Clone, Copy, PartialEq, AzBuf)] +#[derive(Clone, Copy, PartialEq, AzBuf, Debug, Serialize)] pub enum MapPostProcessing { Lock, Scale, } -impl DataComponent for MapPostProcessing { - const KIND: DataComponentKind = DataComponentKind::MapPostProcessing; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct ChargedProjectiles { pub items: Vec<ItemStack>, } -impl DataComponent for ChargedProjectiles { - const KIND: DataComponentKind = DataComponentKind::ChargedProjectiles; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct BundleContents { pub items: Vec<ItemStack>, } -impl DataComponent for BundleContents { - const KIND: DataComponentKind = DataComponentKind::BundleContents; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct PotionContents { + #[serde(skip_serializing_if = "is_default")] pub potion: Option<Potion>, + #[serde(skip_serializing_if = "is_default")] pub custom_color: Option<i32>, + #[serde(skip_serializing_if = "is_default")] pub custom_effects: Vec<MobEffectInstance>, + #[serde(skip_serializing_if = "is_default")] pub custom_name: Option<String>, } -impl DataComponent for PotionContents { - const KIND: DataComponentKind = DataComponentKind::PotionContents; -} + impl PotionContents { pub const fn new() -> Self { PotionContents { @@ -608,95 +680,78 @@ impl Default for PotionContents { } } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct SuspiciousStewEffect { + #[serde(rename = "id")] pub effect: MobEffect, #[var] + #[serde(skip_serializing_if = "is_default")] pub duration: i32, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct SuspiciousStewEffects { pub effects: Vec<SuspiciousStewEffect>, } -impl DataComponent for SuspiciousStewEffects { - const KIND: DataComponentKind = DataComponentKind::SuspiciousStewEffects; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct WritableBookContent { pub pages: Vec<String>, } -impl DataComponent for WritableBookContent { - const KIND: DataComponentKind = DataComponentKind::WritableBookContent; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Serialize)] pub struct WrittenBookContent { #[limit(32)] pub title: Filterable<String>, pub author: String, #[var] + #[serde(skip_serializing_if = "is_default")] pub generation: i32, + #[serde(skip_serializing_if = "is_default")] pub pages: Vec<Filterable<FormattedText>>, + #[serde(skip_serializing_if = "is_default")] pub resolved: bool, } -impl DataComponent for WrittenBookContent { - const KIND: DataComponentKind = DataComponentKind::WrittenBookContent; -} - -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Trim { pub material: TrimMaterial, pub pattern: TrimPattern, } -impl DataComponent for Trim { - const KIND: DataComponentKind = DataComponentKind::Trim; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct DebugStickState { pub properties: NbtCompound, } -impl DataComponent for DebugStickState { - const KIND: DataComponentKind = DataComponentKind::DebugStickState; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct EntityData { pub entity: NbtCompound, } -impl DataComponent for EntityData { - const KIND: DataComponentKind = DataComponentKind::EntityData; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct BucketEntityData { pub entity: NbtCompound, } -impl DataComponent for BucketEntityData { - const KIND: DataComponentKind = DataComponentKind::BucketEntityData; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct BlockEntityData { pub entity: NbtCompound, } -impl DataComponent for BlockEntityData { - const KIND: DataComponentKind = DataComponentKind::BlockEntityData; -} -#[derive(Clone, PartialEq, Debug, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(untagged)] pub enum Instrument { Registry(registry::Instrument), Holder(Holder<registry::Instrument, InstrumentData>), } -impl DataComponent for Instrument { - const KIND: DataComponentKind = DataComponentKind::Instrument; -} -#[derive(Clone, PartialEq, Debug, AzBuf)] +#[derive(Clone, PartialEq, Debug, AzBuf, Serialize)] pub struct InstrumentData { pub sound_event: Holder<SoundEvent, azalea_core::sound::CustomSound>, pub use_duration: f32, @@ -704,33 +759,29 @@ pub struct InstrumentData { pub description: FormattedText, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct OminousBottleAmplifier { #[var] pub amplifier: i32, } -impl DataComponent for OminousBottleAmplifier { - const KIND: DataComponentKind = DataComponentKind::OminousBottleAmplifier; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct Recipes { pub recipes: Vec<ResourceLocation>, } -impl DataComponent for Recipes { - const KIND: DataComponentKind = DataComponentKind::Recipes; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct LodestoneTracker { + #[serde(skip_serializing_if = "is_default")] pub target: Option<GlobalPos>, + #[serde(skip_serializing_if = "is_true")] pub tracked: bool, } -impl DataComponent for LodestoneTracker { - const KIND: DataComponentKind = DataComponentKind::LodestoneTracker; -} -#[derive(Clone, Copy, PartialEq, AzBuf)] +#[derive(Clone, Copy, PartialEq, AzBuf, Debug, Serialize)] +#[serde(rename_all = "snake_case")] pub enum FireworkExplosionShape { SmallBall, LargeBall, @@ -739,28 +790,28 @@ pub enum FireworkExplosionShape { Burst, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct FireworkExplosion { pub shape: FireworkExplosionShape, + #[serde(skip_serializing_if = "is_default")] pub colors: Vec<i32>, + #[serde(skip_serializing_if = "is_default")] pub fade_colors: Vec<i32>, + #[serde(skip_serializing_if = "is_default")] pub has_trail: bool, + #[serde(skip_serializing_if = "is_default")] pub has_twinkle: bool, } -impl DataComponent for FireworkExplosion { - const KIND: DataComponentKind = DataComponentKind::FireworkExplosion; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Fireworks { #[var] + #[serde(skip_serializing_if = "is_default")] pub flight_duration: i32, #[limit(256)] pub explosions: Vec<FireworkExplosion>, } -impl DataComponent for Fireworks { - const KIND: DataComponentKind = DataComponentKind::Fireworks; -} + impl Fireworks { pub const fn new() -> Self { Fireworks { @@ -775,32 +826,32 @@ impl Default for Fireworks { } } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct GameProfileProperty { pub name: String, pub value: String, + #[serde(skip_serializing_if = "is_default")] pub signature: Option<String>, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Profile { + #[serde(skip_serializing_if = "is_default")] pub name: Option<String>, + #[serde(skip_serializing_if = "is_default")] + #[serde(serialize_with = "azalea_core::codec_utils::uuid")] pub id: Option<Uuid>, + #[serde(skip_serializing_if = "is_default")] pub properties: Vec<GameProfileProperty>, } -impl DataComponent for Profile { - const KIND: DataComponentKind = DataComponentKind::Profile; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct NoteBlockSound { pub sound: ResourceLocation, } -impl DataComponent for NoteBlockSound { - const KIND: DataComponentKind = DataComponentKind::NoteBlockSound; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct BannerPattern { #[var] pub pattern: i32, @@ -808,15 +859,14 @@ pub struct BannerPattern { pub color: i32, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct BannerPatterns { pub patterns: Vec<BannerPattern>, } -impl DataComponent for BannerPatterns { - const KIND: DataComponentKind = DataComponentKind::BannerPatterns; -} -#[derive(Clone, Copy, PartialEq, AzBuf)] +#[derive(Clone, Copy, PartialEq, AzBuf, Debug, Serialize)] +#[serde(rename_all = "snake_case")] pub enum DyeColor { White, Orange, @@ -836,40 +886,33 @@ pub enum DyeColor { Black, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct BaseColor { pub color: DyeColor, } -impl DataComponent for BaseColor { - const KIND: DataComponentKind = DataComponentKind::BaseColor; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct PotDecorations { - pub items: Vec<Item>, -} -impl DataComponent for PotDecorations { - const KIND: DataComponentKind = DataComponentKind::PotDecorations; + pub items: [Item; 4], } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct Container { pub items: Vec<ItemStack>, } -impl DataComponent for Container { - const KIND: DataComponentKind = DataComponentKind::Container; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct BlockState { pub properties: HashMap<String, String>, } -impl DataComponent for BlockState { - const KIND: DataComponentKind = DataComponentKind::BlockState; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct BeehiveOccupant { + #[serde(skip_serializing_if = "is_default")] pub entity_data: NbtCompound, #[var] pub ticks_in_hive: i32, @@ -877,39 +920,30 @@ pub struct BeehiveOccupant { pub min_ticks_in_hive: i32, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct Bees { pub occupants: Vec<BeehiveOccupant>, } -impl DataComponent for Bees { - const KIND: DataComponentKind = DataComponentKind::Bees; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Lock { pub key: String, } -impl DataComponent for Lock { - const KIND: DataComponentKind = DataComponentKind::Lock; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct ContainerLoot { - pub loot: NbtCompound, -} -impl DataComponent for ContainerLoot { - const KIND: DataComponentKind = DataComponentKind::ContainerLoot; + pub loot_table: NbtCompound, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(untagged)] pub enum JukeboxPlayable { Referenced(ResourceLocation), Direct(Holder<registry::JukeboxSong, JukeboxSongData>), } -impl DataComponent for JukeboxPlayable { - const KIND: DataComponentKind = DataComponentKind::JukeboxPlayable; -} -#[derive(Clone, PartialEq, AzBuf)] + +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct JukeboxSongData { pub sound_event: Holder<SoundEvent, CustomSound>, pub description: FormattedText, @@ -918,17 +952,26 @@ pub struct JukeboxSongData { pub comparator_output: i32, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Consumable { + #[serde(skip_serializing_if = "is_default")] pub consume_seconds: f32, + #[serde(skip_serializing_if = "is_default")] pub animation: ItemUseAnimation, + #[serde(skip_serializing_if = "is_default_eat_sound")] pub sound: azalea_registry::Holder<SoundEvent, CustomSound>, + #[serde(skip_serializing_if = "is_default")] pub has_consume_particles: bool, + #[serde(skip_serializing_if = "is_default")] pub on_consume_effects: Vec<ConsumeEffect>, } -impl DataComponent for Consumable { - const KIND: DataComponentKind = DataComponentKind::Consumable; +fn is_default_eat_sound(sound: &azalea_registry::Holder<SoundEvent, CustomSound>) -> bool { + matches!( + sound, + azalea_registry::Holder::Reference(SoundEvent::EntityGenericEat) + ) } + impl Consumable { pub const fn new() -> Self { Self { @@ -946,8 +989,10 @@ impl Default for Consumable { } } -#[derive(Clone, Copy, PartialEq, AzBuf)] +#[derive(Clone, Copy, PartialEq, AzBuf, Debug, Default, Serialize)] +#[serde(rename_all = "snake_case")] pub enum ItemUseAnimation { + #[default] None, Eat, Drink, @@ -960,22 +1005,19 @@ pub enum ItemUseAnimation { Brush, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct UseRemainder { pub convert_into: ItemStack, } -impl DataComponent for UseRemainder { - const KIND: DataComponentKind = DataComponentKind::UseRemainder; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct UseCooldown { pub seconds: f32, + #[serde(skip_serializing_if = "is_default")] pub cooldown_group: Option<ResourceLocation>, } -impl DataComponent for UseCooldown { - const KIND: DataComponentKind = DataComponentKind::UseCooldown; -} + impl UseCooldown { pub const fn new() -> Self { Self { @@ -990,66 +1032,60 @@ impl Default for UseCooldown { } } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Enchantable { #[var] pub value: u32, } -impl DataComponent for Enchantable { - const KIND: DataComponentKind = DataComponentKind::Enchantable; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Repairable { pub items: HolderSet<Item, ResourceLocation>, } -impl DataComponent for Repairable { - const KIND: DataComponentKind = DataComponentKind::Repairable; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct ItemModel { pub resource_location: ResourceLocation, } -impl DataComponent for ItemModel { - const KIND: DataComponentKind = DataComponentKind::ItemModel; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct DamageResistant { - // in the vanilla code this is - // ``` - // StreamCodec.composite( - // TagKey.streamCodec(Registries.DAMAGE_TYPE), - // DamageResistant::types, - // DamageResistant::new, - // ); - // ``` - // i'm not entirely sure if this is meant to be a vec or something, i just made it a - // resourcelocation for now + /// In vanilla this only allows tag keys, i.e. it must start with '#' pub types: ResourceLocation, } -impl DataComponent for DamageResistant { - const KIND: DataComponentKind = DataComponentKind::DamageResistant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Equippable { pub slot: EquipmentSlot, + #[serde(skip_serializing_if = "is_default_equip_sound")] pub equip_sound: SoundEvent, + #[serde(skip_serializing_if = "is_default")] pub asset_id: Option<ResourceLocation>, + #[serde(skip_serializing_if = "is_default")] pub camera_overlay: Option<ResourceLocation>, + #[serde(skip_serializing_if = "is_default")] pub allowed_entities: Option<HolderSet<EntityKind, ResourceLocation>>, + #[serde(skip_serializing_if = "is_true")] pub dispensable: bool, + #[serde(skip_serializing_if = "is_true")] pub swappable: bool, + #[serde(skip_serializing_if = "is_true")] pub damage_on_hurt: bool, + #[serde(skip_serializing_if = "is_default")] pub equip_on_interact: bool, + #[serde(skip_serializing_if = "is_default")] pub can_be_sheared: bool, + #[serde(skip_serializing_if = "is_default_shearing_sound")] pub shearing_sound: SoundEvent, } -impl DataComponent for Equippable { - const KIND: DataComponentKind = DataComponentKind::Equippable; +fn is_default_equip_sound(sound: &SoundEvent) -> bool { + matches!(sound, SoundEvent::ItemArmorEquipGeneric) } +fn is_default_shearing_sound(sound: &SoundEvent) -> bool { + matches!(sound, SoundEvent::ItemShearsSnip) +} + impl Equippable { pub const fn new() -> Self { Self { @@ -1073,7 +1109,8 @@ impl Default for Equippable { } } -#[derive(Clone, Copy, Debug, PartialEq, AzBuf)] +#[derive(Clone, Copy, Debug, PartialEq, AzBuf, Serialize)] +#[serde(rename_all = "snake_case")] pub enum EquipmentSlot { Mainhand, Offhand, @@ -1085,37 +1122,32 @@ pub enum EquipmentSlot { Saddle, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Glider; -impl DataComponent for Glider { - const KIND: DataComponentKind = DataComponentKind::Glider; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct TooltipStyle { pub resource_location: ResourceLocation, } -impl DataComponent for TooltipStyle { - const KIND: DataComponentKind = DataComponentKind::TooltipStyle; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct DeathProtection { pub death_effects: Vec<ConsumeEffect>, } -impl DataComponent for DeathProtection { - const KIND: DataComponentKind = DataComponentKind::DeathProtection; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct Weapon { #[var] + #[serde(skip_serializing_if = "is_default_item_damage_per_attack")] pub item_damage_per_attack: i32, + #[serde(skip_serializing_if = "is_default")] pub disable_blocking_for_seconds: f32, } -impl DataComponent for Weapon { - const KIND: DataComponentKind = DataComponentKind::Weapon; +fn is_default_item_damage_per_attack(value: &i32) -> bool { + *value == 1 } + impl Weapon { pub const fn new() -> Self { Self { @@ -1130,65 +1162,52 @@ impl Default for Weapon { } } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct PotionDurationScale { pub value: f32, } -impl DataComponent for PotionDurationScale { - const KIND: DataComponentKind = DataComponentKind::PotionDurationScale; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct VillagerVariant { pub variant: registry::VillagerKind, } -impl DataComponent for VillagerVariant { - const KIND: DataComponentKind = DataComponentKind::VillagerVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct WolfVariant { pub variant: registry::WolfVariant, } -impl DataComponent for WolfVariant { - const KIND: DataComponentKind = DataComponentKind::WolfVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct WolfCollar { pub color: DyeColor, } -impl DataComponent for WolfCollar { - const KIND: DataComponentKind = DataComponentKind::WolfCollar; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct FoxVariant { pub variant: registry::FoxVariant, } -impl DataComponent for FoxVariant { - const KIND: DataComponentKind = DataComponentKind::FoxVariant; -} -#[derive(Clone, Copy, PartialEq, AzBuf)] +#[derive(Clone, Copy, PartialEq, AzBuf, Debug, Serialize)] +#[serde(rename_all = "snake_case")] pub enum SalmonSize { Small, Medium, Large, } -impl DataComponent for SalmonSize { - const KIND: DataComponentKind = DataComponentKind::SalmonSize; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct ParrotVariant { pub variant: registry::ParrotVariant, } -impl DataComponent for ParrotVariant { - const KIND: DataComponentKind = DataComponentKind::ParrotVariant; -} -#[derive(Clone, Copy, PartialEq, AzBuf)] +#[derive(Clone, Copy, PartialEq, AzBuf, Debug, Serialize)] +#[serde(rename_all = "snake_case")] pub enum TropicalFishPattern { Kob, Sunstreak, @@ -1203,140 +1222,112 @@ pub enum TropicalFishPattern { Betty, Clayfish, } -impl DataComponent for TropicalFishPattern { - const KIND: DataComponentKind = DataComponentKind::TropicalFishPattern; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct TropicalFishBaseColor { pub color: DyeColor, } -impl DataComponent for TropicalFishBaseColor { - const KIND: DataComponentKind = DataComponentKind::TropicalFishBaseColor; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct TropicalFishPatternColor { pub color: DyeColor, } -impl DataComponent for TropicalFishPatternColor { - const KIND: DataComponentKind = DataComponentKind::TropicalFishPatternColor; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct MooshroomVariant { pub variant: registry::MooshroomVariant, } -impl DataComponent for MooshroomVariant { - const KIND: DataComponentKind = DataComponentKind::MooshroomVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct RabbitVariant { pub variant: registry::RabbitVariant, } -impl DataComponent for RabbitVariant { - const KIND: DataComponentKind = DataComponentKind::RabbitVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct PigVariant { pub variant: registry::PigVariant, } -impl DataComponent for PigVariant { - const KIND: DataComponentKind = DataComponentKind::PigVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct FrogVariant { pub variant: registry::FrogVariant, } -impl DataComponent for FrogVariant { - const KIND: DataComponentKind = DataComponentKind::FrogVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct HorseVariant { pub variant: registry::HorseVariant, } -impl DataComponent for HorseVariant { - const KIND: DataComponentKind = DataComponentKind::HorseVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct PaintingVariant { pub variant: Holder<registry::PaintingVariant, PaintingVariantData>, } -impl DataComponent for PaintingVariant { - const KIND: DataComponentKind = DataComponentKind::PaintingVariant; -} -#[derive(Clone, PartialEq, AzBuf)] + +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct PaintingVariantData { #[var] pub width: i32, #[var] pub height: i32, pub asset_id: ResourceLocation, + #[serde(skip_serializing_if = "is_default")] pub title: Option<FormattedText>, + #[serde(skip_serializing_if = "is_default")] pub author: Option<FormattedText>, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct LlamaVariant { pub variant: registry::LlamaVariant, } -impl DataComponent for LlamaVariant { - const KIND: DataComponentKind = DataComponentKind::LlamaVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct AxolotlVariant { pub variant: registry::AxolotlVariant, } -impl DataComponent for AxolotlVariant { - const KIND: DataComponentKind = DataComponentKind::AxolotlVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct CatVariant { pub variant: registry::CatVariant, } -impl DataComponent for CatVariant { - const KIND: DataComponentKind = DataComponentKind::CatVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct CatCollar { pub color: DyeColor, } -impl DataComponent for CatCollar { - const KIND: DataComponentKind = DataComponentKind::CatCollar; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct SheepColor { pub color: DyeColor, } -impl DataComponent for SheepColor { - const KIND: DataComponentKind = DataComponentKind::SheepColor; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct ShulkerColor { pub color: DyeColor, } -impl DataComponent for ShulkerColor { - const KIND: DataComponentKind = DataComponentKind::ShulkerColor; -} -#[derive(Clone, PartialEq, AzBuf, Default)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct TooltipDisplay { + #[serde(skip_serializing_if = "is_default")] pub hide_tooltip: bool, + #[serde(skip_serializing_if = "is_default")] pub hidden_components: Vec<DataComponentKind>, } -impl DataComponent for TooltipDisplay { - const KIND: DataComponentKind = DataComponentKind::TooltipDisplay; -} + impl TooltipDisplay { pub const fn new() -> Self { Self { @@ -1345,20 +1336,33 @@ impl TooltipDisplay { } } } +impl Default for TooltipDisplay { + fn default() -> Self { + Self::new() + } +} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct BlocksAttacks { + #[serde(skip_serializing_if = "is_default")] pub block_delay_seconds: f32, + #[serde(skip_serializing_if = "is_default_disable_cooldown_scale")] pub disable_cooldown_scale: f32, + #[serde(skip_serializing_if = "is_default")] pub damage_reductions: Vec<DamageReduction>, + #[serde(skip_serializing_if = "is_default")] pub item_damage: ItemDamageFunction, + #[serde(skip_serializing_if = "is_default")] pub bypassed_by: Option<ResourceLocation>, + #[serde(skip_serializing_if = "is_default")] pub block_sound: Option<azalea_registry::Holder<SoundEvent, CustomSound>>, + #[serde(skip_serializing_if = "is_default")] pub disabled_sound: Option<azalea_registry::Holder<SoundEvent, CustomSound>>, } -impl DataComponent for BlocksAttacks { - const KIND: DataComponentKind = DataComponentKind::BlocksAttacks; +fn is_default_disable_cooldown_scale(value: &f32) -> bool { + *value == 1. } + impl BlocksAttacks { pub fn new() -> Self { Self { @@ -1370,11 +1374,7 @@ impl BlocksAttacks { base: 0., factor: 1., }], - item_damage: ItemDamageFunction { - threshold: 1., - base: 0., - factor: 1., - }, + item_damage: ItemDamageFunction::default(), bypassed_by: None, block_sound: None, disabled_sound: None, @@ -1387,86 +1387,90 @@ impl Default for BlocksAttacks { } } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct DamageReduction { + #[serde(skip_serializing_if = "is_default_horizontal_blocking_angle")] pub horizontal_blocking_angle: f32, + #[serde(skip_serializing_if = "is_default")] pub kind: Option<HolderSet<DamageKind, ResourceLocation>>, pub base: f32, pub factor: f32, } -#[derive(Clone, PartialEq, AzBuf)] +fn is_default_horizontal_blocking_angle(value: &f32) -> bool { + *value == 90. +} +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct ItemDamageFunction { pub threshold: f32, pub base: f32, pub factor: f32, } +impl Default for ItemDamageFunction { + fn default() -> Self { + ItemDamageFunction { + threshold: 1., + base: 0., + factor: 1., + } + } +} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(untagged)] pub enum ProvidesTrimMaterial { Referenced(ResourceLocation), Direct(Holder<TrimMaterial, DirectTrimMaterial>), } -impl DataComponent for ProvidesTrimMaterial { - const KIND: DataComponentKind = DataComponentKind::ProvidesTrimMaterial; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct DirectTrimMaterial { pub assets: MaterialAssetGroup, pub description: FormattedText, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct MaterialAssetGroup { pub base: AssetInfo, + #[serde(skip_serializing_if = "is_default")] pub overrides: Vec<(ResourceLocation, AssetInfo)>, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct AssetInfo { pub suffix: String, } -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct ProvidesBannerPatterns { pub key: ResourceLocation, } -impl DataComponent for ProvidesBannerPatterns { - const KIND: DataComponentKind = DataComponentKind::ProvidesBannerPatterns; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct BreakSound { pub sound: azalea_registry::Holder<SoundEvent, CustomSound>, } -impl DataComponent for BreakSound { - const KIND: DataComponentKind = DataComponentKind::BreakSound; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct WolfSoundVariant { pub variant: azalea_registry::WolfSoundVariant, } -impl DataComponent for WolfSoundVariant { - const KIND: DataComponentKind = DataComponentKind::WolfSoundVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(transparent)] pub struct CowVariant { pub variant: azalea_registry::CowVariant, } -impl DataComponent for CowVariant { - const KIND: DataComponentKind = DataComponentKind::CowVariant; -} -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] +#[serde(untagged)] pub enum ChickenVariant { Referenced(ResourceLocation), Direct(ChickenVariantData), } -impl DataComponent for ChickenVariant { - const KIND: DataComponentKind = DataComponentKind::ChickenVariant; -} -#[derive(Clone, PartialEq, AzBuf)] + +#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)] pub struct ChickenVariantData { pub registry: azalea_registry::ChickenVariant, } diff --git a/azalea-inventory/src/default_components/generated.rs b/azalea-inventory/src/default_components/generated.rs index c62a43dd..6b948ec3 100644 --- a/azalea-inventory/src/default_components/generated.rs +++ b/azalea-inventory/src/default_components/generated.rs @@ -20,7 +20,7 @@ impl DefaultableComponent for AttributeModifiers { Item::CarvedPumpkin => vec![AttributeModifiersEntry { display: AttributeModifierDisplay::Hidden, slot: EquipmentSlotGroup::Head, - attribute: Attribute::WaypointTransmitRange, + kind: Attribute::WaypointTransmitRange, modifier: AttributeModifier { id: "minecraft:waypoint_transmit_range_hide".into(), amount: -1.0, @@ -30,7 +30,7 @@ impl DefaultableComponent for AttributeModifiers { Item::ChainmailBoots => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -40,7 +40,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -52,7 +52,7 @@ impl DefaultableComponent for AttributeModifiers { Item::ChainmailChestplate => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -62,7 +62,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -74,7 +74,7 @@ impl DefaultableComponent for AttributeModifiers { Item::ChainmailHelmet => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -84,7 +84,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -96,7 +96,7 @@ impl DefaultableComponent for AttributeModifiers { Item::ChainmailLeggings => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -106,7 +106,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -118,7 +118,7 @@ impl DefaultableComponent for AttributeModifiers { Item::CreeperHead => vec![AttributeModifiersEntry { display: AttributeModifierDisplay::Hidden, slot: EquipmentSlotGroup::Head, - attribute: Attribute::WaypointTransmitRange, + kind: Attribute::WaypointTransmitRange, modifier: AttributeModifier { id: "minecraft:waypoint_transmit_range_hide".into(), amount: -1.0, @@ -128,7 +128,7 @@ impl DefaultableComponent for AttributeModifiers { Item::DiamondAxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -138,7 +138,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -150,7 +150,7 @@ impl DefaultableComponent for AttributeModifiers { Item::DiamondBoots => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -160,7 +160,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -172,7 +172,7 @@ impl DefaultableComponent for AttributeModifiers { Item::DiamondChestplate => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -182,7 +182,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -194,7 +194,7 @@ impl DefaultableComponent for AttributeModifiers { Item::DiamondHelmet => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -204,7 +204,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -216,7 +216,7 @@ impl DefaultableComponent for AttributeModifiers { Item::DiamondHoe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -226,7 +226,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -238,7 +238,7 @@ impl DefaultableComponent for AttributeModifiers { Item::DiamondHorseArmor => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Body, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.body".into(), @@ -248,7 +248,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Body, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.body".into(), @@ -260,7 +260,7 @@ impl DefaultableComponent for AttributeModifiers { Item::DiamondLeggings => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -270,7 +270,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -282,7 +282,7 @@ impl DefaultableComponent for AttributeModifiers { Item::DiamondPickaxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -292,7 +292,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -304,7 +304,7 @@ impl DefaultableComponent for AttributeModifiers { Item::DiamondShovel => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -314,7 +314,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -326,7 +326,7 @@ impl DefaultableComponent for AttributeModifiers { Item::DiamondSword => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -336,7 +336,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -348,7 +348,7 @@ impl DefaultableComponent for AttributeModifiers { Item::DragonHead => vec![AttributeModifiersEntry { display: AttributeModifierDisplay::Hidden, slot: EquipmentSlotGroup::Head, - attribute: Attribute::WaypointTransmitRange, + kind: Attribute::WaypointTransmitRange, modifier: AttributeModifier { id: "minecraft:waypoint_transmit_range_hide".into(), amount: -1.0, @@ -358,7 +358,7 @@ impl DefaultableComponent for AttributeModifiers { Item::GoldenAxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -368,7 +368,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -380,7 +380,7 @@ impl DefaultableComponent for AttributeModifiers { Item::GoldenBoots => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -390,7 +390,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -402,7 +402,7 @@ impl DefaultableComponent for AttributeModifiers { Item::GoldenChestplate => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -412,7 +412,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -424,7 +424,7 @@ impl DefaultableComponent for AttributeModifiers { Item::GoldenHelmet => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -434,7 +434,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -446,7 +446,7 @@ impl DefaultableComponent for AttributeModifiers { Item::GoldenHoe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -456,7 +456,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -468,7 +468,7 @@ impl DefaultableComponent for AttributeModifiers { Item::GoldenHorseArmor => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Body, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.body".into(), @@ -478,7 +478,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Body, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.body".into(), @@ -490,7 +490,7 @@ impl DefaultableComponent for AttributeModifiers { Item::GoldenLeggings => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -500,7 +500,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -512,7 +512,7 @@ impl DefaultableComponent for AttributeModifiers { Item::GoldenPickaxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -522,7 +522,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -534,7 +534,7 @@ impl DefaultableComponent for AttributeModifiers { Item::GoldenShovel => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -544,7 +544,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -556,7 +556,7 @@ impl DefaultableComponent for AttributeModifiers { Item::GoldenSword => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -566,7 +566,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -578,7 +578,7 @@ impl DefaultableComponent for AttributeModifiers { Item::IronAxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -588,7 +588,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -600,7 +600,7 @@ impl DefaultableComponent for AttributeModifiers { Item::IronBoots => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -610,7 +610,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -622,7 +622,7 @@ impl DefaultableComponent for AttributeModifiers { Item::IronChestplate => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -632,7 +632,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -644,7 +644,7 @@ impl DefaultableComponent for AttributeModifiers { Item::IronHelmet => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -654,7 +654,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -666,7 +666,7 @@ impl DefaultableComponent for AttributeModifiers { Item::IronHoe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -676,7 +676,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -688,7 +688,7 @@ impl DefaultableComponent for AttributeModifiers { Item::IronHorseArmor => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Body, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.body".into(), @@ -698,7 +698,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Body, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.body".into(), @@ -710,7 +710,7 @@ impl DefaultableComponent for AttributeModifiers { Item::IronLeggings => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -720,7 +720,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -732,7 +732,7 @@ impl DefaultableComponent for AttributeModifiers { Item::IronPickaxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -742,7 +742,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -754,7 +754,7 @@ impl DefaultableComponent for AttributeModifiers { Item::IronShovel => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -764,7 +764,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -776,7 +776,7 @@ impl DefaultableComponent for AttributeModifiers { Item::IronSword => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -786,7 +786,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -798,7 +798,7 @@ impl DefaultableComponent for AttributeModifiers { Item::LeatherBoots => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -808,7 +808,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -820,7 +820,7 @@ impl DefaultableComponent for AttributeModifiers { Item::LeatherChestplate => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -830,7 +830,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -842,7 +842,7 @@ impl DefaultableComponent for AttributeModifiers { Item::LeatherHelmet => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -852,7 +852,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -864,7 +864,7 @@ impl DefaultableComponent for AttributeModifiers { Item::LeatherHorseArmor => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Body, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.body".into(), @@ -874,7 +874,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Body, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.body".into(), @@ -886,7 +886,7 @@ impl DefaultableComponent for AttributeModifiers { Item::LeatherLeggings => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -896,7 +896,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -908,7 +908,7 @@ impl DefaultableComponent for AttributeModifiers { Item::Mace => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -918,7 +918,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -930,7 +930,7 @@ impl DefaultableComponent for AttributeModifiers { Item::NetheriteAxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -940,7 +940,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -952,7 +952,7 @@ impl DefaultableComponent for AttributeModifiers { Item::NetheriteBoots => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -962,7 +962,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -972,7 +972,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Feet, - attribute: Attribute::KnockbackResistance, + kind: Attribute::KnockbackResistance, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.boots".into(), @@ -984,7 +984,7 @@ impl DefaultableComponent for AttributeModifiers { Item::NetheriteChestplate => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -994,7 +994,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -1004,7 +1004,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Chest, - attribute: Attribute::KnockbackResistance, + kind: Attribute::KnockbackResistance, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.chestplate".into(), @@ -1016,7 +1016,7 @@ impl DefaultableComponent for AttributeModifiers { Item::NetheriteHelmet => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -1026,7 +1026,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -1036,7 +1036,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::KnockbackResistance, + kind: Attribute::KnockbackResistance, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -1048,7 +1048,7 @@ impl DefaultableComponent for AttributeModifiers { Item::NetheriteHoe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1058,7 +1058,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1070,7 +1070,7 @@ impl DefaultableComponent for AttributeModifiers { Item::NetheriteLeggings => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -1080,7 +1080,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -1090,7 +1090,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Legs, - attribute: Attribute::KnockbackResistance, + kind: Attribute::KnockbackResistance, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.leggings".into(), @@ -1102,7 +1102,7 @@ impl DefaultableComponent for AttributeModifiers { Item::NetheritePickaxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1112,7 +1112,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1124,7 +1124,7 @@ impl DefaultableComponent for AttributeModifiers { Item::NetheriteShovel => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1134,7 +1134,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1146,7 +1146,7 @@ impl DefaultableComponent for AttributeModifiers { Item::NetheriteSword => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1156,7 +1156,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1168,7 +1168,7 @@ impl DefaultableComponent for AttributeModifiers { Item::PiglinHead => vec![AttributeModifiersEntry { display: AttributeModifierDisplay::Hidden, slot: EquipmentSlotGroup::Head, - attribute: Attribute::WaypointTransmitRange, + kind: Attribute::WaypointTransmitRange, modifier: AttributeModifier { id: "minecraft:waypoint_transmit_range_hide".into(), amount: -1.0, @@ -1178,7 +1178,7 @@ impl DefaultableComponent for AttributeModifiers { Item::PlayerHead => vec![AttributeModifiersEntry { display: AttributeModifierDisplay::Hidden, slot: EquipmentSlotGroup::Head, - attribute: Attribute::WaypointTransmitRange, + kind: Attribute::WaypointTransmitRange, modifier: AttributeModifier { id: "minecraft:waypoint_transmit_range_hide".into(), amount: -1.0, @@ -1188,7 +1188,7 @@ impl DefaultableComponent for AttributeModifiers { Item::SkeletonSkull => vec![AttributeModifiersEntry { display: AttributeModifierDisplay::Hidden, slot: EquipmentSlotGroup::Head, - attribute: Attribute::WaypointTransmitRange, + kind: Attribute::WaypointTransmitRange, modifier: AttributeModifier { id: "minecraft:waypoint_transmit_range_hide".into(), amount: -1.0, @@ -1198,7 +1198,7 @@ impl DefaultableComponent for AttributeModifiers { Item::StoneAxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1208,7 +1208,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1220,7 +1220,7 @@ impl DefaultableComponent for AttributeModifiers { Item::StoneHoe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1230,7 +1230,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1242,7 +1242,7 @@ impl DefaultableComponent for AttributeModifiers { Item::StonePickaxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1252,7 +1252,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1264,7 +1264,7 @@ impl DefaultableComponent for AttributeModifiers { Item::StoneShovel => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1274,7 +1274,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1286,7 +1286,7 @@ impl DefaultableComponent for AttributeModifiers { Item::StoneSword => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1296,7 +1296,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1308,7 +1308,7 @@ impl DefaultableComponent for AttributeModifiers { Item::Trident => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1318,7 +1318,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1330,7 +1330,7 @@ impl DefaultableComponent for AttributeModifiers { Item::TurtleHelmet => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -1340,7 +1340,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Head, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.helmet".into(), @@ -1352,7 +1352,7 @@ impl DefaultableComponent for AttributeModifiers { Item::WitherSkeletonSkull => vec![AttributeModifiersEntry { display: AttributeModifierDisplay::Hidden, slot: EquipmentSlotGroup::Head, - attribute: Attribute::WaypointTransmitRange, + kind: Attribute::WaypointTransmitRange, modifier: AttributeModifier { id: "minecraft:waypoint_transmit_range_hide".into(), amount: -1.0, @@ -1362,7 +1362,7 @@ impl DefaultableComponent for AttributeModifiers { Item::WolfArmor => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Body, - attribute: Attribute::Armor, + kind: Attribute::Armor, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.body".into(), @@ -1372,7 +1372,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Body, - attribute: Attribute::ArmorToughness, + kind: Attribute::ArmorToughness, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:armor.body".into(), @@ -1384,7 +1384,7 @@ impl DefaultableComponent for AttributeModifiers { Item::WoodenAxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1394,7 +1394,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1406,7 +1406,7 @@ impl DefaultableComponent for AttributeModifiers { Item::WoodenHoe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1416,7 +1416,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1428,7 +1428,7 @@ impl DefaultableComponent for AttributeModifiers { Item::WoodenPickaxe => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1438,7 +1438,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1450,7 +1450,7 @@ impl DefaultableComponent for AttributeModifiers { Item::WoodenShovel => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1460,7 +1460,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1472,7 +1472,7 @@ impl DefaultableComponent for AttributeModifiers { Item::WoodenSword => vec![ AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackDamage, + kind: Attribute::AttackDamage, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_damage".into(), @@ -1482,7 +1482,7 @@ impl DefaultableComponent for AttributeModifiers { }, AttributeModifiersEntry { slot: EquipmentSlotGroup::Mainhand, - attribute: Attribute::AttackSpeed, + kind: Attribute::AttackSpeed, display: AttributeModifierDisplay::Default, modifier: AttributeModifier { id: "minecraft:base_attack_speed".into(), @@ -1494,7 +1494,7 @@ impl DefaultableComponent for AttributeModifiers { Item::ZombieHead => vec![AttributeModifiersEntry { display: AttributeModifierDisplay::Hidden, slot: EquipmentSlotGroup::Head, - attribute: Attribute::WaypointTransmitRange, + kind: Attribute::WaypointTransmitRange, modifier: AttributeModifier { id: "minecraft:waypoint_transmit_range_hide".into(), amount: -1.0, @@ -3691,7 +3691,7 @@ impl DefaultableComponent for EnchantmentGlintOverride { impl DefaultableComponent for PotDecorations { fn default_for_item(item: Item) -> Option<Self> { let value = match item { - Item::DecoratedPot => vec![Item::Brick, Item::Brick, Item::Brick, Item::Brick], + Item::DecoratedPot => [Item::Brick, Item::Brick, Item::Brick, Item::Brick], _ => return None, }; Some(PotDecorations { items: value }) diff --git a/azalea-inventory/src/default_components/mod.rs b/azalea-inventory/src/default_components/mod.rs index e6150066..7f9ef6fb 100644 --- a/azalea-inventory/src/default_components/mod.rs +++ b/azalea-inventory/src/default_components/mod.rs @@ -2,15 +2,15 @@ pub mod generated; use azalea_registry::Item; -use crate::components::DataComponent; +use crate::components::DataComponentTrait; /// A [`DataComponent`] that some [`Item`]s may have a default value for. -pub trait DefaultableComponent: DataComponent { +pub trait DefaultableComponent: DataComponentTrait { fn default_for_item(item: Item) -> Option<Self> where Self: Sized; } -impl<T: DataComponent> DefaultableComponent for T { +impl<T: DataComponentTrait> DefaultableComponent for T { default fn default_for_item(_item: Item) -> Option<Self> { None } diff --git a/azalea-inventory/src/item/consume_effect.rs b/azalea-inventory/src/item/consume_effect.rs index b9540955..8aab42b8 100644 --- a/azalea-inventory/src/item/consume_effect.rs +++ b/azalea-inventory/src/item/consume_effect.rs @@ -1,20 +1,26 @@ use azalea_buf::AzBuf; -use azalea_core::resource_location::ResourceLocation; +use azalea_core::{codec_utils::is_default, resource_location::ResourceLocation}; use azalea_registry::{HolderSet, MobEffect, SoundEvent}; +use serde::Serialize; use crate::components::MobEffectInstance; -#[derive(Clone, PartialEq, AzBuf)] +#[derive(Clone, PartialEq, Debug, AzBuf, Serialize)] +#[serde(rename_all = "snake_case", tag = "type")] pub enum ConsumeEffect { ApplyEffects { + #[serde(skip_serializing_if = "is_default")] effects: Vec<MobEffectInstance>, + #[serde(skip_serializing_if = "is_default")] probability: f32, }, RemoveEffects { + #[serde(skip_serializing_if = "is_default")] effects: HolderSet<MobEffect, ResourceLocation>, }, ClearAllEffects, TeleportRandomly { + #[serde(skip_serializing_if = "is_default")] diameter: f32, }, PlaySound { diff --git a/azalea-inventory/src/slot.rs b/azalea-inventory/src/slot.rs index 57f57696..444e0b2b 100644 --- a/azalea-inventory/src/slot.rs +++ b/azalea-inventory/src/slot.rs @@ -6,11 +6,13 @@ use std::{ }; use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError}; +use azalea_core::codec_utils::is_default; use azalea_registry::{DataComponentKind, Item}; use indexmap::IndexMap; +use serde::{Serialize, ser::SerializeMap}; use crate::{ - components::{self}, + components::{self, DataComponentUnion}, default_components::get_default_component, }; @@ -114,22 +116,49 @@ impl ItemStack { /// /// This is used for things like getting the damage of an item, or seeing /// how much food it replenishes. - pub fn get_component<'a, T: components::DataComponent>(&'a self) -> Option<Cow<'a, T>> { + pub fn get_component<'a, T: components::DataComponentTrait>(&'a self) -> Option<Cow<'a, T>> { self.as_present().and_then(|i| i.get_component::<T>()) } + + pub fn with_component< + T: components::EncodableDataComponent + components::DataComponentTrait, + >( + mut self, + component: impl Into<Option<T>>, + ) -> Self { + if let ItemStack::Present(i) = &mut self { + let component: Option<T> = component.into(); + let component: Option<DataComponentUnion> = component.map(|c| c.into()); + i.component_patch.components.insert(T::KIND, component); + } + self + } +} +impl Serialize for ItemStack { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + match self { + ItemStack::Empty => serializer.serialize_unit(), + ItemStack::Present(i) => i.serialize(serializer), + } + } } /// An item in an inventory, with a count and NBT. Usually you want /// [`ItemStack`] or [`azalea_registry::Item`] instead. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize)] pub struct ItemStackData { + #[serde(rename = "id")] + pub kind: azalea_registry::Item, /// The amount of the item in this slot. /// /// The count can be zero or negative, but this is rare. pub count: i32, - pub kind: azalea_registry::Item, /// The item's components that the server set to be different from the /// defaults. + #[serde(rename = "components", skip_serializing_if = "is_default")] pub component_patch: DataComponentPatch, } @@ -177,7 +206,7 @@ impl ItemStackData { /// /// This is used for things like getting the damage of an item, or seeing /// how much food it replenishes. - pub fn get_component<'a, T: components::DataComponent>(&'a self) -> Option<Cow<'a, T>> { + pub fn get_component<'a, T: components::DataComponentTrait>(&'a self) -> Option<Cow<'a, T>> { if let Some(c) = self.component_patch.get::<T>() { Some(Cow::Borrowed(c)) } else { @@ -253,8 +282,7 @@ impl From<(Item, i32)> for ItemStackData { /// and Azalea does not implement that yet. #[derive(Default)] pub struct DataComponentPatch { - pub components: - IndexMap<DataComponentKind, Option<Box<dyn components::EncodableDataComponent>>>, + components: IndexMap<DataComponentKind, Option<DataComponentUnion>>, } impl DataComponentPatch { @@ -269,8 +297,8 @@ impl DataComponentPatch { /// # Some(()) /// # } /// ``` - pub fn get<T: components::DataComponent>(&self) -> Option<&T> { - let component = self.components.get(&T::KIND).and_then(|c| c.as_deref())?; + pub fn get<T: components::DataComponentTrait>(&self) -> Option<&T> { + let component = self.components.get(&T::KIND)?; let component_any = component as &dyn Any; component_any.downcast_ref::<T>() } @@ -279,7 +307,13 @@ impl DataComponentPatch { &self, kind: DataComponentKind, ) -> Option<&dyn components::EncodableDataComponent> { - self.components.get(&kind).and_then(|c| c.as_deref()) + self.components.get(&kind).and_then(|c| { + c.as_ref().map(|c| { + // SAFETY: we just got the component from the map, so it must be the correct + // kind + unsafe { c.as_kind(kind) } + }) + }) } /// Returns whether the component in the generic argument is present for @@ -292,13 +326,41 @@ impl DataComponentPatch { /// let is_edible = item.component_patch.has::<components::Food>(); /// # assert!(!is_edible); /// ``` - pub fn has<T: components::DataComponent>(&self) -> bool { + pub fn has<T: components::DataComponentTrait>(&self) -> bool { self.has_kind(T::KIND) } pub fn has_kind(&self, kind: DataComponentKind) -> bool { self.get_kind(kind).is_some() } + + pub fn iter<'a>( + &'a self, + ) -> impl Iterator< + Item = ( + DataComponentKind, + Option<&'a dyn components::EncodableDataComponent>, + ), + > + 'a { + self.components.iter().map(|(&kind, component)| { + component.as_ref().map_or_else( + || (kind, None), + |c| (kind, unsafe { Some(c.as_kind(kind)) }), + ) + }) + } +} + +impl Drop for DataComponentPatch { + fn drop(&mut self) { + // the component values are ManuallyDrop since they're in a union + for (kind, component) in &mut self.components { + if let Some(component) = component { + // SAFETY: we got the kind and component from the map + unsafe { component.drop_as(*kind) }; + } + } + } } impl AzaleaRead for DataComponentPatch { @@ -313,7 +375,7 @@ impl AzaleaRead for DataComponentPatch { let mut components = IndexMap::new(); for _ in 0..components_with_data_count { let component_kind = DataComponentKind::azalea_read(buf)?; - let component_data = components::from_kind(component_kind, buf)?; + let component_data = DataComponentUnion::azalea_read_as(component_kind, buf)?; components.insert(component_kind, Some(component_data)); } @@ -347,7 +409,8 @@ impl AzaleaWrite for DataComponentPatch { kind.azalea_write(buf)?; component_buf.clear(); - component.encode(&mut component_buf)?; + // SAFETY: we got the component from the map and are passing in the same kind + unsafe { component.azalea_write_as(*kind, &mut component_buf) }?; buf.write_all(&component_buf)?; } } @@ -366,7 +429,10 @@ impl Clone for DataComponentPatch { fn clone(&self) -> Self { let mut components = IndexMap::with_capacity(self.components.len()); for (kind, component) in &self.components { - components.insert(*kind, component.as_ref().map(|c| (*c).clone())); + components.insert( + *kind, + component.as_ref().map(|c| unsafe { c.clone_as(*kind) }), + ); } DataComponentPatch { components } } @@ -390,7 +456,9 @@ impl PartialEq for DataComponentPatch { let Some(other_component) = other_component else { return false; }; - if !component.eq((*other_component).clone()) { + // SAFETY: we already checked that the kinds are the same, and we got the + // components from the map, so they must be the correct kinds + if !unsafe { component.eq_as(other_component, *kind) } { return false; } } else if other_component.is_some() { @@ -400,3 +468,22 @@ impl PartialEq for DataComponentPatch { true } } + +impl Serialize for DataComponentPatch { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + let mut s = serializer.serialize_map(Some(self.components.len()))?; + for (kind, component) in &self.components { + if let Some(component) = component { + unsafe { component.serialize_entry_as(&mut s, *kind) }?; + } else { + #[derive(Serialize)] + struct EmptyComponent; + s.serialize_entry(&format!("!{kind}"), &EmptyComponent)?; + } + } + s.end() + } +} diff --git a/azalea-inventory/tests/components.rs b/azalea-inventory/tests/components.rs new file mode 100644 index 00000000..6c20436c --- /dev/null +++ b/azalea-inventory/tests/components.rs @@ -0,0 +1,201 @@ +use std::collections::HashMap; + +use azalea_chat::{ + FormattedText, + style::{Style, TextColor}, + text_component::TextComponent, +}; +use azalea_core::{ + checksum::get_checksum, + position::{BlockPos, GlobalPos}, + registry_holder::RegistryHolder, +}; +use azalea_inventory::{ + ItemStack, + components::{ + AdventureModePredicate, AttributeModifier, AttributeModifierDisplay, + AttributeModifierOperation, AttributeModifiers, AttributeModifiersEntry, BlockPredicate, + CanPlaceOn, ChargedProjectiles, CustomData, CustomName, Enchantments, EquipmentSlotGroup, + Glider, JukeboxPlayable, LodestoneTracker, Lore, MapColor, PotDecorations, Rarity, + }, +}; +use azalea_registry::{Attribute, Block, DataRegistry, Enchantment, Item}; +use simdnbt::owned::{BaseNbt, Nbt, NbtCompound, NbtList, NbtTag}; + +#[test] +fn test_custom_name_checksum() { + let c = CustomName { + name: FormattedText::from("meow"), + }; + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 2222287064); +} +#[test] +fn test_custom_name_checksum_2() { + let c = CustomName { + name: TextComponent::new("meow") + .with_style( + Style::new() + .color(Some(TextColor::parse("red").unwrap())) + .underlined(true), + ) + .into(), + }; + + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 187682122); +} +#[test] +fn test_map_color_checksum() { + let c = MapColor { color: 1 }; + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 1565579036); +} +#[test] +fn test_lore_checksum() { + let c = Lore { + lines: vec!["first".into(), "second".into()], + }; + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 1545409323); +} +#[test] +fn test_rarity_checksum() { + let c = Rarity::Rare; + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 2874400570); +} +#[test] +fn test_enchantments_checksum() { + let mut registry_holder = RegistryHolder::default(); + registry_holder.append( + "enchantment".into(), + vec![ + ("sharpness".into(), Some(NbtCompound::default())), + ("knockback".into(), Some(NbtCompound::default())), + ], + ); + let c = Enchantments { + levels: HashMap::from_iter([(Enchantment::new_raw(0), 5), (Enchantment::new_raw(1), 1)]), + }; + assert_eq!(get_checksum(&c, ®istry_holder).unwrap().0, 3717391112); +} +#[test] +fn test_can_place_on_checksum() { + let c = CanPlaceOn { + predicate: AdventureModePredicate { + predicates: vec![BlockPredicate { + blocks: Some(vec![Block::GrassBlock].into()), + properties: None, + nbt: None, + }], + }, + }; + + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 227436005); +} + +#[test] +fn test_custom_data_nbt() { + let c = CustomData { + nbt: Nbt::Some(BaseNbt::new( + "", + NbtCompound::from_values(vec![ + ("meow".into(), "mrrp".into()), + ( + "nya".into(), + NbtList::Compound(vec![ + NbtTag::Int(1).into(), + NbtTag::Int(2).into(), + NbtCompound::new(), + NbtCompound::from_values(vec![("data".into(), NbtTag::Byte(1))]), + ]) + .into(), + ), + ]), + )), + }; + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 1035780974); +} +#[test] +fn test_attribute_modifiers_checksum() { + let c = AttributeModifiers { + modifiers: vec![AttributeModifiersEntry { + kind: Attribute::Scale, + modifier: AttributeModifier { + id: "example:grow".into(), + amount: 4.0, + operation: AttributeModifierOperation::AddMultipliedBase, + }, + slot: EquipmentSlotGroup::Hand, + display: AttributeModifierDisplay::Default, + }], + }; + + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 2501379836); +} + +#[test] +fn test_firework_explosion_checksum() { + let c = AttributeModifiers { + modifiers: vec![AttributeModifiersEntry { + kind: Attribute::Scale, + modifier: AttributeModifier { + id: "example:grow".into(), + amount: 4.0, + operation: AttributeModifierOperation::AddMultipliedBase, + }, + slot: EquipmentSlotGroup::Hand, + display: AttributeModifierDisplay::Default, + }], + }; + + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 2501379836); +} + +#[test] +fn test_charged_projectile_checksum() { + let c = ChargedProjectiles { + items: vec![ItemStack::from(Item::MusicDiscCat)], + }; + + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 3435761017); +} + +#[test] +fn test_charged_projectile_with_components_checksum() { + let c = ChargedProjectiles { + items: vec![ + ItemStack::from(Item::MusicDiscCat) + .with_component::<JukeboxPlayable>(None) + .with_component(ChargedProjectiles { + items: vec![ItemStack::from(Item::MusicDiscCat)], + }), + ], + }; + + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 170375255); +} + +#[test] +fn test_lodestone_tracker_checksum() { + let c = LodestoneTracker { + target: Some(GlobalPos { + dimension: "meow".into(), + pos: BlockPos::new(1, 2, 3), + }), + tracked: true, + }; + + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 4138292505); +} + +#[test] +fn test_pot_decorations_checksum() { + let c = PotDecorations { + items: [Item::Stick, Item::Brick, Item::Brick, Item::Brick], + }; + + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 1951715383); +} + +#[test] +fn test_glider_checksum() { + let c = Glider; + assert_eq!(get_checksum(&c, &Default::default()).unwrap().0, 3312760008); +} diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index 92814e74..b3f161c9 100644 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -18,7 +18,7 @@ azalea-block.workspace = true azalea-brigadier = { workspace = true, features = ["azalea-buf"] } azalea-buf.workspace = true azalea-chat = { workspace = true, features = ["numbers", "azalea-buf"] } -azalea-core = { workspace = true, optional = true, features = ["serde"] } +azalea-core = { workspace = true, optional = true } azalea-crypto.workspace = true azalea-entity.workspace = true azalea-inventory.workspace = true @@ -41,10 +41,8 @@ tokio-util = { workspace = true, features = ["codec"] } tracing.workspace = true hickory-resolver = { workspace = true, features = ["tokio", "system-config"] } uuid.workspace = true -crc32fast = { workspace = true, optional = true } [features] connecting = [] default = ["packets"] -packets = ["connecting", "dep:azalea-core", "crc32"] -crc32 = ["dep:crc32fast"] +packets = ["connecting", "dep:azalea-core"] diff --git a/azalea-protocol/src/packets/game/s_container_click.rs b/azalea-protocol/src/packets/game/s_container_click.rs index 9c4696ed..a06e7c0c 100644 --- a/azalea-protocol/src/packets/game/s_container_click.rs +++ b/azalea-protocol/src/packets/game/s_container_click.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; -use azalea_buf::{AzBuf, AzaleaWrite}; +use azalea_buf::AzBuf; +use azalea_core::{checksum::Checksum, registry_holder::RegistryHolder}; use azalea_inventory::{ItemStack, operations::ClickType}; use azalea_protocol_macros::ServerboundGamePacket; @@ -35,34 +36,31 @@ pub struct HashedPatchMap { /// The value is a CRC32 hash of the data component's network serialization. /// (kind + data) #[limit(256)] - pub added_components: Vec<(azalea_registry::DataComponentKind, u32)>, + pub added_components: Vec<(azalea_registry::DataComponentKind, Checksum)>, #[limit(256)] pub removed_components: Vec<azalea_registry::DataComponentKind>, } -/// Convert your [`ItemStack`] into a [`HashedStack`] by hashing the data -/// components. -/// -/// This will be necessary if you're writing a client or server, but if you're -/// just making a proxy then you can remove the `crc32` dependency by disabling -/// the `crc32` feature on `azalea-protocol`. -#[cfg(feature = "crc32")] -impl From<&ItemStack> for HashedStack { - fn from(item: &ItemStack) -> Self { +impl HashedStack { + /// Convert your [`ItemStack`] into a [`HashedStack`] by hashing the data + /// components. + /// + /// Minecraft uses this whenever the client sends data components to the + /// server. + /// + /// The [`RegistryHolder`] is required as some components will hash + /// differently based on the server's registries. + pub fn from_item_stack(item: &ItemStack, registries: &RegistryHolder) -> Self { let ItemStack::Present(item) = item else { - return Self(None); + return HashedStack(None); }; let mut added_components = Vec::new(); let mut removed_components = Vec::new(); - for (&kind, data) in &item.component_patch.components { + for (kind, data) in item.component_patch.iter() { if let Some(data) = data { - // encodeCap in TypedDataComponent.java - let mut buf = Vec::new(); - kind.azalea_write(&mut buf).unwrap(); - data.encode(&mut buf).unwrap(); - added_components.push((kind, crc32fast::hash(&buf))); + added_components.push((kind, data.crc_hash(registries))); } else { removed_components.push(kind); } diff --git a/azalea-registry/azalea-registry-macros/src/lib.rs b/azalea-registry/azalea-registry-macros/src/lib.rs index e2f88251..748054db 100644 --- a/azalea-registry/azalea-registry-macros/src/lib.rs +++ b/azalea-registry/azalea-registry-macros/src/lib.rs @@ -81,7 +81,6 @@ pub fn registry(input: TokenStream) -> TokenStream { generated.extend(quote! { #(#attributes)* #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, azalea_buf::AzBuf, simdnbt::ToNbtTag, simdnbt::FromNbtTag)] - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[repr(u32)] pub enum #name { #enum_items @@ -171,6 +170,26 @@ pub fn registry(input: TokenStream) -> TokenStream { } } } + + #[cfg(feature = "serde")] + impl serde::Serialize for #name { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } + } + #[cfg(feature = "serde")] + impl<'de> serde::Deserialize<'de> for #name { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse().map_err(serde::de::Error::custom) + } + } }); generated.into() diff --git a/azalea-registry/src/data.rs b/azalea-registry/src/data.rs index a9f797b6..c1c1efe5 100644 --- a/azalea-registry/src/data.rs +++ b/azalea-registry/src/data.rs @@ -40,6 +40,17 @@ macro_rules! data_registry { Self { id } } } + + #[cfg(feature = "serde")] + impl serde::Serialize for $name { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + // see ChecksumSerializer::serialize_newtype_variant + serializer.serialize_newtype_variant(concat!("minecraft:", $registry_name), self.id, "", &()) + } + } }; } diff --git a/azalea-registry/src/lib.rs b/azalea-registry/src/lib.rs index 8ab12853..90f1ff55 100644 --- a/azalea-registry/src/lib.rs +++ b/azalea-registry/src/lib.rs @@ -18,6 +18,8 @@ use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufRead use azalea_registry_macros::registry; pub use data::*; pub use extra::*; +#[cfg(feature = "serde")] +use serde::Serialize; pub trait Registry: AzaleaRead + AzaleaWrite where @@ -125,7 +127,7 @@ impl<D: Registry, ResourceLocation: AzaleaRead + AzaleaWrite> AzaleaWrite item.azalea_write(buf)?; } } - Self::Named { key, .. } => { + Self::Named { key, contents: _ } => { 0i32.azalea_write_var(buf)?; key.azalea_write(buf)?; } @@ -147,6 +149,42 @@ impl<D: Registry + Debug, ResourceLocation: AzaleaRead + AzaleaWrite + Debug> De } } } +impl<D: Registry, ResourceLocation: AzaleaRead + AzaleaWrite> From<Vec<D>> + for HolderSet<D, ResourceLocation> +{ + fn from(contents: Vec<D>) -> Self { + Self::Direct { contents } + } +} +#[cfg(feature = "serde")] +impl<D: Registry + Serialize, ResourceLocation: AzaleaRead + AzaleaWrite + Serialize> Serialize + for HolderSet<D, ResourceLocation> +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + match self { + Self::Direct { contents } => { + if contents.len() == 1 { + contents[0].serialize(serializer) + } else { + contents.serialize(serializer) + } + } + Self::Named { key, contents: _ } => key.serialize(serializer), + } + } +} +impl<D: Registry, ResourceLocation: AzaleaRead + AzaleaWrite> Default + for HolderSet<D, ResourceLocation> +{ + fn default() -> Self { + Self::Direct { + contents: Vec::new(), + } + } +} /// A reference to either a registry or a custom value (usually something with a /// ResourceLocation). @@ -206,6 +244,25 @@ impl<R: Registry + PartialEq, Direct: AzaleaRead + AzaleaWrite + PartialEq> Part } } } +impl<R: Registry + Default, Direct: AzaleaRead + AzaleaWrite> Default for Holder<R, Direct> { + fn default() -> Self { + Self::Reference(R::default()) + } +} +#[cfg(feature = "serde")] +impl<R: Registry + Serialize, Direct: AzaleaRead + AzaleaWrite + Serialize> Serialize + for Holder<R, Direct> +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + match self { + Self::Reference(value) => value.serialize(serializer), + Self::Direct(value) => value.serialize(serializer), + } + } +} registry! { /// The AI code that's currently being executed for the entity. diff --git a/azalea/Cargo.toml b/azalea/Cargo.toml index a8f15d06..a80b68ed 100644 --- a/azalea/Cargo.toml +++ b/azalea/Cargo.toml @@ -56,12 +56,7 @@ anyhow.workspace = true default = ["log", "serde", "packet-event"] # enables bevy_log::LogPlugin by default log = ["azalea-client/log"] -serde = [ - "dep:serde", - "azalea-core/serde", - "azalea-registry/serde", - "azalea-world/serde", -] +serde = ["dep:serde", "azalea-registry/serde", "azalea-world/serde"] packet-event = ["azalea-client/packet-event"] [[bench]] diff --git a/codegen/lib/code/data_components.py b/codegen/lib/code/data_components.py index c6c88f12..d29096d2 100644 --- a/codegen/lib/code/data_components.py +++ b/codegen/lib/code/data_components.py @@ -65,17 +65,19 @@ def get_actual_variants(): with open(DATA_COMPONENTS_DIR, "r") as f: code = f.read().split("\n") - in_match = False + in_define_macro = False for line in code: - if in_match: - if line == " })": + if in_define_macro: + if line == ");": break - variant_line_prefix = " DataComponentKind::" - if line.startswith(variant_line_prefix): - variant = line[len(variant_line_prefix) :].split(" ", 1)[0] - actual_variants.append(variant) - elif line == " Ok(match kind {": - in_match = True + if line.startswith(" "): + variant_name = line.strip(" ,").split()[0] + if variant_name[0] in "#/": + # skip comments + continue + actual_variants.append(variant_name) + elif line == "define_data_components!(": + in_define_macro = True return actual_variants @@ -87,22 +89,24 @@ def remove_variant(variant: str): first_line_with_variant = None line_after_variant = None - in_match = False + in_define_macro = False for i, line in enumerate(list(code)): - if in_match: - if line == " })": + if in_define_macro: + if line == ");": line_after_variant = i break - variant_line_prefix = " DataComponentKind::" - if line.startswith(variant_line_prefix): + if line.startswith(" "): if first_line_with_variant is not None: line_after_variant = i break - variant_name = line[len(variant_line_prefix) :].split(" ", 1)[0] + variant_name = line.strip().split()[0].strip(",") + if variant_name[0] in "#/": + # skip comments + continue if variant_name == variant: first_line_with_variant = i - elif line == " Ok(match kind {": - in_match = True + elif line == "define_data_components!(": + in_define_macro = True if first_line_with_variant is None: raise ValueError(f"Variant {variant} not found") @@ -117,8 +121,8 @@ def remove_variant(variant: str): for i, line in enumerate(list(code)): if line == f"pub struct {variant} {{" or line == f"pub struct {variant};": line_before_struct = i - 1 - elif line == f"impl DataComponent for {variant} {{": - line_after_struct = i + 3 + elif line == "}": + line_after_struct = i + 1 break if line_before_struct is None: raise ValueError(f"Couldn't find struct {variant}") @@ -135,36 +139,31 @@ def add_variant(variant: str): with open(DATA_COMPONENTS_DIR, "r") as f: code = f.read().split("\n") - in_match = False - last_line_in_match = None + in_define_macro = False + last_line_in_define_macro = None for i, line in enumerate(list(code)): - if in_match: - if line == " })": - last_line_in_match = i + if in_define_macro: + if line == ");": + last_line_in_define_macro = i break - elif line == " Ok(match kind {": - in_match = True + elif line == "define_data_components!(": + in_define_macro = True - if last_line_in_match is None: + if last_line_in_define_macro is None: raise ValueError("Couldn't find end of match") code = ( - code[:last_line_in_match] - + [ - f" DataComponentKind::{variant} => Box::new({variant}::azalea_read(buf)?),", - ] - + code[last_line_in_match:] + code[:last_line_in_define_macro] + + [f" {variant},"] + + code[last_line_in_define_macro:] ) # now insert the struct code.append("") - code.append("#[derive(Clone, PartialEq, AzBuf)]") + code.append("#[derive(Clone, PartialEq, AzBuf, Debug, Serialize)]") code.append(f"pub struct {variant} {{") code.append(" pub todo: todo!(), // see DataComponents.java") code.append("}") - code.append(f"impl DataComponent for {variant} {{") - code.append(f" const KIND: DataComponentKind = DataComponentKind::{variant};") - code.append("}") with open(DATA_COMPONENTS_DIR, "w") as f: f.write("\n".join(code)) @@ -270,7 +269,7 @@ use crate::{ del python_value["amount"] del python_value["type"] - python_value["attribute"] = attribute + python_value["kind"] = attribute del python_value["id"] del python_value["operation"] if display_type is not None: @@ -399,6 +398,7 @@ use crate::{ return f"{target_rust_type}::{lib.utils.to_camel_case(python_value.split(':')[-1])}" if isinstance(python_value, list): # convert Vec<Thing> into Thing + main_vec = "vec![" inner_type = ( target_rust_type.split("<", 1)[1] .rsplit(">", 1)[0] @@ -407,6 +407,11 @@ use crate::{ if (target_rust_type and "<" in target_rust_type) else None ) + # convert [Thing; 2] into Thing + if target_rust_type.startswith("[") and target_rust_type.endswith("]"): + inner_type = target_rust_type.split(";")[0].strip("[]") + main_vec = "[" + if inner_type is None: # if the only field is a Vec, use that as the type rust_type_fields = enum_and_struct_fields.get(target_rust_type, {}) @@ -415,7 +420,6 @@ use crate::{ return python_to_rust_value(python_value, field_type) vectors = [] - main_vec = "vec