veloren_server/events/
invite.rs

1use super::{
2    ServerEvent, event_dispatch,
3    group_manip::{self, update_map_markers},
4};
5use crate::{Settings, client::Client};
6use common::{
7    comp::{
8        self, CharacterState, ChatType, Content, Group, Health, Pos,
9        agent::{Agent, AgentEvent},
10        group::GroupManager,
11        invite::{Invite, InviteKind, InviteResponse, PendingInvites},
12    },
13    consts::MAX_TRADE_RANGE,
14    event::{InitiateInviteEvent, InviteResponseEvent},
15    trade::{TradeResult, Trades},
16    uid::{IdMaps, Uid},
17};
18use common_net::msg::{InviteAnswer, ServerGeneral};
19#[cfg(feature = "worldgen")]
20use specs::ReadExpect;
21use specs::{
22    DispatcherBuilder, Entities, Entity, Read, ReadStorage, SystemData, Write, WriteStorage, shred,
23};
24use std::time::{Duration, Instant};
25use tracing::{error, warn};
26#[cfg(feature = "worldgen")]
27use world::IndexOwned;
28
29/// Time before invite times out
30const INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(31);
31/// Reduced duration shown to the client to help alleviate latency issues
32const PRESENTED_INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(30);
33
34pub(super) fn register_event_systems(builder: &mut DispatcherBuilder) {
35    event_dispatch::<InitiateInviteEvent>(builder, &[]);
36    event_dispatch::<InviteResponseEvent>(builder, &[]);
37}
38
39impl ServerEvent for InitiateInviteEvent {
40    type SystemData<'a> = (
41        Entities<'a>,
42        Write<'a, Trades>,
43        Read<'a, Settings>,
44        Read<'a, IdMaps>,
45        Read<'a, GroupManager>,
46        WriteStorage<'a, PendingInvites>,
47        WriteStorage<'a, Agent>,
48        WriteStorage<'a, Invite>,
49        ReadStorage<'a, Uid>,
50        ReadStorage<'a, Client>,
51        ReadStorage<'a, Pos>,
52        ReadStorage<'a, Group>,
53        ReadStorage<'a, Health>,
54        ReadStorage<'a, CharacterState>,
55    );
56
57    fn handle(
58        events: impl ExactSizeIterator<Item = Self>,
59        (
60            entities,
61            mut trades,
62            settings,
63            id_maps,
64            group_manager,
65            mut pending_invites,
66            mut agents,
67            mut invites,
68            uids,
69            clients,
70            positions,
71            groups,
72            healths,
73            character_states,
74        ): Self::SystemData<'_>,
75    ) {
76        for InitiateInviteEvent(inviter, invitee_uid, kind) in events {
77            let max_group_size = settings.max_player_group_size;
78            let invitee = match id_maps.uid_entity(invitee_uid) {
79                Some(t) => t,
80                None => {
81                    // Inform of failure
82                    if let Some(client) = clients.get(inviter) {
83                        client.send_fallible(ServerGeneral::server_msg(
84                            ChatType::Meta,
85                            Content::Plain("Invite failed, target does not exist.".to_string()),
86                        ));
87                    }
88                    continue;
89                },
90            };
91
92            // Check if entity is trying to invite themselves
93            if uids
94                .get(inviter)
95                .is_some_and(|inviter_uid| *inviter_uid == invitee_uid)
96            {
97                warn!("Entity tried to invite themselves into a group/trade");
98                continue;
99            }
100
101            if matches!(kind, InviteKind::Trade) {
102                let is_alive_and_well = |entity| {
103                    entities.is_alive(entity)
104                        && !comp::is_downed_or_dead(
105                            healths.get(entity),
106                            character_states.get(entity),
107                        )
108                };
109                // Check whether the inviter is in range of the invitee or dead
110                if !within_trading_range(positions.get(inviter), positions.get(invitee))
111                    || !is_alive_and_well(inviter)
112                    || !is_alive_and_well(invitee)
113                {
114                    continue;
115                }
116            }
117
118            if let InviteKind::Group = kind {
119                if !group_manip::can_invite(
120                    &clients,
121                    &groups,
122                    &group_manager,
123                    &mut pending_invites,
124                    max_group_size,
125                    inviter,
126                    invitee,
127                ) {
128                    continue;
129                }
130            } else {
131                // cancel current trades for inviter before inviting someone else to trade
132                if let Some(inviter_uid) = uids.get(inviter).copied()
133                    && let Some(active_trade) = trades.entity_trades.get(&inviter_uid).copied()
134                {
135                    trades
136                        .decline_trade(active_trade, inviter_uid)
137                        .and_then(|u| id_maps.uid_entity(u))
138                        .map(|e| {
139                            if let Some(client) = clients.get(e) {
140                                client.send_fallible(ServerGeneral::FinishedTrade(
141                                    TradeResult::Declined,
142                                ));
143                            }
144                            if let Some(agent) = agents.get_mut(e) {
145                                agent
146                                    .inbox
147                                    .push_back(AgentEvent::FinishedTrade(TradeResult::Declined));
148                            }
149                        });
150                };
151            }
152
153            if invites.contains(invitee) {
154                // Inform inviter that there is already an invite
155                if let Some(client) = clients.get(inviter) {
156                    client.send_fallible(ServerGeneral::server_msg(
157                        ChatType::Meta,
158                        Content::Plain("This player already has a pending invite.".to_string()),
159                    ));
160                }
161                continue;
162            }
163
164            let mut invite_sent = false;
165            // Returns true if insertion was successful
166            let mut send_invite = || {
167                match invites.insert(invitee, Invite { inviter, kind }) {
168                    Err(err) => {
169                        error!("Failed to insert Invite component: {:?}", err);
170                        false
171                    },
172                    Ok(_) => {
173                        match pending_invites.entry(inviter) {
174                            Ok(entry) => {
175                                entry.or_insert_with(|| PendingInvites(Vec::new())).0.push((
176                                    invitee,
177                                    kind,
178                                    Instant::now() + INVITE_TIMEOUT_DUR,
179                                ));
180                                invite_sent = true;
181                                true
182                            },
183                            Err(err) => {
184                                error!(
185                                    "Failed to get entry for pending invites component: {:?}",
186                                    err
187                                );
188                                // Cleanup
189                                invites.remove(invitee);
190                                false
191                            },
192                        }
193                    },
194                }
195            };
196
197            // If client comp
198            if let (Some(client), Some(inviter)) =
199                (clients.get(invitee), uids.get(inviter).copied())
200            {
201                if send_invite() {
202                    client.send_fallible(ServerGeneral::Invite {
203                        inviter,
204                        timeout: PRESENTED_INVITE_TIMEOUT_DUR,
205                        kind,
206                    });
207                }
208            } else if let Some(agent) = agents.get_mut(invitee) {
209                if send_invite()
210                    && let Some(inviter) = uids.get(inviter)
211                {
212                    agent.inbox.push_back(AgentEvent::TradeInvite(*inviter));
213                    invite_sent = true;
214                }
215            } else if let Some(client) = clients.get(inviter) {
216                client.send_fallible(ServerGeneral::server_msg(
217                    ChatType::Meta,
218                    Content::Plain("Can't invite, not a player or npc".to_string()),
219                ));
220            }
221
222            // Notify inviter that the invite is pending
223            if invite_sent && let Some(client) = clients.get(inviter) {
224                client.send_fallible(ServerGeneral::InvitePending(invitee_uid));
225            }
226        }
227    }
228}
229
230#[derive(SystemData)]
231pub struct InviteResponseData<'a> {
232    entities: Entities<'a>,
233    group_manager: Write<'a, GroupManager>,
234    trades: Write<'a, Trades>,
235    #[cfg(feature = "worldgen")]
236    index: ReadExpect<'a, IndexOwned>,
237    id_maps: Read<'a, IdMaps>,
238    invites: WriteStorage<'a, Invite>,
239    pending_invites: WriteStorage<'a, PendingInvites>,
240    groups: WriteStorage<'a, Group>,
241    agents: WriteStorage<'a, comp::Agent>,
242    uids: ReadStorage<'a, Uid>,
243    clients: ReadStorage<'a, Client>,
244    alignments: ReadStorage<'a, comp::Alignment>,
245    map_markers: ReadStorage<'a, comp::MapMarker>,
246}
247
248impl ServerEvent for InviteResponseEvent {
249    type SystemData<'a> = InviteResponseData<'a>;
250
251    fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
252        for InviteResponseEvent(entity, response) in events {
253            match response {
254                InviteResponse::Accept => handle_invite_accept(&mut data, entity),
255                InviteResponse::Decline => handle_invite_decline(&mut data, entity),
256            }
257        }
258    }
259}
260
261pub fn handle_invite_accept(data: &mut InviteResponseData, entity: Entity) {
262    if let Some((inviter, kind)) = get_inviter_and_kind(entity, data) {
263        handle_invite_answer(data, inviter, entity, InviteAnswer::Accepted, kind);
264
265        match kind {
266            InviteKind::Group => {
267                data.group_manager.add_group_member(
268                    inviter,
269                    entity,
270                    &data.entities,
271                    &mut data.groups,
272                    &data.alignments,
273                    &data.uids,
274                    |entity, group_change| {
275                        data.clients
276                            .get(entity)
277                            .and_then(|c| {
278                                group_change
279                                    .try_map_ref(|e| data.uids.get(*e).copied())
280                                    .map(|g| (g, c))
281                            })
282                            .map(|(g, c)| {
283                                update_map_markers(&data.map_markers, &data.uids, c, &group_change);
284                                c.send_fallible(ServerGeneral::GroupUpdate(g));
285                            });
286                    },
287                );
288            },
289            InviteKind::Trade => {
290                if let (Some(inviter_uid), Some(invitee_uid)) = (
291                    data.uids.get(inviter).copied(),
292                    data.uids.get(entity).copied(),
293                ) {
294                    // check if the person that invited me has started a new trade since the
295                    // invitation was sent
296                    if data
297                        .trades
298                        .entity_trades
299                        .get(&inviter_uid)
300                        .copied()
301                        .is_some()
302                    {
303                        for client in data
304                            .clients
305                            .get(entity)
306                            .into_iter()
307                            .chain(data.clients.get(inviter))
308                        {
309                            client.send_fallible(ServerGeneral::server_msg(
310                                ChatType::Meta,
311                                Content::Plain(
312                                    "Trade failed, inviter initiated new trade since sending \
313                                     trade request."
314                                        .to_string(),
315                                ),
316                            ));
317                        }
318                        return;
319                    }
320                    let id = data.trades.begin_trade(inviter_uid, invitee_uid);
321                    let trade = data.trades.trades[&id].clone();
322                    if let Some(agent) = data.agents.get_mut(inviter) {
323                        agent
324                            .inbox
325                            .push_back(AgentEvent::TradeAccepted(invitee_uid));
326                    }
327                    #[cfg(feature = "worldgen")]
328                    let pricing = data
329                        .agents
330                        .get(inviter)
331                        .and_then(|a| {
332                            a.behavior
333                                .trade_site()
334                                .and_then(|id| data.index.get_site_prices(id))
335                        })
336                        .or_else(|| {
337                            data.agents.get(entity).and_then(|a| {
338                                a.behavior
339                                    .trade_site()
340                                    .and_then(|id| data.index.get_site_prices(id))
341                            })
342                        });
343                    #[cfg(not(feature = "worldgen"))]
344                    let pricing = None;
345
346                    data.clients.get(inviter).map(|c| {
347                        c.send(ServerGeneral::UpdatePendingTrade(
348                            id,
349                            trade.clone(),
350                            pricing.clone(),
351                        ))
352                    });
353                    data.clients
354                        .get(entity)
355                        .map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade, pricing)));
356                }
357            },
358        }
359    }
360}
361
362fn get_inviter_and_kind(
363    entity: Entity,
364    data: &mut InviteResponseData,
365) -> Option<(Entity, InviteKind)> {
366    data.invites.remove(entity).and_then(|invite| {
367        let Invite { inviter, kind } = invite;
368        let pending = &mut data.pending_invites.get_mut(inviter)?.0;
369        // Check that inviter has a pending invite and remove it from the list
370        let invite_index = pending.iter().position(|p| p.0 == entity)?;
371        pending.swap_remove(invite_index);
372        // If no pending invites remain remove the component
373        if pending.is_empty() {
374            data.pending_invites.remove(inviter);
375        }
376
377        Some((inviter, kind))
378    })
379}
380
381fn handle_invite_answer(
382    data: &mut InviteResponseData,
383    inviter: Entity,
384    entity: Entity,
385    invite_answer: InviteAnswer,
386    kind: InviteKind,
387) {
388    if matches!(kind, InviteKind::Trade) && matches!(invite_answer, InviteAnswer::Accepted) {
389        // invitee must close current trade if one exists before accepting new one
390        if let Some(invitee_uid) = data.uids.get(entity).copied()
391            && let Some(active_trade) = data.trades.entity_trades.get(&invitee_uid).copied()
392        {
393            data.trades
394                .decline_trade(active_trade, invitee_uid)
395                .and_then(|u| data.id_maps.uid_entity(u))
396                .map(|e| {
397                    if let Some(client) = data.clients.get(e) {
398                        client.send_fallible(ServerGeneral::FinishedTrade(TradeResult::Declined));
399                    }
400                    if let Some(agent) = data.agents.get_mut(e) {
401                        agent
402                            .inbox
403                            .push_back(AgentEvent::FinishedTrade(TradeResult::Declined));
404                    }
405                });
406        };
407    }
408    if let (Some(client), Some(target)) =
409        (data.clients.get(inviter), data.uids.get(entity).copied())
410    {
411        client.send_fallible(ServerGeneral::InviteComplete {
412            target,
413            answer: invite_answer,
414            kind,
415        });
416    }
417}
418
419pub fn handle_invite_decline(data: &mut InviteResponseData, entity: Entity) {
420    if let Some((inviter, kind)) = get_inviter_and_kind(entity, data) {
421        // Inform inviter of rejection
422        handle_invite_answer(data, inviter, entity, InviteAnswer::Declined, kind)
423    }
424}
425
426fn within_trading_range(requester_position: Option<&Pos>, invitee_position: Option<&Pos>) -> bool {
427    match (requester_position, invitee_position) {
428        (Some(rpos), Some(ipos)) => rpos.0.distance_squared(ipos.0) < MAX_TRADE_RANGE.powi(2),
429        _ => false,
430    }
431}