diff --git a/rc_core/src/persist/garage.rs b/rc_core/src/persist/garage.rs index 867f8ee..f68c751 100644 --- a/rc_core/src/persist/garage.rs +++ b/rc_core/src/persist/garage.rs @@ -105,7 +105,7 @@ pub fn db_into_data(garage: oj_rc_database::schema::garage::Model) -> crate::dat name: garage.name, cubes: cube_count, crf_id: garage.crf_id.unwrap_or(0) as u32, - was_rated: true,//garage.was_rated, + was_rated: garage.was_rated, movement_categories: movement_category_into_data(&garage.movement_categories), uuid: super::user::i64_split(garage.uuid), thumbnail_version: garage.thumbnail_version as u32, diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index 87159f7..c189d6b 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -325,7 +325,7 @@ pub(super) struct UserData { } impl UserData { - async fn load_garage_by_slot(&self, slot: i32) -> Result, oj_rc_database::sea_orm::DbErr> { + pub(super) async fn load_garage_by_slot(&self, slot: i32) -> Result, oj_rc_database::sea_orm::DbErr> { //let path = self.root.join(super::GARAGE_DIR).join(format!("{}.json", id)); //crate::persist::GarageSlot::load(&path) self.db.garage_by_user_id_and_slot(self.account.id, slot).await @@ -344,13 +344,16 @@ impl UserData { Ok(self.db.perms_by_user_id(self.account.id).await?.unwrap()) } - async fn err_on_banned(&self) -> Result<(), i16> { + pub(super) async fn err_on_banned(&self) -> Result<(), polariton_server::operations::SimpleOpError> { let perms = self.double_check_permissions().await.map_err(|e| { log::error!("Failed to retrieve user {} permissions: {}", self.account.id, e); - DATABASE_ERR + polariton_server::operations::SimpleOpError::with_message( + DATABASE_ERR, + format!("Failed to retrieve user permissions: {}", e), + ) })?; if perms.banned { - Err(crate::data::error_codes::WebServicesError::Banned as i16) + Err(polariton_server::operations::SimpleOpError::with_code(crate::data::error_codes::WebServicesError::Banned as i16)) } else { Ok(()) } @@ -901,6 +904,7 @@ impl super::User for UserData { name: if let Some(new_name) = vehicle.name { oj_rc_database::sea_orm::ActiveValue::Set(new_name) } else { Default::default() }, total_robot_cpu: oj_rc_database::sea_orm::ActiveValue::Set(cpu_counts.total as _), total_cosmetic_cpu: oj_rc_database::sea_orm::ActiveValue::Set(cpu_counts.cosmetic as _), + was_rated: if let Some(is_rated) = vehicle.was_rated { oj_rc_database::sea_orm::ActiveValue::Set(is_rated) } else { oj_rc_database::sea_orm::ActiveValue::NotSet }, ..Default::default() }; self.save_garage_by_slot(entity, vehicle.slot).await.map_err(|e| { @@ -1156,29 +1160,6 @@ impl super::User for UserData { }.as_transmissible()) } - async fn prepare_factory_upload(&self, vehicle: super::VehicleUploadData) -> Result { - self.err_on_banned().await?; - let slot = self.load_garage_by_slot(vehicle.slot).await.map_err(|e| { - log::error!("Failed to retrieve vehicle slot {} for user_id {} (prepare_factory_upload): {}", vehicle.slot, self.account.id, e); - DATABASE_ERR - })?.ok_or_else(|| { - log::error!("Failed to find vehicle slot {} for user_id {} (prepare_factory_upload)", vehicle.slot, self.account.id); - INVALID_ROBOT_ERR - })?; - Ok(oj_rc_factory::VehicleUploadInfo { - name: vehicle.name, - description: vehicle.description, - thumbnail: vehicle.thumbnail, - added_by: self.account.public_id.clone(), - added_by_display_name: self.account.display_name.clone(), - cpu: slot.total_robot_cpu as u32, - total_robot_ranking: slot.total_robot_ranking as u32, - build_version: vehicle.version, - cube_data: slot.robot_data, - colour_data: slot.colour_data, - }) - } - async fn last_seen(&self) -> Result { self.err_on_banned().await?; let last_seen_aux_opt = self.db.user_aux_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::user_aux::Descriptor::LastSeen).await @@ -1360,6 +1341,24 @@ impl super::User for UserData { paid_currency_award: paid_currency, }) } + + async fn currency_debit(&self, ty: super::CurrencyType, to_sub: u64) -> Result<(), polariton_server::operations::SimpleOpError> { + let is_ok = self.currency_sub_checked(ty, to_sub).await.map_err(|e| { + log::error!("Failed to apply currency debit of {} {:?} for user {}: {}", to_sub, ty, self.account.id, e); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::WebServicesError::DatabaseError as i16, + format!("Failed to apply debit of {} {:?}: {}", to_sub, ty, e), + ) + })?; + if is_ok { + Ok(()) + } else { + Err(polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::WebServicesError::NotEnoughMoney as i16, + format!("Not enough funds to apply debit of {} {:?}", to_sub, ty), + )) + } + } } struct GameEventSetterImpl { diff --git a/rc_core/src/persist/user/factory.rs b/rc_core/src/persist/user/factory.rs new file mode 100644 index 0000000..0e28e2f --- /dev/null +++ b/rc_core/src/persist/user/factory.rs @@ -0,0 +1,63 @@ +use super::account_json::UserData; + +#[async_trait::async_trait] +impl super::FactoryUser for UserData { + async fn prepare_factory_upload(&self, vehicle: super::VehicleUploadData) -> Result { + self.err_on_banned().await?; + let slot = self.load_garage_by_slot(vehicle.slot).await.map_err(|e| { + log::error!("Failed to retrieve vehicle slot {} for user_id {} (prepare_factory_upload): {}", vehicle.slot, self.account.id, e); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::WebServicesError::DatabaseError as i16, + format!("Failed to retrieve vehicle slot {} while preparing factory upload: {}", vehicle.slot, e), + ) + })?.ok_or_else(|| { + log::error!("Failed to find vehicle slot {} for user_id {} (prepare_factory_upload)", vehicle.slot, self.account.id); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::WebServicesError::InvalidRobot as i16, + format!("Failed to find vehicle slot {}", vehicle.slot), + ) + })?; + Ok(oj_rc_factory::VehicleUploadInfo { + name: vehicle.name, + description: vehicle.description, + thumbnail: vehicle.thumbnail, + added_by: self.account.public_id.clone(), + added_by_display_name: self.account.display_name.clone(), + cpu: slot.total_robot_cpu as u32, + total_robot_ranking: slot.total_robot_ranking as u32, + build_version: vehicle.version, + cube_data: slot.robot_data, + colour_data: slot.colour_data, + }) + } + + async fn rate_vehicle(&self, slot: i32, _combat: i32, _cosmetic: i32) -> Result, polariton_server::operations::SimpleOpError> { + self.err_on_banned().await?; + let vehicle = self.load_garage_by_slot(slot).await.map_err(|e| { + log::error!("Failed to retrieve vehicle slot {} for user_id {} (rate_vehicle): {}", slot, self.account.id, e); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::WebServicesError::DatabaseError as i16, + format!("Failed to retrieve vehicle slot {} while rating factory upload: {}", slot, e), + ) + })?.ok_or_else(|| { + log::error!("Failed to find vehicle slot {} for user_id {} (prepare_factory_upload)", slot, self.account.id); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::WebServicesError::InvalidRobot as i16, + format!("Failed to find vehicle slot {}", slot), + ) + })?; + self.db.update_garage(oj_rc_database::schema::garage::ActiveModel { + id: oj_rc_database::sea_orm::ActiveValue::Set(vehicle.id), + was_rated: oj_rc_database::sea_orm::ActiveValue::Set(true), + ..Default::default() + }).await + .map_err(|e| { + log::error!("Failed to save vehicle slot {} for user_id {} (rate_vehicle): {}", slot, self.account.id, e); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::WebServicesError::DatabaseError as i16, + format!("Failed to save vehicle slot {} while rating factory upload: {}", slot, e), + ) + })?; + Ok(vehicle.crf_id) + } +} diff --git a/rc_core/src/persist/user/initial_data.rs b/rc_core/src/persist/user/initial_data.rs index ad01a08..2baa2b9 100644 --- a/rc_core/src/persist/user/initial_data.rs +++ b/rc_core/src/persist/user/initial_data.rs @@ -165,7 +165,7 @@ pub fn default_new_slot(user_id: i32, slot: i32, bay_cpu: i32) -> oj_rc_database slot: oj_rc_database::sea_orm::ActiveValue::Set(slot), name: oj_rc_database::sea_orm::ActiveValue::Set(format!("Bay {}", slot)), crf_id: oj_rc_database::sea_orm::ActiveValue::Set(None), - was_rated: oj_rc_database::sea_orm::ActiveValue::Set(false), + was_rated: oj_rc_database::sea_orm::ActiveValue::Set(true), movement_categories: oj_rc_database::sea_orm::ActiveValue::Set("".to_owned()), uuid: oj_rc_database::sea_orm::ActiveValue::Set(super::uuid_sanitize(current_time)), thumbnail_version: oj_rc_database::sea_orm::ActiveValue::Set(0), diff --git a/rc_core/src/persist/user/mod.rs b/rc_core/src/persist/user/mod.rs index 9a6faaa..7b2bdb8 100644 --- a/rc_core/src/persist/user/mod.rs +++ b/rc_core/src/persist/user/mod.rs @@ -11,7 +11,7 @@ mod inventory; pub use inventory::{UnlockedParts, UnlockOverride}; 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, SingleplayerUser, PurchaseResult}; +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, PurchaseResult, FactoryUser}; pub mod intercom; pub use intercom::generate_token as generate_intercom_token; @@ -23,6 +23,7 @@ mod common; mod chat; mod social; mod singleplayer; +mod factory; pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key"; diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index 103799c..ddf606a 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -61,7 +61,7 @@ pub trait UserAuthenticator { } #[async_trait::async_trait] -pub trait User: ChatUser + SocialUser + LobbyUser + MultiplayerUser + SingleplayerUser + IntercomUser + CommonUser { +pub trait User: ChatUser + SocialUser + LobbyUser + MultiplayerUser + SingleplayerUser + IntercomUser + CommonUser + FactoryUser { async fn unlocked_parts(&self) -> Vec; async fn unlock_parts(&self, parts: &[u32]) -> Result<(), polariton_server::operations::SimpleOpError>; async fn selected_garage(&self) -> (String, u32); @@ -79,12 +79,12 @@ pub trait User: ChatUser + SocialUser + LobbyUser + MultiplayerUser + Singlep async fn set_slot_name(&self, slot: i32, name: String) -> Result<(), i16>; fn signup_date(&self) -> i64; async fn singleplayer_robots(&self, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, singleplayer_config: &crate::persist::config::SingleplayerConfig, cpu_counter: &crate::cubes::CpuListParser) -> Result, i16>; - async fn prepare_factory_upload(&self, vehicle: VehicleUploadData) -> Result; async fn last_seen(&self) -> Result; async fn get_avatar_info(&self) -> Result, i16>; async fn set_avatar_info(&self, info: AvatarInfo) -> Result<(), i16>; fn current_game_event_setter(&self) -> Box; async fn apply_purchase(&self, action: &crate::persist::config::ShopAction) -> Result; + async fn currency_debit(&self, ty: CurrencyType, to_sub: u64) -> Result<(), polariton_server::operations::SimpleOpError>; } #[async_trait::async_trait] @@ -132,6 +132,7 @@ pub struct VehicleData { pub colour_data: Vec, pub weapon_order: Vec, pub crf_id: Option, + pub was_rated: Option, } pub struct VehicleUploadData { @@ -419,6 +420,7 @@ pub trait CommonUser: Send + Sync { async fn currency(&self, ty: CurrencyType, op: CurrencyOp) -> Result; } +#[derive(Debug, Copy, Clone)] pub enum CurrencyType { Free, Paid, @@ -458,3 +460,9 @@ 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>; } + +#[async_trait::async_trait] +pub trait FactoryUser { + async fn prepare_factory_upload(&self, vehicle: VehicleUploadData) -> Result; + async fn rate_vehicle(&self, slot: i32, combat: i32, cosmetic: i32) -> Result, polariton_server::operations::SimpleOpError>; +} diff --git a/rc_services_room/src/operations/crf_purchase.rs b/rc_services_room/src/operations/crf_purchase.rs index 78adac5..1e86ce0 100644 --- a/rc_services_room/src/operations/crf_purchase.rs +++ b/rc_services_room/src/operations/crf_purchase.rs @@ -4,8 +4,8 @@ use oj_rc_factory::VehicleFactoryAdapter; const CODE: u8 = 166; const SLOT_PARAM_KEY: u8 = 43; // in; int -//const FREE_CURRENCY_COST_PARAM_KEY: u8 = 5; // in; int -//const PREMIUM_CURRENCY_COST_PARAM_KEY: u8 = 6; // in; int +const FREE_CURRENCY_COST_PARAM_KEY: u8 = 5; // in; int +const PAID_CURRENCY_COST_PARAM_KEY: u8 = 6; // in; int const FACTORY_ID_PARAM_KEY: u8 = 94; // in; int @@ -19,32 +19,44 @@ async fn do_handling(params: ParameterTable<()>, user: &crate::UserTy, factory: user_info.new_slot(None).await?.slot_i }; if let Some(Typed::Int(factory_id)) = params.remove(&FACTORY_ID_PARAM_KEY) { - // TODO charge for robot? - let vehicle = factory.vehicle(factory_id as _).await.map_err(|e| { - log::error!("Failed to retrieve vehicle {} (for copy-construct) from factory: {}", factory_id, e); - oj_rc_core::data::error_codes::WebServicesError::DatabaseError as i16 - })?; - if let Some((vehicle_to_copy, vehicle_meta)) = vehicle { - // parse cube data for weapon order - let mut cursor = std::io::Cursor::new(&vehicle_to_copy.cube_data); - let weapons = weapon_order.guess_weapons(&mut cursor); - // save to database - let to_save = oj_rc_core::persist::user::VehicleData { - name: Some(vehicle_meta.name), - slot, - robot_data: vehicle_to_copy.cube_data, - colour_data: vehicle_to_copy.colour_data, - weapon_order: weapons, - crf_id: Some(factory_id), - }; - user_info.save_slot(to_save, cpu_counter).await?; - } else { - log::warn!("Failed to retrieve (for copy-construct) non-existent factory vehicle {}", factory_id); - return Err(oj_rc_core::data::error_codes::WebServicesError::DatabaseError as i16); + if let Some(Typed::Int(free_cost)) = params.remove(&FREE_CURRENCY_COST_PARAM_KEY) { + if free_cost < 0 { + return Err(oj_rc_core::data::error_codes::WebServicesError::NotEnoughMoney as i16); + } + if let Some(Typed::Int(paid_cost)) = params.remove(&PAID_CURRENCY_COST_PARAM_KEY) { + if paid_cost < 0 { + return Err(oj_rc_core::data::error_codes::WebServicesError::NotEnoughMoney as i16); + } + let vehicle = factory.vehicle(factory_id as _).await.map_err(|e| { + log::error!("Failed to retrieve vehicle {} (for copy-construct) from factory: {}", factory_id, e); + oj_rc_core::data::error_codes::WebServicesError::DatabaseError as i16 + })?; + if let Some((vehicle_to_copy, vehicle_meta)) = vehicle { + // parse cube data for weapon order + let mut cursor = std::io::Cursor::new(&vehicle_to_copy.cube_data); + let weapons = weapon_order.guess_weapons(&mut cursor); + // save to database + let to_save = oj_rc_core::persist::user::VehicleData { + name: Some(vehicle_meta.name), + slot, + robot_data: vehicle_to_copy.cube_data, + colour_data: vehicle_to_copy.colour_data, + weapon_order: weapons, + crf_id: Some(factory_id), + was_rated: Some(false), + }; + user_info.save_slot(to_save, cpu_counter).await?; + user_info.currency_debit(oj_rc_core::persist::user::CurrencyType::Free, free_cost as _).await?; + if paid_cost > 0 { + user_info.currency_debit(oj_rc_core::persist::user::CurrencyType::Paid, paid_cost as _).await?; + } + } else { + log::warn!("Failed to retrieve (for copy-construct) non-existent factory vehicle {}", factory_id); + return Err(oj_rc_core::data::error_codes::WebServicesError::DatabaseError as i16); + } + } } - } - Ok(params.into()) } diff --git a/rc_services_room/src/operations/machine.rs b/rc_services_room/src/operations/machine.rs index 549d303..ee93dcc 100644 --- a/rc_services_room/src/operations/machine.rs +++ b/rc_services_room/src/operations/machine.rs @@ -81,6 +81,7 @@ async fn do_save(params: ParameterTable<()>, user: &crate::UserTy, cpu_counter: colour_data: colour_data.vec, weapon_order: weapon_order_filtered, crf_id: None, + was_rated: None, }; user_info.save_slot(vehicle_data, cpu_counter).await?; let mut params_out = std::collections::HashMap::with_capacity(1);