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    pub fn disabled(action: Action) -> Self {
263        Self::Disabled {
264            action: Box::new(action),
265        }
266    }
267
268    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
269        Self::Call {
270            name: name.into(),
271            args: args.into_iter().collect(),
272        }
273    }
274}
275
276/// The operation used by a Workshop variable modification action.
277#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278pub enum ModifyOp {
279    Add,
280    Subtract,
281    Multiply,
282    Divide,
283    Modulo,
284    Min,
285    Max,
286    RaiseToPower,
287    AppendToArray,
288    RemoveFromArray,
289    RemoveFromArrayByIndex,
290}
291
292/// A composable Workshop value expression.
293#[derive(Debug, Clone)]
294pub enum Value {
295    Number(f64),
296    String(String),
297    LocalizedString(String),
298    Bool(bool),
299    Null,
300    Array(Vec<Value>),
301    Vector {
302        x: Box<Value>,
303        y: Box<Value>,
304        z: Box<Value>,
305    },
306    Enum {
307        value_type: String,
308        value: String,
309    },
310    GlobalVariable(String),
311    PlayerVariable {
312        player: Box<Value>,
313        variable: String,
314    },
315    Subroutine(String),
316    EventPlayer,
317    Call {
318        name: String,
319        args: Vec<Value>,
320    },
321}
322
323impl Value {
324    pub fn number(value: f64) -> Self {
325        Self::Number(value)
326    }
327
328    pub fn string(value: impl Into<String>) -> Self {
329        Self::String(value.into())
330    }
331
332    pub fn global_variable(name: impl Into<String>) -> Self {
333        Self::GlobalVariable(name.into())
334    }
335
336    pub fn player_variable(player: Value, name: impl Into<String>) -> Self {
337        Self::PlayerVariable {
338            player: Box::new(player),
339            variable: name.into(),
340        }
341    }
342
343    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
344        Self::Call {
345            name: name.into(),
346            args: args.into_iter().collect(),
347        }
348    }
349}