Trade implementation progress.

- Server messages now bring up the trade window.
- When a trade is declined, it closes the window on both clients.
This commit is contained in:
Avi Weinstock 2021-02-11 14:35:36 -05:00
parent ae528124fc
commit aeb2398fc6
13 changed files with 152 additions and 26 deletions

View File

@ -32,6 +32,7 @@ use common::{
recipe::RecipeBook, recipe::RecipeBook,
span, span,
terrain::{block::Block, neighbors, BiomeKind, SitesKind, TerrainChunk, TerrainChunkSize}, terrain::{block::Block, neighbors, BiomeKind, SitesKind, TerrainChunk, TerrainChunkSize},
trade::{PendingTrade, TradeActionMsg},
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
vol::RectVolSize, vol::RectVolSize,
}; };
@ -141,6 +142,8 @@ pub struct Client {
group_members: HashMap<Uid, group::Role>, group_members: HashMap<Uid, group::Role>,
// Pending invites that this client has sent out // Pending invites that this client has sent out
pending_invites: HashSet<Uid>, pending_invites: HashSet<Uid>,
// The pending trade the client is involved in, and it's id
pending_trade: Option<(usize, PendingTrade)>,
_network: Network, _network: Network,
participant: Option<Participant>, participant: Option<Participant>,
@ -429,6 +432,7 @@ impl Client {
group_leader: None, group_leader: None,
group_members: HashMap::new(), group_members: HashMap::new(),
pending_invites: HashSet::new(), pending_invites: HashSet::new(),
pending_trade: None,
_network: network, _network: network,
participant: Some(participant), 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 { pub fn is_dead(&self) -> bool {
self.state self.state
.ecs() .ecs()
@ -766,6 +779,8 @@ impl Client {
pub fn pending_invites(&self) -> &HashSet<Uid> { &self.pending_invites } pub fn pending_invites(&self) -> &HashSet<Uid> { &self.pending_invites }
pub fn pending_trade(&self) -> &Option<(usize, PendingTrade)> { &self.pending_trade }
pub fn send_group_invite(&mut self, invitee: Uid) { pub fn send_group_invite(&mut self, invitee: Uid) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip(
GroupManip::Invite(invitee), GroupManip::Invite(invitee),
@ -1559,6 +1574,13 @@ impl Client {
impulse, 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"), _ => unreachable!("Not a in_game message"),
} }
Ok(()) Ok(())

View File

@ -7,8 +7,8 @@ use common::{
outcome::Outcome, outcome::Outcome,
recipe::RecipeBook, recipe::RecipeBook,
resources::TimeOfDay, resources::TimeOfDay,
trade::PendingTrade,
terrain::{Block, TerrainChunk}, terrain::{Block, TerrainChunk},
trade::PendingTrade,
uid::Uid, uid::Uid,
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
@ -124,6 +124,7 @@ pub enum ServerGeneral {
/// Send a popup notification such as "Waypoint Saved" /// Send a popup notification such as "Waypoint Saved"
Notification(Notification), Notification(Notification),
UpdatePendingTrade(usize, PendingTrade), UpdatePendingTrade(usize, PendingTrade),
DeclinedTrade,
} }
impl ServerGeneral { impl ServerGeneral {
@ -230,7 +231,8 @@ impl ServerMsg {
| ServerGeneral::SetViewDistance(_) | ServerGeneral::SetViewDistance(_)
| ServerGeneral::Outcomes(_) | ServerGeneral::Outcomes(_)
| ServerGeneral::Knockback(_) | ServerGeneral::Knockback(_)
| ServerGeneral::UpdatePendingTrade(_, _) => { | ServerGeneral::UpdatePendingTrade(_, _)
| ServerGeneral::DeclinedTrade => {
c_type == ClientType::Game && presence.is_some() c_type == ClientType::Game && presence.is_some()
}, },
// Always possible // Always possible

View File

@ -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::{ use comp::{
item::{Item, Reagent}, item::{Item, Reagent},
Ori, Pos, Ori, Pos,
@ -80,6 +83,7 @@ pub enum ServerEvent {
DisableLantern(EcsEntity), DisableLantern(EcsEntity),
NpcInteract(EcsEntity, EcsEntity), NpcInteract(EcsEntity, EcsEntity),
InitiateTrade(EcsEntity, EcsEntity), InitiateTrade(EcsEntity, EcsEntity),
ProcessTradeAction(EcsEntity, usize, TradeActionMsg),
Mount(EcsEntity, EcsEntity), Mount(EcsEntity, EcsEntity),
Unmount(EcsEntity), Unmount(EcsEntity),
Possess(Uid, Uid), Possess(Uid, Uid),

View File

@ -1,4 +1,7 @@
use crate::{comp::inventory::slot::InvSlotId, uid::Uid}; use crate::{
comp::inventory::slot::InvSlotId,
uid::Uid,
};
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::warn; use tracing::warn;
@ -12,6 +15,7 @@ pub enum TradeActionMsg {
RemoveItem { item: InvSlotId, quantity: usize }, RemoveItem { item: InvSlotId, quantity: usize },
Phase1Accept, Phase1Accept,
Phase2Accept, Phase2Accept,
Decline,
} }
/// Items are not removed from the inventory during a PendingTrade: all the /// 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_phase1(&self) -> bool { !self.phase1_accepts[0] || !self.phase1_accepts[1] }
pub fn in_phase2(&self) -> bool { 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 { 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<usize> { pub fn which_party(&self, party: Uid) -> Option<usize> {
@ -85,6 +93,7 @@ impl PendingTrade {
self.phase2_accepts[who] = true; self.phase2_accepts[who] = true;
} }
}, },
Decline => {},
} }
} }
} }
@ -108,23 +117,37 @@ impl Trades {
if let Some(party) = trade.which_party(who) { if let Some(party) = trade.which_party(who) {
trade.process_msg(party, msg); trade.process_msg(party, msg);
} else { } 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 { } else {
warn!("Attempt to modify nonexistent trade id {}", id); 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<Uid> {
let mut to_notify = None;
if let Some(trade) = self.trades.remove(&id) { if let Some(trade) = self.trades.remove(&id) {
if let None = trade.which_party(who) { match trade.which_party(who) {
warn!("An entity who is not a party to trade {} tried to decline it", id); 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 // put it back
self.trades.insert(id, trade); self.trades.insert(id, trade);
},
} }
} else { } else {
warn!("Attempt to decline nonexistent trade id {}", id); warn!("Attempt to decline nonexistent trade id {}", id);
} }
to_notify
} }
} }

View File

@ -89,7 +89,8 @@ impl Client {
| ServerGeneral::SetViewDistance(_) | ServerGeneral::SetViewDistance(_)
| ServerGeneral::Outcomes(_) | ServerGeneral::Outcomes(_)
| ServerGeneral::Knockback(_) | ServerGeneral::Knockback(_)
| ServerGeneral::UpdatePendingTrade(_, _) => { | ServerGeneral::UpdatePendingTrade(_, _)
| ServerGeneral::DeclinedTrade => {
self.in_game_stream.try_lock().unwrap().send(g) self.in_game_stream.try_lock().unwrap().send(g)
}, },
// Always possible // Always possible
@ -168,7 +169,8 @@ impl Client {
| ServerGeneral::SetViewDistance(_) | ServerGeneral::SetViewDistance(_)
| ServerGeneral::Outcomes(_) | ServerGeneral::Outcomes(_)
| ServerGeneral::Knockback(_) | ServerGeneral::Knockback(_)
| ServerGeneral::UpdatePendingTrade(_, _) => { | ServerGeneral::UpdatePendingTrade(_, _)
| ServerGeneral::DeclinedTrade => {
PreparedMsg::new(2, &g, &self.in_game_stream) PreparedMsg::new(2, &g, &self.in_game_stream)
}, },
// Always possible // Always possible

View File

@ -240,12 +240,18 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
); );
}, },
InviteKind::Trade => { 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::<Trades>(); let mut trades = state.ecs().write_resource::<Trades>();
let id = trades.begin_trade(inviter_uid, invitee_uid); let id = trades.begin_trade(inviter_uid, invitee_uid);
let trade = trades.trades[&id].clone(); let trade = trades.trades[&id].clone();
clients.get(inviter).map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade.clone()))); clients.get(inviter).map(|c| {
clients.get(entity).map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade))); c.send(ServerGeneral::UpdatePendingTrade(id, trade.clone()))
});
clients
.get(entity)
.map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade)));
} }
}, },
} }

View File

@ -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) { pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) {
let state = server.state_mut(); let state = server.state_mut();

View File

@ -1,6 +1,7 @@
use crate::{state_ext::StateExt, Server}; use crate::{state_ext::StateExt, Server};
use common::{ use common::{
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
trade::{Trades, TradeActionMsg},
span, span,
}; };
use entity_creation::{ use entity_creation::{
@ -13,12 +14,13 @@ use entity_manipulation::{
}; };
use group_manip::handle_group; use group_manip::handle_group;
use interaction::{ 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, handle_unmount,
}; };
use inventory_manip::handle_inventory; use inventory_manip::handle_inventory;
use player::{handle_client_disconnect, handle_exit_ingame}; use player::{handle_client_disconnect, handle_exit_ingame};
use specs::{Entity as EcsEntity, WorldExt}; use specs::{Entity as EcsEntity, WorldExt};
use trade::{handle_initiate_trade, handle_process_trade_action};
mod entity_creation; mod entity_creation;
mod entity_manipulation; mod entity_manipulation;
@ -26,6 +28,7 @@ mod group_manip;
mod interaction; mod interaction;
mod inventory_manip; mod inventory_manip;
mod player; mod player;
mod trade;
pub enum Event { pub enum Event {
ClientConnected { ClientConnected {
@ -107,6 +110,9 @@ impl Server {
ServerEvent::InitiateTrade(interactor, target) => { ServerEvent::InitiateTrade(interactor, target) => {
handle_initiate_trade(self, 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::Mount(mounter, mountee) => handle_mount(self, mounter, mountee),
ServerEvent::Unmount(mounter) => handle_unmount(self, mounter), ServerEvent::Unmount(mounter) => handle_unmount(self, mounter),
ServerEvent::Possess(possessor_uid, possesse_uid) => { ServerEvent::Possess(possessor_uid, possesse_uid) => {

View File

@ -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::<Trades>();
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
}
}
}
}
}

View File

@ -158,6 +158,10 @@ impl Sys {
.get_mut(entity) .get_mut(entity)
.map(|mut s| s.skill_set.unlock_skill_group(skill_group_kind)); .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"), _ => unreachable!("not a client_in_game msg"),
} }
Ok(()) Ok(())

View File

@ -395,6 +395,7 @@ pub enum Event {
bypass_dialog: bool, bypass_dialog: bool,
}, },
DropSlot(comp::slot::Slot), DropSlot(comp::slot::Slot),
DeclineTrade,
ChangeHotbarState(Box<HotbarState>), ChangeHotbarState(Box<HotbarState>),
Ability3(bool), Ability3(bool),
Logout, Logout,
@ -874,6 +875,11 @@ impl Hud {
let inventories = ecs.read_storage::<comp::Inventory>(); let inventories = ecs.read_storage::<comp::Inventory>();
let entities = ecs.entities(); let entities = ecs.entities();
let me = client.entity(); 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::<comp::ControllerInputs>(); //self.input = client.read_storage::<comp::ControllerInputs>();
if let Some(health) = healths.get(me) { if let Some(health) = healths.get(me) {
// Hurt Frame // Hurt Frame
@ -2125,6 +2131,7 @@ impl Hud {
Some(trade::Event::Close) => { Some(trade::Event::Close) => {
self.show.stats = false; self.show.stats = false;
self.show.trade(false); self.show.trade(false);
events.push(Event::DeclineTrade);
if !self.show.social { if !self.show.social {
self.show.want_grab = true; self.show.want_grab = true;
self.force_ungrab = false; self.force_ungrab = false;

View File

@ -149,6 +149,17 @@ impl<'a> Widget for Trade<'a> {
.font_size(self.fonts.cyri.scale(20)) .font_size(self.fonts.cyri.scale(20))
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.trade_title, ui); .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 event
} }

View File

@ -1117,6 +1117,10 @@ impl PlayState for SessionState {
client.disable_lantern(); client.disable_lantern();
} }
}, },
HudEvent::DeclineTrade => {
let mut client = self.client.borrow_mut();
client.decline_trade();
},
HudEvent::ChangeHotbarState(state) => { HudEvent::ChangeHotbarState(state) => {
let client = self.client.borrow(); let client = self.client.borrow();