diff --git a/client/src/lib.rs b/client/src/lib.rs index e7c220d030..d92d20615d 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -32,6 +32,7 @@ use common::{ recipe::RecipeBook, span, terrain::{block::Block, neighbors, BiomeKind, SitesKind, TerrainChunk, TerrainChunkSize}, + trade::{PendingTrade, TradeActionMsg}, uid::{Uid, UidAllocator}, vol::RectVolSize, }; @@ -141,6 +142,8 @@ pub struct Client { group_members: HashMap, // Pending invites that this client has sent out pending_invites: HashSet, + // The pending trade the client is involved in, and it's id + pending_trade: Option<(usize, PendingTrade)>, _network: Network, participant: Option, @@ -429,6 +432,7 @@ impl Client { group_leader: None, group_members: HashMap::new(), pending_invites: HashSet::new(), + pending_trade: None, _network: network, participant: Some(participant), @@ -642,6 +646,15 @@ impl Client { } } + pub fn decline_trade(&mut self) { + if let Some((id, _)) = self.pending_trade.take() { + self.send_msg(ClientGeneral::UpdatePendingTrade( + id, + TradeActionMsg::Decline, + )); + } + } + pub fn is_dead(&self) -> bool { self.state .ecs() @@ -766,6 +779,8 @@ impl Client { pub fn pending_invites(&self) -> &HashSet { &self.pending_invites } + pub fn pending_trade(&self) -> &Option<(usize, PendingTrade)> { &self.pending_trade } + pub fn send_group_invite(&mut self, invitee: Uid) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( GroupManip::Invite(invitee), @@ -1559,6 +1574,13 @@ impl Client { impulse, }); }, + ServerGeneral::UpdatePendingTrade(id, trade) => { + tracing::info!("UpdatePendingTrade {:?} {:?}", id, trade); + self.pending_trade = Some((id, trade)); + }, + ServerGeneral::DeclinedTrade => { + self.pending_trade = None; + }, _ => unreachable!("Not a in_game message"), } Ok(()) diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index 5e3fbd4459..2b4bd02d52 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -7,8 +7,8 @@ use common::{ outcome::Outcome, recipe::RecipeBook, resources::TimeOfDay, - trade::PendingTrade, terrain::{Block, TerrainChunk}, + trade::PendingTrade, uid::Uid, }; use hashbrown::HashMap; @@ -124,6 +124,7 @@ pub enum ServerGeneral { /// Send a popup notification such as "Waypoint Saved" Notification(Notification), UpdatePendingTrade(usize, PendingTrade), + DeclinedTrade, } impl ServerGeneral { @@ -230,7 +231,8 @@ impl ServerMsg { | ServerGeneral::SetViewDistance(_) | ServerGeneral::Outcomes(_) | ServerGeneral::Knockback(_) - | ServerGeneral::UpdatePendingTrade(_, _) => { + | ServerGeneral::UpdatePendingTrade(_, _) + | ServerGeneral::DeclinedTrade => { c_type == ClientType::Game && presence.is_some() }, // Always possible diff --git a/common/src/event.rs b/common/src/event.rs index 30db1e8726..99729be1e3 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -1,4 +1,7 @@ -use crate::{character::CharacterId, comp, rtsim::RtSimEntity, uid::Uid, util::Dir, Explosion}; +use crate::{ + character::CharacterId, comp, rtsim::RtSimEntity, trade::TradeActionMsg, uid::Uid, util::Dir, + Explosion, +}; use comp::{ item::{Item, Reagent}, Ori, Pos, @@ -80,6 +83,7 @@ pub enum ServerEvent { DisableLantern(EcsEntity), NpcInteract(EcsEntity, EcsEntity), InitiateTrade(EcsEntity, EcsEntity), + ProcessTradeAction(EcsEntity, usize, TradeActionMsg), Mount(EcsEntity, EcsEntity), Unmount(EcsEntity), Possess(Uid, Uid), diff --git a/common/src/trade.rs b/common/src/trade.rs index affb25add6..7f92479e10 100644 --- a/common/src/trade.rs +++ b/common/src/trade.rs @@ -1,4 +1,7 @@ -use crate::{comp::inventory::slot::InvSlotId, uid::Uid}; +use crate::{ + comp::inventory::slot::InvSlotId, + uid::Uid, +}; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use tracing::warn; @@ -12,6 +15,7 @@ pub enum TradeActionMsg { RemoveItem { item: InvSlotId, quantity: usize }, Phase1Accept, Phase2Accept, + Decline, } /// Items are not removed from the inventory during a PendingTrade: all the @@ -45,11 +49,15 @@ impl PendingTrade { pub fn in_phase1(&self) -> bool { !self.phase1_accepts[0] || !self.phase1_accepts[1] } pub fn in_phase2(&self) -> bool { - (self.phase1_accepts[0] && self.phase1_accepts[1]) && (!self.phase2_accepts[0] || !self.phase2_accepts[1]) + (self.phase1_accepts[0] && self.phase1_accepts[1]) + && (!self.phase2_accepts[0] || !self.phase2_accepts[1]) } pub fn should_commit(&self) -> bool { - self.phase1_accepts[0] && self.phase1_accepts[1] && self.phase2_accepts[0] && self.phase2_accepts[1] + self.phase1_accepts[0] + && self.phase1_accepts[1] + && self.phase2_accepts[0] + && self.phase2_accepts[1] } pub fn which_party(&self, party: Uid) -> Option { @@ -85,6 +93,7 @@ impl PendingTrade { self.phase2_accepts[who] = true; } }, + Decline => {}, } } } @@ -108,23 +117,37 @@ impl Trades { if let Some(party) = trade.which_party(who) { trade.process_msg(party, msg); } else { - warn!("An entity who is not a party to trade {} tried to modify it", id); + warn!( + "An entity who is not a party to trade {} tried to modify it", + id + ); } } else { warn!("Attempt to modify nonexistent trade id {}", id); } } - pub fn decline_trade(&mut self, id: usize, who: Uid) { + pub fn decline_trade(&mut self, id: usize, who: Uid) -> Option { + let mut to_notify = None; if let Some(trade) = self.trades.remove(&id) { - if let None = trade.which_party(who) { - warn!("An entity who is not a party to trade {} tried to decline it", id); - // put it back - self.trades.insert(id, trade); + match trade.which_party(who) { + Some(i) => { + // let the other person know the trade was declined + to_notify = Some(trade.parties[1 - i]) + }, + None => { + warn!( + "An entity who is not a party to trade {} tried to decline it", + id + ); + // put it back + self.trades.insert(id, trade); + }, } } else { warn!("Attempt to decline nonexistent trade id {}", id); } + to_notify } } diff --git a/server/src/client.rs b/server/src/client.rs index 9fcd913240..bf2fc504b5 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -89,7 +89,8 @@ impl Client { | ServerGeneral::SetViewDistance(_) | ServerGeneral::Outcomes(_) | ServerGeneral::Knockback(_) - | ServerGeneral::UpdatePendingTrade(_, _) => { + | ServerGeneral::UpdatePendingTrade(_, _) + | ServerGeneral::DeclinedTrade => { self.in_game_stream.try_lock().unwrap().send(g) }, // Always possible @@ -168,7 +169,8 @@ impl Client { | ServerGeneral::SetViewDistance(_) | ServerGeneral::Outcomes(_) | ServerGeneral::Knockback(_) - | ServerGeneral::UpdatePendingTrade(_, _) => { + | ServerGeneral::UpdatePendingTrade(_, _) + | ServerGeneral::DeclinedTrade => { PreparedMsg::new(2, &g, &self.in_game_stream) }, // Always possible diff --git a/server/src/events/group_manip.rs b/server/src/events/group_manip.rs index 2efca715d4..d7ff25792c 100644 --- a/server/src/events/group_manip.rs +++ b/server/src/events/group_manip.rs @@ -240,12 +240,18 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani ); }, InviteKind::Trade => { - if let (Some(inviter_uid), Some(invitee_uid)) = (uids.get(inviter).copied(), uids.get(entity).copied()) { + if let (Some(inviter_uid), Some(invitee_uid)) = + (uids.get(inviter).copied(), uids.get(entity).copied()) + { let mut trades = state.ecs().write_resource::(); let id = trades.begin_trade(inviter_uid, invitee_uid); let trade = trades.trades[&id].clone(); - clients.get(inviter).map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade.clone()))); - clients.get(entity).map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade))); + clients.get(inviter).map(|c| { + c.send(ServerGeneral::UpdatePendingTrade(id, trade.clone())) + }); + clients + .get(entity) + .map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade))); } }, } diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index db60f73e62..b134b25324 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -72,14 +72,6 @@ pub fn handle_npc_interaction(server: &mut Server, interactor: EcsEntity, npc_en } } -pub fn handle_initiate_trade(server: &mut Server, interactor: EcsEntity, counterparty: EcsEntity) { - if let Some(uid) = server.state_mut().ecs().uid_from_entity(counterparty) { - handle_invite(server, interactor, uid, InviteKind::Trade); - } else { - warn!("Entity tried to trade with an entity that lacks an uid"); - } -} - pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) { let state = server.state_mut(); diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index fc83542ea0..b965a06ad3 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -1,6 +1,7 @@ use crate::{state_ext::StateExt, Server}; use common::{ event::{EventBus, ServerEvent}, + trade::{Trades, TradeActionMsg}, span, }; use entity_creation::{ @@ -13,12 +14,13 @@ use entity_manipulation::{ }; use group_manip::handle_group; use interaction::{ - handle_initiate_trade, handle_lantern, handle_mount, handle_npc_interaction, handle_possess, + handle_lantern, handle_mount, handle_npc_interaction, handle_possess, handle_unmount, }; use inventory_manip::handle_inventory; use player::{handle_client_disconnect, handle_exit_ingame}; use specs::{Entity as EcsEntity, WorldExt}; +use trade::{handle_initiate_trade, handle_process_trade_action}; mod entity_creation; mod entity_manipulation; @@ -26,6 +28,7 @@ mod group_manip; mod interaction; mod inventory_manip; mod player; +mod trade; pub enum Event { ClientConnected { @@ -107,6 +110,9 @@ impl Server { ServerEvent::InitiateTrade(interactor, target) => { handle_initiate_trade(self, interactor, target) }, + ServerEvent::ProcessTradeAction(entity, trade_id, msg) => { + handle_process_trade_action(self, entity, trade_id, msg); + }, ServerEvent::Mount(mounter, mountee) => handle_mount(self, mounter, mountee), ServerEvent::Unmount(mounter) => handle_unmount(self, mounter), ServerEvent::Possess(possessor_uid, possesse_uid) => { diff --git a/server/src/events/trade.rs b/server/src/events/trade.rs new file mode 100644 index 0000000000..ee2b135f85 --- /dev/null +++ b/server/src/events/trade.rs @@ -0,0 +1,43 @@ +use crate::{ + Server, + comp::inventory::slot::InvSlotId, + events::group_manip::handle_invite, +}; +use common::{ + comp::{ + group::InviteKind, + }, + trade::{Trades, TradeActionMsg, PendingTrade}, + uid::Uid, +}; +use common_net::{msg::ServerGeneral, sync::WorldSyncExt}; +use specs::{world::WorldExt, Entity as EcsEntity}; +use tracing::warn; + + +pub fn handle_initiate_trade(server: &mut Server, interactor: EcsEntity, counterparty: EcsEntity) { + if let Some(uid) = server.state_mut().ecs().uid_from_entity(counterparty) { + handle_invite(server, interactor, uid, InviteKind::Trade); + } else { + warn!("Entity tried to trade with an entity that lacks an uid"); + } +} + +pub fn handle_process_trade_action(server: &mut Server, entity: EcsEntity, trade_id: usize, msg: TradeActionMsg) { + if let Some(uid) = server.state.ecs().uid_from_entity(entity) { + let mut trades = server.state.ecs().write_resource::(); + if let TradeActionMsg::Decline = msg { + let to_notify = trades.decline_trade(trade_id, uid); + to_notify + .and_then(|u| server.state.ecs().entity_from_uid(u.0)) + .map(|e| server.notify_client(e, ServerGeneral::DeclinedTrade)); + } else { + trades.process_trade_action(trade_id, uid, msg); + if let Some(trade) = trades.trades.get(&trade_id) { + if trade.should_commit() { + // TODO: inventory manip + } + } + } + } +} diff --git a/server/src/sys/msg/in_game.rs b/server/src/sys/msg/in_game.rs index 1d90ff70a5..cc6f75dc28 100644 --- a/server/src/sys/msg/in_game.rs +++ b/server/src/sys/msg/in_game.rs @@ -158,6 +158,10 @@ impl Sys { .get_mut(entity) .map(|mut s| s.skill_set.unlock_skill_group(skill_group_kind)); }, + ClientGeneral::UpdatePendingTrade(trade_id, msg) => { + tracing::info!("UpdatePendingTrade {:?} {:?}", trade_id, msg); + server_emitter.emit(ServerEvent::ProcessTradeAction(entity, trade_id, msg)); + }, _ => unreachable!("not a client_in_game msg"), } Ok(()) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index f518b28b3d..11a8c1dc74 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -395,6 +395,7 @@ pub enum Event { bypass_dialog: bool, }, DropSlot(comp::slot::Slot), + DeclineTrade, ChangeHotbarState(Box), Ability3(bool), Logout, @@ -874,6 +875,11 @@ impl Hud { let inventories = ecs.read_storage::(); let entities = ecs.entities(); let me = client.entity(); + + if (client.pending_trade().is_some() && !self.show.trade) || (client.pending_trade().is_none() && self.show.trade) { + self.show.toggle_trade(); + } + //self.input = client.read_storage::(); if let Some(health) = healths.get(me) { // Hurt Frame @@ -2125,6 +2131,7 @@ impl Hud { Some(trade::Event::Close) => { self.show.stats = false; self.show.trade(false); + events.push(Event::DeclineTrade); if !self.show.social { self.show.want_grab = true; self.force_ungrab = false; diff --git a/voxygen/src/hud/trade.rs b/voxygen/src/hud/trade.rs index b703c6cf40..3cd697eeb3 100644 --- a/voxygen/src/hud/trade.rs +++ b/voxygen/src/hud/trade.rs @@ -149,6 +149,17 @@ impl<'a> Widget for Trade<'a> { .font_size(self.fonts.cyri.scale(20)) .color(TEXT_COLOR) .set(state.ids.trade_title, ui); + // Close button + if Button::image(self.imgs.close_btn) + .w_h(24.0, 25.0) + .hover_image(self.imgs.close_btn_hover) + .press_image(self.imgs.close_btn_press) + .top_right_with_margins_on(state.ids.bg, 0.0, 0.0) + .set(state.ids.trade_close, ui) + .was_clicked() + { + event = Some(Event::Close); + } event } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 5b31fcf5e8..b10a7f1d1d 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -1117,6 +1117,10 @@ impl PlayState for SessionState { client.disable_lantern(); } }, + HudEvent::DeclineTrade => { + let mut client = self.client.borrow_mut(); + client.decline_trade(); + }, HudEvent::ChangeHotbarState(state) => { let client = self.client.borrow();