Skip to main content

steel_core/worldgen/feature/features/dripstone/
pointed.rs

1use steel_registry::vanilla_block_tags::BlockTag;
2
3use super::super::super::prelude::*;
4use super::super::super::runner::FeatureDecorationRunner;
5
6impl FeatureDecorationRunner {
7    pub(in crate::worldgen::feature) fn place_pointed_dripstone_feature(
8        region: &mut WorldGenRegion<'_>,
9        random: &mut WorldgenRandom,
10        config: &PointedDripstoneConfiguration,
11        origin: BlockPos,
12    ) -> bool {
13        let Some(tip_direction) = Self::pointed_dripstone_tip_direction(region, random, origin)
14        else {
15            return false;
16        };
17
18        let root_pos = origin.relative(tip_direction.opposite());
19        Self::create_patch_of_dripstone_blocks(region, random, root_pos, config);
20        let height = if random.next_f32() < config.chance_of_taller_dripstone
21            && Self::is_empty_or_water(region.block_state(origin.relative(tip_direction)))
22        {
23            2
24        } else {
25            1
26        };
27        Self::grow_pointed_dripstone(region, origin, tip_direction, height, false);
28        true
29    }
30
31    pub(in crate::worldgen::feature) fn pointed_dripstone_tip_direction(
32        region: &WorldGenRegion<'_>,
33        random: &mut WorldgenRandom,
34        pos: BlockPos,
35    ) -> Option<Direction> {
36        let can_place_above = Self::is_dripstone_base(region.block_state(pos.above()));
37        let can_place_below = Self::is_dripstone_base(region.block_state(pos.below()));
38        if can_place_above && can_place_below {
39            Some(if random.next_bool() {
40                Direction::Down
41            } else {
42                Direction::Up
43            })
44        } else if can_place_above {
45            Some(Direction::Down)
46        } else if can_place_below {
47            Some(Direction::Up)
48        } else {
49            None
50        }
51    }
52
53    fn create_patch_of_dripstone_blocks(
54        region: &mut WorldGenRegion<'_>,
55        random: &mut WorldgenRandom,
56        pos: BlockPos,
57        config: &PointedDripstoneConfiguration,
58    ) {
59        Self::place_dripstone_block_if_possible(region, pos);
60
61        for direction in Self::VANILLA_HORIZONTAL_DIRECTIONS {
62            if random.next_f32() > config.chance_of_directional_spread {
63                continue;
64            }
65
66            let pos1 = pos.relative(direction);
67            Self::place_dripstone_block_if_possible(region, pos1);
68            if random.next_f32() > config.chance_of_spread_radius2 {
69                continue;
70            }
71
72            let pos2 = pos1.relative(Self::random_vanilla_direction(random));
73            Self::place_dripstone_block_if_possible(region, pos2);
74            if random.next_f32() > config.chance_of_spread_radius3 {
75                continue;
76            }
77
78            let pos3 = pos2.relative(Self::random_vanilla_direction(random));
79            Self::place_dripstone_block_if_possible(region, pos3);
80        }
81    }
82
83    pub(in crate::worldgen::feature) fn grow_pointed_dripstone(
84        region: &mut WorldGenRegion<'_>,
85        start_pos: BlockPos,
86        tip_direction: Direction,
87        height: i32,
88        merged_tip: bool,
89    ) {
90        if !Self::is_dripstone_base(
91            region.block_state(start_pos.relative(tip_direction.opposite())),
92        ) {
93            return;
94        }
95
96        let mut pos = start_pos;
97        Self::build_base_to_tip_dripstone_column(tip_direction, height, merged_tip, |state| {
98            let state = if state.get_block() == &vanilla_blocks::POINTED_DRIPSTONE {
99                state.set_value(
100                    &BlockStateProperties::WATERLOGGED,
101                    get_fluid_state_from_block(region.block_state(pos)).is_water(),
102                )
103            } else {
104                state
105            };
106
107            let _ = region.set_block_state(pos, state, UpdateFlags::UPDATE_CLIENTS);
108            pos = pos.relative(tip_direction);
109        });
110    }
111
112    pub(in crate::worldgen::feature) fn build_base_to_tip_dripstone_column(
113        direction: Direction,
114        total_length: i32,
115        merged_tip: bool,
116        mut consumer: impl FnMut(BlockStateId),
117    ) {
118        if total_length >= 3 {
119            consumer(Self::create_pointed_dripstone(
120                direction,
121                SpeleothemThickness::Base,
122            ));
123
124            for _ in 0..total_length - 3 {
125                consumer(Self::create_pointed_dripstone(
126                    direction,
127                    SpeleothemThickness::Middle,
128                ));
129            }
130        }
131
132        if total_length >= 2 {
133            consumer(Self::create_pointed_dripstone(
134                direction,
135                SpeleothemThickness::Frustum,
136            ));
137        }
138
139        if total_length >= 1 {
140            let thickness = if merged_tip {
141                SpeleothemThickness::TipMerge
142            } else {
143                SpeleothemThickness::Tip
144            };
145            consumer(Self::create_pointed_dripstone(direction, thickness));
146        }
147    }
148
149    pub(in crate::worldgen::feature) fn place_dripstone_block_if_possible(
150        region: &mut WorldGenRegion<'_>,
151        pos: BlockPos,
152    ) -> bool {
153        let state = region.block_state(pos);
154        if !state
155            .get_block()
156            .has_tag(&BlockTag::DRIPSTONE_REPLACEABLE_BLOCKS)
157        {
158            return false;
159        }
160
161        region.set_block_state(
162            pos,
163            vanilla_blocks::DRIPSTONE_BLOCK.default_state(),
164            UpdateFlags::UPDATE_CLIENTS,
165        )
166    }
167
168    pub(in crate::worldgen::feature) fn create_pointed_dripstone(
169        direction: Direction,
170        thickness: SpeleothemThickness,
171    ) -> BlockStateId {
172        vanilla_blocks::POINTED_DRIPSTONE
173            .default_state()
174            .set_value(&BlockStateProperties::VERTICAL_DIRECTION, direction)
175            .set_value(&BlockStateProperties::SPELEOTHEM_THICKNESS, thickness)
176    }
177
178    pub(in crate::worldgen::feature) fn is_dripstone_base(state: BlockStateId) -> bool {
179        let block = state.get_block();
180        block == &vanilla_blocks::DRIPSTONE_BLOCK
181            || block.has_tag(&BlockTag::DRIPSTONE_REPLACEABLE_BLOCKS)
182    }
183
184    pub(in crate::worldgen::feature) fn is_empty_or_water(state: BlockStateId) -> bool {
185        state.is_air() || state.get_block() == &vanilla_blocks::WATER
186    }
187
188    fn random_vanilla_direction(random: &mut WorldgenRandom) -> Direction {
189        Self::VANILLA_DIRECTION_VALUES[random.next_i32_bounded(6) as usize]
190    }
191}