steel_core/worldgen/feature/features/tree/
root_system.rs1use super::super::super::prelude::*;
2use super::super::super::runner::FeatureDecorationRunner;
3
4impl FeatureDecorationRunner {
5 pub(in crate::worldgen::feature) fn place_root_system_feature(
6 region: &mut WorldGenRegion<'_>,
7 registry: &Registry,
8 random: &mut WorldgenRandom,
9 config: &RootSystemConfiguration,
10 origin: BlockPos,
11 biome_zoom_seed: i64,
12 ) -> bool {
13 if !region.block_state(origin).is_air() {
14 return false;
15 }
16
17 let mut tree_pos = origin;
18 if Self::place_root_system_dirt_and_tree(
19 region,
20 registry,
21 random,
22 config,
23 &mut tree_pos,
24 origin,
25 biome_zoom_seed,
26 ) {
27 Self::place_root_system_hanging_roots(region, registry, random, config, origin);
28 }
29
30 true
31 }
32
33 fn place_root_system_dirt_and_tree(
34 region: &mut WorldGenRegion<'_>,
35 registry: &Registry,
36 random: &mut WorldgenRandom,
37 config: &RootSystemConfiguration,
38 tree_pos: &mut BlockPos,
39 origin: BlockPos,
40 biome_zoom_seed: i64,
41 ) -> bool {
42 for y in 0..config.root_column_max_height {
43 *tree_pos = tree_pos.above();
44 if region.height_at(HeightmapType::WorldSurfaceWg, tree_pos.x(), tree_pos.z())
45 < tree_pos.y()
46 {
47 return false;
48 }
49
50 if !Self::test_block_predicate(
51 region,
52 registry,
53 &config.allowed_tree_position,
54 *tree_pos,
55 ) || !Self::root_system_space_for_tree(region, config, *tree_pos)
56 {
57 continue;
58 }
59
60 let below_pos = tree_pos.below();
61 let below_state = region.block_state(below_pos);
62 if get_fluid_state_from_block(below_state).is_lava() || !below_state.is_solid() {
63 return false;
64 }
65
66 if Self::place_placed_feature_ref(
67 region,
68 registry,
69 random,
70 *tree_pos,
71 &config.feature,
72 biome_zoom_seed,
73 ) {
74 Self::place_root_system_dirt(
75 region,
76 registry,
77 random,
78 config,
79 origin,
80 origin.y() + y,
81 );
82 return true;
83 }
84 }
85
86 false
87 }
88
89 fn root_system_space_for_tree(
90 region: &WorldGenRegion<'_>,
91 config: &RootSystemConfiguration,
92 pos: BlockPos,
93 ) -> bool {
94 let mut column_pos = pos;
95 for blocks_above_origin in 1..=config.required_vertical_space_for_tree {
96 column_pos = column_pos.above();
97 let state = region.block_state(column_pos);
98 if !Self::root_system_allowed_tree_space(
99 state,
100 blocks_above_origin,
101 config.allowed_vertical_water_for_tree,
102 ) {
103 return false;
104 }
105 }
106
107 if config.level_test_distance > 0 {
108 for direction in [
109 Direction::South,
110 Direction::West,
111 Direction::North,
112 Direction::East,
113 ] {
114 let corner_pos = pos.relative_n(direction, config.level_test_distance);
115 let below = region.block_state(corner_pos.below_n(config.max_level_deviation));
116 let above = region.block_state(corner_pos.above_n(config.max_level_deviation));
117 if below.is_air() || !above.is_air() {
118 return false;
119 }
120 }
121 }
122
123 true
124 }
125
126 fn root_system_allowed_tree_space(
127 state: BlockStateId,
128 blocks_above_origin: i32,
129 allowed_vertical_water_height: i32,
130 ) -> bool {
131 if state.is_air() {
132 return true;
133 }
134
135 let blocks_above_ground = blocks_above_origin + 1;
136 blocks_above_ground <= allowed_vertical_water_height
137 && get_fluid_state_from_block(state).is_water()
138 }
139
140 fn place_root_system_dirt(
141 region: &mut WorldGenRegion<'_>,
142 registry: &Registry,
143 random: &mut WorldgenRandom,
144 config: &RootSystemConfiguration,
145 origin: BlockPos,
146 target_height: i32,
147 ) {
148 for y in origin.y()..target_height {
149 Self::place_root_system_rooted_dirt(
150 region,
151 registry,
152 random,
153 config,
154 origin.at_y(y),
155 origin.x(),
156 origin.z(),
157 );
158 }
159 }
160
161 fn place_root_system_rooted_dirt(
162 region: &mut WorldGenRegion<'_>,
163 registry: &Registry,
164 random: &mut WorldgenRandom,
165 config: &RootSystemConfiguration,
166 mut pos: BlockPos,
167 origin_x: i32,
168 origin_z: i32,
169 ) {
170 for _ in 0..config.root_placement_attempts {
171 pos = pos.offset(
172 random.next_i32_bounded(config.root_radius)
173 - random.next_i32_bounded(config.root_radius),
174 0,
175 random.next_i32_bounded(config.root_radius)
176 - random.next_i32_bounded(config.root_radius),
177 );
178
179 let state = region.block_state(pos);
180 if Self::block_matches_holder_set(state.get_block(), &config.root_replaceable) {
181 let replacement = Self::sample_block_state_provider(
182 region,
183 registry,
184 random,
185 &config.root_state_provider,
186 pos,
187 );
188 let _ = region.set_block_state(pos, replacement, UpdateFlags::UPDATE_CLIENTS);
189 }
190
191 pos = BlockPos::new(origin_x, pos.y(), origin_z);
192 }
193 }
194
195 fn place_root_system_hanging_roots(
196 region: &mut WorldGenRegion<'_>,
197 registry: &Registry,
198 random: &mut WorldgenRandom,
199 config: &RootSystemConfiguration,
200 origin: BlockPos,
201 ) {
202 for _ in 0..config.hanging_root_placement_attempts {
203 let pos = origin.offset(
204 random.next_i32_bounded(config.hanging_root_radius)
205 - random.next_i32_bounded(config.hanging_root_radius),
206 random.next_i32_bounded(config.hanging_roots_vertical_span)
207 - random.next_i32_bounded(config.hanging_roots_vertical_span),
208 random.next_i32_bounded(config.hanging_root_radius)
209 - random.next_i32_bounded(config.hanging_root_radius),
210 );
211
212 if !region.block_state(pos).is_air() {
213 continue;
214 }
215
216 let state = Self::sample_block_state_provider(
217 region,
218 registry,
219 random,
220 &config.hanging_root_state_provider,
221 pos,
222 );
223 let behavior = BLOCK_BEHAVIORS.get_behavior(state.get_block());
224 if behavior.can_survive(state, region, pos) && {
225 let above_pos = pos.above();
226 region
227 .block_state(above_pos)
228 .is_face_sturdy_at(above_pos, Direction::Down)
229 } {
230 let _ = region.set_block_state(pos, state, UpdateFlags::UPDATE_CLIENTS);
231 }
232 }
233 }
234}