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 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 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 let invite_index = pending.iter().position(|p| p.0 == entity)?;
371 pending.swap_remove(invite_index);
372 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 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 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}