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 !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}