Skip to main content

steel_core/worldgen/feature/features/
monster_room.rs

1#![expect(
2    clippy::too_many_lines,
3    reason = "monster room placement is kept linear to mirror vanilla"
4)]
5
6use super::super::prelude::*;
7use super::super::runner::FeatureDecorationRunner;
8use steel_registry::vanilla_block_entity_types;
9
10const SIMPLE_DUNGEON: &str = "minecraft:chests/simple_dungeon";
11const MONSTER_ROOM_MOBS: [&str; 4] = [
12    "minecraft:skeleton",
13    "minecraft:zombie",
14    "minecraft:zombie",
15    "minecraft:spider",
16];
17
18impl FeatureDecorationRunner {
19    pub(in crate::worldgen::feature) fn place_monster_room_feature(
20        region: &WorldGenRegion<'_>,
21        random: &mut WorldgenRandom,
22        origin: BlockPos,
23    ) -> bool {
24        let xr = random.next_i32_bounded(2) + 2;
25        let min_x = -xr - 1;
26        let max_x = xr + 1;
27        let zr = random.next_i32_bounded(2) + 2;
28        let min_z = -zr - 1;
29        let max_z = zr + 1;
30        let mut hole_count = 0;
31
32        for dx in min_x..=max_x {
33            for dy in -1..=4 {
34                for dz in min_z..=max_z {
35                    let hole_pos = origin.offset(dx, dy, dz);
36                    let state = region.block_state(hole_pos);
37                    let solid = state.is_solid();
38                    if dy == -1 && !solid {
39                        return false;
40                    }
41                    if dy == 4 && !solid {
42                        return false;
43                    }
44                    if (dx == min_x || dx == max_x || dz == min_z || dz == max_z)
45                        && dy == 0
46                        && state.is_air()
47                        && region.block_state(hole_pos.above()).is_air()
48                    {
49                        hole_count += 1;
50                    }
51                }
52            }
53        }
54
55        if !(1..=5).contains(&hole_count) {
56            return false;
57        }
58
59        let air = vanilla_blocks::CAVE_AIR.default_state();
60        let mossy_cobble = vanilla_blocks::MOSSY_COBBLESTONE.default_state();
61        let cobble = vanilla_blocks::COBBLESTONE.default_state();
62
63        for dx in min_x..=max_x {
64            for dy in (-1..=3).rev() {
65                for dz in min_z..=max_z {
66                    let wall_block = origin.offset(dx, dy, dz);
67                    let wall_state = region.block_state(wall_block);
68                    if dx == min_x
69                        || dy == -1
70                        || dz == min_z
71                        || dx == max_x
72                        || dy == 4
73                        || dz == max_z
74                    {
75                        if wall_block.y() >= region.min_y()
76                            && !region.block_state(wall_block.below()).is_solid()
77                        {
78                            let _ = region.set_block_state(
79                                wall_block,
80                                air,
81                                UpdateFlags::UPDATE_CLIENTS,
82                            );
83                        } else if wall_state.is_solid()
84                            && wall_state.get_block() != &vanilla_blocks::CHEST
85                        {
86                            let state = if dy == -1 && random.next_i32_bounded(4) != 0 {
87                                mossy_cobble
88                            } else {
89                                cobble
90                            };
91                            let _ = Self::safe_set_feature_block(region, wall_block, state);
92                        }
93                    } else if wall_state.get_block() != &vanilla_blocks::CHEST
94                        && wall_state.get_block() != &vanilla_blocks::SPAWNER
95                    {
96                        let _ = Self::safe_set_feature_block(region, wall_block, air);
97                    }
98                }
99            }
100        }
101
102        for _ in 0..2 {
103            for _ in 0..3 {
104                let x = origin.x() + random.next_i32_bounded(xr * 2 + 1) - xr;
105                let z = origin.z() + random.next_i32_bounded(zr * 2 + 1) - zr;
106                let chest_pos = BlockPos::new(x, origin.y(), z);
107                if region.block_state(chest_pos).is_air() {
108                    let wall_count = Self::VANILLA_HORIZONTAL_DIRECTIONS
109                        .iter()
110                        .filter(|&&direction| {
111                            region.block_state(chest_pos.relative(direction)).is_solid()
112                        })
113                        .count();
114
115                    if wall_count == 1 {
116                        let chest = Self::reorient_chest(
117                            region,
118                            chest_pos,
119                            vanilla_blocks::CHEST.default_state(),
120                        );
121                        if Self::safe_set_feature_block(region, chest_pos, chest) {
122                            Self::set_loot_table_block_entity(
123                                region,
124                                chest_pos,
125                                &vanilla_block_entity_types::CHEST,
126                                chest,
127                                SIMPLE_DUNGEON,
128                                random.next_i64(),
129                            );
130                            break;
131                        }
132                    }
133                }
134            }
135        }
136
137        let spawner = vanilla_blocks::SPAWNER.default_state();
138        if Self::safe_set_feature_block(region, origin, spawner) {
139            let entity_id = MONSTER_ROOM_MOBS[random.next_i32_bounded(4) as usize];
140            Self::set_spawner_entity(region, origin, spawner, entity_id);
141        }
142
143        true
144    }
145
146    fn reorient_chest(
147        region: &WorldGenRegion<'_>,
148        pos: BlockPos,
149        state: BlockStateId,
150    ) -> BlockStateId {
151        let mut solid_neighbor = None;
152
153        for direction in Self::VANILLA_HORIZONTAL_DIRECTIONS {
154            let relative_pos = pos.relative(direction);
155            let neighbor = region.block_state(relative_pos);
156            if neighbor.get_block() == &vanilla_blocks::CHEST {
157                return state;
158            }
159
160            if neighbor.is_solid_render() {
161                if solid_neighbor.is_some() {
162                    solid_neighbor = None;
163                    break;
164                }
165                solid_neighbor = Some(direction);
166            }
167        }
168
169        if let Some(direction) = solid_neighbor {
170            state.set_value(
171                &BlockStateProperties::HORIZONTAL_FACING,
172                direction.opposite(),
173            )
174        } else {
175            let mut lock_dir = state.get_value(&BlockStateProperties::HORIZONTAL_FACING);
176            let mut relative_pos = pos.relative(lock_dir);
177            if region.block_state(relative_pos).is_solid_render() {
178                lock_dir = lock_dir.opposite();
179                relative_pos = pos.relative(lock_dir);
180            }
181            if region.block_state(relative_pos).is_solid_render() {
182                lock_dir = lock_dir.rotate_y_clockwise();
183                relative_pos = pos.relative(lock_dir);
184            }
185            if region.block_state(relative_pos).is_solid_render() {
186                lock_dir = lock_dir.opposite();
187            }
188            state.set_value(&BlockStateProperties::HORIZONTAL_FACING, lock_dir)
189        }
190    }
191}