Skip to main content

wowlab_engine_domain/rotation/lower/action/
terminators.rs

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    // #t(rust_lossy_cast) `GearSlot` is `repr(u8)`; the cast is exact.
286    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}