1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Add basic client AI support

This commit is contained in:
NG (Graham)
2025-09-27 12:36:49 -04:00
parent 91fc2db868
commit e55bd6d288
29 changed files with 652 additions and 317 deletions

View File

@@ -1,6 +1,6 @@
use polariton::operation::Typed;
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct PlayerData {
pub name: String,
pub display_name: String,

View File

@@ -453,9 +453,8 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
fn fake_players(&self) -> Vec<super::FakePlayer> {
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()
}

View File

@@ -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)]

View File

@@ -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<FakePlayerConf> {
//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<FakePlayerConf> {
#[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,
}
}
}

View File

@@ -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<super::ResolvedVehicle, polariton_server::operations::SimpleOpError> {
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![

View File

@@ -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<super::PlayerLobbyDescriptor>, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result<super::FakePlayers, polariton_server::operations::SimpleOpError> {
async fn start_game(&self, game: super::GameDescriptor, players: Vec<super::PlayerLobbyDescriptor>, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result<super::FakePlayers, polariton_server::operations::SimpleOpError> {
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)),
}
})
)

View File

@@ -1,30 +1,45 @@
use super::account_json::UserData;
fn db_to_impl(client_emu: &oj_rc_database::schema::multiplayer_game_player::ClientType) -> Option<crate::persist::config::ClientEmulator> {
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<crate::data::player_data::PlayerData> {
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<Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>, 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 {

View File

@@ -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<crate::data::player_data::PlayerData, polariton_server::operations::SimpleOpError>;
async fn start_game(&self, game: GameDescriptor, players: Vec<PlayerLobbyDescriptor>, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result<FakePlayers, polariton_server::operations::SimpleOpError>;
async fn start_game(&self, game: GameDescriptor, players: Vec<PlayerLobbyDescriptor>, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, cpu_counter: &crate::cubes::CpuListParser, weapon_lister: &crate::cubes::WeaponListParser) -> Result<FakePlayers, polariton_server::operations::SimpleOpError>;
}
pub struct FakePlayers {
pub players: Vec<crate::data::player_data::PlayerData>
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<crate::persist::config::ClientEmulator>,
}
#[derive(Debug)]