From 0d30df8da64b30e884b1ac1f444c067bcf80d972 Mon Sep 17 00:00:00 2001 From: jake Date: Sat, 12 Dec 2020 19:55:27 -0700 Subject: [PATCH] start of trading --- src/entity/gateway/postgres/models.rs | 65 +++++++-- src/entity/item/mod.rs | 5 + src/ship/items/manager.rs | 18 ++- src/ship/packet/builder/trade.rs | 0 src/ship/packet/handler/mod.rs | 1 + src/ship/packet/handler/trade.rs | 181 ++++++++++++++++++++++++++ src/ship/ship.rs | 31 ++++- 7 files changed, 288 insertions(+), 13 deletions(-) create mode 100644 src/ship/packet/builder/trade.rs create mode 100644 src/ship/packet/handler/trade.rs diff --git a/src/entity/gateway/postgres/models.rs b/src/entity/gateway/postgres/models.rs index 02bd750..917aca8 100644 --- a/src/entity/gateway/postgres/models.rs +++ b/src/entity/gateway/postgres/models.rs @@ -597,32 +597,75 @@ pub enum PgItemLocationDetail { mag: u32, }, Shop, + Trade { + id: i32, + character_to: i32, + character_from: i32, + } } impl From for PgItemLocationDetail { fn from(other: ItemLocation) -> PgItemLocationDetail { match other { - ItemLocation::Inventory{character_id} => PgItemLocationDetail::Inventory{character_id: character_id.0}, - ItemLocation::Bank{character_id, name} => PgItemLocationDetail::Bank{character_id: character_id.0, name: name.0}, - ItemLocation::LocalFloor{character_id, map_area, x,y,z} => PgItemLocationDetail::LocalFloor{character_id: character_id.0, map_area, x,y,z}, - ItemLocation::SharedFloor{map_area, x,y,z} => PgItemLocationDetail::SharedFloor{map_area, x,y,z}, + ItemLocation::Inventory{character_id} => PgItemLocationDetail::Inventory{ + character_id: character_id.0 + }, + ItemLocation::Bank{character_id, name} => PgItemLocationDetail::Bank{ + character_id: character_id.0, + name: name.0 + }, + ItemLocation::LocalFloor{character_id, map_area, x,y,z} => PgItemLocationDetail::LocalFloor{ + character_id: character_id.0, + map_area, + x,y,z + }, + ItemLocation::SharedFloor{map_area, x,y,z} => PgItemLocationDetail::SharedFloor{ + map_area, + x,y,z + }, ItemLocation::Consumed => PgItemLocationDetail::Consumed, - ItemLocation::FedToMag{mag} => PgItemLocationDetail::FedToMag{mag: mag.0}, + ItemLocation::FedToMag{mag} => PgItemLocationDetail::FedToMag{ + mag: mag.0 + }, ItemLocation::Shop => PgItemLocationDetail::Shop, + ItemLocation::Trade{id, character_to, character_from} => PgItemLocationDetail::Trade { + id: id.0 as i32, + character_to: character_to.0 as i32, + character_from: character_from.0 as i32, + } } } } impl From for ItemLocation { fn from(other: PgItemLocationDetail) -> ItemLocation { - match other{ - PgItemLocationDetail::Inventory{character_id} => ItemLocation::Inventory{character_id: CharacterEntityId(character_id)}, - PgItemLocationDetail::Bank{character_id, name} => ItemLocation::Bank{character_id: CharacterEntityId(character_id), name: BankName(name)}, - PgItemLocationDetail::LocalFloor{character_id, map_area, x,y,z} => ItemLocation::LocalFloor{character_id: CharacterEntityId(character_id), map_area, x,y,z}, - PgItemLocationDetail::SharedFloor{map_area, x,y,z} => ItemLocation::SharedFloor{map_area, x,y,z}, + match other { + PgItemLocationDetail::Inventory{character_id} => ItemLocation::Inventory{ + character_id: CharacterEntityId(character_id) + }, + PgItemLocationDetail::Bank{character_id, name} => ItemLocation::Bank{ + character_id: CharacterEntityId(character_id), + name: BankName(name) + }, + PgItemLocationDetail::LocalFloor{character_id, map_area, x,y,z} => ItemLocation::LocalFloor{ + character_id: CharacterEntityId(character_id), + map_area, + x,y,z + }, + PgItemLocationDetail::SharedFloor{map_area, x,y,z} => ItemLocation::SharedFloor{ + map_area, + x,y,z + }, PgItemLocationDetail::Consumed => ItemLocation::Consumed, - PgItemLocationDetail::FedToMag{mag} => ItemLocation::FedToMag{mag: ItemEntityId(mag)}, + PgItemLocationDetail::FedToMag{mag} => ItemLocation::FedToMag{ + mag: ItemEntityId(mag) + }, PgItemLocationDetail::Shop => ItemLocation::Shop, + PgItemLocationDetail::Trade {id, character_to, character_from} => ItemLocation::Trade { + id: TradeId(id as usize), + character_to: CharacterEntityId(character_to as u32), + character_from: CharacterEntityId(character_from as u32), + } } } } diff --git a/src/entity/item/mod.rs b/src/entity/item/mod.rs index 9959f54..b4260a3 100644 --- a/src/entity/item/mod.rs +++ b/src/entity/item/mod.rs @@ -47,6 +47,11 @@ pub enum ItemLocation { mag: ItemEntityId, }, Shop, + Trade { + //id: TradeId, + character_to: CharacterEntityId, + character_from: CharacterEntityId, + }, /*Destroyed { // marks an item that has been consumed in some way }, diff --git a/src/ship/items/manager.rs b/src/ship/items/manager.rs index 441c85b..d3fdf0e 100644 --- a/src/ship/items/manager.rs +++ b/src/ship/items/manager.rs @@ -8,7 +8,7 @@ use crate::entity::item::{Meseta, NewItemEntity, ItemEntity, InventoryItemEntity use crate::entity::item::tool::{Tool, ToolType}; use crate::entity::item::weapon; use crate::ship::map::MapArea; -use crate::ship::ship::ItemDropLocation; +use crate::ship::ship::{TradeItem, ItemDropLocation}; use crate::ship::drops::{ItemDrop, ItemDropType}; use crate::ship::location::{AreaClient, RoomId}; use crate::ship::shops::ShopItem; @@ -180,6 +180,11 @@ impl ItemManager { .ok_or(ItemManagerError::NoCharacter(character.id))?) } + pub fn get_character_inventory_mut<'a>(&'a mut self, character: &CharacterEntity) -> Result<&'a mut CharacterInventory, anyhow::Error> { + Ok(self.character_inventory.get_mut(&character.id) + .ok_or(ItemManagerError::NoCharacter(character.id))?) + } + pub fn get_character_bank(&self, character: &CharacterEntity) -> Result<&CharacterBank, anyhow::Error> { Ok(self.character_bank .get(&character.id) @@ -919,4 +924,15 @@ impl ItemManager { Ok(weapon) } + + pub async fn send_items_to_other_player(&mut self, + entity_gateway: &mut EG, + source_character: &CharacterEntity, + dest_character: &CharacterEntity, + items: &Vec) + -> Result<(), anyhow::Error> { + + + + } } diff --git a/src/ship/packet/builder/trade.rs b/src/ship/packet/builder/trade.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/ship/packet/handler/mod.rs b/src/ship/packet/handler/mod.rs index 02761f8..c921d28 100644 --- a/src/ship/packet/handler/mod.rs +++ b/src/ship/packet/handler/mod.rs @@ -7,3 +7,4 @@ pub mod room; pub mod settings; pub mod quest; pub mod ship; +pub mod trade; diff --git a/src/ship/packet/handler/trade.rs b/src/ship/packet/handler/trade.rs new file mode 100644 index 0000000..8f3c04f --- /dev/null +++ b/src/ship/packet/handler/trade.rs @@ -0,0 +1,181 @@ +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}; +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)?; + let trade_partner = trade_partner + .iter() + .filter(|ac| { + ac.local_client.id() == items_to_trade.trade_target + }) + .nth(0) + .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((trade_partner.client, trade_items)); + + 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_partner, _)| { + client_location.get_local_client(*trade_partner).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())) + } +} diff --git a/src/ship/ship.rs b/src/ship/ship.rs index 8091647..1478143 100644 --- a/src/ship/ship.rs +++ b/src/ship/ship.rs @@ -106,6 +106,8 @@ pub enum RecvShipPacket { SaveOptions(SaveOptions), RequestShipList(RequestShipList), RequestShipBlockList(RequestShipBlockList), + ItemsToTrade(ItemsToTrade), + TradeConfirmed(TradeConfirmed), } impl RecvServerPacket for RecvShipPacket { @@ -143,6 +145,8 @@ impl RecvServerPacket for RecvShipPacket { 0xA1 => Ok(RecvShipPacket::RequestShipBlockList(RequestShipBlockList::from_bytes(data)?)), 0xA2 => Ok(RecvShipPacket::RequestQuestList(RequestQuestList::from_bytes(data)?)), 0xAC => Ok(RecvShipPacket::DoneLoadingQuest(DoneLoadingQuest::from_bytes(data)?)), + 0xD0 => Ok(RecvShipPacket::ItemsToTrade(ItemsToTrade::from_bytes(data)?)), + 0xD2 => Ok(RecvShipPacket::TradeConfirmed(TradeConfirmed::from_bytes(data)?)), 0xE7 => Ok(RecvShipPacket::FullCharacterData(Box::new(FullCharacterData::from_bytes(data)?))), 0x1ED => Ok(RecvShipPacket::SaveOptions(SaveOptions::from_bytes(data)?)), _ => Err(PacketParseError::WrongPacketForServerType(u16::from_le_bytes([data[2], data[3]]), data.to_vec())) @@ -184,6 +188,8 @@ pub enum SendShipPacket { DoneLoadingQuest(DoneLoadingQuest), BankItemList(BankItemList), RedirectClient(RedirectClient), + AcknowledgeTrade(AcknowledgeTrade), + CancelTrade(CancelTrade), } impl SendServerPacket for SendShipPacket { @@ -221,6 +227,8 @@ impl SendServerPacket for SendShipPacket { SendShipPacket::DoneLoadingQuest(pkt) => pkt.as_bytes(), SendShipPacket::BankItemList(pkt) => pkt.as_bytes(), SendShipPacket::RedirectClient(pkt) => pkt.as_bytes(), + SendShipPacket::AcknowledgeTrade(pkt) => pkt.as_bytes(), + SendShipPacket::CancelTrade(pkt) => pkt.as_bytes(), } } } @@ -239,6 +247,14 @@ pub struct LoadingQuest { //pub quest_chunk_bin: Option>>, } + +pub enum TradeItem { + Individual(items::ClientItemId), + Stacked(items::ClientItemId, usize), + Meseta(usize), +} + + pub struct ClientState { pub user: UserAccountEntity, pub settings: UserSettingsEntity, @@ -257,6 +273,8 @@ pub struct ClientState { pub tool_shop: Vec, pub armor_shop: Vec, pub tek: Option<(items::ClientItemId, item::weapon::TekSpecialModifier, item::weapon::TekPercentModifier, i32)>, + pub trade: Option<(ClientId, Vec)>, + pub confirmed_trade: bool, } impl ClientState { @@ -277,6 +295,8 @@ impl ClientState { tool_shop: Vec::new(), armor_shop: Vec::new(), tek: None, + trade: None, + confirmed_trade: false, } } } @@ -696,7 +716,16 @@ impl ServerState for ShipServerState { }, RecvShipPacket::RequestShipBlockList(_) => { handler::ship::block_list(id, &self.name, self.blocks.0.len()) - } + }, + RecvShipPacket::ItemsToTrade(items_to_trade) => { + log::warn!("trade! {:?} {:?}", id, items_to_trade); + let block = self.blocks.with_client(id, &self.clients)?; + handler::trade::items_to_trade(id, items_to_trade, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + }, + RecvShipPacket::TradeConfirmed(_) => { + let block = self.blocks.with_client(id, &self.clients)?; + handler::trade::trade_confirmed(id, &mut self.entity_gateway, &block.client_location, &mut self.clients, &mut self.item_manager).await? + }, }) }