steel_core/world/
world_entities.rs1use std::{ptr, sync::Arc};
3
4use steel_protocol::packets::game::{CGameEvent, GameEventType};
5use steel_registry::vanilla_entities;
6use steel_utils::ChunkPos;
7
8use crate::{
9 entity::{
10 Entity, EntityOwnership, NullEntityCallback, PlayerEntityCallback, RemovalReason,
11 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 register_player_entity(self: &Arc<Self>, player: &Arc<Player>) {
85 self.attach_player_entity_callback(player);
86
87 let entity: SharedEntity = player.clone();
88 let lifecycle = match self
89 .entity_manager()
90 .add_live_entity(entity.clone(), EntityOwnership::External)
91 {
92 Ok(lifecycle) => lifecycle,
93 Err(error) => panic!("failed to register player entity: {error}"),
94 };
95 self.apply_entity_lifecycle_changes(lifecycle);
96 }
97
98 fn unride_player_for_removal(&self, player: &Player, store_root_vehicle: bool) {
99 for passenger in player.passengers() {
100 passenger.stop_riding();
101 self.mark_chunk_dirty(ChunkPos::from_entity_pos(passenger.position()));
102 }
103
104 if store_root_vehicle
105 && let Some(root_vehicle) = player.root_vehicle()
106 && root_vehicle.id() != player.id()
107 && root_vehicle.has_exactly_one_player_passenger()
108 {
109 Self::remove_root_vehicle_tree_stored_with_player(root_vehicle);
110 return;
111 }
112
113 if let Some(vehicle) = player.vehicle() {
114 player.stop_riding();
115 self.mark_chunk_dirty(ChunkPos::from_entity_pos(vehicle.position()));
116 }
117 }
118
119 fn remove_root_vehicle_tree_stored_with_player(entity: SharedEntity) {
120 let passengers = entity.passengers();
121 entity.set_removed(RemovalReason::StoredWithPlayer);
122
123 for passenger in passengers {
124 if passenger.entity_type() == &vanilla_entities::PLAYER {
125 continue;
126 }
127 Self::remove_root_vehicle_tree_stored_with_player(passenger);
128 }
129 }
130
131 pub(crate) fn unregister_player_entity(&self, player: &Player) {
132 let entity_id = player.id();
133 self.remove_entity_from_tracker(entity_id);
134
135 self.entity_manager()
136 .remove_live_entity(entity_id, RemovalReason::ChangedWorld);
137 player.set_level_callback(Arc::new(NullEntityCallback));
138 }
139
140 pub(crate) fn register_respawned_player_entity(self: &Arc<Self>, player: &Arc<Player>) {
141 self.register_player_entity(player);
142 self.chunk_map.update_player_status(player);
143 }
144
145 pub(crate) fn add_respawned_player(self: &Arc<Self>, player: Arc<Player>) -> bool {
146 if Self::reject_duplicate_player_membership(&player, self, "respawn") {
147 return false;
148 }
149 if !self.players.insert(player.clone()) {
150 player.connection.close();
151 return false;
152 }
153
154 self.register_respawned_player_entity(&player);
155 player.send_packet(CGameEvent {
156 event: GameEventType::LevelChunksLoadStart,
157 data: 0.0,
158 });
159 true
160 }
161
162 pub(crate) fn detach_player_for_disconnect(
166 self: &Arc<Self>,
167 player: Arc<Player>,
168 ) -> (Arc<Player>, String, PersistentPlayerData) {
169 assert_eq!(
170 player.remove_all_menus(),
171 MenuRemovalStatus::Complete,
172 "disconnect menu removal must run at the packet-processing safe point"
173 );
174
175 let Some(player) = self.take_player_for_removal(&player) else {
176 let domain = self.domain().to_owned();
179 let player_data = PersistentPlayerData::from_player(&player);
180 player.store_ender_pearls_with_player();
181 return (player, domain, player_data);
182 };
183 let entity_id = player.id();
184 let domain = self.domain().to_owned();
185 let player_data = PersistentPlayerData::from_player(&player);
186
187 self.unride_player_for_removal(&player, true);
188 player.store_ender_pearls_with_player();
189 self.unregister_player_entity(&player);
190
191 self.entity_tracker().on_player_leave(entity_id);
193
194 self.player_area_map.on_player_leave(&player);
195
196 (player, domain, player_data)
197 }
198
199 pub(crate) fn remove_player_for_world_change(self: &Arc<Self>, player: &Arc<Player>) {
204 let Some(player) = self.take_player_for_removal(player) else {
205 return;
206 };
207 let entity_id = player.id();
208
209 self.unride_player_for_removal(&player, false);
210 self.unregister_player_entity(&player);
211 self.entity_tracker().on_player_leave(entity_id);
212 self.player_area_map.on_player_leave(&player);
213 }
215
216 pub(crate) fn detach_player_for_domain_switch(
218 self: &Arc<Self>,
219 player: &Arc<Player>,
220 ) -> Option<(PersistentPlayerData, DomainResidenceToken)> {
221 let player = self.take_player_for_removal(player)?;
222 let entity_id = player.id();
223 let player_data = PersistentPlayerData::from_player(&player);
224
225 self.unride_player_for_removal(&player, true);
226 player.store_ender_pearls_with_player();
227 self.unregister_player_entity(&player);
228 self.entity_tracker().on_player_leave(entity_id);
229 self.player_area_map.on_player_leave(&player);
230 let residence_token = player.advance_domain_residence();
231 Some((player_data, residence_token))
232 }
233
234 #[must_use]
240 pub(crate) fn add_player(self: &Arc<Self>, player: Arc<Player>, _reason: ResetReason) -> bool {
241 if Self::reject_duplicate_player_membership(&player, self, "world change") {
242 return false;
243 }
244 if !self.players.insert(player.clone()) {
245 player.connection.close();
246 return false;
247 }
248
249 self.register_player_entity(&player);
250 self.chunk_map.update_player_status(&player);
251
252 player.send_packet(CGameEvent {
253 event: GameEventType::LevelChunksLoadStart,
254 data: 0.0,
255 });
256
257 player.send_packet(CGameEvent {
258 event: GameEventType::ChangeGameMode,
259 data: player.game_mode().into(),
260 });
261
262 true
263 }
264}