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