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