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 !Self::test_block_predicate(region, registry, &config.can_place_on, origin) {
22 return false;
23 }
24
25 origin = origin.above_n(random.next_i32_bounded(4));
26 let height = random.next_i32_bounded(4) + 7;
27 let width = height / 4 + random.next_i32_bounded(2);
28 if width > 1 && random.next_i32_bounded(60) == 0 {
29 origin = origin.above_n(10 + random.next_i32_bounded(30));
30 }
31
32 let spike_state = Self::block_state_from_data(registry, &config.state);
33 Self::place_spike_body(
34 region,
35 registry,
36 random,
37 config,
38 origin,
39 height,
40 width,
41 spike_state,
42 );
43 Self::place_spike_base(region, registry, random, config, origin, width, spike_state);
44
45 true
46 }
47
48 fn place_spike_body(
49 region: &mut WorldGenRegion<'_>,
50 registry: &Registry,
51 random: &mut WorldgenRandom,
52 config: &SpikeConfiguration,
53 origin: BlockPos,
54 height: i32,
55 width: i32,
56 spike_state: BlockStateId,
57 ) {
58 for y_offset in 0..height {
59 let scale = (1.0 - y_offset as f32 / height as f32) * width as f32;
60 let new_width = scale.ceil() as i32;
61
62 for x_offset in -new_width..=new_width {
63 let dx = x_offset.abs() as f32 - 0.25;
64 for z_offset in -new_width..=new_width {
65 let dz = z_offset.abs() as f32 - 0.25;
66 let inside_radius =
67 (x_offset == 0 && z_offset == 0) || dx * dx + dz * dz <= scale * scale;
68 let on_edge = x_offset == -new_width
69 || x_offset == new_width
70 || z_offset == -new_width
71 || z_offset == new_width;
72 if !inside_radius || (on_edge && random.next_f32() > 0.75) {
73 continue;
74 }
75
76 let positive_offset = origin.offset(x_offset, y_offset, z_offset);
77 Self::place_spike_block_if_replaceable(
78 region,
79 registry,
80 config,
81 positive_offset,
82 spike_state,
83 );
84
85 if y_offset != 0 && new_width > 1 {
86 let negative_offset = origin.offset(x_offset, -y_offset, z_offset);
87 Self::place_spike_block_if_replaceable(
88 region,
89 registry,
90 config,
91 negative_offset,
92 spike_state,
93 );
94 }
95 }
96 }
97 }
98 }
99
100 fn place_spike_base(
101 region: &mut WorldGenRegion<'_>,
102 registry: &Registry,
103 random: &mut WorldgenRandom,
104 config: &SpikeConfiguration,
105 origin: BlockPos,
106 width: i32,
107 spike_state: BlockStateId,
108 ) {
109 let pillar_width = (width - 1).clamp(0, 1);
110 for x_offset in -pillar_width..=pillar_width {
111 for z_offset in -pillar_width..=pillar_width {
112 let mut cursor = origin.offset(x_offset, -1, z_offset);
113 let mut run_length = 50;
114 if x_offset.abs() == 1 && z_offset.abs() == 1 {
115 run_length = random.next_i32_bounded(5);
116 }
117
118 while cursor.y() > 50 {
119 let state = region.block_state(cursor);
120 if !state.is_air()
121 && !Self::test_block_predicate(
122 region,
123 registry,
124 &config.can_replace,
125 cursor,
126 )
127 && state != spike_state
128 {
129 break;
130 }
131
132 let _ = region.set_block_state(cursor, spike_state, UpdateFlags::UPDATE_ALL);
133 cursor = cursor.below();
134 run_length -= 1;
135 if run_length <= 0 {
136 cursor = cursor.below_n(random.next_i32_bounded(5) + 1);
137 run_length = random.next_i32_bounded(5);
138 }
139 }
140 }
141 }
142 }
143
144 fn place_spike_block_if_replaceable(
145 region: &mut WorldGenRegion<'_>,
146 registry: &Registry,
147 config: &SpikeConfiguration,
148 pos: BlockPos,
149 spike_state: BlockStateId,
150 ) {
151 let state = region.block_state(pos);
152 if state.is_air() || Self::test_block_predicate(region, registry, &config.can_replace, pos)
153 {
154 let _ = region.set_block_state(pos, spike_state, UpdateFlags::UPDATE_ALL);
155 }
156 }
157}