azalea_core/registry_holder/
components.rs

1use std::{any::Any, fmt::Debug, mem::ManuallyDrop, str::FromStr};
2
3use azalea_registry::builtin::{EnchantmentEffectComponentKind, SoundEvent};
4use simdnbt::{
5    DeserializeError,
6    borrow::{NbtCompound, NbtList, NbtTag},
7};
8
9use crate::registry_holder::{
10    entity_effect::EntityEffect,
11    get_in_compound,
12    value::{AttributeEffect, ValueEffect},
13};
14
15#[macro_export]
16macro_rules! define_effect_components {
17    ( $( $x:ident: $t:ty ),* $(,)? ) => {
18        #[allow(non_snake_case)]
19        pub union EffectComponentUnion {
20            $( $x: ManuallyDrop<$t>, )*
21        }
22
23        impl EffectComponentUnion {
24            /// # Safety
25            ///
26            /// `kind` must be the correct value for this union.
27            pub unsafe fn drop_as(&mut self, kind: EnchantmentEffectComponentKind) {
28                match kind {
29                    $( EnchantmentEffectComponentKind::$x => { unsafe { ManuallyDrop::drop(&mut self.$x) } }, )*
30                }
31            }
32
33            pub fn from_effect_nbt_tag_as(
34                kind: EnchantmentEffectComponentKind,
35                tag: EffectNbtTag,
36            ) -> Result<Self, DeserializeError> {
37                Ok(match kind {
38                    $( EnchantmentEffectComponentKind::$x => {
39                        Self { $x: ManuallyDrop::new(<$t>::from_effect_nbt_tag(tag)?) }
40                    }, )*
41                })
42            }
43
44            /// # Safety
45            ///
46            /// `kind` must be the correct value for this union.
47            pub unsafe fn clone_as(
48                &self,
49                kind: EnchantmentEffectComponentKind,
50            ) -> Self {
51                match kind {
52                    $( EnchantmentEffectComponentKind::$x => {
53                        Self { $x: unsafe { self.$x.clone() } }
54                    }, )*
55                }
56            }
57
58            /// # Safety
59            ///
60            /// `kind` must be the correct value for this union.
61            pub unsafe fn as_kind(&self, kind: EnchantmentEffectComponentKind) -> &dyn ResolvedEffectComponent {
62                match kind {
63                    $( EnchantmentEffectComponentKind::$x => { unsafe { &**(&self.$x as &ManuallyDrop<dyn ResolvedEffectComponent>) } }, )*
64                }
65            }
66        }
67
68        $(
69            impl EffectComponentTrait for $t {
70                const KIND: EnchantmentEffectComponentKind = EnchantmentEffectComponentKind::$x;
71            }
72        )*
73    };
74}
75
76define_effect_components!(
77    DamageProtection: DamageProtection,
78    DamageImmunity: ConditionalEffect<DamageImmunity>,
79    Damage: Damage,
80    SmashDamagePerFallenBlock: SmashDamagePerFallenBlock,
81    Knockback: Knockback,
82    ArmorEffectiveness: ArmorEffectiveness,
83    PostAttack: PostAttack,
84    PostPiercingAttack: PostPiercingAttack,
85    HitBlock: ConditionalEntityEffect,
86    ItemDamage: ConditionalValueEffect,
87    Attributes: AttributeEffect,
88    EquipmentDrops: EquipmentDrops,
89    LocationChanged: ConditionalEffect<LocationBasedEffect>,
90    Tick: Tick,
91    AmmoUse: AmmoUse,
92    ProjectilePiercing: ProjectilePiercing,
93    ProjectileSpawned: ProjectileSpawned,
94    ProjectileSpread: ProjectileSpread,
95    ProjectileCount: ProjectileCount,
96    TridentReturnAcceleration: TridentReturnAcceleration,
97    FishingTimeReduction: FishingTimeReduction,
98    FishingLuckBonus: FishingLuckBonus,
99    BlockExperience: BlockExperience,
100    MobExperience: MobExperience,
101    RepairWithXp: RepairWithXp,
102    CrossbowChargeTime: CrossbowChargeTime,
103    CrossbowChargingSounds: CrossbowChargingSounds,
104    TridentSound: TridentSound,
105    PreventEquipmentDrop: PreventEquipmentDrop,
106    PreventArmorChange: PreventArmorChange,
107    TridentSpinAttackStrength: TridentSpinAttackStrength,
108);
109
110/// A trait that's implemented on all effect components so we can access them
111/// from [`EnchantmentData::get`](super::enchantment::EnchantmentData::get).
112pub trait EffectComponentTrait: Any {
113    const KIND: EnchantmentEffectComponentKind;
114}
115
116// this exists because EffectComponentTrait isn't dyn-compatible
117pub trait ResolvedEffectComponent: Any {}
118impl<T: EffectComponentTrait> ResolvedEffectComponent for T {}
119
120/// An alternative to `simdnbt::borrow::NbtTag` used internally when
121/// deserializing effects.
122///
123/// When deserializing effect components from the registry, we're given NBT tags
124/// in either a list of compounds or a list of lists. This means that we can't
125/// just use `from_nbt_tag`, because `borrow::NbtTag` can't be constructed on
126/// its own. To work around this, we have this `EffectNbtTag` struct that we
127/// *can* construct that we use when deserializing.
128#[derive(Clone, Copy, Debug)]
129pub enum EffectNbtTag<'a, 'tape> {
130    Compound(NbtCompound<'a, 'tape>),
131    List(NbtList<'a, 'tape>),
132}
133
134impl<'a, 'tape> EffectNbtTag<'a, 'tape> {
135    pub fn compound(self, error_name: &str) -> Result<NbtCompound<'a, 'tape>, DeserializeError> {
136        if let Self::Compound(nbt) = self {
137            Ok(nbt)
138        } else {
139            Err(DeserializeError::MismatchedFieldType(error_name.to_owned()))
140        }
141    }
142    pub fn list(self, error_name: &str) -> Result<NbtList<'a, 'tape>, DeserializeError> {
143        if let Self::List(nbt) = self {
144            Ok(nbt)
145        } else {
146            Err(DeserializeError::MismatchedFieldType(error_name.to_owned()))
147        }
148    }
149}
150macro_rules! impl_from_effect_nbt_tag {
151    (<$g:tt : $generic_type:tt $(::$generic_type2:tt)* $(+ $generic_type3:tt)+> $ty:ident <$generic_name:ident>) => {
152        impl<$g: $generic_type$(::$generic_type2)* $(+ $generic_type3)+> $ty<$generic_name> {
153            fn from_effect_nbt_tag(nbt: crate::registry_holder::components::EffectNbtTag) -> Result<Self, DeserializeError> {
154                let nbt = nbt.compound(stringify!($ty))?;
155                simdnbt::Deserialize::from_compound(nbt)
156            }
157        }
158    };
159    ($ty:ident) => {
160        impl $ty {
161            pub fn from_effect_nbt_tag(nbt: crate::registry_holder::components::EffectNbtTag) -> Result<Self, DeserializeError> {
162                let nbt = nbt.compound(stringify!($ty))?;
163                simdnbt::Deserialize::from_compound(nbt)
164            }
165        }
166    };
167}
168pub(crate) use impl_from_effect_nbt_tag;
169
170#[derive(Clone, Debug)]
171pub struct ConditionalEffect<T: simdnbt::Deserialize + Debug + Clone> {
172    pub effect: T,
173    // pub requirements
174}
175#[derive(Clone, Debug)]
176pub struct TargetedConditionalEffect<T: simdnbt::Deserialize + Debug + Clone> {
177    pub effect: T,
178    // pub enchanted
179    // pub affected
180    // pub requirements
181}
182
183// makes for cleaner-looking types
184type ConditionalValueEffect = ConditionalEffect<ValueEffect>;
185type ConditionalEntityEffect = ConditionalEffect<EntityEffect>;
186
187impl<T: simdnbt::Deserialize + Debug + Clone> simdnbt::Deserialize for ConditionalEffect<T> {
188    fn from_compound(nbt: NbtCompound) -> Result<Self, DeserializeError> {
189        let effect = get_in_compound(&nbt, "effect")?;
190        Ok(Self { effect })
191    }
192}
193impl_from_effect_nbt_tag!(<T: simdnbt::Deserialize + Debug + Clone> ConditionalEffect<T>);
194
195impl<T: simdnbt::Deserialize + Debug + Clone> simdnbt::Deserialize
196    for TargetedConditionalEffect<T>
197{
198    fn from_compound(nbt: NbtCompound) -> Result<Self, DeserializeError> {
199        let effect = get_in_compound(&nbt, "effect")?;
200        Ok(Self { effect })
201    }
202}
203
204macro_rules! declare_newtype_components {
205    ( $( $struct_name:ident: $inner_type:ty ),* $(,)? ) => {
206        $(
207            #[derive(Clone, Debug, simdnbt::Deserialize)]
208            pub struct $struct_name(pub $inner_type);
209            impl_from_effect_nbt_tag!($struct_name);
210        )*
211    };
212}
213
214declare_newtype_components! {
215    DamageProtection: ConditionalValueEffect,
216    Damage: ConditionalValueEffect,
217    SmashDamagePerFallenBlock: ConditionalValueEffect,
218    Knockback: ConditionalValueEffect,
219    ArmorEffectiveness: ConditionalValueEffect,
220    PostAttack: TargetedConditionalEffect<EntityEffect>,
221    PostPiercingAttack: ConditionalEffect<EntityEffect>,
222    HitBlock: ConditionalEntityEffect,
223    ItemDamage: ConditionalValueEffect,
224    EquipmentDrops: ConditionalValueEffect,
225    Tick: ConditionalEntityEffect,
226    AmmoUse: ConditionalValueEffect,
227    ProjectilePiercing: ConditionalValueEffect,
228    ProjectileSpawned: ConditionalEntityEffect,
229    ProjectileSpread: ConditionalValueEffect,
230    ProjectileCount: ConditionalValueEffect,
231    TridentReturnAcceleration: ConditionalValueEffect,
232    FishingTimeReduction: ConditionalValueEffect,
233    FishingLuckBonus: ConditionalValueEffect,
234    BlockExperience: ConditionalValueEffect,
235    MobExperience: ConditionalValueEffect,
236    RepairWithXp: ConditionalValueEffect,
237    CrossbowChargeTime: ValueEffect,
238    TridentSpinAttackStrength: ValueEffect,
239
240}
241
242#[derive(Clone, Debug, simdnbt::Deserialize)]
243pub struct DamageImmunity {}
244impl_from_effect_nbt_tag!(DamageImmunity);
245
246#[derive(Clone, Debug)]
247pub struct CrossbowChargingSounds(pub Vec<CrossbowChargingSound>);
248impl simdnbt::FromNbtTag for CrossbowChargingSounds {
249    fn from_nbt_tag(tag: NbtTag) -> Option<Self> {
250        simdnbt::FromNbtTag::from_nbt_tag(tag).map(Self)
251    }
252}
253impl CrossbowChargingSounds {
254    pub fn from_effect_nbt_tag(nbt: EffectNbtTag) -> Result<Self, DeserializeError> {
255        let Ok(nbt) = nbt.list("CrossbowChargingSounds") else {
256            return Ok(Self(vec![simdnbt::Deserialize::from_compound(
257                nbt.compound("CrossbowChargingSounds")?,
258            )?]));
259        };
260
261        Ok(Self(
262            nbt.compounds()
263                .ok_or_else(|| {
264                    DeserializeError::MismatchedFieldType("CrossbowChargingSounds".to_owned())
265                })?
266                .into_iter()
267                .map(|c| simdnbt::Deserialize::from_compound(c))
268                .collect::<Result<_, _>>()?,
269        ))
270    }
271}
272
273#[derive(Clone, Debug, simdnbt::Deserialize)]
274pub struct CrossbowChargingSound {
275    pub start: Option<SoundEvent>,
276    pub mid: Option<SoundEvent>,
277    pub end: Option<SoundEvent>,
278}
279
280#[derive(Clone, Debug)]
281pub struct TridentSound(pub Vec<SoundEvent>);
282impl simdnbt::FromNbtTag for TridentSound {
283    fn from_nbt_tag(tag: NbtTag) -> Option<Self> {
284        let sounds = tag.list()?.strings()?;
285
286        sounds
287            .iter()
288            .map(|s| SoundEvent::from_str(&s.to_str()).ok())
289            .collect::<Option<Vec<_>>>()
290            .map(Self)
291    }
292}
293impl TridentSound {
294    pub fn from_effect_nbt_tag(nbt: EffectNbtTag) -> Result<Self, DeserializeError> {
295        let sounds = nbt
296            .list("TridentSound")?
297            .strings()
298            .ok_or_else(|| DeserializeError::MismatchedFieldType("TridentSound".to_owned()))?;
299
300        sounds
301            .iter()
302            .map(|s| SoundEvent::from_str(&s.to_str()).ok())
303            .collect::<Option<Vec<_>>>()
304            .ok_or_else(|| DeserializeError::MismatchedFieldType("TridentSound".to_owned()))
305            .map(Self)
306    }
307}
308
309#[derive(Clone, Debug, simdnbt::Deserialize)]
310pub struct LocationBasedEffect {
311    // TODO
312}
313impl_from_effect_nbt_tag!(LocationBasedEffect);
314
315#[derive(Clone, Debug, simdnbt::Deserialize)]
316pub struct PreventEquipmentDrop {}
317impl_from_effect_nbt_tag!(PreventEquipmentDrop);
318
319#[derive(Clone, Debug, simdnbt::Deserialize)]
320pub struct PreventArmorChange {}
321impl_from_effect_nbt_tag!(PreventArmorChange);