Server: add "Cheaters Lament", a proof of concept detection mechanism for suspicious player behaviour

This commit is contained in:
Marco Cawthorne 2023-08-07 12:56:17 -07:00
parent 0ea41d9304
commit 60128c6b1e
Signed by: eukara
GPG Key ID: CE2032F0A2882A22
6 changed files with 142 additions and 0 deletions

View File

@ -26,6 +26,7 @@
#include "route.h"
#include "way.h"
#include "lament.h"
/* helper macros */
#define EVALUATE_FIELD(fieldname, changedflag) {\

View File

@ -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 */
}

View File

@ -9,6 +9,7 @@ NSGameRules.qc
client.qc
NSTraceAttack.qc
vote.qc
lament.qc
weapons.qc
modelevent.qc
mapcycle.qc

17
src/server/lament.h Normal file
View File

@ -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);

117
src/server/lament.qc Normal file
View File

@ -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));
}
}

View File

@ -168,6 +168,10 @@ private:
float m_flPainTime;
entity last_used;
float pb_angle_delta;
float pb_player_delta;
vector pb_last_angles;
#endif
};