steel_core/world/
spawn.rs1use super::{
2 Arc, BlockPos, BlockStateExt, ChunkGenerator, ChunkPos, Direction, HeightmapType, SectionPos,
3 World, is_offset_face_full, vanilla_dimension_types,
4};
5
6const fn chunk_min_block_x(pos: ChunkPos) -> i32 {
7 pos.0.x << 4
8}
9
10const fn chunk_min_block_z(pos: ChunkPos) -> i32 {
11 pos.0.y << 4
12}
13
14const fn chunk_max_block_x(pos: ChunkPos) -> i32 {
15 (pos.0.x << 4) + 15
16}
17
18const fn chunk_max_block_z(pos: ChunkPos) -> i32 {
19 (pos.0.y << 4) + 15
20}
21
22impl World {
23 pub async fn initialize_spawn_if_needed(self: &Arc<Self>) -> Result<(), String> {
25 if self.level_data.read().data().initialized {
26 return Ok(());
27 }
28
29 if self.dimension_type.key != vanilla_dimension_types::OVERWORLD.key {
30 self.level_data.write().data_mut().initialized = true;
31 return Ok(());
32 }
33
34 log::info!("Selecting global world spawn for {}...", self.key);
35
36 let origin = self
37 .chunk_map
38 .world_gen_context
39 .generator
40 .initial_spawn_search_origin();
41 let spawn_chunk = ChunkPos::new(
42 SectionPos::block_to_section_coord(origin.x()),
43 SectionPos::block_to_section_coord(origin.z()),
44 );
45
46 let mut spawn_y = self
47 .chunk_map
48 .world_gen_context
49 .generator
50 .spawn_height(self.get_min_y(), self.get_height());
51 if spawn_y < self.get_min_y() {
52 let x = chunk_min_block_x(spawn_chunk) + 8;
53 let z = chunk_min_block_z(spawn_chunk) + 8;
54 spawn_y = self
55 .height_at(HeightmapType::WorldSurface, x, z)
56 .unwrap_or(self.get_min_y());
57 }
58
59 let mut spawn_pos = BlockPos::new(
60 chunk_min_block_x(spawn_chunk) + 8,
61 spawn_y,
62 chunk_min_block_z(spawn_chunk) + 8,
63 );
64
65 spawn_pos = self
66 .chunk_map
67 .with_full_chunks_in_radius(spawn_chunk, 5, || {
68 self.find_spawn_in_loaded_radius(spawn_chunk)
69 .unwrap_or(spawn_pos)
70 })
71 .await
72 .unwrap_or(spawn_pos);
73
74 {
75 let mut level_data = self.level_data.write();
76 let data = level_data.data_mut();
77 data.set_spawn_pos(spawn_pos);
78 data.spawn.angle = 0.0;
79 data.initialized = true;
80 }
81
82 log::info!("World {} spawn initialized at {spawn_pos:?}", self.key);
83 Ok(())
84 }
85
86 #[expect(
87 clippy::similar_names,
88 reason = "dx_chunk/dz_chunk mirror vanilla's dXChunk/dZChunk"
89 )]
90 pub(super) fn find_spawn_in_loaded_radius(&self, spawn_chunk: ChunkPos) -> Option<BlockPos> {
91 let mut x_chunk_offset = 0;
92 let mut z_chunk_offset = 0;
93 let mut dx_chunk = 0;
94 let mut dz_chunk = -1;
95
96 for _ in 0..(11 * 11) {
97 if (-5..=5).contains(&x_chunk_offset) && (-5..=5).contains(&z_chunk_offset) {
98 let candidate_chunk = ChunkPos::new(
99 spawn_chunk.0.x + x_chunk_offset,
100 spawn_chunk.0.y + z_chunk_offset,
101 );
102 if let Some(candidate) = self.spawn_pos_in_chunk(candidate_chunk) {
103 return Some(candidate);
104 }
105 }
106
107 if x_chunk_offset == z_chunk_offset
108 || (x_chunk_offset < 0 && x_chunk_offset == -z_chunk_offset)
109 || (x_chunk_offset > 0 && x_chunk_offset == 1 - z_chunk_offset)
110 {
111 let old_dx = dx_chunk;
112 dx_chunk = -dz_chunk;
113 dz_chunk = old_dx;
114 }
115
116 x_chunk_offset += dx_chunk;
117 z_chunk_offset += dz_chunk;
118 }
119
120 None
121 }
122
123 pub(super) fn spawn_pos_in_chunk(&self, chunk_pos: ChunkPos) -> Option<BlockPos> {
124 for x in chunk_min_block_x(chunk_pos)..=chunk_max_block_x(chunk_pos) {
125 for z in chunk_min_block_z(chunk_pos)..=chunk_max_block_z(chunk_pos) {
126 if let Some(pos) = self.level_respawn_pos(x, z) {
127 return Some(pos);
128 }
129 }
130 }
131
132 None
133 }
134
135 pub(super) fn level_respawn_pos(&self, x: i32, z: i32) -> Option<BlockPos> {
136 let top_y = if self.dimension_type.has_ceiling {
137 self.chunk_map
138 .world_gen_context
139 .generator
140 .spawn_height(self.get_min_y(), self.get_height())
141 } else {
142 self.vanilla_chunk_height_at(HeightmapType::MotionBlocking, x, z)?
143 };
144
145 if top_y < self.get_min_y() {
146 return None;
147 }
148
149 let surface = self.vanilla_chunk_height_at(HeightmapType::WorldSurface, x, z)?;
150 let ocean_floor = self.vanilla_chunk_height_at(HeightmapType::OceanFloor, x, z)?;
151 if surface <= top_y && surface > ocean_floor {
152 return None;
153 }
154
155 for y in (self.get_min_y()..=top_y + 1).rev() {
156 let pos = BlockPos::new(x, y, z);
157 let state = self.get_block_state(pos);
158 if state.has_fluid() {
159 break;
160 }
161
162 if is_offset_face_full(state.get_collision_shape_at(pos), Direction::Up) {
163 return Some(BlockPos::new(x, y + 1, z));
164 }
165 }
166
167 None
168 }
169
170 pub(crate) fn height_at(&self, heightmap_type: HeightmapType, x: i32, z: i32) -> Option<i32> {
171 let chunk_pos = ChunkPos::new(
172 SectionPos::block_to_section_coord(x),
173 SectionPos::block_to_section_coord(z),
174 );
175 self.chunk_map.with_full_chunk(chunk_pos, |chunk| {
176 chunk.get_height(heightmap_type, (x & 15) as usize, (z & 15) as usize)
177 })
178 }
179
180 pub(super) fn vanilla_chunk_height_at(
181 &self,
182 heightmap_type: HeightmapType,
183 x: i32,
184 z: i32,
185 ) -> Option<i32> {
186 self.height_at(heightmap_type, x, z)
187 .map(|first_available| first_available - 1)
188 }
189
190 pub(super) fn heightmap_pos(&self, heightmap_type: HeightmapType, pos: BlockPos) -> BlockPos {
191 BlockPos::new(
192 pos.x(),
193 self.level_height_at(heightmap_type, pos.x(), pos.z()),
194 pos.z(),
195 )
196 }
197
198 #[must_use]
200 pub(crate) fn adjust_spawn_location(&self, spawn_suggestion: BlockPos) -> BlockPos {
201 self.heightmap_pos(HeightmapType::MotionBlockingNoLeaves, spawn_suggestion)
202 }
203
204 pub(super) fn level_height_at(&self, heightmap_type: HeightmapType, x: i32, z: i32) -> i32 {
205 if !Self::is_in_world_bounds_horizontal(BlockPos::new(x, 0, z)) {
206 return self.sea_level + 1;
207 }
208
209 self.height_at(heightmap_type, x, z)
210 .unwrap_or_else(|| self.get_min_y())
211 }
212}