Skip to main content

steel_registry/mob_effect/
mod.rs

1use std::hash::{Hash, Hasher};
2
3pub mod instance;
4
5use crate::RegistryTags;
6use crate::attribute::{AttributeModifierOperation, AttributeRef};
7use crate::particle_type::{ColorParticleOption, ParticleData, ParticleTypeRef};
8use rustc_hash::FxHashMap;
9use steel_utils::{ArgbColor, Identifier, RgbColor};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum MobEffectCategory {
13    Beneficial,
14    Harmful,
15    Neutral,
16}
17
18#[derive(Debug)]
19pub struct MobEffectAttributeModifier {
20    pub attribute: AttributeRef,
21    pub id: Identifier,
22    pub amount: f64,
23    pub operation: AttributeModifierOperation,
24}
25
26/// The Vanilla particle factory associated with a mob effect.
27#[derive(Debug, Clone, Copy)]
28pub enum MobEffectParticle {
29    /// Builds a color payload from the effect color and instance ambience.
30    EffectColor {
31        particle_type: ParticleTypeRef,
32        regular_alpha: u8,
33        ambient_alpha: u8,
34    },
35    /// Uses a simple particle without a payload.
36    Simple(ParticleTypeRef),
37    /// Uses one fixed ARGB color payload for every instance.
38    FixedColor {
39        particle_type: ParticleTypeRef,
40        color: ArgbColor,
41    },
42}
43
44impl MobEffectParticle {
45    #[must_use]
46    pub fn create(self, effect_color: RgbColor, ambient: bool) -> ParticleData {
47        match self {
48            Self::EffectColor {
49                particle_type,
50                regular_alpha,
51                ambient_alpha,
52            } => {
53                let alpha = if ambient {
54                    ambient_alpha
55                } else {
56                    regular_alpha
57                };
58                ParticleData::new(
59                    particle_type,
60                    ColorParticleOption::new(effect_color.with_alpha(alpha)),
61                )
62            }
63            Self::Simple(particle_type) => ParticleData::simple(particle_type),
64            Self::FixedColor {
65                particle_type,
66                color,
67            } => ParticleData::new(particle_type, ColorParticleOption::new(color)),
68        }
69    }
70}
71
72#[derive(Debug)]
73pub struct MobEffect {
74    pub key: Identifier,
75    pub category: MobEffectCategory,
76    pub color: RgbColor,
77    pub particle: MobEffectParticle,
78    pub attribute_modifiers: &'static [MobEffectAttributeModifier],
79}
80
81impl MobEffect {
82    /// Creates the particle options synchronized for one effect instance.
83    #[must_use]
84    pub fn create_particle_options(&self, ambient: bool) -> ParticleData {
85        self.particle.create(self.color, ambient)
86    }
87
88    /// Returns the `VarInt` payload used by vanilla mob-effect holder-registry packets.
89    #[must_use]
90    pub fn packet_holder_id(&self) -> i32 {
91        let id = crate::RegistryEntry::id(self);
92        debug_assert!(i32::try_from(id).is_ok());
93        id as i32
94    }
95}
96
97impl Hash for MobEffect {
98    fn hash<H: Hasher>(&self, state: &mut H) {
99        self.key.hash(state);
100    }
101}
102
103pub type MobEffectRef = &'static MobEffect;
104
105pub struct MobEffectRegistry {
106    effects_by_id: Vec<MobEffectRef>,
107    effects_by_key: FxHashMap<Identifier, usize>,
108    tags: RegistryTags,
109    allows_registering: bool,
110}
111
112impl Default for MobEffectRegistry {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118impl MobEffectRegistry {
119    #[must_use]
120    pub fn new() -> Self {
121        Self {
122            effects_by_id: Vec::new(),
123            effects_by_key: FxHashMap::default(),
124            tags: RegistryTags::default(),
125            allows_registering: true,
126        }
127    }
128
129    pub fn register(&mut self, effect: MobEffectRef) {
130        assert!(
131            self.allows_registering,
132            "Cannot register mob effects after the registry has been frozen"
133        );
134        let idx = self.effects_by_id.len();
135        self.effects_by_key.insert(effect.key.clone(), idx);
136        self.effects_by_id.push(effect);
137    }
138
139    pub fn iter(&self) -> impl Iterator<Item = (usize, MobEffectRef)> + '_ {
140        self.effects_by_id
141            .iter()
142            .enumerate()
143            .map(|(id, &effect)| (id, effect))
144    }
145}
146
147crate::impl_registry!(
148    MobEffectRegistry,
149    MobEffect,
150    effects_by_id,
151    effects_by_key,
152    mob_effects
153);
154
155#[cfg(test)]
156mod tests {
157    use crate::particle_type::{ColorParticleOption, SimpleParticleOptions};
158    use crate::{vanilla_mob_effects, vanilla_particle_types};
159
160    #[test]
161    fn generated_effect_particles_match_vanilla_factories() {
162        let speed = vanilla_mob_effects::SPEED;
163        let regular = speed.create_particle_options(false);
164        let ambient = speed.create_particle_options(true);
165        let Some(regular_color) = regular.downcast_ref::<ColorParticleOption>() else {
166            panic!("speed should use color particle options");
167        };
168        let Some(ambient_color) = ambient.downcast_ref::<ColorParticleOption>() else {
169            panic!("ambient speed should use color particle options");
170        };
171
172        assert_eq!(
173            regular.particle_type().key,
174            vanilla_particle_types::ENTITY_EFFECT.key
175        );
176        assert_eq!(regular_color.color().alpha(), 255);
177        assert_eq!(ambient_color.color().alpha(), 38);
178        assert_eq!(
179            regular_color.color().rgb().raw() & 0x00ff_ffff,
180            speed.color.raw()
181        );
182
183        let trial_omen = vanilla_mob_effects::TRIAL_OMEN;
184        let trial_particle = trial_omen.create_particle_options(false);
185        assert_eq!(
186            trial_particle.particle_type().key,
187            vanilla_particle_types::TRIAL_OMEN.key
188        );
189        assert!(
190            trial_particle
191                .downcast_ref::<SimpleParticleOptions>()
192                .is_some()
193        );
194    }
195}
196
197crate::impl_tagged_registry!(MobEffectRegistry, effects_by_key, "mob effect");