Skip to main content

steel_core/worldgen/template/
placement.rs

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            // Always guard placement: the vanilla fallback may enqueue a block outside
219            // `bounding_box` for processor/finalize parity without intending a write here.
220            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}