Skip to main content

steel_core/worldgen/feature/features/
multiface_growth.rs

1use super::super::prelude::*;
2use super::super::runner::FeatureDecorationRunner;
3use crate::behavior::blocks::multiface_face_property;
4use crate::behavior::blocks::vegetation::{
5    MultifaceSpreadPos, MultifaceSpreadType, multiface_spread_pos,
6};
7use smallvec::SmallVec;
8use steel_registry::vanilla_block_tags::BlockTag;
9
10struct ResolvedMultifaceGrowth<'a> {
11    raw: &'a MultifaceGrowthConfiguration,
12    place_block: BlockRef,
13    default_state: BlockStateId,
14    can_be_placed_on: &'a [BlockRef],
15    is_sculk_vein: bool,
16}
17
18type MultifaceDirections = SmallVec<[Direction; 6]>;
19
20impl FeatureDecorationRunner {
21    pub(in crate::worldgen::feature) fn place_multiface_growth_feature(
22        region: &mut WorldGenRegion<'_>,
23        registry: &Registry,
24        random: &mut WorldgenRandom,
25        config: &MultifaceGrowthConfiguration,
26        origin: BlockPos,
27    ) -> bool {
28        let origin_state = region.block_state(origin);
29        if !Self::multiface_is_air_or_water(origin_state) {
30            return false;
31        }
32
33        let resolved_config = Self::resolve_multiface_growth_config(registry, config);
34        let search_directions = Self::multiface_shuffled_valid_directions(random, &resolved_config);
35        if Self::place_multiface_growth_if_possible(
36            region,
37            random,
38            origin,
39            origin_state,
40            &resolved_config,
41            &search_directions,
42        ) {
43            return true;
44        }
45
46        for search_direction in &search_directions {
47            let placement_directions = Self::multiface_shuffled_valid_directions_except(
48                random,
49                &resolved_config,
50                search_direction.opposite(),
51            );
52
53            for _ in 0..resolved_config.raw.search_range {
54                let pos = origin.relative(*search_direction);
55                let state = region.block_state(pos);
56                if !Self::multiface_is_air_or_water(state)
57                    && !Self::multiface_is_place_block(&resolved_config, state)
58                {
59                    break;
60                }
61
62                if Self::place_multiface_growth_if_possible(
63                    region,
64                    random,
65                    pos,
66                    state,
67                    &resolved_config,
68                    &placement_directions,
69                ) {
70                    return true;
71                }
72            }
73        }
74
75        false
76    }
77
78    fn place_multiface_growth_if_possible(
79        region: &mut WorldGenRegion<'_>,
80        random: &mut WorldgenRandom,
81        pos: BlockPos,
82        old_state: BlockStateId,
83        config: &ResolvedMultifaceGrowth<'_>,
84        placement_directions: &[Direction],
85    ) -> bool {
86        for placement_direction in placement_directions {
87            let neighbor_state = region.block_state(pos.relative(*placement_direction));
88            if !Self::multiface_can_be_placed_on(config, neighbor_state) {
89                continue;
90            }
91
92            let Some(new_state) = Self::multiface_state_for_placement(
93                region,
94                config,
95                old_state,
96                pos,
97                *placement_direction,
98            ) else {
99                return false;
100            };
101
102            let _ = region.set_block_state(pos, new_state, UpdateFlags::UPDATE_ALL);
103            region.mark_pos_for_postprocessing(pos);
104            if random.next_f32() < config.raw.chance_of_spreading {
105                let _ = Self::spread_multiface_from_face_toward_random_direction(
106                    region,
107                    random,
108                    config,
109                    new_state,
110                    pos,
111                    *placement_direction,
112                    true,
113                );
114            }
115
116            return true;
117        }
118
119        false
120    }
121
122    fn spread_multiface_from_face_toward_random_direction(
123        region: &mut WorldGenRegion<'_>,
124
125        random: &mut WorldgenRandom,
126        config: &ResolvedMultifaceGrowth<'_>,
127        state: BlockStateId,
128        pos: BlockPos,
129        starting_face: Direction,
130        post_process: bool,
131    ) -> Option<MultifaceSpreadPos> {
132        let directions = Self::shuffled_directions(random, Self::VANILLA_DIRECTION_VALUES);
133        for spread_direction in directions {
134            if let Some(spread_pos) = Self::spread_multiface_from_face_toward_direction(
135                region,
136                config,
137                state,
138                pos,
139                starting_face,
140                spread_direction,
141                post_process,
142            ) {
143                return Some(spread_pos);
144            }
145        }
146
147        None
148    }
149
150    fn spread_multiface_from_face_toward_direction(
151        region: &mut WorldGenRegion<'_>,
152        config: &ResolvedMultifaceGrowth<'_>,
153        state: BlockStateId,
154        pos: BlockPos,
155        starting_face: Direction,
156        spread_direction: Direction,
157        post_process: bool,
158    ) -> Option<MultifaceSpreadPos> {
159        let spread_pos = Self::multiface_spread_from_face_toward_direction(
160            region,
161            config,
162            state,
163            pos,
164            starting_face,
165            spread_direction,
166        )?;
167        if Self::spread_multiface_to_face(region, config, &spread_pos, post_process) {
168            Some(spread_pos)
169        } else {
170            None
171        }
172    }
173
174    fn multiface_spread_from_face_toward_direction(
175        region: &WorldGenRegion<'_>,
176        config: &ResolvedMultifaceGrowth<'_>,
177        state: BlockStateId,
178        pos: BlockPos,
179        starting_face: Direction,
180        spread_direction: Direction,
181    ) -> Option<MultifaceSpreadPos> {
182        if spread_direction.axis() == starting_face.axis() {
183            return None;
184        }
185
186        if !Self::multiface_is_other_block_valid_as_source(config, state)
187            && (!Self::multiface_has_face(state, starting_face)
188                || Self::multiface_has_face(state, spread_direction))
189        {
190            return None;
191        }
192
193        for spread_type in Self::multiface_spread_types(config) {
194            let spread_pos =
195                multiface_spread_pos(pos, spread_direction, starting_face, spread_type);
196            if Self::multiface_can_spread_into(region, config, pos, &spread_pos) {
197                return Some(spread_pos);
198            }
199        }
200
201        None
202    }
203
204    fn spread_multiface_to_face(
205        region: &mut WorldGenRegion<'_>,
206        config: &ResolvedMultifaceGrowth<'_>,
207        spread_pos: &MultifaceSpreadPos,
208        post_process: bool,
209    ) -> bool {
210        let old_state = region.block_state(spread_pos.pos);
211        let Some(spread_state) = Self::multiface_state_for_placement(
212            region,
213            config,
214            old_state,
215            spread_pos.pos,
216            spread_pos.face,
217        ) else {
218            return false;
219        };
220
221        if post_process {
222            region.mark_pos_for_postprocessing(spread_pos.pos);
223        }
224        region.set_block_state(spread_pos.pos, spread_state, UpdateFlags::UPDATE_CLIENTS)
225    }
226
227    fn multiface_can_spread_into(
228        region: &WorldGenRegion<'_>,
229
230        config: &ResolvedMultifaceGrowth<'_>,
231        source_pos: BlockPos,
232        spread_pos: &MultifaceSpreadPos,
233    ) -> bool {
234        let existing_state = region.block_state(spread_pos.pos);
235        Self::multiface_state_can_be_replaced(
236            region,
237            config,
238            source_pos,
239            spread_pos.pos,
240            spread_pos.face,
241            existing_state,
242        ) && Self::multiface_is_valid_state_for_placement(
243            region,
244            config,
245            existing_state,
246            spread_pos.pos,
247            spread_pos.face,
248        )
249    }
250
251    fn multiface_state_can_be_replaced(
252        region: &WorldGenRegion<'_>,
253
254        config: &ResolvedMultifaceGrowth<'_>,
255        source_pos: BlockPos,
256        placement_pos: BlockPos,
257        placement_direction: Direction,
258        existing_state: BlockStateId,
259    ) -> bool {
260        if config.is_sculk_vein {
261            return Self::sculk_vein_state_can_be_replaced(
262                region,
263                source_pos,
264                placement_pos,
265                placement_direction,
266                existing_state,
267            );
268        }
269
270        Self::default_multiface_state_can_be_replaced(config, existing_state)
271    }
272
273    fn sculk_vein_state_can_be_replaced(
274        region: &WorldGenRegion<'_>,
275        source_pos: BlockPos,
276        placement_pos: BlockPos,
277        placement_direction: Direction,
278        existing_state: BlockStateId,
279    ) -> bool {
280        let against_state = region.block_state(placement_pos.relative(placement_direction));
281        if against_state.get_block() == &vanilla_blocks::SCULK
282            || against_state.get_block() == &vanilla_blocks::SCULK_CATALYST
283            || against_state.get_block() == &vanilla_blocks::MOVING_PISTON
284        {
285            return false;
286        }
287
288        if Self::manhattan_distance(source_pos, placement_pos) == 2 {
289            let neighbor_pos = source_pos.relative(placement_direction.opposite());
290            if region
291                .block_state(neighbor_pos)
292                .is_face_sturdy_at(neighbor_pos, placement_direction)
293            {
294                return false;
295            }
296        }
297
298        let fluid_state = get_fluid_state_from_block(existing_state);
299        if !fluid_state.is_empty() && !fluid_state.is_water() {
300            return false;
301        }
302
303        if existing_state.get_block().has_tag(&BlockTag::FIRE) {
304            return false;
305        }
306
307        existing_state.is_replaceable()
308            || Self::default_multiface_state_can_be_replaced_for_block(
309                existing_state,
310                &vanilla_blocks::SCULK_VEIN,
311            )
312    }
313
314    fn default_multiface_state_can_be_replaced(
315        config: &ResolvedMultifaceGrowth<'_>,
316        existing_state: BlockStateId,
317    ) -> bool {
318        Self::default_multiface_state_can_be_replaced_for_block(existing_state, config.place_block)
319    }
320
321    fn default_multiface_state_can_be_replaced_for_block(
322        existing_state: BlockStateId,
323        place_block: BlockRef,
324    ) -> bool {
325        existing_state.is_air()
326            || existing_state.get_block() == place_block
327            || (existing_state.get_block() == &vanilla_blocks::WATER
328                && get_fluid_state_from_block(existing_state).is_source())
329    }
330
331    fn multiface_state_for_placement(
332        region: &WorldGenRegion<'_>,
333        config: &ResolvedMultifaceGrowth<'_>,
334        old_state: BlockStateId,
335        placement_pos: BlockPos,
336        placement_direction: Direction,
337    ) -> Option<BlockStateId> {
338        if !Self::multiface_is_valid_state_for_placement(
339            region,
340            config,
341            old_state,
342            placement_pos,
343            placement_direction,
344        ) {
345            return None;
346        }
347
348        let mut new_state = if old_state.get_block() == config.place_block {
349            old_state
350        } else {
351            let fluid_state = get_fluid_state_from_block(old_state);
352            if fluid_state.is_water() && fluid_state.is_source() {
353                config
354                    .default_state
355                    .set_value(&BlockStateProperties::WATERLOGGED, true)
356            } else {
357                config.default_state
358            }
359        };
360        new_state = new_state.set_value(multiface_face_property(placement_direction), true);
361        Some(new_state)
362    }
363
364    fn multiface_is_valid_state_for_placement(
365        region: &WorldGenRegion<'_>,
366        config: &ResolvedMultifaceGrowth<'_>,
367        old_state: BlockStateId,
368        placement_pos: BlockPos,
369        placement_direction: Direction,
370    ) -> bool {
371        if old_state.get_block() == config.place_block
372            && Self::multiface_has_face(old_state, placement_direction)
373        {
374            return false;
375        }
376
377        Self::can_attach_to_multiface(region, placement_pos, placement_direction)
378    }
379
380    fn resolve_multiface_growth_config<'a>(
381        registry: &Registry,
382        config: &'a MultifaceGrowthConfiguration,
383    ) -> ResolvedMultifaceGrowth<'a> {
384        ResolvedMultifaceGrowth {
385            raw: config,
386            place_block: config.block,
387            default_state: registry.blocks.get_default_state_id(config.block),
388            can_be_placed_on: &config.can_be_placed_on,
389            is_sculk_vein: config.block == &vanilla_blocks::SCULK_VEIN,
390        }
391    }
392
393    fn multiface_can_be_placed_on(
394        config: &ResolvedMultifaceGrowth<'_>,
395        state: BlockStateId,
396    ) -> bool {
397        config
398            .can_be_placed_on
399            .iter()
400            .any(|block| state.get_block() == *block)
401    }
402
403    fn multiface_is_place_block(config: &ResolvedMultifaceGrowth<'_>, state: BlockStateId) -> bool {
404        state.get_block() == config.place_block
405    }
406
407    fn multiface_is_other_block_valid_as_source(
408        config: &ResolvedMultifaceGrowth<'_>,
409        state: BlockStateId,
410    ) -> bool {
411        config.is_sculk_vein && state.get_block() != &vanilla_blocks::SCULK_VEIN
412    }
413
414    const fn multiface_spread_types(
415        _config: &ResolvedMultifaceGrowth<'_>,
416    ) -> [MultifaceSpreadType; 3] {
417        [
418            MultifaceSpreadType::SamePosition,
419            MultifaceSpreadType::SamePlane,
420            MultifaceSpreadType::WrapAround,
421        ]
422    }
423
424    fn multiface_shuffled_valid_directions(
425        random: &mut WorldgenRandom,
426        config: &ResolvedMultifaceGrowth<'_>,
427    ) -> MultifaceDirections {
428        let mut directions = Self::multiface_valid_directions(config);
429        Self::shuffle_multiface_directions(random, &mut directions);
430        directions
431    }
432
433    fn multiface_shuffled_valid_directions_except(
434        random: &mut WorldgenRandom,
435        config: &ResolvedMultifaceGrowth<'_>,
436        excluded: Direction,
437    ) -> MultifaceDirections {
438        let mut directions = Self::multiface_valid_directions(config);
439        directions.retain(|direction| *direction != excluded);
440        Self::shuffle_multiface_directions(random, &mut directions);
441        directions
442    }
443
444    fn multiface_valid_directions(config: &ResolvedMultifaceGrowth<'_>) -> MultifaceDirections {
445        let mut directions = MultifaceDirections::new();
446        if config.raw.can_place_on_ceiling {
447            directions.push(Direction::Up);
448        }
449        if config.raw.can_place_on_floor {
450            directions.push(Direction::Down);
451        }
452        if config.raw.can_place_on_wall {
453            directions.extend_from_slice(&Self::VANILLA_HORIZONTAL_DIRECTIONS);
454        }
455        directions
456    }
457
458    fn shuffle_multiface_directions(random: &mut WorldgenRandom, directions: &mut [Direction]) {
459        for i in (1..directions.len()).rev() {
460            let Ok(bound) = i32::try_from(i + 1) else {
461                panic!("multiface direction shuffle length exceeds i32 range");
462            };
463            let j = random.next_i32_bounded(bound) as usize;
464            directions.swap(i, j);
465        }
466    }
467
468    fn multiface_has_face(state: BlockStateId, direction: Direction) -> bool {
469        state
470            .try_get_value(multiface_face_property(direction))
471            .unwrap_or(false)
472    }
473
474    fn multiface_is_air_or_water(state: BlockStateId) -> bool {
475        state.is_air() || state.get_block() == &vanilla_blocks::WATER
476    }
477}