1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Get test mode working, complete #12

This commit is contained in:
NGnius (Graham)
2025-03-15 17:21:18 -04:00
parent 6fd4d673ca
commit ec565e904b
31 changed files with 6249 additions and 1743 deletions

1
Cargo.lock generated
View File

@@ -1774,6 +1774,7 @@ name = "rc_services_room"
version = "0.1.0"
dependencies = [
"base64 0.22.1",
"chrono",
"clap",
"env_logger",
"hex",

File diff suppressed because it is too large Load Diff

View File

@@ -11,5 +11,6 @@ pub fn handler() -> OperationsHandler<crate::UserTy> {
.without_state(chat_ignores::ignores_provider())
.without_state(pending_sanctions::pending_sanctions_checker())
.without_state(all_joined_channels::all_channels_provider())
.without_state(polariton_server::operations::Ack::<12, _>::default())
//.without_state(polariton_server::operations::Ack::<00000, _>::default())
}

View File

@@ -15,3 +15,4 @@ base64 = "0.22"
hex = "0.4"
serde.workspace = true
serde_json.workspace = true
chrono = "0.4"

View File

@@ -1,3 +1,3 @@
#!/bin/bash
RUST_BACKTRACE=1 RUST_LOG=debug cargo run
RUST_BACKTRACE=1 RUST_LOG=debug cargo run #&> ../data/rc_services_room.log

View File

@@ -0,0 +1,19 @@
use polariton::operation::Typed;
pub struct AutoRegenHealthConfig {
pub seconds_to_wait_for_heal: f32,
pub seconds_to_full_heal: f32,
pub threshold_to_start_sound: f32,
pub enable_auto_heal: bool,
}
impl AutoRegenHealthConfig {
pub fn as_transmissible<C>(&self) -> Typed<C> {
let mut bytes = Vec::with_capacity(13);
bytes.extend_from_slice(&self.seconds_to_wait_for_heal.to_le_bytes());
bytes.extend_from_slice(&self.seconds_to_full_heal.to_le_bytes());
bytes.extend_from_slice(&self.threshold_to_start_sound.to_le_bytes());
bytes.push(self.enable_auto_heal as u8);
Typed::Bytes(bytes.into())
}
}

View File

@@ -0,0 +1,38 @@
#[repr(i16)]
#[allow(dead_code)]
#[derive(Debug)]
pub enum WebServicesError {
None = 0,
CPUTooHigh = 1,
CPUTooLow = 2,
RobotTierNotAllowed = 3,
CubeIDNotAllowed = 4,
CubeTypeNotAllowed = 5,
DatabaseError = 8,
UnexpectedError = 9,
WrongNumberOfAuthParams = 10,
Banned = 11,
EACValidationFailed = 12,
NotSteamUser = 13,
PromotionDoesntExist = 14,
NotEnoughMoney = 17,
MaxGarageSlots = 18,
PlatformFeatureNotAvailable = 19,
UserDoesNotHaveAllCubeTypes = 20,
ReplaceDailyQuestLimit = 21,
MaintenanceModeError = 125,
RobotShopMaintenanceMode = 126,
InvalidRobot = 140,
ExpiredRobot = 144,
RobotHasSanction = 145,
CustomisationNotOwned = 146,
ItemShopBundleExpired = 147,
UserNotFound = 200,
InvalidUsernameFormat = 201,
UsernameTooLong = 202,
InvalidUsername = 203,
TencentValidationFail = 204,
UsernameAlreadyTaken = 205,
UsernameTooShort = 206,
SaleEnded = 207
}

View File

@@ -32,7 +32,7 @@ impl GarageSlotInfo {
(Typed::Str("wasRated".into()), Typed::Bool(self.was_rated.into())),
(Typed::Str("movementCategories".into()), Typed::Arr(Arr {
ty: TypePrefix::Int, // int
items: self.movement_categories.iter().map(|x| Typed::Int((*x as i32) * 100_000)).collect(),
items: self.movement_categories.iter().map(|x| Typed::Int(x.but_bigger() as i32)).collect(),
})),
(Typed::Str("uniqueId1".into()), Typed::Int(self.uuid.0 as i32)),
(Typed::Str("uniqueId2".into()), Typed::Int(self.uuid.1 as i32)),

View File

@@ -0,0 +1,20 @@
#[repr(i8)]
#[derive(Debug)]
pub enum LobbyType {
None = -1,
CustomGame = 1,
QuickPlay = 2,
Solo = 3
}
impl LobbyType {
pub fn from_int(i: i32) -> Result<Self, i16> {
match i {
-1 => Ok(Self::None),
1 => Ok(Self::CustomGame),
2 => Ok(Self::QuickPlay),
3 => Ok(Self::Solo),
_ => Err(super::error_codes::WebServicesError::UnexpectedError as _),
}
}
}

View File

@@ -21,6 +21,10 @@ pub mod weapon_upgrade;
pub mod player_rank;
pub mod robot_data;
pub mod quest;
pub mod auto_regen;
pub mod voting;
pub mod lobby;
pub mod error_codes;
pub(self) fn encode_7_bit_i32(mut src: i32) -> Vec<u8> {
let mut out = Vec::with_capacity(5);

View File

@@ -54,6 +54,7 @@ pub enum MovementCategorySpecificData {
SprinterLeg(MechLegCategoryData), // same as mech leg
TankTrack,
Rotor(RotorCategoryData),
Ski,
}
impl MovementCategorySpecificData {
@@ -70,6 +71,7 @@ impl MovementCategorySpecificData {
Self::SprinterLeg(x) => x.as_transmissible(),
Self::TankTrack => Vec::default(),
Self::Rotor(x) => x.as_transmissible(),
Self::Ski => Vec::default(),
}
}
}
@@ -161,6 +163,7 @@ pub enum MovementSpecificData {
SprinterLeg(MechLegData), // same as mech leg
TankTrack(TankTrackData),
Rotor(RotorData),
Ski,
}
impl MovementSpecificData {
@@ -177,6 +180,7 @@ impl MovementSpecificData {
Self::SprinterLeg(x) => x.as_transmissible(),
Self::TankTrack(x) => x.as_transmissible(),
Self::Rotor(x) => x.as_transmissible(),
Self::Ski => Vec::default(),
}
}
}
@@ -213,7 +217,7 @@ impl WheelData {
pub struct HoverData {
pub max_hover_height_light: f32,
pub max_hover_height_heaver: f32,
pub max_hover_height_heavy: f32,
pub height_change_speed_light: f32,
pub height_change_speed_heavy: f32,
pub turn_torque_light: f32,
@@ -230,7 +234,7 @@ impl HoverData {
pub fn as_transmissible<C>(&self) -> Vec<(Typed<C>, Typed<C>)> {
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("maxHoverHeightHeavy".into()), Typed::Float(self.max_hover_height_heavy)),
(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)),
@@ -364,8 +368,8 @@ pub struct MechLegData {
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 long_jump_speed_scale_light: f32,
pub long_jump_speed_scale_heavy: f32,
pub max_lateral_force_light: f32,
pub max_lateral_force_heavy: f32,
pub max_damping_force_light: f32,
@@ -383,8 +387,8 @@ impl MechLegData {
(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("longJumpSpeedScaleLight".into()), Typed::Float(self.long_jump_speed_scale_light)),
(Typed::Str("longJumpSpeedScaleHeavy".into()), Typed::Float(self.long_jump_speed_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)),

View File

@@ -0,0 +1,37 @@
use polariton::{operation::Typed, serdes::TypePrefix};
pub struct VoteThresholdData {
pub name: String,
pub localised_name: String,
pub color: String,
pub votes_required: i32,
}
impl VoteThresholdData {
pub fn as_transmissible<C>(&self) -> Typed<C> {
Typed::Dict(polariton::operation::Dict {
key_ty: TypePrefix::Str,
val_ty: TypePrefix::Any,
items: vec![
(Typed::Str("name".into()), Typed::Str(self.name.clone().into())),
(Typed::Str("localisedName".into()), Typed::Str(self.localised_name.clone().into())),
(Typed::Str("color".into()), Typed::Str(self.color.clone().into())),
(Typed::Str("votesRequired".into()), Typed::Int(self.votes_required)),
]
})
}
}
pub enum Vote {
BestPlayed = 0,
BestLooking = 1,
}
impl Vote {
pub fn as_str(&self) -> &'static str {
match self {
Self::BestPlayed => "BestPlayed",
Self::BestLooking => "BestLooking",
}
}
}

View File

@@ -76,3 +76,16 @@ pub(super) fn garage_machine_save_provider() -> SimpleFunc<41, crate::UserTy, im
})
}
pub const DEFAULT_WEAPON_ORDER_PARAM_KEY: u8 = 138;
pub(super) fn weapon_order_provider() -> SimpleFunc<118, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
SimpleFunc::new(|params, user: &crate::UserTy| {
let mut params = params.to_dict();
let lock = user.read().unwrap();
let user_info = lock.user()?;
let weapon_order = user_info.slot_by_id(user_info.selected_garage_slot() as i32)?.weapon_order;
params.insert(DEFAULT_WEAPON_ORDER_PARAM_KEY, weapon_order);
Ok(params.into())
})
}

View File

@@ -72,6 +72,10 @@ mod cube_awards;
mod robot_sanction;
mod building_xp;
mod reconnect_game;
mod regen_config;
mod pageantry;
mod signup_time;
mod validate_machine;
use polariton_server::operations::OperationsHandler;
@@ -161,4 +165,9 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
.without_state(machine::garage_machine_save_provider())
.without_state(polariton_server::operations::Ack::<32, _>::default()) // TODO handle SaveMachineColorRequest instead of ignoring it
.without_state(polariton_server::operations::Ack::<45, _>::default()) // TODO handle UpdateThumbnailVersionRequest instead of ignoring it
.without_state(machine::weapon_order_provider())
.without_state(regen_config::auto_regen_config_provider(&init_ctx.cubes))
.without_state(pageantry::after_battle_vote_thresholds_provider(&init_ctx.cubes))
.without_state(signup_time::user_signup_date_provider())
.without_state(validate_machine::validate_robot_provider())
}

View File

@@ -0,0 +1,13 @@
use polariton_server::operations::Immediate;
use crate::persist::config::ConfigProvider;
const PARAM_KEY: u8 = 203; // bytes
pub(super) fn after_battle_vote_thresholds_provider(conf: &crate::persist::config::ConfigImpl) -> Immediate<169, crate::UserTy> {
Immediate::new(|| {
let mut params = std::collections::HashMap::with_capacity(1);
params.insert(PARAM_KEY, conf.after_battle_vote_config());
params.into()
})
}

View File

@@ -7,8 +7,8 @@ pub(super) fn power_bar_provider() -> SimpleFunc<51, crate::UserTy, impl (Fn(Par
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 */)),
(Typed::Str("refillRatePerSecond".into()), Typed::Float(1000.0)),
(Typed::Str("powerForAllRobots".into()), Typed::Int(1_000_000 /* converted to u32 */)),
].into()
));
Ok(params.into())

View File

@@ -0,0 +1,13 @@
use polariton_server::operations::Immediate;
use crate::persist::config::ConfigProvider;
const PARAM_KEY: u8 = 37; // bytes
pub(super) fn auto_regen_config_provider(conf: &crate::persist::config::ConfigImpl) -> Immediate<35, crate::UserTy> {
Immediate::new(|| {
let mut params = std::collections::HashMap::with_capacity(1);
params.insert(PARAM_KEY, conf.regen_config());
params.into()
})
}

View File

@@ -0,0 +1,14 @@
use polariton_server::operations::SimpleFunc;
use polariton::operation::{ParameterTable, Typed};
const PARAM_KEY: u8 = 71; // long
pub(super) fn user_signup_date_provider() -> SimpleFunc<63, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
SimpleFunc::new(|params, user: &crate::UserTy| {
let mut params = params.to_dict();
let lock = user.read().unwrap();
let user_info = lock.user()?;
params.insert(PARAM_KEY, Typed::Long(user_info.signup_date()));
Ok(params.into())
})
}

View File

@@ -14,7 +14,7 @@ pub(super) fn taunts_config_provider() -> SimpleFunc<164, crate::UserTy, impl (F
items: vec![
(Typed::Str("taunts".into()), TauntsData {
taunts: vec![
TauntData {
/*TauntData {
group_name: "totally_real_group_name".to_string(),
assets: AssetData {
idle_effect: "tbd".to_string(),
@@ -33,7 +33,7 @@ pub(super) fn taunts_config_provider() -> SimpleFunc<164, crate::UserTy, impl (F
rotation: 0,
}
]
}
}*/
]
}.as_transmissible())
]

View File

@@ -0,0 +1,30 @@
use polariton_server::operations::SimpleFunc;
use polariton::operation::{ParameterTable, Typed};
const LOBBY_PARAM_KEY: u8 = 134;
const VALIDATE_ROBOT_RESULT_PARAM_KEY: u8 = 111;
// possible codes
#[allow(dead_code)]
#[repr(u8)]
enum ValidateMachineResult {
Invalid = 0,
Ok = 1,
NoWeapon = 2,
NoMovement = 3,
Sanctioned = 4,
}
pub(super) fn validate_robot_provider() -> SimpleFunc<102, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
SimpleFunc::new(|params, _user: &crate::UserTy| {
let mut params = params.to_dict();
if let Some(Typed::Int(lobby_ty)) = params.get(&LOBBY_PARAM_KEY) {
log::info!("Got lobby type {} ({:?})", lobby_ty, crate::data::lobby::LobbyType::from_int(*lobby_ty));
}
// let lock = user.read().unwrap();
// let user_info = lock.user()?;
// TODO actually validate the vehicle
params.insert(VALIDATE_ROBOT_RESULT_PARAM_KEY, Typed::Int(ValidateMachineResult::Ok as _));
Ok(params.into())
})
}

View File

@@ -0,0 +1,62 @@
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct BattleConfig {
pub regen: AutoRegenHealth,
pub votes: HashMap<Vote, Vec<VoteThreshold>>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AutoRegenHealth {
pub wait_for_heal_s: f32,
pub wait_full_heal_s: f32,
pub sound_start_s: f32,
pub auto_heal: bool,
}
impl std::convert::Into<crate::data::auto_regen::AutoRegenHealthConfig> for AutoRegenHealth {
fn into(self) -> crate::data::auto_regen::AutoRegenHealthConfig {
crate::data::auto_regen::AutoRegenHealthConfig {
seconds_to_wait_for_heal: self.wait_for_heal_s,
seconds_to_full_heal: self.wait_full_heal_s,
threshold_to_start_sound: self.sound_start_s,
enable_auto_heal: self.auto_heal,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct VoteThreshold {
pub name: String,
pub localised_name: String,
pub color: String,
pub votes_required: i32,
}
impl std::convert::Into<crate::data::voting::VoteThresholdData> for VoteThreshold {
fn into(self) -> crate::data::voting::VoteThresholdData {
crate::data::voting::VoteThresholdData {
name: self.name,
localised_name: self.localised_name,
color: self.color,
votes_required: self.votes_required,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Vote {
BestPlayed,
BestLooking,
}
impl std::convert::Into<crate::data::voting::Vote> for Vote {
fn into(self) -> crate::data::voting::Vote {
match self {
Self::BestPlayed => crate::data::voting::Vote::BestPlayed,
Self::BestLooking => crate::data::voting::Vote::BestLooking,
}
}
}

View File

@@ -5,15 +5,16 @@ use serde::{Serialize, Deserialize};
use polariton::operation::{Typed, Dict};
use polariton::serdes::TypePrefix;
use super::super::{MovementCategoryData, MovementData, Cube, ItemCategory, ItemTier};
use super::super::{MovementCategoryData, MovementData, Cube, ItemCategory, ItemTier, BattleConfig};
const CUBE_CONFIG_FILENAME: &str = "cubes.json";
const CUBE_CONFIG_FILENAME: &str = "config.json";
#[derive(Serialize, Deserialize, Debug)]
pub struct CubeConfig {
cubes: HashMap<String, Cube>,
movement: HashMap<ItemCategory, MovementCategoryData>,
lerp_value: f32,
battle: BattleConfig,
}
impl CubeConfig {
@@ -153,4 +154,27 @@ impl <C: Clone> super::ConfigProvider<C> for CubeConfig {
fn ids(&self) -> Vec<u32> {
self.cubes.values().map(|cube| cube.id).collect()
}
fn regen_config(&self) -> Typed<C> {
let regen_data: crate::data::auto_regen::AutoRegenHealthConfig = self.battle.regen.clone().into();
regen_data.as_transmissible()
}
fn after_battle_vote_config(&self) -> Typed<C> {
let mut vote_data = Vec::with_capacity(self.battle.votes.len()); // probably len() == 2
for (key, val) in self.battle.votes.iter() {
let key_data: crate::data::voting::Vote = key.to_owned().into();
let mut val_data = Vec::with_capacity(val.len());
for item in val {
let vote_data: crate::data::voting::VoteThresholdData = item.to_owned().into();
val_data.push(vote_data.as_transmissible());
}
vote_data.push((Typed::Str(key_data.as_str().into()), Typed::ObjArr(val_data.into())));
}
Typed::Dict(Dict {
key_ty: TypePrefix::Str,
val_ty: TypePrefix::Any,
items: vote_data,
})
}
}

View File

@@ -7,4 +7,6 @@ pub trait ConfigProvider<C> {
fn weapon_upgrade_list(&self) -> Typed<C>;
fn tech_tree_nodes(&self, unlocked_cubes: &std::collections::HashSet<u32>) -> Typed<C>;
fn ids(&self) -> Vec<u32>;
fn regen_config(&self) -> Typed<C>;
fn after_battle_vote_config(&self) -> Typed<C>;
}

View File

@@ -22,7 +22,7 @@ pub struct CubeInfo {
pub cpu: u32,
#[serde(default = "default_1")]
pub health: u32,
#[serde(default = "default_1_0")]
#[serde(default)]
pub health_boost: f32,
#[serde(default)]
pub grey_out_in_tutorial: bool,
@@ -59,10 +59,6 @@ fn default_1() -> u32 {
1
}
fn default_1_0() -> f32 {
1.0
}
fn default_63() -> u32 {
63
}
@@ -113,8 +109,8 @@ impl <C: Clone> std::convert::Into<crate::data::cube_list::CubeInfo<C>> for Cube
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default)]
pub enum VisibilityMode {
Mothership,
#[default]
Mothership,
All,
Tutorial,
None,

View File

@@ -16,3 +16,6 @@ pub use weapon::{WeaponData, WeaponUpgradeInfo};
mod tech_tree;
pub use tech_tree::TechTreeData;
mod combat;
pub use combat::BattleConfig;

View File

@@ -11,6 +11,7 @@ pub struct MovementCategoryData {
pub max_hover_height: Option<f32>,
pub light_machine_mass: Option<f32>,
pub heavy_machine_mass: Option<f32>,
#[serde(flatten)]
pub specifics: MovementCategorySpecificData,
}
@@ -31,6 +32,7 @@ impl MovementCategoryData {
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
#[serde(tag = "movement_enum_variant")]
pub enum MovementCategorySpecificData {
#[default]
Wheel,
@@ -44,6 +46,7 @@ pub enum MovementCategorySpecificData {
SprinterLeg(MechLegCategoryData), // same as mech leg
TankTrack,
Rotor(RotorCategoryData),
Ski,
}
impl std::convert::Into<crate::data::movement_list::MovementCategorySpecificData> for MovementCategorySpecificData {
@@ -60,6 +63,7 @@ impl std::convert::Into<crate::data::movement_list::MovementCategorySpecificData
Self::SprinterLeg(x) => crate::data::movement_list::MovementCategorySpecificData::SprinterLeg(x.into()),
Self::TankTrack => crate::data::movement_list::MovementCategorySpecificData::TankTrack,
Self::Rotor(x) => crate::data::movement_list::MovementCategorySpecificData::Rotor(x.into()),
Self::Ski => crate::data::movement_list::MovementCategorySpecificData::Ski,
}
}
}
@@ -123,6 +127,7 @@ pub struct MovementData {
pub max_carry_mass: Option<f32>,
pub horizontal_top_speed: Option<f32>,
pub vertical_top_speed: Option<f32>,
#[serde(flatten)]
pub specifics: MovementSpecificData,
}
@@ -139,6 +144,7 @@ impl std::convert::Into<crate::data::movement_list::MovementData> for MovementDa
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "movement_enum_variant")]
pub enum MovementSpecificData {
Wheel(WheelData),
Hover(HoverData),
@@ -151,6 +157,7 @@ pub enum MovementSpecificData {
SprinterLeg(MechLegData), // same as mech leg
TankTrack(TankTrackData),
Rotor(RotorData),
Ski,
}
impl std::convert::Into<crate::data::movement_list::MovementSpecificData> for MovementSpecificData {
@@ -167,6 +174,7 @@ impl std::convert::Into<crate::data::movement_list::MovementSpecificData> for Mo
Self::SprinterLeg(x) => crate::data::movement_list::MovementSpecificData::SprinterLeg(x.into()),
Self::TankTrack(x) => crate::data::movement_list::MovementSpecificData::TankTrack(x.into()),
Self::Rotor(x) => crate::data::movement_list::MovementSpecificData::Rotor(x.into()),
Self::Ski=> crate::data::movement_list::MovementSpecificData::Ski,
}
}
}
@@ -205,7 +213,7 @@ impl std::convert::Into<crate::data::movement_list::WheelData> for WheelData {
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct HoverData {
pub max_hover_height_light: f32,
pub max_hover_height_heaver: f32,
pub max_hover_height_heavy: f32,
pub height_change_speed_light: f32,
pub height_change_speed_heavy: f32,
pub turn_torque_light: f32,
@@ -222,7 +230,7 @@ impl std::convert::Into<crate::data::movement_list::HoverData> for HoverData {
fn into(self) -> crate::data::movement_list::HoverData {
crate::data::movement_list::HoverData {
max_hover_height_light: self.max_hover_height_light,
max_hover_height_heaver: self.max_hover_height_heaver,
max_hover_height_heavy: self.max_hover_height_heavy,
height_change_speed_light: self.height_change_speed_light,
height_change_speed_heavy: self.height_change_speed_heavy,
turn_torque_light: self.turn_torque_light,
@@ -360,8 +368,8 @@ pub struct MechLegData {
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 long_jump_speed_scale_light: f32,
pub long_jump_speed_scale_heavy: f32,
pub max_lateral_force_light: f32,
pub max_lateral_force_heavy: f32,
pub max_damping_force_light: f32,
@@ -379,8 +387,8 @@ impl std::convert::Into<crate::data::movement_list::MechLegData> for MechLegData
turn_acceleration_heavy: self.turn_acceleration_heavy,
legacy_turn_acceleration_light: self.legacy_turn_acceleration_light,
legacy_turn_acceleration_heavy: self.legacy_turn_acceleration_heavy,
long_jump_speec_scale_light: self.long_jump_speec_scale_light,
long_jump_speec_scale_heavy: self.long_jump_speec_scale_heavy,
long_jump_speed_scale_light: self.long_jump_speed_scale_light,
long_jump_speed_scale_heavy: self.long_jump_speed_scale_heavy,
max_lateral_force_light: self.max_lateral_force_light,
max_lateral_force_heavy: self.max_lateral_force_heavy,
max_damping_force_light: self.max_damping_force_light,

View File

@@ -142,10 +142,10 @@ impl <C: Clone> super::User<C> for UserData {
data: polariton::operation::Typed::Bytes(slot.robot_data.into()),
colour_data: polariton::operation::Typed::Bytes(slot.colour_data.into()),
cube_count: polariton::operation::Typed::Int(slot.cubes as _),
weapon_order: polariton::operation::Typed::IntArr(vec![0].into()), // TODO
weapon_order: polariton::operation::Typed::IntArr(slot.weapon_order.clone().into()),
movement_categories: polariton::operation::Typed::IntArr(slot.movement_categories.into_iter().map(|cat| {
let cat: crate::data::weapon_list::ItemCategory = cat.into();
cat as i32
cat.but_bigger()
}).collect::<Vec<_>>().into()),
control_type: polariton::operation::Typed::Int(control_ty as _),
control_options: control_options.as_transmissible(),
@@ -169,12 +169,34 @@ impl <C: Clone> super::User<C> for UserData {
existing_data.robot_data = vehicle.robot_data;
existing_data.colour_data = vehicle.colour_data;
log::debug!("weapon order: {:?}", vehicle.weapon_order);
existing_data.weapon_order = vehicle.weapon_order;
self.save_garage(&existing_data).map_err(|e| {
log::error!("Failed to save vehicle {}: {}", id, e);
DATABASE_ERR
})?;
Ok(())
}
fn signup_date(&self) -> i64 {
match self.root.metadata() {
Ok(meta) => {
match meta.created() {
Ok(created) => {
match created.duration_since(std::time::SystemTime::UNIX_EPOCH) {
Ok(dur) => {
return super::since_windows_epoch(dur.as_secs() as i64);
},
Err(e) => log::error!("could not get duration since unix epoch of {}: {}", self.root.display(), e),
}
},
Err(e) => log::error!("could not read creation time of {}: {}", self.root.display(), e),
}
},
Err(e) => log::error!("could not retrieve metadata of {}: {}", self.root.display(), e),
}
super::since_windows_epoch(0)
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]

View File

@@ -60,7 +60,7 @@ fn default_garage_slots() -> Vec<crate::persist::GarageSlot> {
control_options: crate::persist::GarageControls { vertical_strafing: false, sideways_driving: false, tracks_turn_on_spot: false, },
mastery_level: 1,
bay_skin_id: "RC_MothershipSkin_Neptune_01".to_owned(), // TODO get the rest of the names
weapon_order: vec![0],
weapon_order: vec![],
robot_data: vec![0; 4],
colour_data: vec![0; 4],
}

View File

@@ -24,3 +24,11 @@ fn __must_impl<T: UserProvider<()>>() {}
fn __test_impl() {
__must_impl::<UserImpl>();
}
pub fn since_windows_epoch(since_unix_epoch: i64) -> i64 {
use chrono::TimeZone;
let windows_epoch = chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::parse_from_str("1601-01-01 00:00:00", "%Y-%m-%d %H:%M:%S").unwrap());
let time_in = chrono::DateTime::<chrono::Utc>::from_timestamp(since_unix_epoch, 0).unwrap();
//let time_in = chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::from_timestamp(since_unix_epoch, 0));
time_in.signed_duration_since(windows_epoch).num_milliseconds() * 10_000
}

View File

@@ -21,6 +21,7 @@ pub trait User<C> {
fn all_slots_by_id(&self) -> UserSlots<C>;
fn slot_by_id(&self, id: i32) -> Result<UserSlotData<C>, i16>;
fn save_slot(&self, vehicle: VehicleData) -> Result<(), i16>;
fn signup_date(&self) -> i64;
}
pub struct UserSlots<C> {

View File

@@ -2,6 +2,8 @@
import sys
import json
import argparse
import re
# weapon required data
WEAPONS = {
@@ -203,6 +205,16 @@ def guess_type(category: str) -> str:
else:
return "NotAFunctionalItem"
TIERS = [
"NoTier",
"T0",
"T1",
"T2",
"T3",
"T4",
"T5",
]
def guess_tier(name: str, sprite: str) -> str:
if guess_category(name, sprite) == "NotAFunctionalItem" and "medium" in name.lower(): # Medium cube variants
return "NoTier"
@@ -288,19 +300,134 @@ def is_variant_guess(name: str, sprite: str) -> bool:
return True
return False
def main():
print(sys.argv)
filename_in = sys.argv[1]
with open(filename_in) as f:
FIELD_NAME_EXCEPTIONS = {
"verticalTopSpeed": "max_vertical_velocity",
}
def rename_field(original: str) -> str:
if original in FIELD_NAME_EXCEPTIONS:
return FIELD_NAME_EXCEPTIONS[original]
else:
return camel_to_snake_case(original)
SEEN_MOVEMENTS = {}
SEEN_WEAPONS = {}
EXCLUDED_FIELDS = [
"T0",
"T1",
"T2",
"T3",
"T4",
"T5",
]
def apply_entry_overrides(entry: dict, cubes_data: dict, weapons_data: dict, movement_data: dict):
if cubes_data is not None:
if entry["hexId"] in cubes_data:
cube_data = cubes_data[entry["hexId"]]
entry["info"]["description"] = cube_data["Description"]
entry["info"]["health"] = cube_data["health"]
entry["info"]["cpu"] = cube_data["cpuRating"]
entry["info"]["visibility"] = cube_data["buildVisibility"]
entry["info"]["ranking"] = cube_data["robotRanking"]
if "ItemSize" in cube_data:
entry["info"]["size"] = cube_data["ItemSize"]
if "ItemType" in cube_data:
entry["info"]["type"] = cube_data["ItemType"]
if "ItemCategory" in cube_data:
entry["info"]["category"] = cube_data["ItemCategory"]
if "PlacementFaces" in cube_data:
entry["info"]["placements"] = cube_data["PlacementFaces"]
if movement_data is not None and not entry["isVariant"]:
if entry["info"]["category"] in movement_data["Movements"]:
if entry["info"]["size"] in movement_data["Movements"][entry["info"]["category"]]:
if entry["info"]["category"] not in SEEN_MOVEMENTS:
SEEN_MOVEMENTS[entry["info"]["category"]] = dict()
SEEN_MOVEMENTS[entry["info"]["category"]][entry["info"]["size"]] = True
specific_data = movement_data["Movements"][entry["info"]["category"]][entry["info"]["size"]]
if "movement" not in entry:
entry["movement"] = dict()
for (key, val) in specific_data.items():
entry["movement"][rename_field(key)] = specific_data[key]
entry["movement"]["movement_enum_variant"] = entry["info"]["category"]
if weapons_data is not None and not entry["isVariant"]:
weapon_key = entry["info"]["size"] + "_" + entry["info"]["category"]
if weapon_key in weapons_data:
if weapon_key in SEEN_WEAPONS:
print("WARN: Already seen weapon key", weapon_key)
SEEN_WEAPONS[weapon_key] = True
if "weapon" not in entry:
entry["weapon"] = dict()
for (key, val) in weapons_data[weapon_key].items():
entry["weapon"][rename_field(key)] = val
def apply_global_overrides(conf: dict, cubes_data: dict, weapons_data: dict, movement_data: dict):
if movement_data is not None:
if "movement" not in conf:
conf["movement"] = dict()
for (category, cat_setting) in movement_data["Movements"].items():
if category not in conf["movement"]:
conf["movement"][category] = dict()
for (field, setting) in cat_setting.items():
if field not in EXCLUDED_FIELDS:
conf["movement"][category][rename_field(field)] = setting
conf["movement"][category]["movement_enum_variant"] = category
def load_or_none(file_json):
if file_json is None:
return None
else:
with open(file_json) as f:
return json.load(f)
CAMEL_TO_SNAKE_PATTERN = re.compile(r'(?<!^)(?=[A-Z])')
def camel_to_snake_case(s: str) -> str:
return CAMEL_TO_SNAKE_PATTERN.sub('_', s).lower()
def main(asset_in, cubes=None, weapons=None, movement=None):
cubes_data = load_or_none(cubes)
weapons_data = load_or_none(weapons)
movement_data = load_or_none(movement)
with open(asset_in) as f:
cubes_asset = json.load(f)
name = cubes_asset["0 MonoBehaviour Base"]["1 string m_Name"]
print(f"found name (expected to be empty): `{name}`")
cubes = cubes_asset["0 MonoBehaviour Base"]["0 CubeTypeData cubeTypes"]["0 Array Array"]
print(f"found {len(cubes)} cubes to process")
cubes_out = {
conf_out = {
"cubes": dict(),
"movement": dict(),
"lerp_value": 10.0,
"lerp_value": 0.1,
"battle": {
"regen": {
"wait_for_heal_s": 5.0,
"wait_full_heal_s": 5.0,
"sound_start_s": 2.5,
"auto_heal": True,
},
"votes": {
"BestPlayed": [
{
"name": "Best Played",
"localised_name": "strBestPlayed",
"color": "0000ff",
"votes_required": 1,
}
],
"BestLooking": [
{
"name": "Best Looking",
"localised_name": "strBestLooking",
"color": "ff0000",
"votes_required": 2,
}
],
}
}
}
last_tech_tree_id = 0
tech_tree_index = 0
@@ -346,6 +473,7 @@ def main():
"nameStrKey": cube["1 string nameStrKey"],
"mirrorCubeId": cube["0 PersistentCubeData cubeData"]["1 string mirrorCubeId"],
"hexId": str(cube["1 string itemCode"]),
"isVariant": is_variant_guess(cube["1 string nameStrKey"], cube["1 string spriteName"]),
}
if "protonium" in name.lower():
new_entry["info"]["protonium"] = True
@@ -364,7 +492,7 @@ def main():
else:
new_entry["info"]["description"] += " (" + str(cube["0 unsigned int itemCodeValue"]) + "_10|" + str(cube["1 string itemCode"]) + "_16)"
is_original = not is_variant_guess(cube["1 string nameStrKey"], cube["1 string spriteName"])
is_original = not new_entry["isVariant"]
# weapons
if new_entry["info"]["type"] == "Weapon":
if is_original and "module" not in new_entry["info"]["category"].lower(): # ignore variants and modules
@@ -410,11 +538,36 @@ def main():
}
tech_tree_specials_index += 1
# overrides
apply_entry_overrides(new_entry, cubes_data, weapons_data, movement_data)
print(f"processed cube {i} into {new_entry}")
cubes_out["cubes"][new_key] = new_entry
with open("../assets/robocraft/cubes.json", "w") as f:
json.dump(cubes_out, f, indent=4)
conf_out["cubes"][new_key] = new_entry
# more overrides
apply_global_overrides(conf_out, cubes_data, weapons_data, movement_data)
with open("../assets/robocraft/config.json", "w") as f:
json.dump(conf_out, f, indent=4)
print(f"processed {len(cubes)} cubes")
if movement_data is not None:
for category in CATEGORIES[1:13]:
if category not in SEEN_MOVEMENTS:
print("Missed movement category", category)
else:
if len(SEEN_MOVEMENTS[category]) != len(TIERS) - 1:
print("Only found", len(SEEN_MOVEMENTS[category]), "of", len(TIERS) - 1, "tiers for movement category", category)
'''for tier in TIERS[1:]:
if tier not in SEEN_MOVEMENTS[category] or SEEN_MOVEMENTS[category][tier] == False:
print("Missed movement tier", tier, "in category", category)'''
if weapons_data is not None:
if len(weapons_data) != len(SEEN_WEAPONS):
print("Only found", len(SEEN_WEAPONS), "of", len(weapons_data), "weapon entries in reference")
if __name__ == "__main__":
main()
parser = argparse.ArgumentParser()
parser.add_argument("asset_json")
parser.add_argument("--cubes")
parser.add_argument("--weapons")
parser.add_argument("--movement")
args = parser.parse_args()
main(args.asset_json, cubes=args.cubes, weapons=args.weapons, movement=args.movement)