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
29const INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(31);
31const 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 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 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 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 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 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 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 invites.remove(invitee);
190 false
191 },
192 }
193 },
194 }
195 };
196
197 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 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 group_change
276 .try_map_ref(|e| data.uids.get(*e).copied())
277 .zip(data.clients.get(entity))
278 .map(|(g, c)| {
279 update_map_markers(&data.map_markers, &data.uids, c, &group_change);
280 c.send_fallible(ServerGeneral::GroupUpdate(g));
281 });
282 },
283 );
284 },
285 InviteKind::Trade => {
286 if let (Some(inviter_uid), Some(invitee_uid)) = (
287 data.uids.get(inviter).copied(),
288 data.uids.get(entity).copied(),
289 ) {
290 if data
293 .trades
294 .entity_trades
295 .get(&inviter_uid)
296 .copied()
297 .is_some()
298 {
299 for client in data
300 .clients
301 .get(entity)
302 .into_iter()
303 .chain(data.clients.get(inviter))
304 {
305 client.send_fallible(ServerGeneral::server_msg(
306 ChatType::Meta,
307 Content::Plain(
308 "Trade failed, inviter initiated new trade since sending \
309 trade request."
310 .to_string(),
311 ),
312 ));
313 }
314 return;
315 }
316 let id = data.trades.begin_trade(inviter_uid, invitee_uid);
317 let trade = data.trades.trades[&id].clone();
318 if let Some(agent) = data.agents.get_mut(inviter) {
319 agent
320 .inbox
321 .push_back(AgentEvent::TradeAccepted(invitee_uid));
322 }
323 #[cfg(feature = "worldgen")]
324 let pricing = data
325 .agents
326 .get(inviter)
327 .and_then(|a| {
328 a.behavior
329 .trade_site()
330 .and_then(|id| data.index.get_site_prices(id))
331 })
332 .or_else(|| {
333 data.agents.get(entity).and_then(|a| {
334 a.behavior
335 .trade_site()
336 .and_then(|id| data.index.get_site_prices(id))
337 })
338 });
339 #[cfg(not(feature = "worldgen"))]
340 let pricing = None;
341
342 data.clients.get(inviter).map(|c| {
343 c.send(ServerGeneral::UpdatePendingTrade(
344 id,
345 trade.clone(),
346 pricing.clone(),
347 ))
348 });
349 data.clients
350 .get(entity)
351 .map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade, pricing)));
352 }
353 },
354 }
355 }
356}
357
358fn get_inviter_and_kind(
359 entity: Entity,
360 data: &mut InviteResponseData,
361) -> Option<(Entity, InviteKind)> {
362 data.invites.remove(entity).and_then(|invite| {
363 let Invite { inviter, kind } = invite;
364 let pending = &mut data.pending_invites.get_mut(inviter)?.0;
365 let invite_index = pending.iter().position(|p| p.0 == entity)?;
367 pending.swap_remove(invite_index);
368 if pending.is_empty() {
370 data.pending_invites.remove(inviter);
371 }
372
373 Some((inviter, kind))
374 })
375}
376
377fn handle_invite_answer(
378 data: &mut InviteResponseData,
379 inviter: Entity,
380 entity: Entity,
381 invite_answer: InviteAnswer,
382 kind: InviteKind,
383) {
384 if matches!(kind, InviteKind::Trade) && matches!(invite_answer, InviteAnswer::Accepted) {
385 if let Some(invitee_uid) = data.uids.get(entity).copied()
387 && let Some(active_trade) = data.trades.entity_trades.get(&invitee_uid).copied()
388 {
389 data.trades
390 .decline_trade(active_trade, invitee_uid)
391 .and_then(|u| data.id_maps.uid_entity(u))
392 .map(|e| {
393 if let Some(client) = data.clients.get(e) {
394 client.send_fallible(ServerGeneral::FinishedTrade(TradeResult::Declined));
395 }
396 if let Some(agent) = data.agents.get_mut(e) {
397 agent
398 .inbox
399 .push_back(AgentEvent::FinishedTrade(TradeResult::Declined));
400 }
401 });
402 };
403 }
404 if let (Some(client), Some(target)) =
405 (data.clients.get(inviter), data.uids.get(entity).copied())
406 {
407 client.send_fallible(ServerGeneral::InviteComplete {
408 target,
409 answer: invite_answer,
410 kind,
411 });
412 }
413}
414
415pub fn handle_invite_decline(data: &mut InviteResponseData, entity: Entity) {
416 if let Some((inviter, kind)) = get_inviter_and_kind(entity, data) {
417 handle_invite_answer(data, inviter, entity, InviteAnswer::Declined, kind)
419 }
420}
421
422fn within_trading_range(requester_position: Option<&Pos>, invitee_position: Option<&Pos>) -> bool {
423 match (requester_position, invitee_position) {
424 (Some(rpos), Some(ipos)) => rpos.0.distance_squared(ipos.0) < MAX_TRADE_RANGE.powi(2),
425 _ => false,
426 }
427}