vintage_schematics/
lib.rs1use std::{
10 fs::File,
11 io::{BufReader, Read},
12 path::Path,
13};
14
15use cfg_if::cfg_if;
16use color_eyre::{
17 eyre,
18 eyre::{Context, bail},
19};
20use formats::{common::Xyz, litematic::Litematic, world_edit::WorldEdit};
21
22use crate::formats::{
23 Loadable, Saveable, Settings, ToInternal,
24 internal::{BlockCodes, Internal},
25 schematic::Schematic,
26 sponge::Sponge,
27 vanilla::Vanilla,
28};
29
30cfg_if! {
31 if #[cfg(feature = "regex-full")] {
32 pub(crate) use regex::Regex;
33 } else if #[cfg(feature = "regex-lite")] {
34 pub(crate) use regex_lite::Regex;
35 } else {
36 compile_error!("no regex backend! please enable the `regex-full` or `regex-lite` feature.");
37 }
38}
39
40pub mod ascii85;
41pub mod convert;
42pub mod entity;
43pub mod formats;
44#[cfg(test)]
45mod tests;
46
47cfg_if! {
48 if #[cfg(feature = "hashmap")] {
49 pub type Map<K, V> = std::collections::HashMap<K, V>;
51 pub type Set<V> = std::collections::HashSet<V>;
53 } else {
54 pub type Map<K, V> = std::collections::BTreeMap<K, V>;
56 pub type Set<V> = std::collections::BTreeSet<V>;
58 }
59}
60
61pub fn convert_file(path: &Path, settings: &Settings) -> eyre::Result<WorldEdit> {
70 let file = BufReader::new(File::open(path).context("failed to open schematic file")?);
71 let ext = path.extension().map(|e| e.to_string_lossy().to_ascii_lowercase());
72 let function = match ext.as_deref() {
73 Some("litematic") => convert::<Litematic>,
74 Some("nbt") => convert::<Vanilla>,
75 Some("schematic") => convert::<Schematic>,
76 Some("schem") => convert::<Sponge>,
77
78 Some(ext) => bail!("unrecognised file extension '{ext}'"),
79 None => bail!("no file extension"),
80 };
81
82 function(file, settings)
83}
84
85pub fn convert<T>(reader: impl Read, settings: &Settings) -> eyre::Result<WorldEdit>
92where
93 T: Loadable + ToInternal,
94{
95 let schematic = T::load(reader, true).context("failed to load schematic file")?;
96 let mut internal = schematic.to_internal().context("failed to convert schematic to internal format")?;
97
98 Ok(WorldEdit::save(&mut internal, settings))
102}
103
104pub fn convert_uncompressed<T>(reader: impl Read, settings: &Settings) -> eyre::Result<WorldEdit>
111where
112 T: Loadable + ToInternal,
113{
114 let schematic = T::load(reader, false).context("failed to load schematic file")?;
115 let mut internal = schematic.to_internal().context("failed to convert schematic to internal format")?;
116
117 Ok(WorldEdit::save(&mut internal, settings))
119}
120
121#[allow(dead_code)]
122fn shitty_renderer(internal: &Internal) {
123 let Xyz {
124 x: width,
125 y: layers,
126 z: height,
127 } = internal.size;
128
129 let BlockCodes::Minecraft(block_codes) = &internal.block_codes else {
130 println!("can only render minecraft format!");
131 return;
132 };
133
134 for y in 0..layers {
135 println!(" === BEGIN LAYER {y} ===");
136 for z in 0..height {
137 for x in 0..width {
138 let Some(block) = internal.blocks.iter().find(|b| b.position.x == x && b.position.y == y && b.position.z == z)
139 else {
140 print!(" ");
141 continue;
142 };
143
144 let symbol = block_codes.get(block.id).map_or('!', |code| match code.name.as_str() {
145 "minecraft:air" => ' ',
146 "minecraft:red_mushroom_block" => 'M',
147 "minecraft:mushroom_stem" => 'S',
148 "minecraft:short_grass" => 'g',
149 "minecraft:grass_block" => 'G',
150 "minecraft:cobblestone" => 'C',
151 "minecraft:stone_bricks" => 'B',
152 "minecraft:oak_log" => 'O',
153 "minecraft:spruce_log" => '$',
154 "minecraft:dirt" => 'D',
155 "minecraft:farmland" => 'F',
156 "minecraft:water" => 'W',
157 _ => '?',
158 });
159 print!("{symbol}");
160 }
161 println!();
162 }
163 println!(" === END LAYER {y} ===");
164 println!();
165 }
166}