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