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