Basic implementation for datapack including functions and tags
This commit is contained in:
parent
78a3d8f520
commit
2560588a17
|
@ -5,9 +5,8 @@ edition = "2021"
|
|||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
serde = ["dep:serde"]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.197", features = ["derive"], optional = true }
|
||||
zip = "0.6.6"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_json = "1.0.114"
|
||||
zip = { version = "0.6.6", default-features = false, features = ["deflate", "time"] }
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
//! Represents a command that can be included in a function.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::util::compile::{CompileOptions, MutCompilerState, MutFunctionCompilerState};
|
||||
|
||||
/// Represents a command that can be included in a function.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Command {
|
||||
/// A command that is already formatted as a string.
|
||||
Raw(String),
|
||||
/// Message to be printed only in debug mode
|
||||
Debug(String),
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Create a new raw command.
|
||||
pub fn raw(command: &str) -> Self {
|
||||
Self::Raw(command.to_string())
|
||||
}
|
||||
|
||||
/// Compile the command into a string.
|
||||
pub fn compile(
|
||||
&self,
|
||||
options: &CompileOptions,
|
||||
global_state: &MutCompilerState,
|
||||
function_state: &MutFunctionCompilerState,
|
||||
) -> String {
|
||||
let _ = options;
|
||||
let _ = global_state;
|
||||
let _ = function_state;
|
||||
match self {
|
||||
Self::Raw(command) => command.clone(),
|
||||
Self::Debug(message) => compile_debug(message, options),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Command {
|
||||
fn from(command: &str) -> Self {
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
//! Function struct and implementation
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
util::compile::{CompileOptions, FunctionCompilerState, MutCompilerState},
|
||||
virtual_fs::VFile,
|
||||
};
|
||||
|
||||
use super::command::Command;
|
||||
|
||||
/// Function that can be called by a command
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct Function {
|
||||
commands: Vec<Command>,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
/// Create a new function.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Add a command to the function.
|
||||
pub fn add_command(&mut self, command: Command) {
|
||||
self.commands.push(command);
|
||||
}
|
||||
|
||||
/// Get the commands of the function.
|
||||
pub fn get_commands(&self) -> &Vec<Command> {
|
||||
&self.commands
|
||||
}
|
||||
|
||||
/// Compile the function into a virtual file.
|
||||
pub fn compile(&self, options: &CompileOptions, state: &MutCompilerState) -> VFile {
|
||||
let function_state = Mutex::new(FunctionCompilerState::default());
|
||||
|
||||
let content = self
|
||||
.commands
|
||||
.iter()
|
||||
.map(|c| c.compile(options, state, &function_state))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
VFile::Text(content)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
//! Datapack module for creating and managing Minecraft datapacks.
|
||||
|
||||
mod command;
|
||||
mod function;
|
||||
mod namespace;
|
||||
pub mod tag;
|
||||
pub use command::Command;
|
||||
pub use function::Function;
|
||||
pub use namespace::Namespace;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::{collections::HashMap, ops::RangeInclusive, path::Path, sync::Mutex};
|
||||
|
||||
use crate::{
|
||||
util::compile::{CompileOptions, CompilerState, MutCompilerState},
|
||||
virtual_fs::{VFile, VFolder},
|
||||
};
|
||||
|
||||
/// A Minecraft datapack.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Datapack {
|
||||
// TODO: Support filter and overlays
|
||||
name: String,
|
||||
description: String,
|
||||
pack_format: u8,
|
||||
supported_formats: Option<RangeInclusive<u8>>,
|
||||
namespaces: HashMap<String, Namespace>,
|
||||
tick: Vec<String>,
|
||||
load: Vec<String>,
|
||||
custom_files: VFolder,
|
||||
}
|
||||
|
||||
impl Datapack {
|
||||
/// Create a new Minecraft datapack.
|
||||
pub fn new(name: &str, pack_format: u8) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
description: String::from("A Minecraft datapack created with shulkerbox"),
|
||||
pack_format,
|
||||
supported_formats: None,
|
||||
namespaces: HashMap::new(),
|
||||
tick: Vec::new(),
|
||||
load: Vec::new(),
|
||||
custom_files: VFolder::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the description of the datapack.
|
||||
pub fn with_description(self, description: &str) -> Self {
|
||||
Self {
|
||||
description: description.to_string(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the supported pack formats of the datapack.
|
||||
pub fn with_supported_formats(self, supported_formats: RangeInclusive<u8>) -> Self {
|
||||
Self {
|
||||
supported_formats: Some(supported_formats),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the custom files of the datapack.
|
||||
pub fn with_template_folder(self, path: &Path) -> std::io::Result<Self> {
|
||||
let mut template = VFolder::try_from(path)?;
|
||||
template.merge(self.custom_files);
|
||||
|
||||
Ok(Self {
|
||||
custom_files: template,
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
/// 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()));
|
||||
}
|
||||
self.namespaces
|
||||
.insert(namespace.get_name().to_string(), namespace);
|
||||
}
|
||||
|
||||
/// Add a function to the tick function list.
|
||||
pub fn add_tick(&mut self, function: &str) {
|
||||
self.tick.push(function.to_string());
|
||||
}
|
||||
|
||||
/// Add a function to the load function list.
|
||||
pub fn add_load(&mut self, function: &str) {
|
||||
self.load.push(function.to_string());
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// Compile the pack into a virtual folder.
|
||||
pub fn compile(&self, options: &CompileOptions) -> VFolder {
|
||||
let compiler_state = Mutex::new(CompilerState::default());
|
||||
|
||||
let mut root_folder = self.custom_files.clone();
|
||||
let mcmeta = generate_mcmeta(self, options, &compiler_state);
|
||||
root_folder.add_file("pack.mcmeta", mcmeta);
|
||||
let mut data_folder = VFolder::new();
|
||||
|
||||
// Compile namespaces
|
||||
for (name, namespace) in &self.namespaces {
|
||||
let namespace_folder = namespace.compile(options, &compiler_state);
|
||||
data_folder.add_existing_folder(name, namespace_folder);
|
||||
}
|
||||
|
||||
// Compile tick and load tag
|
||||
if !self.tick.is_empty() {
|
||||
let mut tick_tag = tag::Tag::new(tag::TagType::Functions, false);
|
||||
for function in &self.tick {
|
||||
tick_tag.add_value(tag::TagValue::Simple(function.to_owned()));
|
||||
}
|
||||
data_folder.add_file(
|
||||
"minecraft/tags/functions/tick.json",
|
||||
tick_tag.compile_no_state(options).1,
|
||||
);
|
||||
}
|
||||
if !self.load.is_empty() {
|
||||
let mut load_tag = tag::Tag::new(tag::TagType::Functions, false);
|
||||
for function in &self.tick {
|
||||
load_tag.add_value(tag::TagValue::Simple(function.to_owned()));
|
||||
}
|
||||
data_folder.add_file(
|
||||
"minecraft/tags/functions/load.json",
|
||||
load_tag.compile_no_state(options).1,
|
||||
);
|
||||
}
|
||||
|
||||
root_folder.add_existing_folder("data", data_folder);
|
||||
root_folder
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_mcmeta(dp: &Datapack, _options: &CompileOptions, _state: &MutCompilerState) -> VFile {
|
||||
let mut content = serde_json::json!({
|
||||
"pack": {
|
||||
"description": dp.description,
|
||||
"pack_format": dp.pack_format
|
||||
}
|
||||
});
|
||||
if let Some(supported_formats) = &dp.supported_formats {
|
||||
content["pack"]["supported_formats"] = serde_json::json!({
|
||||
"min_inclusive": *supported_formats.start(),
|
||||
"max_inclusive": *supported_formats.end()
|
||||
})
|
||||
}
|
||||
|
||||
VFile::Text(content.to_string())
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
//! Namespace of a datapack
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
util::compile::{CompileOptions, MutCompilerState},
|
||||
virtual_fs::VFolder,
|
||||
};
|
||||
|
||||
use super::{function::Function, tag::Tag};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Namespace of a datapack
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Namespace {
|
||||
name: String,
|
||||
functions: HashMap<String, Function>,
|
||||
main_function: Function,
|
||||
tags: HashMap<String, Tag>,
|
||||
}
|
||||
|
||||
impl Namespace {
|
||||
/// Create a new namespace.
|
||||
pub fn new(name: &str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
functions: HashMap::new(),
|
||||
main_function: Function::default(),
|
||||
tags: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the name of the namespace.
|
||||
pub fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Get the main function of the namespace.
|
||||
pub fn get_main_function(&self) -> &Function {
|
||||
&self.main_function
|
||||
}
|
||||
/// Get the main function of the namespace mutably.
|
||||
pub fn get_main_function_mut(&mut self) -> &mut Function {
|
||||
&mut self.main_function
|
||||
}
|
||||
|
||||
/// Get the functions of the namespace.
|
||||
pub fn get_functions(&self) -> &HashMap<String, Function> {
|
||||
&self.functions
|
||||
}
|
||||
|
||||
/// Get the tags of the namespace.
|
||||
pub fn get_tags(&self) -> &HashMap<String, Tag> {
|
||||
&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);
|
||||
}
|
||||
|
||||
/// Add a tag to the namespace.
|
||||
pub fn add_tag(&mut self, name: &str, tag: Tag) {
|
||||
self.tags.insert(name.to_string(), tag);
|
||||
}
|
||||
|
||||
/// Compile the namespace into a virtual folder.
|
||||
pub fn compile(&self, options: &CompileOptions, state: &MutCompilerState) -> VFolder {
|
||||
let mut root_folder = VFolder::new();
|
||||
|
||||
// Compile functions
|
||||
for (path, function) in &self.functions {
|
||||
root_folder.add_file(
|
||||
&format!("functions/{}.mcfunction", path),
|
||||
function.compile(options, state),
|
||||
);
|
||||
}
|
||||
if !self.main_function.get_commands().is_empty() {
|
||||
root_folder.add_file(
|
||||
"functions/main.mcfunction",
|
||||
self.main_function.compile(options, state),
|
||||
);
|
||||
}
|
||||
|
||||
// Compile tags
|
||||
for (path, tag) in &self.tags {
|
||||
let (tag_type, vfile) = tag.compile(options, state);
|
||||
root_folder.add_file(&format!("tags/{}/{}.json", tag_type, path), vfile);
|
||||
}
|
||||
|
||||
root_folder
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
//! A tag for various types.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
util::compile::{CompileOptions, MutCompilerState},
|
||||
virtual_fs::VFile,
|
||||
};
|
||||
|
||||
/// A tag for various types.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Tag {
|
||||
r#type: TagType,
|
||||
replace: bool,
|
||||
values: Vec<TagValue>,
|
||||
}
|
||||
impl Tag {
|
||||
/// Create a new tag.
|
||||
pub fn new(r#type: TagType, replace: bool) -> Self {
|
||||
Self {
|
||||
r#type,
|
||||
replace,
|
||||
values: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a value to the tag.
|
||||
pub fn add_value(&mut self, value: TagValue) {
|
||||
self.values.push(value);
|
||||
}
|
||||
|
||||
/// Compile the tag into a virtual file without state
|
||||
pub fn compile_no_state(&self, _options: &CompileOptions) -> (String, VFile) {
|
||||
let json = serde_json::json!({
|
||||
"replace": self.replace,
|
||||
"values": self.values.iter().map(TagValue::compile).collect::<Vec<_>>()
|
||||
});
|
||||
let type_str = self.r#type.to_string();
|
||||
let vfile = VFile::Text(serde_json::to_string(&json).expect("Failed to serialize tag"));
|
||||
|
||||
(type_str, vfile)
|
||||
}
|
||||
|
||||
/// Compile the tag into a virtual file.
|
||||
pub fn compile(&self, options: &CompileOptions, _state: &MutCompilerState) -> (String, VFile) {
|
||||
self.compile_no_state(options)
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of a tag.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum TagType {
|
||||
/// A tag for blocks.
|
||||
Blocks,
|
||||
/// A tag for fluids.
|
||||
Fluids,
|
||||
/// A tag for items.
|
||||
Items,
|
||||
/// A tag for entities.
|
||||
Entities,
|
||||
/// A tag for game events.
|
||||
GameEvents,
|
||||
/// A tag for functions.
|
||||
Functions,
|
||||
/// A custom tag
|
||||
/// `Others(<registry path>)` => `data/<namespace>/tags/<registry path>`
|
||||
Others(String),
|
||||
}
|
||||
impl ToString for TagType {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Self::Blocks => "blocks".to_string(),
|
||||
Self::Fluids => "fluids".to_string(),
|
||||
Self::Items => "items".to_string(),
|
||||
Self::Entities => "entity_types".to_string(),
|
||||
Self::GameEvents => "game_events".to_string(),
|
||||
Self::Functions => "functions".to_string(),
|
||||
Self::Others(path) => path.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The value of a tag.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum TagValue {
|
||||
/// A simple value, either a resource location or an id of another tag.
|
||||
Simple(String),
|
||||
/// An advanced value, with an id (same as above) and whether the loading of the tag should fail when entry is not found.
|
||||
Advanced {
|
||||
/// The id of the tag.
|
||||
id: String,
|
||||
/// Whether the loading of the tag should fail when the entry is not found.
|
||||
required: bool,
|
||||
},
|
||||
}
|
||||
impl From<&str> for TagValue {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::Simple(value.to_string())
|
||||
}
|
||||
}
|
||||
impl TagValue {
|
||||
/// Compile the tag value into a JSON value.
|
||||
pub fn compile(&self) -> serde_json::Value {
|
||||
match self {
|
||||
Self::Simple(value) => serde_json::Value::String(value.clone()),
|
||||
Self::Advanced { id, required } => {
|
||||
let mut map = serde_json::Map::new();
|
||||
map.insert("id".to_string(), serde_json::Value::String(id.clone()));
|
||||
map.insert("required".to_string(), serde_json::Value::Bool(*required));
|
||||
serde_json::Value::Object(map)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,4 +10,6 @@
|
|||
)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
pub mod datapack;
|
||||
pub mod util;
|
||||
pub mod virtual_fs;
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
//! Compile options for the compiler.
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Compile options for the compiler.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CompileOptions {
|
||||
/// Whether to compile in debug mode.
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
impl Default for CompileOptions {
|
||||
fn default() -> Self {
|
||||
Self { debug: true }
|
||||
}
|
||||
}
|
||||
|
||||
/// State of the compiler that can change during compilation.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct CompilerState {}
|
||||
/// Mutex for the compiler state.
|
||||
pub type MutCompilerState = Mutex<CompilerState>;
|
||||
|
||||
/// State of the compiler for each function that can change during compilation.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct FunctionCompilerState {}
|
||||
/// Mutex for the function compiler state.
|
||||
pub type MutFunctionCompilerState = Mutex<FunctionCompilerState>;
|
|
@ -0,0 +1,3 @@
|
|||
//! Utility functions for the Shulkerbox project.
|
||||
|
||||
pub mod compile;
|
|
@ -7,11 +7,11 @@ use std::{
|
|||
path::Path,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zip::ZipWriter;
|
||||
|
||||
/// Folder representation in virtual file system
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct VFolder {
|
||||
folders: HashMap<String, VFolder>,
|
||||
files: HashMap<String, VFile>,
|
||||
|
@ -185,6 +185,20 @@ impl VFolder {
|
|||
|
||||
files
|
||||
}
|
||||
|
||||
/// Recursively merge another folder into this folder.
|
||||
pub fn merge(&mut self, other: Self) {
|
||||
for (name, folder) in other.folders {
|
||||
if let Some(existing_folder) = self.folders.get_mut(&name) {
|
||||
existing_folder.merge(folder);
|
||||
} else {
|
||||
self.folders.insert(name, folder);
|
||||
}
|
||||
}
|
||||
for (name, file) in other.files {
|
||||
self.files.insert(name, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Path> for VFolder {
|
||||
|
@ -223,8 +237,7 @@ impl TryFrom<&Path> for VFolder {
|
|||
}
|
||||
|
||||
/// File representation in virtual file system
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum VFile {
|
||||
/// Text file
|
||||
Text(String),
|
||||
|
|
Loading…
Reference in New Issue