mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Plumb trade requests through the group invite UI, such that they can be accepted/declined without impacting the counterparty's movement.
This commit is contained in:
parent
250391656f
commit
e9b811b62b
@ -5,6 +5,7 @@
|
||||
string_map: {
|
||||
"hud.group": "Group",
|
||||
"hud.group.invite_to_join": "{name} invited you to their group!",
|
||||
"hud.group.invite_to_trade": "{name} would like to trade with you.",
|
||||
"hud.group.invite": "Invite",
|
||||
"hud.group.kick": "Kick",
|
||||
"hud.group.assign_leader": "Assign Leader",
|
||||
|
@ -20,7 +20,7 @@ use common::{
|
||||
comp::{
|
||||
self,
|
||||
chat::{KillSource, KillType},
|
||||
group,
|
||||
group::{self, InviteKind},
|
||||
skills::Skill,
|
||||
slot::Slot,
|
||||
ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip,
|
||||
@ -69,6 +69,7 @@ const PING_ROLLING_AVERAGE_SECS: usize = 10;
|
||||
|
||||
pub enum Event {
|
||||
Chat(comp::ChatMsg),
|
||||
InviteComplete { target: Uid, answer: InviteAnswer, kind: InviteKind },
|
||||
Disconnect,
|
||||
DisconnectionNotification(u64),
|
||||
InventoryUpdated(InventoryUpdateEvent),
|
||||
@ -130,7 +131,7 @@ pub struct Client {
|
||||
|
||||
max_group_size: u32,
|
||||
// Client has received an invite (inviter uid, time out instant)
|
||||
group_invite: Option<(Uid, std::time::Instant, std::time::Duration)>,
|
||||
group_invite: Option<(Uid, std::time::Instant, std::time::Duration, InviteKind)>,
|
||||
group_leader: Option<Uid>,
|
||||
// Note: potentially representable as a client only component
|
||||
group_members: HashMap<Uid, group::Role>,
|
||||
@ -636,18 +637,16 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dead(&self) -> bool {
|
||||
self.state.ecs().read_storage::<comp::Health>().get(self.entity).map_or(false, |h| h.is_dead)
|
||||
}
|
||||
|
||||
pub fn pick_up(&mut self, entity: EcsEntity) {
|
||||
// Get the health component from the entity
|
||||
|
||||
if let Some(uid) = self.state.read_component_copied(entity) {
|
||||
// If we're dead, exit before sending the message
|
||||
if self
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<comp::Health>()
|
||||
.get(self.entity)
|
||||
.map_or(false, |h| h.is_dead)
|
||||
{
|
||||
if self.is_dead() {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -659,13 +658,7 @@ impl Client {
|
||||
|
||||
pub fn npc_interact(&mut self, npc_entity: EcsEntity) {
|
||||
// If we're dead, exit before sending message
|
||||
if self
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<comp::Health>()
|
||||
.get(self.entity)
|
||||
.map_or(false, |h| h.is_dead)
|
||||
{
|
||||
if self.is_dead() {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -674,6 +667,17 @@ 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 character_list(&self) -> &CharacterList { &self.character_list }
|
||||
@ -737,7 +741,7 @@ impl Client {
|
||||
|
||||
pub fn max_group_size(&self) -> u32 { self.max_group_size }
|
||||
|
||||
pub fn group_invite(&self) -> Option<(Uid, std::time::Instant, std::time::Duration)> {
|
||||
pub fn group_invite(&self) -> Option<(Uid, std::time::Instant, std::time::Duration, InviteKind)> {
|
||||
self.group_invite
|
||||
}
|
||||
|
||||
@ -1094,7 +1098,7 @@ impl Client {
|
||||
// Check if the group invite has timed out and remove if so
|
||||
if self
|
||||
.group_invite
|
||||
.map_or(false, |(_, timeout, dur)| timeout.elapsed() > dur)
|
||||
.map_or(false, |(_, timeout, dur, _)| timeout.elapsed() > dur)
|
||||
{
|
||||
self.group_invite = None;
|
||||
}
|
||||
@ -1468,30 +1472,22 @@ impl Client {
|
||||
},
|
||||
}
|
||||
},
|
||||
ServerGeneral::GroupInvite { inviter, timeout } => {
|
||||
self.group_invite = Some((inviter, std::time::Instant::now(), timeout));
|
||||
ServerGeneral::GroupInvite { inviter, timeout, kind } => {
|
||||
self.group_invite = Some((inviter, std::time::Instant::now(), timeout, kind));
|
||||
},
|
||||
ServerGeneral::InvitePending(uid) => {
|
||||
if !self.pending_invites.insert(uid) {
|
||||
warn!("Received message about pending invite that was already pending");
|
||||
}
|
||||
},
|
||||
ServerGeneral::InviteComplete { target, answer } => {
|
||||
ServerGeneral::InviteComplete { target, answer, kind } => {
|
||||
if !self.pending_invites.remove(&target) {
|
||||
warn!(
|
||||
"Received completed invite message for invite that was not in the list of \
|
||||
pending invites"
|
||||
)
|
||||
}
|
||||
// TODO: expose this as a new event variant instead of going
|
||||
// through the chat
|
||||
let msg = match answer {
|
||||
// TODO: say who accepted/declined/timed out the invite
|
||||
InviteAnswer::Accepted => "Invite accepted",
|
||||
InviteAnswer::Declined => "Invite declined",
|
||||
InviteAnswer::TimedOut => "Invite timed out",
|
||||
};
|
||||
frontend_events.push(Event::Chat(comp::ChatType::Meta.chat_msg(msg)));
|
||||
frontend_events.push(Event::InviteComplete { target, answer, kind });
|
||||
},
|
||||
// Cleanup for when the client goes back to the `presence = None`
|
||||
ServerGeneral::ExitInGameSuccess => {
|
||||
|
@ -3,7 +3,7 @@ use crate::sync;
|
||||
use authc::AuthClientError;
|
||||
use common::{
|
||||
character::{self, CharacterItem},
|
||||
comp,
|
||||
comp::{self, group::InviteKind},
|
||||
outcome::Outcome,
|
||||
recipe::RecipeBook,
|
||||
resources::TimeOfDay,
|
||||
@ -80,6 +80,7 @@ pub enum ServerGeneral {
|
||||
GroupInvite {
|
||||
inviter: sync::Uid,
|
||||
timeout: std::time::Duration,
|
||||
kind: InviteKind,
|
||||
},
|
||||
/// Indicate to the client that their sent invite was not invalid and is
|
||||
/// currently pending
|
||||
@ -92,6 +93,7 @@ pub enum ServerGeneral {
|
||||
InviteComplete {
|
||||
target: sync::Uid,
|
||||
answer: InviteAnswer,
|
||||
kind: InviteKind,
|
||||
},
|
||||
/// Trigger cleanup for when the client goes back to the `Registered` state
|
||||
/// from an ingame state
|
||||
|
@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::time::Duration;
|
||||
use hashbrown::HashMap;
|
||||
use vek::*;
|
||||
|
||||
/// Default duration before an input is considered 'held'.
|
||||
@ -83,6 +84,7 @@ pub enum ControlEvent {
|
||||
EnableLantern,
|
||||
DisableLantern,
|
||||
Interact(Uid),
|
||||
InitiateTrade(Uid),
|
||||
Mount(Uid),
|
||||
Unmount,
|
||||
InventoryManip(InventoryManip),
|
||||
@ -318,3 +320,25 @@ pub struct Mounting(pub Uid);
|
||||
impl Component for Mounting {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
/// A PendingTrade is associated with the entity that initiated the trade.
|
||||
///
|
||||
/// Items are not removed from the inventory during a PendingTrade: all the items are moved
|
||||
/// atomically (if there's space and both parties agree) upon completion
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PendingTrade {
|
||||
/// `counterparty` is the other entity that's being traded with
|
||||
counterparty: Uid,
|
||||
/// `self_offer` represents the items and quantities of the initiator's items being offered
|
||||
self_offer: HashMap<InvSlotId, usize>,
|
||||
/// `other_offer` represents the items and quantities of the counterparty's items being offered
|
||||
other_offer: HashMap<InvSlotId, usize>,
|
||||
/// `locked` is set when going from the first (mutable) screen to the second (readonly, review)
|
||||
/// screen
|
||||
locked: bool,
|
||||
}
|
||||
|
||||
impl Component for PendingTrade {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
|
@ -28,14 +28,24 @@ impl Component for Group {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
pub struct Invite(pub specs::Entity);
|
||||
#[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, std::time::Instant)>);
|
||||
pub struct PendingInvites(pub Vec<(specs::Entity, InviteKind, std::time::Instant)>);
|
||||
impl Component for PendingInvites {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ pub use chat::{
|
||||
};
|
||||
pub use controller::{
|
||||
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input,
|
||||
InventoryManip, LoadoutManip, MountState, Mounting, SlotManip,
|
||||
InventoryManip, LoadoutManip, MountState, Mounting, PendingTrade, SlotManip,
|
||||
};
|
||||
pub use energy::{Energy, EnergyChange, EnergySource};
|
||||
pub use group::Group;
|
||||
|
@ -79,6 +79,7 @@ pub enum ServerEvent {
|
||||
EnableLantern(EcsEntity),
|
||||
DisableLantern(EcsEntity),
|
||||
NpcInteract(EcsEntity, EcsEntity),
|
||||
InitiateTrade(EcsEntity, EcsEntity),
|
||||
Mount(EcsEntity, EcsEntity),
|
||||
Unmount(EcsEntity),
|
||||
Possess(Uid, Uid),
|
||||
|
@ -98,6 +98,13 @@ impl<'a> System<'a> for Sys {
|
||||
server_emitter.emit(ServerEvent::NpcInteract(entity, npc_entity));
|
||||
}
|
||||
},
|
||||
ControlEvent::InitiateTrade(counterparty_uid) => {
|
||||
if let Some(counterparty_entity) =
|
||||
uid_allocator.retrieve_entity_internal(counterparty_uid.id())
|
||||
{
|
||||
server_emitter.emit(ServerEvent::InitiateTrade(entity, counterparty_entity));
|
||||
}
|
||||
},
|
||||
ControlEvent::InventoryManip(manip) => {
|
||||
server_emitter.emit(ServerEvent::InventoryManip(entity, manip.into()));
|
||||
},
|
||||
|
@ -2,7 +2,7 @@ use crate::{client::Client, Server};
|
||||
use common::{
|
||||
comp::{
|
||||
self,
|
||||
group::{self, Group, GroupManager, Invite, PendingInvites},
|
||||
group::{self, Group, GroupManager, Invite, InviteKind, PendingInvites},
|
||||
ChatType, GroupManip,
|
||||
},
|
||||
uid::Uid,
|
||||
@ -20,169 +20,176 @@ 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);
|
||||
|
||||
// TODO: turn chat messages into enums
|
||||
pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupManip) {
|
||||
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();
|
||||
|
||||
match manip {
|
||||
GroupManip::Invite(uid) => {
|
||||
let clients = state.ecs().read_storage::<Client>();
|
||||
let invitee = match state.ecs().entity_from_uid(uid.into()) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
// Inform of failure
|
||||
if let Some(client) = clients.get(entity) {
|
||||
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 to a group
|
||||
if uids
|
||||
.get(entity)
|
||||
.map_or(false, |inviter_uid| *inviter_uid == uid)
|
||||
{
|
||||
warn!("Entity tried to invite themselves into a group");
|
||||
return;
|
||||
}
|
||||
|
||||
// Disallow inviting entity that is already in your group
|
||||
let groups = state.ecs().read_storage::<Group>();
|
||||
let group_manager = state.ecs().read_resource::<GroupManager>();
|
||||
let already_in_same_group = groups.get(entity).map_or(false, |group| {
|
||||
group_manager
|
||||
.group_info(*group)
|
||||
.map_or(false, |g| g.leader == entity)
|
||||
&& groups.get(invitee) == Some(group)
|
||||
});
|
||||
if already_in_same_group {
|
||||
// Inform of failure
|
||||
if let Some(client) = clients.get(entity) {
|
||||
client.send_fallible(ServerGeneral::server_msg(
|
||||
ChatType::Meta,
|
||||
"Invite failed, can't invite someone already in your group",
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
|
||||
|
||||
// Check if group max size is already reached
|
||||
// Adding the current number of pending invites
|
||||
let group_size_limit_reached = state
|
||||
.ecs()
|
||||
.read_storage()
|
||||
.get(entity)
|
||||
.copied()
|
||||
.and_then(|group| {
|
||||
// If entity is currently the leader of a full group then they can't invite
|
||||
// anyone else
|
||||
group_manager
|
||||
.group_info(group)
|
||||
.filter(|i| i.leader == entity)
|
||||
.map(|i| i.num_members)
|
||||
})
|
||||
.unwrap_or(1) as usize
|
||||
+ pending_invites.get(entity).map_or(0, |p| p.0.len())
|
||||
>= max_group_size as usize;
|
||||
if group_size_limit_reached {
|
||||
// Inform inviter that they have reached the group size limit
|
||||
if let Some(client) = clients.get(entity) {
|
||||
client.send_fallible(ServerGeneral::server_msg(
|
||||
ChatType::Meta,
|
||||
"Invite failed, pending invites plus current group size have reached the \
|
||||
group size limit"
|
||||
.to_owned(),
|
||||
));
|
||||
}
|
||||
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(entity) {
|
||||
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(entity)) {
|
||||
Err(err) => {
|
||||
error!("Failed to insert Invite component: {:?}", err);
|
||||
false
|
||||
},
|
||||
Ok(_) => {
|
||||
match pending_invites.entry(entity) {
|
||||
Ok(entry) => {
|
||||
entry
|
||||
.or_insert_with(|| PendingInvites(Vec::new()))
|
||||
.0
|
||||
.push((invitee, 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(entity).copied())
|
||||
{
|
||||
if send_invite() {
|
||||
client.send_fallible(ServerGeneral::GroupInvite {
|
||||
inviter,
|
||||
timeout: PRESENTED_INVITE_TIMEOUT_DUR,
|
||||
});
|
||||
}
|
||||
} else if agents.contains(invitee) {
|
||||
send_invite();
|
||||
} else if let Some(client) = clients.get(entity) {
|
||||
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,
|
||||
"Can't invite, not a player or npc",
|
||||
"Invite failed, target does not exist.",
|
||||
));
|
||||
}
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// Notify inviter that the invite is pending
|
||||
if invite_sent {
|
||||
if let Some(client) = clients.get(entity) {
|
||||
client.send_fallible(ServerGeneral::InvitePending(uid));
|
||||
}
|
||||
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 {
|
||||
// Disallow inviting entity that is already in your group
|
||||
let groups = state.ecs().read_storage::<Group>();
|
||||
let group_manager = state.ecs().read_resource::<GroupManager>();
|
||||
let already_in_same_group = groups.get(inviter).map_or(false, |group| {
|
||||
group_manager
|
||||
.group_info(*group)
|
||||
.map_or(false, |g| g.leader == inviter)
|
||||
&& groups.get(invitee) == Some(group)
|
||||
});
|
||||
if already_in_same_group {
|
||||
// Inform of failure
|
||||
if let Some(client) = clients.get(inviter) {
|
||||
client.send_fallible(ServerGeneral::server_msg(
|
||||
ChatType::Meta,
|
||||
"Invite failed, can't invite someone already in your group",
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if group max size is already reached
|
||||
// Adding the current number of pending invites
|
||||
let group_size_limit_reached = state
|
||||
.ecs()
|
||||
.read_storage()
|
||||
.get(inviter)
|
||||
.copied()
|
||||
.and_then(|group| {
|
||||
// If entity is currently the leader of a full group then they can't invite
|
||||
// anyone else
|
||||
group_manager
|
||||
.group_info(group)
|
||||
.filter(|i| i.leader == inviter)
|
||||
.map(|i| i.num_members)
|
||||
})
|
||||
.unwrap_or(1) as usize
|
||||
+ pending_invites.get(inviter).map_or(0, |p| p.0.len())
|
||||
>= max_group_size as usize;
|
||||
if group_size_limit_reached {
|
||||
// Inform inviter that they have reached the group size limit
|
||||
if let Some(client) = clients.get(inviter) {
|
||||
client.send_fallible(ServerGeneral::server_msg(
|
||||
ChatType::Meta,
|
||||
"Invite failed, pending invites plus current group size have reached the \
|
||||
group size limit"
|
||||
.to_owned(),
|
||||
));
|
||||
}
|
||||
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, 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
|
||||
},
|
||||
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::GroupInvite {
|
||||
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
|
||||
pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupManip) {
|
||||
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) = invites.remove(entity).and_then(|invite| {
|
||||
let inviter = invite.0;
|
||||
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
|
||||
@ -193,7 +200,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
pending_invites.remove(inviter);
|
||||
}
|
||||
|
||||
Some(inviter)
|
||||
Some((inviter, kind))
|
||||
}) {
|
||||
if let (Some(client), Some(target)) =
|
||||
(clients.get(inviter), uids.get(entity).copied())
|
||||
@ -201,35 +208,39 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
client.send_fallible(ServerGeneral::InviteComplete {
|
||||
target,
|
||||
answer: InviteAnswer::Accepted,
|
||||
kind,
|
||||
});
|
||||
}
|
||||
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)));
|
||||
},
|
||||
);
|
||||
if let InviteKind::Group = kind {
|
||||
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)));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
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) = invites.remove(entity).and_then(|invite| {
|
||||
let inviter = invite.0;
|
||||
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
|
||||
@ -240,7 +251,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
pending_invites.remove(inviter);
|
||||
}
|
||||
|
||||
Some(inviter)
|
||||
Some((inviter, kind))
|
||||
}) {
|
||||
// Inform inviter of rejection
|
||||
if let (Some(client), Some(target)) =
|
||||
@ -249,11 +260,13 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
client.send_fallible(ServerGeneral::InviteComplete {
|
||||
target,
|
||||
answer: InviteAnswer::Declined,
|
||||
kind,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
GroupManip::Leave => {
|
||||
let state = server.state_mut();
|
||||
let clients = state.ecs().read_storage::<Client>();
|
||||
let uids = state.ecs().read_storage::<Uid>();
|
||||
let mut group_manager = state.ecs().write_resource::<GroupManager>();
|
||||
@ -276,6 +289,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
);
|
||||
},
|
||||
GroupManip::Kick(uid) => {
|
||||
let state = server.state_mut();
|
||||
let clients = state.ecs().read_storage::<Client>();
|
||||
let uids = state.ecs().read_storage::<Uid>();
|
||||
let alignments = state.ecs().read_storage::<comp::Alignment>();
|
||||
@ -379,6 +393,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
}
|
||||
},
|
||||
GroupManip::AssignLeader(uid) => {
|
||||
let state = server.state_mut();
|
||||
let clients = state.ecs().read_storage::<Client>();
|
||||
let uids = state.ecs().read_storage::<Uid>();
|
||||
let target = match state.ecs().entity_from_uid(uid.into()) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
use specs::{world::WorldExt, Entity as EcsEntity};
|
||||
use tracing::error;
|
||||
use tracing::{error, warn};
|
||||
|
||||
use common::{
|
||||
comp::{self, agent::AgentEvent, inventory::slot::EquipSlot, item, slot::Slot, Inventory, Pos},
|
||||
comp::{self, agent::AgentEvent, group::InviteKind, inventory::slot::EquipSlot, item, slot::Slot, Inventory, Pos},
|
||||
consts::MAX_MOUNT_RANGE,
|
||||
uid::Uid,
|
||||
};
|
||||
@ -10,6 +10,7 @@ use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
|
||||
|
||||
use crate::{
|
||||
client::Client,
|
||||
events::group_manip::handle_invite,
|
||||
presence::{Presence, RegionSubscription},
|
||||
Server,
|
||||
};
|
||||
@ -68,6 +69,14 @@ 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();
|
||||
|
||||
|
@ -13,7 +13,7 @@ use entity_manipulation::{
|
||||
};
|
||||
use group_manip::handle_group;
|
||||
use interaction::{
|
||||
handle_lantern, handle_mount, handle_npc_interaction, handle_possess, handle_unmount,
|
||||
handle_lantern, handle_initiate_trade, handle_mount, handle_npc_interaction, handle_possess, handle_unmount,
|
||||
};
|
||||
use inventory_manip::handle_inventory;
|
||||
use player::{handle_client_disconnect, handle_exit_ingame};
|
||||
@ -103,6 +103,9 @@ impl Server {
|
||||
ServerEvent::NpcInteract(interactor, target) => {
|
||||
handle_npc_interaction(self, interactor, target)
|
||||
},
|
||||
ServerEvent::InitiateTrade(interactor, target) => {
|
||||
handle_initiate_trade(self, interactor, target)
|
||||
},
|
||||
ServerEvent::Mount(mounter, mountee) => handle_mount(self, mounter, mountee),
|
||||
ServerEvent::Unmount(mounter) => handle_unmount(self, mounter),
|
||||
ServerEvent::Possess(possessor_uid, possesse_uid) => {
|
||||
|
@ -32,13 +32,13 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
let timed_out_invites = (&entities, &invites)
|
||||
.join()
|
||||
.filter_map(|(invitee, Invite(inviter))| {
|
||||
.filter_map(|(invitee, Invite { inviter, kind })| {
|
||||
// Retrieve timeout invite from pending invites
|
||||
let pending = &mut pending_invites.get_mut(*inviter)?.0;
|
||||
let index = pending.iter().position(|p| p.0 == invitee)?;
|
||||
|
||||
// Stop if not timed out
|
||||
if pending[index].1 > now {
|
||||
if pending[index].2 > now {
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -57,6 +57,7 @@ impl<'a> System<'a> for Sys {
|
||||
client.send_fallible(ServerGeneral::InviteComplete {
|
||||
target,
|
||||
answer: InviteAnswer::TimedOut,
|
||||
kind: *kind,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ use crate::{
|
||||
use client::{self, Client};
|
||||
use common::{
|
||||
combat,
|
||||
comp::{group::Role, BuffKind, Stats},
|
||||
comp::{group::{InviteKind, Role}, BuffKind, Stats},
|
||||
uid::{Uid, UidAllocator},
|
||||
};
|
||||
use common_net::sync::WorldSyncExt;
|
||||
@ -219,7 +219,7 @@ impl<'a> Widget for Group<'a> {
|
||||
.crop_kids()
|
||||
.set(state.ids.bg, ui);
|
||||
}
|
||||
if let Some((_, timeout_start, timeout_dur)) = open_invite {
|
||||
if let Some((_, timeout_start, timeout_dur, kind)) = open_invite {
|
||||
// Group Menu button
|
||||
Button::image(self.imgs.group_icon)
|
||||
.w_h(49.0, 26.0)
|
||||
@ -792,16 +792,26 @@ impl<'a> Widget for Group<'a> {
|
||||
// into the maximum group size.
|
||||
}
|
||||
}
|
||||
if let Some((invite_uid, _, _)) = open_invite {
|
||||
if let Some((invite_uid, _, _, kind)) = open_invite {
|
||||
self.show.group = true; // Auto open group menu
|
||||
// TODO: add group name here too
|
||||
// Invite text
|
||||
|
||||
let name = uid_to_name_text(invite_uid, &self.client);
|
||||
let invite_text = self
|
||||
.localized_strings
|
||||
.get("hud.group.invite_to_join")
|
||||
.replace("{name}", &name);
|
||||
let invite_text = match kind {
|
||||
InviteKind::Group => {
|
||||
self
|
||||
.localized_strings
|
||||
.get("hud.group.invite_to_join")
|
||||
.replace("{name}", &name)
|
||||
},
|
||||
InviteKind::Trade => {
|
||||
self
|
||||
.localized_strings
|
||||
.get("hud.group.invite_to_trade")
|
||||
.replace("{name}", &name)
|
||||
},
|
||||
};
|
||||
Text::new(&invite_text)
|
||||
.mid_top_with_margin_on(state.ids.bg, 5.0)
|
||||
.font_size(12)
|
||||
|
@ -2810,10 +2810,6 @@ impl Hud {
|
||||
self.show.toggle_bag();
|
||||
true
|
||||
},
|
||||
GameInput::Trade if state => {
|
||||
self.show.toggle_trade();
|
||||
true
|
||||
},
|
||||
GameInput::Social if state => {
|
||||
self.show.toggle_social();
|
||||
true
|
||||
|
@ -6,9 +6,6 @@ use super::{
|
||||
util::loadout_slot_text,
|
||||
Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, QUALITY_COMMON, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
};
|
||||
use common::{
|
||||
comp::{item::Quality,},
|
||||
};
|
||||
use crate::{
|
||||
hud::get_quality_col,
|
||||
i18n::Localization,
|
||||
@ -19,6 +16,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use client::Client;
|
||||
use common::comp::item::Quality;
|
||||
use conrod_core::{
|
||||
color,
|
||||
widget::{self, Button, Image, Rectangle, Scrollbar, Text},
|
||||
@ -129,36 +127,28 @@ impl<'a> Widget for Trade<'a> {
|
||||
|
||||
// BG
|
||||
Image::new(self.imgs.inv_bg_bag)
|
||||
.w_h(424.0, 708.0)
|
||||
.middle()
|
||||
.color(Some(UI_MAIN))
|
||||
.set(state.ids.bg, ui);
|
||||
.w_h(424.0, 708.0)
|
||||
.middle()
|
||||
.color(Some(UI_MAIN))
|
||||
.set(state.ids.bg, ui);
|
||||
Image::new(self.imgs.inv_frame_bag)
|
||||
.w_h(424.0, 708.0)
|
||||
.middle_of(state.ids.bg)
|
||||
.color(Some(UI_HIGHLIGHT_0))
|
||||
.set(state.ids.bg_frame, ui);
|
||||
.w_h(424.0, 708.0)
|
||||
.middle_of(state.ids.bg)
|
||||
.color(Some(UI_HIGHLIGHT_0))
|
||||
.set(state.ids.bg_frame, ui);
|
||||
// Title
|
||||
Text::new(
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.trade.trade_window")
|
||||
)
|
||||
.mid_top_with_margin_on(state.ids.bg_frame, 9.0)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(20))
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||
.set(state.ids.trade_title_bg, ui);
|
||||
Text::new(
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.trade.trade_window")
|
||||
)
|
||||
.top_left_with_margins_on(state.ids.trade_title_bg, 2.0, 2.0)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(20))
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.trade_title, ui);
|
||||
Text::new(&self.localized_strings.get("hud.trade.trade_window"))
|
||||
.mid_top_with_margin_on(state.ids.bg_frame, 9.0)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(20))
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||
.set(state.ids.trade_title_bg, ui);
|
||||
Text::new(&self.localized_strings.get("hud.trade.trade_window"))
|
||||
.top_left_with_margins_on(state.ids.trade_title_bg, 2.0, 2.0)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(self.fonts.cyri.scale(20))
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.trade_title, ui);
|
||||
|
||||
event
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use client::{self, Client};
|
||||
use common::{
|
||||
assets::AssetExt,
|
||||
comp,
|
||||
comp::{inventory::slot::Slot, ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel},
|
||||
comp::{inventory::slot::Slot, group::InviteKind, ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel},
|
||||
consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
|
||||
outcome::Outcome,
|
||||
span,
|
||||
@ -20,7 +20,7 @@ use common::{
|
||||
},
|
||||
vol::ReadVol,
|
||||
};
|
||||
use common_net::msg::PresenceKind;
|
||||
use common_net::msg::{server::InviteAnswer, PresenceKind};
|
||||
|
||||
use crate::{
|
||||
audio::sfx::SfxEvent,
|
||||
@ -121,6 +121,24 @@ impl SessionState {
|
||||
client::Event::Chat(m) => {
|
||||
self.hud.new_message(m);
|
||||
},
|
||||
client::Event::InviteComplete { target, answer, kind } => {
|
||||
// TODO: i18n
|
||||
let kind_str = match kind {
|
||||
InviteKind::Group => "Group",
|
||||
InviteKind::Trade => "Trade",
|
||||
};
|
||||
let target_name = match client.player_list().get(&target) {
|
||||
Some(info) => info.player_alias.clone(),
|
||||
None => "<unknown>".to_string(),
|
||||
};
|
||||
let answer_str = match answer {
|
||||
InviteAnswer::Accepted => "accepted",
|
||||
InviteAnswer::Declined => "declined",
|
||||
InviteAnswer::TimedOut => "timed out",
|
||||
};
|
||||
let msg = format!("{} invite to {} {}", kind_str, target_name, answer_str);
|
||||
self.hud.new_message(ChatType::Meta.chat_msg(msg));
|
||||
}
|
||||
client::Event::InventoryUpdated(inv_event) => {
|
||||
let sfx_triggers = self.scene.sfx_mgr.triggers.read();
|
||||
|
||||
@ -524,6 +542,33 @@ impl PlayState for SessionState {
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::InputUpdate(GameInput::Trade, state)
|
||||
if state != self.key_state.collect =>
|
||||
{
|
||||
self.key_state.collect = state;
|
||||
|
||||
if state {
|
||||
if let Some(interactable) = self.interactable {
|
||||
let mut client = self.client.borrow_mut();
|
||||
match interactable {
|
||||
Interactable::Block(_, _) => {},
|
||||
Interactable::Entity(entity) => {
|
||||
if client
|
||||
.state()
|
||||
.ecs()
|
||||
.read_storage::<comp::Item>()
|
||||
.get(entity)
|
||||
.is_some()
|
||||
{
|
||||
client.pick_up(entity);
|
||||
} else {
|
||||
client.initiate_trade(entity);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*Event::InputUpdate(GameInput::Charge, state) => {
|
||||
self.inputs.charge.set_state(state);
|
||||
},*/
|
||||
|
Loading…
Reference in New Issue
Block a user