wowlab_engine_domain/dbc/passives/
crit_scaling.rs1use super::{
4 AuraSubtypeKind, CLASS_FAMILY_FLAGS, ModifierFilter, ModifierOperation, ModifierPropertyKind,
5 SpellDataFlat, SpellEffect, aura_kind, aura_subtype_semantic, masks_overlap,
6 modifier_property_kind,
7};
8use crate::dbc::SpellSchoolMask;
9
10#[derive(Clone, Copy, Debug, PartialEq)]
16pub enum CritChanceScaledAxis {
18 Player(SpellSchoolMask),
20 Spell,
22}
23
24#[derive(Clone, Debug)]
26pub struct CritChanceScaledCritDamage {
27 pub effect: SpellEffect,
29 pub spell_class_set: i32,
31 pub percent: f64,
33 pub axis: CritChanceScaledAxis,
35}
36
37#[must_use]
45pub fn crit_chance_scaled_crit_damage(
46 spell: &SpellDataFlat,
47 source_effect_index: u8,
48 ranks: f64,
49) -> Vec<CritChanceScaledCritDamage> {
50 let source_dbc_index = i32::from(source_effect_index) - 1;
52 let Some(source) = spell
53 .effects
54 .iter()
55 .find(|effect| effect.index == source_dbc_index)
56 else {
57 return Vec::new();
58 };
59 let percent = source.base_points * ranks;
60
61 if percent.abs() < f64::EPSILON {
62 return Vec::new();
63 }
64
65 spell
66 .effects
67 .iter()
68 .filter(|effect| effect.base_points.abs() < f64::EPSILON)
69 .filter_map(|effect| {
70 crit_damage_axis(effect).map(|axis| CritChanceScaledCritDamage {
71 effect: effect.clone(),
72 spell_class_set: spell.spell_class_set,
73 percent,
74 axis,
75 })
76 })
77 .collect()
78}
79
80#[must_use]
84pub fn crit_chance_scaled_spell_percent(
85 terms: &[CritChanceScaledCritDamage],
86 target_class_set: i32,
87 target_class_masks: [i32; CLASS_FAMILY_FLAGS],
88 target_labels: &[i32],
89) -> f64 {
90 let mut percent = 0.0;
91
92 for term in terms {
93 if term.axis != CritChanceScaledAxis::Spell {
94 continue;
95 }
96
97 let matched = match aura_subtype_semantic(term.effect.aura)
98 .and_then(|semantic| semantic.modifier)
99 .map(|(_, filter)| filter)
100 {
101 Some(ModifierFilter::ClassFamilyMask) => {
102 term.spell_class_set == target_class_set
103 && masks_overlap(&term.effect, target_class_masks)
104 }
105 Some(ModifierFilter::Label) => target_labels.contains(&term.effect.misc_value_1),
106 _ => false,
107 };
108
109 if matched {
110 percent += term.percent;
111 }
112 }
113
114 percent
115}
116
117fn crit_damage_axis(effect: &SpellEffect) -> Option<CritChanceScaledAxis> {
118 if aura_kind(effect) == Some(AuraSubtypeKind::ModCritDamageMultiplier) {
119 return Some(CritChanceScaledAxis::Player(SpellSchoolMask::from_dbc(
120 effect.misc_value_0,
121 )));
122 }
123
124 let is_percent_modifier = aura_subtype_semantic(effect.aura)
125 .and_then(|semantic| semantic.modifier)
126 .is_some_and(|(operation, _)| operation == ModifierOperation::Percent);
127
128 (is_percent_modifier
129 && modifier_property_kind(effect) == Some(ModifierPropertyKind::CritDamage))
130 .then_some(CritChanceScaledAxis::Spell)
131}
132
133#[cfg(test)]
134mod tests;