Skip to main content

wowlab_engine_domain/dbc/semantics/
enchantments.rs

1//! `SpellItemEnchantment` proc/stat resolution semantics.
2
3use wowlab_types::{constants::POWERFUL_EVERSONG_DIAMOND_SPELL_ID, data::RppmMod};
4
5/// DBC `SpellItemEnchantment.Effect` type.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7#[repr(i32)]
8#[non_exhaustive]
9pub enum EnchantEffectKind {
10    TriggerSpell = 3,
11    Stat = 5,
12}
13
14/// Engine behavior selected by the spell triggered from an item enchantment.
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16#[non_exhaustive]
17pub enum EnchantTriggerSemantic {
18    /// Increases critical-strike effectiveness for each distinct Midnight gem descriptor.
19    CritEffectivenessPerUniqueGemColor,
20}
21
22/// Typed identity for a raw `SpellItemEnchantment.Effect` type.
23#[must_use]
24pub const fn enchant_effect_kind(raw: i32) -> Option<EnchantEffectKind> {
25    match raw {
26        3 => Some(EnchantEffectKind::TriggerSpell),
27        5 => Some(EnchantEffectKind::Stat),
28        _ => None,
29    }
30}
31
32/// Whether a raw enchant-effect type has the requested typed identity.
33#[must_use]
34pub fn enchant_effect_is(raw: i32, kind: EnchantEffectKind) -> bool {
35    enchant_effect_kind(raw) == Some(kind)
36}
37
38/// Typed behavior for a raw trigger-spell identity stored in `SpellItemEnchantment`.
39#[must_use]
40pub fn enchant_trigger_semantic(raw_spell_id: i32) -> Option<EnchantTriggerSemantic> {
41    let spell_id = u32::try_from(raw_spell_id).ok()?;
42
43    match spell_id {
44        POWERFUL_EVERSONG_DIAMOND_SPELL_ID => {
45            Some(EnchantTriggerSemantic::CritEffectivenessPerUniqueGemColor)
46        }
47        _ => None,
48    }
49}
50
51const RPPM_MODIFIER_HASTE: i32 = 1;
52const RPPM_MODIFIER_CRIT: i32 = 2;
53
54/// Whether an RPPM proc's rate scales with haste.
55#[must_use]
56pub fn rppm_haste_scaled(modifiers: &[RppmMod]) -> bool {
57    modifiers
58        .iter()
59        .any(|modifier| modifier.mod_type == RPPM_MODIFIER_HASTE)
60}
61
62/// Whether an RPPM proc's rate scales with critical strike chance.
63#[must_use]
64pub fn rppm_crit_scaled(modifiers: &[RppmMod]) -> bool {
65    modifiers
66        .iter()
67        .any(|modifier| modifier.mod_type == RPPM_MODIFIER_CRIT)
68}
69
70#[cfg(test)]
71mod tests {
72    use googletest::prelude::*;
73
74    use super::*;
75
76    #[gtest]
77    fn rppm_scaling_comes_from_modifier_types_not_raw_flags() {
78        let modifiers = [
79            RppmMod {
80                mod_type: RPPM_MODIFIER_HASTE,
81                param: 0,
82                coeff: 0.0,
83            },
84            RppmMod {
85                mod_type: RPPM_MODIFIER_CRIT,
86                param: 0,
87                coeff: 0.0,
88            },
89        ];
90
91        expect_that!(rppm_haste_scaled(&modifiers), is_true());
92        expect_that!(rppm_crit_scaled(&modifiers), is_true());
93        expect_that!(!rppm_haste_scaled(&[]), is_true());
94        expect_that!(!rppm_crit_scaled(&[]), is_true());
95    }
96
97    #[gtest]
98    fn powerful_eversong_diamond_has_typed_trigger_semantics() {
99        let raw_spell_id =
100            wowlab_types::numeric::u32_to_i32_saturating(POWERFUL_EVERSONG_DIAMOND_SPELL_ID);
101
102        expect_that!(
103            enchant_trigger_semantic(raw_spell_id),
104            eq(Some(
105                EnchantTriggerSemantic::CritEffectivenessPerUniqueGemColor
106            ))
107        );
108        expect_that!(enchant_trigger_semantic(-1), eq(None));
109        expect_that!(enchant_trigger_semantic(1), eq(None));
110    }
111}