mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add minimal robot factory functionality and arc archive support for #6
This commit is contained in:
15
Cargo.lock
generated
15
Cargo.lock
generated
@@ -2589,6 +2589,7 @@ dependencies = [
|
|||||||
"polariton",
|
"polariton",
|
||||||
"polariton_server",
|
"polariton_server",
|
||||||
"rc_database",
|
"rc_database",
|
||||||
|
"rc_factory",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -2603,6 +2604,18 @@ dependencies = [
|
|||||||
"sea-orm-migration",
|
"sea-orm-migration",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rc_factory"
|
||||||
|
version = "0.2.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"chrono",
|
||||||
|
"libfj",
|
||||||
|
"log",
|
||||||
|
"sea-orm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rc_microtransactions"
|
name = "rc_microtransactions"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -2636,12 +2649,14 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"hex",
|
"hex",
|
||||||
|
"libfj",
|
||||||
"log",
|
"log",
|
||||||
"polariton",
|
"polariton",
|
||||||
"polariton_auth",
|
"polariton_auth",
|
||||||
"polariton_server",
|
"polariton_server",
|
||||||
"rand 0.9.0",
|
"rand 0.9.0",
|
||||||
"rc_core",
|
"rc_core",
|
||||||
|
"rc_factory",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ members = [
|
|||||||
"rc_social", "rc_social_room",
|
"rc_social", "rc_social_room",
|
||||||
"rc_chat", "rc_chat_room",
|
"rc_chat", "rc_chat_room",
|
||||||
"rc_singleplayer", "rc_singleplayer_room",
|
"rc_singleplayer", "rc_singleplayer_room",
|
||||||
"rc_core", "rc_database",
|
"rc_core", "rc_database", "rc_factory",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
@@ -34,3 +34,4 @@ polariton_server = { version = "0.2", path = "../polariton/server", features = [
|
|||||||
serde = { version = "1.0", features = [ "derive" ] }
|
serde = { version = "1.0", features = [ "derive" ] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
|
chrono = "0.4"
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ The current PC's MAC address is also sent to the server (this is a Robocraft cli
|
|||||||
|
|
||||||
#### Extra crates
|
#### Extra crates
|
||||||
|
|
||||||
There are two crates that are expected to be in the same folder parent folder as this project, since they may change as a result of OpenJam server development.
|
There are two crates that are expected to be in the same parent folder as this project, since they may change as a result of OpenJam server development.
|
||||||
|
|
||||||
- https://git.ngram.ca/OpenJam/libfj (Public-facing FreeJam API and data structures)
|
- https://git.ngram.ca/OpenJam/libfj (Public-facing FreeJam API and data structures)
|
||||||
- https://git.ngram.ca/OpenJam/polariton (Photon Unity Network packet and server)
|
- https://git.ngram.ca/OpenJam/polariton (Photon Unity Network packet and server)
|
||||||
|
|||||||
@@ -14423,6 +14423,12 @@
|
|||||||
"jam_club"
|
"jam_club"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"factory": {
|
||||||
|
"adapter": {
|
||||||
|
"variant": "Arc",
|
||||||
|
"uri": "sqlite:../../arc/rc_archive.db?mode=ro"
|
||||||
|
}
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"banners": [
|
"banners": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,3 +24,4 @@ jsonwebtoken = "9"
|
|||||||
argon2 = { version = "0.5", features = [ "std" ] }
|
argon2 = { version = "0.5", features = [ "std" ] }
|
||||||
|
|
||||||
rc_database = { version = "0.2", path = "../rc_database" }
|
rc_database = { version = "0.2", path = "../rc_database" }
|
||||||
|
rc_factory = { version = "0.2", path = "../rc_factory" }
|
||||||
|
|||||||
@@ -28,6 +28,22 @@ pub fn encode_7_bit_i32(mut src: i32) -> Vec<u8> {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decode_7_bit_i32(reader: &mut dyn std::io::Read) -> std::io::Result<i32> {
|
||||||
|
let mut buf = [0u8; 1];
|
||||||
|
let mut out: i32 = 0;
|
||||||
|
for _ in 0..5 {
|
||||||
|
reader.read_exact(&mut buf)?;
|
||||||
|
let byte = buf[0];
|
||||||
|
let has_more = byte & 0x80;
|
||||||
|
let number = byte & 0x7F;
|
||||||
|
out = (out << 7) | (number as i32);
|
||||||
|
if has_more == 0 {
|
||||||
|
return Ok(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_str_for_binreader(s: &str, writer: &mut dyn std::io::Write) -> std::io::Result<usize> {
|
pub fn write_str_for_binreader(s: &str, writer: &mut dyn std::io::Write) -> std::io::Result<usize> {
|
||||||
let s_bytes = s.as_bytes();
|
let s_bytes = s.as_bytes();
|
||||||
let mut total_len = writer.write(&encode_7_bit_i32(s_bytes.len() as i32))?;
|
let mut total_len = writer.write(&encode_7_bit_i32(s_bytes.len() as i32))?;
|
||||||
@@ -35,6 +51,13 @@ pub fn write_str_for_binreader(s: &str, writer: &mut dyn std::io::Write) -> std:
|
|||||||
Ok(total_len)
|
Ok(total_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_str_for_binwriter(reader: &mut dyn std::io::Read) -> std::io::Result<String> {
|
||||||
|
let len = decode_7_bit_i32(reader)?;
|
||||||
|
let mut buf = vec![0u8; len as usize];
|
||||||
|
reader.read_exact(&mut buf)?;
|
||||||
|
Ok(String::from_utf8_lossy(&buf).into_owned())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cube_id_to_str(id: u32) -> String {
|
pub fn cube_id_to_str(id: u32) -> String {
|
||||||
hex::encode(id.to_be_bytes()).into()
|
hex::encode(id.to_be_bytes()).into()
|
||||||
}
|
}
|
||||||
|
|||||||
33
rc_core/src/factory/adapter_enum.rs
Normal file
33
rc_core/src/factory/adapter_enum.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
pub enum Factory {
|
||||||
|
Arc(rc_factory::arc::ArcAdapter),
|
||||||
|
Custom(Box<dyn rc_factory::VehicleFactoryAdapter + Send + Sync + 'static>),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl rc_factory::VehicleFactoryAdapter for Factory {
|
||||||
|
async fn vehicle(&self, id: u32) -> Result<Option<rc_factory::VehicleInfo>, Box<dyn std::error::Error>> {
|
||||||
|
match self {
|
||||||
|
Self::Arc(x) => x.vehicle(id).await,
|
||||||
|
Self::Custom(x) => x.vehicle(id).await,
|
||||||
|
Self::None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<rc_factory::VehicleQueryInfo>, Box<dyn std::error::Error>> {
|
||||||
|
match self {
|
||||||
|
Self::Arc(x) => x.list(query).await,
|
||||||
|
Self::Custom(x) => x.list(query).await,
|
||||||
|
Self::None => Ok(Vec::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Factory {
|
||||||
|
pub async fn from_config(conf: &crate::persist::FactoryConfig) -> Result<Self, Box<dyn std::error::Error + 'static>> {
|
||||||
|
Ok(match &conf.adapter {
|
||||||
|
crate::persist::AdapterSettings::Arc(x) => Self::Arc(rc_factory::arc::ArcAdapter::init(&x.uri, x.show_expired).await?),
|
||||||
|
crate::persist::AdapterSettings::None => Self::None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
2
rc_core/src/factory/mod.rs
Normal file
2
rc_core/src/factory/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
mod adapter_enum;
|
||||||
|
pub use adapter_enum::Factory;
|
||||||
@@ -9,3 +9,5 @@ pub use persist::user::{UserImpl, UserProvider, UserAuthenticator};
|
|||||||
pub use persist::config::{ConfigImpl, ConfigProvider};
|
pub use persist::config::{ConfigImpl, ConfigProvider};
|
||||||
|
|
||||||
pub mod polariton;
|
pub mod polariton;
|
||||||
|
|
||||||
|
pub mod factory;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use serde::{Serialize, Deserialize};
|
|||||||
use polariton::operation::{Typed, Dict};
|
use polariton::operation::{Typed, Dict};
|
||||||
use polariton::serdes::TypePrefix;
|
use polariton::serdes::TypePrefix;
|
||||||
|
|
||||||
use super::super::{MovementCategoryData, MovementData, Cube, ItemCategory, ItemTier, BattleConfig, Settings, ChatConfig};
|
use super::super::{MovementCategoryData, MovementData, Cube, ItemCategory, ItemTier, BattleConfig, Settings, ChatConfig, FactoryConfig};
|
||||||
|
|
||||||
const CUBE_CONFIG_FILENAME: &str = "config.json";
|
const CUBE_CONFIG_FILENAME: &str = "config.json";
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ pub struct CubeConfig {
|
|||||||
lerp_value: f32,
|
lerp_value: f32,
|
||||||
battle: BattleConfig,
|
battle: BattleConfig,
|
||||||
chat: ChatConfig,
|
chat: ChatConfig,
|
||||||
|
factory: FactoryConfig,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ impl CubeConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl <C: Clone> super::ConfigProvider<C> for CubeConfig {
|
impl <C: Clone> super::ConfigProvider<C> for CubeConfig {
|
||||||
fn cube_list(&self) -> Typed<C> {
|
fn cube_list(&self) -> Typed<C> {
|
||||||
Typed::Dict(Dict {
|
Typed::Dict(Dict {
|
||||||
@@ -271,4 +273,8 @@ impl <C: Clone> super::ConfigProvider<C> for CubeConfig {
|
|||||||
}).collect(),
|
}).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn factory(&self) -> Result<crate::factory::Factory, Box<dyn std::error::Error + 'static>> {
|
||||||
|
crate::factory::Factory::from_config(&self.factory).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use polariton::operation::Typed;
|
use polariton::operation::Typed;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
pub trait ConfigProvider<C: Clone> {
|
pub trait ConfigProvider<C: Clone> {
|
||||||
fn cube_list(&self) -> Typed<C>;
|
fn cube_list(&self) -> Typed<C>;
|
||||||
fn movement_list(&self) -> Typed<C>;
|
fn movement_list(&self) -> Typed<C>;
|
||||||
@@ -20,6 +21,7 @@ pub trait ConfigProvider<C: Clone> {
|
|||||||
fn public_channels(&self) -> Typed<C>;
|
fn public_channels(&self) -> Typed<C>;
|
||||||
fn server_config(&self) -> ServerConfig;
|
fn server_config(&self) -> ServerConfig;
|
||||||
fn garage_upgrades(&self) -> GarageUpgrades;
|
fn garage_upgrades(&self) -> GarageUpgrades;
|
||||||
|
async fn factory(&self) -> Result<crate::factory::Factory, Box<dyn std::error::Error + 'static>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CompleteCampaignProvider {
|
pub struct CompleteCampaignProvider {
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ pub use settings::Settings;
|
|||||||
mod chat;
|
mod chat;
|
||||||
pub use chat::ChatConfig;
|
pub use chat::ChatConfig;
|
||||||
|
|
||||||
|
mod vehicle_factory;
|
||||||
|
pub use vehicle_factory::{FactoryConfig, AdapterSettings, ArcFactorySettings};
|
||||||
|
|
||||||
pub(self) const VALID_ROBOT: &[u8] = &[64,
|
pub(self) const VALID_ROBOT: &[u8] = &[64,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -387,6 +387,7 @@ impl <C: Clone> super::User<C> for UserData {
|
|||||||
slot: polariton::operation::Typed::Int(model.slot as _),
|
slot: polariton::operation::Typed::Int(model.slot as _),
|
||||||
bay_cpu: polariton::operation::Typed::Int(model.bay_cpu as _),
|
bay_cpu: polariton::operation::Typed::Int(model.bay_cpu as _),
|
||||||
mastery_level: polariton::operation::Typed::Int(model.mastery_level as _),
|
mastery_level: polariton::operation::Typed::Int(model.mastery_level as _),
|
||||||
|
slot_i: model.slot as _,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ pub struct NewSlotData<C> {
|
|||||||
pub slot: polariton::operation::Typed<C>,
|
pub slot: polariton::operation::Typed<C>,
|
||||||
pub bay_cpu: polariton::operation::Typed<C>,
|
pub bay_cpu: polariton::operation::Typed<C>,
|
||||||
pub mastery_level: polariton::operation::Typed<C>,
|
pub mastery_level: polariton::operation::Typed<C>,
|
||||||
|
pub slot_i: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VehicleData {
|
pub struct VehicleData {
|
||||||
|
|||||||
30
rc_core/src/persist/vehicle_factory.rs
Normal file
30
rc_core/src/persist/vehicle_factory.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct FactoryConfig {
|
||||||
|
#[serde(default = "default_variant")]
|
||||||
|
pub adapter: AdapterSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_variant() -> AdapterSettings {
|
||||||
|
AdapterSettings::None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(tag = "variant")]
|
||||||
|
pub enum AdapterSettings {
|
||||||
|
#[serde(alias = "sqlite")]
|
||||||
|
Arc(ArcFactorySettings),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct ArcFactorySettings {
|
||||||
|
pub uri: String,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub show_expired: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
16
rc_factory/Cargo.toml
Normal file
16
rc_factory/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "rc_factory"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
sea-orm = { version = "1.1.10", features = [ "runtime-tokio-rustls", "macros" ] }
|
||||||
|
async-trait.workspace = true
|
||||||
|
libfj.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
base64 = "0.22"
|
||||||
122
rc_factory/src/arc/adapter.rs
Normal file
122
rc_factory/src/arc/adapter.rs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder};
|
||||||
|
|
||||||
|
pub struct ArcAdapter {
|
||||||
|
orm: sea_orm::DatabaseConnection,
|
||||||
|
ignore_expiry: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArcAdapter {
|
||||||
|
pub async fn init(uri: &str, show_expired: bool) -> Result<Self, sea_orm::DbErr>{
|
||||||
|
log::debug!("Connecting to Archive of RoboCraft (ARC) vehicle factory database URI: {}", uri);
|
||||||
|
let db = sea_orm::Database::connect(uri).await?;
|
||||||
|
Ok(Self {
|
||||||
|
orm: db,
|
||||||
|
ignore_expiry: show_expired
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_query(&self) -> sea_orm::Select<super::entities::robot_metadata::Entity> {
|
||||||
|
super::entities::robot_metadata::Entity::find()
|
||||||
|
.order_by_desc(super::entities::robot_metadata::Column::RentCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl crate::VehicleFactoryAdapter for ArcAdapter {
|
||||||
|
async fn vehicle(&self, id: u32) -> Result<Option<crate::VehicleInfo>, Box<dyn std::error::Error>> {
|
||||||
|
log::debug!("Get vehicle id {}", id);
|
||||||
|
let cubes = super::entities::robot_cubes::Entity::find_by_id(id).one(&self.orm).await?;
|
||||||
|
if let Some(cubes) = cubes {
|
||||||
|
use base64::Engine;
|
||||||
|
Ok(Some(crate::VehicleInfo {
|
||||||
|
id: id as _,
|
||||||
|
cube_data: base64::prelude::BASE64_STANDARD.decode(cubes.cube_data.as_bytes()).unwrap_or_default(),
|
||||||
|
colour_data: base64::prelude::BASE64_STANDARD.decode(cubes.colour_data.as_bytes()).unwrap_or_default(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<crate::VehicleQueryInfo>, Box<dyn std::error::Error>> {
|
||||||
|
log::debug!("Search vehicles with query {:?}", query);
|
||||||
|
let query_params = if query.default_page {
|
||||||
|
log::debug!("Default vehicle list query");
|
||||||
|
self.default_query()
|
||||||
|
} else {
|
||||||
|
let mut query_builder = super::entities::robot_metadata::Entity::find();
|
||||||
|
match query.order {
|
||||||
|
libfj::robocraft::FactoryOrderType::Suggested => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::RentCount); },
|
||||||
|
libfj::robocraft::FactoryOrderType::CombatRating => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::CombatRating); },
|
||||||
|
libfj::robocraft::FactoryOrderType::CosmeticRating => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::CosmeticRating); },
|
||||||
|
libfj::robocraft::FactoryOrderType::Added => { query_builder = query_builder.order_by_asc(super::entities::robot_metadata::Column::AddedDate); },
|
||||||
|
libfj::robocraft::FactoryOrderType::CPU => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::Cpu); },
|
||||||
|
libfj::robocraft::FactoryOrderType::MostBought => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::BuyCount); },
|
||||||
|
}
|
||||||
|
if !query.text_filter.is_empty() {
|
||||||
|
if query.player_filter {
|
||||||
|
query_builder = query_builder.filter(
|
||||||
|
sea_orm::sea_query::Condition::any()
|
||||||
|
.add(super::entities::robot_metadata::Column::AddedBy.like(query.text_filter.clone()))
|
||||||
|
.add(super::entities::robot_metadata::Column::AddedByDisplayName.like(query.text_filter))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
query_builder = query_builder.filter(
|
||||||
|
sea_orm::sea_query::Condition::any()
|
||||||
|
.add(super::entities::robot_metadata::Column::AddedBy.like(query.text_filter.clone()))
|
||||||
|
.add(super::entities::robot_metadata::Column::AddedByDisplayName.like(query.text_filter.clone()))
|
||||||
|
.add(super::entities::robot_metadata::Column::Name.like(query.text_filter.clone()))
|
||||||
|
.add(super::entities::robot_metadata::Column::Description.like(query.text_filter.clone()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// movement filters not supported
|
||||||
|
// weapon filters not supported
|
||||||
|
if query.minimum_cpu > 0 {
|
||||||
|
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Cpu.gte(query.minimum_cpu as u32));
|
||||||
|
}
|
||||||
|
if query.maximum_cpu < usize::MAX {
|
||||||
|
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Cpu.lte(query.maximum_cpu as u32));
|
||||||
|
}
|
||||||
|
if query.buyable {
|
||||||
|
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Buyable.ne(0));
|
||||||
|
}
|
||||||
|
query_builder
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME add support for query.prepend_featured_bot
|
||||||
|
let metadata_pages = query_params.paginate(&self.orm, query.page_size as u64);
|
||||||
|
let metadatas = metadata_pages.fetch_page(query.page as u64).await?;
|
||||||
|
let mut infos = Vec::with_capacity(metadatas.len());
|
||||||
|
for meta in metadatas {
|
||||||
|
//let cube_amounts = super::entities::robot_cubes::Entity::find_by_id(meta.id).one(&self.orm).await?.map(|x| x.cube_amounts).unwrap_or_else(|| "".to_owned());
|
||||||
|
infos.push(
|
||||||
|
crate::VehicleQueryInfo {
|
||||||
|
id: meta.id as _,
|
||||||
|
name: meta.name,
|
||||||
|
description: meta.description,
|
||||||
|
thumbnail: meta.thumbnail,
|
||||||
|
added_by: meta.added_by,
|
||||||
|
added_by_display_name: meta.added_by_display_name,
|
||||||
|
added_date: crate::traits::parse_rc_date(&meta.added_date).unwrap_or_default(),
|
||||||
|
expiry_date: if self.ignore_expiry { chrono::Utc::now() + chrono::Duration::weeks(2) } else { crate::traits::parse_rc_date(&meta.expiry_date).unwrap_or_default() },
|
||||||
|
cpu: meta.cpu as _,
|
||||||
|
total_robot_ranking: meta.total_robot_ranking as _,
|
||||||
|
rent_count: meta.rent_count as _,
|
||||||
|
buy_count: meta.buy_count as _,
|
||||||
|
buyable: meta.buyable != 0,
|
||||||
|
removed_date: Default::default(),
|
||||||
|
ban_date: Default::default(),
|
||||||
|
featured: meta.featured != 0,
|
||||||
|
banner_message: Default::default(),
|
||||||
|
combat_rating: meta.combat_rating,
|
||||||
|
cosmetic_rating: meta.cosmetic_rating,
|
||||||
|
cube_amounts: Default::default(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
log::debug!("Search vehicles returned {} results", infos.len());
|
||||||
|
Ok(infos)
|
||||||
|
}
|
||||||
|
}
|
||||||
7
rc_factory/src/arc/entities/mod.rs
Normal file
7
rc_factory/src/arc/entities/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.11
|
||||||
|
|
||||||
|
//pub mod prelude;
|
||||||
|
|
||||||
|
pub mod robot_cubes;
|
||||||
|
pub mod robot_metadata;
|
||||||
|
//pub mod state;
|
||||||
5
rc_factory/src/arc/entities/prelude.rs
Normal file
5
rc_factory/src/arc/entities/prelude.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.11
|
||||||
|
|
||||||
|
pub use super::robot_cubes::Entity as RobotCubes;
|
||||||
|
pub use super::robot_metadata::Entity as RobotMetadata;
|
||||||
|
//pub use super::state::Entity as State;
|
||||||
21
rc_factory/src/arc/entities/robot_cubes.rs
Normal file
21
rc_factory/src/arc/entities/robot_cubes.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.11
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "ROBOT_CUBES")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub id: u32,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
pub cube_data: String,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
pub colour_data: String,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
pub cube_amounts: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
39
rc_factory/src/arc/entities/robot_metadata.rs
Normal file
39
rc_factory/src/arc/entities/robot_metadata.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.11
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "ROBOT_METADATA")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub id: u32,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
pub name: String,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
pub description: String,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
pub thumbnail: String,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
pub added_by: String,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
pub added_by_display_name: String,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
pub added_date: String,
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
pub expiry_date: String,
|
||||||
|
pub cpu: u32,
|
||||||
|
pub total_robot_ranking: i32,
|
||||||
|
pub rent_count: i32,
|
||||||
|
pub buy_count: i32,
|
||||||
|
pub buyable: i32,
|
||||||
|
pub featured: i32,
|
||||||
|
#[sea_orm(column_type = "Float")]
|
||||||
|
pub combat_rating: f64,
|
||||||
|
#[sea_orm(column_type = "Float")]
|
||||||
|
pub cosmetic_rating: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
18
rc_factory/src/arc/entities/state.rs
Normal file
18
rc_factory/src/arc/entities/state.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.11
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "STATE")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub id: i32,
|
||||||
|
pub next_page: i32,
|
||||||
|
pub last_page_size: i32,
|
||||||
|
pub last_sequential_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
6
rc_factory/src/arc/mod.rs
Normal file
6
rc_factory/src/arc/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
//! Adapter for sqlite database generated by https://github.com/NGnius/arc
|
||||||
|
|
||||||
|
mod adapter;
|
||||||
|
pub use adapter::ArcAdapter;
|
||||||
|
|
||||||
|
mod entities;
|
||||||
4
rc_factory/src/lib.rs
Normal file
4
rc_factory/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod arc;
|
||||||
|
|
||||||
|
mod traits;
|
||||||
|
pub use traits::{VehicleFactoryAdapter, VehicleInfo, VehicleQueryInfo};
|
||||||
52
rc_factory/src/traits.rs
Normal file
52
rc_factory/src/traits.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait VehicleFactoryAdapter: Send + Sync + 'static {
|
||||||
|
async fn vehicle(&self, id: u32) -> Result<Option<VehicleInfo>, Box<dyn std::error::Error>>;
|
||||||
|
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<VehicleQueryInfo>, Box<dyn std::error::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VehicleInfo {
|
||||||
|
pub id: i32,
|
||||||
|
pub cube_data: Vec<u8>,
|
||||||
|
pub colour_data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_rc_date(s: &str) -> chrono::ParseResult<chrono::DateTime<chrono::Utc>> {
|
||||||
|
let naive = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S")?;
|
||||||
|
Ok(chrono::DateTime::from_naive_utc_and_offset(naive, chrono::Utc))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<libfj::robocraft::FactoryRobotGetInfo> for VehicleInfo {
|
||||||
|
fn from(value: libfj::robocraft::FactoryRobotGetInfo) -> Self {
|
||||||
|
use base64::Engine;
|
||||||
|
Self {
|
||||||
|
id: value.item_id as _,
|
||||||
|
cube_data: base64::prelude::BASE64_STANDARD.decode(value.cube_data.as_bytes()).unwrap_or_default(),
|
||||||
|
colour_data: base64::prelude::BASE64_STANDARD.decode(value.colour_data.as_bytes()).unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VehicleQueryInfo {
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub thumbnail: String, // url
|
||||||
|
pub added_by: String,
|
||||||
|
pub added_by_display_name: String,
|
||||||
|
pub added_date: chrono::DateTime<chrono::Utc>,
|
||||||
|
pub expiry_date: chrono::DateTime<chrono::Utc>,
|
||||||
|
pub cpu: u32,
|
||||||
|
pub total_robot_ranking: u32,
|
||||||
|
pub rent_count: u32,
|
||||||
|
pub buy_count: u32,
|
||||||
|
pub buyable: bool,
|
||||||
|
pub removed_date: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
|
pub ban_date: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
|
pub featured: bool,
|
||||||
|
pub banner_message: Option<String>,
|
||||||
|
pub combat_rating: f64,
|
||||||
|
pub cosmetic_rating: f64,
|
||||||
|
pub cube_amounts: std::collections::HashMap<u32, u32>,
|
||||||
|
}
|
||||||
@@ -19,7 +19,9 @@ base64 = "0.22"
|
|||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
chrono = "0.4"
|
chrono.workspace = true
|
||||||
rc_core = { version = "*", path = "../rc_core" }
|
rc_core = { version = "*", path = "../rc_core" }
|
||||||
|
rc_factory = { version = "*", path = "../rc_factory" }
|
||||||
rand = "0.9"
|
rand = "0.9"
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
libfj.workspace = true
|
||||||
|
|||||||
249
rc_services_room/src/data/crf.rs
Normal file
249
rc_services_room/src/data/crf.rs
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
@@ -28,3 +28,4 @@ pub use rc_core::data::error_codes;
|
|||||||
//pub use rc_core::data::game_mode;
|
//pub use rc_core::data::game_mode;
|
||||||
pub mod score_multipliers;
|
pub mod score_multipliers;
|
||||||
//pub use rc_core::data::campaign;
|
//pub use rc_core::data::campaign;
|
||||||
|
pub mod crf;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub type UserTy = rc_core::UserState<()>;
|
|||||||
pub struct InitConfig {
|
pub struct InitConfig {
|
||||||
pub cubes: rc_core::persist::config::ConfigImpl,
|
pub cubes: rc_core::persist::config::ConfigImpl,
|
||||||
pub users: std::sync::Arc<rc_core::persist::user::UserImpl>,
|
pub users: std::sync::Arc<rc_core::persist::user::UserImpl>,
|
||||||
|
pub factory: std::sync::Arc<rc_core::factory::Factory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -26,9 +27,11 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
let cubes = rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
let cubes = rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
||||||
let users = std::sync::Arc::new(rc_core::persist::user::UserImpl::load(&args.data, &cubes).await.expect("Bad user data"));
|
let users = std::sync::Arc::new(rc_core::persist::user::UserImpl::load(&args.data, &cubes).await.expect("Bad user data"));
|
||||||
|
let factory = std::sync::Arc::new(<rc_core::persist::config::ConfigImpl as rc_core::ConfigProvider<()>>::factory::<'_, '_>(&cubes).await.expect("Bad vehicle factory (CRF) config"));
|
||||||
let init_ctx = std::sync::Arc::new(InitConfig {
|
let init_ctx = std::sync::Arc::new(InitConfig {
|
||||||
cubes,
|
cubes,
|
||||||
users,
|
users,
|
||||||
|
factory,
|
||||||
});
|
});
|
||||||
|
|
||||||
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(&init_ctx), polariton_server::events::EventsHandler::new()));
|
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(&init_ctx), polariton_server::events::EventsHandler::new()));
|
||||||
|
|||||||
15
rc_services_room/src/operations/crf_earnings.rs
Normal file
15
rc_services_room/src/operations/crf_earnings.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
use polariton_server::operations::SimpleFunc;
|
||||||
|
use polariton::operation::{ParameterTable, Typed};
|
||||||
|
|
||||||
|
const PARAM_KEY: u8 = 96;
|
||||||
|
|
||||||
|
pub(super) fn robot_shop_user_earnings_provider() -> SimpleFunc<88, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
|
||||||
|
SimpleFunc::new(|params, _| {
|
||||||
|
let mut params = params.to_dict();
|
||||||
|
params.insert(PARAM_KEY, Typed::HashMap(vec![
|
||||||
|
(Typed::Str("buyCount".into()), Typed::Int(0)),
|
||||||
|
(Typed::Str("earnings".into()), Typed::Int(0)),
|
||||||
|
].into()));
|
||||||
|
Ok(params.into())
|
||||||
|
})
|
||||||
|
}
|
||||||
51
rc_services_room/src/operations/crf_list_query.rs
Normal file
51
rc_services_room/src/operations/crf_list_query.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use polariton_server::operations::{Operation, OperationCode};
|
||||||
|
use polariton::operation::{Typed, ParameterTable};
|
||||||
|
use rc_factory::VehicleFactoryAdapter;
|
||||||
|
|
||||||
|
const CODE: u8 = 86;
|
||||||
|
|
||||||
|
const FILTERS_PARAM_KEY: u8 = 92;
|
||||||
|
const ITEMS_PARAM_KEY: u8 = 93;
|
||||||
|
|
||||||
|
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::Bytes(filters)) = params.remove(&FILTERS_PARAM_KEY) {
|
||||||
|
let mut cursor = std::io::Cursor::new(filters.vec);
|
||||||
|
let filters = crate::data::crf::ShopItemListFilters::parse(&mut cursor).map_err(|e| {
|
||||||
|
log::error!("Failed to parse factory item query: {}", e);
|
||||||
|
rc_core::data::error_codes::WebServicesError::UnexpectedError as i16
|
||||||
|
})?;
|
||||||
|
let vehicles = factory.list(filters.into_core()).await.map_err(|e| {
|
||||||
|
log::error!("Failed to retrieve vehicles from factory: {}", e);
|
||||||
|
rc_core::data::error_codes::WebServicesError::DatabaseError as i16
|
||||||
|
})?;
|
||||||
|
let vehicles: Vec<_> = vehicles.into_iter().map(|x| crate::data::crf::ItemResult::from(x)).collect();
|
||||||
|
params.insert(ITEMS_PARAM_KEY, crate::data::crf::ItemResult::as_transmissible(&vehicles));
|
||||||
|
}
|
||||||
|
Ok(params.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CrfItemListProvider {
|
||||||
|
factory: std::sync::Arc<rc_core::factory::Factory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Operation<()> for CrfItemListProvider {
|
||||||
|
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 CrfItemListProvider {
|
||||||
|
fn op_code() -> u8 {
|
||||||
|
CODE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn crf_item_list_query_provider(factory: &std::sync::Arc<rc_core::factory::Factory>) -> CrfItemListProvider {
|
||||||
|
CrfItemListProvider {
|
||||||
|
factory: factory.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
69
rc_services_room/src/operations/crf_purchase.rs
Normal file
69
rc_services_room/src/operations/crf_purchase.rs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
use polariton::operation::{ParameterTable, Typed, OperationResponse};
|
||||||
|
use 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 FACTORY_ID_PARAM_KEY: u8 = 94; // in; int
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
|
let user_info = user.user()?;
|
||||||
|
let slot = if let Some(Typed::Int(slot)) = params.remove(&SLOT_PARAM_KEY) {
|
||||||
|
user_info.new_slot(Some(slot)).await?;
|
||||||
|
slot
|
||||||
|
} else {
|
||||||
|
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_to_copy = factory.vehicle(factory_id as _).await.map_err(|e| {
|
||||||
|
log::error!("Failed to retrieve vehicle {} (for copy-construct) from factory: {}", factory_id, e);
|
||||||
|
rc_core::data::error_codes::WebServicesError::DatabaseError as i16
|
||||||
|
})?;
|
||||||
|
if let Some(vehicle_to_copy) = vehicle_to_copy {
|
||||||
|
let to_save = rc_core::persist::user::VehicleData {
|
||||||
|
slot,
|
||||||
|
robot_data: vehicle_to_copy.cube_data,
|
||||||
|
colour_data: vehicle_to_copy.colour_data,
|
||||||
|
weapon_order: Vec::default(), // FIXME calculate this somehow? Or maybe get the factory adapter to calculate this
|
||||||
|
};
|
||||||
|
user_info.save_slot(to_save).await?;
|
||||||
|
} else {
|
||||||
|
log::warn!("Failed to retrieve (for copy-construct) non-existent factory vehicle {}", factory_id);
|
||||||
|
return Err(rc_core::data::error_codes::WebServicesError::DatabaseError as i16);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(params.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CrfItemPurchaseProvider {
|
||||||
|
factory: std::sync::Arc<rc_core::factory::Factory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl polariton_server::operations::Operation<()> for CrfItemPurchaseProvider {
|
||||||
|
type User = crate::UserTy;
|
||||||
|
|
||||||
|
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
|
||||||
|
polariton_server::operations::result_to_op_resp::<CODE, ()>(do_handling(params, user, &self.factory).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl polariton_server::operations::OperationCode for CrfItemPurchaseProvider {
|
||||||
|
fn op_code() -> u8 {
|
||||||
|
CODE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub(super) fn crf_copy_to_bay_provider(factory: &std::sync::Arc<rc_core::factory::Factory>) -> CrfItemPurchaseProvider {
|
||||||
|
CrfItemPurchaseProvider {
|
||||||
|
factory: factory.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
51
rc_services_room/src/operations/crf_vehicle_data.rs
Normal file
51
rc_services_room/src/operations/crf_vehicle_data.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use polariton_server::operations::{Operation, OperationCode};
|
||||||
|
use polariton::operation::{Typed, ParameterTable};
|
||||||
|
use rc_factory::VehicleFactoryAdapter;
|
||||||
|
|
||||||
|
const CODE: u8 = 87;
|
||||||
|
|
||||||
|
const ID_PARAM_KEY: u8 = 94;
|
||||||
|
const DATA_PARAM_KEY: u8 = 95;
|
||||||
|
|
||||||
|
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::Int(id)) = params.remove(&ID_PARAM_KEY) {
|
||||||
|
let vehicle = factory.vehicle(id as _).await.map_err(|e| {
|
||||||
|
log::error!("Failed to retrieve vehicle {} from factory: {}", id, e);
|
||||||
|
rc_core::data::error_codes::WebServicesError::DatabaseError as i16
|
||||||
|
})?;
|
||||||
|
if let Some(vehicle) = vehicle {
|
||||||
|
let vehicle_data = crate::data::crf::ItemData::from(vehicle);
|
||||||
|
params.insert(DATA_PARAM_KEY, vehicle_data.as_transmissible());
|
||||||
|
} else {
|
||||||
|
log::warn!("Failed to retrieve non-existent factory vehicle {}", id);
|
||||||
|
return Err(rc_core::data::error_codes::WebServicesError::InvalidRobot as i16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(params.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CrfItemDataProvider {
|
||||||
|
factory: std::sync::Arc<rc_core::factory::Factory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Operation<()> for CrfItemDataProvider {
|
||||||
|
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 CrfItemDataProvider {
|
||||||
|
fn op_code() -> u8 {
|
||||||
|
CODE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn crf_item_data_provider(factory: &std::sync::Arc<rc_core::factory::Factory>) -> CrfItemDataProvider {
|
||||||
|
CrfItemDataProvider {
|
||||||
|
factory: factory.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,10 @@ mod garage_slots_order;
|
|||||||
mod garage_slot_select;
|
mod garage_slot_select;
|
||||||
mod garage_slot_dismantle;
|
mod garage_slot_dismantle;
|
||||||
mod garage_slot_upgrade;
|
mod garage_slot_upgrade;
|
||||||
|
mod crf_earnings;
|
||||||
|
mod crf_list_query;
|
||||||
|
mod crf_vehicle_data;
|
||||||
|
mod crf_purchase;
|
||||||
|
|
||||||
use polariton_server::operations::OperationsHandler;
|
use polariton_server::operations::OperationsHandler;
|
||||||
|
|
||||||
@@ -194,4 +198,8 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
|||||||
.add(garage_slot_select::garage_slot_selector())
|
.add(garage_slot_select::garage_slot_selector())
|
||||||
.add(garage_slot_dismantle::garage_slot_dismantler())
|
.add(garage_slot_dismantle::garage_slot_dismantler())
|
||||||
.add(garage_slot_upgrade::garage_slot_upgrage_provider())
|
.add(garage_slot_upgrade::garage_slot_upgrage_provider())
|
||||||
|
.add(crf_earnings::robot_shop_user_earnings_provider())
|
||||||
|
.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))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user