vintage_schematics/formats/
sponge.rs1use std::io::Read;
4
5use color_eyre::{
6 Help, SectionExt, eyre,
7 eyre::{Context, OptionExt},
8};
9use indexmap::IndexMap;
10use serde::{Deserialize, Serialize};
11use tracing::warn;
12
13use crate::{
14 Map,
15 entity::parse_seven_bit_int,
16 formats::{
17 Loadable, ToInternal,
18 common::{FLATTENING_VERSION, MODERNISED_BLOCKS, MinecraftBlockData, MinecraftBlockEntity, Size},
19 internal::{Block, BlockCodes, Internal, MinecraftBlockCode},
20 read, should_reject,
21 },
22};
23
24#[derive(Debug, Clone, Deserialize, Serialize)]
34#[serde(rename_all = "PascalCase")]
35pub struct Sponge {
36 version: i32,
39
40 #[serde(flatten)]
42 size: Size,
43
44 blocks: SpongeBlocks,
46
47 data_version: i32,
49}
50
51#[derive(Debug, Clone, Deserialize, Serialize)]
53#[serde(rename_all = "PascalCase")]
54pub struct SpongeBlocks {
55 palette: IndexMap<String, i32>,
60
61 data: Vec<u8>,
67
68 #[serde(default)]
70 entities: Vec<SpongeBlockEntity>,
71}
72
73#[derive(Debug, Clone, Deserialize, Serialize)]
74#[serde(rename_all = "PascalCase")]
75pub struct SpongeBlockEntity {
76 pos: (i32, i32, i32),
80
81 data: MinecraftBlockData,
83}
84
85#[derive(Debug, Clone, Deserialize)]
89#[serde(rename_all = "PascalCase")]
90pub struct SpongeWrapper {
91 schematic: Sponge,
93}
94
95impl Loadable for Sponge {
96 fn load(reader: impl Read, compressed: bool) -> eyre::Result<Self>
97 where
98 Self: Sized,
99 {
100 let schematic: SpongeWrapper = read(reader, compressed).context("failed to load schematic")?;
101 let mut schematic = schematic.schematic;
102
103 if schematic.version != 3 {
104 warn!("unsupported schematic version: {}", schematic.version);
105 }
106
107 schematic.modernise();
108
109 Ok(schematic)
110 }
111
112 fn modernise(&mut self) {
113 let replace = |current: &String| {
114 if let Some((current, properties)) = current.split_once('[') {
115 if let Some((_, new)) = MODERNISED_BLOCKS.iter().find(|(old, _)| *old == current) {
116 Some((String::from(current), format!("{new}[{properties}")))
117 } else {
118 None
119 }
120 } else if let Some((_, new)) = MODERNISED_BLOCKS.iter().find(|(old, _)| *old == current) {
121 Some((current.clone(), new.to_string()))
122 } else {
123 None
124 }
125 };
126
127 if self.data_version < FLATTENING_VERSION {
128 let replacements: Vec<_> = self.blocks.palette.keys().filter_map(replace).collect();
129 for (old, new) in replacements {
130 if let Some(position) = self.blocks.palette.get_index_of(&old) {
131 let id = self.blocks.palette.shift_remove(&old).expect("replacement should exist");
132 self.blocks.palette.shift_insert(position, new, id);
133 }
134 }
135 }
136 }
137}
138
139impl ToInternal for Sponge {
140 fn to_internal(self) -> eyre::Result<Internal> {
141 let mut block_bytes = self.blocks.data.into_iter();
143
144 let (ids, block_codes): (Vec<Option<u16>>, BlockCodes) = {
147 let mut ids = Vec::with_capacity(self.blocks.palette.len());
148 let mut block_codes = Vec::with_capacity(self.blocks.palette.len());
149
150 let mut current_id = 0;
151 for (name, _) in self.blocks.palette {
152 let code = parse_sponge_name(&name).context("failed to parse block code")?;
153
154 if should_reject(&code) {
155 ids.push(None);
156 } else {
157 ids.push(Some(current_id));
158 block_codes.push(code);
159 current_id += 1;
160 }
161 }
162
163 (ids, BlockCodes::Minecraft(block_codes))
164 };
165
166 let size = self.size.blocks();
168
169 #[allow(clippy::cast_sign_loss)]
171 let mut blocks = Vec::with_capacity(size as usize);
172
173 for i in 0..size {
174 #[allow(clippy::cast_sign_loss)]
178 let block = parse_seven_bit_int(&mut block_bytes)? as usize;
179
180 let Some(id) = ids[block] else { continue };
182 let id = id as usize;
183
184 let position = self.size.index_position(i);
185
186 blocks.push(Block { id, position });
187 }
188
189 let tile_entities = self
191 .blocks
192 .entities
193 .into_iter()
194 .map(|e| MinecraftBlockEntity {
195 id: e.data.id,
196 x: e.pos.0,
197 y: e.pos.1,
198 z: e.pos.2,
199 components: e.data.components,
200 })
201 .collect();
202
203 Ok(Internal {
204 block_codes,
205 blocks,
206 tile_entities,
207 size: self.size.into(),
208 })
209 }
210}
211
212fn parse_sponge_name(name: &str) -> eyre::Result<MinecraftBlockCode> {
213 let mut new_properties = Map::new();
214
215 let name = if let Some((name, properties)) = name.split_once('[') {
216 let properties = properties
218 .strip_suffix(']')
219 .ok_or_eyre("malformed block properties: missing terminating bracket")
220 .with_section(|| properties.to_string().header("Properties:"))?;
221
222 for pair in properties.split(',') {
224 let (key, value) = pair
225 .split_once('=')
226 .ok_or_eyre("malformed block properties: missing equals delimiter")
227 .with_section(|| pair.to_string().header("Property:"))?;
228 new_properties.insert(key.to_string(), value.to_string());
229 }
230
231 name
232 } else {
233 name
235 };
236
237 Ok(MinecraftBlockCode {
238 name: name.to_string(),
239 properties: new_properties,
240 })
241}