wowlab_engine_domain/rotation/lower/ops/
spell.rs1use super::super::super::{backend::RotationBackend, context::SpellUsableOffsets};
4use crate::rotation::buffer::{AuraSlot, SpellSlot};
5
6pub(crate) struct SpellUsableArgs {
7 pub(crate) spell_offset: usize,
8 pub(crate) resource_current_offset: Option<usize>,
9 pub(crate) secondary_resource_current_offset: Option<usize>,
10 pub(crate) cooldown_base: Option<usize>,
11 pub(crate) gating_aura_offset: Option<usize>,
12 pub(crate) gating_aura_min_stacks: u8,
13 pub(crate) cost_bypass_aura_offset: Option<usize>,
14 pub(crate) cooldown_bypass_aura_offset: Option<usize>,
15 pub(crate) override_spell_offset: Option<usize>,
16 pub(crate) override_cooldown_base: Option<usize>,
17 pub(crate) override_aura_offset: Option<usize>,
18}
19
20impl SpellUsableArgs {
21 pub(crate) fn from_offsets(spell_offset: usize, e: &SpellUsableOffsets) -> Self {
22 Self {
23 spell_offset,
24 resource_current_offset: e.resource_current,
25 secondary_resource_current_offset: e.secondary_resource_current,
26 cooldown_base: e.cooldown_base,
27 gating_aura_offset: e.gating_aura_offset,
28 gating_aura_min_stacks: e.gating_aura_min_stacks,
29 cost_bypass_aura_offset: e.cost_bypass_aura_offset,
30 cooldown_bypass_aura_offset: e.cooldown_bypass_aura_offset,
31 override_spell_offset: e.override_spell_offset,
32 override_cooldown_base: e.override_cooldown_base,
33 override_aura_offset: e.override_aura_offset,
34 }
35 }
36
37 pub(crate) fn bare(spell_offset: usize) -> Self {
38 Self {
39 spell_offset,
40 resource_current_offset: None,
41 secondary_resource_current_offset: None,
42 cooldown_base: None,
43 gating_aura_offset: None,
44 gating_aura_min_stacks: 0,
45 cost_bypass_aura_offset: None,
46 cooldown_bypass_aura_offset: None,
47 override_spell_offset: None,
48 override_cooldown_base: None,
49 override_aura_offset: None,
50 }
51 }
52}
53
54const IS_ENABLED_OFFSET: usize = SpellSlot::OFF_IS_ENABLED - SpellSlot::OFF_IS_USABLE;
55const COST_OFFSET: usize = SpellSlot::OFF_COST - SpellSlot::OFF_IS_USABLE;
56const SECONDARY_COST_OFFSET: usize = SpellSlot::OFF_SECONDARY_COST - SpellSlot::OFF_IS_USABLE;
57
58const AURA_STACKS_OFFSET: usize = AuraSlot::OFF_STACKS - AuraSlot::OFF_EXPIRES_AT;
59
60pub(crate) fn spell_enabled<B>(b: &mut B, spell_offset: usize) -> B::Bool
61where
62 B: RotationBackend,
63{
64 #[cfg(feature = "rotation-optimizations")]
65 return b.load_bool(spell_offset + IS_ENABLED_OFFSET);
66
67 #[cfg(not(feature = "rotation-optimizations"))]
68 let enabled = b.load_i32(spell_offset + IS_ENABLED_OFFSET);
69 #[cfg(not(feature = "rotation-optimizations"))]
70 let zero = b.const_i32(0);
71
72 #[cfg(not(feature = "rotation-optimizations"))]
73 b.cmp_ne_i(enabled, zero)
74}
75
76fn affordable_or_free<B>(
77 b: &mut B,
78 cost_off: usize,
79 cur_off: usize,
80 bypass_off: Option<usize>,
81) -> B::Bool
82where
83 B: RotationBackend,
84{
85 let zero_f = b.const_f64(0.0);
86 let cost = b.load_f64(cost_off);
87 let cur = b.load_f64(cur_off);
88 let affordable = b.cmp_gte_f(cur, cost);
89 let no_cost = b.cmp_eq_f(cost, zero_f);
90 let mut cost_ok = b.or_b(no_cost, affordable);
91
92 if let Some(bypass_off) = bypass_off {
93 let bexp = b.load_f64(bypass_off);
94 let now = b.now();
95 let bypass_active = b.cmp_gt_f(bexp, now);
96
97 cost_ok = b.or_b(cost_ok, bypass_active);
98 }
99
100 cost_ok
101}
102
103struct SpellUsableAt {
104 spell_offset: usize,
105 resource_current_offset: Option<usize>,
106 secondary_resource_current_offset: Option<usize>,
107 cooldown_base: Option<usize>,
108 gating_aura_offset: Option<usize>,
109 gating_aura_min_stacks: u8,
110 cost_bypass_aura_offset: Option<usize>,
111 cooldown_bypass_aura_offset: Option<usize>,
112}
113
114fn spell_usable_at<B>(b: &mut B, args: &SpellUsableAt) -> B::Bool
115where
116 B: RotationBackend,
117{
118 let mut ok = spell_enabled(b, args.spell_offset);
119
120 if let Some(res_off) = args.resource_current_offset {
121 let cost_ok = affordable_or_free(
122 b,
123 args.spell_offset + COST_OFFSET,
124 res_off,
125 args.cost_bypass_aura_offset,
126 );
127
128 ok = b.and_b(ok, cost_ok);
129 }
130
131 if let Some(sec_res_off) = args.secondary_resource_current_offset {
134 let sec_ok = affordable_or_free(
135 b,
136 args.spell_offset + SECONDARY_COST_OFFSET,
137 sec_res_off,
138 None,
139 );
140
141 ok = b.and_b(ok, sec_ok);
142 }
143
144 if let Some(aura_off) = args.gating_aura_offset {
145 let expires_at = b.load_f64(aura_off);
146 let now = b.now();
147 let aura_active = b.cmp_gt_f(expires_at, now);
148 let stacks = b.load_i32(aura_off + AURA_STACKS_OFFSET);
149 let min_stacks = b.const_i32(i32::from(args.gating_aura_min_stacks));
150 let stacks_ok = b.cmp_gte_i(stacks, min_stacks);
151 let gate_ok = b.and_b(aura_active, stacks_ok);
152
153 ok = b.and_b(ok, gate_ok);
154 }
155
156 if let Some(cd_base) = args.cooldown_base {
157 let cooldown_ok =
158 super::cooldown::cooldown_ready(b, cd_base, args.cooldown_bypass_aura_offset);
159
160 ok = b.and_b(ok, cooldown_ok);
161 }
162
163 ok
164}
165
166pub(crate) fn spell_usable<B>(b: &mut B, args: &SpellUsableArgs) -> B::Bool
167where
168 B: RotationBackend,
169{
170 let base_ok = spell_usable_at(
171 b,
172 &SpellUsableAt {
173 spell_offset: args.spell_offset,
174 resource_current_offset: args.resource_current_offset,
175 secondary_resource_current_offset: args.secondary_resource_current_offset,
176 cooldown_base: args.cooldown_base,
177 gating_aura_offset: args.gating_aura_offset,
178 gating_aura_min_stacks: args.gating_aura_min_stacks,
179 cost_bypass_aura_offset: args.cost_bypass_aura_offset,
180 cooldown_bypass_aura_offset: args.cooldown_bypass_aura_offset,
181 },
182 );
183 let (Some(replacement_offset), Some(aura_offset)) =
184 (args.override_spell_offset, args.override_aura_offset)
185 else {
186 return base_ok;
187 };
188 let replacement_ok = spell_usable_at(
189 b,
190 &SpellUsableAt {
191 spell_offset: replacement_offset,
192 resource_current_offset: args.resource_current_offset,
193 secondary_resource_current_offset: args.secondary_resource_current_offset,
194 cooldown_base: args.override_cooldown_base,
195 gating_aura_offset: None,
196 gating_aura_min_stacks: 0,
197 cost_bypass_aura_offset: None,
198 cooldown_bypass_aura_offset: None,
199 },
200 );
201 let expires_at = b.load_f64(aura_offset);
202 let now = b.now();
203 let override_active = b.cmp_gt_f(expires_at, now);
204 let override_inactive = b.not_b(override_active);
205 let use_replacement = b.and_b(override_active, replacement_ok);
206 let use_base = b.and_b(override_inactive, base_ok);
207 let either = b.or_b(use_replacement, use_base);
208
209 let zero_i = b.const_i32(0);
214 let base_enabled_raw = b.load_i32(args.spell_offset + IS_ENABLED_OFFSET);
215 let base_enabled = b.cmp_ne_i(base_enabled_raw, zero_i);
216
217 b.and_b(base_enabled, either)
218}