Compare commits

...

3 Commits

Author SHA1 Message Date
Moritz Hölting 811d715082 allow access to registered scoreboards 2025-02-27 21:41:07 +01:00
Moritz Hölting 1950c29ac3 only generate uninstall function if it contains commands 2025-02-27 16:38:23 +01:00
Moritz Hölting 9337d09d7b implement registering scoreboards in datapack for automatic creation and deletion 2025-02-27 16:34:39 +01:00
6 changed files with 108 additions and 13 deletions

View File

@ -3,7 +3,7 @@ on:
push: push:
branches: branches:
- main - main
- development - develop
- 'releases/**' - 'releases/**'
pull_request: pull_request:

View File

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- support for commands using macros - support for commands using macros
- support for registering scoreboards (automatic creation and deletion)
### Changed ### Changed
- use "return" command for conditionals instead of data storage when using supported pack format - use "return" command for conditionals instead of data storage when using supported pack format

View File

@ -18,7 +18,7 @@ use shulkerbox::{
util::compile::CompileOptions, 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_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_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 .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

View File

@ -5,10 +5,12 @@ use shulkerbox::prelude::*;
fn main() { fn main() {
// create a new datapack // 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" dp.register_scoreboard("example_scoreboard", Some("dummy"), None);
let namespace = dp.namespace_mut("test");
// get the namespace "example"
let namespace = dp.namespace_mut("example");
// get the function "foo" of the namespace "test" and add some commands // get the function "foo" of the namespace "test" and add some commands
let foo_function = namespace.function_mut("foo"); 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 // compile the datapack
let v_folder = dp.compile(&CompileOptions::default()); let v_folder = dp.compile(&CompileOptions::default());

View File

@ -8,7 +8,7 @@ pub use command::{Command, Condition, Execute};
pub use function::Function; pub use function::Function;
pub use namespace::Namespace; pub use namespace::Namespace;
use std::{collections::HashMap, ops::RangeInclusive, sync::Mutex}; use std::{borrow::Cow, collections::BTreeMap, ops::RangeInclusive, sync::Mutex};
use crate::{ use crate::{
util::compile::{CompileOptions, CompilerState, MutCompilerState}, util::compile::{CompileOptions, CompilerState, MutCompilerState},
@ -23,7 +23,10 @@ pub struct Datapack {
description: String, description: String,
pack_format: u8, pack_format: u8,
supported_formats: Option<RangeInclusive<u8>>, supported_formats: Option<RangeInclusive<u8>>,
namespaces: HashMap<String, Namespace>, main_namespace_name: String,
namespaces: BTreeMap<String, Namespace>,
/// Scoreboard name -> (criteria, display name)
scoreboards: BTreeMap<String, (Option<String>, Option<String>)>,
custom_files: VFolder, custom_files: VFolder,
} }
@ -32,12 +35,14 @@ impl Datapack {
/// Create a new Minecraft datapack. /// Create a new Minecraft datapack.
#[must_use] #[must_use]
pub fn new(pack_format: u8) -> Self { pub fn new(main_namespace_name: impl Into<String>, pack_format: u8) -> Self {
Self { Self {
description: String::from("A Minecraft datapack created with shulkerbox"), description: String::from("A Minecraft datapack created with shulkerbox"),
pack_format, pack_format,
supported_formats: None, supported_formats: None,
namespaces: HashMap::new(), main_namespace_name: main_namespace_name.into(),
namespaces: BTreeMap::new(),
scoreboards: BTreeMap::new(),
custom_files: VFolder::new(), custom_files: VFolder::new(),
} }
} }
@ -110,6 +115,25 @@ impl Datapack {
.add_value(tag::TagValue::Simple(function.to_string())); .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)),
);
}
/// Scoreboards registered in the datapack.
#[must_use]
pub fn scoreboards(&self) -> &BTreeMap<String, (Option<String>, Option<String>)> {
&self.scoreboards
}
/// Add a custom file to the datapack. /// Add a custom file to the datapack.
pub fn add_custom_file(&mut self, path: &str, file: VFile) { pub fn add_custom_file(&mut self, path: &str, file: VFile) {
self.custom_files.add_file(path, file); self.custom_files.add_file(path, file);
@ -132,8 +156,64 @@ impl Datapack {
root_folder.add_file("pack.mcmeta", mcmeta); root_folder.add_file("pack.mcmeta", mcmeta);
let mut data_folder = VFolder::new(); let mut data_folder = VFolder::new();
let mut modified_namespaces = self
.namespaces
.iter()
.map(|(name, namespace)| (name.as_str(), Cow::Borrowed(namespace)))
.collect::<BTreeMap<_, _>>();
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("sb/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!(
"{}:sb/register_scoreboards",
self.main_namespace_name
)));
}
if let Some(uninstall_commands) = uninstall_commands {
if !uninstall_commands.is_empty() {
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);
}
}
// Compile namespaces // Compile namespaces
for (name, namespace) in &self.namespaces { for (name, namespace) in modified_namespaces {
let namespace_folder = namespace.compile(&options, &compiler_state); let namespace_folder = namespace.compile(&options, &compiler_state);
data_folder.add_existing_folder(name, namespace_folder); data_folder.add_existing_folder(name, namespace_folder);
} }
@ -180,7 +260,7 @@ mod tests {
fn test_datapack() { fn test_datapack() {
let template_dir = tempfile::tempdir().expect("error creating tempdir"); 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_description("My datapack")
.with_template_folder(template_dir.path()) .with_template_folder(template_dir.path())
.expect("error reading template folder"); .expect("error reading template folder");
@ -193,7 +273,7 @@ mod tests {
#[test] #[test]
fn test_generate_mcmeta() { 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 state = Mutex::new(CompilerState::default());
let mcmeta = generate_mcmeta(dp, &CompileOptions::default(), &state); let mcmeta = generate_mcmeta(dp, &CompileOptions::default(), &state);

View File

@ -17,6 +17,8 @@ pub struct CompileOptions {
pub(crate) pack_format: u8, pub(crate) pack_format: u8,
/// Whether to compile in debug mode. /// Whether to compile in debug mode.
pub(crate) debug: bool, pub(crate) debug: bool,
/// Whether to generate an uninstall function.
pub(crate) uninstall_function: bool,
} }
impl CompileOptions { impl CompileOptions {
@ -25,6 +27,15 @@ impl CompileOptions {
pub fn with_debug(self, debug: bool) -> Self { pub fn with_debug(self, debug: bool) -> Self {
Self { debug, ..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 { impl Default for CompileOptions {
@ -32,6 +43,7 @@ impl Default for CompileOptions {
Self { Self {
pack_format: Datapack::LATEST_FORMAT, pack_format: Datapack::LATEST_FORMAT,
debug: true, debug: true,
uninstall_function: true,
} }
} }
} }