diff --git a/Cargo.toml b/Cargo.toml index 1298cad..99aaf65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ default = ["zip"] zip = ["dep:zip"] [dependencies] +getset = "0.1.2" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.114" zip = { version = "0.6.6", default-features = false, features = ["deflate", "time"], optional = true } diff --git a/README.md b/README.md index e987aff..31f339b 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,10 @@ let mut dp = Datapack::new("shulkerpack", 20) // Create a new datapack with the .with_supported_formats(16..=20) // Add the supported formats of the datapack .with_template_folder(Path::new("./template")) // Add a template folder to the datapack. This will include all files in the template folder in the root of the datapack and can be used for including the "pack.png" file .unwrap(); -let mut namespace = Namespace::new("shulker"); // Create a new namespace with the name "shulker" +let namespace = datapack.namespace_mut("shulker"); // Create a new namespace with the name "shulker" -let mut hello_function = Function::new(); // Create a new function -hello_function.add_command("say Hello, world!".into()); // Add a command to the function -namespace.add_function("hello", hello_function); // Add the function to the namespace - -dp.add_namespace(namespace); // Add the namespace to the datapack +let hello_function = namespace.function_mut("hello"); // Create a new function +hello_function.add_command("say Hello, world!"); // Add a command to the function let v_folder = dp.compile(&CompileOptions::default()); // Compile the datapack with default options diff --git a/src/datapack/command/execute.rs b/src/datapack/command/execute.rs index db2d746..b58679e 100644 --- a/src/datapack/command/execute.rs +++ b/src/datapack/command/execute.rs @@ -1,9 +1,12 @@ -use std::ops::{BitAnd, BitOr, Not}; +use std::ops::{BitAnd, BitOr, Deref, Not}; use serde::{Deserialize, Serialize}; +use crate::util::compile::{CompileOptions, MutCompilerState, MutFunctionCompilerState}; + use super::Command; +#[allow(missing_docs)] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Execute { Align(String, Box), @@ -18,10 +21,228 @@ pub enum Execute { Rotated(String, Box), Store(String, Box), Summon(String, Box), - If(Condition, Box), - Run(String, Box), + If(Condition, Box, Option>), + Run(Box), + Runs(Vec), } +impl Execute { + /// Compile the execute command into a list of strings. + pub fn compile( + &self, + options: &CompileOptions, + global_state: &MutCompilerState, + function_state: &MutFunctionCompilerState, + ) -> Vec { + self.compile_internal( + String::from("execute "), + false, + options, + global_state, + function_state, + ) + } + fn compile_internal( + &self, + prefix: String, + require_grouping: bool, + options: &CompileOptions, + global_state: &MutCompilerState, + function_state: &MutFunctionCompilerState, + ) -> Vec { + match self { + Self::Align(align, next) => format_execute( + prefix, + &format!("align {align} "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::Anchored(anchor, next) => format_execute( + prefix, + &format!("anchored {anchor} "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::As(selector, next) => format_execute( + prefix, + &format!("as {selector} "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::At(selector, next) => format_execute( + prefix, + &format!("at {selector} "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::AsAt(selector, next) => format_execute( + prefix, + &format!("as {selector} at @s "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::Facing(facing, next) => format_execute( + prefix, + &format!("facing {facing} "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::In(dim, next) => format_execute( + prefix, + &format!("in {dim} "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::On(dim, next) => format_execute( + prefix, + &format!("on {dim} "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::Positioned(pos, next) => format_execute( + prefix, + &format!("positioned {pos} "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::Rotated(rot, next) => format_execute( + prefix, + &format!("rotated {rot} "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::Store(store, next) => format_execute( + prefix, + &format!("store {store} "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::Summon(entity, next) => format_execute( + prefix, + &format!("summon {entity} "), + next, + require_grouping, + options, + global_state, + function_state, + ), + Self::If(cond, then, el) => { + // TODO: group commands if needed and only run else commands if the first commands have not executed + let str_cond = cond.clone().compile(options, global_state, function_state); + let require_grouping = str_cond.len() > 1; + let then = then.compile_internal( + String::new(), + require_grouping, + options, + global_state, + function_state, + ); + let then_commands = combine_conditions_commands(str_cond, then); + let el = el + .as_deref() + .map(|el| { + let else_cond = + (!cond.clone()).compile(options, global_state, function_state); + let el = el.compile_internal( + String::new(), + else_cond.len() > 1, + options, + global_state, + function_state, + ); + combine_conditions_commands(else_cond, el) + }) + .unwrap_or_default(); + + then_commands + .into_iter() + .chain(el) + .map(|cmd| prefix.clone() + &cmd + " ") + .collect() + } + Self::Run(command) if !require_grouping => command + .compile(options, global_state, function_state) + .into_iter() + .map(|c| prefix.clone() + "run " + &c) + .collect(), + Self::Run(command) => Command::Group(vec![command.deref().clone()]) + .compile(options, global_state, function_state) + .into_iter() + .map(|c| prefix.clone() + "run " + &c) + .collect(), + Self::Runs(commands) if !require_grouping => commands + .iter() + .flat_map(|c| c.compile(options, global_state, function_state)) + .map(|c| prefix.clone() + "run " + &c) + .collect(), + Self::Runs(commands) => Command::Group(commands.clone()) + .compile(options, global_state, function_state) + .into_iter() + .map(|c| prefix.clone() + "run " + &c) + .collect(), + } + } +} + +fn format_execute( + prefix: String, + new: &str, + next: &Execute, + require_grouping: bool, + options: &CompileOptions, + global_state: &MutCompilerState, + function_state: &MutFunctionCompilerState, +) -> Vec { + next.compile_internal( + prefix + new, + require_grouping, + options, + global_state, + function_state, + ) +} + +fn combine_conditions_commands(conditions: Vec, commands: Vec) -> Vec { + conditions + .into_iter() + .flat_map(|cond| commands.iter().map(move |cmd| cond.clone() + " " + cmd)) + .collect() +} + +#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Condition { Atom(String), @@ -30,11 +251,12 @@ pub enum Condition { Or(Box, Box), } impl Condition { - pub fn normalize(self) -> Self { + /// Normalize the condition. + pub fn normalize(&self) -> Self { match self { - Self::Atom(_) => self, - Self::Not(c) => match *c { - Self::Atom(c) => Self::Not(Box::new(Self::Atom(c))), + Self::Atom(_) => self.clone(), + Self::Not(c) => match *c.clone() { + Self::Atom(c) => Self::Not(Box::new(Self::Atom(c.clone()))), Self::Not(c) => c.normalize(), Self::And(c1, c2) => ((!*c1).normalize()) | ((!*c2).normalize()), Self::Or(c1, c2) => ((!*c1).normalize()) & ((!*c2).normalize()), @@ -43,6 +265,42 @@ impl Condition { Self::Or(c1, c2) => c1.normalize() | c2.normalize(), } } + + /// Compile the condition into a list of strings. + pub fn compile( + &self, + _options: &CompileOptions, + _global_state: &MutCompilerState, + _function_state: &MutFunctionCompilerState, + ) -> Vec { + match self.normalize() { + Self::Atom(a) => vec!["if ".to_string() + &a], + Self::Not(n) => match n.as_ref() { + Self::Atom(a) => vec!["unless ".to_string() + a], + _ => unreachable!("Cannot happen because of normalization"), + }, + Self::And(c1, c2) => { + let c1 = c1.compile(_options, _global_state, _function_state); + let c2 = c2.compile(_options, _global_state, _function_state); + + c1.into_iter() + .flat_map(|c1| c2.iter().map(move |c2| c1.clone() + " " + c2)) + .collect() + } + Self::Or(c1, c2) => { + let mut c1 = c1.compile(_options, _global_state, _function_state); + let c2 = c2.compile(_options, _global_state, _function_state); + c1.extend(c2); + c1 + } + } + } +} + +impl From<&str> for Condition { + fn from(s: &str) -> Self { + Condition::Atom(s.to_string()) + } } impl Not for Condition { diff --git a/src/datapack/command/mod.rs b/src/datapack/command/mod.rs index b9e6734..a5d592d 100644 --- a/src/datapack/command/mod.rs +++ b/src/datapack/command/mod.rs @@ -1,12 +1,15 @@ //! Represents a command that can be included in a function. mod execute; -pub use execute::Execute; + +pub use execute::{Condition, Execute}; use serde::{Deserialize, Serialize}; use crate::util::compile::{CompileOptions, MutCompilerState, MutFunctionCompilerState}; +use super::Function; + /// Represents a command that can be included in a function. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Command { @@ -16,6 +19,8 @@ pub enum Command { Debug(String), /// Execute command Execute(Execute), + /// Group of commands to be called instantly after each other + Group(Vec), } impl Command { @@ -30,14 +35,18 @@ impl Command { options: &CompileOptions, global_state: &MutCompilerState, function_state: &MutFunctionCompilerState, - ) -> String { - let _ = options; - let _ = global_state; - let _ = function_state; + ) -> Vec { match self { - Self::Raw(command) => command.clone(), + Self::Raw(command) => vec![command.clone()], Self::Debug(message) => compile_debug(message, options), - Self::Execute(_) => todo!(), + Self::Execute(ex) => ex.compile(options, global_state, function_state), + Self::Group(commands) => { + // TODO: implement correctly + commands + .iter() + .flat_map(|c| c.compile(options, global_state, function_state)) + .collect() + } } } } @@ -47,14 +56,24 @@ impl From<&str> for Command { Self::raw(command) } } - -fn compile_debug(message: &str, option: &CompileOptions) -> String { - if option.debug { - format!( - r#"tellraw @a [{{"text":"[","color":"dark_blue"}},{{"text":"DEBUG","color":"dark_green","hoverEvent":{{"action":"show_text","value":[{{"text":"Debug message generated by Shulkerbox"}},{{"text":"\nSet debug message to 'false' to disable"}}]}}}},{{"text":"]","color":"dark_blue"}},{{"text":" {}","color":"black"}}]"#, - message - ) - } else { - String::new() +impl From<&Function> for Command { + fn from(value: &Function) -> Self { + Self::Raw(format!("function {}:{}", value.namespace(), value.name())) + } +} +impl From<&mut Function> for Command { + fn from(value: &mut Function) -> Self { + Self::Raw(format!("function {}:{}", value.namespace(), value.name())) + } +} + +fn compile_debug(message: &str, option: &CompileOptions) -> Vec { + if option.debug { + vec![format!( + r#"tellraw @a [{{"text":"[","color":"dark_blue"}},{{"text":"DEBUG","color":"dark_green","hoverEvent":{{"action":"show_text","value":[{{"text":"Debug message generated by Shulkerbox"}},{{"text":"\nSet debug message to 'false' to disable"}}]}}}},{{"text":"]","color":"dark_blue"}},{{"text":" {}","color":"black"}}]"#, + message + )] + } else { + Vec::new() } } diff --git a/src/datapack/function.rs b/src/datapack/function.rs index f81354d..1242b39 100644 --- a/src/datapack/function.rs +++ b/src/datapack/function.rs @@ -2,6 +2,7 @@ use std::sync::Mutex; +use getset::Getters; use serde::{Deserialize, Serialize}; use crate::{ @@ -12,20 +13,28 @@ use crate::{ use super::command::Command; /// Function that can be called by a command -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, Getters)] pub struct Function { commands: Vec, + /// Name of the function + #[get = "pub"] + name: String, + /// Namespace of the function + #[get = "pub"] + namespace: String, } impl Function { - /// Create a new function. - pub fn new() -> Self { - Self::default() + pub(in crate::datapack) fn new(namespace: &str, name: &str) -> Self { + Self { + commands: Vec::new(), + name: name.to_string(), + namespace: namespace.to_string(), + } } - /// Add a command to the function. - pub fn add_command(&mut self, command: Command) { - self.commands.push(command); + pub fn add_command(&mut self, command: impl Into) { + self.commands.push(command.into()); } /// Get the commands of the function. @@ -40,7 +49,7 @@ impl Function { let content = self .commands .iter() - .map(|c| c.compile(options, state, &function_state)) + .flat_map(|c| c.compile(options, state, &function_state)) .collect::>() .join("\n"); VFile::Text(content) diff --git a/src/datapack/mod.rs b/src/datapack/mod.rs index 935389d..335e258 100644 --- a/src/datapack/mod.rs +++ b/src/datapack/mod.rs @@ -4,7 +4,7 @@ mod command; mod function; mod namespace; pub mod tag; -pub use command::Command; +pub use command::{Command, Condition, Execute}; pub use function::Function; pub use namespace::Namespace; use serde::{Deserialize, Serialize}; @@ -72,13 +72,16 @@ impl Datapack { }) } - /// Add a namespace to the datapack. - pub fn add_namespace(&mut self, namespace: Namespace) { - if !namespace.get_main_function().get_commands().is_empty() { - self.add_tick(&format!("{}:main", namespace.get_name())); - } + /// Get a namespace by name. + pub fn namespace(&self, name: &str) -> Option<&Namespace> { + self.namespaces.get(name) + } + + /// Butably get a namespace by name or create a new one if it doesn't exist. + pub fn namespace_mut(&mut self, name: &str) -> &mut Namespace { self.namespaces - .insert(namespace.get_name().to_string(), namespace); + .entry(name.to_string()) + .or_insert_with(|| Namespace::new(name)) } /// Add a function to the tick function list. diff --git a/src/datapack/namespace.rs b/src/datapack/namespace.rs index 699e03e..eae527f 100644 --- a/src/datapack/namespace.rs +++ b/src/datapack/namespace.rs @@ -21,7 +21,7 @@ pub struct Namespace { impl Namespace { /// Create a new namespace. - pub fn new(name: &str) -> Self { + pub(in crate::datapack) fn new(name: &str) -> Self { Self { name: name.to_string(), functions: HashMap::new(), @@ -54,9 +54,16 @@ impl Namespace { &self.tags } - /// Add a function to the namespace. - pub fn add_function(&mut self, name: &str, function: Function) { - self.functions.insert(name.to_string(), function); + /// Get a function by name. + pub fn function(&self, name: &str) -> Option<&Function> { + self.functions.get(name) + } + + /// Mutably get a function by name or create a new one if it doesn't exist. + pub fn function_mut(&mut self, name: &str) -> &mut Function { + self.functions + .entry(name.to_string()) + .or_insert_with(|| Function::new(&self.name, name)) } /// Add a tag to the namespace.