steel_core/worldgen/feature/features/
monster_room.rs1#![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}