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