]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/physics.qc
A few trillion lines of untested junk, committing now so it doesn't become quadrillions
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / physics.qc
1 #include "physics.qh"
2 #include "triggers/trigger/swamp.qh"
3 #include "triggers/trigger/jumppads.qh"
4
5 #ifdef SVQC
6
7 #include "../server/miscfunctions.qh"
8
9 void Physics_AddStats()
10 {
11         // g_movementspeed hack
12         addstat(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW, AS_FLOAT, stat_sv_airspeedlimit_nonqw);
13         addstat(STAT_MOVEVARS_MAXSPEED, AS_FLOAT, stat_sv_maxspeed);
14         addstat(STAT_MOVEVARS_AIRACCEL_QW, AS_FLOAT, stat_sv_airaccel_qw);
15         addstat(STAT_MOVEVARS_AIRSTRAFEACCEL_QW, AS_FLOAT, stat_sv_airstrafeaccel_qw);
16         addstat(STAT_MOVEVARS_HIGHSPEED, AS_FLOAT, stat_movement_highspeed);
17
18         // jet pack
19         addstat(STAT_JETPACK_ACCEL_SIDE, AS_FLOAT, stat_jetpack_accel_side);
20         addstat(STAT_JETPACK_ACCEL_UP, AS_FLOAT, stat_jetpack_accel_up);
21         addstat(STAT_JETPACK_ANTIGRAVITY, AS_FLOAT, stat_jetpack_antigravity);
22         addstat(STAT_JETPACK_FUEL, AS_FLOAT, stat_jetpack_fuel);
23         addstat(STAT_JETPACK_MAXSPEED_UP, AS_FLOAT, stat_jetpack_maxspeed_up);
24         addstat(STAT_JETPACK_MAXSPEED_SIDE, AS_FLOAT, stat_jetpack_maxspeed_side);
25
26         // hack to fix track_canjump
27         addstat(STAT_MOVEVARS_TRACK_CANJUMP, AS_INT, cvar_cl_movement_track_canjump);
28
29         // double jump
30         addstat(STAT_DOUBLEJUMP, AS_INT, stat_doublejump);
31
32         // jump speed caps
33         addstat(STAT_MOVEVARS_JUMPSPEEDCAP_MIN, AS_FLOAT, stat_jumpspeedcap_min);
34         addstat(STAT_MOVEVARS_JUMPSPEEDCAP_MIN, AS_FLOAT, stat_jumpspeedcap_min);
35         addstat(STAT_MOVEVARS_JUMPSPEEDCAP_DISABLE_ONRAMPS, AS_INT, stat_jumpspeedcap_disable_onramps);
36
37         // hacks
38         addstat(STAT_MOVEVARS_FRICTION_ONLAND, AS_FLOAT, stat_sv_friction_on_land);
39         addstat(STAT_MOVEVARS_FRICTION_SLICK, AS_FLOAT, stat_sv_friction_slick);
40 }
41
42 void Physics_UpdateStats(float maxspd_mod)
43 {
44         self.stat_sv_airaccel_qw = AdjustAirAccelQW(autocvar_sv_airaccel_qw, maxspd_mod);
45         if (autocvar_sv_airstrafeaccel_qw)
46                 self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(autocvar_sv_airstrafeaccel_qw, maxspd_mod);
47         else
48                 self.stat_sv_airstrafeaccel_qw = 0;
49         self.stat_sv_airspeedlimit_nonqw = autocvar_sv_airspeedlimit_nonqw * maxspd_mod;
50         self.stat_sv_maxspeed = autocvar_sv_maxspeed * maxspd_mod; // also slow walking
51         self.stat_movement_highspeed = PHYS_HIGHSPEED; // TODO: remove this!
52
53         self.stat_doublejump = PHYS_DOUBLEJUMP;
54
55         self.stat_jetpack_antigravity = PHYS_JETPACK_ANTIGRAVITY;
56         self.stat_jetpack_accel_up = PHYS_JETPACK_ACCEL_UP;
57         self.stat_jetpack_accel_side = PHYS_JETPACK_ACCEL_SIDE;
58         self.stat_jetpack_maxspeed_side = PHYS_JETPACK_MAXSPEED_SIDE;
59         self.stat_jetpack_maxspeed_up = PHYS_JETPACK_MAXSPEED_UP;
60         self.stat_jetpack_fuel = PHYS_JETPACK_FUEL;
61
62         self.stat_jumpspeedcap_min = PHYS_JUMPSPEEDCAP_MIN;
63         self.stat_jumpspeedcap_max = PHYS_JUMPSPEEDCAP_MAX;
64         self.stat_jumpspeedcap_disable_onramps = PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS;
65
66         self.stat_sv_friction_on_land = PHYS_FRICTION_ONLAND;
67         self.stat_sv_friction_slick = PHYS_FRICTION_SLICK;
68 }
69 #endif
70
71 float IsMoveInDirection(vector mv, float angle) // key mix factor
72 {
73         if (mv_x == 0 && mv_y == 0)
74                 return 0; // avoid division by zero
75         angle -= RAD2DEG * atan2(mv_y, mv_x);
76         angle = remainder(angle, 360) / 45;
77         return angle > 1 ? 0 : angle < -1 ? 0 : 1 - fabs(angle);
78 }
79
80 float GeomLerp(float a, float lerp, float b)
81 {
82         return a == 0 ? (lerp < 1 ? 0 : b)
83                 : b == 0 ? (lerp > 0 ? 0 : a)
84                 : a * pow(fabs(b / a), lerp);
85 }
86
87 noref float pmove_waterjumptime;
88
89 const float unstick_count = 27;
90 vector unstick_offsets[unstick_count] =
91 {
92 // 1 no nudge (just return the original if this test passes)
93         '0.000   0.000  0.000',
94 // 6 simple nudges
95         ' 0.000  0.000  0.125', '0.000  0.000 -0.125',
96         '-0.125  0.000  0.000', '0.125  0.000  0.000',
97         ' 0.000 -0.125  0.000', '0.000  0.125  0.000',
98 // 4 diagonal flat nudges
99         '-0.125 -0.125  0.000', '0.125 -0.125  0.000',
100         '-0.125  0.125  0.000', '0.125  0.125  0.000',
101 // 8 diagonal upward nudges
102         '-0.125  0.000  0.125', '0.125  0.000  0.125',
103         ' 0.000 -0.125  0.125', '0.000  0.125  0.125',
104         '-0.125 -0.125  0.125', '0.125 -0.125  0.125',
105         '-0.125  0.125  0.125', '0.125  0.125  0.125',
106 // 8 diagonal downward nudges
107         '-0.125  0.000 -0.125', '0.125  0.000 -0.125',
108         ' 0.000 -0.125 -0.125', '0.000  0.125 -0.125',
109         '-0.125 -0.125 -0.125', '0.125 -0.125 -0.125',
110         '-0.125  0.125 -0.125', '0.125  0.125 -0.125',
111 };
112
113 void PM_ClientMovement_Unstick()
114 {
115         float i;
116         for (i = 0; i < unstick_count; i++)
117         {
118                 vector neworigin = unstick_offsets[i] + self.origin;
119                 tracebox(neworigin, PL_CROUCH_MIN, PL_CROUCH_MAX, neworigin, MOVE_NORMAL, self);
120                 if (!trace_startsolid)
121                 {
122                         setorigin(self, neworigin);
123                         return;// true;
124                 }
125         }
126 }
127
128 void PM_ClientMovement_UpdateStatus()
129 {
130         // make sure player is not stuck
131         PM_ClientMovement_Unstick();
132
133         // set crouched
134         if (PHYS_INPUT_BUTTON_CROUCH(self))
135         {
136                 // wants to crouch, this always works..
137                 if (!IS_DUCKED(self))
138                         SET_DUCKED(self);
139         }
140         else
141         {
142                 // wants to stand, if currently crouching we need to check for a
143                 // low ceiling first
144                 if (IS_DUCKED(self))
145                 {
146                         tracebox(self.origin, PL_MIN, PL_MAX, self.origin, MOVE_NORMAL, self);
147                         if (!trace_startsolid)
148                                 UNSET_DUCKED(self);
149                 }
150         }
151
152         // set onground
153         vector origin1 = self.origin + '0 0 1';
154         vector origin2 = self.origin - '0 0 1';
155
156         tracebox(origin1, self.mins, self.maxs, origin2, MOVE_NORMAL, self);
157         if (trace_fraction < 1 && trace_plane_normal_z > 0.7)
158         {
159                 SET_ONGROUND(self);
160
161                 // this code actually "predicts" an impact; so let's clip velocity first
162                 float f = self.velocity * trace_plane_normal;
163                 if (f < 0) // only if moving downwards actually
164                         self.velocity -= f * trace_plane_normal;
165         }
166         else
167                 UNSET_ONGROUND(self);
168
169         // set watertype/waterlevel
170         origin1 = self.origin;
171         origin1_z += self.mins_z + 1;
172         self.waterlevel = WATERLEVEL_NONE;
173
174         self.watertype = (pointcontents(origin1) == CONTENT_WATER);
175
176         if(self.watertype)
177         {
178                 self.waterlevel = WATERLEVEL_WETFEET;
179                 origin1_z = self.origin_z + (self.mins_z + self.maxs_z) * 0.5;
180                 if(pointcontents(origin1) == CONTENT_WATER)
181                 {
182                         self.waterlevel = WATERLEVEL_SWIMMING;
183                         origin1_z = self.origin_z + 22;
184                         if(pointcontents(origin1) == CONTENT_WATER)
185                                 self.waterlevel = WATERLEVEL_SUBMERGED;
186                 }
187         }
188
189         if(IS_ONGROUND(self) || self.velocity_z <= 0 || pmove_waterjumptime <= 0)
190                 pmove_waterjumptime = 0;
191 }
192
193 #ifdef CSQC
194 .float t_fraction;
195 .vector t_plane_normal;
196 .float t_startsolid;
197 .vector t_endpos;
198 entity t_tracebox(vector thevec1, vector tmin, vector tmax, vector thevec2, float type, entity forentity)
199 {
200         entity e = spawn();
201         tracebox(thevec1, tmin, tmax, thevec2, type, forentity);
202         e.t_fraction = trace_fraction;
203         e.t_plane_normal = trace_plane_normal;
204         e.t_startsolid = trace_startsolid;
205         e.t_endpos = trace_endpos;
206
207         return e;
208 }
209 #endif
210
211 void PM_ClientMovement_Move()
212 {
213 #ifdef CSQC
214
215         int bump;
216         float t;
217         float f;
218         vector neworigin = '0 0 0';
219         vector currentorigin2 = '0 0 0';
220         vector neworigin2 = '0 0 0';
221         vector primalvelocity;
222         entity trace = world, trace2 = world, trace3 = world;
223         entity oldtrace = world;
224
225         PM_ClientMovement_UpdateStatus();
226         primalvelocity = self.velocity;
227         for (bump = 0, t = PHYS_INPUT_TIMELENGTH; bump < 8 && dotproduct(self.velocity, self.velocity) > 0; bump++)
228         {
229                 neworigin = self.velocity * t;
230                 trace = t_tracebox(self.origin, self.mins, self.maxs, neworigin, MOVE_NORMAL, self);
231                 if(trace.t_fraction < 1 && trace.t_plane_normal_z == 0)
232                 {
233                         // may be a step or wall, try stepping up
234                         // first move forward at a higher level
235                         currentorigin2 = self.origin;
236                         currentorigin2_z += PHYS_STEPHEIGHT;
237                         neworigin2 = neworigin;
238                         neworigin_z = self.origin_z + PHYS_STEPHEIGHT;
239
240                         trace2 = t_tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
241                         if(!trace2.t_startsolid)
242                         {
243                                 // then move down from there
244                                 currentorigin2 = trace2.t_endpos;
245                                 neworigin2 = trace2.t_endpos;
246                                 neworigin_z = self.origin_z;
247
248                                 trace3 = t_tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
249                                 // accept the new trace if it made some progress
250                                 if(fabs(trace3.t_endpos_x - trace.t_endpos_x) >= 0.03125 || fabs(trace3.t_endpos_y - trace.t_endpos_y) >= 0.03125)
251                                 {
252                                         oldtrace = trace;
253                                         trace = trace2;
254                                         trace.t_endpos = trace3.t_endpos;
255                                 }
256                                 else if(oldtrace) // TODO: check if we even need this
257                                 {
258                                         trace = oldtrace;
259                                 }
260                         }
261                 }
262
263                 // check if it moved at all
264                 if(trace.t_fraction >= 0.001)
265                         setorigin(self, trace.t_endpos);
266
267                 // check if it moved all the way
268                 if(trace.t_fraction == 1)
269                         break;
270
271                 // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
272                 // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
273                 // this got commented out in a change that supposedly makes the code match QW better
274                 // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block
275                 if(trace.t_plane_normal_z > 0.7)
276                         SET_ONGROUND(self);
277
278                 t -= t * trace.t_fraction;
279
280                 f = dotproduct(self.velocity, trace.t_plane_normal);
281                 self.velocity = self.velocity + trace.t_plane_normal * -f;
282         }
283         if(pmove_waterjumptime > 0)
284                 self.velocity = primalvelocity;
285
286         if(trace && !wasfreed(trace)) { remove(trace); }
287         if(trace2 && !wasfreed(trace2)) { remove(trace2); }
288         if(trace3 && !wasfreed(trace3)) { remove(trace3); }
289 #endif
290 }
291
292 #if 0
293 void PM_ClientMovement_Move()
294 {
295 #ifdef CSQC
296         float t = PHYS_INPUT_TIMELENGTH;
297         vector primalvelocity = self.velocity;
298         PM_ClientMovement_UpdateStatus();
299         float bump = 0;
300         for (bump = 0; bump < 8 && self.velocity * self.velocity > 0; bump++)
301         {
302                 vector neworigin = self.origin + t * self.velocity;
303                 tracebox(self.origin, self.mins, self.maxs, neworigin, MOVE_NORMAL, self);
304                 float old_trace1_fraction = trace_fraction;
305                 vector old_trace1_endpos = trace_endpos;
306                 vector old_trace1_plane_normal = trace_plane_normal;
307                 if (trace_fraction < 1 && trace_plane_normal_z == 0)
308                 {
309                         // may be a step or wall, try stepping up
310                         // first move forward at a higher level
311                         vector currentorigin2 = self.origin;
312                         currentorigin2_z += PHYS_STEPHEIGHT;
313                         vector neworigin2 = neworigin;
314                         neworigin2_z = self.origin_z + PHYS_STEPHEIGHT;
315                         tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
316                         if (!trace_startsolid)
317                         {
318                                 // then move down from there
319                                 currentorigin2 = trace_endpos;
320                                 neworigin2 = trace_endpos;
321                                 neworigin2_z = self.origin_z;
322                                 float old_trace2_fraction = trace_fraction;
323                                 vector old_trace2_plane_normal = trace_plane_normal;
324                                 tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
325                                 // accept the new trace if it made some progress
326                                 if (fabs(trace_endpos_x - old_trace1_endpos_x) >= 0.03125 || fabs(trace_endpos_y - old_trace1_endpos_y) >= 0.03125)
327                                 {
328                                         trace_fraction = old_trace2_fraction;
329                                         trace_endpos = trace_endpos;
330                                         trace_plane_normal = old_trace2_plane_normal;
331                                 }
332                                 else
333                                 {
334                                         trace_fraction = old_trace1_fraction;
335                                         trace_endpos = old_trace1_endpos;
336                                         trace_plane_normal = old_trace1_plane_normal;
337                                 }
338                         }
339                 }
340
341                 // check if it moved at all
342                 if (trace_fraction >= 0.001)
343                         setorigin(self, trace_endpos);
344
345                 // check if it moved all the way
346                 if (trace_fraction == 1)
347                         break;
348
349                 // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
350                 // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
351                 // this got commented out in a change that supposedly makes the code match QW better
352                 // so if this is broken, maybe put it in an if (cls.protocol != PROTOCOL_QUAKEWORLD) block
353                 if (trace_plane_normal_z > 0.7)
354                         SET_ONGROUND(self);
355
356                 t -= t * trace_fraction;
357
358                 float f = self.velocity * trace_plane_normal;
359                 self.velocity -= f * trace_plane_normal;
360         }
361         if (pmove_waterjumptime > 0)
362                 self.velocity = primalvelocity;
363 #endif
364 }
365 #endif
366
367 void CPM_PM_Aircontrol(vector wishdir, float wishspeed)
368 {
369         float k = 32 * (2 * IsMoveInDirection(PHYS_INPUT_MOVEVALUES(self), 0) - 1);
370         if (k <= 0)
371                 return;
372
373         k *= bound(0, wishspeed / PHYS_MAXAIRSPEED, 1);
374
375         float zspeed = self.velocity_z;
376         self.velocity_z = 0;
377         float xyspeed = vlen(self.velocity);
378         self.velocity = normalize(self.velocity);
379
380         float dot = self.velocity * wishdir;
381
382         if (dot > 0) // we can't change direction while slowing down
383         {
384                 k *= pow(dot, PHYS_AIRCONTROL_POWER) * PHYS_INPUT_TIMELENGTH;
385                 xyspeed = max(0, xyspeed - PHYS_AIRCONTROL_PENALTY * sqrt(max(0, 1 - dot*dot)) * k/32);
386                 k *= PHYS_AIRCONTROL;
387                 self.velocity = normalize(self.velocity * xyspeed + wishdir * k);
388         }
389
390         self.velocity = self.velocity * xyspeed;
391         self.velocity_z = zspeed;
392 }
393
394 float AdjustAirAccelQW(float accelqw, float factor)
395 {
396         return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
397 }
398
399 // example config for alternate speed clamping:
400 //   sv_airaccel_qw 0.8
401 //   sv_airaccel_sideways_friction 0
402 //   prvm_globalset server speedclamp_mode 1
403 //     (or 2)
404 void PM_Accelerate(vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
405 {
406         float speedclamp = stretchfactor > 0 ? stretchfactor
407         : accelqw < 0 ? 1 // full clamping, no stretch
408         : -1; // no clamping
409
410         accelqw = fabs(accelqw);
411
412         if (GAMEPLAYFIX_Q2AIRACCELERATE)
413                 wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
414
415         float vel_straight = self.velocity * wishdir;
416         float vel_z = self.velocity_z;
417         vector vel_xy = vec2(self.velocity);
418         vector vel_perpend = vel_xy - vel_straight * wishdir;
419
420         float step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
421
422         float vel_xy_current  = vlen(vel_xy);
423         if (speedlimit)
424                 accelqw = AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
425         float vel_xy_forward =  vel_xy_current  + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
426         float vel_xy_backward = vel_xy_current  - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
427         vel_xy_backward = max(0, vel_xy_backward); // not that it REALLY occurs that this would cause wrong behaviour afterwards
428         vel_straight =          vel_straight    + bound(0, wishspeed - vel_straight,   step) * accelqw + step * (1 - accelqw);
429
430         if (sidefric < 0 && (vel_perpend*vel_perpend))
431                 // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
432         {
433                 float f = max(0, 1 + PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
434                 float fmin = (vel_xy_backward * vel_xy_backward - vel_straight * vel_straight) / (vel_perpend * vel_perpend);
435                 // assume: fmin > 1
436                 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
437                 // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
438                 // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
439                 // obviously, this cannot be
440                 if (fmin <= 0)
441                         vel_perpend *= f;
442                 else
443                 {
444                         fmin = sqrt(fmin);
445                         vel_perpend *= max(fmin, f);
446                 }
447         }
448         else
449                 vel_perpend *= max(0, 1 - PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
450
451         vel_xy = vel_straight * wishdir + vel_perpend;
452
453         if (speedclamp >= 0)
454         {
455                 float vel_xy_preclamp;
456                 vel_xy_preclamp = vlen(vel_xy);
457                 if (vel_xy_preclamp > 0) // prevent division by zero
458                 {
459                         vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
460                         if (vel_xy_current < vel_xy_preclamp)
461                                 vel_xy *= (vel_xy_current / vel_xy_preclamp);
462                 }
463         }
464
465         self.velocity = vel_xy + vel_z * '0 0 1';
466 }
467
468 void PM_AirAccelerate(vector wishdir, float wishspeed)
469 {
470         if (wishspeed == 0)
471                 return;
472
473         vector curvel = self.velocity;
474         curvel_z = 0;
475         float curspeed = vlen(curvel);
476
477         if (wishspeed > curspeed * 1.01)
478                 wishspeed = min(wishspeed, curspeed + PHYS_WARSOWBUNNY_AIRFORWARDACCEL * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH);
479         else
480         {
481                 float f = max(0, (PHYS_WARSOWBUNNY_TOPSPEED - curspeed) / (PHYS_WARSOWBUNNY_TOPSPEED - PHYS_MAXSPEED(self)));
482                 wishspeed = max(curspeed, PHYS_MAXSPEED(self)) + PHYS_WARSOWBUNNY_ACCEL * f * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH;
483         }
484         vector wishvel = wishdir * wishspeed;
485         vector acceldir = wishvel - curvel;
486         float addspeed = vlen(acceldir);
487         acceldir = normalize(acceldir);
488
489         float accelspeed = min(addspeed, PHYS_WARSOWBUNNY_TURNACCEL * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH);
490
491         if (PHYS_WARSOWBUNNY_BACKTOSIDERATIO < 1)
492         {
493                 vector curdir = normalize(curvel);
494                 float dot = acceldir * curdir;
495                 if (dot < 0)
496                         acceldir -= (1 - PHYS_WARSOWBUNNY_BACKTOSIDERATIO) * dot * curdir;
497         }
498
499         self.velocity += accelspeed * acceldir;
500 }
501
502
503 /*
504 =============
505 PlayerJump
506
507 When you press the jump key
508 returns true if handled
509 =============
510 */
511 float PlayerJump (void)
512 {
513         if (PHYS_FROZEN(self))
514                 return true; // no jumping in freezetag when frozen
515
516 #ifdef SVQC
517         if (self.player_blocked)
518                 return true; // no jumping while blocked
519 #endif
520
521         float doublejump = false;
522         float mjumpheight = PHYS_JUMPVELOCITY;
523
524         player_multijump = doublejump;
525         player_jumpheight = mjumpheight;
526 #ifdef SVQC
527         if (MUTATOR_CALLHOOK(PlayerJump))
528 #elif defined(CSQC)
529         if(PM_multijump_checkjump())
530 #endif
531                 return true;
532
533         doublejump = player_multijump;
534         mjumpheight = player_jumpheight;
535
536         if (PHYS_DOUBLEJUMP)
537         {
538                 tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
539                 if (trace_fraction < 1 && trace_plane_normal_z > 0.7)
540                 {
541                         doublejump = true;
542
543                         // we MUST clip velocity here!
544                         float f;
545                         f = self.velocity * trace_plane_normal;
546                         if (f < 0)
547                                 self.velocity -= f * trace_plane_normal;
548                 }
549         }
550
551         if (self.waterlevel >= WATERLEVEL_SWIMMING)
552         {
553                 self.velocity_z = PHYS_MAXSPEED(self) * 0.7;
554                 return true;
555         }
556
557         if (!doublejump)
558                 if (!IS_ONGROUND(self))
559                         return IS_JUMP_HELD(self);
560
561         if (PHYS_TRACK_CANJUMP(self))
562                 if (IS_JUMP_HELD(self))
563                         return true;
564
565         // sv_jumpspeedcap_min/sv_jumpspeedcap_max act as baseline
566         // velocity bounds.  Final velocity is bound between (jumpheight *
567         // min + jumpheight) and (jumpheight * max + jumpheight);
568
569         if(PHYS_JUMPSPEEDCAP_MIN)
570         {
571                 float minjumpspeed = mjumpheight * PHYS_JUMPSPEEDCAP_MIN;
572
573                 if (self.velocity_z < minjumpspeed)
574                         mjumpheight += minjumpspeed - self.velocity_z;
575         }
576
577         if(PHYS_JUMPSPEEDCAP_MAX)
578         {
579                 // don't do jump speedcaps on ramps to preserve old xonotic ramjump style
580                 tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
581
582                 if (!(trace_fraction < 1 && trace_plane_normal_z < 0.98 && PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS))
583                 {
584                         float maxjumpspeed = mjumpheight * PHYS_JUMPSPEEDCAP_MAX;
585
586                         if (self.velocity_z > maxjumpspeed)
587                                 mjumpheight -= self.velocity_z - maxjumpspeed;
588                 }
589         }
590
591         if (!WAS_ONGROUND(self))
592         {
593                 if(autocvar_speedmeter)
594                         dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
595                 if(self.lastground < time - 0.3)
596                 {
597                         self.velocity_x *= (1 - PHYS_FRICTION_ONLAND);
598                         self.velocity_y *= (1 - PHYS_FRICTION_ONLAND);
599                 }
600                 if(self.jumppadcount > 1)
601                         dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
602                 self.jumppadcount = 0;
603         }
604
605         self.velocity_z += mjumpheight;
606
607         UNSET_ONGROUND(self);
608         SET_JUMP_HELD(self);
609
610 #ifdef SVQC
611
612         self.oldvelocity_z = self.velocity_z;
613
614         animdecide_setaction(self, ANIMACTION_JUMP, true);
615
616         if (autocvar_g_jump_grunt)
617                 PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
618 #endif
619         return true;
620 }
621
622 void CheckWaterJump()
623 {
624 // check for a jump-out-of-water
625         makevectors(PHYS_INPUT_ANGLES(self));
626         vector start = self.origin;
627         start_z += 8;
628         v_forward_z = 0;
629         normalize(v_forward);
630         vector end = start + v_forward*24;
631         traceline (start, end, true, self);
632         if (trace_fraction < 1)
633         {       // solid at waist
634                 start_z = start_z + self.maxs_z - 8;
635                 end = start + v_forward*24;
636                 self.movedir = trace_plane_normal * -50;
637                 traceline(start, end, true, self);
638                 if (trace_fraction == 1)
639                 {       // open at eye level
640                         self.velocity_z = 225;
641 #ifdef SVQC
642                         self.flags |= FL_WATERJUMP;
643                         SET_JUMP_HELD(self);
644                         self.teleport_time = time + 2;  // safety net
645 #endif
646                 }
647         }
648 }
649
650
651 #ifdef SVQC
652         #define JETPACK_JUMP(s) s.cvar_cl_jetpack_jump
653 #elif defined(CSQC)
654         float autocvar_cl_jetpack_jump;
655         #define JETPACK_JUMP(s) autocvar_cl_jetpack_jump
656 #endif
657 .float jetpack_stopped;
658 // Hack: shouldn't need to know about this
659 .float multijump_count;
660 void CheckPlayerJump()
661 {
662 #ifdef SVQC
663         float was_flying = ITEMS(self) & IT_USING_JETPACK;
664 #endif
665         if (JETPACK_JUMP(self) < 2)
666 #ifdef SVQC
667                 ITEMS(self) &= ~IT_USING_JETPACK;
668 #endif
669
670         if(PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self))
671         {
672 #ifdef SVQC
673                 float air_jump = !PlayerJump() || self.multijump_count > 0; // PlayerJump() has important side effects
674                 float activate = JETPACK_JUMP(self) && air_jump && PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self);
675                 float has_fuel = !autocvar_g_jetpack_fuel || self.ammo_fuel || ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO;
676 #else
677                 PlayerJump(); // Client only
678                 float has_fuel = true; // TODO
679 #endif
680                 if (!(ITEMS(self) & IT_JETPACK)) { }
681                 else if (self.jetpack_stopped) { }
682                 else if (!has_fuel)
683                 {
684 #ifdef SVQC
685                         if (was_flying) // TODO: ran out of fuel message
686                                 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
687                         else if (activate)
688                                 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
689 #endif
690                         self.jetpack_stopped = true;
691 #ifdef SVQC
692                         ITEMS(self) &= ~IT_USING_JETPACK;
693 #endif
694                 }
695 #ifdef SVQC
696                 else if (activate && !PHYS_FROZEN(self))
697                         ITEMS(self) |= IT_USING_JETPACK;
698 #endif
699         }
700         else
701         {
702                 self.jetpack_stopped = false;
703 #ifdef SVQC
704                 ITEMS(self) &= ~IT_USING_JETPACK;
705 #endif
706         }
707         if (!PHYS_INPUT_BUTTON_JUMP(self))
708                 UNSET_JUMP_HELD(self);
709
710         if (self.waterlevel == WATERLEVEL_SWIMMING)
711                 CheckWaterJump();
712 }
713
714 float racecar_angle(float forward, float down)
715 {
716         if (forward < 0)
717         {
718                 forward = -forward;
719                 down = -down;
720         }
721
722         float ret = vectoyaw('0 1 0' * down + '1 0 0' * forward);
723
724         float angle_mult = forward / (800 + forward);
725
726         if (ret > 180)
727                 return ret * angle_mult + 360 * (1 - angle_mult);
728         else
729                 return ret * angle_mult;
730 }
731
732 void RaceCarPhysics()
733 {
734 #ifdef SVQC
735         // using this move type for "big rigs"
736         // the engine does not push the entity!
737
738         vector rigvel;
739
740         vector angles_save = self.angles;
741         float accel = bound(-1, PHYS_INPUT_MOVEVALUES(self).x / PHYS_MAXSPEED(self), 1);
742         float steer = bound(-1, PHYS_INPUT_MOVEVALUES(self).y / PHYS_MAXSPEED(self), 1);
743
744         if (g_bugrigs_reverse_speeding)
745         {
746                 if (accel < 0)
747                 {
748                         // back accel is DIGITAL
749                         // to prevent speedhack
750                         if (accel < -0.5)
751                                 accel = -1;
752                         else
753                                 accel = 0;
754                 }
755         }
756
757         self.angles_x = 0;
758         self.angles_z = 0;
759         makevectors(self.angles); // new forward direction!
760
761         if (IS_ONGROUND(self) || g_bugrigs_air_steering)
762         {
763                 float myspeed = self.velocity * v_forward;
764                 float upspeed = self.velocity * v_up;
765
766                 // responsiveness factor for steering and acceleration
767                 float f = 1 / (1 + pow(max(-myspeed, myspeed) / g_bugrigs_speed_ref, g_bugrigs_speed_pow));
768                 //MAXIMA: f(v) := 1 / (1 + (v / g_bugrigs_speed_ref) ^ g_bugrigs_speed_pow);
769
770                 float steerfactor;
771                 if (myspeed < 0 && g_bugrigs_reverse_spinning)
772                         steerfactor = -myspeed * g_bugrigs_steer;
773                 else
774                         steerfactor = -myspeed * f * g_bugrigs_steer;
775
776                 float accelfactor;
777                 if (myspeed < 0 && g_bugrigs_reverse_speeding)
778                         accelfactor = g_bugrigs_accel;
779                 else
780                         accelfactor = f * g_bugrigs_accel;
781                 //MAXIMA: accel(v) := f(v) * g_bugrigs_accel;
782
783                 if (accel < 0)
784                 {
785                         if (myspeed > 0)
786                         {
787                                 myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor - g_bugrigs_friction_brake * accel));
788                         }
789                         else
790                         {
791                                 if (!g_bugrigs_reverse_speeding)
792                                         myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
793                         }
794                 }
795                 else
796                 {
797                         if (myspeed >= 0)
798                         {
799                                 myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
800                         }
801                         else
802                         {
803                                 if (g_bugrigs_reverse_stopping)
804                                         myspeed = 0;
805                                 else
806                                         myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor + g_bugrigs_friction_brake * accel));
807                         }
808                 }
809                 // terminal velocity = velocity at which 50 == accelfactor, that is, 1549 units/sec
810                 //MAXIMA: friction(v) := g_bugrigs_friction_floor;
811
812                 self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
813                 makevectors(self.angles); // new forward direction!
814
815                 myspeed += accel * accelfactor * PHYS_INPUT_TIMELENGTH;
816
817                 rigvel = myspeed * v_forward + '0 0 1' * upspeed;
818         }
819         else
820         {
821                 float myspeed = vlen(self.velocity);
822
823                 // responsiveness factor for steering and acceleration
824                 float f = 1 / (1 + pow(max(0, myspeed / g_bugrigs_speed_ref), g_bugrigs_speed_pow));
825                 float steerfactor = -myspeed * f;
826                 self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
827
828                 rigvel = self.velocity;
829                 makevectors(self.angles); // new forward direction!
830         }
831
832         rigvel *= max(0, 1 - vlen(rigvel) * g_bugrigs_friction_air * PHYS_INPUT_TIMELENGTH);
833         //MAXIMA: airfriction(v) := v * v * g_bugrigs_friction_air;
834         //MAXIMA: total_acceleration(v) := accel(v) - friction(v) - airfriction(v);
835         //MAXIMA: solve(total_acceleration(v) = 0, v);
836
837         if (g_bugrigs_planar_movement)
838         {
839                 vector rigvel_xy, neworigin, up;
840                 float mt;
841
842                 rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY; // 4x gravity plays better
843                 rigvel_xy = vec2(rigvel);
844
845                 if (g_bugrigs_planar_movement_car_jumping)
846                         mt = MOVE_NORMAL;
847                 else
848                         mt = MOVE_NOMONSTERS;
849
850                 tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 1024', mt, self);
851                 up = trace_endpos - self.origin;
852
853                 // BUG RIGS: align the move to the surface instead of doing collision testing
854                 // can we move?
855                 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos + rigvel_xy * PHYS_INPUT_TIMELENGTH, mt, self);
856
857                 // align to surface
858                 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos - up + '0 0 1' * rigvel_z * PHYS_INPUT_TIMELENGTH, mt, self);
859
860                 if (trace_fraction < 0.5)
861                 {
862                         trace_fraction = 1;
863                         neworigin = self.origin;
864                 }
865                 else
866                         neworigin = trace_endpos;
867
868                 if (trace_fraction < 1)
869                 {
870                         // now set angles_x so that the car points parallel to the surface
871                         self.angles = vectoangles(
872                                         '1 0 0' * v_forward_x * trace_plane_normal_z
873                                         +
874                                         '0 1 0' * v_forward_y * trace_plane_normal_z
875                                         +
876                                         '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y)
877                                         );
878                         SET_ONGROUND(self);
879                 }
880                 else
881                 {
882                         // now set angles_x so that the car points forward, but is tilted in velocity direction
883                         UNSET_ONGROUND(self);
884                 }
885
886                 self.velocity = (neworigin - self.origin) * (1.0 / PHYS_INPUT_TIMELENGTH);
887                 self.movetype = MOVETYPE_NOCLIP;
888         }
889         else
890         {
891                 rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY; // 4x gravity plays better
892                 self.velocity = rigvel;
893                 self.movetype = MOVETYPE_FLY;
894         }
895
896         trace_fraction = 1;
897         tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 4', MOVE_NORMAL, self);
898         if (trace_fraction != 1)
899         {
900                 self.angles = vectoangles2(
901                                 '1 0 0' * v_forward_x * trace_plane_normal_z
902                                 +
903                                 '0 1 0' * v_forward_y * trace_plane_normal_z
904                                 +
905                                 '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y),
906                                 trace_plane_normal
907                                 );
908         }
909         else
910         {
911                 vector vel_local;
912
913                 vel_local_x = v_forward * self.velocity;
914                 vel_local_y = v_right * self.velocity;
915                 vel_local_z = v_up * self.velocity;
916
917                 self.angles_x = racecar_angle(vel_local_x, vel_local_z);
918                 self.angles_z = racecar_angle(-vel_local_y, vel_local_z);
919         }
920
921         // smooth the angles
922         vector vf1, vu1, smoothangles;
923         makevectors(self.angles);
924         float f = bound(0, PHYS_INPUT_TIMELENGTH * g_bugrigs_angle_smoothing, 1);
925         if (f == 0)
926                 f = 1;
927         vf1 = v_forward * f;
928         vu1 = v_up * f;
929         makevectors(angles_save);
930         vf1 = vf1 + v_forward * (1 - f);
931         vu1 = vu1 + v_up * (1 - f);
932         smoothangles = vectoangles2(vf1, vu1);
933         self.angles_x = -smoothangles_x;
934         self.angles_z =  smoothangles_z;
935 #endif
936 }
937
938 string specialcommand = "xwxwxsxsxaxdxaxdx1x ";
939 .float specialcommand_pos;
940 void SpecialCommand()
941 {
942 #ifdef SVQC
943 #ifdef TETRIS
944         TetrisImpulse();
945 #else
946         if (!CheatImpulse(99))
947                 print("A hollow voice says \"Plugh\".\n");
948 #endif
949 #endif
950 }
951
952 float PM_check_keepaway(void)
953 {
954 #ifdef SVQC
955         return (self.ballcarried && g_keepaway) ? autocvar_g_keepaway_ballcarrier_highspeed : 1;
956 #else
957         return 1;
958 #endif
959 }
960
961 void PM_check_race_movetime(void)
962 {
963 #ifdef SVQC
964         self.race_movetime_frac += PHYS_INPUT_TIMELENGTH;
965         float f = floor(self.race_movetime_frac);
966         self.race_movetime_frac -= f;
967         self.race_movetime_count += f;
968         self.race_movetime = self.race_movetime_frac + self.race_movetime_count;
969 #endif
970 }
971
972 float PM_check_specialcommand(float buttons)
973 {
974 #ifdef SVQC
975         string c;
976         if (!buttons)
977                 c = "x";
978         else if (buttons == 1)
979                 c = "1";
980         else if (buttons == 2)
981                 c = " ";
982         else if (buttons == 128)
983                 c = "s";
984         else if (buttons == 256)
985                 c = "w";
986         else if (buttons == 512)
987                 c = "a";
988         else if (buttons == 1024)
989                 c = "d";
990         else
991                 c = "?";
992
993         if (c == substring(specialcommand, self.specialcommand_pos, 1))
994         {
995                 self.specialcommand_pos += 1;
996                 if (self.specialcommand_pos >= strlen(specialcommand))
997                 {
998                         self.specialcommand_pos = 0;
999                         SpecialCommand();
1000                         return true;
1001                 }
1002         }
1003         else if (self.specialcommand_pos && (c != substring(specialcommand, self.specialcommand_pos - 1, 1)))
1004                 self.specialcommand_pos = 0;
1005 #endif
1006         return false;
1007 }
1008
1009 void PM_check_nickspam(void)
1010 {
1011 #ifdef SVQC
1012         if (time >= self.nickspamtime)
1013                 return;
1014         if (self.nickspamcount >= autocvar_g_nick_flood_penalty_yellow)
1015         {
1016                 // slight annoyance for nick change scripts
1017                 PHYS_INPUT_MOVEVALUES(self) = -1 * PHYS_INPUT_MOVEVALUES(self);
1018                 self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = self.BUTTON_ZOOM = self.BUTTON_CROUCH = self.BUTTON_HOOK = self.BUTTON_USE = 0;
1019
1020                 if (self.nickspamcount >= autocvar_g_nick_flood_penalty_red) // if you are persistent and the slight annoyance above does not stop you, I'll show you!
1021                 {
1022                         PHYS_INPUT_ANGLES(self)_x = random() * 360;
1023                         PHYS_INPUT_ANGLES(self)_y = random() * 360;
1024                         // at least I'm not forcing retardedview by also assigning to angles_z
1025                         self.fixangle = true;
1026                 }
1027         }
1028 #endif
1029 }
1030
1031 void PM_check_punch()
1032 {
1033 #ifdef SVQC
1034         if (self.punchangle != '0 0 0')
1035         {
1036                 float f = vlen(self.punchangle) - 10 * PHYS_INPUT_TIMELENGTH;
1037                 if (f > 0)
1038                         self.punchangle = normalize(self.punchangle) * f;
1039                 else
1040                         self.punchangle = '0 0 0';
1041         }
1042
1043         if (self.punchvector != '0 0 0')
1044         {
1045                 float f = vlen(self.punchvector) - 30 * PHYS_INPUT_TIMELENGTH;
1046                 if (f > 0)
1047                         self.punchvector = normalize(self.punchvector) * f;
1048                 else
1049                         self.punchvector = '0 0 0';
1050         }
1051 #endif
1052 }
1053
1054 void PM_check_spider(void)
1055 {
1056 #ifdef SVQC
1057         if (time >= self.spider_slowness)
1058                 return;
1059         PHYS_MAXSPEED(self) *= 0.5; // half speed while slow from spider
1060         self.stat_sv_airspeedlimit_nonqw *= 0.5;
1061 #endif
1062 }
1063
1064 // predict frozen movement, as frozen players CAN move in some cases
1065 void PM_check_frozen(void)
1066 {
1067         if (!PHYS_FROZEN(self))
1068                 return;
1069         if (PHYS_DODGING_FROZEN
1070 #ifdef SVQC
1071         && IS_REAL_CLIENT(self)
1072 #endif
1073         )
1074         {
1075                 PHYS_INPUT_MOVEVALUES(self)_x = bound(-5, PHYS_INPUT_MOVEVALUES(self).x, 5);
1076                 PHYS_INPUT_MOVEVALUES(self)_y = bound(-5, PHYS_INPUT_MOVEVALUES(self).y, 5);
1077                 PHYS_INPUT_MOVEVALUES(self)_z = bound(-5, PHYS_INPUT_MOVEVALUES(self).z, 5);
1078         }
1079         else
1080                 PHYS_INPUT_MOVEVALUES(self) = '0 0 0';
1081
1082         vector midpoint = ((self.absmin + self.absmax) * 0.5);
1083         if (pointcontents(midpoint) == CONTENT_WATER)
1084         {
1085                 self.velocity = self.velocity * 0.5;
1086
1087                 if (pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
1088                         self.velocity_z = 200;
1089         }
1090 }
1091
1092 void PM_check_hitground()
1093 {
1094 #ifdef SVQC
1095         if (IS_ONGROUND(self))
1096         if (IS_PLAYER(self)) // no fall sounds for observers thank you very much
1097         if (self.wasFlying)
1098         {
1099                 self.wasFlying = 0;
1100                 if (self.waterlevel < WATERLEVEL_SWIMMING)
1101                 if (time >= self.ladder_time)
1102                 if (!self.hook)
1103                 {
1104                         self.nextstep = time + 0.3 + random() * 0.1;
1105                         trace_dphitq3surfaceflags = 0;
1106                         tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
1107                         if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS))
1108                         {
1109                                 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
1110                                         GlobalSound(globalsound_metalfall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
1111                                 else
1112                                         GlobalSound(globalsound_fall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
1113                         }
1114                 }
1115         }
1116 #endif
1117 }
1118
1119 void PM_check_blocked(void)
1120 {
1121 #ifdef SVQC
1122         if (!self.player_blocked)
1123                 return;
1124         PHYS_INPUT_MOVEVALUES(self) = '0 0 0';
1125         self.disableclientprediction = 1;
1126 #endif
1127 }
1128
1129 #ifdef SVQC
1130 float speedaward_lastsent;
1131 float speedaward_lastupdate;
1132 #endif
1133 void PM_check_race(void)
1134 {
1135 #ifdef SVQC
1136         if(!(g_cts || g_race))
1137                 return;
1138         if (vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed)
1139         {
1140                 speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1');
1141                 speedaward_holder = self.netname;
1142                 speedaward_uid = self.crypto_idfp;
1143                 speedaward_lastupdate = time;
1144         }
1145         if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
1146         {
1147                 string rr = (g_cts) ? CTS_RECORD : RACE_RECORD;
1148                 race_send_speedaward(MSG_ALL);
1149                 speedaward_lastsent = speedaward_speed;
1150                 if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
1151                 {
1152                         speedaward_alltimebest = speedaward_speed;
1153                         speedaward_alltimebest_holder = speedaward_holder;
1154                         speedaward_alltimebest_uid = speedaward_uid;
1155                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
1156                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
1157                         race_send_speedaward_alltimebest(MSG_ALL);
1158                 }
1159         }
1160 #endif
1161 }
1162
1163 void PM_check_vortex(void)
1164 {
1165 #ifdef SVQC
1166         // WEAPONTODO
1167         float xyspeed = vlen(vec2(self.velocity));
1168         if (self.weapon == WEP_VORTEX && WEP_CVAR(vortex, charge) && WEP_CVAR(vortex, charge_velocity_rate) && xyspeed > WEP_CVAR(vortex, charge_minspeed))
1169         {
1170                 // add a maximum of charge_velocity_rate when going fast (f = 1), gradually increasing from minspeed (f = 0) to maxspeed
1171                 xyspeed = min(xyspeed, WEP_CVAR(vortex, charge_maxspeed));
1172                 float f = (xyspeed - WEP_CVAR(vortex, charge_minspeed)) / (WEP_CVAR(vortex, charge_maxspeed) - WEP_CVAR(vortex, charge_minspeed));
1173                 // add the extra charge
1174                 self.vortex_charge = min(1, self.vortex_charge + WEP_CVAR(vortex, charge_velocity_rate) * f * PHYS_INPUT_TIMELENGTH);
1175         }
1176 #endif
1177 }
1178
1179 void PM_fly(float maxspd_mod)
1180 {
1181         // noclipping or flying
1182         UNSET_ONGROUND(self);
1183
1184         self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
1185         makevectors(PHYS_INPUT_ANGLES(self));
1186         //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
1187         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1188                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y
1189                                         + '0 0 1' * PHYS_INPUT_MOVEVALUES(self).z;
1190         // acceleration
1191         vector wishdir = normalize(wishvel);
1192         float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
1193         if (time >= self.teleport_time)
1194                 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
1195 }
1196
1197 void PM_swim(float maxspd_mod)
1198 {
1199         // swimming
1200         UNSET_ONGROUND(self);
1201
1202         float jump = PHYS_INPUT_BUTTON_JUMP(self);
1203         // water jump only in certain situations
1204         // this mimics quakeworld code
1205         if (jump && self.waterlevel == WATERLEVEL_SWIMMING && self.velocity_z >= -180)
1206         {
1207                 vector yawangles = '0 1 0' * PHYS_INPUT_ANGLES(self).y;
1208                 makevectors(yawangles);
1209                 vector forward = v_forward;
1210                 vector spot = self.origin + 24 * forward;
1211                 spot_z += 8;
1212                 traceline(spot, spot, MOVE_NOMONSTERS, self);
1213                 if (trace_startsolid)
1214                 {
1215                         spot_z += 24;
1216                         traceline(spot, spot, MOVE_NOMONSTERS, self);
1217                         if (!trace_startsolid)
1218                         {
1219                                 self.velocity = forward * 50;
1220                                 self.velocity_z = 310;
1221                                 pmove_waterjumptime = 2;
1222                                 UNSET_ONGROUND(self);
1223                                 SET_JUMP_HELD(self);
1224                         }
1225                 }
1226         }
1227         makevectors(PHYS_INPUT_ANGLES(self));
1228         //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
1229         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1230                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y
1231                                         + '0 0 1' * PHYS_INPUT_MOVEVALUES(self).z;
1232         if (wishvel == '0 0 0')
1233                 wishvel = '0 0 -60'; // drift towards bottom
1234
1235         vector wishdir = normalize(wishvel);
1236         float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod) * 0.7;
1237
1238         if (IS_DUCKED(self))
1239         wishspeed *= 0.5;
1240
1241 //      if (pmove_waterjumptime <= 0) // TODO: use
1242     {
1243                 // water friction
1244                 float f = 1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION;
1245                 f = min(max(0, f), 1);
1246                 self.velocity *= f;
1247
1248                 f = wishspeed - self.velocity * wishdir;
1249                 if (f > 0)
1250                 {
1251                         float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, f);
1252                         self.velocity += accelspeed * wishdir;
1253                 }
1254
1255                 // holding jump button swims upward slowly
1256                 if (jump)
1257                 {
1258 #if 0
1259                         if (self.watertype & CONTENT_LAVA)
1260                                 self.velocity_z =  50;
1261                         else if (self.watertype & CONTENT_SLIME)
1262                                 self.velocity_z =  80;
1263                         else
1264                         {
1265                                 if (IS_NEXUIZ_DERIVED(gamemode))
1266 #endif
1267                                         self.velocity_z = 200;
1268 #if 0
1269                                 else
1270                                         self.velocity_z = 100;
1271                         }
1272 #endif
1273                 }
1274         }
1275         PM_ClientMovement_Move();
1276         // water acceleration
1277         PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
1278 }
1279
1280 void PM_ladder(float maxspd_mod)
1281 {
1282         // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
1283         UNSET_ONGROUND(self);
1284
1285         float g;
1286         g = PHYS_GRAVITY * PHYS_INPUT_TIMELENGTH;
1287         if (PHYS_ENTGRAVITY(self))
1288                 g *= PHYS_ENTGRAVITY(self);
1289         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1290         {
1291                 g *= 0.5;
1292                 self.velocity_z += g;
1293         }
1294
1295         self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
1296         makevectors(PHYS_INPUT_ANGLES(self));
1297         //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
1298         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self)_x
1299                                         + v_right * PHYS_INPUT_MOVEVALUES(self)_y
1300                                         + '0 0 1' * PHYS_INPUT_MOVEVALUES(self)_z;
1301         self.velocity_z += g;
1302         if (self.ladder_entity.classname == "func_water")
1303         {
1304                 float f = vlen(wishvel);
1305                 if (f > self.ladder_entity.speed)
1306                         wishvel *= (self.ladder_entity.speed / f);
1307
1308                 self.watertype = self.ladder_entity.skin;
1309                 f = self.ladder_entity.origin_z + self.ladder_entity.maxs_z;
1310                 if ((self.origin_z + self.view_ofs_z) < f)
1311                         self.waterlevel = WATERLEVEL_SUBMERGED;
1312                 else if ((self.origin_z + (self.mins_z + self.maxs_z) * 0.5) < f)
1313                         self.waterlevel = WATERLEVEL_SWIMMING;
1314                 else if ((self.origin_z + self.mins_z + 1) < f)
1315                         self.waterlevel = WATERLEVEL_WETFEET;
1316                 else
1317                 {
1318                         self.waterlevel = WATERLEVEL_NONE;
1319                         self.watertype = CONTENT_EMPTY;
1320                 }
1321         }
1322         // acceleration
1323         vector wishdir = normalize(wishvel);
1324         float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
1325         PM_ClientMovement_Move();
1326 #ifdef SVQC
1327         if (time >= self.teleport_time)
1328 #endif
1329                 // water acceleration
1330                 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE*maxspd_mod, 1, 0, 0, 0);
1331 }
1332
1333 void PM_jetpack(float maxspd_mod)
1334 {
1335         //makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
1336         makevectors(PHYS_INPUT_ANGLES(self));
1337         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self)_x
1338                                         + v_right * PHYS_INPUT_MOVEVALUES(self)_y;
1339         // add remaining speed as Z component
1340         float maxairspd = PHYS_MAXAIRSPEED * max(1, maxspd_mod);
1341         // fix speedhacks :P
1342         wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxairspd);
1343         // add the unused velocity as up component
1344         wishvel_z = 0;
1345
1346         // if (self.BUTTON_JUMP)
1347                 wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
1348
1349         // it is now normalized, so...
1350         float a_side = PHYS_JETPACK_ACCEL_SIDE;
1351         float a_up = PHYS_JETPACK_ACCEL_UP;
1352         float a_add = PHYS_JETPACK_ANTIGRAVITY * PHYS_GRAVITY;
1353
1354         wishvel_x *= a_side;
1355         wishvel_y *= a_side;
1356         wishvel_z *= a_up;
1357         wishvel_z += a_add;
1358
1359         float best = 0;
1360         //////////////////////////////////////////////////////////////////////////////////////
1361         // finding the maximum over all vectors of above form
1362         // with wishvel having an absolute value of 1
1363         //////////////////////////////////////////////////////////////////////////////////////
1364         // we're finding the maximum over
1365         //   f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
1366         // for z in the range from -1 to 1
1367         //////////////////////////////////////////////////////////////////////////////////////
1368         // maximum is EITHER attained at the single extreme point:
1369         float a_diff = a_side * a_side - a_up * a_up;
1370         float f;
1371         if (a_diff != 0)
1372         {
1373                 f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
1374                 if (f > -1 && f < 1) // can it be attained?
1375                 {
1376                         best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
1377                         //print("middle\n");
1378                 }
1379         }
1380         // OR attained at z = 1:
1381         f = (a_up + a_add) * (a_up + a_add);
1382         if (f > best)
1383         {
1384                 best = f;
1385                 //print("top\n");
1386         }
1387         // OR attained at z = -1:
1388         f = (a_up - a_add) * (a_up - a_add);
1389         if (f > best)
1390         {
1391                 best = f;
1392                 //print("bottom\n");
1393         }
1394         best = sqrt(best);
1395         //////////////////////////////////////////////////////////////////////////////////////
1396
1397         //print("best possible acceleration: ", ftos(best), "\n");
1398
1399         float fxy, fz;
1400         fxy = bound(0, 1 - (self.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / PHYS_JETPACK_MAXSPEED_SIDE, 1);
1401         if (wishvel_z - PHYS_GRAVITY > 0)
1402                 fz = bound(0, 1 - self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1403         else
1404                 fz = bound(0, 1 + self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1405
1406         float fvel;
1407         fvel = vlen(wishvel);
1408         wishvel_x *= fxy;
1409         wishvel_y *= fxy;
1410         wishvel_z = (wishvel_z - PHYS_GRAVITY) * fz + PHYS_GRAVITY;
1411
1412         fvel = min(1, vlen(wishvel) / best);
1413         if (PHYS_JETPACK_FUEL && !(ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO))
1414                 f = min(1, PHYS_AMMO_FUEL(self) / (PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel));
1415         else
1416                 f = 1;
1417
1418         //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
1419
1420         if (f > 0 && wishvel != '0 0 0')
1421         {
1422                 self.velocity = self.velocity + wishvel * f * PHYS_INPUT_TIMELENGTH;
1423                 UNSET_ONGROUND(self);
1424
1425 #ifdef SVQC
1426                 if (!(ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO))
1427                         self.ammo_fuel -= PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel * f;
1428
1429                 ITEMS(self) |= IT_USING_JETPACK;
1430
1431                 // jetpack also inhibits health regeneration, but only for 1 second
1432                 self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
1433 #endif
1434         }
1435
1436 #ifdef CSQC
1437         float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1438         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1439                 self.velocity_z -= g * 0.5;
1440         else
1441                 self.velocity_z -= g;
1442         PM_ClientMovement_Move();
1443         if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1444                 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1445                         self.velocity_z -= g * 0.5;
1446 #endif
1447 }
1448 #ifdef SVQC
1449 void SV_WalkMove ()
1450 {
1451         // if PHYS_INPUT_TIMELENGTH is 0 (due to client sending the same timestamp twice),
1452         // don't move
1453         if (PHYS_INPUT_TIMELENGTH <= 0)
1454                 return;
1455
1456 //      if (autocvar_sv_gameplayfix_unstickplayers)
1457 //              SV_CheckStuck (self);
1458
1459 //      applygravity = !SV_CheckWater(self) && self.movetype == MOVETYPE_WALK && !(self.flags & FL_WATERJUMP);
1460
1461 //      hitsupercontentsmask = SV_GenericHitSuperContentsMask(self);
1462
1463 //      SV_CheckVelocity(self);
1464
1465         // do a regular slide move unless it looks like you ran into a step
1466 //      float oldonground = self.flags & FL_ONGROUND;
1467
1468         vector start_origin = self.origin;
1469         vector start_velocity = self.velocity;
1470
1471         float clip = 0;
1472 //      clip = SV_FlyMove (self, PHYS_INPUT_TIMELENGTH, applygravity, NULL, hitsupercontentsmask, sv_gameplayfix_stepmultipletimes.integer ? sv_stepheight.value : 0);
1473
1474 //      if(sv_gameplayfix_downtracesupportsongroundflag.integer)
1475 //      if(!(clip & 1))
1476         {
1477                 // only try this if there was no floor in the way in the trace (no,
1478                 // this check seems to be not REALLY necessary, because if clip & 1,
1479                 // our trace will hit that thing too)
1480                 vector upmove = self.origin;
1481                 upmove_z++;
1482                 vector downmove = self.origin;
1483                 upmove_z--;
1484                 float type;
1485                 if (self.movetype == MOVETYPE_FLYMISSILE)
1486                         type = MOVE_MISSILE;
1487                 else if (self.movetype == MOVETYPE_FLY_WORLDONLY)
1488                         type = MOVE_WORLDONLY;
1489                 else if (self.solid == SOLID_TRIGGER || self.solid == SOLID_NOT)
1490                         type = MOVE_NOMONSTERS; // only clip against bmodels
1491                 else
1492                         type = MOVE_NORMAL;
1493                 vector entmins = self.mins;
1494                 vector entmaxs = self.maxs;
1495                 tracebox(upmove, entmins, entmaxs, downmove, type, self);
1496                 if(trace_fraction < 1 && trace_plane_normal_z > 0.7)
1497                         clip |= 1; // but we HAVE found a floor
1498         }
1499
1500         // if the move did not hit the ground at any point, we're not on ground
1501 //      if(!(clip & 1))
1502 //              self.flags = self.flags & ~FL_ONGROUND;
1503
1504 //      SV_CheckVelocity(self);
1505 //      SV_LinkEdict(self);
1506 //      SV_LinkEdict_TouchAreaGrid(self);
1507
1508         if(clip & 8) // teleport
1509                 return;
1510
1511         if (self.flags & FL_WATERJUMP)
1512                 return;
1513
1514 //      if (autocvar_sv_nostep)
1515 //              return;
1516
1517         vector originalmove_origin = self.origin;
1518         vector originalmove_velocity = self.velocity;
1519         float originalmove_flags = self.flags;
1520         entity originalmove_groundentity = self.groundentity;
1521
1522         // if move didn't block on a step, return
1523         if (clip & 2)
1524         {
1525                 // if move was not trying to move into the step, return
1526                 if (fabs(start_velocity_x) < 0.03125 && fabs(start_velocity_y) < 0.03125)
1527                         return;
1528
1529                 if (self.movetype != MOVETYPE_FLY)
1530                 {
1531                         // return if gibbed by a trigger
1532                         if (self.movetype != MOVETYPE_WALK)
1533                                 return;
1534
1535                         // return if attempting to jump while airborn (unless sv_jumpstep)
1536 //                      if (!autocvar_sv_jumpstep)
1537 //                              if (!oldonground && PRVM_serveredictfloat(self, waterlevel) == 0)
1538 //                                      return;
1539                 }
1540
1541                 // try moving up and forward to go up a step
1542                 // back to start pos
1543                 setorigin(self, start_origin);
1544                 self.velocity = start_velocity;
1545
1546                 // move up
1547                 vector upmove = '0 0 0';
1548                 upmove_z = autocvar_sv_stepheight;
1549 //              if(!SV_PushEntity(&trace, self, upmove, true))
1550 //              {
1551 //                      // we got teleported when upstepping... must abort the move
1552 //                      return;
1553 //              }
1554
1555                 // move forward
1556                 self.velocity_z = 0;
1557 //              clip = SV_FlyMove (self, PHYS_INPUT_TIMELENGTH, applygravity, stepnormal, hitsupercontentsmask, 0);
1558                 self.velocity_z += start_velocity_z;
1559 //              if(clip & 8)
1560 //              {
1561 //                      // we got teleported when upstepping... must abort the move
1562 //                      // note that z velocity handling may not be what QC expects here, but we cannot help it
1563 //                      return;
1564 //              }
1565
1566 //              SV_CheckVelocity(self);
1567 //              SV_LinkEdict(self);
1568 //              SV_LinkEdict_TouchAreaGrid(self);
1569
1570                 // check for stuckness, possibly due to the limited precision of floats
1571                 // in the clipping hulls
1572                 if (clip
1573                  && fabs(originalmove_origin_y - self.origin_y < 0.03125)
1574                  && fabs(originalmove_origin_x - self.origin_x < 0.03125))
1575                 {
1576                         //Con_Printf("wall\n");
1577                         // stepping up didn't make any progress, revert to original move
1578                         setorigin(self, originalmove_origin);
1579                         self.velocity = originalmove_velocity;
1580                         self.flags = originalmove_flags;
1581                         self.groundentity = originalmove_groundentity;
1582                         return;
1583                 }
1584
1585                 //Con_Printf("step - ");
1586
1587                 // extra friction based on view angle
1588 //              if (clip & 2 && sv_wallfriction.integer)
1589 //                      SV_WallFriction (self, stepnormal);
1590         }
1591         // don't do the down move if stepdown is disabled, moving upward, not in water, or the move started offground or ended onground
1592 //      else if (!autocvar_sv_gameplayfix_stepdown || self.waterlevel >= 3 || start_velocity_z >= (1.0 / 32.0) || !oldonground || (self.flags & FL_ONGROUND))
1593 //              return;
1594
1595         // move down
1596         vector downmove = '0 0 0';
1597         downmove_z = -autocvar_sv_stepheight + start_velocity_z*PHYS_INPUT_TIMELENGTH;
1598 //      if(!SV_PushEntity (&downtrace, self, downmove, true))
1599 //      {
1600 //              // we got teleported when downstepping... must abort the move
1601 //              return;
1602 //      }
1603
1604         if (trace_fraction < 1 && trace_plane_normal_z > 0.7)
1605         {
1606                 // this has been disabled so that you can't jump when you are stepping
1607                 // up while already jumping (also known as the Quake2 double jump bug)
1608         }
1609         else
1610         {
1611                 //Con_Printf("slope\n");
1612                 // if the push down didn't end up on good ground, use the move without
1613                 // the step up.  This happens near wall / slope combinations, and can
1614                 // cause the player to hop up higher on a slope too steep to climb
1615                 setorigin(self, originalmove_origin);
1616                 self.velocity = originalmove_velocity;
1617                 self.flags = originalmove_flags;
1618                 self.groundentity = originalmove_groundentity;
1619         }
1620
1621 //      SV_CheckVelocity(self);
1622 //      SV_LinkEdict(self);
1623 //      SV_LinkEdict_TouchAreaGrid(self);
1624 }
1625 #endif
1626 void PM_walk(float buttons_prev, float maxspd_mod)
1627 {
1628         if (!WAS_ONGROUND(self))
1629         {
1630                 if (autocvar_speedmeter)
1631                         dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
1632                 if (self.lastground < time - 0.3)
1633                         self.velocity *= (1 - PHYS_FRICTION_ONLAND);
1634                 if (self.jumppadcount > 1)
1635                         dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
1636                 self.jumppadcount = 0;
1637         }
1638         // walking
1639         makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
1640         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1641                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y;
1642         // acceleration
1643         vector wishdir = normalize(wishvel);
1644         float wishspeed = vlen(wishvel);
1645
1646         wishspeed = min(wishspeed, PHYS_MAXSPEED(self) * maxspd_mod);
1647         if (IS_DUCKED(self))
1648                 wishspeed *= 0.5;
1649
1650         // apply edge friction
1651         float f = vlen(vec2(self.velocity));
1652         if (f > 0)
1653         {
1654                 float realfriction;
1655                 trace_dphitq3surfaceflags = 0;
1656                 tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
1657                 // TODO: apply edge friction
1658                 // apply ground friction
1659                 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)
1660                         realfriction = PHYS_FRICTION_SLICK;
1661                 else
1662                         realfriction = PHYS_FRICTION;
1663
1664                 f = 1 - PHYS_INPUT_TIMELENGTH * realfriction * ((f < PHYS_STOPSPEED) ? (PHYS_STOPSPEED / f) : 1);
1665                 f = max(0, f);
1666                 self.velocity *= f;
1667                 /*
1668                    Mathematical analysis time!
1669
1670                    Our goal is to invert this mess.
1671
1672                    For the two cases we get:
1673                         v = v0 * (1 - PHYS_INPUT_TIMELENGTH * (PHYS_STOPSPEED / v0) * PHYS_FRICTION)
1674                           = v0 - PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1675                         v0 = v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1676                    and
1677                         v = v0 * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1678                         v0 = v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1679
1680                    These cases would be chosen ONLY if:
1681                         v0 < PHYS_STOPSPEED
1682                         v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION < PHYS_STOPSPEED
1683                         v < PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1684                    and, respectively:
1685                         v0 >= PHYS_STOPSPEED
1686                         v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION) >= PHYS_STOPSPEED
1687                         v >= PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1688                  */
1689         }
1690         float addspeed = wishspeed - self.velocity * wishdir;
1691         if (addspeed > 0)
1692         {
1693                 float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
1694                 self.velocity += accelspeed * wishdir;
1695         }
1696         float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1697         if (!(GAMEPLAYFIX_NOGRAVITYONGROUND))
1698                 self.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
1699         if (self.velocity * self.velocity)
1700                 PM_ClientMovement_Move();
1701         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1702                 if (!IS_ONGROUND(self) || !GAMEPLAYFIX_NOGRAVITYONGROUND)
1703                         self.velocity_z -= g * 0.5;
1704 }
1705
1706 void PM_air(float buttons_prev, float maxspd_mod)
1707 {
1708         makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
1709         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1710                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y;
1711         // acceleration
1712         vector wishdir = normalize(wishvel);
1713         float wishspeed = vlen(wishvel);
1714
1715 #ifdef SVQC
1716         if (time >= self.teleport_time)
1717 #else
1718         if (pmove_waterjumptime <= 0)
1719 #endif
1720         {
1721                 float maxairspd = PHYS_MAXAIRSPEED * min(maxspd_mod, 1);
1722
1723                 // apply air speed limit
1724                 float airaccelqw = PHYS_AIRACCEL_QW(self);
1725                 float wishspeed0 = wishspeed;
1726                 wishspeed = min(wishspeed, maxairspd);
1727                 if (IS_DUCKED(self))
1728                         wishspeed *= 0.5;
1729                 float airaccel = PHYS_AIRACCELERATE * min(maxspd_mod, 1);
1730
1731                 float accelerating = (self.velocity * wishdir > 0);
1732                 float wishspeed2 = wishspeed;
1733
1734                 // CPM: air control
1735                 if (PHYS_AIRSTOPACCELERATE)
1736                 {
1737                         vector curdir = normalize(vec2(self.velocity));
1738                         airaccel += (PHYS_AIRSTOPACCELERATE*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
1739                 }
1740                 // note that for straight forward jumping:
1741                 // step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
1742                 // accel  = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
1743                 // -->
1744                 // dv/dt = accel * maxspeed (when slow)
1745                 // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
1746                 // log dv/dt = logaccel + logmaxspeed (when slow)
1747                 // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
1748                 float strafity = IsMoveInDirection(PHYS_INPUT_MOVEVALUES(self), -90) + IsMoveInDirection(PHYS_INPUT_MOVEVALUES(self), +90); // if one is nonzero, other is always zero
1749                 if (PHYS_MAXAIRSTRAFESPEED)
1750                         wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED*maxspd_mod, strafity, PHYS_MAXAIRSTRAFESPEED*maxspd_mod));
1751                 if (PHYS_AIRSTRAFEACCELERATE)
1752                         airaccel = GeomLerp(airaccel, strafity, PHYS_AIRSTRAFEACCELERATE*maxspd_mod);
1753                 if (PHYS_AIRSTRAFEACCEL_QW(self))
1754                         airaccelqw =
1755                 (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW(self) : PHYS_AIRACCEL_QW(self)) >= 0) ? +1 : -1)
1756                 *
1757                 (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW(self)), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW(self))));
1758                 // !CPM
1759
1760                 if (PHYS_WARSOWBUNNY_TURNACCEL && accelerating && PHYS_INPUT_MOVEVALUES(self).y == 0 && PHYS_INPUT_MOVEVALUES(self).x != 0)
1761                         PM_AirAccelerate(wishdir, wishspeed2);
1762                 else
1763                         PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR(self), PHYS_AIRACCEL_SIDEWAYS_FRICTION / maxairspd, PHYS_AIRSPEEDLIMIT_NONQW(self));
1764
1765                 if (PHYS_AIRCONTROL)
1766                         CPM_PM_Aircontrol(wishdir, wishspeed2);
1767         }
1768         float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1769         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1770                 self.velocity_z -= g * 0.5;
1771         else
1772                 self.velocity_z -= g;
1773         PM_ClientMovement_Move();
1774         if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1775                 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1776                         self.velocity_z -= g * 0.5;
1777 }
1778
1779 // used for calculating airshots
1780 bool IsFlying(entity a)
1781 {
1782         if(IS_ONGROUND(a))
1783                 return false;
1784         if(a.waterlevel >= WATERLEVEL_SWIMMING)
1785                 return false;
1786         traceline(a.origin, a.origin - '0 0 48', MOVE_NORMAL, a);
1787         if(trace_fraction < 1)
1788                 return false;
1789         return true;
1790 }
1791
1792 void PM_Main()
1793 {
1794         float buttons = PHYS_INPUT_BUTTON_MASK(self);
1795 #ifdef CSQC
1796         self.team = myteam + 1; // is this correct?
1797         if (!(PHYS_INPUT_BUTTON_JUMP(self))) // !jump
1798                 UNSET_JUMP_HELD(self); // canjump = true
1799         pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH;
1800 #endif
1801         PM_ClientMovement_UpdateStatus();
1802
1803 #ifdef SVQC
1804         WarpZone_PlayerPhysics_FixVAngle();
1805 #endif
1806         float maxspeed_mod = 1;
1807         maxspeed_mod *= PM_check_keepaway();
1808         maxspeed_mod *= PHYS_HIGHSPEED;
1809
1810 #ifdef SVQC
1811         Physics_UpdateStats(maxspeed_mod);
1812
1813         if (self.PlayerPhysplug)
1814                 if (self.PlayerPhysplug())
1815                         return;
1816 #endif
1817
1818         PM_check_race_movetime();
1819 #ifdef SVQC
1820         anticheat_physics();
1821 #endif
1822
1823         if (PM_check_specialcommand(buttons))
1824                 return;
1825 #ifdef SVQC
1826         if (sv_maxidle > 0)
1827         {
1828                 if (buttons != self.buttons_old || PHYS_INPUT_MOVEVALUES(self) != self.movement_old || PHYS_INPUT_ANGLES(self) != self.v_angle_old)
1829                         self.parm_idlesince = time;
1830         }
1831 #endif
1832         float buttons_prev = self.buttons_old;
1833         self.buttons_old = buttons;
1834         self.movement_old = PHYS_INPUT_MOVEVALUES(self);
1835         self.v_angle_old = PHYS_INPUT_ANGLES(self);
1836
1837         PM_check_nickspam();
1838
1839         PM_check_punch();
1840 #ifdef SVQC
1841         if (IS_BOT_CLIENT(self))
1842         {
1843                 if (playerdemo_read())
1844                         return;
1845                 bot_think();
1846         }
1847
1848         if (IS_PLAYER(self))
1849 #endif
1850         {
1851 #ifdef SVQC
1852                 if (self.race_penalty)
1853                         if (time > self.race_penalty)
1854                                 self.race_penalty = 0;
1855 #endif
1856
1857                 float not_allowed_to_move = 0;
1858 #ifdef SVQC
1859                 if (self.race_penalty)
1860                         not_allowed_to_move = 1;
1861 #endif
1862 #ifdef SVQC
1863                 if (time < game_starttime)
1864                         not_allowed_to_move = 1;
1865 #endif
1866
1867                 if (not_allowed_to_move)
1868                 {
1869                         self.velocity = '0 0 0';
1870                         self.movetype = MOVETYPE_NONE;
1871 #ifdef SVQC
1872                         self.disableclientprediction = 2;
1873 #endif
1874                 }
1875 #ifdef SVQC
1876                 else if (self.disableclientprediction == 2)
1877                 {
1878                         if (self.movetype == MOVETYPE_NONE)
1879                                 self.movetype = MOVETYPE_WALK;
1880                         self.disableclientprediction = 0;
1881                 }
1882 #endif
1883         }
1884
1885 #ifdef SVQC
1886         if (self.movetype == MOVETYPE_NONE)
1887                 return;
1888
1889         // when we get here, disableclientprediction cannot be 2
1890         self.disableclientprediction = 0;
1891 #endif
1892
1893         PM_check_spider();
1894
1895         PM_check_frozen();
1896
1897         PM_check_blocked();
1898
1899         maxspeed_mod = 1;
1900
1901         if (self.in_swamp)
1902                 maxspeed_mod *= self.swamp_slowdown; //cvar("g_balance_swamp_moverate");
1903
1904         // conveyors: first fix velocity
1905         if (self.conveyor.state)
1906                 self.velocity -= self.conveyor.movedir;
1907
1908 #ifdef SVQC
1909         MUTATOR_CALLHOOK(PlayerPhysics);
1910 #endif
1911 #ifdef CSQC
1912         PM_multijump();
1913 #endif
1914
1915 //      float forcedodge = 1;
1916 //      if(forcedodge) {
1917 //#ifdef CSQC
1918 //              PM_dodging_checkpressedkeys();
1919 //#endif
1920 //              PM_dodging();
1921 //              PM_ClientMovement_Move();
1922 //              return;
1923 //      }
1924
1925 #ifdef SVQC
1926         if (!IS_PLAYER(self))
1927         {
1928                 maxspeed_mod *= autocvar_sv_spectator_speed_multiplier;
1929                 if (!self.spectatorspeed)
1930                         self.spectatorspeed = maxspeed_mod;
1931                 if (self.impulse && self.impulse <= 19 || (self.impulse >= 200 && self.impulse <= 209) || (self.impulse >= 220 && self.impulse <= 229))
1932                 {
1933                         if (self.lastclassname != "player")
1934                         {
1935                                 if (self.impulse == 10 || self.impulse == 15 || self.impulse == 18 || (self.impulse >= 200 && self.impulse <= 209))
1936                                         self.spectatorspeed = bound(1, self.spectatorspeed + 0.5, 5);
1937                                 else if (self.impulse == 11)
1938                                         self.spectatorspeed = maxspeed_mod;
1939                                 else if (self.impulse == 12 || self.impulse == 16  || self.impulse == 19 || (self.impulse >= 220 && self.impulse <= 229))
1940                                         self.spectatorspeed = bound(1, self.spectatorspeed - 0.5, 5);
1941                                 else if (self.impulse >= 1 && self.impulse <= 9)
1942                                         self.spectatorspeed = 1 + 0.5 * (self.impulse - 1);
1943                         } // otherwise just clear
1944                         self.impulse = 0;
1945                 }
1946                 maxspeed_mod *= self.spectatorspeed;
1947         }
1948 #endif
1949
1950         if(PHYS_DEAD(self))
1951                 goto end;
1952
1953 #ifdef SVQC
1954         if (!self.fixangle && !g_bugrigs)
1955                 self.angles = '0 1 0' * PHYS_INPUT_ANGLES(self).y;
1956 #endif
1957
1958         PM_check_hitground();
1959
1960         if(IsFlying(self))
1961                 self.wasFlying = 1;
1962
1963         if (IS_PLAYER(self))
1964                 CheckPlayerJump();
1965
1966         if (self.flags & /* FL_WATERJUMP */ 2048)
1967         {
1968                 self.velocity_x = self.movedir_x;
1969                 self.velocity_y = self.movedir_y;
1970                 if (time > self.teleport_time || self.waterlevel == WATERLEVEL_NONE)
1971                 {
1972                         self.flags &= ~/* FL_WATERJUMP */ 2048;
1973                         self.teleport_time = 0;
1974                 }
1975         }
1976
1977 #ifdef SVQC
1978         else if (g_bugrigs && IS_PLAYER(self))
1979                 RaceCarPhysics();
1980 #endif
1981
1982         else if (self.movetype == MOVETYPE_NOCLIP || self.movetype == MOVETYPE_FLY || self.movetype == MOVETYPE_FLY_WORLDONLY)
1983                 PM_fly(maxspeed_mod);
1984
1985         else if (self.waterlevel >= WATERLEVEL_SWIMMING)
1986                 PM_swim(maxspeed_mod);
1987
1988         else if (time < self.ladder_time)
1989                 PM_ladder(maxspeed_mod);
1990
1991         else if (ITEMS(self) & IT_USING_JETPACK)
1992                 PM_jetpack(maxspeed_mod);
1993
1994         else if (IS_ONGROUND(self))
1995                 PM_walk(buttons_prev, maxspeed_mod);
1996
1997         else
1998                 PM_air(buttons_prev, maxspeed_mod);
1999
2000 #ifdef SVQC
2001         if (!IS_OBSERVER(self))
2002                 PM_check_race();
2003 #endif
2004         PM_check_vortex();
2005
2006 :end
2007         if (IS_ONGROUND(self))
2008                 self.lastground = time;
2009
2010         // conveyors: then break velocity again
2011         if(self.conveyor.state)
2012                 self.velocity += self.conveyor.movedir;
2013
2014         self.lastflags = FLAGS(self);
2015
2016         self.lastclassname = self.classname;
2017 }
2018
2019 #ifdef SVQC
2020 void SV_PlayerPhysics(void)
2021 #elif defined(CSQC)
2022 void CSQC_ClientMovement_PlayerMove_Frame(void)
2023 #endif
2024 {
2025         PM_Main();
2026
2027 #ifdef CSQC
2028         pmove_org = self.origin;
2029         pmove_vel = self.velocity;
2030 #endif
2031 }