use super::group_manip; use crate::{client::Client, Server}; use common::{ comp::{ self, agent::{Agent, AgentEvent}, 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::(); 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::(); // 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::(); if let InviteKind::Group = kind { if !group_manip::can_invite( state, &clients, &mut pending_invites, max_group_size, inviter, invitee, ) { return; } } let mut agents = state.ecs().write_storage::(); let mut invites = state.ecs().write_storage::(); 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 let Some(agent) = agents.get_mut(invitee) { if send_invite() { if let Some(inviter) = uids.get(inviter) { agent.inbox.push_front(AgentEvent::TradeInvite(*inviter)); invite_sent = true; } } } 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 index = server.index.clone(); let state = server.state_mut(); let clients = state.ecs().read_storage::(); let uids = state.ecs().read_storage::(); let mut agents = state.ecs().write_storage::(); let mut invites = state.ecs().write_storage::(); if let Some((inviter, kind)) = invites.remove(entity).and_then(|invite| { let Invite { inviter, kind } = invite; let mut pending_invites = state.ecs().write_storage::(); 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::(); 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::(); let id = trades.begin_trade(inviter_uid, invitee_uid); let trade = trades.trades[&id].clone(); if let Some(agent) = agents.get_mut(inviter) { agent .inbox .push_front(AgentEvent::TradeAccepted(invitee_uid)); } let pricing = agents .get(inviter) .and_then(|a| { a.behavior .trade_site .and_then(|id| index.get_site_prices(id)) }) .or_else(|| { agents.get(entity).and_then(|a| { a.behavior .trade_site .and_then(|id| index.get_site_prices(id)) }) }); clients.get(inviter).map(|c| { c.send(ServerGeneral::UpdatePendingTrade( id, trade.clone(), pricing.clone(), )) }); clients .get(entity) .map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade, pricing))); } }, } } } pub fn handle_invite_decline(server: &mut Server, entity: specs::Entity) { let state = server.state_mut(); let clients = state.ecs().read_storage::(); let uids = state.ecs().read_storage::(); let mut invites = state.ecs().write_storage::(); if let Some((inviter, kind)) = invites.remove(entity).and_then(|invite| { let Invite { inviter, kind } = invite; let mut pending_invites = state.ecs().write_storage::(); 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, }); } } }