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:
@@ -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