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
60fn affordable_or_free<B>(
61 b: &mut B,
62 cost_off: usize,
63 cur_off: usize,
64 bypass_off: Option<usize>,
65) -> B::Bool
66where
67 B: RotationBackend,
68{
69 let zero_f = b.const_f64(0.0);
70 let cost = b.load_f64(cost_off);
71 let cur = b.load_f64(cur_off);
72 let affordable = b.cmp_gte_f(cur, cost);
73 let no_cost = b.cmp_eq_f(cost, zero_f);
74 let mut cost_ok = b.or_b(no_cost, affordable);
75
76 if let Some(bypass_off) = bypass_off {
77 let bexp = b.load_f64(bypass_off);
78 let now = b.now();
79 let bypass_active = b.cmp_gt_f(bexp, now);
80
81 cost_ok = b.or_b(cost_ok, bypass_active);
82 }
83
84 cost_ok
85}
86
87struct SpellUsableAt {
88 spell_offset: usize,
89 resource_current_offset: Option<usize>,
90 secondary_resource_current_offset: Option<usize>,
91 cooldown_base: Option<usize>,
92 gating_aura_offset: Option<usize>,
93 gating_aura_min_stacks: u8,
94 cost_bypass_aura_offset: Option<usize>,
95 cooldown_bypass_aura_offset: Option<usize>,
96}
97
98fn spell_usable_at<B>(b: &mut B, args: &SpellUsableAt) -> B::Bool
99where
100 B: RotationBackend,
101{
102 let zero_i = b.const_i32(0);
103
104 let is_enabled = b.load_i32(args.spell_offset + IS_ENABLED_OFFSET);
105 let mut ok = b.cmp_ne_i(is_enabled, zero_i);
106
107 if let Some(res_off) = args.resource_current_offset {
108 let cost_ok = affordable_or_free(
109 b,
110 args.spell_offset + COST_OFFSET,
111 res_off,
112 args.cost_bypass_aura_offset,
113 );
114
115 ok = b.and_b(ok, cost_ok);
116 }
117
118 if let Some(sec_res_off) = args.secondary_resource_current_offset {
121 let sec_ok = affordable_or_free(
122 b,
123 args.spell_offset + SECONDARY_COST_OFFSET,
124 sec_res_off,
125 None,
126 );
127
128 ok = b.and_b(ok, sec_ok);
129 }
130
131 if let Some(aura_off) = args.gating_aura_offset {
132 let expires_at = b.load_f64(aura_off);
133 let now = b.now();
134 let aura_active = b.cmp_gt_f(expires_at, now);
135 let stacks = b.load_i32(aura_off + AURA_STACKS_OFFSET);
136 let min_stacks = b.const_i32(i32::from(args.gating_aura_min_stacks));
137 let stacks_ok = b.cmp_gte_i(stacks, min_stacks);
138 let gate_ok = b.and_b(aura_active, stacks_ok);
139
140 ok = b.and_b(ok, gate_ok);
141 }
142
143 if let Some(cd_base) = args.cooldown_base {
144 let cooldown_ok =
145 super::cooldown::cooldown_ready(b, cd_base, args.cooldown_bypass_aura_offset);
146
147 ok = b.and_b(ok, cooldown_ok);
148 }
149
150 ok
151}
152
153pub(crate) fn spell_usable<B>(b: &mut B, args: &SpellUsableArgs) -> B::Bool
154where
155 B: RotationBackend,
156{
157 let base_ok = spell_usable_at(
158 b,
159 &SpellUsableAt {
160 spell_offset: args.spell_offset,
161 resource_current_offset: args.resource_current_offset,
162 secondary_resource_current_offset: args.secondary_resource_current_offset,
163 cooldown_base: args.cooldown_base,
164 gating_aura_offset: args.gating_aura_offset,
165 gating_aura_min_stacks: args.gating_aura_min_stacks,
166 cost_bypass_aura_offset: args.cost_bypass_aura_offset,
167 cooldown_bypass_aura_offset: args.cooldown_bypass_aura_offset,
168 },
169 );
170 let (Some(replacement_offset), Some(aura_offset)) =
171 (args.override_spell_offset, args.override_aura_offset)
172 else {
173 return base_ok;
174 };
175 let replacement_ok = spell_usable_at(
176 b,
177 &SpellUsableAt {
178 spell_offset: replacement_offset,
179 resource_current_offset: args.resource_current_offset,
180 secondary_resource_current_offset: args.secondary_resource_current_offset,
181 cooldown_base: args.override_cooldown_base,
182 gating_aura_offset: None,
183 gating_aura_min_stacks: 0,
184 cost_bypass_aura_offset: None,
185 cooldown_bypass_aura_offset: None,
186 },
187 );
188 let expires_at = b.load_f64(aura_offset);
189 let now = b.now();
190 let override_active = b.cmp_gt_f(expires_at, now);
191 let override_inactive = b.not_b(override_active);
192 let use_replacement = b.and_b(override_active, replacement_ok);
193 let use_base = b.and_b(override_inactive, base_ok);
194 let either = b.or_b(use_replacement, use_base);
195
196 let zero_i = b.const_i32(0);
201 let base_enabled_raw = b.load_i32(args.spell_offset + IS_ENABLED_OFFSET);
202 let base_enabled = b.cmp_ne_i(base_enabled_raw, zero_i);
203
204 b.and_b(base_enabled, either)
205}