diff --git a/rc_core/src/data/game_mode.rs b/rc_core/src/data/game_mode.rs index 71a64ff..3e670f1 100644 --- a/rc_core/src/data/game_mode.rs +++ b/rc_core/src/data/game_mode.rs @@ -55,14 +55,14 @@ 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", + Self::Mars1 => "RC_Planet_Mars_01_CTF", // og flat mars + Self::Mars2 => "RC_Planet_Mars_02_BA", // the one with the bridge in the middle + Self::Mars3 => "RC_Planet_Mars_03_BA", // tharsis rift without the rift + Self::Neptune1 => "RC_Planet_Neptune_01_CTF", // og flat GJ1214b gliese lake without the lake + Self::Neptune2 => "RC_Planet_Neptune_02_BA", // the one with the cave + Self::Neptune3 => "RC_Planet_Neptune_03_BA", // spitzer dam + Self::Earth1 => "RC_Planet_Earth_01_BA", // birmingham power station + Self::Earth2 => "RC_Planet_Earth_02_BA", // vanguard } } @@ -82,7 +82,7 @@ impl GameMap { } #[repr(u8)] -#[derive(Copy, Clone, Hash, Eq, PartialEq)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] pub enum GameMode { BattleArena = 0, SuddenDeath = 1, diff --git a/rc_core/src/persist/combat.rs b/rc_core/src/persist/combat.rs index ee91456..3e3730f 100644 --- a/rc_core/src/persist/combat.rs +++ b/rc_core/src/persist/combat.rs @@ -14,6 +14,8 @@ pub struct BattleConfig { pub rotation: GameEventSequence, #[serde(default = "default_multiplayer")] pub multiplayer: super::MultiplayerConfig, + #[serde(default = "default_maps")] + pub maps: super::MapsConfig, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -144,7 +146,7 @@ pub struct GameEvent { } impl GameEvent { - pub fn into_conf(self) -> crate::persist::config::GameEvent { + pub(super) fn into_conf(self) -> crate::persist::config::GameEvent { crate::persist::config::GameEvent { map: self.map.into_conf(), visibility: self.visibility.into_conf(), @@ -154,7 +156,7 @@ impl GameEvent { } } -#[derive(Serialize, Deserialize, Clone, Debug, Copy)] +#[derive(Serialize, Deserialize, Clone, Debug, Copy, Hash, Eq, PartialEq)] pub enum GameMap { // TODO put some more obvious aliases on these Mars1, @@ -168,7 +170,7 @@ pub enum GameMap { } impl GameMap { - fn into_conf(self) -> crate::persist::config::GameMap { + pub(super) fn into_conf(self) -> crate::persist::config::GameMap { match self { Self::Mars1 => crate::persist::config::GameMap::Mars1, Self::Mars2 => crate::persist::config::GameMap::Mars2, @@ -477,8 +479,14 @@ fn default_rotation() -> GameEventSequence { fn default_multiplayer() -> super::MultiplayerConfig { super::MultiplayerConfig { - players_per_game: 2, + players_per_game: 1, enabled: true, network: super::multiplayer::default_net_conf(), } } + +fn default_maps() -> super::MapsConfig { + super::MapsConfig { + map: super::maps::default_map(), + } +} diff --git a/rc_core/src/persist/config/cubes_json.rs b/rc_core/src/persist/config/cubes_json.rs index f26fd01..e4a128b 100644 --- a/rc_core/src/persist/config/cubes_json.rs +++ b/rc_core/src/persist/config/cubes_json.rs @@ -370,4 +370,40 @@ impl super::ConfigProvider for CubeConfig { fn network_config(&self) -> crate::persist::NetworkConf { self.battle.multiplayer.network.clone() } + + fn maps(&self) -> std::collections::HashMap { + self.battle.maps.map.iter().map(|(map, conf)| { + let mut spawns = std::collections::HashMap::>::with_capacity(2); // usually 2 teams + for point in conf.spawn_points.iter() { + if let Some(list) = spawns.get_mut(&point.team) { + list.push(super::Point { + x: point.x, + y: point.y, + z: point.z, + }); + } else { + let mut list = Vec::with_capacity(10); // usually 10 spawn points (suddent death has the most) + list.push(super::Point { + x: point.x, + y: point.y, + z: point.z, + }); + spawns.insert(point.team, list); + } + } + let bases = conf.bases.iter().map(|base| (base.team, super::Sphere { + radius: base.radius, + center: super::Point { + x: base.x, + y: base.y, + z: base.z, + }, + })).collect(); + let map_conf = super::MapConfig { + spawns, + bases, + }; + (map.into_conf(), map_conf) + }).collect() + } } diff --git a/rc_core/src/persist/config/mod.rs b/rc_core/src/persist/config/mod.rs index ccd49d7..a04f80f 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, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode}; +pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode, Point, Sphere, MapConfig}; pub type ConfigImpl = CubeConfig; diff --git a/rc_core/src/persist/config/traits.rs b/rc_core/src/persist/config/traits.rs index d83dcba..b44d46f 100644 --- a/rc_core/src/persist/config/traits.rs +++ b/rc_core/src/persist/config/traits.rs @@ -31,6 +31,7 @@ pub trait ConfigProvider { fn is_multiplayer_enabled(&self) -> bool; // FIXME don't use serializable types in traits fn network_config(&self) -> crate::persist::NetworkConf; + fn maps(&self) -> std::collections::HashMap; } pub struct CompleteCampaignProvider { @@ -270,7 +271,7 @@ pub struct GameEvent { pub auto_heal: bool, } -#[derive(Clone, Debug, Copy)] +#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)] pub enum GameMap { Mars1, Mars2, @@ -329,3 +330,22 @@ pub enum VehicleDescriptor { } // TODO File } + +#[derive(Clone, Debug)] +pub struct Point { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[derive(Clone, Debug)] +pub struct Sphere { + pub radius: f32, + pub center: Point, +} + +#[derive(Clone, Debug)] +pub struct MapConfig { + pub spawns: std::collections::HashMap>, // team -> points + pub bases: std::collections::HashMap, // team -> base +} diff --git a/rc_core/src/persist/maps.rs b/rc_core/src/persist/maps.rs new file mode 100644 index 0000000..d498287 --- /dev/null +++ b/rc_core/src/persist/maps.rs @@ -0,0 +1,211 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct MapsConfig { + #[serde(default = "default_map")] + pub map: std::collections::HashMap, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct MapConfig { + pub spawn_points: Vec, + pub bases: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct SpawnPoint { + pub team: u8, + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct CaptureBase { + pub team: u8, + pub x: f32, + pub y: f32, + pub z: f32, + pub radius: f32, +} + +pub(super) fn default_map() -> std::collections::HashMap { + let mut map = std::collections::HashMap::with_capacity(9); + let coords_t0 = corner_to_center((6.60, 4.09, 20.3), 10.0); + let coords_t1 = corner_to_center((364.60, 10.63, 372.20), 10.0); + map.insert(super::combat::GameMap::Mars1, MapConfig { + spawn_points: vec![ + // team 0 + SpawnPoint { + team: 0, + x: 32.07, + y: 1.73, + z: 49.75, + }, + SpawnPoint { + team: 0, + x: 39.20, + y: 1.73, + z: 40.21, + }, + SpawnPoint { + team: 0, + x: 21.14, + y: 1.73, + z: 44.16, + }, + SpawnPoint { + team: 0, + x: 32.50, + y: 1.73, + z: 30.66, + }, + SpawnPoint { + team: 0, + x: 31.1, + y: 1.73, + z: 6.80, + }, + SpawnPoint { + team: 0, + x: 43.40, + y: 1.73, + z: 8.60, + }, + SpawnPoint { + team: 0, + x: 36.00, + y: 1.73, + z: 18.70, + }, + SpawnPoint { + team: 0, + x: 3.00, + y: 1.73, + z: 57.4, + }, + SpawnPoint { + team: 0, + x: 9.90, + y: 1.73, + z: 47.90, + }, + SpawnPoint { + team: 0, + x: -2.40, + y: 1.73, + z: 46.40, + }, + // team 1 + SpawnPoint { + team: 1, + x: 346.09, + y: 8.10, + z: 339.18, + }, + SpawnPoint { + team: 1, + x: 337.10, + y: 8.10, + z: 346.80, + }, + SpawnPoint { + team: 1, + x: 356.10, + y: 8.10, + z: 344.90, + }, + SpawnPoint { + team: 1, + x: 340.10, + y: 8.10, + z: 358.20, + }, + SpawnPoint { + team: 1, + x: 327.15, + y: 8.10, + z: 381.87, + }, + SpawnPoint { + team: 1, + x: 339.10, + y: 8.10, + z: 383.60, + }, + SpawnPoint { + team: 1, + x: 334.80, + y: 8.10, + z: 372.40, + }, + SpawnPoint { + team: 1, + x: 382.50, + y: 8.10, + z: 335.10, + }, + SpawnPoint { + team: 1, + x: 373.10, + y: 8.10, + z: 342.40, + }, + SpawnPoint { + team: 1, + x: 384.50, + y: 8.10, + z: 346.80, + }, + ], + bases: vec![ + CaptureBase { + team: 0, + x: coords_t0.0, + y: coords_t0.1, + z: coords_t0.2, + radius: 10.0, + }, + CaptureBase { + team: 1, + x: coords_t1.0, + y: coords_t1.1, + z: coords_t1.2, + radius: 10.0, + }, + ], + }); + map.insert(super::combat::GameMap::Mars2, MapConfig { + spawn_points: vec![], + bases: vec![], + }); + map.insert(super::combat::GameMap::Mars3, MapConfig { + spawn_points: vec![], + bases: vec![], + }); + map.insert(super::combat::GameMap::Neptune1, MapConfig { + spawn_points: vec![], + bases: vec![], + }); + map.insert(super::combat::GameMap::Neptune2, MapConfig { + spawn_points: vec![], + bases: vec![], + }); + map.insert(super::combat::GameMap::Neptune3, MapConfig { + spawn_points: vec![], + bases: vec![], + }); + map.insert(super::combat::GameMap::Earth1, MapConfig { + spawn_points: vec![], + bases: vec![], + }); + map.insert(super::combat::GameMap::Earth2, MapConfig { + spawn_points: vec![], + bases: vec![], + }); + map +} + +const fn corner_to_center(corner: (f32, f32, f32), radius: f32) -> (f32, f32, f32) { + (corner.0 + radius, corner.1, corner.2 + radius) +} diff --git a/rc_core/src/persist/mod.rs b/rc_core/src/persist/mod.rs index 5d9e650..3fd44b7 100644 --- a/rc_core/src/persist/mod.rs +++ b/rc_core/src/persist/mod.rs @@ -38,6 +38,9 @@ pub use vehicle_factory::{FactoryConfig, AdapterSettings, ArcFactorySettings}; mod multiplayer; pub use multiplayer::{MultiplayerConfig, NetworkConf}; +mod maps; +pub use maps::{MapsConfig, MapConfig}; + pub(self) const VALID_ROBOT: &[u8] = &[64, 0, 0, diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index 6b75655..dfa0816 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -1367,4 +1367,32 @@ impl super::MultiplayerUser for UserData { }) } } + + async fn game_info(&self, guid: &str) -> Result, super::MultiplayerError> { + if let Some(guid) = crate::persist::user::str_to_i64(guid) { + let game_opt = self.db.game_by_guid(guid.to_owned()).await + .map_err(|e| { + log::error!("Failed to retrieve game {} with user {}: {}", guid, self.account.id, e); + super::MultiplayerError { + code: super::MultiplayerErrorCode::CustomString, + message: format!("Failed to retrieve game {}: {}", guid, e), + } + })?; + Ok(game_opt.map(|game| super::GameDescriptor { + guid: crate::persist::user::i64_as_uuid_str(game.guid), + map: game.map, + mode: crate::data::game_mode::GameMode::from_db(game.mode), + visibility: crate::data::game_mode::MapVisibility::from_db(game.visibility), + auto_heal: game.auto_heal, + is_ranked: matches!(game.variant, oj_rc_database::schema::multiplayer_game::GameType::Ranked), + is_custom: matches!(game.variant, oj_rc_database::schema::multiplayer_game::GameType::Custom), + is_complete: game.is_complete, + })) + } else { + Err(super::MultiplayerError { + code: super::MultiplayerErrorCode::IncorrectGameGuid, + message: format!("Failed to parse game GUID {}", guid), + }) + } + } } diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index 9e738c9..d3a9d1f 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -320,4 +320,5 @@ pub trait MultiplayerUser { async fn current_game(&self) -> Result, MultiplayerError>; async fn game_players(&self, guid: &str) -> Result, MultiplayerError>; async fn complete_game(&self, guid: &str) -> Result<(), MultiplayerError>; + async fn game_info(&self, guid: &str) -> Result, MultiplayerError>; } diff --git a/rc_database/src/wrapper.rs b/rc_database/src/wrapper.rs index cdeebc9..c7a46ab 100644 --- a/rc_database/src/wrapper.rs +++ b/rc_database/src/wrapper.rs @@ -273,6 +273,14 @@ impl Database { entity.insert(&self.orm).await } + pub async fn game_by_guid(&self, game_guid: i64) -> Result, sea_orm::DbErr> { + crate::schema::multiplayer_game::Entity::find() + .filter(crate::schema::multiplayer_game::Column::Guid.eq(game_guid)) + .order_by_desc(crate::schema::multiplayer_game::Column::CreationTime) + .one(&self.orm) + .await + } + pub async fn game_by_user_id_and_completion(&self, user_id: i32, is_complete: bool) -> Result, sea_orm::DbErr> { Ok(crate::schema::multiplayer_game::Entity::find() .find_also_related(crate::schema::multiplayer_game_player::Entity) diff --git a/rc_multiplayer/src/matches/aggregate.rs b/rc_multiplayer/src/matches/aggregate.rs index 151be6d..ef6e1f7 100644 --- a/rc_multiplayer/src/matches/aggregate.rs +++ b/rc_multiplayer/src/matches/aggregate.rs @@ -2,6 +2,7 @@ pub struct GameMatches { matches: std::collections::HashMap>, routing: std::collections::HashMap, // user id to game guid mode_configs: oj_rc_core::data::game_mode::GameModeConfigs, + map_configs: std::collections::HashMap, } impl GameMatches { @@ -10,6 +11,10 @@ impl GameMatches { matches: std::collections::HashMap::new(), routing: std::collections::HashMap::new(), mode_configs: >::gamemodes(conf), + map_configs: >::maps(conf) + .into_iter() + .map(|(map, conf)| (oj_rc_core::data::game_mode::GameMap::from_persist(map).as_str().to_owned(), conf)) + .collect(), } } @@ -19,13 +24,44 @@ impl GameMatches { tx } - async fn start_new_match_engine(&self, _user: &Box, guid: &str) -> tokio::sync::mpsc::Sender { - // TODO figure out gamemode and act accordingly - let engine = super::GenericGamemodeEngine::new( - guid.to_owned(), - super::modes::EliminationLogic::new(&self.mode_configs.elimination) - ); - engine.spawn() + async fn start_new_match_engine(&self, user: &Box, guid: &str) -> Result, oj_rc_core::persist::user::MultiplayerError> { + let game_info = user.game_info(guid).await? + .ok_or_else(|| oj_rc_core::persist::user::MultiplayerError { + code: oj_rc_core::persist::user::MultiplayerErrorCode::CustomString, + message: format!("Failed to find game {}", guid), + })?; + let map_config = self.map_configs.get(&game_info.map) + .map(|x| x.to_owned()) + .unwrap_or_else(|| { + log::warn!("No configuration found for map {}, game {} may not work correctly", game_info.map, guid); + oj_rc_core::persist::config::MapConfig { + spawns: std::collections::HashMap::default(), + bases: std::collections::HashMap::default(), + } + }); + let players = user.game_players(guid).await?; + if players.is_empty() { + log::warn!("No players found to game {}, loading may not work correctly", guid); + } + match game_info.mode { + oj_rc_core::data::game_mode::GameMode::SuddenDeath => { + let engine = super::GenericGamemodeEngine::new( + game_info, + map_config, + players, + super::modes::EliminationLogic::new(&self.mode_configs.elimination) + ); + Ok(engine.spawn()) + } + mode => { + // TODO support mode gamemodes + Err(oj_rc_core::persist::user::MultiplayerError { + code: oj_rc_core::persist::user::MultiplayerErrorCode::CustomString, + message: format!("Game mode {:?} is not supported (yet)", mode), + }) + } + } + } // create a new match @@ -37,7 +73,18 @@ impl GameMatches { sender: std::sync::Arc>, ) { log::info!("Creating new game {}", game_guid); - let tx = self.start_new_match_engine(&user, &game_guid).await; + let tx = match self.start_new_match_engine(&user, &game_guid).await { + Ok(tx) => tx, + Err(e) => { + if response.send(Some(crate::matches::messages::ErrorMessage { + message: e.message.clone(), + inner: Some(Box::new(e)), + })).is_err() { + log::error!("Failed to send NewConnection failure back to event handler"); + } + return; + } + }; self.matches.insert(game_guid.clone(), tx.clone()); self.routing.insert(user.user_id(), game_guid.clone()); if tx.send(super::GameMessage::NewConnection { user, game_guid, connection, response, sender }).await.is_err() { diff --git a/rc_multiplayer/src/matches/generic.rs b/rc_multiplayer/src/matches/generic.rs index 58e3bee..649da9a 100644 --- a/rc_multiplayer/src/matches/generic.rs +++ b/rc_multiplayer/src/matches/generic.rs @@ -112,10 +112,12 @@ pub(super) struct GenericGamemodeEngine { pub user_id_map: tokio::sync::RwLock>, //pub recv: tokio::sync::Mutex>, //pub send: tokio::sync::mpsc::Sender, - pub game_guid: String, + //pub game_guid: String, is_complete: std::sync::atomic::AtomicBool, pub game_start: std::sync::atomic::AtomicI64, - pub player_count: std::sync::atomic::AtomicU8, + pub map_config: std::sync::Arc, + pub game_descriptor: oj_rc_core::persist::user::GameDescriptor, + pub players_info: std::sync::Arc>, pub custom_logic_handler: L, } @@ -123,19 +125,30 @@ impl GenericGamemodeEngine { const END_OF_SYNC_DELAY: std::time::Duration = std::time::Duration::from_millis(100); const COUNTDOWN_DURATION: std::time::Duration = std::time::Duration::from_secs(5); - pub fn new(guid: String, custom: L) -> Self { + pub fn new( + game: oj_rc_core::persist::user::GameDescriptor, + map: oj_rc_core::persist::config::MapConfig, + players: Vec, + custom: L + ) -> Self { Self { users: tokio::sync::RwLock::new(std::collections::HashMap::new()), user_id_map: tokio::sync::RwLock::new(std::collections::HashMap::new()), - game_guid: guid, is_complete: std::sync::atomic::AtomicBool::new(false), game_start: std::sync::atomic::AtomicI64::new(-1), - player_count: std::sync::atomic::AtomicU8::new(0), + map_config: std::sync::Arc::new(map), + game_descriptor: game, + players_info: std::sync::Arc::new(players), custom_logic_handler: custom, } } + #[inline] + pub(super) fn game_guid(&self) -> &'_ str { + &self.game_descriptor.guid + } + pub(super) async fn user_key_by_user_id(&self, user_id: i32) -> Option { self.user_id_map.read().await.get(&user_id).map(|x| *x) } @@ -216,10 +229,10 @@ 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); + 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), + message: format!("Game guid does not match (got: {}, expected: {})", game_guid, self.game_guid()), inner: None, })).unwrap_or_default(); return; @@ -227,41 +240,26 @@ impl GenericGamemodeEngine { let mut users = self.users.write().await; //tokio::time::sleep(std::time::Duration::from_secs(1)).await; //let id = users.len() as u8; - match user.game_players(&game_guid).await { - Ok(players) => { - if self.player_count.load(std::sync::atomic::Ordering::Relaxed) == 0 { - self.player_count.store(players.len() as _, std::sync::atomic::Ordering::Relaxed); - } - let user_id = user.user_id(); - let player_info = players.iter().filter(|p| p.user_id == user_id).next().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(), - }; - if self.custom_logic_handler.on_player_join(&self, &new_user, &players).await { - self.spawn_send_loading_events(&new_user, id, players); - 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(); + let user_id = user.user_id(); + let player_info = self.players_info.iter().filter(|p| p.user_id == user_id).next().unwrap(); + let id = player_info.player_id; + let new_user = UserConnection { + user, + connection: UserSender { + connection, + sender, }, - Err(e) => { - log::error!("Failed to retrieve players for game {}: {}", game_guid, e); - response.send(Some(super::messages::ErrorMessage { - message: "Failed to retrieve players for game".to_owned(), - inner: Some(Box::new(e)), - })).unwrap_or_default(); - } + state: std::sync::Arc::new(UserState::new()), + machine: MachineState::new(), + descriptor: player_info.to_owned(), + }; + 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()); + 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(); } }, super::GameMessage::EndConnection { user_id } => { @@ -287,8 +285,8 @@ impl GenericGamemodeEngine { is_engaged = has_active_connections; if !has_active_connections { 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); + if let Err(e) = conn.user.complete_game(self.game_guid()).await { + log::error!("Failed to mark game {} as complete: {}", self.game_guid(), e); } } } @@ -308,7 +306,7 @@ impl GenericGamemodeEngine { 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::debug!("User {} is loaded {}% into game {}", user_id, progress_percent, self.game_guid); + log::debug!("User {} is loaded {}% into game {}", user_id, progress_percent, self.game_guid()); conn.state.progress.store(progress_percent, std::sync::atomic::Ordering::Relaxed); if progress_percent != 100 { all_users_loading_complete = false; @@ -378,7 +376,7 @@ impl GenericGamemodeEngine { ).await); } } else { - log::error!("Failed to find user {} in connected users for match {}", user_id, self.game_guid); + log::error!("Failed to find user {} in connected users for match {}", user_id, self.game_guid()); } }, @@ -415,20 +413,19 @@ impl GenericGamemodeEngine { } } } - let player_count = self.player_count.load(std::sync::atomic::Ordering::Relaxed) as usize; + let player_count = self.players_info.len(); if ready_count == player_count { - log::info!("All players ({}) awaiting sync for game {}", player_count, self.game_guid); - let total_users = self.users.read().await.len() as u8; + 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, total_users, extra_packets); + self.spawn_send_sync_events(conn, conn.user.user_id(), *user_key, self.players_info.clone(), extra_packets, self.map_config.clone()); } } }, 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 {} ({})", self.game_guid, user_id, user_key); + log::info!("Loading complete for game {}, user {} ({})", self.game_guid(), user_id, user_key); conn.state.progress.store(100, std::sync::atomic::Ordering::Relaxed); conn.state.mode.store(ConnectionMode::WaitingToStart.to_u8(), std::sync::atomic::Ordering::Relaxed); /*let game_start = chrono::DateTime::from_timestamp(self.game_start.load(std::sync::atomic::Ordering::Relaxed), 0).unwrap(); @@ -444,11 +441,11 @@ impl GenericGamemodeEngine { }*/ self.spawn_initial_ingame_events(conn, user_id); } else { - log::warn!("Invalid LoadComplete user key {} for game {}", user_key, self.game_guid); + 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); + 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 @@ -459,8 +456,8 @@ impl GenericGamemodeEngine { } // trigger game start if all_users_loading_complete { - let player_count = self.player_count.load(std::sync::atomic::Ordering::Relaxed) as usize; - log::info!("All players ({}) are ready for game {}", player_count, self.game_guid); + let player_count = self.players_info.len(); + log::info!("All players ({}) are ready for game {}", player_count, self.game_guid()); tokio::time::sleep(Self::END_OF_SYNC_DELAY).await; let game_start = chrono::Utc::now() + Self::COUNTDOWN_DURATION; if self.custom_logic_handler.on_countdown_start(&self, game_start).await { @@ -491,7 +488,7 @@ impl GenericGamemodeEngine { &rlnl::events::ingame::Kill { killee_player_id: remote_player, killer_player_id: killer_player }, true, ).await; - log::info!("Player {} was destroyed by {} ({}) in game {}", remote_player, killer_player, user_id, self.game_guid); + log::info!("Player {} was destroyed by {} ({}) in game {}", remote_player, killer_player, user_id, self.game_guid()); self.custom_logic_handler.on_vehicle_destroyed(&self, killer_player, remote_player).await; }, super::GameMessage::SelfDestruct { user_id, is_classic } => { @@ -503,7 +500,7 @@ impl GenericGamemodeEngine { &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); + 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( @@ -589,22 +586,22 @@ impl GenericGamemodeEngine { } } } - log::info!("Game {} has exited", self.game_guid); + log::info!("Game {} has exited", self.game_guid()); } - fn spawn_send_loading_events(&self, user: &UserConnection, player_id: u8, players: Vec) { + 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(); tokio::spawn(Self::send_loading_events_wrapper(connection, player_id, user_id, players)); } - async fn send_loading_events_wrapper(connection: UserSender, player_id: u8, user_id: i32, players: Vec) { + async fn send_loading_events_wrapper(connection: UserSender, player_id: u8, user_id: i32, players: std::sync::Arc>) { if let Err(e) = Self::send_loading_events(&connection, player_id, players).await { log::error!("Failed to send Loading events for user {} ({}): {}", user_id, player_id, e); } } - async fn send_loading_events(user: &UserSender, player_id: u8, players: Vec) -> std::io::Result<()> { + async fn send_loading_events(user: &UserSender, player_id: u8, players: std::sync::Arc>) -> std::io::Result<()> { let sender = user.rlnl(); sender.send_data( &rlnl::events::ingame::PlayerId { player: player_id }, @@ -615,10 +612,10 @@ impl GenericGamemodeEngine { sender.send_data( &rlnl::events::loading::PlayerIDsAndNames { num_players: players.len() as _, - players: players.into_iter().map(|player| rlnl::events::loading::PlayerIDAndName { + players: players.iter().map(|player| rlnl::events::loading::PlayerIDAndName { player_id: player.player_id as _, - name: rlnl::types::BinaryWriterString(player.public_id), - display_name: rlnl::types::BinaryWriterString(player.display_name), + name: rlnl::types::BinaryWriterString(player.public_id.clone()), + display_name: rlnl::types::BinaryWriterString(player.display_name.clone()), }) .collect(), }, @@ -638,19 +635,20 @@ impl GenericGamemodeEngine { Ok(()) } - fn spawn_send_sync_events(&self, user: &UserConnection, user_id: i32, player_id: u8, num_players: u8, extra_packets: Vec) { + fn spawn_send_sync_events(&self, user: &UserConnection, user_id: i32, player_id: u8, players: std::sync::Arc>, extra_packets: Vec, map: std::sync::Arc) { let connection = user.connection.clone(); - tokio::spawn(Self::send_sync_events_wrapper(connection, user_id, player_id, num_players, extra_packets)); + tokio::spawn(Self::send_sync_events_wrapper(connection, user_id, player_id, players, extra_packets, map)); user.state.mode.store(ConnectionMode::Sync.to_u8(), std::sync::atomic::Ordering::Relaxed); } - async fn send_sync_events_wrapper(connection: UserSender, user_id: i32, player_id: u8, num_players: u8, extra_packets: Vec) { - if let Err(e) = Self::send_sync_events(connection, player_id, num_players, extra_packets).await { + async fn send_sync_events_wrapper(connection: UserSender, user_id: i32, player_id: u8, players: std::sync::Arc>, extra_packets: Vec, map: std::sync::Arc) { + if let Err(e) = Self::send_sync_events(connection, player_id, players, extra_packets, map).await { log::error!("Failed to send Sync events for user {}: {}", user_id, e); } } - async fn send_sync_events(connection: UserSender, _player_id: u8, num_players: u8, extra_packets: Vec) -> std::io::Result<()> { + async fn send_sync_events(connection: UserSender, _player_id: u8, players: std::sync::Arc>, extra_packets: Vec, map: std::sync::Arc) -> std::io::Result<()> { + let num_players = players.len() as u8; let sender = connection.rlnl(); sender.send_empty( rlnl::event_code::NetworkEvent::BeginSync, @@ -681,21 +679,71 @@ impl GenericGamemodeEngine { literustlib::packet::Property::ReliableOrdered, &connection.connection) .await?; - for i in 0..num_players { - sender.send_data( - &rlnl::events::sync::SpawnPoint { - pos: rlnl::types::PosQuatPair { - pos: rlnl::types::CompressedVec3::from((10.0 * (i as f32), 100.0, 10.0 * (i as f32))), - rot: rlnl::types::CompressedQuat { x: 0, y: 0, z: 0 }, + if map.spawns.is_empty() { + // fallback + for i in 0..num_players { + sender.send_data( + &rlnl::events::sync::SpawnPoint { + pos: rlnl::types::PosQuatPair { + pos: rlnl::types::CompressedVec3::from((10.0 * (i as f32), 100.0, 10.0 * (i as f32))), + rot: rlnl::types::CompressedQuat { x: 0, y: 0, z: 0 }, + }, + owner: i, }, - owner: i, - }, - rlnl::event_code::NetworkEvent::FreeSpawnPoint, - literustlib::packet::Property::ReliableOrdered, - &connection.connection) - .await?; + rlnl::event_code::NetworkEvent::FreeSpawnPoint, + literustlib::packet::Property::ReliableOrdered, + &connection.connection) + .await?; + } + } else { + let mut last_spawn_point = std::collections::HashMap::with_capacity(2); // team -> last index + for player in players.iter() { + let team = player.team as u8; + if let Some(team_points) = map.spawns.get(&team) { + if !team_points.is_empty() { + let spawn_index = if let Some(last_spawn_i) = last_spawn_point.get_mut(&team) { + *last_spawn_i = (*last_spawn_i + 1) % team_points.len(); + *last_spawn_i + } else { + last_spawn_point.insert(team, 0usize); + 0 + }; + let spawn = &team_points[spawn_index]; + sender.send_data( + &rlnl::events::sync::SpawnPoint { + pos: rlnl::types::PosQuatPair { + pos: rlnl::types::CompressedVec3::from((spawn.x, spawn.y, spawn.z)), + rot: rlnl::types::CompressedQuat { x: 0, y: 0, z: 0 }, + }, + owner: player.player_id, + }, + rlnl::event_code::NetworkEvent::FreeSpawnPoint, + literustlib::packet::Property::ReliableOrdered, + &connection.connection) + .await?; + continue; + } + + } + // fallback + log::warn!("No spawn point found for player {} on team {}, using bad fallback", player.player_id, team); + sender.send_data( + &rlnl::events::sync::SpawnPoint { + pos: rlnl::types::PosQuatPair { + pos: rlnl::types::CompressedVec3::from((10.0 * (player.player_id as f32), 100.0, 10.0 * (team as f32) + 10.0)), + rot: rlnl::types::CompressedQuat { x: 0, y: 0, z: 0 }, + }, + owner: player.player_id, + }, + rlnl::event_code::NetworkEvent::FreeSpawnPoint, + literustlib::packet::Property::ReliableOrdered, + &connection.connection) + .await?; + + } } + // seems to be for reconnecting /*sender.send_data( &rlnl::events::sync::SyncMachineCubes { diff --git a/rc_multiplayer/src/matches/modes/elimination.rs b/rc_multiplayer/src/matches/modes/elimination.rs index d80c7e7..38d45ba 100644 --- a/rc_multiplayer/src/matches/modes/elimination.rs +++ b/rc_multiplayer/src/matches/modes/elimination.rs @@ -110,7 +110,7 @@ impl CustomGameLogic for EliminationLogic { } self.tracked.destroy_vehicle(&player.descriptor).await; if let Some(winning_team) = self.tracked.winner_team().await { - log::info!("Team {} has won sudden death game {} because player {} left", winning_team, generic.game_guid, player.descriptor.player_id); + log::info!("Team {} has won sudden death game {} because player {} left", winning_team, generic.game_guid(), player.descriptor.player_id); let data = rlnl::events::ingame::GameLoseWin { winning_team, end_reason: rlnl::types::GameEndReason::OneTeamRemaining, @@ -153,7 +153,7 @@ impl CustomGameLogic for EliminationLogic { true, ).await; if let Some(winning_team) = self.tracked.winner_team().await { - log::info!("Team {} has won sudden death game {}", winning_team, generic.game_guid); + log::info!("Team {} has won sudden death game {}", winning_team, generic.game_guid()); let data = rlnl::events::ingame::GameLoseWin { winning_team, end_reason: rlnl::types::GameEndReason::OneTeamRemaining, @@ -174,7 +174,7 @@ impl CustomGameLogic for EliminationLogic { } generic.game_done(); } else { - log::info!("Player {} has been destroyed in sudden death game {}", victim, generic.game_guid); + log::info!("Player {} has been destroyed in sudden death game {}", victim, generic.game_guid()); let data = rlnl::events::ingame::GameLoseWin { winning_team: if conn.descriptor.team == 0 { 1 } else { 0 }, // always the other team end_reason: rlnl::types::GameEndReason::NoPlayersRemaining, @@ -261,3 +261,7 @@ impl CustomGameLogic for EliminationLogic { true } } + +// spawn points (best guess) +// Mars 1: (16, 0, 19) and (355, 7, 372) +// Earth vanguard 2: (-248, 10, -251) and (267, 10, 258)