diff --git a/rc_multiplayer/src/matches/generic.rs b/rc_multiplayer/src/matches/generic.rs index 19bae60..4812914 100644 --- a/rc_multiplayer/src/matches/generic.rs +++ b/rc_multiplayer/src/matches/generic.rs @@ -323,276 +323,28 @@ impl GenericGamemodeEngine { if let Some(msg) = recv.recv().await { match msg { super::GameMessage::NewConnection { user, game_guid, connection, response, sender } => { - if self.game_guid() != game_guid { - log::error!("Game guid does not match (got: {}, expected: {})", game_guid, self.game_guid()); - response.send(Some(super::messages::ErrorMessage { - message: format!("Game guid does not match (got: {}, expected: {})", game_guid, self.game_guid()), - inner: None, - })).unwrap_or_default(); - return; - } else { - let mut users = self.users.write().await; - //tokio::time::sleep(std::time::Duration::from_secs(1)).await; - //let id = users.len() as u8; - let user_id = user.user_id(); - let player_info = self.players_info.iter().find(|p| p.user_id == Some(user_id)).unwrap(); - let id = player_info.player_id; - let new_user = UserConnection { - user, - connection: UserSender { - connection, - sender, - }, - state: std::sync::Arc::new(UserState::new()), - machine: MachineState::new(), - descriptor: player_info.to_owned(), - counters: UserData::new(), - }; - if self.custom_logic_handler.on_player_join(&self, &new_user, &self.players_info).await { - //self.spawn_send_loading_events(&new_user, id, self.players_info.clone()); - crate::events::log_lnl_send_failure(new_user.connection.rlnl().send_data( - &rlnl::events::ingame::PlayerId { player: id }, - rlnl::event_code::NetworkEvent::GameGuidValidated, - literustlib::packet::Property::ReliableOrdered, - &new_user.connection.connection - ).await); - log::debug!("User {} is validated to play game {}", new_user.user.user_id(), game_guid); - self.user_id_map.write().await.insert(new_user.user.user_id(), id); - users.insert(id, new_user); - } - response.send(None).unwrap_or_default(); - } + self.on_new_connection(user, game_guid, connection, response, sender).await; }, super::GameMessage::EndConnection { user_id } => { - if let Some(player_id) = self.user_key_by_user_id(user_id).await { - let conn_opt = self.users.write().await.remove(&player_id); - if let Some(conn) = conn_opt { - if self.custom_logic_handler.on_player_end(&self, &conn).await { - conn.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed); - if !self.is_complete.load(std::sync::atomic::Ordering::Relaxed) { - self.rebroadcast( - user_id, - rlnl::event_code::NetworkEvent::OnAnotherClientDisconnected, - literustlib::packet::Property::ReliableOrdered, - &rlnl::events::ingame::PlayerId { player: player_id }, - true, - ).await; - } else { - // in every other case this packet would've already been sent - // this makes the end-of-match "continue" button send you back to the main menu a bit sooner - // (otherwise it waits for the multiplayer server to disconnect via timeout) - crate::events::log_lnl_send_failure(conn.connection.rlnl().send_empty( - rlnl::event_code::NetworkEvent::PlayerQuitRequestComplete, - literustlib::packet::Property::ReliableOrdered, - &conn.connection.connection, - ).await); - } - let mut has_active_connections = false; - for user in self.users.read().await.values() { - let mode = ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)); - has_active_connections |= !matches!(mode, ConnectionMode::Disconnected); - } - is_engaged = has_active_connections; - if !has_active_connections { - self.is_complete.store(true, std::sync::atomic::Ordering::Relaxed); - if self.custom_logic_handler.on_game_completed(&self).await { - if let Err(e) = conn.user.complete_game(self.game_guid()).await { - log::error!("Failed to mark game {} as complete: {}", self.game_guid(), e); - } - } - } - conn.connection.connection.goodbye(&conn.connection.sender).await; - } - } - - } + is_engaged = self.on_end_connection(user_id).await; }, super::GameMessage::RequestLeave { user_id } => { - log::info!("User {} wants to leave game {}", user_id, self.game_guid()); - if let Some(player_id) = self.user_key_by_user_id(user_id).await { - self.rebroadcast( - user_id, - rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed, - literustlib::packet::Property::ReliableOrdered, - &rlnl::events::ingame::Kill { - killee_player_id: player_id, - killer_player_id: player_id, - }, - true, - ).await; - if let Some(conn) = self.users.read().await.get(&player_id) { - crate::events::log_lnl_send_failure(conn.connection.rlnl().send_empty( - rlnl::event_code::NetworkEvent::PlayerQuitRequestComplete, - literustlib::packet::Property::ReliableOrdered, - &conn.connection.connection, - ).await); - } - } + self.on_request_leave(user_id).await; } super::GameMessage::LoadingProgress { user_id, user_name, progress } => { - let progress_data = rlnl::events::loading::LoadingProgress { - user_name: rlnl::types::BinaryWriterString(user_name), - progress, - }; - for conn in self.users.read().await.values() { - if user_id == conn.user.user_id() { - let progress_percent = ((progress * 100.0).ceil() as u8).clamp(0, 100); - log::info!("User {} is loaded {}% into game {}", user_id, progress_percent, self.game_guid()); - conn.state.progress.store(progress_percent, std::sync::atomic::Ordering::Relaxed); - continue; - } - let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); - match mode { - ConnectionMode::Loading | ConnectionMode::Disconnected => {}, - ConnectionMode::WaitingForSync | ConnectionMode::Sync | ConnectionMode::WaitingToStart => { - crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data( - &progress_data, - rlnl::event_code::NetworkEvent::BroadcastLoadingProgress, - literustlib::packet::Property::ReliableOrdered, - &conn.connection.connection, - ).await); - }, - ConnectionMode::InGame => { - log::warn!("Got loading progress for user {} who is supposed to be already in-game", user_id); - }, - } - } + self.on_loading_progress(user_id, user_name, progress).await; } super::GameMessage::RequestLoadingProgress { user_id } => { - log::info!("Got request loading progress"); - if let Some(user_key) = self.user_key_by_user_id(user_id).await { - if let Some(user_info) = self.users.read().await.get(&user_key) { - self.spawn_send_loading_events(user_info, user_key, self.players_info.clone()); - let sender = user_info.connection.rlnl(); - for conn in self.users.read().await.values() { - if user_id == conn.user.user_id() { continue; } - /*crate::events::log_lnl_send_failure(sender.send_data( - &user_info.1, - rlnl::event_code::NetworkEvent::BroadcastLoadingProgress, - literustlib::packet::Property::ReliableOrdered, - &user_info.0.connection, - ).await);*/ - let event = rlnl::events::loading::LoadingProgress { - user_name: rlnl::types::BinaryWriterString(conn.user.user_name().to_owned()), - progress: (conn.state.progress.load(std::sync::atomic::Ordering::Relaxed) as f32) / 100.0, - }; - crate::events::log_lnl_send_failure(sender.send_data( - &event, - rlnl::event_code::NetworkEvent::BroadcastLoadingProgress, - literustlib::packet::Property::ReliableOrdered, - &user_info.connection.connection, - ).await) - } - for fake in self.fake_users.values() { - let event = rlnl::events::loading::LoadingProgress { - user_name: rlnl::types::BinaryWriterString(fake.descriptor.public_id.clone()), - progress: (fake.state.progress.load(std::sync::atomic::Ordering::Relaxed) as f32) / 100.0, - }; - crate::events::log_lnl_send_failure(sender.send_data( - &event, - rlnl::event_code::NetworkEvent::BroadcastLoadingProgress, - literustlib::packet::Property::ReliableOrdered, - &user_info.connection.connection, - ).await) - } - } else { - log::error!("Failed to find player {} in connected users for match {}", user_key, self.game_guid()); - } - } else { - log::error!("Failed to find user {} in connected users for match {}", user_id, self.game_guid()); - } - + self.on_request_loading_progress(user_id).await; }, super::GameMessage::WeaponSelect { user_id, machine_id, category, size } => { - if let Some(conn) = self.users.read().await.get(&machine_id) { - let category_u32 = category as u32; - let size_u32 = size as u32; - conn.machine.selected_weapon.category.store(category_u32, std::sync::atomic::Ordering::Relaxed); - conn.machine.selected_weapon.size.store(size_u32, std::sync::atomic::Ordering::Relaxed); - let data = rlnl::events::ingame::SelectWeapon { - machine_id, - item_category: category_u32, - item_size: size_u32, - }; - self.rebroadcast( - user_id, - rlnl::event_code::NetworkEvent::BroadcastWeaponSelect, - literustlib::packet::Property::ReliableOrdered, - &data, - true - ).await; - } + self.on_weapon_select(user_id, machine_id, category, size).await; }, super::GameMessage::RequestLoadingSync { user_id } => { - // wait for all users to be ready before transitioning to loading sync - let mut ready_count = 0; - for user in self.users.read().await.values() { - if user.user.user_id() == user_id { - if !matches!(ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::Loading | ConnectionMode::Disconnected) { - log::warn!("Got RequestLoadingSync after user {} was already in/past WaitingForSync stage", user_id); - continue; - } - log::info!("User {} is awaiting sync", user_id); - user.state.mode.store(ConnectionMode::WaitingForSync.to_u8(), std::sync::atomic::Ordering::Relaxed); - ready_count += 1; - } else if matches!(ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::WaitingForSync) { - ready_count += 1; - } - } - let player_count = self.players_info.iter().filter(|x| x.user_id.is_some()).count(); - if ready_count == player_count { - log::info!("All players ({}) awaiting sync for game {}", player_count, self.game_guid()); - for (user_key, conn) in self.users.read().await.iter() { - let extra_packets = self.custom_logic_handler.extra_sync_events(&self, conn).await; - self.spawn_send_sync_events(conn, conn.user.user_id(), *user_key, self.players_info.clone(), extra_packets, self.map_config.clone()); - } - } + self.on_request_loading_sync(user_id).await; }, super::GameMessage::LoadComplete { user_id } => { - if let Some(user_key) = self.user_key_by_user_id(user_id).await { - if let Some(conn) = self.users.read().await.get(&user_key) { - log::info!("Loading complete for game {}, user {} (player {})", self.game_guid(), user_id, user_key); - conn.state.progress.store(100, std::sync::atomic::Ordering::Relaxed); - let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); - if !matches!(mode, ConnectionMode::Sync) { - log::warn!("Player {} completed loading but is in mode {:?} (should be Sync)", conn.descriptor.player_id, mode); - } - conn.state.mode.store(ConnectionMode::WaitingToStart.to_u8(), std::sync::atomic::Ordering::Relaxed); - self.spawn_initial_ingame_events(conn, user_id); - } else { - log::warn!("Invalid LoadComplete user key {} for game {}", user_key, self.game_guid()); - continue; - } - } else { - log::warn!("Unknown LoadComplete user id {} for game {}", user_id, self.game_guid()); - continue; - } - // wait for all users to be ready for starting game start countdown - 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)); - all_users_loading_complete &= matches!(mode, ConnectionMode::WaitingToStart); - } - // trigger game start - if all_users_loading_complete { - let player_count = self.players_info.iter().filter(|x| x.user_id.is_some()).count(); - log::info!("All players ({}) are ready for game {}", player_count, self.game_guid()); - tokio::time::sleep(Self::END_OF_SYNC_DELAY).await; - self.fakes_handler.on_ready( - self.users.read().await.iter() - .map(|(id, real_player)| (*id, real_player.connection.clone())) - .collect() - ); - let game_start = chrono::Utc::now() + Self::COUNTDOWN_DURATION; - if self.custom_logic_handler.on_countdown_start(&self, game_start).await { - let mut senders = Vec::new(); - for conn in self.users.read().await.values() { - senders.push((conn.connection.clone(), conn.state.clone())); - } - self.game_start.store(game_start.timestamp(), std::sync::atomic::Ordering::Relaxed); - super::countdown::match_countdown(senders, game_start); - } - } + self.on_load_complete(user_id).await; }, super::GameMessage::SpotVehicle { user_id, remote_player } => { self.rebroadcast( @@ -604,242 +356,40 @@ impl GenericGamemodeEngine { ).await; }, super::GameMessage::DestroyVehicle { user_id, remote_player, killer_player } => { - // FIXME allow custom_logic_handler to override the MachineDestroyedConfirmed send - self.rebroadcast( - user_id, - rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed, - literustlib::packet::Property::ReliableOrdered, - &rlnl::events::ingame::Kill { killee_player_id: remote_player, killer_player_id: killer_player }, - true, - ).await; - 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) { - killed.counters.deaths.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let data = killed.counters.get_generic_packet(remote_player, rlnl::types::IngameStatId::RobotDestroyed, None); - self.broadcast( - rlnl::event_code::NetworkEvent::UpdateGameStats, - literustlib::packet::Property::ReliableOrdered, - &data, - true, - ).await; - } - } + self.on_destroy_vehicle(user_id, remote_player, killer_player).await; }, super::GameMessage::SelfDestruct { user_id, is_classic } => { - if let Some(player_id) = self.user_key_by_user_id(user_id).await { - self.rebroadcast( - user_id, - rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed, - literustlib::packet::Property::ReliableOrdered, - &rlnl::events::ingame::Kill { killee_player_id: player_id, killer_player_id: player_id }, - true, - ).await; - log::info!("Player {} ({}) self-destructed in game {} (elimination? {})", player_id, user_id, self.game_guid(), is_classic); - if self.custom_logic_handler.on_vehicle_self_destruct(&self, player_id, is_classic).await { - if is_classic { - self.rebroadcast( - user_id, - rlnl::event_code::NetworkEvent::OnAnotherClientDisconnected, - literustlib::packet::Property::ReliableOrdered, - &rlnl::events::ingame::PlayerId { player: player_id }, - true, - ).await; - if let Some(conn) = self.users.read().await.get(&player_id) { - crate::events::log_lnl_send_failure(conn.connection.rlnl().send_empty( - rlnl::event_code::NetworkEvent::PlayerQuitRequestComplete, - literustlib::packet::Property::ReliableOrdered, - &conn.connection.connection - ).await); - conn.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed); - conn.connection.connection.disconnect(); - } - } - } - } - + self.on_self_destruct(user_id, is_classic).await; }, super::GameMessage::FlippingStarted { user_id } => { - if let Some(user_key) = self.user_key_by_user_id(user_id).await { - self.rebroadcast( - user_id, - rlnl::event_code::NetworkEvent::AlignmentRectifierStarted, - literustlib::packet::Property::ReliableOrdered, - &rlnl::events::ingame::PlayerId { player: user_key }, - true, - ).await; - } + self.on_flipping_started(user_id).await; }, - super::GameMessage::MapPing { user_id: _, ping } => { - for (id, conn) in self.users.read().await.iter() { - if (*id as i32) != ping.sender && conn.descriptor.team == ping.team_id { - crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data( - &ping, - rlnl::event_code::NetworkEvent::MapPingEvent, - literustlib::packet::Property::ReliableOrdered, - &conn.connection.connection - ).await); - } - } + super::GameMessage::MapPing { user_id, ping } => { + self.on_map_ping(user_id, ping).await; }, - super::GameMessage::KillBonus { user_id: _, shootee, shooter } => { - if let Some(to_reward) = self.users.read().await.get(&shooter) { - to_reward.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data( - &rlnl::events::ingame::Kill { - killee_player_id: shootee, - killer_player_id: shooter, - }, - rlnl::event_code::NetworkEvent::ConfirmedKill, - literustlib::packet::Property::ReliableOrdered, - &to_reward.connection.connection - ).await); - let data = to_reward.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::Kill, None); - self.broadcast( - rlnl::event_code::NetworkEvent::UpdateGameStats, - literustlib::packet::Property::ReliableOrdered, - &data, - true, - ).await; - } + super::GameMessage::KillBonus { user_id, shootee, shooter } => { + self.on_kill_bonus(user_id, shootee, shooter).await; }, - super::GameMessage::AssistBonus { user_id: _, shootee, shooters } => { - let lock = self.users.read().await; - for shooter in shooters { - if let Some(to_reward) = lock.get(&shooter) { - to_reward.counters.assists.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data( - &rlnl::events::ingame::Kill { - killee_player_id: shootee, - killer_player_id: shooter, - }, - rlnl::event_code::NetworkEvent::ConfirmedAssist, - literustlib::packet::Property::ReliableOrdered, - &to_reward.connection.connection - ).await); - let data = to_reward.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::KillAssist, None); - self.broadcast( - rlnl::event_code::NetworkEvent::UpdateGameStats, - literustlib::packet::Property::ReliableOrdered, - &data, - true, - ).await; - } - } + super::GameMessage::AssistBonus { user_id, shootee, shooters } => { + self.on_assist_bonus(user_id, shootee, shooters).await; }, - super::GameMessage::DestroyCubesBonus { user_id: _, info } => { - let lock = self.users.read().await; - for shooter in info.shooters { - if let Some(to_reward) = lock.get(&shooter.shooting_player_id) { - let mut total_cubes = 0; - for target in shooter.shooter_targets { - if let Some(to_punish) = lock.get(&target.target_player_id) { - let mut total_cubes_received = 0; - for cubes in target.cube_amounts { - // TODO use cube_id for something!? - total_cubes += cubes.cube_count; - total_cubes_received += cubes.cube_count; - } - to_punish.counters.received_cubes.fetch_add(total_cubes_received, std::sync::atomic::Ordering::SeqCst); - } - } - to_reward.counters.cubes.fetch_add(total_cubes, std::sync::atomic::Ordering::SeqCst); - let data = to_reward.counters.get_generic_packet(shooter.shooting_player_id, rlnl::types::IngameStatId::DestroyedCubes, Some(total_cubes)); - self.broadcast( - rlnl::event_code::NetworkEvent::UpdateGameStats, - literustlib::packet::Property::Unreliable, - &data, - true, - ).await; - } - } + super::GameMessage::DestroyCubesBonus { user_id, info } => { + self.on_destroy_cubes_bonus(user_id, info).await; }, - super::GameMessage::HealCubesBonus { user_id: _, info } => { - let lock = self.users.read().await; - for shooter in info.shooters { - if let Some(to_reward) = lock.get(&shooter.shooting_player_id) { - let mut total_cubes = 0; - for target in shooter.shooter_targets { - if let Some(to_punish) = lock.get(&target.target_player_id) { - let mut total_cubes_received = 0; - for cubes in target.cube_amounts { - // TODO use cube_id for something!? - total_cubes += cubes.cube_count; - total_cubes_received += cubes.cube_count; - } - to_punish.counters.received_healed.fetch_add(total_cubes_received, std::sync::atomic::Ordering::SeqCst); - } - } - to_reward.counters.healed.fetch_add(total_cubes, std::sync::atomic::Ordering::SeqCst); - let data = to_reward.counters.get_generic_packet(shooter.shooting_player_id, rlnl::types::IngameStatId::HealCubes, Some(total_cubes)); - self.broadcast( - rlnl::event_code::NetworkEvent::UpdateGameStats, - literustlib::packet::Property::Unreliable, - &data, - true, - ).await; - } - } + super::GameMessage::HealCubesBonus { user_id, info } => { + self.on_heal_cubes_bonus(user_id, info).await; }, super::GameMessage::BroadcastRlnl { user_id, event, event_in, property, data } => { - if self.custom_logic_handler.on_broadcast(&self, user_id, event, event_in, property, &data, false).await { - if let Some(data) = data { - self.broadcast(event, property, &*data, true).await; - } else { - self.broadcast_dataless(event, property, true).await; - } - } + self.on_broadcast(user_id, event, event_in, property, data, false).await; }, super::GameMessage::RebroadcastRlnl { skip_user_id, event, event_in, property, data } => { - if self.custom_logic_handler.on_broadcast(&self, skip_user_id, event, event_in, property, &data, true).await { - if let Some(data) = data { - self.rebroadcast(skip_user_id, event, property, &*data, true).await; - } else { - self.rebroadcast_dataless(skip_user_id, event, property, true).await; - } - } + self.on_broadcast(skip_user_id, event, event_in, property, data, true).await; }, super::GameMessage::CustomLogicRlnl { user_id, event, property, data } => { self.custom_logic_handler.on_custom(&self, user_id, event, property, data).await; }, super::GameMessage::Motion { user_id, motion } => { - //let (looking_at_x, looking_at_y, looking_at_z) = motion.target_point.clone().into(); - //log::info!("Player {} looking at ({}, {}, {})", motion.player_id, looking_at_x, looking_at_y, looking_at_z); - let (x, y, z) = motion.rb_state.rb_pos_rot.pos.into(); - let (x2, y2, z2) = motion.rb_state.center_of_mass.into(); - let (w3, x3, y3, z3) = motion.rb_state.rb_pos_rot.rot.into(); - let quat = num_quaternion::Quaternion::new(w3, x3, y3, z3); - if let Some(unit_quat) = quat.normalize() { - let coords = unit_quat.rotate_vector([x2, y2, z2]); - let (x4, y4, z4) = (x + coords[0], y + coords[1], z + coords[2]); - //log::debug!("Player {} world CoM is at (x, y, z) ({}, {}, {})", motion.player_id, x4, y4, z4); - if self.custom_logic_handler.on_motion(&self, &motion, (x4, y4, z4)).await { - if let Some(conn) = self.users.read().await.get(&motion.player_id) { - conn.machine.location.x.store(x4, std::sync::atomic::Ordering::Relaxed); - conn.machine.location.y.store(y4, std::sync::atomic::Ordering::Relaxed); - conn.machine.location.z.store(z4, std::sync::atomic::Ordering::Relaxed); - use byteserde::ser_heap::ByteSerializeHeap; - let mut ser = byteserde::ser_heap::ByteSerializerHeap::default(); - if let Err(e) = motion.byte_serialize_heap(&mut ser) { - log::error!("Failed to serialize motion data from user {}: {}", user_id, e); - } else { - let data = bytes::Bytes::copy_from_slice(ser.as_slice()); - for conn in self.users.read().await.values() { - if conn.user.user_id() == user_id { continue; } // fun fact: the game hard crashes if you omit this - crate::events::log_lnl_send_failure(conn.connection.sender.send_data(crate::handler::EventData { - message_ty: crate::data::MessageType::RobotMotion, - variant: 0, - data_size: data.len() as _, - data: data.clone(), - }, literustlib::packet::Property::Unreliable, &conn.connection.connection).await); - } - } - } else { - log::warn!("Received machine motion with unknown player id {} from user {}", motion.player_id, user_id); - } - } - } + self.on_motion(user_id, motion).await; }, super::GameMessage::NoOp => {}, } @@ -849,6 +399,565 @@ impl GenericGamemodeEngine { log::info!("Game {} has exited", self.game_guid()); } + async fn on_new_connection(&self, + user: std::sync::Arc>, + game_guid: String, + connection: std::sync::Arc>, + response: tokio::sync::oneshot::Sender>, + sender: std::sync::Arc> + ) { + if self.game_guid() != game_guid { + log::error!("Game guid does not match (got: {}, expected: {})", game_guid, self.game_guid()); + response.send(Some(super::messages::ErrorMessage { + message: format!("Game guid does not match (got: {}, expected: {})", game_guid, self.game_guid()), + inner: None, + })).unwrap_or_default(); + } else { + let mut users = self.users.write().await; + //tokio::time::sleep(std::time::Duration::from_secs(1)).await; + //let id = users.len() as u8; + let user_id = user.user_id(); + let player_info = self.players_info.iter().find(|p| p.user_id == Some(user_id)).unwrap(); + let id = player_info.player_id; + let new_user = UserConnection { + user, + connection: UserSender { + connection, + sender, + }, + state: std::sync::Arc::new(UserState::new()), + machine: MachineState::new(), + descriptor: player_info.to_owned(), + counters: UserData::new(), + }; + if self.custom_logic_handler.on_player_join(self, &new_user, &self.players_info).await { + //self.spawn_send_loading_events(&new_user, id, self.players_info.clone()); + crate::events::log_lnl_send_failure(new_user.connection.rlnl().send_data( + &rlnl::events::ingame::PlayerId { player: id }, + rlnl::event_code::NetworkEvent::GameGuidValidated, + literustlib::packet::Property::ReliableOrdered, + &new_user.connection.connection + ).await); + log::debug!("User {} is validated to play game {}", new_user.user.user_id(), game_guid); + self.user_id_map.write().await.insert(new_user.user.user_id(), id); + users.insert(id, new_user); + } + response.send(None).unwrap_or_default(); + } + } + + async fn on_end_connection(&self, user_id: i32) -> bool { + if let Some(player_id) = self.user_key_by_user_id(user_id).await { + let conn_opt = self.users.write().await.remove(&player_id); + if let Some(conn) = conn_opt { + if self.custom_logic_handler.on_player_end(self, &conn).await { + conn.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed); + if !self.is_complete.load(std::sync::atomic::Ordering::Relaxed) { + self.rebroadcast( + user_id, + rlnl::event_code::NetworkEvent::OnAnotherClientDisconnected, + literustlib::packet::Property::ReliableOrdered, + &rlnl::events::ingame::PlayerId { player: player_id }, + true, + ).await; + } else { + // in every other case this packet would've already been sent + // this makes the end-of-match "continue" button send you back to the main menu a bit sooner + // (otherwise it waits for the multiplayer server to disconnect via timeout) + crate::events::log_lnl_send_failure(conn.connection.rlnl().send_empty( + rlnl::event_code::NetworkEvent::PlayerQuitRequestComplete, + literustlib::packet::Property::ReliableOrdered, + &conn.connection.connection, + ).await); + } + let mut has_active_connections = false; + for user in self.users.read().await.values() { + let mode = ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)); + has_active_connections |= !matches!(mode, ConnectionMode::Disconnected); + } + //is_engaged = has_active_connections; + if !has_active_connections { + self.is_complete.store(true, std::sync::atomic::Ordering::Relaxed); + if self.custom_logic_handler.on_game_completed(self).await { + if let Err(e) = conn.user.complete_game(self.game_guid()).await { + log::error!("Failed to mark game {} as complete: {}", self.game_guid(), e); + } + } + } + conn.connection.connection.goodbye(&conn.connection.sender).await; + return has_active_connections; + } + } + } + true + } + + async fn on_request_leave(&self, user_id: i32) { + log::info!("User {} wants to leave game {}", user_id, self.game_guid()); + if let Some(player_id) = self.user_key_by_user_id(user_id).await { + self.rebroadcast( + user_id, + rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed, + literustlib::packet::Property::ReliableOrdered, + &rlnl::events::ingame::Kill { + killee_player_id: player_id, + killer_player_id: player_id, + }, + true, + ).await; + if let Some(conn) = self.users.read().await.get(&player_id) { + crate::events::log_lnl_send_failure(conn.connection.rlnl().send_empty( + rlnl::event_code::NetworkEvent::PlayerQuitRequestComplete, + literustlib::packet::Property::ReliableOrdered, + &conn.connection.connection, + ).await); + } + } + } + + async fn on_loading_progress(&self, user_id: i32, user_name: String, progress: f32) { + let progress_data = rlnl::events::loading::LoadingProgress { + user_name: rlnl::types::BinaryWriterString(user_name), + progress, + }; + for conn in self.users.read().await.values() { + if user_id == conn.user.user_id() { + let progress_percent = ((progress * 100.0).ceil() as u8).clamp(0, 100); + log::info!("User {} is loaded {}% into game {}", user_id, progress_percent, self.game_guid()); + conn.state.progress.store(progress_percent, std::sync::atomic::Ordering::Relaxed); + continue; + } + let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); + match mode { + ConnectionMode::Loading | ConnectionMode::Disconnected => {}, + ConnectionMode::WaitingForSync | ConnectionMode::Sync | ConnectionMode::WaitingToStart => { + crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data( + &progress_data, + rlnl::event_code::NetworkEvent::BroadcastLoadingProgress, + literustlib::packet::Property::ReliableOrdered, + &conn.connection.connection, + ).await); + }, + ConnectionMode::InGame => { + log::warn!("Got loading progress for user {} who is supposed to be already in-game", user_id); + }, + } + } + } + + async fn on_request_loading_progress(&self, user_id: i32) { + log::info!("Got request loading progress"); + if let Some(user_key) = self.user_key_by_user_id(user_id).await { + if let Some(user_info) = self.users.read().await.get(&user_key) { + self.spawn_send_loading_events(user_info, user_key, self.players_info.clone()); + let sender = user_info.connection.rlnl(); + for conn in self.users.read().await.values() { + if user_id == conn.user.user_id() { continue; } + /*crate::events::log_lnl_send_failure(sender.send_data( + &user_info.1, + rlnl::event_code::NetworkEvent::BroadcastLoadingProgress, + literustlib::packet::Property::ReliableOrdered, + &user_info.0.connection, + ).await);*/ + let event = rlnl::events::loading::LoadingProgress { + user_name: rlnl::types::BinaryWriterString(conn.user.user_name().to_owned()), + progress: (conn.state.progress.load(std::sync::atomic::Ordering::Relaxed) as f32) / 100.0, + }; + crate::events::log_lnl_send_failure(sender.send_data( + &event, + rlnl::event_code::NetworkEvent::BroadcastLoadingProgress, + literustlib::packet::Property::ReliableOrdered, + &user_info.connection.connection, + ).await) + } + for fake in self.fake_users.values() { + let event = rlnl::events::loading::LoadingProgress { + user_name: rlnl::types::BinaryWriterString(fake.descriptor.public_id.clone()), + progress: (fake.state.progress.load(std::sync::atomic::Ordering::Relaxed) as f32) / 100.0, + }; + crate::events::log_lnl_send_failure(sender.send_data( + &event, + rlnl::event_code::NetworkEvent::BroadcastLoadingProgress, + literustlib::packet::Property::ReliableOrdered, + &user_info.connection.connection, + ).await) + } + } else { + log::error!("Failed to find player {} in connected users for match {}", user_key, self.game_guid()); + } + } else { + log::error!("Failed to find user {} in connected users for match {}", user_id, self.game_guid()); + } + } + + async fn on_weapon_select(&self, + user_id: i32, + machine_id: u8, + category: oj_rc_core::data::weapon_list::ItemCategory, + size: oj_rc_core::data::cube_list::ItemTier + ) { + if let Some(conn) = self.users.read().await.get(&machine_id) { + let category_u32 = category as u32; + let size_u32 = size as u32; + conn.machine.selected_weapon.category.store(category_u32, std::sync::atomic::Ordering::Relaxed); + conn.machine.selected_weapon.size.store(size_u32, std::sync::atomic::Ordering::Relaxed); + let data = rlnl::events::ingame::SelectWeapon { + machine_id, + item_category: category_u32, + item_size: size_u32, + }; + self.rebroadcast( + user_id, + rlnl::event_code::NetworkEvent::BroadcastWeaponSelect, + literustlib::packet::Property::ReliableOrdered, + &data, + true + ).await; + } + } + + async fn on_request_loading_sync(&self, user_id: i32) { + // wait for all users to be ready before transitioning to loading sync + let mut ready_count = 0; + for user in self.users.read().await.values() { + if user.user.user_id() == user_id { + if !matches!(ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::Loading | ConnectionMode::Disconnected) { + log::warn!("Got RequestLoadingSync after user {} was already in/past WaitingForSync stage", user_id); + continue; + } + log::info!("User {} is awaiting sync", user_id); + user.state.mode.store(ConnectionMode::WaitingForSync.to_u8(), std::sync::atomic::Ordering::Relaxed); + ready_count += 1; + } else if matches!(ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::WaitingForSync) { + ready_count += 1; + } + } + let player_count = self.players_info.iter().filter(|x| x.user_id.is_some()).count(); + if ready_count == player_count { + log::info!("All players ({}) awaiting sync for game {}", player_count, self.game_guid()); + for (user_key, conn) in self.users.read().await.iter() { + let extra_packets = self.custom_logic_handler.extra_sync_events(self, conn).await; + self.spawn_send_sync_events(conn, conn.user.user_id(), *user_key, self.players_info.clone(), extra_packets, self.map_config.clone()); + } + } + } + + async fn on_load_complete(&self, user_id: i32) { + if let Some(user_key) = self.user_key_by_user_id(user_id).await { + if let Some(conn) = self.users.read().await.get(&user_key) { + log::info!("Loading complete for game {}, user {} (player {})", self.game_guid(), user_id, user_key); + conn.state.progress.store(100, std::sync::atomic::Ordering::Relaxed); + let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); + if !matches!(mode, ConnectionMode::Sync) { + log::warn!("Player {} completed loading but is in mode {:?} (should be Sync)", conn.descriptor.player_id, mode); + } + conn.state.mode.store(ConnectionMode::WaitingToStart.to_u8(), std::sync::atomic::Ordering::Relaxed); + self.spawn_initial_ingame_events(conn, user_id); + } else { + log::warn!("Invalid LoadComplete user key {} for game {}", user_key, self.game_guid()); + return; + } + } else { + log::warn!("Unknown LoadComplete user id {} for game {}", user_id, self.game_guid()); + return; + } + // wait for all users to be ready for starting game start countdown + 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)); + all_users_loading_complete &= matches!(mode, ConnectionMode::WaitingToStart); + } + // trigger game start + if all_users_loading_complete { + let player_count = self.players_info.iter().filter(|x| x.user_id.is_some()).count(); + log::info!("All players ({}) are ready for game {}", player_count, self.game_guid()); + tokio::time::sleep(Self::END_OF_SYNC_DELAY).await; + self.fakes_handler.on_ready( + self.users.read().await.iter() + .map(|(id, real_player)| (*id, real_player.connection.clone())) + .collect() + ); + let game_start = chrono::Utc::now() + Self::COUNTDOWN_DURATION; + if self.custom_logic_handler.on_countdown_start(self, game_start).await { + let mut senders = Vec::new(); + for conn in self.users.read().await.values() { + senders.push((conn.connection.clone(), conn.state.clone())); + } + self.game_start.store(game_start.timestamp(), std::sync::atomic::Ordering::Relaxed); + super::countdown::match_countdown(senders, game_start); + } + } + } + + async fn on_destroy_vehicle(&self, + user_id: i32, + remote_player: u8, + killer_player: u8, + ) { + // FIXME allow custom_logic_handler to override the MachineDestroyedConfirmed send + self.rebroadcast( + user_id, + rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed, + literustlib::packet::Property::ReliableOrdered, + &rlnl::events::ingame::Kill { killee_player_id: remote_player, killer_player_id: killer_player }, + true, + ).await; + 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) { + killed.counters.deaths.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let data = killed.counters.get_generic_packet(remote_player, rlnl::types::IngameStatId::RobotDestroyed, None); + self.broadcast( + rlnl::event_code::NetworkEvent::UpdateGameStats, + literustlib::packet::Property::ReliableOrdered, + &data, + true, + ).await; + } + } + } + + async fn on_self_destruct(&self, user_id: i32, is_classic: bool) { + if let Some(player_id) = self.user_key_by_user_id(user_id).await { + self.rebroadcast( + user_id, + rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed, + literustlib::packet::Property::ReliableOrdered, + &rlnl::events::ingame::Kill { killee_player_id: player_id, killer_player_id: player_id }, + true, + ).await; + log::info!("Player {} ({}) self-destructed in game {} (elimination? {})", player_id, user_id, self.game_guid(), is_classic); + if self.custom_logic_handler.on_vehicle_self_destruct(self, player_id, is_classic).await { + if is_classic { + self.rebroadcast( + user_id, + rlnl::event_code::NetworkEvent::OnAnotherClientDisconnected, + literustlib::packet::Property::ReliableOrdered, + &rlnl::events::ingame::PlayerId { player: player_id }, + true, + ).await; + if let Some(conn) = self.users.read().await.get(&player_id) { + crate::events::log_lnl_send_failure(conn.connection.rlnl().send_empty( + rlnl::event_code::NetworkEvent::PlayerQuitRequestComplete, + literustlib::packet::Property::ReliableOrdered, + &conn.connection.connection + ).await); + conn.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed); + conn.connection.connection.disconnect(); + } + } + } + } + } + + async fn on_flipping_started(&self, user_id: i32) { + if let Some(user_key) = self.user_key_by_user_id(user_id).await { + self.rebroadcast( + user_id, + rlnl::event_code::NetworkEvent::AlignmentRectifierStarted, + literustlib::packet::Property::ReliableOrdered, + &rlnl::events::ingame::PlayerId { player: user_key }, + true, + ).await; + } + } + + async fn on_map_ping(&self, _user_id: i32, ping: rlnl::events::ingame::MapPing) { + for (id, conn) in self.users.read().await.iter() { + if (*id as i32) != ping.sender && conn.descriptor.team == ping.team_id { + crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data( + &ping, + rlnl::event_code::NetworkEvent::MapPingEvent, + literustlib::packet::Property::ReliableOrdered, + &conn.connection.connection + ).await); + } + } + } + + async fn on_kill_bonus(&self, + _user_id: i32, + shootee: u8, + shooter: u8, + ) { + if let Some(to_reward) = self.users.read().await.get(&shooter) { + to_reward.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data( + &rlnl::events::ingame::Kill { + killee_player_id: shootee, + killer_player_id: shooter, + }, + rlnl::event_code::NetworkEvent::ConfirmedKill, + literustlib::packet::Property::ReliableOrdered, + &to_reward.connection.connection + ).await); + let data = to_reward.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::Kill, None); + self.broadcast( + rlnl::event_code::NetworkEvent::UpdateGameStats, + literustlib::packet::Property::ReliableOrdered, + &data, + true, + ).await; + } + } + + async fn on_assist_bonus(&self, + _user_id: i32, + shootee: u8, + shooters: Vec, + ) { + let lock = self.users.read().await; + for shooter in shooters { + if let Some(to_reward) = lock.get(&shooter) { + to_reward.counters.assists.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data( + &rlnl::events::ingame::Kill { + killee_player_id: shootee, + killer_player_id: shooter, + }, + rlnl::event_code::NetworkEvent::ConfirmedAssist, + literustlib::packet::Property::ReliableOrdered, + &to_reward.connection.connection + ).await); + let data = to_reward.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::KillAssist, None); + self.broadcast( + rlnl::event_code::NetworkEvent::UpdateGameStats, + literustlib::packet::Property::ReliableOrdered, + &data, + true, + ).await; + } + } + } + + async fn on_destroy_cubes_bonus(&self, + _user_id: i32, + info: rlnl::events::ingame::DestroyedHealedCubesBonus + ) { + let lock = self.users.read().await; + for shooter in info.shooters { + if let Some(to_reward) = lock.get(&shooter.shooting_player_id) { + let mut total_cubes = 0; + for target in shooter.shooter_targets { + if let Some(to_punish) = lock.get(&target.target_player_id) { + let mut total_cubes_received = 0; + for cubes in target.cube_amounts { + // TODO use cube_id for something!? + total_cubes += cubes.cube_count; + total_cubes_received += cubes.cube_count; + } + to_punish.counters.received_cubes.fetch_add(total_cubes_received, std::sync::atomic::Ordering::SeqCst); + } + } + to_reward.counters.cubes.fetch_add(total_cubes, std::sync::atomic::Ordering::SeqCst); + let data = to_reward.counters.get_generic_packet(shooter.shooting_player_id, rlnl::types::IngameStatId::DestroyedCubes, Some(total_cubes)); + self.broadcast( + rlnl::event_code::NetworkEvent::UpdateGameStats, + literustlib::packet::Property::Unreliable, + &data, + true, + ).await; + } + } + } + + async fn on_heal_cubes_bonus(&self, + _user_id: i32, + info: rlnl::events::ingame::DestroyedHealedCubesBonus, + ) { + let lock = self.users.read().await; + for shooter in info.shooters { + if let Some(to_reward) = lock.get(&shooter.shooting_player_id) { + let mut total_cubes = 0; + for target in shooter.shooter_targets { + if let Some(to_punish) = lock.get(&target.target_player_id) { + let mut total_cubes_received = 0; + for cubes in target.cube_amounts { + // TODO use cube_id for something!? + total_cubes += cubes.cube_count; + total_cubes_received += cubes.cube_count; + } + to_punish.counters.received_healed.fetch_add(total_cubes_received, std::sync::atomic::Ordering::SeqCst); + } + } + to_reward.counters.healed.fetch_add(total_cubes, std::sync::atomic::Ordering::SeqCst); + let data = to_reward.counters.get_generic_packet(shooter.shooting_player_id, rlnl::types::IngameStatId::HealCubes, Some(total_cubes)); + self.broadcast( + rlnl::event_code::NetworkEvent::UpdateGameStats, + literustlib::packet::Property::Unreliable, + &data, + true, + ).await; + } + } + } + + async fn on_broadcast(&self, + user_id: i32, + event: rlnl::event_code::NetworkEvent, + event_in: rlnl::event_code::NetworkEvent, + property: literustlib::packet::Property, + data: Option>, + skip_user: bool, + ) { + if self.custom_logic_handler.on_broadcast(self, user_id, event, event_in, property, &data, skip_user).await { + #[allow(clippy::collapsible_else_if)] + if let Some(data) = data { + if skip_user { + self.rebroadcast(user_id, event, property, &*data, true).await; + } else { + self.broadcast(event, property, &*data, true).await; + } + } else { + if skip_user { + self.rebroadcast_dataless(user_id, event, property, true).await; + } else { + self.broadcast_dataless(event, property, true).await; + } + } + } + } + + async fn on_motion(&self, user_id: i32, motion: rlnl::machine_motion::MachineMotion) { + //let (looking_at_x, looking_at_y, looking_at_z) = motion.target_point.clone().into(); + //log::info!("Player {} looking at ({}, {}, {})", motion.player_id, looking_at_x, looking_at_y, looking_at_z); + let (x, y, z) = motion.rb_state.rb_pos_rot.pos.into(); + let (x2, y2, z2) = motion.rb_state.center_of_mass.into(); + let (w3, x3, y3, z3) = motion.rb_state.rb_pos_rot.rot.into(); + let quat = num_quaternion::Quaternion::new(w3, x3, y3, z3); + if let Some(unit_quat) = quat.normalize() { + let coords = unit_quat.rotate_vector([x2, y2, z2]); + let (x4, y4, z4) = (x + coords[0], y + coords[1], z + coords[2]); + //log::debug!("Player {} world CoM is at (x, y, z) ({}, {}, {})", motion.player_id, x4, y4, z4); + if self.custom_logic_handler.on_motion(self, &motion, (x4, y4, z4)).await { + if let Some(conn) = self.users.read().await.get(&motion.player_id) { + conn.machine.location.x.store(x4, std::sync::atomic::Ordering::Relaxed); + conn.machine.location.y.store(y4, std::sync::atomic::Ordering::Relaxed); + conn.machine.location.z.store(z4, std::sync::atomic::Ordering::Relaxed); + use byteserde::ser_heap::ByteSerializeHeap; + let mut ser = byteserde::ser_heap::ByteSerializerHeap::default(); + if let Err(e) = motion.byte_serialize_heap(&mut ser) { + log::error!("Failed to serialize motion data from user {}: {}", user_id, e); + } else { + let data = bytes::Bytes::copy_from_slice(ser.as_slice()); + for conn in self.users.read().await.values() { + if conn.user.user_id() == user_id { continue; } // fun fact: the game hard crashes if you omit this + crate::events::log_lnl_send_failure(conn.connection.sender.send_data(crate::handler::EventData { + message_ty: crate::data::MessageType::RobotMotion, + variant: 0, + data_size: data.len() as _, + data: data.clone(), + }, literustlib::packet::Property::Unreliable, &conn.connection.connection).await); + } + } + } else { + log::warn!("Received machine motion with unknown player id {} from user {}", motion.player_id, user_id); + } + } + } + } + fn spawn_send_loading_events(&self, user: &UserConnection, player_id: u8, players: std::sync::Arc>) { let connection = user.connection.clone(); let user_id = user.user.user_id(); diff --git a/rc_social_room/src/operations/mod.rs b/rc_social_room/src/operations/mod.rs index f3d34d2..8655589 100644 --- a/rc_social_room/src/operations/mod.rs +++ b/rc_social_room/src/operations/mod.rs @@ -29,4 +29,5 @@ pub fn handler() -> OperationsHandler::default()) // AvatarUpdatedRequest, sent on services_room avatar_set success (just needs to be ack-ed; no params) .add(calculate_mmr::mmr_provider()) + .add(polariton_server::operations::Ack::<25, _>::default()) // save social settings, sent on escape menu settings save (should probably be saved someday...) } diff --git a/clippy.sh b/utils/clippy.sh similarity index 100% rename from clippy.sh rename to utils/clippy.sh