Skip to main content

wowlab_engine_combat/systems/procs/
resource_gain.rs

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