azalea_core/registry_holder/
entity_effect.rs

1use std::{collections::HashMap, str::FromStr};
2
3use azalea_registry::{
4    Holder,
5    builtin::{
6        EnchantmentEntityEffectKind as EntityEffectKind, GameEvent, ParticleKind, SoundEvent,
7    },
8    data::DamageKindKey,
9    identifier::Identifier,
10};
11use simdnbt::{
12    Deserialize, DeserializeError,
13    borrow::{NbtCompound, NbtTag},
14};
15use tracing::error;
16
17use crate::{
18    position::{Vec3, Vec3i},
19    registry_holder::{
20        block_predicate::BlockPredicate, block_state_provider::BlockStateProvider,
21        float_provider::FloatProvider, get_in_compound, value::LevelBasedValue,
22    },
23    sound::CustomSound,
24};
25
26#[derive(Clone, Debug)]
27pub enum EntityEffect {
28    AllOf(AllOf),
29    ApplyMobEffect(ApplyMobEffect),
30    ChangeItemDamage(ChangeItemDamage),
31    DamageEntity(DamageEntity),
32    Explode(Explode),
33    Ignite(Ignite),
34    ApplyImpulse(ApplyEntityImpulse),
35    ApplyExhaustion(ApplyExhaustion),
36    PlaySound(PlaySound),
37    ReplaceBlock(ReplaceBlock),
38    ReplaceDisk(ReplaceDisk),
39    RunFunction(RunFunction),
40    SetBlockProperties(SetBlockProperties),
41    SpawnParticles(SpawnParticles),
42    SummonEntity(SummonEntity),
43}
44
45impl Deserialize for EntityEffect {
46    fn from_compound(nbt: NbtCompound) -> Result<Self, DeserializeError> {
47        let kind = get_in_compound(&nbt, "type")?;
48        let res = match kind {
49            EntityEffectKind::AllOf => Deserialize::from_compound(nbt).map(Self::AllOf),
50            EntityEffectKind::ApplyMobEffect => {
51                Deserialize::from_compound(nbt).map(Self::ApplyMobEffect)
52            }
53            EntityEffectKind::ChangeItemDamage => {
54                Deserialize::from_compound(nbt).map(Self::ChangeItemDamage)
55            }
56            EntityEffectKind::DamageEntity => {
57                Deserialize::from_compound(nbt).map(Self::DamageEntity)
58            }
59            EntityEffectKind::Explode => Deserialize::from_compound(nbt).map(Self::Explode),
60            EntityEffectKind::Ignite => Deserialize::from_compound(nbt).map(Self::Ignite),
61            EntityEffectKind::ApplyImpulse => {
62                Deserialize::from_compound(nbt).map(Self::ApplyImpulse)
63            }
64            EntityEffectKind::ApplyExhaustion => {
65                Deserialize::from_compound(nbt).map(Self::ApplyExhaustion)
66            }
67            EntityEffectKind::PlaySound => Deserialize::from_compound(nbt).map(Self::PlaySound),
68            EntityEffectKind::ReplaceBlock => {
69                Deserialize::from_compound(nbt).map(Self::ReplaceBlock)
70            }
71            EntityEffectKind::ReplaceDisk => Deserialize::from_compound(nbt).map(Self::ReplaceDisk),
72            EntityEffectKind::RunFunction => Deserialize::from_compound(nbt).map(Self::RunFunction),
73            EntityEffectKind::SetBlockProperties => {
74                Deserialize::from_compound(nbt).map(Self::SetBlockProperties)
75            }
76            EntityEffectKind::SpawnParticles => {
77                Deserialize::from_compound(nbt).map(Self::SpawnParticles)
78            }
79            EntityEffectKind::SummonEntity => {
80                Deserialize::from_compound(nbt).map(Self::SummonEntity)
81            }
82        };
83        if res.is_err() {
84            error!("Error deserializing EntityEffect {kind}: {nbt:?}");
85        }
86        res
87    }
88}
89
90#[derive(Clone, Debug, simdnbt::Deserialize)]
91pub struct AllOf {
92    pub effects: Vec<EntityEffect>,
93}
94
95#[derive(Clone, Debug, simdnbt::Deserialize)]
96pub struct ApplyMobEffect {
97    /// IDs of mob effects.
98    pub to_apply: HomogeneousList,
99    pub min_duration: LevelBasedValue,
100    pub max_duration: LevelBasedValue,
101    pub min_amplifier: LevelBasedValue,
102    pub max_amplifier: LevelBasedValue,
103}
104
105// TODO: in vanilla this is just a HolderSetCodec using a RegistryFixedCodec,
106// azalea registries should probably be refactored first tho
107#[derive(Clone, Debug, Default)]
108pub struct HomogeneousList {
109    pub ids: Vec<Identifier>,
110}
111impl simdnbt::FromNbtTag for HomogeneousList {
112    fn from_nbt_tag(tag: NbtTag) -> Option<Self> {
113        if let Some(string) = tag.string() {
114            return Some(Self {
115                ids: vec![Identifier::new(string.to_str())],
116            });
117        }
118        if let Some(list) = tag.list()
119            && let Some(strings) = list.strings()
120        {
121            return Some(Self {
122                ids: strings
123                    .iter()
124                    .map(|&s| Identifier::new(s.to_str()))
125                    .collect(),
126            });
127        }
128        None
129    }
130}
131
132#[derive(Clone, Debug, simdnbt::Deserialize)]
133pub struct ChangeItemDamage {
134    pub amount: LevelBasedValue,
135}
136
137#[derive(Clone, Debug, simdnbt::Deserialize)]
138pub struct DamageEntity {
139    pub min_damage: LevelBasedValue,
140    pub max_damage: LevelBasedValue,
141    #[simdnbt(rename = "damage_type")]
142    pub damage_kind: DamageKindKey,
143}
144
145#[derive(Clone, Debug, simdnbt::Deserialize)]
146pub struct Explode {
147    pub attribute_to_user: Option<bool>,
148    #[simdnbt(rename = "damage_type")]
149    pub damage_kind: Option<DamageKindKey>,
150    pub knockback_multiplier: Option<LevelBasedValue>,
151    pub immune_blocks: Option<HomogeneousList>,
152    pub offset: Option<Vec3>,
153}
154
155#[derive(Clone, Debug, simdnbt::Deserialize)]
156pub struct Ignite {
157    pub duration: LevelBasedValue,
158}
159
160#[derive(Clone, Debug, simdnbt::Deserialize)]
161pub struct ApplyEntityImpulse {
162    pub direction: Vec3,
163    pub coordinate_scale: Vec3,
164    pub magnitude: LevelBasedValue,
165}
166
167#[derive(Clone, Debug, simdnbt::Deserialize)]
168pub struct ApplyExhaustion {
169    pub amount: LevelBasedValue,
170}
171
172#[derive(Clone, Debug)]
173pub struct PlaySound {
174    // #[simdnbt(compact)]
175    pub sound: Vec<Holder<SoundEvent, CustomSound>>,
176    pub volume: FloatProvider,
177    pub pitch: FloatProvider,
178}
179
180impl Deserialize for PlaySound {
181    fn from_compound(nbt: NbtCompound) -> Result<Self, DeserializeError> {
182        let sound = if let Some(list) = nbt.list("sound") {
183            // TODO: this will probably break in the future because it's only handling lists
184            // of strings. you should refactor simdnbt to have an owned NbtTag enum that
185            // contains the borrow types so this works for more than just
186            // strings.
187            list.strings()
188                .ok_or(DeserializeError::MissingField)?
189                .iter()
190                .map(|s| {
191                    SoundEvent::from_str(&s.to_str())
192                        .map(Holder::Reference)
193                        .ok()
194                })
195                .collect::<Option<_>>()
196                .ok_or(DeserializeError::MissingField)?
197        } else {
198            vec![get_in_compound(&nbt, "sound")?]
199        };
200
201        let volume = get_in_compound(&nbt, "volume")?;
202        let pitch = get_in_compound(&nbt, "pitch")?;
203
204        Ok(Self {
205            sound,
206            volume,
207            pitch,
208        })
209    }
210}
211
212#[derive(Clone, Debug, simdnbt::Deserialize)]
213pub struct ReplaceBlock {
214    pub offset: Option<Vec3i>,
215    pub predicate: Option<BlockPredicate>,
216    pub block_state: BlockStateProvider,
217    pub trigger_game_event: Option<GameEvent>,
218}
219
220#[derive(Clone, Debug, simdnbt::Deserialize)]
221pub struct ReplaceDisk {
222    pub radius: LevelBasedValue,
223    pub height: LevelBasedValue,
224    pub offset: Option<Vec3i>,
225    pub predicate: Option<BlockPredicate>,
226    pub block_state: BlockStateProvider,
227    pub trigger_game_event: Option<GameEvent>,
228}
229
230#[derive(Clone, Debug, simdnbt::Deserialize)]
231pub struct RunFunction {
232    pub function: Identifier,
233}
234
235#[derive(Clone, Debug, simdnbt::Deserialize)]
236pub struct SetBlockProperties {
237    pub properties: HashMap<String, String>,
238    pub offset: Option<Vec3i>,
239    pub trigger_game_event: Option<GameEvent>,
240}
241
242#[derive(Clone, Debug, simdnbt::Deserialize)]
243pub struct SpawnParticles {
244    pub particle: ParticleKindCodec,
245    pub horizontal_position: SpawnParticlesPosition,
246    pub vertical_position: SpawnParticlesPosition,
247    pub horizontal_velocity: SpawnParticlesVelocity,
248    pub vertical_velocity: SpawnParticlesVelocity,
249    pub speed: Option<FloatProvider>,
250}
251
252#[derive(Clone, Debug, simdnbt::Deserialize)]
253pub struct ParticleKindCodec {
254    #[simdnbt(rename = "type")]
255    pub kind: ParticleKind,
256}
257
258#[derive(Clone, Debug, simdnbt::Deserialize)]
259pub struct SpawnParticlesPosition {
260    #[simdnbt(rename = "type")]
261    pub kind: Identifier,
262    pub offset: Option<f32>,
263    pub scale: Option<f32>,
264}
265
266#[derive(Clone, Debug, simdnbt::Deserialize)]
267pub struct SpawnParticlesVelocity {
268    pub movement_scale: Option<f32>,
269    pub base: Option<FloatProvider>,
270}
271
272#[derive(Clone, Debug, simdnbt::Deserialize)]
273pub struct SummonEntity {
274    pub entity: HomogeneousList,
275    pub join_team: Option<bool>,
276}