wowlab_engine_combat/systems/procs/
impact.rs1use super::{
2 AuraApplicationEvent, CombatCtx, HookCtx, ImpactActorFilter, ImpactEvent, ImpactFilter,
3 ImpactProc, PeriodicImpactFilter, PeriodicImpactPolicy, ProcDriver, ProcEventKind, ProcHitMask,
4 ProcPhaseMask, ProcSourceKind, ProcTriggerSide, fire_accumulating_impact_procs,
5 fire_impact_effect_procs, with_scratch,
6};
7
8pub(crate) fn weapon_enchants_on_player_impact(ctx: &mut HookCtx<'_>, _impact: ImpactEvent) {
9 attempt_weapon_enchants(ctx);
10}
11
12pub(crate) fn attempt_weapon_enchants(ctx: &mut HookCtx<'_>) {
13 for index in 0..ctx.program.weapon_enchant_bindings.len() {
14 let Some(&binding) = ctx.program.weapon_enchant_bindings.get(index) else {
15 continue;
16 };
17
18 if ctx.roll_rppm(binding.rppm) {
19 ctx.apply_aura(binding.buff_aura);
20 }
21 }
22}
23
24fn impact_proc_matches(proc: &ImpactProc, impact: ImpactEvent) -> bool {
25 proc_policy_allows(impact, proc.driver)
26 && impact_filter_matches(
27 ImpactFilter {
28 actor: proc.actor_filter,
29 spell: proc.spell_filter,
30 periodic: PeriodicImpactFilter::from(PeriodicImpactPolicy {
31 periodic_only: proc.periodic_only,
32 skip_periodic: proc.skip_periodic,
33 }),
34 physical_only: false,
35 crit_only: proc.crit_only,
36 },
37 impact,
38 )
39}
40
41pub(super) fn proc_policy_allows(impact: ImpactEvent, driver: ProcDriver) -> bool {
42 let source = impact.provenance;
43
44 if driver.policy.only_proc_from_class_abilities && !source.policy.allow_class_ability_procs {
45 return false;
46 }
47
48 if matches!(source.kind, ProcSourceKind::Proc) && !driver.policy.can_proc_from_procs {
49 return false;
50 }
51
52 match driver.trigger_side {
53 ProcTriggerSide::Target => {
54 !source.policy.suppress_target_procs || driver.policy.can_proc_from_suppressed_target
55 }
56 ProcTriggerSide::Caster => {
57 !source.policy.suppress_caster_procs
58 || (source.policy.enable_procs_from_suppressed
59 && driver.policy.can_proc_from_suppressed)
60 }
61 }
62}
63
64fn impact_actor_matches(filter: ImpactActorFilter, impact: ImpactEvent) -> bool {
65 match filter {
66 ImpactActorFilter::Player => matches!(impact.source, wowlab_types::sim::ActorId::Player),
67 ImpactActorFilter::Companion => matches!(impact.source, wowlab_types::sim::ActorId::Pet(_)),
68 ImpactActorFilter::Any => true,
69 }
70}
71
72pub(super) fn impact_filter_matches(filter: ImpactFilter, impact: ImpactEvent) -> bool {
73 if !impact_actor_matches(filter.actor, impact) {
74 return false;
75 }
76
77 if !filter.periodic.matches(impact.is_periodic) {
78 return false;
79 }
80
81 if filter.physical_only && !impact.is_physical {
82 return false;
83 }
84
85 if filter.crit_only && !impact.is_crit() {
86 return false;
87 }
88
89 filter.spell.is_none_or(|matches| matches(impact.spell_id))
90}
91
92fn fire_impact_proc(ctx: &mut CombatCtx<'_>, proc: ImpactProc, impact: ImpactEvent) {
93 if !impact_proc_matches(&proc, impact) || !wowlab_engine_rng::proc_chance(ctx.rng, proc.chance)
94 {
95 return;
96 }
97
98 let mut hook_ctx = HookCtx::new(
99 crate::context::HookCtxServices {
100 program: ctx.program,
101 instance: ctx.instance,
102 sink: ctx.sink,
103 rng: ctx.rng,
104 },
105 crate::context::HookCtxRequest::for_target(ctx.now, impact.target)
106 .with_source(impact.source),
107 )
108 .with_driver_spell(impact.spell_id)
109 .with_source_damage_flags(crate::DamageFlags::PROC);
110
111 (proc.fire)(&mut hook_ctx, impact);
112}
113
114pub(crate) fn fire_landed_impact_batch(ctx: &mut CombatCtx<'_>, landed_impacts: &[ImpactEvent]) {
115 let Ok(targets_hit) = u8::try_from(landed_impacts.len()) else {
116 debug_assert!(false, "damage-impact batches are bounded by u8");
117
118 return;
119 };
120
121 if targets_hit == 0
122 || ctx.program.landed_impact_procs.is_empty()
123 || ctx.instance.runtime.procs.landed_impact_procs_dispatching
124 {
125 return;
126 }
127
128 ctx.instance.runtime.procs.landed_impact_procs_dispatching = true;
129 let scratch = std::mem::take(&mut ctx.instance.runtime.scratch.landed_impacts);
130 let scratch = with_scratch(&ctx.program.landed_impact_procs, scratch, |entries| {
131 for (ordinal, impact) in landed_impacts.iter().copied().enumerate() {
132 let target_ordinal =
133 u8::try_from(ordinal).expect("landed impact batch is bounded by u8");
134
135 for proc in entries.iter().copied() {
136 if !proc_policy_allows(impact, proc.driver)
137 || !impact_filter_matches(
138 ImpactFilter {
139 actor: proc.actor_filter,
140 spell: proc.spell_filter,
141 periodic: PeriodicImpactFilter::from(PeriodicImpactPolicy {
142 periodic_only: proc.periodic_only,
143 skip_periodic: proc.skip_periodic,
144 }),
145 physical_only: false,
146 crit_only: proc.crit_only,
147 },
148 impact,
149 )
150 || !wowlab_engine_rng::proc_chance(ctx.rng, proc.chance)
151 {
152 continue;
153 }
154
155 let event = crate::state::LandedImpactEvent {
156 impact,
157 target_ordinal,
158 targets_hit,
159 };
160 let mut hook_ctx = HookCtx::new(
161 crate::context::HookCtxServices {
162 program: ctx.program,
163 instance: ctx.instance,
164 sink: ctx.sink,
165 rng: ctx.rng,
166 },
167 crate::context::HookCtxRequest::for_target(ctx.now, impact.target)
168 .with_source(impact.source),
169 )
170 .with_driver_spell(impact.spell_id)
171 .with_source_damage_flags(crate::DamageFlags::PROC);
172
173 (proc.fire)(&mut hook_ctx, event);
174 }
175 }
176 });
177
178 ctx.instance.runtime.scratch.landed_impacts = scratch;
179 ctx.instance.runtime.procs.landed_impact_procs_dispatching = false;
180}
181
182pub(crate) fn fire_impact_procs(ctx: &mut CombatCtx<'_>, impact: ImpactEvent) {
183 if ctx.program.impact_procs.is_empty()
185 && ctx.program.accumulating_impact_procs.is_empty()
186 && ctx.program.impact_effect_procs.is_empty()
187 {
188 return;
189 }
190 if impact.kind == ProcEventKind::Spell
193 && impact.phase == ProcPhaseMask::HIT
194 && impact.hit_mask.contains(ProcHitMask::LANDED)
195 && impact.amount > 0.0
196 {
197 if !ctx.instance.runtime.procs.impact_procs_dispatching {
199 ctx.instance.runtime.procs.impact_procs_dispatching = true;
200 let scratch = std::mem::take(&mut ctx.instance.runtime.scratch.impacts);
201 let scratch = with_scratch(&ctx.program.impact_procs, scratch, |entries| {
202 for proc in entries.iter().copied() {
203 fire_impact_proc(ctx, proc, impact);
204 }
205 });
206
207 ctx.instance.runtime.scratch.impacts = scratch;
208 ctx.instance.runtime.procs.impact_procs_dispatching = false;
209 }
210
211 fire_accumulating_impact_procs(ctx, impact);
212 }
213
214 fire_impact_effect_procs(ctx, impact, None);
215}
216
217pub(crate) fn fire_aura_application_procs(ctx: &mut CombatCtx<'_>, event: AuraApplicationEvent) {
218 let target = match event.affected {
219 wowlab_types::sim::ActorId::Enemy(target) => target,
220 _ => event.target.unwrap_or(ctx.target),
221 };
222 let impact = ImpactEvent {
223 kind: ProcEventKind::AuraApplication,
224 phase: ProcPhaseMask::HIT,
225 hit_mask: ProcHitMask::LANDED,
226 spell_id: event.aura_spell_id,
227 action: Some(event.action),
228 amount: 0.0,
229 is_periodic: false,
230 is_physical: false,
231 swing_hand: None,
232 swing_speed_ms: 0,
233 source: event.source,
234 target,
235 provenance: event.provenance,
236 };
237
238 fire_impact_effect_procs(
239 ctx,
240 impact,
241 (event.originating_proc_driver_spell_id != 0)
242 .then_some(event.originating_proc_driver_spell_id),
243 );
244}
245use crate::{AuraOps as _, ProcOps as _};