steel_core/worldgen/feature/features/
end_spike.rs1use 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}