steel_core/player/lifecycle/
respawn.rs1use std::ptr;
2
3use super::*;
4use crate::entity::PendingWorldChangeToken;
5use crate::player::connection::NetworkConnection as _;
6use crate::player::player_data::PersistentPlayerData;
7
8#[derive(Clone, Copy)]
9struct DeathRespawnSpawn {
10 position: DVec3,
11 rotation: (f32, f32),
12}
13
14struct PlayerRespawnJob {
15 player: Arc<Player>,
16 source_world: Arc<World>,
17 target_world: Arc<World>,
18 rotation: (f32, f32),
19 kind: RespawnRequestKind,
20 pending_token: PendingWorldChangeToken,
21 phase: PlayerRespawnJobPhase,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25enum RespawnRequestKind {
26 Death,
27 EndCredits,
28}
29
30enum PlayerRespawnJobPhase {
31 Searching(PlayerSpawnSearch),
32 LoadingSpawnChunks {
33 spawn: DeathRespawnSpawn,
34 request: ChunkRequestHandle,
35 },
36}
37
38impl PlayerRespawnJob {
39 fn new(
40 player: Arc<Player>,
41 source_world: Arc<World>,
42 target_world: Arc<World>,
43 respawn_data: RespawnData,
44 kind: RespawnRequestKind,
45 pending_token: PendingWorldChangeToken,
46 ) -> Result<Self, String> {
47 let search = PlayerSpawnSearch::new(
48 &target_world,
49 respawn_data.pos(),
50 target_world.default_gamemode,
51 )?;
52 Ok(Self {
53 player,
54 source_world,
55 target_world,
56 rotation: (respawn_data.yaw, respawn_data.pitch),
57 kind,
58 pending_token,
59 phase: PlayerRespawnJobPhase::Searching(search),
60 })
61 }
62
63 fn still_valid(&self) -> bool {
64 !self.player.connection.closed()
65 && self
66 .player
67 .is_respawn_transition_pending(self.pending_token)
68 && match self.kind {
69 RespawnRequestKind::Death => {
70 self.source_world.contains_player(&self.player)
71 && Player::should_process_respawn(self.player.get_health())
72 }
73 RespawnRequestKind::EndCredits => {
74 Arc::ptr_eq(&self.player.get_world(), &self.source_world)
75 && self.player.has_won_game()
76 }
77 }
78 }
79
80 fn finish_pending(&self) {
81 self.player.finish_respawn_request(self.pending_token);
82 }
83}
84
85impl ServerJob for PlayerRespawnJob {
86 fn poll(&mut self, _context: &mut ServerJobContext) -> JobPoll {
87 if !self.still_valid() {
88 self.finish_pending();
89 return JobPoll::Finished;
90 }
91
92 loop {
93 match &mut self.phase {
94 PlayerRespawnJobPhase::Searching(search) => {
95 match search.poll_with_ready_candidate_budget(
96 &self.target_world,
97 RESPAWN_SEARCH_READY_CANDIDATE_BUDGET,
98 ) {
99 PlayerSpawnSearchPoll::Pending => return JobPoll::Pending,
100 PlayerSpawnSearchPoll::Cancelled => {
101 self.finish_pending();
102 return JobPoll::Finished;
103 }
104 PlayerSpawnSearchPoll::Ready(position) => {
105 let spawn = DeathRespawnSpawn {
106 position,
107 rotation: self.rotation,
108 };
109 let request = self.target_world.request_player_spawn_chunks(position);
110 self.phase =
111 PlayerRespawnJobPhase::LoadingSpawnChunks { spawn, request };
112 }
113 }
114 }
115 PlayerRespawnJobPhase::LoadingSpawnChunks { spawn, request } => {
116 match request.poll() {
117 ChunkRequestState::Pending { .. } => return JobPoll::Pending,
118 ChunkRequestState::Cancelled => {
119 self.finish_pending();
120 return JobPoll::Finished;
121 }
122 ChunkRequestState::Ready => {
123 if request.ready_chunks().is_none() {
124 return JobPoll::Pending;
125 }
126
127 match self.kind {
128 RespawnRequestKind::Death => self.player.finish_death_respawn(
129 &self.source_world,
130 &self.target_world,
131 *spawn,
132 self.pending_token,
133 ),
134 RespawnRequestKind::EndCredits => {
135 self.player.finish_end_credits_respawn(
136 &self.source_world,
137 &self.target_world,
138 *spawn,
139 self.pending_token,
140 );
141 }
142 }
143 return JobPoll::Finished;
144 }
145 }
146 }
147 }
148 }
149 }
150
151 fn cancel(&mut self) {
152 self.finish_pending();
153 }
154}
155
156impl Player {
157 fn begin_respawn_request(&self) -> Option<PendingWorldChangeToken> {
158 let token = self.base.begin_pending_player_respawn()?;
159 if self.begin_respawn_transition(token) {
160 return Some(token);
161 }
162
163 self.finish_pending_world_change(token);
164 None
165 }
166
167 fn finish_respawn_request(&self, token: PendingWorldChangeToken) {
168 self.finish_respawn_transition(token);
169 self.finish_pending_world_change(token);
170 }
171
172 pub fn respawn(&self) {
174 let health = self.get_health();
175 if !Self::should_process_respawn(health) {
176 return;
177 }
178
179 let source_world = self.get_world();
180 let Some(player_arc) = source_world.players.get_by_entity_id(self.id()) else {
181 return;
182 };
183 if !ptr::eq(player_arc.as_ref(), self) {
184 return;
185 }
186 let Some(pending_token) = self.begin_respawn_request() else {
187 if self.is_world_change_pending() {
188 self.defer_death_respawn();
189 }
190 return;
191 };
192
193 let Some(server) = self.server.upgrade() else {
194 self.finish_respawn_request(pending_token);
195 log::error!(
196 "Failed to schedule respawn for player {}: server is gone",
197 self.gameprofile.name
198 );
199 return;
200 };
201 let (target_world, respawn_data) =
202 match server.respawn_world_and_data_for_domain(source_world.domain()) {
203 Ok(resolved) => resolved,
204 Err(error) => {
205 self.finish_respawn_request(pending_token);
206 log::error!(
207 "Failed to schedule respawn for player {}: {error}",
208 self.gameprofile.name
209 );
210 return;
211 }
212 };
213
214 match PlayerRespawnJob::new(
215 player_arc,
216 source_world,
217 target_world,
218 respawn_data,
219 RespawnRequestKind::Death,
220 pending_token,
221 ) {
222 Ok(job) => server.jobs.spawn(job),
223 Err(error) => {
224 self.finish_respawn_request(pending_token);
225 log::error!(
226 "Failed to schedule respawn for player {}: {error}",
227 self.gameprofile.name
228 );
229 }
230 }
231 }
232
233 fn finish_death_respawn(
234 self: &Arc<Self>,
235 source_world: &Arc<World>,
236 target_world: &Arc<World>,
237 spawn: DeathRespawnSpawn,
238 pending_token: PendingWorldChangeToken,
239 ) {
240 if self.connection.closed()
241 || !self.is_respawn_transition_pending(pending_token)
242 || !source_world.contains_player(self)
243 || !Self::should_process_respawn(self.get_health())
244 {
245 self.finish_respawn_request(pending_token);
246 return;
247 }
248
249 let was_removed = self.base.clear_removed();
250 self.reset_state_for_death_respawn_during_world_change(pending_token);
251
252 if !was_removed && Arc::ptr_eq(source_world, target_world) {
255 source_world.unregister_player_entity(self);
256 }
257
258 self.reset(target_world.clone(), ResetReason::Respawn);
260
261 self.send_difficulty();
262
263 let loses_inventory =
265 !target_world.get_game_rule(&KEEP_INVENTORY) && self.game_mode() != GameType::Spectator;
266 {
267 let mut experience = self.experience.lock();
268 if loses_inventory {
269 experience.clear();
271 }
272 experience.dirty = true;
274 }
275 if loses_inventory {
276 self.set_score(0);
277 }
278
279 if self.spawn(spawn.position, spawn.rotation, ResetReason::Respawn) {
283 self.finish_respawn_request(pending_token);
284 return;
285 }
286
287 self.finish_failed_respawn(target_world, pending_token);
288 }
289
290 fn finish_end_credits_respawn(
291 self: &Arc<Self>,
292 source_world: &Arc<World>,
293 target_world: &Arc<World>,
294 spawn: DeathRespawnSpawn,
295 pending_token: PendingWorldChangeToken,
296 ) {
297 if self.connection.closed()
298 || !self.is_respawn_transition_pending(pending_token)
299 || !Arc::ptr_eq(&self.get_world(), source_world)
300 || !self.has_won_game()
301 {
302 self.finish_respawn_request(pending_token);
303 return;
304 }
305
306 self.set_won_game(false);
307 self.reset(target_world.clone(), ResetReason::EndCredits);
308 self.send_difficulty();
309 self.experience.lock().dirty = true;
310 if self.spawn(spawn.position, spawn.rotation, ResetReason::EndCredits) {
311 self.finish_respawn_request(pending_token);
312 return;
313 }
314
315 self.finish_failed_respawn(target_world, pending_token);
316 }
317
318 fn finish_failed_respawn(
319 self: &Arc<Self>,
320 target_world: &Arc<World>,
321 pending_token: PendingWorldChangeToken,
322 ) {
323 let Some(server) = self.server.upgrade() else {
324 self.finish_respawn_request(pending_token);
325 self.cleanup();
326 return;
327 };
328 let player_data = Arc::new(PersistentPlayerData::from_player(self));
329 server.queue_detached_player_disconnect(
330 Arc::clone(self),
331 target_world.domain().to_owned(),
332 player_data,
333 pending_token,
334 );
335 }
336
337 #[cfg(test)]
338 pub(in crate::player) fn reset_state_for_death_respawn(&self) {
339 self.reset_state_for_death_respawn_inner(None);
340 }
341
342 fn reset_state_for_death_respawn_during_world_change(
343 &self,
344 pending_token: PendingWorldChangeToken,
345 ) {
346 self.reset_state_for_death_respawn_inner(Some(pending_token));
347 }
348
349 fn reset_state_for_death_respawn_inner(&self, pending_token: Option<PendingWorldChangeToken>) {
350 assert_eq!(
351 self.remove_all_menus_with_disposition(MenuItemDisposition::Drop),
352 MenuRemovalStatus::Complete,
353 "death respawn menu cleanup must run outside a menu callback"
354 );
355 self.detach_relationships_for_respawn();
356
357 self.attributes().lock().remove_all_transient();
358 self.living_base.reset_for_player_respawn();
359 if let Some(pending_token) = pending_token {
360 self.base.reset_for_player_respawn_during_world_change(
361 Self::dimensions_for_pose(EntityPose::Standing),
362 pending_token,
363 );
364 } else {
365 self.base
366 .reset_for_player_respawn(Self::dimensions_for_pose(EntityPose::Standing));
367 }
368
369 self.set_health(self.get_max_health());
370 self.set_pose(EntityPose::Standing);
371 self.reset_entity_state();
372 self.sync_base_entity_data();
373 self.update_dirty_mob_effect_entity_data();
374
375 *self.food_data.lock() = FoodData::new();
376 *self.block_breaking.lock() = BlockBreakingManager::new();
377 *self.teleport_state.lock() = TeleportState::new();
378 *self.tick_state.lock() = PlayerTickState::new();
379 *self.last_item_in_main_hand.lock() = ItemStack::empty();
380 self.health_sync.lock().reset_for_respawn();
381 self.clear_pending_root_vehicle();
382 self.movement.lock().reset_last_known_client_movement();
383 }
384
385 fn detach_relationships_for_respawn(&self) {
386 for passenger in self.passengers() {
387 passenger.stop_riding();
388 }
389 self.stop_riding();
390 self.base.set_boarding_cooldown(0);
391 }
392
393 pub fn handle_client_command(self: &Arc<Self>, action: ClientCommandAction) {
395 match action {
396 ClientCommandAction::PerformRespawn => {
397 if self.has_won_game() {
398 self.respawn_after_end_credits();
399 } else {
400 self.respawn();
401 }
402 }
403 ClientCommandAction::RequestStats | ClientCommandAction::RequestGameRuleValues => {
404 }
406 }
407 }
408
409 #[must_use]
412 pub(in crate::player) const fn should_process_respawn(health: f32) -> bool {
413 health <= 0.0
414 }
415
416 #[must_use]
418 pub fn has_seen_credits(&self) -> bool {
419 *self.seen_credits.lock()
420 }
421
422 pub fn set_seen_credits(&self, seen_credits: bool) {
424 *self.seen_credits.lock() = seen_credits;
425 }
426
427 #[must_use]
429 pub(crate) fn has_won_game(&self) -> bool {
430 *self.won_game.lock()
431 }
432
433 fn set_won_game(&self, won_game: bool) {
434 *self.won_game.lock() = won_game;
435 }
436
437 pub(crate) fn show_end_credits(&self) {
439 if self.has_won_game() {
440 return;
441 }
442
443 let world = self.get_world();
444 let Some(player) = world.players.get_by_entity_id(self.id()) else {
445 return;
446 };
447 if !ptr::eq(player.as_ref(), self) {
448 return;
449 }
450 let Some(pending_token) = player.begin_pending_world_change() else {
451 return;
452 };
453
454 assert_eq!(
455 player.remove_all_menus(),
456 MenuRemovalStatus::Complete,
457 "End credits menu removal must run outside a menu callback"
458 );
459 world.remove_player_for_world_change(&player);
460 player.finish_pending_world_change(pending_token);
461 if world.contains_player(&player) {
462 return;
463 }
464
465 player.set_won_game(true);
466 player.send_packet(CGameEvent {
467 event: GameEventType::WinGame,
468 data: 0.0,
469 });
470 player.set_seen_credits(true);
471 }
472
473 fn respawn_after_end_credits(self: &Arc<Self>) {
474 if !self.has_won_game() {
475 return;
476 }
477
478 let source_world = self.get_world();
479 let Some(server) = self.server.upgrade() else {
480 log::error!(
481 "Failed to schedule End credits respawn for player {}: server is gone",
482 self.gameprofile.name
483 );
484 return;
485 };
486 if !server.owns_online_player(self) {
487 return;
488 }
489 let Some(pending_token) = self.begin_respawn_request() else {
490 return;
491 };
492 let (target_world, respawn_data) =
493 match server.respawn_world_and_data_for_domain(source_world.domain()) {
494 Ok(resolved) => resolved,
495 Err(error) => {
496 self.finish_respawn_request(pending_token);
497 log::error!(
498 "Failed to schedule End credits respawn for player {}: {error}",
499 self.gameprofile.name
500 );
501 return;
502 }
503 };
504
505 match PlayerRespawnJob::new(
506 Arc::clone(self),
507 source_world,
508 target_world,
509 respawn_data,
510 RespawnRequestKind::EndCredits,
511 pending_token,
512 ) {
513 Ok(job) => server.jobs.spawn(job),
514 Err(error) => {
515 self.finish_respawn_request(pending_token);
516 log::error!(
517 "Failed to schedule End credits respawn for player {}: {error}",
518 self.gameprofile.name
519 );
520 }
521 }
522 }
523}