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,
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<Uid, group::Role>,
// Pending invites that this client has sent out
pending_invites: HashSet<Uid>,
// The pending trade the client is involved in, and it's id
pending_trade: Option<(usize, PendingTrade)>,
_network: Network,
participant: Option<Participant>,
@ -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<Uid> { &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(())

View File

@ -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

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::{
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),

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 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<usize> {
@ -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<Uid> {
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
}
}

View File

@ -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

View File

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

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) {
let state = server.state_mut();

View File

@ -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) => {

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)
.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(())

View File

@ -395,6 +395,7 @@ pub enum Event {
bypass_dialog: bool,
},
DropSlot(comp::slot::Slot),
DeclineTrade,
ChangeHotbarState(Box<HotbarState>),
Ability3(bool),
Logout,
@ -874,6 +875,11 @@ impl Hud {
let inventories = ecs.read_storage::<comp::Inventory>();
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::<comp::ControllerInputs>();
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;

View File

@ -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
}

View File

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