#![allow(dead_code)] use crate::Block; use crate::BlockId; use crate::data_component::DataComponent; use crate::entity_type::EntityType; use crate::sound::Sound; use crate::tag::Taggable; use crc_fast::CrcAlgorithm::Crc32Iscsi; use crc_fast::Digest; use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::tag::NbtTag; use std::any::Any; use std::borrow::Cow; pub trait DataComponentImpl: Send + Sync { fn write_data(&self) -> NbtTag { NbtTag::End } fn get_hash(&self) -> i32 { 0 } /// 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; } 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(), std::any::type_name_of_val(value), T::get_enum().to_name(), std::any::type_name::() ) }) } #[inline] pub fn get_mut(value: &mut dyn DataComponentImpl) -> &mut T { let name = value.get_self_enum().to_name(); let val_type = std::any::type_name_of_val(value); value.as_mut_any().downcast_mut::().unwrap_or_else(|| { panic!( "you are trying to cast {name} ({val_type}) to {} ({})", T::get_enum().to_name(), std::any::type_name::() ) }) } macro_rules! default_impl { ($t: ident) => { fn equal(&self, other: &dyn crate::data_component_impl::DataComponentImpl) -> bool { if let Some(other) = other.as_any().downcast_ref::() { self == other } else { false } } #[inline] fn get_enum() -> crate::data_component::DataComponent where Self: Sized, { crate::data_component::DataComponent::$t } fn get_self_enum(&self) -> crate::data_component::DataComponent { crate::data_component::DataComponent::$t } fn to_dyn(self) -> Box { Box::new(self) } fn clone_dyn(&self) -> Box { Box::new(self.clone()) } fn as_any(&self) -> &dyn std::any::Any { self } fn as_mut_any(&mut self) -> &mut dyn std::any::Any { self } }; } pub 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 } pub 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 } pub 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 } pub 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 } pub 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)); } } } pub 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 Some(sound_name) = sound_compound.get_string("sound_id") else { return IdOr::Id(default); }; let range = sound_compound.get_float("range"); IdOr::Value(basic::SoundEvent { sound_name: sound_name.to_string(), range, }) } else { IdOr::Id(default) } } pub 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 } 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> { BlockId::new(id).map(Self::from_id) } fn from_str(name: &str) -> Option<&'static Self> { Block::from_name(name) } fn to_string(&self) -> String { self.name.to_string() } } impl IDSetContent for crate::item::Item { fn registry_id(&self) -> u16 { self.id } fn from_id(id: u16) -> Option<&'static Self> { crate::item::Item::from_id(id) } fn from_str(name: &str) -> Option<&'static Self> { let name = name.strip_prefix("minecraft:").unwrap_or(name); crate::item::Item::from_registry_key(name) } fn to_string(&self) -> String { self.registry_key.to_string() } } #[derive(Clone, Hash, PartialEq, Debug)] pub enum IDSet { Tag(Cow<'static, str>), IDs(Cow<'static, [&'static T]>), } impl IDSet { pub 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_ref())?].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_ref()) { ids.push(instance); } } Some(Self::IDs(Cow::Owned(ids))) } _ => None, } } pub 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().into())) .collect(); compound.put_list(key, id_vec); } } } } #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum EquipmentType { Hand, HumanoidArmor, AnimalArmor, Saddle, } #[derive(Clone, Hash, Eq, PartialEq)] 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::AnimalArmor, 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 crate::tag::Tag), Single(&'static EntityType), } impl std::hash::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 enum IdOr { Id(Sound), Value(T), } pub mod basic; pub mod block_entity; pub mod book; pub mod combat; pub mod entity_variant; pub mod food; pub mod utility; // Re-export all component implementations pub use basic::*; pub use block_entity::*; pub use book::*; pub use combat::*; pub use entity_variant::*; pub use food::*; pub use utility::*; #[must_use] pub fn read_data(id: DataComponent, data: &NbtTag) -> Option> { match id { DataComponent::MaxStackSize => Some(MaxStackSizeImpl::read_data(data)?.to_dyn()), DataComponent::CustomData => Some(CustomDataImpl::read_data(data)?.to_dyn()), DataComponent::Enchantments => Some(EnchantmentsImpl::read_data(data)?.to_dyn()), DataComponent::Damage => Some(DamageImpl::read_data(data)?.to_dyn()), DataComponent::MaxDamage => Some(MaxDamageImpl::read_data(data)?.to_dyn()), DataComponent::Unbreakable => Some(UnbreakableImpl::read_data(data)?.to_dyn()), DataComponent::Food => Some(FoodImpl::read_data(data)?.to_dyn()), DataComponent::Tool => Some(ToolImpl::read_data(data)?.to_dyn()), DataComponent::Enchantable => Some(EnchantableImpl::read_data(data)?.to_dyn()), DataComponent::DamageResistant => Some(DamageResistantImpl::read_data(data)?.to_dyn()), DataComponent::PotionContents => Some(PotionContentsImpl::read_data(data)?.to_dyn()), DataComponent::PotionDurationScale => { Some(PotionDurationScaleImpl::read_data(data)?.to_dyn()) } DataComponent::SuspiciousStewEffects => { Some(SuspiciousStewEffectsImpl::read_data(data)?.to_dyn()) } DataComponent::Fireworks => Some(FireworksImpl::read_data(data)?.to_dyn()), DataComponent::FireworkExplosion => Some(FireworkExplosionImpl::read_data(data)?.to_dyn()), DataComponent::CustomName => Some(CustomNameImpl::read_data(data)?.to_dyn()), DataComponent::Lore => Some(LoreImpl::read_data(data)?.to_dyn()), DataComponent::ItemName => Some(ItemNameImpl::read_data(data)?.to_dyn()), DataComponent::ItemModel => Some(ItemModelImpl::read_data(data)?.to_dyn()), DataComponent::Consumable => Some(ConsumableImpl::read_data(data)?.to_dyn()), DataComponent::Equippable => Some(EquippableImpl::read_data(data)?.to_dyn()), DataComponent::AttackRange => Some(AttackRangeImpl::read_data(data)?.to_dyn()), DataComponent::KineticWeapon => Some(KineticWeaponImpl::read_data(data)?.to_dyn()), DataComponent::PiercingWeapon => Some(PiercingWeaponImpl::read_data(data)?.to_dyn()), DataComponent::MinimumAttackCharge => { Some(MinimumAttackChargeImpl::read_data(data)?.to_dyn()) } DataComponent::DamageType => Some(DamageTypeImpl::read_data(data)?.to_dyn()), DataComponent::StoredEnchantments => { Some(StoredEnchantmentsImpl::read_data(data)?.to_dyn()) } DataComponent::UseCooldown => Some(UseCooldownImpl::read_data(data)?.to_dyn()), DataComponent::RepairCost => Some(RepairCostImpl::read_data(data)?.to_dyn()), DataComponent::Repairable => Some(RepairableImpl::read_data(data)?.to_dyn()), DataComponent::MapId => Some(MapIdImpl::read_data(data)?.to_dyn()), DataComponent::MapPostProcessing => Some(MapPostProcessingImpl::read_data(data)?.to_dyn()), DataComponent::ChargedProjectiles => { Some(ChargedProjectilesImpl::read_data(data)?.to_dyn()) } DataComponent::BlockEntityData => Some(BlockEntityDataImpl::read_data(data)?.to_dyn()), DataComponent::BundleContents => Some(BundleContentsImpl::read_data(data)?.to_dyn()), DataComponent::Container => Some(ContainerImpl::read_data(data)?.to_dyn()), DataComponent::WrittenBookContent => { Some(WrittenBookContentImpl::read_data(data)?.to_dyn()) } DataComponent::WritableBookContent => { Some(WritableBookContentImpl::read_data(data)?.to_dyn()) } DataComponent::OminousBottleAmplifier => { Some(OminousBottleAmplifierImpl::read_data(data)?.to_dyn()) } DataComponent::BlockState => Some(BlockStateImpl::read_data(data)?.to_dyn()), DataComponent::Profile => Some(ProfileImpl::read_data(data)?.to_dyn()), DataComponent::ChickenVariant => Some(ChickenVariantImpl::read_data(data)?.to_dyn()), DataComponent::VillagerVariant => Some(VillagerVariantImpl::read_data(data)?.to_dyn()), DataComponent::WolfVariant => Some(WolfVariantImpl::read_data(data)?.to_dyn()), DataComponent::WolfSoundVariant => Some(WolfSoundVariantImpl::read_data(data)?.to_dyn()), DataComponent::WolfCollar => Some(WolfCollarImpl::read_data(data)?.to_dyn()), DataComponent::FoxVariant => Some(FoxVariantImpl::read_data(data)?.to_dyn()), DataComponent::SalmonSize => Some(SalmonSizeImpl::read_data(data)?.to_dyn()), DataComponent::ParrotVariant => Some(ParrotVariantImpl::read_data(data)?.to_dyn()), DataComponent::TropicalFishPattern => { Some(TropicalFishPatternImpl::read_data(data)?.to_dyn()) } DataComponent::TropicalFishBaseColor => { Some(TropicalFishBaseColorImpl::read_data(data)?.to_dyn()) } DataComponent::TropicalFishPatternColor => { Some(TropicalFishPatternColorImpl::read_data(data)?.to_dyn()) } DataComponent::MooshroomVariant => Some(MooshroomVariantImpl::read_data(data)?.to_dyn()), DataComponent::RabbitVariant => Some(RabbitVariantImpl::read_data(data)?.to_dyn()), DataComponent::PigVariant => Some(PigVariantImpl::read_data(data)?.to_dyn()), DataComponent::PigSoundVariant => Some(PigSoundVariantImpl::read_data(data)?.to_dyn()), DataComponent::CowVariant => Some(CowVariantImpl::read_data(data)?.to_dyn()), DataComponent::CowSoundVariant => Some(CowSoundVariantImpl::read_data(data)?.to_dyn()), DataComponent::ChickenSoundVariant => { Some(ChickenSoundVariantImpl::read_data(data)?.to_dyn()) } DataComponent::ZombieNautilusVariant => { Some(ZombieNautilusVariantImpl::read_data(data)?.to_dyn()) } DataComponent::FrogVariant => Some(FrogVariantImpl::read_data(data)?.to_dyn()), DataComponent::HorseVariant => Some(HorseVariantImpl::read_data(data)?.to_dyn()), DataComponent::PaintingVariant => Some(PaintingVariantImpl::read_data(data)?.to_dyn()), DataComponent::LlamaVariant => Some(LlamaVariantImpl::read_data(data)?.to_dyn()), DataComponent::AxolotlVariant => Some(AxolotlVariantImpl::read_data(data)?.to_dyn()), DataComponent::CatVariant => Some(CatVariantImpl::read_data(data)?.to_dyn()), DataComponent::CatSoundVariant => Some(CatSoundVariantImpl::read_data(data)?.to_dyn()), DataComponent::CatCollar => Some(CatCollarImpl::read_data(data)?.to_dyn()), DataComponent::SheepColor => Some(SheepColorImpl::read_data(data)?.to_dyn()), DataComponent::ShulkerColor => Some(ShulkerColorImpl::read_data(data)?.to_dyn()), DataComponent::DyedColor => Some(DyedColorImpl::read_data(data)?.to_dyn()), DataComponent::BaseColor => Some(BaseColorImpl::read_data(data)?.to_dyn()), DataComponent::NoteBlockSound => Some(NoteBlockSoundImpl::read_data(data)?.to_dyn()), DataComponent::TooltipStyle => Some(TooltipStyleImpl::read_data(data)?.to_dyn()), DataComponent::Lock => Some(LockImpl::read_data(data)?.to_dyn()), DataComponent::ContainerLoot => Some(ContainerLootImpl::read_data(data)?.to_dyn()), DataComponent::CustomModelData => Some(CustomModelDataImpl::read_data(data)?.to_dyn()), DataComponent::LodestoneTracker => Some(LodestoneTrackerImpl::read_data(data)?.to_dyn()), DataComponent::Glider => Some(GliderImpl::read_data(data)?.to_dyn()), DataComponent::IntangibleProjectile => { Some(IntangibleProjectileImpl::read_data(data)?.to_dyn()) } DataComponent::Trim => Some(TrimImpl::read_data(data)?.to_dyn()), DataComponent::CanPlaceOn => Some(CanPlaceOnImpl::read_data(data)?.to_dyn()), DataComponent::CanBreak => Some(CanBreakImpl::read_data(data)?.to_dyn()), DataComponent::SwingAnimation => Some(SwingAnimationImpl::read_data(data)?.to_dyn()), DataComponent::Rarity => Some(RarityImpl::read_data(data)?.to_dyn()), DataComponent::BannerPatterns => Some(BannerPatternsImpl::read_data(data)?.to_dyn()), _ => None, } } #[cfg(test)] mod tests { use super::*; #[test] fn hash() { assert_eq!(get_str_hash("minecraft:sharpness"), 2734053906u32); assert_eq!(get_i32_hash(3), 3795317917u32); assert_eq!( EnchantmentsImpl { enchantment: Cow::Borrowed(&[(&crate::Enchantment::SHARPNESS, 2)]), } .get_hash(), -1580618251i32 ); assert_eq!(MaxStackSizeImpl { size: 99 }.get_hash(), -1632321551i32); assert_eq!(MapIdImpl { id: 10 }.get_hash(), -919192125i32); } fn assert_round_trip( value: T, read: impl Fn(&NbtTag) -> Option, ) { let restored = read(&value.write_data()).expect("read_data returned None"); assert!(value.equal(&restored)); } #[test] fn max_damage_round_trip() { assert_round_trip(MaxDamageImpl { max_damage: 1561 }, MaxDamageImpl::read_data); } #[test] fn enchantable_round_trip() { assert_round_trip(EnchantableImpl { value: 14 }, EnchantableImpl::read_data); } #[test] fn food_round_trip() { assert_round_trip( FoodImpl { nutrition: 4, saturation: 2.4, can_always_eat: true, }, FoodImpl::read_data, ); } #[test] fn block_entity_data_round_trip() { let mut nbt = NbtCompound::new(); nbt.put_string("id", "minecraft:chest".to_string()); nbt.put_int("x", 12); assert_round_trip(BlockEntityDataImpl { nbt }, BlockEntityDataImpl::read_data); } #[test] fn tool_round_trip() { assert_round_trip( ToolImpl { rules: Cow::Owned(vec![ ToolRule { blocks: IDSet::Tag(Cow::Borrowed("mineable/pickaxe")), speed: Some(6.0), correct_for_drops: Some(true), }, ToolRule { blocks: IDSet::Tag(Cow::Borrowed("incorrect_for_wooden_tool")), speed: None, correct_for_drops: Some(false), }, ]), default_mining_speed: 1.0, damage_per_block: 2, can_destroy_blocks_in_creative: false, }, ToolImpl::read_data, ); } #[test] fn dyed_color_round_trip() { assert_round_trip(DyedColorImpl { rgb: 0x00A0F0 }, DyedColorImpl::read_data); } #[test] fn base_color_round_trip() { assert_round_trip( BaseColorImpl { color: "light_blue".to_string(), }, BaseColorImpl::read_data, ); } #[test] fn note_block_sound_round_trip() { assert_round_trip( NoteBlockSoundImpl { sound: "minecraft:block.note_block.pling".to_string(), }, NoteBlockSoundImpl::read_data, ); } #[test] fn tooltip_style_round_trip() { assert_round_trip( TooltipStyleImpl { id: "minecraft:my_style".to_string(), }, TooltipStyleImpl::read_data, ); } #[test] fn container_loot_round_trip() { assert_round_trip( ContainerLootImpl { loot_table: "minecraft:chests/simple_dungeon".to_string(), seed: 123_456_789, }, ContainerLootImpl::read_data, ); } #[test] fn custom_model_data_round_trip() { assert_round_trip( CustomModelDataImpl { floats: vec![1.5, -2.0], flags: vec![true, false, true], strings: vec!["a".to_string(), "b".to_string()], colors: vec![0xFF0000, 0x00FF00], }, CustomModelDataImpl::read_data, ); } #[test] fn lodestone_tracker_round_trip() { assert_round_trip( LodestoneTrackerImpl { target: Some(LodestoneTarget { dimension: "minecraft:overworld".to_string(), x: 10, y: 64, z: -30, }), tracked: true, }, LodestoneTrackerImpl::read_data, ); assert_round_trip( LodestoneTrackerImpl { target: None, tracked: false, }, LodestoneTrackerImpl::read_data, ); } #[test] fn marker_components_round_trip() { assert_round_trip(GliderImpl, GliderImpl::read_data); assert_round_trip( IntangibleProjectileImpl, IntangibleProjectileImpl::read_data, ); } #[test] fn raw_nbt_components_round_trip() { let mut material = NbtCompound::new(); material.put_string("material", "minecraft:diamond".to_string()); material.put_string("pattern", "minecraft:coast".to_string()); assert_round_trip( TrimImpl::read_data(&NbtTag::Compound(material)).unwrap(), TrimImpl::read_data, ); let mut predicate = NbtCompound::new(); predicate.put_string("blocks", "minecraft:stone".to_string()); assert_round_trip( CanPlaceOnImpl { predicate: NbtTag::Compound(predicate.clone()), }, CanPlaceOnImpl::read_data, ); assert_round_trip(LockImpl { predicate }, LockImpl::read_data); } #[test] fn equal_with_different_types_returns_false() { let enc = EnchantmentsImpl { enchantment: Cow::Borrowed(&[(&crate::Enchantment::SHARPNESS, 2)]), }; let max_stack = MaxStackSizeImpl { size: 64 }; assert!(!enc.equal(&max_stack)); } #[test] fn enchantments_read_data_formats() { let mut direct = NbtCompound::new(); direct.put_int("sharpness", 2); let enc1 = EnchantmentsImpl::read_data(&NbtTag::Compound(direct)).unwrap(); assert!(enc1.enchantment[0].0 == &crate::Enchantment::SHARPNESS); assert_eq!(enc1.enchantment[0].1, 2); let mut levels = NbtCompound::new(); levels.put_int("minecraft:sharpness", 2); let mut wrapped = NbtCompound::new(); wrapped .child_tags .insert("levels".into(), NbtTag::Compound(levels)); let enc2 = EnchantmentsImpl::read_data(&NbtTag::Compound(wrapped)).unwrap(); assert_eq!(enc2.enchantment.len(), 1); assert!(enc2.enchantment[0].0 == &crate::Enchantment::SHARPNESS); assert_eq!(enc2.enchantment[0].1, 2); } #[test] fn swing_animation_round_trip() { assert_round_trip( SwingAnimationImpl { animation_type: SwingAnimationType::Stab, duration: 19, }, SwingAnimationImpl::read_data, ); assert_round_trip(SwingAnimationImpl::DEFAULT, SwingAnimationImpl::read_data); } }