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 if let Some(active_trade) = trades.entity_trades.get(&inviter_uid).copied() {
134 trades
135 .decline_trade(active_trade, inviter_uid)
136 .and_then(|u| id_maps.uid_entity(u))
137 .map(|e| {
138 if let Some(client) = clients.get(e) {
139 client.send_fallible(ServerGeneral::FinishedTrade(
140 TradeResult::Declined,
141 ));
142 }
143 if let Some(agent) = agents.get_mut(e) {
144 agent.inbox.push_back(AgentEvent::FinishedTrade(
145 TradeResult::Declined,
146 ));
147 }
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 if let Some(inviter) = uids.get(inviter) {
211 agent.inbox.push_back(AgentEvent::TradeInvite(*inviter));
212 invite_sent = true;
213 }
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 {
224 if let Some(client) = clients.get(inviter) {
225 client.send_fallible(ServerGeneral::InvitePending(invitee_uid));
226 }
227 }
228 }
229 }
230}
231
232#[derive(SystemData)]
233pub struct InviteResponseData<'a> {
234 entities: Entities<'a>,
235 group_manager: Write<'a, GroupManager>,
236 trades: Write<'a, Trades>,
237 #[cfg(feature = "worldgen")]
238 index: ReadExpect<'a, IndexOwned>,
239 id_maps: Read<'a, IdMaps>,
240 invites: WriteStorage<'a, Invite>,
241 pending_invites: WriteStorage<'a, PendingInvites>,
242 groups: WriteStorage<'a, Group>,
243 agents: WriteStorage<'a, comp::Agent>,
244 uids: ReadStorage<'a, Uid>,
245 clients: ReadStorage<'a, Client>,
246 alignments: ReadStorage<'a, comp::Alignment>,
247 map_markers: ReadStorage<'a, comp::MapMarker>,
248}
249
250impl ServerEvent for InviteResponseEvent {
251 type SystemData<'a> = InviteResponseData<'a>;
252
253 fn handle(events: impl ExactSizeIterator<Item = Self>, mut data: Self::SystemData<'_>) {
254 for InviteResponseEvent(entity, response) in events {
255 match response {
256 InviteResponse::Accept => handle_invite_accept(&mut data, entity),
257 InviteResponse::Decline => handle_invite_decline(&mut data, entity),
258 }
259 }
260 }
261}
262
263pub fn handle_invite_accept(data: &mut InviteResponseData, entity: Entity) {
264 if let Some((inviter, kind)) = get_inviter_and_kind(entity, data) {
265 handle_invite_answer(data, inviter, entity, InviteAnswer::Accepted, kind);
266
267 match kind {
268 InviteKind::Group => {
269 data.group_manager.add_group_member(
270 inviter,
271 entity,
272 &data.entities,
273 &mut data.groups,
274 &data.alignments,
275 &data.uids,
276 |entity, group_change| {
277 data.clients
278 .get(entity)
279 .and_then(|c| {
280 group_change
281 .try_map_ref(|e| data.uids.get(*e).copied())
282 .map(|g| (g, c))
283 })
284 .map(|(g, c)| {
285 update_map_markers(&data.map_markers, &data.uids, c, &group_change);
286 c.send_fallible(ServerGeneral::GroupUpdate(g));
287 });
288 },
289 );
290 },
291 InviteKind::Trade => {
292 if let (Some(inviter_uid), Some(invitee_uid)) = (
293 data.uids.get(inviter).copied(),
294 data.uids.get(entity).copied(),
295 ) {
296 if data
299 .trades
300 .entity_trades
301 .get(&inviter_uid)
302 .copied()
303 .is_some()
304 {
305 for client in data
306 .clients
307 .get(entity)
308 .into_iter()
309 .chain(data.clients.get(inviter))
310 {
311 client.send_fallible(ServerGeneral::server_msg(
312 ChatType::Meta,
313 Content::Plain(
314 "Trade failed, inviter initiated new trade since sending \
315 trade request."
316 .to_string(),
317 ),
318 ));
319 }
320 return;
321 }
322 let id = data.trades.begin_trade(inviter_uid, invitee_uid);
323 let trade = data.trades.trades[&id].clone();
324 if let Some(agent) = data.agents.get_mut(inviter) {
325 agent
326 .inbox
327 .push_back(AgentEvent::TradeAccepted(invitee_uid));
328 }
329 #[cfg(feature = "worldgen")]
330 let pricing = data
331 .agents
332 .get(inviter)
333 .and_then(|a| {
334 a.behavior
335 .trade_site()
336 .and_then(|id| data.index.get_site_prices(id))
337 })
338 .or_else(|| {
339 data.agents.get(entity).and_then(|a| {
340 a.behavior
341 .trade_site()
342 .and_then(|id| data.index.get_site_prices(id))
343 })
344 });
345 #[cfg(not(feature = "worldgen"))]
346 let pricing = None;
347
348 data.clients.get(inviter).map(|c| {
349 c.send(ServerGeneral::UpdatePendingTrade(
350 id,
351 trade.clone(),
352 pricing.clone(),
353 ))
354 });
355 data.clients
356 .get(entity)
357 .map(|c| c.send(ServerGeneral::UpdatePendingTrade(id, trade, pricing)));
358 }
359 },
360 }
361 }
362}
363
364fn get_inviter_and_kind(
365 entity: Entity,
366 data: &mut InviteResponseData,
367) -> Option<(Entity, InviteKind)> {
368 data.invites.remove(entity).and_then(|invite| {
369 let Invite { inviter, kind } = invite;
370 let pending = &mut data.pending_invites.get_mut(inviter)?.0;
371 let invite_index = pending.iter().position(|p| p.0 == entity)?;
373 pending.swap_remove(invite_index);
374 if pending.is_empty() {
376 data.pending_invites.remove(inviter);
377 }
378
379 Some((inviter, kind))
380 })
381}
382
383fn handle_invite_answer(
384 data: &mut InviteResponseData,
385 inviter: Entity,
386 entity: Entity,
387 invite_answer: InviteAnswer,
388 kind: InviteKind,
389) {
390 if matches!(kind, InviteKind::Trade) && matches!(invite_answer, InviteAnswer::Accepted) {
391 if let Some(invitee_uid) = data.uids.get(entity).copied() {
393 if let Some(active_trade) = data.trades.entity_trades.get(&invitee_uid).copied() {
394 data.trades
395 .decline_trade(active_trade, invitee_uid)
396 .and_then(|u| data.id_maps.uid_entity(u))
397 .map(|e| {
398 if let Some(client) = data.clients.get(e) {
399 client
400 .send_fallible(ServerGeneral::FinishedTrade(TradeResult::Declined));
401 }
402 if let Some(agent) = data.agents.get_mut(e) {
403 agent
404 .inbox
405 .push_back(AgentEvent::FinishedTrade(TradeResult::Declined));
406 }
407 });
408 }
409 };
410 }
411 if let (Some(client), Some(target)) =
412 (data.clients.get(inviter), data.uids.get(entity).copied())
413 {
414 client.send_fallible(ServerGeneral::InviteComplete {
415 target,
416 answer: invite_answer,
417 kind,
418 });
419 }
420}
421
422pub fn handle_invite_decline(data: &mut InviteResponseData, entity: Entity) {
423 if let Some((inviter, kind)) = get_inviter_and_kind(entity, data) {
424 handle_invite_answer(data, inviter, entity, InviteAnswer::Declined, kind)
426 }
427}
428
429fn within_trading_range(requester_position: Option<&Pos>, invitee_position: Option<&Pos>) -> bool {
430 match (requester_position, invitee_position) {
431 (Some(rpos), Some(ipos)) => rpos.0.distance_squared(ipos.0) < MAX_TRADE_RANGE.powi(2),
432 _ => false,
433 }
434}