518 lines
28 KiB
Rust
Raw Normal View History

2020-12-12 19:55:27 -07:00
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;
2021-12-10 13:24:59 -07:00
use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms, ItemShops};
use crate::ship::location::{ClientLocation, ClientLocationError, LocalClientId};
2020-12-12 19:55:27 -07:00
use crate::ship::drops::ItemDrop;
2021-10-15 12:17:37 -06:00
use crate::ship::items::{ItemManager, ItemManagerError, ClientItemId, TriggerCreateItem, FloorItem, FloorType, ItemToTradeDetail};
2020-12-12 19:55:27 -07:00
use crate::ship::items::inventory::InventoryItem;
2021-12-10 13:24:59 -07:00
use crate::ship::trade::{TradeItem, TradeState, TradeStatus};
2020-12-12 19:55:27 -07:00
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};
2021-12-12 22:55:08 -07:00
pub const MESETA_ITEM_ID: ClientItemId = ClientItemId(0xFFFFFF01);
pub const OTHER_MESETA_ITEM_ID: ClientItemId = ClientItemId(0xFFFFFFFF);
2021-12-12 15:55:59 -07:00
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
2020-12-12 19:55:27 -07:00
pub enum TradeError {
2021-12-10 13:24:59 -07:00
#[error("no partner")]
2020-12-12 19:55:27 -07:00
CouldNotFindTradePartner,
2021-12-12 15:55:59 -07:00
#[error("invalid item id")]
InvalidItemId(ClientItemId),
2021-12-10 13:24:59 -07:00
#[error("item does not match id")]
2020-12-12 19:55:27 -07:00
ClientItemIdDidNotMatchItem(ClientItemId, [u8; 16]),
2021-12-10 13:24:59 -07:00
#[error("invalid stack {1}")]
2020-12-12 19:55:27 -07:00
InvalidStackAmount(ClientItemId, usize),
2021-12-10 13:24:59 -07:00
#[error("not in trade menu")]
2020-12-12 19:55:27 -07:00
NotInTradeMenu,
2021-12-10 13:24:59 -07:00
#[error("trade menu at an invalid point")]
MismatchedStatus,
2021-12-12 15:55:59 -07:00
#[error("no space in inventory")]
NoInventorySpace,
#[error("no space in stack")]
NoStackSpace,
2021-12-12 22:55:08 -07:00
#[error("invalid meseta amount")]
InvalidMeseta,
2020-12-12 19:55:27 -07:00
}
2021-12-10 13:24:59 -07:00
// TODO: remove target
pub async fn trade_request<EG>(id: ClientId,
trade_request: &TradeRequest,
target: u32,
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>
2020-12-12 19:55:27 -07:00
where
EG: EntityGateway
{
2021-12-10 13:24:59 -07:00
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)?
.into_iter()
.filter(|ac| {
2021-12-12 22:55:08 -07:00
ac.local_client.id() == target as u8 //trade_request.client
2021-12-10 13:24:59 -07:00
})
.next()
.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
2021-12-12 15:55:59 -07:00
.with(&id, |this, other| -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error> {
2021-12-10 13:24:59 -07:00
if this.status == TradeStatus::Trading && other.status == TradeStatus::Trading {
2021-12-12 15:55:59 -07:00
let client = clients.get(&this.client()).ok_or(ShipError::ClientNotFound(this.client()))?;
let inventory = item_manager.get_character_inventory(&client.character)?;
2021-12-12 22:55:08 -07:00
if ClientItemId(item_id) == MESETA_ITEM_ID {
this.meseta += amount as usize;
}
else {
let item = inventory.get_item_by_id(ClientItemId(item_id)).ok_or(ItemManagerError::NoSuchItemId(ClientItemId(item_id)))?;
2021-12-10 13:24:59 -07:00
2021-12-12 22:55:08 -07:00
match item {
InventoryItem::Individual(_) => {
this.items.push(TradeItem::Individual(ClientItemId(item_id)));
},
InventoryItem::Stacked(stacked_item) => {
if stacked_item.count() < amount as usize {
return Err(TradeError::InvalidStackAmount(ClientItemId(item_id), amount as usize).into());
}
this.items.push(TradeItem::Stacked(ClientItemId(item_id), amount as usize));
},
}
2020-12-12 19:55:27 -07:00
}
2021-12-10 13:24:59 -07:00
let trade_request = trade_request.clone();
2021-12-12 15:55:59 -07:00
Ok(Box::new(client_location.get_all_clients_by_client(id)?.into_iter()
2021-12-10 13:24:59 -07:00
.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 {
2021-12-12 15:55:59 -07:00
Err(TradeError::MismatchedStatus.into())
2021-12-10 13:24:59 -07:00
}
})?
2021-12-12 15:55:59 -07:00
.unwrap_or_else(|err| {
2021-12-10 13:24:59 -07:00
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()?;
2021-12-12 22:55:08 -07:00
if ClientItemId(item_id) == MESETA_ITEM_ID {
this.meseta -= amount as usize;
}
else {
let item = inventory.get_item_by_id(ClientItemId(item_id))?;
2021-12-10 13:24:59 -07:00
2021-12-12 22:55:08 -07:00
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)
})?;
2021-12-10 13:24:59 -07:00
2021-12-12 22:55:08 -07:00
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
}
2021-12-10 13:24:59 -07:00
}
2021-12-12 22:55:08 -07:00
},
}
2020-12-12 19:55:27 -07:00
}
2021-12-10 13:24:59 -07:00
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()))))
})))
2020-12-12 19:55:27 -07:00
}
2021-12-10 13:24:59 -07:00
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>> {
2021-12-12 15:55:59 -07:00
if status_is(&this.status, &[TradeStatus::Trading]) && status_is(&other.status, &[TradeStatus::Trading, TradeStatus::Confirmed]) {
2021-12-10 13:24:59 -07:00
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> {
if status_is_not(&this.status, &[TradeStatus::FinalConfirm]) || status_is_not(&other.status, &[TradeStatus::FinalConfirm, TradeStatus::ItemsChecked]) {
return Err(TradeError::MismatchedStatus.into())
2020-12-12 19:55:27 -07:00
}
2021-12-10 13:24:59 -07:00
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);
2021-12-12 22:55:08 -07:00
item_blobs
2021-12-10 13:24:59 -07:00
.map(|item| {
2021-12-12 22:55:08 -07:00
if ClientItemId(item.item_id) == OTHER_MESETA_ITEM_ID {
if item.item_data[0] != 4 {
return Err(TradeError::InvalidItemId(ClientItemId(item.item_id)).into())
}
let amount = u32::from_le_bytes(item.item_data2);
if amount > client.character.meseta {
return Err(TradeError::InvalidMeseta.into())
}
Ok(())
}
else {
let real_item = inventory.get_item_by_id(ClientItemId(item.item_id))
.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();
match real_item {
InventoryItem::Individual(individual_inventory_item) => {
if real_item.as_client_bytes() == trade_item_bytes {
Ok(())
2021-12-10 13:24:59 -07:00
}
else {
2021-12-12 22:55:08 -07:00
Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into())
}
},
InventoryItem::Stacked(stacked_inventory_item) => {
if real_item.as_client_bytes()[0..4] == trade_item_bytes[0..4] {
let amount = trade_item_bytes[5] as usize;
if amount <= stacked_inventory_item.entity_ids.len() {
Ok(())
}
else {
Err(TradeError::InvalidStackAmount(stacked_inventory_item.item_id, amount).into())
}
}
else {
Err(TradeError::ClientItemIdDidNotMatchItem(ClientItemId(item.item_id), trade_item_bytes).into())
2021-12-10 13:24:59 -07:00
}
}
}
}
})
.collect::<Result<Vec<_>, anyhow::Error>>()?;
2020-12-12 19:55:27 -07:00
2021-12-10 13:24:59 -07:00
this.status = TradeStatus::ItemsChecked;
2021-12-12 22:55:08 -07:00
if this.status == TradeStatus::ItemsChecked && other.status == TradeStatus::ItemsChecked {
Ok(Box::new(vec![
(this.client(), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})),
(other.client(), SendShipPacket::AcknowledgeTrade(AcknowledgeTrade {})),
].into_iter()))
}
else {
Ok(Box::new(None.into_iter()))
}
2021-12-10 13:24:59 -07:00
})?
.unwrap_or_else(|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 {})))))
}))
2020-12-12 19:55:27 -07:00
}
pub async fn items_to_trade<EG>(id: ClientId,
items_to_trade_pkt: &ItemsToTrade,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
2021-12-10 13:24:59 -07:00
item_manager: &mut ItemManager,
trades: &mut TradeState)
2020-12-12 19:55:27 -07:00
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
{
2021-12-10 13:24:59 -07:00
let t = inner_items_to_trade(id, items_to_trade_pkt, entity_gateway, client_location, clients, item_manager, trades).await;
2020-12-12 19:55:27 -07:00
match t {
Ok(p) => Ok(p),
Err(err) => {
2021-12-10 13:24:59 -07:00
log::warn!("atrade error: {:?}", err);
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 {}))
})
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))))
2020-12-12 19:55:27 -07:00
}
}
}
pub async fn trade_confirmed<EG>(id: ClientId,
entity_gateway: &mut EG,
client_location: &ClientLocation,
clients: &mut Clients,
2021-12-10 13:24:59 -07:00
item_manager: &mut ItemManager,
trades: &mut TradeState)
2020-12-12 19:55:27 -07:00
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, anyhow::Error>
where
EG: EntityGateway
{
2021-12-10 13:24:59 -07:00
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)),
2020-12-12 19:55:27 -07:00
}
2021-12-10 13:24:59 -07:00
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())
}
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,
2021-12-12 22:55:08 -07:00
(&this_local_client, &this_client.character, &this.items, this.meseta),
(&other_local_client, &other_client.character, &other.items, other.meseta)).await?;
2021-12-10 13:24:59 -07:00
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.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) => {
[
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))
]
},
2021-12-12 22:55:08 -07:00
ItemToTradeDetail::Meseta(amount) => {
[
GameMessage::CreateItem(builder::message::create_meseta(item.add_to, amount)),
GameMessage::PlayerNoLongerHasItem(builder::message::player_no_longer_has_item(item.remove_from, item.current_item_id, amount as u32))
]
},
2021-12-10 13:24:59 -07:00
}
})
.flatten()
.map(move |packet| {
clients_in_room
.clone()
.into_iter()
.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();
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)))
2021-10-15 12:17:37 -06:00
}
2021-12-10 13:24:59 -07:00
}
},
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))
2021-10-15 12:17:37 -06:00
.map(move |client| {
2021-12-10 13:24:59 -07:00
(client.client, SendShipPacket::CancelTrade(CancelTrade {}))
2021-10-15 12:17:37 -06:00
})
2021-12-10 13:24:59 -07:00
.chain(std::iter::once((id, SendShipPacket::CancelTrade(CancelTrade {}))))))
}
2020-12-12 19:55:27 -07:00
}
}