893 lines
30 KiB
Rust
893 lines
30 KiB
Rust
#![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<dyn DataComponentImpl>;
|
|
fn clone_dyn(&self) -> Box<dyn DataComponentImpl>;
|
|
fn as_any(&self) -> &dyn Any;
|
|
fn as_mut_any(&mut self) -> &mut dyn Any;
|
|
}
|
|
|
|
impl Clone for Box<dyn DataComponentImpl> {
|
|
fn clone(&self) -> Self {
|
|
self.clone_dyn()
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get<T: DataComponentImpl + 'static>(value: &dyn DataComponentImpl) -> &T {
|
|
value.as_any().downcast_ref::<T>().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::<T>()
|
|
)
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_mut<T: DataComponentImpl + 'static>(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::<T>().unwrap_or_else(|| {
|
|
panic!(
|
|
"you are trying to cast {name} ({val_type}) to {} ({})",
|
|
T::get_enum().to_name(),
|
|
std::any::type_name::<T>()
|
|
)
|
|
})
|
|
}
|
|
|
|
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>() {
|
|
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<dyn crate::data_component_impl::DataComponentImpl> {
|
|
Box::new(self)
|
|
}
|
|
fn clone_dyn(&self) -> Box<dyn crate::data_component_impl::DataComponentImpl> {
|
|
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<basic::SoundEvent>) -> 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<basic::SoundEvent>) {
|
|
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<basic::SoundEvent> {
|
|
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<T: IDSetContent>(val: &IDSet<T>) -> 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<T: IDSetContent + 'static> {
|
|
Tag(Cow<'static, str>),
|
|
IDs(Cow<'static, [&'static T]>),
|
|
}
|
|
|
|
impl<T: IDSetContent + 'static> IDSet<T> {
|
|
pub fn read(data: &NbtTag) -> Option<Self> {
|
|
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<NbtTag> = 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<H: std::hash::Hasher>(&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<T> {
|
|
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<Box<dyn DataComponentImpl>> {
|
|
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<T: DataComponentImpl + Clone + 'static>(
|
|
value: T,
|
|
read: impl Fn(&NbtTag) -> Option<T>,
|
|
) {
|
|
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);
|
|
}
|
|
}
|