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}; 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(id: ClientId, items_to_trade: &ItemsToTrade, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, item_manager: &mut ItemManager) -> Result + Send>, anyhow::Error> where EG: EntityGateway { let client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; let inventory = item_manager.get_character_inventory_mut(&client.character)?; let trade_partner = client_location.get_client_neighbors(id)? .into_iter() .filter(|ac| { ac.local_client.id() == items_to_trade.trade_target }) .next() .ok_or(TradeError::CouldNotFindTradePartner)?; let item_blobs = items_to_trade.items.iter().take(items_to_trade.count as usize); let trade_items = item_blobs .map(|item| { // TOOD: meseta? let real_item = inventory.get_item_handle_by_id(ClientItemId(item.item_id)) .ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?; let real_item = real_item .item() .ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?; let trade_item_bytes: [u8; 16] = item.item_data.iter() .chain(item.item_data2.iter()) .cloned().collect::>() .try_into() .unwrap(); if real_item.as_client_bytes() == trade_item_bytes { match real_item { InventoryItem::Individual(individual_inventory_item) => { Ok(TradeItem::Individual(individual_inventory_item.item_id)) }, InventoryItem::Stacked(stacked_inventory_item) => { let amount = trade_item_bytes[5] as usize; if amount > stacked_inventory_item.entity_ids.len() { Ok(TradeItem::Stacked(stacked_inventory_item.item_id, amount)) } else { Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into()) } } } } else { Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into()) } }) .collect::, anyhow::Error>>()?; // 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(id: ClientId, items_to_trade_pkt: &ItemsToTrade, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, item_manager: &mut ItemManager) -> Result + Send>, anyhow::Error> where EG: EntityGateway { let t = inner_items_to_trade(id, items_to_trade_pkt, entity_gateway, client_location, clients, item_manager).await; 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()))) } } } // this function is a shitshow due to not thinking of what would happen if I needed more than 1 client at a time pub async fn trade_confirmed(id: ClientId, entity_gateway: &mut EG, client_location: &ClientLocation, clients: &mut Clients, item_manager: &mut ItemManager) -> Result + Send>, anyhow::Error> where EG: EntityGateway { let (this_client_confirmed, other_client_id) = { let client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; (client.confirmed_trade, client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.0) }; let other_client_confirmed = { let client = clients.get(&other_client_id).ok_or(ShipError::ClientNotFound(id))?; client.confirmed_trade }; { let this_client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; this_client.confirmed_trade = true; } let both_confirmed = { let this_client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; let other_client = clients.get(&other_client_id).ok_or(ShipError::ClientNotFound(id))?; this_client.confirmed_trade && other_client.confirmed_trade }; if both_confirmed { { let this_client = clients.get(&id).ok_or(ShipError::ClientNotFound(id))?; let other_client = clients.get(&other_client_id).ok_or(ShipError::ClientNotFound(id))?; let this_character_items = &this_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.1; item_manager.send_items_to_other_player(entity_gateway, &this_client.character, &other_client.character, this_character_items).await?; let other_character_items = &other_client.trade.as_ref().ok_or(TradeError::NotInTradeMenu)?.1; item_manager.send_items_to_other_player(entity_gateway, &other_client.character, &this_client.character, other_character_items).await?; } { let this_client = clients.get_mut(&id).ok_or(ShipError::ClientNotFound(id))?; this_client.trade = None; } { let other_client = clients.get_mut(&other_client_id).ok_or(ShipError::ClientNotFound(id))?; other_client.trade = None; } Ok(Box::new(None.into_iter())) } else { Ok(Box::new(None.into_iter())) } }