1use super::{
2 Arc, BlockBreakingManager, CContainerClose, CGameEvent, CRespawn, CSetDefaultSpawnPosition,
3 CSetHeldSlot, CSetPassengers, DVec3, Entity, GameEventType, GameType, MenuRemovalStatus,
4 MobEffectSyncChange, MobEffectSyncPacket, Player, RegistryEntry, RelativeMovement, ResetReason,
5 World,
6};
7
8impl Player {
9 pub(crate) fn reset(self: &Arc<Self>, new_world: Arc<World>, reason: ResetReason) {
15 self.reset_inner_after(new_world, reason, false, || {});
16 }
17
18 pub(crate) fn reset_after_detached_domain_restore<F>(
20 self: &Arc<Self>,
21 new_world: Arc<World>,
22 restore_state: F,
23 ) where
24 F: FnOnce(),
25 {
26 self.reset_inner_after(new_world, ResetReason::WorldChange, true, || {
27 restore_state();
28 self.living_base.clear_last_damage_source();
30 });
31 }
32
33 fn reset_inner_after<F>(
34 self: &Arc<Self>,
35 new_world: Arc<World>,
36 reason: ResetReason,
37 source_world_detached: bool,
38 restore_state: F,
39 ) where
40 F: FnOnce(),
41 {
42 if reason != ResetReason::InitialJoin {
43 assert_eq!(
44 self.remove_all_menus(),
45 MenuRemovalStatus::Complete,
46 "player reset menu removal must run outside a menu callback"
47 );
48 }
49 if matches!(reason, ResetReason::Respawn | ResetReason::EndCredits) {
50 self.inventory_menu
52 .lock()
53 .behavior_mut()
54 .reset_quick_craft();
55 }
56
57 let old_world = self.get_world();
58 let switching_worlds = !Arc::ptr_eq(&old_world, &new_world);
59
60 if switching_worlds {
61 self.send_packet(CContainerClose { container_id: 0 });
62 if !source_world_detached {
63 old_world.remove_player_for_world_change(self);
64 }
65 self.set_world(new_world.clone());
66 } else if !source_world_detached {
67 old_world.chunk_map.remove_player(self);
68 }
69
70 self.set_client_loaded(false);
71 self.set_velocity(DVec3::ZERO);
72 self.movement.lock().reset_last_known_client_movement();
73 self.set_on_ground(false);
74 self.reset_entity_state();
75 *self.block_breaking.lock() = BlockBreakingManager::new();
76
77 restore_state();
78
79 if reason != ResetReason::InitialJoin {
80 self.send_respawn_packet(&new_world, reason);
81 }
82 }
83
84 pub(crate) fn prepare_respawn_replacement(&self, reason: ResetReason) {
86 debug_assert!(matches!(
87 reason,
88 ResetReason::Respawn | ResetReason::EndCredits
89 ));
90 self.set_client_loaded(false);
91 self.send_respawn_packet(&self.get_world(), reason);
92 }
93
94 fn send_respawn_packet(&self, world: &World, reason: ResetReason) {
95 let data_kept = reason.respawn_data_kept();
97
98 self.send_packet(CRespawn {
99 dimension_type: world.dimension_type.id() as i32,
100 dimension_name: world.key.clone(),
101 hashed_seed: world.obfuscated_seed(),
102 gamemode: self.game_mode() as u8,
103 previous_gamemode: nullable_game_mode_id(self.previous_game_mode()),
104 is_debug: false,
105 is_flat: world.is_flat,
106 has_death_location: false,
107 death_dimension_name: None,
108 death_location: None,
109 portal_cooldown_ticks: self.portal_cooldown(),
110 sea_level: world.sea_level,
111 data_kept,
112 });
113 }
114
115 #[must_use]
124 pub(crate) fn spawn(
125 self: &Arc<Self>,
126 position: DVec3,
127 rotation: (f32, f32),
128 reason: ResetReason,
129 ) -> bool {
130 self.spawn_with_velocity(position, rotation, DVec3::ZERO, reason)
131 }
132
133 #[must_use]
134 pub(crate) fn spawn_with_velocity(
135 self: &Arc<Self>,
136 position: DVec3,
137 rotation: (f32, f32),
138 velocity: DVec3,
139 reason: ResetReason,
140 ) -> bool {
141 self.spawn_with_velocity_packet(
142 position,
143 rotation,
144 velocity,
145 reason,
146 position,
147 rotation,
148 velocity,
149 RelativeMovement::NONE,
150 )
151 }
152
153 #[must_use]
154 #[expect(
155 clippy::too_many_arguments,
156 reason = "packet-relative teleports must keep resolved and protocol values separate"
157 )]
158 pub(crate) fn spawn_with_velocity_packet(
159 self: &Arc<Self>,
160 position: DVec3,
161 rotation: (f32, f32),
162 velocity: DVec3,
163 reason: ResetReason,
164 packet_position: DVec3,
165 packet_rotation: (f32, f32),
166 packet_velocity: DVec3,
167 relatives: RelativeMovement,
168 ) -> bool {
169 let world = self.synchronize_spawn_with_velocity_packet(
170 position,
171 velocity,
172 rotation,
173 packet_position,
174 packet_velocity,
175 packet_rotation,
176 relatives,
177 true,
178 );
179
180 match reason {
182 ResetReason::InitialJoin | ResetReason::WorldChange => {
183 if reason == ResetReason::WorldChange {
184 log::info!(
185 "Player {} changed world to {}",
186 self.gameprofile.name,
187 world.key
188 );
189 }
190 world.add_player(self.clone(), reason)
191 }
192 ResetReason::Respawn | ResetReason::EndCredits => {
193 if world.players.get_by_entity_id(self.id()).is_none() {
194 return world.add_respawned_player(self.clone());
195 }
196
197 world.chunk_map.remove_player(self);
199 world.player_area_map.remove_by_entity_id(self.id());
200 world.entity_tracker().on_player_leave(self);
201
202 self.send_packet(CGameEvent {
203 event: GameEventType::LevelChunksLoadStart,
204 data: 0.0,
205 });
206 world.register_respawned_player_entity(self);
207 true
208 }
209 }
210 }
211
212 pub(crate) fn synchronize_respawn_replacement(
215 self: &Arc<Self>,
216 position: DVec3,
217 rotation: (f32, f32),
218 ) {
219 self.synchronize_spawn_with_velocity_packet(
220 position,
221 DVec3::ZERO,
222 rotation,
223 position,
224 DVec3::ZERO,
225 rotation,
226 RelativeMovement::NONE,
227 false,
228 );
229 }
230
231 #[expect(
232 clippy::too_many_arguments,
233 reason = "packet-relative teleports must keep resolved and protocol values separate"
234 )]
235 fn synchronize_spawn_with_velocity_packet(
236 self: &Arc<Self>,
237 position: DVec3,
238 velocity: DVec3,
239 rotation: (f32, f32),
240 packet_position: DVec3,
241 packet_velocity: DVec3,
242 packet_rotation: (f32, f32),
243 relatives: RelativeMovement,
244 resend_player_context: bool,
245 ) -> Arc<World> {
246 let world = self.get_world();
247
248 self.base.set_position_local(position);
249 self.set_rotation(rotation);
250 self.set_old_position_to_current();
251 self.movement.lock().reset_for_position_sync(position);
252
253 if let Err(error) = self.teleport_with_velocity_packet(
254 position,
255 velocity,
256 rotation,
257 packet_position,
258 packet_velocity,
259 packet_rotation,
260 relatives,
261 ) {
262 panic!(
263 "failed to synchronize player {} spawn position: {error}",
264 self.id()
265 );
266 }
267 self.reset_flying_ticks();
268 self.send_spawn_state_packets(&world);
269 self.reset_sent_info();
270 if resend_player_context {
271 self.server().resend_player_context(self);
272 }
273 self.send_active_effects_for_self();
274 world
275 }
276
277 fn send_spawn_state_packets(&self, world: &World) {
278 self.send_abilities();
279 self.send_packet(CSetHeldSlot {
280 slot: i32::from(self.inventory.lock().get_selected_slot()),
281 });
282 self.send_time_sync(world);
283 self.send_packet(world.initialize_border_packet());
284 self.send_default_spawn_position(world);
285 self.send_weather_sync(world);
286 }
287
288 fn send_time_sync(&self, world: &World) {
289 self.send_packet(world.time_sync_packet());
290 }
291
292 fn send_default_spawn_position(&self, world: &World) {
293 if let Some(server) = self.server.upgrade() {
294 match server.respawn_data_for_domain(world.domain()) {
295 Ok(respawn_data) => {
296 self.send_packet(CSetDefaultSpawnPosition {
297 global_pos: respawn_data.global_pos,
298 yaw: respawn_data.yaw,
299 pitch: respawn_data.pitch,
300 });
301 }
302 Err(error) => {
303 log::error!(
304 "Failed to send default spawn position to player {}: {error}",
305 self.gameprofile.name
306 );
307 }
308 }
309 }
310 }
311
312 fn send_weather_sync(&self, world: &World) {
313 if !world.can_have_weather() || !world.is_raining() {
314 return;
315 }
316
317 let (rain_level, thunder_level) = {
318 let weather = world.weather.lock();
319 (weather.rain_level, weather.thunder_level)
320 };
321
322 self.send_packet(CGameEvent {
323 event: GameEventType::StartRaining,
324 data: 0.0,
325 });
326 self.send_packet(CGameEvent {
327 event: GameEventType::RainLevelChange,
328 data: rain_level,
329 });
330 self.send_packet(CGameEvent {
331 event: GameEventType::ThunderLevelChange,
332 data: thunder_level,
333 });
334 }
335
336 pub(in crate::player) fn passenger_ids_for_packet(entity: &dyn Entity) -> Vec<i32> {
337 entity
338 .passengers()
339 .iter()
340 .map(|passenger| passenger.id())
341 .collect()
342 }
343
344 pub(in crate::player) fn send_mob_effect_sync_packet(&self, packet: MobEffectSyncPacket) {
345 match packet {
346 MobEffectSyncPacket::Update(packet) => self.send_packet(packet),
347 MobEffectSyncPacket::Remove(packet) => self.send_packet(packet),
348 }
349 }
350
351 fn send_active_effects_for_self(&self) {
352 for effect in self.living_base.active_mob_effects() {
353 self.send_mob_effect_sync_packet(
354 MobEffectSyncChange::Update {
355 effect,
356 blend_for_self: false,
357 }
358 .packet(self.id(), true),
359 );
360 }
361 }
362
363 pub(in crate::player) fn send_active_effects_for_vehicle(&self, vehicle: &dyn Entity) {
364 let Some(living_vehicle) = vehicle.as_living_entity() else {
365 return;
366 };
367 for effect in living_vehicle.active_mob_effects() {
368 self.send_mob_effect_sync_packet(
369 MobEffectSyncChange::Update {
370 effect,
371 blend_for_self: false,
372 }
373 .packet(vehicle.id(), false),
374 );
375 }
376 }
377
378 pub(crate) fn send_restored_vehicle_mount_sync(&self, vehicle: &dyn Entity) {
379 self.send_active_effects_for_vehicle(vehicle);
380 self.send_packet(CSetPassengers::new(
381 vehicle.id(),
382 Self::passenger_ids_for_packet(vehicle),
383 ));
384 }
385
386 pub(in crate::player) fn remove_active_effects_for_vehicle(&self, vehicle: &dyn Entity) {
387 let Some(living_vehicle) = vehicle.as_living_entity() else {
388 return;
389 };
390 for effect in living_vehicle.active_mob_effects() {
391 self.send_mob_effect_sync_packet(
392 MobEffectSyncChange::Remove {
393 effect: effect.effect(),
394 }
395 .packet(vehicle.id(), false),
396 );
397 }
398 }
399}
400
401pub(in crate::player) fn nullable_game_mode_id(game_mode: Option<GameType>) -> i8 {
402 game_mode.map_or(-1, |game_mode| game_mode as i8)
403}