vintage_schematics/formats/
mod.rs

1//! The various formats supported by vintage-schematics.
2//!
3//! # Minecraft
4//!
5//! - [`Litematic`](litematic::Litematic)
6//! - [`Sponge`](sponge::Sponge)
7//! - [`Schematic`](schematic::Schematic)
8//! - [`Vanilla`](vanilla::Vanilla)
9//!
10//! # Vintage Story
11//!
12//! - [`WorldEdit`](world_edit::WorldEdit)
13//!
14//! # Other
15//!
16//! - [`Internal`] - the intermediary format used to represent Minecraft schematics internally.
17
18use std::io::BufReader;
19
20use color_eyre::{eyre, eyre::Context};
21use flate2::read::GzDecoder;
22use serde::Deserialize;
23use strum::IntoEnumIterator;
24
25use crate::{
26	Set,
27	convert::mods::Mod,
28	formats::internal::{Internal, MinecraftBlockCode},
29};
30
31pub mod common;
32pub mod internal;
33pub mod litematic;
34pub mod schematic;
35pub mod sponge;
36pub mod vanilla;
37pub mod world_edit;
38
39/// Conversion settings.
40#[derive(Debug, Clone)]
41pub struct Settings {
42	/// Replace missing blocks with leaves.
43	pub replace_missing: bool,
44
45	/// The set of mods to use when converting blocks.
46	pub mods: Set<Mod>,
47}
48
49impl Default for Settings {
50	fn default() -> Self {
51		Self {
52			replace_missing: true,
53			mods: Set::new(),
54		}
55	}
56}
57
58impl Settings {
59	#[must_use]
60	pub fn all() -> Self {
61		Self {
62			replace_missing: true,
63			mods: Mod::iter().collect(),
64		}
65	}
66}
67
68fn read<'a, T: Deserialize<'a>>(reader: impl std::io::Read, compressed: bool) -> eyre::Result<T> {
69	let schematic = if compressed {
70		mininbt::from_reader(GzDecoder::new(reader))
71	} else {
72		mininbt::from_reader(BufReader::new(reader))
73	};
74
75	schematic.context("failed to parse NBT")
76}
77
78pub trait Loadable {
79	/// Loads a schematic from the given reader.
80	/// If `compressed` is set, the data will be decompressed as GZIP.
81	/// Otherwise, it will be read as-is.
82	///
83	/// # Errors
84	///
85	/// Returns an error if the schematic couldn't be loaded.
86	///
87	/// This may occur if the schematic uses unsupported features or is invalid, or when an IO error is encountered.
88	fn load(reader: impl std::io::Read, compressed: bool) -> eyre::Result<Self>
89	where
90		Self: Sized;
91
92	/// Replaces old (pre-flattening) Minecraft block IDs with their modern equivalents.
93	fn modernise(&mut self);
94}
95
96pub trait Saveable {
97	/// Save the given [`Internal`] to a schematic.
98	fn save(internal: &mut Internal, settings: &Settings) -> Self
99	where
100		Self: Sized;
101
102	#[allow(dead_code)]
103	fn is_vintage_story(&self) -> bool;
104}
105
106pub trait ToInternal {
107	/// Converts the schematic to the [`Internal`] format.
108	///
109	/// # Errors
110	///
111	/// Returns an error if the schematic could not be converted to the internal format.
112	/// This may happen if the schematic contains invalid blocks or blocks with invalid properties.
113	fn to_internal(self) -> eyre::Result<Internal>;
114}
115
116/// Returns `true` if the given Minecraft block should be omitted when converting to the [`Internal`] format.
117fn should_reject(block: &MinecraftBlockCode) -> bool {
118	matches!(block.name.as_str(), "minecraft:air" | "minecraft:cave_air" | "minecraft:void_air")
119		// minecraft stores doors as upper and lower halves
120		// we only want the lower half
121		// TODO: better door pattern?
122		|| block.name.ends_with("_door") && block.properties.get("half").is_some_and(|h| h == "upper")
123		|| (block.name == "minecraft:chest" || block.name == "minecraft:trapped_chest") && block.properties.get("type").is_some_and(|t| t == "left")
124}