From c0f9b19928c00bacb59af33af72f0a1eed417e8e Mon Sep 17 00:00:00 2001
From: Kemper <9278383-Kemper-@users.noreply.gitlab.com>
Date: Mon, 25 Oct 2021 00:09:54 +0000
Subject: [PATCH] Add a range limit to trading

Prevents initiating trades with client-side ghosts.
---
 CHANGELOG.md                |  1 +
 common/src/consts.rs        |  1 +
 server/src/events/invite.rs | 18 +++++++++++++++++-
 3 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c3495c3ed1..f73fd13325 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Falling through an airship in flight should no longer be possible (although many issues with airship physics remain)
 - Avoided black hexagons when bloom is enabled by suppressing NaN/Inf pixels during the first bloom blur pass
 - Many know water generation problems
+- Trading over long distances using ghost characters or client-side exploits is no longer possible
 
 ## [0.11.0] - 2021-09-11
 
diff --git a/common/src/consts.rs b/common/src/consts.rs
index 00afd49eaa..591e068df7 100644
--- a/common/src/consts.rs
+++ b/common/src/consts.rs
@@ -1,6 +1,7 @@
 // The limit on distance between the entity and a collectible (squared)
 pub const MAX_PICKUP_RANGE: f32 = 5.0;
 pub const MAX_MOUNT_RANGE: f32 = 14.0;
+pub const MAX_TRADE_RANGE: f32 = 20.0;
 
 pub const GRAVITY: f32 = 25.0;
 pub const FRIC_GROUND: f32 = 0.15;
diff --git a/server/src/events/invite.rs b/server/src/events/invite.rs
index 1fd56d2ba5..47b01728bd 100644
--- a/server/src/events/invite.rs
+++ b/server/src/events/invite.rs
@@ -6,8 +6,9 @@ use common::{
         agent::{Agent, AgentEvent},
         group::GroupManager,
         invite::{Invite, InviteKind, InviteResponse, PendingInvites},
-        ChatType,
+        ChatType, Pos,
     },
+    consts::MAX_TRADE_RANGE,
     trade::{TradeResult, Trades},
     uid::Uid,
 };
@@ -63,6 +64,14 @@ pub fn handle_invite(
     let mut agents = state.ecs().write_storage::<comp::Agent>();
     let mut invites = state.ecs().write_storage::<Invite>();
 
+    if let InviteKind::Trade = kind {
+        // Check whether the inviter is in range of the invitee
+        let positions = state.ecs().read_storage::<comp::Pos>();
+        if !within_trading_range(positions.get(inviter), positions.get(invitee)) {
+            return;
+        }
+    }
+
     if let InviteKind::Group = kind {
         if !group_manip::can_invite(
             state,
@@ -336,3 +345,10 @@ pub fn handle_invite_decline(server: &mut Server, entity: specs::Entity) {
         handle_invite_answer(state, inviter, entity, InviteAnswer::Declined, kind)
     }
 }
+
+fn within_trading_range(requester_position: Option<&Pos>, invitee_position: Option<&Pos>) -> bool {
+    match (requester_position, invitee_position) {
+        (Some(rpos), Some(ipos)) => rpos.0.distance_squared(ipos.0) < MAX_TRADE_RANGE.powi(2),
+        _ => false,
+    }
+}