diff --git a/Cargo.lock b/Cargo.lock index 02c1d1f..54ca045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2606,6 +2606,7 @@ dependencies = [ "log", "oj_polariton_auth", "oj_rc_core", + "oj_rc_factory", "polariton", "polariton_server", "tokio", diff --git a/assets/robocraft/servenvmulti.config b/assets/robocraft/servenvmulti.config index 66a5605..0f8d052 100644 --- a/assets/robocraft/servenvmulti.config +++ b/assets/robocraft/servenvmulti.config @@ -32,5 +32,21 @@ https://pay.rc.ngram.ca/ mp.rc.ngram.ca:4542 + + ws.services.rc.ngram.ca:4532 + :4533 + s.services.rc.ngram.ca:4534 + :4535 + c.services.rc.ngram.ca:4536 + :4537 + sp.services.rc.ngram.ca:4538 + :4539 + lobby.services.rc.ngram.ca:4540 + :4541 + https://live-auth.rc.ngram.ca/ + https://static.rc.ngram.ca/live/data.json + https://pay.rc.ngram.ca/ + mp.rc.ngram.ca:4542 + diff --git a/rc_core/src/data/player_data.rs b/rc_core/src/data/player_data.rs index 33c30a2..21cc050 100644 --- a/rc_core/src/data/player_data.rs +++ b/rc_core/src/data/player_data.rs @@ -1,6 +1,6 @@ use polariton::operation::Typed; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct PlayerData { pub name: String, pub display_name: String, diff --git a/rc_core/src/persist/config/cubes_json.rs b/rc_core/src/persist/config/cubes_json.rs index 747e0f2..9d6329a 100644 --- a/rc_core/src/persist/config/cubes_json.rs +++ b/rc_core/src/persist/config/cubes_json.rs @@ -453,9 +453,8 @@ impl super::ConfigProvider for CubeConfig { fn fake_players(&self) -> Vec { self.battle.multiplayer.fakes.iter().map(|player| super::FakePlayer { - public_id: player.public_id.clone(), - display_name: player.display_name.clone(), team: player.team, + vehicle: player.vehicle.into_conf(), implementation: player.implementation.clone().to_config(), }).collect() } diff --git a/rc_core/src/persist/config/traits.rs b/rc_core/src/persist/config/traits.rs index 493b242..22d6b19 100644 --- a/rc_core/src/persist/config/traits.rs +++ b/rc_core/src/persist/config/traits.rs @@ -376,15 +376,15 @@ pub struct LinksConfig { #[derive(Clone, Debug)] pub struct FakePlayer { - pub public_id: String, - pub display_name: String, pub team: u8, + pub vehicle: VehicleInfo, pub implementation: ClientEmulator, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ClientEmulator { Experiment, + ClientAI, } #[derive(Clone, Debug)] diff --git a/rc_core/src/persist/multiplayer.rs b/rc_core/src/persist/multiplayer.rs index 63c1311..6a8a8f6 100644 --- a/rc_core/src/persist/multiplayer.rs +++ b/rc_core/src/persist/multiplayer.rs @@ -58,10 +58,10 @@ pub(super) fn default_net_conf() -> NetworkConf { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct FakePlayerConf { - pub public_id: String, - pub display_name: String, pub team: u8, #[serde(flatten)] + pub vehicle: super::garage::PrefabVehicle, + #[serde(flatten)] pub implementation: ClientEmulation, } @@ -69,11 +69,41 @@ pub(super) fn default_fake_users() -> Vec { //Vec::default() vec![ FakePlayerConf { - public_id: "ServerExperiment01".to_owned(), - display_name: "Server".to_owned(), team: 1, + vehicle: super::garage::PrefabVehicle { + name: Some("fake0".to_owned()), + username: "Server0".to_owned(), + id: super::garage::PrefabId::Raw { + cube_data: Vec::from(crate::persist::VALID_ROBOT), + colour_data: Vec::from(crate::persist::VALID_COLOUR), + }, + }, implementation: ClientEmulation::Experimental, - } + }, + FakePlayerConf { + team: 0, + vehicle: super::garage::PrefabVehicle { + name: Some("fake1".to_owned()), + username: "Server1".to_owned(), + id: super::garage::PrefabId::Raw { + cube_data: Vec::from(crate::persist::VALID_ROBOT), + colour_data: Vec::from(crate::persist::VALID_COLOUR), + }, + }, + implementation: ClientEmulation::ClientAI, + }, + FakePlayerConf { + team: 1, + vehicle: super::garage::PrefabVehicle { + name: Some("fake2".to_owned()), + username: "Server2".to_owned(), + id: super::garage::PrefabId::Raw { + cube_data: Vec::from(crate::persist::VALID_ROBOT), + colour_data: Vec::from(crate::persist::VALID_COLOUR), + }, + }, + implementation: ClientEmulation::ClientAI, + }, ] } @@ -81,12 +111,14 @@ pub(super) fn default_fake_users() -> Vec { #[serde(tag = "impl")] pub enum ClientEmulation { Experimental, + ClientAI, } impl ClientEmulation { pub(super) fn to_config(self) -> super::config::ClientEmulator { match self { Self::Experimental => super::config::ClientEmulator::Experiment, + Self::ClientAI => super::config::ClientEmulator::ClientAI, } } } diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index 99325d8..aa601fe 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -1,4 +1,5 @@ use argon2::PasswordVerifier; +use sha2::Digest; use crate::persist::config::ConfigProvider; @@ -418,11 +419,22 @@ impl UserData { } pub(super) async fn resolve_vehicle(&self, vehicle: &crate::persist::config::VehicleInfo, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result { + let sha_bytes = sha2::Sha256::digest(vehicle.username.as_bytes()); + let u32_bytes = [ + sha_bytes[0], + sha_bytes[1], + sha_bytes[2], + sha_bytes[3], + ]; + let standard_uuid_uniqueness = (u32::from_be_bytes(u32_bytes) as i64) << 16; // middle 32 bits match &vehicle.id { crate::persist::config::VehicleDescriptor::Factory { factory: factory_id } => { match factory.vehicle(*factory_id).await { Ok(Some(factory_vehicle)) => { - let uuid_i64 = crate::persist::user::uuid_sanitize(crate::persist::user::i64_join((1 << 30, *factory_id))); + let uuid_i64 = crate::persist::user::uuid_sanitize( + standard_uuid_uniqueness + ^ crate::persist::user::i64_join((1 << 30, *factory_id)) + ); let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64); let weapons_guess = weapon_order.guess_weapons(&mut std::io::Cursor::new(&factory_vehicle.0.cube_data)); let weapons_guess = vec![weapons_guess[0], 0, 0]; @@ -470,7 +482,10 @@ impl UserData { } else { db_vehicle.total_robot_cpu }; - let uuid_i64 = crate::persist::user::uuid_sanitize(crate::persist::user::i64_join((1 << 31, *garage as u32))); + let uuid_i64 = crate::persist::user::uuid_sanitize( + standard_uuid_uniqueness + ^ crate::persist::user::i64_join((1 << 31, *garage as u32)) + ); let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64); Ok(super::ResolvedVehicle { mastery: 1, @@ -506,7 +521,6 @@ impl UserData { cube_data, colour_data, } => { - use sha2::Digest; let sha_bytes = sha2::Sha256::digest(cube_data); let u32_bytes = [ sha_bytes[0], @@ -514,7 +528,10 @@ impl UserData { sha_bytes[2], sha_bytes[3], ]; - let uuid_i64 = crate::persist::user::uuid_sanitize(crate::persist::user::i64_join((1 << 29, u32::from_le_bytes(u32_bytes)))); + let uuid_i64 = crate::persist::user::uuid_sanitize( + standard_uuid_uniqueness + ^ crate::persist::user::i64_join((1 << 29, u32::from_le_bytes(u32_bytes))) + ); let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64); let weapons_guess = weapon_order.guess_weapons(&mut std::io::Cursor::new(&cube_data)); let weapons_guess = vec![ diff --git a/rc_core/src/persist/user/lobby.rs b/rc_core/src/persist/user/lobby.rs index 53e8c10..97f3ac4 100644 --- a/rc_core/src/persist/user/lobby.rs +++ b/rc_core/src/persist/user/lobby.rs @@ -1,5 +1,12 @@ use super::account_json::UserData; +fn fake_impl_to_db(client_emu: &crate::persist::config::ClientEmulator) -> oj_rc_database::schema::multiplayer_game_player::ClientType { + match client_emu { + crate::persist::config::ClientEmulator::Experiment => oj_rc_database::schema::multiplayer_game_player::ClientType::ServerExperimental, + crate::persist::config::ClientEmulator::ClientAI => oj_rc_database::schema::multiplayer_game_player::ClientType::ClientAI, + } +} + #[async_trait::async_trait] impl super::LobbyUser for UserData { fn user_id(&self) -> i32 { @@ -17,7 +24,7 @@ impl super::LobbyUser for UserData { }) } - async fn start_game(&self, game: super::GameDescriptor, players: Vec, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result { + async fn start_game(&self, game: super::GameDescriptor, players: Vec, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result { let now = chrono::Utc::now().timestamp(); let guid = crate::persist::user::str_to_i64(&game.guid) .ok_or_else(|| polariton_server::operations::SimpleOpError::with_message( @@ -32,7 +39,7 @@ impl super::LobbyUser for UserData { oj_rc_database::schema::multiplayer_game::GameType::Standard }; - let fake_players = self.generate_fake_players_data(guid, cpu_counter, weapon_lister).await; + let fake_players = self.generate_fake_players_data(guid, factory, cpu_counter, weapon_lister).await?; let game_dbo = oj_rc_database::schema::multiplayer_game::ActiveModel { id: oj_rc_database::sea_orm::ActiveValue::NotSet, @@ -69,14 +76,15 @@ impl super::LobbyUser for UserData { is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(false), public_id: oj_rc_database::sea_orm::ActiveValue::Set(player.public_id), display_name: oj_rc_database::sea_orm::ActiveValue::Set(player.display_name), + variant: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::multiplayer_game_player::ClientType::Client), } }) .chain(fake_players.iter() .enumerate() - .map(|(i, fake)| { + .map(|(i, (fake, variant))| { oj_rc_database::schema::multiplayer_game_player::ActiveModel { id: oj_rc_database::sea_orm::ActiveValue::NotSet, - user_id: oj_rc_database::sea_orm::ActiveValue::Set(None), + user_id: oj_rc_database::sea_orm::ActiveValue::Set(None), // if ClientAI, they will be assigned to a user during game loading game_id: oj_rc_database::sea_orm::ActiveValue::Set(game_dbo.id), creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now), player_id: oj_rc_database::sea_orm::ActiveValue::Set(((i + players_len) as u8) as _), @@ -85,6 +93,7 @@ impl super::LobbyUser for UserData { is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(true), public_id: oj_rc_database::sea_orm::ActiveValue::Set(fake.name.clone()), display_name: oj_rc_database::sea_orm::ActiveValue::Set(fake.display_name.clone()), + variant: oj_rc_database::sea_orm::ActiveValue::Set(fake_impl_to_db(variant)), } }) ) diff --git a/rc_core/src/persist/user/multiplayer.rs b/rc_core/src/persist/user/multiplayer.rs index a76ce54..4ffd446 100644 --- a/rc_core/src/persist/user/multiplayer.rs +++ b/rc_core/src/persist/user/multiplayer.rs @@ -1,30 +1,45 @@ use super::account_json::UserData; +fn db_to_impl(client_emu: &oj_rc_database::schema::multiplayer_game_player::ClientType) -> Option { + match client_emu { + oj_rc_database::schema::multiplayer_game_player::ClientType::ServerExperimental => Some(crate::persist::config::ClientEmulator::Experiment), + oj_rc_database::schema::multiplayer_game_player::ClientType::ClientAI => Some(crate::persist::config::ClientEmulator::ClientAI), + oj_rc_database::schema::multiplayer_game_player::ClientType::Client => None, + } +} + impl UserData { - pub(super) async fn generate_fake_players_data(&self, _guid: i64, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Vec { - self.fake_players.iter() - .map(|fake| crate::data::player_data::PlayerData { - name: fake.public_id.clone(), - display_name: fake.display_name.clone(), - mastery: 1, - tier: 1, - robot_name: "fake".to_owned(), - robot_map: crate::persist::VALID_ROBOT.into(), - group: None, - team: fake.team as _, - has_premium: true, - robot_uuid: "1234_1234".to_owned(), - cpu: cpu_counter.calculate_cpu(&mut std::io::Cursor::new(crate::persist::VALID_ROBOT)).total as _, - avatar_id: Some(0), - weapon_order: weapon_lister.guess_weapons(&mut std::io::Cursor::new(crate::persist::VALID_ROBOT)), - colour_map: crate::persist::VALID_COLOUR.into(), - is_ai: false, - spawn_effect: "Spawn".into(), - death_effect: "Explosion".into(), - player_rank: 1, - weapon_rank: Default::default(), - }) - .collect() + pub(super) async fn generate_fake_players_data(&self, _guid: i64, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result, polariton_server::operations::SimpleOpError> { + let mut fakes = Vec::with_capacity(self.fake_players.len()); + for fake in self.fake_players.iter() { + let vehicle = self.resolve_vehicle(&fake.vehicle, factory, weapon_lister, cpu_counter).await?; + let out = ( + crate::data::player_data::PlayerData { + name: fake.vehicle.username.clone(), + display_name: fake.vehicle.username.clone(), + mastery: vehicle.mastery, + tier: vehicle.mastery, + robot_name: vehicle.robot_name, + robot_map: vehicle.robot_map, + group: None, + team: fake.team as _, + has_premium: true, + robot_uuid: vehicle.robot_uuid, + cpu: vehicle.cpu, + avatar_id: Some(0), + weapon_order: vehicle.weapon_order, + colour_map: vehicle.colour_map, + is_ai: fake.implementation == crate::persist::config::ClientEmulator::ClientAI, + spawn_effect: vehicle.spawn_effect, + death_effect: vehicle.death_effect, + player_rank: 1, + weapon_rank: vehicle.weapon_rank, + }, + fake.implementation + ); + fakes.push(out); + } + Ok(fakes) } } @@ -82,6 +97,7 @@ impl super::MultiplayerUser for UserData { is_rewards_claimed: player.is_claimed, display_name: player.display_name, public_id: player.public_id, + mode: db_to_impl(&player.variant), }) .collect()) } else { diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index 2f9cf26..ea575f1 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -247,11 +247,11 @@ impl SanctionType { pub trait LobbyUser { fn user_id(&self) -> i32; async fn player_data(&self, cpu_counter: &crate::cubes::CpuListParser) -> Result; - async fn start_game(&self, game: GameDescriptor, players: Vec, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result; + 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) -> Result; } pub struct FakePlayers { - pub players: Vec + pub players: Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)> } pub struct CurrentGameEvent { @@ -291,6 +291,7 @@ pub struct PlayerDescriptor { pub public_id: String, pub display_name: String, pub is_rewards_claimed: bool, + pub mode: Option, } #[derive(Debug)] diff --git a/rc_database/src/migration/m20250918_000001_add_player_variant.rs b/rc_database/src/migration/m20250918_000001_add_player_variant.rs new file mode 100644 index 0000000..af45482 --- /dev/null +++ b/rc_database/src/migration/m20250918_000001_add_player_variant.rs @@ -0,0 +1,36 @@ +use sea_orm_migration::prelude::*; + +pub struct Migration; + +impl MigrationName for Migration { + fn name(&self) -> &str { + "m20250918_000001_add_player_variant" + } +} + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + // Define how to apply this migration: Add player variant columns + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(crate::schema::multiplayer_game_player::Entity) + .add_column(ColumnDef::new(crate::schema::multiplayer_game_player::Column::Variant).string().not_null().default(crate::schema::multiplayer_game_player::ClientType::Client)) + .to_owned() + ) + .await + } + + // Define how to rollback this migration: Drop the added column + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(crate::schema::multiplayer_game_player::Entity) + .drop_column(crate::schema::multiplayer_game_player::Column::Variant) + .to_owned() + ) + .await + } +} diff --git a/rc_database/src/migration/mod.rs b/rc_database/src/migration/mod.rs index 48b7b72..6a853de 100644 --- a/rc_database/src/migration/mod.rs +++ b/rc_database/src/migration/mod.rs @@ -11,6 +11,7 @@ mod m20250713_000001_create_game_table; mod m20250713_000002_create_player_table; mod m20250722_000001_create_game_event_table; mod m20250816_000001_add_fake_players; +mod m20250918_000001_add_player_variant; pub struct Migrator; @@ -29,6 +30,7 @@ impl MigratorTrait for Migrator { Box::new(m20250713_000002_create_player_table::Migration), Box::new(m20250722_000001_create_game_event_table::Migration), Box::new(m20250816_000001_add_fake_players::Migration), + Box::new(m20250918_000001_add_player_variant::Migration), ] } } diff --git a/rc_database/src/schema/multiplayer_game_player.rs b/rc_database/src/schema/multiplayer_game_player.rs index f5b3983..69e4872 100644 --- a/rc_database/src/schema/multiplayer_game_player.rs +++ b/rc_database/src/schema/multiplayer_game_player.rs @@ -14,6 +14,7 @@ pub struct Model { pub is_claimed: bool, pub public_id: String, pub display_name: String, + pub variant: ClientType, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -45,3 +46,11 @@ impl Related for Entity { } impl ActiveModelBehavior for ActiveModel {} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "PascalCase")] +pub enum ClientType { + Client, + ClientAI, + ServerExperimental, +} diff --git a/rc_lobby_room/Cargo.toml b/rc_lobby_room/Cargo.toml index dd80aa1..557ea10 100644 --- a/rc_lobby_room/Cargo.toml +++ b/rc_lobby_room/Cargo.toml @@ -16,5 +16,6 @@ polariton.workspace = true oj_polariton_auth = { version = "*", path = "../polariton_auth" } polariton_server.workspace = true oj_rc_core = { version = "*", path = "../rc_core" } +oj_rc_factory = { version = "*", path = "../rc_factory" } async-trait.workspace = true chrono.workspace = true diff --git a/rc_lobby_room/src/lobby.rs b/rc_lobby_room/src/lobby.rs index e9a774f..3c2b8f2 100644 --- a/rc_lobby_room/src/lobby.rs +++ b/rc_lobby_room/src/lobby.rs @@ -38,13 +38,14 @@ pub struct QueueHandler { hostname: String, hostport: u16, network_conf: crate::data::network::NetworkConfigData, + factory: std::sync::Arc, cpu_counter: std::sync::Arc, weapon_guesser: std::sync::Arc, change_strategy: GamemodeChangeStrategy, } impl QueueHandler { - pub fn new(conf: &oj_rc_core::ConfigImpl, game_host: &str, cpu_counter: std::sync::Arc, weapon_guesser: std::sync::Arc,) -> Self { + pub fn new(conf: &oj_rc_core::ConfigImpl, game_host: &str, factory: std::sync::Arc, cpu_counter: std::sync::Arc, weapon_guesser: std::sync::Arc,) -> Self { let (domain, port_str) = game_host.split_once(':').expect("Invalid redirect address (must be domain:port)"); Self { users_in_queue: tokio::sync::Mutex::new(HashMap::new()), @@ -53,6 +54,7 @@ impl QueueHandler { hostname: domain.to_owned(), hostport: port_str.parse().expect("Invalid redirect port"), network_conf: crate::data::network::NetworkConfigData::from_conf(oj_rc_core::ConfigProvider::<()>::network_config(conf)), + factory, cpu_counter, weapon_guesser, change_strategy: GamemodeChangeStrategy::from_core(>::server_config(conf).queue_mode), @@ -90,9 +92,11 @@ impl QueueHandler { is_custom: false, is_complete: false, }; - match user.start_game(game_desc, player_descs, &self.cpu_counter, &self.weapon_guesser).await { + match user.start_game(game_desc, player_descs, self.factory.as_ref(), &self.cpu_counter, &self.weapon_guesser).await { Ok(fakes) => { - let player_datas = players.iter().map(|x| x.player.clone()).chain(fakes.players.into_iter()).collect(); + let player_datas = players.iter().map(|x| x.player.clone()) + .chain(fakes.players.into_iter().map(|(desc, _emu)| desc)) + .collect(); let enter_battle_ev = crate::events::battle_enter::BattleEnter { host: self.hostname.clone(), port: self.hostport, diff --git a/rc_lobby_room/src/main.rs b/rc_lobby_room/src/main.rs index c5eb9ce..b809761 100644 --- a/rc_lobby_room/src/main.rs +++ b/rc_lobby_room/src/main.rs @@ -30,8 +30,9 @@ async fn main() -> std::io::Result<()> { let config = oj_rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data"); let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &config).await.expect("Bad user data")); + let factory = std::sync::Arc::new(>::factory::<'_, '_>(&config).await.expect("Bad vehicle factory (CRF) config")); let parsers = oj_rc_core::cubes::CubeParsers::new(&config); - let queue = std::sync::Arc::new(QueueHandler::new(&config, &args.redirect, parsers.cpu_counter(), parsers.weapon_order())); + let queue = std::sync::Arc::new(QueueHandler::new(&config, &args.redirect, factory.clone(), parsers.cpu_counter(), parsers.weapon_order())); let init_ctx = InitConfig { config, diff --git a/rc_multiplayer/src/matches/aggregate.rs b/rc_multiplayer/src/matches/aggregate.rs index 730baca..b7b5641 100644 --- a/rc_multiplayer/src/matches/aggregate.rs +++ b/rc_multiplayer/src/matches/aggregate.rs @@ -3,7 +3,7 @@ pub struct GameMatches { routing: std::collections::HashMap, // user id to game guid mode_configs: oj_rc_core::data::game_mode::GameModeConfigs, map_configs: std::collections::HashMap, - fake_players: Vec, + //fake_players: Vec, cube_parsers: std::sync::Arc, ba_settings: std::sync::Arc, pit_settings: std::sync::Arc, @@ -21,7 +21,7 @@ impl GameMatches { .into_iter() .map(|(map, conf)| (oj_rc_core::data::game_mode::GameMap::from_persist(map).as_str().to_owned(), conf)) .collect(), - fake_players: >::fake_players(conf), + //fake_players: >::fake_players(conf), cube_parsers, ba_settings: std::sync::Arc::new(>::ba_settings(conf)), pit_settings: std::sync::Arc::new(>::pit_settings(conf)), @@ -36,21 +36,21 @@ impl GameMatches { tx } - fn build_player_emulator(&self, emu: oj_rc_core::persist::config::ClientEmulator) -> Box { + fn build_player_emulator(&self, emu: oj_rc_core::persist::config::ClientEmulator, player: &oj_rc_core::persist::user::PlayerDescriptor) -> Box { + let owned_player = player.to_owned(); match emu { - oj_rc_core::persist::config::ClientEmulator::Experiment => Box::new(super::fake::ExperimentalPlayer::new()), + oj_rc_core::persist::config::ClientEmulator::Experiment => Box::new(super::fake::ExperimentalPlayer::new(owned_player)), + oj_rc_core::persist::config::ClientEmulator::ClientAI => Box::new(super::fake::ClientAIPlayer::new(owned_player)), } } fn build_fake_players(&self, players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> std::collections::HashMap> { - let mut fake_player_i = 0; - let mut fakes = std::collections::HashMap::with_capacity(self.fake_players.len()); + let mut fakes = std::collections::HashMap::new(); for player in players.iter() { - if fake_player_i >= self.fake_players.len() { break; } - if player.user_id.is_none() { - let fake = self.build_player_emulator(self.fake_players[fake_player_i].implementation); + //if player.user_id.is_some() { continue; } + if let Some(emu_mode) = player.mode { + let fake = self.build_player_emulator(emu_mode, player); fakes.insert(player.player_id, fake); - fake_player_i += 1; } } fakes @@ -81,12 +81,14 @@ impl GameMatches { let players = user.game_players(guid).await?; if players.is_empty() { log::warn!("No players found for game {}, loading may not work correctly", guid); + } else { + log::info!("There are {} ({} real) players for game {}", players.len(), players.iter().filter(|x| x.user_id.is_some()).count(), guid); } let fakes = self.build_fake_players(&players); let fakes_handler = super::fake::Handler::start(fakes, players.clone()).await; match game_info.mode { oj_rc_core::data::game_mode::GameMode::SuddenDeath => { - let inner = super::modes::EliminationLogic::new(&self.mode_configs.elimination, &map_config); + let inner = super::modes::EliminationLogic::new(&self.mode_configs.elimination, &map_config, &players); let engine = super::GenericGamemodeEngine::new( game_info, map_config, @@ -108,7 +110,7 @@ impl GameMatches { code: oj_rc_core::persist::user::MultiplayerErrorCode::CustomString, message: e.error_msg().map(|x| x.to_owned()).unwrap_or_else(|| "Failed to resolve special settings for Battle Arena".to_owned()), })?; - let inner = super::modes::BattleArenaLogic::new(&self.mode_configs.battle_arena, &map_config, &self.cube_parsers, resolved_ba_conf); + let inner = super::modes::BattleArenaLogic::new(&self.mode_configs.battle_arena, &map_config, &self.cube_parsers, &players, resolved_ba_conf); let engine = super::GenericGamemodeEngine::new( game_info, map_config, diff --git a/rc_multiplayer/src/matches/engine.rs b/rc_multiplayer/src/matches/engine.rs index 9065c8f..5325e18 100644 --- a/rc_multiplayer/src/matches/engine.rs +++ b/rc_multiplayer/src/matches/engine.rs @@ -9,9 +9,9 @@ pub struct RlnlPacket { #[async_trait::async_trait] pub trait CustomGameLogic: Sized + Send + Sync + 'static { /// Called when player joins the game server (after authentication). - async fn on_player_join(&self, generic: &super::GenericGamemodeEngine, player: &crate::matches::generic::UserConnection, others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool; + async fn on_player_join(&self, generic: &super::GenericGamemodeEngine, connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool; /// Called when player leaves the game server (i.e. player disconnects). - async fn on_player_end(&self, generic: &super::GenericGamemodeEngine, player: &crate::matches::generic::UserConnection) -> bool; + async fn on_player_end(&self, generic: &super::GenericGamemodeEngine, connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool; /// Called when a player's vehicle is destroyed by another player. async fn on_vehicle_destroyed(&self, generic: &super::GenericGamemodeEngine, killer: u8, victim: u8) -> bool; /// Called when a player's vehicle self-destructs. @@ -21,7 +21,7 @@ pub trait CustomGameLogic: Sized + Send + Sync + 'static { /// Called during loading, before the sync stage is entered. /// /// Roughly, loading is as follows: `Loading -> WaitingForSync -> Sync -> WaitingToStart -> InGame`. - async fn extra_sync_events(&self, generic: &super::GenericGamemodeEngine, player: &crate::matches::generic::UserConnection) -> Vec; + async fn extra_sync_events(&self, generic: &super::GenericGamemodeEngine, connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> Vec; /// Called when loading completes and the countdown is starting async fn on_countdown_start(&self, generic: &super::GenericGamemodeEngine, game_start: chrono::DateTime) -> bool; /// Called when the game is marked as complete diff --git a/rc_multiplayer/src/matches/fake/client_ai.rs b/rc_multiplayer/src/matches/fake/client_ai.rs new file mode 100644 index 0000000..b7779d5 --- /dev/null +++ b/rc_multiplayer/src/matches/fake/client_ai.rs @@ -0,0 +1,70 @@ +pub struct ClientAIPlayer { + me: oj_rc_core::persist::user::PlayerDescriptor, + is_complete: std::sync::Arc, + assigned_to: std::sync::atomic::AtomicU16, // player_id but where u16::MAX means None +} + +impl ClientAIPlayer { + pub fn new(me: oj_rc_core::persist::user::PlayerDescriptor) -> Self { + Self { + me, + is_complete: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), + assigned_to: std::sync::atomic::AtomicU16::new(u16::MAX), + } + } + + fn assigned_to_player_id(&self) -> Option { + let id = self.assigned_to.load(std::sync::atomic::Ordering::Relaxed); + if id > u8::MAX as u16 { + None + } else { + Some(id as u8) + } + } + + fn set_assigned_to(&self, player_id: Option) { + self.assigned_to.store(player_id.map(|x| x as u16).unwrap_or(u16::MAX), std::sync::atomic::Ordering::Relaxed); + } +} + +#[async_trait::async_trait] +impl super::FakeUser for ClientAIPlayer { + async fn on_init(&self, descriptors: &[oj_rc_core::persist::user::PlayerDescriptor], player_id: u8) { + let first_fake_i = descriptors.iter() + .filter(|x| x.team == self.me.team) + .enumerate() + .find(|x| x.1.mode != None) + .map(|(i, _)| i) + .unwrap(); + let my_i = descriptors.iter() + .enumerate() + .find(|x| x.1.player_id == player_id) + .map(|(i, _)| i) + .unwrap(); + let real_teammates_count = descriptors.iter() + .filter(|x| x.team == self.me.team && x.mode.is_none()) + .count(); + let my_offset = (my_i - first_fake_i) % real_teammates_count; + for (i, teammate) in descriptors.iter().filter(|x| x.team == self.me.team && x.mode.is_none()).enumerate() { + if i >= my_offset { + self.set_assigned_to(Some(teammate.player_id)); + break; + } + } + if self.assigned_to_player_id().is_none() { + log::warn!("Failed to assign client AI player {} to a real client; offset:{}, first_fake:{}, reals:{}, me:{}", player_id, my_offset, first_fake_i, real_teammates_count, my_i); + } + } + + async fn on_ready(&self, _real_players: &std::collections::HashMap) { + + } + + async fn on_end(&self) { + self.is_complete.store(true, std::sync::atomic::Ordering::Relaxed); + } + + async fn running_on(&self) -> Option { + self.assigned_to_player_id() + } +} diff --git a/rc_multiplayer/src/matches/fake/experimental.rs b/rc_multiplayer/src/matches/fake/experimental.rs index a7da8e3..a6a0c3e 100644 --- a/rc_multiplayer/src/matches/fake/experimental.rs +++ b/rc_multiplayer/src/matches/fake/experimental.rs @@ -2,14 +2,14 @@ use rand::Rng; use byteserde::ser_heap::ByteSerializeHeap; pub struct ExperimentalPlayer { - me: tokio::sync::RwLock>, + me: oj_rc_core::persist::user::PlayerDescriptor, is_complete: std::sync::Arc, } impl ExperimentalPlayer { - pub fn new() -> Self { + pub fn new(me: oj_rc_core::persist::user::PlayerDescriptor) -> Self { Self { - me: tokio::sync::RwLock::new(None), + me, is_complete: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), } } @@ -17,16 +17,14 @@ impl ExperimentalPlayer { #[async_trait::async_trait] impl super::FakeUser for ExperimentalPlayer { - async fn on_init(&self, descriptors: &[oj_rc_core::persist::user::PlayerDescriptor], player_id: u8) { - if let Some(my_desc) = descriptors.iter().find(|x| x.player_id == player_id) { - *self.me.write().await = Some(my_desc.to_owned()); - } + async fn on_init(&self, _descriptors: &[oj_rc_core::persist::user::PlayerDescriptor], _player_id: u8) { + } async fn on_ready(&self, real_players: &std::collections::HashMap) { let movement_rx = real_players.values().map(|x| x.to_owned()).collect(); let is_complete = self.is_complete.clone(); - let player_id = self.me.read().await.as_ref().unwrap().player_id; + let player_id = self.me.player_id; tokio::task::spawn(erratic_behaviour(movement_rx, is_complete, player_id)); } diff --git a/rc_multiplayer/src/matches/fake/handler.rs b/rc_multiplayer/src/matches/fake/handler.rs index 0270fc8..21f2f3b 100644 --- a/rc_multiplayer/src/matches/fake/handler.rs +++ b/rc_multiplayer/src/matches/fake/handler.rs @@ -2,6 +2,9 @@ enum Message { Ready { real_players: std::collections::HashMap, }, + ClientMap { + responder: tokio::sync::oneshot::Sender>>, // real player_id -> client AIs + }, Stop, } @@ -22,6 +25,13 @@ impl Handler { Self::log_failure(self.tx.send(Message::Ready { real_players })); } + /// real player_id -> list of non-user player_id + pub async fn get_client_ais(&self) -> std::collections::HashMap> { + let (responder, rx) = tokio::sync::oneshot::channel(); + Self::log_failure(self.tx.send(Message::ClientMap { responder })); + rx.await.unwrap_or_default() + } + pub fn stop(&self) { Self::log_failure(self.tx.send(Message::Stop)); } @@ -52,6 +62,21 @@ async fn handler_loop(mut rx: tokio::sync::mpsc::UnboundedReceiver, pla fake.on_ready(&real_players).await; } }, + Message::ClientMap { responder } => { + let mut map = std::collections::HashMap::>::new(); + for (player_id, fake) in players.iter() { + if let Some(running_on) = fake.running_on().await { + if let Some(fakes_on) = map.get_mut(&running_on) { + fakes_on.push(*player_id); + } else { + map.insert(running_on, vec![*player_id]); + } + } + } + if let Err(_e) = responder.send(map) { + log::warn!("Failed to send response for client AI map message"); + } + }, Message::Stop => { for fake in players.values() { fake.on_end().await; diff --git a/rc_multiplayer/src/matches/fake/mod.rs b/rc_multiplayer/src/matches/fake/mod.rs index 1e02699..6cf3e82 100644 --- a/rc_multiplayer/src/matches/fake/mod.rs +++ b/rc_multiplayer/src/matches/fake/mod.rs @@ -6,3 +6,6 @@ pub use traits::FakeUser; mod experimental; pub use experimental::ExperimentalPlayer; + +mod client_ai; +pub use client_ai::ClientAIPlayer; diff --git a/rc_multiplayer/src/matches/fake/traits.rs b/rc_multiplayer/src/matches/fake/traits.rs index a02fb0e..839514c 100644 --- a/rc_multiplayer/src/matches/fake/traits.rs +++ b/rc_multiplayer/src/matches/fake/traits.rs @@ -1,7 +1,15 @@ #[async_trait::async_trait] pub trait FakeUser: Send + Sync { async fn on_init(&self, descriptors: &[oj_rc_core::persist::user::PlayerDescriptor], player_id: u8); + async fn on_ready(&self, real_players: &std::collections::HashMap); + //fn on_damage(&self, data: &rlnl::events::ingame::DestroyCubesFull); + async fn on_end(&self); + + /// player id which is running the fake user (client AI only) + async fn running_on(&self) -> Option { + None + } } diff --git a/rc_multiplayer/src/matches/generic.rs b/rc_multiplayer/src/matches/generic.rs index 3fffd04..e9a3819 100644 --- a/rc_multiplayer/src/matches/generic.rs +++ b/rc_multiplayer/src/matches/generic.rs @@ -1,13 +1,28 @@ pub(super) struct UserConnection { pub(super) user: std::sync::Arc>, pub(super) connection: UserSender, + aliases: Vec, +} + +pub(super) struct UserDescriptor { pub(super) state: std::sync::Arc, pub(super) machine: MachineState, - pub(super) descriptor: oj_rc_core::persist::user::PlayerDescriptor, + pub(super) descriptor: std::sync::Arc, pub(super) counters: UserData, } -#[allow(dead_code)] +impl UserDescriptor { + fn new(descriptor: oj_rc_core::persist::user::PlayerDescriptor) -> Self { + Self { + state: std::sync::Arc::new(UserState::new()), + machine: MachineState::new(), + descriptor: std::sync::Arc::new(descriptor), + counters: UserData::new(), + } + } +} + +/*#[allow(dead_code)] pub(super) struct FakeUser { pub(super) state: std::sync::Arc, pub(super) machine: MachineState, @@ -27,7 +42,7 @@ impl FakeUser { counters: UserData::new(), } } -} +}*/ #[derive(Clone)] pub(super) struct UserSender { @@ -188,8 +203,9 @@ impl ConnectionMode { } pub(super) struct GenericGamemodeEngine { - pub users: tokio::sync::RwLock>, - pub user_id_map: tokio::sync::RwLock>, + pub users: tokio::sync::RwLock>>, + descriptors: std::collections::HashMap, + user_id_map: std::collections::HashMap, //pub recv: tokio::sync::Mutex>, //pub send: tokio::sync::mpsc::Sender, //pub game_guid: String, @@ -198,9 +214,9 @@ pub(super) struct GenericGamemodeEngine { pub map_config: std::sync::Arc, pub game_descriptor: oj_rc_core::persist::user::GameDescriptor, pub game_duration: std::time::Duration, - pub players_info: std::sync::Arc>, + //pub players_info: std::sync::Arc>, pub custom_logic_handler: L, - pub fake_users: std::collections::HashMap, + //pub fake_users: std::collections::HashMap, pub fakes_handler: super::fake::Handler, } @@ -217,21 +233,28 @@ impl GenericGamemodeEngine { fakes_handler: super::fake::Handler, ) -> Self { - let fake_users = players.iter() + /*let fake_users = players.iter() .filter(|player| player.user_id.is_none()) .map(|player| (player.team as u8, FakeUser::new(player.to_owned()))) + .collect();*/ + + let descriptors = players.iter() + .map(|player| (player.player_id as u8, UserDescriptor::new(player.to_owned()))) + .collect(); + + let user_id_map = players.iter() + .filter_map(|player| player.user_id.map(|id| (id, player.team as u8))) .collect(); Self { users: tokio::sync::RwLock::new(std::collections::HashMap::new()), - user_id_map: tokio::sync::RwLock::new(std::collections::HashMap::new()), + descriptors, + user_id_map, is_complete: std::sync::atomic::AtomicBool::new(false), game_start: std::sync::atomic::AtomicI64::new(i64::MIN), map_config: std::sync::Arc::new(map), game_descriptor: game, game_duration: std::time::Duration::from_secs((config.game_time_minutes as u64) * 60), - players_info: std::sync::Arc::new(players), custom_logic_handler: custom, - fake_users, fakes_handler, } } @@ -241,15 +264,27 @@ impl GenericGamemodeEngine { &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).copied() + pub(super) fn user_key_by_user_id(&self, user_id: i32) -> Option { + self.user_id_map.get(&user_id).copied() } + pub(super) fn user_descriptor(&self, player_id: u8) -> Option<&'_ UserDescriptor> { + self.descriptors.get(&player_id) + } + + pub(super) fn user_descriptors(&self) -> &'_ std::collections::HashMap { + &self.descriptors + } + + /*pub(super) async fn user_connection(&self, player_id: u8) -> Option<&'_ UserConnection> { + self.users.read().await.get(&player_id) + }*/ + pub(super) async fn rebroadcast(&self, user_id: i32, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T, in_game: bool) { - for conn in self.users.read().await.values() { + for (player_id, conn) in self.users.read().await.iter() { if user_id == conn.user.user_id() { continue; } if in_game { - let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); + let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed)); if !matches!(mode, ConnectionMode::InGame) { continue; } } let sender = crate::handlers::RlnlSender::new(&conn.connection.sender); @@ -263,10 +298,10 @@ impl GenericGamemodeEngine { } pub(super) async fn rebroadcast_dataless(&self, user_id: i32, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, in_game: bool) { - for conn in self.users.read().await.values() { + for (player_id, conn) in self.users.read().await.iter() { if user_id == conn.user.user_id() { continue; } if in_game { - let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); + let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed)); if !matches!(mode, ConnectionMode::InGame) { continue; } } let sender = crate::handlers::RlnlSender::new(&conn.connection.sender); @@ -279,9 +314,9 @@ impl GenericGamemodeEngine { } pub(super) async fn broadcast(&self, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T, in_game: bool) { - for conn in self.users.read().await.values() { + for (player_id, conn) in self.users.read().await.iter() { if in_game { - let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); + let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed)); if !matches!(mode, ConnectionMode::InGame) { continue; } } let sender = conn.connection.rlnl(); @@ -295,9 +330,9 @@ impl GenericGamemodeEngine { } pub(super) async fn broadcast_dataless(&self, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, in_game: bool) { - for conn in self.users.read().await.values() { + for (player_id, conn) in self.users.read().await.iter() { if in_game { - let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); + let mode = ConnectionMode::from_u8(self.user_descriptor(*player_id).unwrap().state.mode.load(std::sync::atomic::Ordering::Relaxed)); if !matches!(mode, ConnectionMode::InGame) { continue; } } let sender = conn.connection.rlnl(); @@ -328,7 +363,19 @@ impl GenericGamemodeEngine { game_start != i64::MIN && game_end <= chrono::Utc::now().timestamp() } - /*pub(super) async fn send_to_player(&self, player_id: u8, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T) { + pub(super) fn players_info(&self) -> Vec> { + self.descriptors.values() + .map(|p| p.descriptor.clone()) + .collect() + } + + pub(super) fn real_player_count(&self) -> usize { + self.descriptors.values() + .filter(|p| p.descriptor.user_id.is_some()) + .count() + } + + pub(super) async fn send_to_player(&self, player_id: u8, code: rlnl::event_code::NetworkEvent, property: literustlib::packet::Property, data: &T) { if let Some(player) = self.users.read().await.get(&player_id) { crate::events::log_lnl_send_failure(player.connection.rlnl().send_data( data, @@ -337,7 +384,7 @@ impl GenericGamemodeEngine { &player.connection.connection, ).await); } - }*/ + } pub(super) fn spawn(self) -> tokio::sync::mpsc::Sender { let (tx, rx) = tokio::sync::mpsc::channel(super::CHANNEL_BOUND); @@ -438,24 +485,22 @@ impl GenericGamemodeEngine { inner: None, })).unwrap_or_default(); } else { - let mut users = self.users.write().await; //tokio::time::sleep(std::time::Duration::from_secs(1)).await; //let id = users.len() as u8; let user_id = user.user_id(); - let player_info = self.players_info.iter().find(|p| p.user_id == Some(user_id)).unwrap(); - let id = player_info.player_id; + let player_info = self.descriptors.values().find(|p| p.descriptor.user_id == Some(user_id)).unwrap(); + let id = player_info.descriptor.player_id; + let aliases = self.fakes_handler.get_client_ais().await.into_iter().find(|(key, _val)| *key == id).map(|(_key, val)| val).unwrap_or_default(); + log::info!("AIs running on new player {}: {:?}", id, aliases); let new_user = UserConnection { user, connection: UserSender { connection, sender, }, - state: std::sync::Arc::new(UserState::new()), - machine: MachineState::new(), - descriptor: player_info.to_owned(), - counters: UserData::new(), + aliases, }; - if self.custom_logic_handler.on_player_join(self, &new_user, &self.players_info).await { + if self.custom_logic_handler.on_player_join(self, &new_user, player_info).await { //self.spawn_send_loading_events(&new_user, id, self.players_info.clone()); crate::events::log_lnl_send_failure(new_user.connection.rlnl().send_data( &rlnl::events::ingame::PlayerId { player: id }, @@ -464,28 +509,55 @@ impl GenericGamemodeEngine { &new_user.connection.connection ).await); 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); + let new_user = std::sync::Arc::new(new_user); + let mut users = self.users.write().await; + users.insert(id, new_user.clone()); + for fake_id in new_user.aliases.iter() { + if let Some(player_desc) = self.user_descriptor(*fake_id) { + if self.custom_logic_handler.on_player_join(self, &new_user, player_desc).await { + users.insert(*fake_id, new_user.clone()); + } + } else { + log::warn!("Non-existent fake player id {} was encountered while connecting, ignoring", *fake_id); + } + } } response.send(None).unwrap_or_default(); } } async fn on_end_connection(&self, user_id: i32) -> bool { - if let Some(player_id) = self.user_key_by_user_id(user_id).await { + if let Some(player_id) = self.user_key_by_user_id(user_id) { let conn_opt = self.users.write().await.remove(&player_id); if let Some(conn) = conn_opt { - if self.custom_logic_handler.on_player_end(self, &conn).await { - conn.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed); - if !self.is_complete.load(std::sync::atomic::Ordering::Relaxed) { - self.rebroadcast( - user_id, - rlnl::event_code::NetworkEvent::OnAnotherClientDisconnected, - literustlib::packet::Property::ReliableOrdered, - &rlnl::events::ingame::PlayerId { player: player_id }, - true, - ).await; - } else { + let user_info = self.user_descriptor(player_id).unwrap(); + if self.custom_logic_handler.on_player_end(self, &conn, user_info).await { + user_info.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed); + let mut disconnecting_players = Vec::with_capacity(conn.aliases.len() + 1); + disconnecting_players.push(player_id); + for fake_id in conn.aliases.iter() { + if let Some(user_desc) = self.user_descriptor(*fake_id) { + user_desc.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed); + let conn_opt = self.users.write().await.remove(&fake_id); + if let Some(conn) = conn_opt { + if self.custom_logic_handler.on_player_end(self, &conn, user_desc).await { + disconnecting_players.push(*fake_id); + } + } + } + } + let is_game_complete = self.is_complete.load(std::sync::atomic::Ordering::Relaxed); + for disconnecter in disconnecting_players { + if !is_game_complete { + self.broadcast( + rlnl::event_code::NetworkEvent::OnAnotherClientDisconnected, + literustlib::packet::Property::ReliableOrdered, + &rlnl::events::ingame::PlayerId { player: disconnecter }, + true, + ).await; + } + } + if is_game_complete { // in every other case this packet would've already been sent // this makes the end-of-match "continue" button send you back to the main menu a bit sooner // (otherwise it waits for the multiplayer server to disconnect via timeout) @@ -495,8 +567,10 @@ impl GenericGamemodeEngine { &conn.connection.connection, ).await); } + let mut has_active_connections = false; - for user in self.users.read().await.values() { + for user in self.descriptors.values() { + if user.descriptor.user_id.is_none() { continue; } // skip non-players let mode = ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)); has_active_connections |= !matches!(mode, ConnectionMode::Disconnected); } @@ -519,7 +593,7 @@ impl GenericGamemodeEngine { async fn on_request_leave(&self, user_id: i32) { log::info!("User {} wants to leave game {}", user_id, self.game_guid()); - if let Some(player_id) = self.user_key_by_user_id(user_id).await { + if let Some(player_id) = self.user_key_by_user_id(user_id) { self.rebroadcast( user_id, rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed, @@ -545,14 +619,15 @@ impl GenericGamemodeEngine { user_name: rlnl::types::BinaryWriterString(user_name), progress, }; - for conn in self.users.read().await.values() { + for (player_id, conn) in self.users.read().await.iter() { + let user_desc = self.user_descriptor(*player_id).unwrap(); if user_id == conn.user.user_id() { let progress_percent = ((progress * 100.0).ceil() as u8).clamp(0, 100); log::info!("User {} is loaded {}% into game {}", user_id, progress_percent, self.game_guid()); - conn.state.progress.store(progress_percent, std::sync::atomic::Ordering::Relaxed); + user_desc.state.progress.store(progress_percent, std::sync::atomic::Ordering::Relaxed); continue; } - let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); + let mode = ConnectionMode::from_u8(user_desc.state.mode.load(std::sync::atomic::Ordering::Relaxed)); match mode { ConnectionMode::Loading | ConnectionMode::Disconnected => {}, ConnectionMode::WaitingForSync | ConnectionMode::Sync | ConnectionMode::WaitingToStart => { @@ -572,40 +647,24 @@ impl GenericGamemodeEngine { async fn on_request_loading_progress(&self, user_id: i32) { log::info!("Got request loading progress"); - if let Some(user_key) = self.user_key_by_user_id(user_id).await { + if let Some(user_key) = self.user_key_by_user_id(user_id) { if let Some(user_info) = self.users.read().await.get(&user_key) { - self.spawn_send_loading_events(user_info, user_key, self.players_info.clone()); + let mut client_ai_map = self.fakes_handler.get_client_ais().await; + self.spawn_send_loading_events(user_info, user_key, self.players_info(), client_ai_map.remove(&user_key).unwrap_or_default()); let sender = user_info.connection.rlnl(); - for conn in self.users.read().await.values() { - if user_id == conn.user.user_id() { continue; } - /*crate::events::log_lnl_send_failure(sender.send_data( - &user_info.1, - rlnl::event_code::NetworkEvent::BroadcastLoadingProgress, - literustlib::packet::Property::ReliableOrdered, - &user_info.0.connection, - ).await);*/ + for user_desc in self.descriptors.values() { + if Some(user_id) == user_desc.descriptor.user_id { continue; } let event = rlnl::events::loading::LoadingProgress { - user_name: rlnl::types::BinaryWriterString(conn.user.user_name().to_owned()), - progress: (conn.state.progress.load(std::sync::atomic::Ordering::Relaxed) as f32) / 100.0, + //user_name: rlnl::types::BinaryWriterString(conn.user.user_name().to_owned()), + user_name: rlnl::types::BinaryWriterString(user_desc.descriptor.public_id.clone()), + progress: (user_desc.state.progress.load(std::sync::atomic::Ordering::Relaxed) as f32) / 100.0, }; crate::events::log_lnl_send_failure(sender.send_data( &event, rlnl::event_code::NetworkEvent::BroadcastLoadingProgress, literustlib::packet::Property::ReliableOrdered, &user_info.connection.connection, - ).await) - } - for fake in self.fake_users.values() { - let event = rlnl::events::loading::LoadingProgress { - user_name: rlnl::types::BinaryWriterString(fake.descriptor.public_id.clone()), - progress: (fake.state.progress.load(std::sync::atomic::Ordering::Relaxed) as f32) / 100.0, - }; - crate::events::log_lnl_send_failure(sender.send_data( - &event, - rlnl::event_code::NetworkEvent::BroadcastLoadingProgress, - literustlib::packet::Property::ReliableOrdered, - &user_info.connection.connection, - ).await) + ).await); } } else { log::error!("Failed to find player {} in connected users for match {}", user_key, self.game_guid()); @@ -621,7 +680,7 @@ impl GenericGamemodeEngine { category: oj_rc_core::data::weapon_list::ItemCategory, size: oj_rc_core::data::cube_list::ItemTier ) { - if let Some(conn) = self.users.read().await.get(&machine_id) { + if let Some(conn) = self.user_descriptor(machine_id) { let category_u32 = category as u32; let size_u32 = size as u32; conn.machine.selected_weapon.category.store(category_u32, std::sync::atomic::Ordering::Relaxed); @@ -644,39 +703,44 @@ impl GenericGamemodeEngine { async fn on_request_loading_sync(&self, user_id: i32) { // wait for all users to be ready before transitioning to loading sync let mut ready_count = 0; - for user in self.users.read().await.values() { + for (player_id, user) in self.users.read().await.iter() { + let user_desc = self.user_descriptor(*player_id).unwrap(); if user.user.user_id() == user_id { - if !matches!(ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::Loading | ConnectionMode::Disconnected) { + if !matches!(ConnectionMode::from_u8(user_desc.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::Loading | ConnectionMode::Disconnected) { log::warn!("Got RequestLoadingSync after user {} was already in/past WaitingForSync stage", user_id); continue; } log::info!("User {} is awaiting sync", user_id); - user.state.mode.store(ConnectionMode::WaitingForSync.to_u8(), std::sync::atomic::Ordering::Relaxed); + user_desc.state.mode.store(ConnectionMode::WaitingForSync.to_u8(), std::sync::atomic::Ordering::Relaxed); ready_count += 1; - } else if matches!(ConnectionMode::from_u8(user.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::WaitingForSync) { + } else if matches!(ConnectionMode::from_u8(user_desc.state.mode.load(std::sync::atomic::Ordering::Relaxed)), ConnectionMode::WaitingForSync) { ready_count += 1; } } - let player_count = self.players_info.iter().filter(|x| x.user_id.is_some()).count(); + let player_count = self.real_player_count(); if ready_count == player_count { 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, self.players_info.clone(), extra_packets, self.map_config.clone()); + let user_info = self.user_descriptor(*user_key).unwrap(); + let extra_packets = self.custom_logic_handler.extra_sync_events(self, conn, user_info).await; + self.spawn_send_sync_events(conn, conn.user.user_id(), *user_key, self.players_info(), extra_packets, self.map_config.clone()); + let user_desc = self.user_descriptor(*user_key).unwrap(); + user_desc.state.mode.store(ConnectionMode::Sync.to_u8(), std::sync::atomic::Ordering::Relaxed); } } } async fn on_load_complete(&self, user_id: i32) { - if let Some(user_key) = self.user_key_by_user_id(user_id).await { + if let Some(user_key) = self.user_key_by_user_id(user_id) { if let Some(conn) = self.users.read().await.get(&user_key) { + let user_desc = self.user_descriptor(user_key).unwrap(); log::info!("Loading complete for game {}, user {} (player {})", self.game_guid(), user_id, user_key); - conn.state.progress.store(100, std::sync::atomic::Ordering::Relaxed); - let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); + user_desc.state.progress.store(100, std::sync::atomic::Ordering::Relaxed); + let mode = ConnectionMode::from_u8(user_desc.state.mode.load(std::sync::atomic::Ordering::Relaxed)); if !matches!(mode, ConnectionMode::Sync) { - log::warn!("Player {} completed loading but is in mode {:?} (should be Sync)", conn.descriptor.player_id, mode); + log::warn!("Player {} completed loading but is in mode {:?} (should be Sync)", user_desc.descriptor.player_id, mode); } - conn.state.mode.store(ConnectionMode::WaitingToStart.to_u8(), std::sync::atomic::Ordering::Relaxed); + user_desc.state.mode.store(ConnectionMode::WaitingToStart.to_u8(), std::sync::atomic::Ordering::Relaxed); self.spawn_initial_ingame_events(conn, user_id); } else { log::warn!("Invalid LoadComplete user key {} for game {}", user_key, self.game_guid()); @@ -688,13 +752,14 @@ impl GenericGamemodeEngine { } // wait for all users to be ready for starting game start countdown let mut all_users_loading_complete = true; - for conn in self.users.read().await.values() { - let mode = ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); + for player_info in self.descriptors.values() { + if player_info.descriptor.user_id.is_none() { continue; } // skip non-user players + let mode = ConnectionMode::from_u8(player_info.state.mode.load(std::sync::atomic::Ordering::Relaxed)); all_users_loading_complete &= matches!(mode, ConnectionMode::WaitingToStart); } // trigger game start if all_users_loading_complete { - let player_count = self.players_info.iter().filter(|x| x.user_id.is_some()).count(); + let player_count = self.real_player_count(); log::info!("All players ({}) are ready for game {}", player_count, self.game_guid()); tokio::time::sleep(Self::END_OF_SYNC_DELAY).await; self.fakes_handler.on_ready( @@ -705,8 +770,9 @@ impl GenericGamemodeEngine { let game_start = chrono::Utc::now() + Self::COUNTDOWN_DURATION; if self.custom_logic_handler.on_countdown_start(self, game_start).await { let mut senders = Vec::new(); - for conn in self.users.read().await.values() { - senders.push((conn.connection.clone(), conn.state.clone())); + for (player_id, conn) in self.users.read().await.iter() { + let user_desc = self.user_descriptor(*player_id).unwrap(); + senders.push((conn.connection.clone(), user_desc.state.clone())); } self.game_start.store(game_start.timestamp(), std::sync::atomic::Ordering::Relaxed); super::countdown::match_countdown(senders, game_start); @@ -742,7 +808,7 @@ impl GenericGamemodeEngine { log::info!("Player {} was destroyed by player {} (user {}) in game {}", remote_player, killer_player, user_id, self.game_guid()); if self.custom_logic_handler.on_vehicle_destroyed(self, killer_player, remote_player).await { // the kill tracking is initiated separately by the client with kill bonus event - if let Some(killed) = self.users.read().await.get(&remote_player) { + if let Some(killed) = self.user_descriptor(remote_player) { killed.counters.deaths.fetch_add(1, std::sync::atomic::Ordering::Relaxed); let data = killed.counters.get_generic_packet(remote_player, rlnl::types::IngameStatId::RobotDestroyed, None); self.broadcast( @@ -756,7 +822,7 @@ impl GenericGamemodeEngine { } async fn on_self_destruct(&self, user_id: i32, is_classic: bool) { - if let Some(player_id) = self.user_key_by_user_id(user_id).await { + if let Some(player_id) = self.user_key_by_user_id(user_id) { self.rebroadcast( user_id, rlnl::event_code::NetworkEvent::MachineDestroyedConfirmed, @@ -780,7 +846,8 @@ impl GenericGamemodeEngine { literustlib::packet::Property::ReliableOrdered, &conn.connection.connection ).await); - conn.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed); + let user_desc = self.user_descriptor(player_id).unwrap(); + user_desc.state.mode.store(ConnectionMode::Disconnected.to_u8(), std::sync::atomic::Ordering::Relaxed); conn.connection.connection.disconnect(); } } @@ -789,7 +856,7 @@ impl GenericGamemodeEngine { } async fn on_flipping_started(&self, user_id: i32) { - if let Some(user_key) = self.user_key_by_user_id(user_id).await { + if let Some(user_key) = self.user_key_by_user_id(user_id) { self.rebroadcast( user_id, rlnl::event_code::NetworkEvent::AlignmentRectifierStarted, @@ -802,7 +869,8 @@ impl GenericGamemodeEngine { async fn on_map_ping(&self, _user_id: i32, ping: rlnl::events::ingame::MapPing) { for (id, conn) in self.users.read().await.iter() { - if (*id as i32) != ping.sender && conn.descriptor.team == ping.team_id { + let user_desc = self.user_descriptor(*id).unwrap(); + if (*id as i32) != ping.sender && user_desc.descriptor.team == ping.team_id { crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data( &ping, rlnl::event_code::NetworkEvent::MapPingEvent, @@ -820,7 +888,8 @@ impl GenericGamemodeEngine { ) { if self.custom_logic_handler.on_kill_bonus(self, shooter, shootee).await { if let Some(to_reward) = self.users.read().await.get(&shooter) { - to_reward.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let to_reward_desc = self.user_descriptor(shooter).unwrap(); + to_reward_desc.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed); crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data( &rlnl::events::ingame::Kill { killee_player_id: shootee, @@ -830,7 +899,7 @@ impl GenericGamemodeEngine { literustlib::packet::Property::ReliableOrdered, &to_reward.connection.connection ).await); - let data = to_reward.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::Kill, None); + let data = to_reward_desc.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::Kill, None); self.broadcast( rlnl::event_code::NetworkEvent::UpdateGameStats, literustlib::packet::Property::ReliableOrdered, @@ -849,7 +918,8 @@ impl GenericGamemodeEngine { let lock = self.users.read().await; for shooter in shooters { if let Some(to_reward) = lock.get(&shooter) { - to_reward.counters.assists.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let to_reward_desc = self.user_descriptor(shooter).unwrap(); + to_reward_desc.counters.assists.fetch_add(1, std::sync::atomic::Ordering::Relaxed); crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data( &rlnl::events::ingame::Kill { killee_player_id: shootee, @@ -859,7 +929,7 @@ impl GenericGamemodeEngine { literustlib::packet::Property::ReliableOrdered, &to_reward.connection.connection ).await); - let data = to_reward.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::KillAssist, None); + let data = to_reward_desc.counters.get_generic_packet(shooter, rlnl::types::IngameStatId::KillAssist, None); self.broadcast( rlnl::event_code::NetworkEvent::UpdateGameStats, literustlib::packet::Property::ReliableOrdered, @@ -874,12 +944,11 @@ impl GenericGamemodeEngine { _user_id: i32, info: rlnl::events::ingame::DestroyedHealedCubesBonus ) { - let lock = self.users.read().await; for shooter in info.shooters { - if let Some(to_reward) = lock.get(&shooter.shooting_player_id) { + if let Some(to_reward) = self.user_descriptor(shooter.shooting_player_id) { let mut total_cubes = 0; for target in shooter.shooter_targets { - if let Some(to_punish) = lock.get(&target.target_player_id) { + if let Some(to_punish) = self.user_descriptor(target.target_player_id) { let mut total_cubes_received = 0; for cubes in target.cube_amounts { // TODO use cube_id for something!? @@ -905,12 +974,11 @@ impl GenericGamemodeEngine { _user_id: i32, info: rlnl::events::ingame::DestroyedHealedCubesBonus, ) { - let lock = self.users.read().await; for shooter in info.shooters { - if let Some(to_reward) = lock.get(&shooter.shooting_player_id) { + if let Some(to_reward) = self.user_descriptor(shooter.shooting_player_id) { let mut total_cubes = 0; for target in shooter.shooter_targets { - if let Some(to_punish) = lock.get(&target.target_player_id) { + if let Some(to_punish) = self.user_descriptor(target.target_player_id) { let mut total_cubes_received = 0; for cubes in target.cube_amounts { // TODO use cube_id for something!? @@ -984,10 +1052,10 @@ impl GenericGamemodeEngine { let (x4, y4, z4) = (x + coords[0], y + coords[1], z + coords[2]); //log::debug!("Player {} world CoM is at (x, y, z) ({}, {}, {})", motion.player_id, x4, y4, z4); if self.custom_logic_handler.on_motion(self, &motion, (x4, y4, z4)).await { - if let Some(conn) = self.users.read().await.get(&motion.player_id) { - conn.machine.location.x.store(x4, std::sync::atomic::Ordering::Relaxed); - conn.machine.location.y.store(y4, std::sync::atomic::Ordering::Relaxed); - conn.machine.location.z.store(z4, std::sync::atomic::Ordering::Relaxed); + if let Some(user_desc) = self.user_descriptor(motion.player_id) { + user_desc.machine.location.x.store(x4, std::sync::atomic::Ordering::Relaxed); + user_desc.machine.location.y.store(y4, std::sync::atomic::Ordering::Relaxed); + user_desc.machine.location.z.store(z4, std::sync::atomic::Ordering::Relaxed); use byteserde::ser_heap::ByteSerializeHeap; let mut ser = byteserde::ser_heap::ByteSerializerHeap::default(); if let Err(e) = motion.byte_serialize_heap(&mut ser) { @@ -1011,19 +1079,19 @@ impl GenericGamemodeEngine { } } - fn spawn_send_loading_events(&self, user: &UserConnection, player_id: u8, players: std::sync::Arc>) { + fn spawn_send_loading_events(&self, user: &UserConnection, player_id: u8, players: Vec>, client_ais: Vec) { 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)); + tokio::spawn(Self::send_loading_events_wrapper(connection, player_id, user_id, players, client_ais)); } - 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 { + async fn send_loading_events_wrapper(connection: UserSender, player_id: u8, user_id: i32, players: Vec>, client_ais: Vec) { + if let Err(e) = Self::send_loading_events(&connection, player_id, players, client_ais).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: std::sync::Arc>) -> std::io::Result<()> { + async fn send_loading_events(user: &UserSender, _player_id: u8, players: Vec>, client_ais: Vec) -> std::io::Result<()> { //tokio::time::sleep(std::time::Duration::from_millis(1)).await; let sender = user.rlnl(); /*sender.send_data( @@ -1048,8 +1116,8 @@ impl GenericGamemodeEngine { ).await?; sender.send_data( &rlnl::events::loading::PlayerIDs { - num_ids: 0, - players: vec![], + num_ids: client_ais.len() as i32, + players: client_ais.into_iter().map(|x| x as i32).collect(), }, rlnl::event_code::NetworkEvent::HostAIs, literustlib::packet::Property::ReliableOrdered, @@ -1058,19 +1126,19 @@ impl GenericGamemodeEngine { Ok(()) } - 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) { + fn spawn_send_sync_events(&self, user: &UserConnection, user_id: i32, player_id: u8, players: Vec>, extra_packets: Vec, map: std::sync::Arc) { let connection = user.connection.clone(); 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); + //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, players: std::sync::Arc>, extra_packets: Vec, map: std::sync::Arc) { + async fn send_sync_events_wrapper(connection: UserSender, user_id: i32, player_id: u8, players: Vec>, 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, players: std::sync::Arc>, extra_packets: Vec, map: std::sync::Arc) -> std::io::Result<()> { + async fn send_sync_events(connection: UserSender, _player_id: u8, players: Vec>, extra_packets: Vec, map: std::sync::Arc) -> std::io::Result<()> { let num_players = players.len() as u8; let sender = connection.rlnl(); sender.send_empty( diff --git a/rc_multiplayer/src/matches/modes/battle_arena.rs b/rc_multiplayer/src/matches/modes/battle_arena.rs index 5c6b61a..79a016b 100644 --- a/rc_multiplayer/src/matches/modes/battle_arena.rs +++ b/rc_multiplayer/src/matches/modes/battle_arena.rs @@ -2,16 +2,16 @@ use crate::matches::{modes::trackers::SurrenderGameTracker, CustomGameLogic}; struct PlayerTracker { connected: tokio::sync::Mutex>>, // team -> set of player_id - in_point: tokio::sync::RwLock>, // player_id -> in point state (if val > u8::MAX then not in a point) - respawning: tokio::sync::RwLock>, // player_id -> time when they'll spawn (time since unix epoch) + in_point: std::collections::HashMap, // player_id -> in point state (if val > u8::MAX then not in a point) + respawning: std::collections::HashMap, // player_id -> time when they'll spawn (time since unix epoch) } impl PlayerTracker { - fn new() -> Self { + fn new(players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> Self { Self { connected: tokio::sync::Mutex::new(std::collections::HashMap::new()), - in_point: tokio::sync::RwLock::new(std::collections::HashMap::new()), - respawning: tokio::sync::RwLock::new(std::collections::HashMap::new()), + in_point: players.iter().map(|player| (player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX))).collect(), + respawning: players.iter().map(|player| (player.player_id, std::sync::atomic::AtomicI64::new(i64::MIN))).collect(), } } @@ -24,8 +24,8 @@ impl PlayerTracker { None } - async fn swap_is_in_point(&self, player_id: u8, point: Option) -> Option { - self.in_point.read().await.get(&player_id).and_then(|x| { + fn swap_is_in_point(&self, player_id: u8, point: Option) -> Option { + self.in_point.get(&player_id).and_then(|x| { let old_point = x.swap(point.map(|x| x as u16).unwrap_or(u16::MAX), std::sync::atomic::Ordering::Relaxed); if old_point > u8::MAX as u16 { None @@ -44,8 +44,6 @@ impl PlayerTracker { new_team.insert(player.player_id); conn_lock.insert(player.team as u8, new_team); } - self.in_point.write().await.insert(player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX)); - self.respawning.write().await.insert(player.player_id, std::sync::atomic::AtomicI64::new(i64::MIN)); } async fn disconnect_player(&self, player_id: u8) { @@ -898,14 +896,14 @@ impl BattleArenaLogic { const CRYSTAL_ID: u32 = 3950293873; const CLASP_ID: u32 = 606866102; - pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig, parsers: &oj_rc_core::cubes::CubeParsers, ba_config: oj_rc_core::data::battle_arena_config::BattleArenaData) -> Self { + pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig, parsers: &oj_rc_core::cubes::CubeParsers, players: &[oj_rc_core::persist::user::PlayerDescriptor], ba_config: oj_rc_core::data::battle_arena_config::BattleArenaData) -> Self { let cube_parser = parsers.locations_of(); let crystals = cube_parser.locations_of_by_distance_to_first(&mut std::io::Cursor::new(&ba_config.base_machine_map), Self::CRYSTAL_ID, Self::CLASP_ID); Self { respawn_full_heal_duration: config.respawn_full_heal_duration, respawn_heal_duration: config.respawn_heal_duration, timer_task: tokio::sync::Mutex::new(None), - player_tracking: PlayerTracker::new(), + player_tracking: PlayerTracker::new(players), capture_tracking: PointTracker::new(map.capture_points.iter().map(|(_, speed)| *speed)), surrender_tracking: super::trackers::SurrenderGameTracker::new(), base_tracking: BaseTracker::new(map.bases.keys(), &crystals, &ba_config), @@ -989,7 +987,8 @@ impl BattleArenaLogic { winning_team, end_reason, }; - for player in generic.users.read().await.values() { + for (player_id, player) in generic.user_descriptors().iter() { + if player.descriptor.user_id.is_none() { continue; } // skip non-user players let is_winner = player.descriptor.team == winning_team as i32; let net_event = match ty { WinMode::BaseFull @@ -1001,21 +1000,18 @@ impl BattleArenaLogic { if is_winner { rlnl::event_code::NetworkEvent::GameWon } else { rlnl::event_code::NetworkEvent::GameLost } } }; - crate::events::log_lnl_send_failure( - player.connection.rlnl() - .send_data( - &payload, - net_event, - literustlib::packet::Property::ReliableOrdered, - &player.connection.connection, - ).await - ); + generic.send_to_player( + *player_id, + net_event, + literustlib::packet::Property::ReliableOrdered, + &payload, + ).await; } } async fn do_destruct_tasks(&self, generic: &crate::matches::GenericGamemodeEngine, player_id: u8) { if let Some(player_team) = self.player_tracking.team(player_id).await { - let was_in_point = self.player_tracking.swap_is_in_point(player_id, None).await; + let was_in_point = self.player_tracking.swap_is_in_point(player_id, None); if let Some(was_in_point) = was_in_point { self.capture_tracking.on_exit(generic, was_in_point, player_id, player_team as i8, self.config.num_segments as f32).await; } @@ -1027,7 +1023,7 @@ impl BattleArenaLogic { let respawn_time = std::time::Duration::from_secs(self.config.respawn_time_seconds as u64); let now = chrono::Utc::now(); let respawn_timestamp = now + respawn_time; - if let Some(player_respawn) = self.player_tracking.respawning.read().await.get(&player_id) { + if let Some(player_respawn) = self.player_tracking.respawning.get(&player_id) { player_respawn.store(respawn_timestamp.timestamp(), std::sync::atomic::Ordering::Relaxed); } let respawn_payload = rlnl::events::ingame::RespawnTime { @@ -1142,13 +1138,13 @@ impl BattleArenaLogic { #[async_trait::async_trait] impl CustomGameLogic for BattleArenaLogic { - async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine, player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool { + async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine, _conn: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool { log::info!("Player {} joined", player.descriptor.player_id); self.player_tracking.track_player(&player.descriptor).await; true } - async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine, player: &crate::matches::generic::UserConnection) -> bool { + async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine, _connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool { if generic.is_game_done() { return true; } @@ -1177,7 +1173,7 @@ impl CustomGameLogic for BattleArenaLogic { true } - async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine, _player: &crate::matches::generic::UserConnection) -> Vec { + async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec { vec![ Some(crate::matches::RlnlPacket { event: rlnl::event_code::NetworkEvent::GameModeSettings, @@ -1336,8 +1332,9 @@ impl CustomGameLogic for BattleArenaLogic { async fn on_countdown_start(&self, generic: &crate::matches::GenericGamemodeEngine, game_start: chrono::DateTime) -> bool { let read_lock = generic.users.read().await; let mut senders = Vec::with_capacity(read_lock.len()); - for conn in read_lock.values() { - senders.push((conn.connection.clone(), conn.state.clone())); + for (player_id, conn) in read_lock.iter() { + let state = generic.user_descriptor(*player_id).unwrap().state.clone(); + senders.push((conn.connection.clone(), state)); } drop(read_lock); let game_end = game_start + generic.game_duration; @@ -1465,7 +1462,7 @@ impl CustomGameLogic for BattleArenaLogic { break; } } - let was_in_point = self.player_tracking.swap_is_in_point(motion.player_id, now_in_point).await; + let was_in_point = self.player_tracking.swap_is_in_point(motion.player_id, now_in_point); if was_in_point != now_in_point { //log::info!("Player {}'s occupied capture point changed from {:?} to {:?}", motion.player_id, was_in_point, now_in_point); if let Some(now_in_point) = now_in_point { @@ -1539,7 +1536,7 @@ impl CustomGameLogic for BattleArenaLogic { } }, (rlnl::event_code::NetworkEvent::SurrenderVoteCast, literustlib::packet::Property::ReliableOrdered) => { - if let Some(player_id) = generic.user_key_by_user_id(user_id).await { + if let Some(player_id) = generic.user_key_by_user_id(user_id) { if let Some(team) = self.player_tracking.team(player_id).await { let maybe_vote = ::downcast_ref::(data.as_ref()); if let Some(vote) = maybe_vote { @@ -1555,7 +1552,7 @@ impl CustomGameLogic for BattleArenaLogic { (rlnl::event_code::NetworkEvent::AwardTeamBaseProtoniumDestroyedRequest, literustlib::packet::Property::ReliableOrdered) => { let maybe_crystal_bonus = ::downcast_ref::(data.as_ref()); if let Some(crystal_destroyed) = maybe_crystal_bonus { - if let Some(generic_player) = generic.users.read().await.get(&crystal_destroyed.player_id) { + if let Some(generic_player) = generic.user_descriptor(crystal_destroyed.player_id) { generic_player.counters.crystals.fetch_add(crystal_destroyed.destroyed_cubes as u32, std::sync::atomic::Ordering::Relaxed); let data = generic_player.counters.get_generic_packet(crystal_destroyed.player_id, rlnl::types::IngameStatId::DestroyedProtoniumCubes, Some(crystal_destroyed.destroyed_cubes as _)); generic.broadcast( diff --git a/rc_multiplayer/src/matches/modes/elimination.rs b/rc_multiplayer/src/matches/modes/elimination.rs index e28b01a..8f0d673 100644 --- a/rc_multiplayer/src/matches/modes/elimination.rs +++ b/rc_multiplayer/src/matches/modes/elimination.rs @@ -2,10 +2,17 @@ use crate::matches::CustomGameLogic; struct PlayerTracker { alive: tokio::sync::Mutex>>, // team -> set of player_id - in_base: tokio::sync::RwLock>, // player_id -> in base state (if base > u8::MAX then not in a base) + in_base: std::collections::HashMap, // player_id -> in base state (if base > u8::MAX then not in a base) } impl PlayerTracker { + fn new(players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> Self { + Self { + alive: tokio::sync::Mutex::new(std::collections::HashMap::with_capacity(2)), + in_base: players.iter().map(|player| (player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX))).collect() + } + } + async fn track_vehicle(&self, player: &oj_rc_core::persist::user::PlayerDescriptor) { let mut alive_lock = self.alive.lock().await; if let Some(team) = alive_lock.get_mut(&(player.team as u8)) { @@ -15,7 +22,7 @@ impl PlayerTracker { new_team.insert(player.player_id); alive_lock.insert(player.team as u8, new_team); } - self.in_base.write().await.insert(player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX)); + //self.in_base.write().await.insert(player.player_id, std::sync::atomic::AtomicU16::new(u16::MAX)); } async fn destroy_vehicle(&self, player: &oj_rc_core::persist::user::PlayerDescriptor) { @@ -67,8 +74,8 @@ impl PlayerTracker { None } - async fn swap_is_in_base(&self, player_id: u8, base: Option) -> Option { - self.in_base.read().await.get(&player_id).and_then(|x| { + fn swap_is_in_base(&self, player_id: u8, base: Option) -> Option { + self.in_base.get(&player_id).and_then(|x| { let base = x.swap(base.map(|x| x as u16).unwrap_or(u16::MAX), std::sync::atomic::Ordering::Relaxed); if base > u8::MAX as u16 { None @@ -288,18 +295,18 @@ impl BaseTracker { winning_team, end_reason: rlnl::types::GameEndReason::BaseCaptured, }; - for conn in generic.users.read().await.values() { - let event = if conn.descriptor.team == winning_team_i32 { + for (player_id, player_info) in generic.user_descriptors().iter() { + let event = if player_info.descriptor.team == winning_team_i32 { rlnl::event_code::NetworkEvent::GameWon } else { rlnl::event_code::NetworkEvent::GameLost }; - crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data( - &win_data, + generic.send_to_player( + *player_id, event, literustlib::packet::Property::ReliableOrdered, - &conn.connection.connection - ).await); + &win_data, + ).await; } generic.game_done(); break; @@ -335,12 +342,9 @@ pub struct EliminationLogic { } impl EliminationLogic { - pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig) -> Self { + pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig, players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> Self { Self { - tracked: PlayerTracker { - alive: tokio::sync::Mutex::new(std::collections::HashMap::new()), - in_base: tokio::sync::RwLock::new(std::collections::HashMap::new()), - }, + tracked: PlayerTracker::new(players), bases: BaseTracker { bases: map.bases.iter().map(|(team, base)| (*team, BaseCounters::new(base.1))).collect(), ticker: super::trackers::TickTracker::new(), @@ -361,12 +365,12 @@ impl EliminationLogic { *lock = None; } - async fn on_last_player_gone(&self, generic: &crate::matches::GenericGamemodeEngine, conn: &crate::matches::generic::UserConnection) { + async fn on_last_player_gone(&self, generic: &crate::matches::GenericGamemodeEngine, conn: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) { log::debug!("Everyone is dead, so long and thanks for all the fish"); generic.game_done(); self.abort_timer_sync().await; let data = rlnl::events::ingame::GameLoseWin { - winning_team: if conn.descriptor.team == 0 { 1 } else { 0 }, // always the other team + winning_team: if player.descriptor.team == 0 { 1 } else { 0 }, // always the other team end_reason: rlnl::types::GameEndReason::NoPlayersRemaining, }; crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data( @@ -385,8 +389,10 @@ impl EliminationLogic { end_reason: rlnl::types::GameEndReason::OneTeamRemaining, }; let winning_team_i32 = winning_team as i32; - for conn in generic.users.read().await.values() { - let event = if conn.descriptor.team == winning_team_i32 { + for (player_id, conn) in generic.users.read().await.iter() { + let user_info = generic.user_descriptor(*player_id).unwrap(); + if user_info.descriptor.user_id.is_none() { continue; } // skip non-user players + let event = if user_info.descriptor.team == winning_team_i32 { rlnl::event_code::NetworkEvent::GameWon } else { rlnl::event_code::NetworkEvent::GameLost @@ -403,12 +409,12 @@ impl EliminationLogic { #[async_trait::async_trait] impl CustomGameLogic for EliminationLogic { - async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine, player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool { + async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine, _conn: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool { self.tracked.track_vehicle(&player.descriptor).await; true } - async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine, player: &crate::matches::generic::UserConnection) -> bool { + async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine, connection: &crate::matches::generic::UserConnection, player: &crate::matches::generic::UserDescriptor) -> bool { if generic.is_game_done() { return true; } @@ -422,14 +428,14 @@ impl CustomGameLogic for EliminationLogic { log::info!("Team {} has won sudden death game {} because player {} left", winning_team, generic.game_guid(), player.descriptor.player_id); self.send_win_info(generic, winning_team).await; } else if self.tracked.alive_count().await.is_empty() { - self.on_last_player_gone(generic, player).await; + self.on_last_player_gone(generic, connection, player).await; } true } async fn on_vehicle_destroyed(&self, generic: &crate::matches::GenericGamemodeEngine, _killer: u8, victim: u8) -> bool { - if let Some(conn) = generic.users.read().await.get(&victim) { - self.tracked.destroy_vehicle(&conn.descriptor).await; + if let Some(victim_info) = generic.user_descriptor(victim) { + self.tracked.destroy_vehicle(&victim_info.descriptor).await; let final_score = rlnl::events::ingame::SetFinalGameScore { player_id: victim, score: 42, @@ -446,7 +452,10 @@ impl CustomGameLogic for EliminationLogic { } else { log::info!("Player {} has been destroyed in sudden death game {}", victim, generic.game_guid()); if self.tracked.alive_count().await.is_empty() { - self.on_last_player_gone(generic, conn).await; + if let Some(user_conn) = generic.users.read().await.get(&victim) { + self.on_last_player_gone(generic, user_conn, victim_info).await; + } + } } } @@ -466,7 +475,7 @@ impl CustomGameLogic for EliminationLogic { true } - async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine, _player: &crate::matches::generic::UserConnection) -> Vec { + async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec { vec![ crate::matches::RlnlPacket { event: rlnl::event_code::NetworkEvent::GameModeSettings, @@ -487,8 +496,9 @@ impl CustomGameLogic for EliminationLogic { async fn on_countdown_start(&self, generic: &crate::matches::GenericGamemodeEngine, game_start: chrono::DateTime) -> bool { let read_lock = generic.users.read().await; let mut senders = Vec::with_capacity(read_lock.len()); - for conn in read_lock.values() { - senders.push((conn.connection.clone(), conn.state.clone())); + for (player_id, conn) in read_lock.iter() { + let state = generic.user_descriptor(*player_id).unwrap().state.clone(); + senders.push((conn.connection.clone(), state)); } drop(read_lock); let game_end = game_start + generic.game_duration; @@ -552,7 +562,7 @@ impl CustomGameLogic for EliminationLogic { break; } } - let was_in_base = self.tracked.swap_is_in_base(motion.player_id, now_in_base).await; + let was_in_base = self.tracked.swap_is_in_base(motion.player_id, now_in_base); if now_in_base != was_in_base { if let Some(was_in_base) = was_in_base { self.bases.on_exit(generic, was_in_base, player_team == was_in_base, motion.player_id).await; @@ -572,7 +582,3 @@ 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) diff --git a/rc_multiplayer/src/matches/modes/no_op.rs b/rc_multiplayer/src/matches/modes/no_op.rs index 541d28e..56b95e6 100644 --- a/rc_multiplayer/src/matches/modes/no_op.rs +++ b/rc_multiplayer/src/matches/modes/no_op.rs @@ -5,11 +5,11 @@ pub struct NoOpLogic; #[async_trait::async_trait] impl CustomGameLogic for NoOpLogic { - async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine, _player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool { + async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool { true } - async fn on_player_end(&self, _generic: &crate::matches::GenericGamemodeEngine, _player: &crate::matches::generic::UserConnection) -> bool { + async fn on_player_end(&self, _generic: &crate::matches::GenericGamemodeEngine, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool { true } @@ -25,7 +25,7 @@ impl CustomGameLogic for NoOpLogic { true } - async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine, _player: &crate::matches::generic::UserConnection) -> Vec { + async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec { Vec::default() } diff --git a/rc_multiplayer/src/matches/modes/pit.rs b/rc_multiplayer/src/matches/modes/pit.rs index 7d70ed4..f0f479c 100644 --- a/rc_multiplayer/src/matches/modes/pit.rs +++ b/rc_multiplayer/src/matches/modes/pit.rs @@ -23,18 +23,18 @@ impl WinTracker { end_reason: rlnl::types::GameEndReason::PitMaxKillsAchieved, }; let winning_team_i32 = winning_team as i32; - for conn in generic.users.read().await.values() { - let event = if conn.descriptor.team == winning_team_i32 { + for (player_id, player_info) in generic.user_descriptors().iter() { + let event = if player_info.descriptor.team == winning_team_i32 { rlnl::event_code::NetworkEvent::GameWon } else { rlnl::event_code::NetworkEvent::GameLost }; - crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data( - &data, + generic.send_to_player( + *player_id, event, literustlib::packet::Property::ReliableOrdered, - &conn.connection.connection - ).await); + &data, + ).await; } } @@ -45,7 +45,7 @@ impl WinTracker { for (player_id, streak) in game.player_tracking.streaks.iter() { let player_streak = streak.load(std::sync::atomic::Ordering::Relaxed); if player_streak >= *streak_threshold { - if let Some(conn) = generic.users.read().await.get(player_id) { + if let Some(conn) = generic.user_descriptor(*player_id) { log::info!("Player {} has reached the streak win condition in game {}", player_id, generic.game_guid()); Self::do_win(generic, game, conn.descriptor.team as u8).await; break; @@ -56,28 +56,28 @@ impl WinTracker { } }, oj_rc_core::persist::config::PitWinCondition::TotalKills(kills_threshold) => { - for (player_id, conn) in generic.users.read().await.iter() { - if conn.counters.kills.load(std::sync::atomic::Ordering::Relaxed) >= *kills_threshold { + for (player_id, player_info) in generic.user_descriptors() { + if player_info.counters.kills.load(std::sync::atomic::Ordering::Relaxed) >= *kills_threshold { log::info!("Player {} has reached the total kills win condition in game {}", player_id, generic.game_guid()); - Self::do_win(generic, game, conn.descriptor.team as u8).await; + Self::do_win(generic, game, player_info.descriptor.team as u8).await; break; } } }, oj_rc_core::persist::config::PitWinCondition::Score(score_threshold) => { - for (player_id, conn) in generic.users.read().await.iter() { - if conn.counters.generic_score() >= *score_threshold { + for (player_id, player_info) in generic.user_descriptors() { + if player_info.counters.generic_score() >= *score_threshold { log::info!("Player {} has reached the total score win condition in game {}", player_id, generic.game_guid()); - Self::do_win(generic, game, conn.descriptor.team as u8).await; + Self::do_win(generic, game, player_info.descriptor.team as u8).await; break; } } }, oj_rc_core::persist::config::PitWinCondition::Damage(dmg_threshold) => { - for (player_id, conn) in generic.users.read().await.iter() { - if conn.counters.cubes.load(std::sync::atomic::Ordering::Relaxed) >= *dmg_threshold { + for (player_id, player_info) in generic.user_descriptors() { + if player_info.counters.cubes.load(std::sync::atomic::Ordering::Relaxed) >= *dmg_threshold { log::info!("Player {} has reached the total damage win condition in game {}", player_id, generic.game_guid()); - Self::do_win(generic, game, conn.descriptor.team as u8).await; + Self::do_win(generic, game, player_info.descriptor.team as u8).await; break; } } @@ -130,7 +130,7 @@ impl PlayerTracker { } } - fn pit_stats(&self, users: &std::collections::HashMap) -> rlnl::events::ingame::PitModeState { + fn pit_stats(&self, users: &std::collections::HashMap) -> rlnl::events::ingame::PitModeState { let mut player_stats = Vec::with_capacity(self.streaks.len()); for (player_id, streak) in self.streaks.iter() { if let Some(user) = users.get(player_id) { @@ -188,7 +188,7 @@ impl PitLogic { log::warn!("Pit game {} has no leader", generic.game_guid()); return; }; - let killer_score = if let Some(user) = generic.users.read().await.get(&killer) { + let killer_score = if let Some(user) = generic.user_descriptor(killer) { user.counters.generic_score() } else { log::warn!("Player {} score not found", killer); @@ -216,7 +216,7 @@ impl PitLogic { } async fn do_leaderboard_update(&self, generic: &crate::matches::GenericGamemodeEngine) { - let latest_stats = self.player_tracking.pit_stats(&*generic.users.read().await); + let latest_stats = self.player_tracking.pit_stats(generic.user_descriptors()); generic.broadcast( rlnl::event_code::NetworkEvent::PitModeState, literustlib::packet::Property::ReliableOrdered, @@ -331,19 +331,20 @@ impl PitLogic { #[async_trait::async_trait] impl CustomGameLogic for PitLogic { - async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine, _player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool { + async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine, _conn: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool { true } - async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine, _player: &crate::matches::generic::UserConnection) -> bool { + async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool { if generic.is_game_done() { return true; } let read_lock = generic.users.read().await; if read_lock.len() == 1 { // nobody to play against, automatically end the game - let last_player = &read_lock[&0]; - WinTracker::do_win(generic, self, last_player.descriptor.team as u8).await; + let player_id = read_lock.keys().next().unwrap(); + let user_info = generic.user_descriptor(*player_id).unwrap(); + WinTracker::do_win(generic, self, user_info.descriptor.team as u8).await; } true } @@ -361,9 +362,18 @@ impl CustomGameLogic for PitLogic { } async fn on_kill_bonus(&self, generic: &crate::matches::GenericGamemodeEngine, killer: u8, victim: u8) -> bool { - if let Some(to_reward) = generic.users.read().await.get(&killer) { + if let Some(to_reward) = generic.user_descriptor(killer) { to_reward.counters.kills.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data( + generic.send_to_player( + killer, + rlnl::event_code::NetworkEvent::ConfirmedKill, + literustlib::packet::Property::ReliableOrdered, + &rlnl::events::ingame::Kill { + killee_player_id: victim, + killer_player_id: killer, + }, + ).await; + /*crate::events::log_lnl_send_failure(to_reward.connection.rlnl().send_data( &rlnl::events::ingame::Kill { killee_player_id: victim, killer_player_id: killer, @@ -371,7 +381,7 @@ impl CustomGameLogic for PitLogic { rlnl::event_code::NetworkEvent::ConfirmedKill, literustlib::packet::Property::ReliableOrdered, &to_reward.connection.connection - ).await); + ).await);*/ let data = to_reward.counters.get_generic_packet(killer, rlnl::types::IngameStatId::Kill, None); generic.broadcast( rlnl::event_code::NetworkEvent::UpdateGameStats, @@ -384,7 +394,7 @@ impl CustomGameLogic for PitLogic { false } - async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine, _player: &crate::matches::generic::UserConnection) -> Vec { + async fn extra_sync_events(&self, _generic: &crate::matches::GenericGamemodeEngine, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec { let mut initial_spawn_packets = self.initial_spawns_to_packets(); initial_spawn_packets.push( crate::matches::RlnlPacket { @@ -403,8 +413,9 @@ impl CustomGameLogic for PitLogic { let read_lock = generic.users.read().await; let game_end = game_start + generic.game_duration; let mut senders = Vec::with_capacity(read_lock.len()); - for conn in read_lock.values() { - senders.push((conn.connection.clone(), conn.state.clone())); + for (player_id, conn) in read_lock.iter() { + let state = generic.user_descriptor(*player_id).unwrap().state.clone(); + senders.push((conn.connection.clone(), state)); } drop(read_lock); let end_packets = if self.settings.wins.iter().any(|cond| matches!(cond, oj_rc_core::persist::config::PitWinCondition::Time)) { diff --git a/rc_multiplayer/src/matches/modes/team_death_match.rs b/rc_multiplayer/src/matches/modes/team_death_match.rs index d8329e6..fe12f70 100644 --- a/rc_multiplayer/src/matches/modes/team_death_match.rs +++ b/rc_multiplayer/src/matches/modes/team_death_match.rs @@ -86,18 +86,18 @@ impl ScoreTracker { end_reason: if was_sudden_death { rlnl::types::GameEndReason::TeamDeathMatchTimeExpiredSuddenDeath } else { rlnl::types::GameEndReason::TeamDeathMatchTimeExpiredMostKills }, }; let winning_team_i32 = winning_team as i32; - for conn in generic.users.read().await.values() { - let event = if conn.descriptor.team == winning_team_i32 { + for (player_id, player_info) in generic.user_descriptors().iter() { + let event = if player_info.descriptor.team == winning_team_i32 { rlnl::event_code::NetworkEvent::GameWonBaseDestroyed } else { rlnl::event_code::NetworkEvent::GameLostBaseDestroyed }; - crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data( - &data, + generic.send_to_player( + *player_id, event, literustlib::packet::Property::ReliableOrdered, - &conn.connection.connection - ).await); + &data, + ).await; } } else { self.is_sudden_death.store(true, std::sync::atomic::Ordering::Relaxed); @@ -111,18 +111,18 @@ impl ScoreTracker { end_reason: rlnl::types::GameEndReason::TeamDeathMatchMaxKillsAchieved, }; let winning_team_i32 = winning_team as i32; - for conn in generic.users.read().await.values() { - let event = if conn.descriptor.team == winning_team_i32 { + for (player_id, player_info) in generic.user_descriptors().iter() { + let event = if player_info.descriptor.team == winning_team_i32 { rlnl::event_code::NetworkEvent::GameWonBaseDestroyed } else { rlnl::event_code::NetworkEvent::GameLostBaseDestroyed }; - crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data( - &data, + generic.send_to_player( + *player_id, event, literustlib::packet::Property::ReliableOrdered, - &conn.connection.connection - ).await); + &data, + ).await; } } @@ -173,7 +173,7 @@ impl PlayerTracker { async fn single_remaining_team(generic: &crate::matches::GenericGamemodeEngine) -> Option { let mut first_remaining_team = None; - for conn in generic.users.read().await.values() { + for conn in generic.user_descriptors().values() { let mode = crate::matches::generic::ConnectionMode::from_u8(conn.state.mode.load(std::sync::atomic::Ordering::Relaxed)); if matches!(mode, crate::matches::generic::ConnectionMode::InGame) { if let Some(first_remaining_team) = first_remaining_team { @@ -241,18 +241,19 @@ impl TeamDeathMatchLogic { end_reason, }; let winning_team_i32 = winning_team as i32; - for conn in generic.users.read().await.values() { - let event = if conn.descriptor.team == winning_team_i32 { + for (player_id, player_info) in generic.user_descriptors().iter() { + if player_info.descriptor.user_id.is_none() { continue; } // skip non-user players + let event = if player_info.descriptor.team == winning_team_i32 { rlnl::event_code::NetworkEvent::GameWonBaseDestroyed } else { rlnl::event_code::NetworkEvent::GameLostBaseDestroyed }; - crate::events::log_lnl_send_failure(conn.connection.rlnl().send_data( - &data, + generic.send_to_player( + *player_id, event, literustlib::packet::Property::ReliableOrdered, - &conn.connection.connection - ).await); + &data + ).await; } } @@ -274,7 +275,7 @@ impl TeamDeathMatchLogic { &respawn_payload, true ).await; - if let Some(user) = generic.users.read().await.get(&player_id) { + if let Some(user) = generic.user_descriptor(player_id) { let player_team = user.descriptor.team as u8; let spawn_point = if let Some(team_spawns) = generic.map_config.spawns.get(&player_team) { if team_spawns.is_empty() { @@ -304,15 +305,16 @@ impl TeamDeathMatchLogic { #[async_trait::async_trait] impl CustomGameLogic for TeamDeathMatchLogic { - async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine, _player: &crate::matches::generic::UserConnection, _others: &[oj_rc_core::persist::user::PlayerDescriptor]) -> bool { + async fn on_player_join(&self, _generic: &crate::matches::GenericGamemodeEngine, _conn: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool { true } - async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine, _player: &crate::matches::generic::UserConnection) -> bool { + async fn on_player_end(&self, generic: &crate::matches::GenericGamemodeEngine, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> bool { if generic.is_game_done() { return true; } if let Some(winning_team) = PlayerTracker::single_remaining_team(generic).await { + log::info!("All players except those on team {} have disconnected, ending game {} early", winning_team, generic.game_guid()); self.do_win(WinReason::OutOfPlayers, generic, winning_team).await; } true @@ -334,7 +336,7 @@ impl CustomGameLogic for TeamDeathMatchLogic { true } - async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine, _player: &crate::matches::generic::UserConnection) -> Vec { + async fn extra_sync_events(&self, generic: &crate::matches::GenericGamemodeEngine, _connection: &crate::matches::generic::UserConnection, _player: &crate::matches::generic::UserDescriptor) -> Vec { vec![ crate::matches::RlnlPacket { event: rlnl::event_code::NetworkEvent::GameModeSettings, @@ -355,8 +357,9 @@ impl CustomGameLogic for TeamDeathMatchLogic { let read_lock = generic.users.read().await; let game_end = game_start + generic.game_duration; let mut senders = Vec::with_capacity(read_lock.len()); - for conn in read_lock.values() { - senders.push((conn.connection.clone(), conn.state.clone())); + for (player_id, conn) in read_lock.iter() { + let state = generic.user_descriptor(*player_id).unwrap().state.clone(); + senders.push((conn.connection.clone(), state)); } drop(read_lock); /*let end_packets = vec![ @@ -427,7 +430,7 @@ impl CustomGameLogic for TeamDeathMatchLogic { } }, (rlnl::event_code::NetworkEvent::SurrenderVoteCast, literustlib::packet::Property::ReliableOrdered) => { - if let Some(player_id) = generic.user_key_by_user_id(user_id).await { + if let Some(player_id) = generic.user_key_by_user_id(user_id) { if let Some(&team) = self.player_tracking.teams.get(&player_id) { let maybe_vote = ::downcast_ref::(data.as_ref()); if let Some(vote) = maybe_vote {