Skip to main content

steel_core/worldgen/template/
loading.rs

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}