use std::convert::TryInto; use log::warn; use rand::Rng; use rand::seq::SliceRandom; use libpso::packet::ship::*; use libpso::packet::messages::*; use crate::common::leveltable::CharacterLevelTable; use crate::common::serverstate::ClientId; use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms, ItemShops, TradeItem, TradeState, TradeStatus}; use crate::ship::location::{ClientLocation, ClientLocationError}; use crate::ship::drops::ItemDrop; use crate::ship::items::{ItemManager, ItemManagerError, ClientItemId, TriggerCreateItem, FloorItem, FloorType, ItemToTradeDetail}; use crate::ship::items::inventory::InventoryItem; use crate::entity::gateway::EntityGateway; use crate::entity::item; use libpso::utf8_to_utf16_array; use crate::ship::packet::builder; use crate::ship::shops::{ShopItem, ToolShopItem, ArmorShopItem}; #[derive(thiserror::Error, Debug)] #[error("")] pub enum TradeError { CouldNotFindTradePartner, ClientItemIdDidNotMatchItem(ClientItemId, [u8; 16]), InvalidStackAmount(ClientItemId, usize), NotInTradeMenu, } pub async fn inner_items_to_trade<EG>(id: ClientId, items_to_trade: &ItemsToTrade, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, item_manager: &mut ItemManager) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> where EG: EntityGateway { let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; let inventory = item_manager.get_character_inventory_mut(&client.character)?; let trade_partner = client_location.get_client_neighbors(id)? .into_iter() .filter(|ac| { ac.local_client.id() == items_to_trade.trade_target }) .next() .ok_or(TradeError::CouldNotFindTradePartner)?; let item_blobs = items_to_trade.items.iter().take(items_to_trade.count as usize); let trade_items = item_blobs .map(|item| { // TOOD: meseta? let real_item = inventory.get_item_handle_by_id(ClientItemId(item.item_id)) .ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?; let real_item = real_item .item() .ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?; let trade_item_bytes: [u8; 16] = item.item_data.iter() .chain(item.item_data2.iter()) .cloned().collect::<Vec<u8>>() .try_into() .unwrap(); if real_item.as_client_bytes() == trade_item_bytes { match real_item { InventoryItem::Individual(individual_inventory_item) => { Ok(TradeItem::Individual(individual_inventory_item.item_id)) }, InventoryItem::Stacked(stacked_inventory_item) => { let amount = trade_item_bytes[5] as usize; if amount > stacked_inventory_item.entity_ids.len() { Ok(TradeItem::Stacked(stacked_inventory_item.item_id, amount)) } else { Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into()) } } } } else { Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into()) } }) .collect::<Result<Vec<_>, anyhow::Error>>()?; // TODO: check room in inventory for items client.trade = Some(TradeState { other_client: trade_partner.client, items: trade_items, status: TradeStatus::Unconfirmed }); Ok(Box::new(vec![(trade_partner.client, SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {}))].into_iter())) } pub async fn items_to_trade<EG>(id: ClientId, items_to_trade_pkt: &ItemsToTrade, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, item_manager: &mut ItemManager) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> where EG: EntityGateway { let t = inner_items_to_trade(id, items_to_trade_pkt, entity_gateway, client_location, clients, item_manager).await; match t { Ok(p) => Ok(p), Err(err) => { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; let trade_partner = client.trade.as_ref() .and_then(|trade_state| { client_location.get_local_client(trade_state.other_client).ok() }) .map(|trade_partner| { (trade_partner.client, SendShipPacket::CancelTrade(CancelTrade {})) }); log::warn!("error in trading: {:?}", err); Ok(Box::new(vec![(id, SendShipPacket::CancelTrade(CancelTrade {}))] .into_iter() .chain(trade_partner.into_iter()))) } } } pub async fn trade_confirmed<EG>(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, item_manager: &mut ItemManager) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> where EG: EntityGateway { { let this_client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; this_client.trade.as_mut().ok_or(TradeError::NotInTradeMenu)?.status = TradeStatus::Confirmed; } let both_confirmed = { let this_client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; let other_client_id = this_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.other_client; let other_client = clients.get(&other_client_id).ok_or(ShipError::ClientNotFound(other_client_id))?; let this_client_trade = this_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?; let other_client_trade = other_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?; this_client_trade.status == TradeStatus::Confirmed && other_client_trade.status == TradeStatus::Confirmed }; if both_confirmed { let this_client_trade = { let this_client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; let this_client_trade = this_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.clone(); this_client.trade = None; this_client_trade }; let other_client_trade = { let other_client = clients.get_mut(&this_client_trade.other_client).ok_or(ShipError::ClientNotFound(this_client_trade.other_client))?; let other_client_trade = other_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.clone(); other_client.trade = None; other_client_trade }; let this_client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; let other_client = clients.get(&this_client_trade.other_client).ok_or(ShipError::ClientNotFound(this_client_trade.other_client))?; let this_local_client = client_location.get_local_client(id)?; let other_local_client = client_location.get_local_client(this_client_trade.other_client)?; let traded_items = item_manager.trade_items( entity_gateway, (&this_local_client, &this_client.character, &this_client_trade.items), (&other_local_client, &other_client.character, &other_client_trade.items) ).await?; let clients_in_room = client_location.get_all_clients_by_client(id)?; let traded_item_packets = traded_items .into_iter() .map(|item| { match item.item_detail { ItemToTradeDetail::Individual(item_detail) => { [ GameMessage::CreateItem(builder::message::create_individual_item(item.add_to, item.item_id, &item_detail).unwrap()), GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.item_id, 1)) // TODO: amount = ? ] }, ItemToTradeDetail::Stacked(tool, amount) => { [ GameMessage::CreateItem(builder::message::create_stacked_item(item.add_to, item.item_id, &tool, amount).unwrap()), GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.item_id, amount as u32)) ] }, } }) .flatten() .map(move |packet| { clients_in_room .clone() .into_iter() .map(move |client| { (client.client, SendShipPacket::Message(Message::new(packet.clone()))) }) }) .flatten(); Ok(Box::new(traded_item_packets)) } else { Ok(Box::new(None.into_iter())) } }