mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add workaround to fix #127
This commit is contained in:
@@ -14528,6 +14528,15 @@
|
|||||||
},
|
},
|
||||||
"permission": "Administrator"
|
"permission": "Administrator"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"regex": "\\?klf",
|
||||||
|
"op": {
|
||||||
|
"type": "BuiltIn",
|
||||||
|
"built_in": "Intercom",
|
||||||
|
"intercom": "KeybindLockFix"
|
||||||
|
},
|
||||||
|
"permission": "Player"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"regex": "\\?help",
|
"regex": "\\?help",
|
||||||
"op": {
|
"op": {
|
||||||
|
|||||||
@@ -269,6 +269,7 @@ enum Intercom {
|
|||||||
DevMessage,
|
DevMessage,
|
||||||
DevBroadcast,
|
DevBroadcast,
|
||||||
Maintenance,
|
Maintenance,
|
||||||
|
KeybindLockFix, // https://git.ngram.ca/OpenJam/rc-servers/issues/127
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Intercom {
|
impl Intercom {
|
||||||
@@ -277,6 +278,7 @@ impl Intercom {
|
|||||||
oj_rc_core::persist::IntercomChatOperation::DevMessage => Self::DevMessage,
|
oj_rc_core::persist::IntercomChatOperation::DevMessage => Self::DevMessage,
|
||||||
oj_rc_core::persist::IntercomChatOperation::DevBroadcast => Self::DevBroadcast,
|
oj_rc_core::persist::IntercomChatOperation::DevBroadcast => Self::DevBroadcast,
|
||||||
oj_rc_core::persist::IntercomChatOperation::Maintenance => Self::Maintenance,
|
oj_rc_core::persist::IntercomChatOperation::Maintenance => Self::Maintenance,
|
||||||
|
oj_rc_core::persist::IntercomChatOperation::KeybindLockFix => Self::KeybindLockFix,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +319,13 @@ impl Intercom {
|
|||||||
} else {
|
} else {
|
||||||
"Missing maintenance message, did not send".to_owned()
|
"Missing maintenance message, did not send".to_owned()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Self::KeybindLockFix => {
|
||||||
|
ctx.user.trigger_workaround(
|
||||||
|
oj_rc_core::persist::user::intercom::IntercomWorkaroundMessage::KeybindLockout { },
|
||||||
|
vec![ctx.user.public_id().to_owned()]
|
||||||
|
).await;
|
||||||
|
"Triggered key lockout workaround".to_owned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,6 +336,7 @@ impl Intercom {
|
|||||||
Self::DevMessage => "Show dev message to yourself".to_owned(),
|
Self::DevMessage => "Show dev message to yourself".to_owned(),
|
||||||
Self::DevBroadcast => "Show dev message to everyone".to_owned(),
|
Self::DevBroadcast => "Show dev message to everyone".to_owned(),
|
||||||
Self::Maintenance => "Broadcast maintenance mode to everyone".to_owned(),
|
Self::Maintenance => "Broadcast maintenance mode to everyone".to_owned(),
|
||||||
|
Self::KeybindLockFix => "Trigger a custom game invite to fix the broken client UI; more info https://git.ngram.ca/OpenJam/rc-servers/issues/127".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ pub enum IntercomChatOperation {
|
|||||||
DevMessage,
|
DevMessage,
|
||||||
DevBroadcast,
|
DevBroadcast,
|
||||||
Maintenance,
|
Maintenance,
|
||||||
|
KeybindLockFix, // https://git.ngram.ca/OpenJam/rc-servers/issues/127
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
|||||||
@@ -118,6 +118,18 @@ impl super::IntercomUser for super::account_json::UserData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn trigger_workaround(&self, msg: IntercomWorkaroundMessage, to: Vec<String>) {
|
||||||
|
let send_to_everyone = to.is_empty();
|
||||||
|
let data = IntercomWebServiceMessage {
|
||||||
|
public_ids: to,
|
||||||
|
data: IntercomWebServiceUserMessage::Workaround(msg),
|
||||||
|
everyone: send_to_everyone,
|
||||||
|
};
|
||||||
|
if let Err(e) = self.post_to_intercom(&data, ".oj_services", "messages").await {
|
||||||
|
log::error!("Failed to send intercom workaround message: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn update_custom_game(&self, msg: IntercomLobbyCustomGameDataMessage) {
|
async fn update_custom_game(&self, msg: IntercomLobbyCustomGameDataMessage) {
|
||||||
let data = IntercomLobbyStateMessage::CustomGame(msg);
|
let data = IntercomLobbyStateMessage::CustomGame(msg);
|
||||||
if let Err(e) = self.post_to_intercom(&data, ".oj_lobby", "state").await {
|
if let Err(e) = self.post_to_intercom(&data, ".oj_lobby", "state").await {
|
||||||
@@ -229,6 +241,7 @@ pub struct IntercomWebServiceMessage {
|
|||||||
pub enum IntercomWebServiceUserMessage {
|
pub enum IntercomWebServiceUserMessage {
|
||||||
DevMessage(IntercomDevMessage),
|
DevMessage(IntercomDevMessage),
|
||||||
Maintenance(IntercomMaintenanceMessage),
|
Maintenance(IntercomMaintenanceMessage),
|
||||||
|
Workaround(IntercomWorkaroundMessage),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
@@ -242,6 +255,14 @@ pub struct IntercomMaintenanceMessage {
|
|||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(tag = "workaround")]
|
||||||
|
pub enum IntercomWorkaroundMessage {
|
||||||
|
/// Trigger fix for getting stuck in build mode due to a bad/slow connection
|
||||||
|
/// more info: https://git.ngram.ca/OpenJam/rc-servers/issues/127
|
||||||
|
KeybindLockout { },
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate_token(salt: &[u8], key: &[u8]) -> String {
|
pub fn generate_token(salt: &[u8], key: &[u8]) -> String {
|
||||||
use sha2::{Digest, Sha512};
|
use sha2::{Digest, Sha512};
|
||||||
let mut hasher = Sha512::new();
|
let mut hasher = Sha512::new();
|
||||||
|
|||||||
@@ -434,6 +434,7 @@ pub trait IntercomUser: CommonUser {
|
|||||||
async fn webservice_listener(&self) -> Result<IntercomListener<super::intercom::IntercomWebServiceUserMessage>, polariton_server::operations::SimpleOpError>;
|
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 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 enter_maintenance(&self, msg: super::intercom::IntercomMaintenanceMessage, to: Vec<String>);
|
||||||
|
async fn trigger_workaround(&self, msg: super::intercom::IntercomWorkaroundMessage, to: Vec<String>);
|
||||||
async fn update_custom_game(&self, msg: super::intercom::IntercomLobbyCustomGameDataMessage);
|
async fn update_custom_game(&self, msg: super::intercom::IntercomLobbyCustomGameDataMessage);
|
||||||
async fn update_status(&self, server_name: &str, msg: oj_serdes::ServerStatus);
|
async fn update_status(&self, server_name: &str, msg: oj_serdes::ServerStatus);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,6 +307,14 @@ struct GameConfig {
|
|||||||
max_cpu: i32,
|
max_cpu: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn game_config_default_map() -> std::collections::HashMap<String, String> {
|
||||||
|
GameConfig::default().as_map()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_config_default_core() -> oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameConfig {
|
||||||
|
GameConfig::default().as_core()
|
||||||
|
}
|
||||||
|
|
||||||
impl core::default::Default for GameConfig {
|
impl core::default::Default for GameConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use oj_rc_core::persist::user::IntercomListener;
|
use oj_rc_core::persist::user::IntercomListener;
|
||||||
use oj_rc_core::persist::user::intercom::IntercomWebServiceUserMessage;
|
use oj_rc_core::persist::user::intercom::{IntercomWebServiceUserMessage, IntercomWorkaroundMessage};
|
||||||
|
|
||||||
pub struct IntercomHandler {
|
pub struct IntercomHandler {
|
||||||
listener: IntercomListener<IntercomWebServiceUserMessage>,
|
listener: IntercomListener<IntercomWebServiceUserMessage>,
|
||||||
user: std::sync::Weak<Box<dyn oj_rc_core::persist::user::User<()> + Send + Sync>>,
|
user: std::sync::Weak<Box<dyn oj_rc_core::persist::user::User<()> + Send + Sync>>,
|
||||||
emitter: polariton_server::events::WeakEventEmitter<()>,
|
emitter: polariton_server::events::WeakEventEmitter<()>,
|
||||||
|
keybind_workaround: std::sync::Arc<crate::workarounds::EditModeInputLockupWorkaround>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntercomHandler {
|
impl IntercomHandler {
|
||||||
@@ -12,25 +13,28 @@ impl IntercomHandler {
|
|||||||
listener: IntercomListener<IntercomWebServiceUserMessage>,
|
listener: IntercomListener<IntercomWebServiceUserMessage>,
|
||||||
user: &std::sync::Arc<Box<dyn oj_rc_core::persist::user::User<()> + Send + Sync>>,
|
user: &std::sync::Arc<Box<dyn oj_rc_core::persist::user::User<()> + Send + Sync>>,
|
||||||
emitter: &polariton_server::events::EventEmitter<()>,
|
emitter: &polariton_server::events::EventEmitter<()>,
|
||||||
|
keybind_workaround: &std::sync::Arc<crate::workarounds::EditModeInputLockupWorkaround>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
listener,
|
listener,
|
||||||
user: std::sync::Arc::downgrade(user),
|
user: std::sync::Arc::downgrade(user),
|
||||||
emitter: emitter.to_owned().downgrade(),
|
emitter: emitter.to_owned().downgrade(),
|
||||||
|
keybind_workaround: keybind_workaround.to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_loop(
|
async fn run_loop(
|
||||||
listener: IntercomListener<IntercomWebServiceUserMessage>,
|
listener: IntercomListener<IntercomWebServiceUserMessage>,
|
||||||
user: std::sync::Weak<Box<dyn oj_rc_core::persist::user::User<()> + Send + Sync>>,
|
user: std::sync::Weak<Box<dyn oj_rc_core::persist::user::User<()> + Send + Sync>>,
|
||||||
emitter: polariton_server::events::WeakEventEmitter<()>
|
emitter: polariton_server::events::WeakEventEmitter<()>,
|
||||||
|
keybind_workaround: std::sync::Arc<crate::workarounds::EditModeInputLockupWorkaround>,
|
||||||
) {
|
) {
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
let mut listener = listener.listen().await;
|
let mut listener = listener.listen().await;
|
||||||
while let Some(msg) = listener.next().await {
|
while let Some(msg) = listener.next().await {
|
||||||
match msg {
|
match msg {
|
||||||
Ok(msg) => {
|
Ok(msg) => {
|
||||||
if let Some(_user) = user.upgrade() {
|
if let Some(user) = user.upgrade() {
|
||||||
match msg {
|
match msg {
|
||||||
IntercomWebServiceUserMessage::DevMessage(msg) => {
|
IntercomWebServiceUserMessage::DevMessage(msg) => {
|
||||||
let clear_event = super::DevMessage {
|
let clear_event = super::DevMessage {
|
||||||
@@ -50,6 +54,18 @@ impl IntercomHandler {
|
|||||||
};
|
};
|
||||||
emitter.emit(event);
|
emitter.emit(event);
|
||||||
}
|
}
|
||||||
|
IntercomWebServiceUserMessage::Workaround(IntercomWorkaroundMessage::KeybindLockout { }) => {
|
||||||
|
let session = keybind_workaround.add_user(user.account_id(), user.public_id().to_owned()).await;
|
||||||
|
let non_me = session.users.iter().next().unwrap();
|
||||||
|
let event = super::CustomGameInvite {
|
||||||
|
inviter_public_id: non_me.public_id.clone(),
|
||||||
|
inviter_display_name: non_me.public_id.clone(),
|
||||||
|
session: session.session_id,
|
||||||
|
avatar_id: Some(6),
|
||||||
|
invited_to_team_a: true,
|
||||||
|
};
|
||||||
|
emitter.emit(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
@@ -64,6 +80,6 @@ impl IntercomHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(self) -> tokio::task::JoinHandle<()> {
|
pub fn run(self) -> tokio::task::JoinHandle<()> {
|
||||||
tokio::spawn(Self::run_loop(self.listener, self.user, self.emitter))
|
tokio::spawn(Self::run_loop(self.listener, self.user, self.emitter, self.keybind_workaround))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ mod operations;
|
|||||||
mod vehicle_validators;
|
mod vehicle_validators;
|
||||||
mod custom_game_tracker;
|
mod custom_game_tracker;
|
||||||
mod user_service;
|
mod user_service;
|
||||||
|
mod workarounds;
|
||||||
|
|
||||||
use oj_polariton_auth::Handshake;
|
use oj_polariton_auth::Handshake;
|
||||||
use tokio::net;
|
use tokio::net;
|
||||||
@@ -29,6 +30,7 @@ pub struct InitConfig {
|
|||||||
pub vehicle_validators: vehicle_validators::InitedVehicleValidators,
|
pub vehicle_validators: vehicle_validators::InitedVehicleValidators,
|
||||||
pub custom_games: std::sync::Arc<custom_game_tracker::CustomGameMesh>,
|
pub custom_games: std::sync::Arc<custom_game_tracker::CustomGameMesh>,
|
||||||
pub user_mesh: std::sync::Arc<user_service::UserMesh>,
|
pub user_mesh: std::sync::Arc<user_service::UserMesh>,
|
||||||
|
pub workarounds: workarounds::Workarounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -55,6 +57,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
vehicle_validators,
|
vehicle_validators,
|
||||||
custom_games: std::sync::Arc::new(custom_game_tracker::CustomGameMesh::new()),
|
custom_games: std::sync::Arc::new(custom_game_tracker::CustomGameMesh::new()),
|
||||||
user_mesh: std::sync::Arc::new(user_service::UserMesh::new()),
|
user_mesh: std::sync::Arc::new(user_service::UserMesh::new()),
|
||||||
|
workarounds: workarounds::Workarounds::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(&init_ctx), polariton_server::events::EventsHandler::new()));
|
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(&init_ctx), polariton_server::events::EventsHandler::new()));
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const RESPONSE_CODE_PARAM_KEY: u8 = 168; // int enum; out
|
|||||||
pub(super) struct CustomGameInviteResponder {
|
pub(super) struct CustomGameInviteResponder {
|
||||||
games: std::sync::Arc<crate::custom_game_tracker::CustomGameMesh>,
|
games: std::sync::Arc<crate::custom_game_tracker::CustomGameMesh>,
|
||||||
mesh: std::sync::Arc<crate::user_service::UserMesh>,
|
mesh: std::sync::Arc<crate::user_service::UserMesh>,
|
||||||
|
keylock_workaround: std::sync::Arc<crate::workarounds::EditModeInputLockupWorkaround>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -20,38 +21,59 @@ impl <C: Send + 'static> SimpleOperation<C> for CustomGameInviteResponder {
|
|||||||
if let Some(Typed::Bool(is_accept)) = params.remove(&ACCEPT_PARAM_KEY) {
|
if let Some(Typed::Bool(is_accept)) = params.remove(&ACCEPT_PARAM_KEY) {
|
||||||
let user_info = user.user()?;
|
let user_info = user.user()?;
|
||||||
let my_pub_id = user_info.public_id();
|
let my_pub_id = user_info.public_id();
|
||||||
let (resp_code, session_opt) = self.games.update_invite_user(my_pub_id, is_accept).await;
|
if let Some(session) = self.keylock_workaround.get_user(user_info.account_id(), my_pub_id).await {
|
||||||
if let Some(session) = session_opt {
|
if is_accept {
|
||||||
if !is_accept {
|
self.keylock_workaround.accept_invite(user_info.account_id()).await;
|
||||||
let event = crate::events::CustomGameInviteDecline {
|
let keylock_workaround = self.keylock_workaround.clone();
|
||||||
public_id: my_pub_id.to_owned(),
|
let account_id = user_info.account_id();
|
||||||
|
let my_owned_pub_id = my_pub_id.to_owned();
|
||||||
|
let mesh = self.mesh.clone();
|
||||||
|
tokio::task::spawn(async move {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(5_000)).await;
|
||||||
|
keylock_workaround.remove_user(account_id).await;
|
||||||
|
mesh.send_event_to(&my_owned_pub_id, crate::events::CustomGameKick {
|
||||||
|
session: session.session_id,
|
||||||
|
was_invited: false,
|
||||||
|
}).await;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.keylock_workaround.remove_user(user_info.account_id()).await;
|
||||||
|
}
|
||||||
|
params.insert(RESPONSE_CODE_PARAM_KEY, Typed::Int(crate::data::custom_games::InviteReplyCustomGameResponseCode::Success as _));
|
||||||
|
} else {
|
||||||
|
let (resp_code, session_opt) = self.games.update_invite_user(my_pub_id, is_accept).await;
|
||||||
|
if let Some(session) = session_opt {
|
||||||
|
if !is_accept {
|
||||||
|
let event = crate::events::CustomGameInviteDecline {
|
||||||
|
public_id: my_pub_id.to_owned(),
|
||||||
|
};
|
||||||
|
let session_members_iter = session.users.iter()
|
||||||
|
.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,
|
||||||
};
|
};
|
||||||
let session_members_iter = session.users.iter()
|
let other_session_members_iter = session.users.iter()
|
||||||
.filter(|mem| !mem.is_invited && mem.public_id != my_pub_id)
|
.filter(|mem| !mem.is_invited && mem.public_id != my_pub_id)
|
||||||
.map(|mem| &mem.public_id as &str);
|
.map(|mem| &mem.public_id as &str);
|
||||||
self.mesh.broadcast_event_to(session_members_iter, event).await;
|
self.mesh.broadcast_event_to(other_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 {
|
params.insert(RESPONSE_CODE_PARAM_KEY, Typed::Int(resp_code as _));
|
||||||
session: session.session_id,
|
};
|
||||||
};
|
|
||||||
let other_session_members_iter = session.users.iter()
|
|
||||||
.filter(|mem| !mem.is_invited && mem.public_id != my_pub_id)
|
|
||||||
.map(|mem| &mem.public_id as &str);
|
|
||||||
self.mesh.broadcast_event_to(other_session_members_iter, event).await;
|
|
||||||
}
|
|
||||||
params.insert(RESPONSE_CODE_PARAM_KEY, Typed::Int(resp_code as _));
|
|
||||||
}
|
}
|
||||||
Ok(params)
|
Ok(params)
|
||||||
}
|
}
|
||||||
@@ -61,5 +83,6 @@ pub(super) fn game_invite_respond_provider<C: Send + 'static>(init_ctx: &crate::
|
|||||||
SimpleOpImpl::new(CustomGameInviteResponder {
|
SimpleOpImpl::new(CustomGameInviteResponder {
|
||||||
games: init_ctx.custom_games.clone(),
|
games: init_ctx.custom_games.clone(),
|
||||||
mesh: init_ctx.user_mesh.clone(),
|
mesh: init_ctx.user_mesh.clone(),
|
||||||
|
keylock_workaround: init_ctx.workarounds.edit_mode_input_lockup(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const RESPONSE_DATA_PARAM_KEY: u8 = 169; // hashtable; out
|
|||||||
|
|
||||||
pub(super) struct CustomGameRetriever {
|
pub(super) struct CustomGameRetriever {
|
||||||
games: std::sync::Arc<crate::custom_game_tracker::CustomGameMesh>,
|
games: std::sync::Arc<crate::custom_game_tracker::CustomGameMesh>,
|
||||||
|
keylock_workaround: std::sync::Arc<crate::workarounds::EditModeInputLockupWorkaround>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -18,7 +19,15 @@ impl <C: Send + 'static> SimpleOperation<C> for CustomGameRetriever {
|
|||||||
async fn handle(&self, mut params: ParameterTable<C>, user: &Self::User) -> Result<ParameterTable<C>, SimpleOpError> {
|
async fn handle(&self, mut params: ParameterTable<C>, user: &Self::User) -> Result<ParameterTable<C>, SimpleOpError> {
|
||||||
let user_info = user.user()?;
|
let user_info = user.user()?;
|
||||||
let my_pub_id = user_info.public_id();
|
let my_pub_id = user_info.public_id();
|
||||||
let game_opt = self.games.get_user_game(my_pub_id).await;
|
let mut is_workaround = false;
|
||||||
|
let game_opt = if let Some(session) = self.keylock_workaround.get_user(user_info.account_id(), my_pub_id).await {
|
||||||
|
is_workaround = true;
|
||||||
|
Some(session)
|
||||||
|
} else if let Some(session) = self.games.get_user_game(my_pub_id).await {
|
||||||
|
Some(session)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
if let Some(game) = game_opt {
|
if let Some(game) = game_opt {
|
||||||
log::debug!("User {} retrieved their custom game session {} info", my_pub_id, game.session_id);
|
log::debug!("User {} retrieved their custom game session {} info", my_pub_id, game.session_id);
|
||||||
params.insert(RESPONSE_CODE_PARAM_KEY, Typed::Int(crate::data::custom_games::SessionRetrieveResponse::SessionRetrieved as _));
|
params.insert(RESPONSE_CODE_PARAM_KEY, Typed::Int(crate::data::custom_games::SessionRetrieveResponse::SessionRetrieved as _));
|
||||||
@@ -26,8 +35,17 @@ impl <C: Send + 'static> SimpleOperation<C> for CustomGameRetriever {
|
|||||||
.map(|member| member.public_id.clone())
|
.map(|member| member.public_id.clone())
|
||||||
.collect();
|
.collect();
|
||||||
let avatars = user_info.list_avatar_info(&pub_ids).await?;
|
let avatars = user_info.list_avatar_info(&pub_ids).await?;
|
||||||
let avatar_map: std::collections::HashMap<_, _> = avatars.into_iter()
|
let mut avatar_map: std::collections::HashMap<_, _> = avatars.into_iter()
|
||||||
.map(|avatar| (avatar.public_id.clone(), avatar)).collect();
|
.map(|avatar| (avatar.public_id.clone(), avatar)).collect();
|
||||||
|
if is_workaround {
|
||||||
|
let leader = game.users.first().unwrap();
|
||||||
|
avatar_map.insert(leader.public_id.clone(), oj_rc_core::persist::user::SocialInfo {
|
||||||
|
public_id: leader.public_id.clone(),
|
||||||
|
display_name: leader.public_id.clone(),
|
||||||
|
avatar_id: Some(6),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let avatar_map = avatar_map;
|
||||||
let resp = crate::data::custom_games::Session {
|
let resp = crate::data::custom_games::Session {
|
||||||
leader: game.users.first().map(|leader| leader.public_id.clone()).unwrap_or_default(),
|
leader: game.users.first().map(|leader| leader.public_id.clone()).unwrap_or_default(),
|
||||||
session: game.session_id,
|
session: game.session_id,
|
||||||
@@ -63,9 +81,13 @@ impl <C: Send + 'static> SimpleOperation<C> for CustomGameRetriever {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn custom_session_provider<C: Send + 'static>(games: &std::sync::Arc<crate::custom_game_tracker::CustomGameMesh>) -> SimpleOpImpl<C, crate::UserTy, CustomGameRetriever> {
|
pub(super) fn custom_session_provider<C: Send + 'static>(
|
||||||
|
games: &std::sync::Arc<crate::custom_game_tracker::CustomGameMesh>,
|
||||||
|
keylock_workaround: std::sync::Arc<crate::workarounds::EditModeInputLockupWorkaround>,
|
||||||
|
) -> SimpleOpImpl<C, crate::UserTy, CustomGameRetriever> {
|
||||||
SimpleOpImpl::new(CustomGameRetriever {
|
SimpleOpImpl::new(CustomGameRetriever {
|
||||||
games: games.to_owned(),
|
games: games.to_owned(),
|
||||||
|
keylock_workaround,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const INVITE_PARAM_KEY: u8 = 189; // hashtable (refer to C# CheckIfHasBeenInvite
|
|||||||
|
|
||||||
pub(super) struct CustomGamePendingInvites {
|
pub(super) struct CustomGamePendingInvites {
|
||||||
games: std::sync::Arc<crate::custom_game_tracker::CustomGameMesh>,
|
games: std::sync::Arc<crate::custom_game_tracker::CustomGameMesh>,
|
||||||
|
keylock_workaround: std::sync::Arc<crate::workarounds::EditModeInputLockupWorkaround>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -20,14 +21,33 @@ impl <C: Send + 'static> SimpleOperation<C> for CustomGamePendingInvites {
|
|||||||
async fn handle(&self, mut params: ParameterTable<C>, user: &Self::User) -> Result<ParameterTable<C>, SimpleOpError> {
|
async fn handle(&self, mut params: ParameterTable<C>, user: &Self::User) -> Result<ParameterTable<C>, SimpleOpError> {
|
||||||
let user_info = user.user()?;
|
let user_info = user.user()?;
|
||||||
let my_pub_id = user_info.public_id();
|
let my_pub_id = user_info.public_id();
|
||||||
if let Some(session) = self.games.get_user_game(my_pub_id).await {
|
let mut is_workaround = false;
|
||||||
|
let session = if let Some(session) = self.keylock_workaround.get_user(user_info.account_id(), my_pub_id).await {
|
||||||
|
log::debug!("User {} is in keylock workaround mode ({})", my_pub_id, session.session_id);
|
||||||
|
is_workaround = true;
|
||||||
|
Some(session)
|
||||||
|
} else if let Some(session) = self.games.get_user_game(my_pub_id).await {
|
||||||
|
Some(session)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(session) = session {
|
||||||
let myself = session.users.iter().find(|u| u.public_id == my_pub_id).unwrap();
|
let myself = session.users.iter().find(|u| u.public_id == my_pub_id).unwrap();
|
||||||
if myself.is_invited {
|
if myself.is_invited {
|
||||||
log::debug!("User {} has checked and is invited to custom game {}", my_pub_id, session.session_id);
|
log::debug!("User {} has checked and is invited to custom game {}", my_pub_id, session.session_id);
|
||||||
params.insert(RESULT_CODE_PARAM_KEY, Typed::Int(CustomGameInviteCode::PendingInvite as _));
|
params.insert(RESULT_CODE_PARAM_KEY, Typed::Int(CustomGameInviteCode::PendingInvite as _));
|
||||||
// build invite data
|
// build invite data
|
||||||
let leader = session.users.first().unwrap();
|
let leader = session.users.first().unwrap();
|
||||||
let leader_avatar = user_info.list_avatar_info(std::slice::from_ref(&leader.public_id)).await?;
|
let leader_avatar = if is_workaround {
|
||||||
|
vec![oj_rc_core::persist::user::SocialInfo {
|
||||||
|
public_id: leader.public_id.clone(),
|
||||||
|
display_name: leader.public_id.clone(),
|
||||||
|
avatar_id: Some(6),
|
||||||
|
}]
|
||||||
|
} else {
|
||||||
|
user_info.list_avatar_info(std::slice::from_ref(&leader.public_id)).await?
|
||||||
|
};
|
||||||
|
|
||||||
let resp = CustomGameInvite {
|
let resp = CustomGameInvite {
|
||||||
inviter_public_id: leader_avatar[0].public_id.clone(),
|
inviter_public_id: leader_avatar[0].public_id.clone(),
|
||||||
inviter_display_name: leader_avatar[0].display_name.clone(),
|
inviter_display_name: leader_avatar[0].display_name.clone(),
|
||||||
@@ -51,6 +71,7 @@ impl <C: Send + 'static> SimpleOperation<C> for CustomGamePendingInvites {
|
|||||||
pub(super) fn pending_invite_provider<C: Send + 'static>(init_ctx: &crate::InitConfig) -> SimpleOpImpl<C, crate::UserTy, CustomGamePendingInvites> {
|
pub(super) fn pending_invite_provider<C: Send + 'static>(init_ctx: &crate::InitConfig) -> SimpleOpImpl<C, crate::UserTy, CustomGamePendingInvites> {
|
||||||
SimpleOpImpl::new(CustomGamePendingInvites {
|
SimpleOpImpl::new(CustomGamePendingInvites {
|
||||||
games: init_ctx.custom_games.clone(),
|
games: init_ctx.custom_games.clone(),
|
||||||
|
keylock_workaround: init_ctx.workarounds.edit_mode_input_lockup(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
|||||||
OperationsHandler::new()
|
OperationsHandler::new()
|
||||||
.modify(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::polariton_operation_modifier(&init_ctx.cubes))
|
.modify(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::polariton_operation_modifier(&init_ctx.cubes))
|
||||||
.add(eac::EacChallengeIgnorer)
|
.add(eac::EacChallengeIgnorer)
|
||||||
.add(more_auth::more_auth_provider(&init_ctx.user_mesh))
|
.add(more_auth::more_auth_provider(&init_ctx.user_mesh, init_ctx.workarounds.edit_mode_input_lockup()))
|
||||||
.add(versioner::version_teller(&init_ctx.cubes))
|
.add(versioner::version_teller(&init_ctx.cubes))
|
||||||
.add(maintenancer::maintenace_teller(&init_ctx.cubes))
|
.add(maintenancer::maintenace_teller(&init_ctx.cubes))
|
||||||
.add(game_quality::QualityConfigTeller)
|
.add(game_quality::QualityConfigTeller)
|
||||||
@@ -163,7 +163,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
|||||||
.add(dev_message::dev_message_provider(&init_ctx.cubes))
|
.add(dev_message::dev_message_provider(&init_ctx.cubes))
|
||||||
.add(custom_games_maps::allowed_maps_provider())
|
.add(custom_games_maps::allowed_maps_provider())
|
||||||
.add(avatar_info::avatar_get_provider())
|
.add(avatar_info::avatar_get_provider())
|
||||||
.add(custom_game_session::custom_session_provider(&init_ctx.custom_games))
|
.add(custom_game_session::custom_session_provider(&init_ctx.custom_games, init_ctx.workarounds.edit_mode_input_lockup()))
|
||||||
.add(user_xp::get_user_xp_provider())
|
.add(user_xp::get_user_xp_provider())
|
||||||
.add(garage_upgrades::garage_upgrades_provider(&init_ctx.cubes))
|
.add(garage_upgrades::garage_upgrades_provider(&init_ctx.cubes))
|
||||||
.add(game_event_params::event_system_params_provider(&init_ctx.cubes))
|
.add(game_event_params::event_system_params_provider(&init_ctx.cubes))
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ use polariton_server::operations::{Operation, OperationCode};
|
|||||||
|
|
||||||
pub struct MoreLobbyAuth {
|
pub struct MoreLobbyAuth {
|
||||||
mesh: std::sync::Arc<crate::user_service::UserMesh>,
|
mesh: std::sync::Arc<crate::user_service::UserMesh>,
|
||||||
|
keybind_workaround: std::sync::Arc<crate::workarounds::EditModeInputLockupWorkaround>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn more_auth_provider(mesh: &std::sync::Arc<crate::user_service::UserMesh>) -> MoreLobbyAuth {
|
pub fn more_auth_provider(mesh: &std::sync::Arc<crate::user_service::UserMesh>, keybind_workaround: std::sync::Arc<crate::workarounds::EditModeInputLockupWorkaround>) -> MoreLobbyAuth {
|
||||||
MoreLobbyAuth {
|
MoreLobbyAuth {
|
||||||
mesh: mesh.to_owned()
|
mesh: mesh.to_owned(),
|
||||||
|
keybind_workaround,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +45,12 @@ impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
|||||||
crate::update_status(user_info.as_ref().as_ref()).await;
|
crate::update_status(user_info.as_ref().as_ref()).await;
|
||||||
let mut resp_params = std::collections::HashMap::with_capacity(1);
|
let mut resp_params = std::collections::HashMap::with_capacity(1);
|
||||||
resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0));
|
resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0));
|
||||||
crate::events::IntercomHandler::new(listener, &user_info, user.event_sender()).run();
|
crate::events::IntercomHandler::new(
|
||||||
|
listener,
|
||||||
|
&user_info,
|
||||||
|
user.event_sender(),
|
||||||
|
&self.keybind_workaround,
|
||||||
|
).run();
|
||||||
return polariton::operation::OperationResponse {
|
return polariton::operation::OperationResponse {
|
||||||
code: Self::op_code(),
|
code: Self::op_code(),
|
||||||
return_code: 0,
|
return_code: 0,
|
||||||
|
|||||||
113
rc_services_room/src/workarounds/input_lockup.rs
Normal file
113
rc_services_room/src/workarounds/input_lockup.rs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// If you lose connection in edit mode and then open chat, you will lose all keyboard input
|
||||||
|
// until you receive a party or game invite
|
||||||
|
// https://git.ngram.ca/OpenJam/rc-servers/issues/127
|
||||||
|
//
|
||||||
|
// This implementation sends a game invite since it's on the same server
|
||||||
|
|
||||||
|
use crate::custom_game_tracker::{SessionInfo, UserInfo};
|
||||||
|
|
||||||
|
const WORKAROUND_SESSION_ID: &str = "sys_0_cg|workaround";
|
||||||
|
const WORKAROUND_PUBLIC_ID: &str = "MrWorkaround66 sys";
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum InviteState {
|
||||||
|
Invited,
|
||||||
|
Accepted,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InviteState {
|
||||||
|
#[inline]
|
||||||
|
fn from_u8(num: u8) -> Self {
|
||||||
|
match num {
|
||||||
|
0 => Self::Invited,
|
||||||
|
1 => Self::Accepted,
|
||||||
|
_ => panic!("Invalid InviteState {}", num),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn to_u8(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EditModeInputLockupWorkaround {
|
||||||
|
active_users: tokio::sync::RwLock<std::collections::HashMap<i32, std::sync::atomic::AtomicU8>>,
|
||||||
|
default_config_map: std::collections::HashMap<String, String>,
|
||||||
|
default_config_core: oj_rc_core::persist::user::intercom::IntercomLobbyCustomGameConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditModeInputLockupWorkaround {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
active_users: tokio::sync::RwLock::new(std::collections::HashMap::new()),
|
||||||
|
default_config_map: crate::custom_game_tracker::game_config_default_map(),
|
||||||
|
default_config_core: crate::custom_game_tracker::game_config_default_core(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_user(&self, id: i32, public_id: String) -> SessionInfo {
|
||||||
|
self.active_users.write().await.insert(id, std::sync::atomic::AtomicU8::new(InviteState::Invited.to_u8()));
|
||||||
|
SessionInfo {
|
||||||
|
session_id: WORKAROUND_SESSION_ID.to_owned(),
|
||||||
|
config: self.default_config_map.clone(),
|
||||||
|
config_core: self.default_config_core.clone(),
|
||||||
|
users: vec![
|
||||||
|
UserInfo {
|
||||||
|
public_id: WORKAROUND_PUBLIC_ID.to_owned(),
|
||||||
|
is_invited: false,
|
||||||
|
team: 1,
|
||||||
|
state: crate::data::custom_games::PlayerSessionStatus::Ready,
|
||||||
|
},
|
||||||
|
UserInfo {
|
||||||
|
public_id,
|
||||||
|
is_invited: true,
|
||||||
|
team: 0,
|
||||||
|
state: crate::data::custom_games::PlayerSessionStatus::Ready,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user(&self, id: i32, public_id: &str) -> Option<SessionInfo> {
|
||||||
|
if let Some(state) = self.active_users.read().await.get(&id) {
|
||||||
|
let state = InviteState::from_u8(state.load(std::sync::atomic::Ordering::Relaxed));
|
||||||
|
let is_invited = matches!(state, InviteState::Invited);
|
||||||
|
Some(SessionInfo {
|
||||||
|
session_id: WORKAROUND_SESSION_ID.to_owned(),
|
||||||
|
config: self.default_config_map.clone(),
|
||||||
|
config_core: self.default_config_core.clone(),
|
||||||
|
users: vec![
|
||||||
|
UserInfo {
|
||||||
|
public_id: WORKAROUND_PUBLIC_ID.to_owned(),
|
||||||
|
is_invited: false,
|
||||||
|
team: 1,
|
||||||
|
state: crate::data::custom_games::PlayerSessionStatus::Ready,
|
||||||
|
},
|
||||||
|
UserInfo {
|
||||||
|
public_id: public_id.to_owned(),
|
||||||
|
is_invited,
|
||||||
|
team: 0,
|
||||||
|
state: crate::data::custom_games::PlayerSessionStatus::Ready,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn accept_invite(&self, id: i32) {
|
||||||
|
let lock = self.active_users.read().await;
|
||||||
|
if let Some(state) = lock.get(&id) {
|
||||||
|
state.store(InviteState::Accepted.to_u8(), std::sync::atomic::Ordering::Relaxed);
|
||||||
|
} else {
|
||||||
|
log::warn!("Tried to use EditModeInputLockupWorkaround for non-added user {}", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove_user(&self, id: i32) {
|
||||||
|
self.active_users.write().await.remove(&id);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
rc_services_room/src/workarounds/mod.rs
Normal file
18
rc_services_room/src/workarounds/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
mod input_lockup;
|
||||||
|
pub use input_lockup::EditModeInputLockupWorkaround;
|
||||||
|
|
||||||
|
pub struct Workarounds {
|
||||||
|
emil: std::sync::Arc<EditModeInputLockupWorkaround>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Workarounds {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
emil: std::sync::Arc::new(EditModeInputLockupWorkaround::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn edit_mode_input_lockup(&self) -> std::sync::Arc<EditModeInputLockupWorkaround> {
|
||||||
|
self.emil.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user