MR 1775 review fixes.

- Separate `invite` machinery from `group_manip` into it's own thing (includes renaming `group_invite` to `invite` where applicable).
- Move some invite/trade machinery to `ControlEvent`.
- Make `TradePhase` a proper enum instead of a bunch of bools.
- Make `TradeId` a proper newtype.
- Remove trades from `Trades` on accept (previously was only on decline).
- Typo fixes/misc cleanup.
- Add bullet point for trading to the changelog.
This commit is contained in:
Avi Weinstock 2021-02-13 18:32:55 -05:00
parent 232ddb0860
commit c984035976
26 changed files with 592 additions and 535 deletions

View File

@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Snow particles - Snow particles
- Basic NPC interaction - Basic NPC interaction
- Lights in dungeons - Lights in dungeons
- Trading system (bound to the `R` key by default, currently only works with players)
### Changed ### Changed

View File

@ -20,7 +20,8 @@ use common::{
comp::{ comp::{
self, self,
chat::{KillSource, KillType}, chat::{KillSource, KillType},
group::{self, InviteKind}, group,
invite::{InviteKind, InviteResponse},
skills::Skill, skills::Skill,
slot::Slot, slot::Slot,
ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip,
@ -32,7 +33,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, TradeResult}, trade::{PendingTrade, TradeAction, TradeId, TradeResult},
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
vol::RectVolSize, vol::RectVolSize,
}; };
@ -140,14 +141,14 @@ pub struct Client {
max_group_size: u32, max_group_size: u32,
// Client has received an invite (inviter uid, time out instant) // Client has received an invite (inviter uid, time out instant)
group_invite: Option<(Uid, std::time::Instant, std::time::Duration, InviteKind)>, invite: Option<(Uid, std::time::Instant, std::time::Duration, InviteKind)>,
group_leader: Option<Uid>, group_leader: Option<Uid>,
// Note: potentially representable as a client only component // Note: potentially representable as a client only component
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 // The pending trade the client is involved in, and it's id
pending_trade: Option<(usize, PendingTrade)>, pending_trade: Option<(TradeId, PendingTrade)>,
_network: Network, _network: Network,
participant: Option<Participant>, participant: Option<Participant>,
@ -432,7 +433,7 @@ impl Client {
chat_mode: ChatMode::default(), chat_mode: ChatMode::default(),
max_group_size, max_group_size,
group_invite: None, invite: None,
group_leader: None, group_leader: None,
group_members: HashMap::new(), group_members: HashMap::new(),
pending_invites: HashSet::new(), pending_invites: HashSet::new(),
@ -546,8 +547,7 @@ impl Client {
| ClientGeneral::TerrainChunkRequest { .. } | ClientGeneral::TerrainChunkRequest { .. }
| ClientGeneral::UnlockSkill(_) | ClientGeneral::UnlockSkill(_)
| ClientGeneral::RefundSkill(_) | ClientGeneral::RefundSkill(_)
| ClientGeneral::UnlockSkillGroup(_) | ClientGeneral::UnlockSkillGroup(_) => &mut self.in_game_stream,
| ClientGeneral::UpdatePendingTrade(_, _) => &mut self.in_game_stream,
//Always possible //Always possible
ClientGeneral::ChatMsg(_) | ClientGeneral::Terminate => { ClientGeneral::ChatMsg(_) | ClientGeneral::Terminate => {
&mut self.general_stream &mut self.general_stream
@ -650,12 +650,14 @@ impl Client {
} }
} }
pub fn trade_action(&mut self, msg: TradeActionMsg) { pub fn perform_trade_action(&mut self, action: TradeAction) {
if let Some((id, _)) = self.pending_trade { if let Some((id, _)) = self.pending_trade {
if let TradeActionMsg::Decline = msg { if let TradeAction::Decline = action {
self.pending_trade.take(); self.pending_trade.take();
} }
self.send_msg(ClientGeneral::UpdatePendingTrade(id, msg)); self.send_msg(ClientGeneral::ControlEvent(
ControlEvent::PerformTradeAction(id, action),
));
} }
} }
@ -687,19 +689,6 @@ impl Client {
} }
} }
pub fn initiate_trade(&mut self, counterparty: EcsEntity) {
// If we're dead, exit before sending message
if self.is_dead() {
return;
}
if let Some(uid) = self.state.read_component_copied(counterparty) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InitiateTrade(
uid,
)));
}
}
pub fn player_list(&self) -> &HashMap<Uid, PlayerInfo> { &self.player_list } pub fn player_list(&self) -> &HashMap<Uid, PlayerInfo> { &self.player_list }
pub fn character_list(&self) -> &CharacterList { &self.character_list } pub fn character_list(&self) -> &CharacterList { &self.character_list }
@ -763,10 +752,8 @@ impl Client {
pub fn max_group_size(&self) -> u32 { self.max_group_size } pub fn max_group_size(&self) -> u32 { self.max_group_size }
pub fn group_invite( pub fn invite(&self) -> Option<(Uid, std::time::Instant, std::time::Duration, InviteKind)> {
&self, self.invite
) -> Option<(Uid, std::time::Instant, std::time::Duration, InviteKind)> {
self.group_invite
} }
pub fn group_info(&self) -> Option<(String, Uid)> { pub fn group_info(&self) -> Option<(String, Uid)> {
@ -777,27 +764,27 @@ 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 pending_trade(&self) -> &Option<(TradeId, PendingTrade)> { &self.pending_trade }
pub fn send_group_invite(&mut self, invitee: Uid) { pub fn send_invite(&mut self, invitee: Uid, kind: InviteKind) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InitiateInvite(
GroupManip::Invite(invitee), invitee, kind,
))) )))
} }
pub fn accept_group_invite(&mut self) { pub fn accept_invite(&mut self) {
// Clear invite // Clear invite
self.group_invite.take(); self.invite.take();
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InviteResponse(
GroupManip::Accept, InviteResponse::Accept,
))); )));
} }
pub fn decline_group_invite(&mut self) { pub fn decline_invite(&mut self) {
// Clear invite // Clear invite
self.group_invite.take(); self.invite.take();
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::GroupManip( self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InviteResponse(
GroupManip::Decline, InviteResponse::Decline,
))); )));
} }
@ -1121,12 +1108,12 @@ impl Client {
frontend_events.append(&mut self.handle_new_messages()?); frontend_events.append(&mut self.handle_new_messages()?);
// 3) Update client local data // 3) Update client local data
// Check if the group invite has timed out and remove if so // Check if the invite has timed out and remove if so
if self if self
.group_invite .invite
.map_or(false, |(_, timeout, dur, _)| timeout.elapsed() > dur) .map_or(false, |(_, timeout, dur, _)| timeout.elapsed() > dur)
{ {
self.group_invite = None; self.invite = None;
} }
// 4) Tick the client's LocalState // 4) Tick the client's LocalState
@ -1503,7 +1490,7 @@ impl Client {
timeout, timeout,
kind, kind,
} => { } => {
self.group_invite = Some((inviter, std::time::Instant::now(), timeout, kind)); self.invite = Some((inviter, std::time::Instant::now(), timeout, kind));
}, },
ServerGeneral::InvitePending(uid) => { ServerGeneral::InvitePending(uid) => {
if !self.pending_invites.insert(uid) { if !self.pending_invites.insert(uid) {
@ -1573,7 +1560,7 @@ impl Client {
}); });
}, },
ServerGeneral::UpdatePendingTrade(id, trade) => { ServerGeneral::UpdatePendingTrade(id, trade) => {
tracing::info!("UpdatePendingTrade {:?} {:?}", id, trade); tracing::trace!("UpdatePendingTrade {:?} {:?}", id, trade);
self.pending_trade = Some((id, trade)); self.pending_trade = Some((id, trade));
}, },
ServerGeneral::FinishedTrade(result) => { ServerGeneral::FinishedTrade(result) => {

View File

@ -4,7 +4,6 @@ use common::{
comp, comp,
comp::{Skill, SkillGroupKind}, comp::{Skill, SkillGroupKind},
terrain::block::Block, terrain::block::Block,
trade::TradeActionMsg,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vek::*; use vek::*;
@ -80,7 +79,6 @@ pub enum ClientGeneral {
//Always possible //Always possible
ChatMsg(String), ChatMsg(String),
Terminate, Terminate,
UpdatePendingTrade(usize, TradeActionMsg),
} }
impl ClientMsg { impl ClientMsg {
@ -116,8 +114,7 @@ impl ClientMsg {
| ClientGeneral::TerrainChunkRequest { .. } | ClientGeneral::TerrainChunkRequest { .. }
| ClientGeneral::UnlockSkill(_) | ClientGeneral::UnlockSkill(_)
| ClientGeneral::RefundSkill(_) | ClientGeneral::RefundSkill(_)
| ClientGeneral::UnlockSkillGroup(_) | ClientGeneral::UnlockSkillGroup(_) => {
| ClientGeneral::UpdatePendingTrade(_, _) => {
c_type == ClientType::Game && presence.is_some() c_type == ClientType::Game && presence.is_some()
}, },
//Always possible //Always possible

View File

@ -3,12 +3,12 @@ use crate::sync;
use authc::AuthClientError; use authc::AuthClientError;
use common::{ use common::{
character::{self, CharacterItem}, character::{self, CharacterItem},
comp::{self, group::InviteKind}, comp::{self, invite::InviteKind},
outcome::Outcome, outcome::Outcome,
recipe::RecipeBook, recipe::RecipeBook,
resources::TimeOfDay, resources::TimeOfDay,
terrain::{Block, TerrainChunk}, terrain::{Block, TerrainChunk},
trade::{PendingTrade, TradeResult}, trade::{PendingTrade, TradeId, TradeResult},
uid::Uid, uid::Uid,
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
@ -123,7 +123,7 @@ pub enum ServerGeneral {
Disconnect(DisconnectReason), Disconnect(DisconnectReason),
/// Send a popup notification such as "Waypoint Saved" /// Send a popup notification such as "Waypoint Saved"
Notification(Notification), Notification(Notification),
UpdatePendingTrade(usize, PendingTrade), UpdatePendingTrade(TradeId, PendingTrade),
FinishedTrade(TradeResult), FinishedTrade(TradeResult),
} }

View File

@ -1,8 +1,10 @@
use crate::{ use crate::{
comp::{ comp::{
inventory::slot::{EquipSlot, InvSlotId, Slot}, inventory::slot::{EquipSlot, InvSlotId, Slot},
invite::{InviteKind, InviteResponse},
BuffKind, BuffKind,
}, },
trade::{TradeAction, TradeId},
uid::Uid, uid::Uid,
util::Dir, util::Dir,
}; };
@ -69,9 +71,6 @@ impl From<InventoryManip> for SlotManip {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupManip { pub enum GroupManip {
Invite(Uid),
Accept,
Decline,
Leave, Leave,
Kick(Uid), Kick(Uid),
AssignLeader(Uid), AssignLeader(Uid),
@ -83,7 +82,9 @@ pub enum ControlEvent {
EnableLantern, EnableLantern,
DisableLantern, DisableLantern,
Interact(Uid), Interact(Uid),
InitiateTrade(Uid), InitiateInvite(Uid, InviteKind),
InviteResponse(InviteResponse),
PerformTradeAction(TradeId, TradeAction),
Mount(Uid), Mount(Uid),
Unmount, Unmount,
InventoryManip(InventoryManip), InventoryManip(InventoryManip),

View File

@ -28,28 +28,6 @@ impl Component for Group {
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>; type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum InviteKind {
Group,
Trade,
}
pub struct Invite {
pub inviter: specs::Entity,
pub kind: InviteKind,
}
impl Component for Invite {
type Storage = IdvStorage<Self>;
}
// Pending invites that an entity currently has sent out
// (invited entity, instant when invite times out)
pub struct PendingInvites(pub Vec<(specs::Entity, InviteKind, std::time::Instant)>);
impl Component for PendingInvites {
type Storage = IdvStorage<Self>;
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct GroupInfo { pub struct GroupInfo {
// TODO: what about enemy groups, either the leader will constantly change because they have to // TODO: what about enemy groups, either the leader will constantly change because they have to

31
common/src/comp/invite.rs Normal file
View File

@ -0,0 +1,31 @@
use serde::{Deserialize, Serialize};
use specs::Component;
use specs_idvs::IdvStorage;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum InviteKind {
Group,
Trade,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum InviteResponse {
Accept,
Decline,
}
pub struct Invite {
pub inviter: specs::Entity,
pub kind: InviteKind,
}
impl Component for Invite {
type Storage = IdvStorage<Self>;
}
/// Pending invites that an entity currently has sent out
/// (invited entity, instant when invite times out)
pub struct PendingInvites(pub Vec<(specs::Entity, InviteKind, std::time::Instant)>);
impl Component for PendingInvites {
type Storage = IdvStorage<Self>;
}

View File

@ -14,6 +14,7 @@ mod health;
pub mod home_chunk; pub mod home_chunk;
mod inputs; mod inputs;
pub mod inventory; pub mod inventory;
pub mod invite;
mod last; mod last;
mod location; mod location;
mod misc; mod misc;

View File

@ -1,8 +1,14 @@
use crate::{ use crate::{
character::CharacterId, comp, rtsim::RtSimEntity, trade::TradeActionMsg, uid::Uid, util::Dir, character::CharacterId,
comp,
rtsim::RtSimEntity,
trade::{TradeAction, TradeId},
uid::Uid,
util::Dir,
Explosion, Explosion,
}; };
use comp::{ use comp::{
invite::{InviteKind, InviteResponse},
item::{Item, Reagent}, item::{Item, Reagent},
Ori, Pos, Ori, Pos,
}; };
@ -82,8 +88,9 @@ pub enum ServerEvent {
EnableLantern(EcsEntity), EnableLantern(EcsEntity),
DisableLantern(EcsEntity), DisableLantern(EcsEntity),
NpcInteract(EcsEntity, EcsEntity), NpcInteract(EcsEntity, EcsEntity),
InitiateTrade(EcsEntity, EcsEntity), InviteResponse(EcsEntity, InviteResponse),
ProcessTradeAction(EcsEntity, usize, TradeActionMsg), InitiateInvite(EcsEntity, Uid, InviteKind),
ProcessTradeAction(EcsEntity, TradeId, TradeAction),
Mount(EcsEntity, EcsEntity), Mount(EcsEntity, EcsEntity),
Unmount(EcsEntity), Unmount(EcsEntity),
Possess(Uid, Uid), Possess(Uid, Uid),

View File

@ -6,16 +6,30 @@ use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{trace, warn}; use tracing::{trace, warn};
/// Clients submit `TradeActionMsg` to the server, which adds the Uid of the #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TradePhase {
Mutate,
Review,
Complete,
}
/// Clients submit `TradeAction` to the server, which adds the Uid of the
/// player out-of-band (i.e. without trusting the client to say who it's /// player out-of-band (i.e. without trusting the client to say who it's
/// accepting on behalf of) /// accepting on behalf of)
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TradeActionMsg { pub enum TradeAction {
AddItem { item: InvSlotId, quantity: u32 }, AddItem {
RemoveItem { item: InvSlotId, quantity: u32 }, item: InvSlotId,
Phase1Accept, quantity: u32,
Phase2Accept, },
RemoveItem {
item: InvSlotId,
quantity: u32,
},
/// Accept needs the phase indicator to avoid progressing too far in the
/// trade if there's latency and a player presses the accept button
/// multiple times
Accept(TradePhase),
Decline, Decline,
} }
@ -36,7 +50,7 @@ pub enum TradeResult {
/// from a trade back into a player's inventory. /// from a trade back into a player's inventory.
/// ///
/// On the flip side, since they are references to *slots*, if a player could /// On the flip side, since they are references to *slots*, if a player could
/// swaps items in their inventory during a trade, they could mutate the trade, /// swap items in their inventory during a trade, they could mutate the trade,
/// enabling them to remove an item from the trade even after receiving the /// enabling them to remove an item from the trade even after receiving the
/// counterparty's phase2 accept. To prevent this, we disallow all /// counterparty's phase2 accept. To prevent this, we disallow all
/// forms of inventory manipulation in `server::events::inventory_manip` if /// forms of inventory manipulation in `server::events::inventory_manip` if
@ -59,11 +73,21 @@ pub struct PendingTrade {
/// `offers[i]` represents the items and quantities of the party i's items /// `offers[i]` represents the items and quantities of the party i's items
/// being offered /// being offered
pub offers: [HashMap<InvSlotId, u32>; 2], pub offers: [HashMap<InvSlotId, u32>; 2],
/// phase1_accepts indicate that the parties wish to proceed to review /// The current phase of the trade
pub phase1_accepts: [bool; 2], pub phase: TradePhase,
/// phase2_accepts indicate that the parties have reviewed the trade and /// `accept_flags` indicate that which parties wish to proceed to the next
/// wish to commit it /// phase of the trade
pub phase2_accepts: [bool; 2], pub accept_flags: [bool; 2],
}
impl TradePhase {
fn next(self) -> TradePhase {
match self {
TradePhase::Mutate => TradePhase::Review,
TradePhase::Review => TradePhase::Complete,
TradePhase::Complete => TradePhase::Complete,
}
}
} }
impl PendingTrade { impl PendingTrade {
@ -71,24 +95,14 @@ impl PendingTrade {
PendingTrade { PendingTrade {
parties: [party, counterparty], parties: [party, counterparty],
offers: [HashMap::new(), HashMap::new()], offers: [HashMap::new(), HashMap::new()],
phase1_accepts: [false, false], phase: TradePhase::Mutate,
phase2_accepts: [false, false], accept_flags: [false, false],
} }
} }
pub fn in_phase1(&self) -> bool { !self.phase1_accepts[0] || !self.phase1_accepts[1] } pub fn phase(&self) -> TradePhase { self.phase }
pub fn in_phase2(&self) -> bool { pub fn should_commit(&self) -> bool { matches!(self.phase, TradePhase::Complete) }
(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]
}
pub fn which_party(&self, party: Uid) -> Option<usize> { pub fn which_party(&self, party: Uid) -> Option<usize> {
self.parties self.parties
@ -104,42 +118,41 @@ impl PendingTrade {
/// - Modifications can only happen in phase 1 /// - Modifications can only happen in phase 1
/// - Whenever a trade is modified, both accept flags get reset /// - Whenever a trade is modified, both accept flags get reset
/// - Accept flags only get set for the current phase /// - Accept flags only get set for the current phase
pub fn process_msg(&mut self, who: usize, msg: TradeActionMsg, inventory: &Inventory) { pub fn process_trade_action(&mut self, who: usize, action: TradeAction, inventory: &Inventory) {
use TradeActionMsg::*; use TradeAction::*;
match msg { match action {
AddItem { AddItem {
item, item,
quantity: delta, quantity: delta,
} => { } => {
if self.in_phase1() && delta > 0 { if self.phase() == TradePhase::Mutate && delta > 0 {
let total = self.offers[who].entry(item).or_insert(0); let total = self.offers[who].entry(item).or_insert(0);
let owned_quantity = inventory.get(item).map(|i| i.amount()).unwrap_or(0); let owned_quantity = inventory.get(item).map(|i| i.amount()).unwrap_or(0);
*total = total.saturating_add(delta).min(owned_quantity); *total = total.saturating_add(delta).min(owned_quantity);
self.phase1_accepts = [false, false]; self.accept_flags = [false, false];
} }
}, },
RemoveItem { RemoveItem {
item, item,
quantity: delta, quantity: delta,
} => { } => {
if self.in_phase1() { if self.phase() == TradePhase::Mutate {
self.offers[who] self.offers[who]
.entry(item) .entry(item)
.and_replace_entry_with(|_, mut total| { .and_replace_entry_with(|_, mut total| {
total = total.saturating_sub(delta); total = total.saturating_sub(delta);
if total > 0 { Some(total) } else { None } if total > 0 { Some(total) } else { None }
}); });
self.phase1_accepts = [false, false]; self.accept_flags = [false, false];
} }
}, },
Phase1Accept => { Accept(phase) => {
if self.in_phase1() { if self.phase == phase {
self.phase1_accepts[who] = true; self.accept_flags[who] = true;
} }
}, if self.accept_flags[0] && self.accept_flags[1] {
Phase2Accept => { self.phase = self.phase.next();
if self.in_phase2() { self.accept_flags = [false, false];
self.phase2_accepts[who] = true;
} }
}, },
Decline => {}, Decline => {},
@ -147,16 +160,19 @@ impl PendingTrade {
} }
} }
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct TradeId(usize);
pub struct Trades { pub struct Trades {
pub next_id: usize, pub next_id: TradeId,
pub trades: HashMap<usize, PendingTrade>, pub trades: HashMap<TradeId, PendingTrade>,
pub entity_trades: HashMap<Uid, usize>, pub entity_trades: HashMap<Uid, TradeId>,
} }
impl Trades { impl Trades {
pub fn begin_trade(&mut self, party: Uid, counterparty: Uid) -> usize { pub fn begin_trade(&mut self, party: Uid, counterparty: Uid) -> TradeId {
let id = self.next_id; let id = self.next_id;
self.next_id = id.wrapping_add(1); self.next_id = TradeId(id.0.wrapping_add(1));
self.trades self.trades
.insert(id, PendingTrade::new(party, counterparty)); .insert(id, PendingTrade::new(party, counterparty));
self.entity_trades.insert(party, id); self.entity_trades.insert(party, id);
@ -166,27 +182,27 @@ impl Trades {
pub fn process_trade_action( pub fn process_trade_action(
&mut self, &mut self,
id: usize, id: TradeId,
who: Uid, who: Uid,
msg: TradeActionMsg, action: TradeAction,
inventory: &Inventory, inventory: &Inventory,
) { ) {
trace!("for trade id {}, message {:?}", id, msg); trace!("for trade id {:?}, message {:?}", id, action);
if let Some(trade) = self.trades.get_mut(&id) { if let Some(trade) = self.trades.get_mut(&id) {
if let Some(party) = trade.which_party(who) { if let Some(party) = trade.which_party(who) {
trade.process_msg(party, msg, inventory); trade.process_trade_action(party, action, inventory);
} else { } else {
warn!( warn!(
"An entity who is not a party to trade {} tried to modify it", "An entity who is not a party to trade {:?} tried to modify it",
id 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) -> Option<Uid> { pub fn decline_trade(&mut self, id: TradeId, who: Uid) -> Option<Uid> {
let mut to_notify = None; let mut to_notify = None;
if let Some(trade) = self.trades.remove(&id) { if let Some(trade) = self.trades.remove(&id) {
match trade.which_party(who) { match trade.which_party(who) {
@ -198,7 +214,7 @@ impl Trades {
}, },
None => { None => {
warn!( warn!(
"An entity who is not a party to trade {} tried to decline it", "An entity who is not a party to trade {:?} tried to decline it",
id id
); );
// put it back // put it back
@ -206,7 +222,7 @@ impl Trades {
}, },
} }
} else { } else {
warn!("Attempt to decline nonexistent trade id {}", id); warn!("Attempt to decline nonexistent trade id {:?}", id);
} }
to_notify to_notify
} }
@ -227,18 +243,18 @@ impl Trades {
} }
pub fn in_immutable_trade(&self, uid: &Uid) -> bool { pub fn in_immutable_trade(&self, uid: &Uid) -> bool {
self.in_trade_with_property(uid, |trade| !trade.in_phase1()) self.in_trade_with_property(uid, |trade| trade.phase() != TradePhase::Mutate)
} }
pub fn in_mutable_trade(&self, uid: &Uid) -> bool { pub fn in_mutable_trade(&self, uid: &Uid) -> bool {
self.in_trade_with_property(uid, |trade| trade.in_phase1()) self.in_trade_with_property(uid, |trade| trade.phase() == TradePhase::Mutate)
} }
pub fn implicit_mutation_occurred(&mut self, uid: &Uid) { pub fn implicit_mutation_occurred(&mut self, uid: &Uid) {
if let Some(trade_id) = self.entity_trades.get(uid) { if let Some(trade_id) = self.entity_trades.get(uid) {
self.trades self.trades
.get_mut(trade_id) .get_mut(trade_id)
.map(|trade| trade.phase1_accepts = [false, false]); .map(|trade| trade.accept_flags = [false, false]);
} }
} }
} }
@ -246,7 +262,7 @@ impl Trades {
impl Default for Trades { impl Default for Trades {
fn default() -> Trades { fn default() -> Trades {
Trades { Trades {
next_id: 0, next_id: TradeId(0),
trades: HashMap::new(), trades: HashMap::new(),
entity_trades: HashMap::new(), entity_trades: HashMap::new(),
} }

View File

@ -98,13 +98,15 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::NpcInteract(entity, npc_entity)); server_emitter.emit(ServerEvent::NpcInteract(entity, npc_entity));
} }
}, },
ControlEvent::InitiateTrade(counterparty_uid) => { ControlEvent::InitiateInvite(inviter_uid, kind) => {
if let Some(counterparty_entity) = server_emitter.emit(ServerEvent::InitiateInvite(entity, inviter_uid, kind));
uid_allocator.retrieve_entity_internal(counterparty_uid.id()) },
{ ControlEvent::InviteResponse(response) => {
server_emitter.emit(ServerEvent::InviteResponse(entity, response));
},
ControlEvent::PerformTradeAction(trade_id, action) => {
server_emitter server_emitter
.emit(ServerEvent::InitiateTrade(entity, counterparty_entity)); .emit(ServerEvent::ProcessTradeAction(entity, trade_id, action));
}
}, },
ControlEvent::InventoryManip(manip) => { ControlEvent::InventoryManip(manip) => {
server_emitter.emit(ServerEvent::InventoryManip(entity, manip.into())); server_emitter.emit(ServerEvent::InventoryManip(entity, manip.into()));

View File

@ -168,8 +168,8 @@ impl State {
ecs.register::<comp::ItemDrop>(); ecs.register::<comp::ItemDrop>();
ecs.register::<comp::ChatMode>(); ecs.register::<comp::ChatMode>();
ecs.register::<comp::Faction>(); ecs.register::<comp::Faction>();
ecs.register::<comp::group::Invite>(); ecs.register::<comp::invite::Invite>();
ecs.register::<comp::group::PendingInvites>(); ecs.register::<comp::invite::PendingInvites>();
ecs.register::<comp::Beam>(); ecs.register::<comp::Beam>();
ecs.register::<comp::PreviousVelDtCache>(); ecs.register::<comp::PreviousVelDtCache>();

View File

@ -13,6 +13,7 @@ use common::{
self, self,
aura::{Aura, AuraKind, AuraTarget}, aura::{Aura, AuraKind, AuraTarget},
buff::{BuffCategory, BuffData, BuffKind, BuffSource}, buff::{BuffCategory, BuffData, BuffKind, BuffSource},
invite::InviteKind,
ChatType, Inventory, Item, LightEmitter, WaypointArea, ChatType, Inventory, Item, LightEmitter, WaypointArea,
}, },
effect::Effect, effect::Effect,
@ -1618,10 +1619,7 @@ fn handle_group_invite(
.expect("Failed to get uid for player"); .expect("Failed to get uid for player");
ecs.read_resource::<EventBus<ServerEvent>>() ecs.read_resource::<EventBus<ServerEvent>>()
.emit_now(ServerEvent::GroupManip( .emit_now(ServerEvent::InitiateInvite(client, uid, InviteKind::Group));
client,
comp::GroupManip::Invite(uid),
));
server.notify_client( server.notify_client(
client, client,

View File

@ -1,63 +1,27 @@
use crate::{client::Client, Server}; use crate::{client::Client, Server, State};
use common::{ use common::{
comp::{ comp::{
self, self,
group::{self, Group, GroupManager, Invite, InviteKind, PendingInvites}, group::{self, Group, GroupManager},
invite::{InviteKind, PendingInvites},
ChatType, GroupManip, ChatType, GroupManip,
}, },
trade::Trades,
uid::Uid, uid::Uid,
}; };
use common_net::{ use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
msg::{InviteAnswer, ServerGeneral}, use specs::{
sync::WorldSyncExt, world::{Entity, WorldExt},
}; ReadStorage, WriteStorage,
use specs::world::WorldExt;
use std::time::{Duration, Instant};
use tracing::{error, warn};
/// Time before invite times out
const INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(31);
/// Reduced duration shown to the client to help alleviate latency issues
const PRESENTED_INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(30);
pub fn handle_invite(
server: &mut Server,
inviter: specs::Entity,
invitee_uid: Uid,
kind: InviteKind,
) {
let max_group_size = server.settings().max_player_group_size;
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let invitee = match state.ecs().entity_from_uid(invitee_uid.into()) {
Some(t) => t,
None => {
// Inform of failure
if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Invite failed, target does not exist.",
));
}
return;
},
}; };
let uids = state.ecs().read_storage::<Uid>(); pub fn can_invite(
state: &State,
// Check if entity is trying to invite themselves clients: &ReadStorage<'_, Client>,
if uids pending_invites: &mut WriteStorage<'_, PendingInvites>,
.get(inviter) max_group_size: u32,
.map_or(false, |inviter_uid| *inviter_uid == invitee_uid) inviter: Entity,
{ invitee: Entity,
warn!("Entity tried to invite themselves into a group/trade"); ) -> bool {
return;
}
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
if let InviteKind::Group = kind {
// Disallow inviting entity that is already in your group // Disallow inviting entity that is already in your group
let groups = state.ecs().read_storage::<Group>(); let groups = state.ecs().read_storage::<Group>();
let group_manager = state.ecs().read_resource::<GroupManager>(); let group_manager = state.ecs().read_resource::<GroupManager>();
@ -75,7 +39,7 @@ pub fn handle_invite(
"Invite failed, can't invite someone already in your group", "Invite failed, can't invite someone already in your group",
)); ));
} }
return; return false;
} }
// Check if group max size is already reached // Check if group max size is already reached
@ -94,200 +58,31 @@ pub fn handle_invite(
.map(|i| i.num_members) .map(|i| i.num_members)
}) })
.unwrap_or(1) as usize .unwrap_or(1) as usize
+ pending_invites.get(inviter).map_or(0, |p| p.0.len()) + pending_invites.get(inviter).map_or(0, |p| {
p.0.iter()
.filter(|(_, k, _)| *k == InviteKind::Group)
.count()
})
>= max_group_size as usize; >= max_group_size as usize;
if group_size_limit_reached { if group_size_limit_reached {
// Inform inviter that they have reached the group size limit // Inform inviter that they have reached the group size limit
if let Some(client) = clients.get(inviter) { if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg( client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta, ChatType::Meta,
"Invite failed, pending invites plus current group size have reached the \ "Invite failed, pending invites plus current group size have reached the group \
group size limit" size limit"
.to_owned(), .to_owned(),
)); ));
} }
return; return false;
}
} }
let agents = state.ecs().read_storage::<comp::Agent>();
let mut invites = state.ecs().write_storage::<Invite>();
if invites.contains(invitee) {
// Inform inviter that there is already an invite
if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"This player already has a pending invite.",
));
}
return;
}
let mut invite_sent = false;
// Returns true if insertion was succesful
let mut send_invite = || {
match invites.insert(invitee, group::Invite { inviter, kind }) {
Err(err) => {
error!("Failed to insert Invite component: {:?}", err);
false
},
Ok(_) => {
match pending_invites.entry(inviter) {
Ok(entry) => {
entry.or_insert_with(|| PendingInvites(Vec::new())).0.push((
invitee,
kind,
Instant::now() + INVITE_TIMEOUT_DUR,
));
invite_sent = true;
true true
},
Err(err) => {
error!(
"Failed to get entry for pending invites component: {:?}",
err
);
// Cleanup
invites.remove(invitee);
false
},
}
},
}
};
// If client comp
if let (Some(client), Some(inviter)) = (clients.get(invitee), uids.get(inviter).copied()) {
if send_invite() {
client.send_fallible(ServerGeneral::Invite {
inviter,
timeout: PRESENTED_INVITE_TIMEOUT_DUR,
kind,
});
}
} else if agents.contains(invitee) {
send_invite();
} else if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Can't invite, not a player or npc",
));
}
// Notify inviter that the invite is pending
if invite_sent {
if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::InvitePending(invitee_uid));
}
}
} }
// TODO: turn chat messages into enums // TODO: turn chat messages into enums
pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupManip) { pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupManip) {
match manip { match manip {
GroupManip::Invite(uid) => {
handle_invite(server, entity, uid, InviteKind::Group);
},
GroupManip::Accept => {
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut invites = state.ecs().write_storage::<Invite>();
if let Some((inviter, kind)) = invites.remove(entity).and_then(|invite| {
let Invite { inviter, kind } = invite;
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
let pending = &mut pending_invites.get_mut(inviter)?.0;
// Check that inviter has a pending invite and remove it from the list
let invite_index = pending.iter().position(|p| p.0 == entity)?;
pending.swap_remove(invite_index);
// If no pending invites remain remove the component
if pending.is_empty() {
pending_invites.remove(inviter);
}
Some((inviter, kind))
}) {
if let (Some(client), Some(target)) =
(clients.get(inviter), uids.get(entity).copied())
{
client.send_fallible(ServerGeneral::InviteComplete {
target,
answer: InviteAnswer::Accepted,
kind,
});
}
match kind {
InviteKind::Group => {
let mut group_manager = state.ecs().write_resource::<GroupManager>();
group_manager.add_group_member(
inviter,
entity,
&state.ecs().entities(),
&mut state.ecs().write_storage(),
&state.ecs().read_storage(),
&uids,
|entity, group_change| {
clients
.get(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send(ServerGeneral::GroupUpdate(g)));
},
);
},
InviteKind::Trade => {
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)));
}
},
}
}
},
GroupManip::Decline => {
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut invites = state.ecs().write_storage::<Invite>();
if let Some((inviter, kind)) = invites.remove(entity).and_then(|invite| {
let Invite { inviter, kind } = invite;
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
let pending = &mut pending_invites.get_mut(inviter)?.0;
// Check that inviter has a pending invite and remove it from the list
let invite_index = pending.iter().position(|p| p.0 == entity)?;
pending.swap_remove(invite_index);
// If no pending invites remain remove the component
if pending.is_empty() {
pending_invites.remove(inviter);
}
Some((inviter, kind))
}) {
// Inform inviter of rejection
if let (Some(client), Some(target)) =
(clients.get(inviter), uids.get(entity).copied())
{
client.send_fallible(ServerGeneral::InviteComplete {
target,
answer: InviteAnswer::Declined,
kind,
});
}
}
},
GroupManip::Leave => { GroupManip::Leave => {
let state = server.state_mut(); let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>(); let clients = state.ecs().read_storage::<Client>();

View File

@ -41,7 +41,12 @@ pub fn snuff_lantern(storage: &mut WriteStorage<comp::LightEmitter>, entity: Ecs
pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::SlotManip) { pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::SlotManip) {
let state = server.state_mut(); let state = server.state_mut();
if let Some(uid) = state.ecs().uid_from_entity(entity) { let uid = state
.ecs()
.uid_from_entity(entity)
.expect("Couldn't get uid for entity");
{
let trades = state.ecs().read_resource::<Trades>(); let trades = state.ecs().read_resource::<Trades>();
if trades.in_immutable_trade(&uid) { if trades.in_immutable_trade(&uid) {
// manipulating the inventory can mutate the trade // manipulating the inventory can mutate the trade
@ -579,14 +584,12 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Slo
new_entity.build(); new_entity.build();
} }
if let Some(uid) = state.ecs().uid_from_entity(entity) {
let mut trades = state.ecs().write_resource::<Trades>(); let mut trades = state.ecs().write_resource::<Trades>();
if trades.in_mutable_trade(&uid) { if trades.in_mutable_trade(&uid) {
// manipulating the inventory mutated the trade, so reset the accept flags // manipulating the inventory mutated the trade, so reset the accept flags
trades.implicit_mutation_occurred(&uid); trades.implicit_mutation_occurred(&uid);
} }
} }
}
fn within_pickup_range<S: FindDist<find_dist::Cylinder>>( fn within_pickup_range<S: FindDist<find_dist::Cylinder>>(
entity_cylinder: Option<find_dist::Cylinder>, entity_cylinder: Option<find_dist::Cylinder>,

253
server/src/events/invite.rs Normal file
View File

@ -0,0 +1,253 @@
use super::group_manip;
use crate::{client::Client, Server};
use common::{
comp::{
self,
group::GroupManager,
invite::{Invite, InviteKind, InviteResponse, PendingInvites},
ChatType,
},
trade::Trades,
uid::Uid,
};
use common_net::{
msg::{InviteAnswer, ServerGeneral},
sync::WorldSyncExt,
};
use specs::world::WorldExt;
use std::time::{Duration, Instant};
use tracing::{error, warn};
/// Time before invite times out
const INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(31);
/// Reduced duration shown to the client to help alleviate latency issues
const PRESENTED_INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(30);
pub fn handle_invite(
server: &mut Server,
inviter: specs::Entity,
invitee_uid: Uid,
kind: InviteKind,
) {
let max_group_size = server.settings().max_player_group_size;
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let invitee = match state.ecs().entity_from_uid(invitee_uid.into()) {
Some(t) => t,
None => {
// Inform of failure
if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Invite failed, target does not exist.",
));
}
return;
},
};
let uids = state.ecs().read_storage::<Uid>();
// Check if entity is trying to invite themselves
if uids
.get(inviter)
.map_or(false, |inviter_uid| *inviter_uid == invitee_uid)
{
warn!("Entity tried to invite themselves into a group/trade");
return;
}
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
if let InviteKind::Group = kind {
if !group_manip::can_invite(
state,
&clients,
&mut pending_invites,
max_group_size,
inviter,
invitee,
) {
return;
}
}
let agents = state.ecs().read_storage::<comp::Agent>();
let mut invites = state.ecs().write_storage::<Invite>();
if invites.contains(invitee) {
// Inform inviter that there is already an invite
if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"This player already has a pending invite.",
));
}
return;
}
let mut invite_sent = false;
// Returns true if insertion was succesful
let mut send_invite = || {
match invites.insert(invitee, Invite { inviter, kind }) {
Err(err) => {
error!("Failed to insert Invite component: {:?}", err);
false
},
Ok(_) => {
match pending_invites.entry(inviter) {
Ok(entry) => {
entry.or_insert_with(|| PendingInvites(Vec::new())).0.push((
invitee,
kind,
Instant::now() + INVITE_TIMEOUT_DUR,
));
invite_sent = true;
true
},
Err(err) => {
error!(
"Failed to get entry for pending invites component: {:?}",
err
);
// Cleanup
invites.remove(invitee);
false
},
}
},
}
};
// If client comp
if let (Some(client), Some(inviter)) = (clients.get(invitee), uids.get(inviter).copied()) {
if send_invite() {
client.send_fallible(ServerGeneral::Invite {
inviter,
timeout: PRESENTED_INVITE_TIMEOUT_DUR,
kind,
});
}
} else if agents.contains(invitee) {
send_invite();
} else if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::server_msg(
ChatType::Meta,
"Can't invite, not a player or npc",
));
}
// Notify inviter that the invite is pending
if invite_sent {
if let Some(client) = clients.get(inviter) {
client.send_fallible(ServerGeneral::InvitePending(invitee_uid));
}
}
}
pub fn handle_invite_response(
server: &mut Server,
entity: specs::Entity,
response: InviteResponse,
) {
match response {
InviteResponse::Accept => handle_invite_accept(server, entity),
InviteResponse::Decline => handle_invite_decline(server, entity),
}
}
pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) {
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut invites = state.ecs().write_storage::<Invite>();
if let Some((inviter, kind)) = invites.remove(entity).and_then(|invite| {
let Invite { inviter, kind } = invite;
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
let pending = &mut pending_invites.get_mut(inviter)?.0;
// Check that inviter has a pending invite and remove it from the list
let invite_index = pending.iter().position(|p| p.0 == entity)?;
pending.swap_remove(invite_index);
// If no pending invites remain remove the component
if pending.is_empty() {
pending_invites.remove(inviter);
}
Some((inviter, kind))
}) {
if let (Some(client), Some(target)) = (clients.get(inviter), uids.get(entity).copied()) {
client.send_fallible(ServerGeneral::InviteComplete {
target,
answer: InviteAnswer::Accepted,
kind,
});
}
match kind {
InviteKind::Group => {
let mut group_manager = state.ecs().write_resource::<GroupManager>();
group_manager.add_group_member(
inviter,
entity,
&state.ecs().entities(),
&mut state.ecs().write_storage(),
&state.ecs().read_storage(),
&uids,
|entity, group_change| {
clients
.get(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send(ServerGeneral::GroupUpdate(g)));
},
);
},
InviteKind::Trade => {
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)));
}
},
}
}
}
pub fn handle_invite_decline(server: &mut Server, entity: specs::Entity) {
let state = server.state_mut();
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut invites = state.ecs().write_storage::<Invite>();
if let Some((inviter, kind)) = invites.remove(entity).and_then(|invite| {
let Invite { inviter, kind } = invite;
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
let pending = &mut pending_invites.get_mut(inviter)?.0;
// Check that inviter has a pending invite and remove it from the list
let invite_index = pending.iter().position(|p| p.0 == entity)?;
pending.swap_remove(invite_index);
// If no pending invites remain remove the component
if pending.is_empty() {
pending_invites.remove(inviter);
}
Some((inviter, kind))
}) {
// Inform inviter of rejection
if let (Some(client), Some(target)) = (clients.get(inviter), uids.get(entity).copied()) {
client.send_fallible(ServerGeneral::InviteComplete {
target,
answer: InviteAnswer::Declined,
kind,
});
}
}
}

View File

@ -16,15 +16,17 @@ use interaction::{
handle_lantern, handle_mount, handle_npc_interaction, handle_possess, handle_unmount, handle_lantern, handle_mount, handle_npc_interaction, handle_possess, handle_unmount,
}; };
use inventory_manip::handle_inventory; use inventory_manip::handle_inventory;
use invite::{handle_invite, handle_invite_response};
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}; use trade::handle_process_trade_action;
mod entity_creation; mod entity_creation;
mod entity_manipulation; mod entity_manipulation;
mod group_manip; mod group_manip;
mod interaction; mod interaction;
mod inventory_manip; mod inventory_manip;
mod invite;
mod player; mod player;
mod trade; mod trade;
@ -105,11 +107,15 @@ impl Server {
ServerEvent::NpcInteract(interactor, target) => { ServerEvent::NpcInteract(interactor, target) => {
handle_npc_interaction(self, interactor, target) handle_npc_interaction(self, interactor, target)
}, },
ServerEvent::InitiateTrade(interactor, target) => { ServerEvent::InitiateInvite(interactor, target, kind) => {
handle_initiate_trade(self, interactor, target) handle_invite(self, interactor, target, kind)
//handle_initiate_trade(self, interactor, target)
}, },
ServerEvent::ProcessTradeAction(entity, trade_id, msg) => { ServerEvent::InviteResponse(entity, response) => {
handle_process_trade_action(self, entity, trade_id, msg); handle_invite_response(self, entity, response)
},
ServerEvent::ProcessTradeAction(entity, trade_id, action) => {
handle_process_trade_action(self, entity, trade_id, action);
}, },
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),

View File

@ -1,33 +1,24 @@
use crate::{events::group_manip::handle_invite, Server}; use crate::Server;
use common::{ use common::{
comp::{group::InviteKind, inventory::Inventory}, comp::inventory::Inventory,
trade::{PendingTrade, TradeActionMsg, TradeResult, Trades}, trade::{PendingTrade, TradeAction, TradeId, TradeResult, Trades},
}; };
use common_net::{msg::ServerGeneral, sync::WorldSyncExt}; use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
use hashbrown::hash_map::Entry;
use specs::{world::WorldExt, Entity as EcsEntity}; use specs::{world::WorldExt, Entity as EcsEntity};
use std::cmp::Ordering; use std::cmp::Ordering;
use tracing::{error, trace, warn}; use tracing::{error, trace};
/// Invoked when pressing the trade button near an entity, triggering the invite
/// UI flow
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");
}
}
/// Invoked when the trade UI is up, handling item changes, accepts, etc /// Invoked when the trade UI is up, handling item changes, accepts, etc
pub fn handle_process_trade_action( pub fn handle_process_trade_action(
server: &mut Server, server: &mut Server,
entity: EcsEntity, entity: EcsEntity,
trade_id: usize, trade_id: TradeId,
msg: TradeActionMsg, action: TradeAction,
) { ) {
if let Some(uid) = server.state.ecs().uid_from_entity(entity) { if let Some(uid) = server.state.ecs().uid_from_entity(entity) {
let mut trades = server.state.ecs().write_resource::<Trades>(); let mut trades = server.state.ecs().write_resource::<Trades>();
if let TradeActionMsg::Decline = msg { if let TradeAction::Decline = action {
let to_notify = trades.decline_trade(trade_id, uid); let to_notify = trades.decline_trade(trade_id, uid);
to_notify to_notify
.and_then(|u| server.state.ecs().entity_from_uid(u.0)) .and_then(|u| server.state.ecs().entity_from_uid(u.0))
@ -36,16 +27,19 @@ pub fn handle_process_trade_action(
}); });
} else { } else {
if let Some(inv) = server.state.ecs().read_component::<Inventory>().get(entity) { if let Some(inv) = server.state.ecs().read_component::<Inventory>().get(entity) {
trades.process_trade_action(trade_id, uid, msg, inv); trades.process_trade_action(trade_id, uid, action, inv);
}
if let Some(trade) = trades.trades.get(&trade_id) {
let mut msg = ServerGeneral::UpdatePendingTrade(trade_id, trade.clone());
if trade.should_commit() {
let result = commit_trade(server.state.ecs(), trade);
msg = ServerGeneral::FinishedTrade(result);
} }
if let Entry::Occupied(entry) = trades.trades.entry(trade_id) {
let parties = entry.get().parties;
let msg = if entry.get().should_commit() {
let result = commit_trade(server.state.ecs(), entry.get());
entry.remove();
ServerGeneral::FinishedTrade(result)
} else {
ServerGeneral::UpdatePendingTrade(trade_id, entry.get().clone())
};
// send the updated state to both parties // send the updated state to both parties
for party in trade.parties.iter() { for party in parties.iter() {
server server
.state .state
.ecs() .ecs()
@ -60,16 +54,16 @@ pub fn handle_process_trade_action(
/// Commit a trade that both parties have agreed to, modifying their respective /// Commit a trade that both parties have agreed to, modifying their respective
/// inventories /// inventories
fn commit_trade(ecs: &specs::World, trade: &PendingTrade) -> TradeResult { fn commit_trade(ecs: &specs::World, trade: &PendingTrade) -> TradeResult {
let mut entities = vec![]; let mut entities = Vec::new();
for who in [0, 1].iter().cloned() { for party in trade.parties.iter() {
match ecs.entity_from_uid(trade.parties[who].0) { match ecs.entity_from_uid(party.0) {
Some(entity) => entities.push(entity), Some(entity) => entities.push(entity),
None => return TradeResult::Declined, None => return TradeResult::Declined,
} }
} }
let mut inventories = ecs.write_component::<Inventory>(); let mut inventories = ecs.write_component::<Inventory>();
for who in [0, 1].iter().cloned() { for entity in entities.iter() {
if inventories.get_mut(entities[who]).is_none() { if inventories.get_mut(*entity).is_none() {
return TradeResult::Declined; return TradeResult::Declined;
} }
} }
@ -106,7 +100,7 @@ fn commit_trade(ecs: &specs::World, trade: &PendingTrade) -> TradeResult {
delta_slots[1 - who] += 1; // overapproximation, assumes the stack won't merge delta_slots[1 - who] += 1; // overapproximation, assumes the stack won't merge
}, },
Ordering::Greater => { Ordering::Greater => {
// no change to delta_slots[who], since they have leftovers // No change to delta_slots[who], since they have leftovers
delta_slots[1 - who] += 1; // overapproximation, assumes the stack won't merge delta_slots[1 - who] += 1; // overapproximation, assumes the stack won't merge
}, },
} }
@ -121,10 +115,10 @@ fn commit_trade(ecs: &specs::World, trade: &PendingTrade) -> TradeResult {
return TradeResult::NotEnoughSpace; return TradeResult::NotEnoughSpace;
} }
} }
let mut items = [vec![], vec![]]; let mut items = [Vec::new(), Vec::new()];
for who in [0, 1].iter().cloned() { for who in [0, 1].iter().cloned() {
for (slot, quantity) in trade.offers[who].iter() { for (slot, quantity) in trade.offers[who].iter() {
// take the items one by one, to benefit from Inventory's stack handling // Take the items one by one, to benefit from Inventory's stack handling
for _ in 0..*quantity { for _ in 0..*quantity {
inventories inventories
.get_mut(entities[who]) .get_mut(entities[who])
@ -140,10 +134,10 @@ fn commit_trade(ecs: &specs::World, trade: &PendingTrade) -> TradeResult {
.expect(invmsg) .expect(invmsg)
.push_all(items[who].drain(..)) .push_all(items[who].drain(..))
{ {
// this should only happen if the arithmetic above for delta_slots says there's // This should only happen if the arithmetic above for delta_slots says there's
// enough space and there isn't (i.e. underapproximates) // enough space and there isn't (i.e. underapproximates)
error!( panic!(
"Not enough space for all the items, destroying leftovers {:?}", "Not enough space for all the items, leftovers are {:?}",
leftovers leftovers
); );
} }

View File

@ -4,16 +4,16 @@ use common::{
self, self,
agent::{Activity, AgentEvent, Tactic, DEFAULT_INTERACTION_TIME}, agent::{Activity, AgentEvent, Tactic, DEFAULT_INTERACTION_TIME},
group, group,
group::Invite,
inventory::slot::EquipSlot, inventory::slot::EquipSlot,
invite::{Invite, InviteResponse},
item::{ item::{
tool::{ToolKind, UniqueKind}, tool::{ToolKind, UniqueKind},
ItemKind, ItemKind,
}, },
skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill}, skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill},
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy, Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy,
GroupManip, Health, Inventory, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, Health, Inventory, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, Stats,
Stats, UnresolvedChatMsg, Vel, UnresolvedChatMsg, Vel,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
metrics::SysMetrics, metrics::SysMetrics,
@ -1577,7 +1577,7 @@ impl<'a> System<'a> for Sys {
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and()); debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
}); });
// Process group invites // Process invites
for (_invite, /*alignment,*/ agent, controller) in for (_invite, /*alignment,*/ agent, controller) in
(&invites, /*&alignments,*/ &mut agents, &mut controllers).join() (&invites, /*&alignments,*/ &mut agents, &mut controllers).join()
{ {
@ -1587,11 +1587,11 @@ impl<'a> System<'a> for Sys {
*agent = Agent::default(); *agent = Agent::default();
controller controller
.events .events
.push(ControlEvent::GroupManip(GroupManip::Accept)); .push(ControlEvent::InviteResponse(InviteResponse::Accept));
} else { } else {
controller controller
.events .events
.push(ControlEvent::GroupManip(GroupManip::Decline)); .push(ControlEvent::InviteResponse(InviteResponse::Decline));
} }
} }
sys_metrics.agent_ns.store( sys_metrics.agent_ns.store(

View File

@ -1,14 +1,14 @@
use super::SysTimer; use super::SysTimer;
use crate::client::Client; use crate::client::Client;
use common::{ use common::{
comp::group::{Invite, PendingInvites}, comp::invite::{Invite, PendingInvites},
span, span,
uid::Uid, uid::Uid,
}; };
use common_net::msg::{InviteAnswer, ServerGeneral}; use common_net::msg::{InviteAnswer, ServerGeneral};
use specs::{Entities, Join, ReadStorage, System, Write, WriteStorage}; use specs::{Entities, Join, ReadStorage, System, Write, WriteStorage};
/// This system removes timed out group invites /// This system removes timed out invites
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
#[allow(clippy::type_complexity)] // TODO: Pending review in #587 #[allow(clippy::type_complexity)] // TODO: Pending review in #587

View File

@ -158,10 +158,6 @@ 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

@ -16,10 +16,7 @@ use crate::{
use client::{self, Client}; use client::{self, Client};
use common::{ use common::{
combat, combat,
comp::{ comp::{group::Role, invite::InviteKind, BuffKind, Stats},
group::{InviteKind, Role},
BuffKind, Stats,
},
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
}; };
use common_net::sync::WorldSyncExt; use common_net::sync::WorldSyncExt;
@ -208,7 +205,7 @@ impl<'a> Widget for Group<'a> {
.unwrap_or_else(|| format!("Npc<{}>", uid)), .unwrap_or_else(|| format!("Npc<{}>", uid)),
}; };
let open_invite = self.client.group_invite(); let open_invite = self.client.invite();
let my_uid = self.client.uid(); let my_uid = self.client.uid();

View File

@ -73,7 +73,7 @@ use common::{
outcome::Outcome, outcome::Outcome,
span, span,
terrain::TerrainChunk, terrain::TerrainChunk,
trade::TradeActionMsg, trade::TradeAction,
uid::Uid, uid::Uid,
util::srgba_to_linear, util::srgba_to_linear,
vol::RectRasterableVol, vol::RectRasterableVol,
@ -399,7 +399,7 @@ pub enum Event {
}, },
DropSlot(comp::slot::Slot), DropSlot(comp::slot::Slot),
ChangeHotbarState(Box<HotbarState>), ChangeHotbarState(Box<HotbarState>),
TradeAction(TradeActionMsg), TradeAction(TradeAction),
Ability3(bool), Ability3(bool),
Logout, Logout,
Quit, Quit,
@ -2131,8 +2131,8 @@ impl Hud {
) )
.set(self.ids.trade, ui_widgets) .set(self.ids.trade, ui_widgets)
{ {
Some(msg) => { Some(action) => {
if let TradeActionMsg::Decline = msg { if let TradeAction::Decline = action {
self.show.stats = false; self.show.stats = false;
self.show.trade(false); self.show.trade(false);
if !self.show.social { if !self.show.social {
@ -2142,7 +2142,7 @@ impl Hud {
self.force_ungrab = true self.force_ungrab = true
}; };
} }
events.push(Event::TradeAction(msg)); events.push(Event::TradeAction(action));
}, },
None => {}, None => {},
} }
@ -2634,7 +2634,7 @@ impl Hud {
events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned()))); events.push(Event::ChangeHotbarState(Box::new(self.hotbar.to_owned())));
} else if let (Inventory(i), Trade(_)) = (a, b) { } else if let (Inventory(i), Trade(_)) = (a, b) {
if let Some(inventory) = inventories.get(entity) { if let Some(inventory) = inventories.get(entity) {
events.push(Event::TradeAction(TradeActionMsg::AddItem { events.push(Event::TradeAction(TradeAction::AddItem {
item: i.0, item: i.0,
quantity: i.amount(inventory).unwrap_or(1), quantity: i.amount(inventory).unwrap_or(1),
})); }));
@ -2642,7 +2642,7 @@ impl Hud {
} else if let (Trade(t), Inventory(_)) = (a, b) { } else if let (Trade(t), Inventory(_)) = (a, b) {
if let Some(inventory) = inventories.get(entity) { if let Some(inventory) = inventories.get(entity) {
if let Some(invslot) = t.invslot { if let Some(invslot) = t.invslot {
events.push(Event::TradeAction(TradeActionMsg::RemoveItem { events.push(Event::TradeAction(TradeAction::RemoveItem {
item: invslot, item: invslot,
quantity: t.amount(inventory).unwrap_or(1), quantity: t.amount(inventory).unwrap_or(1),
})); }));

View File

@ -15,7 +15,7 @@ use crate::{
use client::Client; use client::Client;
use common::{ use common::{
comp::Inventory, comp::Inventory,
trade::{PendingTrade, TradeActionMsg}, trade::{PendingTrade, TradeAction, TradePhase},
}; };
use common_net::sync::WorldSyncExt; use common_net::sync::WorldSyncExt;
use conrod_core::{ use conrod_core::{
@ -120,12 +120,10 @@ impl<'a> Trade<'a> {
ui: &mut UiCell<'_>, ui: &mut UiCell<'_>,
trade: &'a PendingTrade, trade: &'a PendingTrade,
) { ) {
let phase_text = if trade.in_phase1() { let phase_text = match trade.phase() {
self.localized_strings.get("hud.trade.phase1_description") TradePhase::Mutate => self.localized_strings.get("hud.trade.phase1_description"),
} else if trade.in_phase2() { TradePhase::Review => self.localized_strings.get("hud.trade.phase2_description"),
self.localized_strings.get("hud.trade.phase2_description") TradePhase::Complete => self.localized_strings.get("hud.trade.phase3_description"),
} else {
self.localized_strings.get("hud.trade.phase3_description")
}; };
Text::new(&phase_text) Text::new(&phase_text)
@ -146,6 +144,7 @@ impl<'a> Trade<'a> {
let inventories = self.client.inventories(); let inventories = self.client.inventories();
let uid = trade.parties[who]; let uid = trade.parties[who];
let entity = self.client.state().ecs().entity_from_uid(uid.0)?; let entity = self.client.state().ecs().entity_from_uid(uid.0)?;
// TODO: update in accordence with https://gitlab.com/veloren/veloren/-/issues/960
let inventory = inventories.get(entity)?; let inventory = inventories.get(entity)?;
// Alignment for Grid // Alignment for Grid
@ -177,8 +176,7 @@ impl<'a> Trade<'a> {
.color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) .color(Color::Rgba(1.0, 1.0, 1.0, 1.0))
.set(state.ids.offer_headers[who], ui); .set(state.ids.offer_headers[who], ui);
let has_accepted = (trade.in_phase1() && trade.phase1_accepts[who]) let has_accepted = trade.accept_flags[who];
|| (trade.in_phase2() && trade.phase2_accepts[who]);
let accept_indicator = self let accept_indicator = self
.localized_strings .localized_strings
.get("hud.trade.has_accepted") .get("hud.trade.has_accepted")
@ -207,7 +205,7 @@ impl<'a> Trade<'a> {
}) })
.collect(); .collect();
if trade.in_phase1() { if matches!(trade.phase(), TradePhase::Mutate) {
self.phase1_itemwidget(state, ui, inventory, who, &tradeslots); self.phase1_itemwidget(state, ui, inventory, who, &tradeslots);
} else { } else {
self.phase2_itemwidget(state, ui, inventory, who, &tradeslots); self.phase2_itemwidget(state, ui, inventory, who, &tradeslots);
@ -333,11 +331,7 @@ impl<'a> Trade<'a> {
.set(state.ids.accept_button, ui) .set(state.ids.accept_button, ui)
.was_clicked() .was_clicked()
{ {
if trade.in_phase1() { event = Some(TradeAction::Accept(trade.phase()));
event = Some(TradeActionMsg::Phase1Accept);
} else if trade.in_phase2() {
event = Some(TradeActionMsg::Phase2Accept);
}
} }
if Button::image(self.imgs.button) if Button::image(self.imgs.button)
@ -353,7 +347,7 @@ impl<'a> Trade<'a> {
.set(state.ids.decline_button, ui) .set(state.ids.decline_button, ui)
.was_clicked() .was_clicked()
{ {
event = Some(TradeActionMsg::Decline); event = Some(TradeAction::Decline);
} }
event event
} }
@ -371,7 +365,7 @@ impl<'a> Trade<'a> {
.set(state.ids.trade_close, ui) .set(state.ids.trade_close, ui)
.was_clicked() .was_clicked()
{ {
Some(TradeActionMsg::Decline) Some(TradeAction::Decline)
} else { } else {
None None
} }
@ -379,7 +373,7 @@ impl<'a> Trade<'a> {
} }
impl<'a> Widget for Trade<'a> { impl<'a> Widget for Trade<'a> {
type Event = Option<TradeActionMsg>; type Event = Option<TradeAction>;
type State = State; type State = State;
type Style = (); type Style = ();
@ -397,7 +391,7 @@ impl<'a> Widget for Trade<'a> {
let mut event = None; let mut event = None;
let trade = match self.client.pending_trade() { let trade = match self.client.pending_trade() {
Some((_, trade)) => trade, Some((_, trade)) => trade,
None => return Some(TradeActionMsg::Decline), None => return Some(TradeAction::Decline),
}; };
if state.ids.inv_alignment.len() < 2 { if state.ids.inv_alignment.len() < 2 {

View File

@ -19,7 +19,8 @@ pub struct KeyState {
pub auto_walk: bool, pub auto_walk: bool,
pub swap_loadout: bool, pub swap_loadout: bool,
pub respawn: bool, pub respawn: bool,
pub collect: bool, pub interact: bool,
pub trade: bool,
pub analog_matrix: Vec2<f32>, pub analog_matrix: Vec2<f32>,
} }
@ -44,7 +45,8 @@ impl Default for KeyState {
auto_walk: false, auto_walk: false,
swap_loadout: false, swap_loadout: false,
respawn: false, respawn: false,
collect: false, interact: false,
trade: false,
analog_matrix: Vec2::zero(), analog_matrix: Vec2::zero(),
} }
} }

View File

@ -10,7 +10,8 @@ use common::{
assets::AssetExt, assets::AssetExt,
comp, comp,
comp::{ comp::{
group::InviteKind, inventory::slot::Slot, ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel, inventory::slot::Slot, invite::InviteKind, ChatMsg, ChatType, InventoryUpdateEvent, Pos,
Vel,
}, },
consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE}, consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
outcome::Outcome, outcome::Outcome,
@ -23,7 +24,10 @@ use common::{
}, },
vol::ReadVol, vol::ReadVol,
}; };
use common_net::msg::{server::InviteAnswer, PresenceKind}; use common_net::{
msg::{server::InviteAnswer, PresenceKind},
sync::WorldSyncExt,
};
use crate::{ use crate::{
audio::sfx::SfxEvent, audio::sfx::SfxEvent,
@ -528,9 +532,9 @@ impl PlayState for SessionState {
} }
}, },
Event::InputUpdate(GameInput::Interact, state) Event::InputUpdate(GameInput::Interact, state)
if state != self.key_state.collect => if state != self.key_state.interact =>
{ {
self.key_state.collect = state; self.key_state.interact = state;
if state { if state {
if let Some(interactable) = self.interactable { if let Some(interactable) = self.interactable {
@ -559,9 +563,9 @@ impl PlayState for SessionState {
} }
} }
Event::InputUpdate(GameInput::Trade, state) Event::InputUpdate(GameInput::Trade, state)
if state != self.key_state.collect => if state != self.key_state.trade =>
{ {
self.key_state.collect = state; self.key_state.trade = state;
if state { if state {
if let Some(interactable) = self.interactable { if let Some(interactable) = self.interactable {
@ -569,17 +573,11 @@ impl PlayState for SessionState {
match interactable { match interactable {
Interactable::Block(_, _) => {}, Interactable::Block(_, _) => {},
Interactable::Entity(entity) => { Interactable::Entity(entity) => {
if client client
.state() .state()
.ecs() .ecs()
.read_storage::<comp::Item>() .uid_from_entity(entity)
.get(entity) .map(|uid| client.send_invite(uid, InviteKind::Trade));
.is_some()
{
client.pick_up(entity);
} else {
client.initiate_trade(entity);
}
}, },
} }
} }
@ -632,14 +630,14 @@ impl PlayState for SessionState {
}, },
Event::InputUpdate(GameInput::AcceptGroupInvite, true) => { Event::InputUpdate(GameInput::AcceptGroupInvite, true) => {
let mut client = self.client.borrow_mut(); let mut client = self.client.borrow_mut();
if client.group_invite().is_some() { if client.invite().is_some() {
client.accept_group_invite(); client.accept_invite();
} }
}, },
Event::InputUpdate(GameInput::DeclineGroupInvite, true) => { Event::InputUpdate(GameInput::DeclineGroupInvite, true) => {
let mut client = self.client.borrow_mut(); let mut client = self.client.borrow_mut();
if client.group_invite().is_some() { if client.invite().is_some() {
client.decline_group_invite(); client.decline_invite();
} }
}, },
Event::AnalogGameInput(input) => match input { Event::AnalogGameInput(input) => match input {
@ -1150,9 +1148,9 @@ impl PlayState for SessionState {
info!("Event! -> ChangedHotbarState") info!("Event! -> ChangedHotbarState")
}, },
HudEvent::TradeAction(msg) => { HudEvent::TradeAction(action) => {
let mut client = self.client.borrow_mut(); let mut client = self.client.borrow_mut();
client.trade_action(msg); client.perform_trade_action(action);
}, },
HudEvent::Ability3(state) => self.inputs.ability3.set_state(state), HudEvent::Ability3(state) => self.inputs.ability3.set_state(state),
HudEvent::ChangeFOV(new_fov) => { HudEvent::ChangeFOV(new_fov) => {
@ -1258,13 +1256,13 @@ impl PlayState for SessionState {
self.client.borrow_mut().craft_recipe(&r); self.client.borrow_mut().craft_recipe(&r);
}, },
HudEvent::InviteMember(uid) => { HudEvent::InviteMember(uid) => {
self.client.borrow_mut().send_group_invite(uid); self.client.borrow_mut().send_invite(uid, InviteKind::Group);
}, },
HudEvent::AcceptInvite => { HudEvent::AcceptInvite => {
self.client.borrow_mut().accept_group_invite(); self.client.borrow_mut().accept_invite();
}, },
HudEvent::DeclineInvite => { HudEvent::DeclineInvite => {
self.client.borrow_mut().decline_group_invite(); self.client.borrow_mut().decline_invite();
}, },
HudEvent::KickMember(uid) => { HudEvent::KickMember(uid) => {
self.client.borrow_mut().kick_from_group(uid); self.client.borrow_mut().kick_from_group(uid);