steel_core/worldgen/feature/features/
spike.rs1#![expect(
2 clippy::too_many_arguments,
3 reason = "spike body placement mirrors vanilla feature state"
4)]
5
6use super::super::prelude::*;
7use super::super::runner::FeatureDecorationRunner;
8
9impl FeatureDecorationRunner {
10 pub(in crate::worldgen::feature) fn place_spike_feature(
11 region: &mut WorldGenRegion<'_>,
12 registry: &Registry,
13 random: &mut WorldgenRandom,
14 config: &SpikeConfiguration,
15 mut origin: BlockPos,
16 ) -> bool {
17 while region.block_state(origin).is_air() && origin.y() > region.min_y() + 2 {
18 origin = origin.below();
19 }
20
21 if !BlockStateProviderEvaluator::test_block_predicate(
22 region,
23 registry,
24 &config.can_place_on,
25 origin,
26 ) {
27 return false;
28 }
29
30 origin = origin.above_n(random.next_i32_bounded(4));
31 let height = random.next_i32_bounded(4) + 7;
32 let width = height / 4 + random.next_i32_bounded(2);
33 if width > 1 && random.next_i32_bounded(60) == 0 {
34 origin = origin.above_n(10 + random.next_i32_bounded(30));
35 }
36
37 let spike_state =
38 BlockStateProviderEvaluator::block_state_from_data(registry, &config.state);
39 Self::place_spike_body(
40 region,
41 registry,
42 random,
43 config,
44 origin,
45 height,
46 width,
47 spike_state,
48 );
49 Self::place_spike_base(region, registry, random, config, origin, width, spike_state);
50
51 true
52 }
53
54 fn place_spike_body(
55 region: &mut WorldGenRegion<'_>,
56 registry: &Registry,
57 random: &mut WorldgenRandom,
58 config: &SpikeConfiguration,
59 origin: BlockPos,
60 height: i32,
61 width: i32,
62 spike_state: BlockStateId,
63 ) {
64 for y_offset in 0..height {
65 let scale = (1.0 - y_offset as f32 / height as f32) * width as f32;
66 let new_width = scale.ceil() as i32;
67
68 for x_offset in -new_width..=new_width {
69 let dx = x_offset.abs() as f32 - 0.25;
70 for z_offset in -new_width..=new_width {
71 let dz = z_offset.abs() as f32 - 0.25;
72 let inside_radius =
73 (x_offset == 0 && z_offset == 0) || dx * dx + dz * dz <= scale * scale;
74 let on_edge = x_offset == -new_width
75 || x_offset == new_width
76 || z_offset == -new_width
77 || z_offset == new_width;
78 if !inside_radius || (on_edge && random.next_f32() > 0.75) {
79 continue;
80 }
81
82 let positive_offset = origin.offset(x_offset, y_offset, z_offset);
83 Self::place_spike_block_if_replaceable(
84 region,
85 registry,
86 config,
87 positive_offset,
88 spike_state,
89 );
90
91 if y_offset != 0 && new_width > 1 {
92 let negative_offset = origin.offset(x_offset, -y_offset, z_offset);
93 Self::place_spike_block_if_replaceable(
94 region,
95 registry,
96 config,
97 negative_offset,
98 spike_state,
99 );
100 }
101 }
102 }
103 }
104 }
105
106 fn place_spike_base(
107 region: &mut WorldGenRegion<'_>,
108 registry: &Registry,
109 random: &mut WorldgenRandom,
110 config: &SpikeConfiguration,
111 origin: BlockPos,
112 width: i32,
113 spike_state: BlockStateId,
114 ) {
115 let pillar_width = (width - 1).clamp(0, 1);
116 for x_offset in -pillar_width..=pillar_width {
117 for z_offset in -pillar_width..=pillar_width {
118 let mut cursor = origin.offset(x_offset, -1, z_offset);
119 let mut run_length = 50;
120 if x_offset.abs() == 1 && z_offset.abs() == 1 {
121 run_length = random.next_i32_bounded(5);
122 }
123
124 while cursor.y() > 50 {
125 let state = region.block_state(cursor);
126 if !state.is_air()
127 && !BlockStateProviderEvaluator::test_block_predicate(
128 region,
129 registry,
130 &config.can_replace,
131 cursor,
132 )
133 && state != spike_state
134 {
135 break;
136 }
137
138 let _ = region.set_block_state(cursor, spike_state, UpdateFlags::UPDATE_ALL);
139 cursor = cursor.below();
140 run_length -= 1;
141 if run_length <= 0 {
142 cursor = cursor.below_n(random.next_i32_bounded(5) + 1);
143 run_length = random.next_i32_bounded(5);
144 }
145 }
146 }
147 }
148 }
149
150 fn place_spike_block_if_replaceable(
151 region: &mut WorldGenRegion<'_>,
152 registry: &Registry,
153 config: &SpikeConfiguration,
154 pos: BlockPos,
155 spike_state: BlockStateId,
156 ) {
157 let state = region.block_state(pos);
158 if state.is_air()
159 || BlockStateProviderEvaluator::test_block_predicate(
160 region,
161 registry,
162 &config.can_replace,
163 pos,
164 )
165 {
166 let _ = region.set_block_state(pos, spike_state, UpdateFlags::UPDATE_ALL);
167 }
168 }
169}