1
0
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:
NG (Graham)
2026-01-05 16:21:14 -05:00
parent 49255e7e7c
commit 437b7f9fdd
8 changed files with 166 additions and 18 deletions

View File

@@ -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()
}
}

View 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 _;
}
}
}

View File

@@ -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)
}
}

View File

@@ -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 {

View File

@@ -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(())
}
}

View File

@@ -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)]

View 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,
})
}

View File

@@ -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))