mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Allow two or more people to load into the same multiplayer match #30
This commit is contained in:
@@ -118,6 +118,32 @@ impl GameMode {
|
||||
crate::persist::config::GameType::Campaign => Self::Campaign,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn from_db(mode: oj_rc_database::schema::multiplayer_game::GameMode) -> Self {
|
||||
match mode {
|
||||
oj_rc_database::schema::multiplayer_game::GameMode::BattleArena => Self::BattleArena,
|
||||
oj_rc_database::schema::multiplayer_game::GameMode::SuddenDeath => Self::SuddenDeath,
|
||||
oj_rc_database::schema::multiplayer_game::GameMode::Pit => Self::Pit,
|
||||
oj_rc_database::schema::multiplayer_game::GameMode::TestMode => Self::TestMode,
|
||||
oj_rc_database::schema::multiplayer_game::GameMode::SinglePlayer => Self::SinglePlayer,
|
||||
oj_rc_database::schema::multiplayer_game::GameMode::TeamDeathmatch => Self::TeamDeathmatch,
|
||||
oj_rc_database::schema::multiplayer_game::GameMode::Campaign => Self::Campaign,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn to_db(&self) -> oj_rc_database::schema::multiplayer_game::GameMode {
|
||||
match self {
|
||||
Self::BattleArena => oj_rc_database::schema::multiplayer_game::GameMode::BattleArena,
|
||||
Self::SuddenDeath => oj_rc_database::schema::multiplayer_game::GameMode::SuddenDeath,
|
||||
Self::Pit => oj_rc_database::schema::multiplayer_game::GameMode::Pit,
|
||||
Self::TestMode => oj_rc_database::schema::multiplayer_game::GameMode::TestMode,
|
||||
Self::SinglePlayer => oj_rc_database::schema::multiplayer_game::GameMode::SinglePlayer,
|
||||
Self::TeamDeathmatch => oj_rc_database::schema::multiplayer_game::GameMode::TeamDeathmatch,
|
||||
Self::Campaign => oj_rc_database::schema::multiplayer_game::GameMode::Campaign,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
@@ -137,4 +163,22 @@ impl MapVisibility {
|
||||
crate::persist::config::GameVisibility::Bad => Self::Bad,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn from_db(mode: oj_rc_database::schema::multiplayer_game::MapVisibility) -> Self {
|
||||
match mode {
|
||||
oj_rc_database::schema::multiplayer_game::MapVisibility::Good => Self::Good,
|
||||
oj_rc_database::schema::multiplayer_game::MapVisibility::Poor => Self::Poor,
|
||||
oj_rc_database::schema::multiplayer_game::MapVisibility::Bad => Self::Bad,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn to_db(&self) -> oj_rc_database::schema::multiplayer_game::MapVisibility {
|
||||
match self {
|
||||
Self::Good => oj_rc_database::schema::multiplayer_game::MapVisibility::Good,
|
||||
Self::Poor => oj_rc_database::schema::multiplayer_game::MapVisibility::Poor,
|
||||
Self::Bad => oj_rc_database::schema::multiplayer_game::MapVisibility::Bad,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ pub struct PlayerData {
|
||||
pub tier: i32,
|
||||
pub robot_name: String,
|
||||
pub robot_map: Vec<u8>,
|
||||
// -- unused i32 here --
|
||||
pub group: Option<String>, // unused i32 too???
|
||||
pub team: i32,
|
||||
pub has_premium: bool,
|
||||
pub robot_uuid: String,
|
||||
@@ -70,7 +70,7 @@ impl PlayerData {
|
||||
(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("groupId".into()), Typed::Str(self.group.clone().unwrap_or_default().into())),
|
||||
(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())),
|
||||
|
||||
@@ -477,7 +477,7 @@ fn default_rotation() -> GameEventSequence {
|
||||
|
||||
fn default_multiplayer() -> super::MultiplayerConfig {
|
||||
super::MultiplayerConfig {
|
||||
players_per_game: 1,
|
||||
players_per_game: 2,
|
||||
enabled: true,
|
||||
network: super::multiplayer::default_net_conf(),
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ impl AccountProvider {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn multiplayer_init(&self) -> Result<(), oj_rc_database::sea_orm::DbErr> {
|
||||
self.db.complete_all_games().await
|
||||
}
|
||||
|
||||
/*pub fn fake_user<C: Clone>(&self) -> Box<dyn super::User<C> + Send + Sync> {
|
||||
Box::new(UserData {
|
||||
token: super::UserToken { uuid: "fake user!".to_owned(), token: "".to_owned(), refresh_token: "".to_owned() },
|
||||
@@ -306,6 +310,7 @@ impl UserData {
|
||||
tier: 1, // FIXME
|
||||
robot_name: current_slot.name,
|
||||
robot_map: current_slot.robot_data.clone(),
|
||||
group: None, // no platoon
|
||||
team: 0,
|
||||
has_premium: false, // FIXME
|
||||
robot_uuid: super::i64_as_uuid_str(current_slot.uuid).into(),
|
||||
@@ -358,6 +363,7 @@ impl UserData {
|
||||
tier: 1, // FIXME
|
||||
robot_name: vehicle.name.clone().unwrap_or_else(|| factory_vehicle.1.name.clone()),
|
||||
robot_map: factory_vehicle.0.cube_data,
|
||||
group: None,
|
||||
team: team_num,
|
||||
has_premium: false,
|
||||
robot_uuid: uuid_str,
|
||||
@@ -392,6 +398,7 @@ impl UserData {
|
||||
tier: 1, // FIXME
|
||||
robot_name: vehicle.name.clone().unwrap_or_else(|| db_vehicle.name.clone()),
|
||||
robot_map: db_vehicle.robot_data,
|
||||
group: None,
|
||||
team: team_num,
|
||||
has_premium: false,
|
||||
robot_uuid: uuid_str,
|
||||
@@ -430,6 +437,7 @@ impl UserData {
|
||||
tier: 1, // FIXME
|
||||
robot_name: vehicle.name.clone().unwrap_or_else(|| "Raw Robot".to_owned()),
|
||||
robot_map: cube_data.to_owned(),
|
||||
group: None,
|
||||
team: team_num,
|
||||
has_premium: false,
|
||||
robot_uuid: uuid_str,
|
||||
@@ -1109,6 +1117,10 @@ impl super::ChatUser for UserData {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::LobbyUser for UserData {
|
||||
fn user_id(&self) -> i32 {
|
||||
self.account.id
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -1119,6 +1131,66 @@ impl super::LobbyUser for UserData {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async fn start_game(&self, game: super::GameDescriptor, players: Vec<super::PlayerLobbyDescriptor>) -> Result<(), 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(
|
||||
crate::data::error_codes::LobbyReasonCode::UnexpectedError as i16, "Invalid GUID".to_owned()
|
||||
)
|
||||
)?;
|
||||
let variant = if game.is_ranked {
|
||||
oj_rc_database::schema::multiplayer_game::GameType::Ranked
|
||||
} else if game.is_custom {
|
||||
oj_rc_database::schema::multiplayer_game::GameType::Custom
|
||||
} else {
|
||||
oj_rc_database::schema::multiplayer_game::GameType::Standard
|
||||
};
|
||||
|
||||
let game_dbo = oj_rc_database::schema::multiplayer_game::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||
guid: oj_rc_database::sea_orm::ActiveValue::Set(guid),
|
||||
map: oj_rc_database::sea_orm::ActiveValue::Set(game.map),
|
||||
mode: oj_rc_database::sea_orm::ActiveValue::Set(game.mode.to_db()),
|
||||
visibility: oj_rc_database::sea_orm::ActiveValue::Set(game.visibility.to_db()),
|
||||
auto_heal: oj_rc_database::sea_orm::ActiveValue::Set(game.auto_heal),
|
||||
variant: oj_rc_database::sea_orm::ActiveValue::Set(variant),
|
||||
is_complete: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
||||
};
|
||||
let game_dbo = self.db.insert_game(game_dbo).await.map_err(|e| {
|
||||
log::error!("Failed to create game {} through user_id {}: {}", game.guid, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::LobbyReasonCode::UnexpectedError as i16,
|
||||
format!("Failed to create game {}: {}", game.guid, e),
|
||||
)
|
||||
})?;
|
||||
|
||||
let players: Vec<oj_rc_database::schema::multiplayer_game_player::ActiveModel> = players.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, player)| {
|
||||
oj_rc_database::schema::multiplayer_game_player::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
user_id: oj_rc_database::sea_orm::ActiveValue::Set(player.user_id),
|
||||
game_id: oj_rc_database::sea_orm::ActiveValue::Set(game_dbo.id),
|
||||
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||
player_id: oj_rc_database::sea_orm::ActiveValue::Set((i as u8) as _),
|
||||
team: oj_rc_database::sea_orm::ActiveValue::Set(player.team),
|
||||
group: oj_rc_database::sea_orm::ActiveValue::Set(player.group),
|
||||
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
self.db.insert_players(players).await.map_err(|e| {
|
||||
log::error!("Failed to create game players for {} through user_id {}: {}", game.guid, self.account.id, e);
|
||||
polariton_server::operations::SimpleOpError::with_message(
|
||||
crate::data::error_codes::LobbyReasonCode::UnexpectedError as i16,
|
||||
format!("Failed to create game players for {}: {}", game.guid, e),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -1135,4 +1207,72 @@ impl super::MultiplayerUser for UserData {
|
||||
fn display_name(&self) -> &'_ str {
|
||||
&self.account.display_name
|
||||
}
|
||||
|
||||
async fn current_game(&self) -> Result<Option<super::GameDescriptor>, super::MultiplayerError> {
|
||||
Ok(self.db.game_by_user_id_and_completion(self.account.id, false).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve ongoing game for user {}: {}", self.account.id, e);
|
||||
super::MultiplayerError {
|
||||
code: super::MultiplayerErrorCode::CustomString,
|
||||
message: format!("Failed to retrieve ongoing game: {}", e),
|
||||
}
|
||||
})?
|
||||
.map(|game| super::GameDescriptor {
|
||||
guid: crate::persist::user::i64_as_uuid_str(game.guid),
|
||||
map: game.map,
|
||||
mode: crate::data::game_mode::GameMode::from_db(game.mode),
|
||||
visibility: crate::data::game_mode::MapVisibility::from_db(game.visibility),
|
||||
auto_heal: game.auto_heal,
|
||||
is_ranked: matches!(game.variant, oj_rc_database::schema::multiplayer_game::GameType::Ranked),
|
||||
is_custom: matches!(game.variant, oj_rc_database::schema::multiplayer_game::GameType::Custom),
|
||||
is_complete: game.is_complete,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn game_players(&self, guid: &str) -> Result<Vec<super::PlayerDescriptor>, super::MultiplayerError> {
|
||||
if let Some(guid) = crate::persist::user::str_to_i64(guid) {
|
||||
let players = self.db.players_by_game_guid_and_completion_heavy(guid, false).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to retrieve players for game {} for user {}: {}", guid, self.account.id, e);
|
||||
super::MultiplayerError {
|
||||
code: super::MultiplayerErrorCode::CustomString,
|
||||
message: format!("Failed to retrieve players for game {}: {}", guid, e),
|
||||
}
|
||||
})?;
|
||||
Ok(players.into_iter()
|
||||
.map(|(player, user)| super::PlayerDescriptor {
|
||||
user_id: player.user_id,
|
||||
player_id: player.player_id as u8,
|
||||
team: player.team,
|
||||
group: player.group,
|
||||
is_rewards_claimed: player.is_claimed,
|
||||
display_name: user.display_name,
|
||||
public_id: user.public_id,
|
||||
})
|
||||
.collect())
|
||||
} else {
|
||||
Err(super::MultiplayerError {
|
||||
code: super::MultiplayerErrorCode::IncorrectGameGuid,
|
||||
message: format!("Failed to parse game GUID {}", guid),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn complete_game(&self, guid: &str) -> Result<(), super::MultiplayerError> {
|
||||
if let Some(guid) = crate::persist::user::str_to_i64(guid) {
|
||||
self.db.update_complete_game_by_game_guid(guid).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to complete ongoing game with user {}: {}", self.account.id, e);
|
||||
super::MultiplayerError {
|
||||
code: super::MultiplayerErrorCode::CustomString,
|
||||
message: format!("Failed to complete ongoing game: {}", e),
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(super::MultiplayerError {
|
||||
code: super::MultiplayerErrorCode::IncorrectGameGuid,
|
||||
message: format!("Failed to parse game GUID {}", guid),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, LobbyUser, MultiplayerUser};
|
||||
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, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor};
|
||||
|
||||
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";
|
||||
|
||||
|
||||
@@ -229,13 +229,76 @@ impl SanctionType {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait LobbyUser {
|
||||
fn user_id(&self) -> i32;
|
||||
async fn player_data(&self) -> Result<crate::data::player_data::PlayerData, polariton_server::operations::SimpleOpError>;
|
||||
async fn start_game(&self, game: GameDescriptor, players: Vec<PlayerLobbyDescriptor>) -> Result<(), polariton_server::operations::SimpleOpError>;
|
||||
}
|
||||
|
||||
pub struct GameDescriptor {
|
||||
pub guid: String,
|
||||
pub map: String,
|
||||
pub mode: crate::data::game_mode::GameMode,
|
||||
pub visibility: crate::data::game_mode::MapVisibility,
|
||||
pub auto_heal: bool,
|
||||
pub is_ranked: bool,
|
||||
pub is_custom: bool,
|
||||
pub is_complete: bool,
|
||||
}
|
||||
|
||||
pub struct PlayerLobbyDescriptor {
|
||||
pub user_id: i32,
|
||||
pub team: i32,
|
||||
pub group: Option<i32>,
|
||||
}
|
||||
|
||||
pub struct PlayerDescriptor {
|
||||
pub user_id: i32,
|
||||
pub player_id: u8,
|
||||
pub team: i32,
|
||||
pub group: Option<i32>,
|
||||
pub public_id: String,
|
||||
pub display_name: String,
|
||||
pub is_rewards_claimed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MultiplayerError {
|
||||
pub code: MultiplayerErrorCode,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for MultiplayerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}: {}", self.code, self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::error::Error for MultiplayerError {}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug)]
|
||||
pub enum MultiplayerErrorCode {
|
||||
HaxSpeed = 0,
|
||||
HaxException = 1,
|
||||
HaxTeleport = 2,
|
||||
HaxEacViolation = 6,
|
||||
HaxAfk = 7,
|
||||
HaxFirerange = 8,
|
||||
HaxFiredamage = 9,
|
||||
HaxFirerate = 10,
|
||||
HaxFireposition = 11,
|
||||
IncorrectGameGuid = 12,
|
||||
CustomString = 13,
|
||||
TimedOut = 14,
|
||||
GameEnded = 15,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait MultiplayerUser {
|
||||
// TODO
|
||||
fn user_id(&self) -> i32;
|
||||
fn user_name(&self) -> &'_ str;
|
||||
fn display_name(&self) -> &'_ str;
|
||||
async fn current_game(&self) -> Result<Option<GameDescriptor>, MultiplayerError>;
|
||||
async fn game_players(&self, guid: &str) -> Result<Vec<PlayerDescriptor>, MultiplayerError>;
|
||||
async fn complete_game(&self, guid: &str) -> Result<(), MultiplayerError>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user