dfwasm_template/
lib.rs

1mod splitter;
2#[cfg(feature = "codeclient")]
3pub mod template_sender;
4
5pub use splitter::split_templates;
6
7use std::{
8    collections::HashMap,
9    io::{Read, Write},
10};
11
12use base64::{Engine, prelude::BASE64_STANDARD};
13use flate2::{read::GzDecoder, write::GzEncoder};
14use serde_derive::{Deserialize, Serialize};
15use serde_json::Value;
16
17#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct Template {
19    pub blocks: Vec<Block>,
20}
21
22/// The main structure of a block.
23/// This is not actually the type that is serialized/deserialized.
24/// Instead, it converts from/to [`JSONBlock`], which is friendlier with the JSON.
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26#[serde(from = "JSONBlock", into = "JSONBlock")]
27pub enum Block {
28    PlayerEvent {
29        action: String,
30        ls_cancel: bool,
31    },
32    EntityEvent {
33        action: String,
34        ls_cancel: bool,
35    },
36    Function {
37        args: Args,
38        name: String,
39    },
40    Process {
41        args: Args,
42        name: String,
43    },
44    PlayerAction {
45        args: Args,
46        action: String,
47        #[serde(skip_serializing_if = "Option::is_none")]
48        target: Option<Target>,
49    },
50    IfPlayer {
51        args: Args,
52        action: String,
53        #[serde(skip_serializing_if = "Option::is_none")]
54        target: Option<Target>,
55        is_negated: bool,
56    },
57    StartProcess {
58        args: Args,
59        proc: String,
60    },
61    CallFunction {
62        args: Args,
63        func: String,
64    },
65    Control {
66        args: Args,
67        action: String,
68    },
69    SetVariable {
70        args: Args,
71        action: String,
72    },
73    IfEntity {
74        args: Args,
75        action: String,
76        target: Option<Target>,
77        is_negated: bool,
78    },
79    EntityAction {
80        args: Args,
81        action: String,
82        target: Option<Target>,
83    },
84    IfVariable {
85        args: Args,
86        action: String,
87        is_negated: bool,
88    },
89    SelectObject {
90        args: Args,
91        action: String,
92        sub_action: Option<String>,
93        is_negated: bool,
94    },
95    GameAction {
96        args: Args,
97        action: String,
98    },
99    Repeat {
100        args: Args,
101        action: String,
102        sub_action: Option<String>,
103        is_negated: bool,
104    },
105    IfGame {
106        args: Args,
107        action: String,
108        is_negated: bool,
109    },
110    Else,
111    Bracket {
112        direction: BracketDirection,
113        kind: BracketKind,
114    },
115}
116
117#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
118#[serde(from = "JSONArgs", into = "JSONArgs")]
119pub struct Args(pub Vec<(usize, Item)>);
120
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122#[serde(tag = "id", content = "data")]
123pub enum Item {
124    #[serde(rename = "txt")]
125    String { name: String },
126    #[serde(rename = "comp")]
127    Text { name: String },
128    #[serde(rename = "num")]
129    Number { name: String },
130    #[serde(rename = "item")]
131    Item { item: String },
132    #[serde(rename = "loc")]
133    Location {
134        #[serde(rename = "isBlock")]
135        is_block: bool,
136        loc: Location,
137    },
138    #[serde(rename = "vec")]
139    Vector { x: f64, y: f64, z: f64 },
140    #[serde(rename = "var")]
141    Variable { name: String, scope: VariableScope },
142    #[serde(rename = "g_val")]
143    GameValue {
144        #[serde(rename = "type")]
145        kind: String,
146        target: Target,
147    },
148    #[serde(rename = "pn_el")]
149    Param {
150        name: String,
151        #[serde(rename = "type")]
152        kind: ParamKind,
153        #[serde(skip_serializing_if = "Option::is_none")]
154        default_value: Option<Box<Item>>,
155        plural: bool,
156        optional: bool,
157        #[serde(skip_serializing_if = "Option::is_none")]
158        description: Option<String>,
159        #[serde(skip_serializing_if = "Option::is_none")]
160        note: Option<String>,
161    },
162    #[serde(rename = "bl_tag")]
163    Tag {
164        option: String,
165        tag: String,
166        action: String,
167        block: CodeBlock,
168    },
169    #[serde(rename = "snd")]
170    Sound {
171        pitch: f64,
172        vol: f64,
173        sound: String,
174        #[serde(flatten)]
175        extra: HashMap<String, Value>,
176    },
177    #[serde(rename = "part")]
178    Particle {
179        particle: String,
180        #[serde(flatten)]
181        extra: HashMap<String, Value>,
182    },
183    #[serde(rename = "pot")]
184    Potion {
185        #[serde(rename = "pot")]
186        potion: String,
187        #[serde(rename = "dur")]
188        duration: u32,
189        #[serde(rename = "amp")]
190        amplifier: u32,
191    },
192}
193
194#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
195pub struct Location {
196    pub x: f64,
197    pub y: f64,
198    pub z: f64,
199    pub pitch: f64,
200    pub yaw: f64,
201}
202
203/// The internal representation of the arguments in the JSON file.
204/// This is done separately because I wanted [`Args`] to remain as a tuple.
205#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
206struct JSONArgs {
207    items: Vec<SlottedItem>,
208}
209
210#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
211struct SlottedItem {
212    item: Item,
213    slot: usize,
214}
215
216/// The internal representation of a block in the JSON file.
217/// This is done separately beacuse I wanted [`Block`] to remain very user-friendly,
218/// however the format of the JSON file is very wonky. So instead, `serde` translates
219/// to this version of the block, which is easier to convert to JSON.
220#[derive(Debug, Serialize, Deserialize)]
221#[serde(tag = "id")]
222enum JSONBlock {
223    #[serde(rename = "block")]
224    CodeBlock {
225        block: CodeBlock,
226        #[serde(skip_serializing_if = "Option::is_none")]
227        args: Option<Args>,
228        #[serde(skip_serializing_if = "Option::is_none")]
229        action: Option<String>,
230        #[serde(skip_serializing_if = "Option::is_none")]
231        data: Option<String>,
232        #[serde(skip_serializing_if = "Option::is_none")]
233        attribute: Option<String>,
234        #[serde(rename = "subAction")]
235        #[serde(skip_serializing_if = "Option::is_none")]
236        sub_action: Option<String>,
237        #[serde(skip_serializing_if = "Option::is_none")]
238        target: Option<Target>,
239    },
240    #[serde(rename = "bracket")]
241    Bracket {
242        #[serde(rename = "direct")]
243        direction: BracketDirection,
244        #[serde(rename = "type")]
245        kind: BracketKind,
246    },
247}
248
249//
250// -- helper types
251//
252
253#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
254pub enum CodeBlock {
255    #[serde(rename = "event")]
256    PlayerEvent,
257    #[serde(rename = "entity_event")]
258    EntityEvent,
259    #[serde(rename = "func")]
260    Function,
261    #[serde(rename = "process")]
262    Process,
263    #[serde(rename = "player_action")]
264    PlayerAction,
265    #[serde(rename = "if_player")]
266    IfPlayer,
267    #[serde(rename = "start_process")]
268    StartProcess,
269    #[serde(rename = "call_func")]
270    CallFunction,
271    #[serde(rename = "control")]
272    Control,
273    #[serde(rename = "set_var")]
274    SetVariable,
275    #[serde(rename = "if_entity")]
276    IfEntity,
277    #[serde(rename = "entity_action")]
278    EntityAction,
279    #[serde(rename = "if_var")]
280    IfVariable,
281    #[serde(rename = "select_obj")]
282    SelectObject,
283    #[serde(rename = "game_action")]
284    GameAction,
285    #[serde(rename = "repeat")]
286    Repeat,
287    #[serde(rename = "if_game")]
288    IfGame,
289    #[serde(rename = "else")]
290    Else,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
294pub enum Target {
295    Selection,
296    Default,
297    Killer,
298    Damager,
299    Victim,
300    AllPlayers,
301    Shooter,
302    Projectile,
303    AllEntities,
304    AllMobs,
305    LastEntity,
306}
307
308#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
309#[serde(rename_all = "camelCase")]
310enum JSONBlockId {
311    Block,
312    Bracket,
313}
314
315#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
316#[serde(rename_all = "camelCase")]
317pub enum VariableScope {
318    #[serde(rename = "unsaved")]
319    Game,
320    Saved,
321    Local,
322    Line,
323}
324
325#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
326#[serde(rename_all = "camelCase")]
327pub enum ParamKind {
328    #[serde(rename = "any")]
329    Any,
330    #[serde(rename = "dict")]
331    Dictionary,
332    #[serde(rename = "item")]
333    Item,
334    #[serde(rename = "vec")]
335    Vector,
336    #[serde(rename = "num")]
337    Number,
338    #[serde(rename = "part")]
339    Particle,
340    #[serde(rename = "pot")]
341    Potion,
342    #[serde(rename = "txt")]
343    String,
344    #[serde(rename = "loc")]
345    Location,
346    #[serde(rename = "comp")]
347    Text,
348    #[serde(rename = "list")]
349    List,
350    #[serde(rename = "snd")]
351    Sound,
352    #[serde(rename = "var")]
353    Variable,
354}
355
356#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
357#[serde(rename_all = "camelCase")]
358pub enum BracketDirection {
359    Open,
360    Close,
361}
362
363#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
364pub enum BracketKind {
365    #[serde(rename = "norm")]
366    Normal,
367    #[serde(rename = "repeat")]
368    Repeat,
369}
370
371//
372// -- impls
373//
374
375impl Args {
376    pub fn with(values: Vec<Item>) -> Self {
377        Args(values.into_iter().enumerate().collect())
378    }
379
380    pub fn with_tags(args: Vec<Item>, tags: Vec<Item>) -> Self {
381        let tags_begin = 27 - tags.len();
382
383        Args(
384            (0..=27)
385                .zip(args)
386                .chain((tags_begin..=27).zip(tags))
387                .collect(),
388        )
389    }
390}
391
392impl From<JSONBlock> for Block {
393    fn from(json: JSONBlock) -> Self {
394        match json {
395            JSONBlock::CodeBlock {
396                block,
397                args,
398                action,
399                data,
400                attribute,
401                sub_action,
402                target,
403            } => match block {
404                CodeBlock::PlayerEvent => Block::PlayerEvent {
405                    action: action.expect("action should be defined"),
406                    ls_cancel: attribute.is_some(),
407                },
408                CodeBlock::EntityEvent => Block::EntityEvent {
409                    action: action.expect("action should be defined"),
410                    ls_cancel: attribute.is_some(),
411                },
412                CodeBlock::Function => Block::Function {
413                    args: args.expect("args should be defined"),
414                    name: data.expect("data should be defined"),
415                },
416                CodeBlock::Process => Block::Process {
417                    args: args.expect("args should be defined"),
418                    name: data.expect("data should be defined"),
419                },
420                CodeBlock::PlayerAction => Block::PlayerAction {
421                    args: args.expect("args should be defined"),
422                    action: action.expect("action should be defined"),
423                    target,
424                },
425                CodeBlock::IfPlayer => Block::IfPlayer {
426                    args: args.expect("args should be defined"),
427                    action: action.expect("action should be defined"),
428                    target,
429                    is_negated: attribute.is_some(),
430                },
431                CodeBlock::StartProcess => Block::StartProcess {
432                    args: args.expect("args should be defined"),
433                    proc: data.expect("data should be defined"),
434                },
435                CodeBlock::CallFunction => Block::CallFunction {
436                    args: args.expect("args should be defined"),
437                    func: data.expect("data should be defined"),
438                },
439                CodeBlock::Control => Block::Control {
440                    args: args.expect("args should be defined"),
441                    action: action.expect("action should be defined"),
442                },
443                CodeBlock::SetVariable => Block::SetVariable {
444                    args: args.expect("args should be defined"),
445                    action: action.expect("action should be defined"),
446                },
447                CodeBlock::IfEntity => Block::IfEntity {
448                    args: args.expect("args should be defined"),
449                    action: action.expect("action should be defined"),
450                    target,
451                    is_negated: attribute.is_some(),
452                },
453                CodeBlock::EntityAction => Block::EntityAction {
454                    args: args.expect("args should be defined"),
455                    action: action.expect("action should be defined"),
456                    target,
457                },
458                CodeBlock::IfVariable => Block::IfVariable {
459                    args: args.expect("args should be defined"),
460                    action: action.expect("action should be defined"),
461                    is_negated: attribute.is_some(),
462                },
463                CodeBlock::SelectObject => Block::SelectObject {
464                    args: args.expect("args should be defined"),
465                    action: action.expect("action should be defined"),
466                    sub_action,
467                    is_negated: attribute.is_some(),
468                },
469                CodeBlock::GameAction => Block::GameAction {
470                    args: args.expect("args should be defined"),
471                    action: action.expect("action should be defined"),
472                },
473                CodeBlock::Repeat => Block::Repeat {
474                    args: args.expect("args should be defined"),
475                    action: action.expect("action should be defined"),
476                    sub_action,
477                    is_negated: attribute.is_some(),
478                },
479                CodeBlock::IfGame => Block::IfGame {
480                    args: args.expect("args should be defined"),
481                    action: action.expect("action should be defined"),
482                    is_negated: attribute.is_some(),
483                },
484                CodeBlock::Else => Block::Else,
485            },
486            JSONBlock::Bracket { direction, kind } => Block::Bracket { direction, kind },
487        }
488    }
489}
490
491impl From<Block> for JSONBlock {
492    fn from(block: Block) -> Self {
493        match block {
494            Block::PlayerEvent { action, ls_cancel } => JSONBlock::CodeBlock {
495                block: CodeBlock::PlayerEvent,
496                args: None,
497                action: Some(action),
498                data: None,
499                attribute: ls_cancel.then(|| "LS-CANCEL".to_string()),
500                sub_action: None,
501                target: None,
502            },
503            Block::EntityEvent { action, ls_cancel } => JSONBlock::CodeBlock {
504                block: CodeBlock::EntityEvent,
505                args: None,
506                action: Some(action),
507                data: None,
508                attribute: ls_cancel.then(|| "LS-CANCEL".to_string()),
509                sub_action: None,
510                target: None,
511            },
512            Block::Function { args, name } => JSONBlock::CodeBlock {
513                block: CodeBlock::Function,
514                args: Some(args),
515                action: None,
516                data: Some(name),
517                attribute: None,
518                sub_action: None,
519                target: None,
520            },
521            Block::Process { args, name } => JSONBlock::CodeBlock {
522                block: CodeBlock::Process,
523                args: Some(args),
524                action: None,
525                data: Some(name),
526                attribute: None,
527                sub_action: None,
528                target: None,
529            },
530            Block::PlayerAction {
531                args,
532                action,
533                target,
534            } => JSONBlock::CodeBlock {
535                block: CodeBlock::PlayerAction,
536                args: Some(args),
537                action: Some(action),
538                data: None,
539                attribute: None,
540                sub_action: None,
541                target,
542            },
543            Block::IfPlayer {
544                args,
545                action,
546                target,
547                is_negated,
548            } => JSONBlock::CodeBlock {
549                block: CodeBlock::IfPlayer,
550                args: Some(args),
551                action: Some(action),
552                data: None,
553                attribute: is_negated.then(|| "NOT".to_string()),
554                sub_action: None,
555                target,
556            },
557            Block::StartProcess { args, proc } => JSONBlock::CodeBlock {
558                block: CodeBlock::StartProcess,
559                args: Some(args),
560                action: None,
561                data: Some(proc),
562                attribute: None,
563                sub_action: None,
564                target: None,
565            },
566            Block::CallFunction { args, func } => JSONBlock::CodeBlock {
567                block: CodeBlock::CallFunction,
568                args: Some(args),
569                action: None,
570                data: Some(func),
571                attribute: None,
572                sub_action: None,
573                target: None,
574            },
575            Block::Control { args, action } => JSONBlock::CodeBlock {
576                block: CodeBlock::Control,
577                args: Some(args),
578                action: Some(action),
579                data: None,
580                attribute: None,
581                sub_action: None,
582                target: None,
583            },
584            Block::SetVariable { args, action } => JSONBlock::CodeBlock {
585                block: CodeBlock::SetVariable,
586                args: Some(args),
587                action: Some(action),
588                data: None,
589                attribute: None,
590                sub_action: None,
591                target: None,
592            },
593            Block::IfEntity {
594                args,
595                action,
596                target,
597                is_negated,
598            } => JSONBlock::CodeBlock {
599                block: CodeBlock::IfEntity,
600                args: Some(args),
601                action: Some(action),
602                data: None,
603                attribute: is_negated.then(|| "NOT".to_string()),
604                sub_action: None,
605                target,
606            },
607            Block::EntityAction {
608                args,
609                action,
610                target,
611            } => JSONBlock::CodeBlock {
612                block: CodeBlock::EntityAction,
613                args: Some(args),
614                action: Some(action),
615                data: None,
616                attribute: None,
617                sub_action: None,
618                target,
619            },
620            Block::IfVariable {
621                args,
622                action,
623                is_negated,
624            } => JSONBlock::CodeBlock {
625                block: CodeBlock::IfVariable,
626                args: Some(args),
627                action: Some(action),
628                data: None,
629                attribute: is_negated.then(|| "NOT".to_string()),
630                sub_action: None,
631                target: None,
632            },
633            Block::SelectObject {
634                args,
635                action,
636                sub_action,
637                is_negated,
638            } => JSONBlock::CodeBlock {
639                block: CodeBlock::SelectObject,
640                args: Some(args),
641                action: Some(action),
642                data: None,
643                attribute: is_negated.then(|| "NOT".to_string()),
644                sub_action,
645                target: None,
646            },
647            Block::GameAction { args, action } => JSONBlock::CodeBlock {
648                block: CodeBlock::GameAction,
649                args: Some(args),
650                action: Some(action),
651                data: None,
652                attribute: None,
653                sub_action: None,
654                target: None,
655            },
656            Block::Repeat {
657                args,
658                action,
659                sub_action,
660                is_negated,
661            } => JSONBlock::CodeBlock {
662                block: CodeBlock::Repeat,
663                args: Some(args),
664                action: Some(action),
665                data: None,
666                attribute: is_negated.then(|| "NOT".to_string()),
667                sub_action,
668                target: None,
669            },
670            Block::IfGame {
671                args,
672                action,
673                is_negated,
674            } => JSONBlock::CodeBlock {
675                block: CodeBlock::IfGame,
676                args: Some(args),
677                action: Some(action),
678                data: None,
679                attribute: is_negated.then(|| "NOT".to_string()),
680                sub_action: None,
681                target: None,
682            },
683            Block::Else => JSONBlock::CodeBlock {
684                block: CodeBlock::Else,
685                args: None,
686                action: None,
687                data: None,
688                attribute: None,
689                sub_action: None,
690                target: None,
691            },
692            Block::Bracket {
693                direction: bracket_direction,
694                kind: bracket_type,
695            } => JSONBlock::Bracket {
696                direction: bracket_direction,
697                kind: bracket_type,
698            },
699        }
700    }
701}
702
703impl From<JSONArgs> for Args {
704    fn from(json: JSONArgs) -> Self {
705        Args(
706            json.items
707                .into_iter()
708                .map(|item| (item.slot, item.item))
709                .collect(),
710        )
711    }
712}
713
714impl From<Args> for JSONArgs {
715    fn from(args: Args) -> Self {
716        JSONArgs {
717            items: args
718                .0
719                .into_iter()
720                .map(|(slot, item)| SlottedItem { item, slot })
721                .collect(),
722        }
723    }
724}
725
726impl Template {
727    pub fn new(blocks: Vec<Block>) -> Self {
728        Template { blocks }
729    }
730
731    pub fn encode(&self) -> Option<String> {
732        // gzip then base64
733        let json = serde_json::to_string(self).ok()?;
734
735        let mut gz_encoder = GzEncoder::new(Vec::new(), flate2::Compression::default());
736        gz_encoder.write_all(json.as_bytes()).ok()?;
737
738        let gzipped = gz_encoder.finish().ok()?;
739        let base64 = BASE64_STANDARD.encode(gzipped);
740
741        Some(base64)
742    }
743
744    pub fn decode(data: &str) -> Option<Self> {
745        // base64 then gunzip
746        let gzipped = BASE64_STANDARD.decode(data).ok()?;
747
748        let mut gz_decoder = GzDecoder::new(&gzipped[..]);
749        let mut json = String::new();
750        gz_decoder.read_to_string(&mut json).ok()?;
751
752        let template: Template = serde_json::from_str(&json).ok()?;
753
754        Some(template)
755    }
756
757    pub fn get_name(&self) -> Option<String> {
758        if let Some(Block::Function { name, .. }) = self.blocks.first() {
759            Some(name.clone())
760        } else {
761            None
762        }
763    }
764
765    pub fn add_block(&mut self, block: Block) -> &mut Self {
766        self.blocks.push(block);
767        self
768    }
769
770    pub fn set_var(&mut self, action: impl Into<String>, args: Args) -> &mut Self {
771        self.add_block(Block::SetVariable {
772            args,
773            action: action.into(),
774        });
775        self
776    }
777
778    pub fn set_var_bitwise(
779        &mut self,
780        bitwise_op: impl Into<String>,
781        result: Item,
782        a: Item,
783        b: Item,
784    ) -> &mut Self {
785        self.set_var(
786            "Bitwise",
787            Args::with_tags(
788                vec![result, a, b],
789                vec![
790                    Item::Tag {
791                        option: "64-bit".to_string(),
792                        tag: "Bit Precision".to_string(),
793                        action: "Bitwise".to_string(),
794                        block: CodeBlock::SetVariable,
795                    },
796                    Item::Tag {
797                        option: bitwise_op.into(),
798                        tag: "Operator".to_string(),
799                        action: "Bitwise".to_string(),
800                        block: CodeBlock::SetVariable,
801                    },
802                ],
803            ),
804        )
805    }
806
807    pub fn if_var(&mut self, action: impl Into<String>, args: Args) -> &mut Self {
808        self.add_block(Block::IfVariable {
809            args,
810            action: action.into(),
811            is_negated: false,
812        });
813        self
814    }
815
816    pub fn else_block(&mut self) -> &mut Self {
817        self.add_block(Block::Else);
818        self
819    }
820
821    pub fn repeat(&mut self, action: impl Into<String>, args: Args) -> &mut Self {
822        self.add_block(Block::Repeat {
823            args,
824            action: action.into(),
825            sub_action: None,
826            is_negated: false,
827        });
828        self
829    }
830
831    pub fn repeat_subaction(
832        &mut self,
833        action: impl Into<String>,
834        sub_action: impl Into<String>,
835        args: Args,
836    ) -> &mut Self {
837        self.add_block(Block::Repeat {
838            args,
839            action: action.into(),
840            sub_action: Some(sub_action.into()),
841            is_negated: false,
842        });
843        self
844    }
845
846    pub fn open_bracket(&mut self) -> &mut Self {
847        self.add_block(Block::Bracket {
848            direction: BracketDirection::Open,
849            kind: BracketKind::Normal,
850        });
851        self
852    }
853
854    pub fn close_bracket(&mut self) -> &mut Self {
855        self.add_block(Block::Bracket {
856            direction: BracketDirection::Close,
857            kind: BracketKind::Normal,
858        });
859        self
860    }
861
862    pub fn open_bracket_repeat(&mut self) -> &mut Self {
863        self.add_block(Block::Bracket {
864            direction: BracketDirection::Open,
865            kind: BracketKind::Repeat,
866        });
867        self
868    }
869
870    pub fn close_bracket_repeat(&mut self) -> &mut Self {
871        self.add_block(Block::Bracket {
872            direction: BracketDirection::Close,
873            kind: BracketKind::Repeat,
874        });
875        self
876    }
877
878    pub fn control(&mut self, action: impl ToString, args: Args) -> &mut Self {
879        self.add_block(Block::Control {
880            args,
881            action: action.to_string(),
882        });
883        self
884    }
885
886    pub fn call_function(&mut self, func: impl ToString, args: Args) -> &mut Self {
887        self.add_block(Block::CallFunction {
888            args,
889            func: func.to_string(),
890        });
891        self
892    }
893
894    pub fn print(&mut self, args: Args) -> &mut Self {
895        self.add_block(Block::PlayerAction {
896            args,
897            action: "SendMessage".to_string(),
898            target: Some(Target::AllPlayers),
899        });
900        self
901    }
902
903    pub fn start_function(name: impl ToString) -> Self {
904        Self {
905            blocks: vec![Block::Function {
906                args: Args::with_tags(
907                    vec![],
908                    vec![Item::Tag {
909                        option: "False".to_string(),
910                        tag: "Is Hidden".to_string(),
911                        action: "dynamic".to_string(),
912                        block: CodeBlock::Function,
913                    }],
914                ),
915                name: name.to_string(),
916            }],
917        }
918    }
919
920    pub fn start_function_hidden(name: impl ToString) -> Self {
921        Self {
922            blocks: vec![Block::Function {
923                args: Args::with_tags(
924                    vec![],
925                    vec![Item::Tag {
926                        option: "True".to_string(),
927                        tag: "Is Hidden".to_string(),
928                        action: "dynamic".to_string(),
929                        block: CodeBlock::Function,
930                    }],
931                ),
932                name: name.to_string(),
933            }],
934        }
935    }
936}
937
938impl Item {
939    pub fn num(name: impl ToString) -> Self {
940        Item::Number {
941            name: name.to_string(),
942        }
943    }
944
945    pub fn var(kind: impl Into<String>) -> Self {
946        Item::Variable {
947            name: kind.into(),
948            scope: VariableScope::Game,
949        }
950    }
951
952    pub fn string(name: impl Into<String>) -> Self {
953        Item::String { name: name.into() }
954    }
955}
956
957//
958// -- tests
959//
960
961#[cfg(test)]
962mod tests {
963    use super::*;
964
965    #[test]
966    fn test_block_conversion() {
967        let block = Block::PlayerEvent {
968            action: "test".to_string(),
969            ls_cancel: true,
970        };
971        let json: JSONBlock = block.clone().into();
972        let block2: Block = json.into();
973        assert_eq!(block, block2);
974    }
975}