steel_core/player/lifecycle/
spawn_sync.rs1use 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) {
19 self.reset_inner_after(new_world, reason, false, || {});
20 }
21
22 pub(crate) fn reset_after_detached_domain_restore<F>(
24 self: &Arc<Self>,
25 new_world: Arc<World>,
26 restore_state: F,
27 ) where
28 F: FnOnce(),
29 {
30 self.reset_inner_after(new_world, ResetReason::WorldChange, true, restore_state);
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 let data_kept = reason.respawn_data_kept();
82
83 self.send_packet(CRespawn {
84 dimension_type: new_world.dimension_type.id() as i32,
85 dimension_name: new_world.key.clone(),
86 hashed_seed: new_world.obfuscated_seed(),
87 gamemode: self.game_mode() as u8,
88 previous_gamemode: nullable_game_mode_id(self.previous_game_mode()),
89 is_debug: false,
90 is_flat: new_world.is_flat,
91 has_death_location: false,
92 death_dimension_name: None,
93 death_location: None,
94 portal_cooldown_ticks: self.portal_cooldown(),
95 sea_level: new_world.sea_level,
96 data_kept,
97 });
98 }
99 }
100
101 #[must_use]
110 pub(crate) fn spawn(
111 self: &Arc<Self>,
112 position: DVec3,
113 rotation: (f32, f32),
114 reason: ResetReason,
115 ) -> bool {
116 self.spawn_with_velocity(position, rotation, DVec3::ZERO, reason)
117 }
118
119 #[must_use]
120 pub(crate) fn spawn_with_velocity(
121 self: &Arc<Self>,
122 position: DVec3,
123 rotation: (f32, f32),
124 velocity: DVec3,
125 reason: ResetReason,
126 ) -> bool {
127 self.spawn_with_velocity_packet(
128 position,
129 rotation,
130 velocity,
131 reason,
132 position,
133 rotation,
134 velocity,
135 RelativeMovement::NONE,
136 )
137 }
138
139 #[must_use]
140 #[expect(
141 clippy::too_many_arguments,
142 reason = "packet-relative teleports must keep resolved and protocol values separate"
143 )]
144 pub(crate) fn spawn_with_velocity_packet(
145 self: &Arc<Self>,
146 position: DVec3,
147 rotation: (f32, f32),
148 velocity: DVec3,
149 reason: ResetReason,
150 packet_position: DVec3,
151 packet_rotation: (f32, f32),
152 packet_velocity: DVec3,
153 relatives: RelativeMovement,
154 ) -> bool {
155 let world = self.get_world();
156
157 self.base.set_position_local(position);
159 self.set_rotation(rotation);
160 self.set_old_position_to_current();
161 self.movement.lock().reset_for_position_sync(position);
162
163 if let Err(error) = self.teleport_with_velocity_packet(
165 position,
166 velocity,
167 rotation,
168 packet_position,
169 packet_velocity,
170 packet_rotation,
171 relatives,
172 ) {
173 panic!(
174 "failed to synchronize player {} spawn position: {error}",
175 self.id()
176 );
177 }
178 self.reset_flying_ticks();
179
180 self.send_spawn_state_packets(&world);
181
182 self.reset_sent_info();
184
185 self.server().resend_player_context(self);
187 self.send_active_effects_for_self();
188
189 match reason {
191 ResetReason::InitialJoin | ResetReason::WorldChange => {
192 if reason == ResetReason::WorldChange {
193 log::info!(
194 "Player {} changed world to {}",
195 self.gameprofile.name,
196 world.key
197 );
198 }
199 world.add_player(self.clone(), reason)
200 }
201 ResetReason::Respawn | ResetReason::EndCredits => {
202 if world.players.get_by_entity_id(self.id()).is_none() {
203 return world.add_respawned_player(self.clone());
204 }
205
206 world.chunk_map.remove_player(self);
208 world.player_area_map.remove_by_entity_id(self.id());
209 world.entity_tracker().on_player_leave(self.id());
210
211 self.send_packet(CGameEvent {
212 event: GameEventType::LevelChunksLoadStart,
213 data: 0.0,
214 });
215 world.register_respawned_player_entity(self);
216 true
217 }
218 }
219 }
220
221 fn send_spawn_state_packets(&self, world: &World) {
222 self.send_abilities();
223 self.send_packet(CSetHeldSlot {
224 slot: i32::from(self.inventory.lock().get_selected_slot()),
225 });
226 self.send_time_sync(world);
227 self.send_packet(world.initialize_border_packet());
228 self.send_default_spawn_position(world);
229 self.send_weather_sync(world);
230 }
231
232 fn send_time_sync(&self, world: &World) {
233 self.send_packet(world.time_sync_packet());
234 }
235
236 fn send_default_spawn_position(&self, world: &World) {
237 if let Some(server) = self.server.upgrade() {
238 match server.respawn_data_for_domain(world.domain()) {
239 Ok(respawn_data) => {
240 self.send_packet(CSetDefaultSpawnPosition {
241 global_pos: respawn_data.global_pos,
242 yaw: respawn_data.yaw,
243 pitch: respawn_data.pitch,
244 });
245 }
246 Err(error) => {
247 log::error!(
248 "Failed to send default spawn position to player {}: {error}",
249 self.gameprofile.name
250 );
251 }
252 }
253 }
254 }
255
256 fn send_weather_sync(&self, world: &World) {
257 if !world.can_have_weather() || !world.is_raining() {
258 return;
259 }
260
261 let (rain_level, thunder_level) = {
262 let weather = world.weather.lock();
263 (weather.rain_level, weather.thunder_level)
264 };
265
266 self.send_packet(CGameEvent {
267 event: GameEventType::StartRaining,
268 data: 0.0,
269 });
270 self.send_packet(CGameEvent {
271 event: GameEventType::RainLevelChange,
272 data: rain_level,
273 });
274 self.send_packet(CGameEvent {
275 event: GameEventType::ThunderLevelChange,
276 data: thunder_level,
277 });
278 }
279
280 pub(in crate::player) fn passenger_ids_for_packet(entity: &dyn Entity) -> Vec<i32> {
281 entity
282 .passengers()
283 .iter()
284 .map(|passenger| passenger.id())
285 .collect()
286 }
287
288 pub(in crate::player) fn send_mob_effect_sync_packet(&self, packet: MobEffectSyncPacket) {
289 match packet {
290 MobEffectSyncPacket::Update(packet) => self.send_packet(packet),
291 MobEffectSyncPacket::Remove(packet) => self.send_packet(packet),
292 }
293 }
294
295 fn send_active_effects_for_self(&self) {
296 for effect in self.living_base.active_mob_effects() {
297 self.send_mob_effect_sync_packet(
298 MobEffectSyncChange::Update {
299 effect,
300 blend_for_self: false,
301 }
302 .packet(self.id(), true),
303 );
304 }
305 }
306
307 pub(in crate::player) fn send_active_effects_for_vehicle(&self, vehicle: &dyn Entity) {
308 let Some(living_vehicle) = vehicle.as_living_entity() else {
309 return;
310 };
311 for effect in living_vehicle.active_mob_effects() {
312 self.send_mob_effect_sync_packet(
313 MobEffectSyncChange::Update {
314 effect,
315 blend_for_self: false,
316 }
317 .packet(vehicle.id(), false),
318 );
319 }
320 }
321
322 pub(crate) fn send_restored_vehicle_mount_sync(&self, vehicle: &dyn Entity) {
323 self.send_active_effects_for_vehicle(vehicle);
324 self.send_packet(CSetPassengers::new(
325 vehicle.id(),
326 Self::passenger_ids_for_packet(vehicle),
327 ));
328 }
329
330 pub(in crate::player) fn remove_active_effects_for_vehicle(&self, vehicle: &dyn Entity) {
331 let Some(living_vehicle) = vehicle.as_living_entity() else {
332 return;
333 };
334 for effect in living_vehicle.active_mob_effects() {
335 self.send_mob_effect_sync_packet(
336 MobEffectSyncChange::Remove {
337 effect: effect.effect(),
338 }
339 .packet(vehicle.id(), false),
340 );
341 }
342 }
343}
344
345pub(in crate::player) fn nullable_game_mode_id(game_mode: Option<GameType>) -> i8 {
346 game_mode.map_or(-1, |game_mode| game_mode as i8)
347}