Skip to main content

steel_core/worldgen/feature/features/
multiface_growth.rs

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