1use super::*;
2use steel_math::{DEGREE_90, DEGREE_180, DEGREE_270, wrap_degrees};
3use steel_utils::nbt::NbtNumeric;
4
5impl StructureTemplate {
6 pub(crate) fn bounding_box(&self, pos: BlockPos, rotation: Rotation) -> BoundingBox {
7 rotation.get_bounding_box(pos.0, self.size)
8 }
9
10 pub(crate) fn bounding_box_with_transform(
11 &self,
12 position: BlockPos,
13 rotation: Rotation,
14 mirror: StructureMirror,
15 pivot: BlockPos,
16 ) -> BoundingBox {
17 let corner1 = Self::calculate_relative_position(BlockPos::ZERO, mirror, rotation, pivot);
18 let corner2 =
19 Self::calculate_relative_position(BlockPos(self.size - 1), mirror, rotation, pivot);
20 BoundingBox::new(position.0 + corner1.0, position.0 + corner2.0)
21 }
22
23 pub(crate) const fn calculate_relative_position(
24 pos: BlockPos,
25 mirror: StructureMirror,
26 rotation: Rotation,
27 pivot: BlockPos,
28 ) -> BlockPos {
29 let (x, z) = match mirror {
30 StructureMirror::None => (pos.x(), pos.z()),
31 StructureMirror::FrontBack => (-pos.x(), pos.z()),
32 StructureMirror::LeftRight => (pos.x(), -pos.z()),
33 };
34 let pos = rotation.transform_pos(IVec3::new(x, pos.y(), z), pivot.0);
35 BlockPos(pos)
36 }
37
38 pub(super) fn transform_entity_position(
39 pos: DVec3,
40 mirror: StructureMirror,
41 rotation: Rotation,
42 pivot: BlockPos,
43 ) -> DVec3 {
44 let mut x = pos.x;
45 let y = pos.y;
46 let mut z = pos.z;
47 match mirror {
48 StructureMirror::LeftRight => z = 1.0 - z,
49 StructureMirror::FrontBack => x = 1.0 - x,
50 StructureMirror::None => {}
51 }
52
53 let pivot_x = f64::from(pivot.x());
54 let pivot_z = f64::from(pivot.z());
55 match rotation {
56 Rotation::CounterClockwise90 => {
57 DVec3::new(pivot_x - pivot_z + z, y, pivot_x + pivot_z + 1.0 - x)
58 }
59 Rotation::Clockwise90 => {
60 DVec3::new(pivot_x + pivot_z + 1.0 - z, y, pivot_z - pivot_x + x)
61 }
62 Rotation::Clockwise180 => {
63 DVec3::new(pivot_x + pivot_x + 1.0 - x, y, pivot_z + pivot_z + 1.0 - z)
64 }
65 Rotation::None => DVec3::new(x, y, z),
66 }
67 }
68
69 pub(super) fn transform_entity_rotation(
70 (yaw, pitch): (f32, f32),
71 mirror: StructureMirror,
72 rotation: Rotation,
73 ) -> (f32, f32) {
74 let yaw = wrap_degrees(yaw);
75 let rotated = match rotation {
76 Rotation::Clockwise180 => yaw + DEGREE_180,
77 Rotation::CounterClockwise90 => yaw + DEGREE_270,
78 Rotation::Clockwise90 => yaw + DEGREE_90,
79 Rotation::None => yaw,
80 };
81 let mirrored = match mirror {
82 StructureMirror::FrontBack => -yaw,
83 StructureMirror::LeftRight => DEGREE_180 - yaw,
84 StructureMirror::None => yaw,
85 };
86 (rotated + mirrored - yaw, pitch)
87 }
88
89 pub(super) fn transform_entity_additional_nbt(
90 nbt: &mut NbtCompound,
91 mirror: StructureMirror,
92 rotation: Rotation,
93 ) {
94 let Some(facing) = Self::entity_facing(nbt) else {
95 return;
96 };
97 let facing = Self::mirror_direction(rotation.rotate(facing), mirror);
98 let _ = nbt.remove("Facing");
99 nbt.insert("Facing", Self::entity_facing_value(facing));
100 }
101
102 pub(super) fn entity_facing(nbt: &NbtCompound) -> Option<Direction> {
103 nbt.get("Facing")
104 .and_then(NbtNumeric::codec_i8)
105 .map(|value| Direction::from_3d_data_value(i32::from(value)))
106 }
107
108 pub(super) const fn entity_facing_value(direction: Direction) -> i8 {
109 match direction {
110 Direction::Down => 0,
111 Direction::Up => 1,
112 Direction::North => 2,
113 Direction::South => 3,
114 Direction::West => 4,
115 Direction::East => 5,
116 }
117 }
118
119 #[expect(
120 clippy::too_many_arguments,
121 reason = "structure placement call mirrors vanilla template placement context"
122 )]
123 #[expect(
124 clippy::too_many_lines,
125 reason = "template placement follows vanilla's single-pass block placement flow"
126 )]
127 pub(crate) fn place_in_world(
128 &self,
129 region: &mut WorldGenRegion<'_>,
130 registry: &Registry,
131 position: BlockPos,
132 reference_pos: BlockPos,
133 settings: &StructurePlaceSettings<'_>,
134 random: &mut WorldgenRandom,
135 flags: UpdateFlags,
136 ) -> bool {
137 let Some(palette) = self.palette(settings, position, random) else {
138 return false;
139 };
140 if (palette.blocks.is_empty() && self.entities.is_empty())
141 || [self.size.x, self.size.y, self.size.z]
142 .iter()
143 .any(|&axis| axis < 1)
144 {
145 return false;
146 }
147 let mut original_blocks = Vec::with_capacity(palette.blocks.len());
148 let mut processed_blocks = Vec::with_capacity(palette.blocks.len());
149
150 Self::palette_blocks_for_placement(
151 &palette.blocks,
152 position,
153 settings,
154 |block, world_pos| {
155 let original = ProcessedBlockInfo {
156 template_pos: block.pos,
157 world_pos: block.pos,
158 state: block.state,
159 nbt: block.nbt.clone(),
160 };
161 let processed = ProcessedBlockInfo {
162 template_pos: block.pos,
163 world_pos,
164 state: block.state,
165 nbt: block.nbt.clone(),
166 };
167
168 if let Some(processed) = Self::process_block(
169 region,
170 registry,
171 &original,
172 processed,
173 settings,
174 reference_pos,
175 random,
176 ) {
177 original_blocks.push(original);
178 processed_blocks.push(processed);
179 }
180 },
181 );
182
183 let processed_blocks = Self::finalize_processing(
184 region,
185 registry,
186 position,
187 reference_pos,
188 settings,
189 &original_blocks,
190 processed_blocks,
191 random,
192 );
193
194 let mut placed_any = false;
195 let mut placed_positions = Vec::with_capacity(processed_blocks.len());
196 let mut min_x = i32::MAX;
197 let mut min_y = i32::MAX;
198 let mut min_z = i32::MAX;
199 let mut max_x = i32::MIN;
200 let mut max_y = i32::MIN;
201 let mut max_z = i32::MIN;
202 let mut to_fill = Vec::new();
203 let mut locked_fluids = Vec::new();
204 let apply_waterlogging = settings.liquid_settings == LiquidSettingsData::ApplyWaterlogging;
205 for processed in processed_blocks {
206 if !settings.bounding_box.contains_blockpos(processed.world_pos) {
209 continue;
210 }
211
212 let final_state = Self::transform_state(
213 registry,
214 processed.state,
215 settings.mirror,
216 settings.rotation,
217 );
218 let previous_fluid_state =
219 apply_waterlogging.then(|| Self::fluid_state_at(region, processed.world_pos));
220 if processed.nbt.is_some() {
221 let barrier_flags = UpdateFlags::UPDATE_INVISIBLE
222 | UpdateFlags::UPDATE_KNOWN_SHAPE
223 | UpdateFlags::UPDATE_SUPPRESS_DROPS
224 | UpdateFlags::UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS
225 | UpdateFlags::UPDATE_SKIP_ON_PLACE;
226 let _ = region.set_block_state(
227 processed.world_pos,
228 vanilla_blocks::BARRIER.default_state(),
229 barrier_flags,
230 );
231 }
232
233 if !region.set_block_state(processed.world_pos, final_state, flags) {
234 continue;
235 }
236 placed_any = true;
237 min_x = min_x.min(processed.world_pos.x());
238 min_y = min_y.min(processed.world_pos.y());
239 min_z = min_z.min(processed.world_pos.z());
240 max_x = max_x.max(processed.world_pos.x());
241 max_y = max_y.max(processed.world_pos.y());
242 max_z = max_z.max(processed.world_pos.z());
243 placed_positions.push(processed.world_pos);
244
245 if let Some(mut nbt) = processed.nbt {
246 let block_entity_type =
247 Self::block_entity_type_for_nbt_or_state(registry, final_state, &nbt);
248 if Self::should_reseed_template_loot(block_entity_type, &nbt) {
249 nbt.insert("LootTableSeed", NbtTag::Long(random.next_i64()));
250 }
251 Self::place_block_entity(
252 region,
253 processed.world_pos,
254 final_state,
255 block_entity_type,
256 nbt,
257 );
258 } else {
259 let _ = region.remove_block_entity(processed.world_pos);
260 }
261
262 if let Some(previous_fluid_state) = previous_fluid_state {
263 if Self::fluid_state_for_block(final_state).is_source() {
264 locked_fluids.push(processed.world_pos);
265 } else if Self::is_liquid_block_container(final_state) {
266 let _ = Self::place_liquid(
267 region,
268 processed.world_pos,
269 final_state,
270 previous_fluid_state,
271 );
272 if !previous_fluid_state.is_source() {
273 to_fill.push(processed.world_pos);
274 }
275 }
276 }
277 }
278
279 Self::fill_neighbor_source_liquids(region, &mut to_fill, &locked_fluids);
280
281 if placed_any && !flags.contains(UpdateFlags::UPDATE_KNOWN_SHAPE) {
282 Self::update_shape_at_edge(
283 region,
284 flags,
285 &placed_positions,
286 BlockPos::new(min_x, min_y, min_z),
287 BlockPos::new(max_x, max_y, max_z),
288 );
289
290 let placed_update_flags =
291 (flags & !UpdateFlags::UPDATE_NEIGHBORS) | UpdateFlags::UPDATE_KNOWN_SHAPE;
292 for pos in placed_positions {
293 let state = region.block_state(pos);
294 let new_state = Self::update_from_neighbor_shapes(region, state, pos);
295 if state != new_state {
296 let _ = region.set_block_state(pos, new_state, placed_update_flags);
297 }
298 }
299 }
300
301 self.place_entities(region, position, settings);
302
303 true
304 }
305
306 pub(super) fn place_entities(
307 &self,
308 region: &mut WorldGenRegion<'_>,
309 position: BlockPos,
310 settings: &StructurePlaceSettings<'_>,
311 ) {
312 if self.entities.is_empty() {
313 return;
314 }
315
316 let world_offset = DVec3::new(
317 f64::from(position.x()),
318 f64::from(position.y()),
319 f64::from(position.z()),
320 );
321 for entity in &self.entities {
322 let block_pos = Self::calculate_relative_position(
323 entity.block_pos,
324 settings.mirror,
325 settings.rotation,
326 settings.rotation_pivot,
327 )
328 .offset(position.x(), position.y(), position.z());
329 if !settings.bounding_box.contains_blockpos(block_pos) {
330 continue;
331 }
332
333 let pos = Self::transform_entity_position(
334 entity.pos,
335 settings.mirror,
336 settings.rotation,
337 settings.rotation_pivot,
338 ) + world_offset;
339 let rotation = Self::transform_entity_rotation(
340 entity.rotation,
341 settings.mirror,
342 settings.rotation,
343 );
344 let mut nbt = entity.nbt.clone();
345 Self::transform_entity_additional_nbt(&mut nbt, settings.mirror, settings.rotation);
346
347 let mut nbt_bytes = Vec::new();
348 nbt.write(&mut nbt_bytes);
349 let Ok(nbt) = read_borrowed_compound(&mut Cursor::new(&nbt_bytes)) else {
350 log::warn!(
351 "failed to reborrow owned NBT for structure template entity {}",
352 entity.entity_type.key
353 );
354 continue;
355 };
356
357 let Some(runtime_entity) = ENTITIES.create_and_load(
358 EntityLoadRequest {
359 entity_type: entity.entity_type,
360 position: pos,
361 uuid: Uuid::new_v4(),
362 velocity: entity.velocity,
363 rotation,
364 fall_distance: entity.fall_distance,
365 fire_freeze: entity.fire_freeze,
366 on_ground: entity.on_ground,
367 save_data: entity.save_data.clone(),
368 world: region.weak_world(),
369 },
370 &nbt,
371 ) else {
372 continue;
373 };
374 let _ = region.add_fresh_entity(runtime_entity);
375 }
376 }
377
378 pub(crate) fn replace_jigsaw_final_states(
379 &self,
380 region: &mut WorldGenRegion<'_>,
381 registry: &Registry,
382 position: BlockPos,
383 settings: &StructurePlaceSettings<'_>,
384 random: &mut WorldgenRandom,
385 ) {
386 let Some(palette) = self.palette(settings, position, random) else {
387 return;
388 };
389
390 for block in &palette.blocks {
391 if Self::block_for_state(registry, block.state) != &vanilla_blocks::JIGSAW {
392 continue;
393 }
394 let world_pos = Self::transformed_position(position, block.pos, settings);
395 if !settings.bounding_box.contains_blockpos(world_pos) {
396 continue;
397 }
398 let Some(nbt) = block.nbt.as_ref() else {
399 continue;
400 };
401 let final_state = nbt
402 .string("final_state")
403 .map_or_else(|| "minecraft:air".into(), |value| value.to_str());
404 let state = Self::parse_block_state_string(registry, final_state.as_ref())
405 .unwrap_or_else(|| vanilla_blocks::AIR.default_state());
406 let _ = region.set_block_state(world_pos, state, UpdateFlags::UPDATE_ALL);
407 }
408 }
409
410 pub(crate) fn data_markers(
411 &self,
412 registry: &Registry,
413 position: BlockPos,
414 settings: &StructurePlaceSettings<'_>,
415 random: &mut WorldgenRandom,
416 ) -> Vec<StructureDataMarker> {
417 let Some(palette) = self.palette(settings, position, random) else {
418 return Vec::new();
419 };
420
421 let mut markers = Vec::new();
422 for block in &palette.blocks {
423 if Self::block_for_state(registry, block.state) != &vanilla_blocks::STRUCTURE_BLOCK {
424 continue;
425 }
426 let world_pos = Self::transformed_position(position, block.pos, settings);
427 if !settings.bounding_box.contains_blockpos(world_pos) {
428 continue;
429 }
430 let Some(nbt) = block.nbt.as_ref() else {
431 continue;
432 };
433 if nbt
434 .string("mode")
435 .is_none_or(|mode| mode.to_str().as_ref() != "DATA")
436 {
437 continue;
438 }
439 let metadata = nbt
440 .string("metadata")
441 .map_or_default(|metadata| metadata.to_str().into_owned());
442 markers.push(StructureDataMarker {
443 metadata,
444 pos: world_pos,
445 });
446 }
447 markers
448 }
449
450 pub(super) fn update_shape_at_edge(
451 region: &WorldGenRegion<'_>,
452 flags: UpdateFlags,
453 placed_positions: &[BlockPos],
454 min: BlockPos,
455 max: BlockPos,
456 ) {
457 let filled = placed_positions
458 .iter()
459 .map(|pos| (pos.x() - min.x(), pos.y() - min.y(), pos.z() - min.z()))
460 .collect::<BTreeSet<_>>();
461 let x_size = max.x() - min.x() + 1;
462 let y_size = max.y() - min.y() + 1;
463 let z_size = max.z() - min.z() + 1;
464 let edge_flags = flags & !UpdateFlags::UPDATE_NEIGHBORS;
465
466 Self::for_all_shape_faces(
467 x_size,
468 y_size,
469 z_size,
470 |x, y, z| filled.contains(&(x, y, z)),
471 |direction, x, y, z| {
472 let pos = min.offset(x, y, z);
473 let neighbor_pos = pos.relative(direction);
474 let state = region.block_state(pos);
475 let neighbor_state = region.block_state(neighbor_pos);
476 let new_state = BLOCK_BEHAVIORS
477 .get_behavior(state.get_block())
478 .update_shape(state, region, pos, direction, neighbor_pos, neighbor_state);
479 if state != new_state {
480 let _ = region.set_block_state(pos, new_state, edge_flags);
481 }
482
483 let new_neighbor_state = BLOCK_BEHAVIORS
484 .get_behavior(neighbor_state.get_block())
485 .update_shape(
486 neighbor_state,
487 region,
488 neighbor_pos,
489 direction.opposite(),
490 pos,
491 new_state,
492 );
493 if neighbor_state != new_neighbor_state {
494 let _ = region.set_block_state(neighbor_pos, new_neighbor_state, edge_flags);
495 }
496 },
497 );
498 }
499
500 pub(super) fn update_from_neighbor_shapes(
501 region: &WorldGenRegion<'_>,
502 state: BlockStateId,
503 pos: BlockPos,
504 ) -> BlockStateId {
505 let mut updated = state;
506 for direction in Direction::UPDATE_SHAPE_ORDER {
507 let neighbor_pos = pos.relative(direction);
508 let neighbor_state = region.block_state(neighbor_pos);
509 updated = BLOCK_BEHAVIORS
510 .get_behavior(updated.get_block())
511 .update_shape(
512 updated,
513 region,
514 pos,
515 direction,
516 neighbor_pos,
517 neighbor_state,
518 );
519 }
520 updated
521 }
522
523 pub(super) fn fill_neighbor_source_liquids(
524 region: &WorldGenRegion<'_>,
525 to_fill: &mut Vec<BlockPos>,
526 locked_fluids: &[BlockPos],
527 ) {
528 const DIRECTIONS: [Direction; 5] = [
529 Direction::Up,
530 Direction::North,
531 Direction::East,
532 Direction::South,
533 Direction::West,
534 ];
535
536 let mut filled = true;
537 while filled && !to_fill.is_empty() {
538 filled = false;
539 let mut index = 0;
540 while index < to_fill.len() {
541 let pos = to_fill[index];
542 let mut to_place = Self::fluid_state_at(region, pos);
543 for direction in DIRECTIONS {
544 if to_place.is_source() {
545 break;
546 }
547 let neighbor_pos = pos.relative(direction);
548 let neighbor = Self::fluid_state_at(region, neighbor_pos);
549 if neighbor.is_source() && !locked_fluids.contains(&neighbor_pos) {
550 to_place = neighbor;
551 }
552 }
553
554 if to_place.is_source() {
555 let state = region.block_state(pos);
556 if Self::is_liquid_block_container(state) {
557 let _ = Self::place_liquid(region, pos, state, to_place);
558 filled = true;
559 to_fill.remove(index);
560 continue;
561 }
562 }
563
564 index += 1;
565 }
566 }
567 }
568
569 pub(super) fn fluid_state_at(region: &WorldGenRegion<'_>, pos: BlockPos) -> FluidState {
570 Self::fluid_state_for_block(region.block_state(pos))
571 }
572
573 pub(super) fn fluid_state_for_block(state: BlockStateId) -> FluidState {
574 state.get_fluid_state()
575 }
576
577 pub(super) fn is_liquid_block_container(state: BlockStateId) -> bool {
578 BLOCK_BEHAVIORS
579 .get_behavior(state.get_block())
580 .is_liquid_container(state)
581 }
582
583 pub(super) fn place_liquid(
584 region: &WorldGenRegion<'_>,
585 pos: BlockPos,
586 state: BlockStateId,
587 fluid_state: FluidState,
588 ) -> bool {
589 let behavior = BLOCK_BEHAVIORS.get_behavior(state.get_block());
590 behavior.place_liquid(region, pos, state, fluid_state)
591 }
592
593 pub(super) fn for_all_shape_faces(
594 x_size: i32,
595 y_size: i32,
596 z_size: i32,
597 is_full: impl Fn(i32, i32, i32) -> bool,
598 mut consumer: impl FnMut(Direction, i32, i32, i32),
599 ) {
600 for x in 0..x_size {
601 for y in 0..y_size {
602 let mut last_full = false;
603 for z in 0..=z_size {
604 let full = z != z_size && is_full(x, y, z);
605 if !last_full && full {
606 consumer(Direction::North, x, y, z);
607 }
608 if last_full && !full {
609 consumer(Direction::South, x, y, z - 1);
610 }
611 last_full = full;
612 }
613 }
614 }
615
616 for z in 0..z_size {
617 for x in 0..x_size {
618 let mut last_full = false;
619 for y in 0..=y_size {
620 let full = y != y_size && is_full(x, y, z);
621 if !last_full && full {
622 consumer(Direction::Down, x, y, z);
623 }
624 if last_full && !full {
625 consumer(Direction::Up, x, y - 1, z);
626 }
627 last_full = full;
628 }
629 }
630 }
631
632 for y in 0..y_size {
633 for z in 0..z_size {
634 let mut last_full = false;
635 for x in 0..=x_size {
636 let full = x != x_size && is_full(x, y, z);
637 if !last_full && full {
638 consumer(Direction::West, x, y, z);
639 }
640 if last_full && !full {
641 consumer(Direction::East, x - 1, y, z);
642 }
643 last_full = full;
644 }
645 }
646 }
647 }
648
649 pub(super) fn palette(
650 &self,
651 settings: &StructurePlaceSettings<'_>,
652 position: BlockPos,
653 random: &mut WorldgenRandom,
654 ) -> Option<&StructureTemplatePalette> {
655 if self.palettes.is_empty() {
656 return None;
657 }
658 let Ok(bound) = i32::try_from(self.palettes.len()) else {
659 panic!(
660 "structure template palette count {} exceeds i32 range",
661 self.palettes.len()
662 );
663 };
664 let index = match settings.processor_random {
665 StructureProcessorRandom::Placement => random.next_i32_bounded(bound),
666 StructureProcessorRandom::Positional => {
667 let mut random = LegacyRandom::from_seed(Self::block_pos_seed(position) as u64);
668 random.next_i32_bounded(bound)
669 }
670 };
671 Some(&self.palettes[index as usize])
672 }
673
674 pub(super) fn place_block_entity(
675 region: &mut WorldGenRegion<'_>,
676 pos: BlockPos,
677 state: BlockStateId,
678 block_entity_type: Option<BlockEntityTypeRef>,
679 nbt: NbtCompound,
680 ) {
681 let Some(block_entity_type) = block_entity_type else {
682 return;
683 };
684 let _ = region.set_block_entity_data(pos, block_entity_type, state, nbt);
685 }
686
687 pub(super) fn block_entity_type_for_nbt_or_state(
688 registry: &Registry,
689 state: BlockStateId,
690 nbt: &NbtCompound,
691 ) -> Option<BlockEntityTypeRef> {
692 let block = Self::block_for_state(registry, state);
693 if let Some(id) = nbt.string("id") {
694 let id = Identifier::from_str(id.to_str().as_ref()).ok()?;
695 return registry
696 .block_entity_types
697 .by_key(&id)
698 .filter(|block_entity_type| block_entity_type.is_valid(block));
699 }
700 Self::block_entity_type_for_state(registry, state)
701 }
702
703 pub(super) fn block_entity_type_for_state(
704 registry: &Registry,
705 state: BlockStateId,
706 ) -> Option<BlockEntityTypeRef> {
707 let block = Self::block_for_state(registry, state);
708 if block == &vanilla_blocks::SUSPICIOUS_SAND || block == &vanilla_blocks::SUSPICIOUS_GRAVEL
709 {
710 return Some(&vanilla_block_entity_types::BRUSHABLE_BLOCK);
711 }
712 None
713 }
714
715 pub(super) fn should_reseed_template_loot(
716 block_entity_type: Option<BlockEntityTypeRef>,
717 nbt: &NbtCompound,
718 ) -> bool {
719 nbt.contains("LootTable")
720 && block_entity_type.is_some_and(Self::is_randomizable_container_block_entity)
721 }
722
723 pub(super) fn is_randomizable_container_block_entity(
724 block_entity_type: BlockEntityTypeRef,
725 ) -> bool {
726 let key = &block_entity_type.key;
727 key == &vanilla_block_entity_types::BARREL.key
728 || key == &vanilla_block_entity_types::CHEST.key
729 || key == &vanilla_block_entity_types::TRAPPED_CHEST.key
730 || key == &vanilla_block_entity_types::DISPENSER.key
731 || key == &vanilla_block_entity_types::DROPPER.key
732 || key == &vanilla_block_entity_types::HOPPER.key
733 || key == &vanilla_block_entity_types::SHULKER_BOX.key
734 || key == &vanilla_block_entity_types::CRAFTER.key
735 }
736}