1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Add game event refresh freezing when user enters lobby; fix #84

This commit is contained in:
NG (Graham)
2026-08-21 21:04:37 -04:00
parent 1f0aa22f68
commit b717ca1ed3
14 changed files with 347 additions and 86 deletions

View File

@@ -79,6 +79,21 @@ impl GameMap {
crate::persist::config::GameMap::Earth2 => Self::Earth2,
}
}
#[inline]
pub fn from_str(s: &str) -> Option<Self> {
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)]

View File

@@ -379,6 +379,7 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
index: first,
started: chrono::Utc::now().timestamp(),
needs_to_be_saved: true,
lockouts: std::collections::HashMap::new(),
}
}

View File

@@ -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<GameEvents>,
pub index: usize,
pub started: i64,
pub(crate) needs_to_be_saved: bool,
pub(crate) lockouts: std::collections::HashMap<i32, GameEvent>,
}
impl GameEventSequence {
pub fn now(&mut self, updater: Box<dyn crate::persist::user::GameEventSetter>) -> GameEventTransmissible {
pub fn now(&mut self, updater: Box<dyn crate::persist::user::GameEventSetter>, 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 {

View File

@@ -229,14 +229,20 @@ impl super::IntercomUser for super::account_json::UserData {
async fn trigger_workaround(&self, msg: IntercomWorkaroundMessage, to: Vec<String>) {
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<IntercomLobbyCustomGameUserData>,
}
#[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<Self> {
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 {

View File

@@ -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<crate::data::player_data::PlayerData, polariton_server::operations::SimpleOpError> {
self.user_player_data(cpu_counter).await.map_err(|e| {
if let Some(msg) = e.error_msg() {

View File

@@ -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<crate::data::player_data::PlayerData, polariton_server::operations::SimpleOpError>;
#[allow(clippy::too_many_arguments)]
async fn start_game(&self, game: GameDescriptor, players: Vec<PlayerLobbyDescriptor>, 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<FakePlayers, polariton_server::operations::SimpleOpError>;