#![allow(dead_code)] use crate::attributes::Attributes; use crate::data_component::DataComponent; use crate::data_component::DataComponent::{ AttributeModifiers, BlocksAttacks, Consumable, CustomData, CustomName, Damage, DamageResistant, DeathProtection, Enchantable, Enchantments, Equippable, FireworkExplosion, Fireworks, Food, ItemModel, ItemName, JukeboxPlayable, MapId, MaxDamage, MaxStackSize, PotionContents, StoredEnchantments, Tool, Unbreakable, UseCooldown, Weapon, }; use crate::effect::{self, StatusEffect}; use crate::entity_type::EntityType; use crate::sound::Sound; use crate::tag::{RegistryKey, Tag, Taggable}; use crate::{AttributeModifierSlot, Block, Enchantment}; use crc_fast::CrcAlgorithm::Crc32Iscsi; use crc_fast::Digest; use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::tag::NbtTag; use pumpkin_util::registry::RegistryEntryList; use pumpkin_util::text::TextComponent; use serde::de::SeqAccess; use serde::ser::SerializeSeq; use serde::{Deserialize, Serialize, de}; use std::any::Any; use std::borrow::Cow; use std::error::Error; use std::fmt::Debug; use std::str::FromStr; use std::{ collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, }; use pumpkin_nbt::pnbt::PNbtCompound; pub trait DataComponentImpl: Send + Sync { fn write_data(&self) -> NbtTag { NbtTag::End } fn write_data_pnbt(&self, _nbt: &mut PNbtCompound) {} fn get_hash(&self) -> i32 { todo!() } /// make sure other is the same type component, or it will panic fn equal(&self, other: &dyn DataComponentImpl) -> bool; fn get_enum() -> DataComponent where Self: Sized; fn get_self_enum(&self) -> DataComponent; // only for debugging fn to_dyn(self) -> Box; fn clone_dyn(&self) -> Box; fn as_any(&self) -> &dyn Any; fn as_mut_any(&mut self) -> &mut dyn Any; } #[must_use] pub fn read_data(id: DataComponent, data: &NbtTag) -> Option> { match id { MaxStackSize => Some(MaxStackSizeImpl::read_data(data)?.to_dyn()), Enchantments => Some(EnchantmentsImpl::read_data(data)?.to_dyn()), Damage => Some(DamageImpl::read_data(data)?.to_dyn()), Unbreakable => Some(UnbreakableImpl::read_data(data)?.to_dyn()), DamageResistant => Some(DamageResistantImpl::read_data(data)?.to_dyn()), PotionContents => Some(PotionContentsImpl::read_data(data)?.to_dyn()), Fireworks => Some(FireworksImpl::read_data(data)?.to_dyn()), FireworkExplosion => Some(FireworkExplosionImpl::read_data(data)?.to_dyn()), ItemModel => Some(ItemModelImpl::read_data(data)?.to_dyn()), Consumable => Some(ConsumableImpl::read_data(data)?.to_dyn()), Equippable => Some(EquippableImpl::read_data(data)?.to_dyn()), StoredEnchantments => Some(StoredEnchantmentsImpl::read_data(data)?.to_dyn()), UseCooldown => Some(UseCooldownImpl::read_data(data)?.to_dyn()), MapId => Some(MapIdImpl::read_data(data)?.to_dyn()), _ => None, } } #[must_use] pub fn read_data_pnbt( id: DataComponent, nbt: &mut PNbtCompound, ) -> Option> { match id { MaxStackSize => Some(MaxStackSizeImpl::read_data_pnbt(nbt)?.to_dyn()), Enchantments => Some(EnchantmentsImpl::read_data_pnbt(nbt)?.to_dyn()), Damage => Some(DamageImpl::read_data_pnbt(nbt)?.to_dyn()), Unbreakable => Some(UnbreakableImpl::read_data_pnbt(nbt)?.to_dyn()), MapId => Some(MapIdImpl::read_data_pnbt(nbt)?.to_dyn()), _ => None, } } // Also Pumpkin\pumpkin-protocol\src\codec\data_component.rs macro_rules! default_impl { ($t: ident) => { fn equal(&self, other: &dyn DataComponentImpl) -> bool { self == get::(other) } #[inline] fn get_enum() -> DataComponent where Self: Sized, { $t } fn get_self_enum(&self) -> DataComponent { $t } fn to_dyn(self) -> Box { Box::new(self) } fn clone_dyn(&self) -> Box { Box::new(self.clone()) } fn as_any(&self) -> &dyn Any { self } fn as_mut_any(&mut self) -> &mut dyn Any { self } }; } impl Clone for Box { fn clone(&self) -> Self { self.clone_dyn() } } #[inline] pub fn get(value: &dyn DataComponentImpl) -> &T { value.as_any().downcast_ref::().unwrap_or_else(|| { panic!( "you are trying to cast {} to {}", value.get_self_enum().to_name(), T::get_enum().to_name() ) }) } #[inline] pub fn get_mut(value: &mut dyn DataComponentImpl) -> &mut T { value.as_mut_any().downcast_mut::().unwrap() } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CustomDataImpl; impl DataComponentImpl for CustomDataImpl { default_impl!(CustomData); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct MaxStackSizeImpl { pub size: u8, } impl MaxStackSizeImpl { fn read_data(data: &NbtTag) -> Option { data.extract_int().map(|size| Self { size: size as u8 }) } fn read_data_pnbt(nbt: &mut PNbtCompound) -> Option { nbt.get_u8().ok().map(|size| Self { size }) } } impl DataComponentImpl for MaxStackSizeImpl { fn write_data(&self) -> NbtTag { NbtTag::Int(i32::from(self.size)) } fn write_data_pnbt(&self, nbt: &mut PNbtCompound) { nbt.put_u8(self.size); } fn get_hash(&self) -> i32 { get_i32_hash(i32::from(self.size)) as i32 } default_impl!(MaxStackSize); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct MaxDamageImpl { pub max_damage: i32, } impl DataComponentImpl for MaxDamageImpl { default_impl!(MaxDamage); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct DamageImpl { pub damage: i32, } impl DamageImpl { fn read_data(data: &NbtTag) -> Option { data.extract_int().map(|damage| Self { damage }) } fn read_data_pnbt(nbt: &mut PNbtCompound) -> Option { nbt.get_int().ok().map(|damage| Self { damage }) } } impl DataComponentImpl for DamageImpl { fn write_data(&self) -> NbtTag { NbtTag::Int(self.damage) } fn write_data_pnbt(&self, nbt: &mut PNbtCompound) { nbt.put_int(self.damage); } fn get_hash(&self) -> i32 { get_i32_hash(self.damage) as i32 } default_impl!(Damage); } #[derive(Clone, Hash, PartialEq, Eq)] pub struct UnbreakableImpl; impl UnbreakableImpl { const fn read_data(_data: &NbtTag) -> Option { Some(Self) } fn read_data_pnbt(_nbt: &mut PNbtCompound) -> Option { Some(Self) } } impl DataComponentImpl for UnbreakableImpl { fn write_data(&self) -> NbtTag { NbtTag::Compound(NbtCompound::new()) } fn write_data_pnbt(&self, _nbt: &mut PNbtCompound) {} fn get_hash(&self) -> i32 { 0 } default_impl!(Unbreakable); } #[derive(Clone, Hash, PartialEq, Eq)] pub struct CustomNameImpl { // TODO make TextComponent pub name: String, } impl DataComponentImpl for CustomNameImpl { fn write_data(&self) -> NbtTag { NbtTag::String(self.name.clone()) } fn get_hash(&self) -> i32 { get_str_hash(self.name.as_str()) as i32 } default_impl!(CustomName); } #[derive(Clone, Hash, PartialEq, Eq)] pub struct ItemNameImpl { // TODO make TextComponent const pub name: &'static str, } impl DataComponentImpl for ItemNameImpl { default_impl!(ItemName); } #[derive(Clone, Hash, PartialEq, Eq)] pub struct ItemModelImpl { pub id: String, } impl ItemModelImpl { fn read_data(data: &NbtTag) -> Option { data.extract_string().map(|id| Self { id: String::from(id), }) } } impl DataComponentImpl for ItemModelImpl { fn write_data(&self) -> NbtTag { NbtTag::String(self.id.clone()) } fn get_hash(&self) -> i32 { get_str_hash(self.id.as_str()) as i32 } default_impl!(ItemModel); } #[derive(Clone, Hash, PartialEq, Eq)] pub struct LoreImpl; #[derive(Clone, Hash, PartialEq, Eq)] pub struct RarityImpl; #[derive(Clone, Hash, PartialEq, Eq, Default)] pub struct EnchantmentsImpl { pub enchantment: Cow<'static, [(&'static Enchantment, i32)]>, } impl EnchantmentsImpl { fn read_data(data: &NbtTag) -> Option { let data = &data.extract_compound()?.child_tags; let mut enc = Vec::with_capacity(data.len()); for (name, level) in data { enc.push((Enchantment::from_name(name.as_str())?, level.extract_int()?)); } Some(Self { enchantment: Cow::from(enc), }) } fn read_data_pnbt(nbt: &mut PNbtCompound) -> Option { let len = nbt.get_u32().ok()? as usize; let mut enc = Vec::with_capacity(len); for _ in 0..len { let name = nbt.get_string().ok()?; let level = nbt.get_int().ok()?; enc.push((Enchantment::from_name(name.as_str())?, level)); } Some(Self { enchantment: Cow::from(enc), }) } } fn get_str_hash(val: &str) -> u32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&[12u8]); digest.update(&(val.len() as u32).to_le_bytes()); let byte = val.as_bytes(); for i in byte { digest.update(&[*i, 0u8]); } digest.finalize() as u32 } fn get_i32_hash(val: i32) -> u32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&[8u8]); digest.update(&val.to_le_bytes()); digest.finalize() as u32 } fn get_f32_hash(val: f32) -> u32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&[7u8]); digest.update(&val.to_bits().to_le_bytes()); digest.finalize() as u32 } fn get_idor_hash(val: &IdOr) -> u32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&[6u8]); match val { IdOr::Id(sound) => { digest.update(&[1u8]); digest.update(&get_str_hash(sound.to_name()).to_le_bytes()); } IdOr::Value(sound) => { digest.update(&[2u8]); digest.update(&get_str_hash(sound.sound_name.as_str()).to_le_bytes()); if let Some(range) = sound.range { digest.update(&[1u8]); digest.update(&get_f32_hash(range).to_le_bytes()); } else { digest.update(&[0u8]); } } } digest.finalize() as u32 } fn put_idor(nbt: &mut NbtCompound, key: &str, val: &IdOr) { match val { IdOr::Id(id) => nbt.put_string(key, format!("minecraft:{}", id.to_name())), IdOr::Value(sound) => { let mut sound_compound = NbtCompound::new(); sound_compound.put_string("sound_id", sound.sound_name.clone()); if let Some(range) = sound.range { sound_compound.put_float("range", range); } nbt.put(key, NbtTag::Compound(sound_compound)); } } } fn get_idor(nbt: &NbtCompound, key: &str, default: Sound) -> IdOr { if let Some(sound) = nbt.get_string(key) { let sound = sound.strip_prefix("minecraft:").unwrap_or(sound); IdOr::Id(Sound::from_name(sound).unwrap_or(default)) } else if let Some(sound_compound) = nbt.get_compound(key) { let sound_name = sound_compound .get_string("sound_id") .expect("SoundEvent compound must have a 'sound_id' field"); let range = sound_compound.get_float("range"); IdOr::Value(SoundEvent { sound_name: sound_name.to_string(), range, }) } else { IdOr::Id(default) } } fn get_idset_hash(val: &IDSet) -> u32 { let mut digest = Digest::new(Crc32Iscsi); match val { IDSet::Tag(tag) => { digest.update(&[1u8]); digest.update(&get_str_hash(tag).to_le_bytes()); } IDSet::IDs(ids) => { digest.update(&[2u8]); for id in ids.iter() { digest.update(&[3u8]); digest.update(&get_i32_hash(id.registry_id() as i32).to_le_bytes()); } } } digest.finalize() as u32 } #[test] fn hash() { assert_eq!(get_str_hash("minecraft:sharpness"), 2734053906u32); assert_eq!(get_i32_hash(3), 3795317917u32); assert_eq!( EnchantmentsImpl { enchantment: Cow::Borrowed(&[(&Enchantment::SHARPNESS, 2)]), } .get_hash(), -1580618251i32 ); assert_eq!(MaxStackSizeImpl { size: 99 }.get_hash(), -1632321551i32); assert_eq!(MapIdImpl { id: 10 }.get_hash(), -919192125i32); } impl DataComponentImpl for EnchantmentsImpl { fn write_data(&self) -> NbtTag { let mut compound = NbtCompound::new(); for (enc, level) in self.enchantment.iter() { compound.put_int(enc.name, *level); } NbtTag::Compound(compound) } fn write_data_pnbt(&self, nbt: &mut PNbtCompound) { nbt.put_u32(self.enchantment.len() as u32); for (enc, level) in self.enchantment.iter() { nbt.put_string(enc.name); nbt.put_int(*level); } } fn get_hash(&self) -> i32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&[2u8]); for (enc, level) in self.enchantment.iter() { digest.update(&get_str_hash(enc.name).to_le_bytes()); digest.update(&get_i32_hash(*level).to_le_bytes()); } digest.update(&[3u8]); digest.finalize() as i32 } default_impl!(Enchantments); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CanPlaceOnImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CanBreakImpl; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Operation { AddValue, AddMultipliedBase, AddMultipliedTotal, } #[derive(Clone, Debug, PartialEq)] pub struct Modifier { pub r#type: &'static Attributes, pub id: &'static str, pub amount: f64, pub operation: Operation, pub slot: AttributeModifierSlot, } impl Hash for Modifier { fn hash(&self, state: &mut H) { self.r#type.hash(state); self.id.hash(state); unsafe { (*(&raw const self.amount).cast::()).hash(state) }; self.operation.hash(state); self.slot.hash(state); } } #[derive(Clone, Debug, Hash, PartialEq)] pub struct AttributeModifiersImpl { pub attribute_modifiers: Cow<'static, [Modifier]>, } impl DataComponentImpl for AttributeModifiersImpl { default_impl!(AttributeModifiers); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CustomModelDataImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TooltipDisplayImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct RepairCostImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CreativeSlotLockImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct EnchantmentGlintOverrideImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct IntangibleProjectileImpl; #[derive(Clone, Debug, PartialEq)] pub struct FoodImpl { pub nutrition: i32, pub saturation: f32, pub can_always_eat: bool, } impl DataComponentImpl for FoodImpl { default_impl!(Food); } impl Hash for FoodImpl { fn hash(&self, state: &mut H) { self.nutrition.hash(state); unsafe { (*(&raw const self.saturation).cast::()).hash(state) }; self.can_always_eat.hash(state); } } #[derive(Clone, Debug, PartialEq)] pub struct SoundEvent { pub sound_name: String, pub range: Option, } impl Hash for SoundEvent { fn hash(&self, state: &mut H) { self.sound_name.hash(state); if let Some(val) = self.range { true.hash(state); unsafe { (*(&raw const val).cast::()).hash(state) }; } else { false.hash(state); } } } impl SoundEvent { pub const fn new(sound_name: String, range: Option) -> Self { Self { sound_name, range } } } #[derive(Clone, Debug, PartialEq, Hash)] pub enum IdOr { Id(Sound), Value(T), } #[derive(Clone, Debug, PartialEq)] pub struct ConsumableImpl { pub consume_seconds: f32, pub animation: ConsumeAnimation, pub sound_event: IdOr, pub consume_particles: bool, pub effects: Cow<'static, [ConsumeEffect]>, } #[derive(Clone, Debug, PartialEq)] pub enum ConsumeAnimation { None, Eat, Drink, Block, Bow, Spear, Crossbow, Spyglass, Horn, Brush, } impl ConsumeAnimation { #[must_use] pub const fn to_str(&self) -> &'static str { match self { ConsumeAnimation::None => "none", ConsumeAnimation::Eat => "eat", ConsumeAnimation::Drink => "drink", ConsumeAnimation::Block => "block", ConsumeAnimation::Bow => "bow", ConsumeAnimation::Spear => "spear", ConsumeAnimation::Crossbow => "crossbow", ConsumeAnimation::Spyglass => "spyglass", ConsumeAnimation::Horn => "horn", ConsumeAnimation::Brush => "brush", } } } impl TryFrom for ConsumeAnimation { type Error = (); /// Attempts to convert an `i32` value into a [`ConsumeAnimation`]. /// /// # Parameters /// - `value`: The numeric representation of a consume animation. /// /// # Returns /// - `Ok(ConsumeAnimation)` if the value corresponds to a valid consume animation: /// - `0` → `None` /// - `1` → `Eat` /// - `2` → `Drink` /// - `3` → `Block` /// - `4` → `Bow` /// - `5` → `Spear` /// - `6` → `Crossbow` /// - `7` → `Spyglass` /// - `8` → `Horn` /// - `9` → `Brush` /// - `Err(())` if the value does not correspond to any valid consume animation. fn try_from(value: i32) -> Result { match value { 0 => Ok(Self::None), 1 => Ok(Self::Eat), 2 => Ok(Self::Drink), 3 => Ok(Self::Block), 4 => Ok(Self::Bow), 5 => Ok(Self::Spear), 6 => Ok(Self::Crossbow), 7 => Ok(Self::Spyglass), 8 => Ok(Self::Horn), 9 => Ok(Self::Brush), _ => Err(()), } } } impl FromStr for ConsumeAnimation { type Err = (); fn from_str(s: &str) -> Result { match s { "none" => Ok(Self::None), "eat" => Ok(Self::Eat), "drink" => Ok(Self::Drink), "block" => Ok(Self::Block), "bow" => Ok(Self::Bow), "spear" => Ok(Self::Spear), "crossbow" => Ok(Self::Crossbow), "spyglass" => Ok(Self::Spyglass), "horn" => Ok(Self::Horn), "brush" => Ok(Self::Brush), _ => Err(()), } } } impl ConsumableImpl { pub const fn new( consume_seconds: f32, animation: ConsumeAnimation, sound_event: IdOr, consume_particles: bool, effects: Cow<'static, [ConsumeEffect]>, ) -> Self { Self { consume_seconds, animation, sound_event, consume_particles, effects, } } #[must_use] pub fn consume_ticks(&self) -> i32 { (self.consume_seconds * 20.0) as i32 } pub fn read_data(data: &NbtTag) -> Option { let compound = data.extract_compound()?; let consume_seconds = compound.get_float("consume_seconds")?; let animation = compound .get_string("animation")? .parse::() .ok()?; let sound_event = get_idor(compound, "sound", Sound::EntityGenericEat); // Default to generic eat sound if the sound name is invalid let consume_particles = compound.get_bool("has_consume_particles").unwrap_or(false); let opt_list = compound.get_list("on_consume_effects"); let effects: Cow<'static, [ConsumeEffect]> = if let Some(effect_list) = opt_list { effect_list .iter() .filter_map(ConsumeEffect::read_data) .collect() } else { Cow::Borrowed(&[]) }; Some(Self { consume_seconds, animation, sound_event, consume_particles, effects, }) } } impl DataComponentImpl for ConsumableImpl { fn write_data(&self) -> NbtTag { let mut compound = NbtCompound::new(); compound.put_float("consume_seconds", self.consume_seconds); compound.put_string("animation", self.animation.to_str().to_string()); put_idor(&mut compound, "sound", &self.sound_event); compound.put_bool("has_consume_particles", self.consume_particles); let nbt_vec = self.effects.iter().map(|x| x.as_nbt()).collect(); compound.put_list("on_consume_effects", nbt_vec); NbtTag::Compound(compound) } fn get_hash(&self) -> i32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&[2u8]); digest.update(&get_f32_hash(self.consume_seconds).to_le_bytes()); digest.update(&get_i32_hash(self.animation.clone() as i32).to_le_bytes()); digest.update(&get_idor_hash(&self.sound_event).to_be_bytes()); digest.update(&[self.consume_particles as u8]); for effect in self.effects.iter() { digest.update(&effect.get_hash().to_le_bytes()); } digest.update(&[3u8]); digest.finalize() as i32 } default_impl!(Consumable); } impl Hash for ConsumableImpl { fn hash(&self, state: &mut H) { unsafe { (*(&raw const self.consume_seconds).cast::()).hash(state) }; } } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct UseRemainderImpl; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UseCooldownImpl { pub seconds: f32, pub cooldown_group: Option, } impl UseCooldownImpl { pub fn new(seconds: f32, cooldown_group: Option) -> Self { Self { seconds, cooldown_group, } } pub fn read_data(data: &NbtTag) -> Option { let compound = data.extract_compound()?; let seconds = compound.get_float("seconds")?; let cooldown_group = compound.get_string("cooldown_group").map(|s| s.to_string()); Some(Self { seconds, cooldown_group, }) } } impl DataComponentImpl for UseCooldownImpl { fn write_data(&self) -> NbtTag { let mut compound = NbtCompound::new(); compound.put_float("seconds", self.seconds); if let Some(group) = &self.cooldown_group { compound.put_string("cooldown_group", group.clone()); } NbtTag::Compound(compound) } fn get_hash(&self) -> i32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&get_f32_hash(self.seconds).to_le_bytes()); if let Some(group) = &self.cooldown_group { digest.update(&get_str_hash(group).to_le_bytes()); } digest.finalize() as i32 } default_impl!(UseCooldown); } impl Eq for UseCooldownImpl {} impl Hash for UseCooldownImpl { fn hash(&self, state: &mut H) { self.seconds.to_bits().hash(state); self.cooldown_group.hash(state); } } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub enum DamageResistantType { /// Damage always dealt to ender dragons AlwaysHurtsEnderDragons, /// Destroys armor stands in a single hit AlwaysKillsArmorStands, AlwaysMostSignificantFall, /// Damage always notifies nearby hidden silverfish AlwaysTriggersSilverfish, AvoidsGuardianThorns, BurnsArmorStands, BurnFromStepping, BypassesArmor, BypassesCooldown, BypassesEffects, BypassesEnchantments, BypassesInvulnerability, BypassesResistance, BypassesShield, BypassesWolfArmor, CanBreakArmorStands, DamagesHelmet, IgnitesArmorStands, Drowning, /// Damage is reduced by the blast protection enchantment Explosion, /// Damage is reduced by the feather falling enchantment and ignored if slow falling Fall, /// Damage is reduced by the fire protection enchantment or ignored by game rule Fire, /// Damage is reduced by wearing any piece of leather armor or ignored by game rule Freezing, /// So turtles drop bowls when killed by lightning Lightning, PlayerAttack, /// Damage is reduced by the projectile protection enchantment Projectile, MaceSmash, /// Prevents entities from becoming angry at the source of the damage NoAnger, /// Prevents entities from being marked hurt (preventing the server from syncing velocity) NoImpact, NoKnockback, PanicCauses, PanicEnvironmentalCauses, /// Reduces damage dealt to witches by 85% WitchResistantTo, WitherImmuneTo, /// Generic fallback Generic, } impl DamageResistantType { pub fn from_tag(s: &str) -> Self { match s { "#minecraft:always_hurts_ender_dragons" | "minecraft:always_hurts_ender_dragons" | "always_hurts_ender_dragons" => Self::AlwaysHurtsEnderDragons, "#minecraft:always_kills_armor_stands" | "minecraft:always_kills_armor_stands" | "always_kills_armor_stands" => Self::AlwaysKillsArmorStands, "#minecraft:always_most_significant_fall" | "minecraft:always_most_significant_fall" | "always_most_significant_fall" => Self::AlwaysMostSignificantFall, "#minecraft:always_triggers_silverfish" | "minecraft:always_triggers_silverfish" | "always_triggers_silverfish" => Self::AlwaysTriggersSilverfish, "#minecraft:avoids_guardian_thorns" | "minecraft:avoids_guardian_thorns" | "avoids_guardian_thorns" => Self::AvoidsGuardianThorns, "#minecraft:burns_armor_stands" | "minecraft:burns_armor_stands" | "burns_armor_stands" => Self::BurnsArmorStands, "#minecraft:burn_from_stepping" | "minecraft:burn_from_stepping" | "burn_from_stepping" => Self::BurnFromStepping, "#minecraft:bypasses_armor" | "minecraft:bypasses_armor" | "bypasses_armor" => { Self::BypassesArmor } "#minecraft:bypasses_cooldown" | "minecraft:bypasses_cooldown" | "bypasses_cooldown" => Self::BypassesCooldown, "#minecraft:bypasses_effects" | "minecraft:bypasses_effects" | "bypasses_effects" => { Self::BypassesEffects } "#minecraft:bypasses_enchantments" | "minecraft:bypasses_enchantments" | "bypasses_enchantments" => Self::BypassesEnchantments, "#minecraft:bypasses_invulnerability" | "minecraft:bypasses_invulnerability" | "bypasses_invulnerability" => Self::BypassesInvulnerability, "#minecraft:bypasses_resistance" | "minecraft:bypasses_resistance" | "bypasses_resistance" => Self::BypassesResistance, "#minecraft:bypasses_shield" | "minecraft:bypasses_shield" | "bypasses_shield" => { Self::BypassesShield } "#minecraft:bypasses_wolf_armor" | "minecraft:bypasses_wolf_armor" | "bypasses_wolf_armor" => Self::BypassesWolfArmor, "#minecraft:can_break_armor_stand" | "minecraft:can_break_armor_stand" | "can_break_armor_stand" => Self::CanBreakArmorStands, "#minecraft:damages_helmet" | "minecraft:damages_helmet" | "damages_helmet" => { Self::DamagesHelmet } "#minecraft:ignites_armor_stands" | "minecraft:ignites_armor_stands" | "ignites_armor_stands" => Self::IgnitesArmorStands, "#minecraft:is_drowning" | "minecraft:is_drowning" | "is_drowning" => Self::Drowning, "#minecraft:is_explosion" | "minecraft:is_explosion" | "is_explosion" | "explosion" => { Self::Explosion } "#minecraft:is_fall" | "minecraft:is_fall" | "is_fall" | "fall" => Self::Fall, "#minecraft:is_fire" | "minecraft:is_fire" | "is_fire" | "fire" | "in_fire" | "minecraft:in_fire" => Self::Fire, "#minecraft:is_freezing" | "minecraft:is_freezing" | "is_freezing" => Self::Freezing, "#minecraft:is_lightning" | "minecraft:is_lightning" | "is_lightning" => { Self::Lightning } "#minecraft:is_player_attack" | "minecraft:is_player_attack" | "is_player_attack" => { Self::PlayerAttack } "#minecraft:is_projectile" | "minecraft:is_projectile" | "is_projectile" => { Self::Projectile } "#minecraft:mace_smash" | "minecraft:mace_smash" | "mace_smash" => Self::MaceSmash, "#minecraft:no_anger" | "minecraft:no_anger" | "no_anger" => Self::NoAnger, "#minecraft:no_impact" | "minecraft:no_impact" | "no_impact" => Self::NoImpact, "#minecraft:no_knockback" | "minecraft:no_knockback" | "no_knockback" => { Self::NoKnockback } "#minecraft:panic_causes" | "minecraft:panic_causes" | "panic_causes" => { Self::PanicCauses } "#minecraft:panic_environmental_causes" | "minecraft:panic_environmental_causes" | "panic_environmental_causes" => Self::PanicEnvironmentalCauses, "#minecraft:witch_resistant_to" | "minecraft:witch_resistant_to" | "witch_resistant_to" => Self::WitchResistantTo, "#minecraft:wither_immune_to" | "minecraft:wither_immune_to" | "wither_immune_to" => { Self::WitherImmuneTo } _ => Self::Generic, } } pub fn as_str(&self) -> &'static str { match self { Self::AlwaysHurtsEnderDragons => "#minecraft:always_hurts_ender_dragons", Self::AlwaysKillsArmorStands => "#minecraft:always_kills_armor_stands", Self::AlwaysMostSignificantFall => "#minecraft:always_most_significant_fall", Self::AlwaysTriggersSilverfish => "#minecraft:always_triggers_silverfish", Self::AvoidsGuardianThorns => "#minecraft:avoids_guardian_thorns", Self::BurnsArmorStands => "#minecraft:burns_armor_stands", Self::BurnFromStepping => "#minecraft:burn_from_stepping", Self::BypassesArmor => "#minecraft:bypasses_armor", Self::BypassesCooldown => "#minecraft:bypasses_cooldown", Self::BypassesEffects => "#minecraft:bypasses_effects", Self::BypassesEnchantments => "#minecraft:bypasses_enchantments", Self::BypassesInvulnerability => "#minecraft:bypasses_invulnerability", Self::BypassesResistance => "#minecraft:bypasses_resistance", Self::BypassesShield => "#minecraft:bypasses_shield", Self::BypassesWolfArmor => "#minecraft:bypasses_wolf_armor", Self::CanBreakArmorStands => "#minecraft:can_break_armor_stand", Self::DamagesHelmet => "#minecraft:damages_helmet", Self::IgnitesArmorStands => "#minecraft:ignites_armor_stands", Self::Drowning => "#minecraft:is_drowning", Self::Explosion => "#minecraft:is_explosion", Self::Fall => "#minecraft:is_fall", Self::Fire => "#minecraft:is_fire", Self::Freezing => "#minecraft:is_freezing", Self::Lightning => "#minecraft:is_lightning", Self::PlayerAttack => "#minecraft:is_player_attack", Self::Projectile => "#minecraft:is_projectile", Self::MaceSmash => "#minecraft:mace_smash", Self::NoAnger => "#minecraft:no_anger", Self::NoImpact => "#minecraft:no_impact", Self::NoKnockback => "#minecraft:no_knockback", Self::PanicCauses => "#minecraft:panic_causes", Self::PanicEnvironmentalCauses => "#minecraft:panic_environmental_causes", Self::WitchResistantTo => "#minecraft:witch_resistant_to", Self::WitherImmuneTo => "#minecraft:wither_immune_to", Self::Generic => "minecraft:generic", } } } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct DamageResistantImpl { pub res_type: DamageResistantType, } impl DamageResistantImpl { fn read_data(data: &NbtTag) -> Option { let compound = data.extract_compound()?; let type_str = compound.get_string("types")?; Some(Self { res_type: DamageResistantType::from_tag(type_str), }) } } impl std::str::FromStr for DamageResistantType { type Err = (); fn from_str(s: &str) -> Result { Ok(DamageResistantType::from_tag(s)) } } impl DataComponentImpl for DamageResistantImpl { fn write_data(&self) -> NbtTag { let mut compound = NbtCompound::new(); compound.put_string("types", self.res_type.as_str().to_string()); NbtTag::Compound(compound) } fn get_hash(&self) -> i32 { get_str_hash(self.res_type.as_str()) as i32 } default_impl!(DamageResistant); } pub trait IDSetContent { fn registry_id(&self) -> u16; fn to_string(&self) -> String; fn from_id(id: u16) -> Option<&'static Self>; fn from_str(name: &str) -> Option<&'static Self>; } impl IDSetContent for Block { fn registry_id(&self) -> u16 { Taggable::registry_id(self) } fn from_id(id: u16) -> Option<&'static Self> { Some(Block::from_id(id)) } fn from_str(name: &str) -> Option<&'static Self> { Block::from_name(name) } fn to_string(&self) -> String { self.name.to_string() } } #[derive(Clone, Hash, PartialEq, Debug)] pub enum IDSet { Tag(Cow<'static, str>), IDs(Cow<'static, [&'static T]>), } impl IDSet { fn read(data: &NbtTag) -> Option { match data.clone() { NbtTag::String(tag) => { if tag.starts_with("#") { Some(Self::Tag(Cow::Owned(tag.strip_prefix("#")?.to_string()))) } else { Some(Self::IDs(Cow::Owned([T::from_str(tag.as_str())?].to_vec()))) } } NbtTag::List(nbt_tags) => { let mut ids = Vec::<&T>::new(); for nbt in nbt_tags { if let NbtTag::String(id) = nbt && let Some(instance) = T::from_str(id.as_str()) { ids.push(instance); } } Some(Self::IDs(Cow::Owned(ids))) } _ => None, } } fn write(&self, compound: &mut NbtCompound, key: &str) { match self { Self::Tag(cow) => { let mut tag = cow.to_string(); tag.insert(0, '#'); compound.put_string(key, tag); } Self::IDs(arr) => { let id_vec: Vec = arr.iter().map(|x| NbtTag::String(x.to_string())).collect(); compound.put_list(key, id_vec); } } } } #[derive(Clone, PartialEq)] pub struct ToolRule { pub blocks: IDSet, pub speed: Option, pub correct_for_drops: Option, } impl Hash for ToolRule { fn hash(&self, state: &mut H) { self.blocks.hash(state); if let Some(val) = self.speed { true.hash(state); unsafe { (*(&raw const val).cast::()).hash(state) }; } else { false.hash(state); } self.correct_for_drops.hash(state); } } #[derive(Clone, PartialEq)] pub struct ToolImpl { pub rules: Cow<'static, [ToolRule]>, pub default_mining_speed: f32, pub damage_per_block: u32, pub can_destroy_blocks_in_creative: bool, } impl DataComponentImpl for ToolImpl { default_impl!(Tool); } impl Hash for ToolImpl { fn hash(&self, state: &mut H) { self.rules.hash(state); unsafe { (*(&raw const self.default_mining_speed).cast::()).hash(state) }; self.damage_per_block.hash(state); self.can_destroy_blocks_in_creative.hash(state); } } /// Weapon component: specifies durability cost per attack. /// NOTE: If additional fields are added, update `get_hash()` to include them. #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct WeaponImpl { pub item_damage_per_attack: u32, } impl WeaponImpl { fn read_data(data: &NbtTag) -> Option { let compound = data.extract_compound()?; // NOTE: Error handling for item_damage_per_attack: // - Missing key: defaults to 1 (vanilla behavior for unmodified items). // - Wrong NBT type (e.g. float instead of int): silently defaults to 1. // - Negative value: clamped to 0 then cast to u32 (protects against direct NBT manipulation). // This conservative approach prioritizes safety over strict validation. // TODO: Add tracing::warn! at the call site (in pumpkin crate where tracing is available) // to help datapack authors debug negative values or type mismatches. let item_damage_per_attack = compound .get_int("item_damage_per_attack") .unwrap_or(1) .max(0) as u32; // TODO: Deserialize disable_blocking_for_seconds once NBT float API is clarified. // For now, preserve it by storing the raw compound for round-trip fidelity. Some(Self { item_damage_per_attack, }) } } impl DataComponentImpl for WeaponImpl { fn write_data(&self) -> NbtTag { let mut compound = NbtCompound::new(); compound.put_int("item_damage_per_attack", self.item_damage_per_attack as i32); NbtTag::Compound(compound) } fn get_hash(&self) -> i32 { self.item_damage_per_attack as i32 } default_impl!(Weapon); } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] pub enum EquipmentType { #[default] Any, Hand, HumanoidArmor, AnimalArmor, Body, Saddle, } #[derive(Clone, Hash, Eq, PartialEq, Default)] pub struct EquipmentSlotData { pub slot_type: EquipmentType, pub entity_id: i32, pub max_count: i32, pub index: i32, pub name: Cow<'static, str>, } #[derive(Clone, Hash, Eq, PartialEq)] #[repr(i8)] pub enum EquipmentSlot { MainHand(EquipmentSlotData), OffHand(EquipmentSlotData), Feet(EquipmentSlotData), Legs(EquipmentSlotData), Chest(EquipmentSlotData), Head(EquipmentSlotData), Body(EquipmentSlotData), Saddle(EquipmentSlotData), } impl EquipmentSlot { pub const MAIN_HAND: Self = Self::MainHand(EquipmentSlotData { slot_type: EquipmentType::Hand, entity_id: 0, index: 0, max_count: 0, name: Cow::Borrowed("mainhand"), }); pub const OFF_HAND: Self = Self::OffHand(EquipmentSlotData { slot_type: EquipmentType::Hand, entity_id: 1, index: 5, max_count: 0, name: Cow::Borrowed("offhand"), }); pub const FEET: Self = Self::Feet(EquipmentSlotData { slot_type: EquipmentType::HumanoidArmor, entity_id: 0, index: 1, max_count: 1, name: Cow::Borrowed("feet"), }); pub const LEGS: Self = Self::Legs(EquipmentSlotData { slot_type: EquipmentType::HumanoidArmor, entity_id: 1, index: 2, max_count: 1, name: Cow::Borrowed("legs"), }); pub const CHEST: Self = Self::Chest(EquipmentSlotData { slot_type: EquipmentType::HumanoidArmor, entity_id: 2, index: 3, max_count: 1, name: Cow::Borrowed("chest"), }); pub const HEAD: Self = Self::Head(EquipmentSlotData { slot_type: EquipmentType::HumanoidArmor, entity_id: 3, index: 4, max_count: 1, name: Cow::Borrowed("head"), }); pub const BODY: Self = Self::Body(EquipmentSlotData { slot_type: EquipmentType::Body, entity_id: 0, index: 6, max_count: 1, name: Cow::Borrowed("body"), }); pub const SADDLE: Self = Self::Saddle(EquipmentSlotData { slot_type: EquipmentType::Saddle, entity_id: 0, index: 7, max_count: 1, name: Cow::Borrowed("saddle"), }); #[must_use] pub const fn get_entity_slot_id(&self) -> i32 { match self { Self::MainHand(data) => data.entity_id, Self::OffHand(data) => data.entity_id, Self::Feet(data) => data.entity_id, Self::Legs(data) => data.entity_id, Self::Chest(data) => data.entity_id, Self::Head(data) => data.entity_id, Self::Body(data) => data.entity_id, Self::Saddle(data) => data.entity_id, } } #[must_use] pub const fn get_slot_index(&self) -> i32 { match self { Self::MainHand(data) => data.index, Self::OffHand(data) => data.index, Self::Feet(data) => data.index, Self::Legs(data) => data.index, Self::Chest(data) => data.index, Self::Head(data) => data.index, Self::Body(data) => data.index, Self::Saddle(data) => data.index, } } #[must_use] pub fn from_slot_index(name: i32) -> Option<&'static Self> { match name { 0 => Some(&Self::MAIN_HAND), 1 => Some(&Self::FEET), 2 => Some(&Self::LEGS), 3 => Some(&Self::CHEST), 4 => Some(&Self::HEAD), 5 => Some(&Self::OFF_HAND), 6 => Some(&Self::BODY), 7 => Some(&Self::SADDLE), _ => None, } } #[must_use] pub fn get_from_name(name: &str) -> Option<&'static Self> { match name { "mainhand" => Some(&Self::MAIN_HAND), "offhand" => Some(&Self::OFF_HAND), "feet" => Some(&Self::FEET), "legs" => Some(&Self::LEGS), "chest" => Some(&Self::CHEST), "head" => Some(&Self::HEAD), "body" => Some(&Self::BODY), "saddle" => Some(&Self::SADDLE), _ => None, } } #[must_use] pub fn to_name(&self) -> &Cow<'static, str> { match self { Self::MainHand(data) => &data.name, Self::OffHand(data) => &data.name, Self::Feet(data) => &data.name, Self::Legs(data) => &data.name, Self::Chest(data) => &data.name, Self::Head(data) => &data.name, Self::Body(data) => &data.name, Self::Saddle(data) => &data.name, } } #[must_use] pub const fn get_offset_entity_slot_id(&self, offset: i32) -> i32 { self.get_entity_slot_id() + offset } #[must_use] pub const fn slot_type(&self) -> EquipmentType { match self { Self::MainHand(data) => data.slot_type, Self::OffHand(data) => data.slot_type, Self::Feet(data) => data.slot_type, Self::Legs(data) => data.slot_type, Self::Chest(data) => data.slot_type, Self::Head(data) => data.slot_type, Self::Body(data) => data.slot_type, Self::Saddle(data) => data.slot_type, } } #[must_use] pub const fn is_armor_slot(&self) -> bool { matches!( self.slot_type(), EquipmentType::HumanoidArmor | EquipmentType::AnimalArmor ) } #[must_use] pub const fn discriminant(&self) -> i8 { match self { Self::MainHand(_) => 0, Self::OffHand(_) => 1, Self::Feet(_) => 2, Self::Legs(_) => 3, Self::Chest(_) => 4, Self::Head(_) => 5, Self::Body(_) => 6, Self::Saddle(_) => 7, } } } #[derive(Clone, PartialEq, Eq)] pub enum EntityTypeOrTag { Tag(&'static Tag), Single(&'static EntityType), } impl Hash for EntityTypeOrTag { fn hash(&self, state: &mut H) { match self { Self::Tag(tag) => { for x in tag.0 { x.hash(state); } } Self::Single(entity_type) => { entity_type.id.hash(state); } } } } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct EnchantableImpl { pub value: i32, } impl DataComponentImpl for EnchantableImpl { default_impl!(Enchantable); } #[derive(Clone, Hash, PartialEq)] pub struct EquippableImpl { pub slot: &'static EquipmentSlot, pub equip_sound: IdOr, pub asset_id: Option>, pub camera_overlay: Option>, pub allowed_entities: Option>, pub dispensable: bool, pub swappable: bool, pub damage_on_hurt: bool, pub equip_on_interact: bool, pub can_be_sheared: bool, pub shearing_sound: IdOr, } impl EquippableImpl { pub fn read_data(data: &NbtTag) -> Option { let compound = data.extract_compound()?; let slot = EquipmentSlot::get_from_name(compound.get_string("slot")?)?; let asset_id = compound .get_string("asset_id") .map(|str| Cow::Owned(str.to_owned())); let camera_overlay = compound .get_string("camera_overlay") .map(|str| Cow::Owned(str.to_owned())); let dispensable = compound.get_bool("dispensable").unwrap_or(true); let swappable = compound.get_bool("swappable").unwrap_or(true); let damage_on_hurt = compound.get_bool("damage_on_hurt").unwrap_or(true); let equip_on_interact = compound.get_bool("equip_on_interact").unwrap_or(false); let can_be_sheared = compound.get_bool("can_be_sheared").unwrap_or(false); let equip_sound = get_idor(compound, "equip_sound", Sound::ItemArmorEquipGeneric); let shearing_sound = get_idor(compound, "shearing_sound_sound", Sound::ItemShearsSnip); let allowed_entities = if let Some(nbt) = compound.get("allowed_entities") { IDSet::::read(nbt) } else { None }; Some(Self { slot, equip_sound, asset_id, camera_overlay, allowed_entities, dispensable, swappable, damage_on_hurt, equip_on_interact, can_be_sheared, shearing_sound, }) } } impl DataComponentImpl for EquippableImpl { fn get_hash(&self) -> i32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&[16u8]); // is it used? digest.update(&get_i32_hash(self.slot.get_slot_index()).to_le_bytes()); digest.update(&get_idor_hash(&self.equip_sound).to_le_bytes()); if let Some(asset) = &self.asset_id { digest.update(&[1u8]); digest.update(&get_str_hash(asset).to_le_bytes()); } if let Some(overlay) = &self.camera_overlay { digest.update(&[2u8]); digest.update(&get_str_hash(overlay).to_le_bytes()); } if let Some(allowed_entities) = &self.allowed_entities { digest.update(&[3u8]); digest.update(&get_idset_hash(allowed_entities).to_le_bytes()); } digest.update(&[self.dispensable as u8]); digest.update(&[self.swappable as u8]); digest.update(&[self.damage_on_hurt as u8]); digest.update(&[self.equip_on_interact as u8]); digest.update(&[self.can_be_sheared as u8]); digest.update(&get_idor_hash(&self.shearing_sound).to_le_bytes()); digest.finalize() as i32 } fn write_data(&self) -> NbtTag { let mut compound = NbtCompound::new(); compound.put_string("slot", self.slot.to_name().to_string()); put_idor(&mut compound, "equip_sound", &self.equip_sound); put_idor(&mut compound, "shearing_sound", &self.shearing_sound); if let Some(asset_id) = &self.asset_id { compound.put_string("asset_id", asset_id.to_string()); } if let Some(camera_overlay) = &self.camera_overlay { compound.put_string("camera_overlay", camera_overlay.to_string()); } if let Some(allowed_entities) = &self.allowed_entities { allowed_entities.write(&mut compound, "allowed_entities"); } compound.put_bool("dispensable", self.dispensable); compound.put_bool("swappable", self.swappable); compound.put_bool("damage_on_hurt", self.damage_on_hurt); compound.put_bool("equip_on_interact", self.equip_on_interact); compound.put_bool("can_be_sheared", self.can_be_sheared); NbtTag::Compound(compound) } default_impl!(Equippable); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct RepairableImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct GliderImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TooltipStyleImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct DeathProtectionImpl; impl DataComponentImpl for DeathProtectionImpl { default_impl!(DeathProtection); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BlocksAttacksImpl; impl DataComponentImpl for BlocksAttacksImpl { default_impl!(BlocksAttacks); } #[derive(Clone, Hash, PartialEq, Eq)] pub struct StoredEnchantmentsImpl { pub enchantment: Cow<'static, [(&'static Enchantment, i32)]>, } impl StoredEnchantmentsImpl { fn read_data(data: &NbtTag) -> Option { let data = &data.extract_compound()?.child_tags; let mut enc = Vec::with_capacity(data.len()); for (name, level) in data { enc.push((Enchantment::from_name(name.as_str())?, level.extract_int()?)); } Some(Self { enchantment: Cow::from(enc), }) } } impl DataComponentImpl for StoredEnchantmentsImpl { fn write_data(&self) -> NbtTag { let mut data = NbtCompound::new(); for (enc, level) in self.enchantment.iter() { data.put_int(enc.name, *level); } NbtTag::Compound(data) } fn get_hash(&self) -> i32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&[2u8]); for (enc, level) in self.enchantment.iter() { digest.update(&get_str_hash(enc.name).to_le_bytes()); digest.update(&get_i32_hash(*level).to_le_bytes()); } digest.update(&[3u8]); digest.finalize() as i32 } default_impl!(StoredEnchantments); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct DyedColorImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct MapColorImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct MapIdImpl { pub id: i32, } impl MapIdImpl { fn read_data(data: &NbtTag) -> Option { data.extract_int().map(|id| Self { id }) } fn read_data_pnbt(nbt: &mut PNbtCompound) -> Option { nbt.get_i32().ok().map(|id| Self { id }) } } impl DataComponentImpl for MapIdImpl { fn write_data(&self) -> NbtTag { NbtTag::Int(self.id) } fn write_data_pnbt(&self, nbt: &mut PNbtCompound) { nbt.put_i32(self.id); } fn get_hash(&self) -> i32 { get_i32_hash(self.id) as i32 } default_impl!(MapId); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct MapDecorationsImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct MapPostProcessingImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ChargedProjectilesImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BundleContentsImpl; /// Status effect instance for potion contents #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct StatusEffectInstance { pub effect_id: Cow<'static, str>, pub amplifier: i32, pub duration: i32, pub ambient: bool, pub show_particles: bool, pub show_icon: bool, } impl StatusEffectInstance { pub fn read_data(nbt: &NbtTag) -> Option { let compound = nbt.extract_compound()?; let effect_id = Cow::Owned(compound.get_string("id")?.to_string()); let amplifier = compound.get_int("amplifier")?; let duration = compound.get_int("duration")?; let ambient = compound.get_bool("ambient")?; let show_particles = compound.get_bool("show_particles")?; let show_icon = compound.get_bool("show_icon")?; Some(Self { effect_id, amplifier, duration, ambient, show_particles, show_icon, }) } pub fn as_nbt(&self) -> NbtTag { let mut compound = NbtCompound::new(); compound.put_string("id", self.effect_id.to_string()); compound.put_int("amplifier", self.amplifier); compound.put_int("duration", self.duration); compound.put_bool("ambient", self.ambient); compound.put_bool("show_particles", self.show_particles); compound.put_bool("show_icon", self.show_icon); NbtTag::Compound(compound) } pub fn get_hash(&self) -> i32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&get_str_hash(self.effect_id.as_ref()).to_le_bytes()); digest.update(&get_i32_hash(self.amplifier).to_le_bytes()); digest.update(&get_i32_hash(self.duration).to_le_bytes()); digest.update(&[self.ambient as u8]); digest.update(&[self.show_particles as u8]); digest.update(&[self.show_icon as u8]); digest.finalize() as i32 } } impl Hash for ConsumeEffect { fn hash(&self, state: &mut H) { self.to_str().hash(state); match self { ConsumeEffect::ApplyEffects(tuple) => { tuple.0.hash(state); unsafe { (*(&raw const tuple.1).cast::()).hash(state) }; } ConsumeEffect::RemoveEffects(status_effect_instances) => { status_effect_instances.hash(state) } ConsumeEffect::ClearAllEffects => (), ConsumeEffect::TeleportRandomly(dst) => unsafe { (*(&raw const dst).cast::()).hash(state) }, ConsumeEffect::PlaySound(id_or) => id_or.hash(state), } } } #[derive(Clone, Debug, PartialEq)] pub enum ConsumeEffect { /// Vec of status effect and a f32 representing /// the probability ApplyEffects((Cow<'static, [StatusEffectInstance]>, f32)), RemoveEffects(IDSet), ClearAllEffects, /// f32 diameter TeleportRandomly(f32), PlaySound(IdOr), } impl ConsumeEffect { pub fn to_str(&self) -> &str { match self { ConsumeEffect::ApplyEffects(_) => "apply_effects", ConsumeEffect::RemoveEffects(_) => "remove_effects", ConsumeEffect::ClearAllEffects => "clear_all_effects", ConsumeEffect::TeleportRandomly(_) => "teleport_randomly", ConsumeEffect::PlaySound(_) => "play_sound", } } pub fn registry_id(&self) -> u8 { match self { ConsumeEffect::ApplyEffects(_) => 0, ConsumeEffect::RemoveEffects(_) => 1, ConsumeEffect::ClearAllEffects => 2, ConsumeEffect::TeleportRandomly(_) => 3, ConsumeEffect::PlaySound(_) => 4, } } pub fn read_data(nbt: &NbtTag) -> Option { let compound = nbt.extract_compound()?; let r#type = compound.get_string("type")?; match r#type { "remove_effects" => { let idset = IDSet::read(compound.get("effects")?)?; Some(Self::RemoveEffects(idset)) } "clear_all_effects" => Some(Self::ClearAllEffects), "teleport_randomly" => { let dst = compound.get_float("diameter")?; Some(Self::TeleportRandomly(dst)) } "play_sound" => { let sound = get_idor(compound, "sound", Sound::EntityGenericEat); Some(Self::PlaySound(sound)) } "apply_effects" => { let probability = compound.get_float("probability")?; let effects_vec: Vec = compound .get_list("effects")? .iter() .filter_map(StatusEffectInstance::read_data) .collect(); let effects: Cow<'static, [StatusEffectInstance]> = Cow::Owned(effects_vec); Some(Self::ApplyEffects((effects, probability))) } _ => None, } } pub fn as_nbt(&self) -> NbtTag { let mut compound = NbtCompound::new(); compound.put_string("type", self.to_str().to_string()); match self { ConsumeEffect::ApplyEffects(data) => { let nbt_arr = data.0.iter().map(|x| x.as_nbt()).collect(); compound.put_list("effects", nbt_arr); compound.put_float("probability", data.1); } ConsumeEffect::RemoveEffects(idset) => idset.write(&mut compound, "effects"), ConsumeEffect::ClearAllEffects => (), ConsumeEffect::TeleportRandomly(dst) => compound.put_float("diameter", *dst), ConsumeEffect::PlaySound(id_or) => { put_idor(&mut compound, "sound", id_or); } } NbtTag::Compound(compound) } pub fn get_hash(&self) -> i32 { let mut digest = Digest::new(Crc32Iscsi); match self { ConsumeEffect::ApplyEffects((effects, probability)) => { digest.update(&[1u8]); for effect in effects.iter() { digest.update(&effect.get_hash().to_le_bytes()); } digest.update(&[13u8]); digest.update(&get_f32_hash(*probability).to_le_bytes()); } ConsumeEffect::RemoveEffects(idset) => { digest.update(&[2u8]); digest.update(&get_idset_hash(idset).to_le_bytes()); } ConsumeEffect::ClearAllEffects => { digest.update(&[3u8]); } ConsumeEffect::TeleportRandomly(dst) => { digest.update(&[4u8]); digest.update(&get_f32_hash(*dst).to_le_bytes()); } ConsumeEffect::PlaySound(id_or) => { digest.update(&[5u8]); digest.update(&get_idor_hash(id_or).to_le_bytes()); } } digest.finalize() as i32 } } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct PotionContentsImpl { pub potion_id: Option, pub custom_color: Option, pub custom_effects: Vec, pub custom_name: Option, } impl PotionContentsImpl { pub fn read_data(tag: &NbtTag) -> Option { let compound = tag.extract_compound()?; let potion_id = if let Some(id) = compound.get_int("potion") { Some(id) } else if let Some(name) = compound.get_string("potion") { // Handle "minecraft:swiftness" -> "swiftness" let name = name.strip_prefix("minecraft:").unwrap_or(name); crate::potion::Potion::from_name(name).map(|p| p.id as i32) } else { None }; let custom_color = compound.get_int("custom_color"); let custom_name = compound.get_string("custom_name").map(|s| s.to_string()); let custom_effects = compound .get_list("custom_effects") .map(|list| { list.iter() .filter_map(|item| { // Try to get the compound for this specific effect let effect_tag = item.extract_compound()?; // Try to get the ID let id: Cow<'static, str> = Cow::Owned(effect_tag.get_string("id")?.to_string()); // Fallback values for optional fields let amplifier = effect_tag .get_int("amplifier") .or_else(|| effect_tag.get_byte("amplifier").map(i32::from)) .unwrap_or(0); let duration = effect_tag .get_int("duration") .or_else(|| effect_tag.get_byte("duration").map(i32::from)) .unwrap_or(0); let ambient = effect_tag.get_bool("ambient").unwrap_or(false); let show_particles = effect_tag.get_bool("show_particles").unwrap_or(true); let show_icon = effect_tag.get_bool("show_icon").unwrap_or(true); // Create the StatusEffectInstance Some(StatusEffectInstance { effect_id: id, amplifier, duration, ambient, show_particles, show_icon, }) }) .collect::>() }) .unwrap_or_default(); Some(Self { potion_id, custom_color, custom_effects, custom_name, }) } } impl DataComponentImpl for PotionContentsImpl { fn write_data(&self) -> NbtTag { let mut compound = NbtCompound::new(); if let Some(potion_id) = self.potion_id { compound.put_int("potion", potion_id); } if let Some(color) = self.custom_color { compound.put_int("custom_color", color); } if !self.custom_effects.is_empty() { let mut effects_list = Vec::new(); for effect in &self.custom_effects { let mut effect_compound = NbtCompound::new(); effect_compound.put_string("id", effect.effect_id.to_string()); effect_compound.put_int("amplifier", effect.amplifier); effect_compound.put_int("duration", effect.duration); effect_compound.put_byte("ambient", effect.ambient as i8); effect_compound.put_byte("show_particles", effect.show_particles as i8); effect_compound.put_byte("show_icon", effect.show_icon as i8); effects_list.push(NbtTag::Compound(effect_compound)); } compound.put("custom_effects", NbtTag::List(effects_list)); } if let Some(name) = &self.custom_name { compound.put_string("custom_name", name.clone()); } NbtTag::Compound(compound) } fn get_hash(&self) -> i32 { let mut digest = Digest::new(Crc32Iscsi); if let Some(id) = self.potion_id { digest.update(&[1u8]); digest.update(&get_i32_hash(id).to_le_bytes()); } if let Some(color) = self.custom_color { digest.update(&[2u8]); digest.update(&get_i32_hash(color).to_le_bytes()); } if let Some(name) = &self.custom_name { digest.update(&[3u8]); digest.update(&get_str_hash(name).to_le_bytes()); } if !self.custom_effects.is_empty() { digest.update(&[4u8]); for effect in &self.custom_effects { digest.update(&effect.get_hash().to_le_bytes()); } } digest.finalize() as i32 } default_impl!(PotionContents); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct PotionDurationScaleImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct SuspiciousStewEffectsImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct WritableBookContentImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct WrittenBookContentImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TrimImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct DebugStickStateImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct EntityDataImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BucketEntityDataImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BlockEntityDataImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct InstrumentImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ProvidesTrimMaterialImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct OminousBottleAmplifierImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct JukeboxPlayableImpl { pub song: &'static str, } impl DataComponentImpl for JukeboxPlayableImpl { default_impl!(JukeboxPlayable); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ProvidesBannerPatternsImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct RecipesImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct LodestoneTrackerImpl; /// Firework explosion shape types #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum FireworkExplosionShape { SmallBall = 0, LargeBall = 1, Star = 2, Creeper = 3, Burst = 4, } impl FireworkExplosionShape { pub fn from_id(id: i32) -> Option { match id { 0 => Some(Self::SmallBall), 1 => Some(Self::LargeBall), 2 => Some(Self::Star), 3 => Some(Self::Creeper), 4 => Some(Self::Burst), _ => None, } } pub fn to_id(&self) -> i32 { *self as i32 } pub fn to_name(&self) -> &str { match self { Self::SmallBall => "small_ball", Self::LargeBall => "large_ball", Self::Star => "star", Self::Creeper => "creeper", Self::Burst => "burst", } } pub fn from_name(name: &str) -> Option { match name { "small_ball" => Some(Self::SmallBall), "large_ball" => Some(Self::LargeBall), "star" => Some(Self::Star), "creeper" => Some(Self::Creeper), "burst" => Some(Self::Burst), _ => None, } } } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct FireworkExplosionImpl { pub shape: FireworkExplosionShape, pub colors: Vec, pub fade_colors: Vec, pub has_trail: bool, pub has_twinkle: bool, } impl FireworkExplosionImpl { pub fn new( shape: FireworkExplosionShape, colors: Vec, fade_colors: Vec, has_trail: bool, has_twinkle: bool, ) -> Self { Self { shape, colors, fade_colors, has_trail, has_twinkle, } } pub fn read_data(tag: &NbtTag) -> Option { let compound = tag.extract_compound()?; let shape = FireworkExplosionShape::from_name(compound.get_string("shape")?)?; let colors = compound .get_int_array("colors") .map(|v| v.to_vec()) .unwrap_or_default(); let fade_colors = compound .get_int_array("fade_colors") .map(|v| v.to_vec()) .unwrap_or_default(); let has_trail = compound.get_bool("has_trail").unwrap_or(false); let has_twinkle = compound.get_bool("has_twinkle").unwrap_or(false); Some(Self { shape, colors, fade_colors, has_trail, has_twinkle, }) } } impl DataComponentImpl for FireworkExplosionImpl { fn write_data(&self) -> NbtTag { let mut compound = NbtCompound::new(); compound.put_string("shape", self.shape.to_name().to_string()); compound.put("colors", NbtTag::IntArray(self.colors.clone())); compound.put("fade_colors", NbtTag::IntArray(self.fade_colors.clone())); compound.put_bool("has_trail", self.has_trail); compound.put_bool("has_twinkle", self.has_twinkle); NbtTag::Compound(compound) } fn get_hash(&self) -> i32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&[2u8]); digest.update(&[self.shape.to_id() as u8]); for color in &self.colors { digest.update(&get_i32_hash(*color).to_le_bytes()); } digest.update(&[3u8]); for color in &self.fade_colors { digest.update(&get_i32_hash(*color).to_le_bytes()); } digest.update(&[4u8]); digest.update(&[self.has_trail as u8]); digest.update(&[self.has_twinkle as u8]); digest.finalize() as i32 } default_impl!(FireworkExplosion); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct FireworksImpl { pub flight_duration: i32, pub explosions: Vec, } impl FireworksImpl { pub fn new(flight_duration: i32, explosions: Vec) -> Self { Self { flight_duration, explosions, } } pub fn read_data(tag: &NbtTag) -> Option { let compound = tag.extract_compound()?; let flight_duration = compound .get_byte("flight_duration") .map(i32::from) .or_else(|| compound.get_int("flight_duration")) .unwrap_or(1); let mut explosions = Vec::new(); if let Some(list) = compound.get_list("explosions") { for item in list { if let Some(explosion) = FireworkExplosionImpl::read_data(item) { explosions.push(explosion); } } } Some(Self { flight_duration, explosions, }) } } impl DataComponentImpl for FireworksImpl { fn write_data(&self) -> NbtTag { let mut compound = NbtCompound::new(); compound.put_int("flight_duration", self.flight_duration); let explosions_list: Vec = self.explosions.iter().map(|e| e.write_data()).collect(); compound.put_list("explosions", explosions_list); NbtTag::Compound(compound) } fn get_hash(&self) -> i32 { let mut digest = Digest::new(Crc32Iscsi); digest.update(&[2u8]); digest.update(&get_i32_hash(self.flight_duration).to_le_bytes()); for explosion in &self.explosions { digest.update(&get_i32_hash(explosion.get_hash()).to_le_bytes()); } digest.update(&[3u8]); digest.finalize() as i32 } default_impl!(Fireworks); } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ProfileImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct NoteBlockSoundImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BannerPatternsImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BaseColorImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct PotDecorationsImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ContainerImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BlockStateImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BeesImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct LockImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ContainerLootImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BreakSoundImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct VillagerVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct WolfVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct WolfSoundVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct WolfCollarImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct FoxVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct SalmonSizeImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ParrotVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TropicalFishPatternImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TropicalFishBaseColorImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct TropicalFishPatternColorImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct MooshroomVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct RabbitVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct PigVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CowVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ChickenVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct FrogVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct HorseVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct PaintingVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct LlamaVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct AxolotlVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CatVariantImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct CatCollarImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct SheepColorImpl; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ShulkerColorImpl;