mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add singleplayer and campaign rewards screens #70
This commit is contained in:
@@ -107,7 +107,7 @@ impl GameMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_persist(mode: crate::persist::config::GameType) -> Self {
|
pub(crate) fn from_persist(mode: crate::persist::config::GameType) -> Self {
|
||||||
match mode {
|
match mode {
|
||||||
crate::persist::config::GameType::BattleArena => Self::BattleArena,
|
crate::persist::config::GameType::BattleArena => Self::BattleArena,
|
||||||
crate::persist::config::GameType::SuddenDeath => Self::SuddenDeath,
|
crate::persist::config::GameType::SuddenDeath => Self::SuddenDeath,
|
||||||
@@ -144,6 +144,19 @@ impl GameMode {
|
|||||||
Self::Campaign => oj_rc_database::schema::multiplayer_game::GameMode::Campaign,
|
Self::Campaign => oj_rc_database::schema::multiplayer_game::GameMode::Campaign,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_u8(b: u8) -> Option<Self> {
|
||||||
|
match b {
|
||||||
|
0 => Some(Self::BattleArena),
|
||||||
|
1 => Some(Self::SuddenDeath),
|
||||||
|
2 => Some(Self::Pit),
|
||||||
|
3 => Some(Self::TestMode),
|
||||||
|
4 => Some(Self::SinglePlayer),
|
||||||
|
5 => Some(Self::TeamDeathmatch),
|
||||||
|
6 => Some(Self::Campaign),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
|||||||
172
rc_core/src/data/game_result.rs
Normal file
172
rc_core/src/data/game_result.rs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
pub struct GameResult {
|
||||||
|
pub mode: super::game_mode::GameMode,
|
||||||
|
pub is_custom: bool,
|
||||||
|
pub winners: Vec<PlayerAward>,
|
||||||
|
pub losers: Vec<PlayerAward>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameResult {
|
||||||
|
pub fn parse(r: &mut dyn std::io::Read) -> std::io::Result<Self> {
|
||||||
|
let mut bytes_buf = [0u8; 2];
|
||||||
|
r.read_exact(&mut bytes_buf)?;
|
||||||
|
let mode = super::game_mode::GameMode::from_u8(bytes_buf[0])
|
||||||
|
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("Invalid game mode {}", bytes_buf[0])))?;
|
||||||
|
let is_custom = bytes_buf[1] != 0;
|
||||||
|
let mut winners_count_buf = [0u8; 4];
|
||||||
|
r.read_exact(&mut winners_count_buf)?;
|
||||||
|
let winners_count = i32::from_le_bytes(winners_count_buf);
|
||||||
|
let mut winners = Vec::with_capacity(winners_count as _);
|
||||||
|
for _ in 0..winners_count {
|
||||||
|
let award = PlayerAward::parse(r)?;
|
||||||
|
winners.push(award);
|
||||||
|
}
|
||||||
|
let mut losers_count_buf = [0u8; 4];
|
||||||
|
r.read_exact(&mut losers_count_buf)?;
|
||||||
|
let losers_count = i32::from_le_bytes(losers_count_buf);
|
||||||
|
let mut losers = Vec::with_capacity(losers_count as _);
|
||||||
|
for _ in 0..losers_count {
|
||||||
|
let award = PlayerAward::parse(r)?;
|
||||||
|
losers.push(award);
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
mode,
|
||||||
|
is_custom,
|
||||||
|
winners,
|
||||||
|
losers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PlayerAward {
|
||||||
|
pub player_name: String,
|
||||||
|
pub mastery: u8,
|
||||||
|
pub is_alive: bool,
|
||||||
|
pub is_disconnected: bool,
|
||||||
|
pub party_members: u8,
|
||||||
|
pub score: i32,
|
||||||
|
pub score_position: u8,
|
||||||
|
pub score_position_in_team: u8,
|
||||||
|
pub awards: std::collections::HashMap<PlayerAwardId, i32>,
|
||||||
|
pub weapons: Vec<WeaponUsage>,
|
||||||
|
pub players_died_before: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayerAward {
|
||||||
|
pub fn parse(r: &mut dyn std::io::Read) -> std::io::Result<Self> {
|
||||||
|
let player_name = super::read_str_for_binwriter(r)?;
|
||||||
|
let mut bytes_buf = [0u8; 4];
|
||||||
|
r.read_exact(&mut bytes_buf)?;
|
||||||
|
let mastery = bytes_buf[0];
|
||||||
|
let is_alive = bytes_buf[1] != 0;
|
||||||
|
let is_disconnected = bytes_buf[2] != 0;
|
||||||
|
let party_members = bytes_buf[3];
|
||||||
|
let mut score_buf = [0u8; 4];
|
||||||
|
r.read_exact(&mut score_buf)?;
|
||||||
|
let score = i32::from_le_bytes(score_buf);
|
||||||
|
let mut bytes_buf = [0u8; 3];
|
||||||
|
r.read_exact(&mut bytes_buf)?;
|
||||||
|
let score_position = bytes_buf[0];
|
||||||
|
let score_position_in_team = bytes_buf[1];
|
||||||
|
let awards_count = bytes_buf[2];
|
||||||
|
let mut awards = std::collections::HashMap::with_capacity(awards_count as _);
|
||||||
|
for _ in 0..awards_count {
|
||||||
|
let mut bytes_buf = [0u8; 1];
|
||||||
|
r.read_exact(&mut bytes_buf)?;
|
||||||
|
let key = PlayerAwardId::from_u8(bytes_buf[0])
|
||||||
|
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("Invalid player award {}", bytes_buf[0])))?;
|
||||||
|
let mut value_buf = [0u8; 4];
|
||||||
|
r.read_exact(&mut value_buf)?;
|
||||||
|
let value = i32::from_le_bytes(value_buf);
|
||||||
|
awards.insert(key, value);
|
||||||
|
}
|
||||||
|
let mut bytes_buf = [0u8; 1];
|
||||||
|
r.read_exact(&mut bytes_buf)?;
|
||||||
|
let weapons_count = bytes_buf[0];
|
||||||
|
let mut weapons = Vec::with_capacity(weapons_count as _);
|
||||||
|
for _ in 0..weapons_count {
|
||||||
|
let weapon = WeaponUsage::parse(r)?;
|
||||||
|
weapons.push(weapon);
|
||||||
|
}
|
||||||
|
let mut bytes_buf = [0u8; 1];
|
||||||
|
r.read_exact(&mut bytes_buf)?;
|
||||||
|
let players_died_before = bytes_buf[0];
|
||||||
|
Ok(Self {
|
||||||
|
player_name,
|
||||||
|
mastery,
|
||||||
|
is_alive,
|
||||||
|
is_disconnected,
|
||||||
|
party_members,
|
||||||
|
score,
|
||||||
|
score_position,
|
||||||
|
score_position_in_team,
|
||||||
|
awards,
|
||||||
|
weapons,
|
||||||
|
players_died_before,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Hash, Eq, PartialEq, Clone, Copy)]
|
||||||
|
pub enum PlayerAwardId {
|
||||||
|
DestroyedCubes = 1,
|
||||||
|
DestroyedCubesInProtection = 2,
|
||||||
|
DestroyedCubesDefendingBase = 3,
|
||||||
|
Kill = 4,
|
||||||
|
KillAssist = 5,
|
||||||
|
HealCubes = 6,
|
||||||
|
HealAssist = 7,
|
||||||
|
// why are there gaps?
|
||||||
|
Score = 11,
|
||||||
|
CompletionBonus = 17,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayerAwardId {
|
||||||
|
pub fn from_u8(b: u8) -> Option<Self> {
|
||||||
|
match b {
|
||||||
|
1 => Some(Self::DestroyedCubes),
|
||||||
|
2 => Some(Self::DestroyedCubesInProtection),
|
||||||
|
3 => Some(Self::DestroyedCubesDefendingBase),
|
||||||
|
4 => Some(Self::Kill),
|
||||||
|
5 => Some(Self::KillAssist),
|
||||||
|
6 => Some(Self::HealCubes),
|
||||||
|
7 => Some(Self::HealAssist),
|
||||||
|
11 => Some(Self::Score),
|
||||||
|
17 => Some(Self::CompletionBonus),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_u8(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WeaponUsage {
|
||||||
|
pub category: super::weapon_list::ItemCategory,
|
||||||
|
pub size: super::cube_list::ItemTier,
|
||||||
|
pub usage_ratio: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WeaponUsage {
|
||||||
|
pub fn parse(r: &mut dyn std::io::Read) -> std::io::Result<Self> {
|
||||||
|
let mut category_buf = [0u8; 4];
|
||||||
|
r.read_exact(&mut category_buf)?;
|
||||||
|
let category_num = i32::from_le_bytes(category_buf);
|
||||||
|
let category = super::weapon_list::ItemCategory::from_smaller(category_num)
|
||||||
|
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("Invalid item category {}", category_num)))?;
|
||||||
|
let mut size_buf = [0u8; 4];
|
||||||
|
r.read_exact(&mut size_buf)?;
|
||||||
|
let size_num = i32::from_le_bytes(size_buf);
|
||||||
|
let size = super::cube_list::ItemTier::from_u32(size_num as _)
|
||||||
|
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("Invalid item size/tier {}", size_num)))?;
|
||||||
|
let mut ratio_buf = [0u8; 4];
|
||||||
|
r.read_exact(&mut ratio_buf)?;
|
||||||
|
let usage_ratio = f32::from_le_bytes(ratio_buf);
|
||||||
|
Ok(Self {
|
||||||
|
category,
|
||||||
|
size,
|
||||||
|
usage_ratio,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ pub mod sanction;
|
|||||||
pub mod robot_data;
|
pub mod robot_data;
|
||||||
pub mod lobby;
|
pub mod lobby;
|
||||||
pub mod battle_arena_config;
|
pub mod battle_arena_config;
|
||||||
|
pub mod game_result;
|
||||||
|
|
||||||
pub mod error_codes;
|
pub mod error_codes;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ mod inventory;
|
|||||||
pub use inventory::UnlockedParts;
|
pub use inventory::UnlockedParts;
|
||||||
|
|
||||||
mod traits;
|
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, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, CurrencyType, CurrencyOp, MatchRewards};
|
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, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser};
|
||||||
|
|
||||||
pub mod intercom;
|
pub mod intercom;
|
||||||
pub use intercom::generate_token as generate_intercom_token;
|
pub use intercom::generate_token as generate_intercom_token;
|
||||||
@@ -22,6 +22,7 @@ pub use lobby::TeamChooser;
|
|||||||
mod common;
|
mod common;
|
||||||
mod chat;
|
mod chat;
|
||||||
mod social;
|
mod social;
|
||||||
|
mod singleplayer;
|
||||||
|
|
||||||
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";
|
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";
|
||||||
|
|
||||||
|
|||||||
85
rc_core/src/persist/user/singleplayer.rs
Normal file
85
rc_core/src/persist/user/singleplayer.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
use super::account_json::UserData;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn award_to_active_value(opt: Option<&i32>) -> oj_rc_database::sea_orm::ActiveValue<i32> {
|
||||||
|
match opt {
|
||||||
|
Some(t) => oj_rc_database::sea_orm::ActiveValue::Set(*t),
|
||||||
|
None => oj_rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl super::SingleplayerUser for UserData {
|
||||||
|
async fn save_game_result(&self, guid: &str, result: crate::data::game_result::GameResult) -> Result<(), polariton_server::operations::SimpleOpError> {
|
||||||
|
let now = chrono::Utc::now().timestamp();
|
||||||
|
// TODO make this a single transaction
|
||||||
|
let game = self.db.insert_game(oj_rc_database::schema::multiplayer_game::ActiveModel {
|
||||||
|
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||||
|
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||||
|
guid: oj_rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
map: oj_rc_database::sea_orm::ActiveValue::Set(format!("singleplayer|guid:{}", guid)), // invalid by design
|
||||||
|
mode: oj_rc_database::sea_orm::ActiveValue::Set(result.mode.to_db()),
|
||||||
|
visibility: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::multiplayer_game::MapVisibility::Good),
|
||||||
|
auto_heal: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
|
variant: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::multiplayer_game::GameType::Standard),
|
||||||
|
is_complete: oj_rc_database::sea_orm::ActiveValue::Set(true),
|
||||||
|
}).await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("Failed to create singleplayer game {} for user {}: {}", guid, self.account.id, e);
|
||||||
|
polariton_server::operations::SimpleOpError::with_message(
|
||||||
|
crate::data::error_codes::SingleplayerErrorCode::DatabaseError as i16,
|
||||||
|
format!("Failed to create singleplayer game {}: {}", guid, e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let iter = result.winners.iter().map(|p| (0, p)).chain(result.losers.iter().map(|p| (1, p)));
|
||||||
|
for (player_team, player_result) in iter {
|
||||||
|
if player_result.player_name != self.account.display_name { continue; }
|
||||||
|
let player = self.db.insert_player(oj_rc_database::schema::multiplayer_game_player::ActiveModel {
|
||||||
|
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||||
|
user_id: oj_rc_database::sea_orm::ActiveValue::Set(Some(self.account.id)),
|
||||||
|
game_id: oj_rc_database::sea_orm::ActiveValue::Set(game.id),
|
||||||
|
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||||
|
player_id: oj_rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
team: oj_rc_database::sea_orm::ActiveValue::Set(player_team),
|
||||||
|
group: oj_rc_database::sea_orm::ActiveValue::Set(None),
|
||||||
|
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(true),
|
||||||
|
public_id: oj_rc_database::sea_orm::ActiveValue::Set("".to_owned()), // no point in adding this info
|
||||||
|
display_name: oj_rc_database::sea_orm::ActiveValue::Set("".to_owned()),
|
||||||
|
variant: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::multiplayer_game_player::ClientType::Client),
|
||||||
|
}).await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("Failed to create singleplayer game {} player for user {}: {}", guid, self.account.id, e);
|
||||||
|
polariton_server::operations::SimpleOpError::with_message(
|
||||||
|
crate::data::error_codes::SingleplayerErrorCode::DatabaseError as i16,
|
||||||
|
format!("Failed to create singleplayer game {} player: {}", guid, e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
self.db.insert_score(oj_rc_database::schema::multiplayer_game_score::ActiveModel {
|
||||||
|
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||||
|
player_id: oj_rc_database::sea_orm::ActiveValue::Set(player.id),
|
||||||
|
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now),
|
||||||
|
is_claimed: oj_rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
|
kills: award_to_active_value(player_result.awards.get(&crate::data::game_result::PlayerAwardId::Kill)),
|
||||||
|
deaths: oj_rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
assists: award_to_active_value(player_result.awards.get(&crate::data::game_result::PlayerAwardId::KillAssist)),
|
||||||
|
heal_assists: award_to_active_value(player_result.awards.get(&crate::data::game_result::PlayerAwardId::HealAssist)),
|
||||||
|
healed: award_to_active_value(player_result.awards.get(&crate::data::game_result::PlayerAwardId::HealCubes)),
|
||||||
|
received_healed: oj_rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
damaged: award_to_active_value(player_result.awards.get(&crate::data::game_result::PlayerAwardId::DestroyedCubes)),
|
||||||
|
received_damaged: oj_rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
crystals: oj_rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
total: award_to_active_value(player_result.awards.get(&crate::data::game_result::PlayerAwardId::Score)),
|
||||||
|
}).await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("Failed to create singleplayer game {} score for user {}: {}", guid, self.account.id, e);
|
||||||
|
polariton_server::operations::SimpleOpError::with_message(
|
||||||
|
crate::data::error_codes::SingleplayerErrorCode::DatabaseError as i16,
|
||||||
|
format!("Failed to create singleplayer game {} score: {}", guid, e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ pub trait UserAuthenticator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait User<C>: ChatUser + SocialUser + LobbyUser + MultiplayerUser + IntercomUser + CommonUser {
|
pub trait User<C>: ChatUser + SocialUser + LobbyUser + MultiplayerUser + SingleplayerUser + IntercomUser + CommonUser {
|
||||||
async fn unlocked_parts(&self) -> Vec<u32>;
|
async fn unlocked_parts(&self) -> Vec<u32>;
|
||||||
async fn selected_garage(&self) -> (String, u32);
|
async fn selected_garage(&self) -> (String, u32);
|
||||||
async fn select_garage(&self, slot: i32) -> Result<(), i16>;
|
async fn select_garage(&self, slot: i32) -> Result<(), i16>;
|
||||||
@@ -438,3 +438,9 @@ pub struct MatchRewards {
|
|||||||
pub robits_earned: i32,
|
pub robits_earned: i32,
|
||||||
pub premium_robits_earned: i32,
|
pub premium_robits_earned: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait SingleplayerUser: Send + Sync {
|
||||||
|
// regular singleplayer and campaign mode
|
||||||
|
async fn save_game_result(&self, guid: &str, result: crate::data::game_result::GameResult) -> Result<(), polariton_server::operations::SimpleOpError>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -455,6 +455,10 @@ impl Database {
|
|||||||
.and_then(|(_, player)| player))
|
.and_then(|(_, player)| player))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn insert_player(&self, entity: crate::schema::multiplayer_game_player::ActiveModel) -> Result<crate::schema::multiplayer_game_player::Model, sea_orm::DbErr> {
|
||||||
|
entity.insert(&self.orm).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn insert_players(&self, entities: Vec<crate::schema::multiplayer_game_player::ActiveModel>) -> Result<(), sea_orm::DbErr> {
|
pub async fn insert_players(&self, entities: Vec<crate::schema::multiplayer_game_player::ActiveModel>) -> Result<(), sea_orm::DbErr> {
|
||||||
crate::schema::multiplayer_game_player::Entity::insert_many(entities.into_iter()).exec(&self.orm).await?;
|
crate::schema::multiplayer_game_player::Entity::insert_many(entities.into_iter()).exec(&self.orm).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
46
rc_services_room/src/operations/campaign_save_result.rs
Normal file
46
rc_services_room/src/operations/campaign_save_result.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||||
|
use polariton::operation::{ParameterTable, Typed};
|
||||||
|
|
||||||
|
const GAME_RESULT_PARAM_KEY: u8 = 82; // bytes; in
|
||||||
|
const CAMPAIGN_ID_PARAM_KEY: u8 = 22; // string; in
|
||||||
|
const DIFFICULTY_PARAM_KEY: u8 = 23; // int; in
|
||||||
|
const LONG_PLAY_PARAM_KEY: u8 = 84; // float; in
|
||||||
|
const GUID_PARAM_KEY: u8 = 85; // string; in
|
||||||
|
|
||||||
|
const CODE: u8 = 78;
|
||||||
|
|
||||||
|
// SaveCampaignGameAwardsRequest
|
||||||
|
pub(super) struct CampaignGameAwardsSaver;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl <C: Send + 'static> SimpleOperation<C> for CampaignGameAwardsSaver {
|
||||||
|
type User = crate::UserTy;
|
||||||
|
const CODE: u8 = CODE;
|
||||||
|
|
||||||
|
async fn handle(&self, mut params: ParameterTable<C>, user: &Self::User) -> Result<ParameterTable<C>, SimpleOpError> {
|
||||||
|
if let Some(Typed::Bytes(game_result)) = params.remove(&GAME_RESULT_PARAM_KEY) {
|
||||||
|
let mut result_cursor = std::io::Cursor::new(game_result.vec);
|
||||||
|
let game_result = oj_rc_core::data::game_result::GameResult::parse(&mut result_cursor)
|
||||||
|
.map_err(|e| SimpleOpError::with_message(
|
||||||
|
oj_rc_core::data::error_codes::SingleplayerErrorCode::UnexpectedError as i16,
|
||||||
|
format!("Bad game result serialization: {}", e),
|
||||||
|
))?;
|
||||||
|
if let Some(Typed::Float(_long_play)) = params.remove(&LONG_PLAY_PARAM_KEY) {
|
||||||
|
if let Some(Typed::Str(_campaign_id)) = params.remove(&CAMPAIGN_ID_PARAM_KEY) {
|
||||||
|
if let Some(Typed::Int(_difficulty)) = params.remove(&DIFFICULTY_PARAM_KEY) {
|
||||||
|
if let Some(Typed::Str(game_guid)) = params.remove(&GUID_PARAM_KEY) {
|
||||||
|
let user_info = user.user()?;
|
||||||
|
user_info.save_game_result(&game_guid.string, game_result).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ParameterTable::with_capacity(1)) // parameter-less response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn campaign_save_awards_provider<C: Send + 'static>() -> SimpleOpImpl<C, crate::UserTy, CampaignGameAwardsSaver> {
|
||||||
|
SimpleOpImpl::new(CampaignGameAwardsSaver)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -98,6 +98,7 @@ mod garage_slot_set_customisations;
|
|||||||
mod garage_slot_name;
|
mod garage_slot_name;
|
||||||
mod garage_slot_copy;
|
mod garage_slot_copy;
|
||||||
mod steam_promo;
|
mod steam_promo;
|
||||||
|
mod campaign_save_result;
|
||||||
|
|
||||||
use polariton_server::operations::OperationsHandler;
|
use polariton_server::operations::OperationsHandler;
|
||||||
|
|
||||||
@@ -199,7 +200,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
|||||||
.add(player_robot_rank::player_robot_rank_provider())
|
.add(player_robot_rank::player_robot_rank_provider())
|
||||||
.add(validate_machine::validate_campaign_robot_provider())
|
.add(validate_machine::validate_campaign_robot_provider())
|
||||||
.add(singleplayer_campaigns::singleplayer_complete_campaign_provider(init_ctx))
|
.add(singleplayer_campaigns::singleplayer_complete_campaign_provider(init_ctx))
|
||||||
.add(polariton_server::operations::Ack::<78, _>::default()) // TODO handle SaveCampaignGameAwardsRequest instead of ignoring it
|
.add(campaign_save_result::campaign_save_awards_provider())
|
||||||
.add(singleplayer_campaigns::singleplayer_save_complete_campaign_provider()) // TODO handle UpdatePlayerCompletedCampaignWaveRequest saving
|
.add(singleplayer_campaigns::singleplayer_save_complete_campaign_provider()) // TODO handle UpdatePlayerCompletedCampaignWaveRequest saving
|
||||||
.add(garage_slot_limit::garage_slots_limit(&init_ctx.cubes))
|
.add(garage_slot_limit::garage_slots_limit(&init_ctx.cubes))
|
||||||
.add(garage_slot_add::garage_slot_add_provider())
|
.add(garage_slot_add::garage_slot_add_provider())
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod more_auth;
|
mod more_auth;
|
||||||
mod eac;
|
mod eac;
|
||||||
mod load_ai_robots;
|
mod load_ai_robots;
|
||||||
|
mod save_result;
|
||||||
|
|
||||||
use polariton_server::operations::OperationsHandler;
|
use polariton_server::operations::OperationsHandler;
|
||||||
|
|
||||||
@@ -11,5 +12,6 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
|||||||
.add(eac::EacChallengeIgnorer)
|
.add(eac::EacChallengeIgnorer)
|
||||||
.add(load_ai_robots::tdm_machines_provider(&init_ctx.factory, init_ctx.parsers.weapon_order(), &init_ctx.config, init_ctx.parsers.cpu_counter()))
|
.add(load_ai_robots::tdm_machines_provider(&init_ctx.factory, init_ctx.parsers.weapon_order(), &init_ctx.config, init_ctx.parsers.cpu_counter()))
|
||||||
//.add(polariton_server::operations::Ack::<33, _>::default()) // get user clan info (this is equivalent to not being in a clan)
|
//.add(polariton_server::operations::Ack::<33, _>::default()) // get user clan info (this is equivalent to not being in a clan)
|
||||||
.add(polariton_server::operations::Ack::<2, _>::default()) // Save singleplayer result (parameter-less response)
|
//.add(polariton_server::operations::Ack::<2, _>::default()) // Save singleplayer result (parameter-less response)
|
||||||
|
.add(save_result::save_result_provider())
|
||||||
}
|
}
|
||||||
|
|||||||
39
rc_singleplayer_room/src/operations/save_result.rs
Normal file
39
rc_singleplayer_room/src/operations/save_result.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||||
|
use polariton::operation::{ParameterTable, Typed};
|
||||||
|
|
||||||
|
const GAME_RESULT_PARAM_KEY: u8 = 9; // bytes; in
|
||||||
|
const LONG_PLAY_PARAM_KEY: u8 = 12; // float; in
|
||||||
|
const GUID_PARAM_KEY: u8 = 18; // string; in
|
||||||
|
|
||||||
|
const CODE: u8 = 2;
|
||||||
|
|
||||||
|
pub(super) struct GameResultSaver;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl <C: Send + 'static> SimpleOperation<C> for GameResultSaver {
|
||||||
|
type User = crate::UserTy;
|
||||||
|
const CODE: u8 = CODE;
|
||||||
|
|
||||||
|
async fn handle(&self, mut params: ParameterTable<C>, user: &Self::User) -> Result<ParameterTable<C>, SimpleOpError> {
|
||||||
|
if let Some(Typed::Bytes(game_result)) = params.remove(&GAME_RESULT_PARAM_KEY) {
|
||||||
|
let mut result_cursor = std::io::Cursor::new(game_result.vec);
|
||||||
|
let game_result = oj_rc_core::data::game_result::GameResult::parse(&mut result_cursor)
|
||||||
|
.map_err(|e| SimpleOpError::with_message(
|
||||||
|
oj_rc_core::data::error_codes::SingleplayerErrorCode::UnexpectedError as i16,
|
||||||
|
format!("Bad game result serialization: {}", e),
|
||||||
|
))?;
|
||||||
|
if let Some(Typed::Float(_long_play)) = params.remove(&LONG_PLAY_PARAM_KEY) {
|
||||||
|
if let Some(Typed::Str(game_guid)) = params.remove(&GUID_PARAM_KEY) {
|
||||||
|
let user_info = user.user()?;
|
||||||
|
user_info.save_game_result(&game_guid.string, game_result).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ParameterTable::with_capacity(1)) // parameter-less response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn save_result_provider<C: Send + 'static>() -> SimpleOpImpl<C, crate::UserTy, GameResultSaver> {
|
||||||
|
SimpleOpImpl::new(GameResultSaver)
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user