1use wowlab_types::sim::{Condition, RotationAction as AstAction};
2
3use super::{
4 super::{
5 backend::RotationBackend,
6 decision_trace::{ActionOutcome, ActionRecord},
7 },
8 Lowerer,
9 condition::lower_condition,
10};
11
12mod lists;
13mod terminators;
14mod vars;
15
16use lists::{lower_call, lower_run};
17use terminators::{
18 lower_cast, lower_pool, lower_use_item, lower_use_trinket, lower_wait, lower_wait_until,
19};
20pub(super) use vars::store_user_var;
21use vars::{ModifyVarArgs, lower_modify_var, lower_set_var};
22
23pub(super) fn lower_actions<B>(
24 lo: &mut Lowerer<'_>,
25 b: &mut B,
26 list_id: &str,
27 actions: &[AstAction],
28) where
29 B: RotationBackend,
30{
31 b.begin_list_recording(list_id);
32
33 for (idx, action) in actions.iter().enumerate() {
34 if !action.enabled() {
35 b.record_action(ActionRecord {
36 list_id,
37 action_index: idx,
38 action,
39 outcome: ActionOutcome::Disabled,
40 });
41 continue;
42 }
43
44 if lo.unconditionally_returned {
45 b.record_action(ActionRecord {
46 list_id,
47 action_index: idx,
48 action,
49 outcome: ActionOutcome::NotReached,
50 });
51 continue;
52 }
53
54 let mut ctx = LowerCtx {
55 lo,
56 b,
57 action: ActionRef::new(list_id, idx, action),
58 };
59
60 lower_action(&mut ctx);
61 }
62
63 b.end_list_recording();
64}
65
66pub(super) fn init_user_variables<B>(lo: &mut Lowerer<'_>, b: &mut B)
67where
68 B: RotationBackend,
69{
70 let names: Vec<String> = lo.variables.keys().cloned().collect();
71
72 for name in &names {
73 let init = lo
74 .variables
75 .get(name)
76 .expect("variable name pulled from the same map");
77 let init = init.clone();
79
80 store_user_var(lo, b, name, &init);
81 }
82}
83
84struct LoweredGuard<B>
85where
86 B: RotationBackend,
87{
88 bool_val: B::Bool,
89 is_unconditional: bool,
90}
91
92fn lower_optional_guard<B>(
93 lo: &mut Lowerer<'_>,
94 b: &mut B,
95 condition: Option<&Condition>,
96) -> LoweredGuard<B>
97where
98 B: RotationBackend,
99{
100 match condition {
101 Some(c) => LoweredGuard {
102 bool_val: lower_condition(lo, b, c).into_bool(b),
103 is_unconditional: false,
104 },
105 None => LoweredGuard {
106 bool_val: b.const_bool(true),
107 is_unconditional: true,
108 },
109 }
110}
111
112const fn is_terminator(action: &AstAction) -> bool {
113 matches!(
114 action,
115 AstAction::Cast { .. }
116 | AstAction::Wait { .. }
117 | AstAction::WaitUntil { .. }
118 | AstAction::Pool { .. }
119 | AstAction::UseTrinket { .. }
120 | AstAction::UseItem { .. }
121 )
122}
123
124static ALWAYS_TRUE_SENTINEL: Condition = Condition::Bool { value: true };
126
127#[inline]
128fn failing_condition(condition: Option<&Condition>) -> &Condition {
129 condition.unwrap_or(&ALWAYS_TRUE_SENTINEL)
130}
131
132#[derive(Clone, Copy)]
133struct ActionRef<'a> {
134 list_id: &'a str,
135 action_index: usize,
136 action: &'a AstAction,
137 is_term: bool,
138}
139
140impl<'a> ActionRef<'a> {
141 fn new(list_id: &'a str, action_index: usize, action: &'a AstAction) -> Self {
142 Self {
143 list_id,
144 action_index,
145 action,
146 is_term: is_terminator(action),
147 }
148 }
149}
150
151struct LowerCtx<'ctx, 'action, 'rotation, B> {
152 lo: &'ctx mut Lowerer<'rotation>,
153 b: &'ctx mut B,
154 action: ActionRef<'action>,
155}
156
157#[inline]
158fn record_skipped<B>(ctx: &mut LowerCtx<'_, '_, '_, B>, failing: &Condition)
159where
160 B: RotationBackend,
161{
162 let false_bool = ctx.b.const_bool(false);
163
164 ctx.b.record_action_guarded(
165 ctx.action.list_id,
166 ctx.action.action_index,
167 ctx.action.action,
168 ctx.action.is_term,
169 false_bool,
170 failing,
171 );
172}
173
174#[inline]
175fn record_guarded<B>(
176 ctx: &mut LowerCtx<'_, '_, '_, B>,
177 guard: &LoweredGuard<B>,
178 condition: Option<&Condition>,
179) where
180 B: RotationBackend,
181{
182 ctx.b.record_action_guarded(
183 ctx.action.list_id,
184 ctx.action.action_index,
185 ctx.action.action,
186 ctx.action.is_term,
187 guard.bool_val,
188 failing_condition(condition),
189 );
190
191 if guard.is_unconditional {
192 ctx.lo.unconditionally_returned = true;
193 }
194}
195
196fn lower_action<B>(ctx: &mut LowerCtx<'_, '_, '_, B>)
197where
198 B: RotationBackend,
199{
200 match ctx.action.action {
201 AstAction::Cast {
202 spell,
203 empower_rank,
204 condition,
205 ..
206 } => lower_cast(ctx, spell, *empower_rank, condition.as_ref()),
207 AstAction::Wait {
208 seconds, condition, ..
209 } => lower_wait(ctx, *seconds, condition.as_ref()),
210 AstAction::WaitUntil { condition, .. } => lower_wait_until(ctx, condition),
211 AstAction::Pool {
212 extra, condition, ..
213 } => lower_pool(ctx, *extra, condition.as_ref()),
214 AstAction::UseTrinket {
215 slot,
216 empower_rank,
217 condition,
218 ..
219 } => lower_use_trinket(ctx, *slot, *empower_rank, condition.as_ref()),
220 AstAction::UseItem {
221 name,
222 empower_rank,
223 condition,
224 ..
225 } => lower_use_item(ctx, name, *empower_rank, condition.as_ref()),
226 AstAction::SetVar {
227 name,
228 value,
229 condition,
230 ..
231 } => lower_set_var(ctx, name, value, condition.as_ref()),
232 AstAction::ModifyVar {
233 name,
234 op,
235 value,
236 condition,
237 ..
238 } => lower_modify_var(
239 ctx,
240 &ModifyVarArgs {
241 name,
242 op: *op,
243 value,
244 condition: condition.as_ref(),
245 },
246 ),
247 AstAction::Call {
248 list, condition, ..
249 } => lower_call(ctx, list, condition.as_ref()),
250 AstAction::Run {
251 list, condition, ..
252 } => lower_run(ctx, list, condition.as_ref()),
253 _ => unreachable!("rotation validation rejects unsupported action variants"),
254 }
255}
256
257#[inline]
258fn skip_in_run<B>(ctx: &mut LowerCtx<'_, '_, '_, B>, condition: Option<&Condition>) -> bool
259where
260 B: RotationBackend,
261{
262 if ctx.lo.run_depth > 0 {
263 record_skipped(ctx, failing_condition(condition));
264
265 true
266 } else {
267 false
268 }
269}