From b717ca1ed356b6787c51f09ea6b771447ce87d98 Mon Sep 17 00:00:00 2001 From: "NG (Graham)" Date: Fri, 21 Aug 2026 21:04:37 -0400 Subject: [PATCH] Add game event refresh freezing when user enters lobby; fix #84 --- rc_chat_room/src/state/chat/config.rs | 2 +- rc_core/src/data/game_mode.rs | 15 ++ rc_core/src/persist/config/cubes_json.rs | 1 + rc_core/src/persist/config/traits.rs | 123 +++++++++----- rc_core/src/persist/user/intercom.rs | 156 ++++++++++++++++-- rc_core/src/persist/user/lobby.rs | 4 - rc_core/src/persist/user/traits.rs | 1 - rc_lobby_room/src/lobby.rs | 68 ++++++-- rc_services_room/src/custom_game_tracker.rs | 16 +- rc_services_room/src/events/handler.rs | 26 ++- rc_services_room/src/main.rs | 3 + .../src/operations/game_event_params.rs | 9 +- rc_services_room/src/operations/mod.rs | 4 +- rc_services_room/src/operations/more_auth.rs | 5 +- 14 files changed, 347 insertions(+), 86 deletions(-) diff --git a/rc_chat_room/src/state/chat/config.rs b/rc_chat_room/src/state/chat/config.rs index 0a32aad..559dcdd 100644 --- a/rc_chat_room/src/state/chat/config.rs +++ b/rc_chat_room/src/state/chat/config.rs @@ -322,7 +322,7 @@ impl Intercom { } Self::KeybindLockFix => { ctx.user.trigger_workaround( - oj_rc_core::persist::user::intercom::IntercomWorkaroundMessage::KeybindLockout { }, + oj_rc_core::persist::user::intercom::IntercomWorkaroundMessage::WebService( oj_rc_core::persist::user::intercom::IntercomWebServiceWorkaroundMessage::KeybindLockout { }), vec![ctx.user.public_id().to_owned()] ).await; "Triggered key lockout workaround".to_owned() diff --git a/rc_core/src/data/game_mode.rs b/rc_core/src/data/game_mode.rs index da1f5aa..3d4fb8f 100644 --- a/rc_core/src/data/game_mode.rs +++ b/rc_core/src/data/game_mode.rs @@ -79,6 +79,21 @@ impl GameMap { crate::persist::config::GameMap::Earth2 => Self::Earth2, } } + + #[inline] + pub fn from_str(s: &str) -> Option { + match s { + "RC_Planet_Mars_01_CTF" => Some(Self::Mars1), // og flat mars + "RC_Planet_Mars_02_BA" => Some(Self::Mars2), // the one with the bridge in the middle + "RC_Planet_Mars_03_BA" => Some(Self::Mars3), // tharsis rift without the rift + "RC_Planet_Neptune_01_CTF" => Some(Self::Neptune1), // og flat GJ1214b gliese lake without the lake + "RC_Planet_Neptune_02_BA" => Some(Self::Neptune2), // the one with the cave + "RC_Planet_Neptune_03_BA" => Some(Self::Neptune3), // spitzer dam + "RC_Planet_Earth_01_BA" => Some(Self::Earth1), // birmingham power station + "RC_Planet_Earth_02_BA" => Some(Self::Earth2), // vanguard + _ => None, + } + } } #[repr(u8)] diff --git a/rc_core/src/persist/config/cubes_json.rs b/rc_core/src/persist/config/cubes_json.rs index 9d75684..77679c7 100644 --- a/rc_core/src/persist/config/cubes_json.rs +++ b/rc_core/src/persist/config/cubes_json.rs @@ -379,6 +379,7 @@ impl super::ConfigProvider for CubeConfig { index: first, started: chrono::Utc::now().timestamp(), needs_to_be_saved: true, + lockouts: std::collections::HashMap::new(), } } diff --git a/rc_core/src/persist/config/traits.rs b/rc_core/src/persist/config/traits.rs index 98a0f23..24eb734 100644 --- a/rc_core/src/persist/config/traits.rs +++ b/rc_core/src/persist/config/traits.rs @@ -164,17 +164,18 @@ pub struct ChatSystemConfig { pub can_create_channels: bool, } -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct GameEventSequence { pub strategy: GameRotationStrategy, pub modes: Vec, pub index: usize, pub started: i64, pub(crate) needs_to_be_saved: bool, + pub(crate) lockouts: std::collections::HashMap, } impl GameEventSequence { - pub fn now(&mut self, updater: Box) -> GameEventTransmissible { + pub fn now(&mut self, updater: Box, user: i32) -> GameEventTransmissible { let time_now = chrono::Utc::now().timestamp(); let mut item_now = &self.modes[self.index]; let needs_refresh = time_now >= (item_now.duration.as_secs() as i64) + self.started; @@ -207,43 +208,91 @@ impl GameEventSequence { updater.set_singleplayer(sp).await; }); } - let remaining_ticks = ((item_now.duration.as_secs() as i64) - (time_now - self.started)) * 10_000_000; - GameEventTransmissible { - maps: Typed::Arr(polariton::operation::Arr { - ty: polariton::serdes::TypePrefix::Str, - custom_ty: None, - items: vec![ - Typed::Str(crate::data::game_mode::GameMap::from_persist(item_now.singleplayer.map).as_str().into()), - Typed::Str(crate::data::game_mode::GameMap::from_persist(item_now.multiplayer.map).as_str().into()), - ], - }), - visibilities: Typed::Arr(polariton::operation::Arr { - ty: polariton::serdes::TypePrefix::Int, - custom_ty: None, - items: vec![ - Typed::Int(crate::data::game_mode::MapVisibility::from_persist(item_now.singleplayer.visibility) as _), - Typed::Int(crate::data::game_mode::MapVisibility::from_persist(item_now.multiplayer.visibility) as _), - ], - }), - modes: Typed::Arr(polariton::operation::Arr { - ty: polariton::serdes::TypePrefix::Int, - custom_ty: None, - items: vec![ - Typed::Int(crate::data::game_mode::GameMode::from_persist(item_now.singleplayer.mode) as _), - Typed::Int(crate::data::game_mode::GameMode::from_persist(item_now.multiplayer.mode) as _), - ], - }), - auto_heals: Typed::Arr(polariton::operation::Arr { - ty: polariton::serdes::TypePrefix::Bool, - custom_ty: None, - items: vec![ - Typed::Bool(item_now.singleplayer.auto_heal), - Typed::Bool(item_now.multiplayer.auto_heal), - ], - }), - remaining_ticks: Typed::Long(remaining_ticks), + if let Some(multiplayer_override) = self.lockouts.get(&user) { + GameEventTransmissible { + maps: Typed::Arr(polariton::operation::Arr { + ty: polariton::serdes::TypePrefix::Str, + custom_ty: None, + items: vec![ + Typed::Str(crate::data::game_mode::GameMap::from_persist(item_now.singleplayer.map).as_str().into()), + Typed::Str(crate::data::game_mode::GameMap::from_persist(multiplayer_override.map).as_str().into()), + ], + }), + visibilities: Typed::Arr(polariton::operation::Arr { + ty: polariton::serdes::TypePrefix::Int, + custom_ty: None, + items: vec![ + Typed::Int(crate::data::game_mode::MapVisibility::from_persist(item_now.singleplayer.visibility) as _), + Typed::Int(crate::data::game_mode::MapVisibility::from_persist(multiplayer_override.visibility) as _), + ], + }), + modes: Typed::Arr(polariton::operation::Arr { + ty: polariton::serdes::TypePrefix::Int, + custom_ty: None, + items: vec![ + Typed::Int(crate::data::game_mode::GameMode::from_persist(item_now.singleplayer.mode) as _), + Typed::Int(crate::data::game_mode::GameMode::from_persist(multiplayer_override.mode) as _), + ], + }), + auto_heals: Typed::Arr(polariton::operation::Arr { + ty: polariton::serdes::TypePrefix::Bool, + custom_ty: None, + items: vec![ + Typed::Bool(item_now.singleplayer.auto_heal), + Typed::Bool(multiplayer_override.auto_heal), + ], + }), + remaining_ticks: Typed::Long(10_000_000), + } + } else { + let remaining_ticks = ((item_now.duration.as_secs() as i64) - (time_now - self.started)) * 10_000_000; + GameEventTransmissible { + maps: Typed::Arr(polariton::operation::Arr { + ty: polariton::serdes::TypePrefix::Str, + custom_ty: None, + items: vec![ + Typed::Str(crate::data::game_mode::GameMap::from_persist(item_now.singleplayer.map).as_str().into()), + Typed::Str(crate::data::game_mode::GameMap::from_persist(item_now.multiplayer.map).as_str().into()), + ], + }), + visibilities: Typed::Arr(polariton::operation::Arr { + ty: polariton::serdes::TypePrefix::Int, + custom_ty: None, + items: vec![ + Typed::Int(crate::data::game_mode::MapVisibility::from_persist(item_now.singleplayer.visibility) as _), + Typed::Int(crate::data::game_mode::MapVisibility::from_persist(item_now.multiplayer.visibility) as _), + ], + }), + modes: Typed::Arr(polariton::operation::Arr { + ty: polariton::serdes::TypePrefix::Int, + custom_ty: None, + items: vec![ + Typed::Int(crate::data::game_mode::GameMode::from_persist(item_now.singleplayer.mode) as _), + Typed::Int(crate::data::game_mode::GameMode::from_persist(item_now.multiplayer.mode) as _), + ], + }), + auto_heals: Typed::Arr(polariton::operation::Arr { + ty: polariton::serdes::TypePrefix::Bool, + custom_ty: None, + items: vec![ + Typed::Bool(item_now.singleplayer.auto_heal), + Typed::Bool(item_now.multiplayer.auto_heal), + ], + }), + remaining_ticks: Typed::Long(remaining_ticks), + } } } + + /// Returns true if replacing existing entry for user + pub fn add_lockout(&mut self, user: i32, event: GameEvent) -> bool { + self.lockouts.insert(user, event).is_some() + } + + /// Returns true if entry for user existed + pub fn remove_lockout(&mut self, user: i32) -> bool { + self.lockouts.remove(&user).is_some() + } } pub struct GameEventTransmissible { diff --git a/rc_core/src/persist/user/intercom.rs b/rc_core/src/persist/user/intercom.rs index c3e4b8c..7694d05 100644 --- a/rc_core/src/persist/user/intercom.rs +++ b/rc_core/src/persist/user/intercom.rs @@ -229,14 +229,20 @@ impl super::IntercomUser for super::account_json::UserData { async fn trigger_workaround(&self, msg: IntercomWorkaroundMessage, to: Vec) { let send_to_everyone = to.is_empty(); - let data = IntercomWebServiceMessage { - public_ids: to, - data: IntercomWebServiceUserMessage::Workaround(msg), - everyone: send_to_everyone, - }; - if let Err(e) = self.post_to_intercom(&data, ".oj_services", "messages").await { - log::error!("Failed to send intercom workaround message: {}", e); + match msg { + IntercomWorkaroundMessage::Lobby(_lobby) => todo!(), + IntercomWorkaroundMessage::WebService(ws) => { + let data = IntercomWebServiceMessage { + public_ids: to, + data: IntercomWebServiceUserMessage::Workaround(ws), + everyone: send_to_everyone, + }; + if let Err(e) = self.post_to_intercom(&data, ".oj_services", "messages").await { + log::error!("Failed to send intercom workaround message: {}", e); + } + } } + } async fn update_custom_game(&self, msg: IntercomLobbyCustomGameDataMessage) { @@ -266,26 +272,127 @@ pub struct IntercomLobbyCustomGameDataMessage { pub users: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug)] -pub enum CustomGameMode { +#[derive(Serialize, Deserialize, Clone, Debug, Copy)] +pub enum IntercomGameMap { + Mars1, + Mars2, + Mars3, + Neptune1, + Neptune2, + Neptune3, + Earth1, + Earth2, +} + +impl IntercomGameMap { + pub fn into_conf(self) -> crate::persist::config::GameMap { + match self { + Self::Mars1 => crate::persist::config::GameMap::Mars1, + Self::Mars2 => crate::persist::config::GameMap::Mars2, + Self::Mars3 => crate::persist::config::GameMap::Mars3, + Self::Neptune1 => crate::persist::config::GameMap::Neptune1, + Self::Neptune2 => crate::persist::config::GameMap::Neptune2, + Self::Neptune3 => crate::persist::config::GameMap::Neptune3, + Self::Earth1 => crate::persist::config::GameMap::Earth1, + Self::Earth2 => crate::persist::config::GameMap::Earth2, + } + } + + pub fn from_data(map: crate::data::game_mode::GameMap) -> Self { + match map { + crate::data::game_mode::GameMap::Mars1 => Self::Mars1, + crate::data::game_mode::GameMap::Mars2 => Self::Mars2, + crate::data::game_mode::GameMap::Mars3 => Self::Mars3, + crate::data::game_mode::GameMap::Neptune1 => Self::Neptune1, + crate::data::game_mode::GameMap::Neptune2 => Self::Neptune2, + crate::data::game_mode::GameMap::Neptune3 => Self::Neptune3, + crate::data::game_mode::GameMap::Earth1 => Self::Earth1, + crate::data::game_mode::GameMap::Earth2 => Self::Earth2, + } + } + + pub fn from_str(s: &str) -> Option { + match s { + "RC_Planet_Mars_01_CTF" => Some(Self::Mars1), + "RC_Planet_Mars_02_BA" => Some(Self::Mars2), + "RC_Planet_Mars_03_BA" => Some(Self::Mars3), + "RC_Planet_Neptune_01_CTF" => Some(Self::Neptune1), + "RC_Planet_Neptune_02_BA" => Some(Self::Neptune2), + "RC_Planet_Neptune_03_BA" => Some(Self::Neptune3), + "RC_Planet_Earth_01_BA" => Some(Self::Earth1), + "RC_Planet_Earth_02_BA" => Some(Self::Earth2), + _ => None, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Copy)] +pub enum IntercomGameMode { BattleArena, TeamDeathmatch, Pit, SuddenDeath, } +impl IntercomGameMode { + pub fn into_conf(self) -> crate::persist::config::GameType { + match self { + Self::BattleArena => crate::persist::config::GameType::BattleArena, + Self::SuddenDeath => crate::persist::config::GameType::SuddenDeath, + Self::Pit => crate::persist::config::GameType::Pit, + Self::TeamDeathmatch => crate::persist::config::GameType::TeamDeathmatch, + } + } + + pub fn from_data(mode: crate::data::game_mode::GameMode) -> Self { + match mode { + crate::data::game_mode::GameMode::BattleArena => Self::BattleArena, + crate::data::game_mode::GameMode::SuddenDeath => Self::SuddenDeath, + crate::data::game_mode::GameMode::Pit => Self::Pit, + crate::data::game_mode::GameMode::TeamDeathmatch => Self::TeamDeathmatch, + _ => Self::Pit, + } + } +} + #[derive(Serialize, Deserialize, Clone, Debug, Copy)] -pub enum CustomGameVisibility { +pub enum IntercomGameVisibility { Good, Poor, Bad, } +impl IntercomGameVisibility { + pub fn into_conf(self) -> crate::persist::config::GameVisibility { + match self { + Self::Good => crate::persist::config::GameVisibility::Good, + Self::Poor => crate::persist::config::GameVisibility::Poor, + Self::Bad => crate::persist::config::GameVisibility::Bad, + } + } + + pub fn from_data(vis: crate::data::game_mode::MapVisibility) -> Self { + match vis { + crate::data::game_mode::MapVisibility::Good => Self::Good, + crate::data::game_mode::MapVisibility::Poor => Self::Poor, + crate::data::game_mode::MapVisibility::Bad => Self::Bad, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Copy)] +pub struct IntercomGameEvent { + pub map: IntercomGameMap, + pub visibility: IntercomGameVisibility, + pub mode: IntercomGameMode, + pub auto_heal: bool, +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct IntercomLobbyCustomGameConfig { - pub game_mode: CustomGameMode, + pub game_mode: IntercomGameMode, pub map: String, - pub map_visibility: CustomGameVisibility, + pub map_visibility: IntercomGameVisibility, pub health_regen: bool, pub capture_segment_memory: bool, pub base_shields_go_down: bool, @@ -350,7 +457,7 @@ pub struct IntercomWebServiceMessage { pub enum IntercomWebServiceUserMessage { DevMessage(IntercomDevMessage), Maintenance(IntercomMaintenanceMessage), - Workaround(IntercomWorkaroundMessage), + Workaround(IntercomWebServiceWorkaroundMessage), } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -366,10 +473,31 @@ pub struct IntercomMaintenanceMessage { #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(tag = "workaround")] -pub enum IntercomWorkaroundMessage { +pub enum IntercomWebServiceWorkaroundMessage { /// Trigger fix for getting stuck in build mode due to a bad/slow connection /// more info: https://git.ngram.ca/OpenJam/rc-servers/issues/127 KeybindLockout { }, + /// Lock game event expiry to currently-selected game mode for user + /// more info: https://git.ngram.ca/OpenJam/rc-servers/issues/84 + GameModeEventLock { + event: IntercomGameEvent, + }, + /// Return to normal game event expiry behaviour for user + /// more info: https://git.ngram.ca/OpenJam/rc-servers/issues/84 + GameModeEventUnlock { }, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(tag = "workaround")] +pub enum IntercomLobbyWorkaroundMessage { + +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(tag = "workaround")] +pub enum IntercomWorkaroundMessage { + Lobby(IntercomLobbyWorkaroundMessage), + WebService(IntercomWebServiceWorkaroundMessage), } pub fn generate_token(salt: &[u8], key: &[u8]) -> String { diff --git a/rc_core/src/persist/user/lobby.rs b/rc_core/src/persist/user/lobby.rs index ffbfc24..88f9dad 100644 --- a/rc_core/src/persist/user/lobby.rs +++ b/rc_core/src/persist/user/lobby.rs @@ -11,10 +11,6 @@ fn fake_impl_to_db(client_emu: &crate::persist::config::ClientEmulator) -> oj_rc #[async_trait::async_trait] impl super::LobbyUser for UserData { - fn user_id(&self) -> i32 { - self.account.id - } - async fn player_data(&self, cpu_counter: &crate::cubes::CpuListParser) -> Result { self.user_player_data(cpu_counter).await.map_err(|e| { if let Some(msg) = e.error_msg() { diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index 3de5412..ead4985 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -297,7 +297,6 @@ pub enum UserRole { #[async_trait::async_trait] pub trait LobbyUser { - fn user_id(&self) -> i32; async fn player_data(&self, cpu_counter: &crate::cubes::CpuListParser) -> Result; #[allow(clippy::too_many_arguments)] async fn start_game(&self, game: GameDescriptor, players: Vec, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser, team_chooser: &dyn super::TeamChooser, missing_players: usize) -> Result; diff --git a/rc_lobby_room/src/lobby.rs b/rc_lobby_room/src/lobby.rs index d3ba202..89c1e28 100644 --- a/rc_lobby_room/src/lobby.rs +++ b/rc_lobby_room/src/lobby.rs @@ -385,6 +385,18 @@ impl QueueHandler { } } + let to_unlock: Vec = q_entry.users.iter().map(|user| user.user.public_id().to_owned()).collect(); + if let Some(first) = q_entry.users.first() { + let first_user = first.user.clone(); + tokio::spawn(async move { + tokio::time::sleep(std::time::Duration::from_millis(10_000)).await; + log::info!("Unlocking {} users' game mode events after entering match", to_unlock.len()); + first_user.trigger_workaround( + oj_rc_core::persist::user::intercom::IntercomWorkaroundMessage::WebService(oj_rc_core::persist::user::intercom::IntercomWebServiceWorkaroundMessage::GameModeEventUnlock { }), + to_unlock, + ).await; + }); + } } async fn enter_custom_match( @@ -394,15 +406,15 @@ impl QueueHandler { user: &(dyn oj_rc_core::persist::user::LobbyUser + Send + Sync), ) { let mode = match session.config.game_mode { - oj_rc_core::persist::user::intercom::CustomGameMode::BattleArena => oj_rc_core::data::game_mode::GameMode::BattleArena, - oj_rc_core::persist::user::intercom::CustomGameMode::TeamDeathmatch => oj_rc_core::data::game_mode::GameMode::TeamDeathmatch, - oj_rc_core::persist::user::intercom::CustomGameMode::Pit => oj_rc_core::data::game_mode::GameMode::Pit, - oj_rc_core::persist::user::intercom::CustomGameMode::SuddenDeath => oj_rc_core::data::game_mode::GameMode::SuddenDeath, + oj_rc_core::persist::user::intercom::IntercomGameMode::BattleArena => oj_rc_core::data::game_mode::GameMode::BattleArena, + oj_rc_core::persist::user::intercom::IntercomGameMode::TeamDeathmatch => oj_rc_core::data::game_mode::GameMode::TeamDeathmatch, + oj_rc_core::persist::user::intercom::IntercomGameMode::Pit => oj_rc_core::data::game_mode::GameMode::Pit, + oj_rc_core::persist::user::intercom::IntercomGameMode::SuddenDeath => oj_rc_core::data::game_mode::GameMode::SuddenDeath, }; let visibility = match session.config.map_visibility { - oj_rc_core::persist::user::intercom::CustomGameVisibility::Good => oj_rc_core::data::game_mode::MapVisibility::Good, - oj_rc_core::persist::user::intercom::CustomGameVisibility::Poor => oj_rc_core::data::game_mode::MapVisibility::Poor, - oj_rc_core::persist::user::intercom::CustomGameVisibility::Bad => oj_rc_core::data::game_mode::MapVisibility::Bad, + oj_rc_core::persist::user::intercom::IntercomGameVisibility::Good => oj_rc_core::data::game_mode::MapVisibility::Good, + oj_rc_core::persist::user::intercom::IntercomGameVisibility::Poor => oj_rc_core::data::game_mode::MapVisibility::Poor, + oj_rc_core::persist::user::intercom::IntercomGameVisibility::Bad => oj_rc_core::data::game_mode::MapVisibility::Bad, }; let key = QueueKey { map: session.config.map.clone(), @@ -499,7 +511,7 @@ impl QueueHandler { let new_player = QueueUser { emitter: event_emitter, player: player_data, - user_id: oj_rc_core::persist::user::LobbyUser::user_id(lobby_user), + user_id: oj_rc_core::persist::user::CommonUser::account_id(lobby_user), enqueued_at: chrono::Utc::now(), user: user.clone(), }; @@ -657,6 +669,17 @@ impl QueueHandler { self.ensure_autostart_task_running(); + log::info!("Locking user {} game move event as they enter queue", user.public_id()); + let game_event = oj_rc_core::persist::user::intercom::IntercomGameEvent { + map: oj_rc_core::persist::user::intercom::IntercomGameMap::from_str(&map).unwrap_or(oj_rc_core::persist::user::intercom::IntercomGameMap::Mars1), + visibility: oj_rc_core::persist::user::intercom::IntercomGameVisibility::from_data(visibility), + mode: oj_rc_core::persist::user::intercom::IntercomGameMode::from_data(mode), + auto_heal, + }; + user.trigger_workaround(oj_rc_core::persist::user::intercom::IntercomWorkaroundMessage::WebService(oj_rc_core::persist::user::intercom::IntercomWebServiceWorkaroundMessage::GameModeEventLock { + event: game_event.clone(), + }), vec![user.public_id().to_owned()]).await; + let key = QueueKey { map, mode, visibility, auto_heal, }; @@ -669,7 +692,7 @@ impl QueueHandler { let new_player = QueueUser { emitter: event_emitter, player: player_data, - user_id: oj_rc_core::persist::user::LobbyUser::user_id(lobby_user), + user_id: oj_rc_core::persist::user::CommonUser::account_id(lobby_user), enqueued_at: chrono::Utc::now(), user: user.clone(), }; @@ -684,9 +707,13 @@ impl QueueHandler { match self.change_strategy { GamemodeChangeStrategy::Upgrade => { let mut new_queue_map = std::collections::HashMap::::with_capacity(lock.len()); + let mut to_update_lockout = Vec::new(); let mut count = 0; for (_key, mut q_entry) in lock.drain() { count += q_entry.users.len(); + for user in q_entry.users.iter() { + to_update_lockout.push(user.user.public_id().to_owned()); + } if let Some(values) = new_queue_map.get_mut(&key) { values.users.append(&mut q_entry.users); values.platoons.extend(q_entry.platoons); @@ -702,9 +729,17 @@ impl QueueHandler { if count != 0 { log::info!("Upgraded {} users in queue to new gamemode {}", count, key.short()); } + if !to_update_lockout.is_empty() { + log::info!("Updating game event locks for {} users due to lobby upgrade", to_update_lockout.len()); + user.trigger_workaround( + oj_rc_core::persist::user::intercom::IntercomWorkaroundMessage::WebService(oj_rc_core::persist::user::intercom::IntercomWebServiceWorkaroundMessage::GameModeEventLock { event: game_event }), + to_update_lockout, + ).await; + } }, GamemodeChangeStrategy::Notify => { let mut seen = std::collections::HashSet::new(); + let mut to_update_lockout = Vec::new(); for (_key, q_entry) in lock.drain() { for player in q_entry.users { if seen.contains(&player.user_id) { continue; } @@ -714,6 +749,7 @@ impl QueueHandler { text: "Please requeue".to_owned(), }); seen.insert(player.user_id); + to_update_lockout.push(player.user.public_id().to_owned()); } for platoon in q_entry.platoons.into_values() { for player in platoon.members { @@ -730,6 +766,13 @@ impl QueueHandler { if !seen.is_empty() { log::info!("Notified {} users in queue of new gamemode {}", seen.len(), key.short()); } + if !to_update_lockout.is_empty() { + log::info!("Removing game event locks for {} users due to lobby notify", to_update_lockout.len()); + user.trigger_workaround( + oj_rc_core::persist::user::intercom::IntercomWorkaroundMessage::WebService(oj_rc_core::persist::user::intercom::IntercomWebServiceWorkaroundMessage::GameModeEventUnlock { }), + to_update_lockout, + ).await; + } }, GamemodeChangeStrategy::Ignore => { log::debug!("Gamemode appears to have changed to {}, ignoring already-queued players", key.short()); @@ -786,7 +829,12 @@ impl QueueHandler { pub async fn leave_queue(&self, user: std::sync::Arc + Send + Sync>>) { let public_id = user.public_id(); - let user_id = oj_rc_core::persist::user::LobbyUser::user_id(user.as_ref().as_ref()); + let user_id = oj_rc_core::persist::user::CommonUser::account_id(user.as_ref().as_ref()); + log::info!("Unlocking user {} game mode events after leaving queue", public_id); + user.trigger_workaround( + oj_rc_core::persist::user::intercom::IntercomWorkaroundMessage::WebService(oj_rc_core::persist::user::intercom::IntercomWebServiceWorkaroundMessage::GameModeEventUnlock { }), + vec![public_id.to_owned()], + ).await; if let Some(session_id) = self.custom_game_for_user.read().await.get(public_id) { // player is in custom game session let mut lock = self.users_in_custom_games_queue.lock().await; diff --git a/rc_services_room/src/custom_game_tracker.rs b/rc_services_room/src/custom_game_tracker.rs index 94642d6..307e3ff 100644 --- a/rc_services_room/src/custom_game_tracker.rs +++ b/rc_services_room/src/custom_game_tracker.rs @@ -651,20 +651,20 @@ impl GameConfig { fn as_core(&self) -> oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameConfig { oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameConfig { game_mode: match self.game_mode { - oj_rc_core::data::game_mode::GameMode::BattleArena => oj_rc_core::persist::user::intercom::CustomGameMode::BattleArena, - oj_rc_core::data::game_mode::GameMode::TeamDeathmatch => oj_rc_core::persist::user::intercom::CustomGameMode::TeamDeathmatch, - oj_rc_core::data::game_mode::GameMode::Pit => oj_rc_core::persist::user::intercom::CustomGameMode::Pit, - oj_rc_core::data::game_mode::GameMode::SuddenDeath => oj_rc_core::persist::user::intercom::CustomGameMode::SuddenDeath, + oj_rc_core::data::game_mode::GameMode::BattleArena => oj_rc_core::persist::user::intercom::IntercomGameMode::BattleArena, + oj_rc_core::data::game_mode::GameMode::TeamDeathmatch => oj_rc_core::persist::user::intercom::IntercomGameMode::TeamDeathmatch, + oj_rc_core::data::game_mode::GameMode::Pit => oj_rc_core::persist::user::intercom::IntercomGameMode::Pit, + oj_rc_core::data::game_mode::GameMode::SuddenDeath => oj_rc_core::persist::user::intercom::IntercomGameMode::SuddenDeath, invalid => { log::warn!("Custom game set to invalid mode {:?} (using sudden death as fallback)", invalid); - oj_rc_core::persist::user::intercom::CustomGameMode::SuddenDeath + oj_rc_core::persist::user::intercom::IntercomGameMode::SuddenDeath }, }, map: self.map.clone(), map_visibility: match self.map_visibility { - oj_rc_core::data::game_mode::MapVisibility::Good => oj_rc_core::persist::user::intercom::CustomGameVisibility::Good, - oj_rc_core::data::game_mode::MapVisibility::Poor => oj_rc_core::persist::user::intercom::CustomGameVisibility::Poor, - oj_rc_core::data::game_mode::MapVisibility::Bad => oj_rc_core::persist::user::intercom::CustomGameVisibility::Bad, + oj_rc_core::data::game_mode::MapVisibility::Good => oj_rc_core::persist::user::intercom::IntercomGameVisibility::Good, + oj_rc_core::data::game_mode::MapVisibility::Poor => oj_rc_core::persist::user::intercom::IntercomGameVisibility::Poor, + oj_rc_core::data::game_mode::MapVisibility::Bad => oj_rc_core::persist::user::intercom::IntercomGameVisibility::Bad, }, health_regen: self.health_regen, capture_segment_memory: self.capture_segment_memory, diff --git a/rc_services_room/src/events/handler.rs b/rc_services_room/src/events/handler.rs index 1519dd5..4479dc7 100644 --- a/rc_services_room/src/events/handler.rs +++ b/rc_services_room/src/events/handler.rs @@ -1,11 +1,12 @@ use oj_rc_core::persist::user::IntercomListener; -use oj_rc_core::persist::user::intercom::{IntercomWebServiceUserMessage, IntercomWorkaroundMessage}; +use oj_rc_core::persist::user::intercom::{IntercomWebServiceUserMessage, IntercomWebServiceWorkaroundMessage}; pub struct IntercomHandler { listener: IntercomListener, user: std::sync::Weak + Send + Sync>>, emitter: polariton_server::events::WeakEventEmitter<()>, keybind_workaround: std::sync::Arc, + game_event_seq: std::sync::Arc>, } impl IntercomHandler { @@ -14,12 +15,14 @@ impl IntercomHandler { user: &std::sync::Arc + Send + Sync>>, emitter: &polariton_server::events::EventEmitter<()>, keybind_workaround: &std::sync::Arc, + game_event_seq: &std::sync::Arc>, ) -> Self { Self { listener, user: std::sync::Arc::downgrade(user), emitter: emitter.to_owned().downgrade(), keybind_workaround: keybind_workaround.to_owned(), + game_event_seq: game_event_seq.to_owned(), } } @@ -28,6 +31,7 @@ impl IntercomHandler { user: std::sync::Weak + Send + Sync>>, emitter: polariton_server::events::WeakEventEmitter<()>, keybind_workaround: std::sync::Arc, + game_event_seq: std::sync::Arc>, ) { use futures::StreamExt; let mut listener = listener.listen().await; @@ -54,7 +58,7 @@ impl IntercomHandler { }; emitter.emit(event); } - IntercomWebServiceUserMessage::Workaround(IntercomWorkaroundMessage::KeybindLockout { }) => { + IntercomWebServiceUserMessage::Workaround(IntercomWebServiceWorkaroundMessage::KeybindLockout { }) => { let session = keybind_workaround.add_user(user.account_id(), user.public_id().to_owned()).await; let non_me = session.users.first().unwrap(); let event = super::CustomGameInvite { @@ -65,6 +69,22 @@ impl IntercomHandler { invited_to_team_a: true, }; emitter.emit(event); + }, + IntercomWebServiceUserMessage::Workaround(IntercomWebServiceWorkaroundMessage::GameModeEventLock { event }) => { + let is_replaced = game_event_seq.lock().await.add_lockout( + user.account_id(), + oj_rc_core::persist::config::GameEvent { + map: event.map.into_conf(), + visibility: event.visibility.into_conf(), + mode: event.mode.into_conf(), + auto_heal: event.auto_heal, + }, + ); + log::info!("Activated game event lockout for user {} (replaced? {})", user.public_id(), is_replaced); + }, + IntercomWebServiceUserMessage::Workaround(IntercomWebServiceWorkaroundMessage::GameModeEventUnlock { }) => { + let is_existed = game_event_seq.lock().await.remove_lockout(user.account_id()); + log::info!("Deactivated game event lockout for user {} (existed? {})", user.public_id(), is_existed); } } } else { @@ -80,6 +100,6 @@ impl IntercomHandler { } pub fn run(self) -> tokio::task::JoinHandle<()> { - tokio::spawn(Self::run_loop(self.listener, self.user, self.emitter, self.keybind_workaround)) + tokio::spawn(Self::run_loop(self.listener, self.user, self.emitter, self.keybind_workaround, self.game_event_seq)) } } diff --git a/rc_services_room/src/main.rs b/rc_services_room/src/main.rs index 8c4ec02..f498192 100644 --- a/rc_services_room/src/main.rs +++ b/rc_services_room/src/main.rs @@ -31,6 +31,7 @@ pub struct InitConfig { pub custom_games: std::sync::Arc, pub user_mesh: std::sync::Arc, pub workarounds: workarounds::Workarounds, + pub game_event_sequence: std::sync::Arc>, } #[tokio::main] @@ -49,6 +50,7 @@ async fn main() -> std::io::Result<()> { &parsers, vehicle_validator_plugins_path, ); + let game_event_sequence = std::sync::Arc::new(tokio::sync::Mutex::new(>::gamemode_events(&cubes))); let init_ctx = std::sync::Arc::new(InitConfig { cubes, users, @@ -58,6 +60,7 @@ async fn main() -> std::io::Result<()> { custom_games: std::sync::Arc::new(custom_game_tracker::CustomGameMesh::new()), user_mesh: std::sync::Arc::new(user_service::UserMesh::new()), workarounds: workarounds::Workarounds::new(), + game_event_sequence, }); let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(&init_ctx), polariton_server::events::EventsHandler::new())); diff --git a/rc_services_room/src/operations/game_event_params.rs b/rc_services_room/src/operations/game_event_params.rs index 4c071fd..5608d9e 100644 --- a/rc_services_room/src/operations/game_event_params.rs +++ b/rc_services_room/src/operations/game_event_params.rs @@ -21,7 +21,7 @@ RC_Planet_Neptune_01_CTF */ pub struct GameEventsParamsProvider { - sequence: std::sync::Mutex, + sequence: std::sync::Arc>, } #[async_trait::async_trait] @@ -32,7 +32,7 @@ impl Operation<()> for GameEventsParamsProvider { match user.user() { Ok(user_info) => { let mut params = params.to_dict(); - let current_mode = self.sequence.lock().unwrap().now(user_info.current_game_event_setter()); + let current_mode = self.sequence.lock().await.now(user_info.current_game_event_setter(), user_info.account_id()); params.insert(MAP_NAMES_PARAM_KEY, current_mode.maps); params.insert(VISIBILITY_PARAM_KEY, current_mode.visibilities); params.insert(MODE_PARAM_KEY, current_mode.modes); @@ -64,9 +64,8 @@ impl OperationCode for GameEventsParamsProvider { } } -pub(super) fn event_system_params_provider(conf: &oj_rc_core::ConfigImpl) -> GameEventsParamsProvider { - let game_seq = >::gamemode_events(conf); +pub(super) fn event_system_params_provider(init_ctx: &crate::InitConfig) -> GameEventsParamsProvider { GameEventsParamsProvider { - sequence: std::sync::Mutex::new(game_seq), + sequence: init_ctx.game_event_sequence.clone(), } } diff --git a/rc_services_room/src/operations/mod.rs b/rc_services_room/src/operations/mod.rs index 8de70ff..f8afc48 100644 --- a/rc_services_room/src/operations/mod.rs +++ b/rc_services_room/src/operations/mod.rs @@ -124,7 +124,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler OperationsHandler::new() .modify(>::polariton_operation_modifier(&init_ctx.cubes)) .add(eac::EacChallengeIgnorer) - .add(more_auth::more_auth_provider(&init_ctx.user_mesh, init_ctx.workarounds.edit_mode_input_lockup())) + .add(more_auth::more_auth_provider(&init_ctx.user_mesh, init_ctx.workarounds.edit_mode_input_lockup(), &init_ctx.game_event_sequence)) .add(versioner::version_teller(&init_ctx.cubes)) .add(maintenancer::maintenace_teller(&init_ctx.cubes)) .add(game_quality::QualityConfigTeller) @@ -166,7 +166,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler .add(custom_game_session::custom_session_provider(&init_ctx.custom_games, init_ctx.workarounds.edit_mode_input_lockup())) .add(user_xp::get_user_xp_provider()) .add(garage_upgrades::garage_upgrades_provider(&init_ctx.cubes)) - .add(game_event_params::event_system_params_provider(&init_ctx.cubes)) + .add(game_event_params::event_system_params_provider(init_ctx)) .add(garage_bay_uuid::garage_id_provider()) .add(tech_tree_data::tech_tree_layout_provider(&init_ctx.cubes)) .add(item_shop_bundles::item_bundle_provider(&init_ctx.cubes)) diff --git a/rc_services_room/src/operations/more_auth.rs b/rc_services_room/src/operations/more_auth.rs index 623f0e7..4cf38ae 100644 --- a/rc_services_room/src/operations/more_auth.rs +++ b/rc_services_room/src/operations/more_auth.rs @@ -4,12 +4,14 @@ use polariton_server::operations::{Operation, OperationCode}; pub struct MoreLobbyAuth { mesh: std::sync::Arc, keybind_workaround: std::sync::Arc, + game_event_seq: std::sync::Arc>, } -pub fn more_auth_provider(mesh: &std::sync::Arc, keybind_workaround: std::sync::Arc) -> MoreLobbyAuth { +pub fn more_auth_provider(mesh: &std::sync::Arc, keybind_workaround: std::sync::Arc, game_event_seq: &std::sync::Arc>,) -> MoreLobbyAuth { MoreLobbyAuth { mesh: mesh.to_owned(), keybind_workaround, + game_event_seq: game_event_seq.to_owned(), } } @@ -50,6 +52,7 @@ impl Operation for MoreLobbyAuth { &user_info, user.event_sender(), &self.keybind_workaround, + &self.game_event_seq, ).run(); return polariton::operation::OperationResponse { code: Self::op_code(),