wowlab_engine_combat/builder/combat_builder/build/
dbc_procs.rs1use super::{
2 AuraSubtypeKind, CombatSystemBuilder, ImpactActorFilter, ImpactEffectProcDefinition,
3 ImpactSource, SpellIdx, TriggerProgramLowering,
4};
5
6pub(super) struct ImpactProcRegistration<'a> {
7 pub(super) definition: &'a mut CombatSystemBuilder,
8}
9
10impl ImpactProcRegistration<'_> {
11 pub(super) fn auto_register_dbc_impact_procs(&mut self) {
12 let mut registrations = Vec::new();
13 let selected_talent_spell_ids = self.definition.selected_talent_spell_ids.clone();
14
15 for driver_spell_id in selected_talent_spell_ids {
16 let driver = SpellIdx::from_raw(driver_spell_id);
17
18 for effect_index in 1..=self.definition.game_data.max_effect_index(driver) {
19 let aura_subtype = self.definition.game_data.effect_aura(driver, effect_index);
20
21 if ![
22 AuraSubtypeKind::ProcTriggerSpell as i32,
23 AuraSubtypeKind::TriggerSpellWithValue as i32,
24 ]
25 .contains(&aura_subtype)
26 {
27 continue;
28 }
29
30 let proc_mask = self
31 .definition
32 .game_data
33 .proc_type_mask(driver)
34 .unwrap_or(0);
35 let chance = self.definition.game_data.proc_chance(driver).unwrap_or(0.0);
36 let (rppm, rppm_flags) = self.definition.game_data.rppm(driver).unwrap_or((0.0, 0));
37 let rppm_haste_scales = self
38 .definition
39 .game_data
40 .rppm_haste_scales(driver)
41 .unwrap_or(false);
42 let rppm_crit_scales = self
43 .definition
44 .game_data
45 .rppm_crit_scales(driver)
46 .unwrap_or(false);
47 let class_mask = self
48 .definition
49 .game_data
50 .effect_class_mask(driver, effect_index);
51 let has_opaque_sibling = self.has_opaque_sibling(driver, effect_index);
52 let Some(triggered) = self
53 .definition
54 .game_data
55 .trigger_spell(driver, effect_index)
56 else {
57 continue;
58 };
59
60 if !dbc_proc_mask_is_complete(proc_mask)
61 || !dbc_proc_retains_a_rate(rppm, chance)
62 || has_opaque_sibling
63 || class_mask.iter().any(|mask| *mask != 0)
64 || self.definition.impact_effect_procs.iter().any(|existing| {
65 existing.driver_spell_id == driver_spell_id
66 && existing.driver_effect_index == effect_index
67 })
68 {
69 continue;
70 }
71
72 let forwarded_value = (aura_subtype
73 == AuraSubtypeKind::TriggerSpellWithValue as i32)
74 .then(|| self.definition.game_data.base_points(driver, effect_index));
75 let effects = wowlab_engine_domain::dbc::resolve_trigger_program(
76 wowlab_engine_domain::dbc::EffectLookup::new(
77 &self.definition.game_data,
78 wowlab_types::sim::EffectRef::new(driver, effect_index),
79 ),
80 forwarded_value,
81 )
82 .ok()
83 .and_then(|program| self.lowering().lower_trigger_program(&program).ok())
84 .unwrap_or_default();
85
86 if effects.is_empty() {
87 continue;
88 }
89
90 let mut proc =
91 ImpactEffectProcDefinition::new(ImpactSource::ProcTypeMask(proc_mask))
92 .dbc_driver_from_data(wowlab_engine_domain::dbc::EffectLookup::new(
93 &self.definition.game_data,
94 wowlab_types::sim::EffectRef::new(driver, effect_index),
95 ))
96 .profile_spell(triggered.as_u32())
97 .actor_filter(ImpactActorFilter::Player)
98 .chance(chance);
99
100 if self.uses_stacks_for_charges(driver) {
101 proc = proc.use_stacks_for_charges();
102 }
103
104 if rppm > 0.0 {
105 let tracker = self.definition.scaled_rppm(
106 rppm,
107 super::super::RppmScalingOptions {
108 haste: rppm_haste_scales,
109 crit: rppm_crit_scales,
110 auto_attack_speed: false,
111 },
112 );
113
114 proc = proc.rppm(tracker);
115 }
116
117 proc.effects = effects;
118 tracing::debug!(
120 driver_spell_id,
121 effect_index,
122 triggered_spell_id = triggered.as_u32(),
123 proc_mask,
124 chance,
125 rppm_flags,
126 "registered supported DBC AU42 impact proc"
127 );
128 registrations.push(proc);
129 }
130 }
131
132 self.definition.impact_effect_procs.extend(registrations);
133 }
134
135 fn lowering(&self) -> TriggerProgramLowering<'_> {
136 TriggerProgramLowering {
137 definition: &*self.definition,
138 }
139 }
140
141 fn has_opaque_sibling(&self, driver: SpellIdx, effect_index: u8) -> bool {
142 (1..=self.definition.game_data.max_effect_index(driver))
143 .filter(|sibling_index| *sibling_index != effect_index)
144 .any(|sibling_index| {
145 self.definition.game_data.effect_aura(driver, sibling_index)
146 == AuraSubtypeKind::Dummy as i32
147 && self
148 .definition
149 .game_data
150 .base_points(driver, sibling_index)
151 .abs()
152 > f64::EPSILON
153 })
154 }
155
156 fn uses_stacks_for_charges(&self, driver: SpellIdx) -> bool {
157 self.definition
158 .game_data
159 .proc_attributes(driver)
160 .is_some_and(|attributes| {
161 wowlab_engine_domain::dbc::ProcAttributes::from_dbc(attributes)
162 .contains(wowlab_engine_domain::dbc::ProcAttributes::USE_STACKS_FOR_CHARGES)
163 })
164 }
165}
166
167pub(super) fn dbc_proc_mask_is_complete(mask: u64) -> bool {
168 let typed = wowlab_engine_domain::dbc::ProcTypeMask::from_dbc(mask);
169
170 !typed.is_empty()
171 && typed
172 .difference(wowlab_engine_domain::dbc::ProcTypeMask::SUPPORTED_EVENT)
173 .is_empty()
174}
175
176const fn dbc_proc_retains_a_rate(rppm: f64, chance: f64) -> bool {
187 rppm > 0.0 || (chance > 0.0 && chance < 1.0)
188}