From cebb6a5f31ee3c388959c0cb7e5ec0e058c16ff4 Mon Sep 17 00:00:00 2001 From: jake Date: Sun, 27 Sep 2020 20:30:37 -0600 Subject: [PATCH] handle case where items are removed from shop after buying --- src/ship/items/manager.rs | 2 +- src/ship/packet/handler/direct_message.rs | 36 +++++-- tests/test_shops.rs | 119 +++++++++++++++++++++- 3 files changed, 147 insertions(+), 10 deletions(-) diff --git a/src/ship/items/manager.rs b/src/ship/items/manager.rs index 4aa5a40..ace390b 100644 --- a/src/ship/items/manager.rs +++ b/src/ship/items/manager.rs @@ -763,7 +763,7 @@ impl ItemManager { shop_item: &(dyn ShopItem + Send + Sync), item_id: ClientItemId, amount: usize) - -> Result<(&InventoryItem), ItemManagerError> { + -> Result<&InventoryItem, ItemManagerError> { let inventory = self.character_inventory.get_mut(&character.id).ok_or(ItemManagerError::NoCharacter(character.id))?; let item_detail = shop_item.as_item(); diff --git a/src/ship/packet/handler/direct_message.rs b/src/ship/packet/handler/direct_message.rs index 8c2dd21..5ed7751 100644 --- a/src/ship/packet/handler/direct_message.rs +++ b/src/ship/packet/handler/direct_message.rs @@ -10,7 +10,7 @@ use crate::ship::items::{ItemManager, ClientItemId, TriggerCreateItem, FloorItem use crate::entity::gateway::EntityGateway; use libpso::utf8_to_utf16_array; use crate::ship::packet::builder; -use crate::ship::shops::ShopItem; +use crate::ship::shops::{ShopItem, ToolShopItem, ArmorShopItem}; const BANK_ACTION_DEPOSIT: u8 = 0; const BANK_ACTION_WITHDRAW: u8 = 1; @@ -330,22 +330,33 @@ where let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; let area_client = client_location.get_local_client(id).map_err(|err| -> ClientLocationError { err.into() })?; - let item: &(dyn ShopItem + Send + Sync) = match buy_item.shop_type { + + let (item, remove): (&(dyn ShopItem + Send + Sync), bool) = match buy_item.shop_type { SHOP_OPTION_WEAPON => { - client.weapon_shop.get(buy_item.shop_index as usize).ok_or(ShipError::ShopError)? + (client.weapon_shop.get(buy_item.shop_index as usize).ok_or(ShipError::ShopError)?, false) }, SHOP_OPTION_TOOL => { - client.tool_shop.get(buy_item.shop_index as usize).ok_or(ShipError::ShopError)? + let item = client.tool_shop.get(buy_item.shop_index as usize).ok_or(ShipError::ShopError)?; + let remove = match item { + ToolShopItem::Tech(_) => true, + _ => false, + }; + (item, remove) }, SHOP_OPTION_ARMOR => { - client.armor_shop.get(buy_item.shop_index as usize).ok_or(ShipError::ShopError)? + let item = client.armor_shop.get(buy_item.shop_index as usize).ok_or(ShipError::ShopError)?; + let remove = match item { + ArmorShopItem::Unit(_) => true, + _ => false, + }; + (item, remove) }, _ => { return Err(ShipError::ShopError) } }; - if client.character.meseta < item.price() as u32{ + if client.character.meseta < item.price() as u32 { return Err(ShipError::ShopError) } @@ -353,9 +364,20 @@ where entity_gateway.save_character(&client.character).await; let inventory_item = item_manager.player_buys_item(entity_gateway, &client.character, item, ClientItemId(buy_item.item_id), buy_item.amount as usize).await?; - let create = builder::message::create_withdrawn_inventory_item(area_client, inventory_item)?; + if remove { + match buy_item.shop_type { + SHOP_OPTION_TOOL => { + client.tool_shop.remove(buy_item.shop_index as usize); + }, + SHOP_OPTION_ARMOR => { + client.armor_shop.remove(buy_item.shop_index as usize); + }, + _ => {} + } + } + let other_clients_in_area = client_location.get_client_neighbors(id).map_err(|err| -> ClientLocationError { err.into() })?; Ok(Box::new(other_clients_in_area.into_iter() .map(move |c| { diff --git a/tests/test_shops.rs b/tests/test_shops.rs index 4044607..f1c292c 100644 --- a/tests/test_shops.rs +++ b/tests/test_shops.rs @@ -360,7 +360,7 @@ async fn test_other_clients_see_stacked_purchase() { async fn test_buying_item_without_enough_mseseta() { let mut entity_gateway = InMemoryGateway::new(); - let (user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + let (user1, char1) = new_user_character(&mut entity_gateway, "a1", "a").await; let mut ship = ShipServerState::builder() .gateway(entity_gateway.clone()) @@ -445,5 +445,120 @@ async fn test_player_double_buys_from_tool_shop() { let c1 = characters1.get(0).as_ref().unwrap().as_ref().unwrap(); assert!(c1.meseta < 999999); let p1_items = entity_gateway.get_items_by_character(&char1).await; - assert_eq!(p1_items.len(), 10); + assert_eq!(p1_items.len(), 9); +} + + +#[async_std::test] +async fn test_techs_disappear_from_shop_when_bought() { + let mut entity_gateway = InMemoryGateway::new(); + + let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + char1.exp = 80000000; + char1.meseta = 999999; + entity_gateway.save_character(&char1).await; + + let mut ship = ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build(); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await; + + let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest { + client: 255, + target: 255, + shop_type: 0, + })))).await.unwrap().collect::>(); + + let first_tech = match &packets[0].1 { + SendShipPacket::Message(Message {msg: GameMessage::ShopList(shop_list)}) => { + shop_list.items.iter() + .enumerate() + .filter(|(_, item)| { + item.item_bytes[0] == 3 && item.item_bytes[1] == 2 + }) + .nth(0).unwrap().0 + }, + _ => panic!(""), + }; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem { + client: 255, + target: 255, + item_id: 0x10000, + shop_type: 0, + shop_index: first_tech as u8, + amount: 1, + unknown1: 0, + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem { + client: 255, + target: 255, + item_id: 0x10001, + shop_type: 0, + shop_index: first_tech as u8, + amount: 1, + unknown1: 0, + })))).await.unwrap().for_each(drop); + + let p1_items = entity_gateway.get_items_by_character(&char1).await; + assert!(p1_items[0].item != p1_items[1].item); +} + +#[async_std::test] +async fn test_units_disappear_from_shop_when_bought() { + let mut entity_gateway = InMemoryGateway::new(); + + let (_user1, mut char1) = new_user_character(&mut entity_gateway, "a1", "a").await; + char1.exp = 80000000; + char1.meseta = 999999; + entity_gateway.save_character(&char1).await; + + let mut ship = ShipServerState::builder() + .gateway(entity_gateway.clone()) + .build(); + log_in_char(&mut ship, ClientId(1), "a1", "a").await; + join_lobby(&mut ship, ClientId(1)).await; + create_room_with_difficulty(&mut ship, ClientId(1), "room", "", Difficulty::Ultimate).await; + + let packets = ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::ShopRequest(ShopRequest { + client: 255, + target: 255, + shop_type: 2, + })))).await.unwrap().collect::>(); + + let first_unit = match &packets[0].1 { + SendShipPacket::Message(Message {msg: GameMessage::ShopList(shop_list)}) => { + shop_list.items.iter() + .enumerate() + .filter(|(_, item)| { + item.item_bytes[0] == 1 && item.item_bytes[1] == 3 + }) + .nth(0).unwrap().0 + }, + _ => panic!(""), + }; + + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem { + client: 255, + target: 255, + item_id: 0x10000, + shop_type: 2, + shop_index: first_unit as u8, + amount: 1, + unknown1: 0, + })))).await.unwrap().for_each(drop); + ship.handle(ClientId(1), &RecvShipPacket::DirectMessage(DirectMessage::new(0, GameMessage::BuyItem(BuyItem { + client: 255, + target: 255, + item_id: 0x10001, + shop_type: 2, + shop_index: first_unit as u8, + amount: 1, + unknown1: 0, + })))).await.unwrap().for_each(drop); + + let p1_items = entity_gateway.get_items_by_character(&char1).await; + assert!(p1_items[0].item != p1_items[1].item); }