use std::{collections::HashMap, str::FromStr}; use azalea_registry::{ Holder, builtin::{ EnchantmentEntityEffectKind as EntityEffectKind, GameEvent, ParticleKind, SoundEvent, }, data::DamageKindKey, identifier::Identifier, }; use simdnbt::{ Deserialize, DeserializeError, borrow::{NbtCompound, NbtTag}, }; use tracing::error; use crate::{ position::{Vec3, Vec3i}, registry_holder::{ block_predicate::BlockPredicate, block_state_provider::BlockStateProvider, float_provider::FloatProvider, get_in_compound, value::LevelBasedValue, }, sound::CustomSound, }; #[derive(Clone, Debug)] pub enum EntityEffect { AllOf(AllOf), ApplyMobEffect(ApplyMobEffect), ChangeItemDamage(ChangeItemDamage), DamageEntity(DamageEntity), Explode(Explode), Ignite(Ignite), ApplyImpulse(ApplyEntityImpulse), ApplyExhaustion(ApplyExhaustion), PlaySound(PlaySound), ReplaceBlock(ReplaceBlock), ReplaceDisk(ReplaceDisk), RunFunction(RunFunction), SetBlockProperties(SetBlockProperties), SpawnParticles(SpawnParticles), SummonEntity(SummonEntity), } impl Deserialize for EntityEffect { fn from_compound(nbt: NbtCompound) -> Result { let kind = get_in_compound(&nbt, "type")?; let res = match kind { EntityEffectKind::AllOf => Deserialize::from_compound(nbt).map(Self::AllOf), EntityEffectKind::ApplyMobEffect => { Deserialize::from_compound(nbt).map(Self::ApplyMobEffect) } EntityEffectKind::ChangeItemDamage => { Deserialize::from_compound(nbt).map(Self::ChangeItemDamage) } EntityEffectKind::DamageEntity => { Deserialize::from_compound(nbt).map(Self::DamageEntity) } EntityEffectKind::Explode => Deserialize::from_compound(nbt).map(Self::Explode), EntityEffectKind::Ignite => Deserialize::from_compound(nbt).map(Self::Ignite), EntityEffectKind::ApplyImpulse => { Deserialize::from_compound(nbt).map(Self::ApplyImpulse) } EntityEffectKind::ApplyExhaustion => { Deserialize::from_compound(nbt).map(Self::ApplyExhaustion) } EntityEffectKind::PlaySound => Deserialize::from_compound(nbt).map(Self::PlaySound), EntityEffectKind::ReplaceBlock => { Deserialize::from_compound(nbt).map(Self::ReplaceBlock) } EntityEffectKind::ReplaceDisk => Deserialize::from_compound(nbt).map(Self::ReplaceDisk), EntityEffectKind::RunFunction => Deserialize::from_compound(nbt).map(Self::RunFunction), EntityEffectKind::SetBlockProperties => { Deserialize::from_compound(nbt).map(Self::SetBlockProperties) } EntityEffectKind::SpawnParticles => { Deserialize::from_compound(nbt).map(Self::SpawnParticles) } EntityEffectKind::SummonEntity => { Deserialize::from_compound(nbt).map(Self::SummonEntity) } }; if res.is_err() { error!("Error deserializing EntityEffect {kind}: {nbt:?}"); } res } } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct AllOf { pub effects: Vec, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct ApplyMobEffect { /// IDs of mob effects. pub to_apply: HomogeneousList, pub min_duration: LevelBasedValue, pub max_duration: LevelBasedValue, pub min_amplifier: LevelBasedValue, pub max_amplifier: LevelBasedValue, } // TODO: in vanilla this is just a HolderSetCodec using a RegistryFixedCodec, // azalea registries should probably be refactored first tho #[derive(Clone, Debug, Default)] pub struct HomogeneousList { pub ids: Vec, } impl simdnbt::FromNbtTag for HomogeneousList { fn from_nbt_tag(tag: NbtTag) -> Option { if let Some(string) = tag.string() { return Some(Self { ids: vec![Identifier::new(string.to_str())], }); } if let Some(list) = tag.list() && let Some(strings) = list.strings() { return Some(Self { ids: strings .iter() .map(|&s| Identifier::new(s.to_str())) .collect(), }); } None } } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct ChangeItemDamage { pub amount: LevelBasedValue, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct DamageEntity { pub min_damage: LevelBasedValue, pub max_damage: LevelBasedValue, #[simdnbt(rename = "damage_type")] pub damage_kind: DamageKindKey, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct Explode { pub attribute_to_user: Option, #[simdnbt(rename = "damage_type")] pub damage_kind: Option, pub knockback_multiplier: Option, pub immune_blocks: Option, pub offset: Option, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct Ignite { pub duration: LevelBasedValue, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct ApplyEntityImpulse { pub direction: Vec3, pub coordinate_scale: Vec3, pub magnitude: LevelBasedValue, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct ApplyExhaustion { pub amount: LevelBasedValue, } #[derive(Clone, Debug)] pub struct PlaySound { // #[simdnbt(compact)] pub sound: Vec>, pub volume: FloatProvider, pub pitch: FloatProvider, } impl Deserialize for PlaySound { fn from_compound(nbt: NbtCompound) -> Result { let sound = if let Some(list) = nbt.list("sound") { // TODO: this will probably break in the future because it's only handling lists // of strings. you should refactor simdnbt to have an owned NbtTag enum that // contains the borrow types so this works for more than just // strings. list.strings() .ok_or(DeserializeError::MissingField)? .iter() .map(|s| { SoundEvent::from_str(&s.to_str()) .map(Holder::Reference) .ok() }) .collect::>() .ok_or(DeserializeError::MissingField)? } else { vec![get_in_compound(&nbt, "sound")?] }; let volume = get_in_compound(&nbt, "volume")?; let pitch = get_in_compound(&nbt, "pitch")?; Ok(Self { sound, volume, pitch, }) } } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct ReplaceBlock { pub offset: Option, pub predicate: Option, pub block_state: BlockStateProvider, pub trigger_game_event: Option, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct ReplaceDisk { pub radius: LevelBasedValue, pub height: LevelBasedValue, pub offset: Option, pub predicate: Option, pub block_state: BlockStateProvider, pub trigger_game_event: Option, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct RunFunction { pub function: Identifier, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct SetBlockProperties { pub properties: HashMap, pub offset: Option, pub trigger_game_event: Option, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct SpawnParticles { pub particle: ParticleKindCodec, pub horizontal_position: SpawnParticlesPosition, pub vertical_position: SpawnParticlesPosition, pub horizontal_velocity: SpawnParticlesVelocity, pub vertical_velocity: SpawnParticlesVelocity, pub speed: Option, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct ParticleKindCodec { #[simdnbt(rename = "type")] pub kind: ParticleKind, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct SpawnParticlesPosition { #[simdnbt(rename = "type")] pub kind: Identifier, pub offset: Option, pub scale: Option, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct SpawnParticlesVelocity { pub movement_scale: Option, pub base: Option, } #[derive(Clone, Debug, simdnbt::Deserialize)] pub struct SummonEntity { pub entity: HomogeneousList, pub join_team: Option, }