mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Implement factory offset saving
This commit is contained in:
@@ -9,12 +9,16 @@ pub use cpu_count::CpuListParser;
|
||||
mod locations_of;
|
||||
pub use locations_of::{CubeLocationsParser, CubeLocationInfo};
|
||||
|
||||
mod offsetter;
|
||||
pub use offsetter::OffsetParser;
|
||||
|
||||
//pub mod prefabs;
|
||||
|
||||
pub struct CubeParsers {
|
||||
weapon_list: std::sync::Arc<WeaponListParser>,
|
||||
cpu_counter: std::sync::Arc<CpuListParser>,
|
||||
locations: std::sync::Arc<CubeLocationsParser>,
|
||||
offset: std::sync::Arc<OffsetParser>,
|
||||
}
|
||||
|
||||
impl CubeParsers {
|
||||
@@ -24,6 +28,7 @@ impl CubeParsers {
|
||||
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())),
|
||||
offset: std::sync::Arc::new(OffsetParser::with_cubes(cubes.values())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,4 +43,8 @@ impl CubeParsers {
|
||||
pub fn locations_of(&self) -> std::sync::Arc<CubeLocationsParser> {
|
||||
self.locations.clone()
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> std::sync::Arc<OffsetParser> {
|
||||
self.offset.clone()
|
||||
}
|
||||
}
|
||||
|
||||
26
rc_core/src/cubes/offsetter.rs
Normal file
26
rc_core/src/cubes/offsetter.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
pub struct OffsetParser;
|
||||
|
||||
impl OffsetParser {
|
||||
pub fn with_cubes<'a, I: std::iter::Iterator<Item=&'a crate::persist::Cube>>(_iter: I) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// offset is (x, y, z)
|
||||
pub fn offset_inplace_by(&self, cubes: &mut [u8], colours: &mut [u8], offset: (i16, i16, i16)) {
|
||||
// this assumes cubes and colours are valid and length-prefixed
|
||||
let cubes = &mut cubes[4..];
|
||||
let colours = &mut colours[4..];
|
||||
for cube in cubes.chunks_mut(8) {
|
||||
// bytes 4, 5, 6 are x, y, z (respectively)
|
||||
cube[4] = (cube[4] as i16 + offset.0) as _;
|
||||
cube[5] = (cube[5] as i16 + offset.1) as _;
|
||||
cube[6] = (cube[6] as i16 + offset.2) as _;
|
||||
}
|
||||
for colour in colours.chunks_mut(4) {
|
||||
// last 3 bytes are x, y, z (respectively)
|
||||
colour[1] = (colour[1] as i16 + offset.0) as _;
|
||||
colour[2] = (colour[2] as i16 + offset.1) as _;
|
||||
colour[3] = (colour[3] as i16 + offset.2) as _;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,4 +85,19 @@ impl Colour {
|
||||
}
|
||||
Ok(cubes)
|
||||
}
|
||||
|
||||
pub fn dump(&self, w: &mut dyn std::io::Write) -> std::io::Result<usize> {
|
||||
w.write_all(&[self.colour, self.x, self.y, self.z])?;
|
||||
Ok(4)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,14 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
|
||||
Self::None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_vehicle(&self, id: i32, cube_data: Option<Vec<u8>>, colour_data: Option<Vec<u8>>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match self {
|
||||
Self::Arc(x) => x.update_vehicle(id, cube_data, colour_data).await,
|
||||
Self::Custom(x) => x.update_vehicle(id, cube_data, colour_data).await,
|
||||
Self::None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Factory {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, ActiveModelTrait, Set, TransactionTrait};
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, PaginatorTrait, QueryFilter, QueryOrder, Set, TransactionTrait};
|
||||
use base64::Engine;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -46,6 +46,23 @@ impl ArcAdapter {
|
||||
fn parse_cube_amounts(&self, cube_amounts: &str) -> std::collections::HashMap<u32, u32> {
|
||||
serde_json::from_str(cube_amounts).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn calculate_cube_amounts(&self, data: &[u8]) -> String {
|
||||
let mut counts: HashMap<u32, u32> = HashMap::new();
|
||||
let data = &data[4..];
|
||||
for chunk in data.chunks_exact(8) {
|
||||
let id_bytes: [u8; 4] = chunk[0..4].try_into().unwrap();
|
||||
let part_id = u32::from_le_bytes(id_bytes);
|
||||
|
||||
*counts.entry(part_id).or_insert(0) += 1;
|
||||
}
|
||||
let mut str_map: HashMap<String, u32> = HashMap::new();
|
||||
for (k, v) in counts {
|
||||
str_map.insert(k.to_string(), v);
|
||||
}
|
||||
|
||||
serde_json::to_string(&str_map).unwrap_or_else(|_| "{}".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -208,22 +225,7 @@ impl crate::VehicleFactoryAdapter for ArcAdapter {
|
||||
}
|
||||
let transaction = self.orm.begin().await?;
|
||||
|
||||
let cube_amounts = {
|
||||
let mut counts: HashMap<u32, u32> = HashMap::new();
|
||||
let data = &vehicle.cube_data[4..];
|
||||
for chunk in data.chunks_exact(8) {
|
||||
let id_bytes: [u8; 4] = chunk[0..4].try_into().unwrap();
|
||||
let part_id = u32::from_le_bytes(id_bytes);
|
||||
|
||||
*counts.entry(part_id).or_insert(0) += 1;
|
||||
}
|
||||
let mut str_map: HashMap<String, u32> = HashMap::new();
|
||||
for (k, v) in counts {
|
||||
str_map.insert(k.to_string(), v);
|
||||
}
|
||||
|
||||
serde_json::to_string(&str_map).unwrap_or_else(|_| "{}".to_string())
|
||||
};
|
||||
let cube_amounts = self.calculate_cube_amounts(&vehicle.cube_data);
|
||||
let cubes = super::entities::robot_cubes::ActiveModel {
|
||||
id: sea_orm::ActiveValue::NotSet,
|
||||
cube_data: Set(base64::prelude::BASE64_STANDARD.encode(&vehicle.cube_data)),
|
||||
@@ -299,4 +301,24 @@ impl crate::VehicleFactoryAdapter for ArcAdapter {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_vehicle(&self, id: i32, cube_data: Option<Vec<u8>>, colour_data: Option<Vec<u8>>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if self.is_readonly {
|
||||
//return Ok(()); // this soft-locks the client into an infinite reload cycle
|
||||
return Err("Unsupported: cannot update factory in read-only mode".into()); // this works though
|
||||
}
|
||||
// TODO this should probably be a transaction
|
||||
if let Some(cubes) = super::entities::robot_cubes::Entity::find_by_id(id as u32).one(&self.orm).await? {
|
||||
let mut to_update = cubes.into_active_model();
|
||||
if let Some(cube_data) = cube_data {
|
||||
to_update.cube_data = Set(base64::prelude::BASE64_STANDARD.encode(&cube_data));
|
||||
to_update.cube_amounts = Set(self.calculate_cube_amounts(&cube_data));
|
||||
}
|
||||
if let Some(colour_data) = colour_data {
|
||||
to_update.colour_data = Set(base64::prelude::BASE64_STANDARD.encode(&colour_data));
|
||||
}
|
||||
to_update.update(&self.orm).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ pub trait VehicleFactoryAdapter: Send + Sync + 'static {
|
||||
async fn rate_vehicle(&self, id: i32, combat: i32, cosmetic: i32) -> Result<(), Box<dyn std::error::Error>>;
|
||||
/// Just update any purchase trackers
|
||||
async fn purchase(&self, id: i32) -> Result<(), Box<dyn std::error::Error>>;
|
||||
async fn update_vehicle(&self, id: i32, cube_data: Option<Vec<u8>>, colour_data: Option<Vec<u8>>) -> Result<(), Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
66
rc_services_room/src/operations/crf_shift_vehicle.rs
Normal file
66
rc_services_room/src/operations/crf_shift_vehicle.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed};
|
||||
use oj_rc_factory::VehicleFactoryAdapter;
|
||||
|
||||
const CODE: u8 = 97;
|
||||
|
||||
const ID_PARAM_KEY: u8 = 45; // int; in
|
||||
const OFFSET_X_PARAM_KEY: u8 = 104; // int; in
|
||||
const OFFSET_Z_PARAM_KEY: u8 = 105; // int; in
|
||||
const EXPECTED_FIRST_X_PARAM_KEY: u8 = 106; // int; in
|
||||
const EXPECTED_FIRST_Y_PARAM_KEY: u8 = 107; // int; in
|
||||
const EXPECTED_FIRST_Z_PARAM_KEY: u8 = 108; // int; in
|
||||
|
||||
pub(super) struct FactoryVehicleOffsetApplier {
|
||||
factory: std::sync::Arc<oj_rc_core::factory::Factory>,
|
||||
offsetter: std::sync::Arc<oj_rc_core::cubes::OffsetParser>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl <C: Send + 'static> SimpleOperation<C> for FactoryVehicleOffsetApplier {
|
||||
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::Int(factory_id)) = params.remove(&ID_PARAM_KEY) {
|
||||
if let Some(Typed::Int(offset_x)) = params.remove(&OFFSET_X_PARAM_KEY) {
|
||||
if let Some(Typed::Int(offset_z)) = params.remove(&OFFSET_Z_PARAM_KEY) {
|
||||
if let Some(Typed::Int(expected_first_x)) = params.remove(&EXPECTED_FIRST_X_PARAM_KEY) {
|
||||
if let Some(Typed::Int(expected_first_y)) = params.remove(&EXPECTED_FIRST_Y_PARAM_KEY) {
|
||||
if let Some(Typed::Int(expected_first_z)) = params.remove(&EXPECTED_FIRST_Z_PARAM_KEY) {
|
||||
let _ = user.user()?; // double-check they're authenticated
|
||||
log::debug!("Factory vehicle {} has first expected cube at (x:{}, y:{}, z:{}) offset (x:{}, z:{})", factory_id, expected_first_x, expected_first_y, expected_first_z, offset_x, offset_z);
|
||||
let vehicle_opt = self.factory.vehicle(factory_id).await.map_err(|e| {
|
||||
log::error!("Failed to retrieve vehicle {} (for offset) from factory: {}", factory_id, e);
|
||||
SimpleOpError::with_message(
|
||||
oj_rc_core::data::error_codes::WebServicesError::DatabaseError as i16,
|
||||
format!("Failed to retrieve vehicle (for offset) from factory: {}", e)
|
||||
)
|
||||
})?;
|
||||
if let Some((mut vehicle, _)) = vehicle_opt {
|
||||
self.offsetter.offset_inplace_by(&mut vehicle.cube_data, &mut vehicle.colour_data, (offset_x as _, 0, offset_z as _));
|
||||
self.factory.update_vehicle(factory_id, Some(vehicle.cube_data), Some(vehicle.colour_data)).await
|
||||
.map_err(|e| {
|
||||
log::error!("Failed to update factory vehicle {} (for offset): {}", factory_id, e);
|
||||
SimpleOpError::with_message(
|
||||
oj_rc_core::data::error_codes::WebServicesError::DatabaseError as i16,
|
||||
format!("Failed to retrieve factory vehicle (for offset): {}", e)
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ParameterTable::with_capacity(1))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn factory_offset_provider<C: Send + 'static>(factory: &std::sync::Arc<oj_rc_core::factory::Factory>, offsetter: std::sync::Arc<oj_rc_core::cubes::OffsetParser>) -> SimpleOpImpl<C, crate::UserTy, FactoryVehicleOffsetApplier> {
|
||||
SimpleOpImpl::new(FactoryVehicleOffsetApplier {
|
||||
factory: factory.to_owned(),
|
||||
offsetter,
|
||||
})
|
||||
}
|
||||
@@ -102,6 +102,7 @@ mod campaign_save_result;
|
||||
mod item_shop_purchase;
|
||||
mod code_redeem;
|
||||
mod crf_rate_vehicle;
|
||||
mod crf_shift_vehicle;
|
||||
|
||||
use polariton_server::operations::OperationsHandler;
|
||||
|
||||
@@ -224,7 +225,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
||||
.add(garage_slot_copy::garage_slot_copy_provider())
|
||||
.add(polariton_server::operations::Ack::<12, _>::default()) // TODO handle UpdatePlayerDailyQuestProgressRequest instead of ignoring it
|
||||
.add(crf_rate_vehicle::crf_rating_provider(&init_ctx.factory))
|
||||
//.add(polariton_server::operations::Ack::<97, _>::default()) // TODO handle UpdateShopRobotOffsetRequest instead of ignoring it (this seems to break newly-uploaded vehicles for now)
|
||||
.add(crf_shift_vehicle::factory_offset_provider(&init_ctx.factory, init_ctx.parsers.offset()))
|
||||
.add(steam_promo::steam_promos_provider())
|
||||
.add(item_shop_purchase::item_purchase_provider(&init_ctx.cubes))
|
||||
.add(code_redeem::code_redeem_provider(&init_ctx.cubes))
|
||||
|
||||
Reference in New Issue
Block a user