diff --git a/Cargo.lock b/Cargo.lock
index c7ae93b..d86b22d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1787,6 +1787,32 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "rc_singleplayer"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "env_logger",
+ "log",
+ "polariton",
+ "polariton_auth",
+ "polariton_server",
+ "tokio",
+]
+
+[[package]]
+name = "rc_singleplayer_room"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "env_logger",
+ "log",
+ "polariton",
+ "polariton_auth",
+ "polariton_server",
+ "tokio",
+]
+
[[package]]
name = "rc_social"
version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index bfd512b..6260bfb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,7 +10,8 @@ members = [
"rc_services", "rc_services_room",
"rc_static_data", "rc_microtransactions",
"rc_social", "rc_social_room",
- "rc_chat", "rc_chat_room"
+ "rc_chat", "rc_chat_room",
+ "rc_singleplayer", "rc_singleplayer_room",
]
[workspace.dependencies]
diff --git a/assets/robocraft/servenvmulti.config b/assets/robocraft/servenvmulti.config
index 008ad64..a60934c 100644
--- a/assets/robocraft/servenvmulti.config
+++ b/assets/robocraft/servenvmulti.config
@@ -6,7 +6,11 @@
127.0.0.1:4532
127.0.0.1:4533
127.0.0.1:4534
- 127.0.0.1:4535
+ 127.0.0.1:4535
+ 127.0.0.1:4536
+ 127.0.0.1:4537
+ 127.0.0.1:4538
+ 127.0.0.1:4539
http://127.0.0.1:8001/
http://127.0.0.1:8010/live/data.json
diff --git a/rc_services_room/src/data/game_mode.rs b/rc_services_room/src/data/game_mode.rs
new file mode 100644
index 0000000..55ed80c
--- /dev/null
+++ b/rc_services_room/src/data/game_mode.rs
@@ -0,0 +1,41 @@
+use polariton::{operation::Typed, serdes::TypePrefix};
+
+pub struct GameModeConfig {
+ pub respawn_heal_duration: f32,
+ pub respawn_full_heal_duration: f32,
+ pub kill_limit: i32,
+ pub game_time_minutes: i32,
+}
+
+impl GameModeConfig {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::HashMap(vec![
+ (Typed::Str("respawnHealDuration".into()), Typed::Float(self.respawn_heal_duration)),
+ (Typed::Str("respawnFullHealDuration".into()), Typed::Float(self.respawn_full_heal_duration)),
+ (Typed::Str("killLimit".into()), Typed::Int(self.kill_limit)),
+ (Typed::Str("gameTimeMinutes".into()), Typed::Int(self.game_time_minutes)),
+ ].into())
+ }
+}
+
+pub struct GameModeConfigs {
+ pub battle_arena: GameModeConfig,
+ pub elimination: GameModeConfig,
+ pub the_pit: GameModeConfig,
+ pub team_deathmatch: GameModeConfig,
+}
+
+impl GameModeConfigs {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::Dict(polariton::operation::Dict {
+ key_ty: TypePrefix::Str,
+ val_ty: TypePrefix::HashMap,
+ items: vec![
+ (Typed::Str("BattleArena".into()), self.battle_arena.as_transmissible()),
+ (Typed::Str("Elimination".into()), self.elimination.as_transmissible()),
+ (Typed::Str("ThePit".into()), self.the_pit.as_transmissible()),
+ (Typed::Str("TeamDeathmatch".into()), self.team_deathmatch.as_transmissible()),
+ ],
+ })
+ }
+}
diff --git a/rc_services_room/src/data/mod.rs b/rc_services_room/src/data/mod.rs
index 3cb0aa2..15e2519 100644
--- a/rc_services_room/src/data/mod.rs
+++ b/rc_services_room/src/data/mod.rs
@@ -25,8 +25,11 @@ pub mod auto_regen;
pub mod voting;
pub mod lobby;
pub mod error_codes;
+pub mod game_mode;
+pub mod score_multipliers;
pub(self) fn encode_7_bit_i32(mut src: i32) -> Vec {
+ if src == 0 { return vec![0] }
let mut out = Vec::with_capacity(5);
while src != 0 {
let last_7 = (src & 0x7F) as u8;
diff --git a/rc_services_room/src/data/score_multipliers.rs b/rc_services_room/src/data/score_multipliers.rs
new file mode 100644
index 0000000..cc4d4ba
--- /dev/null
+++ b/rc_services_room/src/data/score_multipliers.rs
@@ -0,0 +1,109 @@
+use polariton::operation::Typed;
+
+pub struct ScoreMultipliersData {
+ pub max_cpu: f32,
+ pub stat_multipliers: std::collections::HashMap,
+ pub completed_battle_base_multiplier: f32,
+ pub completed_battle_bonus_multiplier: f32,
+ pub delta_scaler: f32,
+ pub defeat_score: u32,
+ pub victory_score: u32,
+ pub max_score_ratio: f32,
+}
+
+impl ScoreMultipliersData {
+ fn dump(&self, writer: &mut dyn std::io::Write) -> std::io::Result {
+ writer.write_all(&self.max_cpu.to_le_bytes())?;
+ for (key, val) in self.stat_multipliers.iter() {
+ writer.write_all(&(*key as u32).to_le_bytes())?;
+ writer.write_all(&val.base.to_le_bytes())?;
+ writer.write_all(&val.bonus.to_le_bytes())?;
+ }
+ writer.write_all(&self.completed_battle_base_multiplier.to_le_bytes())?;
+ writer.write_all(&self.completed_battle_bonus_multiplier.to_le_bytes())?;
+ writer.write_all(&self.delta_scaler.to_le_bytes())?;
+ writer.write_all(&self.defeat_score.to_le_bytes())?;
+ writer.write_all(&self.victory_score.to_le_bytes())?;
+ writer.write_all(&self.max_score_ratio.to_le_bytes())?;
+ Ok(28 + (12 * self.stat_multipliers.len()))
+ }
+
+ pub fn as_transmissible(&self) -> Typed {
+ let mut buf = Vec::new();
+ self.dump(&mut std::io::Cursor::new(&mut buf)).unwrap();
+ Typed::Bytes(buf.into())
+ }
+}
+
+pub struct ScoreMultiplier {
+ pub base: f32,
+ pub bonus: f32,
+}
+
+impl std::default::Default for ScoreMultiplier {
+ fn default() -> Self {
+ Self {
+ base: 0.5,
+ bonus: 0.9
+ }
+ }
+}
+
+#[allow(dead_code)]
+#[repr(u8)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum InGameStat {
+ None = 0,
+ DestroyedCubes = 1,
+ DestroyedCubesInProtection = 2,
+ DestroyedCubesDefendingTheBase = 3,
+ Kill = 4,
+ KillAssist = 5,
+ HealCubes = 6,
+ HealAssist = 7,
+ DestroyedProtoniumCubes = 8,
+ BaseCaptureClassicMode = 9,
+ RobotDestroyed = 10,
+ Score = 11,
+ HealthPercentageBonusClassicMode = 12,
+ Points = 13,
+ CurrentKillStreak = 14,
+ BestKillStreak = 15,
+ CapturePointBattleArenaMode = 16,
+ EqualiserDestroyedBattleArenaMode = 17,
+ BattleArenaObjectives = 18,
+}
+
+impl std::default::Default for ScoreMultipliersData {
+ fn default() -> Self {
+ Self {
+ max_cpu: 1000.0,
+ stat_multipliers: vec![
+ (InGameStat::DestroyedCubes, ScoreMultiplier::default()),
+ (InGameStat::DestroyedCubesInProtection, ScoreMultiplier::default()),
+ (InGameStat::DestroyedCubesDefendingTheBase, ScoreMultiplier::default()),
+ (InGameStat::Kill, ScoreMultiplier::default()),
+ (InGameStat::KillAssist, ScoreMultiplier::default()),
+ (InGameStat::HealCubes, ScoreMultiplier::default()),
+ (InGameStat::HealAssist, ScoreMultiplier::default()),
+ (InGameStat::DestroyedProtoniumCubes, ScoreMultiplier::default()),
+ (InGameStat::BaseCaptureClassicMode, ScoreMultiplier::default()),
+ (InGameStat::RobotDestroyed, ScoreMultiplier::default()),
+ (InGameStat::Score, ScoreMultiplier::default()),
+ (InGameStat::HealthPercentageBonusClassicMode, ScoreMultiplier::default()),
+ (InGameStat::Points, ScoreMultiplier::default()),
+ (InGameStat::CurrentKillStreak, ScoreMultiplier::default()),
+ (InGameStat::BestKillStreak, ScoreMultiplier::default()),
+ (InGameStat::CapturePointBattleArenaMode, ScoreMultiplier::default()),
+ (InGameStat::EqualiserDestroyedBattleArenaMode, ScoreMultiplier::default()),
+ (InGameStat::BattleArenaObjectives, ScoreMultiplier::default()),
+ ].into_iter().collect(),
+ completed_battle_base_multiplier: 1.0,
+ completed_battle_bonus_multiplier: 1.2,
+ delta_scaler: 0.5,
+ defeat_score: 500,
+ victory_score: 2_000,
+ max_score_ratio: 2.0,
+ }
+ }
+}
diff --git a/rc_services_room/src/operations/all_customisations_info.rs b/rc_services_room/src/operations/all_customisations_info.rs
index d3259ad..ed02faf 100644
--- a/rc_services_room/src/operations/all_customisations_info.rs
+++ b/rc_services_room/src/operations/all_customisations_info.rs
@@ -20,7 +20,7 @@ pub(super) fn all_customisations_provider() -> SimpleFunc<216, crate::UserTy, im
items: vec![
CustomisationData {
id: "RC_MothershipSkin_Neptune_01".to_string(),
- localised_name: "Neptune 01".to_string(),
+ localised_name: "strNeptune".to_string(),
skin_scene_name: "RC_MothershipSkin_Neptune_01".to_string(),
simulation_prefab: "TODO_sim_prefab".to_string(),
preview_image_name: "TODO_preview_img".to_string(),
@@ -31,12 +31,61 @@ pub(super) fn all_customisations_provider() -> SimpleFunc<216, crate::UserTy, im
params.insert(SPAWNS_KEY, Typed::Arr(Arr {
ty: TypePrefix::HashMap, // hashtable
items: vec![
+ // TODO set these up with the correct values (IDs are correct)
CustomisationData {
- id: "spawn0".to_string(),
- localised_name: "Default".to_string(),
- skin_scene_name: "TODO_skin".to_string(),
- simulation_prefab: "TODO_sim_prefab".to_string(),
- preview_image_name: "TODO_preview_img".to_string(),
+ id: "Spawn".to_string(),
+ localised_name: "strSpawnFXWarpIn".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Respawn_WarpIn".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Spawn_BlackHole".to_string(),
+ localised_name: "strSpawnFXWarpIn".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Respawn_WarpIn".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Spawn_Lander".to_string(),
+ localised_name: "strSpawnFXWarpIn".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Respawn_WarpIn".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Spawn_Lootcrate".to_string(),
+ localised_name: "strSpawnFXWarpIn".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Respawn_WarpIn".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Spawn_Warp".to_string(),
+ localised_name: "strSpawnFXWarpIn".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Respawn_WarpIn".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Spawn_Present".to_string(),
+ localised_name: "strSpawnFXWarpIn".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Respawn_WarpIn".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Spawn_EasterEgg".to_string(),
+ localised_name: "strSpawnFXWarpIn".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Respawn_WarpIn".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
],
@@ -44,12 +93,61 @@ pub(super) fn all_customisations_provider() -> SimpleFunc<216, crate::UserTy, im
params.insert(DEATHS_KEY, Typed::Arr(Arr {
ty: TypePrefix::HashMap, // hashtable
items: vec![
+ // TODO set these up with the correct values (IDs are correct)
CustomisationData {
- id: "death0".to_string(),
- localised_name: "Default".to_string(),
- skin_scene_name: "TODO_skin".to_string(),
- simulation_prefab: "TODO_sim_prefab".to_string(),
- preview_image_name: "TODO_preview_img".to_string(),
+ id: "Explosion".to_string(),
+ localised_name: "strDeathFXEmergencyWarp".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Death_WarpOut".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Explosion_Toon".to_string(),
+ localised_name: "strDeathFXEmergencyWarp".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Death_WarpOut".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Explosion_Feathers_Rainbow".to_string(),
+ localised_name: "strDeathFXEmergencyWarp".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Death_WarpOut".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Explosion_Nuclear".to_string(),
+ localised_name: "strDeathFXEmergencyWarp".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Death_WarpOut".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Explosion_Warp".to_string(),
+ localised_name: "strDeathFXEmergencyWarp".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Death_WarpOut".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Explosion_BlackHole".to_string(),
+ localised_name: "strDeathFXEmergencyWarp".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Death_WarpOut".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
+ is_default: true,
+ }.as_transmissible(),
+ CustomisationData {
+ id: "Explosion_Firework".to_string(),
+ localised_name: "strDeathFXEmergencyWarp".to_string(),
+ skin_scene_name: "Splash_Loading_Screen".to_string(),
+ simulation_prefab: "Death_WarpOut".to_string(),
+ preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
],
diff --git a/rc_services_room/src/operations/game_mode_config.rs b/rc_services_room/src/operations/game_mode_config.rs
new file mode 100644
index 0000000..37e405c
--- /dev/null
+++ b/rc_services_room/src/operations/game_mode_config.rs
@@ -0,0 +1,15 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::ParameterTable;
+
+use crate::persist::config::ConfigProvider;
+
+const PARAM_KEY: u8 = 1;
+
+pub(super) fn game_mode_config_provider(conf: &crate::persist::config::ConfigImpl) -> SimpleFunc<113, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ let game_config = conf.game_mode_config();
+ SimpleFunc::new(move |params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, game_config.clone());
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/mod.rs b/rc_services_room/src/operations/mod.rs
index d2cd65c..94f5be0 100644
--- a/rc_services_room/src/operations/mod.rs
+++ b/rc_services_room/src/operations/mod.rs
@@ -76,6 +76,9 @@ mod regen_config;
mod pageantry;
mod signup_time;
mod validate_machine;
+mod game_mode_config;
+mod score_multipliers_config;
+mod player_robot_rank;
use polariton_server::operations::OperationsHandler;
@@ -170,4 +173,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler
.without_state(pageantry::after_battle_vote_thresholds_provider(&init_ctx.cubes))
.without_state(signup_time::user_signup_date_provider())
.without_state(validate_machine::validate_robot_provider())
+ .without_state(game_mode_config::game_mode_config_provider(&init_ctx.cubes))
+ .without_state(score_multipliers_config::tdm_ai_score_config_provider())
+ .without_state(player_robot_rank::player_robot_rank_provider())
}
diff --git a/rc_services_room/src/operations/player_robot_rank.rs b/rc_services_room/src/operations/player_robot_rank.rs
new file mode 100644
index 0000000..a7f48e0
--- /dev/null
+++ b/rc_services_room/src/operations/player_robot_rank.rs
@@ -0,0 +1,23 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed};
+
+const USERNAME_PARAM_KEY: u8 = 30; // in; str
+const RANK_PARAM_KEY: u8 = 84; // out; int
+const CPU_PARAM_KEY: u8 = 177; // out; int
+const COSMETIC_CPU_PARAM_KEY: u8 = 176; // out; int
+
+pub(super) fn player_robot_rank_provider() -> SimpleFunc<79, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, user: &crate::UserTy| {
+ let lock = user.read().unwrap();
+ let user = lock.user()?;
+ let mut params = params.to_dict();
+ if let Some(Typed::Str(username)) = params.get(&USERNAME_PARAM_KEY) {
+ log::debug!("Get robot rank for user {}", username.string);
+ }
+ let robot = user.slot_by_id(user.selected_garage_slot() as i32)?;
+ params.insert(RANK_PARAM_KEY, robot.robot_rank);
+ params.insert(CPU_PARAM_KEY, robot.cpu);
+ params.insert(COSMETIC_CPU_PARAM_KEY, robot.cosmetic_cpu);
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/score_multipliers_config.rs b/rc_services_room/src/operations/score_multipliers_config.rs
new file mode 100644
index 0000000..0582e46
--- /dev/null
+++ b/rc_services_room/src/operations/score_multipliers_config.rs
@@ -0,0 +1,15 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::ParameterTable;
+
+use crate::data::score_multipliers::*;
+
+const PARAM_KEY: u8 = 137;
+
+pub(super) fn tdm_ai_score_config_provider(/*conf: &crate::persist::config::ConfigImpl*/) -> SimpleFunc<117, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ //let game_config = conf.game_mode_config();
+ SimpleFunc::new(move |params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, ScoreMultipliersData::default().as_transmissible());
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/persist/combat.rs b/rc_services_room/src/persist/combat.rs
index de2cd38..3af2115 100644
--- a/rc_services_room/src/persist/combat.rs
+++ b/rc_services_room/src/persist/combat.rs
@@ -6,6 +6,8 @@ use serde::{Serialize, Deserialize};
pub struct BattleConfig {
pub regen: AutoRegenHealth,
pub votes: HashMap>,
+ #[serde(default = "default_game_modes")]
+ pub games: GameModes,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
@@ -60,3 +62,70 @@ impl std::convert::Into for Vote {
}
}
}
+
+#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
+pub struct GameMode {
+ pub respawn_heal_duration: f32,
+ pub respawn_full_heal_duration: f32,
+ pub kill_limit: i32,
+ pub game_time_m: i32,
+}
+
+impl std::convert::Into for GameMode {
+ fn into(self) -> crate::data::game_mode::GameModeConfig {
+ crate::data::game_mode::GameModeConfig {
+ respawn_heal_duration: self.respawn_heal_duration,
+ respawn_full_heal_duration: self.respawn_full_heal_duration,
+ kill_limit: self.kill_limit,
+ game_time_minutes: self.game_time_m,
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
+pub struct GameModes {
+ pub battle_arena: GameMode,
+ pub elimination: GameMode,
+ pub pit: GameMode,
+ pub team_deathmatch: GameMode,
+}
+
+impl std::convert::Into for GameModes {
+ fn into(self) -> crate::data::game_mode::GameModeConfigs {
+ crate::data::game_mode::GameModeConfigs {
+ battle_arena: self.battle_arena.into(),
+ elimination: self.elimination.into(),
+ the_pit: self.pit.into(),
+ team_deathmatch: self.team_deathmatch.into(),
+ }
+ }
+}
+
+fn default_game_modes() -> GameModes {
+ GameModes {
+ battle_arena: GameMode {
+ respawn_heal_duration: 10.0,
+ respawn_full_heal_duration: 10.0,
+ kill_limit: 0,
+ game_time_m: 20,
+ },
+ elimination: GameMode {
+ respawn_heal_duration: 10.0,
+ respawn_full_heal_duration: 10.0,
+ kill_limit: 10,
+ game_time_m: 10,
+ },
+ pit: GameMode {
+ respawn_heal_duration: 20.0,
+ respawn_full_heal_duration: 20.0,
+ kill_limit: 15,
+ game_time_m: 15,
+ },
+ team_deathmatch: GameMode {
+ respawn_heal_duration: 10.0,
+ respawn_full_heal_duration: 10.0,
+ kill_limit: 10,
+ game_time_m: 10,
+ },
+ }
+}
diff --git a/rc_services_room/src/persist/config/cubes_json.rs b/rc_services_room/src/persist/config/cubes_json.rs
index 25326a8..69958e7 100644
--- a/rc_services_room/src/persist/config/cubes_json.rs
+++ b/rc_services_room/src/persist/config/cubes_json.rs
@@ -177,4 +177,9 @@ impl super::ConfigProvider for CubeConfig {
items: vote_data,
})
}
+
+ fn game_mode_config(&self) -> Typed {
+ let game_mode_data: crate::data::game_mode::GameModeConfigs = self.battle.games.into();
+ game_mode_data.as_transmissible()
+ }
}
diff --git a/rc_services_room/src/persist/config/traits.rs b/rc_services_room/src/persist/config/traits.rs
index 729799f..110c2b6 100644
--- a/rc_services_room/src/persist/config/traits.rs
+++ b/rc_services_room/src/persist/config/traits.rs
@@ -9,4 +9,5 @@ pub trait ConfigProvider {
fn ids(&self) -> Vec;
fn regen_config(&self) -> Typed;
fn after_battle_vote_config(&self) -> Typed;
+ fn game_mode_config(&self) -> Typed;
}
diff --git a/rc_services_room/src/persist/user/account_json.rs b/rc_services_room/src/persist/user/account_json.rs
index 20dbc32..f8d3be0 100644
--- a/rc_services_room/src/persist/user/account_json.rs
+++ b/rc_services_room/src/persist/user/account_json.rs
@@ -150,6 +150,9 @@ impl super::User for UserData {
control_type: polariton::operation::Typed::Int(control_ty as _),
control_options: control_options.as_transmissible(),
mastery_level: polariton::operation::Typed::Int(0), // TODO
+ robot_rank: polariton::operation::Typed::Int(slot.total_robot_ranking as _),
+ cpu: polariton::operation::Typed::Int(slot.total_robot_cpu as _),
+ cosmetic_cpu: polariton::operation::Typed::Int(slot.total_cosmetic_cpu as _),
})
},
Err(e) => {
diff --git a/rc_services_room/src/persist/user/traits.rs b/rc_services_room/src/persist/user/traits.rs
index 0ac71b3..1c938a6 100644
--- a/rc_services_room/src/persist/user/traits.rs
+++ b/rc_services_room/src/persist/user/traits.rs
@@ -38,6 +38,9 @@ pub struct UserSlotData {
pub control_type: polariton::operation::Typed,
pub control_options: polariton::operation::Typed,
pub mastery_level: polariton::operation::Typed,
+ pub robot_rank: polariton::operation::Typed,
+ pub cpu: polariton::operation::Typed,
+ pub cosmetic_cpu: polariton::operation::Typed,
}
pub struct VehicleData {
diff --git a/rc_singleplayer/Cargo.toml b/rc_singleplayer/Cargo.toml
new file mode 100644
index 0000000..7e41ab0
--- /dev/null
+++ b/rc_singleplayer/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "rc_singleplayer"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+log.workspace = true
+env_logger.workspace = true
+tokio = { version = "1.43", features = [ "net", "macros", "rt-multi-thread", "io-util" ] }
+clap.workspace = true
+polariton.workspace = true
+polariton_auth = { version = "*", path = "../polariton_auth" }
+polariton_server.workspace = true
diff --git a/rc_singleplayer/build_arm64.sh b/rc_singleplayer/build_arm64.sh
new file mode 100755
index 0000000..7010ff6
--- /dev/null
+++ b/rc_singleplayer/build_arm64.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+cargo build --release --target aarch64-unknown-linux-musl
diff --git a/rc_singleplayer/run_debug.sh b/rc_singleplayer/run_debug.sh
new file mode 100755
index 0000000..cc15e3f
--- /dev/null
+++ b/rc_singleplayer/run_debug.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+RUST_BACKTRACE=1 RUST_LOG=debug cargo run -- -1
diff --git a/rc_singleplayer/src/cli.rs b/rc_singleplayer/src/cli.rs
new file mode 100644
index 0000000..36edfb4
--- /dev/null
+++ b/rc_singleplayer/src/cli.rs
@@ -0,0 +1,31 @@
+use clap::Parser;
+
+#[derive(Parser, Debug)]
+#[command(version, about, long_about = None)]
+pub struct CliArgs {
+ /// TCP port on which to accept connections
+ #[arg(short, long, default_value_t = 4538)]
+ pub port: u16,
+
+ /// IP Address on which to accept connections
+ #[arg(long, default_value_t = {"127.0.0.1".to_string()})]
+ pub ip: String,
+
+ /// Domain and port of the game server to send new connections
+ #[arg(long, default_value_t = {"127.0.0.1:4539".to_string()})]
+ pub redirect: String,
+
+ /// Name of game server to send new connections
+ #[arg(long, default_value_t = {"ngram_is_ngnius".to_string()})]
+ pub room_name: String,
+
+ /// Handle one connection and then exit
+ #[arg(short = '1', long)]
+ pub once: bool,
+}
+
+impl CliArgs {
+ pub fn get() -> Self {
+ Self::parse()
+ }
+}
diff --git a/rc_singleplayer/src/main.rs b/rc_singleplayer/src/main.rs
new file mode 100644
index 0000000..ef6bfea
--- /dev/null
+++ b/rc_singleplayer/src/main.rs
@@ -0,0 +1,263 @@
+mod cli;
+mod state;
+
+use polariton_auth::Handshake;
+use tokio::net;
+
+use polariton::packet::{Data, Message, Packet, StandardMessage};
+use polariton::operation::{OperationResponse, Typed};
+
+#[tokio::main]
+async fn main() -> std::io::Result<()> {
+ env_logger::init();
+ let args = cli::CliArgs::get();
+ log::debug!("Got cli args {:?}", args);
+
+ let ip_addr: std::net::IpAddr = args.ip.parse().expect("Invalid IP address");
+
+ // memory leak, but only once (so not a big deal)
+ let redirect_static = Box::leak(Box::new(args.redirect.clone()));
+ let room_name_static = Box::leak(Box::new(args.room_name.clone()));
+
+ let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?;
+
+ if args.once {
+ log::warn!("Handling first connection and then exiting");
+ let (socket, address) = listener.accept().await?;
+ process_socket(socket, address, redirect_static, room_name_static).await;
+ Ok(())
+ } else {
+ loop {
+ let (socket, address) = listener.accept().await?;
+ tokio::spawn(process_socket(socket, address, redirect_static, room_name_static));
+ }
+ }
+}
+
+async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAddr, redirect_url: &str, lobby_name: &str) {
+ log::debug!("Accepting connection from address {}", address);
+
+ let enc = match do_connect_handshake(&mut socket, lobby_name, redirect_url).await {
+ Some(x) => x,
+ None => {
+ log::error!("Failed to do connect handshake with {}", address);
+ return;
+ }
+ };
+ let sock_state = state::State::new(enc);
+ while let Ok(packet) = polariton_server::utils::receive_packet_async(&mut socket, &sock_state.serdes_ctx()).await {
+ match packet {
+ Packet::Ping(ping) => {
+ polariton_server::utils::handle_ping_async(ping, &mut socket, &sock_state.serdes_ctx()).await.unwrap_or_default();
+ },
+ Packet::Packet(packet) => log::warn!("Not handling packet {:?}", packet),
+ }
+ }
+ log::debug!("Goodbye connection from address {}", address);
+}
+
+const APP_ID: &str = "SinglePlayerServer";
+
+struct AuthImpl;
+
+const TOKEN_KEY: u8 = 216; // token;refresh_token
+//const UNKNOWN_BYTE_KEY: u8 = 217;
+const SERVICE_KEY: u8 = 224;
+const USERNAME_KEY: u8 = 225;
+
+//const CCU_KEY: u8 = 245;
+
+#[derive(Debug)]
+enum AuthError {
+ WrongService { expected: String, actual: String },
+ MissingService,
+ MissingToken,
+ MissingUsername,
+}
+
+impl AuthError {
+ fn log_err(&self) {
+ match self {
+ Self::WrongService { expected, actual } => log::error!("(auth fail) Got unexpected service {}, expected {}", actual, expected),
+ Self::MissingService => log::error!("(auth fail) No service name param ({}) received", SERVICE_KEY),
+ Self::MissingToken => log::error!("(auth fail) No token param ({}) received", TOKEN_KEY),
+ Self::MissingUsername => log::error!("(auth fail) No username param ({}) received", USERNAME_KEY),
+ }
+ }
+}
+
+impl polariton_auth::AuthProvider for AuthImpl {
+ fn validate(&mut self, params: &std::collections::HashMap) -> Result, AuthError> {
+ if let Some(Typed::Str(token)) = params.get(&TOKEN_KEY) {
+ if let Some(Typed::Str(service)) = params.get(&SERVICE_KEY) {
+ if let Some(Typed::Str(user)) = params.get(&USERNAME_KEY) {
+ if service.string == APP_ID {
+ let params_resp = std::collections::HashMap::::new();
+ //params_resp.insert(CCU_KEY, Typed::Byte(0));
+ log::debug!("Auth success for {} (token: {})", user.string, token.string);
+ Ok(params_resp)
+ } else { Err(AuthError::WrongService { expected: APP_ID.to_owned(), actual: service.string.to_owned() }) }
+ } else { Err(AuthError::MissingUsername) }
+ } else { Err(AuthError::MissingService) }
+ } else { Err(AuthError::MissingToken) }
+ }
+}
+
+async fn do_connect_handshake(
+ socket: &mut net::TcpStream,
+ game_server_name: &str,
+ game_server_url: &str,
+) -> Option {
+ let handshake = Handshake::new(APP_ID);
+ // connect
+ log::debug!("(connect) Handling first packet");
+ let packet1 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read connect packet: {}", e);
+ return None;
+ }
+ };
+ let (handshake, to_send) = match handshake.connect(&packet1) {
+ Ok(x) => (x.handshake, x.extra),
+ Err(e) => {
+ log::error!("Failed to handle connect handshake: {:?}", e.extra);
+ return None;
+ }
+ };
+ match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await {
+ Ok(_) => {},
+ Err(e) => {
+ log::error!("Failed to send connect ack packet: {}", e);
+ return None;
+ }
+ }
+ // encrypt
+ log::debug!("(connect) Handling second packet");
+ let mut packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) public key packet: {}", e);
+ return None;
+ }
+ };
+ while let Packet::Ping(ping) = packet2 {
+ polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default();
+ packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) public key packet: {}", e);
+ return None;
+ }
+ };
+ }
+ let (handshake, to_send, crypto) = match handshake.encrypt(&packet2) {
+ Ok(x) => (x.handshake, x.extra.0, x.extra.1),
+ Err(e) => {
+ log::error!("Failed to handle encryption handshake: {:?}", e.extra);
+ return None;
+ }
+ };
+ match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await {
+ Ok(_) => {},
+ Err(e) => {
+ log::error!("Failed to send encryption ack packet: {}", e);
+ return None;
+ }
+ }
+ // pre-auth
+ let handshake = handshake.with_auth(AuthImpl);
+ let op_ctx = polariton::serdes::SerdesContext::default();
+ let ctx = polariton::packet::SerdesContext::new(&op_ctx, &crypto);
+ // authenticate
+ log::debug!("(connect) Handling third packet");
+ let mut packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) auth packet: {}", e);
+ return None;
+ }
+ };
+ while let Packet::Ping(ping) = packet3 {
+ polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default();
+ packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) auth packet: {}", e);
+ return None;
+ }
+ };
+ }
+ let to_send = match handshake.authenticate(&packet3, &crypto) {
+ Ok(x) => x,
+ Err(h) => match h.extra {
+ polariton_auth::AuthError::Validation(e) => {
+ e.log_err();
+ return None;
+ },
+ e => {
+ log::error!("Failed to handle auth handshake: {:?}", e);
+ return None;
+ },
+ },
+ };
+ match polariton_server::utils::send_packet_async(&to_send, socket, &ctx).await {
+ Ok(_) => {},
+ Err(e) => {
+ log::error!("Failed to send auth ack packet: {}", e);
+ return None;
+ }
+ }
+
+ // redirect to lobby
+ log::debug!("(connect) Handling fourth packet");
+ let mut packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) join packet: {}", e);
+ return None;
+ }
+ };
+ while let Packet::Ping(ping) = packet_j {
+ polariton_server::utils::handle_ping_async(ping, socket, &ctx).await.unwrap_or_default();
+ packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) join packet: {}", e);
+ return None;
+ }
+ };
+ }
+ log::debug!("(connect) Got fourth packet {:?}", packet_j);
+ if let Packet::Packet(msg) = &packet_j {
+ if let Message::Standard(st) = &msg.message {
+ if let Data::OpReq(req) = &st.data {
+ if req.code == 226 { // join lobby
+ log::debug!("Max players from lobby join request: {:?}", req.params.to_owned().to_dict().get(&255));
+ let mut params = std::collections::HashMap::::new();
+ params.insert(230 /* game server address */, Typed::Str(game_server_url.into()));
+ params.insert(255 /* room name */, Typed::Str(game_server_name.into()));
+ let resp = Packet::from_message(
+ Message::Standard(
+ StandardMessage { flags: 0,
+ data: Data::OpResp(OperationResponse {
+ code: req.code,
+ return_code: 0,
+ message: Typed::Null,
+ params: params.into(),
+ }),
+ }.encrypt(true)), 0, true, &ctx).unwrap();
+ match polariton_server::utils::send_packet_async(&resp, socket, &ctx).await {
+ Ok(_) => {},
+ Err(e) => {
+ log::error!("Failed to send lobby ack packet: {}", e);
+ return None;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Some(crypto)
+}
diff --git a/rc_singleplayer/src/state.rs b/rc_singleplayer/src/state.rs
new file mode 100644
index 0000000..9d6664f
--- /dev/null
+++ b/rc_singleplayer/src/state.rs
@@ -0,0 +1,17 @@
+const SERDES_SERDES_CTX_REF: &'static polariton::serdes::SerdesContext<(), polariton::serdes::NoCustomSerdes> = &polariton::serdes::SerdesContext::default_const();
+
+pub struct State {
+ pub crypto: polariton_auth::CryptoImpl,
+}
+
+impl State {
+ pub fn new(c: polariton_auth::CryptoImpl) -> Self {
+ Self {
+ crypto: c,
+ }
+ }
+
+ pub fn serdes_ctx(&self) -> polariton::packet::SerdesContext<'_, (), polariton::serdes::NoCustomSerdes> {
+ polariton::packet::SerdesContext::new(SERDES_SERDES_CTX_REF, &self.crypto)
+ }
+}
diff --git a/rc_singleplayer_room/Cargo.toml b/rc_singleplayer_room/Cargo.toml
new file mode 100644
index 0000000..3c2139a
--- /dev/null
+++ b/rc_singleplayer_room/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "rc_singleplayer_room"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+log.workspace = true
+env_logger.workspace = true
+tokio = { version = "1.43", features = [ "net", "macros", "rt-multi-thread", "io-util" ] }
+clap.workspace = true
+polariton.workspace = true
+polariton_auth = { version = "*", path = "../polariton_auth" }
+polariton_server.workspace = true
diff --git a/rc_singleplayer_room/build_arm64.sh b/rc_singleplayer_room/build_arm64.sh
new file mode 100755
index 0000000..7010ff6
--- /dev/null
+++ b/rc_singleplayer_room/build_arm64.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+cargo build --release --target aarch64-unknown-linux-musl
diff --git a/rc_singleplayer_room/run_debug.sh b/rc_singleplayer_room/run_debug.sh
new file mode 100755
index 0000000..cc15e3f
--- /dev/null
+++ b/rc_singleplayer_room/run_debug.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+RUST_BACKTRACE=1 RUST_LOG=debug cargo run -- -1
diff --git a/rc_singleplayer_room/src/cli.rs b/rc_singleplayer_room/src/cli.rs
new file mode 100644
index 0000000..1ecd871
--- /dev/null
+++ b/rc_singleplayer_room/src/cli.rs
@@ -0,0 +1,23 @@
+use clap::Parser;
+
+#[derive(Parser, Debug)]
+#[command(version, about, long_about = None)]
+pub struct CliArgs {
+ /// TCP port on which to accept connections
+ #[arg(short, long, default_value_t = 4539)]
+ pub port: u16,
+
+ /// IP Address on which to accept connections
+ #[arg(long, default_value_t = {"127.0.0.1".to_string()})]
+ pub ip: String,
+
+ /// Handle one connection and then exit
+ #[arg(short = '1', long)]
+ pub once: bool,
+}
+
+impl CliArgs {
+ pub fn get() -> Self {
+ Self::parse()
+ }
+}
diff --git a/rc_singleplayer_room/src/data/mod.rs b/rc_singleplayer_room/src/data/mod.rs
new file mode 100644
index 0000000..159a924
--- /dev/null
+++ b/rc_singleplayer_room/src/data/mod.rs
@@ -0,0 +1,23 @@
+pub mod player_data;
+
+pub(self) fn encode_7_bit_i32(mut src: i32) -> Vec {
+ if src == 0 { return vec![0] }
+ let mut out = Vec::with_capacity(5);
+ while src != 0 {
+ let last_7 = (src & 0x7F) as u8;
+ src = src >> 7;
+ if src != 0 {
+ out.push(last_7 | 0x80);
+ } else {
+ out.push(last_7);
+ }
+ }
+ out
+}
+
+pub(self) fn write_str_for_binreader(s: &str, writer: &mut dyn std::io::Write) -> std::io::Result {
+ let s_bytes = s.as_bytes();
+ let mut total_len = writer.write(&encode_7_bit_i32(s_bytes.len() as i32))?;
+ total_len += writer.write(s_bytes)?;
+ Ok(total_len)
+}
diff --git a/rc_singleplayer_room/src/data/player_data.rs b/rc_singleplayer_room/src/data/player_data.rs
new file mode 100644
index 0000000..f863eb3
--- /dev/null
+++ b/rc_singleplayer_room/src/data/player_data.rs
@@ -0,0 +1,77 @@
+use polariton::operation::Typed;
+
+pub struct PlayerData {
+ pub name: String,
+ pub display_name: String,
+ pub mastery: i32,
+ pub tier: i32,
+ pub robot_name: String,
+ pub robot_map: Vec,
+ // -- unused i32 here --
+ pub team: i32,
+ pub has_premium: bool,
+ pub robot_uuid: String,
+ pub cpu: i32,
+ pub weapon_order: Vec,
+ pub colour_map: Vec,
+ pub is_ai: bool,
+ pub spawn_effect: String,
+ pub death_effect: String,
+ pub player_rank: i32,
+ pub weapon_rank: std::collections::HashMap,
+}
+
+impl PlayerData {
+ fn dump(&self, writer: &mut dyn std::io::Write) -> std::io::Result {
+ let mut total_len = super::write_str_for_binreader(&self.name, writer)?;
+ total_len += super::write_str_for_binreader(&self.display_name, writer)?;
+ writer.write_all(&self.mastery.to_le_bytes())?;
+ writer.write_all(&self.tier.to_le_bytes())?;
+ total_len += super::write_str_for_binreader(&self.robot_name, writer)?;
+ writer.write_all(&(self.robot_map.len() as i32).to_le_bytes())?;
+ writer.write_all(&self.robot_map)?;
+ writer.write_all(&[0xDE, 0xAD, 0xBE, 0xEF])?;
+ writer.write_all(&self.team.to_le_bytes())?;
+ writer.write_all(&[self.has_premium as u8])?;
+ total_len += super::write_str_for_binreader(&self.robot_uuid, writer)?;
+ writer.write_all(&self.cpu.to_le_bytes())?;
+ writer.write_all(&(self.weapon_order.len() as i32).to_le_bytes())?;
+ for weapon_key in self.weapon_order.iter() {
+ writer.write_all(&weapon_key.to_le_bytes())?;
+ }
+ writer.write_all(&(self.colour_map.len() as i32).to_le_bytes())?;
+ writer.write_all(&self.colour_map)?;
+ writer.write_all(&[self.is_ai as u8])?;
+ total_len += super::write_str_for_binreader(&self.spawn_effect, writer)?;
+ total_len += super::write_str_for_binreader(&self.death_effect, writer)?;
+ writer.write_all(&self.player_rank.to_le_bytes())?;
+ writer.write_all(&(self.weapon_rank.len() as i32).to_le_bytes())?;
+ for (key, val) in self.weapon_rank.iter() {
+ writer.write_all(&key.to_le_bytes())?;
+ writer.write_all(&val.to_le_bytes())?;
+ }
+ Ok(42 + self.robot_map.len() + (self.weapon_order.len() * 4) + self.colour_map.len() + (self.weapon_rank.len() * 8) + total_len)
+ }
+}
+
+pub struct PlayerDatas {
+ pub players: Vec,
+}
+
+impl PlayerDatas {
+ fn dump(&self, writer: &mut dyn std::io::Write) -> std::io::Result {
+ writer.write_all(&(self.players.len() as i32).to_le_bytes())?;
+ let mut total_len = 4;
+ for data in self.players.iter() {
+ total_len += data.dump(writer)?;
+ }
+ Ok(total_len)
+ }
+
+ pub fn as_transmissible(&self) -> Typed {
+ let mut buf = Vec::new();
+ let write_size = self.dump(&mut std::io::Cursor::new(&mut buf)).unwrap();
+ log::debug!("PlayerDatas serialized to {} bytes: {:?}", write_size, buf);
+ Typed::Bytes(buf.into())
+ }
+}
diff --git a/rc_singleplayer_room/src/main.rs b/rc_singleplayer_room/src/main.rs
new file mode 100644
index 0000000..9a0215d
--- /dev/null
+++ b/rc_singleplayer_room/src/main.rs
@@ -0,0 +1,256 @@
+mod cli;
+mod state;
+
+mod data;
+mod operations;
+
+use polariton_auth::Handshake;
+use tokio::net;
+
+use polariton::packet::{Data, Message, Packet, StandardMessage};
+use polariton::operation::{OperationResponse, Typed};
+
+pub type UserTy = std::sync::RwLock;
+
+#[tokio::main]
+async fn main() -> std::io::Result<()> {
+ env_logger::init();
+ let args = cli::CliArgs::get();
+ log::debug!("Got cli args {:?}", args);
+
+ let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler()));
+
+ 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?;
+
+ if args.once {
+ log::warn!("Handling first connection and then exiting");
+ let (socket, address) = listener.accept().await?;
+ process_socket(socket, address, server).await;
+ Ok(())
+ } else {
+ loop {
+ let (socket, address) = listener.accept().await?;
+ tokio::spawn(process_socket(socket, address, server.clone()));
+ }
+ }
+}
+
+async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAddr, server: std::sync::Arc>) {
+ log::debug!("Accepting connection from address {}", address);
+ let enc = match do_connect_handshake(&mut socket).await {
+ Some(x) => x,
+ None => {
+ log::error!("Failed to do connect handshake with {}", address);
+ return;
+ }
+ };
+ let user_state = state::UserState::new();
+ server.handle_async(socket, user_state, enc, Default::default()).await;
+ log::debug!("Goodbye connection from address {}", address);
+}
+
+const APP_ID: &str = "SinglePlayerServer";
+
+struct AuthImpl;
+
+const TOKEN_KEY: u8 = 216; // token;refresh_token
+//const UNKNOWN_BYTE_KEY: u8 = 217;
+const SERVICE_KEY: u8 = 224;
+const USERNAME_KEY: u8 = 225;
+
+//const CCU_KEY: u8 = 245;
+
+#[derive(Debug)]
+enum AuthError {
+ WrongService { expected: String, actual: String },
+ MissingService,
+ MissingToken,
+ MissingUsername,
+}
+
+impl AuthError {
+ fn log_err(&self) {
+ match self {
+ Self::WrongService { expected, actual } => log::error!("(auth fail) Got unexpected service {}, expected {}", actual, expected),
+ Self::MissingService => log::error!("(auth fail) No service name param ({}) received", SERVICE_KEY),
+ Self::MissingToken => log::error!("(auth fail) No token param ({}) received", TOKEN_KEY),
+ Self::MissingUsername => log::error!("(auth fail) No username param ({}) received", USERNAME_KEY),
+ }
+ }
+}
+
+impl polariton_auth::AuthProvider for AuthImpl {
+ fn validate(&mut self, params: &std::collections::HashMap) -> Result, AuthError> {
+ if let Some(Typed::Str(token)) = params.get(&TOKEN_KEY) {
+ if let Some(Typed::Str(service)) = params.get(&SERVICE_KEY) {
+ if let Some(Typed::Str(user)) = params.get(&USERNAME_KEY) {
+ if service.string == APP_ID {
+ let params_resp = std::collections::HashMap::::new();
+ //params_resp.insert(CCU_KEY, Typed::Byte(0));
+ log::debug!("Auth success for {} (token: {})", user.string, token.string);
+ Ok(params_resp)
+ } else { Err(AuthError::WrongService { expected: APP_ID.to_owned(), actual: service.string.to_owned() }) }
+ } else { Err(AuthError::MissingUsername) }
+ } else { Err(AuthError::MissingService) }
+ } else { Err(AuthError::MissingToken) }
+ }
+}
+
+async fn do_connect_handshake(
+ socket: &mut net::TcpStream,
+) -> Option {
+ let handshake = Handshake::new(APP_ID);
+ // connect
+ log::debug!("(connect) Handling first packet");
+ let packet1 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read connect packet: {}", e);
+ return None;
+ }
+ };
+ let (handshake, to_send) = match handshake.connect(&packet1) {
+ Ok(x) => (x.handshake, x.extra),
+ Err(e) => {
+ log::error!("Failed to handle connect handshake: {:?}", e.extra);
+ return None;
+ }
+ };
+ match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await {
+ Ok(_) => {},
+ Err(e) => {
+ log::error!("Failed to send connect ack packet: {}", e);
+ return None;
+ }
+ }
+ // encrypt
+ log::debug!("(connect) Handling second packet");
+ let mut packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) public key packet: {}", e);
+ return None;
+ }
+ };
+ while let Packet::Ping(ping) = packet2 {
+ polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default();
+ packet2 = match polariton_server::utils::receive_packet_async(socket, &Default::default()).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) public key packet: {}", e);
+ return None;
+ }
+ };
+ }
+ let (handshake, to_send, crypto) = match handshake.encrypt(&packet2) {
+ Ok(x) => (x.handshake, x.extra.0, x.extra.1),
+ Err(e) => {
+ log::error!("Failed to handle encryption handshake: {:?}", e.extra);
+ return None;
+ }
+ };
+ match polariton_server::utils::send_packet_async(&to_send, socket, &Default::default()).await {
+ Ok(_) => {},
+ Err(e) => {
+ log::error!("Failed to send encryption ack packet: {}", e);
+ return None;
+ }
+ }
+ // pre-auth
+ let handshake = handshake.with_auth(AuthImpl);
+ let op_ctx = polariton::serdes::SerdesContext::default();
+ let ctx = polariton::packet::SerdesContext::new(&op_ctx, &crypto);
+ // authenticate
+ log::debug!("(connect) Handling third packet");
+ let mut packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) auth packet: {}", e);
+ return None;
+ }
+ };
+ while let Packet::Ping(ping) = packet3 {
+ polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default();
+ packet3 = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) auth packet: {}", e);
+ return None;
+ }
+ };
+ }
+ let to_send = match handshake.authenticate(&packet3, &crypto) {
+ Ok(x) => x,
+ Err(h) => match h.extra {
+ polariton_auth::AuthError::Validation(e) => {
+ e.log_err();
+ return None;
+ },
+ e => {
+ log::error!("Failed to handle auth handshake: {:?}", e);
+ return None;
+ },
+ },
+ };
+ match polariton_server::utils::send_packet_async(&to_send, socket, &ctx).await {
+ Ok(_) => {},
+ Err(e) => {
+ log::error!("Failed to send auth ack packet: {}", e);
+ return None;
+ }
+ }
+
+ // join lobby
+ log::debug!("(join lobby) Handling fourth packet");
+ let mut packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) join packet: {}", e);
+ return None;
+ }
+ };
+ while let Packet::Ping(ping) = packet_j {
+ polariton_server::utils::handle_ping_async(ping, socket, &Default::default()).await.unwrap_or_default();
+ packet_j = match polariton_server::utils::receive_packet_async(socket, &ctx).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) join packet: {}", e);
+ return None;
+ }
+ };
+ }
+ if let Packet::Packet(msg) = &packet_j {
+ if let Message::Standard(st) = &msg.message {
+ if let Data::OpReq(req) = &st.data {
+ if req.code == 226 { // join lobby (but for real this time)
+ let mut params = std::collections::HashMap::::new();
+ //params.insert(252 /* actors in game */, Typed::Str(game_server_url.into()));
+ params.insert(254 /* game server address */, Typed::Int(42));
+ params.insert(249 /* actor properties */, Typed::HashMap(Vec::new().into()));
+ params.insert(248 /* game properties */, Typed::HashMap(Vec::new().into()));
+ let resp = Packet::from_message(
+ Message::Standard(
+ StandardMessage { flags: 0,
+ data: Data::OpResp(OperationResponse {
+ code: req.code,
+ return_code: 0,
+ message: Typed::Null,
+ params: params.into(),
+ }),
+ }.encrypt(true)), 0, true, &ctx).unwrap();
+ match polariton_server::utils::send_packet_async(&resp, socket, &ctx).await {
+ Ok(_) => {},
+ Err(e) => {
+ log::error!("Failed to send lobby ack packet: {}", e);
+ return None;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Some(crypto)
+}
diff --git a/rc_singleplayer_room/src/operations/eac.rs b/rc_singleplayer_room/src/operations/eac.rs
new file mode 100644
index 0000000..fc2366f
--- /dev/null
+++ b/rc_singleplayer_room/src/operations/eac.rs
@@ -0,0 +1,23 @@
+use polariton_server::operations::{Operation, OperationCode};
+
+pub struct EacChallengeIgnorer;
+
+impl Operation for EacChallengeIgnorer {
+ type State = ();
+ type User = crate::UserTy;
+
+ fn handle(&self, params: polariton::operation::ParameterTable, _: &mut Self::State, _: &Self::User) -> polariton::operation::OperationResponse {
+ polariton::operation::OperationResponse {
+ code: 5, // skip the challenge (hopefully)
+ return_code: 0,
+ message: polariton::operation::Typed::Null,
+ params,
+ }
+ }
+}
+
+impl OperationCode for EacChallengeIgnorer {
+ fn op_code() -> u8 {
+ 4
+ }
+}
diff --git a/rc_singleplayer_room/src/operations/load_ai_robots.rs b/rc_singleplayer_room/src/operations/load_ai_robots.rs
new file mode 100644
index 0000000..88a7b93
--- /dev/null
+++ b/rc_singleplayer_room/src/operations/load_ai_robots.rs
@@ -0,0 +1,855 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::ParameterTable;
+
+use crate::data::player_data::*;
+
+const PARAM_KEY: u8 = 8;
+
+const VALID_ROBOT: &[u8] = &[64,
+ 0,
+ 0,
+ 0,
+ 38,
+ 190,
+ 25,
+ 77,
+ 24,
+ 6,
+ 29,
+ 0,
+ 80,
+ 135,
+ 103,
+ 211,
+ 27,
+ 4,
+ 27,
+ 6,
+ 80,
+ 135,
+ 103,
+ 211,
+ 21,
+ 4,
+ 27,
+ 12,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 26,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 27,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 28,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 25,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 29,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 23,
+ 4,
+ 29,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 23,
+ 4,
+ 28,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 23,
+ 4,
+ 27,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 23,
+ 4,
+ 26,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 23,
+ 4,
+ 25,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 25,
+ 4,
+ 29,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 25,
+ 4,
+ 27,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 25,
+ 4,
+ 28,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 25,
+ 4,
+ 26,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 25,
+ 4,
+ 25,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 26,
+ 4,
+ 29,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 26,
+ 4,
+ 28,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 26,
+ 4,
+ 27,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 26,
+ 4,
+ 26,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 26,
+ 4,
+ 25,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 22,
+ 4,
+ 25,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 22,
+ 4,
+ 26,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 22,
+ 4,
+ 27,
+ 23,
+ 198,
+ 224,
+ 138,
+ 13,
+ 22,
+ 4,
+ 28,
+ 23,
+ 198,
+ 224,
+ 138,
+ 13,
+ 22,
+ 4,
+ 29,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 25,
+ 5,
+ 29,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 23,
+ 5,
+ 29,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 5,
+ 29,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 22,
+ 5,
+ 28,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 26,
+ 5,
+ 28,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 25,
+ 5,
+ 26,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 23,
+ 5,
+ 26,
+ 0,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 24,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 23,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 22,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 21,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 20,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 19,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 18,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 17,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 16,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 15,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 24,
+ 4,
+ 14,
+ 22,
+ 198,
+ 224,
+ 138,
+ 13,
+ 23,
+ 4,
+ 17,
+ 12,
+ 198,
+ 224,
+ 138,
+ 13,
+ 25,
+ 4,
+ 17,
+ 6,
+ 198,
+ 224,
+ 138,
+ 13,
+ 23,
+ 4,
+ 16,
+ 12,
+ 198,
+ 224,
+ 138,
+ 13,
+ 25,
+ 4,
+ 16,
+ 6,
+ 198,
+ 224,
+ 138,
+ 13,
+ 23,
+ 4,
+ 15,
+ 12,
+ 198,
+ 224,
+ 138,
+ 13,
+ 25,
+ 4,
+ 15,
+ 6,
+ 198,
+ 224,
+ 138,
+ 13,
+ 23,
+ 4,
+ 14,
+ 12,
+ 198,
+ 224,
+ 138,
+ 13,
+ 25,
+ 4,
+ 14,
+ 6,
+ 198,
+ 224,
+ 138,
+ 13,
+ 22,
+ 4,
+ 17,
+ 12,
+ 198,
+ 224,
+ 138,
+ 13,
+ 26,
+ 4,
+ 17,
+ 6,
+ 198,
+ 224,
+ 138,
+ 13,
+ 22,
+ 4,
+ 16,
+ 12,
+ 198,
+ 224,
+ 138,
+ 13,
+ 26,
+ 4,
+ 16,
+ 6,
+ 198,
+ 224,
+ 138,
+ 13,
+ 22,
+ 4,
+ 15,
+ 12,
+ 198,
+ 224,
+ 138,
+ 13,
+ 26,
+ 4,
+ 15,
+ 6,
+ 198,
+ 224,
+ 138,
+ 13,
+ 22,
+ 4,
+ 14,
+ 12,
+ 198,
+ 224,
+ 138,
+ 13,
+ 26,
+ 4,
+ 14,
+ 6,
+ 240,
+ 75,
+ 110,
+ 137,
+ 21,
+ 4,
+ 15,
+ 12,
+ 240,
+ 75,
+ 110,
+ 137,
+ 27,
+ 4,
+ 15,
+ 6];
+
+const VALID_COLOUR: &[u8] = &[64,
+ 0,
+ 0,
+ 0,
+ 0,
+ 24,
+ 6,
+ 29,
+ 0,
+ 27,
+ 4,
+ 27,
+ 0,
+ 21,
+ 4,
+ 27,
+ 0,
+ 24,
+ 4,
+ 26,
+ 0,
+ 24,
+ 4,
+ 27,
+ 0,
+ 24,
+ 4,
+ 28,
+ 0,
+ 24,
+ 4,
+ 25,
+ 0,
+ 24,
+ 4,
+ 29,
+ 0,
+ 23,
+ 4,
+ 29,
+ 0,
+ 23,
+ 4,
+ 28,
+ 0,
+ 23,
+ 4,
+ 27,
+ 0,
+ 23,
+ 4,
+ 26,
+ 0,
+ 23,
+ 4,
+ 25,
+ 0,
+ 25,
+ 4,
+ 29,
+ 0,
+ 25,
+ 4,
+ 27,
+ 0,
+ 25,
+ 4,
+ 28,
+ 0,
+ 25,
+ 4,
+ 26,
+ 0,
+ 25,
+ 4,
+ 25,
+ 0,
+ 26,
+ 4,
+ 29,
+ 0,
+ 26,
+ 4,
+ 28,
+ 0,
+ 26,
+ 4,
+ 27,
+ 0,
+ 26,
+ 4,
+ 26,
+ 0,
+ 26,
+ 4,
+ 25,
+ 0,
+ 22,
+ 4,
+ 25,
+ 0,
+ 22,
+ 4,
+ 26,
+ 0,
+ 22,
+ 4,
+ 27,
+ 0,
+ 22,
+ 4,
+ 28,
+ 0,
+ 22,
+ 4,
+ 29,
+ 1,
+ 25,
+ 5,
+ 29,
+ 1,
+ 23,
+ 5,
+ 29,
+ 1,
+ 24,
+ 5,
+ 29,
+ 1,
+ 22,
+ 5,
+ 28,
+ 1,
+ 26,
+ 5,
+ 28,
+ 1,
+ 25,
+ 5,
+ 26,
+ 1,
+ 23,
+ 5,
+ 26,
+ 0,
+ 24,
+ 4,
+ 24,
+ 0,
+ 24,
+ 4,
+ 23,
+ 0,
+ 24,
+ 4,
+ 22,
+ 0,
+ 24,
+ 4,
+ 21,
+ 0,
+ 24,
+ 4,
+ 20,
+ 0,
+ 24,
+ 4,
+ 19,
+ 0,
+ 24,
+ 4,
+ 18,
+ 0,
+ 24,
+ 4,
+ 17,
+ 0,
+ 24,
+ 4,
+ 16,
+ 0,
+ 24,
+ 4,
+ 15,
+ 0,
+ 24,
+ 4,
+ 14,
+ 0,
+ 23,
+ 4,
+ 17,
+ 0,
+ 25,
+ 4,
+ 17,
+ 0,
+ 23,
+ 4,
+ 16,
+ 0,
+ 25,
+ 4,
+ 16,
+ 0,
+ 23,
+ 4,
+ 15,
+ 0,
+ 25,
+ 4,
+ 15,
+ 0,
+ 23,
+ 4,
+ 14,
+ 0,
+ 25,
+ 4,
+ 14,
+ 0,
+ 22,
+ 4,
+ 17,
+ 0,
+ 26,
+ 4,
+ 17,
+ 0,
+ 22,
+ 4,
+ 16,
+ 0,
+ 26,
+ 4,
+ 16,
+ 0,
+ 22,
+ 4,
+ 15,
+ 0,
+ 26,
+ 4,
+ 15,
+ 0,
+ 22,
+ 4,
+ 14,
+ 0,
+ 26,
+ 4,
+ 14,
+ 0,
+ 21,
+ 4,
+ 15,
+ 0,
+ 27,
+ 4,
+ 15];
+
+pub(super) fn tdm_machines_provider() -> SimpleFunc<1, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, user: &crate::UserTy| {
+ let ulock = user.read().unwrap();
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, PlayerDatas {
+ players: vec![
+ PlayerData {
+ name: ulock.uuid.clone(),
+ display_name: ulock.uuid.clone(),
+ mastery: 1,
+ tier: 1,
+ robot_name: "RE_machine_name_mine_sp".to_owned(),
+ robot_map: VALID_ROBOT.iter().map(|x| *x).collect(),
+ team: 0,
+ has_premium: false,
+ robot_uuid: "12345_12345".to_owned(),
+ cpu: 0,
+ weapon_order: vec![20000200, 0, 0],
+ colour_map: VALID_COLOUR.iter().map(|x| *x).collect(),
+ is_ai: false,
+ spawn_effect: "Spawn_Warp".to_owned(),
+ death_effect: "Explosion_Warp".to_owned(),
+ player_rank: 1,
+ weapon_rank: vec![(20000200, 1), (0, 0)].into_iter().collect(),
+ },
+ // TODO: use SingleplayerEvent 3 (SpawnRobot) to spawn enemy bots instead
+ // doing it through this op response seems to have a bug/flaw (intentional?) in the code
+ /*PlayerData {
+ name: "RE_username0".to_owned(),
+ display_name: "RE_displayname0".to_owned(),
+ mastery: 1,
+ tier: 1,
+ robot_name: "RE_machine_name0_sp".to_owned(),
+ robot_map: VALID_ROBOT.iter().map(|x| *x).collect(),
+ team: 1,
+ has_premium: false,
+ robot_uuid: "123_123".to_owned(),
+ cpu: 0,
+ weapon_order: vec![20000200, 0, 0],
+ colour_map: VALID_COLOUR.iter().map(|x| *x).collect(),
+ is_ai: true,
+ spawn_effect: "Spawn_Warp".to_owned(),
+ death_effect: "Explosion_Warp".to_owned(),
+ player_rank: 1,
+ weapon_rank: vec![(20000200, 1), (0, 0)].into_iter().collect(),
+ },
+ PlayerData {
+ name: "RE_username1".to_owned(),
+ display_name: "RE_displayname1".to_owned(),
+ mastery: 1,
+ tier: 1,
+ robot_name: "RE_machine_name1_sp".to_owned(),
+ robot_map: VALID_ROBOT.iter().map(|x| *x).collect(),
+ team: 1,
+ has_premium: false,
+ robot_uuid: "1_1".to_owned(),
+ cpu: 0,
+ weapon_order: vec![20000200, 0, 0],
+ colour_map: VALID_COLOUR.iter().map(|x| *x).collect(),
+ is_ai: true,
+ spawn_effect: "Spawn_Warp".to_owned(),
+ death_effect: "Explosion_Warp".to_owned(),
+ player_rank: 1,
+ weapon_rank: vec![(20000200, 1), (0, 0)].into_iter().collect(),
+ },*/
+ ]
+ }.as_transmissible());
+ Ok(params.into())
+ })
+}
diff --git a/rc_singleplayer_room/src/operations/mod.rs b/rc_singleplayer_room/src/operations/mod.rs
new file mode 100644
index 0000000..cfc8f9f
--- /dev/null
+++ b/rc_singleplayer_room/src/operations/mod.rs
@@ -0,0 +1,13 @@
+mod more_auth;
+mod eac;
+mod load_ai_robots;
+
+use polariton_server::operations::OperationsHandler;
+
+pub fn handler() -> OperationsHandler {
+ OperationsHandler::::new()
+ .without_state(more_auth::MoreLobbyAuth)
+ .without_state(eac::EacChallengeIgnorer)
+ .without_state(load_ai_robots::tdm_machines_provider())
+ //.without_state(polariton_server::operations::Ack::<33, _>::default()) // get user clan info (this is equivalent to not being in a clan)
+}
diff --git a/rc_singleplayer_room/src/operations/more_auth.rs b/rc_singleplayer_room/src/operations/more_auth.rs
new file mode 100644
index 0000000..c1fc5ee
--- /dev/null
+++ b/rc_singleplayer_room/src/operations/more_auth.rs
@@ -0,0 +1,42 @@
+use polariton::operation::Typed;
+use polariton_server::operations::{Operation, OperationCode};
+
+pub struct MoreLobbyAuth;
+
+impl MoreLobbyAuth {
+ const AUTH_PAYLOAD_KEY: u8 = 245;
+}
+
+impl Operation for MoreLobbyAuth {
+ type State = ();
+ type User = crate::UserTy;
+
+ fn handle(&self, params: polariton::operation::ParameterTable, _: &mut Self::State, user: &Self::User) -> polariton::operation::OperationResponse {
+ let params_dict = params.to_dict();
+ if let Some(Typed::Str(auth_payload)) = params_dict.get(&Self::AUTH_PAYLOAD_KEY) {
+ let mut write_lock = user.write().unwrap();
+ if write_lock.update_with_auth(&auth_payload.string) {
+ let mut resp_params = std::collections::HashMap::new();
+ resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0));
+ return polariton::operation::OperationResponse {
+ code: 230,
+ return_code: 0,
+ message: polariton::operation::Typed::Null,
+ params: resp_params.into(),
+ }
+ }
+ }
+ polariton::operation::OperationResponse {
+ code: 230,
+ return_code: 120,
+ message: polariton::operation::Typed::Null,
+ params: std::collections::HashMap::new().into(),
+ }
+ }
+}
+
+impl OperationCode for MoreLobbyAuth {
+ fn op_code() -> u8 {
+ 230
+ }
+}
diff --git a/rc_singleplayer_room/src/state.rs b/rc_singleplayer_room/src/state.rs
new file mode 100644
index 0000000..bc39273
--- /dev/null
+++ b/rc_singleplayer_room/src/state.rs
@@ -0,0 +1,27 @@
+use std::sync::RwLock;
+
+#[derive(Default, Debug)]
+pub struct UserState {
+ pub uuid: String,
+ pub token: String,
+ pub refresh_token: String,
+}
+
+impl UserState {
+ pub fn update_with_auth(&mut self, auth_str: &str) -> bool {
+ let splits: Vec<&str> = auth_str.split(';').collect();
+ if splits.len() != 3 {
+ log::warn!("Invalid auth payload: {}", auth_str);
+ false
+ } else {
+ self.uuid = splits[0].to_owned();
+ self.token = splits[1].to_owned();
+ self.refresh_token = splits[2].to_owned();
+ true
+ }
+ }
+
+ pub fn new() -> crate::UserTy {
+ RwLock::new(UserState::default())
+ }
+}
diff --git a/rc_social_room/src/operations/platoon_data.rs b/rc_social_room/src/operations/platoon_data.rs
index bd22125..d6ac699 100644
--- a/rc_social_room/src/operations/platoon_data.rs
+++ b/rc_social_room/src/operations/platoon_data.rs
@@ -1,15 +1,15 @@
use polariton_server::operations::SimpleFunc;
use polariton::operation::ParameterTable;
-//const PLATOON_ID_PARAM_KEY: u8 = 16;
+const PLATOON_ID_PARAM_KEY: u8 = 16;
//const PLATOON_LEADER_PARAM_KEY: u8 = 17;
//const USER_LIST_PARAM_KEY: u8 = 7;
pub(super) fn platoon_provider() -> SimpleFunc<18, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result, i16>) + Sync + Sync, C> {
SimpleFunc::new(|params, _| {
- //let mut params = params.to_dict();
+ let mut params = params.to_dict();
// if platoon ID is not provided, you're not in a platoon
- //Ok(params.into())
- Ok(params)
+ params.insert(PLATOON_ID_PARAM_KEY, polariton::operation::Typed::Null);
+ Ok(params.into())
})
}