Skip to main content

steel_core/worldgen/template/
placement.rs

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