507 lines
21 KiB
Rust
507 lines
21 KiB
Rust
use std::collections::BTreeMap;
|
|
use std::convert::TryInto;
|
|
use futures::Future;
|
|
|
|
use crate::entity::account::*;
|
|
use crate::entity::character::*;
|
|
use crate::entity::gateway::{EntityGateway, EntityGatewayTransaction, GatewayError};
|
|
use crate::entity::item::*;
|
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
// TODO: implement multiple banks
|
|
|
|
pub struct InMemoryGatewayTransaction<'a> {
|
|
working_gateway: InMemoryGateway,
|
|
original_gateway: &'a mut InMemoryGateway,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl<'a> EntityGatewayTransaction for InMemoryGatewayTransaction<'a> {
|
|
fn gateway(&mut self) -> &mut dyn EntityGateway {
|
|
&mut self.working_gateway
|
|
}
|
|
|
|
async fn commit(mut self: Box<Self>) -> Result<(), GatewayError> {
|
|
self.original_gateway.users.lock().unwrap().clear();
|
|
self.original_gateway.users.lock().unwrap().extend(self.working_gateway.users.lock().unwrap().clone());
|
|
|
|
self.original_gateway.user_settings.lock().unwrap().clear();
|
|
self.original_gateway.user_settings.lock().unwrap().extend(self.working_gateway.user_settings.lock().unwrap().clone());
|
|
|
|
self.original_gateway.characters.lock().unwrap().clear();
|
|
self.original_gateway.characters.lock().unwrap().extend(self.working_gateway.characters.lock().unwrap().clone());
|
|
|
|
self.original_gateway.character_meseta.lock().unwrap().clear();
|
|
self.original_gateway.character_meseta.lock().unwrap().extend(self.working_gateway.character_meseta.lock().unwrap().clone());
|
|
|
|
self.original_gateway.bank_meseta.lock().unwrap().clear();
|
|
self.original_gateway.bank_meseta.lock().unwrap().extend(self.working_gateway.bank_meseta.lock().unwrap().clone());
|
|
|
|
self.original_gateway.items.lock().unwrap().clear();
|
|
self.original_gateway.items.lock().unwrap().extend(self.working_gateway.items.lock().unwrap().clone());
|
|
|
|
self.original_gateway.inventories.lock().unwrap().clear();
|
|
self.original_gateway.inventories.lock().unwrap().extend(self.working_gateway.inventories.lock().unwrap().clone());
|
|
|
|
self.original_gateway.banks.lock().unwrap().clear();
|
|
self.original_gateway.banks.lock().unwrap().extend(self.working_gateway.banks.lock().unwrap().clone());
|
|
|
|
self.original_gateway.equips.lock().unwrap().clear();
|
|
self.original_gateway.equips.lock().unwrap().extend(self.working_gateway.equips.lock().unwrap().clone());
|
|
|
|
self.original_gateway.mag_modifiers.lock().unwrap().clear();
|
|
self.original_gateway.mag_modifiers.lock().unwrap().extend(self.working_gateway.mag_modifiers.lock().unwrap().clone());
|
|
|
|
self.original_gateway.weapon_modifiers.lock().unwrap().clear();
|
|
self.original_gateway.weapon_modifiers.lock().unwrap().extend(self.working_gateway.weapon_modifiers.lock().unwrap().clone());
|
|
|
|
self.original_gateway.trades.lock().unwrap().clear();
|
|
self.original_gateway.trades.lock().unwrap().extend(self.working_gateway.trades.lock().unwrap().clone());
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct InMemoryGateway {
|
|
users: Arc<Mutex<BTreeMap<UserAccountId, UserAccountEntity>>>,
|
|
user_settings: Arc<Mutex<BTreeMap<UserSettingsId, UserSettingsEntity>>>,
|
|
characters: Arc<Mutex<BTreeMap<CharacterEntityId, CharacterEntity>>>,
|
|
character_meseta: Arc<Mutex<BTreeMap<CharacterEntityId, Meseta>>>,
|
|
bank_meseta: Arc<Mutex<BTreeMap<(CharacterEntityId, BankName), Meseta>>>,
|
|
items: Arc<Mutex<BTreeMap<ItemEntityId, ItemEntity>>>,
|
|
inventories: Arc<Mutex<BTreeMap<CharacterEntityId, InventoryEntity>>>,
|
|
banks: Arc<Mutex<BTreeMap<CharacterEntityId, BankEntity>>>,
|
|
equips: Arc<Mutex<BTreeMap<CharacterEntityId, EquippedEntity>>>,
|
|
mag_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<mag::MagModifier>>>>,
|
|
weapon_modifiers: Arc<Mutex<BTreeMap<ItemEntityId, Vec<weapon::WeaponModifier>>>>,
|
|
trades: Arc<Mutex<Vec<TradeEntity>>>,
|
|
}
|
|
|
|
impl Default for InMemoryGateway {
|
|
fn default() -> InMemoryGateway {
|
|
InMemoryGateway {
|
|
users: Arc::new(Mutex::new(BTreeMap::new())),
|
|
user_settings: Arc::new(Mutex::new(BTreeMap::new())),
|
|
characters: Arc::new(Mutex::new(BTreeMap::new())),
|
|
character_meseta: Arc::new(Mutex::new(BTreeMap::new())),
|
|
bank_meseta: Arc::new(Mutex::new(BTreeMap::new())),
|
|
items: Arc::new(Mutex::new(BTreeMap::new())),
|
|
inventories: Arc::new(Mutex::new(BTreeMap::new())),
|
|
banks: Arc::new(Mutex::new(BTreeMap::new())),
|
|
equips: Arc::new(Mutex::new(BTreeMap::new())),
|
|
mag_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
|
|
weapon_modifiers: Arc::new(Mutex::new(BTreeMap::new())),
|
|
trades: Arc::new(Mutex::new(Vec::new())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl InMemoryGateway {
|
|
fn apply_modifiers(&self, inventory: InventoryEntity ) -> InventoryEntity {
|
|
let items = self.items.lock().unwrap();
|
|
let inventory_items = inventory.items.into_iter()
|
|
.map(|item| {
|
|
item.map_individual(|mut item| {
|
|
item.item = match item.item {
|
|
ItemDetail::Weapon(mut weapon) => {
|
|
if let Some(weapon_modifiers) = self.weapon_modifiers.lock().unwrap().get(&item.id) {
|
|
for weapon_modifier in weapon_modifiers.iter() {
|
|
weapon.apply_modifier(weapon_modifier);
|
|
}
|
|
}
|
|
ItemDetail::Weapon(weapon)
|
|
},
|
|
ItemDetail::Mag(mag) => {
|
|
let mut mag = mag::Mag::baby_mag(mag.color as u16);
|
|
if let Some(mag_modifiers) = self.mag_modifiers.lock().unwrap().get(&item.id) {
|
|
for mag_modifier in mag_modifiers.iter() {
|
|
match mag_modifier {
|
|
mag::MagModifier::FeedMag {food} => {
|
|
if let Some(mag_feed) = items.get(food) {
|
|
if let ItemDetail::Tool(mag_feed) = mag_feed.item {
|
|
mag.feed(mag_feed.tool)
|
|
}
|
|
}
|
|
},
|
|
mag::MagModifier::OwnerChange(class, section_id) => {
|
|
mag.change_owner(*class, *section_id)
|
|
},
|
|
mag::MagModifier::MagCell(mag_cell_id) => {
|
|
if let Some(mag_cell) = items.get(mag_cell_id) {
|
|
if let ItemDetail::Tool(mag_cell) = mag_cell.item {
|
|
mag.apply_mag_cell(mag_cell.tool.try_into().unwrap()).unwrap()
|
|
}
|
|
}
|
|
},
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
ItemDetail::Mag(mag)
|
|
}
|
|
_ => {
|
|
item.item
|
|
}
|
|
};
|
|
|
|
item
|
|
})
|
|
})
|
|
.collect();
|
|
InventoryEntity::new(inventory_items)
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl EntityGateway for InMemoryGateway {
|
|
async fn transaction<'a>(&'a mut self) -> Result<Box<dyn EntityGatewayTransaction + 'a>, GatewayError>
|
|
{
|
|
let working_gateway = {
|
|
let users = self.users.lock().unwrap().clone();
|
|
let user_settings = self.user_settings.lock().unwrap().clone();
|
|
let characters = self.characters.lock().unwrap().clone();
|
|
let character_meseta = self.character_meseta.lock().unwrap().clone();
|
|
let bank_meseta = self.bank_meseta.lock().unwrap().clone();
|
|
let items = self.items.lock().unwrap().clone();
|
|
let inventories = self.inventories.lock().unwrap().clone();
|
|
let banks = self.banks.lock().unwrap().clone();
|
|
let equips = self.equips.lock().unwrap().clone();
|
|
let mag_modifiers = self.mag_modifiers.lock().unwrap().clone();
|
|
let weapon_modifiers = self.weapon_modifiers.lock().unwrap().clone();
|
|
let trades = self.trades.lock().unwrap().clone();
|
|
|
|
InMemoryGateway {
|
|
users: Arc::new(Mutex::new(users)),
|
|
user_settings: Arc::new(Mutex::new(user_settings)),
|
|
characters: Arc::new(Mutex::new(characters)),
|
|
character_meseta: Arc::new(Mutex::new(character_meseta)),
|
|
bank_meseta: Arc::new(Mutex::new(bank_meseta)),
|
|
items: Arc::new(Mutex::new(items)),
|
|
inventories: Arc::new(Mutex::new(inventories)),
|
|
banks: Arc::new(Mutex::new(banks)),
|
|
equips: Arc::new(Mutex::new(equips)),
|
|
mag_modifiers: Arc::new(Mutex::new(mag_modifiers)),
|
|
weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)),
|
|
trades: Arc::new(Mutex::new(trades)),
|
|
}
|
|
};
|
|
|
|
Ok(Box::new(InMemoryGatewayTransaction {
|
|
working_gateway,
|
|
original_gateway: self,
|
|
}))
|
|
}
|
|
|
|
|
|
async fn with_transaction<'a, F, Fut, R, E>(&'a mut self, func: F) -> Result<R, E>
|
|
where
|
|
Fut: Future<Output = Result<(Box<dyn EntityGatewayTransaction + 'a>, R), E>> + Send + 'a,
|
|
F: FnOnce(Box<dyn EntityGatewayTransaction + 'a>) -> Fut + Send,
|
|
R: Send,
|
|
E: From<GatewayError>,
|
|
{
|
|
let users = self.users.lock().unwrap().clone();
|
|
let user_settings = self.user_settings.lock().unwrap().clone();
|
|
let characters = self.characters.lock().unwrap().clone();
|
|
let character_meseta = self.character_meseta.lock().unwrap().clone();
|
|
let bank_meseta = self.bank_meseta.lock().unwrap().clone();
|
|
let items = self.items.lock().unwrap().clone();
|
|
let inventories = self.inventories.lock().unwrap().clone();
|
|
let banks = self.banks.lock().unwrap().clone();
|
|
let equips = self.equips.lock().unwrap().clone();
|
|
let mag_modifiers = self.mag_modifiers.lock().unwrap().clone();
|
|
let weapon_modifiers = self.weapon_modifiers.lock().unwrap().clone();
|
|
let trades = self.trades.lock().unwrap().clone();
|
|
|
|
let working_gateway = InMemoryGateway {
|
|
users: Arc::new(Mutex::new(users)),
|
|
user_settings: Arc::new(Mutex::new(user_settings)),
|
|
characters: Arc::new(Mutex::new(characters)),
|
|
character_meseta: Arc::new(Mutex::new(character_meseta)),
|
|
bank_meseta: Arc::new(Mutex::new(bank_meseta)),
|
|
items: Arc::new(Mutex::new(items)),
|
|
inventories: Arc::new(Mutex::new(inventories)),
|
|
banks: Arc::new(Mutex::new(banks)),
|
|
equips: Arc::new(Mutex::new(equips)),
|
|
mag_modifiers: Arc::new(Mutex::new(mag_modifiers)),
|
|
weapon_modifiers: Arc::new(Mutex::new(weapon_modifiers)),
|
|
trades: Arc::new(Mutex::new(trades)),
|
|
};
|
|
|
|
let transaction = Box::new(InMemoryGatewayTransaction {
|
|
working_gateway,
|
|
original_gateway: self,
|
|
});
|
|
|
|
let (transaction, result) = func(transaction).await?;
|
|
|
|
transaction.commit().await?;
|
|
Ok(result)
|
|
}
|
|
|
|
async fn create_user(&mut self, user: NewUserAccountEntity) -> Result<UserAccountEntity, GatewayError> {
|
|
let mut users = self.users.lock().unwrap();
|
|
let id = users
|
|
.iter()
|
|
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
|
|
+ 1;
|
|
let user = UserAccountEntity {
|
|
id: UserAccountId(id),
|
|
username: user.username,
|
|
password: user.password,
|
|
guildcard: user.guildcard,
|
|
team_id: user.team_id,
|
|
banned_until: user.banned_until,
|
|
muted_until: user.muted_until,
|
|
created_at: chrono::Utc::now(),
|
|
flags: user.flags,
|
|
activated: user.activated,
|
|
at_login: false,
|
|
at_character: false,
|
|
at_ship: false,
|
|
};
|
|
users.insert(user.id, user.clone());
|
|
Ok(user)
|
|
}
|
|
|
|
async fn get_user_by_id(&mut self, id: UserAccountId) -> Result<UserAccountEntity, GatewayError> {
|
|
let users = self.users.lock().unwrap();
|
|
users.get(&id).cloned().ok_or(GatewayError::Error)
|
|
}
|
|
|
|
async fn get_user_by_name(&mut self, username: String) -> Result<UserAccountEntity, GatewayError> {
|
|
let users = self.users.lock().unwrap();
|
|
users
|
|
.iter()
|
|
.find(|(_, k)| k.username == username)
|
|
.map(|(_, k)| k.clone())
|
|
.ok_or(GatewayError::Error)
|
|
}
|
|
|
|
async fn save_user(&mut self, user: &UserAccountEntity) -> Result<(), GatewayError> {
|
|
let mut users = self.users.lock().unwrap();
|
|
users.insert(user.id, user.clone());
|
|
Ok(())
|
|
}
|
|
|
|
async fn create_user_settings(&mut self, settings: NewUserSettingsEntity) -> Result<UserSettingsEntity, GatewayError> {
|
|
let mut user_settings = self.user_settings.lock().unwrap();
|
|
let id = user_settings
|
|
.iter()
|
|
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
|
|
+ 1;
|
|
let new_settings = UserSettingsEntity {
|
|
id: UserSettingsId(id),
|
|
user_id: settings.user_id,
|
|
settings: settings.settings,
|
|
};
|
|
user_settings.insert(new_settings.id, new_settings.clone());
|
|
Ok(new_settings)
|
|
}
|
|
|
|
async fn get_user_settings_by_user(&mut self, user: &UserAccountEntity) -> Result<UserSettingsEntity, GatewayError> {
|
|
let user_settings = self.user_settings.lock().unwrap();
|
|
user_settings
|
|
.iter()
|
|
.find(|(_, k)| k.user_id == user.id)
|
|
.map(|(_, k)| k.clone())
|
|
.ok_or(GatewayError::Error)
|
|
}
|
|
|
|
async fn get_characters_by_user(&mut self, user: &UserAccountEntity) -> Result<[Option<CharacterEntity>; 4], GatewayError> {
|
|
let characters = self.characters.lock().unwrap();
|
|
const NONE: Option<CharacterEntity> = None;
|
|
let mut chars = [NONE; 4];
|
|
characters
|
|
.iter()
|
|
.filter(|(_, c)| c.user_id == user.id)
|
|
.for_each(|(_, c)| chars[c.slot as usize] = Some(c.clone()));
|
|
Ok(chars)
|
|
}
|
|
|
|
async fn create_character(&mut self, character: NewCharacterEntity) -> Result<CharacterEntity, GatewayError> {
|
|
let mut characters = self.characters.lock().unwrap();
|
|
let id = characters
|
|
.iter()
|
|
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
|
|
+ 1;
|
|
|
|
let new_character = CharacterEntity {
|
|
id: CharacterEntityId(id),
|
|
user_id: character.user_id,
|
|
slot: character.slot,
|
|
name: character.name,
|
|
exp: character.exp,
|
|
char_class: character.char_class,
|
|
section_id: character.section_id,
|
|
appearance: character.appearance,
|
|
techs: character.techs,
|
|
config: character.config,
|
|
info_board: character.info_board,
|
|
guildcard: character.guildcard,
|
|
materials: character.materials,
|
|
tech_menu: character.tech_menu,
|
|
option_flags: character.option_flags,
|
|
keyboard_config: character.keyboard_config,
|
|
gamepad_config: character.gamepad_config,
|
|
};
|
|
characters.insert(new_character.id, new_character.clone());
|
|
Ok(new_character)
|
|
}
|
|
|
|
async fn save_character(&mut self, char: &CharacterEntity) -> Result<(), GatewayError> {
|
|
let mut characters = self.characters.lock().unwrap();
|
|
characters.insert(char.id, char.clone());
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_guild_card_data_by_user(&mut self, user: &UserAccountEntity) -> Result<GuildCardDataEntity, GatewayError> {
|
|
Ok(GuildCardDataEntity::new(user.id))
|
|
}
|
|
|
|
async fn create_item(&mut self, item: NewItemEntity) -> Result<ItemEntity, GatewayError> {
|
|
let mut items = self.items.lock().unwrap();
|
|
let id = items
|
|
.iter()
|
|
.fold(0, |sum, (i, _)| std::cmp::max(sum, i.0))
|
|
+ 1;
|
|
let new_item = ItemEntity {
|
|
id: ItemEntityId(id),
|
|
item: item.item,
|
|
};
|
|
items.insert(ItemEntityId(id), new_item.clone());
|
|
Ok(new_item)
|
|
}
|
|
|
|
async fn add_item_note(&mut self, _item_id: &ItemEntityId, _item_note: ItemNote) -> Result<(), GatewayError> {
|
|
Ok(())
|
|
}
|
|
|
|
async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) -> Result<(), GatewayError> {
|
|
self.mag_modifiers.lock().unwrap()
|
|
.entry(*mag_item_id)
|
|
.or_insert_with(Vec::new)
|
|
.push(mag::MagModifier::FeedMag {
|
|
food: *tool_item_id
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) -> Result<(), GatewayError> {
|
|
self.mag_modifiers.lock().unwrap()
|
|
.entry(*mag_item_id)
|
|
.or_insert_with(Vec::new)
|
|
.push(mag::MagModifier::OwnerChange(character.char_class, character.section_id));
|
|
Ok(())
|
|
}
|
|
|
|
async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) -> Result<(), GatewayError> {
|
|
self.mag_modifiers.lock().unwrap()
|
|
.entry(*mag_item_id)
|
|
.or_insert_with(Vec::new)
|
|
.push(mag::MagModifier::MagCell(*mag_cell_id));
|
|
Ok(())
|
|
}
|
|
|
|
async fn add_weapon_modifier(&mut self, item_id: &ItemEntityId, modifier: weapon::WeaponModifier) -> Result<(), GatewayError> {
|
|
self.weapon_modifiers.lock().unwrap()
|
|
.entry(*item_id)
|
|
.or_insert_with(Vec::new)
|
|
.push(modifier);
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result<InventoryEntity, GatewayError> {
|
|
let inventories = self.inventories.lock().unwrap();
|
|
Ok(inventories
|
|
.iter()
|
|
.find(|(id, _)| **id == *char_id)
|
|
.map(|(_, inv)| inv.clone())
|
|
.map(|inv| self.apply_modifiers(inv))
|
|
.unwrap_or_default())
|
|
}
|
|
|
|
async fn get_character_bank(&mut self, char_id: &CharacterEntityId, _bank_name: &BankName) -> Result<BankEntity, GatewayError> {
|
|
let banks = self.banks.lock().unwrap();
|
|
Ok(banks
|
|
.iter()
|
|
.find(|(id, _)| **id == *char_id)
|
|
.map(|(_, b)| b.clone())
|
|
.unwrap_or_default())
|
|
}
|
|
|
|
async fn set_character_inventory(&mut self, char_id: &CharacterEntityId, inventory: &InventoryEntity) -> Result<(), GatewayError> {
|
|
let mut inventories = self.inventories.lock().unwrap();
|
|
inventories.insert(*char_id, inventory.clone());
|
|
Ok(())
|
|
}
|
|
|
|
// TOOD: impl bank name
|
|
async fn set_character_bank(&mut self, char_id: &CharacterEntityId, bank: &BankEntity, _bank_name: &BankName) -> Result<(), GatewayError> {
|
|
let mut banks = self.banks.lock().unwrap();
|
|
banks.insert(*char_id, bank.clone());
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_character_equips(&mut self, char_id: &CharacterEntityId) -> Result<EquippedEntity, GatewayError> {
|
|
let equips = self.equips.lock().unwrap();
|
|
Ok(equips
|
|
.iter()
|
|
.find(|(id, _)| **id == *char_id)
|
|
.map(|(_, inv)| inv.clone())
|
|
.unwrap_or_default())
|
|
}
|
|
|
|
async fn set_character_equips(&mut self, char_id: &CharacterEntityId, equipped: &EquippedEntity) -> Result<(), GatewayError> {
|
|
let mut equips = self.equips.lock().unwrap();
|
|
equips.insert(*char_id, equipped.clone());
|
|
Ok(())
|
|
}
|
|
|
|
async fn set_character_meseta(&mut self, char_id: &CharacterEntityId, meseta: Meseta) -> Result<(), GatewayError> {
|
|
let mut character_meseta = self.character_meseta.lock().unwrap();
|
|
character_meseta.insert(*char_id, meseta);
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_character_meseta(&mut self, char_id: &CharacterEntityId) -> Result<Meseta, GatewayError> {
|
|
let mut character_meseta = self.character_meseta.lock().unwrap();
|
|
if let Some(meseta) = character_meseta.get_mut(char_id) {
|
|
Ok(*meseta)
|
|
}
|
|
else {
|
|
Err(GatewayError::Error)
|
|
}
|
|
}
|
|
|
|
async fn set_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName, meseta: Meseta) -> Result<(), GatewayError> {
|
|
let mut bank_meseta = self.bank_meseta.lock().unwrap();
|
|
bank_meseta.insert((*char_id, bank.clone()), meseta);
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_bank_meseta(&mut self, char_id: &CharacterEntityId, bank: &BankName) -> Result<Meseta, GatewayError> {
|
|
let mut bank_meseta = self.bank_meseta.lock().unwrap();
|
|
if let Some(meseta) = bank_meseta.get_mut(&(*char_id, bank.clone())) {
|
|
Ok(*meseta)
|
|
}
|
|
else {
|
|
Err(GatewayError::Error)
|
|
}
|
|
}
|
|
|
|
async fn create_trade(&mut self, char_id1: &CharacterEntityId, char_id2: &CharacterEntityId) -> Result<TradeEntity, GatewayError> {
|
|
let mut trades = self.trades.lock().unwrap();
|
|
let id = trades.len() as u32;
|
|
let new_trade = TradeEntity {
|
|
id: TradeId(id),
|
|
character1: *char_id1,
|
|
character2: *char_id2,
|
|
};
|
|
trades.push(new_trade.clone());
|
|
Ok(new_trade)
|
|
}
|
|
}
|