1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Add factory upload support to trait, make client handle rejections gracefully #6

This commit is contained in:
NG (Graham)
2025-05-18 21:52:03 -04:00
parent fd75dba80e
commit a0dba39475
12 changed files with 196 additions and 12 deletions

View File

@@ -1,249 +0,0 @@
use std::i64;
#[allow(dead_code)]
pub struct ShopItemListFilters {
pub page: u32,
pub page_size: u32,
pub weapon_filter: i32,
pub movement_filter: i32,
pub weapon_groups: String,
pub movement_groups: String,
pub player: bool,
pub sort_mode: i32,
pub min_cpu: i32,
pub max_cpu: i32,
pub min_robot_ranking: i32,
pub max_robot_ranking: i32,
pub text: String,
pub text_search_field: i32,
pub show_featured: bool,
pub show_hidden: bool, // dev-only?
pub no_filters: bool,
}
impl ShopItemListFilters {
pub fn parse(r: &mut dyn std::io::Read) -> std::io::Result<Self> {
Ok(Self {
page: read_u32(r)?,
page_size: read_u32(r)?,
weapon_filter: read_i32(r)?,
movement_filter: read_i32(r)?,
weapon_groups: rc_core::data::read_str_for_binwriter(r)?,
movement_groups: rc_core::data::read_str_for_binwriter(r)?,
player: read_bool(r)?,
sort_mode: read_i32(r)?,
min_cpu: read_i32(r)?,
max_cpu: read_i32(r)?,
min_robot_ranking: read_i32(r)?,
max_robot_ranking: read_i32(r)?,
text: rc_core::data::read_str_for_binwriter(r)?,
text_search_field: read_i32(r)?,
show_featured: read_bool(r)?,
show_hidden: read_bool(r)?,
no_filters: read_bool(r)?,
})
}
pub fn into_core(self) -> libfj::robocraft::ListQuery {
let weapon_groups = split_u32(&self.weapon_groups);
let movement_groups = split_u32(&self.movement_groups);
libfj::robocraft::ListQuery {
page: self.page as _,
page_size: self.page_size as _,
order: libfj::robocraft::FactoryOrderType::try_from(self.sort_mode as u8).unwrap_or(libfj::robocraft::FactoryOrderType::Suggested),
player_filter: self.player,
movement_filter: movement_groups.clone(),
movement_category_filter: movement_groups,
weapon_filter: weapon_groups.clone(),
weapon_category_filter: weapon_groups,
minimum_cpu: if self.min_cpu <= 0 { 0 } else { self.min_cpu as _ },
maximum_cpu: if self.max_cpu <= 0 { usize::MAX } else { self.max_cpu as _ },
text_filter: self.text,
text_search_field: libfj::robocraft::FactoryTextSearchField::try_from(self.text_search_field as u8).unwrap_or(libfj::robocraft::FactoryTextSearchField::All),
buyable: true,
prepend_featured_robot: true,
featured_only: self.show_featured,
default_page: self.no_filters,
}
}
}
pub struct ItemResult {
pub id: i32,
pub name: String,
pub description: String,
pub thumbnail: String,
pub style_rating: f64,
pub combat_rating: f64,
pub cpu: i32,
pub total_robot_ranking: i32,
pub expiry_date: i64, // ticks until expiry (from now)
pub buyable: bool,
pub added_by: String,
pub added_by_display_name: String,
pub added_date: i64, // tick until added (from now -- probably negative)
pub rent_count: i32,
pub buy_count: i32,
pub featured: bool,
pub banner_message: String,
pub cube_counts: Vec<(u32, u32)>,
}
// a tick is 100ns
impl ItemResult {
pub fn dump(&self, w: &mut dyn std::io::Write) -> std::io::Result<usize> {
let mut total_len = write_i32(w, self.id)?;
total_len += rc_core::data::write_str_for_binreader(&self.name, w)?;
total_len += rc_core::data::write_str_for_binreader(&self.description, w)?;
total_len += rc_core::data::write_str_for_binreader(&self.thumbnail, w)?;
total_len += write_f64(w, self.style_rating)?;
total_len += write_f64(w, self.combat_rating)?;
total_len += write_i32(w, self.cpu)?;
total_len += write_i32(w, self.total_robot_ranking)?;
total_len += write_i64(w, self.expiry_date)?;
total_len += write_bool(w, self.buyable)?;
total_len += rc_core::data::write_str_for_binreader(&self.added_by, w)?;
total_len += rc_core::data::write_str_for_binreader(&self.added_by_display_name, w)?;
total_len += write_i64(w, self.added_date)?;
total_len += write_i32(w, self.rent_count)?;
total_len += write_i32(w, self.buy_count)?;
total_len += write_bool(w, self.featured)?;
total_len += rc_core::data::write_str_for_binreader(&self.banner_message, w)?;
total_len += write_i32(w, self.cube_counts.len() as i32)?;
for (key, val) in self.cube_counts.iter() {
total_len += write_u32(w, *key)?;
total_len += write_u32(w, *val)?;
}
Ok(total_len)
}
pub fn dump_many(items: &[Self], w: &mut dyn std::io::Write) -> std::io::Result<usize> {
let mut total_len = write_i32(w, items.len() as _)?;
for item in items.iter() {
total_len += item.dump(w)?;
}
Ok(total_len)
}
pub fn as_transmissible<C>(items: &[Self]) -> polariton::operation::Typed<C> {
let mut buf = Vec::new();
Self::dump_many(items, &mut buf).unwrap();
polariton::operation::Typed::Bytes(buf.into())
}
}
fn ticks_from_now(time: &chrono::DateTime<chrono::Utc>) -> i64 {
let dur = time.signed_duration_since(chrono::offset::Utc::now());
dur.num_nanoseconds()
.map(|x| x/100)
.unwrap_or_else(|| dur.num_milliseconds() * 1_000_000 / 100)
}
impl std::convert::From<rc_factory::VehicleQueryInfo> for ItemResult {
fn from(value: rc_factory::VehicleQueryInfo) -> Self {
Self {
id: value.id,
name: value.name,
description: value.description,
thumbnail: value.thumbnail,
style_rating: value.cosmetic_rating,
combat_rating: value.combat_rating,
cpu: value.cpu as i32,
total_robot_ranking: value.total_robot_ranking as i32,
//expiry_date: ticks_from_now(&value.expiry_date),
expiry_date: i32::MAX as _,
buyable: value.buyable,
added_by: value.added_by,
added_by_display_name: value.added_by_display_name,
added_date: ticks_from_now(&value.added_date),
rent_count: value.rent_count as _,
buy_count: value.buy_count as _,
featured: value.featured,
banner_message: value.banner_message.unwrap_or_default(),
cube_counts: value.cube_amounts.into_iter().collect(),
}
}
}
pub struct ItemData {
pub index: i32,
pub cube_data: Vec<u8>,
pub colour_data: Vec<u8>,
}
impl ItemData {
pub fn as_transmissible<C>(&self) -> polariton::operation::Typed<C> {
polariton::operation::Typed::HashMap(vec![
(polariton::operation::Typed::Str("itemIndex".into()), polariton::operation::Typed::Int(self.index)),
(polariton::operation::Typed::Str("cubeData".into()), polariton::operation::Typed::Bytes(self.cube_data.clone().into())),
(polariton::operation::Typed::Str("colourData".into()), polariton::operation::Typed::Bytes(self.colour_data.clone().into())),
].into())
}
}
impl std::convert::From<rc_factory::VehicleInfo> for ItemData {
fn from(value: rc_factory::VehicleInfo) -> Self {
Self {
index: value.id,
cube_data: value.cube_data,
colour_data: value.colour_data,
}
}
}
#[inline]
fn read_i32(r: &mut dyn std::io::Read) -> std::io::Result<i32> {
let mut buf = [0u8; 4];
r.read_exact(&mut buf)?;
Ok(i32::from_le_bytes(buf))
}
#[inline]
fn write_i32(w: &mut dyn std::io::Write, num: i32) -> std::io::Result<usize> {
w.write_all(&num.to_le_bytes())?;
Ok(4)
}
#[inline]
fn write_i64(w: &mut dyn std::io::Write, num: i64) -> std::io::Result<usize> {
w.write_all(&num.to_le_bytes())?;
Ok(8)
}
#[inline]
fn write_f64(w: &mut dyn std::io::Write, num: f64) -> std::io::Result<usize> {
w.write_all(&num.to_le_bytes())?;
Ok(8)
}
#[inline]
fn read_u32(r: &mut dyn std::io::Read) -> std::io::Result<u32> {
let mut buf = [0u8; 4];
r.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
#[inline]
fn write_u32(w: &mut dyn std::io::Write, num: u32) -> std::io::Result<usize> {
w.write_all(&num.to_le_bytes())?;
Ok(4)
}
#[inline]
fn read_bool(r: &mut dyn std::io::Read) -> std::io::Result<bool> {
let mut buf = [0u8; 1];
r.read_exact(&mut buf)?;
Ok(buf[0] != 0)
}
#[inline]
fn write_bool(w: &mut dyn std::io::Write, b: bool) -> std::io::Result<usize> {
w.write_all(&[b as u8])?;
Ok(1)
}
#[inline]
fn split_u32(s: &str) -> Vec<u32> {
s.split(',').filter_map(|x| x.parse().ok()).collect()
}

View File

@@ -28,4 +28,4 @@ pub use rc_core::data::error_codes;
//pub use rc_core::data::game_mode;
pub mod score_multipliers;
//pub use rc_core::data::campaign;
pub mod crf;
pub use rc_core::data::crf;

View File

@@ -0,0 +1,51 @@
use polariton_server::operations::{Operation, OperationCode};
use polariton::operation::{Typed, ParameterTable};
use rc_factory::VehicleFactoryAdapter;
const CODE: u8 = 96;
const DATA_PARAM_KEY: u8 = 101; // in; dict
const VERSION_PARAM_KEY: u8 = 99; // in; str
const SUCCESS_PARAM_KEY: u8 = 103; // out; bool
async fn do_handling(params: ParameterTable<()>, user: &crate::UserTy, factory: &std::sync::Arc<rc_core::factory::Factory>) -> Result<ParameterTable, i16> {
let mut params = params.to_dict();
if let Some(Typed::Str(version)) = params.remove(&VERSION_PARAM_KEY) {
if let Some(Typed::Dict(data)) = params.remove(&DATA_PARAM_KEY) {
let upload_info = crate::data::crf::UploadData::from_transmissibles(version.string, data)?;
let user_info = user.user()?;
let prepared = user_info.prepare_factory_upload(upload_info.into_core()).await?;
let success = factory.upload(prepared).await.map_err(|e| {
log::error!("Failed to upload to factory: {}", e);
rc_core::data::error_codes::WebServicesError::UnexpectedError as i16
})?;
params.insert(SUCCESS_PARAM_KEY, Typed::Bool(success));
}
}
Ok(params.into())
}
pub struct CrfUploadProvider {
factory: std::sync::Arc<rc_core::factory::Factory>,
}
#[async_trait::async_trait]
impl Operation<()> for CrfUploadProvider {
type User = crate::UserTy;
async fn handle_async(&self, params: polariton::operation::ParameterTable<()>, user: &Self::User) -> polariton::operation::OperationResponse<()> {
polariton_server::operations::result_to_op_resp::<CODE, ()>(do_handling(params, user, &self.factory).await)
}
}
impl OperationCode for CrfUploadProvider {
fn op_code() -> u8 {
CODE
}
}
pub(super) fn crf_upload_provider(factory: &std::sync::Arc<rc_core::factory::Factory>) -> CrfUploadProvider {
CrfUploadProvider {
factory: factory.to_owned(),
}
}

View File

@@ -90,6 +90,7 @@ mod crf_earnings;
mod crf_list_query;
mod crf_vehicle_data;
mod crf_purchase;
mod crf_upload;
use polariton_server::operations::OperationsHandler;
@@ -202,4 +203,5 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
.add(crf_list_query::crf_item_list_query_provider(&init_ctx.factory))
.add(crf_vehicle_data::crf_item_data_provider(&init_ctx.factory))
.add(crf_purchase::crf_copy_to_bay_provider(&init_ctx.factory, init_ctx.parsers.weapon_order()))
.add(crf_upload::crf_upload_provider(&init_ctx.factory))
}