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: {
|
string_map: {
|
||||||
"hud.group": "Group",
|
"hud.group": "Group",
|
||||||
"hud.group.invite_to_join": "{name} invited you to their 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.invite": "Invite",
|
||||||
"hud.group.kick": "Kick",
|
"hud.group.kick": "Kick",
|
||||||
"hud.group.assign_leader": "Assign Leader",
|
"hud.group.assign_leader": "Assign Leader",
|
||||||
|
@ -20,7 +20,7 @@ use common::{
|
|||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
chat::{KillSource, KillType},
|
chat::{KillSource, KillType},
|
||||||
group,
|
group::{self, InviteKind},
|
||||||
skills::Skill,
|
skills::Skill,
|
||||||
slot::Slot,
|
slot::Slot,
|
||||||
ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip,
|
ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip,
|
||||||
@ -69,6 +69,7 @@ const PING_ROLLING_AVERAGE_SECS: usize = 10;
|
|||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Chat(comp::ChatMsg),
|
Chat(comp::ChatMsg),
|
||||||
|
InviteComplete { target: Uid, answer: InviteAnswer, kind: InviteKind },
|
||||||
Disconnect,
|
Disconnect,
|
||||||
DisconnectionNotification(u64),
|
DisconnectionNotification(u64),
|
||||||
InventoryUpdated(InventoryUpdateEvent),
|
InventoryUpdated(InventoryUpdateEvent),
|
||||||
@ -130,7 +131,7 @@ 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)>,
|
group_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>,
|
||||||
@ -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) {
|
pub fn pick_up(&mut self, entity: EcsEntity) {
|
||||||
// Get the health component from the entity
|
// Get the health component from the entity
|
||||||
|
|
||||||
if let Some(uid) = self.state.read_component_copied(entity) {
|
if let Some(uid) = self.state.read_component_copied(entity) {
|
||||||
// If we're dead, exit before sending the message
|
// If we're dead, exit before sending the message
|
||||||
if self
|
if self.is_dead() {
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<comp::Health>()
|
|
||||||
.get(self.entity)
|
|
||||||
.map_or(false, |h| h.is_dead)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,13 +658,7 @@ impl Client {
|
|||||||
|
|
||||||
pub fn npc_interact(&mut self, npc_entity: EcsEntity) {
|
pub fn npc_interact(&mut self, npc_entity: EcsEntity) {
|
||||||
// If we're dead, exit before sending message
|
// If we're dead, exit before sending message
|
||||||
if self
|
if self.is_dead() {
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<comp::Health>()
|
|
||||||
.get(self.entity)
|
|
||||||
.map_or(false, |h| h.is_dead)
|
|
||||||
{
|
|
||||||
return;
|
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 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 }
|
||||||
@ -737,7 +741,7 @@ 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(&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
|
self.group_invite
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1094,7 +1098,7 @@ impl Client {
|
|||||||
// Check if the group invite has timed out and remove if so
|
// Check if the group invite has timed out and remove if so
|
||||||
if self
|
if self
|
||||||
.group_invite
|
.group_invite
|
||||||
.map_or(false, |(_, timeout, dur)| timeout.elapsed() > dur)
|
.map_or(false, |(_, timeout, dur, _)| timeout.elapsed() > dur)
|
||||||
{
|
{
|
||||||
self.group_invite = None;
|
self.group_invite = None;
|
||||||
}
|
}
|
||||||
@ -1468,30 +1472,22 @@ impl Client {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ServerGeneral::GroupInvite { inviter, timeout } => {
|
ServerGeneral::GroupInvite { inviter, timeout, kind } => {
|
||||||
self.group_invite = Some((inviter, std::time::Instant::now(), timeout));
|
self.group_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) {
|
||||||
warn!("Received message about pending invite that was already pending");
|
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) {
|
if !self.pending_invites.remove(&target) {
|
||||||
warn!(
|
warn!(
|
||||||
"Received completed invite message for invite that was not in the list of \
|
"Received completed invite message for invite that was not in the list of \
|
||||||
pending invites"
|
pending invites"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// TODO: expose this as a new event variant instead of going
|
frontend_events.push(Event::InviteComplete { target, answer, kind });
|
||||||
// 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)));
|
|
||||||
},
|
},
|
||||||
// Cleanup for when the client goes back to the `presence = None`
|
// Cleanup for when the client goes back to the `presence = None`
|
||||||
ServerGeneral::ExitInGameSuccess => {
|
ServerGeneral::ExitInGameSuccess => {
|
||||||
|
@ -3,7 +3,7 @@ use crate::sync;
|
|||||||
use authc::AuthClientError;
|
use authc::AuthClientError;
|
||||||
use common::{
|
use common::{
|
||||||
character::{self, CharacterItem},
|
character::{self, CharacterItem},
|
||||||
comp,
|
comp::{self, group::InviteKind},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
recipe::RecipeBook,
|
recipe::RecipeBook,
|
||||||
resources::TimeOfDay,
|
resources::TimeOfDay,
|
||||||
@ -80,6 +80,7 @@ pub enum ServerGeneral {
|
|||||||
GroupInvite {
|
GroupInvite {
|
||||||
inviter: sync::Uid,
|
inviter: sync::Uid,
|
||||||
timeout: std::time::Duration,
|
timeout: std::time::Duration,
|
||||||
|
kind: InviteKind,
|
||||||
},
|
},
|
||||||
/// Indicate to the client that their sent invite was not invalid and is
|
/// Indicate to the client that their sent invite was not invalid and is
|
||||||
/// currently pending
|
/// currently pending
|
||||||
@ -92,6 +93,7 @@ pub enum ServerGeneral {
|
|||||||
InviteComplete {
|
InviteComplete {
|
||||||
target: sync::Uid,
|
target: sync::Uid,
|
||||||
answer: InviteAnswer,
|
answer: InviteAnswer,
|
||||||
|
kind: InviteKind,
|
||||||
},
|
},
|
||||||
/// Trigger cleanup for when the client goes back to the `Registered` state
|
/// Trigger cleanup for when the client goes back to the `Registered` state
|
||||||
/// from an ingame state
|
/// from an ingame state
|
||||||
|
@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use specs::{Component, DerefFlaggedStorage};
|
use specs::{Component, DerefFlaggedStorage};
|
||||||
use specs_idvs::IdvStorage;
|
use specs_idvs::IdvStorage;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use hashbrown::HashMap;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
/// Default duration before an input is considered 'held'.
|
/// Default duration before an input is considered 'held'.
|
||||||
@ -83,6 +84,7 @@ pub enum ControlEvent {
|
|||||||
EnableLantern,
|
EnableLantern,
|
||||||
DisableLantern,
|
DisableLantern,
|
||||||
Interact(Uid),
|
Interact(Uid),
|
||||||
|
InitiateTrade(Uid),
|
||||||
Mount(Uid),
|
Mount(Uid),
|
||||||
Unmount,
|
Unmount,
|
||||||
InventoryManip(InventoryManip),
|
InventoryManip(InventoryManip),
|
||||||
@ -318,3 +320,25 @@ pub struct Mounting(pub Uid);
|
|||||||
impl Component for Mounting {
|
impl Component for Mounting {
|
||||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
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>>;
|
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 {
|
impl Component for Invite {
|
||||||
type Storage = IdvStorage<Self>;
|
type Storage = IdvStorage<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pending invites that an entity currently has sent out
|
// Pending invites that an entity currently has sent out
|
||||||
// (invited entity, instant when invite times 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 {
|
impl Component for PendingInvites {
|
||||||
type Storage = IdvStorage<Self>;
|
type Storage = IdvStorage<Self>;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ pub use chat::{
|
|||||||
};
|
};
|
||||||
pub use controller::{
|
pub use controller::{
|
||||||
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input,
|
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 energy::{Energy, EnergyChange, EnergySource};
|
||||||
pub use group::Group;
|
pub use group::Group;
|
||||||
|
@ -79,6 +79,7 @@ pub enum ServerEvent {
|
|||||||
EnableLantern(EcsEntity),
|
EnableLantern(EcsEntity),
|
||||||
DisableLantern(EcsEntity),
|
DisableLantern(EcsEntity),
|
||||||
NpcInteract(EcsEntity, EcsEntity),
|
NpcInteract(EcsEntity, EcsEntity),
|
||||||
|
InitiateTrade(EcsEntity, EcsEntity),
|
||||||
Mount(EcsEntity, EcsEntity),
|
Mount(EcsEntity, EcsEntity),
|
||||||
Unmount(EcsEntity),
|
Unmount(EcsEntity),
|
||||||
Possess(Uid, Uid),
|
Possess(Uid, Uid),
|
||||||
|
@ -98,6 +98,13 @@ 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) => {
|
||||||
|
if let Some(counterparty_entity) =
|
||||||
|
uid_allocator.retrieve_entity_internal(counterparty_uid.id())
|
||||||
|
{
|
||||||
|
server_emitter.emit(ServerEvent::InitiateTrade(entity, counterparty_entity));
|
||||||
|
}
|
||||||
|
},
|
||||||
ControlEvent::InventoryManip(manip) => {
|
ControlEvent::InventoryManip(manip) => {
|
||||||
server_emitter.emit(ServerEvent::InventoryManip(entity, manip.into()));
|
server_emitter.emit(ServerEvent::InventoryManip(entity, manip.into()));
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@ use crate::{client::Client, Server};
|
|||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
group::{self, Group, GroupManager, Invite, PendingInvites},
|
group::{self, Group, GroupManager, Invite, InviteKind, PendingInvites},
|
||||||
ChatType, GroupManip,
|
ChatType, GroupManip,
|
||||||
},
|
},
|
||||||
uid::Uid,
|
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
|
/// Reduced duration shown to the client to help alleviate latency issues
|
||||||
const PRESENTED_INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(30);
|
const PRESENTED_INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
// TODO: turn chat messages into enums
|
pub fn handle_invite(server: &mut Server, inviter: specs::Entity, invitee_uid: Uid, kind: InviteKind) {
|
||||||
pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupManip) {
|
|
||||||
let max_group_size = server.settings().max_player_group_size;
|
let max_group_size = server.settings().max_player_group_size;
|
||||||
let state = server.state_mut();
|
let state = server.state_mut();
|
||||||
|
let clients = state.ecs().read_storage::<Client>();
|
||||||
match manip {
|
let invitee = match state.ecs().entity_from_uid(invitee_uid.into()) {
|
||||||
GroupManip::Invite(uid) => {
|
Some(t) => t,
|
||||||
let clients = state.ecs().read_storage::<Client>();
|
None => {
|
||||||
let invitee = match state.ecs().entity_from_uid(uid.into()) {
|
// Inform of failure
|
||||||
Some(t) => t,
|
if let Some(client) = clients.get(inviter) {
|
||||||
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) {
|
|
||||||
client.send_fallible(ServerGeneral::server_msg(
|
client.send_fallible(ServerGeneral::server_msg(
|
||||||
ChatType::Meta,
|
ChatType::Meta,
|
||||||
"Can't invite, not a player or npc",
|
"Invite failed, target does not exist.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Notify inviter that the invite is pending
|
let uids = state.ecs().read_storage::<Uid>();
|
||||||
if invite_sent {
|
|
||||||
if let Some(client) = clients.get(entity) {
|
// Check if entity is trying to invite themselves
|
||||||
client.send_fallible(ServerGeneral::InvitePending(uid));
|
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 => {
|
GroupManip::Accept => {
|
||||||
|
let state = server.state_mut();
|
||||||
let clients = state.ecs().read_storage::<Client>();
|
let clients = state.ecs().read_storage::<Client>();
|
||||||
let uids = state.ecs().read_storage::<Uid>();
|
let uids = state.ecs().read_storage::<Uid>();
|
||||||
let mut invites = state.ecs().write_storage::<Invite>();
|
let mut invites = state.ecs().write_storage::<Invite>();
|
||||||
if let Some(inviter) = invites.remove(entity).and_then(|invite| {
|
if let Some((inviter, kind)) = invites.remove(entity).and_then(|invite| {
|
||||||
let inviter = invite.0;
|
let Invite { inviter, kind } = invite;
|
||||||
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
|
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
|
||||||
let pending = &mut pending_invites.get_mut(inviter)?.0;
|
let pending = &mut pending_invites.get_mut(inviter)?.0;
|
||||||
// Check that inviter has a pending invite and remove it from the list
|
// 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);
|
pending_invites.remove(inviter);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(inviter)
|
Some((inviter, kind))
|
||||||
}) {
|
}) {
|
||||||
if let (Some(client), Some(target)) =
|
if let (Some(client), Some(target)) =
|
||||||
(clients.get(inviter), uids.get(entity).copied())
|
(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 {
|
client.send_fallible(ServerGeneral::InviteComplete {
|
||||||
target,
|
target,
|
||||||
answer: InviteAnswer::Accepted,
|
answer: InviteAnswer::Accepted,
|
||||||
|
kind,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let mut group_manager = state.ecs().write_resource::<GroupManager>();
|
if let InviteKind::Group = kind {
|
||||||
group_manager.add_group_member(
|
let mut group_manager = state.ecs().write_resource::<GroupManager>();
|
||||||
inviter,
|
group_manager.add_group_member(
|
||||||
entity,
|
inviter,
|
||||||
&state.ecs().entities(),
|
entity,
|
||||||
&mut state.ecs().write_storage(),
|
&state.ecs().entities(),
|
||||||
&state.ecs().read_storage(),
|
&mut state.ecs().write_storage(),
|
||||||
&uids,
|
&state.ecs().read_storage(),
|
||||||
|entity, group_change| {
|
&uids,
|
||||||
clients
|
|entity, group_change| {
|
||||||
.get(entity)
|
clients
|
||||||
.and_then(|c| {
|
.get(entity)
|
||||||
group_change
|
.and_then(|c| {
|
||||||
.try_map(|e| uids.get(e).copied())
|
group_change
|
||||||
.map(|g| (g, c))
|
.try_map(|e| uids.get(e).copied())
|
||||||
})
|
.map(|g| (g, c))
|
||||||
.map(|(g, c)| c.send(ServerGeneral::GroupUpdate(g)));
|
})
|
||||||
},
|
.map(|(g, c)| c.send(ServerGeneral::GroupUpdate(g)));
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GroupManip::Decline => {
|
GroupManip::Decline => {
|
||||||
|
let state = server.state_mut();
|
||||||
let clients = state.ecs().read_storage::<Client>();
|
let clients = state.ecs().read_storage::<Client>();
|
||||||
let uids = state.ecs().read_storage::<Uid>();
|
let uids = state.ecs().read_storage::<Uid>();
|
||||||
let mut invites = state.ecs().write_storage::<Invite>();
|
let mut invites = state.ecs().write_storage::<Invite>();
|
||||||
if let Some(inviter) = invites.remove(entity).and_then(|invite| {
|
if let Some((inviter, kind)) = invites.remove(entity).and_then(|invite| {
|
||||||
let inviter = invite.0;
|
let Invite { inviter, kind } = invite;
|
||||||
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
|
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
|
||||||
let pending = &mut pending_invites.get_mut(inviter)?.0;
|
let pending = &mut pending_invites.get_mut(inviter)?.0;
|
||||||
// Check that inviter has a pending invite and remove it from the list
|
// 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);
|
pending_invites.remove(inviter);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(inviter)
|
Some((inviter, kind))
|
||||||
}) {
|
}) {
|
||||||
// Inform inviter of rejection
|
// Inform inviter of rejection
|
||||||
if let (Some(client), Some(target)) =
|
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 {
|
client.send_fallible(ServerGeneral::InviteComplete {
|
||||||
target,
|
target,
|
||||||
answer: InviteAnswer::Declined,
|
answer: InviteAnswer::Declined,
|
||||||
|
kind,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GroupManip::Leave => {
|
GroupManip::Leave => {
|
||||||
|
let state = server.state_mut();
|
||||||
let clients = state.ecs().read_storage::<Client>();
|
let clients = state.ecs().read_storage::<Client>();
|
||||||
let uids = state.ecs().read_storage::<Uid>();
|
let uids = state.ecs().read_storage::<Uid>();
|
||||||
let mut group_manager = state.ecs().write_resource::<GroupManager>();
|
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) => {
|
GroupManip::Kick(uid) => {
|
||||||
|
let state = server.state_mut();
|
||||||
let clients = state.ecs().read_storage::<Client>();
|
let clients = state.ecs().read_storage::<Client>();
|
||||||
let uids = state.ecs().read_storage::<Uid>();
|
let uids = state.ecs().read_storage::<Uid>();
|
||||||
let alignments = state.ecs().read_storage::<comp::Alignment>();
|
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) => {
|
GroupManip::AssignLeader(uid) => {
|
||||||
|
let state = server.state_mut();
|
||||||
let clients = state.ecs().read_storage::<Client>();
|
let clients = state.ecs().read_storage::<Client>();
|
||||||
let uids = state.ecs().read_storage::<Uid>();
|
let uids = state.ecs().read_storage::<Uid>();
|
||||||
let target = match state.ecs().entity_from_uid(uid.into()) {
|
let target = match state.ecs().entity_from_uid(uid.into()) {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use specs::{world::WorldExt, Entity as EcsEntity};
|
use specs::{world::WorldExt, Entity as EcsEntity};
|
||||||
use tracing::error;
|
use tracing::{error, warn};
|
||||||
|
|
||||||
use common::{
|
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,
|
consts::MAX_MOUNT_RANGE,
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
};
|
};
|
||||||
@ -10,6 +10,7 @@ use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::Client,
|
client::Client,
|
||||||
|
events::group_manip::handle_invite,
|
||||||
presence::{Presence, RegionSubscription},
|
presence::{Presence, RegionSubscription},
|
||||||
Server,
|
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) {
|
pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) {
|
||||||
let state = server.state_mut();
|
let state = server.state_mut();
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ use entity_manipulation::{
|
|||||||
};
|
};
|
||||||
use group_manip::handle_group;
|
use group_manip::handle_group;
|
||||||
use interaction::{
|
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 inventory_manip::handle_inventory;
|
||||||
use player::{handle_client_disconnect, handle_exit_ingame};
|
use player::{handle_client_disconnect, handle_exit_ingame};
|
||||||
@ -103,6 +103,9 @@ 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) => {
|
||||||
|
handle_initiate_trade(self, interactor, target)
|
||||||
|
},
|
||||||
ServerEvent::Mount(mounter, mountee) => handle_mount(self, mounter, mountee),
|
ServerEvent::Mount(mounter, mountee) => handle_mount(self, mounter, mountee),
|
||||||
ServerEvent::Unmount(mounter) => handle_unmount(self, mounter),
|
ServerEvent::Unmount(mounter) => handle_unmount(self, mounter),
|
||||||
ServerEvent::Possess(possessor_uid, possesse_uid) => {
|
ServerEvent::Possess(possessor_uid, possesse_uid) => {
|
||||||
|
@ -32,13 +32,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
let timed_out_invites = (&entities, &invites)
|
let timed_out_invites = (&entities, &invites)
|
||||||
.join()
|
.join()
|
||||||
.filter_map(|(invitee, Invite(inviter))| {
|
.filter_map(|(invitee, Invite { inviter, kind })| {
|
||||||
// Retrieve timeout invite from pending invites
|
// Retrieve timeout invite from pending invites
|
||||||
let pending = &mut pending_invites.get_mut(*inviter)?.0;
|
let pending = &mut pending_invites.get_mut(*inviter)?.0;
|
||||||
let index = pending.iter().position(|p| p.0 == invitee)?;
|
let index = pending.iter().position(|p| p.0 == invitee)?;
|
||||||
|
|
||||||
// Stop if not timed out
|
// Stop if not timed out
|
||||||
if pending[index].1 > now {
|
if pending[index].2 > now {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +57,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
client.send_fallible(ServerGeneral::InviteComplete {
|
client.send_fallible(ServerGeneral::InviteComplete {
|
||||||
target,
|
target,
|
||||||
answer: InviteAnswer::TimedOut,
|
answer: InviteAnswer::TimedOut,
|
||||||
|
kind: *kind,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ use crate::{
|
|||||||
use client::{self, Client};
|
use client::{self, Client};
|
||||||
use common::{
|
use common::{
|
||||||
combat,
|
combat,
|
||||||
comp::{group::Role, BuffKind, Stats},
|
comp::{group::{InviteKind, Role}, BuffKind, Stats},
|
||||||
uid::{Uid, UidAllocator},
|
uid::{Uid, UidAllocator},
|
||||||
};
|
};
|
||||||
use common_net::sync::WorldSyncExt;
|
use common_net::sync::WorldSyncExt;
|
||||||
@ -219,7 +219,7 @@ impl<'a> Widget for Group<'a> {
|
|||||||
.crop_kids()
|
.crop_kids()
|
||||||
.set(state.ids.bg, ui);
|
.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
|
// Group Menu button
|
||||||
Button::image(self.imgs.group_icon)
|
Button::image(self.imgs.group_icon)
|
||||||
.w_h(49.0, 26.0)
|
.w_h(49.0, 26.0)
|
||||||
@ -792,16 +792,26 @@ impl<'a> Widget for Group<'a> {
|
|||||||
// into the maximum group size.
|
// 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
|
self.show.group = true; // Auto open group menu
|
||||||
// TODO: add group name here too
|
// TODO: add group name here too
|
||||||
// Invite text
|
// Invite text
|
||||||
|
|
||||||
let name = uid_to_name_text(invite_uid, &self.client);
|
let name = uid_to_name_text(invite_uid, &self.client);
|
||||||
let invite_text = self
|
let invite_text = match kind {
|
||||||
.localized_strings
|
InviteKind::Group => {
|
||||||
.get("hud.group.invite_to_join")
|
self
|
||||||
.replace("{name}", &name);
|
.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)
|
Text::new(&invite_text)
|
||||||
.mid_top_with_margin_on(state.ids.bg, 5.0)
|
.mid_top_with_margin_on(state.ids.bg, 5.0)
|
||||||
.font_size(12)
|
.font_size(12)
|
||||||
|
@ -2810,10 +2810,6 @@ impl Hud {
|
|||||||
self.show.toggle_bag();
|
self.show.toggle_bag();
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
GameInput::Trade if state => {
|
|
||||||
self.show.toggle_trade();
|
|
||||||
true
|
|
||||||
},
|
|
||||||
GameInput::Social if state => {
|
GameInput::Social if state => {
|
||||||
self.show.toggle_social();
|
self.show.toggle_social();
|
||||||
true
|
true
|
||||||
|
@ -6,9 +6,6 @@ use super::{
|
|||||||
util::loadout_slot_text,
|
util::loadout_slot_text,
|
||||||
Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, QUALITY_COMMON, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, QUALITY_COMMON, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
||||||
};
|
};
|
||||||
use common::{
|
|
||||||
comp::{item::Quality,},
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
hud::get_quality_col,
|
hud::get_quality_col,
|
||||||
i18n::Localization,
|
i18n::Localization,
|
||||||
@ -19,6 +16,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use client::Client;
|
use client::Client;
|
||||||
|
use common::comp::item::Quality;
|
||||||
use conrod_core::{
|
use conrod_core::{
|
||||||
color,
|
color,
|
||||||
widget::{self, Button, Image, Rectangle, Scrollbar, Text},
|
widget::{self, Button, Image, Rectangle, Scrollbar, Text},
|
||||||
@ -129,36 +127,28 @@ impl<'a> Widget for Trade<'a> {
|
|||||||
|
|
||||||
// BG
|
// BG
|
||||||
Image::new(self.imgs.inv_bg_bag)
|
Image::new(self.imgs.inv_bg_bag)
|
||||||
.w_h(424.0, 708.0)
|
.w_h(424.0, 708.0)
|
||||||
.middle()
|
.middle()
|
||||||
.color(Some(UI_MAIN))
|
.color(Some(UI_MAIN))
|
||||||
.set(state.ids.bg, ui);
|
.set(state.ids.bg, ui);
|
||||||
Image::new(self.imgs.inv_frame_bag)
|
Image::new(self.imgs.inv_frame_bag)
|
||||||
.w_h(424.0, 708.0)
|
.w_h(424.0, 708.0)
|
||||||
.middle_of(state.ids.bg)
|
.middle_of(state.ids.bg)
|
||||||
.color(Some(UI_HIGHLIGHT_0))
|
.color(Some(UI_HIGHLIGHT_0))
|
||||||
.set(state.ids.bg_frame, ui);
|
.set(state.ids.bg_frame, ui);
|
||||||
// Title
|
// Title
|
||||||
Text::new(
|
Text::new(&self.localized_strings.get("hud.trade.trade_window"))
|
||||||
&self
|
.mid_top_with_margin_on(state.ids.bg_frame, 9.0)
|
||||||
.localized_strings
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
.get("hud.trade.trade_window")
|
.font_size(self.fonts.cyri.scale(20))
|
||||||
)
|
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
||||||
.mid_top_with_margin_on(state.ids.bg_frame, 9.0)
|
.set(state.ids.trade_title_bg, ui);
|
||||||
.font_id(self.fonts.cyri.conrod_id)
|
Text::new(&self.localized_strings.get("hud.trade.trade_window"))
|
||||||
.font_size(self.fonts.cyri.scale(20))
|
.top_left_with_margins_on(state.ids.trade_title_bg, 2.0, 2.0)
|
||||||
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
.set(state.ids.trade_title_bg, ui);
|
.font_size(self.fonts.cyri.scale(20))
|
||||||
Text::new(
|
.color(TEXT_COLOR)
|
||||||
&self
|
.set(state.ids.trade_title, ui);
|
||||||
.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
|
event
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ use client::{self, Client};
|
|||||||
use common::{
|
use common::{
|
||||||
assets::AssetExt,
|
assets::AssetExt,
|
||||||
comp,
|
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},
|
consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
span,
|
span,
|
||||||
@ -20,7 +20,7 @@ use common::{
|
|||||||
},
|
},
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use common_net::msg::PresenceKind;
|
use common_net::msg::{server::InviteAnswer, PresenceKind};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
audio::sfx::SfxEvent,
|
audio::sfx::SfxEvent,
|
||||||
@ -121,6 +121,24 @@ impl SessionState {
|
|||||||
client::Event::Chat(m) => {
|
client::Event::Chat(m) => {
|
||||||
self.hud.new_message(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) => {
|
client::Event::InventoryUpdated(inv_event) => {
|
||||||
let sfx_triggers = self.scene.sfx_mgr.triggers.read();
|
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) => {
|
/*Event::InputUpdate(GameInput::Charge, state) => {
|
||||||
self.inputs.charge.set_state(state);
|
self.inputs.charge.set_state(state);
|
||||||
},*/
|
},*/
|
||||||
|
Loading…
Reference in New Issue
Block a user