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