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