Skip to main content

wowlab_types/types/sim/rotation/
action.rs

1//! Rotation actions and their authored parameters.
2
3use serde::{Deserialize, Serialize};
4
5use super::Condition;
6
7const fn default_enabled() -> bool {
8    true
9}
10
11/// Weapon hand for auto-attack queries.
12#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
13#[serde(rename_all = "snake_case")]
14#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
15#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
16#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
17// #t(rust_non_exhaustive_on_public) weapon hands are a fixed set (Main, Off)
18pub enum Hand {
19    Main,
20    Off,
21}
22
23/// Parsed rotation action.
24#[derive(Clone, Debug, Deserialize, Serialize)]
25#[serde(tag = "type", rename_all = "snake_case")]
26#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
27#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
28#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
29#[non_exhaustive]
30pub enum Action {
31    Cast {
32        spell: String,
33        /// One-based release rank for empower spells. Omitted means rank one.
34        #[serde(default, skip_serializing_if = "Option::is_none")]
35        empower_rank: Option<u8>,
36        #[serde(default = "default_enabled")]
37        enabled: bool,
38        #[serde(skip_serializing_if = "Option::is_none")]
39        condition: Option<Condition>,
40        #[serde(skip_serializing_if = "Option::is_none")]
41        target_if: Option<TargetIf>,
42    },
43    Call {
44        list: String,
45        #[serde(default = "default_enabled")]
46        enabled: bool,
47        #[serde(skip_serializing_if = "Option::is_none")]
48        condition: Option<Condition>,
49    },
50    Run {
51        list: String,
52        #[serde(default = "default_enabled")]
53        enabled: bool,
54        #[serde(skip_serializing_if = "Option::is_none")]
55        condition: Option<Condition>,
56    },
57    SetVar {
58        name: String,
59        value: Condition,
60        #[serde(default = "default_enabled")]
61        enabled: bool,
62        #[serde(skip_serializing_if = "Option::is_none")]
63        condition: Option<Condition>,
64    },
65    ModifyVar {
66        name: String,
67        op: VarOp,
68        value: Condition,
69        #[serde(default = "default_enabled")]
70        enabled: bool,
71        #[serde(skip_serializing_if = "Option::is_none")]
72        condition: Option<Condition>,
73    },
74    Wait {
75        seconds: f64,
76        #[serde(default = "default_enabled")]
77        enabled: bool,
78        #[serde(skip_serializing_if = "Option::is_none")]
79        condition: Option<Condition>,
80    },
81    WaitUntil {
82        #[serde(default = "default_enabled")]
83        enabled: bool,
84        condition: Condition,
85    },
86    Pool {
87        #[serde(default = "default_enabled")]
88        enabled: bool,
89        #[serde(skip_serializing_if = "Option::is_none")]
90        extra: Option<f64>,
91        #[serde(skip_serializing_if = "Option::is_none")]
92        condition: Option<Condition>,
93    },
94    UseTrinket {
95        slot: u8,
96        #[serde(default, skip_serializing_if = "Option::is_none")]
97        empower_rank: Option<u8>,
98        #[serde(default = "default_enabled")]
99        enabled: bool,
100        #[serde(skip_serializing_if = "Option::is_none")]
101        condition: Option<Condition>,
102    },
103    UseItem {
104        name: String,
105        #[serde(default, skip_serializing_if = "Option::is_none")]
106        empower_rank: Option<u8>,
107        #[serde(default = "default_enabled")]
108        enabled: bool,
109        #[serde(skip_serializing_if = "Option::is_none")]
110        condition: Option<Condition>,
111    },
112}
113
114impl Action {
115    #[must_use]
116    pub fn enabled(&self) -> bool {
117        match self {
118            Action::Cast { enabled, .. }
119            | Action::Call { enabled, .. }
120            | Action::Run { enabled, .. }
121            | Action::SetVar { enabled, .. }
122            | Action::ModifyVar { enabled, .. }
123            | Action::Wait { enabled, .. }
124            | Action::WaitUntil { enabled, .. }
125            | Action::Pool { enabled, .. }
126            | Action::UseTrinket { enabled, .. }
127            | Action::UseItem { enabled, .. } => *enabled,
128        }
129    }
130
131    /// The gating condition guarding this action, if any.
132    #[must_use]
133    pub fn condition(&self) -> Option<&Condition> {
134        match self {
135            Action::Cast { condition, .. }
136            | Action::Call { condition, .. }
137            | Action::Run { condition, .. }
138            | Action::SetVar { condition, .. }
139            | Action::ModifyVar { condition, .. }
140            | Action::Wait { condition, .. }
141            | Action::Pool { condition, .. }
142            | Action::UseTrinket { condition, .. }
143            | Action::UseItem { condition, .. } => condition.as_ref(),
144            Action::WaitUntil { condition, .. } => Some(condition),
145        }
146    }
147
148    /// The value expression assigned by [`Action::SetVar`] / [`Action::ModifyVar`].
149    #[must_use]
150    pub fn value(&self) -> Option<&Condition> {
151        match self {
152            Action::SetVar { value, .. } | Action::ModifyVar { value, .. } => Some(value),
153            _ => None,
154        }
155    }
156
157    /// The `target_if` selector attached to an [`Action::Cast`].
158    #[must_use]
159    pub fn target_if(&self) -> Option<&TargetIf> {
160        match self {
161            Action::Cast { target_if, .. } => target_if.as_ref(),
162            _ => None,
163        }
164    }
165
166    /// Every condition subtree this action evaluates (value, gating condition, `target_if`).
167    pub fn conditions(&self) -> impl Iterator<Item = &Condition> {
168        self.value()
169            .into_iter()
170            .chain(self.condition())
171            .chain(self.target_if().map(|target| &target.expr))
172    }
173}
174
175/// Target selection filter.
176#[derive(Clone, Debug, Deserialize, Serialize)]
177#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
178#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
179#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
180pub struct TargetIf {
181    pub mode: TargetIfMode,
182    pub expr: Condition,
183}
184
185/// How `target_if` selects among candidates.
186#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
187#[serde(rename_all = "snake_case")]
188#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
189#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
190#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
191// #t(rust_non_exhaustive_on_public) target_if modes are a fixed set (Min, Max, First)
192pub enum TargetIfMode {
193    Min,
194    Max,
195    First,
196}
197
198/// Variable operation.
199#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
200#[serde(rename_all = "snake_case")]
201#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
202#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
203#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
204#[non_exhaustive]
205pub enum VarOp {
206    Add,
207    Sub,
208    Mul,
209    Div,
210    Mod,
211    Max,
212    Min,
213    Floor,
214    Ceil,
215    Reset,
216}
217
218impl VarOp {
219    /// Lowercase wire name matching the serde representation (e.g. `"add"`).
220    #[must_use]
221    pub fn name(self) -> &'static str {
222        match self {
223            VarOp::Add => "add",
224            VarOp::Sub => "sub",
225            VarOp::Mul => "mul",
226            VarOp::Div => "div",
227            VarOp::Mod => "mod",
228            VarOp::Max => "max",
229            VarOp::Min => "min",
230            VarOp::Floor => "floor",
231            VarOp::Ceil => "ceil",
232            VarOp::Reset => "reset",
233        }
234    }
235}
236
237impl std::fmt::Display for VarOp {
238    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239        f.write_str(self.name())
240    }
241}