steel_core/worldgen/feature/features/dripstone/
pointed.rs1use 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}