From 81916d1f572247cafb71169db8f61b3d4cbc7465 Mon Sep 17 00:00:00 2001 From: jake Date: Mon, 27 Dec 2021 00:48:42 -0700 Subject: [PATCH] TRADING JUST IN TIME TO BARELY MISS XMAS --- src/ship/items/manager.rs | 51 +- src/ship/packet/handler/trade.rs | 56 +- src/ship/trade.rs | 15 +- tests/test_trade.rs | 4271 ++++++++++++++++++++++++++++++ 4 files changed, 4372 insertions(+), 21 deletions(-) create mode 100644 tests/test_trade.rs diff --git a/src/ship/items/manager.rs b/src/ship/items/manager.rs index 044aabd..3031511 100644 --- a/src/ship/items/manager.rs +++ b/src/ship/items/manager.rs @@ -275,15 +275,6 @@ impl ItemManager { )) } - /*pub fn get_character_bank_mut(&mut self, character: &CharacterEntity) -> Result<&CharacterBank, ItemManagerError> { - Ok(self.character_bank - .get_mut(&character.id) - .ok_or(ItemManagerError::NoCharacter(character.id))? - .entry(BankName("".to_string())) - .or_insert(CharacterBank::new(Vec::new()))) - //.ok_or(ItemManagerError::InvalidBankName(BankName("".to_string())))?) - }*/ - pub fn remove_character_from_room(&mut self, character: &CharacterEntity) { self.character_inventory.remove(&character.id); self.character_floor.remove(&character.id); @@ -986,14 +977,41 @@ impl ItemManager { let p1_inventory = it.manager.get_character_inventory(p1.1)?; let p2_inventory = it.manager.get_character_inventory(p2.1)?; - [(p2_inventory, p1_inventory, p2.2), (p1_inventory, p2_inventory, p1.2)].iter() - .map(|(src_inventory, dest_inventory, to_trade)| { - to_trade + [(p2_inventory, p1_inventory, p2.2, p1.2), (p1_inventory, p2_inventory, p1.2, p2.2)].iter() + .map(|(src_inventory, dest_inventory, trade_recv, trade_send)| { + let item_slots_lost_to_trade = trade_send + .iter() + .fold(0, |acc, item| { + match item { + TradeItem::Individual(..) => { + acc + 1 + }, + TradeItem::Stacked(item_id, amount) => { + let stacked_inventory_item = try { + src_inventory + .get_item_by_id(*item_id)? + .stacked() + }; + if let Some(Some(item)) = stacked_inventory_item { + if item.count() == *amount { + acc + 1 + } + else { + acc + } + } + else { + acc + } + } + } + }); + trade_recv .iter() .try_fold(dest_inventory.count(), |acc, item| { match item { TradeItem::Individual(..) => { - if acc >= 30 { + if acc >= (30 + item_slots_lost_to_trade) { Err(TradeError::NoInventorySpace) } else { @@ -1017,7 +1035,12 @@ impl ItemManager { Err(TradeError::NoStackSpace) }, SpaceForStack::No(NoThereIsNotSpace::FullInventory) => { - Err(TradeError::NoInventorySpace) + if acc >= (30 + item_slots_lost_to_trade) { + Err(TradeError::NoInventorySpace) + } + else { + Ok(acc + 1) + } }, } } diff --git a/src/ship/packet/handler/trade.rs b/src/ship/packet/handler/trade.rs index 8300bb9..1782546 100644 --- a/src/ship/packet/handler/trade.rs +++ b/src/ship/packet/handler/trade.rs @@ -42,6 +42,14 @@ pub enum TradeError { NoStackSpace, #[error("invalid meseta amount")] InvalidMeseta, + #[error("tried starting a trade while in one already")] + ClientAlreadyInTrade, + #[error("tried starting a trade while with player already in a trade")] + OtherAlreadyInTrade, + #[error("tried to trade item not specified in trade request")] + SketchyTrade, + #[error("items in trade window and items attempted to trade do not match")] + MismatchedTradeItems, } @@ -63,6 +71,9 @@ where TradeRequestCommand::Initialize(ref act, meseta) => { match act { TradeRequestInitializeCommand::Initialize => { + if trades.in_trade(&id) { + return Err(TradeError::ClientAlreadyInTrade.into()) + } let trade_partner = client_location.get_client_neighbors(id)? .into_iter() .filter(|ac| { @@ -70,6 +81,9 @@ where }) .next() .ok_or(TradeError::CouldNotFindTradePartner)?; + if trades.in_trade(&trade_partner.client) { + return Err(TradeError::OtherAlreadyInTrade.into()) + } 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) @@ -180,7 +194,7 @@ where 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; + *this.items[trade_item_index].stacked_mut()?.1 -= amount as usize; }, std::cmp::Ordering::Equal => { this.items.remove(trade_item_index); @@ -267,6 +281,15 @@ where .chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))) })) }, + TradeRequestCommand::Cancel => { + trades.remove_trade(&id); + Ok(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 {})))))) + } } } @@ -297,17 +320,31 @@ where } let 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 inventory = item_manager.get_character_inventory(&client.character)?; - let item_blobs = items_to_trade.items.iter().take(items_to_trade.count as usize); - item_blobs + if items_to_trade.count as usize != (this.items.len() + (if this.meseta != 0 { 1 } else { 0 })) { + return Err(TradeError::MismatchedTradeItems.into()) + } + + items_to_trade.items + .iter() + .take(items_to_trade.count as usize) .map(|item| { if ClientItemId(item.item_id) == OTHER_MESETA_ITEM_ID { if item.item_data[0] != 4 { return Err(TradeError::InvalidItemId(ClientItemId(item.item_id)).into()) } let amount = u32::from_le_bytes(item.item_data2); - if amount > client.character.meseta { + let character_meseta = item_manager.get_character_meseta(&client.character.id).map_err(|_| TradeError::InvalidMeseta)?; + let other_character_meseta = item_manager.get_character_meseta(&other_client.character.id).map_err(|_| TradeError::InvalidMeseta)?; + if amount > character_meseta.0 { + return Err(TradeError::InvalidMeseta.into()) + } + if (amount + other_character_meseta.0) > 999999 { + return Err(TradeError::InvalidMeseta.into()) + } + if amount != this.meseta as u32{ return Err(TradeError::InvalidMeseta.into()) } Ok(()) @@ -315,6 +352,10 @@ where else { let real_item = inventory.get_item_by_id(ClientItemId(item.item_id)) .ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?; + let real_trade_item = this.items + .iter() + .find(|i| i.item_id() == ClientItemId(item.item_id)) + .ok_or_else(|| TradeError::SketchyTrade)?; let trade_item_bytes: [u8; 16] = item.item_data.iter() .chain(item.item_data2.iter()) .cloned().collect::>() @@ -333,7 +374,12 @@ where 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(()) + if real_trade_item.stacked().ok_or_else(|| TradeError::SketchyTrade)?.1 == amount { + Ok(()) + } + else { + Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into()) + } } else { Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into()) diff --git a/src/ship/trade.rs b/src/ship/trade.rs index e6e1ae3..c58fdb2 100644 --- a/src/ship/trade.rs +++ b/src/ship/trade.rs @@ -14,12 +14,19 @@ pub enum TradeItem { impl TradeItem { pub fn stacked(&self) -> Option<(items::ClientItemId, usize)> { match self { - TradeItem::Stacked(item_id, amount) => Some((*item_id, *amount as usize)), + TradeItem::Stacked(item_id, amount) => Some((*item_id, *amount)), _ => None } } - pub fn item_id(&self) -> items::ClientItemId { + pub fn stacked_mut(&mut self) -> Option<(items::ClientItemId, &mut usize)> { + match self { + TradeItem::Stacked(item_id, ref mut amount) => Some((*item_id, amount)), + _ => None + } + } + + pub fn item_id(&self) -> items::ClientItemId { match self { TradeItem::Individual(item_id) => *item_id, TradeItem::Stacked(item_id, _) => *item_id, @@ -125,6 +132,10 @@ impl TradeState { self.trades.insert(*receiver, RefCell::new(state)); } + pub fn in_trade(&self, client: &ClientId) -> bool { + self.trades.contains_key(client) + } + pub fn with (&self, client: &ClientId, func: F) -> Result where F: Fn(&mut ClientTradeState, &mut ClientTradeState) -> T diff --git a/tests/test_trade.rs b/tests/test_trade.rs new file mode 100644 index 0000000..25d4a52 --- /dev/null +++ b/tests/test_trade.rs @@ -0,0 +1,4271 @@ +use std::convert::TryInto; +use elseware::common::serverstate::{ClientId, ServerState}; +use elseware::entity::gateway::{EntityGateway, InMemoryGateway}; +use elseware::entity::item; +use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket}; +use elseware::entity::item::{Meseta, ItemEntity}; +use elseware::ship::items::transaction::TransactionError; +use elseware::ship::packet::handler::trade::TradeError; + +use libpso::packet::ship::*; +use libpso::packet::messages::*; + +#[path = "common.rs"] +mod common; +use common::*; + +async fn initialize_trade(ship: &mut ShipServerState, client1: ClientId, client2: ClientId) { + ship.handle(client1, &RecvShipPacket::DirectMessage(DirectMessage::new(client2.0 as u32 -1, GameMessage::TradeRequest(TradeRequest { + client: client1.0 as u8 -1, + target: 0, + trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0) + })))).await.unwrap().for_each(drop); + + ship.handle(client2, &RecvShipPacket::DirectMessage(DirectMessage::new(client1.0 as u32 -1, GameMessage::TradeRequest(TradeRequest { + client: client2.0 as u8 -1, + target: 0, + trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Respond, 0) + })))).await.unwrap().for_each(drop); +} + +async fn confirm_trade(ship: &mut ShipServerState, client1: ClientId, client2: ClientId) { + ship.handle(client1, &RecvShipPacket::DirectMessage(DirectMessage::new(client2.0 as u32 -1, GameMessage::TradeRequest(TradeRequest { + client: client1.0 as u8 -1, + target: 0, + trade: TradeRequestCommand::Confirm + })))).await.unwrap().for_each(drop); + + ship.handle(client2, &RecvShipPacket::DirectMessage(DirectMessage::new(client1.0 as u32 -1, GameMessage::TradeRequest(TradeRequest { + client: client2.0 as u8 -1, + target: 0, + trade: TradeRequestCommand::Confirm + })))).await.unwrap().for_each(drop); +} + +async fn finalconfirm_trade(ship: &mut ShipServerState, client1: ClientId, client2: ClientId) { + ship.handle(client1, &RecvShipPacket::DirectMessage(DirectMessage::new(client2.0 as u32 -1, GameMessage::TradeRequest(TradeRequest { + client: client1.0 as u8 -1, + target: 0, + trade: TradeRequestCommand::FinalConfirm + })))).await.unwrap().for_each(drop); + + ship.handle(client2, &RecvShipPacket::DirectMessage(DirectMessage::new(client1.0 as u32 -1, GameMessage::TradeRequest(TradeRequest { + client: client2.0 as u8 -1, + target: 0, + trade: TradeRequestCommand::FinalConfirm + })))).await.unwrap().for_each(drop); +} + +#[derive(Default)] +struct TradeItemBuilder { + count: usize, + items: [TradeItem; 32], +} + +impl TradeItemBuilder { + fn individual(mut self, item: &elseware::entity::item::InventoryItemEntity, item_id: u32) -> Self { + let idata = item.with_individual(|i| i.item.as_client_bytes()).unwrap(); + self.items[self.count] = TradeItem { + item_data: idata[0..12].try_into().unwrap(), + item_id: item_id, + item_data2: idata[12..16].try_into().unwrap(), + }; + + self.count += 1; + self + } + + fn stacked(mut self, item: &elseware::entity::item::InventoryItemEntity, item_id: u32, amount: u8) -> Self { + let idata = item + .with_stacked(|i| i[0].item.tool().unwrap().as_stacked_bytes(i.len())) + .map(|mut data| { + data[5] = amount; + data + }) + .unwrap(); + self.items[self.count] = TradeItem { + item_data: idata[0..12].try_into().unwrap(), + item_id: item_id, + item_data2: idata[12..16].try_into().unwrap(), + }; + + self.count += 1; + self + } + + fn meseta(mut self, amount: usize) -> Self { + self.items[self.count] = TradeItem { + item_data: [4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + item_id: 0xFFFFFFFF, + item_data2: u32::to_le_bytes(amount as u32), + }; + + self.count += 1; + self + } + + fn build(self) -> [TradeItem; 32] { + self.items + } +} + + +#[async_std::test] +async fn test_trade_one_individual_item() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let mut p1_inv = Vec::new(); + p1_inv.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap()); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10000) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 5); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + .. + })))); + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 0); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); +} + +#[async_std::test] +async fn test_trade_player2_to_player1() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let mut p2_inv = Vec::new(); + p2_inv.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap()); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_inv)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 0); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x210000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let titems = TradeItemBuilder::default() + .individual(&p2_items.items[0], 0x210000) + .build(); + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 5); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + .. + })))); + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); +} + +#[async_std::test] +async fn test_reverse_trade_ack_order() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let mut p1_inv = Vec::new(); + p1_inv.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap()); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(2), ClientId(1)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(2), ClientId(1)).await; + finalconfirm_trade(&mut ship, ClientId(2), ClientId(1)).await; + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10000) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 5); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + .. + })))); + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 0); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); +} + +#[async_std::test] +async fn test_trade_one_stacked_item() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 2) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 2) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 5); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + .. + })))); + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 0); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); +} + +#[async_std::test] +async fn test_trade_partial_stacked_item() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + assert_eq!(p1_items.items[0].with_stacked(|i| i.len()).unwrap(), 2); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 1) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 5); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + .. + })))); + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + assert_eq!(p1_items.items[0].with_stacked(|i| i.len()).unwrap(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + assert_eq!(p2_items.items[0].with_stacked(|i| i.len()).unwrap(), 1); +} + +#[async_std::test] +async fn test_trade_individual_both() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_inv = vec![ + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap()]; + let p2_inv = vec![ + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap()]; + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_inv)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10000) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let titems = TradeItemBuilder::default() + .individual(&p2_items.items[0], 0x210000) + .build(); + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 8); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, + .. + }), + .. + })))); + assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 0, + item_id: 0x10000, + .. + }), + .. + })))); + assert!(matches!(ack[6], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[7], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + assert!(matches!(p1_items.items[0].with_individual(|i| i.clone()).unwrap(), item::ItemEntity{item: item::ItemDetail::Weapon(item::weapon::Weapon {weapon: item::weapon::WeaponType::Handgun, ..}), ..})); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + assert!(matches!(p2_items.items[0].with_individual(|i| i.clone()).unwrap(), item::ItemEntity{item: item::ItemDetail::Weapon(item::weapon::Weapon {weapon: item::weapon::WeaponType::Saber, ..}), ..})); +} + +#[async_std::test] +async fn test_trade_stacked_both() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p2_stack = futures::future::join_all((0..3).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monofluid, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(vec![p2_stack])).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 2) + })))).await.unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210000, 3) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 2) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let titems = TradeItemBuilder::default() + .stacked(&p2_items.items[0], 0x210000, 3) + .build(); + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 8); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, + .. + }), + .. + })))); + assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 0, + item_id: 0x10000, + .. + }), + .. + })))); + assert!(matches!(ack[6], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[7], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + assert_eq!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 3); + assert!(matches!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monofluid, ..}), ..})); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + assert_eq!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 2); + assert!(matches!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); +} + +#[async_std::test] +async fn test_trade_partial_stack_both() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p2_stack = futures::future::join_all((0..3).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monofluid, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(vec![p2_stack])).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210000, 2) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 1) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let titems = TradeItemBuilder::default() + .stacked(&p2_items.items[0], 0x210000, 2) + .build(); + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 8); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, + amount: 2, + .. + }), + .. + })))); + assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 0, + item_id: 0x10000, + amount: 1, + .. + }), + .. + })))); + assert!(matches!(ack[6], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[7], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 2); + assert_eq!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 1); + assert_eq!(p1_items.items[1].with_stacked(|i| i.clone()).unwrap().len(), 2); + assert!(matches!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); + assert!(matches!(p1_items.items[1].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monofluid, ..}), ..})); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 2); + assert_eq!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 1); + assert_eq!(p2_items.items[1].with_stacked(|i| i.clone()).unwrap().len(), 1); + assert!(matches!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monofluid, ..}), ..})); + assert!(matches!(p2_items.items[1].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); +} + +#[async_std::test] +async fn test_trade_same_stacked_item_to_eachother() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack = futures::future::join_all((0..3).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p2_stack = futures::future::join_all((0..4).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(vec![p2_stack])).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210000, 3) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 1) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let titems = TradeItemBuilder::default() + .stacked(&p2_items.items[0], 0x210000, 3) + .build(); + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 8); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, + amount: 3, + .. + }), + .. + })))); + assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 0, + item_id: 0x10000, + amount: 1, + .. + }), + .. + })))); + assert!(matches!(ack[6], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[7], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + assert_eq!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 5); + assert!(matches!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + assert_eq!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 2); + assert!(matches!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); +} + +#[async_std::test] +async fn test_trade_stacked_when_already_have_partial_stack() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack = futures::future::join_all((0..3).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p2_stack = futures::future::join_all((0..3).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(vec![p2_stack])).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 2) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 2) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 5); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810001, + item_data: [3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], + .. + }), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810001, + item_data: [3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], + .. + }), + .. + })))); + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 0, + item_id: 0x10000, + amount: 2, + .. + }), + .. + })))); + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + assert_eq!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 1); + assert!(matches!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + assert_eq!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 5); + assert!(matches!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); +} + +#[async_std::test] +async fn test_trade_individual_for_stacked() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_inv = vec![ + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap()]; + + let p2_stack = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(vec![p2_stack])).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210000, 2) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10000) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let titems = TradeItemBuilder::default() + .stacked(&p2_items.items[0], 0x210000, 2) + .build(); + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 8); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_data: [3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_data: [3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, + .. + }), + .. + })))); + assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[5], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 0, + item_id: 0x10000, + .. + }), + .. + })))); + assert!(matches!(ack[6], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[7], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + assert_eq!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 2); + assert!(matches!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + assert!(matches!(p2_items.items[0].with_individual(|i| i.clone()).unwrap(), item::ItemEntity{item: item::ItemDetail::Weapon(item::weapon::Weapon {weapon: item::weapon::WeaponType::Saber, ..}), ..})); +} + +#[async_std::test] +async fn test_trade_multiple_individual() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_inv = vec![ + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Buster, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + ]; + let p2_inv = vec![ + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Autogun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + ]; + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_inv)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 2); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 2); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10001, 1) + })))).await.unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210000, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210001, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10000) + .individual(&p1_items.items[1], 0x10001) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 2, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let titems = TradeItemBuilder::default() + .individual(&p2_items.items[0], 0x210000) + .individual(&p2_items.items[1], 0x210001) + .build(); + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 2, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 14); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_data: [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, + .. + }), + .. + })))); + assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_data: [0, 6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_data: [0, 6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], // handgun + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[5], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210001, + .. + }), + .. + })))); + assert!(matches!(ack[6], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber + item_id: 0x810003, + .. + }), + .. + })))); + assert!(matches!(ack[7], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_data: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber + item_id: 0x810003, + .. + }), + .. + })))); + assert!(matches!(ack[8], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 0, + item_id: 0x10000, + .. + }), + .. + })))); + assert!(matches!(ack[9], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_data: [0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber + item_id: 0x810004, + .. + }), + .. + })))); + assert!(matches!(ack[10], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_data: [0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0], // saber + item_id: 0x810004, + .. + }), + .. + })))); + assert!(matches!(ack[11], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 0, + item_id: 0x10001, + .. + }), + .. + })))); + assert!(matches!(ack[12], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[13], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 2); + assert!(matches!(p1_items.items[0].with_individual(|i| i.clone()).unwrap(), item::ItemEntity{item: item::ItemDetail::Weapon(item::weapon::Weapon {weapon: item::weapon::WeaponType::Handgun, ..}), ..})); + assert!(matches!(p1_items.items[1].with_individual(|i| i.clone()).unwrap(), item::ItemEntity{item: item::ItemDetail::Weapon(item::weapon::Weapon {weapon: item::weapon::WeaponType::Autogun, ..}), ..})); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 2); + assert!(matches!(p2_items.items[0].with_individual(|i| i.clone()).unwrap(), item::ItemEntity{item: item::ItemDetail::Weapon(item::weapon::Weapon {weapon: item::weapon::WeaponType::Saber, ..}), ..})); + assert!(matches!(p2_items.items[1].with_individual(|i| i.clone()).unwrap(), item::ItemEntity{item: item::ItemDetail::Weapon(item::weapon::Weapon {weapon: item::weapon::WeaponType::Buster, ..}), ..})); +} + + +#[async_std::test] +async fn test_trade_multiple_stacked() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack1 = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + let p1_stack2 = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Dimate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p2_stack1 = futures::future::join_all((0..3).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monofluid, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + let p2_stack2 = futures::future::join_all((0..3).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Difluid, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack1, p1_stack2])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(vec![p2_stack1, p2_stack2])).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 2); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 2); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 2) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10001, 2) + })))).await.unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210000, 3) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210001, 3) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 2) + .stacked(&p1_items.items[1], 0x10001, 2) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 2, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let titems = TradeItemBuilder::default() + .stacked(&p2_items.items[0], 0x210000, 3) + .stacked(&p2_items.items[1], 0x210001, 3) + .build(); + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 2, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 14); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_id: 0x810001, + .. + }), + .. + })))); + assert!(matches!(ack[2], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210000, + .. + }), + .. + })))); + assert!(matches!(ack[3], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[4], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 0, + item_id: 0x810002, + .. + }), + .. + })))); + assert!(matches!(ack[5], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 1, + item_id: 0x210001, + .. + }), + .. + })))); + assert!(matches!(ack[6], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810003, + .. + }), + .. + })))); + assert!(matches!(ack[7], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810003, + .. + }), + .. + })))); + assert!(matches!(ack[8], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 0, + item_id: 0x10000, + .. + }), + .. + })))); + assert!(matches!(ack[9], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810004, + .. + }), + .. + })))); + assert!(matches!(ack[10], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem { + client: 1, + item_id: 0x810004, + .. + }), + .. + })))); + assert!(matches!(ack[11], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem { + client: 0, + item_id: 0x10001, + .. + }), + .. + })))); + assert!(matches!(ack[12], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[13], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 2); + assert_eq!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 3); + assert!(matches!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monofluid, ..}), ..})); + assert_eq!(p1_items.items[1].with_stacked(|i| i.clone()).unwrap().len(), 3); + assert!(matches!(p1_items.items[1].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Difluid, ..}), ..})); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 2); + assert_eq!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 2); + assert!(matches!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); + assert_eq!(p2_items.items[1].with_stacked(|i| i.clone()).unwrap().len(), 2); + assert!(matches!(p2_items.items[1].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Dimate, ..}), ..})); +} + +#[async_std::test] +async fn test_trade_not_enough_inventory_space_individual() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_inv = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + } + ).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p2_inv = futures::future::join_all((0..30).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + } + ).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_inv)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 2); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 30); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10000) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.err().unwrap(); + match ack.downcast::>().unwrap() { + TransactionError::Action(a) => { + assert_eq!(a.downcast::().unwrap(), TradeError::NoInventorySpace); + }, + _ => panic!() + } + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 2); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 30); +} + +#[async_std::test] +async fn test_trade_not_enough_inventory_space_stacked() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p2_inv = futures::future::join_all((0..30).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + } + ).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_inv)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 30); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 2) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 2) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.err().unwrap(); + match ack.downcast::>().unwrap() { + TransactionError::Action(a) => { + assert_eq!(a.downcast::().unwrap(), TradeError::NoInventorySpace); + }, + _ => panic!() + } + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 30); +} + +#[async_std::test] +async fn test_trade_stack_too_big() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack = futures::future::join_all((0..8).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p2_stack = futures::future::join_all((0..7).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(vec![p2_stack])).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + assert_eq!(p1_items.items[0].with_stacked(|i| i.len()).unwrap(), 8); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + assert_eq!(p2_items.items[0].with_stacked(|i| i.len()).unwrap(), 7); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 4) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 4) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.err().unwrap(); + match ack.downcast::>().unwrap() { + TransactionError::Action(a) => { + assert_eq!(a.downcast::().unwrap(), TradeError::NoStackSpace); + }, + _ => panic!() + } + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + assert_eq!(p1_items.items[0].with_stacked(|i| i.len()).unwrap(), 8); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + assert_eq!(p2_items.items[0].with_stacked(|i| i.len()).unwrap(), 7); +} + +#[async_std::test] +async fn test_trade_meseta() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + entity_gateway.set_character_meseta(&char1.id, Meseta(2323)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0xFFFFFF01, 23) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .meseta(23) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 5); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + .. + })))); + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); + assert_eq!(c1_meseta, Meseta(2300)); + let c2_meseta = entity_gateway.get_character_meseta(&char2.id).await.unwrap(); + assert_eq!(c2_meseta, Meseta(23)); +} + +#[async_std::test] +async fn test_trade_too_much_meseta() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + entity_gateway.set_character_meseta(&char1.id, Meseta(4000)).await.unwrap(); + entity_gateway.set_character_meseta(&char2.id, Meseta(999000)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0xFFFFFF01, 2000) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .meseta(2000) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); + + let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); + assert_eq!(c1_meseta, Meseta(4000)); + let c2_meseta = entity_gateway.get_character_meseta(&char2.id).await.unwrap(); + assert_eq!(c2_meseta, Meseta(999000)); +} + +#[async_std::test] +async fn test_trade_invalid_amount_of_meseta() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + entity_gateway.set_character_meseta(&char1.id, Meseta(4000)).await.unwrap(); + entity_gateway.set_character_meseta(&char2.id, Meseta(999000)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0xFFFFFF01, 5000) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .meseta(5000) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); + + let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); + assert_eq!(c1_meseta, Meseta(4000)); + let c2_meseta = entity_gateway.get_character_meseta(&char2.id).await.unwrap(); + assert_eq!(c2_meseta, Meseta(999000)); +} + +#[async_std::test] +async fn test_trade_meseta_request_and_items_dont_match() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + entity_gateway.set_character_meseta(&char1.id, Meseta(4000)).await.unwrap(); + entity_gateway.set_character_meseta(&char2.id, Meseta(999000)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0xFFFFFF01, 50) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .meseta(23) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); + + let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); + assert_eq!(c1_meseta, Meseta(4000)); + let c2_meseta = entity_gateway.get_character_meseta(&char2.id).await.unwrap(); + assert_eq!(c2_meseta, Meseta(999000)); +} + +#[async_std::test] +async fn test_player_declined_trade() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let ack = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::Cancel + })))).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); +} + +#[async_std::test] +async fn test_back_out_of_trade_last_minute() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let mut p1_inv = Vec::new(); + p1_inv.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap()); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let ack = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::Cancel + })))).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); +} + +#[async_std::test] +async fn test_valid_trade_when_both_inventories_are_full() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_inv = futures::future::join_all((0..30).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + } + ).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p2_inv = futures::future::join_all((0..30).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + } + ).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_inv)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 30); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 30); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10001, 1) + })))).await.unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210000, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210001, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10000) + .individual(&p1_items.items[1], 0x10001) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 2, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let titems = TradeItemBuilder::default() + .individual(&p2_items.items[0], 0x210000) + .individual(&p2_items.items[1], 0x210001) + .build(); + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 2, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 14); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 30); + assert_eq!(p1_items.items.iter().filter(|i| matches!(i.individual().unwrap().item, item::ItemDetail::Weapon(item::weapon::Weapon { weapon: item::weapon::WeaponType::Saber, ..}, ..))).count(), 28); + assert_eq!(p1_items.items.iter().filter(|i| matches!(i.individual().unwrap().item, item::ItemDetail::Weapon(item::weapon::Weapon { weapon: item::weapon::WeaponType::Handgun, ..}, ..))).count(), 2); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 30); + assert_eq!(p2_items.items.iter().filter(|i| matches!(i.individual().unwrap().item, item::ItemDetail::Weapon(item::weapon::Weapon { weapon: item::weapon::WeaponType::Saber, ..}, ..))).count(), 2); + assert_eq!(p2_items.items.iter().filter(|i| matches!(i.individual().unwrap().item, item::ItemDetail::Weapon(item::weapon::Weapon { weapon: item::weapon::WeaponType::Handgun, ..}, ..))).count(), 28); +} + +#[async_std::test] +async fn test_invalid_trade_when_both_inventories_are_full() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_inv = futures::future::join_all((0..30).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + } + ).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p2_inv = futures::future::join_all((0..30).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + } + ).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(p2_inv)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 30); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 30); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10001, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10002, 1) + })))).await.unwrap().for_each(drop); + + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210000, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(2), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::AddItem(0x210001, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10000) + .individual(&p1_items.items[1], 0x10001) + .individual(&p1_items.items[1], 0x10002) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 3, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let titems = TradeItemBuilder::default() + .individual(&p2_items.items[0], 0x210000) + .individual(&p2_items.items[1], 0x210001) + .build(); + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 2, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.err().unwrap(); + match ack.downcast::>().unwrap() { + TransactionError::Action(a) => { + assert_eq!(a.downcast::().unwrap(), TradeError::NoInventorySpace); + }, + _ => panic!() + } + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 30); + assert_eq!(p1_items.items.iter().filter(|i| matches!(i.individual().unwrap().item, item::ItemDetail::Weapon(item::weapon::Weapon { weapon: item::weapon::WeaponType::Saber, ..}, ..))).count(), 30); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 30); + assert_eq!(p2_items.items.iter().filter(|i| matches!(i.individual().unwrap().item, item::ItemDetail::Weapon(item::weapon::Weapon { weapon: item::weapon::WeaponType::Handgun, ..}, ..))).count(), 30); +} + +#[async_std::test] +async fn test_client_tries_to_start_two_trades() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + let (_user2, _char3) = new_user_character(&mut entity_gateway, "a3", "a").await; + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + log_in_char(&mut ship, ClientId(3), "a3", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + join_lobby(&mut ship, ClientId(3)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + join_room(&mut ship, ClientId(3), 0).await; + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + let ack = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 0, + target: 0, + trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0) + })))).await.err().unwrap(); + assert_eq!(ack.downcast::().unwrap(), TradeError::ClientAlreadyInTrade); +} + +#[async_std::test] +async fn test_client_tries_trading_with_client_already_trading() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, _char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, _char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + let (_user2, _char3) = new_user_character(&mut entity_gateway, "a3", "a").await; + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + log_in_char(&mut ship, ClientId(3), "a3", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + join_lobby(&mut ship, ClientId(3)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + join_room(&mut ship, ClientId(3), 0).await; + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + let ack = ship.handle(ClientId(3), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::TradeRequest(TradeRequest { + client: 2, + target: 0, + trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 0) + })))).await.err().unwrap(); + assert_eq!(ack.downcast::().unwrap(), TradeError::OtherAlreadyInTrade); + + let ack = ship.handle(ClientId(3), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 2, + target: 0, + trade: TradeRequestCommand::Initialize(TradeRequestInitializeCommand::Initialize, 1) + })))).await.err().unwrap(); + assert_eq!(ack.downcast::().unwrap(), TradeError::OtherAlreadyInTrade); +} + +#[async_std::test] +async fn test_add_then_remove_individual_item() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let mut p1_inv = Vec::new(); + for _ in 0..2 { + p1_inv.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap()); + } + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 2); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10001, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::RemoveItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[1], 0x10001) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 5); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + .. + })))); + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); +} + +#[async_std::test] +async fn test_add_then_remove_stacked_item() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack1 = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p1_stack2 = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monofluid, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack1, p1_stack2])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 2); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 2) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10001, 2) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::RemoveItem(0x10000, 2) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[1], 0x10001, 2) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 5); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + .. + })))); + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + assert!(matches!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); + assert_eq!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 2); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 1); + assert_eq!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 2); + assert!(matches!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monofluid, ..}), ..})); +} + +#[async_std::test] +async fn test_add_then_remove_partial_stack() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack1 = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + let p1_stack2 = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monofluid, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack1, p1_stack2])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 2); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 2) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10001, 2) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::RemoveItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 1) + .stacked(&p1_items.items[1], 0x10001, 2) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 2, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 8); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + assert_eq!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 1); + assert!(matches!(p1_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 2); + assert_eq!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap().len(), 1); + assert_eq!(p2_items.items[1].with_stacked(|i| i.clone()).unwrap().len(), 2); + assert!(matches!(p2_items.items[0].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monomate, ..}), ..})); + assert!(matches!(p2_items.items[1].with_stacked(|i| i.clone()).unwrap()[0], item::ItemEntity{item: item::ItemDetail::Tool(item::tool::Tool {tool: item::tool::ToolType::Monofluid, ..}), ..})); +} + +#[async_std::test] +async fn test_add_then_remove_meseta() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + entity_gateway.set_character_meseta(&char1.id, Meseta(2323)).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0xFFFFFF01, 23) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::RemoveItem(0xFFFFFF01, 5) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .meseta(18) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 0, + unknown2: 0, + count: 0, + items: Default::default(), + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))); + + let ack = ship.handle(ClientId(1), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 0); + + let ack = ship.handle(ClientId(2), &RecvShipPacket::TradeConfirmed(TradeConfirmed { + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 5); + assert!(matches!(ack[0], (ClientId(1), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[1], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::CreateItem(CreateItem {..}), + .. + })))); + assert!(matches!(ack[2], (ClientId(2), SendShipPacket::Message(Message { + msg: GameMessage::PlayerNoLongerHasItem(PlayerNoLongerHasItem {..}), + .. + })))); + assert!(matches!(ack[3], (ClientId(2), SendShipPacket::TradeSuccessful {..}))); + assert!(matches!(ack[4], (ClientId(1), SendShipPacket::TradeSuccessful {..}))); + + let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); + assert_eq!(c1_meseta, Meseta(2305)); + let c2_meseta = entity_gateway.get_character_meseta(&char2.id).await.unwrap(); + assert_eq!(c2_meseta, Meseta(18)); +} + +#[async_std::test] +async fn test_items_to_trade_data_does_not_match() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let mut p1_inv = Vec::new(); + p1_inv.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap()); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let new_item = item::InventoryItemEntity::Individual( + ItemEntity { + id: p1_items.items[0].with_individual(|i| i.id).unwrap(), + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 2, + special: None, + attrs: [None, None, None], + tekked: true, + } + )}); + let titems = TradeItemBuilder::default() + .individual(&new_item, 0x10000) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + dbg!(&ack); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); +} + +#[async_std::test] +async fn test_items_to_trade_id_does_not_match() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let mut p1_inv = Vec::new(); + p1_inv.push(entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Handgun, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap()); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10001) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); +} + +#[async_std::test] +async fn test_stack_is_same_amount_in_request_and_items_to_trade() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 2) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 1) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); +} + +#[async_std::test] +async fn test_stack_is_same_amount_in_request_and_items_to_trade2() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_stack = futures::future::join_all((0..2).map(|_| { + let mut entity_gateway = entity_gateway.clone(); + async move { + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Tool( + item::tool::Tool { + tool: item::tool::ToolType::Monomate, + } + ) + }).await + }})) + .await + .into_iter() + .collect::,_>>() + .unwrap(); + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(vec![p1_stack])).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .stacked(&p1_items.items[0], 0x10000, 2) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 1); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); +} + +#[async_std::test] +async fn test_items_to_trade_count_less_than() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_inv = vec![ + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Brand, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Buster, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + ]; + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 3); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10001, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10000) + .individual(&p1_items.items[1], 0x10001) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 1, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 3); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); +} + +#[async_std::test] +async fn test_items_to_trade_count_greater_than() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + entity_gateway.set_character_meseta(&char1.id, Meseta(23)).await.unwrap(); + + let p1_inv = vec![ + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Brand, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Buster, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + ]; + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 3); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10001, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0xFFFFFF01, 5) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10000) + .individual(&p1_items.items[1], 0x10001) + .meseta(5) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 4, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 3); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); +} + +#[async_std::test] +async fn test_items_to_trade_count_mismatch_with_meseta() { + let mut entity_gateway = InMemoryGateway::default(); + + let (_user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (_user2, char2) = new_user_character(&mut entity_gateway, "a2", "a").await; + + let p1_inv = vec![ + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Saber, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Brand, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + entity_gateway.create_item( + item::NewItemEntity { + item: item::ItemDetail::Weapon( + item::weapon::Weapon { + weapon: item::weapon::WeaponType::Buster, + grind: 0, + special: None, + attrs: [None, None, None], + tekked: true, + } + ), + }).await.unwrap(), + ]; + + entity_gateway.set_character_inventory(&char1.id, &item::InventoryEntity::new(p1_inv)).await.unwrap(); + entity_gateway.set_character_inventory(&char2.id, &item::InventoryEntity::new(Vec::::new())).await.unwrap(); + + let mut ship = Box::new(ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build()); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + log_in_char(&mut ship, ClientId(2), "a2", "a").await; + + join_lobby(&mut ship, ClientId(1)).await; + join_lobby(&mut ship, ClientId(2)).await; + + create_room(&mut ship, ClientId(1), "room", "").await; + join_room(&mut ship, ClientId(2), 0).await; + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 3); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); + + initialize_trade(&mut ship, ClientId(1), ClientId(2)).await; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10000, 1) + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(1, GameMessage::TradeRequest(TradeRequest { + client: 1, + target: 0, + trade: TradeRequestCommand::AddItem(0x10001, 1) + })))).await.unwrap().for_each(drop); + + confirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + finalconfirm_trade(&mut ship, ClientId(1), ClientId(2)).await; + + let titems = TradeItemBuilder::default() + .individual(&p1_items.items[0], 0x10000) + .individual(&p1_items.items[1], 0x10001) + .build(); + let ack = ship.handle(ClientId(1), &RecvShipPacket::ItemsToTrade(ItemsToTrade { + trade_target: 1, + unknown2: 0, + count: 3, + items: titems, + })).await.unwrap().collect::>(); + assert_eq!(ack.len(), 2); + assert!(matches!(ack[0], (ClientId(2), SendShipPacket::CancelTrade(..)))); + assert!(matches!(ack[1], (ClientId(1), SendShipPacket::CancelTrade(..)))); + + let p1_items = entity_gateway.get_character_inventory(&char1.id).await.unwrap(); + assert_eq!(p1_items.items.len(), 3); + let p2_items = entity_gateway.get_character_inventory(&char2.id).await.unwrap(); + assert_eq!(p2_items.items.len(), 0); +}