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