minecraft_command_types/datapack/
mod.rs1pub mod pack;
2pub mod tag;
3
4use crate::datapack::pack::Pack;
5use crate::datapack::pack::feature::Features;
6use crate::datapack::pack::filter::Filter;
7use crate::datapack::pack::language::Language;
8use crate::datapack::pack::overlay::Overlays;
9use crate::datapack::tag::{Tag, TagType, Worldgen};
10use nonempty::NonEmpty;
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::collections::BTreeMap;
14use std::path::Path;
15use std::{fs, io};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(rename_all = "snake_case")]
19pub struct PackMCMeta {
20 pub pack: Pack,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub features: Option<Features>,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub filter: Option<Filter>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub overlays: Option<Overlays>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub language: Option<BTreeMap<String, Language>>,
29}
30
31#[derive(Clone)]
32pub enum FilePathNode<T> {
33 Directory(String, Vec<FilePathNode<T>>),
34 File(String, T),
35}
36
37impl<T> FilePathNode<T> {
38 pub fn from_str(path: &str, value: T) -> Self {
39 let mut parts = path.split('/').rev();
40 let file_name = parts.next().expect("Path cannot be empty");
41 let mut current_node = FilePathNode::File(file_name.to_string(), value);
42
43 for part in parts {
44 current_node = FilePathNode::Directory(part.to_string(), vec![current_node]);
45 }
46
47 current_node
48 }
49
50 pub fn from_nonempty_vec_string(vec: &NonEmpty<String>, value: T) -> Self {
51 let mut vec = vec.iter().rev();
52
53 let file_name = vec.next().expect("Path cannot be empty");
54 let mut current_node = FilePathNode::File(file_name.to_string(), value);
55
56 for part in vec {
57 current_node = FilePathNode::Directory(part.to_string(), vec![current_node]);
58 }
59
60 current_node
61 }
62}
63
64#[derive(Clone, Default)]
65pub struct Namespace {
66 pub functions: Vec<FilePathNode<String>>,
67 pub tags: BTreeMap<TagType, Vec<FilePathNode<Tag>>>,
68
69 pub advancements: Vec<FilePathNode<Value>>,
70 pub banner_patterns: Vec<FilePathNode<Value>>,
71 pub cat_variants: Vec<FilePathNode<Value>>,
72 pub chat_types: Vec<FilePathNode<Value>>,
73 pub chicken_variants: Vec<FilePathNode<Value>>,
74 pub cow_variants: Vec<FilePathNode<Value>>,
75 pub damage_types: Vec<FilePathNode<Value>>,
76 pub dialogs: Vec<FilePathNode<Value>>,
77 pub dimensions: Vec<FilePathNode<Value>>,
78 pub dimension_types: Vec<FilePathNode<Value>>,
79 pub enchantments: Vec<FilePathNode<Value>>,
80 pub enchantment_providers: Vec<FilePathNode<Value>>,
81 pub frog_variants: Vec<FilePathNode<Value>>,
82 pub instruments: Vec<FilePathNode<Value>>,
83 pub item_modifiers: Vec<FilePathNode<Value>>,
84 pub jukebox_songs: Vec<FilePathNode<Value>>,
85 pub loot_tables: Vec<FilePathNode<Value>>,
86 pub painting_variants: Vec<FilePathNode<Value>>,
87 pub pig_variants: Vec<FilePathNode<Value>>,
88 pub predicates: Vec<FilePathNode<Value>>,
89 pub recipes: Vec<FilePathNode<Value>>,
90 pub test_environments: Vec<FilePathNode<Value>>,
91 pub test_instances: Vec<FilePathNode<Value>>,
92 pub timelines: Vec<FilePathNode<Value>>,
93 pub trial_spawners: Vec<FilePathNode<Value>>,
94 pub trim_materials: Vec<FilePathNode<Value>>,
95 pub trim_patterns: Vec<FilePathNode<Value>>,
96 pub wolf_sound_variants: Vec<FilePathNode<Value>>,
97 pub wolf_variants: Vec<FilePathNode<Value>>,
98 pub worldgen: Worldgen,
99}
100
101fn write_file_path_nodes<T>(
102 base_path: &Path,
103 nodes: &[FilePathNode<T>],
104 extension: &str,
105 serializer: &impl Fn(&T) -> io::Result<String>,
106) -> io::Result<()> {
107 fs::create_dir_all(base_path)?;
108 for node in nodes {
109 match node {
110 FilePathNode::Directory(name, children) => {
111 let dir_path = base_path.join(name);
112 write_file_path_nodes(&dir_path, children, extension, serializer)?;
113 }
114 FilePathNode::File(name, content) => {
115 let filename = if name.ends_with(extension) {
116 name.clone()
117 } else {
118 format!("{}{}", name, extension)
119 };
120 let file_path = base_path.join(filename);
121 let serialized_content = serializer(content)?;
122 fs::write(file_path, serialized_content)?;
123 }
124 }
125 }
126 Ok(())
127}
128
129impl Namespace {
130 pub fn write(&self, namespace_path: &Path) -> io::Result<()> {
131 let json_serializer = |v: &Value| serde_json::to_string_pretty(v).map_err(io::Error::other);
132
133 if !self.functions.is_empty() {
134 write_file_path_nodes(
135 &namespace_path.join("function"),
136 &self.functions,
137 ".mcfunction",
138 &|content_str| Ok(content_str.clone()),
139 )?;
140 }
141
142 if !self.tags.is_empty() {
143 let tags_root_path = namespace_path.join("tags");
144 for (tag_type, nodes) in &self.tags {
145 let type_path = if tag_type.is_worldgen() {
146 tags_root_path.join("worldgen").join(tag_type.to_string())
147 } else {
148 tags_root_path.join(tag_type.to_string())
149 };
150
151 write_file_path_nodes(&type_path, nodes, ".json", &|tag| {
152 serde_json::to_string_pretty(tag).map_err(io::Error::other)
153 })?;
154 }
155 }
156
157 macro_rules! generate_write_file_path_nodes {
158 ($field_name:expr, $folder_name:expr) => {
159 if !$field_name.is_empty() {
160 write_file_path_nodes(
161 &namespace_path.join($folder_name),
162 &$field_name,
163 ".json",
164 &json_serializer,
165 )?;
166 }
167 };
168 }
169
170 generate_write_file_path_nodes!(self.advancements, "advancement");
171 generate_write_file_path_nodes!(self.banner_patterns, "banner_pattern");
172 generate_write_file_path_nodes!(self.cat_variants, "cat_variant");
173 generate_write_file_path_nodes!(self.chat_types, "chat_type");
174 generate_write_file_path_nodes!(self.chicken_variants, "chicken_variant");
175 generate_write_file_path_nodes!(self.cow_variants, "cow_variant");
176 generate_write_file_path_nodes!(self.damage_types, "damage_type");
177 generate_write_file_path_nodes!(self.dialogs, "dialog");
178 generate_write_file_path_nodes!(self.dimensions, "dimension");
179 generate_write_file_path_nodes!(self.dimension_types, "dimension_type");
180 generate_write_file_path_nodes!(self.enchantments, "enchantment");
181 generate_write_file_path_nodes!(self.enchantment_providers, "enchantment_provider");
182 generate_write_file_path_nodes!(self.frog_variants, "frog_variant");
183 generate_write_file_path_nodes!(self.instruments, "instrument");
184 generate_write_file_path_nodes!(self.item_modifiers, "item_modifier");
185 generate_write_file_path_nodes!(self.jukebox_songs, "jukebox_song");
186 generate_write_file_path_nodes!(self.loot_tables, "loot_table");
187 generate_write_file_path_nodes!(self.painting_variants, "painting_variant");
188 generate_write_file_path_nodes!(self.pig_variants, "pig_variant");
189 generate_write_file_path_nodes!(self.predicates, "predicate");
190 generate_write_file_path_nodes!(self.recipes, "recipe");
191 generate_write_file_path_nodes!(self.test_environments, "test_environment");
192 generate_write_file_path_nodes!(self.test_instances, "test_instance");
193 generate_write_file_path_nodes!(self.timelines, "timeline");
194 generate_write_file_path_nodes!(self.trial_spawners, "trial_spawner");
195 generate_write_file_path_nodes!(self.trim_materials, "trim_material");
196 generate_write_file_path_nodes!(self.trim_patterns, "trim_pattern");
197 generate_write_file_path_nodes!(self.wolf_sound_variants, "wolf_sound_variant");
198 generate_write_file_path_nodes!(self.wolf_variants, "wolf_variant");
199
200 Ok(())
201 }
202}
203
204pub struct Datapack {
205 pub pack: PackMCMeta,
206 pub namespaces: BTreeMap<String, Namespace>,
207}
208
209impl Datapack {
210 #[inline]
211 #[must_use]
212 pub fn new(pack_format: i32, description: Value) -> Datapack {
213 Datapack {
214 pack: PackMCMeta {
215 pack: Pack {
216 pack_format: Some(pack_format),
217 description,
218 max_format: None,
219 min_format: None,
220 supported_formats: None,
221 },
222 features: None,
223 filter: None,
224 overlays: None,
225 language: None,
226 },
227 namespaces: BTreeMap::new(),
228 }
229 }
230 pub fn write(&self, datapack_directory: &Path) -> io::Result<()> {
231 fs::create_dir_all(datapack_directory)?;
232
233 let mcmeta_path = datapack_directory.join("pack.mcmeta");
234 let mcmeta_content = serde_json::to_string_pretty(&self.pack).map_err(io::Error::other)?;
235 fs::write(mcmeta_path, mcmeta_content)?;
236
237 let data_path = datapack_directory.join("data");
238
239 for (name, namespace) in &self.namespaces {
240 let namespace_path = data_path.join(name);
241 namespace.write(&namespace_path)?;
242 }
243
244 Ok(())
245 }
246
247 pub fn get_namespace_mut(&mut self, name: &str) -> &mut Namespace {
248 self.namespaces.entry(name.to_string()).or_default()
249 }
250
251 pub fn add_namespace(&mut self, name: String, namespace: Namespace) {
252 self.namespaces.insert(name, namespace);
253 }
254}