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

Implement basic battle arena functionality #32

This commit is contained in:
NG (Graham)
2025-08-30 21:36:01 -04:00
parent 1403a6e3d0
commit 7f9ed8bf35
41 changed files with 1836 additions and 327 deletions

View File

@@ -10,6 +10,7 @@ authors.workspace = true
[dependencies]
log.workspace = true
polariton.workspace = true
base64 = "0.22"
hex = "0.4"
serde.workspace = true
serde_json.workspace = true

View File

@@ -0,0 +1,92 @@
pub struct CubeLocationsParser;
pub struct CubeLocationInfo {
pub x: u8,
pub y: u8,
pub z: u8,
extras: u8,
}
impl CubeLocationInfo {
pub fn orientation(&self) -> u8 {
self.extras & 0x7F
}
pub fn is_destroyed(&self) -> bool {
self.extras & 0x80 != 0
}
}
impl CubeLocationsParser {
pub fn with_cubes<'a, I: std::iter::Iterator<Item=&'a crate::persist::Cube>>(_iter: I) -> Self {
Self
}
pub fn locations_of(&self, r: &mut dyn std::io::Read, cube_id: u32) -> Vec<CubeLocationInfo> {
match super::parser::Cube::parse_list(r) {
Ok(cubes) => {
cubes.into_iter()
.filter(|x| x.id == cube_id)
.map(|cube| CubeLocationInfo {
x: cube.x,
y: cube.y,
z: cube.z,
extras: cube.orientation,
})
.collect()
}
Err(e) => {
log::error!("Failed to parse cube data to find cube locations: {}", e);
Vec::default()
}
}
}
pub fn locations_of_by_distance_to_first(&self, r: &mut dyn std::io::Read, locations_of_id: u32, distance_to_id: u32) -> Vec<CubeLocationInfo> {
match super::parser::Cube::parse_list(r) {
Ok(cubes) => {
if let Some(target) = cubes.iter().filter(|x| x.id == distance_to_id).next() {
let target_x = target.x as f32;
let target_y = target.y as f32;
let target_z = target.z as f32;
let mut relevant_cubes: Vec<(f32, CubeLocationInfo)> = cubes.into_iter()
.filter(|x| x.id == locations_of_id)
.map(|cube| {
let distance = (
(cube.x as f32 - target_x).powi(2)
+ (cube.y as f32 - target_y).powi(2)
+ (cube.z as f32 - target_z).powi(2)
).sqrt();
(distance, CubeLocationInfo {
x: cube.x,
y: cube.y,
z: cube.z,
extras: cube.orientation,
})
})
.collect();
relevant_cubes.sort_by_key(|(distance, _)| (distance * 1_000_000.0) as i64);
relevant_cubes.into_iter()
.map(|(_, cube)| cube)
.collect()
} else {
log::warn!("No cube with id {} to calculate distance", distance_to_id);
cubes.into_iter()
.filter(|x| x.id == locations_of_id)
.map(|cube| CubeLocationInfo {
x: cube.x,
y: cube.y,
z: cube.z,
extras: cube.orientation,
})
.collect()
}
}
Err(e) => {
log::error!("Failed to parse cube data to find cube locations: {}", e);
Vec::default()
}
}
}
}

View File

@@ -6,9 +6,15 @@ pub use weapon_list::WeaponListParser;
mod cpu_count;
pub use cpu_count::CpuListParser;
mod locations_of;
pub use locations_of::{CubeLocationsParser, CubeLocationInfo};
//pub mod prefabs;
pub struct CubeParsers {
weapon_list: std::sync::Arc<WeaponListParser>,
cpu_counter: std::sync::Arc<CpuListParser>,
locations: std::sync::Arc<CubeLocationsParser>,
}
impl CubeParsers {
@@ -17,6 +23,7 @@ impl CubeParsers {
Self {
weapon_list: std::sync::Arc::new(WeaponListParser::with_cubes(cubes.values())),
cpu_counter: std::sync::Arc::new(CpuListParser::with_cubes(cubes.values())),
locations: std::sync::Arc::new(CubeLocationsParser::with_cubes(cubes.values())),
}
}
@@ -27,4 +34,8 @@ impl CubeParsers {
pub fn cpu_counter(&self) -> std::sync::Arc<CpuListParser> {
self.cpu_counter.clone()
}
pub fn locations_of(&self) -> std::sync::Arc<CubeLocationsParser> {
self.locations.clone()
}
}

View File

@@ -1,5 +1,7 @@
#![allow(dead_code)]
use std::io::Write;
pub struct Cube {
pub id: u32,
pub x: u8,
@@ -35,6 +37,22 @@ impl Cube {
}
Ok(cubes)
}
pub fn dump(&self, w: &mut dyn std::io::Write) -> std::io::Result<usize> {
w.write_all(&self.id.to_le_bytes())?;
w.write_all(&[self.x, self.y, self.z, self.orientation])?;
Ok(8)
}
pub fn dump_list(items: Vec<Self>) -> std::io::Result<Vec<u8>> {
let mut buf = Vec::with_capacity(4 + (items.len() * 8));
let mut dumped = std::io::Cursor::new(&mut buf);
dumped.write_all(&(items.len() as u32).to_le_bytes())?;
for item in items {
item.dump(&mut dumped)?;
}
Ok(buf)
}
}
pub struct Colour {

View File

@@ -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<u64>,
pub base_machine_map: Vec<u8>, // aka team base model, converted into base64
pub equalizer_model: Vec<u8>, // converted into base64
pub equalizer_health: i64,
pub equalizer_trigger_time_seconds: Vec<u64>,
pub equalizer_warning_seconds: i64,
pub equalizer_duration_seconds: Vec<u64>,
pub capture_time_seconds_per_player: Vec<i64>,
pub num_segments: i32,
pub heal_escalation_time_seconds: i64,
}
fn to_obj_arr_u<C>(slice: &[u64]) -> Typed<C> {
Typed::<C>::ObjArr(slice.iter().map(|x| Typed::<C>::Long(*x as i64)).collect::<Vec<Typed<C>>>().into())
}
fn to_obj_arr_i<C>(slice: &[i64]) -> Typed<C> {
Typed::<C>::ObjArr(slice.iter().map(|x| Typed::<C>::Long(*x)).collect::<Vec<Typed<C>>>().into())
}
impl BattleArenaData {
pub fn as_transmissible<C>(&self) -> Typed<C> {
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())
}
}

View File

@@ -15,6 +15,7 @@ pub mod channel;
pub mod sanction;
pub mod robot_data;
pub mod lobby;
pub mod battle_arena_config;
pub mod error_codes;

View File

@@ -248,7 +248,7 @@ fn default_game_modes() -> GameModes {
respawn_heal_duration: 10.0,
respawn_full_heal_duration: 0.5,
kill_limit: 0,
game_time_m: 20,
game_time_m: 5,
},
elimination: GameMode {
respawn_heal_duration: 10.0,
@@ -369,6 +369,21 @@ fn default_rotation() -> GameEventSequence {
strategy: GameRotationStrategy::Sequence,
modes: vec![
GameEvents {
singleplayer: GameEvent {
map: GameMap::Earth1,
visibility: GameVisibility::Good,
mode: GameType::BattleArena,
auto_heal: true,
},
multiplayer: GameEvent {
map: GameMap::Earth1,
visibility: GameVisibility::Good,
mode: GameType::BattleArena,
auto_heal: true,
},
duration_s: 5*60, // 5 minutes
}
/*GameEvents {
singleplayer: GameEvent {
map: GameMap::Neptune1,
visibility: GameVisibility::Good,
@@ -487,7 +502,7 @@ fn default_rotation() -> GameEventSequence {
auto_heal: true,
},
duration_s: 5*60,
},
},*/
]
}
}
@@ -498,6 +513,7 @@ fn default_multiplayer() -> super::MultiplayerConfig {
enabled: true,
network: super::multiplayer::default_net_conf(),
fakes: super::multiplayer::default_fake_users(),
battle_arena: super::multiplayer::default_ba_conf(),
}
}

View File

@@ -410,9 +410,18 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
z: base.z,
},
}, base.percent_per_second))).collect();
let capture_points = conf.capture_points.iter().map(|point| (super::Sphere {
radius: point.radius,
center: super::Point {
x: point.x,
y: point.y,
z: point.z,
}
}, point.percent_per_second)).collect();
let map_conf = super::MapConfig {
spawns,
bases,
capture_points,
};
(map.into_conf(), map_conf)
}).collect()
@@ -441,4 +450,10 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
total: self.battle.energy.total,
}
}
fn ba_settings(&self) -> super::BattleArenaResolver {
super::BattleArenaResolver {
data: self.battle.multiplayer.battle_arena.clone(),
}
}
}

View File

@@ -2,7 +2,7 @@ mod cubes_json;
pub use cubes_json::CubeConfig;
mod traits;
pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode, Point, Sphere, MapConfig, LinksConfig, FakePlayer, ClientEmulator, EnergyConfig};
pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode, Point, Sphere, MapConfig, LinksConfig, FakePlayer, ClientEmulator, EnergyConfig, BattleArenaResolver};
pub type ConfigImpl = CubeConfig;

View File

@@ -35,6 +35,7 @@ pub trait ConfigProvider<C: Clone> {
fn url_links(&self) -> LinksConfig;
fn fake_players(&self) -> Vec<FakePlayer>;
fn energy(&self) -> EnergyConfig;
fn ba_settings(&self) -> BattleArenaResolver;
}
pub struct CompleteCampaignProvider {
@@ -358,7 +359,8 @@ pub struct Sphere {
#[derive(Clone, Debug)]
pub struct MapConfig {
pub spawns: std::collections::HashMap<u8, Vec<Point>>, // team -> points
pub bases: std::collections::HashMap<u8, (Sphere, f32)>, // team -> base
pub bases: std::collections::HashMap<u8, (Sphere, f32)>, // team -> (base, capture speed)
pub capture_points: Vec<(Sphere, f32)>, // (capture point, capture speed)
}
#[derive(Clone, Debug)]
@@ -386,3 +388,38 @@ pub struct EnergyConfig {
pub refill_rate: f32,
pub total: u32,
}
pub struct BattleArenaResolver {
pub(super) data: crate::persist::multiplayer::BattleArenaConfig,
}
impl BattleArenaResolver {
pub async fn resolve(&self, user: &dyn crate::persist::user::CommonUser, factory: &crate::factory::Factory, weapon_list: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result<crate::data::battle_arena_config::BattleArenaData, polariton_server::operations::SimpleOpError> {
let equalizer_data = user.resolve_config_vehicle(&self.data.equalizer.clone().into_conf(), factory, weapon_list, cpu_counter).await?;
let base_data = user.resolve_config_vehicle(&self.data.base.clone().into_conf(), factory, weapon_list, cpu_counter).await?;
Ok(crate::data::battle_arena_config::BattleArenaData {
protonium_health: self.data.crystal_health as i64,
respawn_time_seconds: self.data.respawn_time_s as i64,
heal_over_time_per_tower: vec![10, 10, 10, 10], // Unused?
base_machine_map: base_data.robot_map,
equalizer_model: equalizer_data.robot_map,
equalizer_health: self.data.equalizer_health as i64,
equalizer_trigger_time_seconds: vec![10, 10, 10, 10, 10], // TODO
equalizer_warning_seconds: self.data.equalizer_warning_s as i64, // TODO
equalizer_duration_seconds: vec![20, 20, 20, 20, 20], // TODO
capture_time_seconds_per_player: vec![30, 20, 10, 5, 1], // TODO
num_segments: self.data.num_segments as i32,
heal_escalation_time_seconds: 5, // Unused?
})
}
pub async fn resolve_typed<C>(&self, user: &dyn crate::persist::user::CommonUser, factory: &crate::factory::Factory, weapon_list: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result<Typed<C>, polariton_server::operations::SimpleOpError> {
Ok(Typed::Dict(polariton::operation::Dict {
key_ty: polariton::serdes::TypePrefix::Str, // str
val_ty: polariton::serdes::TypePrefix::HashMap, // hashmap
items: vec![
(Typed::Str("BattleArenaSettings".into()), self.resolve(user, factory, weapon_list, cpu_counter).await?.as_transmissible())
],
}))
}
}

View File

@@ -185,6 +185,16 @@ pub struct PrefabVehicle {
pub id: PrefabId,
}
impl PrefabVehicle {
pub(super) fn into_conf(&self) -> super::config::VehicleInfo {
crate::persist::config::VehicleInfo {
name: self.name.clone(),
username: self.username.clone(),
id: self.id.clone().into(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum PrefabId {

View File

@@ -10,6 +10,7 @@ pub struct MapsConfig {
pub struct MapConfig {
pub spawn_points: Vec<SpawnPoint>,
pub bases: Vec<CaptureBase>,
pub capture_points: Vec<CapturePoint>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
@@ -35,13 +36,12 @@ impl SpawnPoint {
self
}
fn rotated_from(mut self, x: f32, y: f32, z: f32, rot: num_quaternion::Quaternion<f32>) -> Self {
if let Some(unit_rot) = rot.normalize() {
let rotated = unit_rot.rotate_vector([self.x, self.y, self.z]);
self.x = rotated[0] + x;
self.y = rotated[1] + y;
self.z = rotated[2] + z;
}
fn rotated(mut self, rot: num_quaternion::Quaternion<f32>) -> Self {
let unit_rot = rot.normalize().expect("Bad rotation quaternion for SpawnPoint");
let rotated = unit_rot.rotate_vector([self.x, self.y, self.z]);
self.x = rotated[0];
self.y = rotated[1];
self.z = rotated[2];
self
}
}
@@ -65,8 +65,37 @@ impl CaptureBase {
}
}
const DEFAULT_PERCENT_PER_SECOND: f32 = 2.5;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct CapturePoint {
pub x: f32,
pub y: f32,
pub z: f32,
pub radius: f32,
pub percent_per_second: f32,
}
impl CapturePoint {
const fn offset(mut self, x: f32, y: f32, z: f32) -> Self {
self.x += x;
self.y += y;
self.z += z;
self
}
fn rotated(mut self, rot: num_quaternion::Quaternion<f32>) -> Self {
let unit_rot = rot.normalize().expect("Bad rotation quaternion for CapturePoint");
let rotated = unit_rot.rotate_vector([self.x, self.y, self.z]);
self.x = rotated[0];
self.y = rotated[1];
self.z = rotated[2];
self
}
}
const DEFAULT_BASE_PERCENT_PER_SECOND: f32 = 2.5;
const DEFAULT_BASE_RADIUS: f32 = 20.0;
const DEFAULT_CAPTURE_PERCENT_PER_SECOND: f32 = DEFAULT_BASE_PERCENT_PER_SECOND * 1.5;
const DEFAULT_CAPTURE_RADIUS: f32 = 14.0;
pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap, MapConfig> {
let mut map = std::collections::HashMap::with_capacity(9);
@@ -204,7 +233,7 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 2.700,
z: -240.888,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
CaptureBase {
team: 1,
@@ -212,9 +241,37 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 2.770,
z: 243.600,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
],
capture_points: vec![
CapturePoint {
x: 177.240,
y: -0.264,
z: 141.540,
radius: DEFAULT_CAPTURE_RADIUS,
percent_per_second: DEFAULT_CAPTURE_PERCENT_PER_SECOND,
},
CapturePoint {
x: -9.480,
y: 30.396,
z: -164.676,
radius: DEFAULT_CAPTURE_RADIUS,
percent_per_second: DEFAULT_CAPTURE_PERCENT_PER_SECOND,
},
CapturePoint {
x: -197.400,
y: -0.228,
z: 142.452,
radius: DEFAULT_CAPTURE_RADIUS,
percent_per_second: DEFAULT_CAPTURE_PERCENT_PER_SECOND,
}
].into_iter().map(|p| p.rotated(num_quaternion::Quaternion {
w: 0.707107,
x: 0.0,
y: 0.707107,
z: 0.0,
}).offset(13.320, 0.0, -9.84)).collect(),
});
map.insert(super::combat::GameMap::Earth2, MapConfig { // level4
spawn_points: vec![
@@ -400,12 +457,12 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: -71.445,
z: 266.000,
},
].into_iter().map(|x| x.rotated_from(0.0, 86.718, 0.0, num_quaternion::Quaternion {
].into_iter().map(|x| x.rotated(num_quaternion::Quaternion {
x: 0.0,
y: 0.707107,
z: 0.0,
w: 0.707107,
})).collect(),
}).offset(0.0, 86.718, 0.0)).collect(),
bases: vec![
CaptureBase {
team: 0,
@@ -413,7 +470,7 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 13.900,
z: -253.920,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
CaptureBase {
team: 1,
@@ -421,9 +478,10 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 13.900,
z: 259.200,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
],
capture_points: vec![], // TODO
});
map.insert(super::combat::GameMap::Mars1, MapConfig {
spawn_points: vec![
@@ -557,7 +615,7 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 4.09,
z: 20.3,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
CaptureBase {
team: 1,
@@ -565,9 +623,10 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 10.63,
z: 372.20,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
],
capture_points: vec![], // TODO
});
map.insert(super::combat::GameMap::Mars2, MapConfig {
spawn_points: vec![
@@ -701,7 +760,7 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 21.140,
z: 187.560,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
CaptureBase {
team: 1,
@@ -709,9 +768,10 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 21.230,
z: 620.280,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
].into_iter().map(|x| x.offset(-434.640, 0.0, -414.720)).collect(),
capture_points: vec![], // TODO
});
map.insert(super::combat::GameMap::Mars3, MapConfig {
spawn_points: vec![
@@ -845,7 +905,7 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 32.340,
z: -309.720,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
CaptureBase {
team: 1,
@@ -853,9 +913,10 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 32.320,
z: 207.360,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
].into_iter().map(|x| x.offset(49.608, 0.0, 52.493)).collect(),
capture_points: vec![], // TODO
});
map.insert(super::combat::GameMap::Neptune1, MapConfig {
spawn_points: vec![
@@ -989,7 +1050,7 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 1.580,
z: -82.150,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
CaptureBase {
team: 1,
@@ -997,9 +1058,10 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: -0.110,
z: 292.956,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
].into_iter().map(|x| x.offset(405.542, 0.0, 10.668)).collect(),
capture_points: vec![], // TODO
});
map.insert(super::combat::GameMap::Neptune2, MapConfig {
spawn_points: vec![
@@ -1133,7 +1195,7 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 18.370,
z: -181.488,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
CaptureBase {
team: 1,
@@ -1141,9 +1203,10 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 18.390,
z: 196.416,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
],
capture_points: vec![], // TODO
});
map.insert(super::combat::GameMap::Neptune3, MapConfig {
spawn_points: vec![
@@ -1277,7 +1340,7 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 42.270,
z: -151.440,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
CaptureBase {
team: 1,
@@ -1285,9 +1348,10 @@ pub(super) fn default_map() -> std::collections::HashMap<super::combat::GameMap,
y: 42.290,
z: 147.240,
radius: DEFAULT_BASE_RADIUS,
percent_per_second: DEFAULT_PERCENT_PER_SECOND,
percent_per_second: DEFAULT_BASE_PERCENT_PER_SECOND,
},
],
capture_points: vec![], // TODO
});
map
}

File diff suppressed because one or more lines are too long

View File

@@ -22,11 +22,7 @@ impl SingleplayerConfig {
crate::persist::config::SingleplayerConfig {
max_teammates: self.max_teammates,
max_enemies: self.max_enemies,
vehicles: self.vehicles.iter().map(|v| crate::persist::config::VehicleInfo {
name: v.name.clone(),
username: v.username.clone(),
id: v.id.clone().into(),
}).collect()
vehicles: self.vehicles.iter().map(|v| v.into_conf()).collect()
}
}
}

View File

@@ -417,6 +417,130 @@ impl UserData {
})
}
pub(super) async fn resolve_vehicle(&self, vehicle: &crate::persist::config::VehicleInfo, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result<super::ResolvedVehicle, polariton_server::operations::SimpleOpError> {
match &vehicle.id {
crate::persist::config::VehicleDescriptor::Factory { factory: factory_id } => {
match factory.vehicle(*factory_id).await {
Ok(Some(factory_vehicle)) => {
let uuid_i64 = crate::persist::user::uuid_sanitize(crate::persist::user::i64_join((1 << 30, *factory_id)));
let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64);
let weapons_guess = weapon_order.guess_weapons(&mut std::io::Cursor::new(&factory_vehicle.0.cube_data));
let weapons_guess = vec![weapons_guess[0], 0, 0];
let weapon_ranks = weapons_guess.iter().map(|&x| (x, if x == 0 { 0 } else { 1 })).collect();
let cpu_count = if factory_vehicle.1.cpu == 0 {
cpu_counter.calculate_cpu(&mut std::io::Cursor::new(&factory_vehicle.0.cube_data)).total as i32
} else {
factory_vehicle.1.cpu as i32
};
Ok(super::ResolvedVehicle {
mastery: 1,
tier: 1, // FIXME
robot_name: vehicle.name.clone().unwrap_or_else(|| factory_vehicle.1.name.clone()),
robot_map: factory_vehicle.0.cube_data,
robot_uuid: uuid_str,
cpu: cpu_count,
weapon_order: weapons_guess,
colour_map: factory_vehicle.0.colour_data,
spawn_effect: "Spawn".to_owned(),
death_effect: "Explosion".to_owned(),
weapon_rank: weapon_ranks,
})
},
Ok(None) => {
log::error!("Prefab vehicle {} does not exist in factory", factory_id);
return Err(polariton_server::operations::SimpleOpError::with_message(
crate::data::error_codes::SingleplayerErrorCode::UnexpectedError as i16,
format!("Prefab vehicle {} does not exist in factory", factory_id),
));
},
Err(e) => {
log::error!("Failed to retrieve prefab vehicle {} from factory: {}", factory_id, e);
return Err(polariton_server::operations::SimpleOpError::with_message(
crate::data::error_codes::SingleplayerErrorCode::DatabaseError as i16,
format!("Failed to retrieve prefab vehicle {} from factory: {}", factory_id, e),
));
}
}
},
crate::persist::config::VehicleDescriptor::Database { garage } => {
match self.db.garage_by_id(*garage).await {
Ok(Some(db_vehicle)) => {
let cpu_count = if db_vehicle.total_robot_cpu <= 0 {
cpu_counter.calculate_cpu(&mut std::io::Cursor::new(&db_vehicle.robot_data)).total as i32
} else {
db_vehicle.total_robot_cpu
};
let uuid_i64 = crate::persist::user::uuid_sanitize(crate::persist::user::i64_join((1 << 31, *garage as u32)));
let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64);
Ok(super::ResolvedVehicle {
mastery: 1,
tier: 1, // FIXME
robot_name: vehicle.name.clone().unwrap_or_else(|| db_vehicle.name.clone()),
robot_map: db_vehicle.robot_data,
robot_uuid: uuid_str,
cpu: cpu_count,
weapon_order: oj_rc_database::schema::parse_int_csv(&db_vehicle.weapon_order).into_iter().map(|x| x as i32).collect::<Vec<_>>(),
colour_map: db_vehicle.colour_data,
spawn_effect: db_vehicle.spawn_animation_id,
death_effect: db_vehicle.death_animation_id,
weapon_rank: oj_rc_database::schema::parse_int_csv(&db_vehicle.weapon_order).into_iter().map(|x| (x as i32, if x == 0 { 0 } else { 1 })).collect(),
})
},
Ok(None) => {
log::error!("Prefab vehicle {} does not exist in main garage database", garage);
return Err(polariton_server::operations::SimpleOpError::with_message(
crate::data::error_codes::SingleplayerErrorCode::UnexpectedError as i16,
format!("Prefab vehicle {} does not exist in main garage database", garage),
));
}
Err(e) => {
log::error!("Failed to retrieve prefab vehicle {} from main garage database: {}", garage, e);
return Err(polariton_server::operations::SimpleOpError::with_message(
crate::data::error_codes::SingleplayerErrorCode::DatabaseError as i16,
format!("Failed to retrieve prefab vehicle {} from main garage database: {}", garage, e),
));
}
}
},
crate::persist::config::VehicleDescriptor::Raw {
cube_data,
colour_data,
} => {
use sha2::Digest;
let sha_bytes = sha2::Sha256::digest(&cube_data);
let u32_bytes = [
sha_bytes[0],
sha_bytes[1],
sha_bytes[2],
sha_bytes[3],
];
let uuid_i64 = crate::persist::user::uuid_sanitize(crate::persist::user::i64_join((1 << 29, u32::from_le_bytes(u32_bytes))));
let uuid_str = crate::persist::user::i64_as_uuid_str(uuid_i64);
let weapons_guess = weapon_order.guess_weapons(&mut std::io::Cursor::new(&cube_data));
let weapons_guess = vec![
weapons_guess.get(0).map(|x| *x).unwrap_or(0),
weapons_guess.get(1).map(|x| *x).unwrap_or(0),
weapons_guess.get(2).map(|x| *x).unwrap_or(0),
];
let weapon_ranks = weapons_guess.iter().map(|&x| (x, if x == 0 { 0 } else { 1 })).collect();
let cpu_counts = cpu_counter.calculate_cpu(&mut std::io::Cursor::new(&cube_data));
Ok(super::ResolvedVehicle {
mastery: 1,
tier: 1, // FIXME
robot_name: vehicle.name.clone().unwrap_or_else(|| "Raw Robot".to_owned()),
robot_map: cube_data.to_owned(),
robot_uuid: uuid_str,
cpu: cpu_counts.total as i32,
weapon_order: weapons_guess,
colour_map: colour_data.to_owned(),
spawn_effect: "Spawn".to_owned(),
death_effect: "Explosion".to_owned(),
weapon_rank: weapon_ranks,
})
}
}
}
async fn resolve_some_singleplayer_vehicles(&self, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, singleplayer_config: &crate::persist::config::SingleplayerConfig, cpu_counter: &crate::cubes::CpuListParser) -> Result<Vec<crate::data::player_data::PlayerData>, i16> {
use rand::seq::IndexedRandom;
let mut players = Vec::with_capacity((singleplayer_config.max_enemies + singleplayer_config.max_teammates + 1) as usize);
@@ -439,121 +563,29 @@ impl UserData {
};
seen_usernames.insert(username.clone());
let team_num = if i < singleplayer_config.max_enemies { 1 } else { 0 };
let enemy = match &vehicle.id {
crate::persist::config::VehicleDescriptor::Factory { factory: factory_id } => {
//use oj_rc_factory::VehicleFactoryAdapter;
match factory.vehicle(*factory_id).await {
Ok(Some(factory_vehicle)) => {
let weapons_guess = weapon_order.guess_weapons(&mut std::io::Cursor::new(&factory_vehicle.0.cube_data));
let weapons_guess = vec![weapons_guess[0], 0, 0];
let weapon_ranks = weapons_guess.iter().map(|&x| (x, if x == 0 { 0 } else { 1 })).collect();
let cpu_count = if factory_vehicle.1.cpu == 0 {
cpu_counter.calculate_cpu(&mut std::io::Cursor::new(&factory_vehicle.0.cube_data)).total as i32
} else {
factory_vehicle.1.cpu as i32
};
crate::data::player_data::PlayerData {
name: username.clone(),
display_name: username.clone(),
mastery: 1,
tier: 1, // FIXME
robot_name: vehicle.name.clone().unwrap_or_else(|| factory_vehicle.1.name.clone()),
robot_map: factory_vehicle.0.cube_data,
group: None,
team: team_num,
has_premium: false,
robot_uuid: uuid_str,
cpu: cpu_count,
avatar_id: None, // not serialised
weapon_order: weapons_guess,
colour_map: factory_vehicle.0.colour_data,
is_ai: true,
spawn_effect: "Spawn".to_owned(),
death_effect: "Explosion".to_owned(),
player_rank: 1,
weapon_rank: weapon_ranks,
}
},
Ok(None) => {
log::error!("Prefab vehicle {} does not exist in factory", factory_id);
return Err(crate::data::error_codes::SingleplayerErrorCode::UnexpectedError as i16);
},
Err(e) => {
log::error!("Failed to retrieve prefab vehicle {} from factory: {}", factory_id, e);
return Err(crate::data::error_codes::SingleplayerErrorCode::DatabaseError as i16);
}
}
},
crate::persist::config::VehicleDescriptor::Database { garage } => {
match self.db.garage_by_id(*garage).await {
Ok(Some(db_vehicle)) => {
let cpu_count = if db_vehicle.total_robot_cpu <= 0 {
cpu_counter.calculate_cpu(&mut std::io::Cursor::new(&db_vehicle.robot_data)).total as i32
} else {
db_vehicle.total_robot_cpu
};
crate::data::player_data::PlayerData {
name: username.clone(),
display_name: username.clone(),
mastery: 1,
tier: 1, // FIXME
robot_name: vehicle.name.clone().unwrap_or_else(|| db_vehicle.name.clone()),
robot_map: db_vehicle.robot_data,
group: None,
team: team_num,
has_premium: false,
robot_uuid: uuid_str,
cpu: cpu_count,
avatar_id: None, // not serialised
weapon_order: oj_rc_database::schema::parse_int_csv(&db_vehicle.weapon_order).into_iter().map(|x| x as i32).collect::<Vec<_>>(),
colour_map: db_vehicle.colour_data,
is_ai: true,
spawn_effect: "Spawn".to_owned(),
death_effect: "Explosion".to_owned(),
player_rank: 1,
weapon_rank: oj_rc_database::schema::parse_int_csv(&db_vehicle.weapon_order).into_iter().map(|x| (x as i32, if x == 0 { 0 } else { 1 })).collect(),
}
},
Ok(None) => {
log::error!("Prefab vehicle {} does not exist in main garage database", garage);
return Err(crate::data::error_codes::SingleplayerErrorCode::UnexpectedError as i16);
}
Err(e) => {
log::error!("Failed to retrieve prefab vehicle {} from main garage database: {}", garage, e);
return Err(crate::data::error_codes::SingleplayerErrorCode::DatabaseError as i16);
}
}
},
crate::persist::config::VehicleDescriptor::Raw {
cube_data,
colour_data,
} => {
let weapons_guess = weapon_order.guess_weapons(&mut std::io::Cursor::new(&cube_data));
let weapons_guess = vec![weapons_guess[0], 0, 0];
let weapon_ranks = weapons_guess.iter().map(|&x| (x, if x == 0 { 0 } else { 1 })).collect();
let cpu_counts = cpu_counter.calculate_cpu(&mut std::io::Cursor::new(&cube_data));
crate::data::player_data::PlayerData {
name: username.clone(),
display_name: username.clone(),
mastery: 1,
tier: 1, // FIXME
robot_name: vehicle.name.clone().unwrap_or_else(|| "Raw Robot".to_owned()),
robot_map: cube_data.to_owned(),
group: None,
team: team_num,
has_premium: false,
robot_uuid: uuid_str,
cpu: cpu_counts.total as i32,
avatar_id: None, // not serialised
weapon_order: weapons_guess,
colour_map: colour_data.to_owned(),
is_ai: true,
spawn_effect: "Spawn".to_owned(),
death_effect: "Explosion".to_owned(),
player_rank: 1,
weapon_rank: weapon_ranks,
}
}
let enemy_vehicle = self.resolve_vehicle(vehicle, factory, weapon_order, cpu_counter).await?;
let weapons = vec![enemy_vehicle.weapon_order[0], 0, 0];
let weapon_ranks = weapons.iter().map(|&x| (x, if x == 0 { 0 } else { 1 })).collect();
let enemy = crate::data::player_data::PlayerData {
name: username.clone(),
display_name: username.clone(),
mastery: enemy_vehicle.mastery,
tier: enemy_vehicle.tier,
robot_name: enemy_vehicle.robot_name,
robot_map: enemy_vehicle.robot_map,
group: None,
team: team_num,
has_premium: false,
robot_uuid: uuid_str,
cpu: enemy_vehicle.cpu,
avatar_id: None, // not serialised
weapon_order: weapons,
colour_map: enemy_vehicle.colour_map,
is_ai: true,
spawn_effect: enemy_vehicle.spawn_effect,
death_effect: enemy_vehicle.death_effect,
player_rank: 1,
weapon_rank: weapon_ranks,
};
next_id += 1;
players.push(enemy);

View File

@@ -0,0 +1,8 @@
use super::account_json::UserData;
#[async_trait::async_trait]
impl super::CommonUser for UserData {
async fn resolve_config_vehicle(&self, vehicle: &crate::persist::config::VehicleInfo, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result<super::ResolvedVehicle, polariton_server::operations::SimpleOpError> {
self.resolve_vehicle(vehicle, factory, weapon_order, cpu_counter).await
}
}

View File

@@ -11,13 +11,14 @@ mod inventory;
pub use inventory::UnlockedParts;
mod traits;
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers};
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser};
mod intercom;
pub use intercom::generate_token as generate_intercom_token;
mod multiplayer;
mod lobby;
mod common;
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";

View File

@@ -61,7 +61,7 @@ pub trait UserAuthenticator {
}
#[async_trait::async_trait]
pub trait User<C>: ChatUser + LobbyUser + MultiplayerUser + IntercomUser {
pub trait User<C>: ChatUser + LobbyUser + MultiplayerUser + IntercomUser + CommonUser {
fn public_id(&self) -> &'_ str;
fn is_mod(&self) -> bool;
fn is_admin(&self) -> bool;
@@ -203,7 +203,7 @@ pub struct AvatarInfo {
}
#[async_trait::async_trait]
pub trait ChatUser {
pub trait ChatUser: CommonUser {
async fn subscribed_channels(&self) -> Result<polariton::operation::Typed<()>, i16>;
async fn subscribed_channels_strings(&self) -> Result<Vec<String>, i16>;
async fn add_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Result<polariton::operation::Typed<()>, i16>;
@@ -325,7 +325,7 @@ pub enum MultiplayerErrorCode {
}
#[async_trait::async_trait]
pub trait MultiplayerUser {
pub trait MultiplayerUser: CommonUser {
fn user_id(&self) -> i32;
fn user_name(&self) -> &'_ str;
fn display_name(&self) -> &'_ str;
@@ -339,3 +339,22 @@ pub trait MultiplayerUser {
pub trait IntercomUser {
async fn save_custom_avatar(&self, image: Vec<u8>) -> Result<(), polariton_server::operations::SimpleOpError>;
}
pub struct ResolvedVehicle {
pub mastery: i32,
pub tier: i32,
pub robot_name: String,
pub robot_map: Vec<u8>,
pub robot_uuid: String,
pub cpu: i32,
pub weapon_order: Vec<i32>,
pub colour_map: Vec<u8>,
pub spawn_effect: String,
pub death_effect: String,
pub weapon_rank: std::collections::HashMap<i32, i32>,
}
#[async_trait::async_trait]
pub trait CommonUser: Send + Sync {
async fn resolve_config_vehicle(&self, vehicle: &crate::persist::config::VehicleInfo, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result<ResolvedVehicle, polariton_server::operations::SimpleOpError>;
}