Skip to main content

wowlab_engine_combat/systems/procs/
resource_gain.rs

1use super::{
2    CombatCtx, CombatInstance, HookCtx, RESOURCE_SOURCE_AUTO_ATTACK, ResourceEvent,
3    ResourceEventKind, ResourceGain, ResourceGainEvent, ResourceGainProcAttempts,
4    ResourceObservation, ResourceTelemetry, ResourceType, ResourceValues, TelemetrySink,
5    with_scratch,
6};
7
8#[inline]
9pub(crate) fn emit_resource(
10    program: &crate::state::CombatProgram,
11    state: &CombatInstance,
12    sink: &mut TelemetrySink,
13    event: ResourceTelemetry,
14) {
15    let rt: Option<ResourceType> = if event.secondary {
16        program.config.base_stats.secondary_resource_type
17    } else {
18        program.config.base_stats.resource_type
19    };
20
21    if let Some(rt) = rt
22        && let Some(res) = program.buffer_resource(state, rt)
23    {
24        sink.emit_resource(
25            &ResourceEvent::new(
26                ResourceObservation {
27                    kind: event.kind,
28                    resource_type: u8::from(rt),
29                    values: ResourceValues {
30                        amount: event.amount,
31                        current: res.current,
32                        maximum: res.max,
33                    },
34                },
35                event.now.as_millis(),
36            )
37            .with_source(event.source_spell_id),
38        );
39    }
40}
41
42// #t(fn: rust_unchecked_indexing) finalization builds attempt counters in exact proc registration order and equal length.
43pub(crate) fn fire_resource_gain_procs(ctx: &mut HookCtx<'_>, event: ResourceGainEvent) {
44    if event.actual <= 0.0
45        || ctx.program.resource_gain_procs.is_empty()
46        || ctx.instance.runtime.procs.resource_gain_procs_dispatching
47    {
48        return;
49    }
50
51    ctx.instance.runtime.procs.resource_gain_procs_dispatching = true;
52    let scratch = std::mem::take(&mut ctx.instance.runtime.scratch.resource_gains);
53    let scratch = with_scratch(&ctx.program.resource_gain_procs, scratch, |entries| {
54        for (index, proc) in entries.iter().enumerate() {
55            if proc.secondary != event.secondary {
56                continue;
57            }
58
59            let attempts = match proc.attempt_mode {
60                ResourceGainProcAttempts::Event => 1,
61                ResourceGainProcAttempts::PerWholeUnit => {
62                    wowlab_types::numeric::f64_to_u32_saturating_trunc(event.actual.floor())
63                }
64            };
65
66            for _ in 0..attempts {
67                let Some(runtime_attempts) = ctx
68                    .instance
69                    .runtime
70                    .procs
71                    .resource_gain_attempts
72                    .get_mut(index)
73                else {
74                    continue;
75                };
76
77                *runtime_attempts = runtime_attempts.saturating_add(1);
78                let chance = proc.prd_constant * f64::from(*runtime_attempts);
79                let success = wowlab_engine_rng::proc_chance(ctx.rng(), chance);
80
81                if success {
82                    ctx.instance.runtime.procs.resource_gain_attempts[index] = 0;
83                    (proc.fire)(ctx, event);
84                }
85            }
86        }
87    });
88
89    ctx.instance.runtime.scratch.resource_gains = scratch;
90    ctx.instance.runtime.procs.resource_gain_procs_dispatching = false;
91}
92
93const fn resource_gain_source_spell_id(source: crate::state::ResourceGainSource) -> u32 {
94    match source {
95        crate::state::ResourceGainSource::Unattributed => 0,
96        crate::state::ResourceGainSource::AutoAttack => RESOURCE_SOURCE_AUTO_ATTACK,
97        crate::state::ResourceGainSource::Spell(id)
98        | crate::state::ResourceGainSource::SpellEffect { spell_id: id, .. } => id,
99    }
100}
101
102pub(crate) fn gain_resource_with_procs(
103    ctx: &mut CombatCtx<'_>,
104    gain: ResourceGain,
105    secondary: bool,
106) -> (f64, f64) {
107    let (actual, wasted) = if secondary {
108        crate::systems::gain_secondary_resource_from(ctx.program, ctx.instance, gain, ctx.now)
109    } else {
110        crate::systems::gain_resource_from(ctx.program, ctx.instance, gain, ctx.now)
111    };
112
113    emit_resource(
114        ctx.program,
115        ctx.instance,
116        ctx.sink,
117        ResourceTelemetry {
118            kind: ResourceEventKind::Gain { wasted },
119            amount: actual + wasted,
120            secondary,
121            now: ctx.now,
122            source_spell_id: resource_gain_source_spell_id(gain.source),
123        },
124    );
125    let event = ResourceGainEvent {
126        actual,
127        wasted,
128        source: gain.source,
129        secondary,
130    };
131    let source = ctx.source;
132    let target = ctx.target;
133    let now = ctx.now;
134    let mut hook_ctx = HookCtx::new(
135        crate::context::HookCtxServices {
136            program: ctx.program,
137            instance: ctx.instance,
138            sink: ctx.sink,
139            rng: ctx.rng,
140        },
141        crate::context::HookCtxRequest::for_target(now, target).with_source(source),
142    );
143
144    fire_resource_gain_procs(&mut hook_ctx, event);
145
146    (actual, wasted)
147}
148
149pub(crate) fn gain_hook_resource_with_procs(
150    ctx: &mut HookCtx<'_>,
151    gain: ResourceGain,
152    secondary: bool,
153) -> (f64, f64) {
154    let (actual, wasted) = if secondary {
155        crate::systems::gain_secondary_resource_from(ctx.program, ctx.instance, gain, ctx.now)
156    } else {
157        crate::systems::gain_resource_from(ctx.program, ctx.instance, gain, ctx.now)
158    };
159
160    emit_resource(
161        ctx.program,
162        ctx.instance,
163        ctx.sink,
164        ResourceTelemetry {
165            kind: ResourceEventKind::Gain { wasted },
166            amount: actual + wasted,
167            secondary,
168            now: ctx.now,
169            source_spell_id: resource_gain_source_spell_id(gain.source),
170        },
171    );
172    fire_resource_gain_procs(
173        ctx,
174        ResourceGainEvent {
175            actual,
176            wasted,
177            source: gain.source,
178            secondary,
179        },
180    );
181
182    (actual, wasted)
183}