1use wowlab_types::constants::HUNDRED;
4
5use crate::{constants::AURA_PANDEMIC_THRESHOLD, rotation::backend::RotationBackend};
6
7pub(super) mod aura;
8pub(super) mod cooldown;
9pub(super) mod direct;
10pub(super) mod resource;
11pub(super) mod spell;
12pub(super) mod timestamp;
13pub(super) mod unit;
14
15pub(in crate::rotation) trait EvalRuleOps {
16 type Bool: Copy;
17 type Int: Copy;
18 type Float: Copy;
19
20 fn const_bool(&mut self, value: bool) -> Self::Bool;
21 fn const_i32(&mut self, value: i32) -> Self::Int;
22 fn const_f64(&mut self, value: f64) -> Self::Float;
23 fn sub_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float;
24 fn mul_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float;
25 fn safe_div_f(&mut self, numerator: Self::Float, denominator: Self::Float) -> Self::Float;
26 fn max_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float;
27 fn cmp_gt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool;
28 fn cmp_lt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool;
29 fn cmp_lte_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool;
30 fn cmp_gt_i(&mut self, lhs: Self::Int, rhs: Self::Int) -> Self::Bool;
31 fn and_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool;
32 fn or_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool;
33 fn select_f(
34 &mut self,
35 condition: Self::Bool,
36 if_true: Self::Float,
37 if_false: Self::Float,
38 ) -> Self::Float;
39 fn select_b(
40 &mut self,
41 condition: Self::Bool,
42 if_true: Self::Bool,
43 if_false: Self::Bool,
44 ) -> Self::Bool;
45}
46
47impl<B> EvalRuleOps for B
48where
49 B: RotationBackend,
50{
51 type Bool = B::Bool;
52 type Int = B::Int;
53 type Float = B::Float;
54
55 fn const_bool(&mut self, value: bool) -> Self::Bool {
56 RotationBackend::const_bool(self, value)
57 }
58
59 fn const_i32(&mut self, value: i32) -> Self::Int {
60 RotationBackend::const_i32(self, value)
61 }
62
63 fn const_f64(&mut self, value: f64) -> Self::Float {
64 RotationBackend::const_f64(self, value)
65 }
66
67 fn sub_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
68 RotationBackend::sub_f(self, lhs, rhs)
69 }
70
71 fn mul_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
72 RotationBackend::mul_f(self, lhs, rhs)
73 }
74
75 fn safe_div_f(&mut self, numerator: Self::Float, denominator: Self::Float) -> Self::Float {
76 RotationBackend::safe_div_f(self, numerator, denominator)
77 }
78
79 fn max_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
80 RotationBackend::max_f(self, lhs, rhs)
81 }
82
83 fn cmp_gt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
84 RotationBackend::cmp_gt_f(self, lhs, rhs)
85 }
86
87 fn cmp_lt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
88 RotationBackend::cmp_lt_f(self, lhs, rhs)
89 }
90
91 fn cmp_lte_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
92 RotationBackend::cmp_lte_f(self, lhs, rhs)
93 }
94
95 fn cmp_gt_i(&mut self, lhs: Self::Int, rhs: Self::Int) -> Self::Bool {
96 RotationBackend::cmp_gt_i(self, lhs, rhs)
97 }
98
99 fn and_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool {
100 RotationBackend::and_b(self, lhs, rhs)
101 }
102
103 fn or_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool {
104 RotationBackend::or_b(self, lhs, rhs)
105 }
106
107 fn select_f(
108 &mut self,
109 condition: Self::Bool,
110 if_true: Self::Float,
111 if_false: Self::Float,
112 ) -> Self::Float {
113 RotationBackend::select_f(self, condition, if_true, if_false)
114 }
115
116 fn select_b(
117 &mut self,
118 condition: Self::Bool,
119 if_true: Self::Bool,
120 if_false: Self::Bool,
121 ) -> Self::Bool {
122 RotationBackend::select_b(self, condition, if_true, if_false)
123 }
124}
125
126pub(in crate::rotation) struct ScalarEvalOps;
127
128impl EvalRuleOps for ScalarEvalOps {
129 type Bool = bool;
130 type Int = i32;
131 type Float = f64;
132
133 fn const_bool(&mut self, value: bool) -> Self::Bool {
134 value
135 }
136
137 fn const_i32(&mut self, value: i32) -> Self::Int {
138 value
139 }
140
141 fn const_f64(&mut self, value: f64) -> Self::Float {
142 value
143 }
144
145 fn sub_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
146 lhs - rhs
147 }
148
149 fn mul_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
150 lhs * rhs
151 }
152
153 fn safe_div_f(&mut self, numerator: Self::Float, denominator: Self::Float) -> Self::Float {
154 wowlab_types::numeric::safe_div(numerator, denominator)
155 }
156
157 fn max_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Float {
158 lhs.max(rhs)
159 }
160
161 fn cmp_gt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
162 lhs > rhs
163 }
164
165 fn cmp_lt_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
166 lhs < rhs
167 }
168
169 fn cmp_lte_f(&mut self, lhs: Self::Float, rhs: Self::Float) -> Self::Bool {
170 lhs <= rhs
171 }
172
173 fn cmp_gt_i(&mut self, lhs: Self::Int, rhs: Self::Int) -> Self::Bool {
174 lhs > rhs
175 }
176
177 fn and_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool {
178 lhs && rhs
179 }
180
181 fn or_b(&mut self, lhs: Self::Bool, rhs: Self::Bool) -> Self::Bool {
182 lhs || rhs
183 }
184
185 fn select_f(
186 &mut self,
187 condition: Self::Bool,
188 if_true: Self::Float,
189 if_false: Self::Float,
190 ) -> Self::Float {
191 if condition { if_true } else { if_false }
192 }
193
194 fn select_b(
195 &mut self,
196 condition: Self::Bool,
197 if_true: Self::Bool,
198 if_false: Self::Bool,
199 ) -> Self::Bool {
200 if condition { if_true } else { if_false }
201 }
202}
203
204pub(in crate::rotation) fn eval_cooldown_ready<O>(
205 ops: &mut O,
206 ready_at: O::Float,
207 current_charges: O::Int,
208 max_charges: O::Int,
209 now: O::Float,
210 bypass_active: O::Bool,
211) -> O::Bool
212where
213 O: EvalRuleOps,
214{
215 let one = ops.const_i32(1);
216 let zero = ops.const_i32(0);
217 let charged = ops.cmp_gt_i(max_charges, one);
218 let has_charge = ops.cmp_gt_i(current_charges, zero);
219 let time_ready = ops.cmp_lte_f(ready_at, now);
220 let ready = ops.select_b(charged, has_charge, time_ready);
221
222 ops.or_b(ready, bypass_active)
223}
224
225pub(in crate::rotation) fn eval_aura_refreshable<O>(
226 ops: &mut O,
227 expires_at: O::Float,
228 base_duration: O::Float,
229 now: O::Float,
230) -> O::Bool
231where
232 O: EvalRuleOps,
233{
234 let zero = ops.const_f64(0.0);
235 let positive = ops.cmp_gt_f(expires_at, zero);
236 let now_before = ops.cmp_lt_f(now, expires_at);
237 let active = ops.and_b(positive, now_before);
238 let remaining = ops.sub_f(expires_at, now);
239 let pandemic_factor = ops.const_f64(AURA_PANDEMIC_THRESHOLD);
240 let pandemic_window = ops.mul_f(pandemic_factor, base_duration);
241 let in_pandemic = ops.cmp_lt_f(remaining, pandemic_window);
242 let inactive_is_refreshable = ops.const_bool(true);
243
244 ops.select_b(active, in_pandemic, inactive_is_refreshable)
245}
246
247pub(in crate::rotation) fn eval_resource_pct<O>(
248 ops: &mut O,
249 current: O::Float,
250 max: O::Float,
251) -> O::Float
252where
253 O: EvalRuleOps,
254{
255 pct_from(ops, current, max)
256}
257
258pub(in crate::rotation) fn eval_resource_deficit<O>(
259 ops: &mut O,
260 current: O::Float,
261 max: O::Float,
262) -> O::Float
263where
264 O: EvalRuleOps,
265{
266 clamped_diff(ops, current, max)
267}
268
269pub(in crate::rotation) fn eval_resource_deficit_pct<O>(
270 ops: &mut O,
271 current: O::Float,
272 max: O::Float,
273) -> O::Float
274where
275 O: EvalRuleOps,
276{
277 let deficit = clamped_diff(ops, current, max);
278
279 pct_from(ops, deficit, max)
280}
281
282pub(in crate::rotation) fn eval_resource_time_to_max<O>(
283 ops: &mut O,
284 current: O::Float,
285 max: O::Float,
286 regen_per_sec: O::Float,
287) -> O::Float
288where
289 O: EvalRuleOps,
290{
291 let deficit = clamped_diff(ops, current, max);
292 let zero = ops.const_f64(0.0);
293 let positive_regen = ops.cmp_gt_f(regen_per_sec, zero);
294 let time = ops.safe_div_f(deficit, regen_per_sec);
295
296 ops.select_f(positive_regen, time, zero)
297}
298
299pub(in crate::rotation) fn eval_unit_health_pct<O>(
300 ops: &mut O,
301 health: O::Float,
302 max_health: O::Float,
303) -> O::Float
304where
305 O: EvalRuleOps,
306{
307 pct_from(ops, health, max_health)
308}
309
310pub(in crate::rotation) fn eval_unit_health_deficit<O>(
311 ops: &mut O,
312 health: O::Float,
313 max_health: O::Float,
314) -> O::Float
315where
316 O: EvalRuleOps,
317{
318 clamped_diff(ops, health, max_health)
319}
320
321fn clamped_diff<O>(ops: &mut O, current: O::Float, max: O::Float) -> O::Float
322where
323 O: EvalRuleOps,
324{
325 let difference = ops.sub_f(max, current);
326 let zero = ops.const_f64(0.0);
327
328 ops.max_f(difference, zero)
329}
330
331fn pct_from<O>(ops: &mut O, current: O::Float, max: O::Float) -> O::Float
332where
333 O: EvalRuleOps,
334{
335 let ratio = ops.safe_div_f(current, max);
336 let hundred = ops.const_f64(HUNDRED);
337
338 ops.mul_f(ratio, hundred)
339}