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