diff --git a/src/ship/items/actions.rs b/src/ship/items/actions.rs index 1f06d0f..a717519 100644 --- a/src/ship/items/actions.rs +++ b/src/ship/items/actions.rs @@ -787,3 +787,48 @@ where Ok((transaction, result)) }).await } + + +fn sell_inventory_item<'a>(character_id: CharacterEntityId) + -> impl Fn((ItemStateProxy<'a>, Box), InventoryItem) + -> Pin, Box), InventoryItem), ItemStateError>> + Send + 'a>> +{ + move |(mut item_state, mut transaction), inventory_item| { + Box::pin(async move { + let mut inventory = item_state.inventory(&character_id)?; + let price = inventory_item.item.sell_price()?; + inventory.add_meseta_no_overflow(price)?; + + let mut transaction = inventory_item.with_entity_id(transaction, |mut transaction, entity_id| { + async move { + transaction.gateway().add_item_note(&entity_id, ItemNote::SoldToShop).await?; + Ok(transaction) + }}).await?; + transaction.gateway().set_character_meseta(&character_id, inventory.meseta).await?; + item_state.set_inventory(inventory); + Ok(((item_state, transaction), inventory_item)) + }) + } +} + +pub async fn sell_item<'a, EG> ( + item_state: &'a mut ItemState, + entity_gateway: &mut EG, + character: &CharacterEntity, + item_id: ClientItemId, + amount: u32, +) -> Result +where + EG: EntityGateway, +{ + entity_gateway.with_transaction(|transaction| async move { + let item_state_proxy = ItemStateProxy::new(item_state); + let ((item_state_proxy, transaction), result) = ItemStateAction::default() + .act(take_item_from_inventory(character.id, item_id, amount)) + .act(sell_inventory_item(character.id)) + .commit((item_state_proxy, transaction)) + .await?; + item_state_proxy.commit(); + Ok((transaction, result)) + }).await +} diff --git a/src/ship/items/state.rs b/src/ship/items/state.rs index 3967182..22ce757 100644 --- a/src/ship/items/state.rs +++ b/src/ship/items/state.rs @@ -9,9 +9,10 @@ use crate::ship::map::MapArea; use crate::ship::location::{AreaClient, RoomId}; use crate::entity::character::{CharacterEntity, CharacterEntityId}; use crate::entity::gateway::{EntityGateway, GatewayError}; -use crate::entity::item::tool::Tool; +use crate::entity::item::tool::{Tool, ToolType}; use crate::entity::item::mag::Mag; use crate::ship::drops::ItemDrop; +use crate::ship::shops::{ShopItem, ArmorShopItem, ToolShopItem, WeaponShopItem}; // TODO: Commit trait that ItemStateProxy and EntityTransaction implement that .commit requires and acts on upon everything succeeding (like 3 less lines of code!) @@ -65,6 +66,9 @@ pub enum ItemStateError { #[error("item is not mag food {0}")] NotMagFood(ClientItemId), + + #[error("item is not sellable")] + ItemNotSellable, } pub enum FloorType { @@ -299,6 +303,64 @@ impl InventoryItemDetail { }, } } + + // TODO: this should probably go somewhere a bit more fundamental like ItemDetail + pub fn sell_price(&self) -> Result { + match self { + InventoryItemDetail::Individual(individual_item) => { + match &individual_item.item { + // TODO: can wrapped items be sold? + ItemDetail::Weapon(w) => { + if !w.tekked { + return Ok(1u32) + } + if w.is_rare_item() { + return Ok(10u32) + } + Ok((WeaponShopItem::from(w).price() / 8) as u32) + }, + ItemDetail::Armor(a) => { + if a.is_rare_item() { + return Ok(10u32) + } + Ok((ArmorShopItem::from(a).price() / 8) as u32) + }, + ItemDetail::Shield(s) => { + if s.is_rare_item() { + return Ok(10u32) + } + Ok((ArmorShopItem::from(s).price() / 8) as u32) + }, + ItemDetail::Unit(u) => { + if u.is_rare_item() { + return Ok(10u32) + } + Ok((ArmorShopItem::from(u).price() / 8) as u32) + }, + ItemDetail::Tool(t) => { + if !matches!(t.tool, ToolType::PhotonDrop | ToolType::PhotonSphere | ToolType::PhotonCrystal) && t.is_rare_item() { + return Ok(10u32) + } + Ok((ToolShopItem::from(t).price() / 8) as u32) + }, + ItemDetail::TechniqueDisk(d) => { + Ok((ToolShopItem::from(d).price() / 8) as u32) + }, + ItemDetail::Mag(_m) => { + Err(ItemStateError::ItemNotSellable) + }, + ItemDetail::ESWeapon(_e) => { + Ok(10u32) + }, + } + }, + // the number of stacked items sold is handled by the caller. this is just the price of 1 + InventoryItemDetail::Stacked(stacked_item) => { + Ok(((ToolShopItem::from(&stacked_item.tool).price() / 8) as u32) * stacked_item.count() as u32) + }, + } + } + } diff --git a/src/ship/packet/handler/message.rs b/src/ship/packet/handler/message.rs index 0977f43..4680c6c 100644 --- a/src/ship/packet/handler/message.rs +++ b/src/ship/packet/handler/message.rs @@ -8,7 +8,7 @@ use crate::ship::location::{ClientLocation, ClientLocationError}; use crate::ship::items::{ItemManager, ClientItemId}; use crate::ship::packet::builder; use crate::ship::items::state::ItemState; -use crate::ship::items::actions::{drop_item, drop_partial_item, drop_meseta, equip_item, unequip_item, sort_inventory, use_item, feed_mag}; +use crate::ship::items::actions::{drop_item, drop_partial_item, drop_meseta, equip_item, unequip_item, sort_inventory, use_item, feed_mag, sell_item}; pub async fn request_exp(id: ClientId, request_exp: &RequestExp, @@ -404,7 +404,7 @@ where EG: EntityGateway { let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; - //item_manager.player_sells_item(entity_gateway, &mut client.character, ClientItemId(sold_item.item_id), sold_item.amount as usize).await?; + sell_item(item_state, entity_gateway, &client.character, ClientItemId(sold_item.item_id), sold_item.amount as u32).await?; // TODO: send the packet to other clients Ok(Box::new(None.into_iter())) } diff --git a/tests/test_shops.rs b/tests/test_shops.rs index c4c4655..fa6da02 100644 --- a/tests/test_shops.rs +++ b/tests/test_shops.rs @@ -4,6 +4,7 @@ use elseware::entity::item; use elseware::ship::ship::{ShipServerState, RecvShipPacket, SendShipPacket}; use elseware::ship::room::Difficulty; use elseware::ship::items::manager::ItemManagerError; +use elseware::ship::items::state::ItemStateError; use libpso::packet::ship::*; use libpso::packet::messages::*; @@ -1142,7 +1143,7 @@ async fn test_player_cant_sell_if_meseta_would_go_over_max() { item_id: 0x10000, amount: 1, })))).await.err().unwrap(); - assert!(matches!(ack.downcast::().unwrap(), ItemManagerError::WalletFull)); + assert!(matches!(ack.downcast::().unwrap(), ItemStateError::FullOfMeseta)); let c1_meseta = entity_gateway.get_character_meseta(&char1.id).await.unwrap(); assert_eq!(c1_meseta.0, 999995);