wowlab_engine_combat/systems/procs/
resource_gain.rs1use 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}