|
@ -6,11 +6,12 @@ use libpso::packet::ship::*; |
|
|
use libpso::packet::messages::*;
|
|
|
use libpso::packet::messages::*;
|
|
|
use crate::common::leveltable::CharacterLevelTable;
|
|
|
use crate::common::leveltable::CharacterLevelTable;
|
|
|
use crate::common::serverstate::ClientId;
|
|
|
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::ship::{SendShipPacket, ShipError, Clients, Rooms, ItemShops};
|
|
|
|
|
|
use crate::ship::location::{ClientLocation, ClientLocationError, LocalClientId};
|
|
|
use crate::ship::drops::ItemDrop;
|
|
|
use crate::ship::drops::ItemDrop;
|
|
|
use crate::ship::items::{ItemManager, ItemManagerError, ClientItemId, TriggerCreateItem, FloorItem, FloorType, ItemToTradeDetail};
|
|
|
use crate::ship::items::{ItemManager, ItemManagerError, ClientItemId, TriggerCreateItem, FloorItem, FloorType, ItemToTradeDetail};
|
|
|
use crate::ship::items::inventory::InventoryItem;
|
|
|
use crate::ship::items::inventory::InventoryItem;
|
|
|
|
|
|
use crate::ship::trade::{TradeItem, TradeState, TradeStatus};
|
|
|
use crate::entity::gateway::EntityGateway;
|
|
|
use crate::entity::gateway::EntityGateway;
|
|
|
use crate::entity::item;
|
|
|
use crate::entity::item;
|
|
|
use libpso::utf8_to_utf16_array;
|
|
|
use libpso::utf8_to_utf16_array;
|
|
@ -18,80 +19,320 @@ use crate::ship::packet::builder; |
|
|
use crate::ship::shops::{ShopItem, ToolShopItem, ArmorShopItem};
|
|
|
use crate::ship::shops::{ShopItem, ToolShopItem, ArmorShopItem};
|
|
|
|
|
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
#[error("")]
|
|
|
|
|
|
pub enum TradeError {
|
|
|
pub enum TradeError {
|
|
|
|
|
|
#[error("no partner")]
|
|
|
CouldNotFindTradePartner,
|
|
|
CouldNotFindTradePartner,
|
|
|
|
|
|
#[error("item does not match id")]
|
|
|
ClientItemIdDidNotMatchItem(ClientItemId, [u8; 16]),
|
|
|
ClientItemIdDidNotMatchItem(ClientItemId, [u8; 16]),
|
|
|
|
|
|
#[error("invalid stack {1}")]
|
|
|
InvalidStackAmount(ClientItemId, usize),
|
|
|
InvalidStackAmount(ClientItemId, usize),
|
|
|
|
|
|
#[error("not in trade menu")]
|
|
|
NotInTradeMenu,
|
|
|
NotInTradeMenu,
|
|
|
|
|
|
#[error("trade menu at an invalid point")]
|
|
|
|
|
|
MismatchedStatus,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub async fn inner_items_to_trade<EG>(id: ClientId,
|
|
|
|
|
|
items_to_trade: &ItemsToTrade,
|
|
|
|
|
|
|
|
|
// TODO: remove target
|
|
|
|
|
|
pub async fn trade_request<EG>(id: ClientId,
|
|
|
|
|
|
trade_request: &TradeRequest,
|
|
|
|
|
|
target: u32,
|
|
|
entity_gateway: &mut EG,
|
|
|
entity_gateway: &mut EG,
|
|
|
client_location: &ClientLocation,
|
|
|
client_location: &ClientLocation,
|
|
|
clients: &mut Clients,
|
|
|
clients: &mut Clients,
|
|
|
item_manager: &mut ItemManager)
|
|
|
|
|
|
|
|
|
item_manager: &mut ItemManager,
|
|
|
|
|
|
trades: &mut TradeState)
|
|
|
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
|
|
|
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
|
|
|
where
|
|
|
where
|
|
|
EG: EntityGateway
|
|
|
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_request = trade_request.clone(); // TODO: make this function take ownership of packet
|
|
|
|
|
|
match trade_request.trade {
|
|
|
|
|
|
TradeRequestCommand::Initialize(ref act, meseta) => {
|
|
|
|
|
|
match act {
|
|
|
|
|
|
TradeRequestInitializeCommand::Initialize => {
|
|
|
let trade_partner = client_location.get_client_neighbors(id)?
|
|
|
let trade_partner = client_location.get_client_neighbors(id)?
|
|
|
.into_iter()
|
|
|
.into_iter()
|
|
|
.filter(|ac| {
|
|
|
.filter(|ac| {
|
|
|
ac.local_client.id() == items_to_trade.trade_target
|
|
|
|
|
|
|
|
|
ac.local_client.id() == trade_request.client
|
|
|
})
|
|
|
})
|
|
|
.next()
|
|
|
.next()
|
|
|
.ok_or(TradeError::CouldNotFindTradePartner)?;
|
|
|
.ok_or(TradeError::CouldNotFindTradePartner)?;
|
|
|
|
|
|
trades.new_trade(&id, &trade_partner.client);
|
|
|
|
|
|
Ok(Box::new(client_location.get_all_clients_by_client(id)?.into_iter()
|
|
|
|
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
|
|
|
|
|
|
})))
|
|
|
|
|
|
},
|
|
|
|
|
|
TradeRequestInitializeCommand::Respond => {
|
|
|
|
|
|
Ok(trades
|
|
|
|
|
|
.with(&id, |this, other| -> Option<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>> {
|
|
|
|
|
|
if this.status == TradeStatus::ReceivedRequest && other.status == TradeStatus::SentRequest {
|
|
|
|
|
|
this.status = TradeStatus::Trading;
|
|
|
|
|
|
other.status = TradeStatus::Trading;
|
|
|
|
|
|
|
|
|
|
|
|
let trade_request = trade_request.clone();
|
|
|
|
|
|
Some(Box::new(client_location.get_all_clients_by_client(id).ok()?.into_iter()
|
|
|
|
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
|
|
|
|
|
|
})))
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
})?
|
|
|
|
|
|
.unwrap_or_else(|| -> Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send> {
|
|
|
|
|
|
trades.remove_trade(&id);
|
|
|
|
|
|
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
|
|
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
|
|
|
|
|
|
})
|
|
|
|
|
|
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
TradeRequestCommand::AddItem(item_id, amount) => {
|
|
|
|
|
|
Ok(trades
|
|
|
|
|
|
.with(&id, |this, other| -> Option<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>> {
|
|
|
|
|
|
if this.status == TradeStatus::Trading && other.status == TradeStatus::Trading {
|
|
|
|
|
|
let client = clients.get(&this.client())?;//.ok_or(ShipError::ClientNotFound(id)).ok()?;
|
|
|
|
|
|
let inventory = item_manager.get_character_inventory(&client.character).ok()?;
|
|
|
|
|
|
let item = inventory.get_item_by_id(ClientItemId(item_id))?;
|
|
|
|
|
|
|
|
|
|
|
|
match item {
|
|
|
|
|
|
InventoryItem::Individual(_) => {
|
|
|
|
|
|
this.items.push(TradeItem::Individual(ClientItemId(item_id)));
|
|
|
|
|
|
},
|
|
|
|
|
|
InventoryItem::Stacked(stacked_item) => {
|
|
|
|
|
|
if stacked_item.count() < amount as usize {
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.items.push(TradeItem::Stacked(ClientItemId(item_id), amount as usize));
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let trade_request = trade_request.clone();
|
|
|
|
|
|
Some(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
|
|
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
|
|
|
|
|
|
})))
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
})?
|
|
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
|
|
trades.remove_trade(&id);
|
|
|
|
|
|
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
|
|
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
|
|
|
|
|
|
})
|
|
|
|
|
|
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
|
|
|
|
|
|
}))
|
|
|
|
|
|
},
|
|
|
|
|
|
TradeRequestCommand::RemoveItem(item_id, amount) => {
|
|
|
|
|
|
Ok(trades
|
|
|
|
|
|
.with(&id, |this, other| -> Option<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>> {
|
|
|
|
|
|
if this.status == TradeStatus::Trading && other.status == TradeStatus::Trading {
|
|
|
|
|
|
let client = clients.get(&this.client())?; //.ok_or(ShipError::ClientNotFound(id)).ok()?;
|
|
|
|
|
|
let inventory = item_manager.get_character_inventory(&client.character).ok()?;
|
|
|
|
|
|
let item = inventory.get_item_by_id(ClientItemId(item_id))?;
|
|
|
|
|
|
|
|
|
|
|
|
match item {
|
|
|
|
|
|
InventoryItem::Individual(_) => {
|
|
|
|
|
|
this.items.retain(|item| {
|
|
|
|
|
|
item.item_id() != ClientItemId(item_id)
|
|
|
|
|
|
})
|
|
|
|
|
|
//this.items.push(TradeItem::Individual(ClientItemId(item_id)));
|
|
|
|
|
|
},
|
|
|
|
|
|
InventoryItem::Stacked(stacked_item) => {
|
|
|
|
|
|
let trade_item_index = this.items.iter()
|
|
|
|
|
|
.position(|item| {
|
|
|
|
|
|
item.item_id() == ClientItemId(item_id)
|
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
|
|
match this.items[trade_item_index].stacked()?.1.cmp(&(amount as usize)) {
|
|
|
|
|
|
std::cmp::Ordering::Greater => {
|
|
|
|
|
|
this.items[trade_item_index].stacked()?.1 -= amount as usize;
|
|
|
|
|
|
},
|
|
|
|
|
|
std::cmp::Ordering::Equal => {
|
|
|
|
|
|
this.items.remove(trade_item_index);
|
|
|
|
|
|
},
|
|
|
|
|
|
std::cmp::Ordering::Less => {
|
|
|
|
|
|
return None
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
let trade_request = trade_request.clone();
|
|
|
|
|
|
Some(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
|
|
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
|
|
|
|
|
|
})))
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
})?
|
|
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
|
|
trades.remove_trade(&id);
|
|
|
|
|
|
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
|
|
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
|
|
|
|
|
|
})
|
|
|
|
|
|
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
|
|
|
|
|
|
}))
|
|
|
|
|
|
},
|
|
|
|
|
|
TradeRequestCommand::Confirm => {
|
|
|
|
|
|
Ok(trades
|
|
|
|
|
|
.with(&id, |this, other| -> Option<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>> {
|
|
|
|
|
|
if this.status == TradeStatus::Trading && (other.status == TradeStatus::Trading || other.status == TradeStatus::Confirmed) {
|
|
|
|
|
|
this.status = TradeStatus::Confirmed;
|
|
|
|
|
|
|
|
|
|
|
|
let trade_request = trade_request.clone();
|
|
|
|
|
|
Some(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
|
|
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
|
|
|
|
|
|
})))
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
})?
|
|
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
|
|
trades.remove_trade(&id);
|
|
|
|
|
|
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
|
|
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
|
|
|
|
|
|
})
|
|
|
|
|
|
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
|
|
|
|
|
|
}))
|
|
|
|
|
|
},
|
|
|
|
|
|
TradeRequestCommand::FinalConfirm => {
|
|
|
|
|
|
Ok(trades
|
|
|
|
|
|
.with(&id, |this, other| -> Option<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>> {
|
|
|
|
|
|
if this.status == TradeStatus::Confirmed && (other.status == TradeStatus::Confirmed || other.status == TradeStatus::FinalConfirm) {
|
|
|
|
|
|
this.status = TradeStatus::FinalConfirm;
|
|
|
|
|
|
|
|
|
|
|
|
let trade_request = trade_request.clone();
|
|
|
|
|
|
Some(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
|
|
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::DirectMessage(DirectMessage::new(target, GameMessage::TradeRequest(trade_request.clone()))))
|
|
|
|
|
|
})))
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
})?
|
|
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
|
|
trades.remove_trade(&id);
|
|
|
|
|
|
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
|
|
|
|
.filter(move |client| client.local_client.id() == target as u8)
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
|
|
|
|
|
|
})
|
|
|
|
|
|
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
|
|
|
|
|
|
}))
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn status_is<const N: usize>(status: &TradeStatus, statuses: &[TradeStatus; N]) -> bool {
|
|
|
|
|
|
statuses.iter().any(|s| s == status)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn status_is_not<const N: usize>(status: &TradeStatus, statuses: &[TradeStatus; N]) -> bool {
|
|
|
|
|
|
!status_is(status, statuses)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
trades: &mut TradeState)
|
|
|
|
|
|
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
|
|
|
|
|
|
where
|
|
|
|
|
|
EG: EntityGateway
|
|
|
|
|
|
{
|
|
|
|
|
|
Ok(trades
|
|
|
|
|
|
.with(&id, |this, other| -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> {
|
|
|
|
|
|
println!("statuses {:?} {:?}", this.status, other.status);
|
|
|
|
|
|
if status_is_not(&this.status, &[TradeStatus::FinalConfirm]) || status_is_not(&other.status, &[TradeStatus::FinalConfirm, TradeStatus::ItemsChecked]) {
|
|
|
|
|
|
//if this.status != TradeStatus::FinalConfirm || (other.status != TradeStatus::FinalConfirm || other.status != TradeStatus::ItemsChecked) {
|
|
|
|
|
|
return Err(TradeError::MismatchedStatus.into())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let client = clients.get(&this.client()).ok_or(ShipError::ClientNotFound(this.client()))?;
|
|
|
|
|
|
let inventory = item_manager.get_character_inventory(&client.character)?;
|
|
|
|
|
|
|
|
|
let item_blobs = items_to_trade.items.iter().take(items_to_trade.count as usize);
|
|
|
let item_blobs = items_to_trade.items.iter().take(items_to_trade.count as usize);
|
|
|
let trade_items = item_blobs
|
|
|
let trade_items = item_blobs
|
|
|
.map(|item| {
|
|
|
.map(|item| {
|
|
|
// TOOD: meseta?
|
|
|
// 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()
|
|
|
|
|
|
|
|
|
let real_item = inventory.get_item_by_id(ClientItemId(item.item_id))
|
|
|
.ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?;
|
|
|
.ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item.item_id)))?;
|
|
|
let trade_item_bytes: [u8; 16] = item.item_data.iter()
|
|
|
let trade_item_bytes: [u8; 16] = item.item_data.iter()
|
|
|
.chain(item.item_data2.iter())
|
|
|
.chain(item.item_data2.iter())
|
|
|
.cloned().collect::<Vec<u8>>()
|
|
|
.cloned().collect::<Vec<u8>>()
|
|
|
.try_into()
|
|
|
.try_into()
|
|
|
.unwrap();
|
|
|
.unwrap();
|
|
|
if real_item.as_client_bytes() == trade_item_bytes {
|
|
|
|
|
|
match real_item {
|
|
|
match real_item {
|
|
|
InventoryItem::Individual(individual_inventory_item) => {
|
|
|
InventoryItem::Individual(individual_inventory_item) => {
|
|
|
|
|
|
println!("real indiv item: {:?} {:?} ==? {:?}", real_item, real_item.as_client_bytes(), trade_item_bytes);
|
|
|
|
|
|
if real_item.as_client_bytes() == trade_item_bytes {
|
|
|
Ok(TradeItem::Individual(individual_inventory_item.item_id))
|
|
|
Ok(TradeItem::Individual(individual_inventory_item.item_id))
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into())
|
|
|
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
InventoryItem::Stacked(stacked_inventory_item) => {
|
|
|
InventoryItem::Stacked(stacked_inventory_item) => {
|
|
|
|
|
|
println!("real stack item: {:?} {:?} ==? {:?}", real_item, real_item.as_client_bytes(), trade_item_bytes);
|
|
|
|
|
|
if real_item.as_client_bytes()[0..4] == trade_item_bytes[0..4] {
|
|
|
let amount = trade_item_bytes[5] as usize;
|
|
|
let amount = trade_item_bytes[5] as usize;
|
|
|
if amount > stacked_inventory_item.entity_ids.len() {
|
|
|
|
|
|
|
|
|
if amount <= stacked_inventory_item.entity_ids.len() {
|
|
|
Ok(TradeItem::Stacked(stacked_inventory_item.item_id, amount))
|
|
|
Ok(TradeItem::Stacked(stacked_inventory_item.item_id, amount))
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
else {
|
|
|
Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into())
|
|
|
Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into())
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
else {
|
|
|
Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into())
|
|
|
Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into())
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
})
|
|
|
})
|
|
|
.collect::<Result<Vec<_>, anyhow::Error>>()?;
|
|
|
.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()))
|
|
|
|
|
|
|
|
|
this.status = TradeStatus::ItemsChecked;
|
|
|
|
|
|
Ok(Box::new(std::iter::once((other.client(), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})))))
|
|
|
|
|
|
})?
|
|
|
|
|
|
.unwrap_or_else(|err| {
|
|
|
|
|
|
println!("asdf {:?}", err);
|
|
|
|
|
|
log::warn!("trade error: {:?}", err);
|
|
|
|
|
|
let (this, other) = trades.remove_trade(&id);
|
|
|
|
|
|
Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
|
|
|
|
.filter(move |client| other.as_ref().map(|other| client.client == other.client() ).unwrap_or_else(|| false))
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
|
|
|
|
|
|
})
|
|
|
|
|
|
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {})))))
|
|
|
|
|
|
}))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
pub async fn items_to_trade<EG>(id: ClientId,
|
|
|
pub async fn items_to_trade<EG>(id: ClientId,
|
|
@ -99,28 +340,25 @@ pub async fn items_to_trade<EG>(id: ClientId, |
|
|
entity_gateway: &mut EG,
|
|
|
entity_gateway: &mut EG,
|
|
|
client_location: &ClientLocation,
|
|
|
client_location: &ClientLocation,
|
|
|
clients: &mut Clients,
|
|
|
clients: &mut Clients,
|
|
|
item_manager: &mut ItemManager)
|
|
|
|
|
|
|
|
|
item_manager: &mut ItemManager,
|
|
|
|
|
|
trades: &mut TradeState)
|
|
|
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
|
|
|
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
|
|
|
where
|
|
|
where
|
|
|
EG: EntityGateway
|
|
|
EG: EntityGateway
|
|
|
{
|
|
|
{
|
|
|
let t = inner_items_to_trade(id, items_to_trade_pkt, entity_gateway, client_location, clients, item_manager).await;
|
|
|
|
|
|
|
|
|
let t = inner_items_to_trade(id, items_to_trade_pkt, entity_gateway, client_location, clients, item_manager, trades).await;
|
|
|
match t {
|
|
|
match t {
|
|
|
Ok(p) => Ok(p),
|
|
|
Ok(p) => Ok(p),
|
|
|
Err(err) => {
|
|
|
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()
|
|
|
|
|
|
|
|
|
log::warn!("atrade error: {:?}", err);
|
|
|
|
|
|
println!("qwer");
|
|
|
|
|
|
let (this, other) = trades.remove_trade(&id);
|
|
|
|
|
|
Ok(Box::new(client_location.get_all_clients_by_client(id)?.into_iter()
|
|
|
|
|
|
.filter(move |client| other.as_ref().map(|other| client.client == other.client()).unwrap_or_else(|| false))
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
|
|
|
})
|
|
|
})
|
|
|
.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())))
|
|
|
|
|
|
|
|
|
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@ -129,50 +367,56 @@ pub async fn trade_confirmed<EG>(id: ClientId, |
|
|
entity_gateway: &mut EG,
|
|
|
entity_gateway: &mut EG,
|
|
|
client_location: &ClientLocation,
|
|
|
client_location: &ClientLocation,
|
|
|
clients: &mut Clients,
|
|
|
clients: &mut Clients,
|
|
|
item_manager: &mut ItemManager)
|
|
|
|
|
|
|
|
|
item_manager: &mut ItemManager,
|
|
|
|
|
|
trades: &mut TradeState)
|
|
|
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
|
|
|
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
|
|
|
where
|
|
|
where
|
|
|
EG: EntityGateway
|
|
|
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?;
|
|
|
|
|
|
|
|
|
enum TradeReady<'a> {
|
|
|
|
|
|
OnePlayer,
|
|
|
|
|
|
BothPlayers(crate::ship::location::RoomId,
|
|
|
|
|
|
(crate::ship::location::AreaClient, &'a crate::ship::ship::ClientState, crate::ship::trade::ClientTradeState),
|
|
|
|
|
|
(crate::ship::location::AreaClient, &'a crate::ship::ship::ClientState, crate::ship::trade::ClientTradeState)),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let trade_instructions = trades
|
|
|
|
|
|
.with(&id, |this, other| -> Result<_, anyhow::Error> {
|
|
|
|
|
|
if status_is_not(&this.status, &[TradeStatus::ItemsChecked]) || status_is_not(&other.status, &[TradeStatus::ItemsChecked, TradeStatus::TradeComplete]) {
|
|
|
|
|
|
return Err(TradeError::MismatchedStatus.into())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: check for space in inventory!
|
|
|
|
|
|
this.status = TradeStatus::TradeComplete;
|
|
|
|
|
|
|
|
|
|
|
|
if this.status == TradeStatus::TradeComplete && other.status == TradeStatus::TradeComplete {
|
|
|
|
|
|
let this_client = clients.get(&this.client()).ok_or(ShipError::ClientNotFound(this.client()))?;
|
|
|
|
|
|
let other_client = clients.get(&other.client()).ok_or(ShipError::ClientNotFound(other.client()))?;
|
|
|
|
|
|
let this_local_client = client_location.get_local_client(this.client())?;
|
|
|
|
|
|
let other_local_client = client_location.get_local_client(other.client())?;
|
|
|
|
|
|
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(TradeReady::BothPlayers(room_id,
|
|
|
|
|
|
(this_local_client, this_client, this.clone()),
|
|
|
|
|
|
(other_local_client, other_client, other.clone())))
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
Ok(TradeReady::OnePlayer)
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: this match needs to handle errors better
|
|
|
|
|
|
match trade_instructions {
|
|
|
|
|
|
Ok(Ok(trade)) => {
|
|
|
|
|
|
match trade {
|
|
|
|
|
|
TradeReady::OnePlayer => {
|
|
|
|
|
|
Ok(Box::new(None.into_iter()) as Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>)
|
|
|
|
|
|
},
|
|
|
|
|
|
TradeReady::BothPlayers(room_id, (this_local_client, this_client, this), (other_local_client, other_client, other)) => {
|
|
|
|
|
|
let traded_items = item_manager.trade_items(entity_gateway,
|
|
|
|
|
|
room_id,
|
|
|
|
|
|
(&this_local_client, &this_client.character, &this.items),
|
|
|
|
|
|
(&other_local_client, &other_client.character, &other.items)).await?;
|
|
|
|
|
|
|
|
|
let clients_in_room = client_location.get_all_clients_by_client(id)?;
|
|
|
let clients_in_room = client_location.get_all_clients_by_client(id)?;
|
|
|
let traded_item_packets = traded_items
|
|
|
let traded_item_packets = traded_items
|
|
@ -181,14 +425,14 @@ where |
|
|
match item.item_detail {
|
|
|
match item.item_detail {
|
|
|
ItemToTradeDetail::Individual(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 = ?
|
|
|
|
|
|
|
|
|
GameMessage::CreateItem(builder::message::create_individual_item(item.add_to, item.new_item_id, &item_detail).unwrap()),
|
|
|
|
|
|
GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.current_item_id, 1)) // TODO: amount = ?
|
|
|
]
|
|
|
]
|
|
|
},
|
|
|
},
|
|
|
ItemToTradeDetail::Stacked(tool, 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))
|
|
|
|
|
|
|
|
|
GameMessage::CreateItem(builder::message::create_stacked_item(item.add_to, item.new_item_id, &tool, amount).unwrap()),
|
|
|
|
|
|
GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.current_item_id, amount as u32))
|
|
|
]
|
|
|
]
|
|
|
},
|
|
|
},
|
|
|
}
|
|
|
}
|
|
@ -198,14 +442,37 @@ where |
|
|
clients_in_room
|
|
|
clients_in_room
|
|
|
.clone()
|
|
|
.clone()
|
|
|
.into_iter()
|
|
|
.into_iter()
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::Message(Message::new(packet.clone())))
|
|
|
|
|
|
|
|
|
.filter_map(move |client| {
|
|
|
|
|
|
match packet {
|
|
|
|
|
|
GameMessage::PlayerNoLongerHasItem(ref no_longer) => {
|
|
|
|
|
|
if client.local_client == no_longer.client {
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
Some((client.client, SendShipPacket::Message(Message::new(packet.clone()))))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => Some((client.client, SendShipPacket::Message(Message::new(packet.clone()))))
|
|
|
|
|
|
}
|
|
|
})
|
|
|
})
|
|
|
})
|
|
|
})
|
|
|
.flatten();
|
|
|
.flatten();
|
|
|
Ok(Box::new(traded_item_packets))
|
|
|
|
|
|
|
|
|
let close_trade = vec![
|
|
|
|
|
|
(this.client(), SendShipPacket::TradeSuccessful(TradeSuccessful::default())),
|
|
|
|
|
|
(other.client(), SendShipPacket::TradeSuccessful(TradeSuccessful::default()))
|
|
|
|
|
|
].into_iter();
|
|
|
|
|
|
Ok(Box::new(traded_item_packets.chain(close_trade)))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
err @ _ => {
|
|
|
|
|
|
let (this, other) = trades.remove_trade(&id);
|
|
|
|
|
|
Ok(Box::new(client_location.get_all_clients_by_client(id).unwrap().into_iter()
|
|
|
|
|
|
.filter(move |client| other.as_ref().map(|other| client.client == other.client()).unwrap_or_else(|| false))
|
|
|
|
|
|
.map(move |client| {
|
|
|
|
|
|
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
|
|
|
|
|
|
})
|
|
|
|
|
|
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))))
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
|
|
|
Ok(Box::new(None.into_iter()))
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|