Skip to main content

steel_core/worldgen/feature/features/
huge_mushroom.rs

1#![expect(
2    clippy::fn_params_excessive_bools,
3    reason = "mushroom cap side flags mirror vanilla block properties"
4)]
5
6use steel_registry::vanilla_block_tags::BlockTag;
7
8use super::super::prelude::*;
9use super::super::runner::FeatureDecorationRunner;
10
11#[derive(Clone, Copy)]
12enum HugeMushroomKind {
13    Brown,
14    Red,
15}
16
17impl FeatureDecorationRunner {
18    pub(crate) fn place_huge_brown_mushroom_feature(
19        level: &impl LevelAccessor,
20        registry: &Registry,
21        random: &mut WorldgenRandom,
22        config: &HugeMushroomConfiguration,
23        origin: BlockPos,
24    ) -> bool {
25        Self::place_huge_mushroom_feature(
26            level,
27            registry,
28            random,
29            config,
30            origin,
31            HugeMushroomKind::Brown,
32        )
33    }
34
35    pub(crate) fn place_huge_red_mushroom_feature(
36        level: &impl LevelAccessor,
37        registry: &Registry,
38        random: &mut WorldgenRandom,
39        config: &HugeMushroomConfiguration,
40        origin: BlockPos,
41    ) -> bool {
42        Self::place_huge_mushroom_feature(
43            level,
44            registry,
45            random,
46            config,
47            origin,
48            HugeMushroomKind::Red,
49        )
50    }
51
52    fn place_huge_mushroom_feature(
53        level: &impl LevelAccessor,
54        registry: &Registry,
55        random: &mut WorldgenRandom,
56        config: &HugeMushroomConfiguration,
57        origin: BlockPos,
58        kind: HugeMushroomKind,
59    ) -> bool {
60        let tree_height = Self::huge_mushroom_tree_height(random);
61        if !Self::is_valid_huge_mushroom_position(
62            level,
63            registry,
64            config,
65            origin,
66            tree_height,
67            kind,
68        ) {
69            return false;
70        }
71
72        match kind {
73            HugeMushroomKind::Brown => {
74                Self::make_brown_mushroom_cap(level, registry, random, config, origin, tree_height);
75            }
76            HugeMushroomKind::Red => {
77                Self::make_red_mushroom_cap(level, registry, random, config, origin, tree_height);
78            }
79        }
80        Self::place_huge_mushroom_trunk(level, registry, random, config, origin, tree_height);
81        true
82    }
83
84    fn huge_mushroom_tree_height(random: &mut WorldgenRandom) -> i32 {
85        let mut tree_height = random.next_i32_bounded(3) + 4;
86        if random.next_i32_bounded(12) == 0 {
87            tree_height *= 2;
88        }
89        tree_height
90    }
91
92    fn is_valid_huge_mushroom_position(
93        level: &impl LevelReader,
94        registry: &Registry,
95        config: &HugeMushroomConfiguration,
96        origin: BlockPos,
97        tree_height: i32,
98        kind: HugeMushroomKind,
99    ) -> bool {
100        if origin.y() < level.min_y() + 1 || origin.y() + tree_height + 2 > level.max_y_exclusive()
101        {
102            return false;
103        }
104
105        if !BlockStateProviderEvaluator::test_block_predicate(
106            level,
107            registry,
108            &config.can_place_on,
109            origin.below(),
110        ) {
111            return false;
112        }
113
114        for dy in 0..=tree_height {
115            let radius =
116                Self::huge_mushroom_tree_radius_for_height(kind, -1, -1, config.foliage_radius, dy);
117            for dx in -radius..=radius {
118                for dz in -radius..=radius {
119                    let state = level.get_block_state(origin.offset(dx, dy, dz));
120                    if !state.is_air() && !state.get_block().has_tag(&BlockTag::LEAVES) {
121                        return false;
122                    }
123                }
124            }
125        }
126
127        true
128    }
129
130    fn make_brown_mushroom_cap(
131        level: &impl LevelAccessor,
132        registry: &Registry,
133        random: &mut WorldgenRandom,
134        config: &HugeMushroomConfiguration,
135        origin: BlockPos,
136        tree_height: i32,
137    ) {
138        let radius = config.foliage_radius;
139        for dx in -radius..=radius {
140            for dz in -radius..=radius {
141                let min_x = dx == -radius;
142                let max_x = dx == radius;
143                let min_z = dz == -radius;
144                let max_z = dz == radius;
145                let x_edge = min_x || max_x;
146                let z_edge = min_z || max_z;
147                if x_edge && z_edge {
148                    continue;
149                }
150
151                let pos = origin.offset(dx, tree_height, dz);
152                let west = min_x || z_edge && dx == 1 - radius;
153                let east = max_x || z_edge && dx == radius - 1;
154                let north = min_z || x_edge && dz == 1 - radius;
155                let south = max_z || x_edge && dz == radius - 1;
156                let mut state = BlockStateProviderEvaluator::sample_block_state_provider(
157                    level,
158                    registry,
159                    random,
160                    &config.cap_provider,
161                    origin,
162                );
163
164                if Self::has_mushroom_horizontal_properties(state) {
165                    state =
166                        Self::set_mushroom_horizontal_properties(state, west, east, north, south);
167                }
168
169                Self::place_huge_mushroom_block(level, pos, state);
170            }
171        }
172    }
173
174    fn make_red_mushroom_cap(
175        level: &impl LevelAccessor,
176        registry: &Registry,
177        random: &mut WorldgenRandom,
178        config: &HugeMushroomConfiguration,
179        origin: BlockPos,
180        tree_height: i32,
181    ) {
182        for dy in tree_height - 3..=tree_height {
183            let radius = if dy < tree_height {
184                config.foliage_radius
185            } else {
186                config.foliage_radius - 1
187            };
188            let center = config.foliage_radius - 2;
189
190            for dx in -radius..=radius {
191                for dz in -radius..=radius {
192                    let min_x = dx == -radius;
193                    let max_x = dx == radius;
194                    let min_z = dz == -radius;
195                    let max_z = dz == radius;
196                    let x_edge = min_x || max_x;
197                    let z_edge = min_z || max_z;
198                    if dy < tree_height && x_edge == z_edge {
199                        continue;
200                    }
201
202                    let pos = origin.offset(dx, dy, dz);
203                    let mut state = BlockStateProviderEvaluator::sample_block_state_provider(
204                        level,
205                        registry,
206                        random,
207                        &config.cap_provider,
208                        origin,
209                    );
210
211                    if Self::has_mushroom_horizontal_properties(state)
212                        && state.try_get_value(&BlockStateProperties::UP).is_some()
213                    {
214                        state = state.set_value(&BlockStateProperties::UP, dy >= tree_height - 1);
215                        state = Self::set_mushroom_horizontal_properties(
216                            state,
217                            dx < -center,
218                            dx > center,
219                            dz < -center,
220                            dz > center,
221                        );
222                    }
223
224                    Self::place_huge_mushroom_block(level, pos, state);
225                }
226            }
227        }
228    }
229
230    fn place_huge_mushroom_trunk(
231        level: &impl LevelAccessor,
232        registry: &Registry,
233        random: &mut WorldgenRandom,
234        config: &HugeMushroomConfiguration,
235        origin: BlockPos,
236        tree_height: i32,
237    ) {
238        for dy in 0..tree_height {
239            let pos = origin.above_n(dy);
240            let state = BlockStateProviderEvaluator::sample_block_state_provider(
241                level,
242                registry,
243                random,
244                &config.stem_provider,
245                origin,
246            );
247            Self::place_huge_mushroom_block(level, pos, state);
248        }
249    }
250
251    fn place_huge_mushroom_block(level: &impl LevelAccessor, pos: BlockPos, state: BlockStateId) {
252        let current_state = level.get_block_state(pos);
253        if current_state.is_air()
254            || current_state
255                .get_block()
256                .has_tag(&BlockTag::REPLACEABLE_BY_MUSHROOMS)
257        {
258            let _ = level.set_block_state(pos, state, UpdateFlags::UPDATE_ALL);
259        }
260    }
261
262    fn has_mushroom_horizontal_properties(state: BlockStateId) -> bool {
263        state.try_get_value(&BlockStateProperties::WEST).is_some()
264            && state.try_get_value(&BlockStateProperties::EAST).is_some()
265            && state.try_get_value(&BlockStateProperties::NORTH).is_some()
266            && state.try_get_value(&BlockStateProperties::SOUTH).is_some()
267    }
268
269    fn set_mushroom_horizontal_properties(
270        mut state: BlockStateId,
271        west: bool,
272        east: bool,
273        north: bool,
274        south: bool,
275    ) -> BlockStateId {
276        state = state.set_value(&BlockStateProperties::WEST, west);
277        state = state.set_value(&BlockStateProperties::EAST, east);
278        state = state.set_value(&BlockStateProperties::NORTH, north);
279        state.set_value(&BlockStateProperties::SOUTH, south)
280    }
281
282    const fn huge_mushroom_tree_radius_for_height(
283        kind: HugeMushroomKind,
284        _trunk_height: i32,
285        tree_height: i32,
286        leaf_radius: i32,
287        y_offset: i32,
288    ) -> i32 {
289        match kind {
290            HugeMushroomKind::Brown => {
291                if y_offset <= 3 {
292                    0
293                } else {
294                    leaf_radius
295                }
296            }
297            HugeMushroomKind::Red => {
298                if (y_offset < tree_height && y_offset >= tree_height - 3)
299                    || y_offset == tree_height
300                {
301                    leaf_radius
302                } else {
303                    0
304                }
305            }
306        }
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn huge_red_mushroom_validation_radius_matches_vanilla_sentinel_call() {
316        assert_eq!(
317            FeatureDecorationRunner::huge_mushroom_tree_radius_for_height(
318                HugeMushroomKind::Red,
319                -1,
320                -1,
321                3,
322                4,
323            ),
324            0
325        );
326    }
327
328    #[test]
329    fn huge_brown_mushroom_validation_radius_ignores_tree_height_like_vanilla() {
330        assert_eq!(
331            FeatureDecorationRunner::huge_mushroom_tree_radius_for_height(
332                HugeMushroomKind::Brown,
333                -1,
334                -1,
335                3,
336                4,
337            ),
338            3
339        );
340    }
341}