1 /*
2 sv_wallhack.c -- functions to prevent wallhack cheats
3
4 Copyright (C) 2013 Laszlo Menczel
5
6 This is free software distributed under the terms of the GNU
7 General Public License version 2. NO WARRANTY, see 'LICENSE.TXT'.
8 */
9
10 #ifdef ANTIWALLHACK // added whole file
11
12 #include <time.h> // for random seed generation
13 #include "server.h"
14
15 //======================================================================
16
17 static vec3_t pred_ppos, pred_opos;
18
19 static trajectory_t traject;
20
21 static int rand_seed;
22
23 static float delta_sign[8][3] =
24 {
25 { 1, 1, 1 },
26 { 1, 1, 1 },
27 { 1, 1, -1 },
28 { 1, 1, -1 },
29 { -1, 1, 1 },
30 { 1, -1, 1 },
31 { -1, 1, -1 },
32 { 1, -1, -1 }
33 };
34
35 static vec3_t delta[8];
36
37 //======================================================================
38 // local functions
39 //======================================================================
40
41 #define POS_LIM 1.0
42 #define NEG_LIM -1.0
43
zero_vector(vec3_t v)44 static int zero_vector(vec3_t v)
45 {
46 if (v[0] > POS_LIM || v[0] < NEG_LIM)
47 return 0;
48
49 if (v[1] > POS_LIM || v[1] < NEG_LIM)
50 return 0;
51
52 if (v[2] > POS_LIM || v[2] < NEG_LIM)
53 return 0;
54
55 return 1;
56 }
57
58 //======================================================================
59 /*
60 The following functions for predicting player positions
61 have been adopted from 'g_unlagged.c' which is part of the
62 'unlagged' system created by Neil "haste" Toronto.
63 WEB site: http://www.ra.is/unlagged
64 */
65
66 #define OVERCLIP 1.001f
67
predict_clip_velocity(vec3_t in,vec3_t normal,vec3_t out)68 static void predict_clip_velocity(vec3_t in, vec3_t normal, vec3_t out)
69 {
70 float backoff;
71
72 // find the magnitude of the vector "in" along "normal"
73 backoff = DotProduct(in, normal);
74
75 // tilt the plane a bit to avoid floating-point error issues
76 if (backoff < 0)
77 backoff *= OVERCLIP;
78 else
79 backoff /= OVERCLIP;
80
81 // slide along
82 VectorMA(in, -backoff, normal, out);
83 }
84
85 //======================================================================
86
87 #define MAX_CLIP_PLANES 5
88 #define Z_ADJUST 1
89
predict_slide_move(sharedEntity_t * ent,float frametime,trajectory_t * tr,vec3_t result)90 static int predict_slide_move(sharedEntity_t * ent, float frametime, trajectory_t * tr, vec3_t result)
91 {
92 int count, numbumps, numplanes, i, j, k;
93
94 float d, time_left, into;
95
96 vec3_t planes[MAX_CLIP_PLANES],
97 velocity, origin, clipVelocity, endVelocity, endClipVelocity, dir, end;
98
99 trace_t trace;
100
101 numbumps = 4;
102
103 VectorCopy(tr->trBase, origin);
104 origin[2] += Z_ADJUST; // move it off the floor
105
106 VectorCopy(tr->trDelta, velocity);
107 VectorCopy(tr->trDelta, endVelocity);
108
109 time_left = frametime;
110
111 numplanes = 0;
112
113 for (count = 0; count < numbumps; count++)
114 {
115 // calculate position we are trying to move to
116 VectorMA(origin, time_left, velocity, end);
117
118 // see if we can make it there
119 SV_Trace(&trace, origin, ent->r.mins, ent->r.maxs, end, ent->s.number,
120 CONTENTS_SOLID, qfalse);
121
122 if (trace.allsolid)
123 {
124 // entity is completely trapped in another solid
125 VectorCopy(origin, result);
126 return 0;
127 }
128
129 if (trace.fraction > 0.99) // moved the entire distance
130 {
131 VectorCopy(trace.endpos, result);
132 return 1;
133 }
134
135 if (trace.fraction > 0) // covered some distance
136 VectorCopy(trace.endpos, origin);
137
138 time_left -= time_left * trace.fraction;
139
140 if (numplanes >= MAX_CLIP_PLANES)
141 {
142 // this shouldn't really happen
143 VectorCopy(origin, result);
144 return 0;
145 }
146
147 // if this is the same plane we hit before, nudge velocity
148 // out along it, which fixes some epsilon issues with
149 // non-axial planes
150 for (i = 0; i < numplanes; i++)
151 if (DotProduct(trace.plane.normal, planes[i]) > 0.99)
152 {
153 VectorAdd(trace.plane.normal, velocity, velocity);
154 break;
155 }
156
157 if (i < numplanes)
158 continue;
159
160 VectorCopy(trace.plane.normal, planes[numplanes]);
161 numplanes++;
162
163 // modify velocity so it parallels all of the clip planes
164 // find a plane that it enters
165 for (i = 0; i < numplanes; i++)
166 {
167 into = DotProduct(velocity, planes[i]);
168 if (into >= 0.1) // move doesn't interact with the plane
169 continue;
170
171 // slide along the plane
172 predict_clip_velocity(velocity, planes[i], clipVelocity);
173
174 // slide along the plane
175 predict_clip_velocity(endVelocity, planes[i], endClipVelocity);
176
177 // see if there is a second plane that the new move enters
178 for (j = 0; j < numplanes; j++)
179 {
180 if (j == i)
181 continue;
182
183 if (DotProduct(clipVelocity, planes[j]) >= 0.1) // move doesn't interact with the plane
184 continue;
185
186 // try clipping the move to the plane
187 predict_clip_velocity(clipVelocity, planes[j], clipVelocity);
188 predict_clip_velocity(endClipVelocity, planes[j], endClipVelocity);
189
190 // see if it goes back into the first clip plane
191 if (DotProduct(clipVelocity, planes[i]) >= 0)
192 continue;
193
194 // slide the original velocity along the crease
195 CrossProduct(planes[i], planes[j], dir);
196 VectorNormalize(dir);
197 d = DotProduct(dir, velocity);
198 VectorScale(dir, d, clipVelocity);
199
200 CrossProduct(planes[i], planes[j], dir);
201 VectorNormalize(dir);
202 d = DotProduct(dir, endVelocity);
203 VectorScale(dir, d, endClipVelocity);
204
205 // see if there is a third plane the new move enters
206 for (k = 0; k < numplanes; k++)
207 {
208 if (k == i || k == j)
209 continue;
210
211 if (DotProduct(clipVelocity, planes[k]) >= 0.1) // move doesn't interact with the plane
212 continue;
213
214 // stop dead at a tripple plane interaction
215 VectorCopy(origin, result);
216 return 1;
217 }
218 }
219
220 // if we have fixed all interactions, try another move
221 VectorCopy(clipVelocity, velocity);
222 VectorCopy(endClipVelocity, endVelocity);
223 break;
224 }
225 }
226
227 VectorCopy(origin, result);
228
229 if (count == 0)
230 return 1;
231
232 return 0;
233 }
234
235 //======================================================================
236
237 #define STEPSIZE 18
238
239 // 'frametime' is interpreted as seconds
240
predict_move(sharedEntity_t * ent,float frametime,trajectory_t * tr,vec3_t result)241 static void predict_move(sharedEntity_t * ent, float frametime, trajectory_t * tr, vec3_t result)
242 {
243 float stepSize;
244
245 vec3_t start_o, start_v, down, up;
246
247 trace_t trace;
248
249 VectorCopy(tr->trBase, result); // assume the move fails
250
251 if (zero_vector(tr->trDelta)) // not moving
252 return;
253
254 if (predict_slide_move(ent, frametime, tr, result)) // move completed
255 return;
256
257 VectorCopy(tr->trBase, start_o);
258 VectorCopy(tr->trDelta, start_v);
259
260 VectorCopy(start_o, up);
261 up[2] += STEPSIZE;
262
263 // test the player position if they were a stepheight higher
264 SV_Trace(&trace, start_o, ent->r.mins, ent->r.maxs, up, ent->s.number,
265 CONTENTS_SOLID, qfalse);
266
267 if (trace.allsolid) // can't step up
268 return;
269
270 stepSize = trace.endpos[2] - start_o[2];
271
272 // try slidemove from this position
273 VectorCopy(trace.endpos, tr->trBase);
274 VectorCopy(start_v, tr->trDelta);
275
276 predict_slide_move(ent, frametime, tr, result);
277
278 // push down the final amount
279 VectorCopy(tr->trBase, down);
280 down[2] -= stepSize;
281 SV_Trace(&trace, tr->trBase, ent->r.mins, ent->r.maxs, down, ent->s.number,
282 CONTENTS_SOLID, qfalse);
283
284 if (!trace.allsolid)
285 VectorCopy(trace.endpos, result);
286 }
287
288 //======================================================================
289 /*
290 Calculates the view point of a player model at position 'org' using
291 information in the player state 'ps' of its client, and stores the
292 viewpoint coordinates in 'vp'.
293 */
294
calc_viewpoint(playerState_t * ps,vec3_t org,vec3_t vp)295 static void calc_viewpoint(playerState_t * ps, vec3_t org, vec3_t vp)
296 {
297 VectorCopy(org, vp);
298
299 if ( ps->leanf != 0 )
300 {
301 vec3_t right, v3ViewAngles;
302 VectorCopy( ps->viewangles, v3ViewAngles );
303 v3ViewAngles[2] += ps->leanf / 2.0f;
304 AngleVectors( v3ViewAngles, NULL, right, NULL );
305 VectorMA( org, ps->leanf, right, org );
306 }
307
308 if (ps->pm_flags & PMF_DUCKED)
309 vp[2] += CROUCH_VIEWHEIGHT;
310 else
311 vp[2] += DEFAULT_VIEWHEIGHT;
312 }
313
314 //======================================================================
315
316 #define MAX_PITCH 20
317
player_in_fov(vec3_t viewangle,vec3_t ppos,vec3_t opos)318 static int player_in_fov(vec3_t viewangle, vec3_t ppos, vec3_t opos)
319 {
320 float yaw, pitch, cos_angle;
321 vec3_t dir, los;
322
323 /*
324 FIXME:
325 For some reason my FOV calculation does not work correctly for large
326 pitch values. It does not matter, the test's purpose is to eliminate
327 info that would reveal the position of opponents behind the player
328 on the same floor.
329 */
330 if (viewangle[PITCH] > MAX_PITCH || viewangle[PITCH] < -1 * MAX_PITCH)
331 return 1;
332
333 // calculate unit vector of the direction the player looks at
334 yaw = viewangle[YAW] * (M_PI * 2 / 360);
335 pitch = viewangle[PITCH] * (M_PI * 2 / 360);
336 dir[0] = cos(yaw) * cos(pitch);
337 dir[1] = sin(yaw);
338 dir[2] = cos(yaw) * sin(pitch);
339
340 // calculate unit vector corresponding to line of sight to opponent
341 VectorSubtract(opos, ppos, los);
342 VectorNormalize(los);
343
344 // calculate and test the angle between the two vectors
345 cos_angle = DotProduct(dir, los);
346 if (cos_angle > 0) // +/- 90 degrees (fov = 180)
347 return 1;
348
349 return 0;
350 }
351
352 //======================================================================
353
copy_trajectory(trajectory_t * src,trajectory_t * dst)354 static void copy_trajectory(trajectory_t * src, trajectory_t * dst)
355 {
356 dst->trType = src->trType;
357 dst->trTime = src->trTime;
358 dst->trDuration = src->trDuration;
359 VectorCopy(src->trBase, dst->trBase);
360 VectorCopy(src->trDelta, dst->trDelta);
361 }
362
363 //======================================================================
364
is_visible(vec3_t start,vec3_t end)365 int is_visible(vec3_t start, vec3_t end)
366 {
367 trace_t trace;
368
369 CM_BoxTrace(&trace, start, end, NULL, NULL, 0, CONTENTS_SOLID, 0);
370
371 if (trace.contents & CONTENTS_SOLID)
372 return 0;
373
374 return 1;
375 }
376
377 //======================================================================
378
379 #define MIN_DIST 200.0
380 #define MAX_DIST 1000.0
381 #define MIN_OFS_FACT 0.1
382 #define MAX_OFS_FACT 0.4
383 #define OFS_FACT_DIFF (MAX_OFS_FACT - MIN_OFS_FACT)
384
randomize_position(sharedEntity_t * anchor,sharedEntity_t * object)385 static void randomize_position(sharedEntity_t *anchor, sharedEntity_t *object)
386 {
387 vec3_t los;
388 float dist, ofs, rand_fact, dist_fact;
389
390 VectorSubtract(anchor->s.pos.trBase, object->s.pos.trBase, los);
391 dist = VectorLength(los);
392
393 if (dist > MAX_DIST)
394 dist_fact = MIN_OFS_FACT;
395 else if (dist < MIN_DIST)
396 dist_fact = MAX_OFS_FACT;
397 else
398 dist_fact = MAX_OFS_FACT - OFS_FACT_DIFF * (dist - MIN_DIST) / (MAX_DIST - MIN_DIST);
399
400 rand_fact = (float) (rand() % 100) / 100.0;
401 ofs = dist * dist_fact;
402 ofs += ofs * rand_fact;
403
404 if (rand() & 1)
405 object->s.pos.trBase[0] += ofs;
406 else
407 object->s.pos.trBase[0] -= ofs;
408
409 rand_fact = (float) (rand() % 100) / 100.0;
410 ofs = dist * dist_fact;
411 ofs += ofs * rand_fact;
412
413 if (rand() & 1)
414 object->s.pos.trBase[1] += ofs;
415 else
416 object->s.pos.trBase[1] -= ofs;
417
418 rand_fact = (float) (rand() % 100) / 100.0;
419 ofs = dist * dist_fact;
420 ofs += ofs * rand_fact;
421
422 if (rand() & 1)
423 object->s.pos.trBase[2] += ofs;
424 else
425 object->s.pos.trBase[2] -= ofs;
426 }
427
428 //======================================================================
429 /*
430 'can_see' checks if 'player' can see 'other' or not. First
431 a check is made if 'other' is in the maximum allowed fov of
432 'player'. If not, then zero is returned w/o any further checks.
433 Next traces are carried out from the present viewpoint of 'player'
434 to the corners of the bounding box of 'other'. If any of these
435 traces are successful (i.e. nothing solid is between the start
436 and end positions) then non-zero is returned.
437
438 Otherwise the expected positions of the two players are calculated,
439 by extrapolating their movements for PREDICT_TIME seconds and the above
440 tests are carried out again. The result is reported by returning non-zero
441 (expected to become visible) or zero (not expected to become visible
442 in the next frame).
443 */
444
445 #define PREDICT_TIME 0.1
446 #define VOFS 6
447
can_see(sharedEntity_t * pent,sharedEntity_t * oent,playerState_t * ps)448 static int can_see(sharedEntity_t *pent, sharedEntity_t*oent, playerState_t *ps)
449 {
450 vec3_t viewpoint, tmp;
451 int i;
452
453 /* check if 'other' is in the maximum fov allowed */
454 if (!player_in_fov(pent->s.apos.trBase, pent->s.pos.trBase, oent->s.pos.trBase))
455 return 0;
456
457 /* check if visible in this frame */
458 calc_viewpoint(ps, pent->s.pos.trBase, viewpoint);
459
460 for (i = 0; i < 8; i++)
461 {
462 VectorCopy(oent->s.pos.trBase, tmp);
463 tmp[0] += delta[i][0];
464 tmp[1] += delta[i][1];
465 tmp[2] += delta[i][2] + VOFS;
466
467 if (is_visible(viewpoint, tmp))
468 return 1;
469 }
470
471 /* predict player positions */
472 copy_trajectory(&pent->s.pos, &traject);
473 predict_move(pent, PREDICT_TIME, &traject, pred_ppos);
474
475 copy_trajectory(&oent->s.pos, &traject);
476 predict_move(oent, PREDICT_TIME, &traject, pred_opos);
477
478 /*
479 Check again if 'other' is in the maximum fov allowed.
480 FIXME: We use the original viewangle that may have
481 changed during the move. This could introduce some
482 errors.
483 */
484 if (!player_in_fov(pent->s.apos.trBase, pred_ppos, pred_opos))
485 return 0;
486
487 /* check if expected to be visible in the next frame */
488 calc_viewpoint(ps, pred_ppos, viewpoint);
489
490 for (i = 0; i < 8; i++)
491 {
492 VectorCopy(pred_opos, tmp);
493 tmp[0] += delta[i][0];
494 tmp[1] += delta[i][1];
495 tmp[2] += delta[i][2] + VOFS;
496
497 if (is_visible(viewpoint, tmp))
498 return 1;
499 }
500
501 return 0;
502 }
503
504 //======================================================================
505 // public functions
506 //======================================================================
507
AWH_Init(void)508 void AWH_Init(void)
509 {
510 int i;
511
512 for (i = 0; i < 8; i++)
513 {
514 delta[i][0] = ((float) awh_bbox_horz->integer * delta_sign[i][0]) / 2.0;
515 delta[i][1] = ((float) awh_bbox_horz->integer * delta_sign[i][1]) / 2.0;
516 delta[i][2] = ((float) awh_bbox_vert->integer * delta_sign[i][2]) / 2.0;
517 }
518
519 rand_seed = (int) time(NULL);
520 }
521
522 //======================================================================
523
AWH_CanSee(int player,int other)524 int AWH_CanSee(int player, int other)
525 {
526 sharedEntity_t *pent, *oent;
527 playerState_t *ps;
528
529 ps = SV_GameClientNum(player);
530 pent = SV_GentityNum(player);
531 oent = SV_GentityNum(other);
532
533 return can_see(pent, oent, ps);
534 }
535
536 //======================================================================
537
538 // The value below is equal to the default value of the Cvar
539 // 's_alMaxDistance' (= 1024).
540 #define SOUND_HEARING_LIMIT 1024
541
AWH_CanHear(int player,int other)542 int AWH_CanHear(int player, int other)
543 {
544 sharedEntity_t *pent, *oent;
545 vec3_t dist;
546
547 pent = SV_GentityNum(player);
548 oent = SV_GentityNum(other);
549
550 VectorSubtract(pent->s.pos.trBase, oent->s.pos.trBase, dist);
551 if (VectorLength(dist) > SOUND_HEARING_LIMIT)
552 return 0;
553
554 return 1;
555 }
556
557 //======================================================================
558 /*
559 Randomizes the position of 'other'. Amount of displacement depends
560 on the distance of 'other' from 'player'. Checks if the new position
561 is still invisible from the position of 'player', returns if yes,
562 otherwise tries again. After three attempts the function gives up and
563 restores the original position of 'other'.
564 */
565
AWH_RandomizePos(int player,int other)566 void AWH_RandomizePos(int player, int other)
567 {
568 int i;
569 sharedEntity_t *pent, *oent;
570 playerState_t *ps;
571 vec3_t pos;
572
573 ps = SV_GameClientNum(player);
574 pent = SV_GentityNum(player);
575 oent = SV_GentityNum(other);
576 VectorCopy(oent->s.pos.trBase, pos);
577
578 for (i = 0; i < 3; i++)
579 {
580 randomize_position(pent, oent);
581
582 if (!can_see(pent, oent, ps))
583 return;
584 else
585 VectorCopy(pos, oent->s.pos.trBase);
586 }
587 }
588
589 #endif
590