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