Skip to main content

wowlab_engine_combat/systems/procs/
accumulating.rs

1use super::{
2    CombatCtx, HookCtx, ImpactEvent, ImpactFilter, PeriodicImpactFilter, PeriodicImpactPolicy,
3    RANDOM_CONTRIBUTION_RANGE_MULTIPLIER, impact_filter_matches, proc_policy_allows, with_scratch,
4};
5
6// #t(fn: rust_unchecked_indexing) finalization builds accumulator storage in exact proc registration order and equal length.
7pub(super) fn fire_accumulating_impact_procs(ctx: &mut CombatCtx<'_>, impact: ImpactEvent) {
8    if ctx
9        .instance
10        .runtime
11        .procs
12        .accumulating_impact_procs_dispatching
13    {
14        return;
15    }
16
17    ctx.instance
18        .runtime
19        .procs
20        .accumulating_impact_procs_dispatching = true;
21    let scratch = std::mem::take(&mut ctx.instance.runtime.scratch.accumulating_impacts);
22    let scratch = with_scratch(&ctx.program.accumulating_impact_procs, scratch, |entries| {
23        for (index, proc) in entries.iter().enumerate() {
24            if !proc_policy_allows(impact, proc.driver)
25                || !impact_filter_matches(
26                    ImpactFilter {
27                        actor: proc.actor_filter,
28                        spell: proc.spell_filter,
29                        periodic: PeriodicImpactFilter::from(PeriodicImpactPolicy {
30                            periodic_only: proc.periodic_only,
31                            skip_periodic: proc.skip_periodic,
32                        }),
33                        physical_only: false,
34                        crit_only: proc.crit_only,
35                    },
36                    impact,
37                )
38            {
39                continue;
40            }
41
42            let Some(accumulator) = ctx
43                .instance
44                .runtime
45                .procs
46                .accumulating_impact_accumulators
47                .get(index)
48                .copied()
49            else {
50                continue;
51            };
52            let mut accumulator = accumulator;
53
54            if accumulator.is_nan() {
55                accumulator = (ctx.rng)() * proc.threshold;
56            }
57
58            let contribution = (proc.contribution)(ctx.program, impact).max(0.0);
59
60            if !advance_threshold_accumulator(
61                &mut accumulator,
62                proc.threshold,
63                contribution,
64                (ctx.rng)(),
65            ) {
66                ctx.instance.runtime.procs.accumulating_impact_accumulators[index] = accumulator;
67                continue;
68            }
69
70            ctx.instance.runtime.procs.accumulating_impact_accumulators[index] = accumulator;
71
72            let mut hook_ctx = HookCtx::new(
73                crate::context::HookCtxServices {
74                    program: ctx.program,
75                    instance: ctx.instance,
76                    sink: ctx.sink,
77                    rng: ctx.rng,
78                },
79                crate::context::HookCtxRequest::for_target(ctx.now, impact.target)
80                    .with_source(impact.source),
81            )
82            .with_driver_spell(impact.spell_id)
83            .with_source_damage_flags(crate::DamageFlags::PROC);
84
85            (proc.fire)(&mut hook_ctx, impact);
86        }
87    });
88
89    ctx.instance.runtime.scratch.accumulating_impacts = scratch;
90    ctx.instance
91        .runtime
92        .procs
93        .accumulating_impact_procs_dispatching = false;
94}
95
96pub(super) const fn advance_threshold_accumulator(
97    accumulator: &mut f64,
98    threshold: f64,
99    contribution: f64,
100    roll: f64,
101) -> bool {
102    *accumulator += roll * contribution * RANDOM_CONTRIBUTION_RANGE_MULTIPLIER;
103
104    if *accumulator < threshold {
105        return false;
106    }
107
108    *accumulator -= threshold;
109
110    true
111}