mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Implement item shop #70
This commit is contained in:
@@ -1,110 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use polariton::operation::Typed;
|
||||
|
||||
pub struct ItemShopBundle {
|
||||
pub sku: String,
|
||||
pub bundle_name_key: String,
|
||||
pub sprite: String,
|
||||
pub is_sprite_full_size: bool,
|
||||
pub category: ItemShopCategory,
|
||||
pub currency: CurrencyType, // str
|
||||
pub price: i32,
|
||||
pub discount_time: i64, // seconds since unix epoch
|
||||
pub discount_price: i32,
|
||||
pub recurrence: ItemShopRecurrence,
|
||||
pub owns_required_cube: bool,
|
||||
//pub is_discounted: bool,
|
||||
pub is_limited_edition: bool,
|
||||
}
|
||||
|
||||
impl ItemShopBundle {
|
||||
pub fn as_transmissible(&self) -> Typed {
|
||||
let mut buf = Vec::new();
|
||||
let mut writer = std::io::Cursor::new(&mut buf);
|
||||
self.dump(&mut writer).unwrap();
|
||||
Typed::Bytes(buf.into())
|
||||
}
|
||||
|
||||
fn dump(&self, writer: &mut dyn Write) -> std::io::Result<usize> {
|
||||
let sku_bytes = self.sku.as_bytes();
|
||||
let mut total_len = writer.write(&oj_rc_core::data::encode_7_bit_i32(sku_bytes.len() as i32))?;
|
||||
total_len += writer.write(sku_bytes)?;
|
||||
|
||||
let bundle_name_key_bytes = self.bundle_name_key.as_bytes();
|
||||
total_len += writer.write(&oj_rc_core::data::encode_7_bit_i32(bundle_name_key_bytes.len() as i32))?;
|
||||
total_len += writer.write(bundle_name_key_bytes)?;
|
||||
|
||||
let sprite_bytes = self.sprite.as_bytes();
|
||||
total_len += writer.write(&oj_rc_core::data::encode_7_bit_i32(sprite_bytes.len() as i32))?;
|
||||
total_len += writer.write(sprite_bytes)?;
|
||||
|
||||
total_len += writer.write(&[self.is_sprite_full_size as u8])?;
|
||||
|
||||
let currency_bytes = self.currency.as_str().as_bytes();
|
||||
total_len += writer.write(&oj_rc_core::data::encode_7_bit_i32(currency_bytes.len() as i32))?;
|
||||
total_len += writer.write(currency_bytes)?;
|
||||
|
||||
total_len += writer.write(&self.price.to_le_bytes())?;
|
||||
|
||||
total_len += writer.write(&self.discount_time.to_le_bytes())?;
|
||||
|
||||
total_len += writer.write(&self.discount_price.to_le_bytes())?;
|
||||
|
||||
total_len += writer.write(&(self.recurrence as i32).to_le_bytes())?;
|
||||
|
||||
total_len += writer.write(&[self.owns_required_cube as u8])?;
|
||||
|
||||
total_len += writer.write(&(self.category as i32).to_le_bytes())?;
|
||||
|
||||
total_len += writer.write(&[self.is_limited_edition as u8])?;
|
||||
|
||||
Ok(total_len)
|
||||
}
|
||||
|
||||
pub fn as_transmissible_vec(items: Vec<Self>) -> Typed {
|
||||
let mut buf = Vec::new();
|
||||
let mut writer = std::io::Cursor::new(&mut buf);
|
||||
writer.write_all(&(items.len() as i32).to_le_bytes()).unwrap();
|
||||
for item in items.iter() {
|
||||
item.dump(&mut writer).unwrap();
|
||||
}
|
||||
Typed::Bytes(buf.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ItemShopCategory {
|
||||
Cube = 0,
|
||||
GarageBaySkin = 1,
|
||||
Bundle = 2,
|
||||
DeathEffect = 3,
|
||||
SpawnEffect = 4,
|
||||
Emotigram = 5,
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ItemShopRecurrence {
|
||||
Daily = 0,
|
||||
Weekly = 1,
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum CurrencyType {
|
||||
Robits = 0,
|
||||
CosmeticCredits = 1,
|
||||
}
|
||||
|
||||
impl CurrencyType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Robits => "Robits",
|
||||
Self::CosmeticCredits => "CosmeticCredits",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ pub mod customisation_info;
|
||||
pub use oj_rc_core::data::garage_bay;
|
||||
pub mod custom_games;
|
||||
//pub use oj_rc_core::data::tech_tree;
|
||||
pub mod item_shop_bundle;
|
||||
//pub use oj_rc_core::data::item_shop_bundle;
|
||||
pub mod player_robopass_season;
|
||||
//pub use oj_rc_core::data::weapon_upgrade;
|
||||
pub mod player_rank;
|
||||
|
||||
@@ -1,144 +1,31 @@
|
||||
use polariton_server::operations::SimpleFunc;
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::ParameterTable;
|
||||
|
||||
use crate::data::item_shop_bundle::*;
|
||||
const CODE: u8 = 188;
|
||||
|
||||
const PARAM_KEY: u8 = 65;
|
||||
|
||||
pub(super) fn item_bundle_provider() -> SimpleFunc<188, 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, ItemShopBundle::as_transmissible_vec(vec![
|
||||
// weekly (top row of 3)
|
||||
ItemShopBundle {
|
||||
sku: "buy cc 100".to_owned(),
|
||||
bundle_name_key: "strRealMoneyStoreName_CosmeticCredits1".to_owned(),
|
||||
sprite: "ItemShop_CosmeticCredits".to_owned(),
|
||||
is_sprite_full_size: false,
|
||||
category: ItemShopCategory::Bundle,
|
||||
currency: CurrencyType::Robits,
|
||||
price: 100_000,
|
||||
discount_time: 0,
|
||||
discount_price: 100_000,
|
||||
recurrence: ItemShopRecurrence::Weekly,
|
||||
owns_required_cube: true,
|
||||
is_limited_edition: false,
|
||||
},
|
||||
ItemShopBundle {
|
||||
sku: "buy robopass 1 1".to_owned(),
|
||||
bundle_name_key: "strRealMoneyStoreName_RoboPass".to_owned(),
|
||||
sprite: "Store_RoboPass_Season2".to_owned(),
|
||||
is_sprite_full_size: true,
|
||||
category: ItemShopCategory::Bundle,
|
||||
currency: CurrencyType::CosmeticCredits,
|
||||
price: 10_000_000,
|
||||
discount_time: 0,
|
||||
discount_price: 10_000_000,
|
||||
recurrence: ItemShopRecurrence::Weekly,
|
||||
owns_required_cube: false,
|
||||
is_limited_edition: true,
|
||||
},
|
||||
ItemShopBundle {
|
||||
sku: "buy robit 100000".to_owned(),
|
||||
bundle_name_key: "strRealMoneyStoreName_RobitsBundle2".to_owned(),
|
||||
sprite: "ItemShop_Robits".to_owned(),
|
||||
is_sprite_full_size: false,
|
||||
category: ItemShopCategory::Bundle,
|
||||
currency: CurrencyType::CosmeticCredits,
|
||||
price: 100,
|
||||
discount_time: 0,
|
||||
discount_price: 100,
|
||||
recurrence: ItemShopRecurrence::Weekly,
|
||||
owns_required_cube: true,
|
||||
is_limited_edition: false,
|
||||
},
|
||||
// daily (lower row or 6)
|
||||
ItemShopBundle {
|
||||
sku: "buy robopass 1 1".to_owned(),
|
||||
bundle_name_key: "strRoboPassSeason02".to_owned(),
|
||||
sprite: "Store_RoboPass".to_owned(),
|
||||
is_sprite_full_size: true,
|
||||
category: ItemShopCategory::Bundle,
|
||||
currency: CurrencyType::Robits,
|
||||
price: 10_000,
|
||||
discount_time: (chrono::Utc::now() + std::time::Duration::from_secs(24*60*60)).timestamp(),
|
||||
discount_price: 1_000,
|
||||
recurrence: ItemShopRecurrence::Daily,
|
||||
owns_required_cube: false,
|
||||
is_limited_edition: false,
|
||||
},
|
||||
ItemShopBundle {
|
||||
sku: "buy robopass 1 2".to_owned(),
|
||||
bundle_name_key: "strRoboPassSeason02".to_owned(),
|
||||
sprite: "Store_RoboPass".to_owned(),
|
||||
is_sprite_full_size: true,
|
||||
category: ItemShopCategory::Cube,
|
||||
currency: CurrencyType::Robits,
|
||||
price: 10_000,
|
||||
discount_time: (chrono::Utc::now() + std::time::Duration::from_secs(24*60*60)).timestamp(),
|
||||
discount_price: 2_000,
|
||||
recurrence: ItemShopRecurrence::Daily,
|
||||
owns_required_cube: false,
|
||||
is_limited_edition: false,
|
||||
},
|
||||
ItemShopBundle {
|
||||
sku: "buy robopass 1 3".to_owned(),
|
||||
bundle_name_key: "strRoboPassSeason02".to_owned(),
|
||||
sprite: "Store_RoboPass".to_owned(),
|
||||
is_sprite_full_size: true,
|
||||
category: ItemShopCategory::Cube,
|
||||
currency: CurrencyType::Robits,
|
||||
price: 10_000,
|
||||
discount_time: (chrono::Utc::now() + std::time::Duration::from_secs(24*60*60)).timestamp(),
|
||||
discount_price: 3_000,
|
||||
recurrence: ItemShopRecurrence::Daily,
|
||||
owns_required_cube: false,
|
||||
is_limited_edition: false,
|
||||
},
|
||||
ItemShopBundle {
|
||||
sku: "buy robopass 1 4".to_owned(),
|
||||
bundle_name_key: "strRoboPassSeason02".to_owned(),
|
||||
sprite: "Store_RoboPass".to_owned(),
|
||||
is_sprite_full_size: true,
|
||||
category: ItemShopCategory::Cube,
|
||||
currency: CurrencyType::Robits,
|
||||
price: 10_000,
|
||||
discount_time: (chrono::Utc::now() + std::time::Duration::from_secs(24*60*60)).timestamp(),
|
||||
discount_price: 4_000,
|
||||
recurrence: ItemShopRecurrence::Daily,
|
||||
owns_required_cube: false,
|
||||
is_limited_edition: false,
|
||||
},
|
||||
ItemShopBundle {
|
||||
sku: "buy robopass 1 5".to_owned(),
|
||||
bundle_name_key: "strRoboPassSeason02".to_owned(),
|
||||
sprite: "Store_RoboPass".to_owned(),
|
||||
is_sprite_full_size: true,
|
||||
category: ItemShopCategory::Cube,
|
||||
currency: CurrencyType::Robits,
|
||||
price: 10_000,
|
||||
discount_time: (chrono::Utc::now() + std::time::Duration::from_secs(24*60*60)).timestamp(),
|
||||
discount_price: 5_000,
|
||||
recurrence: ItemShopRecurrence::Daily,
|
||||
owns_required_cube: false,
|
||||
is_limited_edition: false,
|
||||
},
|
||||
ItemShopBundle {
|
||||
sku: "buy robopass 1 6".to_owned(),
|
||||
bundle_name_key: "strRoboPassSeason02".to_owned(),
|
||||
sprite: "Store_RoboPass".to_owned(),
|
||||
is_sprite_full_size: true,
|
||||
category: ItemShopCategory::Cube,
|
||||
currency: CurrencyType::Robits,
|
||||
price: 10_000,
|
||||
discount_time: (chrono::Utc::now() + std::time::Duration::from_secs(24*60*60)).timestamp(),
|
||||
discount_price: 6_000,
|
||||
recurrence: ItemShopRecurrence::Daily,
|
||||
owns_required_cube: false,
|
||||
is_limited_edition: false,
|
||||
},
|
||||
]));
|
||||
Ok(params.into())
|
||||
pub(super) struct ItemBundleRetriever {
|
||||
resolver: oj_rc_core::persist::config::ShopEntriesResolver,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<()> for ItemBundleRetriever {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable, user: &Self::User) -> Result<ParameterTable, SimpleOpError> {
|
||||
//let mut params = ParameterTable::<C>::with_capacity(2);
|
||||
let user_info = user.user()?;
|
||||
let entries = self.resolver.resolve_entries(user_info.as_ref().as_ref()).await;
|
||||
params.insert(PARAM_KEY, entries);
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn item_bundle_provider(conf: &oj_rc_core::ConfigImpl) -> SimpleOpImpl<(), crate::UserTy, ItemBundleRetriever> {
|
||||
SimpleOpImpl::new(ItemBundleRetriever {
|
||||
resolver: <oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::shop_entries(conf),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
98
rc_services_room/src/operations/item_shop_purchase.rs
Normal file
98
rc_services_room/src/operations/item_shop_purchase.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
|
||||
use polariton::operation::{ParameterTable, Typed, Arr};
|
||||
|
||||
const CODE: u8 = 189;
|
||||
|
||||
const SKU_PARAM_KEY: u8 = 32; // str; in
|
||||
const CURRENCY_PARAM_KEY: u8 = 53; // str; in
|
||||
const PRICE_PARAM_KEY: u8 = 65; // int; in
|
||||
const NEW_CUBES_PARAM_KEY: u8 = 72; // arr of str; out
|
||||
|
||||
pub(super) struct ItemBundleBuyer {
|
||||
resolver: oj_rc_core::persist::config::ShopEntriesResolver,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SimpleOperation<()> for ItemBundleBuyer {
|
||||
type User = crate::UserTy;
|
||||
const CODE: u8 = CODE;
|
||||
|
||||
async fn handle(&self, mut params: ParameterTable, user: &Self::User) -> Result<ParameterTable, SimpleOpError> {
|
||||
if let Some(Typed::Str(sku)) = params.remove(&SKU_PARAM_KEY) {
|
||||
if let Some(Typed::Str(currency)) = params.remove(&CURRENCY_PARAM_KEY) {
|
||||
if let Some(Typed::Int(price)) = params.remove(&PRICE_PARAM_KEY) {
|
||||
let user_info = user.user()?;
|
||||
let transactions = self.resolver.resolve_transactions().await;
|
||||
// TODO refactor this into oj_rc_core in one of the User traits
|
||||
if let Some(transaction) = transactions.get(&sku.string) {
|
||||
let mut is_ok = false;
|
||||
if currency.string == "Robits" && transaction.cost_free == price {
|
||||
let ty = oj_rc_core::persist::user::CurrencyType::Free;
|
||||
let op = oj_rc_core::persist::user::CurrencyOp::AddSub(-price as _);
|
||||
user_info.currency(ty, op).await?;
|
||||
is_ok = true;
|
||||
} else if currency.string == "CosmeticCredits" && transaction.cost_paid == price {
|
||||
let ty = oj_rc_core::persist::user::CurrencyType::Paid;
|
||||
let op = oj_rc_core::persist::user::CurrencyOp::AddSub(-price as _);
|
||||
user_info.currency(ty, op).await?;
|
||||
is_ok = true;
|
||||
}
|
||||
if is_ok {
|
||||
let mut new_cubes = Vec::new();
|
||||
for award in transaction.gives.iter() {
|
||||
match award {
|
||||
oj_rc_core::persist::config::ShopGain::Cube(x) => {
|
||||
new_cubes.push(*x);
|
||||
},
|
||||
oj_rc_core::persist::config::ShopGain::Experience(xp) => {
|
||||
user_info.currency(
|
||||
oj_rc_core::persist::user::CurrencyType::Experience,
|
||||
oj_rc_core::persist::user::CurrencyOp::AddSub(*xp as _),
|
||||
).await?;
|
||||
},
|
||||
oj_rc_core::persist::config::ShopGain::FreeCurrency(c) => {
|
||||
user_info.currency(
|
||||
oj_rc_core::persist::user::CurrencyType::Free,
|
||||
oj_rc_core::persist::user::CurrencyOp::AddSub(*c as _),
|
||||
).await?;
|
||||
},
|
||||
oj_rc_core::persist::config::ShopGain::PaidCurrency(c) => {
|
||||
user_info.currency(
|
||||
oj_rc_core::persist::user::CurrencyType::Paid,
|
||||
oj_rc_core::persist::user::CurrencyOp::AddSub(*c as _),
|
||||
).await?;
|
||||
},
|
||||
oj_rc_core::persist::config::ShopGain::TechPoints(tp) => {
|
||||
user_info.currency(
|
||||
oj_rc_core::persist::user::CurrencyType::TechPoints,
|
||||
oj_rc_core::persist::user::CurrencyOp::AddSub(*tp as _),
|
||||
).await?;
|
||||
},
|
||||
}
|
||||
}
|
||||
let new_cubes_len = new_cubes.len();
|
||||
if !new_cubes.is_empty() {
|
||||
user_info.unlock_parts(&new_cubes).await?;
|
||||
}
|
||||
|
||||
let new_cubes_typed: Vec<_> = new_cubes.into_iter().map(|x| Typed::Str(hex::encode((x as i32).to_be_bytes()).into())).collect();
|
||||
//params.insert(NEW_CUBES_PARAM_KEY, Typed::Arr(new_cubes_i32.into()));
|
||||
params.insert(NEW_CUBES_PARAM_KEY, Typed::Arr(Arr {
|
||||
ty: polariton::serdes::TypePrefix::Str,
|
||||
items: new_cubes_typed,
|
||||
}));
|
||||
log::info!("SKU {} purchased (ok? {}) {} ops received, {} cubes unlocked", sku.string, is_ok, transaction.gives.len(), new_cubes_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn item_purchase_provider(conf: &oj_rc_core::ConfigImpl) -> SimpleOpImpl<(), crate::UserTy, ItemBundleBuyer> {
|
||||
SimpleOpImpl::new(ItemBundleBuyer {
|
||||
resolver: <oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::shop_entries(conf),
|
||||
})
|
||||
}
|
||||
@@ -99,6 +99,7 @@ mod garage_slot_name;
|
||||
mod garage_slot_copy;
|
||||
mod steam_promo;
|
||||
mod campaign_save_result;
|
||||
mod item_shop_purchase;
|
||||
|
||||
use polariton_server::operations::OperationsHandler;
|
||||
|
||||
@@ -151,7 +152,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
||||
.add(game_event_params::event_system_params_provider(&init_ctx.cubes))
|
||||
.add(garage_bay_uuid::garage_id_provider())
|
||||
.add(tech_tree_data::tech_tree_layout_provider(&init_ctx.cubes))
|
||||
.add(item_shop_bundles::item_bundle_provider())
|
||||
.add(item_shop_bundles::item_bundle_provider(&init_ctx.cubes))
|
||||
.add(robot_customisations::bay_customisations_provider())
|
||||
.add(player_data::player_data_provider())
|
||||
.add(player_robopass::player_robopass_season_provider())
|
||||
@@ -223,4 +224,5 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
|
||||
.add(polariton_server::operations::Ack::<90, _>::default()) // TODO handle SubmitCRFRatingRequest instead of ignoring it
|
||||
//.add(polariton_server::operations::Ack::<97, _>::default()) // TODO handle UpdateShopRobotOffsetRequest instead of ignoring it (this seems to break newly-uploaded vehicles for now)
|
||||
.add(steam_promo::steam_promos_provider())
|
||||
.add(item_shop_purchase::item_purchase_provider(&init_ctx.cubes))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user