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