From b3b6dad6ad0162a08ee17872d5a8949c5c5a6d15 Mon Sep 17 00:00:00 2001 From: jake Date: Fri, 10 Dec 2021 13:24:59 -0700 Subject: [PATCH] trades! --- src/entity/gateway/inmemory.rs | 1 - src/ship/items/manager.rs | 182 +++++----- src/ship/items/transaction.rs | 2 +- src/ship/mod.rs | 1 + src/ship/packet/handler/trade.rs | 553 +++++++++++++++++++++++-------- src/ship/ship.rs | 37 +-- src/ship/trade.rs | 172 ++++++++++ 7 files changed, 687 insertions(+), 261 deletions(-) create mode 100644 src/ship/trade.rs diff --git a/src/entity/gateway/inmemory.rs b/src/entity/gateway/inmemory.rs index 24b6500..deb737b 100644 --- a/src/entity/gateway/inmemory.rs +++ b/src/entity/gateway/inmemory.rs @@ -268,7 +268,6 @@ impl EntityGateway for InMemoryGateway { } async fn get_character_inventory(&mut self, char_id: &CharacterEntityId) -> Result { - println!("getting inv"); let inventories = self.inventories.lock().unwrap(); Ok(inventories .iter() diff --git a/src/ship/items/manager.rs b/src/ship/items/manager.rs index 0f0b1b2..f669309 100644 --- a/src/ship/items/manager.rs +++ b/src/ship/items/manager.rs @@ -1,3 +1,4 @@ +use log::warn; use crate::ship::items::ClientItemId; use std::collections::HashMap; use std::cell::RefCell; @@ -9,7 +10,8 @@ use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, ItemEntityId, Inven use crate::entity::item::tool::{Tool, ToolType}; use crate::entity::item::weapon; use crate::ship::map::MapArea; -use crate::ship::ship::{TradeItem, ItemDropLocation}; +use crate::ship::ship::ItemDropLocation; +use crate::ship::trade::TradeItem; use crate::ship::drops::{ItemDrop, ItemDropType}; use crate::ship::location::{AreaClient, RoomId}; use crate::ship::shops::ShopItem; @@ -921,41 +923,41 @@ impl ItemManager { pub async fn trade_items(&mut self, entity_gateway: &mut EG, + room_id: RoomId, p1: (&AreaClient, &CharacterEntity, &Vec), p2: (&AreaClient, &CharacterEntity, &Vec)) -> Result, anyhow::Error> { - let it = ItemTransaction::new(&self, (p1, p2)) - .act(|it, (p1, p2)| -> Result<_, anyhow::Error> { + let it = ItemTransaction::new(&self, (p1, p2, room_id)) + .act(|it, (p1, p2, room_id)| -> Result<_, anyhow::Error> { let p1_inventory = it.manager.get_character_inventory(p1.1)?; let p2_inventory = it.manager.get_character_inventory(p2.1)?; //TODO: inv-selftrade+othertrade <= 30 //if p1_inventory - let trade_items = [(p1, p2, p1_inventory), (p2, p1, p2_inventory)] - .map(|((src_client, dest_client, src_inventory))| { + + let trade_items = [(p1, p2, p1_inventory, p2_inventory), (p2, p1, p2_inventory, p1_inventory)] + .map(|(src_client, dest_client, src_inventory, dest_inventory)| { src_client.2.iter() - .map(|item| -> Option<(Option, Vec>>)> { + .map(|item| -> Option<(Option, Box>)> { match item { TradeItem::Individual(item_id) => { let item = src_inventory.get_item_by_id(*item_id)?.individual()?; + let new_item_id = it.manager.room_item_id_counter.borrow_mut().get_mut(&room_id)?(); Some(( Some(ItemToTrade { add_to: *dest_client.0, remove_from: *src_client.0, - item_id: *item_id, + current_item_id: *item_id, + new_item_id: new_item_id, item_detail: ItemToTradeDetail::Individual(item.item.clone()) }), - vec![ - Box::new(AddIndividualItemToInventory { - character_id: dest_client.1.id, - item_id: item.entity_id, - }), - Box::new(RemoveIndividualItemFromInventory { - character_id: src_client.1.id, - item_id: item.entity_id, - }) - ] + Box::new(TradeIndividualItem { + src_character_id: src_client.1.id, + dest_character_id: dest_client.1.id, + current_item_id: *item_id, + new_item_id: new_item_id, + }), )) }, TradeItem::Stacked(item_id, amount) => { @@ -964,38 +966,33 @@ impl ItemManager { None } else { + let new_item_id = it.manager.room_item_id_counter.borrow_mut().get_mut(&room_id)?(); Some(( Some(ItemToTrade { add_to: *dest_client.0, remove_from: *src_client.0, - item_id: *item_id, + current_item_id: *item_id, + new_item_id: new_item_id, item_detail: ItemToTradeDetail::Stacked(item.tool, *amount) }), - vec![ - Box::new(AddStackedItemToInventory { - character_id: dest_client.1.id, - item_ids: item.entity_ids.iter().cloned().take(*amount).collect(), - }), - Box::new(RemoveStackedItemFromInventory { - character_id: src_client.1.id, - item_ids: item.entity_ids.iter().cloned().take(*amount).collect(), - }), - ] + Box::new(TradeStackedItem { + src_character_id: src_client.1.id, + dest_character_id: dest_client.1.id, + //item_ids: item.entity_ids.iter().cloned().take(*amount).collect(), + current_item_id: *item_id, + new_item_id: new_item_id, + amount: *amount, + }), )) } }, TradeItem::Meseta(amount) => { - Some((None, - vec![ - Box::new(AddMesetaToInventory { - character_id: dest_client.1.id, - amount: *amount, - }), - Box::new(RemoveMesetaFromInventory { - character_id: src_client.1.id, - amount: *amount, - }), - ] + Some((None, // is there a packet that informs other clients about meseta changes? + Box::new(TradeMeseta { + src_character_id: src_client.1.id, + dest_character_id: dest_client.1.id, + amount: *amount, + }), )) } } @@ -1003,11 +1000,13 @@ impl ItemManager { .collect::>>() }); + if let [Some(p1_trades), Some(p2_trades)] = trade_items { - let (p1_item_trades, p1_item_actions): (Vec>, Vec>>>) = p1_trades.into_iter().unzip(); - let (p2_item_trades, p2_item_actions): (Vec>, Vec>>>) = p2_trades.into_iter().unzip(); + let (p1_item_trades, p1_item_actions): (Vec>, Vec>>) = p1_trades.into_iter().unzip(); + let (p2_item_trades, p2_item_actions): (Vec>, Vec>>) = p2_trades.into_iter().unzip(); + let item_trades = p1_item_trades.into_iter().flatten().chain(p2_item_trades.into_iter().flatten()); - let item_actions = p1_item_actions.into_iter().flatten().chain(p2_item_actions.into_iter().flatten()); + let item_actions = p1_item_actions.into_iter().chain(p2_item_actions.into_iter()); for action in item_actions { it.action(action); @@ -1020,25 +1019,30 @@ impl ItemManager { } }); - it.commit(self, entity_gateway) + Ok(it.commit(self, entity_gateway) .await - .map_err(|err| err.into()) + //.map_err(|err| err.into()) + .unwrap()) } } +#[derive(Debug)] pub enum ItemToTradeDetail { Individual(ItemDetail), Stacked(Tool, usize), } +#[derive(Debug)] pub struct ItemToTrade { pub add_to: AreaClient, pub remove_from: AreaClient, - pub item_id: ClientItemId, + pub current_item_id: ClientItemId, + pub new_item_id: ClientItemId, pub item_detail: ItemToTradeDetail, } +#[derive(Debug)] struct RemoveFromLocalFloor { character_id: CharacterEntityId, item_id: ClientItemId, @@ -1046,7 +1050,7 @@ struct RemoveFromLocalFloor { #[async_trait::async_trait] impl ItemAction for RemoveFromLocalFloor { - async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + async fn commit(&self, item_manager: &mut ItemManager, _entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { let local_floor = item_manager.character_floor.get_mut(&self.character_id).ok_or(ItemManagerError::NoCharacter(self.character_id))?; local_floor.remove_item(&self.item_id); Ok(()) @@ -1054,6 +1058,7 @@ impl ItemAction for RemoveFromLocalFloor { } +#[derive(Debug)] struct RemoveFromSharedFloor { room_id: RoomId, item_id: ClientItemId, @@ -1061,7 +1066,7 @@ struct RemoveFromSharedFloor { #[async_trait::async_trait] impl ItemAction for RemoveFromSharedFloor { - async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + async fn commit(&self, item_manager: &mut ItemManager, _entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { let shared_floor = item_manager.room_floor.get_mut(&self.room_id).ok_or(ItemManagerError::NoRoom(self.room_id))?; shared_floor.remove_item(&self.item_id); Ok(()) @@ -1069,6 +1074,7 @@ impl ItemAction for RemoveFromSharedFloor { } +#[derive(Debug)] struct AddIndividualFloorItemToInventory{ character: CharacterEntity, item: IndividualFloorItem, @@ -1080,7 +1086,6 @@ impl ItemAction for AddIndividualFloorItemToInventory { let inventory = item_manager.character_inventory.get_mut(&self.character.id).ok_or(ItemManagerError::NoCharacter(self.character.id))?; let inv_item = inventory.add_individual_floor_item(&self.item); - entity_gateway.add_item_note( &self.item.entity_id, ItemNote::Pickup { @@ -1098,6 +1103,7 @@ impl ItemAction for AddIndividualFloorItemToInventory { } +#[derive(Debug)] struct AddStackedFloorItemToInventory{ character_id: CharacterEntityId, item: StackedFloorItem, @@ -1115,6 +1121,7 @@ impl ItemAction for AddStackedFloorItemToInventory { } +#[derive(Debug)] struct AddMesetaFloorItemToInventory{ character: CharacterEntity, item: MesetaFloorItem, @@ -1131,73 +1138,62 @@ impl ItemAction for AddMesetaFloorItemToInventory { } -struct AddIndividualItemToInventory { - character_id: CharacterEntityId, - item_id: ItemEntityId, +#[derive(Debug)] +struct TradeIndividualItem { + src_character_id: CharacterEntityId, + dest_character_id: CharacterEntityId, + current_item_id: ClientItemId, + new_item_id: ClientItemId, } #[async_trait::async_trait] -impl ItemAction for AddIndividualItemToInventory { - async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { - Ok(()) - } -} - -struct AddStackedItemToInventory { - character_id: CharacterEntityId, - item_ids: Vec, -} - -#[async_trait::async_trait] -impl ItemAction for AddStackedItemToInventory { - async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { - Ok(()) - } -} +impl ItemAction for TradeIndividualItem { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + let src_inventory = item_manager.character_inventory.get_mut(&self.src_character_id).ok_or(ItemManagerError::NoCharacter(self.src_character_id))?; + let inventory_item = src_inventory.take_item_by_id(self.current_item_id).ok_or(ItemManagerError::NoSuchItemId(self.current_item_id))?; + entity_gateway.set_character_inventory(&self.src_character_id, &src_inventory.as_inventory_entity(&self.src_character_id)).await?; -struct RemoveIndividualItemFromInventory { - character_id: CharacterEntityId, - item_id: ItemEntityId, -} + let dest_inventory = item_manager.character_inventory.get_mut(&self.dest_character_id).ok_or(ItemManagerError::NoCharacter(self.dest_character_id))?; + dest_inventory.add_item(inventory_item); + entity_gateway.set_character_inventory(&self.dest_character_id, &dest_inventory.as_inventory_entity(&self.dest_character_id)).await?; -#[async_trait::async_trait] -impl ItemAction for RemoveIndividualItemFromInventory { - async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { Ok(()) } } -struct RemoveStackedItemFromInventory { - character_id: CharacterEntityId, - item_ids: Vec, +#[derive(Debug)] +struct TradeStackedItem { + src_character_id: CharacterEntityId, + dest_character_id: CharacterEntityId, + current_item_id: ClientItemId, + new_item_id: ClientItemId, + amount: usize, } #[async_trait::async_trait] -impl ItemAction for RemoveStackedItemFromInventory { - async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { - Ok(()) - } -} +impl ItemAction for TradeStackedItem { + async fn commit(&self, item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { + let src_inventory = item_manager.character_inventory.get_mut(&self.src_character_id).ok_or(ItemManagerError::NoCharacter(self.src_character_id))?; + let inventory_item = src_inventory.take_stacked_item_by_id(self.current_item_id, self.amount).ok_or(ItemManagerError::NoSuchItemId(self.current_item_id))?; + entity_gateway.set_character_inventory(&self.src_character_id, &src_inventory.as_inventory_entity(&self.src_character_id)).await?; -struct AddMesetaToInventory { - character_id: CharacterEntityId, - amount: usize, -} + let dest_inventory = item_manager.character_inventory.get_mut(&self.dest_character_id).ok_or(ItemManagerError::NoCharacter(self.dest_character_id))?; + dest_inventory.add_stacked_item(inventory_item); + entity_gateway.set_character_inventory(&self.dest_character_id, &dest_inventory.as_inventory_entity(&self.dest_character_id)).await?; -#[async_trait::async_trait] -impl ItemAction for AddMesetaToInventory { - async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { Ok(()) } } -struct RemoveMesetaFromInventory { - character_id: CharacterEntityId, +#[derive(Debug)] +struct TradeMeseta { + src_character_id: CharacterEntityId, + dest_character_id: CharacterEntityId, amount: usize, } #[async_trait::async_trait] -impl ItemAction for RemoveMesetaFromInventory { +impl ItemAction for TradeMeseta { async fn commit(&self, _item_manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError> { Ok(()) } diff --git a/src/ship/items/transaction.rs b/src/ship/items/transaction.rs index 4c385c5..4c2132a 100644 --- a/src/ship/items/transaction.rs +++ b/src/ship/items/transaction.rs @@ -20,7 +20,7 @@ pub enum TransactionCommitError { } #[async_trait::async_trait] -pub trait ItemAction: std::marker::Send + std::marker::Sync { +pub trait ItemAction: std::marker::Send + std::marker::Sync + std::fmt::Debug { async fn commit(&self, manager: &mut ItemManager, entity_gateway: &mut EG) -> Result<(), TransactionCommitError>; } diff --git a/src/ship/mod.rs b/src/ship/mod.rs index f29531b..7ad6a1b 100644 --- a/src/ship/mod.rs +++ b/src/ship/mod.rs @@ -11,3 +11,4 @@ pub mod drops; pub mod packet; pub mod quests; pub mod shops; +pub mod trade; diff --git a/src/ship/packet/handler/trade.rs b/src/ship/packet/handler/trade.rs index d67d28c..bd311d2 100644 --- a/src/ship/packet/handler/trade.rs +++ b/src/ship/packet/handler/trade.rs @@ -6,11 +6,12 @@ use libpso::packet::ship::*; use libpso::packet::messages::*; use crate::common::leveltable::CharacterLevelTable; use crate::common::serverstate::ClientId; -use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms, ItemShops, TradeItem, TradeState, TradeStatus}; -use crate::ship::location::{ClientLocation, ClientLocationError}; +use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms, ItemShops}; +use crate::ship::location::{ClientLocation, ClientLocationError, LocalClientId}; use crate::ship::drops::ItemDrop; use crate::ship::items::{ItemManager, ItemManagerError, ClientItemId, TriggerCreateItem, FloorItem, FloorType, ItemToTradeDetail}; use crate::ship::items::inventory::InventoryItem; +use crate::ship::trade::{TradeItem, TradeState, TradeStatus}; use crate::entity::gateway::EntityGateway; use crate::entity::item; use libpso::utf8_to_utf16_array; @@ -18,80 +19,320 @@ use crate::ship::packet::builder; use crate::ship::shops::{ShopItem, ToolShopItem, ArmorShopItem}; #[derive(thiserror::Error, Debug)] -#[error("")] pub enum TradeError { + #[error("no partner")] CouldNotFindTradePartner, + #[error("item does not match id")] ClientItemIdDidNotMatchItem(ClientItemId, [u8; 16]), + #[error("invalid stack {1}")] InvalidStackAmount(ClientItemId, usize), + #[error("not in trade menu")] NotInTradeMenu, + #[error("trade menu at an invalid point")] + MismatchedStatus, } - -pub async fn inner_items_to_trade(id: ClientId, - items_to_trade: &ItemsToTrade, - entity_gateway: &mut EG, - client_location: &ClientLocation, - clients: &mut Clients, - item_manager: &mut ItemManager) - -> Result + Send>, anyhow::Error> +// TODO: remove target +pub async fn trade_request(id: ClientId, + trade_request: &TradeRequest, + target: u32, + entity_gateway: &mut EG, + client_location: &ClientLocation, + clients: &mut Clients, + item_manager: &mut ItemManager, + trades: &mut TradeState) + -> Result + Send>, anyhow::Error> where EG: EntityGateway { - let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; - let inventory = item_manager.get_character_inventory_mut(&client.character)?; - let trade_partner = client_location.get_client_neighbors(id)? - .into_iter() - .filter(|ac| { - ac.local_client.id() == items_to_trade.trade_target - }) - .next() - .ok_or(TradeError::CouldNotFindTradePartner)?; - - let item_blobs = items_to_trade.items.iter().take(items_to_trade.count as usize); - let trade_items = item_blobs - .map(|item| { - // TOOD: meseta? - let real_item = inventory.get_item_handle_by_id(ClientItemId(item.item_id)) - .ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?; - let real_item = real_item - .item() - .ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?; - let trade_item_bytes: [u8; 16] = item.item_data.iter() - .chain(item.item_data2.iter()) - .cloned().collect::>() - .try_into() - .unwrap(); - if real_item.as_client_bytes() == trade_item_bytes { - match real_item { - InventoryItem::Individual(individual_inventory_item) => { - Ok(TradeItem::Individual(individual_inventory_item.item_id)) - }, - InventoryItem::Stacked(stacked_inventory_item) => { - let amount = trade_item_bytes[5] as usize; - if amount > stacked_inventory_item.entity_ids.len() { - Ok(TradeItem::Stacked(stacked_inventory_item.item_id, amount)) + let trade_request = trade_request.clone(); // TODO: make this function take ownership of packet + match trade_request.trade { + TradeRequestCommand::Initialize(ref act, meseta) => { + match act { + TradeRequestInitializeCommand::Initialize => { + let trade_partner = client_location.get_client_neighbors(id)? + .into_iter() + .filter(|ac| { + ac.local_client.id() == trade_request.client + }) + .next() + .ok_or(TradeError::CouldNotFindTradePartner)?; + trades.new_trade(&id, &trade_partner.client); + Ok(Box::new(client_location.get_all_clients_by_client(id)?.into_iter() + .filter(move |client| client.local_client.id() == target as u8) + .map(move |client| { + (client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone())))) + }))) + }, + TradeRequestInitializeCommand::Respond => { + Ok(trades + .with(&id, |this, other| -> Option + Send>> { + if this.status == TradeStatus::ReceivedRequest && other.status == TradeStatus::SentRequest { + this.status = TradeStatus::Trading; + other.status = TradeStatus::Trading; + + let trade_request = trade_request.clone(); + Some(Box::new(client_location.get_all_clients_by_client(id).ok()?.into_iter() + .filter(move |client| client.local_client.id() == target as u8) + .map(move |client| { + (client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone())))) + }))) + } + else { + None + } + })? + .unwrap_or_else(|| -> Box + Send> { + trades.remove_trade(&id); + Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| client.local_client.id() == target as u8) + .map(move |client| { + (client.client, SendShipPacket::CancelTrade(CancelTrade {})) + }) + .chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))) + })) + } + } + }, + TradeRequestCommand::AddItem(item_id, amount) => { + Ok(trades + .with(&id, |this, other| -> Option + Send>> { + if this.status == TradeStatus::Trading && other.status == TradeStatus::Trading { + let client = clients.get(&this.client())?;//.ok_or(ShipError::ClientNotFound(id)).ok()?; + let inventory = item_manager.get_character_inventory(&client.character).ok()?; + let item = inventory.get_item_by_id(ClientItemId(item_id))?; + + match item { + InventoryItem::Individual(_) => { + this.items.push(TradeItem::Individual(ClientItemId(item_id))); + }, + InventoryItem::Stacked(stacked_item) => { + if stacked_item.count() < amount as usize { + return None; + } + this.items.push(TradeItem::Stacked(ClientItemId(item_id), amount as usize)); + }, } - else { - Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into()) + + let trade_request = trade_request.clone(); + Some(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| client.local_client.id() == target as u8) + .map(move |client| { + (client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone())))) + }))) + } + else { + None + } + })? + .unwrap_or_else(|| { + trades.remove_trade(&id); + Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| client.local_client.id() == target as u8) + .map(move |client| { + (client.client, SendShipPacket::CancelTrade(CancelTrade {})) + }) + .chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))) + })) + }, + TradeRequestCommand::RemoveItem(item_id, amount) => { + Ok(trades + .with(&id, |this, other| -> Option + Send>> { + if this.status == TradeStatus::Trading && other.status == TradeStatus::Trading { + let client = clients.get(&this.client())?; //.ok_or(ShipError::ClientNotFound(id)).ok()?; + let inventory = item_manager.get_character_inventory(&client.character).ok()?; + let item = inventory.get_item_by_id(ClientItemId(item_id))?; + + match item { + InventoryItem::Individual(_) => { + this.items.retain(|item| { + item.item_id() != ClientItemId(item_id) + }) + //this.items.push(TradeItem::Individual(ClientItemId(item_id))); + }, + InventoryItem::Stacked(stacked_item) => { + let trade_item_index = this.items.iter() + .position(|item| { + item.item_id() == ClientItemId(item_id) + })?; + + match this.items[trade_item_index].stacked()?.1.cmp(&(amount as usize)) { + std::cmp::Ordering::Greater => { + this.items[trade_item_index].stacked()?.1 -= amount as usize; + }, + std::cmp::Ordering::Equal => { + this.items.remove(trade_item_index); + }, + std::cmp::Ordering::Less => { + return None + } + } + }, } + let trade_request = trade_request.clone(); + Some(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| client.local_client.id() == target as u8) + .map(move |client| { + (client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone())))) + }))) } - } - } - else { - Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into()) + else { + None + } + })? + .unwrap_or_else(|| { + trades.remove_trade(&id); + Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| client.local_client.id() == target as u8) + .map(move |client| { + (client.client, SendShipPacket::CancelTrade(CancelTrade {})) + }) + .chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))) + })) + }, + TradeRequestCommand::Confirm => { + Ok(trades + .with(&id, |this, other| -> Option + Send>> { + if this.status == TradeStatus::Trading && (other.status == TradeStatus::Trading || other.status == TradeStatus::Confirmed) { + this.status = TradeStatus::Confirmed; + + let trade_request = trade_request.clone(); + Some(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| client.local_client.id() == target as u8) + .map(move |client| { + (client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone())))) + }))) + } + else { + None + } + })? + .unwrap_or_else(|| { + trades.remove_trade(&id); + Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| client.local_client.id() == target as u8) + .map(move |client| { + (client.client, SendShipPacket::CancelTrade(CancelTrade {})) + }) + .chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))) + })) + }, + TradeRequestCommand::FinalConfirm => { + Ok(trades + .with(&id, |this, other| -> Option + Send>> { + if this.status == TradeStatus::Confirmed && (other.status == TradeStatus::Confirmed || other.status == TradeStatus::FinalConfirm) { + this.status = TradeStatus::FinalConfirm; + + let trade_request = trade_request.clone(); + Some(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| client.local_client.id() == target as u8) + .map(move |client| { + (client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone())))) + }))) + } + else { + None + } + })? + .unwrap_or_else(|| { + trades.remove_trade(&id); + Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| client.local_client.id() == target as u8) + .map(move |client| { + (client.client, SendShipPacket::CancelTrade(CancelTrade {})) + }) + .chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))) + })) + }, + } +} + + +fn status_is(status: &TradeStatus, statuses: &[TradeStatus; N]) -> bool { + statuses.iter().any(|s| s == status) +} + +fn status_is_not(status: &TradeStatus, statuses: &[TradeStatus; N]) -> bool { + !status_is(status, statuses) +} + +pub async fn inner_items_to_trade(id: ClientId, + items_to_trade: &ItemsToTrade, + entity_gateway: &mut EG, + client_location: &ClientLocation, + clients: &mut Clients, + item_manager: &mut ItemManager, + trades: &mut TradeState) + -> Result + Send>, anyhow::Error> +where + EG: EntityGateway +{ + Ok(trades + .with(&id, |this, other| -> Result + Send>, anyhow::Error> { + println!("statuses {:?} {:?}", this.status, other.status); + if status_is_not(&this.status, &[TradeStatus::FinalConfirm]) || status_is_not(&other.status, &[TradeStatus::FinalConfirm, TradeStatus::ItemsChecked]) { + //if this.status != TradeStatus::FinalConfirm || (other.status != TradeStatus::FinalConfirm || other.status != TradeStatus::ItemsChecked) { + return Err(TradeError::MismatchedStatus.into()) } - }) - .collect::, anyhow::Error>>()?; - // TODO: check room in inventory for items - client.trade = Some(TradeState { - other_client: trade_partner.client, - items: trade_items, - status: TradeStatus::Unconfirmed - }); + let client = clients.get(&this.client()).ok_or(ShipError::ClientNotFound(this.client()))?; + let inventory = item_manager.get_character_inventory(&client.character)?; + + let item_blobs = items_to_trade.items.iter().take(items_to_trade.count as usize); + let trade_items = item_blobs + .map(|item| { + // TOOD: meseta? + let real_item = inventory.get_item_by_id(ClientItemId(item.item_id)) + .ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?; + let trade_item_bytes: [u8; 16] = item.item_data.iter() + .chain(item.item_data2.iter()) + .cloned().collect::>() + .try_into() + .unwrap(); + match real_item { + InventoryItem::Individual(individual_inventory_item) => { + println!("real indiv item: {:?} {:?} ==? {:?}", real_item, real_item.as_client_bytes(), trade_item_bytes); + if real_item.as_client_bytes() == trade_item_bytes { + Ok(TradeItem::Individual(individual_inventory_item.item_id)) + } + else { + Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into()) + } + }, + InventoryItem::Stacked(stacked_inventory_item) => { + println!("real stack item: {:?} {:?} ==? {:?}", real_item, real_item.as_client_bytes(), trade_item_bytes); + if real_item.as_client_bytes()[0..4] == trade_item_bytes[0..4] { + let amount = trade_item_bytes[5] as usize; + if amount <= stacked_inventory_item.entity_ids.len() { + Ok(TradeItem::Stacked(stacked_inventory_item.item_id, amount)) + } + else { + Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into()) + } + } + else { + Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into()) + } + } + } + }) + .collect::, anyhow::Error>>()?; - Ok(Box::new(vec![(trade_partner.client, SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {}))].into_iter())) + this.status = TradeStatus::ItemsChecked; + Ok(Box::new(std::iter::once((other.client(), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {}))))) + })? + .unwrap_or_else(|err| { + println!("asdf {:?}", err); + log::warn!("trade error: {:?}", err); + let (this, other) = trades.remove_trade(&id); + Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| other.as_ref().map(|other| client.client == other.client() ).unwrap_or_else(|| false)) + .map(move |client| { + (client.client, SendShipPacket::CancelTrade(CancelTrade {})) + }) + .chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))) + })) } pub async fn items_to_trade(id: ClientId, @@ -99,28 +340,25 @@ pub async fn items_to_trade(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager) + item_manager: &mut ItemManager, + trades: &mut TradeState) -> Result + Send>, anyhow::Error> where EG: EntityGateway { - let t = inner_items_to_trade(id, items_to_trade_pkt, entity_gateway, client_location, clients, item_manager).await; + let t = inner_items_to_trade(id, items_to_trade_pkt, entity_gateway, client_location, clients, item_manager, trades).await; match t { Ok(p) => Ok(p), Err(err) => { - let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; - let trade_partner = client.trade.as_ref() - .and_then(|trade_state| { - client_location.get_local_client(trade_state.other_client).ok() - }) - .map(|trade_partner| { - (trade_partner.client, SendShipPacket::CancelTrade(CancelTrade {})) - }); - - log::warn!("error in trading: {:?}", err); - Ok(Box::new(vec![(id, SendShipPacket::CancelTrade(CancelTrade {}))] - .into_iter() - .chain(trade_partner.into_iter()))) + log::warn!("atrade error: {:?}", err); + println!("qwer"); + let (this, other) = trades.remove_trade(&id); + Ok(Box::new(client_location.get_all_clients_by_client(id)?.into_iter() + .filter(move |client| other.as_ref().map(|other| client.client == other.client()).unwrap_or_else(|| false)) + .map(move |client| { + (client.client, SendShipPacket::CancelTrade(CancelTrade {})) + }) + .chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))) } } } @@ -129,83 +367,112 @@ pub async fn trade_confirmed(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, - item_manager: &mut ItemManager) + item_manager: &mut ItemManager, + trades: &mut TradeState) -> Result + Send>, anyhow::Error> where EG: EntityGateway { - { - let this_client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; - this_client.trade.as_mut().ok_or(TradeError::NotInTradeMenu)?.status = TradeStatus::Confirmed; + enum TradeReady<'a> { + OnePlayer, + BothPlayers(crate::ship::location::RoomId, + (crate::ship::location::AreaClient, &'a crate::ship::ship::ClientState, crate::ship::trade::ClientTradeState), + (crate::ship::location::AreaClient, &'a crate::ship::ship::ClientState, crate::ship::trade::ClientTradeState)), } - let both_confirmed = { - let this_client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; - let other_client_id = this_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.other_client; - let other_client = clients.get(&other_client_id).ok_or(ShipError::ClientNotFound(other_client_id))?; - - let this_client_trade = this_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?; - let other_client_trade = other_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?; - this_client_trade.status == TradeStatus::Confirmed && other_client_trade.status == TradeStatus::Confirmed - }; - - if both_confirmed { - let this_client_trade = { - let this_client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; - let this_client_trade = this_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.clone(); - this_client.trade = None; - this_client_trade - }; - let other_client_trade = { - let other_client = clients.get_mut(&this_client_trade.other_client).ok_or(ShipError::ClientNotFound(this_client_trade.other_client))?; - let other_client_trade = other_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.clone(); - other_client.trade = None; - other_client_trade - }; - let this_client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; - let other_client = clients.get(&this_client_trade.other_client).ok_or(ShipError::ClientNotFound(this_client_trade.other_client))?; - - let this_local_client = client_location.get_local_client(id)?; - let other_local_client = client_location.get_local_client(this_client_trade.other_client)?; - - let traded_items = item_manager.trade_items( - entity_gateway, - (&this_local_client, &this_client.character, &this_client_trade.items), - (&other_local_client, &other_client.character, &other_client_trade.items) - ).await?; - - let clients_in_room = client_location.get_all_clients_by_client(id)?; - let traded_item_packets = traded_items - .into_iter() - .map(|item| { - match item.item_detail { - ItemToTradeDetail::Individual(item_detail) => { - [ - GameMessage::CreateItem(builder::message::create_individual_item(item.add_to, item.item_id, &item_detail).unwrap()), - GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.item_id, 1)) // TODO: amount = ? - ] - }, - ItemToTradeDetail::Stacked(tool, amount) => { - [ - GameMessage::CreateItem(builder::message::create_stacked_item(item.add_to, item.item_id, &tool, amount).unwrap()), - GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.item_id, amount as u32)) - ] - }, + let trade_instructions = trades + .with(&id, |this, other| -> Result<_, anyhow::Error> { + if status_is_not(&this.status, &[TradeStatus::ItemsChecked]) || status_is_not(&other.status, &[TradeStatus::ItemsChecked, TradeStatus::TradeComplete]) { + return Err(TradeError::MismatchedStatus.into()) + } + + // TODO: check for space in inventory! + this.status = TradeStatus::TradeComplete; + + if this.status == TradeStatus::TradeComplete && other.status == TradeStatus::TradeComplete { + let this_client = clients.get(&this.client()).ok_or(ShipError::ClientNotFound(this.client()))?; + let other_client = clients.get(&other.client()).ok_or(ShipError::ClientNotFound(other.client()))?; + let this_local_client = client_location.get_local_client(this.client())?; + let other_local_client = client_location.get_local_client(other.client())?; + let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?; + + Ok(TradeReady::BothPlayers(room_id, + (this_local_client, this_client, this.clone()), + (other_local_client, other_client, other.clone()))) + } + else { + Ok(TradeReady::OnePlayer) + } + }); + + // TODO: this match needs to handle errors better + match trade_instructions { + Ok(Ok(trade)) => { + match trade { + TradeReady::OnePlayer => { + Ok(Box::new(None.into_iter()) as Box + Send>) + }, + TradeReady::BothPlayers(room_id, (this_local_client, this_client, this), (other_local_client, other_client, other)) => { + let traded_items = item_manager.trade_items(entity_gateway, + room_id, + (&this_local_client, &this_client.character, &this.items), + (&other_local_client, &other_client.character, &other.items)).await?; + + let clients_in_room = client_location.get_all_clients_by_client(id)?; + let traded_item_packets = traded_items + .into_iter() + .map(|item| { + match item.item_detail { + ItemToTradeDetail::Individual(item_detail) => { + [ + GameMessage::CreateItem(builder::message::create_individual_item(item.add_to, item.new_item_id, &item_detail).unwrap()), + GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.current_item_id, 1)) // TODO: amount = ? + ] + }, + ItemToTradeDetail::Stacked(tool, amount) => { + [ + GameMessage::CreateItem(builder::message::create_stacked_item(item.add_to, item.new_item_id, &tool, amount).unwrap()), + GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.current_item_id, amount as u32)) + ] + }, + } + }) + .flatten() + .map(move |packet| { + clients_in_room + .clone() + .into_iter() + .filter_map(move |client| { + match packet { + GameMessage::PlayerNoLongerHasItem(ref no_longer) => { + if client.local_client == no_longer.client { + None + } + else { + Some((client.client, SendShipPacket::Message(Message::new(packet.clone())))) + } + } + _ => Some((client.client, SendShipPacket::Message(Message::new(packet.clone())))) + } + }) + }) + .flatten(); + let close_trade = vec![ + (this.client(), SendShipPacket::TradeSuccessful(TradeSuccessful::default())), + (other.client(), SendShipPacket::TradeSuccessful(TradeSuccessful::default())) + ].into_iter(); + Ok(Box::new(traded_item_packets.chain(close_trade))) } - }) - .flatten() - .map(move |packet| { - clients_in_room - .clone() - .into_iter() + } + }, + err @ _ => { + let (this, other) = trades.remove_trade(&id); + Ok(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter() + .filter(move |client| other.as_ref().map(|other| client.client == other.client()).unwrap_or_else(|| false)) .map(move |client| { - (client.client, SendShipPacket::Message(Message::new(packet.clone()))) + (client.client, SendShipPacket::CancelTrade(CancelTrade {})) }) - }) - .flatten(); - Ok(Box::new(traded_item_packets)) - } - else { - Ok(Box::new(None.into_iter())) + .chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))) + } } } diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 8166936..0b97463 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -33,6 +33,7 @@ use crate::ship::quests; use crate::ship::map::{MapsError, MapAreaError, MapArea}; use crate::ship::packet::handler; use crate::ship::shops::{WeaponShop, ToolShop, ArmorShop, WeaponShopItem, ToolShopItem, ArmorShopItem}; +use crate::ship::trade::TradeState; pub const SHIP_PORT: u16 = 23423; pub const QUEST_CATEGORY_MENU_ID: u32 = 0xA2; @@ -41,7 +42,7 @@ pub type Rooms = [Option; MAX_ROOMS]; pub type Clients = HashMap; #[derive(Error, Debug)] -#[error("")] +#[error("shiperror")] pub enum ShipError { ClientNotFound(ClientId), NoCharacterInSlot(ClientId, u32), @@ -73,6 +74,7 @@ pub enum ShipError { InvalidShip(usize), InvalidBlock(usize), InvalidItem(items::ClientItemId), + TradeError(#[from] crate::ship::packet::handler::trade::TradeError), } #[derive(Debug)] @@ -190,6 +192,7 @@ pub enum SendShipPacket { RedirectClient(RedirectClient), AcknowledgeTrade(AcknowledgeTrade), CancelTrade(CancelTrade), + TradeSuccessful(TradeSuccessful), } impl SendServerPacket for SendShipPacket { @@ -229,6 +232,7 @@ impl SendServerPacket for SendShipPacket { SendShipPacket::RedirectClient(pkt) => pkt.as_bytes(), SendShipPacket::AcknowledgeTrade(pkt) => pkt.as_bytes(), SendShipPacket::CancelTrade(pkt) => pkt.as_bytes(), + SendShipPacket::TradeSuccessful(pkt) => pkt.as_bytes(), } } } @@ -248,25 +252,10 @@ pub struct LoadingQuest { } -#[derive(Clone)] -pub enum TradeItem { - Individual(items::ClientItemId), - Stacked(items::ClientItemId, usize), - Meseta(usize), -} -#[derive(Clone, Eq, PartialEq)] -pub enum TradeStatus { - Confirmed, - Unconfirmed, -} -#[derive(Clone)] -pub struct TradeState { - pub other_client: ClientId, - pub items: Vec, - pub status: TradeStatus, -} + + pub struct ClientState { pub user: UserAccountEntity, @@ -286,7 +275,6 @@ pub struct ClientState { pub tool_shop: Vec, pub armor_shop: Vec, pub tek: Option<(items::ClientItemId, item::weapon::TekSpecialModifier, item::weapon::TekPercentModifier, i32)>, - pub trade: Option, } impl ClientState { @@ -307,7 +295,6 @@ impl ClientState { tool_shop: Vec::new(), armor_shop: Vec::new(), tek: None, - trade: None, } } } @@ -411,6 +398,7 @@ impl ShipServerStateBuilder { auth_token: self.auth_token.unwrap_or_else(|| AuthToken("".into())), ship_list: Vec::new(), shipgate_sender: None, + trades: Default::default(), } } } @@ -457,6 +445,7 @@ pub struct ShipServerState { auth_token: AuthToken, ship_list: Vec, shipgate_sender: Option>, + trades: TradeState, } impl ShipServerState { @@ -561,6 +550,9 @@ impl ShipServerState { GameMessage::TekAccept(tek_accept) => { handler::direct_message::accept_tek_item(id, tek_accept, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? }, + GameMessage::TradeRequest(trade_request) => { + handler::trade::trade_request(id, trade_request, target, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await? + }, _ => { let cmsg = msg.clone(); Box::new(block.client_location.get_all_clients_by_client(id).unwrap().into_iter() @@ -729,13 +721,12 @@ impl ServerState for ShipServerState { handler::ship::block_list(id, &self.name, self.blocks.0.len()) }, RecvShipPacket::ItemsToTrade(items_to_trade) => { - log::warn!("trade! {:?} {:?}", id, items_to_trade); let block = self.blocks.with_client(id, &self.clients)?; - handler::trade::items_to_trade(id, items_to_trade, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + handler::trade::items_to_trade(id, items_to_trade, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await? }, RecvShipPacket::TradeConfirmed(_) => { let block = self.blocks.with_client(id, &self.clients)?; - handler::trade::trade_confirmed(id, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + handler::trade::trade_confirmed(id, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager, &mut self.trades).await? }, }) } diff --git a/src/ship/trade.rs b/src/ship/trade.rs new file mode 100644 index 0000000..eb88edc --- /dev/null +++ b/src/ship/trade.rs @@ -0,0 +1,172 @@ +use std::collections::HashMap; +use std::cell::RefCell; +use async_std::sync::{Arc, Mutex}; + +use crate::common::serverstate::ClientId; +use crate::ship::items; + +pub const MESETA_ITEM_ID: items::ClientItemId = items::ClientItemId(0xFFFFFF01); + +#[derive(Debug, Clone)] +pub enum TradeItem { + Individual(items::ClientItemId), + Stacked(items::ClientItemId, usize), + Meseta(usize), +} + +impl TradeItem { + pub fn stacked(&self) -> Option<(items::ClientItemId, usize)> { + match self { + TradeItem::Stacked(item_id, amount) => Some((*item_id, *amount as usize)), + _ => None + } + } + + pub fn item_id(&self) -> items::ClientItemId { + match self { + TradeItem::Individual(item_id) => *item_id, + TradeItem::Stacked(item_id, _) => *item_id, + TradeItem::Meseta(_) => MESETA_ITEM_ID, + } + } +} + + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum TradeStatus { + SentRequest, + ReceivedRequest, + Trading, + Confirmed, + FinalConfirm, + ItemsChecked, + TradeComplete, +} + + +#[derive(Debug, Clone)] +pub struct ClientTradeState { + client: ClientId, + other_client: ClientId, + pub items: Vec, + pub status: TradeStatus, +} + + +impl ClientTradeState { + pub fn client(&self) -> ClientId { + self.client + } + + pub fn other_client(&self) -> ClientId { + self.other_client + } +} + +/* +pub struct ClientTradeHandle<'a> { + handle: std::cell::Ref<'a, ClientTradeState>, +} + +impl<'a> ClientTradeHandle<'a> { + fn new(handle: std::cell::Ref<'a, ClientTradeState>) -> ClientTradeHandle<'a> { + ClientTradeHandle { + handle: handle, + } + } +} + +impl<'a> std::ops::Deref for ClientTradeHandle<'a> { + type Target = ClientTradeState; + fn deref(&self) -> &ClientTradeState { + &self.handle + } +} + +impl<'a> std::ops::DerefMut for ClientTradeHandle<'a> { + fn deref_mut(&mut self) -> &mut ClientTradeState { + &mut self.handle + } +} + +impl<'a> Clone for ClientTradeHandle<'a> { + +} +*/ + + +#[derive(thiserror::Error, Debug)] +#[error("")] +pub enum TradeStateError { + ClientNotInTrade(ClientId), + MismatchedTrade(ClientId, ClientId), +} + +#[derive(Default)] +pub struct TradeState { + trades: HashMap>, +} + +impl TradeState { + pub fn new_trade(&mut self, sender: &ClientId, receiver: &ClientId) { + let state = ClientTradeState { + client: *sender, + other_client: *receiver, + items: Default::default(), + status: TradeStatus::SentRequest, + }; + self.trades.insert(*sender, RefCell::new(state)); + + let state = ClientTradeState { + client: *receiver, + other_client: *sender, + items: Default::default(), + status: TradeStatus::ReceivedRequest, + }; + self.trades.insert(*receiver, RefCell::new(state)); + } + + pub fn with (&self, client: &ClientId, func: F) -> Result + where + F: Fn(&mut ClientTradeState, &mut ClientTradeState) -> T + { + let mut c1 = self.trades.get(client).ok_or_else(|| TradeStateError::ClientNotInTrade(*client))?.borrow_mut(); + let mut c2 = self.trades.get(&c1.other_client).ok_or_else(|| TradeStateError::ClientNotInTrade(c1.other_client))?.borrow_mut(); + + // sanity check + if c1.client != c2.other_client { + return Err(TradeStateError::MismatchedTrade(c1.client, c2.client)); + } + + Ok(func(&mut *c1, &mut *c2)) + } + /* + pub fn with (&self, client: &ClientId, func: F) -> Result + where + F: Fn(&mut ClientTradeHandle, &mut ClientTradeHandle) -> T + { + let c1 = ClientTradeHandle::new(self.trades.get(client).ok_or_else(|| TradeStateError::ClientNotInTrade(*client))?.borrow_mut()); + let c2 = ClientTradeHandle::new(self.trades.get(&c1.other_client).ok_or_else(|| TradeStateError::ClientNotInTrade(c1.other_client))?.borrow_mut()); + + // sanity check + if c1.client != c2.other_client { + return Err(TradeStateError::MismatchedTrade(c1.client, c2.client)); + } + + Ok(func(&mut c1, &mut c2)) + } + */ + + // TODO: is it possible for this to not return Options? + pub fn remove_trade(&mut self, client: &ClientId) -> (Option, Option) { + let c1 = self.trades.remove(client).map(|c| c.into_inner()); + let c2 = if let Some(ref state) = c1 { + self.trades.remove(&state.other_client).map(|c| c.into_inner()) + } + else { + None + }; + + (c1, c2) + } +}