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