Skip to main content

workshop_rs/
program.rs

1//! Canonical Workshop program concepts.
2
3use crate::settings::Settings;
4
5/// A complete Workshop program built from Workshop concepts.
6#[derive(Debug, Clone, Default)]
7pub struct Program {
8    pub settings: Option<Settings>,
9    pub global_variables: Vec<Variable>,
10    pub player_variables: Vec<Variable>,
11    pub subroutines: Vec<Subroutine>,
12    pub rules: Vec<Rule>,
13}
14
15impl Program {
16    pub fn new() -> Self {
17        Self::default()
18    }
19
20    pub fn global_variable(&mut self, variable: Variable) -> &mut Self {
21        self.global_variables.push(variable);
22        self
23    }
24
25    pub fn player_variable(&mut self, variable: Variable) -> &mut Self {
26        self.player_variables.push(variable);
27        self
28    }
29
30    pub fn subroutine(&mut self, subroutine: Subroutine) -> &mut Self {
31        self.subroutines.push(subroutine);
32        self
33    }
34
35    pub fn rule(&mut self, rule: Rule) -> &mut Self {
36        self.rules.push(rule);
37        self
38    }
39}
40
41/// A Workshop global or player variable declaration.
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct Variable {
44    pub name: String,
45    /// The raw Workshop declaration index, when the declaration has one.
46    pub index: Option<u32>,
47}
48
49impl Variable {
50    pub fn new(name: impl Into<String>) -> Self {
51        Self {
52            name: name.into(),
53            index: None,
54        }
55    }
56
57    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
58        Self {
59            name: name.into(),
60            index: Some(index),
61        }
62    }
63}
64
65/// A Workshop subroutine declaration.
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct Subroutine {
68    pub name: String,
69    /// The raw Workshop declaration index, when the declaration has one.
70    pub index: Option<u32>,
71}
72
73impl Subroutine {
74    pub fn new(name: impl Into<String>) -> Self {
75        Self {
76            name: name.into(),
77            index: None,
78        }
79    }
80
81    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
82        Self {
83            name: name.into(),
84            index: Some(index),
85        }
86    }
87}
88
89/// A Workshop rule with explicit conditions and a linear action stream.
90#[derive(Debug, Clone)]
91pub struct Rule {
92    pub name: String,
93    pub disabled: bool,
94    pub event: Event,
95    pub conditions: Vec<Condition>,
96    pub actions: Vec<Action>,
97}
98
99impl Rule {
100    pub fn new(name: impl Into<String>, event: Event) -> Self {
101        Self {
102            name: name.into(),
103            disabled: false,
104            event,
105            conditions: Vec::new(),
106            actions: Vec::new(),
107        }
108    }
109
110    pub fn condition(mut self, condition: impl Into<Condition>) -> Self {
111        self.conditions.push(condition.into());
112        self
113    }
114
115    pub fn action(mut self, action: Action) -> Self {
116        self.actions.push(action);
117        self
118    }
119}
120
121/// A rule condition. Conditions remain distinct from general value expressions.
122#[derive(Debug, Clone)]
123pub struct Condition {
124    pub value: Value,
125    pub disabled: bool,
126}
127
128impl Condition {
129    pub fn new(value: Value) -> Self {
130        Self {
131            value,
132            disabled: false,
133        }
134    }
135
136    pub fn disabled(value: Value) -> Self {
137        Self {
138            value,
139            disabled: true,
140        }
141    }
142}
143
144impl From<Value> for Condition {
145    fn from(value: Value) -> Self {
146        Self::new(value)
147    }
148}
149
150/// A Workshop event identity and its native filters.
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub enum Event {
153    Global,
154    EachPlayer,
155    EachPlayerWithFilters {
156        team: EventTeam,
157        target: EventTarget,
158    },
159    Player {
160        kind: PlayerEventKind,
161        team: EventTeam,
162        target: EventTarget,
163    },
164    Subroutine(String),
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum EventTeam {
169    All,
170    Team1,
171    Team2,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub enum EventTarget {
176    All,
177    Slot(u8),
178    Hero(String),
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum PlayerEventKind {
183    DealtDamage,
184    DealtFinalBlow,
185    DealtHealing,
186    DealtKnockback,
187    Died,
188    EarnedElimination,
189    Joined,
190    Left,
191    ReceivedHealing,
192    ReceivedKnockback,
193    TookDamage,
194}
195
196/// A Workshop action line. Control flow is represented in the same order as
197/// the Workshop source, including its explicit `End` lines.
198#[derive(Debug, Clone)]
199pub enum Action {
200    SetGlobalVariable {
201        variable: String,
202        value: Value,
203    },
204    ModifyGlobalVariable {
205        variable: String,
206        op: ModifyOp,
207        value: Value,
208    },
209    SetPlayerVariable {
210        player: Value,
211        variable: String,
212        value: Value,
213    },
214    ModifyPlayerVariable {
215        player: Value,
216        variable: String,
217        op: ModifyOp,
218        value: Value,
219    },
220    AssignMember {
221        target: Value,
222        op: Option<ModifyOp>,
223        value: Value,
224    },
225    CallSubroutine {
226        subroutine: String,
227    },
228    If {
229        condition: Value,
230    },
231    ElseIf {
232        condition: Value,
233    },
234    Else,
235    While {
236        condition: Value,
237    },
238    ForGlobalVariable {
239        variable: String,
240        start: Value,
241        stop: Value,
242        step: Value,
243    },
244    ForPlayerVariable {
245        player: Value,
246        variable: String,
247        start: Value,
248        stop: Value,
249        step: Value,
250    },
251    End,
252    Disabled {
253        action: Box<Action>,
254    },
255    Call {
256        name: String,
257        args: Vec<Value>,
258    },
259}
260
261impl Action {
262    /// Mark an action as disabled.
263    pub fn disabled(action: Action) -> Self {
264        Self::Disabled {
265            action: Box::new(action),
266        }
267    }
268
269    /// Construct a dynamic action call by canonical Workshop id.
270    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
271        Self::Call {
272            name: name.into(),
273            args: args.into_iter().collect(),
274        }
275    }
276}
277
278/// The operation used by a Workshop variable modification action.
279#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280pub enum ModifyOp {
281    Add,
282    Subtract,
283    Multiply,
284    Divide,
285    Modulo,
286    Min,
287    Max,
288    RaiseToPower,
289    AppendToArray,
290    RemoveFromArray,
291    RemoveFromArrayByIndex,
292}
293
294/// A composable Workshop value expression.
295#[derive(Debug, Clone)]
296pub enum Value {
297    Number(f64),
298    String(String),
299    LocalizedString(String),
300    Bool(bool),
301    Null,
302    Array(Vec<Value>),
303    Vector {
304        x: Box<Value>,
305        y: Box<Value>,
306        z: Box<Value>,
307    },
308    Enum {
309        value_type: String,
310        value: String,
311    },
312    GlobalVariable(String),
313    PlayerVariable {
314        player: Box<Value>,
315        variable: String,
316    },
317    Subroutine(String),
318    EventPlayer,
319    Call {
320        name: String,
321        args: Vec<Value>,
322    },
323}
324
325impl Value {
326    /// Construct a numeric Workshop literal.
327    pub fn number(value: f64) -> Self {
328        Self::Number(value)
329    }
330
331    /// Construct a custom Workshop string literal.
332    pub fn string(value: impl Into<String>) -> Self {
333        Self::String(value.into())
334    }
335
336    pub fn global_variable(name: impl Into<String>) -> Self {
337        Self::GlobalVariable(name.into())
338    }
339
340    pub fn player_variable(player: Value, name: impl Into<String>) -> Self {
341        Self::PlayerVariable {
342            player: Box::new(player),
343            variable: name.into(),
344        }
345    }
346
347    /// Construct a dynamic value call by canonical Workshop id.
348    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
349        Self::Call {
350            name: name.into(),
351            args: args.into_iter().collect(),
352        }
353    }
354}
355
356impl From<bool> for Value {
357    fn from(value: bool) -> Self {
358        Self::Bool(value)
359    }
360}
361
362impl From<f64> for Value {
363    fn from(value: f64) -> Self {
364        Self::Number(value)
365    }
366}
367
368impl From<f32> for Value {
369    fn from(value: f32) -> Self {
370        Self::Number(f64::from(value))
371    }
372}
373
374macro_rules! impl_integer_value {
375    ($($type:ty),+ $(,)?) => {
376        $(
377            impl From<$type> for Value {
378                fn from(value: $type) -> Self {
379                    Self::Number(value as f64)
380                }
381            }
382        )+
383    };
384}
385
386impl_integer_value!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
387
388impl From<String> for Value {
389    fn from(value: String) -> Self {
390        Self::String(value)
391    }
392}
393
394impl From<&str> for Value {
395    fn from(value: &str) -> Self {
396        Self::String(value.to_string())
397    }
398}
399
400impl<T: Into<Value>> From<Vec<T>> for Value {
401    fn from(values: Vec<T>) -> Self {
402        Self::Array(values.into_iter().map(Into::into).collect())
403    }
404}
405
406impl<T: Into<Value>, const N: usize> From<[T; N]> for Value {
407    fn from(values: [T; N]) -> Self {
408        Self::Array(values.into_iter().map(Into::into).collect())
409    }
410}
411
412include!(concat!(env!("OUT_DIR"), "/typed_api.rs"));