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

Use resolvable vehicles in campaign mode instead of raw cube data

This commit is contained in:
NG (Graham)
2025-12-02 17:08:42 -05:00
parent b58af3d893
commit 687f8dabbe
10 changed files with 284 additions and 159 deletions

View File

@@ -384,13 +384,18 @@ pub(super) fn default_campaigns() -> super::SingleplayerConfig {
player_spawn_location: 0,
robots_in_wave: vec![
super::WaveRobot {
name: "strCampaignAnimalName".to_owned(),
vehicle: super::PrefabVehicle {
name: Some("strCampaignAnimalName".to_owned()),
username: "[ignored]".to_owned(),
id: super::PrefabId::Raw {
cube_data: super::VALID_ROBOT.into(),
colour_data: super::VALID_COLOUR.into(),
},
},
weapon: "strT5PlasmaGoldenName".to_owned(),
movement: "strT5SteeringWheelGoldenName".to_owned(),
rank: "strT0".to_owned(),
count: 5,
robot_data: super::VALID_ROBOT.into(),
colour_data: super::VALID_COLOUR.into(),
time_to_spawn: 1,
kills_to_spawn: 0,
time_to_despawn: 60,
@@ -442,22 +447,37 @@ fn default_rotation() -> GameEventSequence {
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: 30,
},
GameEvents {
singleplayer: GameEvent {
map: GameMap::Earth1,
visibility: GameVisibility::Good,
mode: GameType::BattleArena,
auto_heal: true,
},
multiplayer: GameEvent {
map: GameMap::Earth2,
visibility: GameVisibility::Good,
mode: GameType::BattleArena,
auto_heal: true,
},
duration_s: 30, // 30 seconds
},
/*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::Pit,
auto_heal: true,
},
duration_s: 5*60, // 5 minutes
}*/
GameEvents {
singleplayer: GameEvent {
map: GameMap::Neptune1,
visibility: GameVisibility::Good,
@@ -576,14 +596,14 @@ fn default_rotation() -> GameEventSequence {
auto_heal: true,
},
duration_s: 5*60,
},
},*/
]
}
}
fn default_multiplayer() -> super::MultiplayerConfig {
super::MultiplayerConfig {
players_per_game: 1,
players_per_game: 2,
enabled: true,
network: super::multiplayer::default_net_conf(),
fakes: super::multiplayer::default_fake_users(),

View File

@@ -0,0 +1,130 @@
use polariton::operation::Typed;
pub(super) struct CompleteCampaignData {
pub(super) difficulty_map: std::collections::HashMap<i32, crate::persist::CampaignDifficulty>,
pub(super) waves: Vec<crate::persist::Wave>,
}
pub struct CompleteCampaignProvider {
map: std::collections::HashMap<String, CompleteCampaignData>,
}
impl CompleteCampaignProvider {
pub(super) fn new(map: std::collections::HashMap<String, CompleteCampaignData>) -> Self {
Self { map }
}
pub async fn get<C>(
&self,
id: &str,
difficulty: &i32,
user: &dyn crate::persist::user::CommonUser,
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
weapon_order: &crate::cubes::WeaponListParser,
cpu_counter: &crate::cubes::CpuListParser,
) -> Result<Typed<C>, i16> {
if let Some(campaign) = self.map.get(id) {
if let Some(difficulty_conf) = campaign.difficulty_map.get(difficulty) {
let mut waves = Vec::with_capacity(campaign.waves.len());
for wave in campaign.waves.iter() {
let mut vehicles = Vec::with_capacity(wave.robots_in_wave.len());
for vehicle in wave.robots_in_wave.iter() {
let resolved = user.resolve_config_vehicle(&vehicle.vehicle.into_conf(), factory, weapon_order, cpu_counter).await?;
vehicles.push(crate::data::campaign::CompleteWaveRobotData {
name: resolved.robot_name,
robot_data: resolved.robot_map,
colour_data: resolved.colour_map,
time_to_spawn: vehicle.time_to_spawn,
kills_to_spawn: vehicle.kills_to_spawn,
time_to_despawn: vehicle.time_to_despawn,
kills_to_despawn: vehicle.kills_to_despawn,
initial_robot_amount: vehicle.initial_robot_amount,
periodic_robot_amount: vehicle.periodic_robot_amount,
spawn_interval: vehicle.spawn_interval,
min_robot_amount: vehicle.min_robot_amount,
max_robot_amount: vehicle.max_robot_amount,
is_boss: vehicle.is_boss,
is_kill_requirement: vehicle.is_kill_requirement,
});
}
waves.push(crate::data::campaign::CompleteWaveData {
player_spawn_location: wave.player_spawn_location,
robots_in_wave: vehicles,
kill_target: wave.kill_target,
time_min: wave.time_min,
time_max: wave.time_max,
});
}
let details_data = crate::data::campaign::CampaignWavesDifficultyData {
difficulty: difficulty_conf.clone().into(),
waves,
};
Ok(details_data.as_transmissible())
} else {
log::warn!("Couldn't find difficulty {} in campaign `{}`", difficulty, id);
Err(crate::data::error_codes::WebServicesError::DatabaseError as i16)
}
} else {
log::warn!("Couldn't find campaign {} (ignoring difficulty {})", id, difficulty);
Err(crate::data::error_codes::WebServicesError::DatabaseError as i16)
}
}
}
pub struct CampaignResolver {
pub(super) singleplayer: crate::persist::SingleplayerConfig,
}
impl CampaignResolver {
pub fn campaigns_parameters<C>(&self) -> Typed<C> {
self.singleplayer.clone().into_campaign_params().as_transmissible()
}
pub async fn campaign_waves<C>(
&self,
user: &dyn crate::persist::user::CommonUser,
factory: &dyn oj_rc_factory::VehicleFactoryAdapter,
weapon_order: &crate::cubes::WeaponListParser,
cpu_counter: &crate::cubes::CpuListParser,
) -> Result<Typed<C>, polariton_server::operations::SimpleOpError> {
let mut campaigns = Vec::with_capacity(self.singleplayer.campaigns.len());
for campaign in self.singleplayer.campaigns.iter() {
let mut waves = Vec::with_capacity(campaign.waves.len());
for wave in campaign.waves.iter() {
let mut vehicles = Vec::with_capacity(wave.robots_in_wave.len());
for robot in wave.robots_in_wave.iter() {
let resolved = user.resolve_config_vehicle(&robot.vehicle.into_conf(), factory, weapon_order, cpu_counter).await?;
vehicles.push(crate::data::campaign::WaveRobotData {
name: resolved.robot_name,
weapon: robot.weapon.clone(),
movement: robot.movement.clone(),
rank: robot.rank.clone(),
count: robot.count,
});
}
waves.push(crate::data::campaign::WaveData {
robots_in_wave: vehicles,
});
}
campaigns.push(crate::data::campaign::WavesData {
id: campaign.id.clone(),
waves,
campaign_type: campaign.campaign_type.into(),
});
}
Ok(crate::data::campaign::LiveCampaignWaves {
waves: campaigns,
}.as_transmissible())
}
pub fn campaign_version<C>(&self) -> Typed<C> {
let mut locked_map = std::collections::HashMap::with_capacity(self.singleplayer.campaigns.len());
for campaign in self.singleplayer.campaigns.iter() {
locked_map.insert(campaign.id.clone(), true);
}
crate::data::campaign::GameModeVersionParameters {
current_version: 0,
is_locked: locked_map,
}.as_transmissible()
}
}

View File

@@ -244,43 +244,27 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
game_mode_data.as_transmissible()
}
fn campaigns_parameters(&self) -> Typed<C> {
self.battle.singleplayer.clone().into_campaign_params().as_transmissible()
}
fn campaign_waves(&self) -> Typed<C> {
self.battle.singleplayer.clone().into_waves().as_transmissible()
}
fn campaign_version(&self) -> Typed<C> {
let mut locked_map = std::collections::HashMap::with_capacity(self.battle.singleplayer.campaigns.len());
for campaign in self.battle.singleplayer.campaigns.iter() {
locked_map.insert(campaign.id.clone(), true);
}
crate::data::campaign::GameModeVersionParameters {
current_version: 0,
is_locked: locked_map,
}.as_transmissible()
}
fn campaign_details(&self) -> super::CompleteCampaignProvider {
let mut map = std::collections::HashMap::with_capacity(self.battle.singleplayer.campaigns.len());
for campaign in self.battle.singleplayer.campaigns.iter() {
//let waves_data: Vec<crate::data::campaign::CompleteWaveData> = campaign.waves.iter().map(|x| x.clone().into()).collect();
let mut difficulty_map = std::collections::HashMap::with_capacity(campaign.difficulties.len());
for difficulty in campaign.difficulties.iter() {
let difficulty_data: crate::data::campaign::CampaignDifficultyData = difficulty.clone().into();
let complete_campaign = crate::data::campaign::CampaignWavesDifficultyData {
difficulty: difficulty_data,
waves: campaign.waves.iter().map(|x| x.clone().into()).collect(),
};
difficulty_map.insert(difficulty.level, complete_campaign);
difficulty_map.insert(difficulty.level, difficulty.to_owned());
}
map.insert(campaign.id.clone(), difficulty_map);
map.insert(campaign.id.clone(), super::campaign::CompleteCampaignData {
difficulty_map,
waves: campaign.waves.clone(),
});
}
super::CompleteCampaignProvider::new(map)
}
fn campaigns(&self) -> super::CampaignResolver {
super::CampaignResolver {
singleplayer: self.battle.singleplayer.clone(),
}
}
fn client_config(&self) -> Typed<C> {
let conf_data: crate::data::client_config::GameplaySettings = self.settings.gameplay.clone().into();
Typed::Dict(Dict {

View File

@@ -2,11 +2,14 @@ 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, BattleArenaResolver, PitSettings, PitWinCondition, TeamDeathMatchSettings};
pub use traits::{ConfigProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode, Point, Sphere, MapConfig, LinksConfig, FakePlayer, ClientEmulator, EnergyConfig, BattleArenaResolver, PitSettings, PitWinCondition, TeamDeathMatchSettings};
mod validation;
pub use validation::{SelfValidator, ValidationInfo, ValidationMessage};
mod campaign;
pub use campaign::{CampaignResolver, CompleteCampaignProvider};
pub type ConfigImpl = CubeConfig;
fn __must_impl<T: ConfigProvider<()>>() {}

View File

@@ -12,10 +12,8 @@ pub trait ConfigProvider<C: Clone> {
fn regen_config(&self) -> Typed<C>;
fn after_battle_vote_config(&self) -> Typed<C>;
fn game_mode_config(&self) -> Typed<C>;
fn campaigns_parameters(&self) -> Typed<C>;
fn campaign_waves(&self) -> Typed<C>;
fn campaign_version(&self) -> Typed<C>;
fn campaign_details(&self) -> CompleteCampaignProvider;
fn campaign_details(&self) -> super::CompleteCampaignProvider;
fn campaigns(&self) -> super::CampaignResolver;
fn client_config(&self) -> Typed<C>;
fn login_messages(&self) -> DevMessageProvider<C>;
fn public_channels(&self) -> Typed<C>;
@@ -40,30 +38,6 @@ pub trait ConfigProvider<C: Clone> {
fn tdm_settings(&self) -> TeamDeathMatchSettings;
}
pub struct CompleteCampaignProvider {
map: std::collections::HashMap<String, std::collections::HashMap<i32, crate::data::campaign::CampaignWavesDifficultyData>>,
}
impl CompleteCampaignProvider {
pub fn new(map: std::collections::HashMap<String, std::collections::HashMap<i32, crate::data::campaign::CampaignWavesDifficultyData>>) -> Self {
Self { map }
}
pub fn get<C>(&self, id: &str, difficulty: &i32) -> Result<Typed<C>, i16> {
if let Some(campaign) = self.map.get(id) {
if let Some(details) = campaign.get(difficulty) {
Ok(details.as_transmissible())
} else {
log::warn!("Couldn't find difficulty {} in campaign `{}`", difficulty, id);
Err(crate::data::error_codes::WebServicesError::DatabaseError as i16)
}
} else {
log::warn!("Couldn't find campaign {} (ignoring difficulty {})", id, difficulty);
Err(crate::data::error_codes::WebServicesError::DatabaseError as i16)
}
}
}
pub struct DevMessageProvider<C: Clone> {
messages: Vec<TypedDevMessage<C>>,
}

View File

@@ -134,6 +134,30 @@ pub(super) fn default_fake_users() -> Vec<FakePlayerConf> {
},
implementation: ClientEmulation::ClientAI,
},
FakePlayerConf {
team: None,
vehicle: super::garage::PrefabVehicle {
name: Some("fake3".to_owned()),
username: "Server3".to_owned(),
id: super::garage::PrefabId::Raw {
cube_data: Vec::from(crate::persist::VALID_ROBOT),
colour_data: Vec::from(crate::persist::VALID_COLOUR),
},
},
implementation: ClientEmulation::ClientAI,
},
FakePlayerConf {
team: None,
vehicle: super::garage::PrefabVehicle {
name: Some("fake4".to_owned()),
username: "Server4".to_owned(),
id: super::garage::PrefabId::Raw {
cube_data: Vec::from(crate::persist::VALID_ROBOT),
colour_data: Vec::from(crate::persist::VALID_COLOUR),
},
},
implementation: ClientEmulation::ClientAI,
},
]
}

View File

@@ -114,7 +114,7 @@ fn default_server_conf() -> ServerSettings {
ServerSettings {
database: default_db_conn(),
auto_signup: false,
queue_mode: QueueMode::Notify,
queue_mode: QueueMode::Upgrade,
cdn_url: default_cdn_root_url(),
auth_url: default_auth_root_url(),
intercom_url: default_intercom_root_url(),

View File

@@ -14,10 +14,6 @@ impl SingleplayerConfig {
crate::data::campaign::CampaignsGameParameters { campaigns: self.campaigns.into_iter().map(|x| x.into_campaign_params()).collect() }
}
pub fn into_waves(self) -> crate::data::campaign::LiveCampaignWaves {
crate::data::campaign::LiveCampaignWaves { waves: self.campaigns.into_iter().map(|x| x.into_waves()).collect() }
}
pub fn into_singleplayer_conf(&self) -> crate::persist::config::SingleplayerConfig {
crate::persist::config::SingleplayerConfig {
max_teammates: self.max_teammates,
@@ -86,14 +82,6 @@ impl Campaign {
map: self.map,
}
}
pub fn into_waves(self) -> crate::data::campaign::WavesData {
crate::data::campaign::WavesData {
id: self.id,
waves: self.waves.into_iter().map(|x| x.into()).collect(),
campaign_type: self.campaign_type.into(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
@@ -150,35 +138,13 @@ pub struct Wave {
pub time_max: i32,
}
impl std::convert::From<Wave> for crate::data::campaign::WaveData {
fn from(val: Wave) -> Self {
crate::data::campaign::WaveData {
robots_in_wave: val.robots_in_wave.into_iter().map(|x| x.into()).collect(),
}
}
}
impl std::convert::From<Wave> for crate::data::campaign::CompleteWaveData {
fn from(val: Wave) -> Self {
crate::data::campaign::CompleteWaveData {
player_spawn_location: val.player_spawn_location,
robots_in_wave: val.robots_in_wave.into_iter().map(|x| x.into()).collect(),
kill_target: val.kill_target,
time_min: val.time_min,
time_max: val.time_max,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct WaveRobot { // times appear to be in seconds
pub name: String,
pub vehicle: super::garage::PrefabVehicle,
pub weapon: String,
pub movement: String,
pub rank: String,
pub count: i32,
pub robot_data: Vec<u8>,
pub colour_data: Vec<u8>,
#[serde(default)]
pub time_to_spawn: i32,
#[serde(default)]
@@ -207,39 +173,6 @@ fn default_1() -> i32 {
1
}
impl std::convert::From<WaveRobot> for crate::data::campaign::WaveRobotData {
fn from(val: WaveRobot) -> Self {
crate::data::campaign::WaveRobotData {
name: val.name,
weapon: val.weapon,
movement: val.movement,
rank: val.rank,
count: val.count,
}
}
}
impl std::convert::From<WaveRobot> for crate::data::campaign::CompleteWaveRobotData {
fn from(val: WaveRobot) -> Self {
crate::data::campaign::CompleteWaveRobotData {
name: val.name,
robot_data: val.robot_data,
colour_data: val.colour_data,
time_to_spawn: val.time_to_spawn,
kills_to_spawn: val.kills_to_spawn,
time_to_despawn: val.time_to_despawn,
kills_to_despawn: val.kills_to_despawn,
initial_robot_amount: val.initial_robot_amount,
periodic_robot_amount: val.periodic_robot_amount,
spawn_interval: val.spawn_interval,
min_robot_amount: val.min_robot_amount,
max_robot_amount: val.max_robot_amount,
is_boss: val.is_boss,
is_kill_requirement: val.is_kill_requirement,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
pub enum CampaignType {
TimedElimination = 0,