mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add basic server-only player harness
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2785,6 +2785,7 @@ dependencies = [
|
||||
"log",
|
||||
"num-quaternion",
|
||||
"oj_rc_core",
|
||||
"rand 0.9.0",
|
||||
"rlnl",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -489,6 +489,7 @@ fn default_multiplayer() -> super::MultiplayerConfig {
|
||||
players_per_game: 2,
|
||||
enabled: true,
|
||||
network: super::multiplayer::default_net_conf(),
|
||||
fakes: super::multiplayer::default_fake_users(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -418,4 +418,13 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
|
||||
wiki_url: self.settings.server.wiki_url.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
implementation: player.implementation.clone().to_config(),
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ mod cubes_json;
|
||||
pub use cubes_json::CubeConfig;
|
||||
|
||||
mod traits;
|
||||
pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode, Point, Sphere, MapConfig, LinksConfig};
|
||||
pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode, Point, Sphere, MapConfig, LinksConfig, FakePlayer, ClientEmulator};
|
||||
|
||||
pub type ConfigImpl = CubeConfig;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ pub trait ConfigProvider<C: Clone> {
|
||||
fn network_config(&self) -> crate::persist::NetworkConf;
|
||||
fn maps(&self) -> std::collections::HashMap<GameMap, MapConfig>;
|
||||
fn url_links(&self) -> LinksConfig;
|
||||
fn fake_players(&self) -> Vec<FakePlayer>;
|
||||
}
|
||||
|
||||
pub struct CompleteCampaignProvider {
|
||||
@@ -365,3 +366,16 @@ pub struct LinksConfig {
|
||||
pub support_url: String,
|
||||
pub wiki_url: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FakePlayer {
|
||||
pub public_id: String,
|
||||
pub display_name: String,
|
||||
pub team: u8,
|
||||
pub implementation: ClientEmulator,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ClientEmulator {
|
||||
Experiment,
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ pub struct MultiplayerConfig {
|
||||
pub enabled: bool,
|
||||
#[serde(default = "default_net_conf")]
|
||||
pub network: NetworkConf,
|
||||
#[serde(default = "default_fake_users")]
|
||||
pub fakes: Vec<FakePlayerConf>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
@@ -46,3 +48,38 @@ pub(super) fn default_net_conf() -> NetworkConf {
|
||||
max_delay_for_disconnect_ms: 1000,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct FakePlayerConf {
|
||||
pub public_id: String,
|
||||
pub display_name: String,
|
||||
pub team: u8,
|
||||
#[serde(flatten)]
|
||||
pub implementation: ClientEmulation,
|
||||
}
|
||||
|
||||
pub(super) fn default_fake_users() -> Vec<FakePlayerConf> {
|
||||
//Vec::default()
|
||||
vec![
|
||||
FakePlayerConf {
|
||||
public_id: "ServerExperiment01".to_owned(),
|
||||
display_name: "Server".to_owned(),
|
||||
team: 1,
|
||||
implementation: ClientEmulation::Experimental,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(tag = "impl")]
|
||||
pub enum ClientEmulation {
|
||||
Experimental,
|
||||
}
|
||||
|
||||
impl ClientEmulation {
|
||||
pub(super) fn to_config(self) -> super::config::ClientEmulator {
|
||||
match self {
|
||||
Self::Experimental => super::config::ClientEmulator::Experiment,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::persist::config::ConfigProvider;
|
||||
pub struct AccountProvider {
|
||||
cubes: std::sync::Arc<Vec<u32>>,
|
||||
garage_upgrades: std::sync::Arc<crate::persist::config::GarageUpgrades>,
|
||||
fake_players: std::sync::Arc<Vec<crate::persist::config::FakePlayer>>,
|
||||
auto_signups: bool,
|
||||
cdn: std::sync::Arc<String>,
|
||||
secret: std::sync::Arc<Vec<u8>>,
|
||||
@@ -22,6 +23,7 @@ impl AccountProvider {
|
||||
Ok(Self {
|
||||
cubes: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::ids(conf)),
|
||||
garage_upgrades: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::garage_upgrades(conf)),
|
||||
fake_players: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::fake_players(conf)),
|
||||
auto_signups: server_settings.auto_signup,
|
||||
cdn: std::sync::Arc::new(server_settings.cdn_url),
|
||||
secret: std::sync::Arc::new(std::fs::read(&token_path)?),
|
||||
@@ -102,6 +104,7 @@ impl <C: Clone> super::UserProvider<C> for AccountProvider {
|
||||
perms: user_perms,
|
||||
cubes: self.cubes.clone(),
|
||||
garage_upgrades: self.garage_upgrades.clone(),
|
||||
fake_players: self.fake_players.clone(),
|
||||
cdn: self.cdn.clone(),
|
||||
db: self.db.clone(),
|
||||
secret: self.secret.clone(),
|
||||
@@ -137,6 +140,7 @@ impl <C: Clone> super::UserProvider<C> for AccountProvider {
|
||||
perms: user_perms,
|
||||
cubes: self.cubes.clone(),
|
||||
garage_upgrades: self.garage_upgrades.clone(),
|
||||
fake_players: self.fake_players.clone(),
|
||||
cdn: self.cdn.clone(),
|
||||
db: self.db.clone(),
|
||||
secret: self.secret.clone(),
|
||||
@@ -298,6 +302,7 @@ pub(super) struct UserData {
|
||||
pub(super) perms: oj_rc_database::schema::permissions::Model,
|
||||
pub(super) cubes: std::sync::Arc<Vec<u32>>,
|
||||
pub(super) garage_upgrades: std::sync::Arc<crate::persist::config::GarageUpgrades>,
|
||||
pub(super) fake_players: std::sync::Arc<Vec<crate::persist::config::FakePlayer>>,
|
||||
pub(super) cdn: std::sync::Arc<String>,
|
||||
pub(super) db: std::sync::Arc<oj_rc_database::Database>,
|
||||
pub(super) secret: std::sync::Arc<Vec<u8>>,
|
||||
|
||||
@@ -17,7 +17,7 @@ impl super::LobbyUser for UserData {
|
||||
})
|
||||
}
|
||||
|
||||
async fn start_game(&self, game: super::GameDescriptor, players: Vec<super::PlayerLobbyDescriptor>) -> Result<super::FakePlayers, polariton_server::operations::SimpleOpError> {
|
||||
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> {
|
||||
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 +32,7 @@ impl super::LobbyUser for UserData {
|
||||
oj_rc_database::schema::multiplayer_game::GameType::Standard
|
||||
};
|
||||
|
||||
let fake_players = self.generate_fake_players_data(guid).await;
|
||||
let fake_players = self.generate_fake_players_data(guid, cpu_counter, weapon_lister).await;
|
||||
|
||||
let game_dbo = oj_rc_database::schema::multiplayer_game::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
use super::account_json::UserData;
|
||||
|
||||
impl UserData {
|
||||
pub(super) async fn generate_fake_players_data(&self, _guid: i64) -> Vec<crate::data::player_data::PlayerData> {
|
||||
vec![
|
||||
crate::data::player_data::PlayerData {
|
||||
name: "FakeUser".to_owned(),
|
||||
display_name: "Server".to_owned(),
|
||||
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: "Very bad but very good".to_owned(),
|
||||
robot_name: "fake".to_owned(),
|
||||
robot_map: crate::persist::VALID_ROBOT.into(),
|
||||
group: None,
|
||||
team: 2,
|
||||
team: fake.team as _,
|
||||
has_premium: true,
|
||||
robot_uuid: "1234_1234".to_owned(),
|
||||
cpu: 42,
|
||||
cpu: cpu_counter.calculate_cpu(&mut std::io::Cursor::new(crate::persist::VALID_ROBOT)).total as _,
|
||||
avatar_id: Some(0),
|
||||
weapon_order: vec![0,0,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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ 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>) -> Result<FakePlayers, 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>;
|
||||
}
|
||||
|
||||
pub struct FakePlayers {
|
||||
|
||||
@@ -39,11 +39,12 @@ pub struct QueueHandler {
|
||||
hostport: u16,
|
||||
network_conf: crate::data::network::NetworkConfigData,
|
||||
cpu_counter: std::sync::Arc<oj_rc_core::cubes::CpuListParser>,
|
||||
weapon_guesser: std::sync::Arc<oj_rc_core::cubes::WeaponListParser>,
|
||||
change_strategy: GamemodeChangeStrategy,
|
||||
}
|
||||
|
||||
impl QueueHandler {
|
||||
pub fn new(conf: &oj_rc_core::ConfigImpl, game_host: &str, cpu_counter: std::sync::Arc<oj_rc_core::cubes::CpuListParser>) -> Self {
|
||||
pub fn new(conf: &oj_rc_core::ConfigImpl, game_host: &str, cpu_counter: std::sync::Arc<oj_rc_core::cubes::CpuListParser>, weapon_guesser: std::sync::Arc<oj_rc_core::cubes::WeaponListParser>,) -> 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 {
|
||||
hostport: port_str.parse().expect("Invalid redirect port"),
|
||||
network_conf: crate::data::network::NetworkConfigData::from_conf(oj_rc_core::ConfigProvider::<()>::network_config(conf)),
|
||||
cpu_counter,
|
||||
weapon_guesser,
|
||||
change_strategy: GamemodeChangeStrategy::from_core(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::server_config(conf).queue_mode),
|
||||
}
|
||||
}
|
||||
@@ -81,7 +83,7 @@ impl QueueHandler {
|
||||
is_custom: false,
|
||||
is_complete: false,
|
||||
};
|
||||
match user.start_game(game_desc, player_descs).await {
|
||||
match user.start_game(game_desc, player_descs, &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 enter_battle_ev = crate::events::battle_enter::BattleEnter {
|
||||
|
||||
@@ -31,7 +31,7 @@ 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 parsers = oj_rc_core::cubes::CubeParsers::new(&config);
|
||||
let queue = std::sync::Arc::new(QueueHandler::new(&config, &args.redirect, parsers.cpu_counter()));
|
||||
let queue = std::sync::Arc::new(QueueHandler::new(&config, &args.redirect, parsers.cpu_counter(), parsers.weapon_order()));
|
||||
|
||||
let init_ctx = InitConfig {
|
||||
config,
|
||||
|
||||
@@ -19,6 +19,7 @@ byteserde = "0.6.2"
|
||||
chrono.workspace = true
|
||||
atomic_float = "1.1"
|
||||
num-quaternion.workspace = true
|
||||
rand.workspace = true
|
||||
|
||||
#literustlib_server = { version = "0.1", path = "../../LiteRustLib/server" }
|
||||
#literustlib = { version = "0.1", path = "../../LiteRustLib" }
|
||||
|
||||
@@ -3,6 +3,7 @@ pub struct GameMatches {
|
||||
routing: std::collections::HashMap<i32, String>, // user id to game guid
|
||||
mode_configs: oj_rc_core::data::game_mode::GameModeConfigs,
|
||||
map_configs: std::collections::HashMap<String, oj_rc_core::persist::config::MapConfig>,
|
||||
fake_players: Vec<oj_rc_core::persist::config::FakePlayer>,
|
||||
}
|
||||
|
||||
impl GameMatches {
|
||||
@@ -15,6 +16,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: <oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::fake_players(conf),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +26,26 @@ impl GameMatches {
|
||||
tx
|
||||
}
|
||||
|
||||
fn build_player_emulator(&self, emu: oj_rc_core::persist::config::ClientEmulator) -> Box<dyn super::fake::FakeUser> {
|
||||
match emu {
|
||||
oj_rc_core::persist::config::ClientEmulator::Experiment => Box::new(super::fake::ExperimentalPlayer::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_fake_players(&self, players: &[oj_rc_core::persist::user::PlayerDescriptor]) -> std::collections::HashMap<u8, Box<dyn super::fake::FakeUser>> {
|
||||
let mut fake_player_i = 0;
|
||||
let mut fakes = std::collections::HashMap::with_capacity(self.fake_players.len());
|
||||
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);
|
||||
fakes.insert(player.player_id, fake);
|
||||
fake_player_i += 1;
|
||||
}
|
||||
}
|
||||
fakes
|
||||
}
|
||||
|
||||
async fn start_new_match_engine(&self, user: &Box<dyn oj_rc_core::persist::user::MultiplayerUser + Send + Sync + 'static>, guid: &str) -> Result<tokio::sync::mpsc::Sender<super::GameMessage>, oj_rc_core::persist::user::MultiplayerError> {
|
||||
let game_info = user.game_info(guid).await?
|
||||
.ok_or_else(|| oj_rc_core::persist::user::MultiplayerError {
|
||||
@@ -41,8 +63,10 @@ impl GameMatches {
|
||||
});
|
||||
let players = user.game_players(guid).await?;
|
||||
if players.is_empty() {
|
||||
log::warn!("No players found to game {}, loading may not work correctly", guid);
|
||||
log::warn!("No players found for game {}, loading may not work correctly", 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);
|
||||
@@ -51,6 +75,7 @@ impl GameMatches {
|
||||
map_config,
|
||||
players,
|
||||
inner,
|
||||
fakes_handler,
|
||||
);
|
||||
Ok(engine.spawn())
|
||||
}
|
||||
|
||||
99
rc_multiplayer/src/matches/fake/experimental.rs
Normal file
99
rc_multiplayer/src/matches/fake/experimental.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use rand::Rng;
|
||||
use byteserde::ser_heap::ByteSerializeHeap;
|
||||
|
||||
pub struct ExperimentalPlayer {
|
||||
me: tokio::sync::RwLock<Option<oj_rc_core::persist::user::PlayerDescriptor>>,
|
||||
is_complete: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
||||
}
|
||||
|
||||
impl ExperimentalPlayer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
me: tokio::sync::RwLock::new(None),
|
||||
is_complete: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::FakeUser for ExperimentalPlayer {
|
||||
async fn on_init(&self, descriptors: &Vec<oj_rc_core::persist::user::PlayerDescriptor>, player_id: u8) {
|
||||
if let Some(my_desc) = descriptors.iter().filter(|x| x.player_id == player_id).next() {
|
||||
*self.me.write().await = Some(my_desc.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_ready(&self, real_players: &std::collections::HashMap<u8, crate::matches::generic::UserSender>) {
|
||||
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;
|
||||
tokio::task::spawn(erratic_behaviour(movement_rx, is_complete, player_id));
|
||||
}
|
||||
|
||||
async fn on_end(&self) {
|
||||
self.is_complete.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
fn random_map_coord(rng: &mut rand::rngs::ThreadRng) -> f32 {
|
||||
const CONVERSION_FACTOR: f32 = 300.0 / (i16::MAX as f32);
|
||||
let int = rng.random::<i16>();
|
||||
(int as f32) * CONVERSION_FACTOR
|
||||
}
|
||||
|
||||
fn random_height_coord(rng: &mut rand::rngs::ThreadRng) -> f32 {
|
||||
const CONVERSION_FACTOR: f32 = 50.0 / (i16::MAX as f32);
|
||||
let int = rng.random::<i16>();
|
||||
((int as f32) * CONVERSION_FACTOR) + 10.0
|
||||
}
|
||||
|
||||
async fn erratic_behaviour(send_to: Vec<crate::matches::generic::UserSender>, is_complete: std::sync::Arc<std::sync::atomic::AtomicBool>, player_id: u8) {
|
||||
const SLEEP_PERIOD: std::time::Duration = std::time::Duration::from_secs(1);
|
||||
let mut fake_timestamp = 42.0;
|
||||
while !is_complete.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
let (pos_x, pos_y, pos_z) = {
|
||||
let mut rng = rand::rng();
|
||||
let pos_x: f32 = random_map_coord(&mut rng);
|
||||
let pos_y: f32 = random_height_coord(&mut rng);
|
||||
let pos_z: f32 = random_map_coord(&mut rng);
|
||||
(pos_x, pos_y, pos_z)
|
||||
};
|
||||
let motion = rlnl::machine_motion::MachineMotion {
|
||||
last_sent_seconds_a: 1.0,
|
||||
last_sent_seconds_b: 2.0,
|
||||
timestamp: fake_timestamp,
|
||||
player_id,
|
||||
target_point: rlnl::types::CompressedVec3::from((50.0, 50.0, 50.0)),
|
||||
rb_state: rlnl::machine_motion::RigidBodyState {
|
||||
rb_pos_rot: rlnl::types::PosQuatPair {
|
||||
pos: rlnl::types::CompressedVec3::from((pos_x, pos_y, pos_z)),
|
||||
rot: rlnl::types::CompressedQuat { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
angular_velocity: rlnl::types::CompressedVec3::from((0.0, 0.0, 0.0)),
|
||||
center_of_mass: rlnl::types::CompressedVec3::from((1.0, 1.0, 1.0)),
|
||||
},
|
||||
};
|
||||
let mut ser = byteserde::ser_heap::ByteSerializerHeap::default();
|
||||
if let Err(e) = motion.byte_serialize_heap(&mut ser) {
|
||||
log::error!("Failed to serialize motion data from experimental fake player: {}", e);
|
||||
} else {
|
||||
let motion_data = bytes::Bytes::copy_from_slice(ser.as_slice());
|
||||
for conn in send_to.iter() {
|
||||
send_motion_data_to(conn, motion_data.clone()).await;
|
||||
}
|
||||
log::info!("Moved experimental bot to ({}, {}, {})", pos_x, pos_y, pos_z);
|
||||
}
|
||||
fake_timestamp += 1.0;
|
||||
tokio::time::sleep(SLEEP_PERIOD).await;
|
||||
}
|
||||
is_complete.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
async fn send_motion_data_to(to: &crate::matches::generic::UserSender, motion: bytes::Bytes) {
|
||||
crate::events::log_lnl_send_failure(to.sender.send_data(crate::handler::EventData {
|
||||
message_ty: crate::data::MessageType::RobotMotion,
|
||||
variant: 0,
|
||||
data_size: motion.len() as _,
|
||||
data: motion,
|
||||
}, literustlib::packet::Property::Unreliable, &to.connection).await);
|
||||
}
|
||||
63
rc_multiplayer/src/matches/fake/handler.rs
Normal file
63
rc_multiplayer/src/matches/fake/handler.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
enum Message {
|
||||
Ready {
|
||||
real_players: std::collections::HashMap<u8, crate::matches::generic::UserSender>,
|
||||
},
|
||||
Stop,
|
||||
}
|
||||
|
||||
pub struct Handler {
|
||||
tx: tokio::sync::mpsc::UnboundedSender<Message>,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub async fn start(fakes: std::collections::HashMap<u8, Box<dyn super::FakeUser + 'static>>, descriptors: Vec<oj_rc_core::persist::user::PlayerDescriptor>) -> Self {
|
||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
tokio::task::spawn(handler_loop(rx, fakes, descriptors));
|
||||
Self {
|
||||
tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_ready(&self, real_players: std::collections::HashMap<u8, crate::matches::generic::UserSender>) {
|
||||
Self::log_failure(self.tx.send(Message::Ready { real_players }));
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
Self::log_failure(self.tx.send(Message::Stop));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn log_failure<T>(res: Result<(), tokio::sync::mpsc::error::SendError<T>>) {
|
||||
if let Err(_e) = res {
|
||||
log::warn!("Failed to send message to fake user handler thread");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handler_loop(mut rx: tokio::sync::mpsc::UnboundedReceiver<Message>, players: std::collections::HashMap<u8, Box<dyn super::FakeUser + 'static>>, descriptors: Vec<oj_rc_core::persist::user::PlayerDescriptor>) {
|
||||
for player in descriptors.iter() {
|
||||
if player.user_id.is_none() {
|
||||
let player_id = player.player_id;
|
||||
if let Some(fake_player) = players.get(&player_id) {
|
||||
fake_player.on_init(&descriptors, player_id).await;
|
||||
} else {
|
||||
log::warn!("No fake user implementation for player {}", player_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Some(msg) = rx.recv().await {
|
||||
match msg {
|
||||
Message::Ready { real_players } => {
|
||||
for fake in players.values() {
|
||||
fake.on_ready(&real_players).await;
|
||||
}
|
||||
},
|
||||
Message::Stop => {
|
||||
for fake in players.values() {
|
||||
fake.on_end().await;
|
||||
}
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
8
rc_multiplayer/src/matches/fake/mod.rs
Normal file
8
rc_multiplayer/src/matches/fake/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
mod handler;
|
||||
pub use handler::Handler;
|
||||
|
||||
mod traits;
|
||||
pub use traits::FakeUser;
|
||||
|
||||
mod experimental;
|
||||
pub use experimental::ExperimentalPlayer;
|
||||
7
rc_multiplayer/src/matches/fake/traits.rs
Normal file
7
rc_multiplayer/src/matches/fake/traits.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[async_trait::async_trait]
|
||||
pub trait FakeUser: Send + Sync {
|
||||
async fn on_init(&self, descriptors: &Vec<oj_rc_core::persist::user::PlayerDescriptor>, player_id: u8);
|
||||
async fn on_ready(&self, real_players: &std::collections::HashMap<u8, crate::matches::generic::UserSender>);
|
||||
//fn on_damage(&self, data: &rlnl::events::ingame::DestroyCubesFull);
|
||||
async fn on_end(&self);
|
||||
}
|
||||
@@ -194,6 +194,7 @@ pub(super) struct GenericGamemodeEngine<L: super::CustomGameLogic> {
|
||||
pub players_info: std::sync::Arc<Vec<oj_rc_core::persist::user::PlayerDescriptor>>,
|
||||
pub custom_logic_handler: L,
|
||||
pub fake_users: std::collections::HashMap<u8, FakeUser>,
|
||||
pub fakes_handler: super::fake::Handler,
|
||||
}
|
||||
|
||||
impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
@@ -204,7 +205,8 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
game: oj_rc_core::persist::user::GameDescriptor,
|
||||
map: oj_rc_core::persist::config::MapConfig,
|
||||
players: Vec<oj_rc_core::persist::user::PlayerDescriptor>,
|
||||
custom: L
|
||||
custom: L,
|
||||
fakes_handler: super::fake::Handler,
|
||||
) -> Self {
|
||||
|
||||
let fake_users = players.iter()
|
||||
@@ -221,6 +223,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
players_info: std::sync::Arc::new(players),
|
||||
custom_logic_handler: custom,
|
||||
fake_users,
|
||||
fakes_handler,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,7 +473,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
}
|
||||
for fake in self.fake_users.values() {
|
||||
let event = rlnl::events::loading::LoadingProgress {
|
||||
user_name: rlnl::types::BinaryWriterString("FakeUser".to_owned()),
|
||||
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(
|
||||
@@ -555,6 +558,11 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
let player_count = self.players_info.iter().filter(|x| x.user_id.is_some()).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(
|
||||
self.users.read().await.iter()
|
||||
.map(|(id, real_player)| (*id, real_player.connection.clone()))
|
||||
.collect()
|
||||
);
|
||||
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();
|
||||
@@ -812,6 +820,7 @@ impl <L: super::CustomGameLogic> GenericGamemodeEngine<L> {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.fakes_handler.stop();
|
||||
log::info!("Game {} has exited", self.game_guid());
|
||||
}
|
||||
|
||||
|
||||
@@ -16,4 +16,6 @@ pub mod modes;
|
||||
|
||||
mod timer;
|
||||
|
||||
pub(self) mod fake;
|
||||
|
||||
pub const CHANNEL_BOUND: usize = 16;
|
||||
|
||||
Reference in New Issue
Block a user