Skip to main content

steel_core/worldgen/feature/features/
end_spike.rs

1use std::f64::consts::PI;
2use std::sync::Arc;
3
4use glam::DVec3;
5use steel_registry::vanilla_entities;
6
7use super::super::prelude::*;
8use super::super::runner::FeatureDecorationRunner;
9use crate::entity::{Entity, entities::EndCrystalEntity, next_entity_id};
10
11const END_SPIKE_COUNT: usize = 10;
12const END_SPIKE_DISTANCE: f64 = 42.0;
13const END_SPIKE_ANGLE_STEP: f64 = PI / 10.0;
14const END_SPIKE_CLEAR_AIR_MIN_Y: i32 = 65;
15const END_SPIKE_CAGE_RADIUS: i32 = 2;
16const END_SPIKE_CAGE_HEIGHT: i32 = 3;
17
18impl FeatureDecorationRunner {
19    pub(in crate::worldgen::feature) fn place_end_spike_feature(
20        region: &mut WorldGenRegion<'_>,
21        random: &mut WorldgenRandom,
22        config: &EndSpikeConfiguration,
23        origin: BlockPos,
24    ) -> bool {
25        let generated_spikes;
26        let spikes = if config.spikes.is_empty() {
27            generated_spikes = Self::end_spikes_for_level(region.seed());
28            &generated_spikes
29        } else {
30            &config.spikes
31        };
32
33        for spike in spikes {
34            if Self::end_spike_center_is_within_chunk(spike, origin) {
35                Self::place_end_spike(region, random, config, spike);
36            }
37        }
38
39        true
40    }
41
42    fn end_spikes_for_level(seed: i64) -> Vec<EndSpike> {
43        let mut seed_random = LegacyRandom::from_seed(seed as u64);
44        let cache_key = seed_random.next_i64() & 65_535;
45        let mut random = LegacyRandom::from_seed(cache_key as u64);
46        let mut sizes = [0_i32; END_SPIKE_COUNT];
47        for (index, size) in sizes.iter_mut().enumerate() {
48            *size = index as i32;
49        }
50        for bound in (2..=END_SPIKE_COUNT).rev() {
51            let swap_to = random.next_i32_bounded(bound as i32) as usize;
52            sizes.swap(bound - 1, swap_to);
53        }
54
55        sizes
56            .iter()
57            .enumerate()
58            .map(|(index, size)| {
59                let angle = 2.0 * (-PI + END_SPIKE_ANGLE_STEP * index as f64);
60                EndSpike {
61                    center_x: fast_floor(END_SPIKE_DISTANCE * angle.cos()),
62                    center_z: fast_floor(END_SPIKE_DISTANCE * angle.sin()),
63                    radius: 2 + size / 3,
64                    height: 76 + size * 3,
65                    guarded: *size == 1 || *size == 2,
66                }
67            })
68            .collect()
69    }
70
71    const fn end_spike_center_is_within_chunk(spike: &EndSpike, origin: BlockPos) -> bool {
72        SectionPos::block_to_section_coord(origin.x())
73            == SectionPos::block_to_section_coord(spike.center_x)
74            && SectionPos::block_to_section_coord(origin.z())
75                == SectionPos::block_to_section_coord(spike.center_z)
76    }
77
78    fn place_end_spike(
79        region: &mut WorldGenRegion<'_>,
80        random: &mut WorldgenRandom,
81        config: &EndSpikeConfiguration,
82        spike: &EndSpike,
83    ) {
84        Self::place_end_spike_body(region, spike);
85        if spike.guarded {
86            Self::place_end_spike_cage(region, spike);
87        }
88        Self::place_end_spike_crystal(region, random, config, spike);
89    }
90
91    fn place_end_spike_body(region: &mut WorldGenRegion<'_>, spike: &EndSpike) {
92        let radius_squared_plus_one = spike.radius * spike.radius + 1;
93        let obsidian = REGISTRY
94            .blocks
95            .get_default_state_id(&vanilla_blocks::OBSIDIAN);
96        let air = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::AIR);
97
98        for x in (spike.center_x - spike.radius)..=(spike.center_x + spike.radius) {
99            for y in region.min_y()..=(spike.height + 10) {
100                for z in (spike.center_z - spike.radius)..=(spike.center_z + spike.radius) {
101                    let pos = BlockPos::new(x, y, z);
102                    if Self::end_spike_inside_radius(spike, pos, radius_squared_plus_one)
103                        && y < spike.height
104                    {
105                        let _ = region.set_block_state(pos, obsidian, UpdateFlags::UPDATE_ALL);
106                    } else if y > END_SPIKE_CLEAR_AIR_MIN_Y {
107                        let _ = region.set_block_state(pos, air, UpdateFlags::UPDATE_ALL);
108                    }
109                }
110            }
111        }
112    }
113
114    fn end_spike_inside_radius(
115        spike: &EndSpike,
116        pos: BlockPos,
117        radius_squared_plus_one: i32,
118    ) -> bool {
119        let dx = f64::from(pos.x() - spike.center_x);
120        let dz = f64::from(pos.z() - spike.center_z);
121        dx * dx + dz * dz <= f64::from(radius_squared_plus_one)
122    }
123
124    fn place_end_spike_cage(region: &mut WorldGenRegion<'_>, spike: &EndSpike) {
125        for dx in -END_SPIKE_CAGE_RADIUS..=END_SPIKE_CAGE_RADIUS {
126            for dz in -END_SPIKE_CAGE_RADIUS..=END_SPIKE_CAGE_RADIUS {
127                for dy in 0..=END_SPIKE_CAGE_HEIGHT {
128                    let touches_width_limit = dx.abs() == END_SPIKE_CAGE_RADIUS;
129                    let touches_depth_limit = dz.abs() == END_SPIKE_CAGE_RADIUS;
130                    let top = dy == END_SPIKE_CAGE_HEIGHT;
131                    if !touches_width_limit && !touches_depth_limit && !top {
132                        continue;
133                    }
134
135                    let x_edge = touches_width_limit || top;
136                    let z_edge = touches_depth_limit || top;
137                    let state = Self::end_spike_iron_bars_state(x_edge, z_edge, dx, dz);
138                    let pos =
139                        BlockPos::new(spike.center_x + dx, spike.height + dy, spike.center_z + dz);
140                    let _ = region.set_block_state(pos, state, UpdateFlags::UPDATE_ALL);
141                }
142            }
143        }
144    }
145
146    fn end_spike_iron_bars_state(x_edge: bool, z_edge: bool, dx: i32, dz: i32) -> BlockStateId {
147        REGISTRY
148            .blocks
149            .get_default_state_id(&vanilla_blocks::IRON_BARS)
150            .set_value(
151                &BlockStateProperties::NORTH,
152                x_edge && dz != -END_SPIKE_CAGE_RADIUS,
153            )
154            .set_value(
155                &BlockStateProperties::SOUTH,
156                x_edge && dz != END_SPIKE_CAGE_RADIUS,
157            )
158            .set_value(
159                &BlockStateProperties::WEST,
160                z_edge && dx != -END_SPIKE_CAGE_RADIUS,
161            )
162            .set_value(
163                &BlockStateProperties::EAST,
164                z_edge && dx != END_SPIKE_CAGE_RADIUS,
165            )
166    }
167
168    fn place_end_spike_crystal(
169        region: &mut WorldGenRegion<'_>,
170        random: &mut WorldgenRandom,
171        config: &EndSpikeConfiguration,
172        spike: &EndSpike,
173    ) {
174        let position = DVec3::new(
175            f64::from(spike.center_x) + 0.5,
176            f64::from(spike.height + 1),
177            f64::from(spike.center_z) + 0.5,
178        );
179        let crystal = Arc::new(EndCrystalEntity::new(
180            &vanilla_entities::END_CRYSTAL,
181            next_entity_id(),
182            position,
183            region.weak_world(),
184        ));
185        crystal.set_beam_target(config.crystal_beam_target.map(BlockPos));
186        crystal.set_invulnerable(config.crystal_invulnerable);
187        crystal.snap_to(position, random.next_f32() * 360.0, 0.0);
188        let _ = region.add_fresh_entity(crystal);
189
190        let crystal_pos = BlockPos::from(position);
191        let bedrock = REGISTRY
192            .blocks
193            .get_default_state_id(&vanilla_blocks::BEDROCK);
194        let fire = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::FIRE);
195        let _ = region.set_block_state(crystal_pos.below(), bedrock, UpdateFlags::UPDATE_ALL);
196        let _ = region.set_block_state(crystal_pos, fire, UpdateFlags::UPDATE_ALL);
197    }
198}