1use wowlab_types::{constants::WAIT_UNTIL_POLL_SECS, game::GearSlot, sim::Condition};
2
3#[cfg(feature = "rotation-optimizations")]
4use super::super::super::{
5 buffer::EvalKind,
6 condition::{ResolvedKey, resolve_field_read},
7};
8use super::{
9 super::{
10 super::{backend::RotationBackend, condition::spell_usable_condition},
11 condition::lower_condition,
12 },
13 LowerCtx, LoweredGuard, failing_condition, lower_optional_guard, record_guarded,
14 record_skipped, skip_in_run,
15};
16
17pub(super) fn lower_cast<B>(
18 ctx: &mut LowerCtx<'_, '_, '_, B>,
19 spell: &str,
20 empower_rank: Option<u8>,
21 condition: Option<&Condition>,
22) where
23 B: RotationBackend,
24{
25 let Ok(spell_id) = ctx.lo.resolver.resolve_spell(spell) else {
26 record_skipped(ctx, failing_condition(condition));
27
28 return;
29 };
30
31 if skip_in_run(ctx, condition) {
32 return;
33 }
34
35 let cast_condition = cast_condition(ctx, spell, spell_id, condition);
36
37 #[cfg(feature = "rotation-optimizations")]
38 {
39 let operands = match &cast_condition {
40 Condition::And { operands } => operands.as_slice(),
41 condition => std::slice::from_ref(condition),
42 };
43
44 if let Some(guard) =
45 lower_cast_conjunction(ctx, operands, spell_id.0, empower_rank.unwrap_or(0))
46 {
47 record_guarded(ctx, &guard, Some(&cast_condition));
48
49 return;
50 }
51 }
52
53 let guard = LoweredGuard {
54 bool_val: lower_condition(ctx.lo, ctx.b, &cast_condition).into_bool(ctx.b),
55 is_unconditional: false,
56 };
57
58 ctx.b
59 .return_cast_if(guard.bool_val, spell_id.0, empower_rank.unwrap_or(0));
60 record_guarded(ctx, &guard, Some(&cast_condition));
61}
62
63#[cfg(feature = "rotation-optimizations")]
64fn lower_cast_conjunction<B>(
65 ctx: &mut LowerCtx<'_, '_, '_, B>,
66 operands: &[Condition],
67 spell_id: u32,
68 empower_rank: u8,
69) -> Option<LoweredGuard<B>>
70where
71 B: RotationBackend,
72{
73 if operands
74 .iter()
75 .any(|operand| matches!(operand, Condition::Bool { value: false }))
76 {
77 return None;
78 }
79
80 let mut meaningful = operands
81 .iter()
82 .filter(|operand| !matches!(operand, Condition::Bool { value: true }));
83 let first = meaningful.next()?;
84 let first = lower_condition(ctx.lo, ctx.b, first).into_bool(ctx.b);
85 let mut guard = ctx.b.begin_cast_guard(first);
86
87 for operand in meaningful {
88 let value = lower_condition(ctx.lo, ctx.b, operand).into_bool(ctx.b);
89
90 ctx.b.push_cast_guard(&mut guard, value);
91 }
92
93 Some(LoweredGuard {
94 bool_val: ctx.b.finish_cast_guard(guard, spell_id, empower_rank),
95 is_unconditional: false,
96 })
97}
98
99fn cast_condition<B>(
100 ctx: &LowerCtx<'_, '_, '_, B>,
101 spell: &str,
102 spell_id: wowlab_types::sim::SpellIdx,
103 condition: Option<&Condition>,
104) -> Condition
105where
106 B: RotationBackend,
107{
108 #[cfg(not(feature = "rotation-optimizations"))]
109 let _ = (ctx, spell_id);
110
111 let usable = spell_usable_condition(spell);
112 let Some(condition) = condition else {
113 return usable;
114 };
115
116 #[cfg(feature = "rotation-optimizations")]
117 {
118 if same_spell_usable_read(ctx, condition, spell_id) {
119 return usable;
120 }
121
122 if let Condition::And { operands } = condition {
123 let mut deduplicated = Vec::with_capacity(operands.len().saturating_add(1));
124
125 deduplicated.push(usable);
126 deduplicated.extend(
127 operands
128 .iter()
129 .filter(|operand| !same_spell_usable_read(ctx, operand, spell_id))
130 .cloned(),
131 );
132
133 return Condition::And {
134 operands: deduplicated,
135 };
136 }
137 }
138
139 Condition::And {
140 operands: vec![usable, condition.clone()],
141 }
142}
143
144#[cfg(feature = "rotation-optimizations")]
145fn same_spell_usable_read<B>(
146 ctx: &LowerCtx<'_, '_, '_, B>,
147 condition: &Condition,
148 spell_id: wowlab_types::sim::SpellIdx,
149) -> bool
150where
151 B: RotationBackend,
152{
153 let Condition::Read { field } = condition else {
154 return false;
155 };
156 let Ok(resolved) = resolve_field_read(field, ctx.lo.table, ctx.lo.resolver) else {
157 return false;
158 };
159 let Some(descriptor) = ctx.lo.table.get(resolved.descriptor_id) else {
160 return false;
161 };
162
163 descriptor.eval_kind == EvalKind::SpellUsable
164 && resolved.key == Some(ResolvedKey::Spell(spell_id))
165}
166
167pub(super) fn lower_wait<B>(
168 ctx: &mut LowerCtx<'_, '_, '_, B>,
169 seconds: f64,
170 condition: Option<&Condition>,
171) where
172 B: RotationBackend,
173{
174 if skip_in_run(ctx, condition) {
175 return;
176 }
177
178 let secs = crate::numeric::rotation_payload_f32(seconds);
179 let guard = lower_optional_guard(ctx.lo, ctx.b, condition);
180
181 ctx.b.return_wait_if(guard.bool_val, secs);
182 record_guarded(ctx, &guard, condition);
183}
184
185pub(super) fn lower_wait_until<B>(ctx: &mut LowerCtx<'_, '_, '_, B>, condition: &Condition)
186where
187 B: RotationBackend,
188{
189 if ctx.lo.run_depth > 0 {
190 record_skipped(ctx, condition);
191
192 return;
193 }
194
195 let satisfied = lower_condition(ctx.lo, ctx.b, condition).into_bool(ctx.b);
196 let unsatisfied = ctx.b.not_b(satisfied);
197
198 ctx.b.return_wait_if(unsatisfied, WAIT_UNTIL_POLL_SECS);
199 ctx.b.record_action_guarded(
200 ctx.action.list_id,
201 ctx.action.action_index,
202 ctx.action.action,
203 ctx.action.is_term,
204 unsatisfied,
205 condition,
206 );
207}
208
209pub(super) fn lower_pool<B>(
210 ctx: &mut LowerCtx<'_, '_, '_, B>,
211 extra: Option<f64>,
212 condition: Option<&Condition>,
213) where
214 B: RotationBackend,
215{
216 if skip_in_run(ctx, condition) {
217 return;
218 }
219
220 let target = crate::numeric::rotation_payload_f32(extra.unwrap_or(0.0));
221 let guard = lower_optional_guard(ctx.lo, ctx.b, condition);
222
223 ctx.b.return_pool_if(guard.bool_val, target);
224 record_guarded(ctx, &guard, condition);
225}
226
227pub(super) fn lower_use_trinket<B>(
228 ctx: &mut LowerCtx<'_, '_, '_, B>,
229 slot: u8,
230 empower_rank: Option<u8>,
231 condition: Option<&Condition>,
232) where
233 B: RotationBackend,
234{
235 if skip_in_run(ctx, condition) {
236 return;
237 }
238
239 let Some(gear_slot) = GearSlot::trinket_from_index(slot) else {
240 tracing::warn!(
241 slot,
242 "use_trinket references an invalid trinket index, skipping"
243 );
244 record_skipped(ctx, failing_condition(condition));
245
246 return;
247 };
248
249 emit_use_item(ctx, gear_slot, empower_rank, condition);
250}
251
252pub(super) fn lower_use_item<B>(
253 ctx: &mut LowerCtx<'_, '_, '_, B>,
254 name: &str,
255 empower_rank: Option<u8>,
256 condition: Option<&Condition>,
257) where
258 B: RotationBackend,
259{
260 if skip_in_run(ctx, condition) {
261 return;
262 }
263
264 let Some(gear_slot) = GearSlot::from_slug(name) else {
265 tracing::warn!(
266 item = name,
267 "use_item references an unknown gear slot name, skipping"
268 );
269 record_skipped(ctx, failing_condition(condition));
270
271 return;
272 };
273
274 emit_use_item(ctx, gear_slot, empower_rank, condition);
275}
276
277fn emit_use_item<B>(
278 ctx: &mut LowerCtx<'_, '_, '_, B>,
279 gear_slot: GearSlot,
280 empower_rank: Option<u8>,
281 condition: Option<&Condition>,
282) where
283 B: RotationBackend,
284{
285 let gear_slot = gear_slot as u8;
287 let guard = lower_optional_guard(ctx.lo, ctx.b, condition);
288
289 ctx.b
290 .return_use_item_if(guard.bool_val, gear_slot, empower_rank.unwrap_or(0));
291 record_guarded(ctx, &guard, condition);
292}