Skip to main content

wowlab_engine_domain/rotation/buffer/
aura.rs

1pub use wowlab_types::sim::{AuraKey, AuraOn, AuraProjectionKey};
2
3wowlab_engine_macros::define_slot! {
4    #[slot(domain = "aura", kind = keyed, key_domain = "aura")]
5    pub struct AuraSlot {
6        #[expr("is_active", Float, TimestampActive)]
7        #[expr("remaining", Float, TimestampRemaining)]
8        #[expr("elapsed", Float, TimestampElapsed)]
9        #[expr("is_inactive", Float, TimestampInactive)]
10        #[expr("is_refreshable", Float, AuraRefreshable)]
11        pub expires_at: f64,
12
13        #[expr("duration", Float, Direct)]
14        pub base_duration: f64,
15
16        #[expr("stacks", Int, Direct)]
17        #[expr("value", Int, Direct)]
18        #[expr("react", Int, Direct)]
19        pub stacks: i32,
20
21        #[expr("stacks_max", Int, Direct)]
22        pub max_stacks: i32,
23
24        #[expr("next_tick", Float, TimestampRemaining)]
25        pub next_tick: f64,
26
27        #[expr("tick_time", Float, Direct)]
28        #[expr("is_ticking", Float, PositiveFloat)]
29        pub tick_interval: f64,
30
31        /// Unhasted tick interval, set at apply; used to recompute next tick against current haste.
32        pub base_tick_interval: f64,
33
34        /// Current wall-clock multiplier from aura time-rate effects.
35        pub time_rate_multiplier: f64,
36
37        #[expr("ticks_remaining", Int, Direct)]
38        pub remaining_ticks: i32,
39
40        /// `1` when the aura carries a source-side stat snapshot, `0` for live/dynamic stats.
41        pub snapshot_has: i32,
42
43        pub snapshot_ap: f64,
44        pub snapshot_sp: f64,
45        pub snapshot_crit: f64,
46        pub snapshot_vers: f64,
47        pub snapshot_mastery_mult: f64,
48        /// Rolling damage multiplier; pandemic "keep higher" compares this.
49        pub snapshot_damage_mult: f64,
50    }
51
52    impl {
53        #[inline]
54        pub fn remove(&mut self) {
55            *self = Self::default();
56        }
57
58        /// True when the slot holds an aura instance, regardless of active state at any `now`.
59        #[inline]
60        #[must_use]
61        pub fn is_occupied(&self) -> bool {
62            self.expires_at > 0.0
63        }
64
65        /// True when occupied and not yet expired at `now`; exact negation of `is_inactive`.
66        #[inline]
67        #[must_use]
68        pub fn is_active(&self, now: f64) -> bool {
69            self.expires_at > 0.0 && now < self.expires_at
70        }
71
72        #[inline]
73        #[must_use]
74        pub fn is_inactive(&self, now: f64) -> bool {
75            !self.is_active(now)
76        }
77
78        #[must_use]
79        pub fn is_refreshable(&self, now: f64) -> bool {
80            crate::rotation::lower::ops::eval_aura_refreshable(
81                &mut crate::rotation::lower::ops::ScalarEvalOps,
82                self.expires_at,
83                self.base_duration,
84                now,
85            )
86        }
87
88        #[must_use]
89        pub fn is_ticking(&self) -> bool {
90            self.tick_interval > 0.0
91        }
92    }
93}
94
95/// Opaque occupied-aura state used only for an exact aura transfer.
96#[derive(Clone, Copy, Debug)]
97pub struct AuraTransferState(AuraSlot);
98
99impl AuraTransferState {
100    #[must_use]
101    pub fn capture(slot: &AuraSlot) -> Option<Self> {
102        slot.is_occupied().then_some(Self(*slot))
103    }
104
105    #[must_use]
106    pub fn with_expiry(mut self, expires_at: f64) -> Self {
107        self.0.expires_at = expires_at;
108
109        self
110    }
111
112    #[must_use]
113    pub fn restart_ticks(mut self, now: f64, duration: f64) -> Self {
114        if self.0.tick_interval > 0.0 {
115            self.0.next_tick = now + self.0.tick_interval;
116            self.0.remaining_ticks =
117                wowlab_types::numeric::f64_to_i32_saturating_ceil(duration / self.0.tick_interval);
118        }
119
120        self
121    }
122
123    #[must_use]
124    pub const fn stacks(self) -> i32 {
125        self.0.stacks
126    }
127
128    #[must_use]
129    pub const fn expires_at(self) -> f64 {
130        self.0.expires_at
131    }
132
133    #[must_use]
134    pub const fn next_tick(self) -> f64 {
135        self.0.next_tick
136    }
137
138    #[must_use]
139    pub const fn tick_interval(self) -> f64 {
140        self.0.tick_interval
141    }
142
143    pub(super) const fn slot(self) -> AuraSlot {
144        self.0
145    }
146}