From 60128c6b1e439a52cd3f74253f97d22e03d11a88 Mon Sep 17 00:00:00 2001 From: Marco Cawthorne Date: Mon, 7 Aug 2023 12:56:17 -0700 Subject: [PATCH] Server: add "Cheaters Lament", a proof of concept detection mechanism for suspicious player behaviour --- src/server/defs.h | 1 + src/server/entry.qc | 2 + src/server/include.src | 1 + src/server/lament.h | 17 ++++++ src/server/lament.qc | 117 ++++++++++++++++++++++++++++++++++++ src/shared/NSClientPlayer.h | 4 ++ 6 files changed, 142 insertions(+) create mode 100644 src/server/lament.h create mode 100644 src/server/lament.qc diff --git a/src/server/defs.h b/src/server/defs.h index 82d21195..5974b0ee 100644 --- a/src/server/defs.h +++ b/src/server/defs.h @@ -26,6 +26,7 @@ #include "route.h" #include "way.h" +#include "lament.h" /* helper macros */ #define EVALUATE_FIELD(fieldname, changedflag) {\ diff --git a/src/server/entry.qc b/src/server/entry.qc index 7825112a..2741f957 100644 --- a/src/server/entry.qc +++ b/src/server/entry.qc @@ -255,6 +255,8 @@ SV_RunClientCommand(void) if (self.classname != "player" && self.classname != "spectator") return; + CheatersLament((NSClientPlayer)cl, input_angles, input_buttons, input_timelength); + if (!Plugin_RunClientCommand()) { /* TODO */ } diff --git a/src/server/include.src b/src/server/include.src index b32c8f5c..7a11d9e1 100644 --- a/src/server/include.src +++ b/src/server/include.src @@ -9,6 +9,7 @@ NSGameRules.qc client.qc NSTraceAttack.qc vote.qc +lament.qc weapons.qc modelevent.qc mapcycle.qc diff --git a/src/server/lament.h b/src/server/lament.h new file mode 100644 index 00000000..2efe1c9c --- /dev/null +++ b/src/server/lament.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 Vera Visions LLC. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +void CheatersLament(NSClientPlayer, vector, float, float); \ No newline at end of file diff --git a/src/server/lament.qc b/src/server/lament.qc new file mode 100644 index 00000000..12a2477e --- /dev/null +++ b/src/server/lament.qc @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023 Vera Visions LLC. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* cheaters lament + +a system designed to analyze movement deltas and warn about suspicious +behaviour as it is happening. + +explanation: + +intense movement comes with decreased accuracy. if a player is +showcasing signs of above average reaction time and consistent success +in trailing (and shooting) valid targets we are going to inform the +server admin of suspicious behaviour. + +the system does not act on its own, it merely gives feedback and +expects the host to act upon it. + +we can tally the feedback and create a score based on suspicious +behaviour as a result. these results should ideally be made public +to the other players so they themselves can make a choice + +technical deep dive: + +we accumulate the camera angle deltas over time to track +the average mouse movement intensity over a short period of time. +waiting a full second will actually remove a full 360 degrees +of delta. +then we also accumulate accuracy information, we increases a counter +whenever the player is actively firing, while also aiming at an opposing +player within an radius of less than 10 degrees (to compensate for rocket shots +and othe projectile trails). +whenever the player is firing and not aiming with that level of precision, +the counter goes down. + +if the deltas together reach a high point together, that must mean that +the player is having frantic mouse movements, combined with +incredible hit accuracy. + +the high point calibration is taken from analyzing various demos +of known pro players as well as some cheating incidents. + +limitations: + +when going through teleporters, the angle deltas may be high +enough to trigger false positives. + +this is obviously not a perfect system, but it is to be used +as a tool to assist in the decision making that goes into +moderating a game server. +*/ + +void +CheatersLament(NSClientPlayer playerEntity, vector absAngles, float buttons, float timeLength) +{ + vector angleDelta = playerEntity.pb_last_angles - absAngles; /* diff between old and new */ + + /* only decrement above 0 */ + if (playerEntity.pb_angle_delta > 0.0) + playerEntity.pb_angle_delta -= timeLength * 360; + else + playerEntity.pb_angle_delta = 0.0f; + + /* ditto */ + if (playerEntity.pb_player_delta > 0.0) + playerEntity.pb_player_delta -= timeLength; + else + playerEntity.pb_player_delta = 0.0f; + + /* if the player is firing, calculate the player delta */ + if (buttons & INPUT_BUTTON0 || buttons & INPUT_BUTTON3) { /* primary & secondary fire counts */ + vector deltaPosition; + float deltaDegrees; + + makevectors( absAngles ); + for (entity e = world; (e = find(e, ::classname, "player"));) { + deltaPosition = normalize( e.origin - playerEntity.origin ); + deltaDegrees = deltaPosition * v_forward; + + other = world; + traceline(e.origin, playerEntity.origin, MOVE_OTHERONLY, playerEntity); + + if (trace_fraction == 1.0f && deltaDegrees >= 0.95) { + playerEntity.pb_player_delta += timeLength * 4.0f; + } + } + } + + playerEntity.pb_last_angles = absAngles; /* set so we don't accumulate */ + + /* if our angle delta is less than 256, don't bother reporting */ + if (vlen(angleDelta) < 256.0) { + return; + } + + /* we will add the difference only if it is big enough from last frame */ + playerEntity.pb_angle_delta += vlen(angleDelta); + + /* if our delta is consistently above 1024 and we've been trailing a player for 2 + whole second... suspect foul play */ + if (playerEntity.pb_angle_delta > 1024.0 && playerEntity.pb_player_delta > 2.0) { + localcmd(sprintf("echo Lamenting %S (%s) with (a: %f p: %f)\n", playerEntity.netname, infokey(playerEntity, INFOKEY_P_IP), playerEntity.pb_angle_delta, playerEntity.pb_player_delta)); + } +} \ No newline at end of file diff --git a/src/shared/NSClientPlayer.h b/src/shared/NSClientPlayer.h index db185c92..85b05261 100644 --- a/src/shared/NSClientPlayer.h +++ b/src/shared/NSClientPlayer.h @@ -168,6 +168,10 @@ private: float m_flPainTime; entity last_used; + + float pb_angle_delta; + float pb_player_delta; + vector pb_last_angles; #endif };