vintage_schematics/formats/
sponge.rs1use std::{fmt::Formatter, io::Read};
4
5use eyre::{Context, OptionExt};
6use serde::{
7 Deserialize, Deserializer, Serialize, Serializer,
8 de::{MapAccess, Visitor},
9};
10use tracing::warn;
11
12use crate::{
13 Map,
14 entity::parse_seven_bit_int,
15 formats::{
16 Loadable, ToInternal,
17 common::{FLATTENING_VERSION, MODERNISED_BLOCKS, MinecraftBlockData, MinecraftBlockEntity, Size},
18 internal::{Block, BlockCodes, Internal, MinecraftBlockCode},
19 read, should_reject,
20 },
21};
22
23#[derive(Debug, Clone, Deserialize, Serialize)]
33#[serde(rename_all = "PascalCase")]
34pub struct Sponge {
35 version: i32,
38
39 #[serde(flatten)]
41 size: Size,
42
43 blocks: SpongeBlocks,
45
46 data_version: i32,
48}
49
50#[derive(Debug, Clone, Deserialize, Serialize)]
52#[serde(rename_all = "PascalCase")]
53pub struct SpongeBlocks {
54 palette: SpongePalette,
59
60 data: Vec<u8>,
66
67 #[serde(default)]
69 entities: Vec<SpongeBlockEntity>,
70}
71
72#[derive(Debug, Clone)]
76struct SpongePalette(Vec<(String, i32)>);
77
78impl<'de> Deserialize<'de> for SpongePalette {
79 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80 where
81 D: Deserializer<'de>,
82 {
83 deserializer.deserialize_map(SpongePaletteVisitor)
84 }
85}
86
87impl Serialize for SpongePalette {
88 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
89 where
90 S: Serializer,
91 {
92 serializer.collect_map(self.0.iter().map(|(key, value)| (key, value)))
93 }
94}
95
96struct SpongePaletteVisitor;
98
99impl<'de> Visitor<'de> for SpongePaletteVisitor {
100 type Value = SpongePalette;
101
102 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { write!(formatter, "a compound value") }
103
104 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
105 where
106 A: MapAccess<'de>,
107 {
108 let mut out = Vec::with_capacity(map.size_hint().unwrap_or_default());
109 while let Some((key, value)) = map.next_entry()? {
110 out.push((key, value));
111 }
112 Ok(SpongePalette(out.into_iter().collect()))
113 }
114}
115
116#[derive(Debug, Clone, Deserialize, Serialize)]
117#[serde(rename_all = "PascalCase")]
118pub struct SpongeBlockEntity {
119 pos: (i32, i32, i32),
123
124 data: MinecraftBlockData,
126}
127
128#[derive(Debug, Clone, Deserialize)]
132#[serde(rename_all = "PascalCase")]
133pub struct SpongeWrapper {
134 schematic: Sponge,
136}
137
138impl Loadable for Sponge {
139 fn load(reader: impl Read, compressed: bool) -> eyre::Result<Self>
140 where
141 Self: Sized,
142 {
143 let schematic: SpongeWrapper = read(reader, compressed).context("failed to load schematic")?;
144 let mut schematic = schematic.schematic;
145
146 if schematic.version != 3 {
147 warn!("unsupported schematic version: {}", schematic.version);
148 }
149
150 schematic.modernise();
151
152 Ok(schematic)
153 }
154
155 fn modernise(&mut self) {
156 let replace = |current: &String| {
157 if let Some((current, properties)) = current.split_once('[') {
158 if let Some((_, new)) = MODERNISED_BLOCKS.iter().find(|(old, _)| *old == current) {
159 Some((String::from(current), format!("{new}[{properties}")))
160 } else {
161 None
162 }
163 } else if let Some((_, new)) = MODERNISED_BLOCKS.iter().find(|(old, _)| *old == current) {
164 Some((current.clone(), new.to_string()))
165 } else {
166 None
167 }
168 };
169
170 if self.data_version < FLATTENING_VERSION {
171 let replacements: Vec<_> = self.blocks.palette.0.iter().map(|(k, _)| k).filter_map(replace).collect();
172 for (old, new) in replacements {
173 if let Some(entry) = self.blocks.palette.0.iter_mut().find(|(k, _)| *k == old) {
174 entry.0 = new;
175 }
176 }
177 }
178 }
179}
180
181impl ToInternal for Sponge {
182 fn to_internal(self) -> eyre::Result<Internal> {
183 let mut block_bytes = self.blocks.data.into_iter();
185
186 let (ids, block_codes): (Vec<Option<u16>>, BlockCodes) = {
189 let mut ids = Vec::with_capacity(self.blocks.palette.0.len());
190 let mut block_codes = Vec::with_capacity(self.blocks.palette.0.len());
191
192 let mut current_id = 0;
193 for (name, _) in self.blocks.palette.0 {
194 let code = parse_sponge_name(&name).context("failed to parse block code")?;
195
196 if should_reject(&code) {
197 ids.push(None);
198 } else {
199 ids.push(Some(current_id));
200 block_codes.push(code);
201 current_id += 1;
202 }
203 }
204
205 (ids, BlockCodes::Minecraft(block_codes))
206 };
207
208 let size = self.size.blocks();
210
211 #[allow(clippy::cast_sign_loss)]
213 let mut blocks = Vec::with_capacity(size as usize);
214
215 for i in 0..size {
216 #[allow(clippy::cast_sign_loss)]
220 let block = parse_seven_bit_int(&mut block_bytes)? as usize;
221
222 let Some(id) = ids[block] else { continue };
224 let id = id as usize;
225
226 let position = self.size.index_position(i);
227
228 blocks.push(Block { id, position });
229 }
230
231 let tile_entities = self
233 .blocks
234 .entities
235 .into_iter()
236 .map(|e| MinecraftBlockEntity {
237 id: e.data.id,
238 x: e.pos.0,
239 y: e.pos.1,
240 z: e.pos.2,
241 components: e.data.components,
242 })
243 .collect();
244
245 Ok(Internal {
246 block_codes,
247 blocks,
248 tile_entities,
249 size: self.size.into(),
250 })
251 }
252}
253
254fn parse_sponge_name(name: &str) -> eyre::Result<MinecraftBlockCode> {
255 let mut new_properties = Map::new();
256
257 let name = if let Some((name, properties)) = name.split_once('[') {
258 #[cfg_attr(not(feature = "color-eyre"), allow(unused_mut))]
260 let mut parsed = properties.strip_suffix(']').ok_or_eyre("malformed block properties: missing terminating bracket");
261
262 #[cfg(feature = "color-eyre")]
263 {
264 use color_eyre::{Help, SectionExt};
265 parsed = parsed.with_section(|| properties.to_string().header("Properties:"));
266 }
267
268 let properties = parsed?;
269
270 for pair in properties.split(',') {
272 #[cfg_attr(not(feature = "color-eyre"), allow(unused_mut))]
273 let mut parsed = pair.split_once('=').ok_or_eyre("malformed block properties: missing equals delimiter");
274
275 #[cfg(feature = "color-eyre")]
276 {
277 use color_eyre::{Help, SectionExt};
278 parsed = parsed.with_section(|| pair.to_string().header("Property:"));
279 }
280
281 let (key, value) = parsed?;
282 new_properties.insert(key.to_string(), value.to_string());
283 }
284
285 name
286 } else {
287 name
289 };
290
291 Ok(MinecraftBlockCode {
292 name: name.to_string(),
293 properties: new_properties,
294 })
295}