Skip to main content

wowlab_engine_domain/dbc/semantics/
schools.rs

1use wowlab_types::combat::DamageSchool;
2
3bitflags::bitflags! {
4    /// DBC `SpellSchoolMask`: one bit per damage school.
5    #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
6    pub struct SpellSchoolMask: u8 {
7        const PHYSICAL = 1 << 0;
8        const HOLY = 1 << 1;
9        const FIRE = 1 << 2;
10        const NATURE = 1 << 3;
11        const FROST = 1 << 4;
12        const SHADOW = 1 << 5;
13        const ARCANE = 1 << 6;
14        const CHAOS = Self::PHYSICAL.bits()
15            | Self::HOLY.bits()
16            | Self::FIRE.bits()
17            | Self::NATURE.bits()
18            | Self::FROST.bits()
19            | Self::SHADOW.bits()
20            | Self::ARCANE.bits();
21    }
22}
23
24impl SpellSchoolMask {
25    #[must_use]
26    pub const fn from_dbc(raw: i32) -> Self {
27        Self::from_bits_truncate(raw.to_le_bytes()[0])
28    }
29
30    /// Resolve the engine's single-school damage channel from a possibly multi-school mask.
31    #[must_use]
32    pub fn primary_school(self) -> DamageSchool {
33        if self.bits() == Self::CHAOS.bits() {
34            return DamageSchool::Chaos;
35        }
36
37        [
38            (Self::PHYSICAL, DamageSchool::Physical),
39            (Self::HOLY, DamageSchool::Holy),
40            (Self::FIRE, DamageSchool::Fire),
41            (Self::NATURE, DamageSchool::Nature),
42            (Self::FROST, DamageSchool::Frost),
43            (Self::SHADOW, DamageSchool::Shadow),
44            (Self::ARCANE, DamageSchool::Arcane),
45        ]
46        .into_iter()
47        .find_map(|(mask, school)| self.contains(mask).then_some(school))
48        .unwrap_or(DamageSchool::Physical)
49    }
50}
51
52impl From<DamageSchool> for SpellSchoolMask {
53    fn from(school: DamageSchool) -> Self {
54        match school {
55            DamageSchool::Physical => Self::PHYSICAL,
56            DamageSchool::Holy => Self::HOLY,
57            DamageSchool::Fire => Self::FIRE,
58            DamageSchool::Nature => Self::NATURE,
59            DamageSchool::Frost => Self::FROST,
60            DamageSchool::Shadow => Self::SHADOW,
61            DamageSchool::Arcane => Self::ARCANE,
62            DamageSchool::Chaos => Self::CHAOS,
63        }
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use googletest::prelude::*;
70
71    use super::*;
72
73    #[gtest]
74    fn chaos_is_the_complete_school_mask_but_not_physical_resolution() {
75        let chaos = SpellSchoolMask::CHAOS;
76
77        expect_that!(chaos.contains(SpellSchoolMask::PHYSICAL), is_true());
78        expect_that!(chaos.primary_school(), eq(DamageSchool::Chaos));
79    }
80
81    #[gtest]
82    fn multi_school_resolution_uses_lowest_set_school() {
83        let mask = SpellSchoolMask::FIRE | SpellSchoolMask::SHADOW;
84
85        expect_that!(mask.primary_school(), eq(DamageSchool::Fire));
86    }
87}