Skip to main content

wowlab_engine_domain/rotation/lower/ops/
cooldown.rs

1use 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}