diff --git a/Cargo.lock b/Cargo.lock
index de74a00..b0dbc96 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1550,6 +1550,14 @@ dependencies = [
"simple-rijndael",
]
+[[package]]
+name = "polariton_server"
+version = "0.1.0"
+dependencies = [
+ "log",
+ "polariton",
+]
+
[[package]]
name = "powerfmt"
version = "0.2.0"
@@ -1759,6 +1767,21 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "rc_services_room"
+version = "0.1.0"
+dependencies = [
+ "base64 0.22.1",
+ "clap",
+ "env_logger",
+ "hex",
+ "log",
+ "polariton",
+ "polariton_auth",
+ "polariton_server",
+ "tokio",
+]
+
[[package]]
name = "rc_static_data"
version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index cf90889..a3644dd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,7 @@ edition = "2021"
[workspace]
members = [
- "auth", "polariton_auth", "rc_services", "rc_static_data",
+ "auth", "polariton_auth", "rc_services", "rc_static_data", "rc_services_room"
]
[workspace.dependencies]
@@ -14,4 +14,5 @@ libfj = { version = "0.7.5", path = "../libfj" }
log = "0.4"
env_logger = "0.11"
clap = { version = "4.5", features = [ "derive" ] }
-polariton = { version = "*", path = "../polariton" }
+polariton = { version = "0.1", path = "../polariton" }
+polariton_server = { version = "0.1", path = "../polariton/server" }
diff --git a/README.md b/README.md
index 2829c86..f01909c 100644
--- a/README.md
+++ b/README.md
@@ -6,15 +6,17 @@ A collection of open source servers for FreeJam games
### CardLife
-To get CardLife to use these servers, replace the ServerConfig.json file in the game files with [this ServerConfig.json](assets/cardlife/ServerConfig.json).
+To get CardLife to use these servers, replace the `ServerConfig.json` file in the game files with [this ServerConfig.json](assets/cardlife/ServerConfig.json).
### Robocraft
-To get Robocraft to use these servers, please add the following to your OS's `hosts` file:
+To get Robocraft to use these servers, place [this servenvmulti.config](assets/robocraft/serenvmulti.config) file in the game files.
+
+
+You may also need to add the following to your OS's `hosts` file:
```
127.0.0.1 robocraftstaticdata.s3.amazonaws.com
-127.0.0.1 services-1.servers.robocraftgame.com
```
The `hosts` file can be found at `/etc/hosts` on Linux and `C:\Windows\system32\drivers\etc\hosts` on Windows. Usually this requires elevated permissions (root/admin) to edit.
diff --git a/assets/robocraft/servenvmulti.config b/assets/robocraft/servenvmulti.config
new file mode 100644
index 0000000..47eb7c7
--- /dev/null
+++ b/assets/robocraft/servenvmulti.config
@@ -0,0 +1,12 @@
+
+ dev
+ robocraft
+
+
+ 127.0.0.1:4532
+ not.used.hopefully:4534
+ chat.server.not.a.valid.tld:4534
+ http://127.0.0.1:8001/
+
+
+
diff --git a/rc_services/src/main.rs b/rc_services/src/main.rs
index 26c515b..f879cd3 100644
--- a/rc_services/src/main.rs
+++ b/rc_services/src/main.rs
@@ -24,10 +24,17 @@ async fn main() -> std::io::Result<()> {
let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?;
+ #[cfg(not(debug_assertions))]
loop {
let (socket, address) = listener.accept().await?;
tokio::spawn(process_socket(socket, address, NonZero::new(args.retries), redirect_static, room_name_static));
}
+ #[cfg(debug_assertions)]
+ {
+ let (socket, address) = listener.accept().await?;
+ process_socket(socket, address, NonZero::new(args.retries), redirect_static, room_name_static).await;
+ Ok(())
+ }
}
async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAddr, retries: Option>, game_server_url: &str, game_server_name: &str) {
@@ -50,6 +57,7 @@ async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAdd
Packet::Packet(packet) => log::warn!("Not handling packet {:?}", packet),
}
}
+ log::debug!("Goodbye connection from address {}", address);
}
async fn handle_ping(ping: Ping, buf: &mut Vec, socket: &mut net::TcpStream) {
diff --git a/rc_services_room/Cargo.toml b/rc_services_room/Cargo.toml
new file mode 100644
index 0000000..635ae2d
--- /dev/null
+++ b/rc_services_room/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "rc_services_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
+base64 = "0.22"
+hex = "0.4"
diff --git a/rc_services_room/build_arm64.sh b/rc_services_room/build_arm64.sh
new file mode 100755
index 0000000..7010ff6
--- /dev/null
+++ b/rc_services_room/build_arm64.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+cargo build --release --target aarch64-unknown-linux-musl
diff --git a/rc_services_room/run_debug.sh b/rc_services_room/run_debug.sh
new file mode 100755
index 0000000..eedcd2c
--- /dev/null
+++ b/rc_services_room/run_debug.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+RUST_BACKTRACE=1 RUST_LOG=debug cargo run
diff --git a/rc_services_room/src/cli.rs b/rc_services_room/src/cli.rs
new file mode 100644
index 0000000..018412d
--- /dev/null
+++ b/rc_services_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 = 4533)]
+ pub port: u16,
+
+ /// IP Address on which to accept connections
+ #[arg(long, default_value_t = {"127.0.0.1".to_string()})]
+ pub ip: String,
+
+ /// Socket read tries before giving up (0 to never give up)
+ #[arg(long, default_value_t = 5)]
+ pub retries: usize,
+}
+
+impl CliArgs {
+ pub fn get() -> Self {
+ Self::parse()
+ }
+}
diff --git a/rc_services_room/src/data/battle_arena_config.rs b/rc_services_room/src/data/battle_arena_config.rs
new file mode 100644
index 0000000..67d9247
--- /dev/null
+++ b/rc_services_room/src/data/battle_arena_config.rs
@@ -0,0 +1,44 @@
+use polariton::operation::Typed;
+use base64::{Engine, engine::general_purpose::STANDARD};
+
+pub struct BattleArenaData {
+ pub protonium_health: i64,
+ pub respawn_time_seconds: i64,
+ pub heal_over_time_per_tower: Vec,
+ pub base_machine_map: Vec, // aka team base model, converted into base64
+ pub equalizer_model: Vec, // converted into base64
+ pub equalizer_health: i64,
+ pub equalizer_trigger_time_seconds: Vec,
+ pub equalizer_warning_seconds: i64,
+ pub equalizer_duration_seconds: Vec,
+ pub capture_time_seconds_per_player: Vec,
+ pub num_segments: i32,
+ pub heal_escalation_time_seconds: i64,
+}
+
+fn to_obj_arr_u(slice: &[u64]) -> Typed {
+ Typed::ObjArr(slice.iter().map(|x| Typed::Long(*x as i64)).collect::>().into())
+}
+
+fn to_obj_arr_i(slice: &[i64]) -> Typed {
+ Typed::ObjArr(slice.iter().map(|x| Typed::Long(*x)).collect::>().into())
+}
+
+impl BattleArenaData {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::HashMap(vec![
+ (Typed::Str("protoniumHealth".into()), Typed::Long(self.protonium_health)),
+ (Typed::Str("respawnTimeSeconds".into()), Typed::Long(self.respawn_time_seconds)),
+ (Typed::Str("healOverTimePerTower".into()), to_obj_arr_u(&self.heal_over_time_per_tower)),
+ (Typed::Str("baseMachineMap".into()), Typed::Str(STANDARD.encode(&self.base_machine_map).into())),
+ (Typed::Str("equalizerModel".into()), Typed::Str(STANDARD.encode(&self.equalizer_model).into())),
+ (Typed::Str("equalizerHealth".into()), Typed::Long(self.equalizer_health)),
+ (Typed::Str("equalizerTriggerTimeSeconds".into()), to_obj_arr_u(&self.equalizer_trigger_time_seconds)),
+ (Typed::Str("equalizerWarningSeconds".into()), Typed::Long(self.equalizer_warning_seconds)),
+ (Typed::Str("equalizerDurationSeconds".into()), to_obj_arr_u(&self.equalizer_duration_seconds)),
+ (Typed::Str("captureTimeSecondsPerPlayer".into()), to_obj_arr_i(&self.capture_time_seconds_per_player)),
+ (Typed::Str("numSegments".into()), Typed::Int(self.num_segments)),
+ (Typed::Str("healEscalationTimeSeconds".into()), Typed::Long(self.heal_escalation_time_seconds)),
+ ].into())
+ }
+}
diff --git a/rc_services_room/src/data/client_config.rs b/rc_services_room/src/data/client_config.rs
new file mode 100644
index 0000000..3ba7f20
--- /dev/null
+++ b/rc_services_room/src/data/client_config.rs
@@ -0,0 +1,32 @@
+use polariton::operation::Typed;
+
+pub struct GameplaySettings {
+ pub show_tutorial_after_date: String,
+ pub health_threshold: f32, // percent
+ pub microbot_sphere: f32, // radius
+ pub misfire_angle: f32, // degrees?
+ pub shield_dps: i32,
+ pub shield_hps: u32,
+ pub request_review_level: u32,
+ pub critical_ratio: f32,
+ pub cross_promo_image: String, // url
+ pub cross_promo_link: String, // url
+}
+
+impl GameplaySettings {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::HashMap(vec![
+ // TODO
+ (Typed::Str("showTutorialAfterDate".into()), Typed::Str(self.show_tutorial_after_date.clone().into())),
+ (Typed::Str("healthTresholdPercent".into()), Typed::Float(self.health_threshold)),
+ (Typed::Str("microbotSphereRadius".into()), Typed::Float(self.microbot_sphere)),
+ (Typed::Str("smartRotationMisfireAngle".into()), Typed::Float(self.misfire_angle)),
+ (Typed::Str("fusionShieldDPS".into()), Typed::Int(self.shield_dps)),
+ (Typed::Str("fusionShieldHPS".into()), Typed::Int(self.shield_hps as i32)),
+ (Typed::Str("requestReviewAtLevel".into()), Typed::Int(self.request_review_level as i32)),
+ (Typed::Str("criticalRatio".into()), Typed::Float(self.critical_ratio)),
+ (Typed::Str("crossPromotionAdImageUrl".into()), Typed::Str(self.cross_promo_image.clone().into())),
+ (Typed::Str("crossPromotionAdLinkUrl".into()), Typed::Str(self.cross_promo_link.clone().into())),
+ ].into())
+ }
+}
diff --git a/rc_services_room/src/data/cosmetic_limits.rs b/rc_services_room/src/data/cosmetic_limits.rs
new file mode 100644
index 0000000..3af0ab2
--- /dev/null
+++ b/rc_services_room/src/data/cosmetic_limits.rs
@@ -0,0 +1,18 @@
+use polariton::operation::Typed;
+
+pub struct CosmeticLimitsData {
+ pub others_max_holo_and_trails: u32,
+ pub others_max_headlamps: u32,
+ pub others_max_cosmetic_items_with_particles: u32,
+}
+
+impl CosmeticLimitsData {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::HashMap(vec![
+ (Typed::Str("OthersMaxNumberHoloAndTrails".into()), Typed::Int(self.others_max_holo_and_trails as i32)),
+ (Typed::Str("OthersMaxNumberHeadlamps".into()), Typed::Int(self.others_max_headlamps as i32)),
+ (Typed::Str("OthersMaxCosmeticItemsWithParticleSystem".into()), Typed::Int(self.others_max_cosmetic_items_with_particles as i32)),
+ ].into()
+ )
+ }
+}
diff --git a/rc_services_room/src/data/cpu_limits.rs b/rc_services_room/src/data/cpu_limits.rs
new file mode 100644
index 0000000..4963369
--- /dev/null
+++ b/rc_services_room/src/data/cpu_limits.rs
@@ -0,0 +1,22 @@
+use polariton::operation::Typed;
+
+pub struct CpuLimitsData {
+ pub premium_for_life_cosmetic_gpu: i32,
+ pub premium_cosmetic_cpu: i32,
+ pub no_premium_cosmetic_cpu: i32,
+ pub max_regular_health: i32,
+ pub max_megabot_health: i32,
+}
+
+impl CpuLimitsData {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::HashMap(vec![
+ (Typed::Str("PremiumForLifeCosmeticCPU".into()), Typed::Int(self.premium_for_life_cosmetic_gpu)),
+ (Typed::Str("PremiumCosmeticCPU".into()), Typed::Int(self.premium_cosmetic_cpu)),
+ (Typed::Str("NoPremiumCosmeticCPU".into()), Typed::Int(self.no_premium_cosmetic_cpu)),
+ (Typed::Str("MaxRegularHealth".into()), Typed::Int(self.max_regular_health)),
+ (Typed::Str("MaxMegabotHealth".into()), Typed::Int(self.max_megabot_health)),
+ ].into()
+ )
+ }
+}
diff --git a/rc_services_room/src/data/crf_config.rs b/rc_services_room/src/data/crf_config.rs
new file mode 100644
index 0000000..458cd98
--- /dev/null
+++ b/rc_services_room/src/data/crf_config.rs
@@ -0,0 +1,17 @@
+use polariton::operation::Typed;
+
+pub struct RobotShopConfig {
+ pub cpu_ranges: Vec,
+ pub submission_mult: f32,
+ pub earnings_mult: f32,
+}
+
+impl RobotShopConfig {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::HashMap(vec![
+ (Typed::Str("robotShopPriceRanges".into()), Typed::IntArr(self.cpu_ranges.clone().into())),
+ (Typed::Str("submissionMultiplier".into()), Typed::Float(self.submission_mult)),
+ (Typed::Str("earningsMultiplier".into()), Typed::Float(self.earnings_mult)),
+ ].into())
+ }
+}
diff --git a/rc_services_room/src/data/cube_list.rs b/rc_services_room/src/data/cube_list.rs
new file mode 100644
index 0000000..2c12e17
--- /dev/null
+++ b/rc_services_room/src/data/cube_list.rs
@@ -0,0 +1,120 @@
+#![allow(dead_code)]
+use std::collections::HashMap;
+use polariton::operation::Typed;
+
+pub struct CubeInfo {
+ pub cpu: u32,
+ pub health: u32,
+ pub health_boost: f32,
+ pub grey_out_in_tutorial: bool,
+ pub visibility: VisibilityMode,
+ pub indestructible: bool,
+ pub category: u32,
+ pub placements: u32, // default 63
+ pub protonium: bool,
+ pub unlocked_by_league: bool,
+ pub league_unlock_index: i32,
+ pub stats: HashMap,
+ pub description: String,
+ pub size: ItemTier,
+ pub type_: ItemType,
+ pub ranking: i32,
+ pub cosmetic: bool,
+ pub variant_of: String, // cube id (in hex)
+ pub ignore_in_weapon_list: bool,
+}
+
+impl CubeInfo {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::HashMap(vec![
+ (Typed::Str("cpuRating".into()), Typed::Int(self.cpu as i32)),
+ (Typed::Str("health".into()), Typed::Int(self.health as i32)),
+ (Typed::Str("healthBoost".into()), Typed::Float(self.health_boost)),
+ (Typed::Str("GreyOutInTutorial".into()), Typed::Bool(self.grey_out_in_tutorial.into())),
+ (Typed::Str("buildVisibility".into()), Typed::Str(self.visibility.as_str().into())),
+ (Typed::Str("isIndestructible".into()), Typed::Bool(self.indestructible.into())),
+ (Typed::Str("ItemCategory".into()), Typed::Int(self.category as i32)),
+ (Typed::Str("PlacementFaces".into()), Typed::Int(self.placements as i32)),
+ (Typed::Str("protoniumCrystal".into()), Typed::Bool(self.protonium.into())),
+ (Typed::Str("UnlockedByLeague".into()), Typed::Bool(self.unlocked_by_league.into())),
+ (Typed::Str("LeagueUnlockIndex".into()), Typed::Int(self.league_unlock_index)),
+ (Typed::Str("DisplayStats".into()), {
+ let items: Vec<(Typed, Typed)> = self.stats.iter().map(|(key, val)| (Typed::Str(key.into()), val.to_owned())).collect();
+ Typed::HashMap(items.into())
+ }),
+ (Typed::Str("Description".into()), Typed::Str(self.description.clone().into())),
+ (Typed::Str("ItemSize".into()), Typed::Int(self.size as i32)),
+ (Typed::Str("ItemType".into()), Typed::Str(self.type_.as_str().into())),
+ (Typed::Str("robotRanking".into()), Typed::Int(self.ranking)),
+ (Typed::Str("isCosmetic".into()), Typed::Bool(self.cosmetic.into())),
+ (Typed::Str("variantOf".into()), Typed::Str(self.variant_of.clone().into())),
+ (Typed::Str("ignoreInWeaponsList".into()), Typed::Bool(self.ignore_in_weapon_list.into())), // optional
+ ].into())
+ }
+}
+
+#[derive(Clone, Copy)]
+pub enum VisibilityMode {
+ Mothership,
+ All,
+ Tutorial,
+ None,
+}
+
+impl VisibilityMode {
+ fn as_str(&self) -> &'static str {
+ match self {
+ Self::Mothership => "Mothership",
+ Self::All => "All",
+ Self::Tutorial => "Tutorial",
+ Self::None => "None",
+ }
+ }
+}
+
+#[repr(u32)]
+#[derive(Clone, Copy)]
+pub enum ItemTier {
+ NoTier = 0,
+ T0 = 100,
+ T1 = 200,
+ T2 = 300,
+ T3 = 400,
+ T4 = 500,
+ T5 = 600,
+}
+
+impl ItemTier {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ ItemTier::NoTier => "NotAWeapon",
+ ItemTier::T0 => "T0",
+ ItemTier::T1 => "T1",
+ ItemTier::T2 => "T2",
+ ItemTier::T3 => "T3",
+ ItemTier::T4 => "T4",
+ ItemTier::T5 => "T5",
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+pub enum ItemType {
+ NoFunction,
+ Weapon,
+ Module,
+ Movement,
+ Cosmetic,
+}
+
+impl ItemType {
+ fn as_str(&self) -> &'static str {
+ match self {
+ Self::NoFunction => "NotAFunctionalItem",
+ Self::Weapon => "Weapon",
+ Self::Module => "Module",
+ Self::Movement => "Movement",
+ Self::Cosmetic => "Cosmetic",
+ }
+ }
+}
diff --git a/rc_services_room/src/data/customisation_info.rs b/rc_services_room/src/data/customisation_info.rs
new file mode 100644
index 0000000..ebfdc58
--- /dev/null
+++ b/rc_services_room/src/data/customisation_info.rs
@@ -0,0 +1,23 @@
+use polariton::operation::Typed;
+
+pub struct CustomisationData {
+ pub id: String,
+ pub localised_name: String,
+ pub skin_scene_name: String,
+ pub simulation_prefab: String,
+ pub preview_image_name: String,
+ pub is_default: bool,
+}
+
+impl CustomisationData {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::HashMap(vec![
+ (Typed::Str("id".into()), Typed::Str(self.id.clone().into())),
+ (Typed::Str("localisedName".into()), Typed::Str(self.localised_name.clone().into())),
+ (Typed::Str("skinsceneName".into()), Typed::Str(self.skin_scene_name.clone().into())),
+ (Typed::Str("simulationPrefab".into()), Typed::Str(self.simulation_prefab.clone().into())),
+ (Typed::Str("previewImageName".into()), Typed::Str(self.preview_image_name.clone().into())),
+ (Typed::Str("isDefault".into()), Typed::Bool(self.is_default.into())),
+ ].into())
+ }
+}
diff --git a/rc_services_room/src/data/damage_boost.rs b/rc_services_room/src/data/damage_boost.rs
new file mode 100644
index 0000000..a43123d
--- /dev/null
+++ b/rc_services_room/src/data/damage_boost.rs
@@ -0,0 +1,17 @@
+use polariton::operation::{Typed, Dict};
+
+pub struct DamageBoostData {
+ pub damage_map: Vec<(u32, f32)>, // (cpu, boost)
+}
+
+impl DamageBoostData {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 42, // obj
+ items: self.damage_map.iter()
+ .map(|(cpu, boost)| (Typed::Str(cpu.to_string().into()), Typed::Float(*boost)))
+ .collect(),
+ })
+ }
+}
diff --git a/rc_services_room/src/data/mod.rs b/rc_services_room/src/data/mod.rs
new file mode 100644
index 0000000..5d19c43
--- /dev/null
+++ b/rc_services_room/src/data/mod.rs
@@ -0,0 +1,14 @@
+pub mod cube_list;
+pub mod special_item;
+pub mod premium_config;
+pub mod palette;
+pub mod client_config;
+pub mod crf_config;
+pub mod weapon_list;
+pub mod movement_list;
+pub mod damage_boost;
+pub mod battle_arena_config;
+pub mod cpu_limits;
+pub mod cosmetic_limits;
+pub mod taunts_config;
+pub mod customisation_info;
diff --git a/rc_services_room/src/data/movement_list.rs b/rc_services_room/src/data/movement_list.rs
new file mode 100644
index 0000000..76d2324
--- /dev/null
+++ b/rc_services_room/src/data/movement_list.rs
@@ -0,0 +1,450 @@
+#![allow(dead_code)]
+
+use polariton::operation::{Typed, Dict};
+
+use super::cube_list::ItemTier;
+
+#[derive(Default)]
+pub struct MovementCategoryData {
+ pub horizontal_top_speed: Option,
+ pub vertical_top_speed: Option,
+ pub min_required_items: Option,
+ pub min_item_modifier: Option,
+ pub max_hover_height: Option,
+ pub light_machine_mass: Option,
+ pub heavy_machine_mass: Option,
+ pub specifics: MovementCategorySpecificData,
+ pub stats: Vec<(ItemTier, MovementData)>,
+}
+
+impl MovementCategoryData {
+ pub fn as_transmissible(&self) -> Typed {
+ let mut out = Vec::new();
+ self.horizontal_top_speed.map(|x| out.push((Typed::Str("horizontalTopSpeed".into()), Typed::Float(x))));
+ self.vertical_top_speed.map(|x| out.push((Typed::Str("verticalTopSpeed".into()), Typed::Float(x))));
+ self.min_required_items.map(|x| out.push((Typed::Str("minRequiredItems".into()), Typed::Int(x))));
+ self.min_item_modifier.map(|x| out.push((Typed::Str("minItemsModifier".into()), Typed::Float(x))));
+ self.max_hover_height.map(|x| out.push((Typed::Str("maxHoverHeight".into()), Typed::Float(x))));
+ self.light_machine_mass.map(|x| out.push((Typed::Str("lightMachineMass".into()), Typed::Float(x))));
+ self.heavy_machine_mass.map(|x| out.push((Typed::Str("heavyMachineMass".into()), Typed::Float(x))));
+ out.append(&mut self.specifics.as_transmissible());
+ for (tier, mov_data) in self.stats.iter() {
+ out.push((Typed::Str(tier.as_str().into()), mov_data.as_transmissible()));
+ }
+ Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 42, // any
+ items: out.into(),
+ })
+ }
+}
+
+#[derive(Default)]
+pub enum MovementCategorySpecificData {
+ #[default]
+ Wheel,
+ Hover(HoverCategoryData),
+ Wing,
+ Rudder, // same as wing
+ Thruster,
+ Propeller, // same as thruster
+ InsectLeg,
+ MechLeg(MechLegCategoryData),
+ SprinterLeg(MechLegCategoryData), // same as mech leg
+ TankTrack,
+ Rotor(RotorCategoryData),
+}
+
+impl MovementCategorySpecificData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ match self {
+ Self::Wheel => Vec::default(),
+ Self::Hover(x) => x.as_transmissible(),
+ Self::Wing => Vec::default(),
+ Self::Rudder => Vec::default(),
+ Self::Thruster => Vec::default(),
+ Self::Propeller => Vec::default(),
+ Self::InsectLeg => Vec::default(),
+ Self::MechLeg(x) => x.as_transmissible(),
+ Self::SprinterLeg(x) => x.as_transmissible(),
+ Self::TankTrack => Vec::default(),
+ Self::Rotor(x) => x.as_transmissible(),
+ }
+ }
+}
+
+pub struct HoverCategoryData {
+ pub height_tolerance: f32,
+ pub force_y_offset: f32,
+ pub turning_scale: f32,
+ pub small_angle_turning_scale: f32,
+ pub max_vertical_velocity: f32,
+ pub hover_damping: f32,
+ pub angular_damping: f32,
+ pub deceleration_multiplier: f32,
+}
+
+impl HoverCategoryData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("heightTolerance".into()), Typed::Float(self.height_tolerance)),
+ (Typed::Str("forceYOffset".into()), Typed::Float(self.force_y_offset)),
+ (Typed::Str("turningScale".into()), Typed::Float(self.turning_scale)),
+ (Typed::Str("smallAngleTurningScale".into()), Typed::Float(self.small_angle_turning_scale)),
+ (Typed::Str("verticalTopSpeed".into()), Typed::Float(self.max_vertical_velocity)),
+ (Typed::Str("hoverDamping".into()), Typed::Float(self.hover_damping)),
+ (Typed::Str("angularDamping".into()), Typed::Float(self.angular_damping)),
+ (Typed::Str("decelerationMultiplier".into()), Typed::Float(self.deceleration_multiplier)),
+ ]
+ }
+}
+
+pub struct MechLegCategoryData {
+ pub deceleration_multiplier: f32,
+}
+
+impl MechLegCategoryData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("decelerationMultiplier".into()), Typed::Float(self.deceleration_multiplier)),
+ ]
+ }
+}
+
+pub struct RotorCategoryData {
+ pub max_turn_rate: f32,
+}
+
+
+impl RotorCategoryData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("maxTurnRate".into()), Typed::Float(self.max_turn_rate)),
+ ]
+ }
+}
+
+pub struct MovementData {
+ pub speed_boost: Option,
+ pub max_carry_mass: Option,
+ pub horizontal_top_speed: Option,
+ pub vertical_top_speed: Option,
+ pub specifics: MovementSpecificData,
+}
+
+impl MovementData {
+ pub fn as_transmissible(&self) -> Typed {
+ let mut out = Vec::new();
+ self.speed_boost.map(|x| out.push((Typed::Str("speedBoost".into()), Typed::Float(x))));
+ self.max_carry_mass.map(|x| out.push((Typed::Str("maxCarryMass".into()), Typed::Float(x))));
+ self.horizontal_top_speed.map(|x| out.push((Typed::Str("horizontalTopSpeed".into()), Typed::Float(x))));
+ self.vertical_top_speed.map(|x| out.push((Typed::Str("verticalTopSpeed".into()), Typed::Float(x))));
+ out.append(&mut self.specifics.as_transmissible());
+ Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 42, // any
+ items: out.into(),
+ })
+ }
+}
+
+pub enum MovementSpecificData {
+ Wheel(WheelData),
+ Hover(HoverData),
+ Wing(AerofoilData),
+ Rudder(AerofoilData), // same as wing
+ Thruster(ThrusterData),
+ Propeller(ThrusterData), // same as thruster
+ InsectLeg(InsectLegData),
+ MechLeg(MechLegData),
+ SprinterLeg(MechLegData), // same as mech leg
+ TankTrack(TankTrackData),
+ Rotor(RotorData),
+}
+
+impl MovementSpecificData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ match self {
+ Self::Wheel(x) => x.as_transmissible(),
+ Self::Hover(x) => x.as_transmissible(),
+ Self::Wing(x) => x.as_transmissible(),
+ Self::Rudder(x) => x.as_transmissible(),
+ Self::Thruster(x) => x.as_transmissible(),
+ Self::Propeller(x) => x.as_transmissible(),
+ Self::InsectLeg(x) => x.as_transmissible(),
+ Self::MechLeg(x) => x.as_transmissible(),
+ Self::SprinterLeg(x) => x.as_transmissible(),
+ Self::TankTrack(x) => x.as_transmissible(),
+ Self::Rotor(x) => x.as_transmissible(),
+ }
+ }
+}
+
+pub struct WheelData {
+ pub steering_speed_light: f32,
+ pub steering_speed_heavy: f32,
+ pub steering_force_multiplier_light: f32,
+ pub steering_force_multiplier_heavy: f32,
+ pub lateral_acceleration_light: f32,
+ pub lateral_acceleration_heavy: f32,
+ pub time_to_max_acceleration_light: f32,
+ pub time_to_max_acceleration_heavy: f32,
+ pub brake_force_light: f32,
+ pub brake_force_heavy: f32,
+}
+
+impl WheelData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("steeringSpeedLight".into()), Typed::Float(self.steering_speed_light)),
+ (Typed::Str("steeringSpeedHeavy".into()), Typed::Float(self.steering_speed_heavy)),
+ (Typed::Str("steeringForceMultiplierLight".into()), Typed::Float(self.steering_force_multiplier_light)),
+ (Typed::Str("steeringForceMultiplierHeavy".into()), Typed::Float(self.steering_force_multiplier_heavy)),
+ (Typed::Str("lateralAccelerationLight".into()), Typed::Float(self.lateral_acceleration_light)),
+ (Typed::Str("lateralAccelerationHeavy".into()), Typed::Float(self.lateral_acceleration_heavy)),
+ (Typed::Str("timeToMaxAccelerationLight".into()), Typed::Float(self.time_to_max_acceleration_light)),
+ (Typed::Str("timeToMaxAccelerationHeavy".into()), Typed::Float(self.time_to_max_acceleration_heavy)),
+ (Typed::Str("brakeForceLight".into()), Typed::Float(self.brake_force_light)),
+ (Typed::Str("brakeForceHeavy".into()), Typed::Float(self.brake_force_heavy)),
+ ]
+ }
+}
+
+pub struct HoverData {
+ pub max_hover_height_light: f32,
+ pub max_hover_height_heaver: f32,
+ pub height_change_speed_light: f32,
+ pub height_change_speed_heavy: f32,
+ pub turn_torque_light: f32,
+ pub turn_torque_heavy: f32,
+ pub acceleration_light: f32,
+ pub acceleration_heavy: f32,
+ pub max_angular_velocity_light: f32,
+ pub max_angular_velocity_heavy: f32,
+ pub lateral_damping_light: f32,
+ pub lateral_damping_heavy: f32,
+}
+
+impl HoverData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("maxHoverHeightLight".into()), Typed::Float(self.max_hover_height_light)),
+ (Typed::Str("maxHoverHeightHeavy".into()), Typed::Float(self.max_hover_height_heaver)),
+ (Typed::Str("heightChangeSpeedLight".into()), Typed::Float(self.height_change_speed_light)),
+ (Typed::Str("heightChangeSpeedHeavy".into()), Typed::Float(self.height_change_speed_heavy)),
+ (Typed::Str("turnTorqueLight".into()), Typed::Float(self.turn_torque_light)),
+ (Typed::Str("turnTorqueHeavy".into()), Typed::Float(self.turn_torque_heavy)),
+ (Typed::Str("accelerationLight".into()), Typed::Float(self.acceleration_light)),
+ (Typed::Str("accelerationHeavy".into()), Typed::Float(self.acceleration_heavy)),
+ (Typed::Str("maxAngularVelocityLight".into()), Typed::Float(self.max_angular_velocity_light)),
+ (Typed::Str("maxAngularVelocityHeavy".into()), Typed::Float(self.max_angular_velocity_heavy)),
+ (Typed::Str("lateralDampingLight".into()), Typed::Float(self.lateral_damping_light)),
+ (Typed::Str("lateralDampingHeavy".into()), Typed::Float(self.lateral_damping_heavy)),
+ ]
+ }
+}
+
+pub struct AerofoilData {
+ pub barrel_speed_light: f32,
+ pub barrel_speed_heavy: f32,
+ pub bank_speed_light: f32,
+ pub bank_speed_heavy: f32,
+ pub elevation_speed_light: f32,
+ pub elevation_speed_heavy: f32,
+ pub rudder_speed_light: f32,
+ pub rudder_speed_heavy: f32,
+ pub thrust_light: f32,
+ pub thrust_heavy: f32,
+ pub vtol_velocity_light: f32,
+ pub vtol_velocity_heavy: f32,
+}
+
+impl AerofoilData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("barrelSpeedLight".into()), Typed::Float(self.barrel_speed_light)),
+ (Typed::Str("barrelSpeedHeavy".into()), Typed::Float(self.barrel_speed_heavy)),
+ (Typed::Str("bankSpeedLight".into()), Typed::Float(self.bank_speed_light)),
+ (Typed::Str("bankSpeedHeavy".into()), Typed::Float(self.bank_speed_heavy)),
+ (Typed::Str("elevationSpeedLight".into()), Typed::Float(self.elevation_speed_light)),
+ (Typed::Str("elevationSpeedHeavy".into()), Typed::Float(self.elevation_speed_heavy)),
+ (Typed::Str("rudderSpeedLight".into()), Typed::Float(self.rudder_speed_light)),
+ (Typed::Str("rudderSpeedHeavy".into()), Typed::Float(self.rudder_speed_heavy)),
+ (Typed::Str("thrustLight".into()), Typed::Float(self.thrust_light)),
+ (Typed::Str("thrustHeavy".into()), Typed::Float(self.thrust_heavy)),
+ (Typed::Str("vtolVelocityLight".into()), Typed::Float(self.vtol_velocity_light)),
+ (Typed::Str("vtolVelocityHeavy".into()), Typed::Float(self.vtol_velocity_heavy)),
+ ]
+ }
+}
+
+pub struct ThrusterData {
+ pub acceleration_delay_light: f32,
+ pub acceleration_delay_heavy: f32,
+}
+
+impl ThrusterData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("accelerationDelayLight".into()), Typed::Float(self.acceleration_delay_light)),
+ (Typed::Str("accelerationDelayHeavy".into()), Typed::Float(self.acceleration_delay_heavy)),
+ ]
+ }
+}
+
+pub struct InsectLegData {
+ pub ideal_height_light: f32,
+ pub ideal_height_heavy: f32,
+ pub ideal_crouching_height_light: f32,
+ pub ideal_crouching_height_heavy: f32,
+ pub ideal_height_range_light: f32,
+ pub ideal_height_range_heavy: f32,
+ pub jump_height_light: f32,
+ pub jump_height_heavy: f32,
+ pub max_upwards_force_light: f32,
+ pub max_upwards_force_heavy: f32,
+ pub max_lateral_force_light: f32,
+ pub max_lateral_force_heavy: f32,
+ pub max_turning_force_light: f32,
+ pub max_turning_force_heavy: f32,
+ pub max_damping_force_light: f32,
+ pub max_damping_force_heavy: f32,
+ pub max_stopped_force_light: f32,
+ pub max_stopped_force_heavy: f32,
+ pub max_new_stopped_force_light: f32,
+ pub max_new_stopped_force_heavy: f32,
+ pub upwards_damping_force_light: f32,
+ pub upwards_damping_force_heavy: f32,
+ pub lateral_damp_force_light: f32,
+ pub lateral_damp_force_heavy: f32,
+ pub swagger_force_light: f32,
+ pub swagger_force_heavy: f32,
+}
+
+impl InsectLegData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("idealHeightLight".into()), Typed::Float(self.ideal_height_light)),
+ (Typed::Str("idealHeightHeavy".into()), Typed::Float(self.ideal_height_heavy)),
+ (Typed::Str("idealCrouchingHeightLight".into()), Typed::Float(self.ideal_crouching_height_light)),
+ (Typed::Str("idealCrouchingHeightHeavy".into()), Typed::Float(self.ideal_crouching_height_heavy)),
+ (Typed::Str("idealHeightRangeLight".into()), Typed::Float(self.ideal_height_range_light)),
+ (Typed::Str("idealHeightRangeHeavy".into()), Typed::Float(self.ideal_height_range_heavy)),
+ (Typed::Str("jumpHeightLight".into()), Typed::Float(self.jump_height_light)),
+ (Typed::Str("jumpHeightHeavy".into()), Typed::Float(self.jump_height_heavy)),
+ (Typed::Str("maxUpwardsForceLight".into()), Typed::Float(self.max_upwards_force_light)),
+ (Typed::Str("maxUpwardsForceHeavy".into()), Typed::Float(self.max_upwards_force_heavy)),
+ (Typed::Str("maxLateralForceLight".into()), Typed::Float(self.max_lateral_force_light)),
+ (Typed::Str("maxLateralForceHeavy".into()), Typed::Float(self.max_lateral_force_heavy)),
+ (Typed::Str("maxTurningForceLight".into()), Typed::Float(self.max_turning_force_light)),
+ (Typed::Str("maxTurningForceHeavy".into()), Typed::Float(self.max_turning_force_heavy)),
+ (Typed::Str("maxDampingForceLight".into()), Typed::Float(self.max_damping_force_light)),
+ (Typed::Str("maxDampingForceHeavy".into()), Typed::Float(self.max_damping_force_heavy)),
+ (Typed::Str("maxStoppedForceLight".into()), Typed::Float(self.max_stopped_force_light)),
+ (Typed::Str("maxStoppedForceHeavy".into()), Typed::Float(self.max_stopped_force_heavy)),
+ (Typed::Str("maxNewStoppedForceLight".into()), Typed::Float(self.max_new_stopped_force_light)),
+ (Typed::Str("maxNewStoppedForceHeavy".into()), Typed::Float(self.max_new_stopped_force_heavy)),
+ (Typed::Str("upwardsDampingForceLight".into()), Typed::Float(self.upwards_damping_force_light)),
+ (Typed::Str("upwardsDampingForceHeavy".into()), Typed::Float(self.upwards_damping_force_heavy)),
+ (Typed::Str("lateralDampForceLight".into()), Typed::Float(self.lateral_damp_force_light)),
+ (Typed::Str("lateralDampForceHeavy".into()), Typed::Float(self.lateral_damp_force_heavy)),
+ (Typed::Str("swaggerForceLight".into()), Typed::Float(self.swagger_force_light)),
+ (Typed::Str("swaggerForceHeavy".into()), Typed::Float(self.swagger_force_heavy)),
+ ]
+ }
+}
+
+pub struct MechLegData {
+ pub time_grounded_after_jump_light: f32,
+ pub time_grounded_after_jump_heavy: f32,
+ pub jump_height_light: f32,
+ pub jump_height_heavy: f32,
+ pub turn_acceleration_light: f32,
+ pub turn_acceleration_heavy: f32,
+ pub legacy_turn_acceleration_light: f32,
+ pub legacy_turn_acceleration_heavy: f32,
+ pub long_jump_speec_scale_light: f32,
+ pub long_jump_speec_scale_heavy: f32,
+ pub max_lateral_force_light: f32,
+ pub max_lateral_force_heavy: f32,
+ pub max_damping_force_light: f32,
+ pub max_damping_force_heavy: f32,
+}
+
+impl MechLegData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("timeGroundedAfterJumpLight".into()), Typed::Float(self.time_grounded_after_jump_light)),
+ (Typed::Str("timeGroundedAfterJumpHeavy".into()), Typed::Float(self.time_grounded_after_jump_heavy)),
+ (Typed::Str("jumpHeightLight".into()), Typed::Float(self.jump_height_light)),
+ (Typed::Str("jumpHeightHeavy".into()), Typed::Float(self.jump_height_heavy)),
+ (Typed::Str("turnAccelerationLight".into()), Typed::Float(self.turn_acceleration_light)),
+ (Typed::Str("turnAccelerationHeavy".into()), Typed::Float(self.turn_acceleration_heavy)),
+ (Typed::Str("legacyTurnAccelerationLight".into()), Typed::Float(self.legacy_turn_acceleration_light)),
+ (Typed::Str("legacyTurnAccelerationHeavy".into()), Typed::Float(self.legacy_turn_acceleration_heavy)),
+ (Typed::Str("longJumpSpeedScaleLight".into()), Typed::Float(self.long_jump_speec_scale_light)),
+ (Typed::Str("longJumpSpeedScaleHeavy".into()), Typed::Float(self.long_jump_speec_scale_heavy)),
+ (Typed::Str("maxLateralForceLight".into()), Typed::Float(self.max_lateral_force_light)),
+ (Typed::Str("maxLateralForceHeavy".into()), Typed::Float(self.max_lateral_force_heavy)),
+ (Typed::Str("maxDampingForceLight".into()), Typed::Float(self.max_damping_force_light)),
+ (Typed::Str("maxDampingForceHeavy".into()), Typed::Float(self.max_damping_force_heavy)),
+ ]
+ }
+}
+
+pub struct TankTrackData {
+ pub max_turn_rate_moving_light: f32,
+ pub max_turn_rate_moving_heavy: f32,
+ pub max_turn_rate_stopped_light: f32,
+ pub max_turn_rate_stopped_heavy: f32,
+ pub turn_acceleration_light: f32,
+ pub turn_acceleration_heavy: f32,
+ pub lateral_acceleration_light: f32,
+ pub lateral_acceleration_heavy: f32,
+}
+
+impl TankTrackData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("maxTurnRateMovingLight".into()), Typed::Float(self.max_turn_rate_moving_light)),
+ (Typed::Str("maxTurnRateMovingHeavy".into()), Typed::Float(self.max_turn_rate_moving_heavy)),
+ (Typed::Str("maxTurnRateStoppedLight".into()), Typed::Float(self.max_turn_rate_stopped_light)),
+ (Typed::Str("maxTurnRateStoppedHeavy".into()), Typed::Float(self.max_turn_rate_stopped_heavy)),
+ (Typed::Str("turnAccelerationLight".into()), Typed::Float(self.turn_acceleration_light)),
+ (Typed::Str("turnAccelerationHeavy".into()), Typed::Float(self.turn_acceleration_heavy)),
+ (Typed::Str("lateralAccelerationLight".into()), Typed::Float(self.lateral_acceleration_light)),
+ (Typed::Str("lateralAccelerationHeavy".into()), Typed::Float(self.lateral_acceleration_heavy)),
+ ]
+ }
+}
+
+pub struct RotorData {
+ pub height_acceleration_light: f32,
+ pub height_acceleration_heavy: f32,
+ pub strafe_acceleration_light: f32,
+ pub strafe_acceleration_heavy: f32,
+ pub turn_acceleration_light: f32,
+ pub turn_acceleration_heavy: f32,
+ pub height_max_change_speed_light: f32,
+ pub height_max_change_speed_heavy: f32,
+ pub level_acceleration_light: f32,
+ pub level_acceleration_heavy: f32,
+}
+
+
+impl RotorData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("heightAccelerationLight".into()), Typed::Float(self.height_acceleration_light)),
+ (Typed::Str("heightAccelerationHeavy".into()), Typed::Float(self.height_acceleration_heavy)),
+ (Typed::Str("strafeAccelerationLight".into()), Typed::Float(self.strafe_acceleration_light)),
+ (Typed::Str("strafeAccelerationHeavy".into()), Typed::Float(self.strafe_acceleration_heavy)),
+ (Typed::Str("turnAccelerationLight".into()), Typed::Float(self.turn_acceleration_light)),
+ (Typed::Str("turnAccelerationHeavy".into()), Typed::Float(self.turn_acceleration_heavy)),
+ (Typed::Str("heightMaxChangeSpeedLight".into()), Typed::Float(self.height_max_change_speed_light)),
+ (Typed::Str("heightMaxChangeSpeedHeavy".into()), Typed::Float(self.height_max_change_speed_heavy)),
+ (Typed::Str("levelAccelerationLight".into()), Typed::Float(self.level_acceleration_light)),
+ (Typed::Str("levelAccelerationHeavy".into()), Typed::Float(self.level_acceleration_heavy)),
+ ]
+ }
+}
diff --git a/rc_services_room/src/data/palette.rs b/rc_services_room/src/data/palette.rs
new file mode 100644
index 0000000..37a9eb8
--- /dev/null
+++ b/rc_services_room/src/data/palette.rs
@@ -0,0 +1,76 @@
+#![allow(dead_code)]
+pub struct ColourValue {
+ pub r: u8,
+ pub g: u8,
+ pub b: u8,
+ pub a: u8,
+}
+
+impl ColourValue {
+ fn read_no_alpha(reader: &mut R) -> std::io::Result {
+ let mut buf = [0u8; 1];
+ reader.read(&mut buf)?;
+ let r = buf[0];
+ reader.read(&mut buf)?;
+ let g = buf[0];
+ reader.read(&mut buf)?;
+ let b = buf[0];
+ Ok(Self {
+ r, g, b, a: u8::MAX,
+ })
+ }
+
+ fn write_no_alpha(&self, writer: &mut W) -> std::io::Result {
+ writer.write(&[self.r, self.g, self.b])
+ }
+}
+
+pub struct Colour {
+ pub index: u8,
+ pub diffuse: ColourValue,
+ pub specular: ColourValue,
+ pub overlay: ColourValue,
+ pub premium: bool,
+}
+
+impl Colour {
+ fn read_with_index(index: u8, reader: &mut R) -> std::io::Result {
+ let diffuse = ColourValue::read_no_alpha(reader)?;
+ let specular = ColourValue::read_no_alpha(reader)?;
+ let overlay = ColourValue::read_no_alpha(reader)?;
+ let mut buf = [0u8; 1];
+ reader.read(&mut buf)?;
+ let premium = buf[0] != 0;
+ Ok(Self {
+ index, diffuse, specular, overlay, premium,
+ })
+ }
+
+ pub fn read_many(reader: &mut R) -> std::io::Result> {
+ let mut buf = [0u8; 4];
+ reader.read(&mut buf)?;
+ let count = i32::from_le_bytes(buf);
+ let mut results = Vec::with_capacity(count as _);
+ for i in 0..count {
+ results.push(Self::read_with_index(i as u8, reader)?);
+ }
+ Ok(results)
+ }
+
+ pub fn write(&self, writer: &mut W) -> std::io::Result {
+ let mut total = 0;
+ total += self.diffuse.write_no_alpha(writer)?;
+ total += self.specular.write_no_alpha(writer)?;
+ total += self.overlay.write_no_alpha(writer)?;
+ total += writer.write(&[self.premium as u8])?;
+ Ok(total)
+ }
+
+ pub fn write_many(items: &[Self], writer: &mut W) -> std::io::Result {
+ let mut total = writer.write(&(items.len() as i32).to_le_bytes())?;
+ for item in items.iter() {
+ total += item.write(writer)?;
+ }
+ Ok(total)
+ }
+}
diff --git a/rc_services_room/src/data/premium_config.rs b/rc_services_room/src/data/premium_config.rs
new file mode 100644
index 0000000..19a8230
--- /dev/null
+++ b/rc_services_room/src/data/premium_config.rs
@@ -0,0 +1,45 @@
+use polariton::operation::{Typed, Dict};
+
+pub struct PremiumEffects {
+ pub factor: PremiumFactor,
+ pub multiplayer: PremiumMultiplayer,
+}
+
+impl PremiumEffects {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 104, // hashtable
+ items: vec![
+ (Typed::Str("PremiumFactor".into()), self.factor.as_transmissible()),
+ (Typed::Str("TieredMultiplayer".into()), self.multiplayer.as_transmissible()),
+ ]
+ })
+ }
+}
+
+pub struct PremiumFactor {
+ pub factor: i32, // percent
+ pub party_bonus: i32, // percent
+}
+
+impl PremiumFactor {
+ fn as_transmissible(&self) -> Typed {
+ Typed::HashMap(vec![
+ (Typed::Str("Factor".into()), Typed::Int(self.factor)),
+ (Typed::Str("PartyBonusPercentagePerPlayer".into()), Typed::Int(self.party_bonus)),
+ ].into())
+ }
+}
+
+pub struct PremiumMultiplayer {
+ pub tier_multiplier: f64,
+}
+
+impl PremiumMultiplayer {
+ fn as_transmissible(&self) -> Typed {
+ Typed::HashMap(vec![
+ (Typed::Str("BonusPerTierMultiplier".into()), Typed::Double(self.tier_multiplier)),
+ ].into())
+ }
+}
diff --git a/rc_services_room/src/data/special_item.rs b/rc_services_room/src/data/special_item.rs
new file mode 100644
index 0000000..a149bfb
--- /dev/null
+++ b/rc_services_room/src/data/special_item.rs
@@ -0,0 +1,17 @@
+use polariton::operation::Typed;
+
+pub struct SpecialItem {
+ pub name: String,
+ pub sprite: String,
+ pub size: u32,
+}
+
+impl SpecialItem {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::HashMap(vec![
+ (Typed::Str("name".into()), Typed::Str(self.name.clone().into())),
+ (Typed::Str("spriteName".into()), Typed::Str(self.sprite.clone().into())),
+ (Typed::Str("mothershipSize".into()), Typed::Int(self.size as i32)),
+ ].into())
+ }
+}
diff --git a/rc_services_room/src/data/taunts_config.rs b/rc_services_room/src/data/taunts_config.rs
new file mode 100644
index 0000000..ce38214
--- /dev/null
+++ b/rc_services_room/src/data/taunts_config.rs
@@ -0,0 +1,86 @@
+use polariton::operation::{Typed, Dict};
+
+pub struct TauntsData {
+ pub taunts: Vec,
+}
+
+impl TauntsData {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::Dict(Dict {
+ key_ty: 115,
+ val_ty: 42,
+ items: self.taunts.iter().map(|t| (Typed::Str(t.group_name.clone().into()), t.as_transmissible())).collect(),
+ })
+ }
+}
+
+pub struct TauntData {
+ pub group_name: String, // parent key
+ // Dict
+ pub assets: AssetData,
+ pub animation_offset_x: f32,
+ pub animation_offset_y: f32,
+ pub animation_offset_z: f32,
+ pub cubes: Vec,
+}
+
+impl TauntData {
+ pub fn as_transmissible(&self) -> Typed {
+ let mut items = vec![
+ (Typed::Str("defaultAnimOffsetx".into()), Typed::Float(self.animation_offset_x)),
+ (Typed::Str("defaultAnimOffsety".into()), Typed::Float(self.animation_offset_y)),
+ (Typed::Str("defaultAnimOffsetz".into()), Typed::Float(self.animation_offset_z)),
+ (Typed::Str("cubes".into()), Typed::Dict(Dict {
+ key_ty: 115,
+ val_ty: 42,
+ items: self.cubes.iter().enumerate().map(|(i, cube)| (Typed::Str(i.to_string().into()), cube.as_transmissible())).collect(),
+ })),
+ ];
+ items.append(&mut self.assets.as_transmissible());
+ Typed::Dict(Dict {
+ key_ty: 115,
+ val_ty: 42,
+ items,
+ })
+ }
+}
+
+pub struct AssetData {
+ pub idle_effect: String,
+ pub active_effect: String,
+ pub sound_effect: String,
+}
+
+impl AssetData {
+ pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> {
+ vec![
+ (Typed::Str("idleEffect".into()), Typed::Str(self.idle_effect.clone().into())),
+ (Typed::Str("tauntEffect".into()), Typed::Str(self.active_effect.clone().into())),
+ (Typed::Str("tauntSoundEffect".into()), Typed::Str(self.sound_effect.clone().into())),
+ ]
+ }
+}
+
+pub struct CubeData {
+ pub cube_id: u32, // hex
+ pub position_x: i32,
+ pub position_y: i32,
+ pub position_z: i32,
+ pub rotation: u8,
+}
+
+impl CubeData {
+ pub fn as_transmissible(&self) -> Typed {
+ Typed::Dict(Dict {
+ key_ty: 115,
+ val_ty: 42,
+ items: vec![
+ (Typed::Str("cubeid".into()), Typed::Str(hex::encode((self.cube_id as i32).to_le_bytes()).into())),
+ (Typed::Str("positionx".into()), Typed::Int(self.position_x)),
+ (Typed::Str("positiony".into()), Typed::Int(self.position_y)),
+ (Typed::Str("positionz".into()), Typed::Int(self.position_z)),
+ (Typed::Str("rotation".into()), Typed::Byte(self.rotation)),
+ ]
+ })
+ }
+}
diff --git a/rc_services_room/src/data/weapon_list.rs b/rc_services_room/src/data/weapon_list.rs
new file mode 100644
index 0000000..cbe4a90
--- /dev/null
+++ b/rc_services_room/src/data/weapon_list.rs
@@ -0,0 +1,213 @@
+#![allow(dead_code)]
+use polariton::operation::Typed;
+
+#[derive(Default)]
+pub struct WeaponData {
+ pub damage_inflicted: Option,
+ pub protonium_damage_scale: Option,
+ pub projectile_speed: Option,
+ pub projectile_range: Option,
+ pub base_inaccuracy: Option,
+ pub base_air_inaccuracy: Option,
+ pub movement_inaccuracy: Option,
+ pub movement_max_speed: Option,
+ pub movement_min_speed: Option,
+ pub gun_rotation_slow: Option,
+ pub movement_inaccuracy_decay: Option,
+ pub slow_rotation_decay: Option,
+ pub quick_rotation_decay: Option,
+ pub movement_inaccuracy_recovery: Option,
+ pub repeat_fire_inaccuracy_total_degrees: Option,
+ pub repeat_fire_inaccuracy_decay: Option,
+ pub repeat_fire_innaccuracy_recovery: Option,
+ pub fire_instant_accuracy_decay: Option, // degrees
+ pub accuracy_non_recover_time: Option,
+ pub accuracy_decay: Option,
+ pub damage_radius: Option,
+ pub plasma_time_to_full_damage: Option,
+ pub plasma_starting_radius_scale: Option,
+ pub nano_dps: Option,
+ pub nano_hps: Option,
+ pub tesla_damage: Option,
+ pub tesla_charges: Option,
+ pub aeroflak_proximity_damage: Option,
+ pub aeroflak_damage_radius: Option,
+ pub aeroflak_explosion_radius: Option,
+ pub aeroflak_ground_clearance: Option,
+ pub aeroflak_max_stacks: Option,
+ pub aeroflak_damage_per_stack: Option,
+ pub aeroflak_stack_expire: Option,
+ pub shot_cooldown: Option,
+ pub smart_rotation_cooldown: Option,
+ pub smart_rotation_cooldown_extra: Option,
+ pub smart_rotation_max_stacks: Option,
+ pub spin_up_time: Option,
+ pub spin_down_time: Option,
+ pub spin_initial_cooldown: Option,
+ pub group_fire_scales: Vec,
+ pub mana_cost: Option,
+ pub lock_time: Option,
+ pub full_lock_release: Option,
+ pub change_lock_time: Option,
+ pub max_rotation_speed: Option,
+ pub initial_rotation_speed: Option,
+ pub rotation_acceleration: Option,
+ pub nano_healing_priority_time: Option,
+ pub module_range: Option,
+ pub shield_lifetime: Option,
+ pub teleport_time: Option,
+ pub camera_time: Option,
+ pub camera_delay: Option,
+ pub to_invisible_speed: Option,
+ pub to_invisible_duration: Option,
+ pub to_visible_duration: Option,
+ pub countdown_time: Option,
+ pub stun_time: Option,
+ pub stun_radius: Option,
+ pub effect_duration: Option,
+}
+
+impl WeaponData {
+ pub fn as_transmissible(&self) -> Typed {
+ let mut out = Vec::new();
+
+ self.damage_inflicted.map(|x| out.push((Typed::Str("damageInflicted".into()), Typed::Int(x))));
+ self.protonium_damage_scale.map(|x| out.push((Typed::Str("protoniumDamageScale".into()), Typed::Float(x))));
+ self.projectile_speed.map(|x| out.push((Typed::Str("projectileSpeed".into()), Typed::Float(x))));
+ self.projectile_range.map(|x| out.push((Typed::Str("projectileRange".into()), Typed::Float(x))));
+ self.base_inaccuracy.map(|x| out.push((Typed::Str("baseInaccuracy".into()), Typed::Float(x))));
+ self.base_air_inaccuracy.map(|x| out.push((Typed::Str("baseAirInaccuracy".into()), Typed::Float(x))));
+ self.movement_inaccuracy.map(|x| out.push((Typed::Str("movementInaccuracy".into()), Typed::Float(x))));
+ self.movement_max_speed.map(|x| out.push((Typed::Str("movementMaxThresholdSpeed".into()), Typed::Float(x))));
+ self.movement_min_speed.map(|x| out.push((Typed::Str("movementMinThresholdSpeed".into()), Typed::Float(x))));
+ self.gun_rotation_slow.map(|x| out.push((Typed::Str("gunRotationThresholdSlow".into()), Typed::Float(x))));
+ self.movement_inaccuracy_decay.map(|x| out.push((Typed::Str("movementInaccuracyDecayTime".into()), Typed::Float(x))));
+ self.slow_rotation_decay.map(|x| out.push((Typed::Str("slowRotationInaccuracyDecayTime".into()), Typed::Float(x))));
+ self.quick_rotation_decay.map(|x| out.push((Typed::Str("quickRotationInaccuracyDecayTime".into()), Typed::Float(x))));
+ self.movement_inaccuracy_recovery.map(|x| out.push((Typed::Str("movementInaccuracyRecoveryTime".into()), Typed::Float(x))));
+ self.repeat_fire_inaccuracy_total_degrees.map(|x| out.push((Typed::Str("repeatFireInaccuracyTotalDegrees".into()), Typed::Float(x))));
+ self.repeat_fire_inaccuracy_decay.map(|x| out.push((Typed::Str("repeatFireInaccuracyDecayTime".into()), Typed::Float(x))));
+ self.repeat_fire_innaccuracy_recovery.map(|x| out.push((Typed::Str("repeatFireInaccuracyRecoveryTime".into()), Typed::Float(x))));
+ self.fire_instant_accuracy_decay.map(|x| out.push((Typed::Str("fireInstantAccuracyDecayDegrees".into()), Typed::Float(x)))); // degrees
+ self.accuracy_non_recover_time.map(|x| out.push((Typed::Str("accuracyNonRecoverTime".into()), Typed::Float(x))));
+ self.accuracy_decay.map(|x| out.push((Typed::Str("accuracyDecayTime".into()), Typed::Float(x))));
+ self.damage_radius.map(|x| out.push((Typed::Str("damageRadius".into()), Typed::Float(x))));
+ self.plasma_time_to_full_damage.map(|x| out.push((Typed::Str("plasmaTimeToFullDamage".into()), Typed::Float(x))));
+ self.plasma_starting_radius_scale.map(|x| out.push((Typed::Str("plasmaStartingRadiusScale".into()), Typed::Float(x))));
+ self.nano_dps.map(|x| out.push((Typed::Str("nanoDPS".into()), Typed::Float(x))));
+ self.nano_hps.map(|x| out.push((Typed::Str("nanoHPS".into()), Typed::Float(x))));
+ self.tesla_damage.map(|x| out.push((Typed::Str("teslaDamage".into()), Typed::Float(x))));
+ self.tesla_charges.map(|x| out.push((Typed::Str("teslaCharges".into()), Typed::Float(x))));
+ self.aeroflak_proximity_damage.map(|x| out.push((Typed::Str("aeroflakProximityDamage".into()), Typed::Float(x))));
+ self.aeroflak_damage_radius.map(|x| out.push((Typed::Str("aeroflakDamageRadius".into()), Typed::Float(x))));
+ self.aeroflak_explosion_radius.map(|x| out.push((Typed::Str("aeroflakExplosionRadius".into()), Typed::Float(x))));
+ self.aeroflak_ground_clearance.map(|x| out.push((Typed::Str("aeroflakGroundClearance".into()), Typed::Float(x))));
+ self.aeroflak_max_stacks.map(|x| out.push((Typed::Str("aeroflakBuffMaxStacks".into()), Typed::Int(x))));
+ self.aeroflak_damage_per_stack.map(|x| out.push((Typed::Str("aeroflakBuffDamagePerStack".into()), Typed::Int(x))));
+ self.aeroflak_stack_expire.map(|x| out.push((Typed::Str("aeroflakBuffTimeToExpire".into()), Typed::Float(x))));
+ self.shot_cooldown.map(|x| out.push((Typed::Str("cooldownBetweenShots".into()), Typed::Float(x))));
+ self.smart_rotation_cooldown.map(|x| out.push((Typed::Str("smartRotationCooldown".into()), Typed::Float(x))));
+ self.smart_rotation_cooldown_extra.map(|x| out.push((Typed::Str("smartRotationExtraCooldownTime".into()), Typed::Float(x))));
+ self.smart_rotation_max_stacks.map(|x| out.push((Typed::Str("smartRotationMaxStacks".into()), Typed::Float(x))));
+ self.spin_up_time.map(|x| out.push((Typed::Str("spinUpTime".into()), Typed::Float(x))));
+ self.spin_down_time.map(|x| out.push((Typed::Str("spinDownTime".into()), Typed::Float(x))));
+ self.spin_initial_cooldown.map(|x| out.push((Typed::Str("spinInitialCooldown".into()), Typed::Float(x))));
+
+ if !self.group_fire_scales.is_empty() {
+ let typed_arr: Vec = self.group_fire_scales.iter().map(|x| Typed::Float(*x)).collect();
+ out.push((Typed::Str("groupFireScales".into()), Typed::ObjArr(typed_arr.into())));
+ }
+
+ self.mana_cost.map(|x| out.push((Typed::Str("manaCost".into()), Typed::Float(x))));
+ self.lock_time.map(|x| out.push((Typed::Str("lockTime".into()), Typed::Float(x))));
+ self.full_lock_release.map(|x| out.push((Typed::Str("fullLockRelease".into()), Typed::Float(x))));
+ self.change_lock_time.map(|x| out.push((Typed::Str("changeLockTime".into()), Typed::Float(x))));
+ self.max_rotation_speed.map(|x| out.push((Typed::Str("maxRotationSpeed".into()), Typed::Float(x))));
+ self.initial_rotation_speed.map(|x| out.push((Typed::Str("initialRotationSpeed".into()), Typed::Float(x))));
+ self.rotation_acceleration.map(|x| out.push((Typed::Str("rotationAcceleration".into()), Typed::Float(x))));
+ self.nano_healing_priority_time.map(|x| out.push((Typed::Str("nanoHealingPriorityTime".into()), Typed::Float(x))));
+ self.module_range.map(|x| out.push((Typed::Str("moduleRange".into()), Typed::Float(x))));
+ self.shield_lifetime.map(|x| out.push((Typed::Str("shieldLifetime".into()), Typed::Float(x))));
+ self.teleport_time.map(|x| out.push((Typed::Str("teleportTime".into()), Typed::Float(x))));
+ self.camera_time.map(|x| out.push((Typed::Str("cameraTime".into()), Typed::Float(x))));
+ self.camera_delay.map(|x| out.push((Typed::Str("cameraDelay".into()), Typed::Float(x))));
+ self.to_invisible_speed.map(|x| out.push((Typed::Str("toInvisibleSpeed".into()), Typed::Float(x))));
+ self.to_invisible_duration.map(|x| out.push((Typed::Str("toInvisibleDuration".into()), Typed::Float(x))));
+ self.to_visible_duration.map(|x| out.push((Typed::Str("toVisibleDuration".into()), Typed::Float(x))));
+ self.countdown_time.map(|x| out.push((Typed::Str("countdownTime".into()), Typed::Float(x))));
+ self.stun_time.map(|x| out.push((Typed::Str("stunTime".into()), Typed::Float(x))));
+ self.stun_radius.map(|x| out.push((Typed::Str("stunRadius".into()), Typed::Float(x))));
+ self.effect_duration.map(|x| out.push((Typed::Str("effectDuration".into()), Typed::Float(x))));
+ Typed::HashMap(out.into())
+ }
+}
+
+#[repr(u32)]
+#[derive(Clone, Copy)]
+pub enum ItemCategory {
+ NoFunction,
+ Wheel,
+ Hover,
+ Wing,
+ Rudder,
+ Thruster,
+ InsectLeg,
+ MechLeg,
+ Ski,
+ TankTrack,
+ Rotor,
+ SrpinterLeg,
+ Propeller,
+ Laser = 100,
+ Plasma = 200,
+ Mortar = 250,
+ Rail = 300,
+ Nano = 400,
+ Tesla = 500,
+ Aeroflak = 600,
+ Ion = 650,
+ Seeker = 701,
+ Chaingun = 750,
+ ShieldModule = 800,
+ GhostModule,
+ BlinkModule,
+ EmpModule,
+ WindowmakerModule,
+ EnergyModule = 900,
+}
+
+impl ItemCategory {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ ItemCategory::NoFunction => "NotAFunctionalItem",
+ ItemCategory::Wheel => "Wheel",
+ ItemCategory::Hover => "Hover",
+ ItemCategory::Wing => "Wing",
+ ItemCategory::Rudder => "Rudder",
+ ItemCategory::Thruster => "Thruster",
+ ItemCategory::InsectLeg => "InsectLeg",
+ ItemCategory::MechLeg => "MechLeg",
+ ItemCategory::Ski => "Ski",
+ ItemCategory::TankTrack => "TankTrack",
+ ItemCategory::Rotor => "Rotor",
+ ItemCategory::SrpinterLeg => "SrpinterLeg",
+ ItemCategory::Propeller => "Propeller",
+ ItemCategory::Laser => "Laser",
+ ItemCategory::Plasma => "Plasma",
+ ItemCategory::Mortar => "Mortar",
+ ItemCategory::Rail => "Rail",
+ ItemCategory::Nano => "Nano",
+ ItemCategory::Tesla => "Tesla",
+ ItemCategory::Aeroflak => "Aeroflak",
+ ItemCategory::Ion => "Ion",
+ ItemCategory::Seeker => "Seeker",
+ ItemCategory::Chaingun => "Chaingun",
+ ItemCategory::ShieldModule => "ShieldModule",
+ ItemCategory::GhostModule => "GhostModule",
+ ItemCategory::BlinkModule => "BlinkModule",
+ ItemCategory::EmpModule => "EmpModule",
+ ItemCategory::WindowmakerModule => "WindowmakerModule",
+ ItemCategory::EnergyModule => "EnergyModule",
+ }
+ }
+}
diff --git a/rc_services_room/src/events/mod.rs b/rc_services_room/src/events/mod.rs
new file mode 100644
index 0000000..e69de29
diff --git a/rc_services_room/src/main.rs b/rc_services_room/src/main.rs
new file mode 100644
index 0000000..52b2619
--- /dev/null
+++ b/rc_services_room/src/main.rs
@@ -0,0 +1,402 @@
+mod cli;
+mod state;
+
+mod data;
+mod events;
+mod operations;
+
+use std::num::NonZero;
+use std::sync::Arc;
+
+use polariton_auth::Handshake;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::net;
+
+use polariton::packet::{Cryptographer, Data, Message, Packet, Ping, StandardMessage, StandardPacket};
+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 op_handler = Arc::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?;
+
+ #[cfg(not(debug_assertions))]
+ loop {
+ let (socket, address) = listener.accept().await?;
+ tokio::spawn(process_socket(socket, address, NonZero::new(args.retries), op_handler.clone()));
+ }
+ #[cfg(debug_assertions)]
+ {
+ let (socket, address) = listener.accept().await?;
+ process_socket(socket, address, NonZero::new(args.retries), op_handler.clone()).await;
+ Ok(())
+ }
+}
+
+async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAddr, retries: Option>, op_handler: Arc>) {
+ log::debug!("Accepting connection from address {}", address);
+
+ let mut read_buf = Vec::new();
+ let mut write_buf = Vec::new();
+ let enc = match do_connect_handshake(&mut read_buf, &mut socket, retries).await {
+ Some(x) => x,
+ None => {
+ log::error!("Failed to do connect handshake with {}", address);
+ return;
+ }
+ };
+ let sock_state = state::State::new(enc);
+ let user_state = sock_state.user();
+ while let Ok(packet) = receive_packet(&mut read_buf, &mut socket, retries, sock_state.binrw_args()).await {
+ match packet {
+ Packet::Ping(ping) => {
+ handle_ping(ping, &mut write_buf, &mut socket).await;
+ },
+ Packet::Packet(packet) => {
+ // remove packet's advertised size from the buffer
+ for _ in 0..packet.header.len {
+ read_buf.remove(0);
+ }
+ match packet.message {
+ Message::Ping(ping) => {
+ handle_ping(ping, &mut write_buf, &mut socket).await;
+ },
+ Message::Standard(msg) => {
+
+ let is_encrypted = msg.is_encrypted();
+ match msg.data {
+ Data::OpReq(req) => {
+ let resp = op_handler.handle_op(&user_state, req);
+ let result = send_packet(
+ Packet::from_message(
+ Message::Standard(StandardMessage {
+ flags: 0,
+ data: Data::OpResp(resp),
+ }.encrypt(is_encrypted)),
+ packet.header.channel,
+ packet.header.is_reliable(),
+ sock_state.binrw_args()).unwrap(),
+ &mut write_buf, &mut socket, sock_state.binrw_args()).await;
+ match result {
+ Ok(_) => {},
+ Err(e) => {
+ log::error!("Failed to send operation response packet: {}", e);
+ }
+ }
+ },
+ data => log::warn!("Failed to handle packet with message data {:?}", data),
+ }
+ }
+ }
+ }
+ //log::warn!("Not handling packet {:?}", packet),
+ }
+ }
+ log::debug!("Goodbye connection from address {}", address);
+}
+
+async fn handle_ping(ping: Ping, buf: &mut Vec, socket: &mut net::TcpStream) {
+ buf.clear();
+ let resp = Packet::Ping(polariton_auth::ping_pong(ping));
+ resp.to_buf(buf, None).unwrap();
+ let write_count = socket.write(buf).await.unwrap();
+ log::debug!("(ping) Write {} bytes to socket: {:?}", write_count, buf);
+ buf.clear();
+}
+
+fn buf_likely_valid(buf: &[u8]) -> bool {
+ buf.is_empty() || buf[0] == Packet::PING_MAGIC || buf[0] == Packet::FRAMED_MAGIC
+}
+
+async fn read_more(buf: &mut Vec, socket: &mut net::TcpStream) -> Result {
+ let read_count = socket.read_buf(buf).await?;
+ log::debug!("Read {} bytes from socket: {:?}", read_count, buf);
+ Ok(read_count)
+}
+
+async fn receive_packet(buf: &mut Vec, socket: &mut net::TcpStream, max_retries: Option>, args: Option>>) -> Result {
+ if buf.is_empty() {
+ let read_count = read_more(buf, socket).await?;
+ if read_count == 0 { return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "socket did not read any bytes")); } // bad packet
+ }
+
+ let mut last_err = None;
+ let mut must_succeed_next = false;
+ if let Some(max_retries) = max_retries {
+ for _ in 0..max_retries.get() {
+ match Packet::from_buf(&buf, args.clone()) {
+ Ok(packet) => {
+ log::debug!("Received packet {:?}", packet);
+ return Ok(packet);
+ },
+ Err(e) => last_err = Some(e),
+ }
+ if must_succeed_next {
+ break;
+ }
+ must_succeed_next = read_more(buf, socket).await? == 0;
+ }
+ return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, last_err.unwrap()));
+ } else {
+ while buf_likely_valid(buf.as_slice()) {
+ match Packet::from_buf(&buf, args.clone()) {
+ Ok(packet) => {
+ log::debug!("Received packet {:?}", packet);
+ return Ok(packet);
+ },
+ Err(e) => last_err = Some(e),
+ }
+ if must_succeed_next {
+ break;
+ }
+ must_succeed_next = read_more(buf, socket).await? == 0;
+ }
+ return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, last_err.unwrap()));
+ }
+}
+
+async fn send_packet(packet: Packet, buf: &mut Vec, socket: &mut net::TcpStream, args: Option>>) -> Result<(), std::io::Error> {
+ log::debug!("Sending packet {:?}", packet);
+ buf.clear();
+ packet.to_buf(buf, args).map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?;
+ let write_count = socket.write(buf).await?;
+ log::debug!("Write {} bytes to socket: {:?}", write_count, buf);
+ #[cfg(debug_assertions)]
+ {
+ // print out unencrypted packet too
+ if let Packet::Packet(standard_p) = packet {
+ if let Message::Standard(standard_m) = standard_p.message {
+ if standard_m.is_encrypted() {
+ let standard_m = standard_m.encrypt(false);
+ let packet = Packet::Packet(StandardPacket { header: standard_p.header, message: Message::Standard(standard_m) });
+ packet.to_buf(buf, None).map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?;
+ log::debug!("Unencrypted bytes of packet: {:?} (len: {})", buf, buf.len());
+ }
+
+ }
+ }
+ }
+ buf.clear();
+ Ok(())
+}
+
+const APP_ID: &str = "WebServicesServer";
+
+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(
+ buf: &mut Vec,
+ socket: &mut net::TcpStream,
+ max_retries: Option>,
+) -> Option>> {
+ let handshake = Handshake::new(APP_ID);
+ // connect
+ log::debug!("(connect) Handling first packet");
+ let packet1 = match receive_packet(buf, socket, max_retries, None).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read connect packet: {}", e);
+ return None;
+ }
+ };
+ buf.clear();
+ 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 send_packet(to_send, buf, socket, None).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 receive_packet(buf, socket, max_retries, None).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) public key packet: {}", e);
+ return None;
+ }
+ };
+ buf.clear();
+ while let Packet::Ping(ping) = packet2 {
+ handle_ping(ping, buf, socket).await;
+ packet2 = match receive_packet(buf, socket, max_retries, None).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) public key packet: {}", e);
+ return None;
+ }
+ };
+ buf.clear();
+ }
+ 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 send_packet(to_send, buf, socket, None).await {
+ Ok(_) => {},
+ Err(e) => {
+ log::error!("Failed to send encryption ack packet: {}", e);
+ return None;
+ }
+ }
+ // pre-auth
+ let handshake = handshake.with_auth(AuthImpl);
+ // authenticate
+ log::debug!("(connect) Handling third packet");
+ let mut packet3 = match receive_packet(buf, socket, max_retries, Some(crypto.clone())).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) auth packet: {}", e);
+ return None;
+ }
+ };
+ buf.clear();
+ while let Packet::Ping(ping) = packet3 {
+ handle_ping(ping, buf, socket).await;
+ packet3 = match receive_packet(buf, socket, max_retries, Some(crypto.clone())).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) auth packet: {}", e);
+ return None;
+ }
+ };
+ buf.clear();
+ }
+ let to_send = match handshake.authenticate(&packet3, crypto.clone()) {
+ 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 send_packet(to_send, buf, socket, Some(crypto.clone())).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 receive_packet(buf, socket, max_retries, Some(crypto.clone())).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) join packet: {}", e);
+ return None;
+ }
+ };
+ buf.clear();
+ while let Packet::Ping(ping) = packet_j {
+ handle_ping(ping, buf, socket).await;
+ packet_j = match receive_packet(buf, socket, max_retries, Some(crypto.clone())).await {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to read (maybe) join packet: {}", e);
+ return None;
+ }
+ };
+ buf.clear();
+ }
+ 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, Some(crypto.clone())).unwrap();
+ match send_packet(resp, buf, socket, Some(crypto.clone())).await {
+ Ok(_) => {},
+ Err(e) => {
+ log::error!("Failed to send lobby ack packet: {}", e);
+ return None;
+ }
+ }
+ }
+ }
+ }
+ }
+ buf.clear();
+
+ Some(crypto)
+}
diff --git a/rc_services_room/src/operations/all_customisations_info.rs b/rc_services_room/src/operations/all_customisations_info.rs
new file mode 100644
index 0000000..1cbabcc
--- /dev/null
+++ b/rc_services_room/src/operations/all_customisations_info.rs
@@ -0,0 +1,64 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed, Arr};
+
+use crate::data::customisation_info::CustomisationData;
+
+const SKINS_KEY: u8 = 228;
+const SPAWNS_KEY: u8 = 229;
+const DEATHS_KEY: u8 = 230;
+
+const OWNED_SKINS_KEY: u8 = 231;
+const OWNED_SPAWNS_KEY: u8 = 232;
+const OWNED_DEATHS_KEY: u8 = 233;
+const OWNED_EMOTES_KEY: u8 = 76;
+
+pub(super) fn all_customisations_provider() -> SimpleFunc<216, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(SKINS_KEY, Typed::Arr(Arr {
+ ty: 104, // hashtable
+ items: vec![
+ CustomisationData {
+ id: "skin0".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(),
+ is_default: true,
+ }.as_transmissible(),
+ ],
+ }));
+ params.insert(SPAWNS_KEY, Typed::Arr(Arr {
+ ty: 104, // hashtable
+ items: vec![
+ 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(),
+ is_default: true,
+ }.as_transmissible(),
+ ],
+ }));
+ params.insert(DEATHS_KEY, Typed::Arr(Arr {
+ ty: 104, // hashtable
+ items: vec![
+ 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(),
+ is_default: true,
+ }.as_transmissible(),
+ ],
+ }));
+
+ params.insert(OWNED_SKINS_KEY, Typed::StrArr(vec![].into()));
+ params.insert(OWNED_SPAWNS_KEY, Typed::StrArr(vec![].into()));
+ params.insert(OWNED_DEATHS_KEY, Typed::StrArr(vec![].into()));
+ params.insert(OWNED_EMOTES_KEY, Typed::StrArr(vec![].into()));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/battle_arena_config.rs b/rc_services_room/src/operations/battle_arena_config.rs
new file mode 100644
index 0000000..e32a8d6
--- /dev/null
+++ b/rc_services_room/src/operations/battle_arena_config.rs
@@ -0,0 +1,33 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed, Dict};
+
+use crate::data::battle_arena_config::*;
+
+const PARAM_KEY: u8 = 1;
+
+pub(super) fn battle_arena_config_provider() -> SimpleFunc<53, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 104, // obj
+ items: vec![
+ (Typed::Str("BattleArenaSettings".into()), BattleArenaData {
+ protonium_health: 1_000,
+ respawn_time_seconds: 10,
+ heal_over_time_per_tower: vec![10, 10, 10, 10],
+ base_machine_map: Vec::default(),
+ equalizer_model: Vec::default(),
+ equalizer_health: 1_000_000,
+ equalizer_trigger_time_seconds: vec![10, 10, 10, 10, 10],
+ equalizer_warning_seconds: 10,
+ equalizer_duration_seconds: vec![20, 20, 20, 20, 20],
+ capture_time_seconds_per_player: vec![30, 20, 10, 5, 1],
+ num_segments: 4,
+ heal_escalation_time_seconds: 5,
+ }.as_transmissible())
+ ],
+ }));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/client_config.rs b/rc_services_room/src/operations/client_config.rs
new file mode 100644
index 0000000..88186ef
--- /dev/null
+++ b/rc_services_room/src/operations/client_config.rs
@@ -0,0 +1,31 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed, Dict};
+
+use crate::data::client_config::*;
+
+const PARAM_KEY: u8 = 36;
+
+pub(super) fn client_config_provider() -> SimpleFunc<34, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 104, // hashtable
+ items: vec![
+ (Typed::Str("GameplaySettings".into()), GameplaySettings {
+ show_tutorial_after_date: "2025-01-01".to_owned(),
+ health_threshold: 10.0,
+ microbot_sphere: 10.0,
+ misfire_angle: 20.0,
+ shield_dps: 100,
+ shield_hps: 2_000,
+ request_review_level: 10_000,
+ critical_ratio: 10.0,
+ cross_promo_image: "https://git.ngni.us/TODO".to_owned(), // TODO
+ cross_promo_link: "https://git.ngni.us/OpenJam/servers".to_owned(),
+ }.as_transmissible())
+ ].into(),
+ }));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/cosmetic_config.rs b/rc_services_room/src/operations/cosmetic_config.rs
new file mode 100644
index 0000000..b0e3576
--- /dev/null
+++ b/rc_services_room/src/operations/cosmetic_config.rs
@@ -0,0 +1,18 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::ParameterTable;
+
+use crate::data::cosmetic_limits::CosmeticLimitsData;
+
+const PARAM_KEY: u8 = 196;
+
+pub(super) fn cosmetic_limits_config_provider() -> SimpleFunc<72, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, CosmeticLimitsData {
+ others_max_holo_and_trails: 16,
+ others_max_headlamps: 8,
+ others_max_cosmetic_items_with_particles: 12,
+ }.as_transmissible());
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/cpu_limits_config.rs b/rc_services_room/src/operations/cpu_limits_config.rs
new file mode 100644
index 0000000..252ee7e
--- /dev/null
+++ b/rc_services_room/src/operations/cpu_limits_config.rs
@@ -0,0 +1,20 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::ParameterTable;
+
+use crate::data::cpu_limits::CpuLimitsData;
+
+const PARAM_KEY: u8 = 194;
+
+pub(super) fn cpu_config_provider() -> SimpleFunc<75, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, CpuLimitsData {
+ premium_for_life_cosmetic_gpu: 12,
+ premium_cosmetic_cpu: 6,
+ no_premium_cosmetic_cpu: 3,
+ max_regular_health: 2_000_000,
+ max_megabot_health: 200_000_000,
+ }.as_transmissible());
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/crf_config.rs b/rc_services_room/src/operations/crf_config.rs
new file mode 100644
index 0000000..59ed562
--- /dev/null
+++ b/rc_services_room/src/operations/crf_config.rs
@@ -0,0 +1,18 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::ParameterTable;
+
+use crate::data::crf_config::*;
+
+const PARAM_KEY: u8 = 110;
+
+pub(super) fn crf_config_provider() -> SimpleFunc<92, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, RobotShopConfig {
+ cpu_ranges: vec![100, 500, 1_000, 2_000],
+ submission_mult: 1.0,
+ earnings_mult: 1.0,
+ }.as_transmissible());
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/cube_list.rs b/rc_services_room/src/operations/cube_list.rs
new file mode 100644
index 0000000..8ac4a24
--- /dev/null
+++ b/rc_services_room/src/operations/cube_list.rs
@@ -0,0 +1,43 @@
+use std::collections::HashMap;
+
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed, Dict};
+
+use crate::data::cube_list::*;
+
+const PARAM_KEY: u8 = 1;
+
+pub(super) fn cube_list_provider() -> SimpleFunc<2, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 104, // hashtable
+ items: vec![
+ //(u32 in base16 aka hex, hashtable)
+ (Typed::Str("DEADBEEF".into()), CubeInfo {
+ cpu: 1,
+ health: 1,
+ health_boost: 1.0,
+ grey_out_in_tutorial: false,
+ visibility: VisibilityMode::All,
+ indestructible: true,
+ category: 1,
+ placements: 63,
+ protonium: false,
+ unlocked_by_league: false,
+ league_unlock_index: 1,
+ stats: HashMap::default(),
+ description: "This is a very descriptive description".to_string(),
+ size: ItemTier::NoTier,
+ type_: ItemType::NoFunction,
+ ranking: 1,
+ cosmetic: false,
+ variant_of: "0".to_string(),
+ ignore_in_weapon_list: true,
+ }.as_transmissible())
+ ].into(),
+ }));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/damage_boost_stats.rs b/rc_services_room/src/operations/damage_boost_stats.rs
new file mode 100644
index 0000000..1398c29
--- /dev/null
+++ b/rc_services_room/src/operations/damage_boost_stats.rs
@@ -0,0 +1,26 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed, Dict};
+
+use crate::data::damage_boost::*;
+
+const PARAM_KEY: u8 = 192;
+
+pub(super) fn damage_boost_provider() -> SimpleFunc<163, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 42, // obj
+ items: vec![
+ (Typed::Str("damageBoost".into()), DamageBoostData {
+ damage_map: vec![
+ (100, 1000.0),
+ (1000, 100.0),
+ (2000, 1.0),
+ ],
+ }.as_transmissible())
+ ],
+ }));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/eac.rs b/rc_services_room/src/operations/eac.rs
new file mode 100644
index 0000000..fc2bcd4
--- /dev/null
+++ b/rc_services_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: 161, // skip the challenge (hopefully)
+ return_code: 0,
+ message: polariton::operation::Typed::Null,
+ params,
+ }
+ }
+}
+
+impl OperationCode for EacChallengeIgnorer {
+ fn op_code() -> u8 {
+ 160
+ }
+}
diff --git a/rc_services_room/src/operations/game_quality.rs b/rc_services_room/src/operations/game_quality.rs
new file mode 100644
index 0000000..6187d36
--- /dev/null
+++ b/rc_services_room/src/operations/game_quality.rs
@@ -0,0 +1,82 @@
+use std::collections::HashMap;
+
+use polariton::operation::{Typed, ParameterTable, OperationResponse, Dict};
+use polariton_server::operations::{Operation, OperationCode};
+
+pub struct QualityConfigTeller;
+
+impl Operation for QualityConfigTeller {
+ type State = ();
+ type User = crate::UserTy;
+
+ fn handle(&self, _: ParameterTable, _: &mut Self::State, _: &Self::User) -> OperationResponse {
+ let quality_levels = Typed::HashMap(vec![
+ (Typed::Str("extremLow".into()), Typed::Dict(Dict {
+ key_ty: 115,
+ val_ty: 42,
+ items: vec![
+ (Typed::Str("Level".into()), Typed::Long(0)),
+ (Typed::Str("default".into()), Typed::Float(0.0)),
+ ],
+ })),
+ (Typed::Str("low".into()), Typed::Dict(Dict {
+ key_ty: 115,
+ val_ty: 42,
+ items: vec![
+ (Typed::Str("Level".into()), Typed::Long(1)),
+ (Typed::Str("default".into()), Typed::Float(0.0)),
+ ],
+ })),
+ (Typed::Str("normal".into()), Typed::Dict(Dict {
+ key_ty: 115,
+ val_ty: 42,
+ items: vec![
+ (Typed::Str("Level".into()), Typed::Long(2)),
+ (Typed::Str("default".into()), Typed::Float(0.0)),
+ ],
+ })),
+ (Typed::Str("beautiful".into()), Typed::Dict(Dict {
+ key_ty: 115,
+ val_ty: 42,
+ items: vec![
+ (Typed::Str("Level".into()), Typed::Long(3)),
+ (Typed::Str("default".into()), Typed::Float(0.0)),
+ ],
+ })),
+ (Typed::Str("fantastic".into()), Typed::Dict(Dict {
+ key_ty: 115,
+ val_ty: 42,
+ items: vec![
+ (Typed::Str("Level".into()), Typed::Long(4)),
+ (Typed::Str("default".into()), Typed::Float(f32::MAX)),
+ ],
+ })),
+ ].into());
+ let mem_thresholds = Typed::HashMap(vec![
+ (Typed::Str("low".into()), Typed::Int(69)),
+ (Typed::Str("extremeLow".into()), Typed::Int(42)),
+ ].into());
+ let mut resp_params = HashMap::new();
+ resp_params.insert(1 /* dict */, Typed::Dict(
+ Dict {
+ key_ty: 115, // str
+ val_ty: 104, // hash table
+ items: vec![
+ (Typed::Str("qualityLevels".into()), quality_levels),
+ (Typed::Str("systemMemoryThresholds".into()), mem_thresholds),
+ ],
+ }));
+ OperationResponse {
+ code: 104,
+ return_code: 0,
+ message: Typed::Null,
+ params: resp_params.into(),
+ }
+ }
+}
+
+impl OperationCode for QualityConfigTeller {
+ fn op_code() -> u8 {
+ 104
+ }
+}
diff --git a/rc_services_room/src/operations/load_analytics.rs b/rc_services_room/src/operations/load_analytics.rs
new file mode 100644
index 0000000..d2232fc
--- /dev/null
+++ b/rc_services_room/src/operations/load_analytics.rs
@@ -0,0 +1,34 @@
+use polariton::operation::Dict;
+use polariton_server::operations::{Operation, OperationCode};
+
+pub struct NoAnalytics;
+
+impl NoAnalytics {
+ const ANALYTICS_DICT_KEY: u8 = 83;
+}
+
+impl Operation for NoAnalytics {
+ type State = ();
+ type User = crate::UserTy;
+
+ fn handle(&self, _: polariton::operation::ParameterTable, _: &mut Self::State, _: &Self::User) -> polariton::operation::OperationResponse {
+ let mut resp_params = std::collections::HashMap::new();
+ resp_params.insert(Self::ANALYTICS_DICT_KEY, polariton::operation::Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 115, // str
+ items: Vec::new(),
+ }));
+ polariton::operation::OperationResponse {
+ code: 70,
+ return_code: 0,
+ message: polariton::operation::Typed::Null,
+ params: resp_params.into(),
+ }
+ }
+}
+
+impl OperationCode for NoAnalytics {
+ fn op_code() -> u8 {
+ 70
+ }
+}
diff --git a/rc_services_room/src/operations/login_flags.rs b/rc_services_room/src/operations/login_flags.rs
new file mode 100644
index 0000000..a2f1ceb
--- /dev/null
+++ b/rc_services_room/src/operations/login_flags.rs
@@ -0,0 +1,45 @@
+use polariton_server::operations::{Operation, OperationCode};
+
+pub struct UserFlagsTeller;
+
+impl UserFlagsTeller {
+ const REMOVE_OBSOLETE_CUBES_KEY: u8 = 113;
+ const REMOVE_UNOWNED_CUBES_KEY: u8 = 114;
+ const REWARD_TITLE_KEY: u8 = 115;
+ const REWARD_BODY_KEY: u8 = 116;
+ const REFUND_OBSOLETE_CUBES_KEY: u8 = 117;
+ const CUBES_ARE_REPLACED_KEY: u8 = 118;
+ const NEW_USER_KEY: u8 = 119;
+ const AB_TEST_KEY: u8 = 166;
+ const AB_GROUP_KEY: u8 = 167;
+}
+
+impl Operation for UserFlagsTeller {
+ type State = ();
+ type User = crate::UserTy;
+
+ fn handle(&self, _: polariton::operation::ParameterTable, _: &mut Self::State, _: &Self::User) -> polariton::operation::OperationResponse {
+ let mut resp_params = std::collections::HashMap::new();
+ resp_params.insert(Self::REMOVE_OBSOLETE_CUBES_KEY, polariton::operation::Typed::Bool(false.into()));
+ resp_params.insert(Self::REMOVE_UNOWNED_CUBES_KEY, polariton::operation::Typed::Bool(false.into()));
+ resp_params.insert(Self::REWARD_TITLE_KEY, polariton::operation::Typed::Str("Yay a reward!".into()));
+ resp_params.insert(Self::REWARD_BODY_KEY, polariton::operation::Typed::Str("I love you very much so here's nothing as a reward.".into()));
+ resp_params.insert(Self::REFUND_OBSOLETE_CUBES_KEY, polariton::operation::Typed::Bool(false.into()));
+ resp_params.insert(Self::CUBES_ARE_REPLACED_KEY, polariton::operation::Typed::Bool(false.into()));
+ resp_params.insert(Self::NEW_USER_KEY, polariton::operation::Typed::Bool(false.into()));
+ resp_params.insert(Self::AB_TEST_KEY, polariton::operation::Typed::Str("".into()));
+ resp_params.insert(Self::AB_GROUP_KEY, polariton::operation::Typed::Str("".into()));
+ polariton::operation::OperationResponse {
+ code: 105,
+ return_code: 0,
+ message: polariton::operation::Typed::Null,
+ params: resp_params.into(),
+ }
+ }
+}
+
+impl OperationCode for UserFlagsTeller {
+ fn op_code() -> u8 {
+ 105
+ }
+}
diff --git a/rc_services_room/src/operations/maintenancer.rs b/rc_services_room/src/operations/maintenancer.rs
new file mode 100644
index 0000000..c31c711
--- /dev/null
+++ b/rc_services_room/src/operations/maintenancer.rs
@@ -0,0 +1,28 @@
+use std::collections::HashMap;
+
+use polariton_server::operations::{Operation, OperationCode};
+
+pub struct MaintenanceModeTeller;
+
+impl Operation for MaintenanceModeTeller {
+ type State = ();
+ type User = crate::UserTy;
+
+ fn handle(&self, _: polariton::operation::ParameterTable, _: &mut Self::State, _: &Self::User) -> polariton::operation::OperationResponse {
+ let mut resp_params = HashMap::new();
+ resp_params.insert(20 /* is in maintenance mode? */, polariton::operation::Typed::Bool(false.into()));
+ resp_params.insert(19 /* maintenace mode message */, polariton::operation::Typed::Str("OpenJam's servers are currently undergoing maintenance".into()));
+ polariton::operation::OperationResponse {
+ code: 20,
+ return_code: 0,
+ message: polariton::operation::Typed::Null,
+ params: resp_params.into(),
+ }
+ }
+}
+
+impl OperationCode for MaintenanceModeTeller {
+ fn op_code() -> u8 {
+ 20
+ }
+}
diff --git a/rc_services_room/src/operations/mod.rs b/rc_services_room/src/operations/mod.rs
new file mode 100644
index 0000000..9ac2fc3
--- /dev/null
+++ b/rc_services_room/src/operations/mod.rs
@@ -0,0 +1,57 @@
+mod eac;
+mod more_auth;
+mod versioner;
+mod maintenancer;
+mod game_quality;
+mod login_flags;
+mod load_analytics;
+mod platform_config;
+mod tier_banding;
+mod cube_list;
+mod special_items;
+mod premium_config;
+mod palette_town;
+mod client_config;
+mod crf_config;
+mod weapon_stats;
+mod movement_stats;
+mod power_bar_stats;
+mod damage_boost_stats;
+mod battle_arena_config;
+mod cpu_limits_config;
+mod cosmetic_config;
+mod taunts_config;
+mod all_customisations_info;
+
+use polariton_server::operations::OperationsHandler;
+
+pub fn handler() -> OperationsHandler {
+ OperationsHandler::new()
+ .without_state(eac::EacChallengeIgnorer)
+ .without_state(more_auth::MoreLobbyAuth)
+ .without_state(versioner::VersionTeller)
+ .without_state(maintenancer::MaintenanceModeTeller)
+ .without_state(game_quality::QualityConfigTeller)
+ .without_state(login_flags::UserFlagsTeller)
+ .without_state(polariton_server::operations::Ack::<132, _>::default()) // verify user level
+ .without_state(load_analytics::NoAnalytics)
+ .without_state(polariton_server::operations::Ack::<131, _>::default()) // analytics updated notification
+ .without_state(platform_config::platform_config_provider())
+ .without_state(tier_banding::tiers_banding_provider())
+ .without_state(cube_list::cube_list_provider())
+ .without_state(special_items::special_item_list_provider())
+ .without_state(premium_config::premium_config_provider())
+ .without_state(palette_town::kanto())
+ .without_state(client_config::client_config_provider())
+ .without_state(crf_config::crf_config_provider())
+ .without_state(weapon_stats::weapon_config_provider())
+ .without_state(movement_stats::movement_config_provider())
+ .without_state(power_bar_stats::power_bar_provider())
+ .without_state(damage_boost_stats::damage_boost_provider())
+ .without_state(battle_arena_config::battle_arena_config_provider())
+ .without_state(cpu_limits_config::cpu_config_provider())
+ .without_state(cosmetic_config::cosmetic_limits_config_provider())
+ .without_state(taunts_config::taunts_config_provider())
+ .without_state(all_customisations_info::all_customisations_provider())
+ //.without_state(polariton_server::operations::Ack::<70, _>::default())
+}
diff --git a/rc_services_room/src/operations/more_auth.rs b/rc_services_room/src/operations/more_auth.rs
new file mode 100644
index 0000000..b154590
--- /dev/null
+++ b/rc_services_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_services_room/src/operations/movement_stats.rs b/rc_services_room/src/operations/movement_stats.rs
new file mode 100644
index 0000000..8e2fdd3
--- /dev/null
+++ b/rc_services_room/src/operations/movement_stats.rs
@@ -0,0 +1,142 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed, Dict};
+
+use crate::data::movement_list::*;
+use crate::data::cube_list::ItemTier;
+use crate::data::weapon_list::ItemCategory;
+
+const PARAM_KEY: u8 = 1;
+
+pub(super) fn movement_config_provider() -> SimpleFunc<62, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 104, // hashtable
+ items: vec![
+ (Typed::Str("Global".into()), Typed::HashMap(vec![
+ (Typed::Str("lerpValue".into()), Typed::Float(10.0)),
+ ].into())),
+ (Typed::Str("Movements".into()), Typed::HashMap(vec![
+ (Typed::Str(ItemCategory::Wheel.as_str().into()), MovementCategoryData {
+ horizontal_top_speed: Some(1.0),
+ vertical_top_speed: Some(1.0),
+ specifics: MovementCategorySpecificData::Wheel,
+ stats: vec![
+ (ItemTier::T0, MovementData {
+ speed_boost: Some(1.0),
+ max_carry_mass: Some(1.0),
+ horizontal_top_speed: Some(1.0),
+ vertical_top_speed: Some(1.0),
+ specifics: MovementSpecificData::Wheel(WheelData {
+ steering_speed_light: 1.0,
+ steering_speed_heavy: 1.0,
+ steering_force_multiplier_light: 1.0,
+ steering_force_multiplier_heavy: 1.0,
+ lateral_acceleration_light: 1.0,
+ lateral_acceleration_heavy: 1.0,
+ time_to_max_acceleration_light: 1.0,
+ time_to_max_acceleration_heavy: 1.0,
+ brake_force_light: 1.0,
+ brake_force_heavy: 1.0,
+ }),
+ }),
+ (ItemTier::T1, MovementData {
+ speed_boost: Some(1.0),
+ max_carry_mass: Some(1.0),
+ horizontal_top_speed: Some(1.0),
+ vertical_top_speed: Some(1.0),
+ specifics: MovementSpecificData::Wheel(WheelData {
+ steering_speed_light: 1.0,
+ steering_speed_heavy: 1.0,
+ steering_force_multiplier_light: 1.0,
+ steering_force_multiplier_heavy: 1.0,
+ lateral_acceleration_light: 1.0,
+ lateral_acceleration_heavy: 1.0,
+ time_to_max_acceleration_light: 1.0,
+ time_to_max_acceleration_heavy: 1.0,
+ brake_force_light: 1.0,
+ brake_force_heavy: 1.0,
+ }),
+ }),
+ (ItemTier::T2, MovementData {
+ speed_boost: Some(1.0),
+ max_carry_mass: Some(1.0),
+ horizontal_top_speed: Some(1.0),
+ vertical_top_speed: Some(1.0),
+ specifics: MovementSpecificData::Wheel(WheelData {
+ steering_speed_light: 1.0,
+ steering_speed_heavy: 1.0,
+ steering_force_multiplier_light: 1.0,
+ steering_force_multiplier_heavy: 1.0,
+ lateral_acceleration_light: 1.0,
+ lateral_acceleration_heavy: 1.0,
+ time_to_max_acceleration_light: 1.0,
+ time_to_max_acceleration_heavy: 1.0,
+ brake_force_light: 1.0,
+ brake_force_heavy: 1.0,
+ }),
+ }),
+ (ItemTier::T3, MovementData {
+ speed_boost: Some(1.0),
+ max_carry_mass: Some(1.0),
+ horizontal_top_speed: Some(1.0),
+ vertical_top_speed: Some(1.0),
+ specifics: MovementSpecificData::Wheel(WheelData {
+ steering_speed_light: 1.0,
+ steering_speed_heavy: 1.0,
+ steering_force_multiplier_light: 1.0,
+ steering_force_multiplier_heavy: 1.0,
+ lateral_acceleration_light: 1.0,
+ lateral_acceleration_heavy: 1.0,
+ time_to_max_acceleration_light: 1.0,
+ time_to_max_acceleration_heavy: 1.0,
+ brake_force_light: 1.0,
+ brake_force_heavy: 1.0,
+ }),
+ }),
+ (ItemTier::T4, MovementData {
+ speed_boost: Some(1.0),
+ max_carry_mass: Some(1.0),
+ horizontal_top_speed: Some(1.0),
+ vertical_top_speed: Some(1.0),
+ specifics: MovementSpecificData::Wheel(WheelData {
+ steering_speed_light: 1.0,
+ steering_speed_heavy: 1.0,
+ steering_force_multiplier_light: 1.0,
+ steering_force_multiplier_heavy: 1.0,
+ lateral_acceleration_light: 1.0,
+ lateral_acceleration_heavy: 1.0,
+ time_to_max_acceleration_light: 1.0,
+ time_to_max_acceleration_heavy: 1.0,
+ brake_force_light: 1.0,
+ brake_force_heavy: 1.0,
+ }),
+ }),
+ (ItemTier::T5, MovementData {
+ speed_boost: Some(1.0),
+ max_carry_mass: Some(1.0),
+ horizontal_top_speed: Some(1.0),
+ vertical_top_speed: Some(1.0),
+ specifics: MovementSpecificData::Wheel(WheelData {
+ steering_speed_light: 1.0,
+ steering_speed_heavy: 1.0,
+ steering_force_multiplier_light: 1.0,
+ steering_force_multiplier_heavy: 1.0,
+ lateral_acceleration_light: 1.0,
+ lateral_acceleration_heavy: 1.0,
+ time_to_max_acceleration_light: 1.0,
+ time_to_max_acceleration_heavy: 1.0,
+ brake_force_light: 1.0,
+ brake_force_heavy: 1.0,
+ }),
+ }),
+ ],
+ ..Default::default()
+ }.as_transmissible()),
+ ].into())),
+ ].into(),
+ }));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/palette_town.rs b/rc_services_room/src/operations/palette_town.rs
new file mode 100644
index 0000000..776fb92
--- /dev/null
+++ b/rc_services_room/src/operations/palette_town.rs
@@ -0,0 +1,61 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed};
+
+use crate::data::palette::*;
+
+const PALETTE_KEY: u8 = 34;
+const ORDER_KEY: u8 = 149;
+
+pub(super) fn kanto() -> SimpleFunc<31, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PALETTE_KEY, Typed::Bytes({
+ let mut buf = Vec::new();
+ Colour::write_many(vec![
+ // Red
+ Colour {
+ index: 0,
+ diffuse: ColourValue { r: 255, g: 0, b: 0, a: u8::MAX },
+ specular: ColourValue { r: 255, g: 0, b: 0, a: u8::MAX },
+ overlay: ColourValue { r: 255, g: 0, b: 0, a: u8::MAX },
+ premium: false,
+ },
+ // Blue
+ Colour {
+ index: 1,
+ diffuse: ColourValue { r: 0, g: 0, b: 255, a: u8::MAX },
+ specular: ColourValue { r: 0, g: 0, b: 255, a: u8::MAX },
+ overlay: ColourValue { r: 0, g: 0, b: 255, a: u8::MAX },
+ premium: false,
+ },
+ // Green
+ Colour {
+ index: 2,
+ diffuse: ColourValue { r: 0, g: 255, b: 0, a: u8::MAX },
+ specular: ColourValue { r: 0, g: 255, b: 0, a: u8::MAX },
+ overlay: ColourValue { r: 0, g: 255, b: 0, a: u8::MAX },
+ premium: false,
+ },
+ // Black
+ Colour {
+ index: 3,
+ diffuse: ColourValue { r: 0, g: 0, b: 0, a: u8::MAX },
+ specular: ColourValue { r: 0, g: 0, b: 0, a: u8::MAX },
+ overlay: ColourValue { r: 0, g: 0, b: 0, a: u8::MAX },
+ premium: false,
+ },
+ // White
+ Colour {
+ index: 4,
+ diffuse: ColourValue { r: 255, g: 255, b: 255, a: u8::MAX },
+ specular: ColourValue { r: 255, g: 255, b: 255, a: u8::MAX },
+ overlay: ColourValue { r: 255, g: 255, b: 255, a: u8::MAX },
+ premium: false,
+ },
+ ].as_slice(), &mut buf).unwrap_or_default();
+ buf.into()
+ }));
+ params.insert(ORDER_KEY, Typed::Bytes(vec![0u8, 1, 2, 3, 4].into()));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/platform_config.rs b/rc_services_room/src/operations/platform_config.rs
new file mode 100644
index 0000000..94b1314
--- /dev/null
+++ b/rc_services_room/src/operations/platform_config.rs
@@ -0,0 +1,29 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed, Dict};
+
+const PLATFORM_CONFIG_KEY: u8 = 197;
+
+pub(super) fn platform_config_provider() -> SimpleFunc<165, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PLATFORM_CONFIG_KEY, Typed::Dict(Dict {
+ key_ty: 42, // obj
+ val_ty: 42, // obj
+ items: vec![
+ (Typed::Str("BuyPremiumAvailable".into()), Typed::Bool(false.into())),
+ (Typed::Str("MainShopButtonAvailable".into()), Typed::Bool(false.into())),
+ (Typed::Str("RoboPassButtonAvailable".into()), Typed::Bool(false.into())),
+ (Typed::Str("LanguageSelectionAvailable".into()), Typed::Bool(false.into())),
+ (Typed::Str("AutoJoinPublicChatRoom".into()), Typed::Bool(false.into())), // TODO maybe?
+ (Typed::Str("CanCreateChatRooms".into()), Typed::Bool(false.into())), // TODO
+ (Typed::Str("CurseVoiceEnabled".into()), Typed::Bool(false.into())),
+ (Typed::Str("DeltaDNAEnabled".into()), Typed::Bool(false.into())),
+ (Typed::Str("UseDecimalSystem".into()), Typed::Bool(false.into())),
+ (Typed::Str("FeedbackURL".into()), Typed::Str("https://mstdn.ca/@ngram".into())),
+ (Typed::Str("SupportURL".into()), Typed::Str("https://git.ngni.us/OpenJam/servers".into())),
+ (Typed::Str("WikiURL".into()), Typed::Str("https://docs.rs/libfj/latest/libfj/".into())),
+ ].into(),
+ }));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/power_bar_stats.rs b/rc_services_room/src/operations/power_bar_stats.rs
new file mode 100644
index 0000000..e65306c
--- /dev/null
+++ b/rc_services_room/src/operations/power_bar_stats.rs
@@ -0,0 +1,16 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed};
+
+const PARAM_KEY: u8 = 61;
+
+pub(super) fn power_bar_provider() -> SimpleFunc<51, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, Typed::HashMap(vec![
+ (Typed::Str("refillRatePerSecond".into()), Typed::Float(1.0)),
+ (Typed::Str("powerForAllRobots".into()), Typed::Int(1_000 /* converted to u32 */)),
+ ].into()
+ ));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/premium_config.rs b/rc_services_room/src/operations/premium_config.rs
new file mode 100644
index 0000000..cdf1d32
--- /dev/null
+++ b/rc_services_room/src/operations/premium_config.rs
@@ -0,0 +1,22 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::ParameterTable;
+
+use crate::data::premium_config::*;
+
+const PARAM_KEY: u8 = 1;
+
+pub(super) fn premium_config_provider() -> SimpleFunc<5, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, PremiumEffects {
+ factor: PremiumFactor {
+ factor: 100,
+ party_bonus: 100,
+ },
+ multiplayer: PremiumMultiplayer {
+ tier_multiplier: 2.0,
+ }
+ }.as_transmissible());
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/special_items.rs b/rc_services_room/src/operations/special_items.rs
new file mode 100644
index 0000000..5bd0114
--- /dev/null
+++ b/rc_services_room/src/operations/special_items.rs
@@ -0,0 +1,25 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed, Dict};
+
+use crate::data::special_item::*;
+
+const PARAM_KEY: u8 = 1;
+
+pub(super) fn special_item_list_provider() -> SimpleFunc<6, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 104, // hashtable
+ items: vec![
+ //(u32 in base16 aka hex, hashtable)
+ (Typed::Str("DEADBEEF".into()), SpecialItem {
+ name: "cool".to_string(),
+ sprite: "chair".to_string(),
+ size: 1,
+ }.as_transmissible())
+ ].into(),
+ }));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/taunts_config.rs b/rc_services_room/src/operations/taunts_config.rs
new file mode 100644
index 0000000..a9b55ed
--- /dev/null
+++ b/rc_services_room/src/operations/taunts_config.rs
@@ -0,0 +1,43 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed};
+
+use crate::data::taunts_config::*;
+
+const PARAM_KEY: u8 = 195;
+
+pub(super) fn taunts_config_provider() -> SimpleFunc<164, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, Typed::Dict(polariton::operation::Dict {
+ key_ty: 115,
+ val_ty: 42,
+ items: vec![
+ (Typed::Str("taunts".into()), TauntsData {
+ taunts: vec![
+ TauntData {
+ group_name: "totally_real_group_name".to_string(),
+ assets: AssetData {
+ idle_effect: "tbd".to_string(),
+ active_effect: "something".to_string(),
+ sound_effect: "3rd thing here".to_string(),
+ },
+ animation_offset_x: 0.0,
+ animation_offset_y: 0.0,
+ animation_offset_z: 0.0,
+ cubes: vec![
+ CubeData {
+ cube_id: 1,
+ position_x: 0,
+ position_y: 0,
+ position_z: 0,
+ rotation: 0,
+ }
+ ]
+ }
+ ]
+ }.as_transmissible())
+ ]
+ }));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/tier_banding.rs b/rc_services_room/src/operations/tier_banding.rs
new file mode 100644
index 0000000..35d4cd5
--- /dev/null
+++ b/rc_services_room/src/operations/tier_banding.rs
@@ -0,0 +1,21 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed, Dict};
+
+const PARAM_KEY: u8 = 1;
+
+pub(super) fn tiers_banding_provider() -> SimpleFunc<7, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 42, // obj
+ items: vec![
+ (Typed::Str("tiersbands".into()), Typed::IntArr(vec![
+ 1
+ ].into())),
+ (Typed::Str("maximumRobotRankingARobotCanObtain".into()), Typed::Int(1)),
+ ].into(),
+ }));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/operations/versioner.rs b/rc_services_room/src/operations/versioner.rs
new file mode 100644
index 0000000..a7b2730
--- /dev/null
+++ b/rc_services_room/src/operations/versioner.rs
@@ -0,0 +1,30 @@
+use polariton_server::operations::{Operation, OperationCode};
+
+pub struct VersionTeller;
+
+impl VersionTeller {
+ const VERSION_NUMBER_KEY: u8 = 112;
+ const LATEST_VERSION: i32 = 2855;
+}
+
+impl Operation for VersionTeller {
+ type State = ();
+ type User = crate::UserTy;
+
+ fn handle(&self, _: polariton::operation::ParameterTable, _: &mut Self::State, _: &Self::User) -> polariton::operation::OperationResponse {
+ let mut resp_params = std::collections::HashMap::new();
+ resp_params.insert(Self::VERSION_NUMBER_KEY, polariton::operation::Typed::Int(Self::LATEST_VERSION));
+ polariton::operation::OperationResponse {
+ code: 103,
+ return_code: 0,
+ message: polariton::operation::Typed::Null,
+ params: resp_params.into(),
+ }
+ }
+}
+
+impl OperationCode for VersionTeller {
+ fn op_code() -> u8 {
+ 103
+ }
+}
diff --git a/rc_services_room/src/operations/weapon_stats.rs b/rc_services_room/src/operations/weapon_stats.rs
new file mode 100644
index 0000000..d0b24d7
--- /dev/null
+++ b/rc_services_room/src/operations/weapon_stats.rs
@@ -0,0 +1,47 @@
+use polariton_server::operations::SimpleFunc;
+use polariton::operation::{ParameterTable, Typed, Dict};
+
+use crate::data::weapon_list::*;
+use crate::data::cube_list::ItemTier;
+
+const PARAM_KEY: u8 = 57;
+
+pub(super) fn weapon_config_provider() -> SimpleFunc<47, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> {
+ SimpleFunc::new(|params, _| {
+ let mut params = params.to_dict();
+ params.insert(PARAM_KEY, Typed::Dict(Dict {
+ key_ty: 115, // str
+ val_ty: 104, // hashtable
+ items: vec![
+ // (Item category, map)
+ (Typed::Str(ItemCategory::Laser.as_str().into()), Typed::HashMap(vec![
+ (Typed::Str(ItemTier::T0.as_str().into()), WeaponData {
+ damage_inflicted: Some(42),
+ ..Default::default()
+ }.as_transmissible()),
+ (Typed::Str(ItemTier::T1.as_str().into()), WeaponData {
+ damage_inflicted: Some(420),
+ ..Default::default()
+ }.as_transmissible()),
+ (Typed::Str(ItemTier::T2.as_str().into()), WeaponData {
+ damage_inflicted: Some(4200),
+ ..Default::default()
+ }.as_transmissible()),
+ (Typed::Str(ItemTier::T3.as_str().into()), WeaponData {
+ damage_inflicted: Some(42000),
+ ..Default::default()
+ }.as_transmissible()),
+ (Typed::Str(ItemTier::T4.as_str().into()), WeaponData {
+ damage_inflicted: Some(420000),
+ ..Default::default()
+ }.as_transmissible()),
+ (Typed::Str(ItemTier::T5.as_str().into()), WeaponData {
+ damage_inflicted: Some(4200000),
+ ..Default::default()
+ }.as_transmissible()),
+ ].into()))
+ ].into(),
+ }));
+ Ok(params.into())
+ })
+}
diff --git a/rc_services_room/src/state.rs b/rc_services_room/src/state.rs
new file mode 100644
index 0000000..7adeeb0
--- /dev/null
+++ b/rc_services_room/src/state.rs
@@ -0,0 +1,43 @@
+use std::sync::{Arc, RwLock};
+
+pub struct State {
+ pub crypto: Box>,
+}
+
+impl State {
+ pub fn new(c: Box>) -> Self {
+ Self {
+ crypto: c,
+ }
+ }
+
+ pub fn binrw_args(&self) -> polariton::packet::WriteArgs {
+ Some(self.crypto.clone())
+ }
+
+ pub fn user(&self) -> crate::UserTy {
+ RwLock::new(UserState::default())
+ }
+}
+
+#[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
+ }
+ }
+}