aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.lock23
-rw-r--r--Cargo.toml2
-rw-r--r--azalea-buf/Cargo.toml1
-rw-r--r--azalea-buf/azalea-buf-macros/src/read.rs10
-rw-r--r--azalea-buf/azalea-buf-macros/src/write.rs12
-rw-r--r--azalea-chat/src/base_component.rs12
-rw-r--r--azalea-chat/src/click_event.rs16
-rw-r--r--azalea-chat/src/component.rs6
-rw-r--r--azalea-chat/src/hover_event.rs20
-rw-r--r--azalea-chat/src/lib.rs2
-rw-r--r--azalea-chat/src/style.rs132
-rw-r--r--azalea-chat/src/text_component.rs18
-rw-r--r--azalea-chat/src/translatable_component.rs10
-rw-r--r--azalea-chat/tests/integration_test.rs2
-rw-r--r--azalea-client/src/plugins/inventory.rs25
-rw-r--r--azalea-core/Cargo.toml6
-rw-r--r--azalea-core/src/checksum.rs823
-rw-r--r--azalea-core/src/codec_utils.rs83
-rw-r--r--azalea-core/src/direction.rs8
-rw-r--r--azalea-core/src/filterable.rs3
-rw-r--r--azalea-core/src/hit_result.rs19
-rw-r--r--azalea-core/src/lib.rs2
-rw-r--r--azalea-core/src/position.rs39
-rw-r--r--azalea-core/src/resource_location.rs3
-rw-r--r--azalea-core/src/sound.rs3
-rw-r--r--azalea-inventory/Cargo.toml4
-rw-r--r--azalea-inventory/src/components.rs1122
-rw-r--r--azalea-inventory/src/default_components/generated.rs274
-rw-r--r--azalea-inventory/src/default_components/mod.rs6
-rw-r--r--azalea-inventory/src/item/consume_effect.rs10
-rw-r--r--azalea-inventory/src/slot.rs117
-rw-r--r--azalea-inventory/tests/components.rs201
-rw-r--r--azalea-protocol/Cargo.toml6
-rw-r--r--azalea-protocol/src/packets/game/s_container_click.rs34
-rw-r--r--azalea-registry/azalea-registry-macros/src/lib.rs21
-rw-r--r--azalea-registry/src/data.rs11
-rw-r--r--azalea-registry/src/lib.rs59
-rw-r--r--azalea/Cargo.toml7
-rw-r--r--codegen/lib/code/data_components.py80
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
diff --git a/Cargo.lock b/Cargo.lock
index ba291f43..07ddee83 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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]]
diff --git a/Cargo.toml b/Cargo.toml
index 421f8302..85a24965 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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, &registry_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!["
for v in python_value:
# handle tags correctly
if isinstance(v, str) and v.startswith("#minecraft:"):