mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add support for matchmaker sending clients off to game server
This commit is contained in:
@@ -72,3 +72,32 @@ pub enum SingleplayerErrorCode {
|
||||
MaintenanceMode = 4,
|
||||
DuplicateLogin = 5,
|
||||
}
|
||||
|
||||
#[repr(i16)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum LobbyReasonCode {
|
||||
UnexpectedError = -1,
|
||||
Ok = 0,
|
||||
MaintenanceMode = 1,
|
||||
RobotValidationError = 2,
|
||||
LoggedInOtherLocation = 3,
|
||||
GroupFailedChecks = 4,
|
||||
ConnectionTestFailed = 5,
|
||||
WrongGameModeForParty = 6,
|
||||
BrawlConnectionTestFailed = 7,
|
||||
PartyNotAllowed = 8,
|
||||
NoSuitableLobbyFound = 9,
|
||||
EventSystemExpired = 10
|
||||
}
|
||||
|
||||
impl LobbyReasonCode {
|
||||
pub(crate) fn from_service_error(err: i16) -> Self {
|
||||
match err {
|
||||
0 /* None */ => Self::Ok,
|
||||
125 => Self::MaintenanceMode,
|
||||
140 => Self::RobotValidationError,
|
||||
_ => Self::UnexpectedError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ impl GameMap {
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Hash, Eq, PartialEq)]
|
||||
pub enum GameMode {
|
||||
BattleArena = 0,
|
||||
SuddenDeath = 1,
|
||||
@@ -121,7 +121,7 @@ impl GameMode {
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Hash, Eq, PartialEq)]
|
||||
pub enum MapVisibility {
|
||||
Good = 0,
|
||||
Poor = 1,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use polariton::operation::Typed;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlayerData {
|
||||
pub name: String,
|
||||
pub display_name: String,
|
||||
@@ -12,6 +13,7 @@ pub struct PlayerData {
|
||||
pub has_premium: bool,
|
||||
pub robot_uuid: String,
|
||||
pub cpu: i32,
|
||||
pub avatar_id: Option<i32>,
|
||||
pub weapon_order: Vec<i32>,
|
||||
pub colour_map: Vec<u8>,
|
||||
pub is_ai: bool,
|
||||
@@ -52,6 +54,41 @@ impl PlayerData {
|
||||
}
|
||||
Ok(42 + self.robot_map.len() + (self.weapon_order.len() * 4) + self.colour_map.len() + (self.weapon_rank.len() * 8) + total_len)
|
||||
}
|
||||
|
||||
pub fn as_transmissible<C>(&self) -> Typed<C> {
|
||||
let weapon_ranks = Typed::Dict(polariton::operation::Dict {
|
||||
key_ty: polariton::serdes::TypePrefix::Int,
|
||||
val_ty: polariton::serdes::TypePrefix::Int,
|
||||
items: self.weapon_rank.clone().into_iter().map(|(k, v)| (Typed::Int(k), Typed::Int(v))).collect(),
|
||||
});
|
||||
Typed::HashMap(vec![
|
||||
(Typed::Str("name".into()), Typed::Str(self.name.clone().into())),
|
||||
(Typed::Str("displayName".into()), Typed::Str(self.display_name.clone().into())),
|
||||
(Typed::Str("robotName".into()), Typed::Str(self.robot_name.clone().into())),
|
||||
(Typed::Str("cubeMap".into()), Typed::Bytes(self.robot_map.clone().into())),
|
||||
(Typed::Str("colourMap".into()), Typed::Bytes(self.colour_map.clone().into())),
|
||||
(Typed::Str("spawnEffect".into()), Typed::Str(self.spawn_effect.clone().into())),
|
||||
(Typed::Str("deathEffect".into()), Typed::Str(self.death_effect.clone().into())),
|
||||
//(Typed::Str("groupId".into()), Typed::Int(self.group)), // FIXME
|
||||
(Typed::Str("groupId".into()), Typed::Int(self.team)), // not strongly enforced in EnterBattleEventListener
|
||||
(Typed::Str("team".into()), Typed::Int(self.team)), // not strongly enforced in EnterBattleEventListener (but has to be parsable into an i32)
|
||||
(Typed::Str("hasPremium".into()), Typed::Bool(self.has_premium)),
|
||||
(Typed::Str("weaponOrder".into()), Typed::IntArr(self.weapon_order.clone().into())),
|
||||
(Typed::Str("robotUniqueID".into()), Typed::Str(self.robot_uuid.clone().into())),
|
||||
(Typed::Str("cpu".into()), Typed::Int(self.cpu)), // not strongly enforced in EnterBattleEventListener
|
||||
(Typed::Str("useCustomAvatar".into()), Typed::Bool(self.avatar_id.is_none())),
|
||||
(Typed::Str("avatarId".into()), Typed::Int(self.avatar_id.unwrap_or(0))),
|
||||
(Typed::Str("masteryLevel".into()), Typed::Int(self.mastery)),
|
||||
(Typed::Str("tier".into()), Typed::Int(self.tier)),
|
||||
(Typed::Str("playerRank".into()), Typed::Int(self.player_rank)),
|
||||
(Typed::Str("weaponRanks".into()), weapon_ranks),
|
||||
(Typed::Str("isAI".into()), Typed::Bool(self.is_ai)),
|
||||
// only required if clanName exists
|
||||
/*(Typed::Str("clanName".into()), Typed::Str(todo!())),
|
||||
(Typed::Str("clanUseCustomAvatar".into()), Typed::Bool(todo!())),
|
||||
(Typed::Str("clanDefaultAvatarID".into()), Typed::Int(todo!())),*/
|
||||
].into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlayerDatas {
|
||||
|
||||
@@ -12,6 +12,8 @@ pub struct BattleConfig {
|
||||
pub singleplayer: super::SingleplayerConfig,
|
||||
#[serde(default = "default_rotation")]
|
||||
pub rotation: GameEventSequence,
|
||||
#[serde(default = "default_multiplayer")]
|
||||
pub multiplayer: super::MultiplayerConfig,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
@@ -472,3 +474,11 @@ fn default_rotation() -> GameEventSequence {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn default_multiplayer() -> super::MultiplayerConfig {
|
||||
super::MultiplayerConfig {
|
||||
players_per_game: 1,
|
||||
enabled: true,
|
||||
network: super::multiplayer::default_net_conf(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,4 +353,16 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
|
||||
items: id_map,
|
||||
})
|
||||
}*/
|
||||
|
||||
fn players_per_game(&self) -> usize {
|
||||
self.battle.multiplayer.players_per_game
|
||||
}
|
||||
|
||||
fn is_multiplayer_enabled(&self) -> bool {
|
||||
self.battle.multiplayer.enabled
|
||||
}
|
||||
|
||||
fn network_config(&self) -> crate::persist::NetworkConf {
|
||||
self.battle.multiplayer.network.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,11 @@ pub trait ConfigProvider<C: Clone> {
|
||||
fn cubes(&self) -> &'_ std::collections::HashMap<String, crate::persist::Cube>;
|
||||
fn chat_system_config(&self) -> ChatSystemConfig;
|
||||
fn gamemode_events(&self) -> GameEventSequence;
|
||||
// FIXME don't use serializable types in traits
|
||||
fn singleplayer_details(&self) -> SingleplayerConfig;
|
||||
fn players_per_game(&self) -> usize;
|
||||
fn is_multiplayer_enabled(&self) -> bool;
|
||||
// FIXME don't use serializable types in traits
|
||||
fn network_config(&self) -> crate::persist::NetworkConf;
|
||||
}
|
||||
|
||||
pub struct CompleteCampaignProvider {
|
||||
|
||||
@@ -35,6 +35,9 @@ pub use chat::{ChatConfig, ChatCommand, ChatOperation, BuiltInChatOperation};
|
||||
mod vehicle_factory;
|
||||
pub use vehicle_factory::{FactoryConfig, AdapterSettings, ArcFactorySettings};
|
||||
|
||||
mod multiplayer;
|
||||
pub use multiplayer::{MultiplayerConfig, NetworkConf};
|
||||
|
||||
pub(self) const VALID_ROBOT: &[u8] = &[64,
|
||||
0,
|
||||
0,
|
||||
|
||||
48
rc_core/src/persist/multiplayer.rs
Normal file
48
rc_core/src/persist/multiplayer.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct MultiplayerConfig {
|
||||
pub players_per_game: usize,
|
||||
pub enabled: bool,
|
||||
#[serde(default = "default_net_conf")]
|
||||
pub network: NetworkConf,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct NetworkConf {
|
||||
pub network_channel_ty: String,
|
||||
pub max_sent_message_queue_size: u16,
|
||||
pub is_acks_long: bool,
|
||||
pub network_drop_threshold: u8,
|
||||
pub packet_size: u16,
|
||||
pub max_combined_reliable_message_count: u16,
|
||||
pub max_combined_reliable_message_size: u16,
|
||||
pub min_update_timeout: u16,
|
||||
pub max_delay: i64,
|
||||
pub overflow_threshold: u8,
|
||||
pub max_packet_size: u16,
|
||||
pub resend_delay_base: f64,
|
||||
pub resend_delay_rtt_mult: f64,
|
||||
pub network_peer_update_interval: i32,
|
||||
pub max_delay_for_disconnect_ms: i32,
|
||||
}
|
||||
|
||||
pub(super) fn default_net_conf() -> NetworkConf {
|
||||
NetworkConf {
|
||||
network_channel_ty: "3113".to_owned(),
|
||||
max_sent_message_queue_size: 64,
|
||||
is_acks_long: true,
|
||||
network_drop_threshold: 80,
|
||||
packet_size: 1200,
|
||||
max_combined_reliable_message_count: 20,
|
||||
max_combined_reliable_message_size: 200,
|
||||
min_update_timeout: 1,
|
||||
max_delay: 1,
|
||||
overflow_threshold: 10,
|
||||
max_packet_size: 5888,
|
||||
resend_delay_base: 0.1,
|
||||
resend_delay_rtt_mult: 0.5,
|
||||
network_peer_update_interval: 1,
|
||||
max_delay_for_disconnect_ms: 1000,
|
||||
}
|
||||
}
|
||||
@@ -261,6 +261,50 @@ impl UserData {
|
||||
self.perms.administrator | self.perms.developer
|
||||
}
|
||||
|
||||
async fn user_player_data(&self) -> Result<crate::data::player_data::PlayerData, polariton_server::operations::SimpleOpError> {
|
||||
let current_slot = self.db.garage_selected(self.account.id).await.map_err(|e| {
|
||||
log::error!("Failed to retrieve selected vehicle for user_id {} (user_player_data): {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(DATABASE_ERR, format!("Could not retrieve selected garage: {}", e))
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
log::error!("Failed to find selected vehicle for user_id {} (user_player_data)", self.account.id);
|
||||
polariton_server::operations::SimpleOpError::with_message(INVALID_ROBOT_ERR, "No selected garage".to_owned())
|
||||
})?;
|
||||
let user_uuid = self.token.uuid.clone();
|
||||
let weapon_order = oj_rc_database::schema::parse_int_csv(¤t_slot.weapon_order).into_iter().map(|x| x as i32).collect::<Vec<_>>();
|
||||
let user_avatar_aux = self.db.user_aux_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::user_aux::Descriptor::AvatarId).await.map_err(|e| {
|
||||
log::error!("Failed to retrieve avatar for user_id {} (user_player_data): {}", self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(DATABASE_ERR, format!("Could not retrieve avatar: {}", e))
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
log::error!("Failed to find avatar for user_id {} (user_player_data)", self.account.id);
|
||||
polariton_server::operations::SimpleOpError::with_message(UNEXPECTED_ERR, "No avatar".to_owned())
|
||||
})?;
|
||||
let avatar_id: Result<i32, _> = user_avatar_aux.data.parse();
|
||||
|
||||
// real user MUST be last
|
||||
Ok(crate::data::player_data::PlayerData {
|
||||
name: user_uuid.clone(),
|
||||
display_name: self.account.display_name.clone(),
|
||||
mastery: current_slot.mastery_level as i32,
|
||||
tier: 1, // FIXME
|
||||
robot_name: current_slot.name,
|
||||
robot_map: current_slot.robot_data.clone(),
|
||||
team: 0,
|
||||
has_premium: false, // FIXME
|
||||
robot_uuid: super::i64_as_uuid_str(current_slot.uuid).into(),
|
||||
cpu: current_slot.total_robot_cpu as i32,
|
||||
avatar_id: avatar_id.ok(),
|
||||
weapon_order: weapon_order.clone(),
|
||||
colour_map: current_slot.colour_data.clone(),
|
||||
is_ai: false,
|
||||
spawn_effect: current_slot.spawn_animation_id,
|
||||
death_effect: current_slot.death_animation_id,
|
||||
player_rank: 1, // FIXME
|
||||
weapon_rank: oj_rc_database::schema::parse_int_csv(¤t_slot.weapon_order).into_iter().map(|x| (x as i32, if x == 0 { 0 } else { 1 })).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn resolve_some_singleplayer_vehicles(&self, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, singleplayer_config: &crate::persist::config::SingleplayerConfig) -> Result<Vec<crate::data::player_data::PlayerData>, i16> {
|
||||
use rand::seq::IndexedRandom;
|
||||
let mut players = Vec::with_capacity((singleplayer_config.max_enemies + singleplayer_config.max_teammates + 1) as usize);
|
||||
@@ -302,6 +346,7 @@ impl UserData {
|
||||
has_premium: false,
|
||||
robot_uuid: uuid_str,
|
||||
cpu: 420,
|
||||
avatar_id: None, // not serialised
|
||||
weapon_order: weapons_guess,
|
||||
colour_map: factory_vehicle.0.colour_data,
|
||||
is_ai: true,
|
||||
@@ -335,6 +380,7 @@ impl UserData {
|
||||
has_premium: false,
|
||||
robot_uuid: uuid_str,
|
||||
cpu: db_vehicle.total_robot_cpu as i32,
|
||||
avatar_id: None, // not serialised
|
||||
weapon_order: oj_rc_database::schema::parse_int_csv(&db_vehicle.weapon_order).into_iter().map(|x| x as i32).collect::<Vec<_>>(),
|
||||
colour_map: db_vehicle.colour_data,
|
||||
is_ai: true,
|
||||
@@ -372,6 +418,7 @@ impl UserData {
|
||||
has_premium: false,
|
||||
robot_uuid: uuid_str,
|
||||
cpu: 420, // FIXME
|
||||
avatar_id: None, // not serialised
|
||||
weapon_order: weapons_guess,
|
||||
colour_map: colour_data.to_owned(),
|
||||
is_ai: true,
|
||||
@@ -790,37 +837,10 @@ impl <C: Clone> super::User<C> for UserData {
|
||||
async fn singleplayer_robots(&self, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, singleplayer_config: &crate::persist::config::SingleplayerConfig) -> Result<polariton::operation::Typed<C>, i16> {
|
||||
//self.err_on_banned().await?;
|
||||
let mut vehicles = self.resolve_some_singleplayer_vehicles(factory, weapon_order, singleplayer_config).await?;
|
||||
let current_slot = self.db.garage_selected(self.account.id).await.map_err(|e| {
|
||||
log::error!("Failed to retrieve selected vehicle for user_id {} (singleplayer_robots): {}", self.account.id, e);
|
||||
DATABASE_ERR
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
log::error!("Failed to find selected vehicle for user_id {} (singleplayer_robots)", self.account.id);
|
||||
INVALID_ROBOT_ERR
|
||||
})?;
|
||||
let user_uuid = self.token.uuid.clone();
|
||||
let weapon_order = oj_rc_database::schema::parse_int_csv(¤t_slot.weapon_order).into_iter().map(|x| x as i32).collect::<Vec<_>>();
|
||||
let user_bot = self.user_player_data().await?;
|
||||
|
||||
// real user MUST be last
|
||||
vehicles.push(crate::data::player_data::PlayerData {
|
||||
name: user_uuid.clone(),
|
||||
display_name: self.account.display_name.clone(),
|
||||
mastery: current_slot.mastery_level as i32,
|
||||
tier: 1, // FIXME
|
||||
robot_name: current_slot.name,
|
||||
robot_map: current_slot.robot_data.clone(),
|
||||
team: 0,
|
||||
has_premium: false, // FIXME
|
||||
robot_uuid: super::i64_as_uuid_str(current_slot.uuid).into(),
|
||||
cpu: current_slot.total_robot_cpu as i32,
|
||||
weapon_order: weapon_order.clone(),
|
||||
colour_map: current_slot.colour_data.clone(),
|
||||
is_ai: false,
|
||||
spawn_effect: "Spawn_Warp".to_owned(), // FIXME
|
||||
death_effect: "Explosion_Warp".to_owned(), // FIXME
|
||||
player_rank: 1, // FIXME
|
||||
weapon_rank: oj_rc_database::schema::parse_int_csv(¤t_slot.weapon_order).into_iter().map(|x| (x as i32, if x == 0 { 0 } else { 1 })).collect(),
|
||||
});
|
||||
vehicles.push(user_bot);
|
||||
Ok(crate::data::player_data::PlayerDatas {
|
||||
players: vehicles,
|
||||
}.as_transmissible())
|
||||
@@ -1074,3 +1094,17 @@ impl super::ChatUser for UserData {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::LobbyUser for UserData {
|
||||
async fn player_data(&self) -> Result<crate::data::player_data::PlayerData, polariton_server::operations::SimpleOpError> {
|
||||
self.user_player_data().await.map_err(|e| {
|
||||
if let Some(msg) = e.error_msg() {
|
||||
polariton_server::operations::SimpleOpError::with_message(crate::data::error_codes::LobbyReasonCode::from_service_error(e.error_code()) as i16, msg.to_owned())
|
||||
} else {
|
||||
polariton_server::operations::SimpleOpError::with_code(crate::data::error_codes::LobbyReasonCode::from_service_error(e.error_code()) as i16)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ mod inventory;
|
||||
pub use inventory::UnlockedParts;
|
||||
|
||||
mod traits;
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType};
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser};
|
||||
|
||||
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ pub trait UserAuthenticator {
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait User<C>: ChatUser {
|
||||
pub trait User<C>: ChatUser + LobbyUser {
|
||||
fn ext(&self, ty: std::any::TypeId) -> Option<&'_ (dyn std::any::Any + Send + Sync + 'static)>;
|
||||
fn token(&self) -> &'_ super::UserToken;
|
||||
fn is_mod(&self) -> bool;
|
||||
@@ -225,3 +225,8 @@ impl SanctionType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait LobbyUser {
|
||||
async fn player_data(&self) -> Result<crate::data::player_data::PlayerData, polariton_server::operations::SimpleOpError>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user