wowlab_engine_combat/context/hook/
rppm.rs1use wowlab_engine_rng as stochastic;
2use wowlab_types::sim::{SimTime, SpellIdx};
3
4use super::{ProcOps, proc_counters};
5use crate::{context::HookCtx, state::LocalRppmIdx, systems};
6
7impl ProcOps for HookCtx<'_> {
8 fn roll_rppm(&mut self, idx: LocalRppmIdx) -> bool {
9 let now_secs = self.now.as_secs_f64();
10 let scaling = systems::current_rppm_scaling(&self.view().combat());
11
12 if let Some(tracker) = self.state.defs.rppm_trackers.get_mut(idx.as_usize()) {
13 stochastic::roll_rppm(tracker, now_secs, scaling, self.rng)
14 } else {
15 false
16 }
17 }
18
19 fn roll_threshold(
21 &mut self,
22 idx: crate::state::LocalThresholdIdx,
23 increment_scale: f64,
24 ) -> bool {
25 let Some(tracker) = self.state.defs.threshold_trackers.get_mut(idx.as_usize()) else {
26 return false;
27 };
28
29 if tracker.accumulated.is_nan() {
30 tracker.accumulated = (self.rng)();
31 }
32
33 let increment = (self.rng)() * tracker.increment_max * increment_scale;
34
35 stochastic::roll_threshold(tracker, increment)
36 }
37
38 fn roll_item_rppm(&mut self, item_id: u32) -> bool {
39 let Some(&idx) = self.state.index.item_rppm_indices.get(&item_id) else {
40 return false;
41 };
42
43 self.roll_rppm(idx)
44 }
45
46 fn roll_system_rppm(&mut self, driver_spell_id: u32) -> bool {
52 if !self.proc_category_ready(driver_spell_id) {
53 return false;
54 }
55
56 let Some(&idx) = self.state.index.system_rppm_indices.get(&driver_spell_id) else {
57 return false;
58 };
59
60 if !self.roll_rppm(idx) {
61 return false;
62 }
63
64 self.start_proc_category_cooldown(driver_spell_id);
65
66 true
67 }
68
69 fn proc_category_ready(&self, driver_spell_id: u32) -> bool {
71 self.state
72 .runtime
73 .procs
74 .proc_category_ready_at
75 .get(&driver_spell_id)
76 .is_none_or(|ready_at| self.now >= *ready_at)
77 }
78
79 fn start_proc_category_cooldown(&mut self, driver_spell_id: u32) {
81 let recovery_ms = self
82 .state
83 .config
84 .game_data
85 .proc_category_recovery_ms(SpellIdx::from_raw(driver_spell_id))
86 .unwrap_or(0);
87
88 if recovery_ms == 0 {
89 return;
90 }
91
92 self.state.runtime.procs.proc_category_ready_at.insert(
93 driver_spell_id,
94 self.now.saturating_add(SimTime::from_millis(recovery_ms)),
95 );
96 }
97
98 fn roll_enchant_rppm(&mut self, enchantment_id: u32) -> bool {
99 let Some(&idx) = self.state.index.enchant_rppm_indices.get(&enchantment_id) else {
100 return false;
101 };
102
103 self.roll_rppm(idx)
104 }
105
106 fn roll_accumulating_proc(&mut self, driver_spell_id: u32, chance_per_attempt: f64) -> bool {
107 self.roll_attempt_proc(driver_spell_id, |attempt| {
108 chance_per_attempt * f64::from(attempt)
109 })
110 }
111
112 fn roll_attempt_proc(
114 &mut self,
115 driver_spell_id: u32,
116 chance_for_attempt: impl FnOnce(u32) -> f64,
117 ) -> bool {
118 let attempts = self
119 .state
120 .runtime
121 .procs
122 .proc_attempts
123 .get(&driver_spell_id)
124 .copied()
125 .unwrap_or(0);
126 let attempt = attempts.saturating_add(1);
127
128 if stochastic::proc_chance(self.rng, chance_for_attempt(attempt)) {
129 self.state
130 .runtime
131 .procs
132 .proc_attempts
133 .remove(&driver_spell_id);
134
135 true
136 } else {
137 self.state
138 .runtime
139 .procs
140 .proc_attempts
141 .insert(driver_spell_id, attempt);
142
143 false
144 }
145 }
146
147 fn add_proc_counter(&mut self, driver_spell_id: u32, amount: u32) -> u32 {
149 proc_counters::add(self, driver_spell_id, amount)
150 }
151
152 fn reset_proc_counter(&mut self, driver_spell_id: u32) {
154 proc_counters::reset(self, driver_spell_id);
155 }
156
157 fn proc_counter(&self, driver_spell_id: u32) -> u32 {
159 proc_counters::get(self, driver_spell_id)
160 }
161
162 fn mark_proc_flags_once(&mut self, driver_spell_id: u32, flags: u32) -> bool {
164 proc_counters::mark_once(self, driver_spell_id, flags)
165 }
166
167 fn consume_proc_counter(&mut self, driver_spell_id: u32, amount: u32) -> u32 {
169 proc_counters::consume(self, driver_spell_id, amount)
170 }
171
172 fn advance_proc_counter_by(
174 &mut self,
175 driver_spell_id: u32,
176 amount: u32,
177 threshold: u32,
178 ) -> bool {
179 proc_counters::advance(self, driver_spell_id, amount, threshold)
180 }
181
182 fn advance_proc_counter(&mut self, driver_spell_id: u32, threshold: u32) -> bool {
184 self.advance_proc_counter_by(driver_spell_id, 1, threshold)
185 }
186}