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 !Self::test_block_predicate(level, registry, &config.can_place_on, origin.below()) {
106            return false;
107        }
108
109        for dy in 0..=tree_height {
110            let radius =
111                Self::huge_mushroom_tree_radius_for_height(kind, -1, -1, config.foliage_radius, dy);
112            for dx in -radius..=radius {
113                for dz in -radius..=radius {
114                    let state = level.get_block_state(origin.offset(dx, dy, dz));
115                    if !state.is_air() && !state.get_block().has_tag(&BlockTag::LEAVES) {
116                        return false;
117                    }
118                }
119            }
120        }
121
122        true
123    }
124
125    fn make_brown_mushroom_cap(
126        level: &impl LevelAccessor,
127        registry: &Registry,
128        random: &mut WorldgenRandom,
129        config: &HugeMushroomConfiguration,
130        origin: BlockPos,
131        tree_height: i32,
132    ) {
133        let radius = config.foliage_radius;
134        for dx in -radius..=radius {
135            for dz in -radius..=radius {
136                let min_x = dx == -radius;
137                let max_x = dx == radius;
138                let min_z = dz == -radius;
139                let max_z = dz == radius;
140                let x_edge = min_x || max_x;
141                let z_edge = min_z || max_z;
142                if x_edge && z_edge {
143                    continue;
144                }
145
146                let pos = origin.offset(dx, tree_height, dz);
147                let west = min_x || z_edge && dx == 1 - radius;
148                let east = max_x || z_edge && dx == radius - 1;
149                let north = min_z || x_edge && dz == 1 - radius;
150                let south = max_z || x_edge && dz == radius - 1;
151                let mut state = Self::sample_block_state_provider(
152                    level,
153                    registry,
154                    random,
155                    &config.cap_provider,
156                    origin,
157                );
158
159                if Self::has_mushroom_horizontal_properties(state) {
160                    state =
161                        Self::set_mushroom_horizontal_properties(state, west, east, north, south);
162                }
163
164                Self::place_huge_mushroom_block(level, pos, state);
165            }
166        }
167    }
168
169    fn make_red_mushroom_cap(
170        level: &impl LevelAccessor,
171        registry: &Registry,
172        random: &mut WorldgenRandom,
173        config: &HugeMushroomConfiguration,
174        origin: BlockPos,
175        tree_height: i32,
176    ) {
177        for dy in tree_height - 3..=tree_height {
178            let radius = if dy < tree_height {
179                config.foliage_radius
180            } else {
181                config.foliage_radius - 1
182            };
183            let center = config.foliage_radius - 2;
184
185            for dx in -radius..=radius {
186                for dz in -radius..=radius {
187                    let min_x = dx == -radius;
188                    let max_x = dx == radius;
189                    let min_z = dz == -radius;
190                    let max_z = dz == radius;
191                    let x_edge = min_x || max_x;
192                    let z_edge = min_z || max_z;
193                    if dy < tree_height && x_edge == z_edge {
194                        continue;
195                    }
196
197                    let pos = origin.offset(dx, dy, dz);
198                    let mut state = Self::sample_block_state_provider(
199                        level,
200                        registry,
201                        random,
202                        &config.cap_provider,
203                        origin,
204                    );
205
206                    if Self::has_mushroom_horizontal_properties(state)
207                        && state.try_get_value(&BlockStateProperties::UP).is_some()
208                    {
209                        state = state.set_value(&BlockStateProperties::UP, dy >= tree_height - 1);
210                        state = Self::set_mushroom_horizontal_properties(
211                            state,
212                            dx < -center,
213                            dx > center,
214                            dz < -center,
215                            dz > center,
216                        );
217                    }
218
219                    Self::place_huge_mushroom_block(level, pos, state);
220                }
221            }
222        }
223    }
224
225    fn place_huge_mushroom_trunk(
226        level: &impl LevelAccessor,
227        registry: &Registry,
228        random: &mut WorldgenRandom,
229        config: &HugeMushroomConfiguration,
230        origin: BlockPos,
231        tree_height: i32,
232    ) {
233        for dy in 0..tree_height {
234            let pos = origin.above_n(dy);
235            let state = Self::sample_block_state_provider(
236                level,
237                registry,
238                random,
239                &config.stem_provider,
240                origin,
241            );
242            Self::place_huge_mushroom_block(level, pos, state);
243        }
244    }
245
246    fn place_huge_mushroom_block(level: &impl LevelAccessor, pos: BlockPos, state: BlockStateId) {
247        let current_state = level.get_block_state(pos);
248        if current_state.is_air()
249            || current_state
250                .get_block()
251                .has_tag(&BlockTag::REPLACEABLE_BY_MUSHROOMS)
252        {
253            let _ = level.set_block_state(pos, state, UpdateFlags::UPDATE_ALL);
254        }
255    }
256
257    fn has_mushroom_horizontal_properties(state: BlockStateId) -> bool {
258        state.try_get_value(&BlockStateProperties::WEST).is_some()
259            && state.try_get_value(&BlockStateProperties::EAST).is_some()
260            && state.try_get_value(&BlockStateProperties::NORTH).is_some()
261            && state.try_get_value(&BlockStateProperties::SOUTH).is_some()
262    }
263
264    fn set_mushroom_horizontal_properties(
265        mut state: BlockStateId,
266        west: bool,
267        east: bool,
268        north: bool,
269        south: bool,
270    ) -> BlockStateId {
271        state = state.set_value(&BlockStateProperties::WEST, west);
272        state = state.set_value(&BlockStateProperties::EAST, east);
273        state = state.set_value(&BlockStateProperties::NORTH, north);
274        state.set_value(&BlockStateProperties::SOUTH, south)
275    }
276
277    const fn huge_mushroom_tree_radius_for_height(
278        kind: HugeMushroomKind,
279        _trunk_height: i32,
280        tree_height: i32,
281        leaf_radius: i32,
282        y_offset: i32,
283    ) -> i32 {
284        match kind {
285            HugeMushroomKind::Brown => {
286                if y_offset <= 3 {
287                    0
288                } else {
289                    leaf_radius
290                }
291            }
292            HugeMushroomKind::Red => {
293                if (y_offset < tree_height && y_offset >= tree_height - 3)
294                    || y_offset == tree_height
295                {
296                    leaf_radius
297                } else {
298                    0
299                }
300            }
301        }
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308
309    #[test]
310    fn huge_red_mushroom_validation_radius_matches_vanilla_sentinel_call() {
311        assert_eq!(
312            FeatureDecorationRunner::huge_mushroom_tree_radius_for_height(
313                HugeMushroomKind::Red,
314                -1,
315                -1,
316                3,
317                4,
318            ),
319            0
320        );
321    }
322
323    #[test]
324    fn huge_brown_mushroom_validation_radius_ignores_tree_height_like_vanilla() {
325        assert_eq!(
326            FeatureDecorationRunner::huge_mushroom_tree_radius_for_height(
327                HugeMushroomKind::Brown,
328                -1,
329                -1,
330                3,
331                4,
332            ),
333            3
334        );
335    }
336}