wowlab_engine_domain/rotation/lower/ops/
cooldown.rs1use super::super::super::backend::RotationBackend;
2use crate::rotation::buffer::CooldownSlot;
3
4const CURRENT_CHARGES_OFFSET: usize =
5 CooldownSlot::OFF_CURRENT_CHARGES - CooldownSlot::OFF_READY_AT;
6const MAX_CHARGES_OFFSET: usize = CooldownSlot::OFF_MAX_CHARGES - CooldownSlot::OFF_READY_AT;
7const NEXT_CHARGE_AT_OFFSET: usize = CooldownSlot::OFF_NEXT_CHARGE_AT - CooldownSlot::OFF_READY_AT;
8const RECHARGE_TIME_OFFSET: usize = CooldownSlot::OFF_RECHARGE_TIME - CooldownSlot::OFF_READY_AT;
9
10pub(crate) fn cooldown_ready<B>(
11 b: &mut B,
12 offset: usize,
13 bypass_aura_offset: Option<usize>,
14) -> B::Bool
15where
16 B: RotationBackend,
17{
18 let ready_at = b.load_f64(offset);
19 let cur = b.load_i32(offset + CURRENT_CHARGES_OFFSET);
20 let max = b.load_i32(offset + MAX_CHARGES_OFFSET);
21 let now = b.now();
22 let bypass_active = match bypass_aura_offset {
23 Some(bypass_off) => {
24 let expires_at = b.load_f64(bypass_off);
25
26 b.cmp_gt_f(expires_at, now)
27 }
28 None => b.const_bool(false),
29 };
30
31 super::eval_cooldown_ready(b, ready_at, cur, max, now, bypass_active)
32}
33
34pub(crate) fn cooldown_full_recharge<B>(b: &mut B, offset: usize) -> B::Float
35where
36 B: RotationBackend,
37{
38 let ready_at = b.load_f64(offset);
39 let next_charge_at = b.load_f64(offset + NEXT_CHARGE_AT_OFFSET);
40 let recharge_time = b.load_f64(offset + RECHARGE_TIME_OFFSET);
41 let cur = b.load_i32(offset + CURRENT_CHARGES_OFFSET);
42 let max = b.load_i32(offset + MAX_CHARGES_OFFSET);
43 let now = b.now();
44 let zero = b.const_f64(0.0);
45 let one_i = b.const_i32(1);
46
47 let single_remaining = b.sub_f(ready_at, now);
48 let single = b.max_f(single_remaining, zero);
49
50 let next_remaining = b.sub_f(next_charge_at, now);
51 let next_remaining = b.max_f(next_remaining, zero);
52 let max_f = b.int_to_float(max);
53 let cur_f = b.int_to_float(cur);
54 let missing = b.sub_f(max_f, cur_f);
55 let one_f = b.const_f64(1.0);
56 let extra_charges = b.sub_f(missing, one_f);
57 let extra_charges = b.max_f(extra_charges, zero);
58 let extra_time = b.mul_f(extra_charges, recharge_time);
59 let multi = b.add_f(next_remaining, extra_time);
60
61 let at_full = b.cmp_gte_i(cur, max);
62 let multi = b.select_f(at_full, zero, multi);
63
64 let has_multiple = b.cmp_gt_i(max, one_i);
65
66 b.select_f(has_multiple, multi, single)
67}
68
69#[cfg(test)]
70mod tests {
71 use googletest::prelude::*;
72 use wowlab_types::sim::FastMap;
73
74 use super::*;
75 use crate::rotation::{
76 buffer::{BufferOffsets, ByteOffset, DenseBuffer, SlotMaps},
77 interp_backend::{InterpBackend, InterpFrame},
78 };
79
80 const OFFSET: usize = 64;
81
82 fn backend(buffer: &mut DenseBuffer) -> InterpBackend<'_> {
83 InterpBackend::new(InterpFrame { buffer, now: 10.0 })
84 }
85
86 fn empty_buffer() -> DenseBuffer {
87 let offsets = BufferOffsets {
88 slots: SlotMaps::default(),
89 player: ByteOffset::new(0),
90 combat: ByteOffset::new(0),
91 pet: ByteOffset::new(0),
92 standalone: FastMap::default(),
93 };
94
95 DenseBuffer::new(offsets, 512)
96 }
97
98 #[gtest]
99 fn cooldown_ready_single_charge_uses_time() -> Result<()> {
100 let mut buf = empty_buffer();
101 let mut b = backend(&mut buf);
102
103 b.store_i32(OFFSET + MAX_CHARGES_OFFSET, 1);
104 b.store_i32(OFFSET + CURRENT_CHARGES_OFFSET, 0);
105
106 b.store_f64(OFFSET, 5.0);
107 verify_that!(cooldown_ready(&mut b, OFFSET, None), eq(true))?;
108
109 b.store_f64(OFFSET, 15.0);
110 verify_that!(cooldown_ready(&mut b, OFFSET, None), eq(false))?;
111
112 Ok(())
113 }
114
115 #[gtest]
116 fn cooldown_ready_multi_charge_uses_count() -> Result<()> {
117 let mut buf = empty_buffer();
118 let mut b = backend(&mut buf);
119
120 b.store_i32(OFFSET + MAX_CHARGES_OFFSET, 2);
121 b.store_f64(OFFSET, 99.0);
122
123 b.store_i32(OFFSET + CURRENT_CHARGES_OFFSET, 1);
124 verify_that!(cooldown_ready(&mut b, OFFSET, None), eq(true))?;
125
126 b.store_i32(OFFSET + CURRENT_CHARGES_OFFSET, 0);
127 verify_that!(cooldown_ready(&mut b, OFFSET, None), eq(false))?;
128
129 Ok(())
130 }
131
132 #[gtest]
133 fn cooldown_full_recharge_single_charge_clamps() -> Result<()> {
134 let mut buf = empty_buffer();
135 let mut b = backend(&mut buf);
136
137 b.store_i32(OFFSET + MAX_CHARGES_OFFSET, 1);
138
139 b.store_f64(OFFSET, 13.0);
140 verify_that!(cooldown_full_recharge(&mut b, OFFSET), near(3.0, 1e-9))?;
141
142 b.store_f64(OFFSET, 8.0);
143 verify_that!(cooldown_full_recharge(&mut b, OFFSET), near(0.0, 1e-9))?;
144
145 Ok(())
146 }
147
148 #[gtest]
149 fn cooldown_full_recharge_multi_charge_not_full() -> Result<()> {
150 let mut buf = empty_buffer();
151 let mut b = backend(&mut buf);
152
153 b.store_i32(OFFSET + MAX_CHARGES_OFFSET, 3);
154 b.store_i32(OFFSET + CURRENT_CHARGES_OFFSET, 1);
155 b.store_f64(OFFSET + NEXT_CHARGE_AT_OFFSET, 12.0);
156 b.store_f64(OFFSET + RECHARGE_TIME_OFFSET, 5.0);
157 verify_that!(cooldown_full_recharge(&mut b, OFFSET), near(7.0, 1e-9))?;
158
159 Ok(())
160 }
161
162 #[gtest]
163 fn cooldown_full_recharge_multi_charge_at_full_is_zero() -> Result<()> {
164 let mut buf = empty_buffer();
165 let mut b = backend(&mut buf);
166
167 b.store_i32(OFFSET + MAX_CHARGES_OFFSET, 3);
168 b.store_i32(OFFSET + CURRENT_CHARGES_OFFSET, 3);
169 b.store_f64(OFFSET + NEXT_CHARGE_AT_OFFSET, 12.0);
170 b.store_f64(OFFSET + RECHARGE_TIME_OFFSET, 5.0);
171 verify_that!(cooldown_full_recharge(&mut b, OFFSET), near(0.0, 1e-9))?;
172
173 Ok(())
174 }
175
176 #[gtest]
177 fn cooldown_full_recharge_one_below_max_has_no_extra_charges() -> Result<()> {
178 let mut buf = empty_buffer();
179 let mut b = backend(&mut buf);
180
181 b.store_i32(OFFSET + MAX_CHARGES_OFFSET, 3);
182 b.store_i32(OFFSET + CURRENT_CHARGES_OFFSET, 2);
183 b.store_f64(OFFSET + NEXT_CHARGE_AT_OFFSET, 12.0);
184 b.store_f64(OFFSET + RECHARGE_TIME_OFFSET, 5.0);
185 verify_that!(cooldown_full_recharge(&mut b, OFFSET), near(2.0, 1e-9))?;
186
187 Ok(())
188 }
189}