1use super::*;
2
3impl StructureTemplate {
4 pub(crate) fn load_vanilla(registry: &Registry, key: &Identifier) -> Result<Self, String> {
5 let Some(bytes) = vanilla_template_pools::vanilla_template_nbt_bytes(key) else {
6 return Err(format!("vanilla structure template {key} is not bundled"));
7 };
8 Self::load_gzip_nbt(registry, bytes, &key.to_string())
9 }
10
11 pub(super) fn load_gzip_nbt(
12 registry: &Registry,
13 bytes: &[u8],
14 context: &str,
15 ) -> Result<Self, String> {
16 let mut decoder = GzDecoder::new(bytes);
17 let mut data = Vec::new();
18 decoder
19 .read_to_end(&mut data)
20 .map_err(|err| format!("failed to decompress structure template {context}: {err}"))?;
21
22 let nbt = read_nbt(&mut Cursor::new(&data))
23 .map_err(|err| format!("failed to parse structure template {context}: {err}"))?;
24 let root = match nbt {
25 BorrowedNbt::Some(root) => root,
26 BorrowedNbt::None => {
27 return Err(format!("structure template {context} is empty"));
28 }
29 };
30 let compound = root.as_compound();
31
32 let size = Self::read_vec3(compound.list("size"), context, "size")?;
33 let palettes = Self::read_palettes(registry, &compound, context)?;
34 let blocks = compound
35 .list("blocks")
36 .and_then(|list| list.compounds())
37 .ok_or_else(|| format!("structure template {context} has non-compound blocks list"))?;
38
39 let mut loaded_palettes = Vec::with_capacity(palettes.len());
40 for palette in &palettes {
41 loaded_palettes.push(StructureTemplatePalette {
42 blocks: Self::read_blocks(registry, &blocks, palette, context)?,
43 });
44 }
45
46 let entities = Self::read_entities(registry, &compound, context)?;
47
48 Ok(Self {
49 size,
50 palettes: loaded_palettes,
51 entities,
52 })
53 }
54
55 pub(super) fn read_vec3(
56 list: Option<BorrowedNbtList<'_, '_>>,
57 context: &str,
58 field: &str,
59 ) -> Result<IVec3, String> {
60 let ints = list
61 .and_then(|list| list.ints())
62 .ok_or_else(|| format!("structure template {context} has non-int {field} list"))?;
63 if ints.len() < 3 {
64 return Err(format!(
65 "structure template {context} {field} list has fewer than 3 entries"
66 ));
67 }
68 Ok(IVec3::new(ints[0], ints[1], ints[2]))
69 }
70
71 pub(super) fn read_vec3d(
72 list: Option<BorrowedNbtList<'_, '_>>,
73 context: &str,
74 field: &str,
75 ) -> Result<DVec3, String> {
76 let doubles = list
77 .and_then(|list| list.doubles())
78 .ok_or_else(|| format!("structure template {context} has non-double {field} list"))?;
79 if doubles.len() < 3 {
80 return Err(format!(
81 "structure template {context} {field} list has fewer than 3 entries"
82 ));
83 }
84 Ok(DVec3::new(doubles[0], doubles[1], doubles[2]))
85 }
86
87 pub(super) fn read_palettes(
88 registry: &Registry,
89 compound: &BorrowedNbtCompound<'_, '_>,
90 context: &str,
91 ) -> Result<Vec<Vec<BlockStateId>>, String> {
92 if let Some(palette) = compound.list("palette").and_then(|list| list.compounds()) {
93 return Ok(vec![Self::read_palette(registry, &palette, context)?]);
94 }
95
96 let palettes = compound
97 .list("palettes")
98 .and_then(|list| list.lists())
99 .ok_or_else(|| {
100 format!("structure template {context} is missing palette or palettes")
101 })?;
102 if palettes.is_empty() {
103 return Err(format!(
104 "structure template {context} has empty palettes list"
105 ));
106 }
107
108 let mut result = Vec::with_capacity(palettes.len());
109 for palette in palettes {
110 let entries = palette.compounds().ok_or_else(|| {
111 format!("structure template {context} has non-compound palette entry")
112 })?;
113 result.push(Self::read_palette(registry, &entries, context)?);
114 }
115 Ok(result)
116 }
117
118 pub(super) fn read_palette(
119 registry: &Registry,
120 entries: &BorrowedNbtCompoundList<'_, '_>,
121 context: &str,
122 ) -> Result<Vec<BlockStateId>, String> {
123 let mut states = Vec::with_capacity(entries.len());
124 for entry in entries.clone() {
125 let Some(name) = entry.string("Name") else {
126 return Err(format!(
127 "structure template {context} has palette entry without Name"
128 ));
129 };
130 let name = Identifier::from_str(name.to_str().as_ref()).map_err(|err| {
131 format!("structure template {context} has invalid block identifier: {err}")
132 })?;
133 let mut properties = BTreeMap::new();
134 if let Some(props) = entry.compound("Properties") {
135 for (key, value) in props.iter() {
136 let Some(value) = value.string() else {
137 return Err(format!(
138 "structure template {context} has non-string property {} on {name}",
139 key.to_str()
140 ));
141 };
142 properties.insert(key.to_str().into_owned(), value.to_str().into_owned());
143 }
144 }
145 states.push(WorldgenStateResolver::block_state_from_data(
146 registry,
147 &BlockStateData { name, properties },
148 "structure template palette",
149 ));
150 }
151 Ok(states)
152 }
153
154 pub(super) fn read_blocks(
155 registry: &Registry,
156 blocks: &BorrowedNbtCompoundList<'_, '_>,
157 palette: &[BlockStateId],
158 context: &str,
159 ) -> Result<Vec<StructureBlockInfo>, String> {
160 let mut full_blocks = Vec::new();
161 let mut other_blocks = Vec::new();
162 let mut block_entities = Vec::new();
163
164 for block in blocks.clone() {
165 let pos = Self::read_vec3(block.list("pos"), context, "block pos")?;
166 let state_index = block
167 .int("state")
168 .ok_or_else(|| format!("structure template {context} block is missing state"))?;
169 if state_index < 0 {
170 return Err(format!(
171 "structure template {context} has negative palette state {state_index}"
172 ));
173 }
174 let state_index = usize::try_from(state_index).map_err(|_| {
175 format!("structure template {context} state index does not fit usize")
176 })?;
177 let Some(&state) = palette.get(state_index) else {
178 return Err(format!(
179 "structure template {context} state index {state_index} exceeds palette length {}",
180 palette.len()
181 ));
182 };
183 let nbt = block.compound("nbt").map(|nbt| nbt.to_owned());
184 let info = StructureBlockInfo {
185 pos: BlockPos::new(pos[0], pos[1], pos[2]),
186 state,
187 nbt,
188 };
189
190 if info.nbt.is_some() {
191 block_entities.push(info);
192 } else if Self::is_static_full_block(registry, state) {
193 full_blocks.push(info);
194 } else {
195 other_blocks.push(info);
196 }
197 }
198
199 Self::sort_block_infos(&mut full_blocks);
200 Self::sort_block_infos(&mut other_blocks);
201 Self::sort_block_infos(&mut block_entities);
202
203 full_blocks.extend(other_blocks);
204 full_blocks.extend(block_entities);
205 Ok(full_blocks)
206 }
207
208 pub(super) fn read_entities(
209 registry: &Registry,
210 compound: &BorrowedNbtCompound<'_, '_>,
211 context: &str,
212 ) -> Result<Vec<StructureEntityInfo>, String> {
213 let Some(entities) = compound.list("entities").and_then(|list| list.compounds()) else {
214 return Ok(Vec::new());
215 };
216
217 let mut result = Vec::with_capacity(entities.len());
218 for entity in entities.clone() {
219 let pos = Self::read_vec3d(entity.list("pos"), context, "entity pos")?;
220 let block_pos = Self::read_vec3(entity.list("blockPos"), context, "entity blockPos")?;
221 let entity_nbt = entity.compound("nbt").ok_or_else(|| {
222 format!("structure template {context} has entity entry without nbt")
223 })?;
224 let id = entity_nbt
225 .string("id")
226 .ok_or_else(|| format!("structure template {context} has entity nbt without id"))?;
227 let id = Identifier::from_str(id.to_str().as_ref()).map_err(|err| {
228 format!("structure template {context} has invalid entity identifier: {err}")
229 })?;
230 let entity_type = registry.entity_types.by_key(&id).ok_or_else(|| {
231 format!("structure template {context} references unknown entity type {id}")
232 })?;
233 let rotation = Self::read_entity_rotation(&entity_nbt);
234 let velocity = Self::read_optional_vec3d(&entity_nbt, "Motion");
235 let fall_distance = entity_nbt.double("fall_distance").unwrap_or(0.0);
236 let fire_freeze = EntityFireFreezeState::from_parts(
237 Self::read_optional_int(&entity_nbt, "Fire").unwrap_or(0),
238 Self::read_optional_int(&entity_nbt, "TicksFrozen").unwrap_or(0),
239 false,
240 false,
241 entity_nbt
242 .byte("HasVisualFire")
243 .is_some_and(|value| value != 0),
244 );
245 let on_ground = entity_nbt.byte("OnGround").is_some_and(|value| value != 0);
246 let save_data = EntityBaseSaveData {
247 air_supply: Self::read_optional_int(&entity_nbt, "Air")
248 .unwrap_or(DEFAULT_MAX_AIR_SUPPLY),
249 portal_cooldown: Self::read_optional_int(&entity_nbt, "PortalCooldown")
250 .unwrap_or(0),
251 no_gravity: entity_nbt.byte("NoGravity").is_some_and(|value| value != 0),
252 invulnerable: entity_nbt
253 .byte("Invulnerable")
254 .is_some_and(|value| value != 0),
255 custom_name: Self::read_custom_name(&entity_nbt),
256 custom_name_visible: entity_nbt
257 .byte("CustomNameVisible")
258 .is_some_and(|value| value != 0),
259 silent: entity_nbt.byte("Silent").is_some_and(|value| value != 0),
260 glowing: entity_nbt.byte("Glowing").is_some_and(|value| value != 0),
261 tags: Self::read_entity_tags(&entity_nbt),
262 custom_data: entity_nbt
263 .compound("data")
264 .map_or_else(NbtCompound::new, |compound| compound.to_owned()),
265 };
266 let mut nbt = entity_nbt.to_owned();
267 Self::strip_entity_base_fields(&mut nbt);
268
269 result.push(StructureEntityInfo {
270 pos,
271 block_pos: BlockPos::new(block_pos[0], block_pos[1], block_pos[2]),
272 entity_type,
273 rotation,
274 velocity,
275 fall_distance,
276 fire_freeze,
277 on_ground,
278 save_data,
279 nbt,
280 });
281 }
282
283 Ok(result)
284 }
285
286 pub(super) fn read_entity_rotation(nbt: &BorrowedNbtCompound<'_, '_>) -> (f32, f32) {
287 let Some(rotation) = nbt.list("Rotation").and_then(|list| list.floats()) else {
288 return (0.0, 0.0);
289 };
290 if rotation.len() < 2 {
291 return (0.0, 0.0);
292 }
293 (rotation[0], rotation[1])
294 }
295
296 pub(super) fn read_optional_vec3d(nbt: &BorrowedNbtCompound<'_, '_>, field: &str) -> DVec3 {
297 let Some(values) = nbt.list(field).and_then(|list| list.doubles()) else {
298 return DVec3::ZERO;
299 };
300 if values.len() < 3 {
301 return DVec3::ZERO;
302 }
303 DVec3::new(values[0], values[1], values[2])
304 }
305
306 pub(super) fn read_optional_int(nbt: &BorrowedNbtCompound<'_, '_>, field: &str) -> Option<i32> {
307 nbt.int(field)
308 .or_else(|| nbt.short(field).map(i32::from))
309 .or_else(|| nbt.byte(field).map(i32::from))
310 }
311
312 pub(super) fn read_custom_name(nbt: &BorrowedNbtCompound<'_, '_>) -> Option<TextComponent> {
313 let tag = nbt.get("CustomName")?;
314 TextComponent::from_nbt(&tag.to_owned())
315 }
316
317 pub(super) fn read_entity_tags(nbt: &BorrowedNbtCompound<'_, '_>) -> BTreeSet<String> {
318 nbt.list("Tags")
319 .and_then(|list| list.strings())
320 .map(|tags| {
321 tags.iter()
322 .take(MAX_ENTITY_TAGS)
323 .map(|tag| tag.to_str().into_owned())
324 .collect()
325 })
326 .unwrap_or_default()
327 }
328
329 pub(super) fn strip_entity_base_fields(nbt: &mut NbtCompound) {
330 for field in [
331 "id",
332 "Pos",
333 "Motion",
334 "Rotation",
335 "UUID",
336 "fall_distance",
337 "Fire",
338 "Air",
339 "OnGround",
340 "NoGravity",
341 "Invulnerable",
342 "PortalCooldown",
343 "CustomName",
344 "CustomNameVisible",
345 "Silent",
346 "Glowing",
347 "TicksFrozen",
348 "HasVisualFire",
349 "Tags",
350 "data",
351 ] {
352 let _ = nbt.remove(field);
353 }
354 }
355
356 pub(super) fn is_static_full_block(registry: &Registry, state: BlockStateId) -> bool {
357 let Some(block) = registry.blocks.by_state_id(state) else {
358 return false;
359 };
360 !block.config.dynamic_shape
361 && blocks::shapes::is_shape_full_block(
362 registry.blocks.get_static_collision_shape(state),
363 )
364 }
365
366 pub(super) fn sort_block_infos(blocks: &mut [StructureBlockInfo]) {
367 blocks.sort_by(|left, right| {
368 left.pos
369 .y()
370 .cmp(&right.pos.y())
371 .then(left.pos.x().cmp(&right.pos.x()))
372 .then(left.pos.z().cmp(&right.pos.z()))
373 });
374 }
375
376 pub(crate) const fn size(&self, rotation: Rotation) -> IVec3 {
377 rotation.rotate_size(self.size)
378 }
379
380 pub(crate) const fn zero_position_with_transform(
381 &self,
382 zero_pos: BlockPos,
383 rotation: Rotation,
384 ) -> BlockPos {
385 let x = self.size.x - 1;
386 let z = self.size.z - 1;
387 match rotation {
388 Rotation::None => zero_pos,
389 Rotation::Clockwise90 => zero_pos.offset(z, 0, 0),
390 Rotation::Clockwise180 => zero_pos.offset(x, 0, z),
391 Rotation::CounterClockwise90 => zero_pos.offset(0, 0, x),
392 }
393 }
394}