1use std::{ptr, sync::Arc};
3
4use steel_protocol::packets::game::{CGameEvent, GameEventType};
5use steel_registry::{vanilla_custom_stats, vanilla_entities};
6use steel_utils::ChunkPos;
7
8use crate::{
9 entity::{
10 AddEntityError, Entity, EntityOwnership, LivingEntity, NullEntityCallback,
11 PlayerEntityCallback, RemovalReason, SharedEntity,
12 },
13 player::connection::NetworkConnection,
14 player::player_data::PersistentPlayerData,
15 player::player_inventory::MenuRemovalStatus,
16 player::{DomainResidenceToken, Player, ResetReason},
17 world::World,
18};
19
20impl World {
21 #[must_use]
23 pub(crate) fn contains_player(&self, player: &Player) -> bool {
24 self.players
25 .get_by_entity_id(player.id())
26 .is_some_and(|registered| ptr::eq(registered.as_ref(), player))
27 }
28
29 fn loaded_world_memberships(player: &Arc<Player>) -> Vec<Arc<World>> {
30 player.server.upgrade().map_or_else(Vec::new, |server| {
31 server
32 .worlds
33 .values()
34 .filter(|world| world.contains_player(player))
35 .cloned()
36 .collect()
37 })
38 }
39
40 fn reject_duplicate_player_membership(
41 player: &Arc<Player>,
42 target_world: &Arc<World>,
43 operation: &str,
44 ) -> bool {
45 let mut memberships = Self::loaded_world_memberships(player);
46 if target_world.contains_player(player)
47 && !memberships
48 .iter()
49 .any(|world| Arc::ptr_eq(world, target_world))
50 {
51 memberships.push(Arc::clone(target_world));
52 }
53 if memberships.is_empty() {
54 return false;
55 }
56
57 tracing::error!(
58 player = %player.gameprofile.name,
59 target_world = %target_world.key,
60 membership_count = memberships.len(),
61 operation,
62 "Refusing to register a player that already belongs to a loaded world"
63 );
64 player.connection.close();
65 for world in memberships {
66 world.remove_player_for_world_change(player);
67 }
68 true
69 }
70
71 fn take_player_for_removal(&self, player: &Arc<Player>) -> Option<Arc<Player>> {
72 if !self.contains_player(player) {
73 return None;
74 }
75 self.chunk_map.remove_player(player);
76 self.players.remove_player_sync(player)
77 }
78
79 fn attach_player_entity_callback(self: &Arc<Self>, player: &Arc<Player>) {
80 let callback = Arc::new(PlayerEntityCallback::new(player.id(), Arc::downgrade(self)));
81 player.set_level_callback(callback);
82 }
83
84 fn try_register_player_entity(
85 self: &Arc<Self>,
86 player: &Arc<Player>,
87 ) -> Result<(), AddEntityError> {
88 let entity: SharedEntity = Arc::<Player>::clone(player);
89 let lifecycle = self
90 .entity_manager()
91 .add_live_entity(entity, EntityOwnership::External)?;
92 self.attach_player_entity_callback(player);
93 self.apply_entity_lifecycle_changes(lifecycle);
94 Ok(())
95 }
96
97 fn register_player_entity(self: &Arc<Self>, player: &Arc<Player>) {
98 if let Err(error) = self.try_register_player_entity(player) {
99 panic!("failed to register player entity: {error}");
100 }
101 }
102
103 fn unride_player_for_removal(&self, player: &Player, store_root_vehicle: bool) {
104 for passenger in player.passengers() {
105 passenger.stop_riding();
106 self.mark_chunk_dirty(ChunkPos::from_entity_pos(passenger.position()));
107 }
108
109 if store_root_vehicle
110 && let Some(root_vehicle) = player.root_vehicle()
111 && root_vehicle.id() != player.id()
112 && root_vehicle.has_exactly_one_player_passenger()
113 {
114 Self::remove_root_vehicle_tree_stored_with_player(root_vehicle);
115 return;
116 }
117
118 if let Some(vehicle) = player.vehicle() {
119 player.stop_riding();
120 self.mark_chunk_dirty(ChunkPos::from_entity_pos(vehicle.position()));
121 }
122 }
123
124 fn remove_root_vehicle_tree_stored_with_player(entity: SharedEntity) {
125 let passengers = entity.passengers();
126 entity.set_removed(RemovalReason::StoredWithPlayer);
127
128 for passenger in passengers {
129 if passenger.entity_type() == &vanilla_entities::PLAYER {
130 continue;
131 }
132 Self::remove_root_vehicle_tree_stored_with_player(passenger);
133 }
134 }
135
136 pub(crate) fn unregister_player_entity(&self, player: &Player) {
137 let entity_id = player.id();
138 self.remove_entity_from_tracker(entity_id);
139
140 self.entity_manager()
141 .remove_live_entity(entity_id, RemovalReason::ChangedWorld);
142 player.set_level_callback(Arc::new(NullEntityCallback));
143 }
144
145 pub(crate) fn register_respawned_player_entity(self: &Arc<Self>, player: &Arc<Player>) {
146 self.register_player_entity(player);
147 self.chunk_map.update_player_status(player);
148 }
149
150 pub(crate) fn install_respawned_player(
155 self: &Arc<Self>,
156 player: Arc<Player>,
157 expected_old_player: Option<&Arc<Player>>,
158 ) -> bool {
159 let installed = if let Some(expected_old_player) = expected_old_player {
160 self.players
161 .replace_player(expected_old_player, Arc::clone(&player))
162 } else {
163 self.players.insert(Arc::clone(&player))
164 };
165 if !installed {
166 return false;
167 }
168
169 if let Err(error) = self.try_register_player_entity(&player) {
170 let rolled_back = if let Some(expected_old_player) = expected_old_player {
171 self.players
172 .replace_player(&player, Arc::clone(expected_old_player))
173 } else {
174 self.players.remove_player_sync(&player).is_some()
175 };
176 if !rolled_back {
177 tracing::error!(
178 player = %player.gameprofile.name,
179 world = %self.key,
180 "Respawn entity registration failed after world player ownership changed"
181 );
182 }
183 tracing::warn!(
184 player = %player.gameprofile.name,
185 world = %self.key,
186 "Refusing to install respawned player entity: {error}"
187 );
188 return false;
189 }
190
191 self.chunk_map.update_player_status(&player);
192 self.update_sleeping_player_list();
193 player.send_packet(CGameEvent {
194 event: GameEventType::LevelChunksLoadStart,
195 data: 0.0,
196 });
197 true
198 }
199
200 pub(crate) fn add_respawned_player(self: &Arc<Self>, player: Arc<Player>) -> bool {
201 self.install_respawned_player(player, None)
202 }
203
204 pub(crate) fn detach_player_for_disconnect(
208 self: &Arc<Self>,
209 player: Arc<Player>,
210 ) -> (Arc<Player>, String, PersistentPlayerData) {
211 assert_eq!(
212 player.remove_all_menus(),
213 MenuRemovalStatus::Complete,
214 "disconnect menu removal must run at the packet-processing safe point"
215 );
216
217 player.award_custom_stat(&vanilla_custom_stats::LEAVE_GAME);
218 let Some(player) = self.take_player_for_removal(&player) else {
219 let domain = self.domain().to_owned();
222 let player_data = PersistentPlayerData::from_player(&player);
223 player.store_ender_pearls_with_player();
224 return (player, domain, player_data);
225 };
226 let domain = self.domain().to_owned();
227 if player.is_sleeping() {
228 player.stop_sleep_in_bed(true, false);
229 }
230 let player_data = PersistentPlayerData::from_player(&player);
231
232 self.unride_player_for_removal(&player, true);
233 player.store_ender_pearls_with_player();
234 self.unregister_player_entity(&player);
235
236 self.entity_tracker().on_player_leave(&player);
238
239 self.player_area_map.on_player_leave(&player);
240 (player, domain, player_data)
241 }
242
243 pub(crate) fn remove_player_for_world_change(self: &Arc<Self>, player: &Arc<Player>) {
248 let Some(player) = self.take_player_for_removal(player) else {
249 return;
250 };
251 player.award_custom_stat(&vanilla_custom_stats::LEAVE_GAME);
252 self.unride_player_for_removal(&player, false);
253 self.unregister_player_entity(&player);
254 self.entity_tracker().on_player_leave(&player);
255 self.player_area_map.on_player_leave(&player);
256 }
258
259 pub(crate) fn detach_player_for_respawn(
265 self: &Arc<Self>,
266 player: &Arc<Player>,
267 retain_player_map_entry: bool,
268 ) -> bool {
269 if !self.contains_player(player) {
270 return false;
271 }
272 if player.is_sleeping() {
273 player.stop_sleep_in_bed(true, false);
274 }
275
276 let detached_player = if retain_player_map_entry {
277 Arc::clone(player)
278 } else {
279 let Some(detached_player) = self.players.remove_player_sync(player) else {
280 return false;
281 };
282 detached_player
283 };
284
285 self.chunk_map.remove_player(&detached_player);
286 self.unride_player_for_removal(&detached_player, false);
287 self.unregister_player_entity(&detached_player);
288 self.entity_tracker().on_player_leave(&detached_player);
289 self.player_area_map.on_player_leave(&detached_player);
290 self.update_sleeping_player_list();
291 true
292 }
293
294 pub(crate) fn remove_respawned_player(self: &Arc<Self>, player: &Arc<Player>) -> bool {
299 self.detach_player_for_respawn(player, false)
300 }
301
302 pub(crate) fn detach_player_for_domain_switch(
304 self: &Arc<Self>,
305 player: &Arc<Player>,
306 ) -> Option<(PersistentPlayerData, DomainResidenceToken)> {
307 let player = self.take_player_for_removal(player)?;
308
309 player.award_custom_stat(&vanilla_custom_stats::LEAVE_GAME);
310 let player_data = PersistentPlayerData::from_player(&player);
311
312 self.unride_player_for_removal(&player, true);
313 player.store_ender_pearls_with_player();
314 self.unregister_player_entity(&player);
315 self.entity_tracker().on_player_leave(&player);
316 self.player_area_map.on_player_leave(&player);
317 let residence_token = player.advance_domain_residence();
318 Some((player_data, residence_token))
319 }
320
321 #[must_use]
327 pub(crate) fn add_player(self: &Arc<Self>, player: Arc<Player>, _reason: ResetReason) -> bool {
328 if Self::reject_duplicate_player_membership(&player, self, "world change") {
329 return false;
330 }
331 if !self.players.insert(player.clone()) {
332 player.connection.close();
333 return false;
334 }
335
336 self.register_player_entity(&player);
337 self.chunk_map.update_player_status(&player);
338 self.update_sleeping_player_list();
339
340 player.send_packet(CGameEvent {
341 event: GameEventType::LevelChunksLoadStart,
342 data: 0.0,
343 });
344
345 true
346 }
347}