Skip to main content

steel_core/server/
player_lifecycle.rs

1use std::ptr;
2
3use crate::player::connection::NetworkConnection;
4
5use super::{
6    Arc, CLogin, CSetDefaultSpawnPosition, CommonPlayerSpawnInfo, DVec3, DomainPlayerData,
7    DomainPlayerState, DomainResidenceToken, EnderPearlRestoreJob, Entity, IMMEDIATE_RESPAWN,
8    Identifier, LIMITED_CRAFTING, PersistentEnderPearl, PersistentRootVehicle, Player,
9    PreparedSpawn, REDUCED_DEBUG_INFO, RegistryEntry, RespawnData, RootVehicleRestoreJob, Server,
10    UnpreparedDomainPlayerData, UnpreparedDomainPlayerState, Uuid, World, apply_default_spawn,
11    local_respawn_data_for_world,
12};
13
14pub(super) struct PreparedDomainRestores {
15    pub(super) target_world: Arc<World>,
16    pub(super) root_vehicle: Option<PersistentRootVehicle>,
17    /// Whether the player rides the restored vehicle.
18    pub(super) mount_player: bool,
19    pub(super) ender_pearls: Vec<PreparedEnderPearlRestore>,
20}
21
22pub(super) struct PreparedEnderPearlRestore {
23    pub(super) world: Arc<World>,
24    pub(super) payload: PersistentEnderPearl,
25}
26
27impl Server {
28    pub(super) async fn load_join_domain(&self, player: &Player) -> Result<String, String> {
29        match self
30            .player_data_storage
31            .load_global(player.gameprofile.id())
32            .await
33        {
34            Ok(Some(global)) if self.worlds.has_domain(&global.last_active_domain) => {
35                Ok(global.last_active_domain)
36            }
37            Ok(Some(global)) => {
38                log::warn!(
39                    "Player {} last active domain {} no longer exists, using default domain",
40                    player.gameprofile.name(),
41                    global.last_active_domain
42                );
43                Ok(self.worlds.default_domain().to_owned())
44            }
45            Ok(None) => Ok(self.worlds.default_domain().to_owned()),
46            Err(e) => Err(format!("failed to load global player data: {e}")),
47        }
48    }
49
50    pub(super) async fn load_domain_player_state(
51        &self,
52        player: &Player,
53        target_domain: &str,
54        explicit_target_world: Option<Arc<World>>,
55    ) -> Result<DomainPlayerState, String> {
56        let state = self
57            .load_unprepared_domain_player_state(player, target_domain, explicit_target_world)
58            .await?;
59        self.prepare_domain_player_state(target_domain, state).await
60    }
61
62    pub(in crate::server) async fn load_unprepared_domain_player_state(
63        &self,
64        player: &Player,
65        target_domain: &str,
66        explicit_target_world: Option<Arc<World>>,
67    ) -> Result<UnpreparedDomainPlayerState, String> {
68        let has_explicit_target = explicit_target_world.is_some();
69        let default_world = self
70            .worlds
71            .default_world(target_domain)
72            .cloned()
73            .ok_or_else(|| format!("domain {target_domain} has no default world"))?;
74
75        match self
76            .player_data_storage
77            .load_domain(target_domain, player.gameprofile.id())
78            .await
79        {
80            Ok(Some(saved_data)) => {
81                let saved_world = self.resolve_saved_world(
82                    &saved_data.world,
83                    target_domain,
84                    explicit_target_world.as_ref().map(|world| &world.key),
85                    player.gameprofile.name(),
86                );
87                let (world, data) = if let Some(saved_world) = saved_world {
88                    (
89                        saved_world,
90                        UnpreparedDomainPlayerData::SavedRestored {
91                            data: Box::new(saved_data),
92                        },
93                    )
94                } else {
95                    (
96                        explicit_target_world.unwrap_or(default_world),
97                        UnpreparedDomainPlayerData::SavedWithoutLocation {
98                            data: Box::new(saved_data),
99                        },
100                    )
101                };
102                log::info!("Loaded saved data for player {}", player.gameprofile.name());
103                Ok(UnpreparedDomainPlayerState {
104                    world,
105                    data,
106                    explicit_target: has_explicit_target,
107                })
108            }
109            Ok(None) => {
110                log::debug!(
111                    "No saved data for player {} in domain {}, using defaults",
112                    player.gameprofile.name(),
113                    target_domain
114                );
115                Ok(UnpreparedDomainPlayerState {
116                    world: explicit_target_world.unwrap_or(default_world),
117                    data: UnpreparedDomainPlayerData::FirstVisit,
118                    explicit_target: has_explicit_target,
119                })
120            }
121            Err(e) => Err(format!(
122                "failed to load domain player data for {} in domain {}: {e}",
123                player.gameprofile.name(),
124                target_domain
125            )),
126        }
127    }
128
129    async fn prepare_domain_player_state(
130        &self,
131        target_domain: &str,
132        state: UnpreparedDomainPlayerState,
133    ) -> Result<DomainPlayerState, String> {
134        let UnpreparedDomainPlayerState {
135            mut world,
136            explicit_target,
137            data,
138        } = state;
139        let (data, spawn_position) = match data {
140            UnpreparedDomainPlayerData::SavedRestored { data } => {
141                let spawn_position = DVec3::new(data.pos[0], data.pos[1], data.pos[2]);
142                (DomainPlayerData::SavedRestored { data }, spawn_position)
143            }
144            UnpreparedDomainPlayerData::SavedWithoutLocation { data } => {
145                let (spawn_world, spawn) = self
146                    .prepare_target_spawn(target_domain, explicit_target, &world)
147                    .await?;
148                world = spawn_world;
149                (
150                    DomainPlayerData::SavedWithoutLocation { data, spawn },
151                    spawn.position,
152                )
153            }
154            UnpreparedDomainPlayerData::FirstVisit => {
155                let (spawn_world, spawn) = self
156                    .prepare_target_spawn(target_domain, explicit_target, &world)
157                    .await?;
158                world = spawn_world;
159                (DomainPlayerData::FirstVisit { spawn }, spawn.position)
160            }
161        };
162        let spawn_chunk_request = world.prepare_player_spawn_chunks(spawn_position).await?;
163        Ok(DomainPlayerState {
164            world,
165            data,
166            spawn_chunk_request,
167        })
168    }
169
170    async fn prepare_target_spawn(
171        &self,
172        target_domain: &str,
173        explicit_target_world: bool,
174        world: &Arc<World>,
175    ) -> Result<(Arc<World>, PreparedSpawn), String> {
176        if explicit_target_world {
177            return Ok((Arc::clone(world), Self::prepare_default_spawn(world).await?));
178        }
179
180        let (world, respawn_data) = self.respawn_world_and_data_for_domain(target_domain)?;
181        let spawn = Self::prepare_respawn_spawn(&world, &respawn_data).await?;
182        Ok((world, spawn))
183    }
184
185    async fn prepare_default_spawn(world: &Arc<World>) -> Result<PreparedSpawn, String> {
186        let (spawn, spawn_pos) = {
187            let level_data = world.level_data.read();
188            (
189                level_data.data().spawn.clone(),
190                level_data.data().spawn_pos(),
191            )
192        };
193        let position = world
194            .find_adjusted_shared_spawn_pos(spawn_pos, world.default_gamemode())
195            .await?;
196        Ok(PreparedSpawn {
197            position,
198            rotation: (spawn.angle, 0.0),
199        })
200    }
201
202    async fn prepare_respawn_spawn(
203        world: &Arc<World>,
204        respawn_data: &RespawnData,
205    ) -> Result<PreparedSpawn, String> {
206        let position = world
207            .find_adjusted_shared_spawn_pos(respawn_data.pos(), world.default_gamemode())
208            .await?;
209        Ok(PreparedSpawn {
210            position,
211            rotation: (respawn_data.yaw, respawn_data.pitch),
212        })
213    }
214
215    fn resolve_saved_world(
216        &self,
217        saved_world: &str,
218        target_domain: &str,
219        explicit_target_world: Option<&Identifier>,
220        player_name: &str,
221    ) -> Option<Arc<World>> {
222        let Ok(saved_world_key) = saved_world.parse::<Identifier>() else {
223            log::warn!(
224                "Saved world {saved_world} for player {player_name} is invalid, using target spawn"
225            );
226            return None;
227        };
228        if saved_world_key.namespace.as_ref() != target_domain {
229            log::warn!(
230                "Saved world {saved_world_key} for player {player_name} is outside target domain {target_domain}, using target spawn"
231            );
232            return None;
233        }
234        if let Some(explicit_target_world) = explicit_target_world
235            && explicit_target_world != &saved_world_key
236        {
237            return None;
238        }
239        let Some(saved_world) = self.worlds.get(&saved_world_key) else {
240            log::warn!(
241                "Saved world {saved_world_key} for player {player_name} is missing, using target spawn"
242            );
243            return None;
244        };
245        Some(Arc::clone(saved_world))
246    }
247
248    pub(super) fn apply_domain_player_state(player: &Arc<Player>, state: &DomainPlayerState) {
249        match &state.data {
250            DomainPlayerData::SavedRestored { data } => {
251                data.apply_to_player(player);
252            }
253            DomainPlayerData::SavedWithoutLocation { data, spawn } => {
254                apply_default_spawn(player, &state.world, *spawn);
255                data.apply_to_player_without_location(player);
256            }
257            DomainPlayerData::FirstVisit { spawn } => {
258                player.reset_domain_data_for_first_visit();
259                apply_default_spawn(player, &state.world, *spawn);
260            }
261        }
262    }
263
264    pub(super) fn prepare_domain_restores(
265        &self,
266        player: &Player,
267        state: &DomainPlayerState,
268        mount_player: bool,
269    ) -> PreparedDomainRestores {
270        let target_domain = state.world.domain();
271        let ender_pearls = Self::ender_pearls_to_restore(state)
272            .into_iter()
273            .filter_map(|payload| {
274                self.resolve_pearl_world(&payload.world, target_domain, player)
275                    .map(|world| PreparedEnderPearlRestore { world, payload })
276            })
277            .collect();
278        PreparedDomainRestores {
279            target_world: Arc::clone(&state.world),
280            root_vehicle: Self::root_vehicle_to_restore(state),
281            mount_player,
282            ender_pearls,
283        }
284    }
285
286    pub(super) fn install_domain_restores(
287        player: &Player,
288        residence_token: DomainResidenceToken,
289        restores: &PreparedDomainRestores,
290    ) -> bool {
291        player.install_pending_domain_restores(
292            residence_token,
293            &restores.target_world,
294            restores.root_vehicle.clone(),
295            restores
296                .ender_pearls
297                .iter()
298                .map(|restore| restore.payload.clone())
299                .collect(),
300        )
301    }
302
303    pub(super) fn schedule_domain_restores(
304        &self,
305        player: &Arc<Player>,
306        residence_token: DomainResidenceToken,
307        restores: PreparedDomainRestores,
308    ) {
309        if let Some(root_vehicle) = restores.root_vehicle {
310            if let Some(job) = RootVehicleRestoreJob::new(
311                Arc::clone(player),
312                Arc::clone(&restores.target_world),
313                &root_vehicle,
314                residence_token,
315                restores.mount_player,
316            ) {
317                self.jobs.spawn(job);
318            } else {
319                player.take_matching_pending_root_vehicle(
320                    residence_token,
321                    &restores.target_world,
322                    root_vehicle.attach,
323                    root_vehicle.entity.uuid,
324                );
325            }
326        }
327
328        for restore in restores.ender_pearls {
329            let pearl_uuid = Uuid::from_bytes(restore.payload.entity.uuid);
330            if let Some(job) = EnderPearlRestoreJob::new(
331                Arc::clone(player),
332                restore.world,
333                restore.payload.entity,
334                residence_token,
335            ) {
336                self.jobs.spawn(job);
337            } else {
338                player.discard_pending_ender_pearl(residence_token, pearl_uuid);
339            }
340        }
341    }
342
343    pub(super) fn root_vehicle_to_restore(
344        state: &DomainPlayerState,
345    ) -> Option<PersistentRootVehicle> {
346        match &state.data {
347            DomainPlayerData::SavedRestored { data } => data.root_vehicle.clone(),
348            DomainPlayerData::SavedWithoutLocation { .. } | DomainPlayerData::FirstVisit { .. } => {
349                None
350            }
351        }
352    }
353
354    fn ender_pearls_to_restore(state: &DomainPlayerState) -> Vec<PersistentEnderPearl> {
355        match &state.data {
356            DomainPlayerData::SavedRestored { data }
357            | DomainPlayerData::SavedWithoutLocation { data, .. } => data.ender_pearls.clone(),
358            DomainPlayerData::FirstVisit { .. } => Vec::new(),
359        }
360    }
361
362    pub(super) fn resolve_pearl_world(
363        &self,
364        world_key: &str,
365        expected_domain: &str,
366        player: &Player,
367    ) -> Option<Arc<World>> {
368        let Ok(key) = world_key.parse::<Identifier>() else {
369            log::warn!(
370                "Saved ender pearl world {world_key} for player {} is invalid, skipping",
371                player.gameprofile.name()
372            );
373            return None;
374        };
375        let Some(world) = self.worlds.get(&key) else {
376            log::warn!(
377                "Saved ender pearl world {key} for player {} is missing, skipping",
378                player.gameprofile.name()
379            );
380            return None;
381        };
382        if world.domain() != expected_domain {
383            log::warn!(
384                "Saved ender pearl world {key} belongs to domain {}, not player domain \
385                 {expected_domain}, skipping",
386                world.domain()
387            );
388            return None;
389        }
390        Some(Arc::clone(world))
391    }
392
393    pub(super) fn send_login_packet(&self, player: &Player, world: &World) {
394        let reduced_debug_info = world.get_game_rule(&REDUCED_DEBUG_INFO);
395        let immediate_respawn = world.get_game_rule(&IMMEDIATE_RESPAWN);
396        let do_limited_crafting = world.get_game_rule(&LIMITED_CRAFTING);
397
398        // Get world data
399        let hashed_seed = world.obfuscated_seed();
400
401        player.send_packet(CLogin {
402            player_id: player.id(),
403            hardcore: false,
404            levels: self.worlds.keys().cloned().collect(),
405            max_players: self.config.max_players as i32,
406            chunk_radius: self.config.view_distance.into(),
407            simulation_distance: self.config.simulation_distance.into(),
408            reduced_debug_info,
409            show_death_screen: !immediate_respawn,
410            do_limited_crafting,
411            common_player_spawn_info: CommonPlayerSpawnInfo {
412                dimension_type: world.dimension_type.id() as i32,
413                dimension: world.key.clone(),
414                seed: hashed_seed,
415                game_type: player.game_mode(),
416                previous_game_type: player.previous_game_mode(),
417                is_debug: false,
418                is_flat: world.is_flat,
419                last_death_location: None,
420                portal_cooldown: 0,
421                sea_level: world.sea_level,
422            },
423            online_mode: self.config.online_mode,
424            enforces_secure_chat: self.enforces_secure_chat(),
425        });
426    }
427
428    /// Gets all the players on the server
429    pub fn get_players(&self) -> Vec<Arc<Player>> {
430        let mut players = vec![];
431        self.online_players.iter_players(|_, p: &Arc<Player>| {
432            players.push(Arc::clone(p));
433            true
434        });
435        players
436    }
437
438    /// Returns whether this exact player owns the online session for its UUID.
439    pub(crate) fn owns_online_player(&self, player: &Player) -> bool {
440        self.online_players
441            .get_by_uuid(&player.gameprofile.id())
442            .is_some_and(|online| ptr::eq(online.as_ref(), player))
443    }
444
445    /// Returns the world that owns this exact online player.
446    ///
447    /// The player's stored world pointer intentionally remains unchanged while
448    /// detached domain data is loading, so live membership must also be present
449    /// in that world's player index.
450    pub(crate) fn live_world_for_player(&self, player: &Player) -> Option<Arc<World>> {
451        if !self.owns_online_player(player) {
452            return None;
453        }
454
455        let world = player.get_world();
456        world.contains_player(player).then_some(world)
457    }
458
459    /// Returns the live world in which a player may participate in gameplay commands.
460    ///
461    /// A queued domain switch still has source-world membership until the safe
462    /// point, but gameplay work must stop as soon as that transition owns the
463    /// player. Finalization is live again and therefore remains available.
464    pub(crate) fn command_world_for_player(&self, player: &Player) -> Option<Arc<World>> {
465        if player.connection.closed() || player.domain_switch_blocks_gameplay() {
466            return None;
467        }
468        self.live_world_for_player(player)
469    }
470
471    /// Returns the total number of players currently online across all worlds.
472    #[must_use]
473    pub fn player_count(&self) -> usize {
474        self.online_players.len()
475    }
476
477    /// Returns a sample of up to 12 online players for the server list ping.
478    #[must_use]
479    pub fn player_sample(&self) -> Vec<(String, String)> {
480        const MAX_SAMPLE: usize = 12;
481
482        let players = self.get_players();
483        if players.is_empty() {
484            return vec![];
485        }
486
487        let sample_size = players.len().min(MAX_SAMPLE);
488        // Random starting offset into the player list
489        let offset = if players.len() > sample_size {
490            (rand::random::<u64>() as usize) % (players.len() - sample_size + 1)
491        } else {
492            0
493        };
494
495        let mut sample: Vec<(String, String)> = players[offset..offset + sample_size]
496            .iter()
497            .map(|p| {
498                (
499                    p.gameprofile.name().to_owned(),
500                    p.gameprofile.id().hyphenated().to_string(),
501                )
502            })
503            .collect();
504
505        // Shuffle using Fisher-Yates with random indices
506        for i in (1..sample.len()).rev() {
507            let j = (rand::random::<u64>() as usize) % (i + 1);
508            sample.swap(i, j);
509        }
510
511        sample
512    }
513
514    /// Returns the server default world or if not exists the first world.
515    /// # Panics
516    /// if no world exists on this server crisis is there!
517    pub fn overworld(&self) -> &Arc<World> {
518        self.worlds.server_default_world().unwrap_or_else(|| {
519            self.worlds
520                .values()
521                .next()
522                .expect("At least one world must exist")
523        })
524    }
525
526    /// Resolves the default respawn world and data for a domain.
527    pub fn respawn_world_and_data_for_domain(
528        &self,
529        domain: &str,
530    ) -> Result<(Arc<World>, RespawnData), String> {
531        let default_world = self
532            .worlds
533            .default_world(domain)
534            .cloned()
535            .ok_or_else(|| format!("domain {domain} has no default world"))?;
536        let respawn_data = {
537            let level_data = default_world.level_data.read();
538            level_data.data().respawn_data_or_local(&default_world.key)
539        };
540
541        let Some(target_world) = self
542            .worlds
543            .get(respawn_data.dimension())
544            .filter(|world| world.domain() == domain)
545            .cloned()
546        else {
547            let respawn_data = default_world
548                .world_border_adjusted_respawn_data(local_respawn_data_for_world(&default_world));
549            return Ok((Arc::clone(&default_world), respawn_data));
550        };
551
552        let respawn_data = target_world.world_border_adjusted_respawn_data(respawn_data);
553        Ok((target_world, respawn_data))
554    }
555
556    /// Returns the default respawn data sent to clients in the given domain.
557    pub fn respawn_data_for_domain(&self, domain: &str) -> Result<RespawnData, String> {
558        self.respawn_world_and_data_for_domain(domain)
559            .map(|(_, respawn_data)| respawn_data)
560    }
561
562    /// Sets the default respawn data for the respawn data's domain and broadcasts it.
563    pub fn set_respawn_data(&self, respawn_data: RespawnData) -> Result<(), String> {
564        let domain = respawn_data.dimension().namespace.as_ref();
565        let default_world = self
566            .worlds
567            .default_world(domain)
568            .cloned()
569            .ok_or_else(|| format!("domain {domain} has no default world"))?;
570        let target_world = self
571            .worlds
572            .get(respawn_data.dimension())
573            .filter(|world| world.domain() == domain)
574            .cloned()
575            .ok_or_else(|| {
576                format!(
577                    "respawn dimension {} is not loaded in domain {domain}",
578                    respawn_data.dimension()
579                )
580            })?;
581
582        if Arc::ptr_eq(&default_world, &target_world) {
583            let mut level_data = default_world.level_data.write();
584            let data = level_data.data_mut();
585            data.set_spawn_pos(respawn_data.pos());
586            data.spawn.angle = respawn_data.yaw;
587            data.set_respawn_data(respawn_data.clone());
588        } else {
589            default_world
590                .level_data
591                .write()
592                .data_mut()
593                .set_respawn_data(respawn_data.clone());
594
595            let mut level_data = target_world.level_data.write();
596            let data = level_data.data_mut();
597            data.set_spawn_pos(respawn_data.pos());
598            data.spawn.angle = respawn_data.yaw;
599        }
600
601        let packet = CSetDefaultSpawnPosition {
602            global_pos: respawn_data.global_pos.clone(),
603            yaw: respawn_data.yaw,
604            pitch: respawn_data.pitch,
605        };
606        for world in self
607            .worlds
608            .values()
609            .filter(|world| world.domain() == domain)
610        {
611            world.broadcast_to_all(packet.clone());
612        }
613
614        Ok(())
615    }
616
617    /// Returns the default domain's conventional nether world, if present.
618    pub fn nether(&self) -> Option<&Arc<World>> {
619        let key = Identifier::new(self.worlds.default_domain().to_owned(), "the_nether");
620        self.worlds.get(&key)
621    }
622
623    /// Returns the default domain's conventional end world, if present.
624    pub fn the_end(&self) -> Option<&Arc<World>> {
625        let key = Identifier::new(self.worlds.default_domain().to_owned(), "the_end");
626        self.worlds.get(&key)
627    }
628}