diff --git a/Cargo.lock b/Cargo.lock index adb6042..efcc087 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3068,6 +3068,7 @@ dependencies = [ "log", "polariton", "polariton_server", + "rand 0.9.0", "rc_database", "rc_factory", "serde", diff --git a/rc_core/Cargo.toml b/rc_core/Cargo.toml index 5501cca..6549732 100644 --- a/rc_core/Cargo.toml +++ b/rc_core/Cargo.toml @@ -17,6 +17,7 @@ chrono = "0.4" polariton_server.workspace = true tokio = { version = "1.43", features = [ "net", "macros", "rt-multi-thread", "io-util", "time" ] } async-trait.workspace = true +rand = { version = "0.9", features = [ "thread_rng" ] } # auth libfj.workspace = true diff --git a/rc_core/src/data/game_mode.rs b/rc_core/src/data/game_mode.rs index 55ed80c..68475cf 100644 --- a/rc_core/src/data/game_mode.rs +++ b/rc_core/src/data/game_mode.rs @@ -39,3 +39,102 @@ impl GameModeConfigs { }) } } + +pub enum GameMap { + Mars1, + Mars2, + Mars3, + Neptune1, + Neptune2, + Neptune3, + Earth1, + Earth2, +} + +impl GameMap { + #[inline] + pub fn as_str(&self) -> &'static str { + match self { + Self::Mars1 => "RC_Planet_Mars_01_CTF", + Self::Mars2 => "RC_Planet_Mars_02_BA", + Self::Mars3 => "RC_Planet_Mars_03_BA", + Self::Neptune1 => "RC_Planet_Neptune_01_CTF", + Self::Neptune2 => "RC_Planet_Neptune_02_BA", + Self::Neptune3 => "RC_Planet_Neptune_03_BA", + Self::Earth1 => "RC_Planet_Earth_01_BA", + Self::Earth2 => "RC_Planet_Earth_02_BA", + } + } + + #[inline] + pub fn from_persist(map: crate::persist::config::GameMap) -> Self { + match map { + crate::persist::config::GameMap::Mars1 => Self::Mars1, + crate::persist::config::GameMap::Mars2 => Self::Mars2, + crate::persist::config::GameMap::Mars3 => Self::Mars3, + crate::persist::config::GameMap::Neptune1 => Self::Neptune1, + crate::persist::config::GameMap::Neptune2 => Self::Neptune2, + crate::persist::config::GameMap::Neptune3 => Self::Neptune3, + crate::persist::config::GameMap::Earth1 => Self::Earth1, + crate::persist::config::GameMap::Earth2 => Self::Earth2, + } + } +} + +#[repr(u8)] +#[derive(Copy, Clone)] +pub enum GameMode { + BattleArena = 0, + SuddenDeath = 1, + Pit = 2, + TestMode = 3, + SinglePlayer = 4, + TeamDeathmatch = 5, + Campaign = 6, +} + +impl GameMode { + pub fn as_str(&self) -> &'static str { + match self { + GameMode::BattleArena => "BattleArena", + GameMode::SuddenDeath => "SuddenDeath", + GameMode::Pit => "Pit", + GameMode::TestMode => "TestMode", + GameMode::SinglePlayer => "SinglePlayerTDM", + GameMode::TeamDeathmatch => "TeamDeathmatch", + GameMode::Campaign => "Campaign", + } + } + + #[inline] + pub fn from_persist(mode: crate::persist::config::GameType) -> Self { + match mode { + crate::persist::config::GameType::BattleArena => Self::BattleArena, + crate::persist::config::GameType::SuddenDeath => Self::SuddenDeath, + crate::persist::config::GameType::Pit => Self::Pit, + crate::persist::config::GameType::TestMode => Self::TestMode, + crate::persist::config::GameType::SinglePlayer => Self::SinglePlayer, + crate::persist::config::GameType::TeamDeathmatch => Self::TeamDeathmatch, + crate::persist::config::GameType::Campaign => Self::Campaign, + } + } +} + +#[repr(u8)] +#[derive(Copy, Clone)] +pub enum MapVisibility { + Good = 0, + Poor = 1, + Bad = 2, // VeryPoor +} + +impl MapVisibility { + #[inline] + pub fn from_persist(mode: crate::persist::config::GameVisibility) -> Self { + match mode { + crate::persist::config::GameVisibility::Good => Self::Good, + crate::persist::config::GameVisibility::Poor => Self::Poor, + crate::persist::config::GameVisibility::Bad => Self::Bad, + } + } +} diff --git a/rc_core/src/persist/combat.rs b/rc_core/src/persist/combat.rs index 09d36e1..80ba80c 100644 --- a/rc_core/src/persist/combat.rs +++ b/rc_core/src/persist/combat.rs @@ -10,6 +10,8 @@ pub struct BattleConfig { pub games: GameModes, #[serde(default = "default_campaigns")] pub singleplayer: super::Campaigns, + #[serde(default = "default_rotation")] + pub rotation: GameEventSequence, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -103,6 +105,124 @@ impl std::convert::Into for GameModes { } } +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct GameEventSequence { + pub strategy: GameRotationStrategy, + pub modes: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Copy)] +pub enum GameRotationStrategy { + Sequence, + Random, +} + +impl GameRotationStrategy { + pub fn into_conf(self) -> crate::persist::config::GameRotationStrategy { + match self { + Self::Sequence => crate::persist::config::GameRotationStrategy::Sequence, + Self::Random => crate::persist::config::GameRotationStrategy::Random, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct GameEvents { + pub singleplayer: GameEvent, + pub multiplayer: GameEvent, + pub duration_s: u64, // seconds +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct GameEvent { + pub map: GameMap, + pub visibility: GameVisibility, + pub mode: GameType, + pub auto_heal: bool, +} + +impl GameEvent { + pub fn into_conf(self) -> crate::persist::config::GameEvent { + crate::persist::config::GameEvent { + map: self.map.into_conf(), + visibility: self.visibility.into_conf(), + mode: self.mode.into_conf(), + auto_heal: self.auto_heal, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Copy)] +pub enum GameMap { + // TODO put some more obvious aliases on these + Mars1, + Mars2, + Mars3, + Neptune1, + Neptune2, + Neptune3, + Earth1, + Earth2, +} + +impl GameMap { + 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, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Copy)] +pub enum GameVisibility { + Good, + Poor, + Bad, +} + +impl GameVisibility { + 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, + } + } +} + + +#[derive(Serialize, Deserialize, Clone, Debug, Copy)] +pub enum GameType { + BattleArena, + SuddenDeath, + Pit, + TestMode, + SinglePlayer, + TeamDeathmatch, + Campaign, +} + +impl GameType { + 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::TestMode => crate::persist::config::GameType::TestMode, + Self::SinglePlayer => crate::persist::config::GameType::SinglePlayer, + Self::TeamDeathmatch => crate::persist::config::GameType::TeamDeathmatch, + Self::Campaign => crate::persist::config::GameType::Campaign, + } + } +} + fn default_game_modes() -> GameModes { GameModes { battle_arena: GameMode { @@ -200,3 +320,131 @@ fn default_campaigns() -> super::Campaigns { ] } } + +fn default_rotation() -> GameEventSequence { + GameEventSequence { + strategy: GameRotationStrategy::Sequence, + modes: vec![ + GameEvents { + singleplayer: GameEvent { + map: GameMap::Neptune1, + visibility: GameVisibility::Good, + mode: GameType::SuddenDeath, + auto_heal: true, + }, + multiplayer: GameEvent { + map: GameMap::Neptune3, + visibility: GameVisibility::Poor, + mode: GameType::BattleArena, + auto_heal: true, + }, + duration_s: 5*60, // 5 minutes + }, + GameEvents { + singleplayer: GameEvent { + map: GameMap::Neptune2, + visibility: GameVisibility::Good, + mode: GameType::SuddenDeath, + auto_heal: true, + }, + multiplayer: GameEvent { + map: GameMap::Mars1, + visibility: GameVisibility::Poor, + mode: GameType::Pit, + auto_heal: true, + }, + duration_s: 5*60, + }, + GameEvents { + singleplayer: GameEvent { + map: GameMap::Neptune3, + visibility: GameVisibility::Good, + mode: GameType::SuddenDeath, + auto_heal: true, + }, + multiplayer: GameEvent { + map: GameMap::Mars1, + visibility: GameVisibility::Poor, + mode: GameType::TestMode, + auto_heal: true, + }, + duration_s: 5*60, + }, + GameEvents { + singleplayer: GameEvent { + map: GameMap::Mars1, + visibility: GameVisibility::Good, + mode: GameType::SuddenDeath, + auto_heal: true, + }, + multiplayer: GameEvent { + map: GameMap::Neptune3, + visibility: GameVisibility::Poor, + mode: GameType::BattleArena, + auto_heal: true, + }, + duration_s: 5*60, + }, + GameEvents { + singleplayer: GameEvent { + map: GameMap::Mars2, + visibility: GameVisibility::Good, + mode: GameType::SuddenDeath, + auto_heal: true, + }, + multiplayer: GameEvent { + map: GameMap::Mars1, + visibility: GameVisibility::Poor, + mode: GameType::Pit, + auto_heal: true, + }, + duration_s: 5*60, + }, + GameEvents { + singleplayer: GameEvent { + map: GameMap::Mars3, + visibility: GameVisibility::Good, + mode: GameType::SuddenDeath, + auto_heal: true, + }, + multiplayer: GameEvent { + map: GameMap::Mars1, + visibility: GameVisibility::Poor, + mode: GameType::TestMode, + auto_heal: true, + }, + duration_s: 5*60, + }, + GameEvents { + singleplayer: GameEvent { + map: GameMap::Earth1, + visibility: GameVisibility::Good, + mode: GameType::SuddenDeath, + auto_heal: true, + }, + multiplayer: GameEvent { + map: GameMap::Mars1, + visibility: GameVisibility::Poor, + mode: GameType::Pit, + auto_heal: true, + }, + duration_s: 5*60, + }, + GameEvents { + singleplayer: GameEvent { + map: GameMap::Earth2, + visibility: GameVisibility::Good, + mode: GameType::SuddenDeath, + auto_heal: true, + }, + multiplayer: GameEvent { + map: GameMap::Mars1, + visibility: GameVisibility::Poor, + mode: GameType::TestMode, + auto_heal: true, + }, + duration_s: 5*60, + }, + ] + } +} diff --git a/rc_core/src/persist/config/cubes_json.rs b/rc_core/src/persist/config/cubes_json.rs index faea074..63cfc63 100644 --- a/rc_core/src/persist/config/cubes_json.rs +++ b/rc_core/src/persist/config/cubes_json.rs @@ -288,4 +288,19 @@ impl super::ConfigProvider for CubeConfig { commands: self.chat.commands.clone(), } } + + fn gamemode_events(&self) -> super::GameEventSequence { + let strategy = self.battle.rotation.strategy.into_conf(); + let first = strategy.next(self.battle.rotation.modes.len() - 1, self.battle.rotation.modes.len()); + super::GameEventSequence { + strategy: self.battle.rotation.strategy.into_conf(), + modes: self.battle.rotation.modes.clone().into_iter().map(|event| super::GameEvents { + singleplayer: event.singleplayer.into_conf(), + multiplayer: event.multiplayer.into_conf(), + duration: std::time::Duration::from_secs(event.duration_s), + }).collect(), + index: first, + started: chrono::Utc::now().timestamp(), + } + } } diff --git a/rc_core/src/persist/config/mod.rs b/rc_core/src/persist/config/mod.rs index 10dbe41..ea1d9c9 100644 --- a/rc_core/src/persist/config/mod.rs +++ b/rc_core/src/persist/config/mod.rs @@ -2,7 +2,7 @@ mod cubes_json; pub use cubes_json::CubeConfig; mod traits; -pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig}; +pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType}; pub type ConfigImpl = CubeConfig; diff --git a/rc_core/src/persist/config/traits.rs b/rc_core/src/persist/config/traits.rs index 766a0f7..27746e6 100644 --- a/rc_core/src/persist/config/traits.rs +++ b/rc_core/src/persist/config/traits.rs @@ -24,6 +24,7 @@ pub trait ConfigProvider { async fn factory(&self) -> Result>; fn cubes(&self) -> &'_ std::collections::HashMap; fn chat_system_config(&self) -> ChatSystemConfig; + fn gamemode_events(&self) -> GameEventSequence; } pub struct CompleteCampaignProvider { @@ -128,3 +129,131 @@ pub struct ChatSystemConfig { pub command_channel: String, pub commands: Vec, } + +#[derive(Clone, Debug)] +pub struct GameEventSequence { + pub strategy: GameRotationStrategy, + pub modes: Vec, + pub index: usize, + pub started: i64, +} + +impl GameEventSequence { + pub fn now(&mut self) -> 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 { + // needs refresh + self.index = self.strategy.next(self.index, self.modes.len()); + item_now = &self.modes[self.index]; + self.started = time_now; + } + 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, + 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, + 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, + 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, + items: vec![ + Typed::Bool(item_now.singleplayer.auto_heal), + Typed::Bool(item_now.multiplayer.auto_heal), + ], + }), + remaining_ticks: Typed::Long(remaining_ticks), + } + } +} + +pub struct GameEventTransmissible { + pub maps: Typed, + pub visibilities: Typed, + pub modes: Typed, + pub auto_heals: Typed, + pub remaining_ticks: Typed, +} + +#[derive(Clone, Debug)] +pub enum GameRotationStrategy { + Sequence, + Random, +} + +impl GameRotationStrategy { + pub(super) fn next(&self, last: usize, count: usize) -> usize { + match self { + Self::Sequence => (last+1) % count, + Self::Random => { + use rand::prelude::*; + let mut rng = rand::rng(); + let num: u64 = rng.random(); + let num = num.clamp(0, usize::MAX as u64) as usize; + num % count + } + } + } +} + +#[derive(Clone, Debug)] +pub struct GameEvents { + pub singleplayer: GameEvent, + pub multiplayer: GameEvent, + pub duration: std::time::Duration, +} + +#[derive(Clone, Debug)] +pub struct GameEvent { + pub map: GameMap, + pub visibility: GameVisibility, + pub mode: GameType, + pub auto_heal: bool, +} + +#[derive(Clone, Debug, Copy)] +pub enum GameMap { + Mars1, + Mars2, + Mars3, + Neptune1, + Neptune2, + Neptune3, + Earth1, + Earth2, +} + +#[derive(Clone, Debug, Copy)] +pub enum GameVisibility { + Good, + Poor, + Bad, +} + + +#[derive(Clone, Debug, Copy)] +pub enum GameType { + BattleArena, + SuddenDeath, + Pit, + TestMode, + SinglePlayer, + TeamDeathmatch, + Campaign, +} diff --git a/rc_services_room/src/data/custom_games.rs b/rc_services_room/src/data/custom_games.rs index b333ff0..0c51da7 100644 --- a/rc_services_room/src/data/custom_games.rs +++ b/rc_services_room/src/data/custom_games.rs @@ -1,38 +1,6 @@ #![allow(dead_code)] -#[repr(u8)] -#[derive(Copy, Clone)] -pub enum GameMode { - BattleArena = 0, - SuddenDeath = 1, - Pit = 2, - TestMode = 3, - SinglePlayer = 4, - TeamDeathmatch = 5, - Campaign = 6, -} - -impl GameMode { - pub fn as_str(&self) -> &'static str { - match self { - GameMode::BattleArena => "BattleArena", - GameMode::SuddenDeath => "SuddenDeath", - GameMode::Pit => "Pit", - GameMode::TestMode => "TestMode", - GameMode::SinglePlayer => "SinglePlayerTDM", - GameMode::TeamDeathmatch => "TeamDeathmatch", - GameMode::Campaign => "Campaign", - } - } -} - -#[repr(u8)] -#[derive(Copy, Clone)] -pub enum MapVisibility { - Good = 0, - Poor = 1, - Bad = 2, // VeryPoor -} +pub use rc_core::data::game_mode::GameMode; #[repr(u8)] #[derive(Copy, Clone)] diff --git a/rc_services_room/src/operations/game_event_params.rs b/rc_services_room/src/operations/game_event_params.rs index 2180a3c..71fc8d8 100644 --- a/rc_services_room/src/operations/game_event_params.rs +++ b/rc_services_room/src/operations/game_event_params.rs @@ -1,7 +1,7 @@ -use polariton_server::operations::SimpleFunc; -use polariton::{operation::{Arr, ParameterTable, Typed}, serdes::TypePrefix}; +use polariton_server::operations::{Operation, OperationCode}; +//use polariton::{operation::{Arr, ParameterTable, Typed}, serdes::TypePrefix}; -use crate::data::custom_games::*; +const CODE: u8 = 24; const MAP_NAMES_PARAM_KEY: u8 = 78; const VISIBILITY_PARAM_KEY: u8 = 66; @@ -20,38 +20,40 @@ RC_Planet_Mars_01_CTF RC_Planet_Neptune_01_CTF */ -pub(super) fn event_system_params_provider() -> SimpleFunc<24, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { - SimpleFunc::new(|params, _| { +pub struct GameEventsParamsProvider { + sequence: std::sync::Mutex, +} + +#[async_trait::async_trait] +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(); - params.insert(MAP_NAMES_PARAM_KEY, Typed::Arr(Arr { - ty: TypePrefix::Str, // str - items: vec![ - Typed::Str("RC_Planet_Neptune_03_BA".into()), - Typed::Str("RC_Planet_Mars_02_BA".into()), - ], - })); - params.insert(VISIBILITY_PARAM_KEY, Typed::Arr(Arr { - ty: TypePrefix::Int, // int - items: vec![ - Typed::Int(MapVisibility::Good as _), - Typed::Int(MapVisibility::Bad as _), - ], - })); - params.insert(MODE_PARAM_KEY, Typed::Arr(Arr { - ty: TypePrefix::Int, // int - items: vec![ - Typed::Int(GameMode::SinglePlayer as _), - Typed::Int(GameMode::BattleArena as _), - ], - })); - params.insert(AUTO_HEAL_PARAM_KEY, Typed::Arr(Arr { - ty: TypePrefix::Bool, // bool - items: vec![ - Typed::Bool(true.into()), - Typed::Bool(false.into()), - ], - })); - params.insert(REMAINING_TICKS_PARAM_KEY, Typed::Long(24 * 60 * 60 * 1_000_000 * 10 /* 24 hours in ticks (100ns units) */)); - Ok(params.into()) - }) + 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(), + } + } +} + +impl OperationCode for GameEventsParamsProvider { + fn op_code() -> u8 { + CODE + } +} + +pub(super) fn event_system_params_provider(conf: &rc_core::ConfigImpl) -> GameEventsParamsProvider { + let game_seq = >::gamemode_events(conf); + GameEventsParamsProvider { + sequence: std::sync::Mutex::new(game_seq), + } } diff --git a/rc_services_room/src/operations/mod.rs b/rc_services_room/src/operations/mod.rs index d6620a9..8b59a3c 100644 --- a/rc_services_room/src/operations/mod.rs +++ b/rc_services_room/src/operations/mod.rs @@ -144,7 +144,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler .add(custom_game_session::get_custom_session_provider()) .add(user_xp::get_user_xp_provider()) .add(garage_upgrades::garage_upgrades_provider(&init_ctx.cubes)) - .add(game_event_params::event_system_params_provider()) + .add(game_event_params::event_system_params_provider(&init_ctx.cubes)) .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())