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