mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Implement lobby parts of custom games #38
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2576,6 +2576,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"git-version",
|
||||
"log",
|
||||
"oj_polariton_auth",
|
||||
|
||||
63
rc_auth/src/robocraft/intercom/lobby.rs
Normal file
63
rc_auth/src/robocraft/intercom/lobby.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use actix_web::{rt, web::{Payload, Data, Json, Path}, Error, HttpRequest, HttpResponse, get, post};
|
||||
//use actix_ws::AggregatedMessage;
|
||||
//use futures::StreamExt as _;
|
||||
|
||||
#[get("/intercom/userless/.oj_lobby")]
|
||||
pub async fn lobby_state_ws(req: HttpRequest, stream: Payload, auth: Data<super::IntercomAuth>, reg: Data<super::Users>) -> Result<HttpResponse, Error> {
|
||||
auth.validate(&req, "state/.oj_lobby")?;
|
||||
let (res, mut session, _stream) = actix_ws::handle(&req, stream)?;
|
||||
|
||||
/*let mut stream = stream
|
||||
.aggregate_continuations()
|
||||
.max_continuation_size(2_usize.pow(20)); // aggregate continuation frames up to 1MiB
|
||||
*/
|
||||
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(16);
|
||||
reg.register_lobby_state_service(tx).await;
|
||||
log::debug!("Registered lobby state intercom websocket");
|
||||
|
||||
// start task but don't wait for it
|
||||
rt::spawn(async move {
|
||||
let mut is_ok = false;
|
||||
while let Some(op) = rx.recv().await {
|
||||
match op {
|
||||
super::IntercomOp::Message(msg) => {
|
||||
if let Err(e) = session.text(serde_json::to_string(&msg).unwrap()).await {
|
||||
log::warn!("Failed to send lobby state intercom message: {}", e);
|
||||
break;
|
||||
}
|
||||
},
|
||||
super::IntercomOp::Info(info) => {
|
||||
match info {
|
||||
super::IntercomInfo::Close => {
|
||||
is_ok = true;
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if !is_ok {
|
||||
reg.remove_lobby_state_service().await;
|
||||
}
|
||||
rx.close();
|
||||
session.close(Some(actix_ws::CloseReason {
|
||||
code: actix_ws::CloseCode::Normal,
|
||||
description: Some("End of channel".to_owned()),
|
||||
})).await.expect("Failed to close a services intercom websocket session");
|
||||
log::debug!("Lobby state intercom websocket closed");
|
||||
});
|
||||
|
||||
// respond immediately with response connected to WS session
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[post("/intercom/.oj_lobby/{name}/state")]
|
||||
pub async fn lobby_state_msg(req: HttpRequest, body: Json<oj_rc_core::persist::user::intercom::IntercomLobbyStateMessage>, auth: Data<super::IntercomAuth>, reg: Data<super::Users>, name: Path<String>) -> Result<HttpResponse, super::IntercomOpError> {
|
||||
log::debug!("Got lobby state intercom message");
|
||||
auth.validate(&req, &format!(".oj_lobby/{}/state", name))?;
|
||||
log::debug!("Authenticated intercom lobby state message from {}", name);
|
||||
reg.broadcast_lobby_message(body.0).await;
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
@@ -10,11 +10,17 @@ pub use user_registry::Users;
|
||||
mod status;
|
||||
pub use status::{status_set, status_get};
|
||||
|
||||
enum IntercomOp {
|
||||
Message(oj_rc_core::persist::user::intercom::IntercomWebServiceUserMessage),
|
||||
mod lobby;
|
||||
pub use lobby::{lobby_state_ws, lobby_state_msg};
|
||||
|
||||
enum IntercomOp<M: serde::Serialize + serde::Deserialize<'static> + 'static> {
|
||||
Message(M),
|
||||
Info(IntercomInfo),
|
||||
}
|
||||
|
||||
enum IntercomInfo {
|
||||
Close,
|
||||
}
|
||||
|
||||
type WebServicesIntercomOp = IntercomOp<oj_rc_core::persist::user::intercom::IntercomWebServiceUserMessage>;
|
||||
type LobbyStateIntercomOp = IntercomOp<oj_rc_core::persist::user::intercom::IntercomLobbyStateMessage>;
|
||||
|
||||
@@ -13,7 +13,7 @@ pub async fn services_ws(req: HttpRequest, stream: Payload, auth: Data<super::In
|
||||
*/
|
||||
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(16);
|
||||
reg.register_service(name.clone(), tx).await;
|
||||
reg.register_web_service(name.clone(), tx).await;
|
||||
log::debug!("Registered web services intercom websocket for user {}", name);
|
||||
|
||||
// start task but don't wait for it
|
||||
@@ -39,7 +39,7 @@ pub async fn services_ws(req: HttpRequest, stream: Payload, auth: Data<super::In
|
||||
|
||||
}
|
||||
if !is_ok {
|
||||
reg.remove_service(name.clone()).await;
|
||||
reg.remove_web_service(name.clone()).await;
|
||||
}
|
||||
rx.close();
|
||||
session.close(Some(actix_ws::CloseReason {
|
||||
@@ -58,6 +58,6 @@ pub async fn service_msg(req: HttpRequest, body: Json<oj_rc_core::persist::user:
|
||||
log::debug!("Got intercom message from {} to {:?}", name, body.public_ids.as_slice());
|
||||
auth.validate(&req, &format!(".oj_services/{}/messages", name))?;
|
||||
log::debug!("Authenticated intercom message from {} to {:?}", name, body.public_ids.as_slice());
|
||||
reg.broadcast_service_message(body.0).await;
|
||||
reg.broadcast_web_service_message(body.0).await;
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
struct ServiceListeners {
|
||||
web_services: tokio::sync::RwLock<std::collections::HashMap<String, tokio::sync::mpsc::Sender<super::WebServicesIntercomOp>>>,
|
||||
lobby_state: tokio::sync::RwLock<Option<tokio::sync::mpsc::Sender<super::LobbyStateIntercomOp>>>,
|
||||
}
|
||||
|
||||
pub struct Users {
|
||||
service_listeners: tokio::sync::RwLock<std::collections::HashMap<String, tokio::sync::mpsc::Sender<super::IntercomOp>>>,
|
||||
service_listeners: ServiceListeners,
|
||||
service_status: tokio::sync::RwLock<std::collections::HashMap<String, oj_serdes::ServerStatus>>,
|
||||
}
|
||||
|
||||
impl Users {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
service_listeners: tokio::sync::RwLock::new(std::collections::HashMap::with_capacity(16)),
|
||||
service_listeners: ServiceListeners {
|
||||
web_services: tokio::sync::RwLock::new(std::collections::HashMap::with_capacity(16)),
|
||||
lobby_state: tokio::sync::RwLock::new(None),
|
||||
},
|
||||
service_status: tokio::sync::RwLock::new(std::collections::HashMap::with_capacity(16)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn register_service(&self, public_id: String, sender: tokio::sync::mpsc::Sender<super::IntercomOp>) {
|
||||
let mut write_lock = self.service_listeners.write().await;
|
||||
pub(super) async fn register_web_service(&self, public_id: String, sender: tokio::sync::mpsc::Sender<super::WebServicesIntercomOp>) {
|
||||
let mut write_lock = self.service_listeners.web_services.write().await;
|
||||
if let Some(old_sender) = write_lock.insert(public_id.clone(), sender) {
|
||||
if !old_sender.is_closed() {
|
||||
log::warn!("Replaced web services intercom channel for user {} (why duplicate!?)", public_id);
|
||||
@@ -21,26 +29,43 @@ impl Users {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_service(&self, public_id: String) {
|
||||
let mut write_lock = self.service_listeners.write().await;
|
||||
pub(super) async fn register_lobby_state_service(&self, sender: tokio::sync::mpsc::Sender<super::LobbyStateIntercomOp>) {
|
||||
let mut write_lock = self.service_listeners.lobby_state.write().await;
|
||||
if let Some(old_sender) = write_lock.replace(sender) {
|
||||
if !old_sender.is_closed() {
|
||||
log::warn!("Replaced lobby state intercom channel (why duplicate!?)");
|
||||
old_sender.send(super::IntercomOp::Info(super::IntercomInfo::Close)).await.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_web_service(&self, public_id: String) {
|
||||
let mut write_lock = self.service_listeners.web_services.write().await;
|
||||
if write_lock.remove(&public_id).is_none() {
|
||||
log::warn!("Tried to remove web services intercom channel for user {} without listener", public_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn broadcast_service_message(&self, msg: oj_rc_core::persist::user::intercom::IntercomWebServiceMessage) {
|
||||
let read_lock = self.service_listeners.read().await;
|
||||
pub async fn remove_lobby_state_service(&self) {
|
||||
let mut write_lock = self.service_listeners.lobby_state.write().await;
|
||||
if write_lock.take().is_none() {
|
||||
log::warn!("Tried to remove lobby state intercom channel without listener");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn broadcast_web_service_message(&self, msg: oj_rc_core::persist::user::intercom::IntercomWebServiceMessage) {
|
||||
let read_lock = self.service_listeners.web_services.read().await;
|
||||
if msg.everyone {
|
||||
if !msg.public_ids.is_empty() { return; } // invalid
|
||||
for (public_id, tx) in read_lock.iter() {
|
||||
if let Err(e) = tx.send(super::IntercomOp::Message(msg.data.clone())).await {
|
||||
if let Err(e) = tx.send(super::WebServicesIntercomOp::Message(msg.data.clone())).await {
|
||||
log::error!("Failed to send web service intercom message to {}: {}", public_id, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for public_id in msg.public_ids {
|
||||
if let Some(tx) = read_lock.get(&public_id) {
|
||||
if let Err(e) = tx.send(super::IntercomOp::Message(msg.data.clone())).await {
|
||||
if let Err(e) = tx.send(super::WebServicesIntercomOp::Message(msg.data.clone())).await {
|
||||
log::error!("Failed to send web service intercom message to {}: {}", public_id, e);
|
||||
}
|
||||
} else {
|
||||
@@ -50,6 +75,15 @@ impl Users {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn broadcast_lobby_message(&self, msg: oj_rc_core::persist::user::intercom::IntercomLobbyStateMessage) {
|
||||
let read_lock = self.service_listeners.lobby_state.read().await;
|
||||
if let Some(listener) = &*read_lock {
|
||||
if let Err(e) = listener.send(super::LobbyStateIntercomOp::Message(msg)).await {
|
||||
log::error!("Failed to send lobby state intercom message: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn statuses(&self) -> std::collections::HashMap<String, oj_serdes::ServerStatus> {
|
||||
self.service_status.read().await.clone()
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ pub struct AccountProvider {
|
||||
domain: std::sync::Arc<String>,
|
||||
cdn: std::sync::Arc<String>,
|
||||
auth: std::sync::Arc<String>,
|
||||
intercom: std::sync::Arc<String>,
|
||||
secret: std::sync::Arc<Vec<u8>>,
|
||||
pub(super) intercom: std::sync::Arc<String>,
|
||||
pub(super) intercom_http_client: std::sync::Arc<reqwest::Client>,
|
||||
pub(super) secret: std::sync::Arc<Vec<u8>>,
|
||||
db: std::sync::Arc<oj_rc_database::Database>,
|
||||
}
|
||||
|
||||
@@ -38,6 +39,7 @@ impl AccountProvider {
|
||||
cdn: std::sync::Arc::new(server_settings.cdn_url),
|
||||
auth: std::sync::Arc::new(server_settings.auth_url),
|
||||
intercom: std::sync::Arc::new(server_settings.intercom_url),
|
||||
intercom_http_client: std::sync::Arc::new(reqwest::Client::new()),
|
||||
secret: std::sync::Arc::new(secret),
|
||||
db: std::sync::Arc::new(db),
|
||||
})
|
||||
|
||||
@@ -116,6 +116,13 @@ impl super::IntercomUser for super::account_json::UserData {
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_custom_game(&self, msg: IntercomLobbyCustomGameDataMessage) {
|
||||
let data = IntercomLobbyStateMessage::CustomGame(msg);
|
||||
if let Err(e) = self.post_to_intercom(&data, ".oj_lobby", "state").await {
|
||||
log::error!("Failed to send intercom custom game state message: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_status(&self, server_name: &str, msg: oj_serdes::ServerStatus) {
|
||||
if let Err(e) = self.post_to_intercom(&msg, ".status", server_name).await {
|
||||
log::error!("Failed to send intercom status message: {}", e);
|
||||
@@ -123,6 +130,91 @@ impl super::IntercomUser for super::account_json::UserData {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(tag = "state")]
|
||||
pub enum IntercomLobbyStateMessage {
|
||||
CustomGame(IntercomLobbyCustomGameDataMessage),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct IntercomLobbyCustomGameDataMessage {
|
||||
pub session_id: String,
|
||||
pub config: IntercomLobbyCustomGameConfig,
|
||||
pub users: Vec<IntercomLobbyCustomGameUserData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum CustomGameMode {
|
||||
BattleArena,
|
||||
TeamDeathmatch,
|
||||
Pit,
|
||||
SuddenDeath,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
|
||||
pub enum CustomGameVisibility {
|
||||
Good,
|
||||
Poor,
|
||||
Bad,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct IntercomLobbyCustomGameConfig {
|
||||
pub game_mode: CustomGameMode,
|
||||
pub map: String,
|
||||
pub map_visibility: CustomGameVisibility,
|
||||
pub health_regen: bool,
|
||||
pub capture_segment_memory: bool,
|
||||
pub base_shields_go_down: bool,
|
||||
pub damage_mult: i32,
|
||||
pub health_mult: i32,
|
||||
pub power_mult: i32,
|
||||
pub game_time: i32, // minutes
|
||||
pub capture_speed: i32, // seconds
|
||||
pub points_kill_streak: bool,
|
||||
pub points_total_required: i32,
|
||||
pub number_of_kills_to_win: i32,
|
||||
pub respawn_time: i32,
|
||||
pub core_appear_frequency: i32,
|
||||
pub core_health_multiplier: i32,
|
||||
pub core_destroy_time: i32,
|
||||
pub protonium_harvest: i32,
|
||||
pub ceiling_multiplier: i32,
|
||||
pub min_cpu: i32,
|
||||
pub max_cpu: i32,
|
||||
}
|
||||
|
||||
impl IntercomLobbyCustomGameConfig {
|
||||
pub fn as_core(&self) -> super::GameOverrides {
|
||||
super::GameOverrides {
|
||||
capture_segment_memory: self.capture_segment_memory,
|
||||
base_shields_go_down: self.base_shields_go_down,
|
||||
damage_mult: self.damage_mult,
|
||||
health_mult: self.health_mult,
|
||||
power_mult: self.power_mult,
|
||||
game_time: self.game_time,
|
||||
capture_speed: self.capture_speed,
|
||||
points_kill_streak: self.points_kill_streak,
|
||||
points_total_required: self.points_total_required,
|
||||
number_of_kills_to_win: self.number_of_kills_to_win,
|
||||
respawn_time: self.respawn_time,
|
||||
core_appear_frequency: self.core_appear_frequency,
|
||||
core_health_multiplier: self.core_health_multiplier,
|
||||
core_destroy_time: self.core_destroy_time,
|
||||
protonium_harvest: self.protonium_harvest,
|
||||
ceiling_multiplier: self.ceiling_multiplier,
|
||||
min_cpu: self.min_cpu,
|
||||
max_cpu: self.max_cpu,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct IntercomLobbyCustomGameUserData {
|
||||
pub public_id: String,
|
||||
pub team: u8,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct IntercomWebServiceMessage {
|
||||
pub public_ids: Vec<String>,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use super::account_json::UserData;
|
||||
|
||||
pub enum TeamChooser {
|
||||
@@ -87,6 +89,7 @@ impl super::LobbyUser for UserData {
|
||||
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),
|
||||
overrides: oj_rc_database::sea_orm::ActiveValue::Set("".to_owned()),
|
||||
};
|
||||
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);
|
||||
@@ -165,4 +168,143 @@ impl super::LobbyUser for UserData {
|
||||
players: forced_fake_players.into_iter().chain(filler_players).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn start_custom_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 conf_str = if let Some(config) = game.overrides {
|
||||
serde_json::to_string_pretty(&CustomGameOverrides::from_user(&config)).unwrap()
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
|
||||
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),
|
||||
overrides: oj_rc_database::sea_orm::ActiveValue::Set(conf_str),
|
||||
};
|
||||
let game_dbo = self.db.insert_game(game_dbo).await.map_err(|e| {
|
||||
log::error!("Failed to create custom 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 custom 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(Some(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),
|
||||
public_id: oj_rc_database::sea_orm::ActiveValue::Set(player.public_id),
|
||||
display_name: oj_rc_database::sea_orm::ActiveValue::Set(player.display_name),
|
||||
variant: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::multiplayer_game_player::ClientType::Client),
|
||||
}
|
||||
})
|
||||
.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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub(super) struct CustomGameOverrides {
|
||||
pub capture_segment_memory: bool,
|
||||
pub base_shields_go_down: bool,
|
||||
pub damage_mult: i32,
|
||||
pub health_mult: i32,
|
||||
pub power_mult: i32,
|
||||
pub game_time: i32, // minutes
|
||||
pub capture_speed: i32, // seconds
|
||||
pub points_kill_streak: bool,
|
||||
pub points_total_required: i32,
|
||||
pub number_of_kills_to_win: i32,
|
||||
pub respawn_time: i32,
|
||||
pub core_appear_frequency: i32,
|
||||
pub core_health_multiplier: i32,
|
||||
pub core_destroy_time: i32,
|
||||
pub protonium_harvest: i32,
|
||||
pub ceiling_multiplier: i32,
|
||||
pub min_cpu: i32,
|
||||
pub max_cpu: i32,
|
||||
}
|
||||
|
||||
impl CustomGameOverrides {
|
||||
pub(super) fn from_user(intercom: &super::GameOverrides) -> Self {
|
||||
Self {
|
||||
capture_segment_memory: intercom.capture_segment_memory,
|
||||
base_shields_go_down: intercom.base_shields_go_down,
|
||||
damage_mult: intercom.damage_mult,
|
||||
health_mult: intercom.health_mult,
|
||||
power_mult: intercom.power_mult,
|
||||
game_time: intercom.game_time,
|
||||
capture_speed: intercom.capture_speed,
|
||||
points_kill_streak: intercom.points_kill_streak,
|
||||
points_total_required: intercom.points_total_required,
|
||||
number_of_kills_to_win: intercom.number_of_kills_to_win,
|
||||
respawn_time: intercom.respawn_time,
|
||||
core_appear_frequency: intercom.core_appear_frequency,
|
||||
core_health_multiplier: intercom.core_health_multiplier,
|
||||
core_destroy_time: intercom.core_destroy_time,
|
||||
protonium_harvest: intercom.protonium_harvest,
|
||||
ceiling_multiplier: intercom.ceiling_multiplier,
|
||||
min_cpu: intercom.min_cpu,
|
||||
max_cpu: intercom.max_cpu,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn to_user(&self) -> super::GameOverrides {
|
||||
super::GameOverrides {
|
||||
capture_segment_memory: self.capture_segment_memory,
|
||||
base_shields_go_down: self.base_shields_go_down,
|
||||
damage_mult: self.damage_mult,
|
||||
health_mult: self.health_mult,
|
||||
power_mult: self.power_mult,
|
||||
game_time: self.game_time,
|
||||
capture_speed: self.capture_speed,
|
||||
points_kill_streak: self.points_kill_streak,
|
||||
points_total_required: self.points_total_required,
|
||||
number_of_kills_to_win: self.number_of_kills_to_win,
|
||||
respawn_time: self.respawn_time,
|
||||
core_appear_frequency: self.core_appear_frequency,
|
||||
core_health_multiplier: self.core_health_multiplier,
|
||||
core_destroy_time: self.core_destroy_time,
|
||||
protonium_harvest: self.protonium_harvest,
|
||||
ceiling_multiplier: self.ceiling_multiplier,
|
||||
min_cpu: self.min_cpu,
|
||||
max_cpu: self.max_cpu,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ mod inventory;
|
||||
pub use inventory::{UnlockedParts, UnlockOverride};
|
||||
|
||||
mod traits;
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo, ClanData, ClanMember, ClanMemberRank, ClanType, ClanSearchQuery, ClanInviteData};
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo, ClanData, ClanMember, ClanMemberRank, ClanType, ClanSearchQuery, ClanInviteData, Userless, GameOverrides};
|
||||
|
||||
pub mod intercom;
|
||||
pub use intercom::generate_token as generate_intercom_token;
|
||||
@@ -24,6 +24,7 @@ mod chat;
|
||||
mod social;
|
||||
mod singleplayer;
|
||||
mod factory;
|
||||
mod userless;
|
||||
|
||||
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";
|
||||
|
||||
|
||||
@@ -134,6 +134,15 @@ impl super::MultiplayerUser for UserData {
|
||||
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,
|
||||
overrides: if game.overrides.is_empty() { None } else {
|
||||
match serde_json::from_str::<super::lobby::CustomGameOverrides>(&game.overrides) {
|
||||
Ok(x) => Some(x.to_user()),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to parse overrides JSON: {}\n{}", e, game.overrides);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -204,6 +213,15 @@ impl super::MultiplayerUser for UserData {
|
||||
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,
|
||||
overrides: if game.overrides.is_empty() { None } else {
|
||||
match serde_json::from_str::<super::lobby::CustomGameOverrides>(&game.overrides) {
|
||||
Ok(x) => Some(x.to_user()),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to parse overrides JSON: {}\n{}", e, game.overrides);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
Err(super::MultiplayerError {
|
||||
|
||||
@@ -23,6 +23,7 @@ impl super::SingleplayerUser for UserData {
|
||||
auto_heal: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
||||
variant: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::multiplayer_game::GameType::Standard),
|
||||
is_complete: oj_rc_database::sea_orm::ActiveValue::Set(true),
|
||||
overrides: oj_rc_database::sea_orm::ActiveValue::Set("".to_owned()),
|
||||
}).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to create singleplayer game {} for user {}: {}", guid, self.account.id, e);
|
||||
|
||||
@@ -282,6 +282,8 @@ pub trait LobbyUser {
|
||||
async fn team_chooser(&self, game: &GameDescriptor) -> super::TeamChooser;
|
||||
#[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>;
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn start_custom_game(&self, game: GameDescriptor, players: Vec<PlayerLobbyDescriptor>) -> Result<(), polariton_server::operations::SimpleOpError>;
|
||||
}
|
||||
|
||||
pub struct FakePlayers {
|
||||
@@ -306,6 +308,28 @@ pub struct GameDescriptor {
|
||||
pub is_ranked: bool,
|
||||
pub is_custom: bool,
|
||||
pub is_complete: bool,
|
||||
pub overrides: Option<GameOverrides>,
|
||||
}
|
||||
|
||||
pub struct GameOverrides {
|
||||
pub capture_segment_memory: bool,
|
||||
pub base_shields_go_down: bool,
|
||||
pub damage_mult: i32,
|
||||
pub health_mult: i32,
|
||||
pub power_mult: i32,
|
||||
pub game_time: i32, // minutes
|
||||
pub capture_speed: i32, // seconds
|
||||
pub points_kill_streak: bool,
|
||||
pub points_total_required: i32,
|
||||
pub number_of_kills_to_win: i32,
|
||||
pub respawn_time: i32,
|
||||
pub core_appear_frequency: i32,
|
||||
pub core_health_multiplier: i32,
|
||||
pub core_destroy_time: i32,
|
||||
pub protonium_harvest: i32,
|
||||
pub ceiling_multiplier: i32,
|
||||
pub min_cpu: i32,
|
||||
pub max_cpu: i32,
|
||||
}
|
||||
|
||||
pub struct PlayerLobbyDescriptor {
|
||||
@@ -394,6 +418,7 @@ pub trait IntercomUser: CommonUser {
|
||||
async fn webservice_listener(&self) -> Result<IntercomListener<super::intercom::IntercomWebServiceUserMessage>, polariton_server::operations::SimpleOpError>;
|
||||
async fn show_dev_message(&self, msg: super::intercom::IntercomDevMessage, to: Vec<String>);
|
||||
async fn enter_maintenance(&self, msg: super::intercom::IntercomMaintenanceMessage, to: Vec<String>);
|
||||
async fn update_custom_game(&self, msg: super::intercom::IntercomLobbyCustomGameDataMessage);
|
||||
async fn update_status(&self, server_name: &str, msg: oj_serdes::ServerStatus);
|
||||
}
|
||||
|
||||
@@ -643,3 +668,8 @@ pub trait FactoryUser {
|
||||
async fn prepare_factory_upload(&self, vehicle: VehicleUploadData) -> Result<oj_rc_factory::VehicleUploadInfo, polariton_server::operations::SimpleOpError>;
|
||||
async fn rate_vehicle(&self, slot: i32, combat: i32, cosmetic: i32) -> Result<Option<i32>, polariton_server::operations::SimpleOpError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Userless: Send + Sync {
|
||||
async fn lobby_state_listener(&self) -> Result<super::IntercomListener<super::intercom::IntercomLobbyStateMessage>, reqwest_websocket::Error>;
|
||||
}
|
||||
|
||||
29
rc_core/src/persist/user/userless.rs
Normal file
29
rc_core/src/persist/user/userless.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
//use serde::{Serialize, Deserialize};
|
||||
|
||||
impl super::AccountProvider {
|
||||
async fn listen_on_websocket<D: serde::de::DeserializeOwned>(&self, server_name: &str) -> Result<super::IntercomListener<D>, reqwest_websocket::Error> {
|
||||
use reqwest_websocket::RequestBuilderExt;
|
||||
let token = super::generate_intercom_token(format!("state/{}", server_name).as_bytes(), &self.secret);
|
||||
let auth_header_val = format!("Internal {}", token);
|
||||
let url = format!("{}/intercom/userless/{}", self.intercom, server_name);
|
||||
log::debug!("Listening on websocket {}", url);
|
||||
let websocket = self.intercom_http_client.get(url)
|
||||
.header("Authorization", auth_header_val)
|
||||
.upgrade()
|
||||
.send()
|
||||
.await?
|
||||
.into_websocket()
|
||||
.await?;
|
||||
Ok(super::IntercomListener {
|
||||
websocket,
|
||||
_d: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Userless for super::AccountProvider {
|
||||
async fn lobby_state_listener(&self) -> Result<super::IntercomListener<super::intercom::IntercomLobbyStateMessage>, reqwest_websocket::Error> {
|
||||
self.listen_on_websocket(".oj_lobby").await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
pub struct Migration;
|
||||
|
||||
impl MigrationName for Migration {
|
||||
fn name(&self) -> &str {
|
||||
"m20260317_000001_add_game_overrides"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
// Define how to apply this migration: Add game overrides columns
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(crate::schema::multiplayer_game::Entity)
|
||||
.add_column(ColumnDef::new(crate::schema::multiplayer_game::Column::Overrides).string().not_null().default("".to_owned()))
|
||||
.to_owned()
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// Define how to rollback this migration: Drop the added column
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(crate::schema::multiplayer_game::Entity)
|
||||
.drop_column(crate::schema::multiplayer_game::Column::Overrides)
|
||||
.to_owned()
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ mod m20260126_000001_create_factory_vehicle_table;
|
||||
mod m20260215_000001_create_friend_table;
|
||||
mod m20260221_000001_create_clan_table;
|
||||
mod m20260221_000002_create_clan_member_table;
|
||||
mod m20260317_000001_add_game_overrides;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -43,6 +44,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260215_000001_create_friend_table::Migration),
|
||||
Box::new(m20260221_000001_create_clan_table::Migration),
|
||||
Box::new(m20260221_000002_create_clan_member_table::Migration),
|
||||
Box::new(m20260317_000001_add_game_overrides::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ pub struct Model {
|
||||
pub auto_heal: bool,
|
||||
pub variant: GameType,
|
||||
pub is_complete: bool,
|
||||
pub overrides: String, // JSON-encoded oj_rc_ore::persist::user::lobby::CustomGameConfig
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
||||
@@ -21,3 +21,4 @@ async-trait.workspace = true
|
||||
git-version.workspace = true
|
||||
chrono.workspace = true
|
||||
oj_serdes.workspace = true
|
||||
futures.workspace = true
|
||||
|
||||
74
rc_lobby_room/src/events/handler.rs
Normal file
74
rc_lobby_room/src/events/handler.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
//use oj_rc_core::persist::user::IntercomListener;
|
||||
use oj_rc_core::persist::user::intercom::IntercomLobbyStateMessage;
|
||||
|
||||
const RETRY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
|
||||
|
||||
pub struct IntercomHandler<U: oj_rc_core::persist::user::Userless> {
|
||||
userless: std::sync::Arc<U>,
|
||||
lobby: std::sync::Arc<crate::QueueHandler>,
|
||||
}
|
||||
|
||||
impl <U: oj_rc_core::persist::user::Userless + 'static> IntercomHandler<U> {
|
||||
pub fn new(
|
||||
userless: std::sync::Arc<U>,
|
||||
lobby: std::sync::Arc<crate::QueueHandler>,
|
||||
) -> Self {
|
||||
Self {
|
||||
userless,
|
||||
lobby,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_loop(
|
||||
userless: std::sync::Arc<U>,
|
||||
lobby: std::sync::Arc<crate::QueueHandler>,
|
||||
) {
|
||||
loop {
|
||||
let listener = match userless.lobby_state_listener().await {
|
||||
Ok(listener) => {
|
||||
log::debug!("Connected lobby state intercom listener");
|
||||
listener
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to connect to lobby state intercom: {} (retrying in {}s)", e, RETRY_TIMEOUT.as_secs());
|
||||
tokio::time::sleep(RETRY_TIMEOUT).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
use futures::StreamExt;
|
||||
let mut listener = listener.listen().await;
|
||||
while let Some(msg) = listener.next().await {
|
||||
match msg {
|
||||
Ok(msg) => {
|
||||
match msg {
|
||||
IntercomLobbyStateMessage::CustomGame(state) => {
|
||||
if state.users.is_empty() {
|
||||
// disband
|
||||
lobby.remove_custom_queue(&state.session_id).await;
|
||||
} else {
|
||||
let is_create = lobby.update_custom_queue(
|
||||
&state.session_id,
|
||||
state.users.iter().map(|user| (user.public_id.clone(), user.team)),
|
||||
state.config,
|
||||
).await;
|
||||
if is_create {
|
||||
log::debug!("Created custom game {} lobby data to {} members", state.session_id, state.users.len());
|
||||
} else {
|
||||
log::debug!("Updated custom game {} lobby data to {} members", state.session_id, state.users.len());
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Bad intercom message received: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(self) -> tokio::task::JoinHandle<()> {
|
||||
tokio::spawn(Self::run_loop(self.userless, self.lobby))
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
pub mod battle_found;
|
||||
pub mod battle_enter;
|
||||
pub mod enqueue_error;
|
||||
|
||||
mod handler;
|
||||
pub use handler::IntercomHandler;
|
||||
|
||||
@@ -63,8 +63,21 @@ struct QueueUser {
|
||||
user: std::sync::Arc<Box<dyn oj_rc_core::persist::user::User<()> + Send + Sync>>,
|
||||
}
|
||||
|
||||
struct CustomGameQueue {
|
||||
config: oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameConfig,
|
||||
users: Vec<CustomGameQueueUser>,
|
||||
}
|
||||
|
||||
struct CustomGameQueueUser {
|
||||
public_id: String,
|
||||
team: u8,
|
||||
queue_user: Option<QueueUser>, // if None, the user is not enqueued
|
||||
}
|
||||
|
||||
pub struct QueueHandler {
|
||||
users_in_queue: std::sync::Arc<tokio::sync::Mutex<HashMap<QueueKey, Vec<QueueUser>>>>,
|
||||
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>>>,
|
||||
users_per_game: usize,
|
||||
is_enabled: bool,
|
||||
hostname: String,
|
||||
@@ -84,6 +97,8 @@ impl QueueHandler {
|
||||
let mp_settings = oj_rc_core::ConfigProvider::<()>::multiplayer_settings(conf);
|
||||
Self {
|
||||
users_in_queue: std::sync::Arc::new(tokio::sync::Mutex::new(HashMap::new())),
|
||||
users_in_custom_games_queue: std::sync::Arc::new(tokio::sync::Mutex::new(HashMap::new())),
|
||||
custom_game_for_user: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
|
||||
users_per_game: oj_rc_core::ConfigProvider::<()>::players_per_game(conf),
|
||||
is_enabled: mp_settings.is_enabled,
|
||||
hostname: domain.to_owned(),
|
||||
@@ -220,6 +235,7 @@ impl QueueHandler {
|
||||
is_ranked: false,
|
||||
is_custom: false,
|
||||
is_complete: false,
|
||||
overrides: None,
|
||||
};
|
||||
let team_picker = user.team_chooser(&game_desc).await;
|
||||
/*let team_picker = match key.mode {
|
||||
@@ -272,7 +288,88 @@ impl QueueHandler {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn enter_custom_match(
|
||||
&self,
|
||||
_session_id: &str,
|
||||
session: &mut CustomGameQueue,
|
||||
user: &(dyn oj_rc_core::persist::user::LobbyUser + Send + Sync),
|
||||
) {
|
||||
let mode = match session.config.game_mode {
|
||||
oj_rc_core::persist::user::intercom::CustomGameMode::BattleArena => oj_rc_core::data::game_mode::GameMode::BattleArena,
|
||||
oj_rc_core::persist::user::intercom::CustomGameMode::TeamDeathmatch => oj_rc_core::data::game_mode::GameMode::TeamDeathmatch,
|
||||
oj_rc_core::persist::user::intercom::CustomGameMode::Pit => oj_rc_core::data::game_mode::GameMode::Pit,
|
||||
oj_rc_core::persist::user::intercom::CustomGameMode::SuddenDeath => oj_rc_core::data::game_mode::GameMode::SuddenDeath,
|
||||
};
|
||||
let visibility = match session.config.map_visibility {
|
||||
oj_rc_core::persist::user::intercom::CustomGameVisibility::Good => oj_rc_core::data::game_mode::MapVisibility::Good,
|
||||
oj_rc_core::persist::user::intercom::CustomGameVisibility::Poor => oj_rc_core::data::game_mode::MapVisibility::Poor,
|
||||
oj_rc_core::persist::user::intercom::CustomGameVisibility::Bad => oj_rc_core::data::game_mode::MapVisibility::Bad,
|
||||
};
|
||||
let key = QueueKey {
|
||||
map: session.config.map.clone(),
|
||||
mode,
|
||||
visibility,
|
||||
auto_heal: session.config.health_regen,
|
||||
};
|
||||
let guid_str = key.unique_guid();
|
||||
let game_desc = oj_rc_core::persist::user::GameDescriptor {
|
||||
guid: guid_str.clone(),
|
||||
map: session.config.map.clone(),
|
||||
mode,
|
||||
visibility,
|
||||
auto_heal: session.config.health_regen,
|
||||
is_ranked: false,
|
||||
is_custom: true,
|
||||
is_complete: false,
|
||||
overrides: Some(session.config.as_core()),
|
||||
};
|
||||
let player_descs = session.users.iter()
|
||||
.map(|user| {
|
||||
let q_user = user.queue_user.as_ref().unwrap();
|
||||
oj_rc_core::persist::user::PlayerLobbyDescriptor {
|
||||
user_id: q_user.user_id,
|
||||
team: user.team as i32,
|
||||
group: None,
|
||||
public_id: q_user.player.name.clone(),
|
||||
display_name: q_user.player.display_name.clone(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
match user.start_custom_game(game_desc, player_descs).await {
|
||||
Ok(_) => {
|
||||
let player_datas = session.users.iter()
|
||||
.map(|x| x.queue_user.as_ref().unwrap().player.clone())
|
||||
.collect();
|
||||
let enter_battle_ev = crate::events::battle_enter::BattleEnter {
|
||||
host: self.hostname.clone(),
|
||||
port: self.hostport,
|
||||
map: session.config.map.clone(),
|
||||
mode,
|
||||
guid: guid_str.clone(),
|
||||
is_ranked: false,
|
||||
is_custom: true,
|
||||
visibility: Some(key.visibility),
|
||||
auto_heal: key.auto_heal,
|
||||
player_datas,
|
||||
network_config: self.network_conf.clone(),
|
||||
};
|
||||
let arc_event = std::sync::Arc::new(enter_battle_ev);
|
||||
for user in session.users.iter_mut() {
|
||||
let player = user.queue_user.take().unwrap();
|
||||
tokio::spawn(Self::send_events_to_player(arc_event.clone(), player.emitter.clone()));
|
||||
}
|
||||
log::info!("{} players are entering custom match {}; {}", session.users.len(), guid_str, key.short());
|
||||
},
|
||||
Err(e) => {
|
||||
if let Some(msg) = e.error_msg() {
|
||||
log::error!("Cannot send enter battle events to players since LobbyUser.start_game(...) failed: {} ({})", msg, e.error_code());
|
||||
} else {
|
||||
log::error!("Cannot send enter battle events to players since LobbyUser.start_game(...) failed ({})", e.error_code());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_events_to_player(enter_event: std::sync::Arc<crate::events::battle_enter::BattleEnter>, sender: polariton_server::events::EventEmitter) {
|
||||
@@ -283,6 +380,118 @@ impl QueueHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn join_custom_queue(&self, user: std::sync::Arc<Box<dyn oj_rc_core::persist::user::User<()> + Send + Sync>>, event_emitter: polariton_server::events::EventEmitter) {
|
||||
if !self.is_enabled {
|
||||
event_emitter.emit(crate::events::enqueue_error::QueueJoinError {
|
||||
code: oj_rc_core::data::error_codes::LobbyReasonCode::NoSuitableLobbyFound as i16,
|
||||
text: "Multiplayer is not enabled".to_owned(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
let public_id = user.public_id();
|
||||
if let Some(session_id) = self.custom_game_for_user.read().await.get(public_id) {
|
||||
let mut lock = self.users_in_custom_games_queue.lock().await;
|
||||
let session = lock.get_mut(session_id).unwrap();
|
||||
let lobby_user = user.as_ref().as_ref();
|
||||
match lobby_user.player_data(&self.cpu_counter).await {
|
||||
Ok(mut player_data) => {
|
||||
let target_queuer = session.users.iter_mut()
|
||||
.find(|user| user.public_id == public_id)
|
||||
.unwrap();
|
||||
player_data.team = target_queuer.team as i32;
|
||||
let new_player = QueueUser {
|
||||
emitter: event_emitter,
|
||||
player: player_data,
|
||||
user_id: oj_rc_core::persist::user::LobbyUser::user_id(lobby_user),
|
||||
enqueued_at: chrono::Utc::now(),
|
||||
user: user.clone(),
|
||||
};
|
||||
target_queuer.queue_user = Some(new_player);
|
||||
},
|
||||
Err(e) => {
|
||||
event_emitter.emit(crate::events::enqueue_error::QueueJoinError {
|
||||
code: e.error_code(),
|
||||
text: e.error_msg().map(|x| x.to_owned()).unwrap_or_else(|| "Unknown queue join error".to_owned()),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
let is_all_enqueued = session.users.iter().all(|user| user.queue_user.is_some());
|
||||
if is_all_enqueued {
|
||||
self.enter_custom_match(session_id, session, lobby_user).await;
|
||||
}
|
||||
} else {
|
||||
log::debug!("Rejecting join queue for unknown custom game for user {}", public_id);
|
||||
event_emitter.emit(crate::events::enqueue_error::QueueJoinError {
|
||||
code: oj_rc_core::data::error_codes::LobbyReasonCode::NoSuitableLobbyFound as i16,
|
||||
text: "User is not in a custom game".to_owned(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_custom_queue(&self, session_id: &str) -> bool {
|
||||
log::debug!("Disbanding custom game {}", session_id);
|
||||
self.custom_game_for_user.write().await.retain(|_key, val| val != session_id);
|
||||
let mut lock = self.users_in_custom_games_queue.lock().await;
|
||||
lock.remove(session_id).is_some()
|
||||
}
|
||||
|
||||
pub async fn update_custom_queue(&self, session_id: &str, members: impl std::iter::Iterator<Item = (String, u8)>, config: oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameConfig) -> bool {
|
||||
let mut lock = self.users_in_custom_games_queue.lock().await;
|
||||
let members: std::collections::HashMap<_, _> = members.collect();
|
||||
let mut user_map_lock = self.custom_game_for_user.write().await;
|
||||
if let Some(session) = lock.get_mut(session_id) {
|
||||
log::debug!("Updating existing custom game {}", session_id);
|
||||
let member_ids: std::collections::HashSet<_> = members.keys().collect();
|
||||
// remove users who are no longer part of the custom game
|
||||
user_map_lock.retain(|key, val| (members.contains_key(key) && val == session_id) || (!members.contains_key(key) && val != session_id));
|
||||
session.users.retain(|user| member_ids.contains(&user.public_id));
|
||||
// update team assignments
|
||||
session.users.iter_mut()
|
||||
.for_each(|user| {
|
||||
let new_team = *members.get(&user.public_id).unwrap();
|
||||
user.team = new_team;
|
||||
if let Some(q_user) = &mut user.queue_user {
|
||||
q_user.player.team = new_team as i32;
|
||||
}
|
||||
});
|
||||
// collect users which are still members
|
||||
let existing_ids: std::collections::HashSet<_> = session.users.iter()
|
||||
.map(|mem| mem.public_id.clone())
|
||||
.collect();
|
||||
// add new members
|
||||
for (id, team) in members.iter() {
|
||||
if !existing_ids.contains(id) {
|
||||
session.users.push(CustomGameQueueUser {
|
||||
public_id: id.to_owned(),
|
||||
team: *team,
|
||||
queue_user: None,
|
||||
});
|
||||
user_map_lock.insert(id.to_owned(), session_id.to_owned());
|
||||
}
|
||||
}
|
||||
// update config overrides
|
||||
session.config = config;
|
||||
false
|
||||
} else {
|
||||
log::debug!("Creating new custom game {}", session_id);
|
||||
lock.insert(session_id.to_owned(), CustomGameQueue {
|
||||
config,
|
||||
users: members.iter().map(|(mem_id, team)| {
|
||||
user_map_lock.insert(mem_id.to_owned(), session_id.to_owned());
|
||||
CustomGameQueueUser {
|
||||
public_id: mem_id.to_owned(),
|
||||
team: *team,
|
||||
queue_user: None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
if !self.is_enabled {
|
||||
event_emitter.emit(crate::events::enqueue_error::QueueJoinError {
|
||||
@@ -378,12 +587,23 @@ impl QueueHandler {
|
||||
}
|
||||
|
||||
pub async fn leave_queue(&self, user: std::sync::Arc<Box<dyn oj_rc_core::persist::user::User<()> + Send + Sync>>) {
|
||||
let public_id = user.public_id();
|
||||
let user_id = oj_rc_core::persist::user::LobbyUser::user_id(user.as_ref().as_ref());
|
||||
for (queue_key, queue) in self.users_in_queue.lock().await.iter_mut() {
|
||||
if let Some((i, _)) = queue.iter().enumerate().find(|(_, user)| user.user_id == user_id) {
|
||||
queue.remove(i);
|
||||
log::info!("User {} was removed from queue {}", user_id, queue_key.short());
|
||||
break;
|
||||
if let Some(session_id) = self.custom_game_for_user.read().await.get(public_id) {
|
||||
// player is in custom game session
|
||||
let mut lock = self.users_in_custom_games_queue.lock().await;
|
||||
let session = lock.get_mut(session_id).unwrap();
|
||||
let target = session.users.iter_mut().find(|user| user.public_id == public_id).unwrap();
|
||||
target.queue_user = None;
|
||||
log::info!("User {} was removed from custom game {} queue", user_id, session_id);
|
||||
} else {
|
||||
// fallback to regular multiplayer
|
||||
for (queue_key, queue) in self.users_in_queue.lock().await.iter_mut() {
|
||||
if let Some((i, _)) = queue.iter().enumerate().find(|(_, user)| user.user_id == user_id) {
|
||||
queue.remove(i);
|
||||
log::info!("User {} was removed from queue {}", user_id, queue_key.short());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,11 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(&init_ctx), polariton_server::events::EventsHandler::new()));
|
||||
|
||||
crate::events::IntercomHandler::new(
|
||||
init_ctx.users.clone(),
|
||||
init_ctx.queue.clone(),
|
||||
).run();
|
||||
|
||||
let ip_addr: std::net::IpAddr = args.ip.parse().expect("Invalid IP address");
|
||||
|
||||
let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?;
|
||||
|
||||
@@ -30,24 +30,49 @@ impl <C: Send + 'static> SimpleOperation<C> for QueueJoinProvider {
|
||||
if let Some(Typed::Bool(is_leader)) = params.remove(&IS_GROUP_LEADER_PARAM_KEY) {
|
||||
if let Some(Typed::Int(lobby_ty)) = params.remove(&LOBBY_TY_PARAM_KEY) {
|
||||
let lobby_ty = oj_rc_core::data::lobby::LobbyType::from_int(lobby_ty)?;
|
||||
if let Some(Typed::Str(event_to_join)) = params.remove(&EVENT_TO_JOIN_PARAM_KEY) {
|
||||
log::debug!("Got lobby join queue request of platoon {} ({} players, is_leader:{}) slot {} lobby {:?} event {}", group_id.string, group_size, is_leader, slot_id, lobby_ty, event_to_join.string);
|
||||
params.insert(ESTIMATED_QUEUE_TIME_PARAM_KEY, Typed::Int(42));
|
||||
params.insert(PERSONAL_RANKING_PARAM_KEY, Typed::Double(42.0));
|
||||
let events = user.event_sender();
|
||||
let user_info = user.user()?;
|
||||
if let Some(current_lobby) = user_info.current_game_event_setter().get_multiplayer().await {
|
||||
self.queue_handler.join_queue(
|
||||
current_lobby.map,
|
||||
current_lobby.mode,
|
||||
current_lobby.visibility,
|
||||
current_lobby.auto_heal,
|
||||
log::debug!("Got lobby join queue request of platoon {} ({} players, is_leader:{}) slot {} lobby {:?}", group_id.string, group_size, is_leader, slot_id, lobby_ty);
|
||||
match lobby_ty {
|
||||
oj_rc_core::data::lobby::LobbyType::None => {
|
||||
log::warn!("Cannot join queue for None mode (???)");
|
||||
},
|
||||
oj_rc_core::data::lobby::LobbyType::CustomGame => {
|
||||
log::debug!("Joining platoon {} to custom game lobby", group_id.string);
|
||||
params.insert(ESTIMATED_QUEUE_TIME_PARAM_KEY, Typed::Int(42));
|
||||
params.insert(PERSONAL_RANKING_PARAM_KEY, Typed::Double(42.0));
|
||||
let events = user.event_sender();
|
||||
let user_info = user.user()?;
|
||||
self.queue_handler.join_custom_queue(
|
||||
user_info.clone(),
|
||||
events.to_owned(),
|
||||
).await;
|
||||
}
|
||||
|
||||
},
|
||||
oj_rc_core::data::lobby::LobbyType::QuickPlay => {
|
||||
// regular multiplayer
|
||||
if let Some(Typed::Str(event_to_join)) = params.remove(&EVENT_TO_JOIN_PARAM_KEY) {
|
||||
log::debug!("Joining platoon {} to multiplayer lobby with event {}", group_id.string, event_to_join.string);
|
||||
params.insert(ESTIMATED_QUEUE_TIME_PARAM_KEY, Typed::Int(42));
|
||||
params.insert(PERSONAL_RANKING_PARAM_KEY, Typed::Double(42.0));
|
||||
let events = user.event_sender();
|
||||
let user_info = user.user()?;
|
||||
if let Some(current_lobby) = user_info.current_game_event_setter().get_multiplayer().await {
|
||||
self.queue_handler.join_queue(
|
||||
current_lobby.map,
|
||||
current_lobby.mode,
|
||||
current_lobby.visibility,
|
||||
current_lobby.auto_heal,
|
||||
user_info.clone(),
|
||||
events.to_owned(),
|
||||
).await;
|
||||
}
|
||||
} else {
|
||||
log::warn!("Missing multiplayer event, not joining platoon {} to multiplayer lobby queue", group_id.string);
|
||||
}
|
||||
},
|
||||
oj_rc_core::data::lobby::LobbyType::Solo => {
|
||||
log::warn!("Cannot join queue for solo mode (it's singleplayer!)");
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap_fn(|req, srv| {
|
||||
use actix_web::dev::Service;
|
||||
log::trace!("Request {} {}", req.method(), req.path());
|
||||
srv.call(req)
|
||||
})
|
||||
.app_data(cli_args2.clone())
|
||||
.service(index)
|
||||
.service(robocraft::robopay_store)
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct UserInfo {
|
||||
pub struct SessionInfo {
|
||||
pub session_id: String,
|
||||
pub config: std::collections::HashMap<String, String>,
|
||||
pub config_core: oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameConfig,
|
||||
pub users: Vec<UserInfo>,
|
||||
}
|
||||
|
||||
@@ -36,7 +37,7 @@ struct GameHandle {
|
||||
struct UserHandle {
|
||||
public_id: String,
|
||||
is_invited: std::sync::atomic::AtomicBool,
|
||||
team: u8,
|
||||
team: std::sync::atomic::AtomicU8,
|
||||
status: std::sync::atomic::AtomicU8,
|
||||
}
|
||||
|
||||
@@ -48,7 +49,7 @@ impl CustomGameMesh {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_game(&self, public_id: &str) -> Result<String, SessionCreateResponseCode> {
|
||||
pub async fn create_game(&self, public_id: &str) -> Result<SessionInfo, SessionCreateResponseCode> {
|
||||
let mut user_lock = self.user_to_game.write().await;
|
||||
if user_lock.contains_key(public_id) {
|
||||
log::debug!("Rejected custom game session create; user {} is already in a session", public_id);
|
||||
@@ -58,18 +59,19 @@ impl CustomGameMesh {
|
||||
let owner_handle = UserHandle {
|
||||
public_id: public_id.to_owned(),
|
||||
is_invited: std::sync::atomic::AtomicBool::new(false),
|
||||
team: 0,
|
||||
team: std::sync::atomic::AtomicU8::new(0),
|
||||
status: std::sync::atomic::AtomicU8::new(PlayerSessionStatus::Ready.to_u8()),
|
||||
};
|
||||
let game_handle = GameHandle {
|
||||
users: vec![owner_handle],
|
||||
config: GameConfig::default(),
|
||||
};
|
||||
let session = Self::session_from_game(&game_id, &game_handle);
|
||||
let mut games_lock = self.games.write().await;
|
||||
user_lock.insert(public_id.to_owned(), game_id.clone());
|
||||
games_lock.insert(game_id.clone(), game_handle);
|
||||
log::debug!("Custom game session {} created", game_id);
|
||||
Ok(game_id)
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
pub async fn leave_game(&self, public_id: &str) -> (SessionLeaveResponseCode, Option<SessionInfo>) {
|
||||
@@ -85,6 +87,7 @@ impl CustomGameMesh {
|
||||
}
|
||||
log::debug!("Removed {} invited stragglers from custom game {}", game.users.len() - 1, game_id);
|
||||
}
|
||||
game.users.clear();
|
||||
let session = Self::session_from_game(&game_id, game);
|
||||
games_lock.remove(&game_id);
|
||||
session
|
||||
@@ -123,18 +126,7 @@ impl CustomGameMesh {
|
||||
pub async fn get_user_game(&self, public_id: &str) -> Option<SessionInfo> {
|
||||
if let Some(game_id) = self.user_to_game.read().await.get(public_id) {
|
||||
if let Some(game) = self.games.read().await.get(game_id) {
|
||||
return Some(SessionInfo {
|
||||
session_id: game_id.to_owned(),
|
||||
config: game.config.as_map(),
|
||||
users: game.users.iter()
|
||||
.map(|user| UserInfo {
|
||||
public_id: user.public_id.clone(),
|
||||
is_invited: user.is_invited.load(std::sync::atomic::Ordering::Relaxed),
|
||||
team: user.team,
|
||||
state: PlayerSessionStatus::from_u8(user.status.load(std::sync::atomic::Ordering::Relaxed)).unwrap(),
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
return Some(Self::session_from_game(&game_id, game));
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -150,7 +142,7 @@ impl CustomGameMesh {
|
||||
let invitee_handle = UserHandle {
|
||||
public_id: invitee.to_owned(),
|
||||
is_invited: std::sync::atomic::AtomicBool::new(true),
|
||||
team: if is_team_a { 0 } else { 1 },
|
||||
team: std::sync::atomic::AtomicU8::new(if is_team_a { 0 } else { 1 }),
|
||||
status: std::sync::atomic::AtomicU8::new(PlayerSessionStatus::Unknown.to_u8())
|
||||
};
|
||||
game.users.push(invitee_handle);
|
||||
@@ -215,12 +207,45 @@ impl CustomGameMesh {
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn update_user_team(&self, updater: &str, updatee: &str, is_team_b: bool) -> (ChangeTeamResponseCode, Option<SessionInfo>) {
|
||||
if let Some(game_id) = self.user_to_game.read().await.get(updatee) {
|
||||
if let Some(game) = self.games.read().await.get(game_id) {
|
||||
let leader = game.users.first().unwrap();
|
||||
if leader.public_id != updater {
|
||||
return (ChangeTeamResponseCode::UserIsNotSessionLeader, None);
|
||||
}
|
||||
let target = game.users.iter()
|
||||
.find(|mem| mem.public_id == updatee)
|
||||
.unwrap();
|
||||
let team = if is_team_b { 1 } else { 0 };
|
||||
target.team.store(team, std::sync::atomic::Ordering::Relaxed);
|
||||
let session = Self::session_from_game(&game_id, game);
|
||||
return (ChangeTeamResponseCode::Success, Some(session));
|
||||
}
|
||||
}
|
||||
(ChangeTeamResponseCode::UnknownFail0, None)
|
||||
}
|
||||
|
||||
pub async fn set_config_field(&self, public_id: &str, field: &str, value: &str) -> (AdjustCustomGameConfigResponseCode, Option<SessionInfo>) {
|
||||
if let Some(game_id) = self.user_to_game.read().await.get(public_id) {
|
||||
if let Some(game) = self.games.write().await.get_mut(game_id) {
|
||||
if game.users[0].public_id != public_id {
|
||||
log::debug!("Update custom game session {} config rejected (not leader)", game_id);
|
||||
return (AdjustCustomGameConfigResponseCode::AdjustmentRejected, None);
|
||||
// the first time a user joins/creates a custom game, the game client
|
||||
// automatically tries to set these fields (regardless if the user is not the leader)
|
||||
const STUPID_FIELDS: &[&str] = &[
|
||||
"DamageMultiplier",
|
||||
"HealthMultiplier",
|
||||
"PowerMultiplier",
|
||||
"GameTime",
|
||||
];
|
||||
if STUPID_FIELDS.contains(&field /* &&str lol */) {
|
||||
log::debug!("Update custom game session {} config ignored (not leader; client bug with field {})", game_id, field);
|
||||
return (AdjustCustomGameConfigResponseCode::Success, None);
|
||||
} else {
|
||||
log::debug!("Update custom game session {} config rejected (not leader) {} = {}", game_id, field, value);
|
||||
return (AdjustCustomGameConfigResponseCode::AdjustmentRejected, None);
|
||||
}
|
||||
|
||||
}
|
||||
if let Ok(_) = game.config.set_field(field, value) {
|
||||
log::debug!("Update custom game session {} config {} to {}", game_id, field, value);
|
||||
@@ -238,11 +263,12 @@ impl CustomGameMesh {
|
||||
SessionInfo {
|
||||
session_id: game_id.to_owned(),
|
||||
config: game.config.as_map(),
|
||||
config_core: game.config.as_core(),
|
||||
users: game.users.iter()
|
||||
.map(|user| UserInfo {
|
||||
public_id: user.public_id.clone(),
|
||||
is_invited: user.is_invited.load(std::sync::atomic::Ordering::Relaxed),
|
||||
team: user.team,
|
||||
team: user.team.load(std::sync::atomic::Ordering::Relaxed),
|
||||
state: PlayerSessionStatus::from_u8(user.status.load(std::sync::atomic::Ordering::Relaxed))
|
||||
.unwrap_or(PlayerSessionStatus::Unknown),
|
||||
})
|
||||
@@ -610,4 +636,44 @@ impl GameConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_core(&self) -> oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameConfig {
|
||||
oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameConfig {
|
||||
game_mode: match self.game_mode {
|
||||
oj_rc_core::data::game_mode::GameMode::BattleArena => oj_rc_core::persist::user::intercom::CustomGameMode::BattleArena,
|
||||
oj_rc_core::data::game_mode::GameMode::TeamDeathmatch => oj_rc_core::persist::user::intercom::CustomGameMode::TeamDeathmatch,
|
||||
oj_rc_core::data::game_mode::GameMode::Pit => oj_rc_core::persist::user::intercom::CustomGameMode::Pit,
|
||||
oj_rc_core::data::game_mode::GameMode::SuddenDeath => oj_rc_core::persist::user::intercom::CustomGameMode::SuddenDeath,
|
||||
invalid => {
|
||||
log::warn!("Custom game set to invalid mode {:?} (using sudden death as fallback)", invalid);
|
||||
oj_rc_core::persist::user::intercom::CustomGameMode::SuddenDeath
|
||||
},
|
||||
},
|
||||
map: self.map.clone(),
|
||||
map_visibility: match self.map_visibility {
|
||||
oj_rc_core::data::game_mode::MapVisibility::Good => oj_rc_core::persist::user::intercom::CustomGameVisibility::Good,
|
||||
oj_rc_core::data::game_mode::MapVisibility::Poor => oj_rc_core::persist::user::intercom::CustomGameVisibility::Poor,
|
||||
oj_rc_core::data::game_mode::MapVisibility::Bad => oj_rc_core::persist::user::intercom::CustomGameVisibility::Bad,
|
||||
},
|
||||
health_regen: self.health_regen,
|
||||
capture_segment_memory: self.capture_segment_memory,
|
||||
base_shields_go_down: self.base_shields_go_down,
|
||||
damage_mult: self.damage_mult,
|
||||
health_mult: self.health_mult,
|
||||
power_mult: self.power_mult,
|
||||
game_time: self.game_time,
|
||||
capture_speed: self.capture_speed,
|
||||
points_kill_streak: self.points_kill_streak,
|
||||
points_total_required: self.points_total_required,
|
||||
number_of_kills_to_win: self.number_of_kills_to_win,
|
||||
respawn_time: self.respawn_time,
|
||||
core_appear_frequency: self.core_appear_frequency,
|
||||
core_health_multiplier: self.core_health_multiplier,
|
||||
core_destroy_time: self.core_destroy_time,
|
||||
protonium_harvest: self.protonium_harvest,
|
||||
ceiling_multiplier: self.ceiling_multiplier,
|
||||
min_cpu: self.min_cpu,
|
||||
max_cpu: self.max_cpu,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,17 @@ pub enum CheckCanJoinQueueResponseCode {
|
||||
UserNotInSession5 = 5,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ChangeTeamResponseCode {
|
||||
UnknownFail0 = 0,
|
||||
UserIsNotSessionLeader = 1,
|
||||
UnknownFail2 = 2,
|
||||
UnknownFail3 = 3,
|
||||
Success = 4,
|
||||
UnknownFail5 = 5,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum PlayerSessionStatus {
|
||||
|
||||
@@ -32,6 +32,17 @@ impl <C: Send + 'static> SimpleOperation<C> for CustomGameConfigChanger {
|
||||
.filter(|mem| !mem.is_invited && mem.public_id != my_pub_id)
|
||||
.map(|mem| &mem.public_id as &str);
|
||||
self.mesh.broadcast_event_to(session_members_iter, event).await;
|
||||
user_info.update_custom_game(oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameDataMessage {
|
||||
session_id: session.session_id.clone(),
|
||||
config: session.config_core,
|
||||
users: session.users.iter()
|
||||
.filter(|user| !user.is_invited)
|
||||
.map(|user| oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameUserData {
|
||||
public_id: user.public_id.clone(),
|
||||
team: user.team,
|
||||
})
|
||||
.collect(),
|
||||
}).await;
|
||||
}
|
||||
params.insert(RESPONSE_CODE_PARAM_KEY, Typed::Int(resp_code as _));
|
||||
}
|
||||
|
||||
@@ -22,9 +22,19 @@ impl <C: Send + 'static> SimpleOperation<C> for CustomGameCreator {
|
||||
Err(e) => {
|
||||
params.insert(RESPONSE_CODE_PARAM_KEY, Typed::Int(e as _));
|
||||
},
|
||||
Ok(game_id) => {
|
||||
Ok(session) => {
|
||||
user_info.update_custom_game(oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameDataMessage {
|
||||
session_id: session.session_id.clone(),
|
||||
config: session.config_core,
|
||||
users: vec![
|
||||
oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameUserData {
|
||||
public_id: user_info.public_id().to_owned(),
|
||||
team: 0,
|
||||
}
|
||||
],
|
||||
}).await;
|
||||
params.insert(RESPONSE_CODE_PARAM_KEY, Typed::Int(crate::data::custom_games::SessionCreateResponseCode::SessionCreated as _));
|
||||
params.insert(IDK_PARAM_KEY, Typed::Str(game_id.into()));
|
||||
params.insert(IDK_PARAM_KEY, Typed::Str(session.session_id.into()));
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
|
||||
@@ -30,6 +30,18 @@ impl <C: Send + 'static> SimpleOperation<C> for CustomGameInviteResponder {
|
||||
.filter(|mem| !mem.is_invited && mem.public_id != my_pub_id)
|
||||
.map(|mem| &mem.public_id as &str);
|
||||
self.mesh.broadcast_event_to(session_members_iter, event).await;
|
||||
} else {
|
||||
user_info.update_custom_game(oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameDataMessage {
|
||||
session_id: session.session_id.clone(),
|
||||
config: session.config_core,
|
||||
users: session.users.iter()
|
||||
.filter(|user| !user.is_invited)
|
||||
.map(|user| oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameUserData {
|
||||
public_id: user.public_id.clone(),
|
||||
team: user.team,
|
||||
})
|
||||
.collect()
|
||||
}).await;
|
||||
}
|
||||
let event = crate::events::CustomGameRefresh {
|
||||
session: session.session_id,
|
||||
|
||||
@@ -29,11 +29,22 @@ impl <C: Send + 'static> SimpleOperation<C> for CustomGameMemberKicker {
|
||||
};
|
||||
self.mesh.send_event_to(&user_to_kick.string, kick_event).await;
|
||||
let update_event = crate::events::CustomGameRefresh {
|
||||
session: session.session_id,
|
||||
session: session.session_id.clone(),
|
||||
};
|
||||
let members_iter = session.users.iter()
|
||||
.map(|mem| &mem.public_id as &str);
|
||||
self.mesh.broadcast_event_to(members_iter, update_event).await;
|
||||
user_info.update_custom_game(oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameDataMessage {
|
||||
session_id: session.session_id,
|
||||
config: session.config_core,
|
||||
users: session.users.iter()
|
||||
.filter(|user| !user.is_invited)
|
||||
.map(|user| oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameUserData {
|
||||
public_id: user.public_id.clone(),
|
||||
team: user.team,
|
||||
})
|
||||
.collect()
|
||||
}).await;
|
||||
}
|
||||
params.insert(RESPONSE_CODE_PARAM_KEY, Typed::Int(resp_code as _));
|
||||
}
|
||||
|
||||
@@ -20,11 +20,22 @@ impl <C: Send + 'static> SimpleOperation<C> for CustomGameLeaver {
|
||||
let (resp_code, session_opt) = self.games.leave_game(user_info.public_id()).await;
|
||||
if let Some(session) = session_opt {
|
||||
let event = crate::events::CustomGameRefresh {
|
||||
session: session.session_id,
|
||||
session: session.session_id.clone(),
|
||||
};
|
||||
let session_members = session.users.iter()
|
||||
.map(|mem| &mem.public_id as &str);
|
||||
self.mesh.broadcast_event_to(session_members, event).await;
|
||||
user_info.update_custom_game(oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameDataMessage {
|
||||
session_id: session.session_id,
|
||||
config: session.config_core,
|
||||
users: session.users.iter()
|
||||
.filter(|user| !user.is_invited)
|
||||
.map(|user| oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameUserData {
|
||||
public_id: user.public_id.clone(),
|
||||
team: user.team,
|
||||
})
|
||||
.collect()
|
||||
}).await;
|
||||
}
|
||||
params.insert(RESPONSE_CODE_PARAM_KEY, Typed::Int(resp_code as _));
|
||||
Ok(params)
|
||||
|
||||
59
rc_services_room/src/operations/custom_game_team.rs
Normal file
59
rc_services_room/src/operations/custom_game_team.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed};
|
||||
|
||||
const CODE: u8 = 151;
|
||||
|
||||
const USERNAME_PARAM_KEY: u8 = 185; // str; in
|
||||
//const CHANGER_PARAM_KEY: u8 = 186; // always null?; in
|
||||
const IS_TEAM_B_PARAM_KEY: u8 = 187; // bool; in
|
||||
const RESPONSE_CODE_PARAM_KEY: u8 = 168; // int enum; out
|
||||
|
||||
pub(super) struct CustomGameTeamChanger {
|
||||
games: std::sync::Arc<crate::custom_game_tracker::CustomGameMesh>,
|
||||
mesh: std::sync::Arc<crate::user_service::UserMesh>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl <C: Send + 'static> SimpleOperation<C> for CustomGameTeamChanger {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable<C>, user: &Self::User) -> Result<ParameterTable<C>, SimpleOpError> {
|
||||
if let Some(Typed::Str(username)) = params.remove(&USERNAME_PARAM_KEY) {
|
||||
if let Some(Typed::Bool(is_team_b)) = params.remove(&IS_TEAM_B_PARAM_KEY) {
|
||||
let user_info = user.user()?;
|
||||
let my_pub_id = user_info.public_id();
|
||||
let (resp_code, session_opt) = self.games.update_user_team(my_pub_id, &username.string, is_team_b).await;
|
||||
if let Some(session) = session_opt {
|
||||
let event = crate::events::CustomGameRefresh {
|
||||
session: session.session_id.clone(),
|
||||
};
|
||||
let session_members_iter = session.users.iter()
|
||||
.filter(|mem| !mem.is_invited)
|
||||
.map(|mem| &mem.public_id as &str);
|
||||
self.mesh.broadcast_event_to(session_members_iter, event).await;
|
||||
user_info.update_custom_game(oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameDataMessage {
|
||||
session_id: session.session_id,
|
||||
config: session.config_core,
|
||||
users: session.users.iter()
|
||||
.filter(|user| !user.is_invited)
|
||||
.map(|user| oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameUserData {
|
||||
public_id: user.public_id.clone(),
|
||||
team: user.team,
|
||||
})
|
||||
.collect()
|
||||
}).await;
|
||||
}
|
||||
params.insert(RESPONSE_CODE_PARAM_KEY, Typed::Int(resp_code as _));
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn game_team_change_provider<C: Send + 'static>(init_ctx: &crate::InitConfig) -> SimpleOpImpl<C, crate::UserTy, CustomGameTeamChanger> {
|
||||
SimpleOpImpl::new(CustomGameTeamChanger {
|
||||
games: init_ctx.custom_games.clone(),
|
||||
mesh: init_ctx.user_mesh.clone(),
|
||||
})
|
||||
}
|
||||
@@ -116,6 +116,7 @@ mod custom_game_invite_respond;
|
||||
mod custom_game_kick;
|
||||
mod custom_game_player_state;
|
||||
mod custom_game_can_join_queue;
|
||||
mod custom_game_team;
|
||||
|
||||
use polariton_server::operations::OperationsHandler;
|
||||
|
||||
@@ -256,4 +257,5 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
||||
.add(custom_game_kick::game_kick_provider(init_ctx))
|
||||
.add(custom_game_player_state::game_player_status_update_provider(init_ctx))
|
||||
.add(custom_game_can_join_queue::game_can_queue_provider(init_ctx))
|
||||
.add(custom_game_team::game_team_change_provider(init_ctx))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user