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

Implement non-event part of singleplayer for #7

This commit is contained in:
NGnius (Graham)
2025-03-18 16:47:11 -04:00
parent 792585a031
commit 67c14fe80a
35 changed files with 2127 additions and 17 deletions

View File

@@ -0,0 +1,41 @@
use polariton::{operation::Typed, serdes::TypePrefix};
pub struct GameModeConfig {
pub respawn_heal_duration: f32,
pub respawn_full_heal_duration: f32,
pub kill_limit: i32,
pub game_time_minutes: i32,
}
impl GameModeConfig {
pub fn as_transmissible<C>(&self) -> Typed<C> {
Typed::HashMap(vec![
(Typed::Str("respawnHealDuration".into()), Typed::Float(self.respawn_heal_duration)),
(Typed::Str("respawnFullHealDuration".into()), Typed::Float(self.respawn_full_heal_duration)),
(Typed::Str("killLimit".into()), Typed::Int(self.kill_limit)),
(Typed::Str("gameTimeMinutes".into()), Typed::Int(self.game_time_minutes)),
].into())
}
}
pub struct GameModeConfigs {
pub battle_arena: GameModeConfig,
pub elimination: GameModeConfig,
pub the_pit: GameModeConfig,
pub team_deathmatch: GameModeConfig,
}
impl GameModeConfigs {
pub fn as_transmissible<C>(&self) -> Typed<C> {
Typed::Dict(polariton::operation::Dict {
key_ty: TypePrefix::Str,
val_ty: TypePrefix::HashMap,
items: vec![
(Typed::Str("BattleArena".into()), self.battle_arena.as_transmissible()),
(Typed::Str("Elimination".into()), self.elimination.as_transmissible()),
(Typed::Str("ThePit".into()), self.the_pit.as_transmissible()),
(Typed::Str("TeamDeathmatch".into()), self.team_deathmatch.as_transmissible()),
],
})
}
}

View File

@@ -25,8 +25,11 @@ pub mod auto_regen;
pub mod voting;
pub mod lobby;
pub mod error_codes;
pub mod game_mode;
pub mod score_multipliers;
pub(self) fn encode_7_bit_i32(mut src: i32) -> Vec<u8> {
if src == 0 { return vec![0] }
let mut out = Vec::with_capacity(5);
while src != 0 {
let last_7 = (src & 0x7F) as u8;

View File

@@ -0,0 +1,109 @@
use polariton::operation::Typed;
pub struct ScoreMultipliersData {
pub max_cpu: f32,
pub stat_multipliers: std::collections::HashMap<InGameStat, ScoreMultiplier>,
pub completed_battle_base_multiplier: f32,
pub completed_battle_bonus_multiplier: f32,
pub delta_scaler: f32,
pub defeat_score: u32,
pub victory_score: u32,
pub max_score_ratio: f32,
}
impl ScoreMultipliersData {
fn dump(&self, writer: &mut dyn std::io::Write) -> std::io::Result<usize> {
writer.write_all(&self.max_cpu.to_le_bytes())?;
for (key, val) in self.stat_multipliers.iter() {
writer.write_all(&(*key as u32).to_le_bytes())?;
writer.write_all(&val.base.to_le_bytes())?;
writer.write_all(&val.bonus.to_le_bytes())?;
}
writer.write_all(&self.completed_battle_base_multiplier.to_le_bytes())?;
writer.write_all(&self.completed_battle_bonus_multiplier.to_le_bytes())?;
writer.write_all(&self.delta_scaler.to_le_bytes())?;
writer.write_all(&self.defeat_score.to_le_bytes())?;
writer.write_all(&self.victory_score.to_le_bytes())?;
writer.write_all(&self.max_score_ratio.to_le_bytes())?;
Ok(28 + (12 * self.stat_multipliers.len()))
}
pub fn as_transmissible<C>(&self) -> Typed<C> {
let mut buf = Vec::new();
self.dump(&mut std::io::Cursor::new(&mut buf)).unwrap();
Typed::Bytes(buf.into())
}
}
pub struct ScoreMultiplier {
pub base: f32,
pub bonus: f32,
}
impl std::default::Default for ScoreMultiplier {
fn default() -> Self {
Self {
base: 0.5,
bonus: 0.9
}
}
}
#[allow(dead_code)]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InGameStat {
None = 0,
DestroyedCubes = 1,
DestroyedCubesInProtection = 2,
DestroyedCubesDefendingTheBase = 3,
Kill = 4,
KillAssist = 5,
HealCubes = 6,
HealAssist = 7,
DestroyedProtoniumCubes = 8,
BaseCaptureClassicMode = 9,
RobotDestroyed = 10,
Score = 11,
HealthPercentageBonusClassicMode = 12,
Points = 13,
CurrentKillStreak = 14,
BestKillStreak = 15,
CapturePointBattleArenaMode = 16,
EqualiserDestroyedBattleArenaMode = 17,
BattleArenaObjectives = 18,
}
impl std::default::Default for ScoreMultipliersData {
fn default() -> Self {
Self {
max_cpu: 1000.0,
stat_multipliers: vec![
(InGameStat::DestroyedCubes, ScoreMultiplier::default()),
(InGameStat::DestroyedCubesInProtection, ScoreMultiplier::default()),
(InGameStat::DestroyedCubesDefendingTheBase, ScoreMultiplier::default()),
(InGameStat::Kill, ScoreMultiplier::default()),
(InGameStat::KillAssist, ScoreMultiplier::default()),
(InGameStat::HealCubes, ScoreMultiplier::default()),
(InGameStat::HealAssist, ScoreMultiplier::default()),
(InGameStat::DestroyedProtoniumCubes, ScoreMultiplier::default()),
(InGameStat::BaseCaptureClassicMode, ScoreMultiplier::default()),
(InGameStat::RobotDestroyed, ScoreMultiplier::default()),
(InGameStat::Score, ScoreMultiplier::default()),
(InGameStat::HealthPercentageBonusClassicMode, ScoreMultiplier::default()),
(InGameStat::Points, ScoreMultiplier::default()),
(InGameStat::CurrentKillStreak, ScoreMultiplier::default()),
(InGameStat::BestKillStreak, ScoreMultiplier::default()),
(InGameStat::CapturePointBattleArenaMode, ScoreMultiplier::default()),
(InGameStat::EqualiserDestroyedBattleArenaMode, ScoreMultiplier::default()),
(InGameStat::BattleArenaObjectives, ScoreMultiplier::default()),
].into_iter().collect(),
completed_battle_base_multiplier: 1.0,
completed_battle_bonus_multiplier: 1.2,
delta_scaler: 0.5,
defeat_score: 500,
victory_score: 2_000,
max_score_ratio: 2.0,
}
}
}

View File

@@ -20,7 +20,7 @@ pub(super) fn all_customisations_provider() -> SimpleFunc<216, crate::UserTy, im
items: vec![
CustomisationData {
id: "RC_MothershipSkin_Neptune_01".to_string(),
localised_name: "Neptune 01".to_string(),
localised_name: "strNeptune".to_string(),
skin_scene_name: "RC_MothershipSkin_Neptune_01".to_string(),
simulation_prefab: "TODO_sim_prefab".to_string(),
preview_image_name: "TODO_preview_img".to_string(),
@@ -31,12 +31,61 @@ pub(super) fn all_customisations_provider() -> SimpleFunc<216, crate::UserTy, im
params.insert(SPAWNS_KEY, Typed::Arr(Arr {
ty: TypePrefix::HashMap, // hashtable
items: vec![
// TODO set these up with the correct values (IDs are correct)
CustomisationData {
id: "spawn0".to_string(),
localised_name: "Default".to_string(),
skin_scene_name: "TODO_skin".to_string(),
simulation_prefab: "TODO_sim_prefab".to_string(),
preview_image_name: "TODO_preview_img".to_string(),
id: "Spawn".to_string(),
localised_name: "strSpawnFXWarpIn".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Respawn_WarpIn".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Spawn_BlackHole".to_string(),
localised_name: "strSpawnFXWarpIn".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Respawn_WarpIn".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Spawn_Lander".to_string(),
localised_name: "strSpawnFXWarpIn".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Respawn_WarpIn".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Spawn_Lootcrate".to_string(),
localised_name: "strSpawnFXWarpIn".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Respawn_WarpIn".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Spawn_Warp".to_string(),
localised_name: "strSpawnFXWarpIn".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Respawn_WarpIn".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Spawn_Present".to_string(),
localised_name: "strSpawnFXWarpIn".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Respawn_WarpIn".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Spawn_EasterEgg".to_string(),
localised_name: "strSpawnFXWarpIn".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Respawn_WarpIn".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
],
@@ -44,12 +93,61 @@ pub(super) fn all_customisations_provider() -> SimpleFunc<216, crate::UserTy, im
params.insert(DEATHS_KEY, Typed::Arr(Arr {
ty: TypePrefix::HashMap, // hashtable
items: vec![
// TODO set these up with the correct values (IDs are correct)
CustomisationData {
id: "death0".to_string(),
localised_name: "Default".to_string(),
skin_scene_name: "TODO_skin".to_string(),
simulation_prefab: "TODO_sim_prefab".to_string(),
preview_image_name: "TODO_preview_img".to_string(),
id: "Explosion".to_string(),
localised_name: "strDeathFXEmergencyWarp".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Death_WarpOut".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Explosion_Toon".to_string(),
localised_name: "strDeathFXEmergencyWarp".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Death_WarpOut".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Explosion_Feathers_Rainbow".to_string(),
localised_name: "strDeathFXEmergencyWarp".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Death_WarpOut".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Explosion_Nuclear".to_string(),
localised_name: "strDeathFXEmergencyWarp".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Death_WarpOut".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Explosion_Warp".to_string(),
localised_name: "strDeathFXEmergencyWarp".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Death_WarpOut".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Explosion_BlackHole".to_string(),
localised_name: "strDeathFXEmergencyWarp".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Death_WarpOut".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
CustomisationData {
id: "Explosion_Firework".to_string(),
localised_name: "strDeathFXEmergencyWarp".to_string(),
skin_scene_name: "Splash_Loading_Screen".to_string(),
simulation_prefab: "Death_WarpOut".to_string(),
preview_image_name: "RC_Splash_Screen_01_Japanese".to_string(),
is_default: true,
}.as_transmissible(),
],

View File

@@ -0,0 +1,15 @@
use polariton_server::operations::SimpleFunc;
use polariton::operation::ParameterTable;
use crate::persist::config::ConfigProvider;
const PARAM_KEY: u8 = 1;
pub(super) fn game_mode_config_provider(conf: &crate::persist::config::ConfigImpl) -> SimpleFunc<113, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
let game_config = conf.game_mode_config();
SimpleFunc::new(move |params, _| {
let mut params = params.to_dict();
params.insert(PARAM_KEY, game_config.clone());
Ok(params.into())
})
}

View File

@@ -76,6 +76,9 @@ mod regen_config;
mod pageantry;
mod signup_time;
mod validate_machine;
mod game_mode_config;
mod score_multipliers_config;
mod player_robot_rank;
use polariton_server::operations::OperationsHandler;
@@ -170,4 +173,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
.without_state(pageantry::after_battle_vote_thresholds_provider(&init_ctx.cubes))
.without_state(signup_time::user_signup_date_provider())
.without_state(validate_machine::validate_robot_provider())
.without_state(game_mode_config::game_mode_config_provider(&init_ctx.cubes))
.without_state(score_multipliers_config::tdm_ai_score_config_provider())
.without_state(player_robot_rank::player_robot_rank_provider())
}

View File

@@ -0,0 +1,23 @@
use polariton_server::operations::SimpleFunc;
use polariton::operation::{ParameterTable, Typed};
const USERNAME_PARAM_KEY: u8 = 30; // in; str
const RANK_PARAM_KEY: u8 = 84; // out; int
const CPU_PARAM_KEY: u8 = 177; // out; int
const COSMETIC_CPU_PARAM_KEY: u8 = 176; // out; int
pub(super) fn player_robot_rank_provider() -> SimpleFunc<79, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
SimpleFunc::new(|params, user: &crate::UserTy| {
let lock = user.read().unwrap();
let user = lock.user()?;
let mut params = params.to_dict();
if let Some(Typed::Str(username)) = params.get(&USERNAME_PARAM_KEY) {
log::debug!("Get robot rank for user {}", username.string);
}
let robot = user.slot_by_id(user.selected_garage_slot() as i32)?;
params.insert(RANK_PARAM_KEY, robot.robot_rank);
params.insert(CPU_PARAM_KEY, robot.cpu);
params.insert(COSMETIC_CPU_PARAM_KEY, robot.cosmetic_cpu);
Ok(params.into())
})
}

View File

@@ -0,0 +1,15 @@
use polariton_server::operations::SimpleFunc;
use polariton::operation::ParameterTable;
use crate::data::score_multipliers::*;
const PARAM_KEY: u8 = 137;
pub(super) fn tdm_ai_score_config_provider(/*conf: &crate::persist::config::ConfigImpl*/) -> SimpleFunc<117, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
//let game_config = conf.game_mode_config();
SimpleFunc::new(move |params, _| {
let mut params = params.to_dict();
params.insert(PARAM_KEY, ScoreMultipliersData::default().as_transmissible());
Ok(params.into())
})
}

View File

@@ -6,6 +6,8 @@ use serde::{Serialize, Deserialize};
pub struct BattleConfig {
pub regen: AutoRegenHealth,
pub votes: HashMap<Vote, Vec<VoteThreshold>>,
#[serde(default = "default_game_modes")]
pub games: GameModes,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
@@ -60,3 +62,70 @@ impl std::convert::Into<crate::data::voting::Vote> for Vote {
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
pub struct GameMode {
pub respawn_heal_duration: f32,
pub respawn_full_heal_duration: f32,
pub kill_limit: i32,
pub game_time_m: i32,
}
impl std::convert::Into<crate::data::game_mode::GameModeConfig> for GameMode {
fn into(self) -> crate::data::game_mode::GameModeConfig {
crate::data::game_mode::GameModeConfig {
respawn_heal_duration: self.respawn_heal_duration,
respawn_full_heal_duration: self.respawn_full_heal_duration,
kill_limit: self.kill_limit,
game_time_minutes: self.game_time_m,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
pub struct GameModes {
pub battle_arena: GameMode,
pub elimination: GameMode,
pub pit: GameMode,
pub team_deathmatch: GameMode,
}
impl std::convert::Into<crate::data::game_mode::GameModeConfigs> for GameModes {
fn into(self) -> crate::data::game_mode::GameModeConfigs {
crate::data::game_mode::GameModeConfigs {
battle_arena: self.battle_arena.into(),
elimination: self.elimination.into(),
the_pit: self.pit.into(),
team_deathmatch: self.team_deathmatch.into(),
}
}
}
fn default_game_modes() -> GameModes {
GameModes {
battle_arena: GameMode {
respawn_heal_duration: 10.0,
respawn_full_heal_duration: 10.0,
kill_limit: 0,
game_time_m: 20,
},
elimination: GameMode {
respawn_heal_duration: 10.0,
respawn_full_heal_duration: 10.0,
kill_limit: 10,
game_time_m: 10,
},
pit: GameMode {
respawn_heal_duration: 20.0,
respawn_full_heal_duration: 20.0,
kill_limit: 15,
game_time_m: 15,
},
team_deathmatch: GameMode {
respawn_heal_duration: 10.0,
respawn_full_heal_duration: 10.0,
kill_limit: 10,
game_time_m: 10,
},
}
}

View File

@@ -177,4 +177,9 @@ impl <C: Clone> super::ConfigProvider<C> for CubeConfig {
items: vote_data,
})
}
fn game_mode_config(&self) -> Typed<C> {
let game_mode_data: crate::data::game_mode::GameModeConfigs = self.battle.games.into();
game_mode_data.as_transmissible()
}
}

View File

@@ -9,4 +9,5 @@ pub trait ConfigProvider<C> {
fn ids(&self) -> Vec<u32>;
fn regen_config(&self) -> Typed<C>;
fn after_battle_vote_config(&self) -> Typed<C>;
fn game_mode_config(&self) -> Typed<C>;
}

View File

@@ -150,6 +150,9 @@ impl <C: Clone> super::User<C> for UserData {
control_type: polariton::operation::Typed::Int(control_ty as _),
control_options: control_options.as_transmissible(),
mastery_level: polariton::operation::Typed::Int(0), // TODO
robot_rank: polariton::operation::Typed::Int(slot.total_robot_ranking as _),
cpu: polariton::operation::Typed::Int(slot.total_robot_cpu as _),
cosmetic_cpu: polariton::operation::Typed::Int(slot.total_cosmetic_cpu as _),
})
},
Err(e) => {

View File

@@ -38,6 +38,9 @@ pub struct UserSlotData<C> {
pub control_type: polariton::operation::Typed<C>,
pub control_options: polariton::operation::Typed<C>,
pub mastery_level: polariton::operation::Typed<C>,
pub robot_rank: polariton::operation::Typed<C>,
pub cpu: polariton::operation::Typed<C>,
pub cosmetic_cpu: polariton::operation::Typed<C>,
}
pub struct VehicleData {