Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
anticheat.qc
Go to the documentation of this file.
1#include "anticheat.qh"
2
4#include <common/state.qh>
5#include <common/stats.qh>
7#include <server/antilag.qh>
8#include <server/client.qh>
10#include <server/gamelog.qh>
11#include <server/main.qh>
12
14
16
21MEAN_DECLARE(anticheat_div0_evade, 5);
22
24MEAN_DECLARE(anticheat_div0_strafebot_old, 5);
25
27MEAN_DECLARE(anticheat_div0_strafebot_new, 5);
28
30MEAN_DECLARE(anticheat_div0_snapback, 5);
31
32// Snap-aim detection: we track the average angular speed of aiming over time, in "radians per second".
33// Signal: a high-power mean. Cheaters will have high "signal" here.
34// Noise: a low-power mean. Active/shivery players will have high "noise" here.
35// Note one can always artificially add noise - so very high values of both signal and noise need to be checked too.
36MEAN_DECLARE(anticheat_idle_snapaim_signal, 5);
37MEAN_DECLARE(anticheat_idle_snapaim_noise, 1);
38
39// TEMP DEBUG STUFF.
40MEAN_DECLARE(anticheat_idle_snapaim_m2, 2);
41MEAN_DECLARE(anticheat_idle_snapaim_m3, 3);
42MEAN_DECLARE(anticheat_idle_snapaim_m4, 4);
43MEAN_DECLARE(anticheat_idle_snapaim_m7, 7);
44MEAN_DECLARE(anticheat_idle_snapaim_m10, 10);
45
48MEAN_DECLARE(anticheat_speedhack, 5);
49
52MEAN_DECLARE(anticheat_speedhack_m1, 1);
53MEAN_DECLARE(anticheat_speedhack_m2, 2);
54MEAN_DECLARE(anticheat_speedhack_m3, 3);
55MEAN_DECLARE(anticheat_speedhack_m4, 4);
56MEAN_DECLARE(anticheat_speedhack_m5, 5);
57
59{
60 float cosangle = normalize(m0) * normalize(m1);
61 if(cosangle >= 0)
62 return 0;
63 return 0.5 - 0.5 * cos(cosangle * cosangle * (4 * M_PI));
64 // returns 0 for: -1, -sqrt(0.5), 0 (angles that commonly happen with kbd)
65}
66
68{
69 float f;
70
71 // div0_evade -> SPECTATORS
72 makevectors(this.v_angle);
73 if(CS(this).anticheat_div0_evade_offset == 0)
74 {
76 CS(this).anticheat_div0_evade_offset = servertime + sys_frametime * (3 * f - 1);
77 CS(this).anticheat_div0_evade_v_angle = this.v_angle;
78 CS(this).anticheat_div0_evade_forward_initial = v_forward;
79 MEAN_ACCUMULATE(CS(this), anticheat_div0_evade, 0, 1);
80 }
81 else
82 {
84 CS(this).anticheat_div0_evade_v_angle = this.v_angle;
85 MEAN_ACCUMULATE(CS(this), anticheat_div0_evade, 0.5 - 0.5 * (CS(this).anticheat_div0_evade_forward_initial * v_forward), 1);
86 }
87
88 MEAN_ACCUMULATE(CS(this), anticheat_div0_strafebot_old, movement_oddity(CS(this).movement, CS(this).anticheat_div0_strafebot_movement_prev), 1);
89 CS(this).anticheat_div0_strafebot_movement_prev = CS(this).movement;
90
91 // Note: this actually tries to detect snap-aim.
93 float cosangle = CS(this).anticheat_div0_strafebot_forward_prev * v_forward;
94 float angle = cosangle < -1 ? M_PI : cosangle > 1 ? 0 : acos(cosangle);
95 /*
96 if (angle >= 10 * M_PI / 180)
97 printf("SNAP %s: %f for %f, %f since fixangle\n", this.netname, angle * 180 / M_PI, cosangle, time - CS(this).anticheat_fixangle_endtime);
98 */
99 MEAN_ACCUMULATE(CS(this), anticheat_div0_strafebot_new, angle / M_PI, 1);
100
101 if (autocvar_slowmo > 0) {
102 // Technically this is a NOP, as the engine should be ensuring
103 // this in the first place. Let's guard against dividing by
104 // zero anyway.
105 float dt = max(0.001, frametime) / autocvar_slowmo;
106
107 float anglespeed = angle / dt;
108 MEAN_ACCUMULATE(CS(this), anticheat_idle_snapaim_signal, anglespeed, dt);
109 MEAN_ACCUMULATE(CS(this), anticheat_idle_snapaim_noise, anglespeed, dt);
110 MEAN_ACCUMULATE(CS(this), anticheat_idle_snapaim_m2, anglespeed, dt);
111 MEAN_ACCUMULATE(CS(this), anticheat_idle_snapaim_m3, anglespeed, dt);
112 MEAN_ACCUMULATE(CS(this), anticheat_idle_snapaim_m4, anglespeed, dt);
113 MEAN_ACCUMULATE(CS(this), anticheat_idle_snapaim_m7, anglespeed, dt);
114 MEAN_ACCUMULATE(CS(this), anticheat_idle_snapaim_m10, anglespeed, dt);
115
116 // Detect snapping _back_.
117 float f = bound(0, dt * 4, 1); // About 0.25 seconds horizon for snapping back.
118 vector aim_move = v_forward - CS(this).anticheat_div0_strafebot_forward_prev;
119 vector snapback_prev = CS(this).anticheat_div0_snapback_prev;
120 float snapback_len = vlen(snapback_prev);
121 if (snapback_len != 0) {
122 float aim_snap = max(0, (aim_move * snapback_prev) / -snapback_len);
123 // Scales with aim_move, but is positive only when snapping back, otherwise zero.
124 MEAN_ACCUMULATE(CS(this), anticheat_div0_snapback, aim_snap, dt);
125 }
126 CS(this).anticheat_div0_snapback_prev = snapback_prev * (1 - f) + aim_move * f;
127 }
128 }
129 CS(this).anticheat_div0_strafebot_forward_prev = v_forward;
130
131 // generic speedhack detection: correlate anticheat_speedhack_movetime (UPDATED BEFORE THIS) and server time
132 CS(this).anticheat_speedhack_movetime_frac += frametime;
134 CS(this).anticheat_speedhack_movetime_frac -= f;
135 CS(this).anticheat_speedhack_movetime_count += f;
136 CS(this).anticheat_speedhack_movetime = CS(this).anticheat_speedhack_movetime_frac + CS(this).anticheat_speedhack_movetime_count;
137 f = CS(this).anticheat_speedhack_movetime - servertime;
138 if(CS(this).anticheat_speedhack_offset == 0)
139 CS(this).anticheat_speedhack_offset = f;
140 else
141 {
142 MEAN_ACCUMULATE(CS(this), anticheat_speedhack, max(0, f - CS(this).anticheat_speedhack_offset), 1);
143 CS(this).anticheat_speedhack_offset += (f - CS(this).anticheat_speedhack_offset) * frametime * 0.1;
144 }
145
146 // new generic speedhack detection
147 if (CS(this).anticheat_speedhack_lasttime > 0) {
148 float dt = servertime - CS(this).anticheat_speedhack_lasttime;
149 const float falloff = 0.2;
150 CS(this).anticheat_speedhack_accu *= exp(-dt * falloff);
151 CS(this).anticheat_speedhack_accu += frametime * falloff;
152 // NOTE: at cl_netfps x, this actually averages not to 1, but to 1/x * falloff / (1 - exp(-1/x * falloff))
153 // For 15 netfps (absolute minimum bearable), and 0.2 falloff, this is: 1.0067
154 CS(this).anticheat_speedhack_lasttime = servertime;
155 MEAN_ACCUMULATE(CS(this), anticheat_speedhack_m1, CS(this).anticheat_speedhack_accu, frametime);
156 MEAN_ACCUMULATE(CS(this), anticheat_speedhack_m2, CS(this).anticheat_speedhack_accu, frametime);
157 MEAN_ACCUMULATE(CS(this), anticheat_speedhack_m3, CS(this).anticheat_speedhack_accu, frametime);
158 MEAN_ACCUMULATE(CS(this), anticheat_speedhack_m4, CS(this).anticheat_speedhack_accu, frametime);
159 MEAN_ACCUMULATE(CS(this), anticheat_speedhack_m5, CS(this).anticheat_speedhack_accu, frametime);
160 } else {
161 CS(this).anticheat_speedhack_accu = 1;
162 CS(this).anticheat_speedhack_lasttime = servertime;
163 }
164}
165
167{
168 // div0_evade -> SPECTATORS
169 this.angles = CS(spectatee).anticheat_div0_evade_v_angle;
170}
171
173{
174 // div0_evade -> SPECTATORS
175 CS(this).anticheat_div0_evade_offset = 0;
176}
177
178string anticheat_display(float f, float t, float tmin, float mi, float ma)
179{
180 string s;
181 s = ftos(f);
182 if (t >= tmin) {
183 if(f <= mi)
184 return strcat(s, ":N");
185 if(f >= ma)
186 return strcat(s, ":Y");
187 }
188 return strcat(s, ":-");
189}
190
191#define ANTICHEATS(ANTICHEAT) \
192 ANTICHEAT("speedhack", MEAN_EVALUATE(CS(this), anticheat_speedhack), 240, 0, 9999); /* Actually this one seems broken. */ \
193 ANTICHEAT("speedhack_m1", MEAN_EVALUATE(CS(this), anticheat_speedhack_m1), 240, 1.01, 1.25); \
194 ANTICHEAT("speedhack_m2", MEAN_EVALUATE(CS(this), anticheat_speedhack_m2), 240, 1.01, 1.25); \
195 ANTICHEAT("speedhack_m3", MEAN_EVALUATE(CS(this), anticheat_speedhack_m3), 240, 1.01, 1.25); \
196 ANTICHEAT("speedhack_m4", MEAN_EVALUATE(CS(this), anticheat_speedhack_m4), 240, 1.01, 1.25); \
197 ANTICHEAT("speedhack_m5", MEAN_EVALUATE(CS(this), anticheat_speedhack_m5), 240, 1.01, 1.25); \
198 ANTICHEAT("div0_strafebot_old", MEAN_EVALUATE(CS(this), anticheat_div0_strafebot_old), 120, 0.15, 0.4); \
199 ANTICHEAT("div0_strafebot_new", MEAN_EVALUATE(CS(this), anticheat_div0_strafebot_new), 120, 0.25, 0.8); \
200 ANTICHEAT("div0_evade", MEAN_EVALUATE(CS(this), anticheat_div0_evade), 120, 0.2, 0.5); \
201 ANTICHEAT("idle_snapaim", MEAN_EVALUATE(CS(this), anticheat_idle_snapaim_signal) - MEAN_EVALUATE(CS(this), anticheat_idle_snapaim_noise), 120, 0, 9999); \
202 ANTICHEAT("idle_snapaim_signal", MEAN_EVALUATE(CS(this), anticheat_idle_snapaim_signal), 120, 0, 9999); \
203 ANTICHEAT("idle_snapaim_noise", MEAN_EVALUATE(CS(this), anticheat_idle_snapaim_noise), 120, 0, 9999); \
204 ANTICHEAT("idle_snapaim_m2", MEAN_EVALUATE(CS(this), anticheat_idle_snapaim_m2), 120, 0, 9999); \
205 ANTICHEAT("idle_snapaim_m3", MEAN_EVALUATE(CS(this), anticheat_idle_snapaim_m3), 120, 0, 9999); \
206 ANTICHEAT("idle_snapaim_m4", MEAN_EVALUATE(CS(this), anticheat_idle_snapaim_m4), 120, 0, 9999); \
207 ANTICHEAT("idle_snapaim_m7", MEAN_EVALUATE(CS(this), anticheat_idle_snapaim_m7), 120, 0, 9999); \
208 ANTICHEAT("idle_snapaim_m10", MEAN_EVALUATE(CS(this), anticheat_idle_snapaim_m10), 120, 0, 9999); \
209 ANTICHEAT("div0_snapback", MEAN_EVALUATE(CS(this), anticheat_div0_snapback), 120, 0, 9999)
210
213 return;
214 GameLogEcho(strcat(":anticheat:_time:", ftos(this.playerid), ":", ftos(servertime - CS(this).anticheat_jointime)));
215#define ANTICHEAT_REPORT_ONE(name, f, tmin, mi, ma) \
216 GameLogEcho(strcat(":anticheat:", name, ":", anticheat_display(f, servertime - CS(this).anticheat_jointime, tmin, mi, ma)))
218#undef ANTICHEAT_REPORT_ONE
219}
220
224#define ANTICHEAT_REPORT_ONE(name, f, tmin, mi, ma) \
225 PlayerStats_GameReport_Event_Player(this, strcat(PLAYERSTATS_ANTICHEAT, name), f)
227#undef ANTICHEAT_REPORT_ONE
228}
229
232#define ANTICHEAT_REGISTER_ONE(name, unused_f, unused_tmin, unused_mi, unused_ma) \
233 PlayerStats_GameReport_AddEvent(strcat(PLAYERSTATS_ANTICHEAT, name))
235#undef ANTICHEAT_REGISTER_ONE
236}
237
238#undef ANTICHEATS
239
244
246{
247 CS(this).anticheat_fixangle_endtime = servertime + ANTILAG_LATENCY(this) + 0.2;
248}
249
255
257{
258 CS(this).anticheat_speedhack_offset = 0;
259 CS(this).anticheat_jointime = servertime;
260}
void anticheat_fixangle(entity this)
Definition anticheat.qc:245
float anticheat_jointime
Definition anticheat.qc:13
float anticheat_div0_evade_offset
Definition anticheat.qc:18
float anticheat_div0_evade_evasion_delta
Definition anticheat.qc:17
vector anticheat_div0_strafebot_forward_prev
Definition anticheat.qc:26
vector anticheat_div0_evade_v_angle
Definition anticheat.qc:19
float anticheat_speedhack_accu
Definition anticheat.qc:50
float anticheat_speedhack_lasttime
Definition anticheat.qc:51
void anticheat_init(entity this)
Definition anticheat.qc:256
vector anticheat_div0_strafebot_movement_prev
Definition anticheat.qc:23
string anticheat_display(float f, float t, float tmin, float mi, float ma)
Definition anticheat.qc:178
void anticheat_report_to_playerstats(entity this)
Definition anticheat.qc:221
#define ANTICHEAT_REGISTER_ONE(name, unused_f, unused_tmin, unused_mi, unused_ma)
float anticheat_speedhack_movetime_count
Definition anticheat.qc:47
vector anticheat_div0_evade_forward_initial
Definition anticheat.qc:20
void anticheat_spectatecopy(entity this, entity spectatee)
Definition anticheat.qc:166
void anticheat_startframe()
Definition anticheat.qc:240
#define ANTICHEAT_REPORT_ONE(name, f, tmin, mi, ma)
float anticheat_fixangle_endtime
Definition anticheat.qc:15
void anticheat_physics(entity this)
Definition anticheat.qc:67
void anticheat_report_to_eventlog(entity this)
Definition anticheat.qc:211
void anticheat_prethink(entity this)
Definition anticheat.qc:172
void anticheat_register_to_playerstats()
Definition anticheat.qc:230
vector anticheat_div0_snapback_prev
Definition anticheat.qc:29
void anticheat_endframe()
Definition anticheat.qc:250
float anticheat_speedhack_movetime_frac
Definition anticheat.qc:47
float movement_oddity(vector m0, vector m1)
Definition anticheat.qc:58
float anticheat_speedhack_offset
Definition anticheat.qc:46
#define ANTICHEATS(ANTICHEAT)
Definition anticheat.qc:191
float anticheat_speedhack_movetime
Definition anticheat.qc:47
#define ANTILAG_LATENCY(e)
Definition antilag.qh:19
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
int falloff
Definition impulse.qh:12
vector movement
Definition player.qh:229
vector v_angle
Definition player.qh:237
float frametime
float time
vector v_forward
ent angles
Definition ent_cs.qc:121
void GameLogEcho(string s)
Definition gamelog.qc:15
bool autocvar_sv_eventlog
Definition gamelog.qh:3
float servertime
Definition net.qh:348
float angle
Definition viewloc.qc:114
#define MEAN_DECLARE(prefix, m)
Definition math.qh:24
#define MEAN_ACCUMULATE(s, prefix, v, w)
Definition math.qh:22
float exp(float e)
Definition mathlib.qc:73
#define M_PI
Definition mathlib.qh:108
float bound(float min, float value, float max)
float cos(float f)
float random(void)
float vlen(vector v)
vector normalize(vector v)
string ftos(float f)
float fabs(float f)
float floor(float f)
float max(float f,...)
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
void PlayerStats_GameReport_AddEvent(string event_id)
const string PLAYERSTATS_ANTICHEAT
#define PlayerStats_GameReport_Event_Player(ent, eventid, val)
#define makevectors
Definition post.qh:21
vector
Definition self.qh:92
int playerid
Definition client.qh:82
float sys_frametime
Definition common.qh:57
#define autocvar_slowmo
Definition main.qh:16
ClientState CS(Client this)
Definition state.qh:47
#define FOREACH_CLIENT(cond, body)
Definition utils.qh:50