From b9c319165ebb7415fed2e420bcca8c98269fcd96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20H=C3=B6lting?= <87192362+moritz-hoelting@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:18:58 +0100 Subject: [PATCH] implement registering scoreboards in datapack for automatic creation and deletion --- .github/workflows/test.yml | 2 +- README.md | 2 +- examples/basic.rs | 10 +++-- src/datapack/mod.rs | 89 +++++++++++++++++++++++++++++++++++--- src/util/compile.rs | 12 +++++ 5 files changed, 102 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 845e627..e81c3d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,7 @@ on: push: branches: - main - - development + - develop - 'releases/**' pull_request: diff --git a/README.md b/README.md index c819828..8869d5a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ use shulkerbox::{ util::compile::CompileOptions, }; -let mut dp = Datapack::new("shulkerpack", 20) // Create a new datapack with the name "shulkerpack" and the pack format 20 +let mut dp = Datapack::new("shulker", 20) // Create a new datapack with the name "shulkerpack" and the pack format 20 .with_description("I created this datapack with rust") // Add a description to the datapack .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 diff --git a/examples/basic.rs b/examples/basic.rs index 551c9d0..ac44740 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -5,10 +5,12 @@ use shulkerbox::prelude::*; fn main() { // create a new datapack - let mut dp = Datapack::new(16).with_supported_formats(16..=20); + let mut dp = Datapack::new("example", 16).with_supported_formats(16..=20); - // get the namespace "test" - let namespace = dp.namespace_mut("test"); + dp.register_scoreboard("example_scoreboard", Some("dummy"), None); + + // get the namespace "example" + let namespace = dp.namespace_mut("example"); // get the function "foo" of the namespace "test" and add some commands let foo_function = namespace.function_mut("foo"); @@ -32,7 +34,7 @@ fn main() { )), ))); - dp.add_load("test:foo"); + dp.add_load("example:foo"); // compile the datapack let v_folder = dp.compile(&CompileOptions::default()); diff --git a/src/datapack/mod.rs b/src/datapack/mod.rs index eb20440..ebbfc70 100644 --- a/src/datapack/mod.rs +++ b/src/datapack/mod.rs @@ -8,7 +8,7 @@ pub use command::{Command, Condition, Execute}; pub use function::Function; pub use namespace::Namespace; -use std::{collections::HashMap, ops::RangeInclusive, sync::Mutex}; +use std::{borrow::Cow, collections::BTreeMap, ops::RangeInclusive, sync::Mutex}; use crate::{ util::compile::{CompileOptions, CompilerState, MutCompilerState}, @@ -23,7 +23,10 @@ pub struct Datapack { description: String, pack_format: u8, supported_formats: Option>, - namespaces: HashMap, + main_namespace_name: String, + namespaces: BTreeMap, + /// Scoreboard name -> (criteria, display name) + scoreboards: BTreeMap, Option)>, custom_files: VFolder, } @@ -32,12 +35,14 @@ impl Datapack { /// Create a new Minecraft datapack. #[must_use] - pub fn new(pack_format: u8) -> Self { + pub fn new(main_namespace_name: impl Into, pack_format: u8) -> Self { Self { description: String::from("A Minecraft datapack created with shulkerbox"), pack_format, supported_formats: None, - namespaces: HashMap::new(), + main_namespace_name: main_namespace_name.into(), + namespaces: BTreeMap::new(), + scoreboards: BTreeMap::new(), custom_files: VFolder::new(), } } @@ -110,6 +115,19 @@ impl Datapack { .add_value(tag::TagValue::Simple(function.to_string())); } + /// Register a scoreboard. + pub fn register_scoreboard( + &mut self, + name: &str, + criteria: Option<&str>, + display_name: Option<&str>, + ) { + self.scoreboards.insert( + name.to_string(), + (criteria.map(String::from), display_name.map(String::from)), + ); + } + /// Add a custom file to the datapack. pub fn add_custom_file(&mut self, path: &str, file: VFile) { self.custom_files.add_file(path, file); @@ -132,8 +150,65 @@ impl Datapack { root_folder.add_file("pack.mcmeta", mcmeta); let mut data_folder = VFolder::new(); + let mut modified_namespaces = self + .namespaces + .iter() + .map(|(name, namespace)| (name.as_str(), Cow::Borrowed(namespace))) + .collect::>(); + + let mut uninstall_commands = options.uninstall_function.then(Vec::new); + + if !self.scoreboards.is_empty() { + let main_namespace = modified_namespaces + .entry(&self.main_namespace_name) + .or_insert_with(|| Cow::Owned(Namespace::new(&self.main_namespace_name))); + let register_scoreboard_function = main_namespace + .to_mut() + .function_mut("shu/register_scoreboards"); + for (name, (criteria, display_name)) in &self.scoreboards { + let mut creation_command = format!( + "scoreboard objectives add {name} {criteria}", + criteria = criteria.as_deref().unwrap_or("dummy") + ); + if let Some(display_name) = display_name { + creation_command.push(' '); + creation_command.push_str(display_name); + } + register_scoreboard_function.add_command(Command::Raw(creation_command)); + + if let Some(uninstall_commands) = uninstall_commands.as_mut() { + uninstall_commands + .push(Command::Raw(format!("scoreboard objectives remove {name}"))); + } + } + + let minecraft_namespace = modified_namespaces + .entry("minecraft") + .or_insert_with(|| Cow::Owned(Namespace::new("minecraft"))); + minecraft_namespace + .to_mut() + .tag_mut("load", tag::TagType::Function) + .add_value(tag::TagValue::Simple(format!( + "{}:shu/register_scoreboards", + self.main_namespace_name + ))); + } + + if let Some(uninstall_commands) = uninstall_commands { + let main_namespace = modified_namespaces + .entry(&self.main_namespace_name) + .or_insert_with(|| Cow::Owned(Namespace::new(&self.main_namespace_name))); + let uninstall_function = main_namespace.to_mut().function_mut("uninstall"); + uninstall_function + .get_commands_mut() + .extend(uninstall_commands); + } + + dbg!(&self.namespaces); + dbg!(&modified_namespaces); + // Compile namespaces - for (name, namespace) in &self.namespaces { + for (name, namespace) in modified_namespaces { let namespace_folder = namespace.compile(&options, &compiler_state); data_folder.add_existing_folder(name, namespace_folder); } @@ -180,7 +255,7 @@ mod tests { fn test_datapack() { let template_dir = tempfile::tempdir().expect("error creating tempdir"); - let mut dp = Datapack::new(Datapack::LATEST_FORMAT) + let mut dp = Datapack::new("main", Datapack::LATEST_FORMAT) .with_description("My datapack") .with_template_folder(template_dir.path()) .expect("error reading template folder"); @@ -193,7 +268,7 @@ mod tests { #[test] fn test_generate_mcmeta() { - let dp = &Datapack::new(Datapack::LATEST_FORMAT).with_description("foo"); + let dp = &Datapack::new("main", Datapack::LATEST_FORMAT).with_description("foo"); let state = Mutex::new(CompilerState::default()); let mcmeta = generate_mcmeta(dp, &CompileOptions::default(), &state); diff --git a/src/util/compile.rs b/src/util/compile.rs index 374aa65..78ee3ce 100644 --- a/src/util/compile.rs +++ b/src/util/compile.rs @@ -17,6 +17,8 @@ pub struct CompileOptions { pub(crate) pack_format: u8, /// Whether to compile in debug mode. pub(crate) debug: bool, + /// Whether to generate an uninstall function. + pub(crate) uninstall_function: bool, } impl CompileOptions { @@ -25,6 +27,15 @@ impl CompileOptions { pub fn with_debug(self, debug: bool) -> Self { Self { debug, ..self } } + + /// Set whether to generate an uninstall function. + #[must_use] + pub fn with_uninstall_function(self, uninstall_function: bool) -> Self { + Self { + uninstall_function, + ..self + } + } } impl Default for CompileOptions { @@ -32,6 +43,7 @@ impl Default for CompileOptions { Self { pack_format: Datapack::LATEST_FORMAT, debug: true, + uninstall_function: true, } } }