Skip to main content

steel_core/worldgen/feature/features/
spike.rs

1#![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}