diff --git a/rc_core/src/persist/config/traits.rs b/rc_core/src/persist/config/traits.rs index fff0745..58ac340 100644 --- a/rc_core/src/persist/config/traits.rs +++ b/rc_core/src/persist/config/traits.rs @@ -144,7 +144,7 @@ pub struct GameEventSequence { } impl GameEventSequence { - pub fn now(&mut self) -> GameEventTransmissible { + pub fn now(&mut self, updater: Box) -> GameEventTransmissible { let time_now = chrono::Utc::now().timestamp(); let mut item_now = &self.modes[self.index]; if time_now >= (item_now.duration.as_secs() as i64) + self.started { @@ -152,6 +152,26 @@ impl GameEventSequence { self.index = self.strategy.next(self.index, self.modes.len()); item_now = &self.modes[self.index]; self.started = time_now; + let mp = crate::persist::user::CurrentGameEvent { + map: crate::data::game_mode::GameMap::from_persist(item_now.multiplayer.map).as_str().to_owned(), + visibility: crate::data::game_mode::MapVisibility::from_persist(item_now.multiplayer.visibility), + mode: crate::data::game_mode::GameMode::from_persist(item_now.multiplayer.mode), + auto_heal: item_now.multiplayer.auto_heal, + start: self.started, + end: self.started + item_now.duration.as_secs() as i64, + }; + let sp = crate::persist::user::CurrentGameEvent { + map: crate::data::game_mode::GameMap::from_persist(item_now.singleplayer.map).as_str().to_owned(), + visibility: crate::data::game_mode::MapVisibility::from_persist(item_now.singleplayer.visibility), + mode: crate::data::game_mode::GameMode::from_persist(item_now.singleplayer.mode), + auto_heal: item_now.singleplayer.auto_heal, + start: self.started, + end: self.started + item_now.duration.as_secs() as i64, + }; + tokio::spawn(async move { + updater.set_multiplayer(mp).await; + updater.set_singleplayer(sp).await; + }); } let remaining_ticks = ((item_now.duration.as_secs() as i64) - (time_now - self.started)) * 10_000_000; GameEventTransmissible { diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index 9cb64c7..6b75655 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -978,6 +978,79 @@ impl super::User for UserData { })?; Ok(()) } + + fn current_game_event_setter(&self) -> Box { + Box::new(GameEventSetterImpl { + db: self.db.clone(), + }) + } +} + +struct GameEventSetterImpl { + db: std::sync::Arc, +} + +impl GameEventSetterImpl { + async fn insert_event(&self, variant: oj_rc_database::schema::game_event::EventVariant, event: super::CurrentGameEvent) { + let now = chrono::Utc::now().timestamp(); + let model = oj_rc_database::schema::game_event::ActiveModel { + id: oj_rc_database::sea_orm::ActiveValue::NotSet, + creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now), + map: oj_rc_database::sea_orm::ActiveValue::Set(event.map), + mode: oj_rc_database::sea_orm::ActiveValue::Set(event.mode.to_db()), + visibility: oj_rc_database::sea_orm::ActiveValue::Set(event.visibility.to_db()), + auto_heal: oj_rc_database::sea_orm::ActiveValue::Set(event.auto_heal), + start: oj_rc_database::sea_orm::ActiveValue::Set(event.start), + end: oj_rc_database::sea_orm::ActiveValue::Set(event.end), + variant: oj_rc_database::sea_orm::ActiveValue::Set(variant), + }; + if let Err(e) = self.db.insert_game_event(model).await { + log::error!("Failed to save new game event: {}", e); + } + } + + async fn select_event_now(&self, variant: oj_rc_database::schema::game_event::EventVariant) -> Option { + let now = chrono::Utc::now().timestamp(); + match self.db.game_event_at_time(now, variant).await { + Err(e) => { + log::error!("Failed to retrieve current game event: {}", e); + None + }, + Ok(None) => { + log::warn!("Failed to find current game event"); + None + }, + Ok(Some(event)) => { + Some(super::CurrentGameEvent { + map: event.map, + visibility: crate::data::game_mode::MapVisibility::from_db(event.visibility), + mode: crate::data::game_mode::GameMode::from_db(event.mode), + auto_heal: event.auto_heal, + start: event.start, + end: event.end, + }) + }, + } + } +} + +#[async_trait::async_trait] +impl super::GameEventSetter for GameEventSetterImpl { + async fn set_multiplayer(&self, event: super::CurrentGameEvent) { + self.insert_event(oj_rc_database::schema::game_event::EventVariant::Multiplayer, event).await + } + + async fn get_multiplayer(&self) -> Option { + self.select_event_now(oj_rc_database::schema::game_event::EventVariant::Multiplayer).await + } + + async fn set_singleplayer(&self, event: super::CurrentGameEvent) { + self.insert_event(oj_rc_database::schema::game_event::EventVariant::Singleplayer, event).await + } + + async fn get_singleplayer(&self) -> Option { + self.select_event_now(oj_rc_database::schema::game_event::EventVariant::Singleplayer).await + } } #[async_trait::async_trait] diff --git a/rc_core/src/persist/user/mod.rs b/rc_core/src/persist/user/mod.rs index a54d0a8..699caee 100644 --- a/rc_core/src/persist/user/mod.rs +++ b/rc_core/src/persist/user/mod.rs @@ -11,7 +11,7 @@ mod inventory; pub use inventory::UnlockedParts; mod traits; -pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor}; +pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent}; pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key"; diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index 3fc48dc..9e738c9 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -81,6 +81,15 @@ pub trait User: ChatUser + LobbyUser + MultiplayerUser { async fn last_seen(&self) -> Result; async fn get_avatar_info(&self) -> Result, i16>; async fn set_avatar_info(&self, info: AvatarInfo) -> Result<(), i16>; + fn current_game_event_setter(&self) -> Box; +} + +#[async_trait::async_trait] +pub trait GameEventSetter: Send + Sync + 'static { + async fn set_multiplayer(&self, event: CurrentGameEvent); + async fn get_multiplayer(&self) -> Option; + async fn set_singleplayer(&self, event: CurrentGameEvent); + async fn get_singleplayer(&self) -> Option; } pub struct UserSlots { @@ -234,6 +243,15 @@ pub trait LobbyUser { async fn start_game(&self, game: GameDescriptor, players: Vec) -> Result<(), polariton_server::operations::SimpleOpError>; } +pub struct CurrentGameEvent { + pub map: String, + pub visibility: crate::data::game_mode::MapVisibility, + pub mode: crate::data::game_mode::GameMode, + pub auto_heal: bool, + pub start: i64, // seconds since Unix epoch + pub end: i64, // seconds since Unix epoch +} + pub struct GameDescriptor { pub guid: String, pub map: String, diff --git a/rc_database/src/migration/m20250722_000001_create_game_event_table.rs b/rc_database/src/migration/m20250722_000001_create_game_event_table.rs new file mode 100644 index 0000000..22f5bbc --- /dev/null +++ b/rc_database/src/migration/m20250722_000001_create_game_event_table.rs @@ -0,0 +1,45 @@ +use sea_orm_migration::prelude::*; + +pub struct Migration; + +impl MigrationName for Migration { + fn name(&self) -> &str { + "m20250722_000001_create_game_event_table" + } +} + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + // Define how to apply this migration: Create the Permissions table. + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(crate::schema::game_event::Entity) + .col( + ColumnDef::new(crate::schema::game_event::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(crate::schema::game_event::Column::CreationTime).big_integer().not_null()) + .col(ColumnDef::new(crate::schema::game_event::Column::Map).string().not_null()) + .col(ColumnDef::new(crate::schema::game_event::Column::Mode).string().not_null()) + .col(ColumnDef::new(crate::schema::game_event::Column::Visibility).string().not_null()) + .col(ColumnDef::new(crate::schema::game_event::Column::AutoHeal).boolean().not_null()) + .col(ColumnDef::new(crate::schema::game_event::Column::Start).big_integer().not_null()) + .col(ColumnDef::new(crate::schema::game_event::Column::End).big_integer().not_null()) + .col(ColumnDef::new(crate::schema::game_event::Column::Variant).string().not_null()) + .to_owned(), + ) + .await + } + + // Define how to rollback this migration: Drop the Permissions table. + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(crate::schema::game_event::Entity).to_owned()) + .await + } +} diff --git a/rc_database/src/migration/mod.rs b/rc_database/src/migration/mod.rs index f3fcb90..f0889cb 100644 --- a/rc_database/src/migration/mod.rs +++ b/rc_database/src/migration/mod.rs @@ -9,6 +9,7 @@ mod m20250526_000001_add_garage_customisation; mod m20250529_000001_create_sanction_table; mod m20250713_000001_create_game_table; mod m20250713_000002_create_player_table; +mod m20250722_000001_create_game_event_table; pub struct Migrator; @@ -25,6 +26,7 @@ impl MigratorTrait for Migrator { Box::new(m20250529_000001_create_sanction_table::Migration), Box::new(m20250713_000001_create_game_table::Migration), Box::new(m20250713_000002_create_player_table::Migration), + Box::new(m20250722_000001_create_game_event_table::Migration), ] } } diff --git a/rc_database/src/schema/game_event.rs b/rc_database/src/schema/game_event.rs new file mode 100644 index 0000000..ae63a3b --- /dev/null +++ b/rc_database/src/schema/game_event.rs @@ -0,0 +1,28 @@ +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "game_events")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub creation_time: i64, // seconds since unix epoch + pub map: String, + pub mode: super::multiplayer_game::GameMode, + pub visibility: super::multiplayer_game::MapVisibility, + pub auto_heal: bool, + pub start: i64, // seconds since unix epoch + pub end: i64, // seconds since unix epoch + pub variant: EventVariant, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "PascalCase")] +pub enum EventVariant { + Multiplayer, + Singleplayer, +} diff --git a/rc_database/src/schema/mod.rs b/rc_database/src/schema/mod.rs index 953e7f3..fc4c34a 100644 --- a/rc_database/src/schema/mod.rs +++ b/rc_database/src/schema/mod.rs @@ -8,6 +8,7 @@ pub mod common_query; pub mod sanction; pub mod multiplayer_game; pub mod multiplayer_game_player; +pub mod game_event; pub fn parse_int_csv(s: &str) -> Vec { s.split(',').filter_map(|i_as_s| { diff --git a/rc_database/src/wrapper.rs b/rc_database/src/wrapper.rs index a9d6c06..cdeebc9 100644 --- a/rc_database/src/wrapper.rs +++ b/rc_database/src/wrapper.rs @@ -345,4 +345,18 @@ impl Database { crate::schema::multiplayer_game_player::Entity::insert_many(entities.into_iter()).exec(&self.orm).await?; Ok(()) } + + pub async fn game_event_at_time(&self, time: i64, variant: crate::schema::game_event::EventVariant) -> Result, sea_orm::DbErr> { + crate::schema::game_event::Entity::find() + .filter(crate::schema::game_event::Column::Start.lte(time)) + .filter(crate::schema::game_event::Column::End.gte(time)) + .filter(crate::schema::game_event::Column::Variant.eq(variant)) + .order_by_desc(crate::schema::game_event::Column::Start) + .one(&self.orm) + .await + } + + pub async fn insert_game_event(&self, entity: crate::schema::game_event::ActiveModel) -> Result { + entity.insert(&self.orm).await + } } diff --git a/rc_lobby_room/src/operations/join_queue.rs b/rc_lobby_room/src/operations/join_queue.rs index 90b0945..d7dd004 100644 --- a/rc_lobby_room/src/operations/join_queue.rs +++ b/rc_lobby_room/src/operations/join_queue.rs @@ -36,14 +36,17 @@ impl SimpleOperation for QueueJoinProvider { params.insert(PERSONAL_RANKING_PARAM_KEY, Typed::Double(42.0)); let events = user.event_sender(); let user_info = user.user()?; - self.queue_handler.join_queue( - "FIXME_map".to_owned(), - oj_rc_core::data::game_mode::GameMode::BattleArena, // FIXME - oj_rc_core::data::game_mode::MapVisibility::Good, // FIXME - true, // FIXME - user_info.as_ref().as_ref(), - events.to_owned(), - ).await; + if let Some(current_lobby) = user_info.current_game_event_setter().get_multiplayer().await { + self.queue_handler.join_queue( + current_lobby.map, + current_lobby.mode, + current_lobby.visibility, + current_lobby.auto_heal, + user_info.as_ref().as_ref(), + events.to_owned(), + ).await; + } + } } } diff --git a/rc_services_room/src/operations/game_event_params.rs b/rc_services_room/src/operations/game_event_params.rs index aa5dd6c..9c9cebb 100644 --- a/rc_services_room/src/operations/game_event_params.rs +++ b/rc_services_room/src/operations/game_event_params.rs @@ -28,20 +28,33 @@ pub struct GameEventsParamsProvider { impl Operation<()> for GameEventsParamsProvider { type User = crate::UserTy; - async fn handle_async(&self, params: polariton::operation::ParameterTable<()>, _user: &Self::User) -> polariton::operation::OperationResponse<()> { - let mut params = params.to_dict(); - let current_mode = self.sequence.lock().unwrap().now(); - 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); - params.insert(AUTO_HEAL_PARAM_KEY, current_mode.auto_heals); - params.insert(REMAINING_TICKS_PARAM_KEY, current_mode.remaining_ticks); - polariton::operation::OperationResponse { - code: Self::op_code(), - return_code: 0, - message: polariton::operation::Typed::Null, - params: params.into(), + async fn handle_async(&self, params: polariton::operation::ParameterTable<()>, user: &Self::User) -> polariton::operation::OperationResponse<()> { + 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()); + 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); + params.insert(AUTO_HEAL_PARAM_KEY, current_mode.auto_heals); + params.insert(REMAINING_TICKS_PARAM_KEY, current_mode.remaining_ticks); + polariton::operation::OperationResponse { + code: Self::op_code(), + return_code: 0, + message: polariton::operation::Typed::Null, + params: params.into(), + } + }, + Err(e) => { + polariton::operation::OperationResponse { + code: Self::op_code(), + return_code: e, + message: polariton::operation::Typed::Null, + params: params.into(), + } + } } + } }