From 297a2ab2467a6def4a07658ceff42e4f6679e3b9 Mon Sep 17 00:00:00 2001 From: "NG (Graham)" Date: Sat, 31 May 2025 16:33:34 -0400 Subject: [PATCH] Add garage slot copy op functionality to fix #28 --- rc_core/src/persist/user/account_json.rs | 74 +++++++++++++++++++ rc_core/src/persist/user/traits.rs | 1 + rc_database/src/wrapper.rs | 5 +- .../src/operations/garage_slot_copy.rs | 35 +++++++++ rc_services_room/src/operations/mod.rs | 2 + 5 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 rc_services_room/src/operations/garage_slot_copy.rs diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index 0733c96..02719b7 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -601,6 +601,80 @@ impl super::User for UserData { }) } + async fn copy_slot(&self, slot: i32, into_slot: Option, append: &str) -> Result<(), i16> { + let slot_to_copy = self.db.garage_by_user_id_and_slot(self.account.id, slot as u32).await.map_err(|e| { + log::error!("Failed to retrieve vehicle slot {} to copy for user_id {}: {}", slot, self.account.id, e); + DATABASE_ERR + })?.ok_or_else(|| { + log::error!("No vehicle slot {} to copy for user_id {}", slot, self.account.id); + UNEXPECTED_ERR + })?; + let new_name = format!("{} {}", slot_to_copy.name, append); + let new_slot = if let Some(existing_slot) = into_slot { + log::info!("Copy slot {} -> {} as `{}`", slot, existing_slot, new_name); + if let Some(existing_g) = self.db.garage_by_user_id_and_slot(self.account.id, existing_slot as u32).await.map_err(|e| { + log::error!("Failed to retrieve vehicle slot {} for user_id {}: {}", slot, self.account.id, e); + DATABASE_ERR + })? { + use rc_database::sea_orm::IntoActiveModel; + let mut to_update = existing_g.into_active_model(); + // copy everything except id, user_id, creation_time, slot, was_rated, bay_cpu, mastery_level, selected + to_update.name = rc_database::sea_orm::ActiveValue::Set(new_name); + to_update.crf_id = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.crf_id); + to_update.movement_categories = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.movement_categories); + to_update.thumbnail_version = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.thumbnail_version); + to_update.total_robot_cpu = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.total_robot_cpu); + to_update.total_cosmetic_cpu = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.total_cosmetic_cpu); + to_update.total_robot_ranking = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.total_robot_ranking); + to_update.tutorial_robot = rc_database::sea_orm::ActiveValue::Set(false); + to_update.starter_robot_index = rc_database::sea_orm::ActiveValue::Set(None); + to_update.control_type = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.control_type); + to_update.vertical_strafing = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.vertical_strafing); + to_update.sideways_driving = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.sideways_driving); + to_update.tracks_turn_on_spot = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.tracks_turn_on_spot); + to_update.bay_skin_id = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.bay_skin_id); + to_update.death_animation_id = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.death_animation_id); + to_update.spawn_animation_id = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.spawn_animation_id); + to_update.weapon_order = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.weapon_order); + to_update.robot_data = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.robot_data); + to_update.colour_data = rc_database::sea_orm::ActiveValue::Set(slot_to_copy.colour_data); + self.db.update_garage(to_update).await.map_err(|e| { + log::error!("Failed to update garage slot {} copied from {} for user_id {}: {}", existing_slot, slot, self.account.id, e); + DATABASE_ERR + })?; + existing_slot as u32 + } else { + log::warn!("No existing vehicle slot {} for user_id {}, copying to new slot", slot, self.account.id); + return >::copy_slot(self, slot, None, append).await; + } + } else { + log::info!("Copy slot {} -> as `{}`", slot, new_name); + use rc_database::sea_orm::IntoActiveModel; + let mut to_insert = slot_to_copy.into_active_model(); + let max_slot = self.db.garage_max_slot_by_user_id(self.account.id).await.map_err(|e| { + log::error!("Failed to get max garage slot during copy for user_id {}: {}", self.account.id, e); + DATABASE_ERR + })?; + let now = chrono::Utc::now().timestamp(); + let uuid = super::uuid_sanitize(now); + to_insert.id = Default::default(); + to_insert.creation_time = rc_database::sea_orm::ActiveValue::Set(now); + to_insert.uuid = rc_database::sea_orm::ActiveValue::Set(uuid); + to_insert.slot = rc_database::sea_orm::ActiveValue::Set(max_slot + 1); + to_insert.name = rc_database::sea_orm::ActiveValue::Set(new_name); + self.db.insert_garage(to_insert).await.map_err(|e| { + log::error!("Failed to insert garage slot copied from {} for user_id {}: {}", slot, self.account.id, e); + DATABASE_ERR + })?; + max_slot + 1 + }; + self.db.update_garage_selected_by_user_id_and_slot(self.account.id, new_slot).await.map_err(|e| { + log::error!("Failed to select copied garage slot {} for user_id {}: {}", new_slot, self.account.id, e); + DATABASE_ERR + })?; + Ok(()) + } + async fn upgrade_slot(&self, increments: i32) -> Result, i16> { self.err_on_banned().await?; if increments <= 0 { diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index d51b620..2ff2d52 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -68,6 +68,7 @@ pub trait User: ChatUser { async fn save_slot(&self, vehicle: VehicleData) -> Result<(), i16>; async fn save_slot_order(&self, slots: Vec) -> Result<(), i16>; async fn new_slot(&self, reset_slot: Option) -> Result, i16>; + async fn copy_slot(&self, slot: i32, into_slot: Option, append: &str) -> Result<(), i16>; async fn upgrade_slot(&self, increments: i32) -> Result, i16>; async fn save_slot_controls(&self, controls: ControlData) -> Result<(), i16>; async fn save_slot_customisations(&self, customs: CustomisationData) -> Result<(), i16>; diff --git a/rc_database/src/wrapper.rs b/rc_database/src/wrapper.rs index 1392ccb..eb2ee41 100644 --- a/rc_database/src/wrapper.rs +++ b/rc_database/src/wrapper.rs @@ -180,12 +180,11 @@ impl Database { entity.insert(&self.orm).await } - /*pub async fn update_garage(&self, entity: crate::schema::garage::ActiveModel, id: u32) -> Result { + pub async fn update_garage(&self, entity: crate::schema::garage::ActiveModel) -> Result { crate::schema::garage::Entity::update(entity) - .filter(crate::schema::garage::Column::Id.eq(id)) .exec(&self.orm) .await - }*/ + } pub async fn update_garage_by_user_id_and_slot(&self, mut entity: crate::schema::garage::ActiveModel, user_id: u32, slot: u32) -> Result, sea_orm::DbErr> { let id_opt = crate::schema::garage::Entity::find() diff --git a/rc_services_room/src/operations/garage_slot_copy.rs b/rc_services_room/src/operations/garage_slot_copy.rs new file mode 100644 index 0000000..920077a --- /dev/null +++ b/rc_services_room/src/operations/garage_slot_copy.rs @@ -0,0 +1,35 @@ +use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl}; +use polariton::operation::{ParameterTable, Typed}; + +const CODE: u8 = 69; // nice + +const ORIGINAL_SLOT_PARAM_KEY: u8 = 43; // int; in +const REUSE_SLOT_PARAM_KEY: u8 = 8; // int (optional); in +const COPY_STR_PARAM_KEY: u8 = 213; // str; in + +pub(super) struct GarageSlotCopier; + +#[async_trait::async_trait] +impl SimpleOperation for GarageSlotCopier { + type User = crate::UserTy; + const CODE: u8 = CODE; + + async fn handle(&self, params: ParameterTable, user: &Self::User) -> Result, SimpleOpError> { + let mut params = params.to_dict(); + if let Some(Typed::Int(slot)) = params.remove(&ORIGINAL_SLOT_PARAM_KEY) { + if let Some(Typed::Str(copy_str)) = params.remove(©_STR_PARAM_KEY) { + let user_info = user.user()?; + if let Some(Typed::Int(reuse_slot)) = params.remove(&REUSE_SLOT_PARAM_KEY) { + user_info.copy_slot(slot, Some(reuse_slot), ©_str.string).await?; + } else { + user_info.copy_slot(slot, None, ©_str.string).await?; + } + } + } + Ok(params.into()) + } +} + +pub(super) fn garage_slot_copy_provider() -> SimpleOpImpl { + SimpleOpImpl::new(GarageSlotCopier) +} diff --git a/rc_services_room/src/operations/mod.rs b/rc_services_room/src/operations/mod.rs index 188dcdb..f5c9bb9 100644 --- a/rc_services_room/src/operations/mod.rs +++ b/rc_services_room/src/operations/mod.rs @@ -96,6 +96,7 @@ mod avatar_set; mod garage_slot_controls; mod garage_slot_set_customisations; mod garage_slot_name; +mod garage_slot_copy; use polariton_server::operations::OperationsHandler; @@ -215,4 +216,5 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler .add(garage_slot_controls::garage_slot_controls_provider()) .add(garage_slot_set_customisations::garage_slot_customisation_provider()) .add(garage_slot_name::garage_slot_rename_provider()) + .add(garage_slot_copy::garage_slot_copy_provider()) }