mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Factor in platoons during team assignment #88
This commit is contained in:
@@ -2,25 +2,6 @@ use serde::{Serialize, Deserialize};
|
|||||||
|
|
||||||
use super::account_json::UserData;
|
use super::account_json::UserData;
|
||||||
|
|
||||||
pub enum TeamChooser {
|
|
||||||
/// Alternating between team 0 and team 1
|
|
||||||
Alternating,
|
|
||||||
/// All players will be put on the specified team
|
|
||||||
AllOn(u8),
|
|
||||||
/// Each player will be put on their own team (like in Pit mode)
|
|
||||||
OnePer,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TeamChooser {
|
|
||||||
pub fn team(&self, index: usize) -> i32 {
|
|
||||||
match self {
|
|
||||||
Self::Alternating => (index % 2) as i32,
|
|
||||||
Self::AllOn(team) => *team as i32,
|
|
||||||
Self::OnePer => index as i32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fake_impl_to_db(client_emu: &crate::persist::config::ClientEmulator) -> oj_rc_database::schema::multiplayer_game_player::ClientType {
|
fn fake_impl_to_db(client_emu: &crate::persist::config::ClientEmulator) -> oj_rc_database::schema::multiplayer_game_player::ClientType {
|
||||||
match client_emu {
|
match client_emu {
|
||||||
crate::persist::config::ClientEmulator::Experiment => oj_rc_database::schema::multiplayer_game_player::ClientType::ServerExperimental,
|
crate::persist::config::ClientEmulator::Experiment => oj_rc_database::schema::multiplayer_game_player::ClientType::ServerExperimental,
|
||||||
@@ -45,10 +26,10 @@ impl super::LobbyUser for UserData {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn team_chooser(&self, game: &super::GameDescriptor) -> TeamChooser {
|
async fn team_chooser(&self, game: &super::GameDescriptor) -> super::StandardTeamChooser {
|
||||||
match game.mode {
|
match game.mode {
|
||||||
crate::data::game_mode::GameMode::Pit => TeamChooser::OnePer,
|
crate::data::game_mode::GameMode::Pit => super::StandardTeamChooser::OnePer,
|
||||||
_ => TeamChooser::Alternating,
|
_ => super::StandardTeamChooser::alternating(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +40,7 @@ impl super::LobbyUser for UserData {
|
|||||||
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
|
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
|
||||||
cpu_counter: &crate::cubes::CpuListParser,
|
cpu_counter: &crate::cubes::CpuListParser,
|
||||||
weapon_lister: &crate::cubes::WeaponListParser,
|
weapon_lister: &crate::cubes::WeaponListParser,
|
||||||
chooser: &TeamChooser,
|
chooser: &dyn super::TeamChooser,
|
||||||
missing_players: usize,
|
missing_players: usize,
|
||||||
) -> Result<super::FakePlayers, polariton_server::operations::SimpleOpError> {
|
) -> Result<super::FakePlayers, polariton_server::operations::SimpleOpError> {
|
||||||
let now = chrono::Utc::now().timestamp();
|
let now = chrono::Utc::now().timestamp();
|
||||||
@@ -76,8 +57,8 @@ impl super::LobbyUser for UserData {
|
|||||||
oj_rc_database::schema::multiplayer_game::GameType::Standard
|
oj_rc_database::schema::multiplayer_game::GameType::Standard
|
||||||
};
|
};
|
||||||
|
|
||||||
let forced_fake_players = self.generate_forced_fake_players_data(guid, &players, factory, cpu_counter, weapon_lister, chooser).await?;
|
let forced_fake_players = self.generate_forced_fake_players_data(&game.guid, &players, factory, cpu_counter, weapon_lister, chooser).await?;
|
||||||
let filler_players = self.generate_filler_players_data(guid, &players, factory, cpu_counter, weapon_lister, chooser, missing_players).await?;
|
let filler_players = self.generate_filler_players_data(&game.guid, factory, cpu_counter, weapon_lister, chooser, missing_players, players.len() + forced_fake_players.len()).await?;
|
||||||
|
|
||||||
let game_dbo = oj_rc_database::schema::multiplayer_game::ActiveModel {
|
let game_dbo = oj_rc_database::schema::multiplayer_game::ActiveModel {
|
||||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||||
@@ -102,6 +83,17 @@ impl super::LobbyUser for UserData {
|
|||||||
let players_len = players.len();
|
let players_len = players.len();
|
||||||
let forced_fake_players_len = forced_fake_players.len();
|
let forced_fake_players_len = forced_fake_players.len();
|
||||||
|
|
||||||
|
let mut group_map = std::collections::HashMap::new();
|
||||||
|
let mut group_num = 1;
|
||||||
|
for player in players.iter() {
|
||||||
|
if let Some(group) = &player.group {
|
||||||
|
if !group_map.contains_key(group) {
|
||||||
|
group_map.insert(group.to_owned(), group_num);
|
||||||
|
group_num += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let players: Vec<oj_rc_database::schema::multiplayer_game_player::ActiveModel> = players.into_iter()
|
let players: Vec<oj_rc_database::schema::multiplayer_game_player::ActiveModel> = players.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, player)| {
|
.map(|(i, player)| {
|
||||||
@@ -112,7 +104,7 @@ impl super::LobbyUser for UserData {
|
|||||||
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||||
player_id: oj_rc_database::sea_orm::ActiveValue::Set((i as u8) as _),
|
player_id: oj_rc_database::sea_orm::ActiveValue::Set((i as u8) as _),
|
||||||
team: oj_rc_database::sea_orm::ActiveValue::Set(player.team),
|
team: oj_rc_database::sea_orm::ActiveValue::Set(player.team),
|
||||||
group: oj_rc_database::sea_orm::ActiveValue::Set(player.group),
|
group: oj_rc_database::sea_orm::ActiveValue::Set(player.group.as_ref().and_then(|x| group_map.get(x)).copied()),
|
||||||
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
public_id: oj_rc_database::sea_orm::ActiveValue::Set(player.public_id),
|
public_id: oj_rc_database::sea_orm::ActiveValue::Set(player.public_id),
|
||||||
display_name: oj_rc_database::sea_orm::ActiveValue::Set(player.display_name),
|
display_name: oj_rc_database::sea_orm::ActiveValue::Set(player.display_name),
|
||||||
@@ -220,7 +212,7 @@ impl super::LobbyUser for UserData {
|
|||||||
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||||
player_id: oj_rc_database::sea_orm::ActiveValue::Set((i as u8) as _),
|
player_id: oj_rc_database::sea_orm::ActiveValue::Set((i as u8) as _),
|
||||||
team: oj_rc_database::sea_orm::ActiveValue::Set(player.team),
|
team: oj_rc_database::sea_orm::ActiveValue::Set(player.team),
|
||||||
group: oj_rc_database::sea_orm::ActiveValue::Set(player.group),
|
group: oj_rc_database::sea_orm::ActiveValue::Set(None),
|
||||||
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
public_id: oj_rc_database::sea_orm::ActiveValue::Set(player.public_id),
|
public_id: oj_rc_database::sea_orm::ActiveValue::Set(player.public_id),
|
||||||
display_name: oj_rc_database::sea_orm::ActiveValue::Set(player.display_name),
|
display_name: oj_rc_database::sea_orm::ActiveValue::Set(player.display_name),
|
||||||
|
|||||||
@@ -18,13 +18,14 @@ pub use intercom::generate_token as generate_intercom_token;
|
|||||||
|
|
||||||
mod multiplayer;
|
mod multiplayer;
|
||||||
mod lobby;
|
mod lobby;
|
||||||
pub use lobby::TeamChooser;
|
|
||||||
mod common;
|
mod common;
|
||||||
mod chat;
|
mod chat;
|
||||||
mod social;
|
mod social;
|
||||||
mod singleplayer;
|
mod singleplayer;
|
||||||
mod factory;
|
mod factory;
|
||||||
mod userless;
|
mod userless;
|
||||||
|
mod team;
|
||||||
|
pub use team::{TeamChooser, StandardTeamChooser};
|
||||||
|
|
||||||
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";
|
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";
|
||||||
|
|
||||||
|
|||||||
@@ -12,18 +12,24 @@ impl UserData {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(super) async fn generate_fake_players_data(
|
pub(super) async fn generate_fake_players_data(
|
||||||
&self,
|
&self,
|
||||||
_guid: i64,
|
guid_str: &str,
|
||||||
real_players: &[super::PlayerLobbyDescriptor],
|
|
||||||
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
|
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
|
||||||
cpu_counter: &crate::cubes::CpuListParser,
|
cpu_counter: &crate::cubes::CpuListParser,
|
||||||
weapon_lister: &crate::cubes::WeaponListParser,
|
weapon_lister: &crate::cubes::WeaponListParser,
|
||||||
chooser: &super::TeamChooser,
|
chooser: &dyn super::TeamChooser,
|
||||||
fake_players: &[crate::persist::config::FakePlayer],
|
fake_players: &[crate::persist::config::FakePlayer],
|
||||||
|
total_offset: usize,
|
||||||
) -> Result<Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>, polariton_server::operations::SimpleOpError> {
|
) -> Result<Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>, polariton_server::operations::SimpleOpError> {
|
||||||
let mut fakes = Vec::with_capacity(fake_players.len());
|
let mut fakes = Vec::with_capacity(fake_players.len());
|
||||||
let mut fake_i = real_players.len();
|
for (i, fake) in fake_players.iter().enumerate() {
|
||||||
for fake in fake_players.iter() {
|
|
||||||
let vehicle = self.resolve_vehicle(&fake.vehicle, factory, weapon_lister, cpu_counter).await?;
|
let vehicle = self.resolve_vehicle(&fake.vehicle, factory, weapon_lister, cpu_counter).await?;
|
||||||
|
let fake_lobby_desc = super::PlayerLobbyDescriptor {
|
||||||
|
user_id: -1,
|
||||||
|
team: -1,
|
||||||
|
public_id: fake.vehicle.username.clone(),
|
||||||
|
display_name: fake.vehicle.username.clone(),
|
||||||
|
group: None,
|
||||||
|
};
|
||||||
let out = (
|
let out = (
|
||||||
crate::data::player_data::PlayerData {
|
crate::data::player_data::PlayerData {
|
||||||
name: fake.vehicle.username.clone(),
|
name: fake.vehicle.username.clone(),
|
||||||
@@ -33,12 +39,7 @@ impl UserData {
|
|||||||
robot_name: vehicle.robot_name,
|
robot_name: vehicle.robot_name,
|
||||||
robot_map: vehicle.robot_map,
|
robot_map: vehicle.robot_map,
|
||||||
group: None,
|
group: None,
|
||||||
team: fake.team.map(|t| t as i32)
|
team: chooser.choose_team(guid_str, total_offset + i, &fake_lobby_desc),
|
||||||
.unwrap_or_else(|| {
|
|
||||||
let assigned_team = chooser.team(fake_i);
|
|
||||||
fake_i += 1;
|
|
||||||
assigned_team
|
|
||||||
}),
|
|
||||||
has_premium: true,
|
has_premium: true,
|
||||||
robot_uuid: vehicle.robot_uuid,
|
robot_uuid: vehicle.robot_uuid,
|
||||||
cpu: vehicle.cpu,
|
cpu: vehicle.cpu,
|
||||||
@@ -61,43 +62,44 @@ impl UserData {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(super) async fn generate_forced_fake_players_data(
|
pub(super) async fn generate_forced_fake_players_data(
|
||||||
&self,
|
&self,
|
||||||
guid: i64,
|
guid_str: &str,
|
||||||
real_players: &[super::PlayerLobbyDescriptor],
|
real_players: &[super::PlayerLobbyDescriptor],
|
||||||
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
|
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
|
||||||
cpu_counter: &crate::cubes::CpuListParser,
|
cpu_counter: &crate::cubes::CpuListParser,
|
||||||
weapon_lister: &crate::cubes::WeaponListParser,
|
weapon_lister: &crate::cubes::WeaponListParser,
|
||||||
chooser: &super::TeamChooser,
|
chooser: &dyn super::TeamChooser,
|
||||||
) -> Result<Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>, polariton_server::operations::SimpleOpError> {
|
) -> Result<Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>, polariton_server::operations::SimpleOpError> {
|
||||||
|
let offset = real_players.len();
|
||||||
self.generate_fake_players_data(
|
self.generate_fake_players_data(
|
||||||
guid,
|
guid_str,
|
||||||
real_players,
|
|
||||||
factory,
|
factory,
|
||||||
cpu_counter,
|
cpu_counter,
|
||||||
weapon_lister,
|
weapon_lister,
|
||||||
chooser,
|
chooser,
|
||||||
&self.fake_players,
|
&self.fake_players,
|
||||||
|
offset,
|
||||||
).await
|
).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(super) async fn generate_filler_players_data(
|
pub(super) async fn generate_filler_players_data(
|
||||||
&self,
|
&self,
|
||||||
guid: i64,
|
guid_str: &str,
|
||||||
real_players: &[super::PlayerLobbyDescriptor],
|
|
||||||
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
|
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
|
||||||
cpu_counter: &crate::cubes::CpuListParser,
|
cpu_counter: &crate::cubes::CpuListParser,
|
||||||
weapon_lister: &crate::cubes::WeaponListParser,
|
weapon_lister: &crate::cubes::WeaponListParser,
|
||||||
chooser: &super::TeamChooser,
|
chooser: &dyn super::TeamChooser,
|
||||||
count: usize,
|
count: usize,
|
||||||
|
total_offset: usize,
|
||||||
) -> Result<Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>, polariton_server::operations::SimpleOpError> {
|
) -> Result<Vec<(crate::data::player_data::PlayerData, crate::persist::config::ClientEmulator)>, polariton_server::operations::SimpleOpError> {
|
||||||
self.generate_fake_players_data(
|
self.generate_fake_players_data(
|
||||||
guid,
|
guid_str,
|
||||||
real_players,
|
|
||||||
factory,
|
factory,
|
||||||
cpu_counter,
|
cpu_counter,
|
||||||
weapon_lister,
|
weapon_lister,
|
||||||
chooser,
|
chooser,
|
||||||
&self.filler_players[0..count],
|
&self.filler_players[0..count],
|
||||||
|
total_offset
|
||||||
).await
|
).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
116
rc_core/src/persist/user/team.rs
Normal file
116
rc_core/src/persist/user/team.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
pub trait TeamChooser: Send + Sync {
|
||||||
|
fn choose_team(&self, game: &str, index: usize, player: &super::PlayerLobbyDescriptor) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum StandardTeamChooser {
|
||||||
|
/// Alternating between team 0 and team 1
|
||||||
|
Alternating(TwoTeamPartyAware),
|
||||||
|
/// All players will be put on the specified team
|
||||||
|
AllOn(u8),
|
||||||
|
/// Each player will be put on their own team (like in Pit mode)
|
||||||
|
OnePer,
|
||||||
|
//Custom(Box<dyn TeamChooser>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StandardTeamChooser {
|
||||||
|
pub fn alternating() -> Self {
|
||||||
|
Self::Alternating(TwoTeamPartyAware::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TeamChooser for StandardTeamChooser {
|
||||||
|
fn choose_team(&self, game: &str, index: usize, player: &super::PlayerLobbyDescriptor) -> i32 {
|
||||||
|
match self {
|
||||||
|
Self::Alternating(t) => t.choose_team(game, index, player),
|
||||||
|
Self::AllOn(team) => *team as i32,
|
||||||
|
Self::OnePer => index as i32,
|
||||||
|
//Self::Custom(t) => t.choose_team(game, index, player),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TwoTeamPartyAware {
|
||||||
|
tracker: std::sync::Mutex<std::collections::HashMap<String, BalanceInfo>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BalanceInfo {
|
||||||
|
overall: i64,
|
||||||
|
/// platoon ID -> team
|
||||||
|
platoons: std::collections::HashMap<String, i8>,
|
||||||
|
created_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TwoTeamPartyAware {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
tracker: std::sync::Mutex::new(std::collections::HashMap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manually remove stale team infos
|
||||||
|
fn do_cleanup(tracker: &mut std::collections::HashMap<String, BalanceInfo>) {
|
||||||
|
let max_ttl = chrono::TimeDelta::from_std(std::time::Duration::from_secs(10)).unwrap();
|
||||||
|
let now = chrono::Utc::now();
|
||||||
|
tracker.retain(|_, bal_info| (now - bal_info.created_at) < max_ttl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TeamChooser for TwoTeamPartyAware {
|
||||||
|
fn choose_team(&self, game: &str, _index: usize, player: &super::PlayerLobbyDescriptor) -> i32 {
|
||||||
|
let mut lock = self.tracker.lock().unwrap();
|
||||||
|
// the algorithm is basically:
|
||||||
|
// if player is in platoon AND platoon is known platoon:
|
||||||
|
// use platoon's team assignment
|
||||||
|
// else if overall > 0:
|
||||||
|
// choose team 1 for player, remember team assignment for all future platoon members
|
||||||
|
// overall -= 1
|
||||||
|
// else:
|
||||||
|
// choose team 0 for player, remember team assignment for all future platoon members
|
||||||
|
// overall += 1
|
||||||
|
//
|
||||||
|
// this roughly balances player count between the two teams
|
||||||
|
// while ensuring players on the same platoon are put on the same team
|
||||||
|
if let Some(bal_info) = lock.get_mut(game) {
|
||||||
|
if let Some(platoon_id) = &player.group {
|
||||||
|
if let Some(&platoon_team) = bal_info.platoons.get(platoon_id) {
|
||||||
|
if platoon_team == 0 {
|
||||||
|
bal_info.overall += 1;
|
||||||
|
} else {
|
||||||
|
bal_info.overall -= 1;
|
||||||
|
}
|
||||||
|
return platoon_team as i32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let chosen_team = if bal_info.overall > 0 {
|
||||||
|
bal_info.overall -= 1;
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
bal_info.overall += 1;
|
||||||
|
0
|
||||||
|
};
|
||||||
|
if let Some(platoon_id) = &player.group {
|
||||||
|
bal_info.platoons.insert(platoon_id.to_owned(), chosen_team);
|
||||||
|
}
|
||||||
|
return chosen_team as i32;
|
||||||
|
} else {
|
||||||
|
Self::do_cleanup(&mut lock);
|
||||||
|
let init_info = if let Some(platoon) = &player.group {
|
||||||
|
let mut platoon_teams = std::collections::HashMap::new();
|
||||||
|
platoon_teams.insert(platoon.to_owned(), 0);
|
||||||
|
BalanceInfo {
|
||||||
|
overall: 1,
|
||||||
|
platoons: platoon_teams,
|
||||||
|
created_at: chrono::Utc::now(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BalanceInfo {
|
||||||
|
overall: 1,
|
||||||
|
platoons: std::collections::HashMap::new(),
|
||||||
|
created_at: chrono::Utc::now(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
lock.insert(game.to_owned(), init_info);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -279,9 +279,9 @@ pub enum UserRole {
|
|||||||
pub trait LobbyUser {
|
pub trait LobbyUser {
|
||||||
fn user_id(&self) -> i32;
|
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 player_data(&self, cpu_counter: &crate::cubes::CpuListParser) -> Result<crate::data::player_data::PlayerData, polariton_server::operations::SimpleOpError>;
|
||||||
async fn team_chooser(&self, game: &GameDescriptor) -> super::TeamChooser;
|
async fn team_chooser(&self, game: &GameDescriptor) -> super::StandardTeamChooser;
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
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, team_chooser: &super::TeamChooser, missing_players: usize) -> 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, team_chooser: &dyn super::TeamChooser, missing_players: usize) -> Result<FakePlayers, polariton_server::operations::SimpleOpError>;
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn start_custom_game(&self, game: GameDescriptor, players: Vec<PlayerLobbyDescriptor>) -> Result<(), polariton_server::operations::SimpleOpError>;
|
async fn start_custom_game(&self, game: GameDescriptor, players: Vec<PlayerLobbyDescriptor>) -> Result<(), polariton_server::operations::SimpleOpError>;
|
||||||
}
|
}
|
||||||
@@ -337,7 +337,7 @@ pub struct PlayerLobbyDescriptor {
|
|||||||
pub team: i32,
|
pub team: i32,
|
||||||
pub public_id: String,
|
pub public_id: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
pub group: Option<i32>,
|
pub group: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::{collections::HashMap, hash::Hash};
|
use std::{collections::HashMap, hash::Hash};
|
||||||
|
|
||||||
|
use oj_rc_core::persist::user::TeamChooser;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum GamemodeChangeStrategy {
|
pub enum GamemodeChangeStrategy {
|
||||||
Upgrade, // move enqueued players into newer gamemode
|
Upgrade, // move enqueued players into newer gamemode
|
||||||
@@ -55,6 +57,41 @@ impl QueueKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Queue {
|
||||||
|
users: Vec<QueueUser>,
|
||||||
|
platoons: HashMap<String, Platoon>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Platoon {
|
||||||
|
total: u8,
|
||||||
|
members: Vec<PlatoonUser>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A QueueUser wrapper for platoon members
|
||||||
|
struct PlatoonUser(QueueUser);
|
||||||
|
|
||||||
|
impl PlatoonUser {
|
||||||
|
fn from_queue_user(qu: QueueUser) -> Self {
|
||||||
|
Self(qu)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_for_queue(&self) -> QueueUser {
|
||||||
|
QueueUser {
|
||||||
|
emitter: self.0.emitter.clone(),
|
||||||
|
player: self.0.player.clone(),
|
||||||
|
user_id: self.0.user_id,
|
||||||
|
enqueued_at: self.0.enqueued_at,
|
||||||
|
user: self.0.user.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PlatoonInfo {
|
||||||
|
pub total: u8,
|
||||||
|
pub platoon_id: String,
|
||||||
|
pub is_leader: bool,
|
||||||
|
}
|
||||||
|
|
||||||
struct QueueUser {
|
struct QueueUser {
|
||||||
emitter: polariton_server::events::EventEmitter,
|
emitter: polariton_server::events::EventEmitter,
|
||||||
player: oj_rc_core::data::player_data::PlayerData,
|
player: oj_rc_core::data::player_data::PlayerData,
|
||||||
@@ -75,7 +112,7 @@ struct CustomGameQueueUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct QueueHandler {
|
pub struct QueueHandler {
|
||||||
users_in_queue: std::sync::Arc<tokio::sync::Mutex<HashMap<QueueKey, Vec<QueueUser>>>>,
|
users_in_queue: std::sync::Arc<tokio::sync::Mutex<HashMap<QueueKey, Queue>>>,
|
||||||
users_in_custom_games_queue: std::sync::Arc<tokio::sync::Mutex<HashMap<String, CustomGameQueue>>>,
|
users_in_custom_games_queue: std::sync::Arc<tokio::sync::Mutex<HashMap<String, CustomGameQueue>>>,
|
||||||
custom_game_for_user: std::sync::Arc<tokio::sync::RwLock<HashMap<String, String>>>,
|
custom_game_for_user: std::sync::Arc<tokio::sync::RwLock<HashMap<String, String>>>,
|
||||||
users_per_game: usize,
|
users_per_game: usize,
|
||||||
@@ -131,23 +168,24 @@ impl QueueHandler {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
let now = chrono::Utc::now();
|
let now = chrono::Utc::now();
|
||||||
let mut to_start: Vec<(QueueKey, Vec<QueueUser>)> = Vec::new();
|
let mut to_start: Vec<(QueueKey, Queue)> = Vec::new();
|
||||||
let mut next_deadline: Option<chrono::DateTime<chrono::Utc>> = None;
|
let mut next_deadline: Option<chrono::DateTime<chrono::Utc>> = None;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut lock = users_in_queue.lock().await;
|
let mut lock = users_in_queue.lock().await;
|
||||||
|
|
||||||
let mut empty_keys: Vec<QueueKey> = Vec::new();
|
//let mut empty_keys: Vec<QueueKey> = Vec::new();
|
||||||
let mut expired_keys: Vec<QueueKey> = Vec::new();
|
let mut expired_keys: Vec<QueueKey> = Vec::new();
|
||||||
|
|
||||||
for (key, users) in lock.iter() {
|
for (key, q_entry) in lock.iter() {
|
||||||
if users.is_empty() {
|
if q_entry.users.is_empty() { continue; }
|
||||||
|
/*if q_entry.users.is_empty() && q_entry.platoons.is_empty() {
|
||||||
empty_keys.push(key.clone());
|
empty_keys.push(key.clone());
|
||||||
continue;
|
continue;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// first user is always oldest within a queue
|
// first user is always oldest within a queue
|
||||||
let deadline = users[0].enqueued_at + autostart_after;
|
let deadline = q_entry.users[0].enqueued_at + autostart_after;
|
||||||
if now >= deadline {
|
if now >= deadline {
|
||||||
expired_keys.push(key.clone());
|
expired_keys.push(key.clone());
|
||||||
} else {
|
} else {
|
||||||
@@ -158,23 +196,23 @@ impl QueueHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for k in empty_keys {
|
/*for k in empty_keys {
|
||||||
lock.remove(&k);
|
lock.remove(&k);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
for k in expired_keys {
|
for k in expired_keys {
|
||||||
if let Some(players) = lock.remove(&k) {
|
if let Some(q_entry) = lock.remove(&k) {
|
||||||
if !players.is_empty() {
|
if !q_entry.users.is_empty() {
|
||||||
to_start.push((k, players));
|
to_start.push((k, q_entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// start expired queues outside the lock
|
// start expired queues outside the lock
|
||||||
for (key, players) in to_start {
|
for (key, q_entry) in to_start {
|
||||||
// choose the oldest queued user's LobbyUser handle
|
// choose the oldest queued user's LobbyUser handle
|
||||||
let starter = match players.first() {
|
let starter = match q_entry.users.first() {
|
||||||
Some(p) => p.user.clone(),
|
Some(p) => p.user.clone(),
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
@@ -188,7 +226,7 @@ impl QueueHandler {
|
|||||||
weapon_guesser.clone(),
|
weapon_guesser.clone(),
|
||||||
users_per_game,
|
users_per_game,
|
||||||
key,
|
key,
|
||||||
players,
|
q_entry,
|
||||||
starter.as_ref().as_ref(),
|
starter.as_ref().as_ref(),
|
||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
@@ -208,7 +246,7 @@ impl QueueHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn enter_match(&self, key: QueueKey, players: Vec<QueueUser>, user: &(dyn oj_rc_core::persist::user::LobbyUser + Send + Sync)) {
|
async fn enter_match(&self, key: QueueKey, q_entry: Queue, user: &(dyn oj_rc_core::persist::user::LobbyUser + Send + Sync)) {
|
||||||
Self::enter_match_static(
|
Self::enter_match_static(
|
||||||
self.hostname.clone(),
|
self.hostname.clone(),
|
||||||
self.hostport,
|
self.hostport,
|
||||||
@@ -218,13 +256,13 @@ impl QueueHandler {
|
|||||||
self.weapon_guesser.clone(),
|
self.weapon_guesser.clone(),
|
||||||
self.users_per_game,
|
self.users_per_game,
|
||||||
key,
|
key,
|
||||||
players,
|
q_entry,
|
||||||
user,
|
user,
|
||||||
).await
|
).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn enter_match_static(hostname: String, hostport: u16, network_conf: crate::data::network::NetworkConfigData, factory: std::sync::Arc<oj_rc_core::factory::Factory>, cpu_counter: std::sync::Arc<oj_rc_core::cubes::CpuListParser>, weapon_guesser: std::sync::Arc<oj_rc_core::cubes::WeaponListParser>, users_per_game: usize, key: QueueKey, mut players: Vec<QueueUser>, user: &(dyn oj_rc_core::persist::user::LobbyUser + Send + Sync)) {
|
async fn enter_match_static(hostname: String, hostport: u16, network_conf: crate::data::network::NetworkConfigData, factory: std::sync::Arc<oj_rc_core::factory::Factory>, cpu_counter: std::sync::Arc<oj_rc_core::cubes::CpuListParser>, weapon_guesser: std::sync::Arc<oj_rc_core::cubes::WeaponListParser>, users_per_game: usize, key: QueueKey, mut q_entry: Queue, user: &(dyn oj_rc_core::persist::user::LobbyUser + Send + Sync)) {
|
||||||
let guid_str = key.unique_guid();
|
let guid_str = key.unique_guid();
|
||||||
let game_desc = oj_rc_core::persist::user::GameDescriptor {
|
let game_desc = oj_rc_core::persist::user::GameDescriptor {
|
||||||
guid: guid_str.clone(),
|
guid: guid_str.clone(),
|
||||||
@@ -242,22 +280,26 @@ impl QueueHandler {
|
|||||||
oj_rc_core::data::game_mode::GameMode::Pit => |i| i as i32, // each player is on a different team
|
oj_rc_core::data::game_mode::GameMode::Pit => |i| i as i32, // each player is on a different team
|
||||||
_ => |i| (i % 2) as i32, // alternate teams
|
_ => |i| (i % 2) as i32, // alternate teams
|
||||||
};*/
|
};*/
|
||||||
for (i, player) in players.iter_mut().enumerate() {
|
let mut player_descs = Vec::with_capacity(q_entry.users.len());
|
||||||
player.player.team = team_picker.team(i);
|
for (i, player) in q_entry.users.iter_mut().enumerate() {
|
||||||
|
let mut lobby_desc = oj_rc_core::persist::user::PlayerLobbyDescriptor {
|
||||||
|
user_id: player.user_id,
|
||||||
|
team: -1,
|
||||||
|
group: player.player.group.clone(),
|
||||||
|
public_id: player.player.name.clone(),
|
||||||
|
display_name: player.player.display_name.clone(),
|
||||||
|
};
|
||||||
|
let team = team_picker.choose_team(&guid_str, i, &lobby_desc);
|
||||||
|
lobby_desc.team = team;
|
||||||
|
player.player.team = team;
|
||||||
|
player_descs.push(lobby_desc);
|
||||||
}
|
}
|
||||||
let player_descs = players.iter().map(|x| oj_rc_core::persist::user::PlayerLobbyDescriptor {
|
|
||||||
user_id: x.user_id,
|
|
||||||
team: x.player.team,
|
|
||||||
group: None, // TODO support platoons
|
|
||||||
public_id: x.player.name.clone(),
|
|
||||||
display_name: x.player.display_name.clone(),
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
let missing = users_per_game.saturating_sub(players.len());
|
let missing = users_per_game.saturating_sub(q_entry.users.len());
|
||||||
|
|
||||||
match user.start_game(game_desc, player_descs, factory.as_ref(), &cpu_counter, &weapon_guesser, &team_picker, missing).await {
|
match user.start_game(game_desc, player_descs, factory.as_ref(), &cpu_counter, &weapon_guesser, &team_picker, missing).await {
|
||||||
Ok(fakes) => {
|
Ok(fakes) => {
|
||||||
let player_datas = players.iter().map(|x| x.player.clone())
|
let player_datas = q_entry.users.iter().map(|x| x.player.clone())
|
||||||
.chain(fakes.players.into_iter().map(|(desc, _emu)| desc),)
|
.chain(fakes.players.into_iter().map(|(desc, _emu)| desc),)
|
||||||
.collect();
|
.collect();
|
||||||
let enter_battle_ev = crate::events::battle_enter::BattleEnter {
|
let enter_battle_ev = crate::events::battle_enter::BattleEnter {
|
||||||
@@ -274,10 +316,10 @@ impl QueueHandler {
|
|||||||
network_config: network_conf.clone(),
|
network_config: network_conf.clone(),
|
||||||
};
|
};
|
||||||
let arc_event = std::sync::Arc::new(enter_battle_ev);
|
let arc_event = std::sync::Arc::new(enter_battle_ev);
|
||||||
for player in players.iter() {
|
for player in q_entry.users.iter() {
|
||||||
tokio::spawn(Self::send_events_to_player(arc_event.clone(), player.emitter.clone()));
|
tokio::spawn(Self::send_events_to_player(arc_event.clone(), player.emitter.clone()));
|
||||||
}
|
}
|
||||||
log::info!("{} players are entering match {}", players.len(), guid_str);
|
log::info!("{} players are entering match {}", q_entry.users.len(), guid_str);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Some(msg) = e.error_msg() {
|
if let Some(msg) = e.error_msg() {
|
||||||
@@ -491,8 +533,55 @@ impl QueueHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn join_or_create_platoon(&self, new_player: QueueUser, platoon: PlatoonInfo, q_entry: &mut Queue) {
|
||||||
|
let new_player = PlatoonUser::from_queue_user(new_player);
|
||||||
|
log::debug!("Existing platoon count {}", q_entry.platoons.len());
|
||||||
|
if let Some(p_entry) = q_entry.platoons.get_mut(&platoon.platoon_id) {
|
||||||
|
// update
|
||||||
|
log::debug!("Adding user {} to existing platoon {} ({}/{})", new_player.0.user_id, platoon.platoon_id, p_entry.members.len() + 1, p_entry.total);
|
||||||
|
if platoon.is_leader {
|
||||||
|
p_entry.total = platoon.total;
|
||||||
|
}
|
||||||
|
p_entry.members.push(new_player);
|
||||||
|
let wants_usize = p_entry.total as usize;
|
||||||
|
if wants_usize == p_entry.members.len() {
|
||||||
|
log::info!("All members of platoon {} are in queue, actually adding them to queue", platoon.platoon_id);
|
||||||
|
for member in p_entry.members.iter() {
|
||||||
|
q_entry.users.push(member.clone_for_queue());
|
||||||
|
}
|
||||||
|
q_entry.users.sort_by_key(|u| u.enqueued_at);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// create
|
||||||
|
log::debug!("Adding user {} to new platoon {} (1/{})", new_player.0.user_id, platoon.platoon_id, platoon.total);
|
||||||
|
q_entry.platoons.insert(platoon.platoon_id.clone(), Platoon {
|
||||||
|
total: if platoon.is_leader { platoon.total } else { u8::MAX },
|
||||||
|
members: vec![new_player],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn join_queue(&self, map: String, mode: oj_rc_core::data::game_mode::GameMode, visibility: oj_rc_core::data::game_mode::MapVisibility, auto_heal: bool, user: std::sync::Arc<Box<dyn oj_rc_core::persist::user::User<()> + Send + Sync>>, event_emitter: polariton_server::events::EventEmitter) {
|
fn platoon_leave_queue(&self, user_id: i32, platoon_id: String, q_entry: &mut Queue) {
|
||||||
|
let needs_remove = if let Some(platoon) = q_entry.platoons.get_mut(&platoon_id) {
|
||||||
|
let wants_usize = platoon.total as usize;
|
||||||
|
if wants_usize == platoon.members.len() {
|
||||||
|
// other platoon members are in queue, they also need to be removed
|
||||||
|
let some_group = Some(platoon_id.clone());
|
||||||
|
q_entry.users.retain(|q_user| q_user.player.group != some_group);
|
||||||
|
log::info!("Removed platoon {} from actual queue because user {} left queue", platoon_id, user_id);
|
||||||
|
}
|
||||||
|
platoon.members.retain(|p_mem| p_mem.0.user_id != user_id);
|
||||||
|
platoon.members.is_empty()
|
||||||
|
} else {
|
||||||
|
log::error!("Failed to find platoon {} to remove user {}: the lobby is in a bad state", platoon_id, user_id);
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if needs_remove {
|
||||||
|
q_entry.platoons.remove(&platoon_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn join_queue(&self, map: String, mode: oj_rc_core::data::game_mode::GameMode, visibility: oj_rc_core::data::game_mode::MapVisibility, auto_heal: bool, user: std::sync::Arc<Box<dyn oj_rc_core::persist::user::User<()> + Send + Sync>>, event_emitter: polariton_server::events::EventEmitter, platoon: Option<PlatoonInfo>) {
|
||||||
if !self.is_enabled {
|
if !self.is_enabled {
|
||||||
event_emitter.emit(crate::events::enqueue_error::QueueJoinError {
|
event_emitter.emit(crate::events::enqueue_error::QueueJoinError {
|
||||||
code: oj_rc_core::data::error_codes::LobbyReasonCode::NoSuitableLobbyFound as i16,
|
code: oj_rc_core::data::error_codes::LobbyReasonCode::NoSuitableLobbyFound as i16,
|
||||||
@@ -508,7 +597,10 @@ impl QueueHandler {
|
|||||||
};
|
};
|
||||||
let lobby_user = user.as_ref().as_ref();
|
let lobby_user = user.as_ref().as_ref();
|
||||||
match lobby_user.player_data(&self.cpu_counter).await {
|
match lobby_user.player_data(&self.cpu_counter).await {
|
||||||
Ok(player_data) => {
|
Ok(mut player_data) => {
|
||||||
|
if let Some(platoon) = &platoon {
|
||||||
|
player_data.group = Some(platoon.platoon_id.clone());
|
||||||
|
}
|
||||||
let new_player = QueueUser {
|
let new_player = QueueUser {
|
||||||
emitter: event_emitter,
|
emitter: event_emitter,
|
||||||
player: player_data,
|
player: player_data,
|
||||||
@@ -516,25 +608,30 @@ impl QueueHandler {
|
|||||||
enqueued_at: chrono::Utc::now(),
|
enqueued_at: chrono::Utc::now(),
|
||||||
user: user.clone(),
|
user: user.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut lock = self.users_in_queue.lock().await;
|
let mut lock = self.users_in_queue.lock().await;
|
||||||
|
for q_key in lock.keys() {
|
||||||
|
log::debug!("Existing queue for {} (matches joiner? {})", q_key.short(), q_key.short().to_string() == key.short().to_string());
|
||||||
|
}
|
||||||
// handle game event change
|
// handle game event change
|
||||||
if !lock.contains_key(&key) && lock.len() == 1 {
|
if !lock.contains_key(&key) && !lock.is_empty() {
|
||||||
log::debug!("Game event change detected");
|
log::debug!("Game event change detected");
|
||||||
match self.change_strategy {
|
match self.change_strategy {
|
||||||
GamemodeChangeStrategy::Upgrade => {
|
GamemodeChangeStrategy::Upgrade => {
|
||||||
let mut new_queue_map = std::collections::HashMap::<QueueKey, Vec<QueueUser>>::with_capacity(lock.len());
|
let mut new_queue_map = std::collections::HashMap::<QueueKey, Queue>::with_capacity(lock.len());
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
for (_key, mut users) in lock.drain() {
|
for (_key, mut q_entry) in lock.drain() {
|
||||||
count += users.len();
|
count += q_entry.users.len();
|
||||||
if let Some(values) = new_queue_map.get_mut(&key) {
|
if let Some(values) = new_queue_map.get_mut(&key) {
|
||||||
values.append(&mut users);
|
values.users.append(&mut q_entry.users);
|
||||||
|
values.platoons.extend(q_entry.platoons.into_iter());
|
||||||
} else {
|
} else {
|
||||||
new_queue_map.insert(key.clone(), users);
|
new_queue_map.insert(key.clone(), q_entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for users in new_queue_map.values_mut() {
|
for q_entry in new_queue_map.values_mut() {
|
||||||
users.sort_by_key(|u| u.enqueued_at);
|
q_entry.users.sort_by_key(|u| u.enqueued_at);
|
||||||
}
|
}
|
||||||
*lock = new_queue_map;
|
*lock = new_queue_map;
|
||||||
if count != 0 {
|
if count != 0 {
|
||||||
@@ -542,18 +639,31 @@ impl QueueHandler {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
GamemodeChangeStrategy::Notify => {
|
GamemodeChangeStrategy::Notify => {
|
||||||
let mut count = 0;
|
let mut seen = std::collections::HashSet::new();
|
||||||
for (_key, users) in lock.drain() {
|
for (_key, q_entry) in lock.drain() {
|
||||||
count += users.len();
|
for player in q_entry.users {
|
||||||
for player in users {
|
if seen.contains(&player.user_id) { continue; }
|
||||||
|
log::debug!("Notifying user {} of event expiry", player.user_id);
|
||||||
player.emitter.emit(crate::events::enqueue_error::QueueJoinError {
|
player.emitter.emit(crate::events::enqueue_error::QueueJoinError {
|
||||||
code: oj_rc_core::data::error_codes::LobbyReasonCode::EventSystemExpired as i16,
|
code: oj_rc_core::data::error_codes::LobbyReasonCode::EventSystemExpired as i16,
|
||||||
text: "Please requeue".to_owned(),
|
text: "Please requeue".to_owned(),
|
||||||
});
|
});
|
||||||
|
seen.insert(player.user_id);
|
||||||
|
}
|
||||||
|
for platoon in q_entry.platoons.into_values() {
|
||||||
|
for player in platoon.members {
|
||||||
|
if seen.contains(&player.0.user_id) { continue; }
|
||||||
|
log::debug!("Notifying user {} of event expiry", player.0.user_id);
|
||||||
|
player.0.emitter.emit(crate::events::enqueue_error::QueueJoinError {
|
||||||
|
code: oj_rc_core::data::error_codes::LobbyReasonCode::EventSystemExpired as i16,
|
||||||
|
text: "Please requeue".to_owned(),
|
||||||
|
});
|
||||||
|
seen.insert(player.0.user_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if count != 0 {
|
if !seen.is_empty() {
|
||||||
log::info!("Notified {} users in queue of new gamemode {}", count, key.short());
|
log::info!("Notified {} users in queue of new gamemode {}", seen.len(), key.short());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GamemodeChangeStrategy::Ignore => {
|
GamemodeChangeStrategy::Ignore => {
|
||||||
@@ -561,21 +671,44 @@ impl QueueHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let players_len = if let Some(players) = lock.get_mut(&key) {
|
let players_len = if let Some(q_entry) = lock.get_mut(&key) {
|
||||||
log::info!("User {} entered queue for existing match {}", new_player.user_id, key.short());
|
if let Some(platoon) = platoon {
|
||||||
players.push(new_player);
|
log::info!("User {} platooned {} queue for existing match {}", new_player.user_id, platoon.platoon_id, key.short());
|
||||||
players.len()
|
self.join_or_create_platoon(new_player, platoon, q_entry);
|
||||||
|
} else {
|
||||||
|
log::info!("User {} entered queue for existing match {}", new_player.user_id, key.short());
|
||||||
|
q_entry.users.push(new_player);
|
||||||
|
}
|
||||||
|
q_entry.users.len()
|
||||||
} else {
|
} else {
|
||||||
log::info!("User {} entered queue for new match {}", new_player.user_id, key.short());
|
if let Some(platoon) = platoon {
|
||||||
lock.insert(key.clone(), vec![new_player]);
|
log::info!("User {} platooned {} queue for new match {}", new_player.user_id, platoon.platoon_id, key.short());
|
||||||
1
|
let mut q_entry = Queue {
|
||||||
|
users: Vec::default(),
|
||||||
|
platoons: HashMap::default(),
|
||||||
|
};
|
||||||
|
self.join_or_create_platoon(new_player, platoon, &mut q_entry);
|
||||||
|
lock.insert(key.clone(), q_entry);
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
log::info!("User {} entered queue for new match {}", new_player.user_id, key.short());
|
||||||
|
lock.insert(key.clone(), Queue {
|
||||||
|
users: vec![new_player],
|
||||||
|
platoons: HashMap::default(),
|
||||||
|
});
|
||||||
|
1
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
for q_key in lock.keys() {
|
||||||
|
log::debug!("Now queue for {} (matches joiner? {})", q_key.short(), q_key.short().to_string() == key.short().to_string());
|
||||||
|
}
|
||||||
let game_ready = players_len >= self.users_per_game;
|
let game_ready = players_len >= self.users_per_game;
|
||||||
let players = if game_ready { lock.remove(&key) } else { None };
|
let players = if game_ready { lock.remove(&key) } else { None };
|
||||||
drop(lock);
|
drop(lock);
|
||||||
if let Some(players) = players {
|
if let Some(players) = players {
|
||||||
self.enter_match(key, players, lobby_user).await;
|
self.enter_match(key, players, lobby_user).await;
|
||||||
}
|
}
|
||||||
|
log::debug!("join_queue complete success");
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
event_emitter.emit(crate::events::enqueue_error::QueueJoinError {
|
event_emitter.emit(crate::events::enqueue_error::QueueJoinError {
|
||||||
@@ -598,13 +731,24 @@ impl QueueHandler {
|
|||||||
log::info!("User {} was removed from custom game {} queue", user_id, session_id);
|
log::info!("User {} was removed from custom game {} queue", user_id, session_id);
|
||||||
} else {
|
} else {
|
||||||
// fallback to regular multiplayer
|
// fallback to regular multiplayer
|
||||||
for (queue_key, queue) in self.users_in_queue.lock().await.iter_mut() {
|
let mut to_remove = Vec::new();
|
||||||
if let Some((i, _)) = queue.iter().enumerate().find(|(_, user)| user.user_id == user_id) {
|
let mut q_lock = self.users_in_queue.lock().await;
|
||||||
queue.remove(i);
|
for (queue_key, queue) in q_lock.iter_mut() {
|
||||||
|
if let Some((i, _)) = queue.users.iter().enumerate().find(|(_, user)| user.user_id == user_id) {
|
||||||
|
let q_user = queue.users.remove(i);
|
||||||
|
if let Some(platoon_id) = q_user.player.group {
|
||||||
|
self.platoon_leave_queue(user_id, platoon_id, queue);
|
||||||
|
}
|
||||||
|
if queue.users.is_empty() && queue.platoons.is_empty() {
|
||||||
|
to_remove.push(queue_key.to_owned());
|
||||||
|
}
|
||||||
log::info!("User {} was removed from queue {}", user_id, queue_key.short());
|
log::info!("User {} was removed from queue {}", user_id, queue_key.short());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for key in to_remove {
|
||||||
|
q_lock.remove(&key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,15 @@ impl <C: Send + 'static> SimpleOperation<C> for QueueJoinProvider {
|
|||||||
current_lobby.auto_heal,
|
current_lobby.auto_heal,
|
||||||
user_info.clone(),
|
user_info.clone(),
|
||||||
events.to_owned(),
|
events.to_owned(),
|
||||||
|
if group_size > 1 {
|
||||||
|
Some(crate::lobby::PlatoonInfo {
|
||||||
|
total: group_size as _,
|
||||||
|
platoon_id: group_id.string,
|
||||||
|
is_leader,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
|||||||
.modify(oj_rc_core::polariton::RcOpModifier)
|
.modify(oj_rc_core::polariton::RcOpModifier)
|
||||||
.add(more_auth::MoreLobbyAuth)
|
.add(more_auth::MoreLobbyAuth)
|
||||||
//.add(eac::EacChallengeIgnorer)
|
//.add(eac::EacChallengeIgnorer)
|
||||||
//.add(polariton_server::operations::Ack::<2, _>::default())
|
.add(polariton_server::operations::Ack::<2, _>::default())
|
||||||
.add(no_quit::quit_blocker_provider())
|
.add(no_quit::quit_blocker_provider())
|
||||||
.add(join_queue::join_queue_provider(&init_ctx.queue))
|
.add(join_queue::join_queue_provider(&init_ctx.queue))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ impl SimpleOperation<crate::data::custom::CustomType> for PlatoonPendingInviter
|
|||||||
let user_info = user.user()?;
|
let user_info = user.user()?;
|
||||||
let my_public_id = user_info.public_id();
|
let my_public_id = user_info.public_id();
|
||||||
log::debug!("Checking for pending platoon invite of user {}", my_public_id);
|
log::debug!("Checking for pending platoon invite of user {}", my_public_id);
|
||||||
if let Some(platoon_id) = self.social.platoon_of_user(my_public_id).await {
|
if let Some(platoon_id) = self.social.platoon_invite_of_user(my_public_id).await {
|
||||||
let members = self.social.users_of_platoon(&platoon_id).await;
|
let members = self.social.users_of_platoon(&platoon_id).await;
|
||||||
let social_infos = user_info.list_social_info(&[members[0].public_id.clone()]).await?;
|
let social_infos = user_info.list_social_info(&[members[0].public_id.clone()]).await?;
|
||||||
params.insert(INVITER_NAME_PARAM_KEY, Typed::Str(social_infos[0].public_id.clone().into()));
|
params.insert(INVITER_NAME_PARAM_KEY, Typed::Str(social_infos[0].public_id.clone().into()));
|
||||||
|
|||||||
@@ -92,6 +92,20 @@ impl SocialMesh {
|
|||||||
.map(|x| x.to_owned())
|
.map(|x| x.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn platoon_invite_of_user(&self, public_id: &str) -> Option<String> {
|
||||||
|
if let Some(platoon_id) = self.platoons.platoon_by_user.read().await.get(public_id) {
|
||||||
|
if let Some(platoon) = self.platoons.platoon_by_id.read().await.get(platoon_id) {
|
||||||
|
let myself = platoon.iter()
|
||||||
|
.find(|mem| mem.public_id == public_id)
|
||||||
|
.unwrap();
|
||||||
|
if matches!(myself.status, crate::data::platoon::MemberStatus::Invited) {
|
||||||
|
return Some(platoon_id.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn users_of_platoon(&self, platoon_id: &str) -> Vec<PlatoonMemberInfo> {
|
pub async fn users_of_platoon(&self, platoon_id: &str) -> Vec<PlatoonMemberInfo> {
|
||||||
self.platoons.platoon_by_id.read().await
|
self.platoons.platoon_by_id.read().await
|
||||||
.get(platoon_id)
|
.get(platoon_id)
|
||||||
@@ -150,12 +164,14 @@ impl SocialMesh {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_user_from_platoon(&self, public_id: &str) -> bool {
|
pub async fn remove_user_from_platoon(&self, public_id: &str) -> bool {
|
||||||
if let Some(platoon_id) = self.platoons.platoon_by_user.write().await.remove(public_id) {
|
let mut platoon_by_user_lock = self.platoons.platoon_by_user.write().await;
|
||||||
|
if let Some(platoon_id) = platoon_by_user_lock.remove(public_id) {
|
||||||
let mut platoon_by_id_lock = self.platoons.platoon_by_id.write().await;
|
let mut platoon_by_id_lock = self.platoons.platoon_by_id.write().await;
|
||||||
if let Some(platoon) = platoon_by_id_lock.get_mut(&platoon_id) {
|
if let Some(platoon) = platoon_by_id_lock.get_mut(&platoon_id) {
|
||||||
platoon.retain(|member| member.public_id != public_id);
|
platoon.retain(|member| member.public_id != public_id);
|
||||||
if platoon.is_empty() {
|
if platoon.len() <= 1 {
|
||||||
platoon_by_id_lock.remove(&platoon_id);
|
platoon_by_id_lock.remove(&platoon_id);
|
||||||
|
platoon_by_user_lock.retain(|_pub_id, platoon_id_val| platoon_id_val != &platoon_id);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user