Skip to main content

wowlab_engine_domain/dbc/semantics/
ratings.rs

1//! Combat-rating semantics carried in the `misc_value_0` of a `MOD_RATING` aura effect.
2
3/// A secondary/tertiary combat rating granted by a `MOD_RATING` effect.
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5#[non_exhaustive]
6pub enum CombatRating {
7    Crit,
8    Haste,
9    Versatility,
10    Mastery,
11}
12
13bitflags::bitflags! {
14    /// DBC combat-rating bitmask carried in a `MOD_RATING` effect's `misc_value_0`.
15    /// A single effect may set several bits (e.g. all three versatility variants).
16    #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
17    pub struct CombatRatingMask: i32 {
18        const CRIT = 1 << 10;
19        const HASTE_MELEE = 1 << 14;
20        const VERSATILITY_DAMAGE_DONE = 1 << 16;
21        const VERSATILITY_DAMAGE_TAKEN = 1 << 17;
22        const VERSATILITY_HEALING_DONE = 1 << 18;
23        const VERSATILITY_HEALING_TAKEN = 1 << 19;
24        const MASTERY = 1 << 25;
25        const HASTE_SPELL = 1 << 30;
26
27        const HASTE = Self::HASTE_MELEE.bits()
28            | Self::HASTE_SPELL.bits();
29        const VERSATILITY = Self::VERSATILITY_DAMAGE_DONE.bits()
30            | Self::VERSATILITY_DAMAGE_TAKEN.bits()
31            | Self::VERSATILITY_HEALING_DONE.bits()
32            | Self::VERSATILITY_HEALING_TAKEN.bits();
33    }
34}
35
36impl CombatRatingMask {
37    #[must_use]
38    pub const fn from_dbc(raw: i32) -> Self {
39        Self::from_bits_truncate(raw)
40    }
41
42    /// Primary rating the mask grants (Crit wins when several bits are set), or `None`.
43    #[must_use]
44    pub fn primary_rating(self) -> Option<CombatRating> {
45        [
46            (Self::CRIT, CombatRating::Crit),
47            (Self::HASTE, CombatRating::Haste),
48            (Self::VERSATILITY, CombatRating::Versatility),
49            (Self::MASTERY, CombatRating::Mastery),
50        ]
51        .into_iter()
52        .find_map(|(mask, rating)| self.intersects(mask).then_some(rating))
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use googletest::prelude::*;
59
60    use super::*;
61
62    #[gtest]
63    fn each_bit_resolves_to_its_rating() {
64        expect_that!(
65            CombatRatingMask::from_dbc(1 << 25).primary_rating(),
66            eq(Some(CombatRating::Mastery))
67        );
68        expect_that!(
69            CombatRatingMask::from_dbc(1 << 30).primary_rating(),
70            eq(Some(CombatRating::Haste))
71        );
72    }
73
74    #[gtest]
75    fn crit_wins_when_several_bits_are_set() {
76        let mask = CombatRatingMask::CRIT | CombatRatingMask::HASTE;
77
78        expect_that!(mask.primary_rating(), eq(Some(CombatRating::Crit)));
79    }
80
81    #[gtest]
82    fn unmodelled_bits_resolve_to_none() {
83        expect_that!(
84            CombatRatingMask::from_dbc(1 << 5).primary_rating(),
85            eq(None)
86        );
87    }
88}