use std::{any::Any, fmt::Debug, mem::ManuallyDrop, str::FromStr}; use azalea_registry::builtin::{EnchantmentEffectComponentKind, SoundEvent}; use simdnbt::{ DeserializeError, borrow::{NbtCompound, NbtList, NbtTag}, }; use crate::registry_holder::{ entity_effect::EntityEffect, get_in_compound, value::{AttributeEffect, ValueEffect}, }; #[macro_export] macro_rules! define_effect_components { ( $( $x:ident: $t:ty ),* $(,)? ) => { #[allow(non_snake_case)] pub union EffectComponentUnion { $( $x: ManuallyDrop<$t>, )* } impl EffectComponentUnion { /// # Safety /// /// `kind` must be the correct value for this union. pub unsafe fn drop_as(&mut self, kind: EnchantmentEffectComponentKind) { match kind { $( EnchantmentEffectComponentKind::$x => { unsafe { ManuallyDrop::drop(&mut self.$x) } }, )* } } pub fn from_effect_nbt_tag_as( kind: EnchantmentEffectComponentKind, tag: EffectNbtTag, ) -> Result { Ok(match kind { $( EnchantmentEffectComponentKind::$x => { Self { $x: ManuallyDrop::new(<$t>::from_effect_nbt_tag(tag)?) } }, )* }) } /// # Safety /// /// `kind` must be the correct value for this union. pub unsafe fn clone_as( &self, kind: EnchantmentEffectComponentKind, ) -> Self { match kind { $( EnchantmentEffectComponentKind::$x => { Self { $x: unsafe { self.$x.clone() } } }, )* } } /// # Safety /// /// `kind` must be the correct value for this union. pub unsafe fn as_kind(&self, kind: EnchantmentEffectComponentKind) -> &dyn ResolvedEffectComponent { match kind { $( EnchantmentEffectComponentKind::$x => { unsafe { &**(&self.$x as &ManuallyDrop) } }, )* } } } $( impl EffectComponentTrait for $t { const KIND: EnchantmentEffectComponentKind = EnchantmentEffectComponentKind::$x; } )* }; } define_effect_components!( DamageProtection: DamageProtection, DamageImmunity: ConditionalEffect, Damage: Damage, SmashDamagePerFallenBlock: SmashDamagePerFallenBlock, Knockback: Knockback, ArmorEffectiveness: ArmorEffectiveness, PostAttack: PostAttack, PostPiercingAttack: PostPiercingAttack, HitBlock: ConditionalEntityEffect, ItemDamage: ConditionalValueEffect, Attributes: AttributeEffect, EquipmentDrops: EquipmentDrops, LocationChanged: ConditionalEffect, Tick: Tick, AmmoUse: AmmoUse, ProjectilePiercing: ProjectilePiercing, ProjectileSpawned: ProjectileSpawned, ProjectileSpread: ProjectileSpread, ProjectileCount: ProjectileCount, TridentReturnAcceleration: TridentReturnAcceleration, FishingTimeReduction: FishingTimeReduction, FishingLuckBonus: FishingLuckBonus, BlockExperience: BlockExperience, MobExperience: MobExperience, RepairWithXp: RepairWithXp, CrossbowChargeTime: CrossbowChargeTime, CrossbowChargingSounds: CrossbowChargingSounds, TridentSound: TridentSound, PreventEquipmentDrop: PreventEquipmentDrop, PreventArmorChange: PreventArmorChange, TridentSpinAttackStrength: TridentSpinAttackStrength, ); /// A trait that's implemented on all effect components so we can access them /// from [`EnchantmentData::get`](super::enchantment::EnchantmentData::get). pub trait EffectComponentTrait: Any { const KIND: EnchantmentEffectComponentKind; } // this exists because EffectComponentTrait isn't dyn-compatible pub trait ResolvedEffectComponent: Any {} impl ResolvedEffectComponent for T {} /// An alternative to `simdnbt::borrow::NbtTag` used internally when /// deserializing effects. /// /// When deserializing effect components from the registry, we're given NBT tags /// in either a list of compounds or a list of lists. This means that we can't /// just use `from_nbt_tag`, because `borrow::NbtTag` can't be constructed on /// its own. To work around this, we have this `EffectNbtTag` struct that we /// *can* construct that we use when deserializing. #[derive(Clone, Copy, Debug)] pub enum EffectNbtTag<'a, 'tape> { Compound(NbtCompound<'a, 'tape>), List(NbtList<'a, 'tape>), } impl<'a, 'tape> EffectNbtTag<'a, 'tape> { pub fn compound(self, error_name: &str) -> Result, DeserializeError> { if let Self::Compound(nbt) = self { Ok(nbt) } else { Err(DeserializeError::MismatchedFieldType(error_name.to_owned())) } } pub fn list(self, error_name: &str) -> Result, DeserializeError> { if let Self::List(nbt) = self { Ok(nbt) } else { Err(DeserializeError::MismatchedFieldType(error_name.to_owned())) } } } macro_rules! impl_from_effect_nbt_tag { (<$g:tt : $generic_type:tt $(::$generic_type2:tt)* $(+ $generic_type3:tt)+> $ty:ident <$generic_name:ident>) => { impl<$g: $generic_type$(::$generic_type2)* $(+ $generic_type3)+> $ty<$generic_name> { fn from_effect_nbt_tag(nbt: crate::registry_holder::components::EffectNbtTag) -> Result { let nbt = nbt.compound(stringify!($ty))?; simdnbt::Deserialize::from_compound(nbt) } } }; ($ty:ident) => { impl $ty { pub fn from_effect_nbt_tag(nbt: crate::registry_holder::components::EffectNbtTag) -> Result { let nbt = nbt.compound(stringify!($ty))?; simdnbt::Deserialize::from_compound(nbt) } } }; } pub(crate) use impl_from_effect_nbt_tag; #[derive(Clone, Debug)] pub struct ConditionalEffect { pub effect: T, // pub requirements } #[derive(Clone, Debug)] pub struct TargetedConditionalEffect { pub effect: T, // pub enchanted // pub affected // pub requirements } // makes for cleaner-looking types type ConditionalValueEffect = ConditionalEffect; type ConditionalEntityEffect = ConditionalEffect; impl simdnbt::Deserialize for ConditionalEffect { fn from_compound(nbt: NbtCompound) -> Result { let effect = get_in_compound(&nbt, "effect")?; Ok(Self { effect }) } } impl_from_effect_nbt_tag!( ConditionalEffect); impl simdnbt::Deserialize for TargetedConditionalEffect { fn from_compound(nbt: NbtCompound) -> Result { let effect = get_in_compound(&nbt, "effect")?; Ok(Self { effect }) } } macro_rules! declare_newtype_components { ( $( $struct_name:ident: $inner_type:ty ),* $(,)? ) => { $( #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct $struct_name(pub $inner_type); impl_from_effect_nbt_tag!($struct_name); )* }; } declare_newtype_components! { DamageProtection: ConditionalValueEffect, Damage: ConditionalValueEffect, SmashDamagePerFallenBlock: ConditionalValueEffect, Knockback: ConditionalValueEffect, ArmorEffectiveness: ConditionalValueEffect, PostAttack: TargetedConditionalEffect, PostPiercingAttack: ConditionalEffect, HitBlock: ConditionalEntityEffect, ItemDamage: ConditionalValueEffect, EquipmentDrops: ConditionalValueEffect, Tick: ConditionalEntityEffect, AmmoUse: ConditionalValueEffect, ProjectilePiercing: ConditionalValueEffect, ProjectileSpawned: ConditionalEntityEffect, ProjectileSpread: ConditionalValueEffect, ProjectileCount: ConditionalValueEffect, TridentReturnAcceleration: ConditionalValueEffect, FishingTimeReduction: ConditionalValueEffect, FishingLuckBonus: ConditionalValueEffect, BlockExperience: ConditionalValueEffect, MobExperience: ConditionalValueEffect, RepairWithXp: ConditionalValueEffect, CrossbowChargeTime: ValueEffect, TridentSpinAttackStrength: ValueEffect, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct DamageImmunity {} impl_from_effect_nbt_tag!(DamageImmunity); #[derive(Clone, Debug)] pub struct CrossbowChargingSounds(pub Vec); impl simdnbt::FromNbtTag for CrossbowChargingSounds { fn from_nbt_tag(tag: NbtTag) -> Option { simdnbt::FromNbtTag::from_nbt_tag(tag).map(Self) } } impl CrossbowChargingSounds { pub fn from_effect_nbt_tag(nbt: EffectNbtTag) -> Result { let Ok(nbt) = nbt.list("CrossbowChargingSounds") else { return Ok(Self(vec![simdnbt::Deserialize::from_compound( nbt.compound("CrossbowChargingSounds")?, )?])); }; Ok(Self( nbt.compounds() .ok_or_else(|| { DeserializeError::MismatchedFieldType("CrossbowChargingSounds".to_owned()) })? .into_iter() .map(|c| simdnbt::Deserialize::from_compound(c)) .collect::>()?, )) } } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct CrossbowChargingSound { pub start: Option, pub mid: Option, pub end: Option, } #[derive(Clone, Debug)] pub struct TridentSound(pub Vec); impl simdnbt::FromNbtTag for TridentSound { fn from_nbt_tag(tag: NbtTag) -> Option { let sounds = tag.list()?.strings()?; sounds .iter() .map(|s| SoundEvent::from_str(&s.to_str()).ok()) .collect::>>() .map(Self) } } impl TridentSound { pub fn from_effect_nbt_tag(nbt: EffectNbtTag) -> Result { let sounds = nbt .list("TridentSound")? .strings() .ok_or_else(|| DeserializeError::MismatchedFieldType("TridentSound".to_owned()))?; sounds .iter() .map(|s| SoundEvent::from_str(&s.to_str()).ok()) .collect::>>() .ok_or_else(|| DeserializeError::MismatchedFieldType("TridentSound".to_owned())) .map(Self) } } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct LocationBasedEffect { // TODO } impl_from_effect_nbt_tag!(LocationBasedEffect); #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct PreventEquipmentDrop {} impl_from_effect_nbt_tag!(PreventEquipmentDrop); #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct PreventArmorChange {} impl_from_effect_nbt_tag!(PreventArmorChange);