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