vintage_schematics/formats/
vanilla.rs

1//! Vanilla "structure file" format support.
2
3use std::io::Read;
4
5use color_eyre::{eyre, eyre::Context};
6use serde::{Deserialize, Serialize};
7
8use crate::formats::{
9	Loadable, ToInternal,
10	common::{FLATTENING_VERSION, MODERNISED_BLOCKS, MinecraftBlockData, MinecraftBlockEntity},
11	internal::{Block, BlockCodes, Internal, MinecraftBlockCode},
12	read, should_reject,
13};
14
15/// The vanilla ["structure file" format](https://minecraft.wiki/w/Structure_file).
16#[derive(Debug, Clone, Deserialize, Serialize)]
17pub struct Vanilla {
18	pub size: (i32, i32, i32),
19	pub blocks: Vec<VanillaBlock>,
20	pub palette: Vec<MinecraftBlockCode>,
21
22	#[serde(rename = "DataVersion")]
23	pub data_version: i32,
24}
25
26#[derive(Debug, Clone, Deserialize, Serialize)]
27pub struct VanillaBlock {
28	pub state: u32,
29	pub pos: (i32, i32, i32),
30	#[serde(default)]
31	nbt: Option<MinecraftBlockData>,
32}
33
34impl Loadable for Vanilla {
35	fn load(reader: impl Read, compressed: bool) -> eyre::Result<Self> {
36		let mut schematic: Self = read(reader, compressed).context("failed to load schematic")?;
37		schematic.modernise();
38		Ok(schematic)
39	}
40
41	fn modernise(&mut self) {
42		if self.data_version < FLATTENING_VERSION {
43			for (old, new) in MODERNISED_BLOCKS {
44				if let Some(position) = self.palette.iter().position(|c| c.name == *old) {
45					self.palette[position].name = new.to_string();
46				}
47			}
48		}
49	}
50}
51
52impl ToInternal for Vanilla {
53	fn to_internal(self) -> eyre::Result<Internal> {
54		// - new block IDs, where `None` means the block should be rejected
55		// - converted block codes
56		let (rejected, block_codes): (Vec<bool>, BlockCodes) = {
57			let mut rejected = Vec::with_capacity(self.palette.len());
58			let mut block_codes = Vec::with_capacity(self.palette.len());
59
60			for block in self.palette {
61				let is_rejected = should_reject(&block);
62				rejected.push(is_rejected);
63				if !is_rejected {
64					block_codes.push(block);
65				}
66			}
67
68			(rejected, BlockCodes::Minecraft(block_codes))
69		};
70
71		let mut current_id = 0;
72		let mut ids = Vec::with_capacity(rejected.len());
73		for &is_rejected in &rejected {
74			ids.push(current_id);
75			if !is_rejected {
76				current_id += 1;
77			}
78		}
79
80		let tile_entities = {
81			let mut tile_entities = Vec::new();
82			for block in &self.blocks {
83				if let Some(nbt) = &block.nbt {
84					tile_entities.push(MinecraftBlockEntity {
85						id: nbt.id.clone(),
86						x: block.pos.0,
87						y: block.pos.1,
88						z: block.pos.2,
89						components: nbt.components.clone(),
90					});
91				}
92			}
93
94			tile_entities
95		};
96
97		let blocks = self
98			.blocks
99			.into_iter()
100			.map(|b| (b.state as usize, b))
101			.filter(|(id, _)| !rejected[*id])
102			.map(|(id, b)| {
103				Block {
104					// update id to account for rejected blocks
105					// if we've rejected 3 blocks, the ID should be reduced by 3
106					id: ids[id],
107					position: b.pos.into(),
108				}
109			})
110			.collect();
111
112		Ok(Internal {
113			block_codes,
114			blocks,
115			tile_entities,
116			size: self.size.into(),
117		})
118	}
119}