Skip to main content

steel_core/world/
spawn.rs

1use 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    /// Initializes this world's default spawn using vanilla's first-world spawn search.
24    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    /// Mirrors vanilla `Entity.adjustSpawnLocation` for cross-world returns.
199    #[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}