diff --git a/rc_multiplayer/src/matches/generic.rs b/rc_multiplayer/src/matches/generic.rs index 68295ee..5092580 100644 --- a/rc_multiplayer/src/matches/generic.rs +++ b/rc_multiplayer/src/matches/generic.rs @@ -594,7 +594,7 @@ impl GenericGamemodeEngine { &rlnl::events::ingame::Kill { killee_player_id: remote_player, killer_player_id: killer_player }, true, ).await; - log::info!("Player {} was destroyed by {} ({}) in game {}", remote_player, killer_player, user_id, self.game_guid()); + log::info!("Player {} was destroyed by player {} (user {}) in game {}", remote_player, killer_player, user_id, self.game_guid()); if self.custom_logic_handler.on_vehicle_destroyed(&self, killer_player, remote_player).await { // the kill tracking is initiated separately by the client with kill bonus event if let Some(killed) = self.users.read().await.get(&remote_player) { @@ -817,26 +817,6 @@ impl GenericGamemodeEngine { }, literustlib::packet::Property::Unreliable, &conn.connection.connection).await); } } - /*if self.game_start.load(std::sync::atomic::Ordering::Relaxed) == -1 { - let mut all_users_loading_complete = true; - for conn in self.users.read().await.values() { - let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); - let is_in_sync = matches!(mode, ConnectionMode::Sync); - log::info!("Player {} is in mode {:?}", conn.descriptor.player_id, mode); - all_users_loading_complete &= is_in_sync && conn.state.progress.load(std::sync::atomic::Ordering::Relaxed) == 100; - } - if all_users_loading_complete { - for (id, conn) in self.users.read().await.iter() { - if let Err(e) = conn.connection.rlnl().send_empty( - rlnl::event_code::NetworkEvent::EndOfSync, - literustlib::packet::Property::ReliableOrdered, - &conn.connection.connection - ).await { - log::error!("Failed to send EndOfSync event to user {}: {}", id, e); - } - } - } - }*/ } else { log::warn!("Received machine motion with unknown player id {} from user {}", motion.player_id, user_id); } @@ -986,7 +966,6 @@ impl GenericGamemodeEngine { .await?; continue; } - } // fallback log::warn!("No spawn point found for player {} on team {}, using bad fallback", player.player_id, team); @@ -1063,7 +1042,12 @@ impl GenericGamemodeEngine { } pub(super) fn game_done(&self) { - self.is_complete.store(true, std::sync::atomic::Ordering::SeqCst); + let old = self.is_complete.swap(true, std::sync::atomic::Ordering::SeqCst); + if old { + log::warn!("Game {} was marked as done again", self.game_guid()); + } else { + log::debug!("Game {} is marked done (handler will exit once all players have disconnected)", self.game_guid()); + } } pub(super) fn is_game_done(&self) -> bool { diff --git a/rc_multiplayer/src/matches/modes/battle_arena.rs b/rc_multiplayer/src/matches/modes/battle_arena.rs index cb2b18d..77db201 100644 --- a/rc_multiplayer/src/matches/modes/battle_arena.rs +++ b/rc_multiplayer/src/matches/modes/battle_arena.rs @@ -597,6 +597,73 @@ impl BattleArenaLogic { } } } + + async fn do_respawn_tasks(&self, generic: &crate::matches::GenericGamemodeEngine, player_id: u8) { + log::info!("Handling respawn player {} in game {}", player_id, generic.game_guid()); + let respawn_time = std::time::Duration::from_secs(self.config.respawn_time_seconds as u64); + let now = chrono::Utc::now(); + let respawn_timestamp = now + respawn_time; + if let Some(player_respawn) = self.player_tracking.respawning.read().await.get(&player_id) { + player_respawn.store(respawn_timestamp.timestamp(), std::sync::atomic::Ordering::Relaxed); + } + let respawn_payload = rlnl::events::ingame::RespawnTime { + owner: player_id, + waiting_time: self.config.respawn_time_seconds as i16, + }; + generic.broadcast( + rlnl::event_code::NetworkEvent::SetRespawnWaitingTime, + literustlib::packet::Property::ReliableOrdered, + &respawn_payload, + true + ).await; + if let Some(player_team) = self.player_tracking.team(player_id).await { + let spawn_point = if let Some(team_spawns) = generic.map_config.spawns.get(&player_team) { + if team_spawns.is_empty() { + oj_rc_core::persist::config::Point { + x: 10.0 * (player_id as f32), + y: 100.0, + z: 10.0 * (player_team as f32) + 10.0, + } + } else { + let index = (player_id as usize) % team_spawns.len(); + team_spawns[index].clone() + } + } else { + oj_rc_core::persist::config::Point { + x: 10.0 * (player_id as f32), + y: 100.0, + z: 10.0 * (player_team as f32) + 10.0, + } + }; + let connections = generic.users.read().await.values().map(|player_info| player_info.connection.clone()).collect(); + tokio::task::spawn(Self::respawn_player_after(respawn_timestamp, connections, spawn_point, player_id)); + } else { + log::error!("Player {} cannot respawn because they are not in a team!?", player_id); + } + } + + async fn respawn_player_after(after: chrono::DateTime, players: Vec, spawn: oj_rc_core::persist::config::Point, player_id: u8) { + let sleep_dur = after.signed_duration_since(chrono::Utc::now()).to_std().expect("Respawn duration too long to sleep"); + tokio::time::sleep(sleep_dur).await; + let spawn_payload = rlnl::events::sync::SpawnPoint { + pos: rlnl::types::PosQuatPair { + pos: rlnl::types::CompressedVec3::from((spawn.x, spawn.y, spawn.z)), + rot: rlnl::types::CompressedQuat { x: 0, y: 0, z: 0 }, + }, + owner: player_id, + }; + log::debug!("Respawning player {} after {}ms", player_id, sleep_dur.as_millis()); + for player in players { + if !player.connection.is_connected() { continue; } + crate::events::log_lnl_send_failure(player.rlnl().send_data( + &spawn_payload, + rlnl::event_code::NetworkEvent::FreeRespawnPoint, + literustlib::packet::Property::ReliableOrdered, + &player.connection, + ).await); + } + + } } #[async_trait::async_trait] @@ -619,13 +686,13 @@ impl CustomGameLogic for BattleArenaLogic { async fn on_vehicle_destroyed(&self, generic: &crate::matches::GenericGamemodeEngine, _killer: u8, victim: u8) -> bool { self.do_destruct_tasks(generic, victim).await; - // TODO handle respawn + self.do_respawn_tasks(generic, victim).await; true } async fn on_vehicle_self_destruct(&self, generic: &crate::matches::GenericGamemodeEngine, user: u8, _is_classic: bool) -> bool { self.do_destruct_tasks(generic, user).await; - // TODO handle respawn + self.do_respawn_tasks(generic, user).await; true }