schematic_mesher/resource_pack/
blockstate.rs1use serde::{Deserialize, Deserializer, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
11pub enum BlockstateDefinition {
12 Variants(HashMap<String, Vec<ModelVariant>>),
14 Multipart(Vec<MultipartCase>),
16}
17
18impl<'de> Deserialize<'de> for BlockstateDefinition {
19 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
20 where
21 D: Deserializer<'de>,
22 {
23 #[derive(Deserialize)]
24 struct RawBlockstate {
25 variants: Option<HashMap<String, VariantValue>>,
26 multipart: Option<Vec<MultipartCase>>,
27 }
28
29 let raw = RawBlockstate::deserialize(deserializer)?;
30
31 if let Some(variants) = raw.variants {
32 let parsed: HashMap<String, Vec<ModelVariant>> = variants
33 .into_iter()
34 .map(|(k, v)| (k, v.into_vec()))
35 .collect();
36 Ok(BlockstateDefinition::Variants(parsed))
37 } else if let Some(multipart) = raw.multipart {
38 Ok(BlockstateDefinition::Multipart(multipart))
39 } else {
40 Ok(BlockstateDefinition::Variants(HashMap::new()))
42 }
43 }
44}
45
46#[derive(Debug, Clone, Deserialize)]
48#[serde(untagged)]
49enum VariantValue {
50 Single(ModelVariant),
51 Multiple(Vec<ModelVariant>),
52}
53
54impl VariantValue {
55 fn into_vec(self) -> Vec<ModelVariant> {
56 match self {
57 VariantValue::Single(v) => vec![v],
58 VariantValue::Multiple(v) => v,
59 }
60 }
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct ModelVariant {
66 pub model: String,
68 #[serde(default)]
70 pub x: i32,
71 #[serde(default)]
73 pub y: i32,
74 #[serde(default)]
76 pub uvlock: bool,
77 #[serde(default = "default_weight")]
79 pub weight: u32,
80}
81
82fn default_weight() -> u32 {
83 1
84}
85
86impl ModelVariant {
87 pub fn model_location(&self) -> String {
89 if self.model.contains(':') {
90 self.model.clone()
91 } else {
92 format!("minecraft:{}", self.model)
93 }
94 }
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct MultipartCase {
100 #[serde(default)]
102 pub when: Option<MultipartCondition>,
103 pub apply: ApplyValue,
105}
106
107#[derive(Debug, Clone, Deserialize, Serialize)]
109#[serde(untagged)]
110pub enum ApplyValue {
111 Single(ModelVariant),
112 Multiple(Vec<ModelVariant>),
113}
114
115impl ApplyValue {
116 pub fn variants(&self) -> Vec<&ModelVariant> {
117 match self {
118 ApplyValue::Single(v) => vec![v],
119 ApplyValue::Multiple(v) => v.iter().collect(),
120 }
121 }
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126#[serde(untagged)]
127pub enum MultipartCondition {
128 Or { OR: Vec<HashMap<String, String>> },
130 And { AND: Vec<HashMap<String, String>> },
132 Simple(HashMap<String, String>),
134}
135
136impl MultipartCondition {
137 pub fn matches(&self, properties: &HashMap<String, String>) -> bool {
139 match self {
140 MultipartCondition::Or { OR } => {
141 OR.iter().any(|cond| Self::matches_simple(cond, properties))
142 }
143 MultipartCondition::And { AND } => {
144 AND.iter().all(|cond| Self::matches_simple(cond, properties))
145 }
146 MultipartCondition::Simple(cond) => Self::matches_simple(cond, properties),
147 }
148 }
149
150 fn matches_simple(
152 condition: &HashMap<String, String>,
153 properties: &HashMap<String, String>,
154 ) -> bool {
155 condition.iter().all(|(key, expected_value)| {
156 if expected_value.contains('|') {
158 let allowed: Vec<&str> = expected_value.split('|').collect();
159 properties
160 .get(key)
161 .map(|v| allowed.contains(&v.as_str()))
162 .unwrap_or_else(|| {
163 allowed.iter().any(|v| Self::is_default_value(v))
165 })
166 } else {
167 properties
168 .get(key)
169 .map(|v| v == expected_value)
170 .unwrap_or_else(|| {
171 Self::is_default_value(expected_value)
173 })
174 }
175 })
176 }
177
178 fn is_default_value(value: &str) -> bool {
181 matches!(
182 value,
183 "false" | "none" | "0" | "normal" | "bottom" | "floor"
184 )
185 }
186}
187
188pub fn build_property_string(properties: &HashMap<String, String>) -> String {
192 if properties.is_empty() {
193 return String::new();
194 }
195
196 let mut pairs: Vec<_> = properties.iter().collect();
197 pairs.sort_by_key(|(k, _)| *k);
198
199 pairs
200 .into_iter()
201 .map(|(k, v)| format!("{}={}", k, v))
202 .collect::<Vec<_>>()
203 .join(",")
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_parse_simple_variants() {
212 let json = r#"{
213 "variants": {
214 "": { "model": "block/stone" }
215 }
216 }"#;
217
218 let def: BlockstateDefinition = serde_json::from_str(json).unwrap();
219 match def {
220 BlockstateDefinition::Variants(variants) => {
221 assert!(variants.contains_key(""));
222 assert_eq!(variants[""].len(), 1);
223 assert_eq!(variants[""][0].model, "block/stone");
224 }
225 _ => panic!("Expected Variants"),
226 }
227 }
228
229 #[test]
230 fn test_parse_variants_with_rotation() {
231 let json = r#"{
232 "variants": {
233 "facing=north": { "model": "block/furnace", "y": 0 },
234 "facing=east": { "model": "block/furnace", "y": 90 },
235 "facing=south": { "model": "block/furnace", "y": 180 },
236 "facing=west": { "model": "block/furnace", "y": 270 }
237 }
238 }"#;
239
240 let def: BlockstateDefinition = serde_json::from_str(json).unwrap();
241 match def {
242 BlockstateDefinition::Variants(variants) => {
243 assert_eq!(variants.len(), 4);
244 assert_eq!(variants["facing=east"][0].y, 90);
245 }
246 _ => panic!("Expected Variants"),
247 }
248 }
249
250 #[test]
251 fn test_parse_weighted_variants() {
252 let json = r#"{
253 "variants": {
254 "": [
255 { "model": "block/stone", "weight": 10 },
256 { "model": "block/stone_mirrored", "weight": 5 }
257 ]
258 }
259 }"#;
260
261 let def: BlockstateDefinition = serde_json::from_str(json).unwrap();
262 match def {
263 BlockstateDefinition::Variants(variants) => {
264 assert_eq!(variants[""].len(), 2);
265 assert_eq!(variants[""][0].weight, 10);
266 assert_eq!(variants[""][1].weight, 5);
267 }
268 _ => panic!("Expected Variants"),
269 }
270 }
271
272 #[test]
273 fn test_parse_multipart() {
274 let json = r#"{
275 "multipart": [
276 { "apply": { "model": "block/fence_post" } },
277 { "when": { "north": "true" }, "apply": { "model": "block/fence_side" } }
278 ]
279 }"#;
280
281 let def: BlockstateDefinition = serde_json::from_str(json).unwrap();
282 match def {
283 BlockstateDefinition::Multipart(cases) => {
284 assert_eq!(cases.len(), 2);
285 assert!(cases[0].when.is_none());
286 assert!(cases[1].when.is_some());
287 }
288 _ => panic!("Expected Multipart"),
289 }
290 }
291
292 #[test]
293 fn test_multipart_condition_simple() {
294 let cond = MultipartCondition::Simple(
295 [("facing".to_string(), "north".to_string())]
296 .into_iter()
297 .collect(),
298 );
299
300 let props: HashMap<String, String> =
301 [("facing".to_string(), "north".to_string())].into_iter().collect();
302 assert!(cond.matches(&props));
303
304 let wrong_props: HashMap<String, String> =
305 [("facing".to_string(), "south".to_string())].into_iter().collect();
306 assert!(!cond.matches(&wrong_props));
307 }
308
309 #[test]
310 fn test_multipart_condition_or() {
311 let json = r#"{ "OR": [{ "facing": "north" }, { "facing": "south" }] }"#;
312 let cond: MultipartCondition = serde_json::from_str(json).unwrap();
313
314 let north: HashMap<String, String> =
315 [("facing".to_string(), "north".to_string())].into_iter().collect();
316 let south: HashMap<String, String> =
317 [("facing".to_string(), "south".to_string())].into_iter().collect();
318 let east: HashMap<String, String> =
319 [("facing".to_string(), "east".to_string())].into_iter().collect();
320
321 assert!(cond.matches(&north));
322 assert!(cond.matches(&south));
323 assert!(!cond.matches(&east));
324 }
325
326 #[test]
327 fn test_multipart_condition_pipe_values() {
328 let cond = MultipartCondition::Simple(
329 [("facing".to_string(), "north|south".to_string())]
330 .into_iter()
331 .collect(),
332 );
333
334 let north: HashMap<String, String> =
335 [("facing".to_string(), "north".to_string())].into_iter().collect();
336 let south: HashMap<String, String> =
337 [("facing".to_string(), "south".to_string())].into_iter().collect();
338 let east: HashMap<String, String> =
339 [("facing".to_string(), "east".to_string())].into_iter().collect();
340
341 assert!(cond.matches(&north));
342 assert!(cond.matches(&south));
343 assert!(!cond.matches(&east));
344 }
345
346 #[test]
347 fn test_build_property_string() {
348 let props: HashMap<String, String> = [
349 ("facing".to_string(), "north".to_string()),
350 ("half".to_string(), "bottom".to_string()),
351 ]
352 .into_iter()
353 .collect();
354
355 assert_eq!(build_property_string(&props), "facing=north,half=bottom");
356 }
357
358 #[test]
359 fn test_build_property_string_empty() {
360 let props: HashMap<String, String> = HashMap::new();
361 assert_eq!(build_property_string(&props), "");
362 }
363}