]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/physics.qc
Network dphitcontentsmask for accurate prediction on clips, almost fix stairs prediction
[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                 if(autocvar_speedmeter)
498                         dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
499                 if(self.lastground < time - 0.3)
500                 {
501                         self.velocity_x *= (1 - PHYS_FRICTION_ONLAND);
502                         self.velocity_y *= (1 - PHYS_FRICTION_ONLAND);
503                 }
504                 if(self.jumppadcount > 1)
505                         dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
506                 self.jumppadcount = 0;
507         }
508
509         self.velocity_z += mjumpheight;
510
511         UNSET_ONGROUND(self);
512         SET_JUMP_HELD(self);
513
514 #ifdef SVQC
515
516         self.oldvelocity_z = self.velocity_z;
517
518         animdecide_setaction(self, ANIMACTION_JUMP, true);
519
520         if (autocvar_g_jump_grunt)
521                 PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
522 #endif
523         return true;
524 }
525
526 void CheckWaterJump()
527 {
528 // check for a jump-out-of-water
529         makevectors(PHYS_INPUT_ANGLES(self));
530         vector start = self.origin;
531         start_z += 8;
532         v_forward_z = 0;
533         normalize(v_forward);
534         vector end = start + v_forward*24;
535         traceline (start, end, true, self);
536         if (trace_fraction < 1)
537         {       // solid at waist
538                 start_z = start_z + self.maxs_z - 8;
539                 end = start + v_forward*24;
540                 self.movedir = trace_plane_normal * -50;
541                 traceline(start, end, true, self);
542                 if (trace_fraction == 1)
543                 {       // open at eye level
544                         self.velocity_z = 225;
545 #ifdef SVQC
546                         self.flags |= FL_WATERJUMP;
547                         SET_JUMP_HELD(self);
548                         self.teleport_time = time + 2;  // safety net
549 #endif
550                 }
551         }
552 }
553
554
555 #ifdef SVQC
556         #define JETPACK_JUMP(s) s.cvar_cl_jetpack_jump
557 #elif defined(CSQC)
558         float autocvar_cl_jetpack_jump;
559         #define JETPACK_JUMP(s) autocvar_cl_jetpack_jump
560 #endif
561 .float jetpack_stopped;
562 // Hack: shouldn't need to know about this
563 .float multijump_count;
564 void CheckPlayerJump()
565 {
566 #ifdef SVQC
567         float was_flying = ITEMS(self) & IT_USING_JETPACK;
568 #endif
569         if (JETPACK_JUMP(self) < 2)
570 #ifdef SVQC
571                 ITEMS(self) &= ~IT_USING_JETPACK;
572 #endif
573
574         if(PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self))
575         {
576 #ifdef SVQC
577                 float air_jump = !PlayerJump() || self.multijump_count > 0; // PlayerJump() has important side effects
578                 float activate = JETPACK_JUMP(self) && air_jump && PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self);
579                 float has_fuel = !autocvar_g_jetpack_fuel || self.ammo_fuel || ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO;
580 #else
581                 PlayerJump(); // Client only
582                 float has_fuel = true; // TODO
583 #endif
584                 if (!(ITEMS(self) & IT_JETPACK)) { }
585                 else if (self.jetpack_stopped) { }
586                 else if (!has_fuel)
587                 {
588 #ifdef SVQC
589                         if (was_flying) // TODO: ran out of fuel message
590                                 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
591                         else if (activate)
592                                 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
593 #endif
594                         self.jetpack_stopped = true;
595 #ifdef SVQC
596                         ITEMS(self) &= ~IT_USING_JETPACK;
597 #endif
598                 }
599 #ifdef SVQC
600                 else if (activate && !PHYS_FROZEN(self))
601                         ITEMS(self) |= IT_USING_JETPACK;
602 #endif
603         }
604         else
605         {
606                 self.jetpack_stopped = false;
607 #ifdef SVQC
608                 ITEMS(self) &= ~IT_USING_JETPACK;
609 #endif
610         }
611         if (!PHYS_INPUT_BUTTON_JUMP(self))
612                 UNSET_JUMP_HELD(self);
613
614         if (self.waterlevel == WATERLEVEL_SWIMMING)
615                 CheckWaterJump();
616 }
617
618 float racecar_angle(float forward, float down)
619 {
620         if (forward < 0)
621         {
622                 forward = -forward;
623                 down = -down;
624         }
625
626         float ret = vectoyaw('0 1 0' * down + '1 0 0' * forward);
627
628         float angle_mult = forward / (800 + forward);
629
630         if (ret > 180)
631                 return ret * angle_mult + 360 * (1 - angle_mult);
632         else
633                 return ret * angle_mult;
634 }
635
636 void RaceCarPhysics()
637 {
638 #ifdef SVQC
639         // using this move type for "big rigs"
640         // the engine does not push the entity!
641
642         vector rigvel;
643
644         vector angles_save = self.angles;
645         float accel = bound(-1, PHYS_INPUT_MOVEVALUES(self).x / PHYS_MAXSPEED(self), 1);
646         float steer = bound(-1, PHYS_INPUT_MOVEVALUES(self).y / PHYS_MAXSPEED(self), 1);
647
648         if (g_bugrigs_reverse_speeding)
649         {
650                 if (accel < 0)
651                 {
652                         // back accel is DIGITAL
653                         // to prevent speedhack
654                         if (accel < -0.5)
655                                 accel = -1;
656                         else
657                                 accel = 0;
658                 }
659         }
660
661         self.angles_x = 0;
662         self.angles_z = 0;
663         makevectors(self.angles); // new forward direction!
664
665         if (IS_ONGROUND(self) || g_bugrigs_air_steering)
666         {
667                 float myspeed = self.velocity * v_forward;
668                 float upspeed = self.velocity * v_up;
669
670                 // responsiveness factor for steering and acceleration
671                 float f = 1 / (1 + pow(max(-myspeed, myspeed) / g_bugrigs_speed_ref, g_bugrigs_speed_pow));
672                 //MAXIMA: f(v) := 1 / (1 + (v / g_bugrigs_speed_ref) ^ g_bugrigs_speed_pow);
673
674                 float steerfactor;
675                 if (myspeed < 0 && g_bugrigs_reverse_spinning)
676                         steerfactor = -myspeed * g_bugrigs_steer;
677                 else
678                         steerfactor = -myspeed * f * g_bugrigs_steer;
679
680                 float accelfactor;
681                 if (myspeed < 0 && g_bugrigs_reverse_speeding)
682                         accelfactor = g_bugrigs_accel;
683                 else
684                         accelfactor = f * g_bugrigs_accel;
685                 //MAXIMA: accel(v) := f(v) * g_bugrigs_accel;
686
687                 if (accel < 0)
688                 {
689                         if (myspeed > 0)
690                         {
691                                 myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor - g_bugrigs_friction_brake * accel));
692                         }
693                         else
694                         {
695                                 if (!g_bugrigs_reverse_speeding)
696                                         myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
697                         }
698                 }
699                 else
700                 {
701                         if (myspeed >= 0)
702                         {
703                                 myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
704                         }
705                         else
706                         {
707                                 if (g_bugrigs_reverse_stopping)
708                                         myspeed = 0;
709                                 else
710                                         myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor + g_bugrigs_friction_brake * accel));
711                         }
712                 }
713                 // terminal velocity = velocity at which 50 == accelfactor, that is, 1549 units/sec
714                 //MAXIMA: friction(v) := g_bugrigs_friction_floor;
715
716                 self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
717                 makevectors(self.angles); // new forward direction!
718
719                 myspeed += accel * accelfactor * PHYS_INPUT_TIMELENGTH;
720
721                 rigvel = myspeed * v_forward + '0 0 1' * upspeed;
722         }
723         else
724         {
725                 float myspeed = vlen(self.velocity);
726
727                 // responsiveness factor for steering and acceleration
728                 float f = 1 / (1 + pow(max(0, myspeed / g_bugrigs_speed_ref), g_bugrigs_speed_pow));
729                 float steerfactor = -myspeed * f;
730                 self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
731
732                 rigvel = self.velocity;
733                 makevectors(self.angles); // new forward direction!
734         }
735
736         rigvel *= max(0, 1 - vlen(rigvel) * g_bugrigs_friction_air * PHYS_INPUT_TIMELENGTH);
737         //MAXIMA: airfriction(v) := v * v * g_bugrigs_friction_air;
738         //MAXIMA: total_acceleration(v) := accel(v) - friction(v) - airfriction(v);
739         //MAXIMA: solve(total_acceleration(v) = 0, v);
740
741         if (g_bugrigs_planar_movement)
742         {
743                 vector rigvel_xy, neworigin, up;
744                 float mt;
745
746                 rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY; // 4x gravity plays better
747                 rigvel_xy = vec2(rigvel);
748
749                 if (g_bugrigs_planar_movement_car_jumping)
750                         mt = MOVE_NORMAL;
751                 else
752                         mt = MOVE_NOMONSTERS;
753
754                 tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 1024', mt, self);
755                 up = trace_endpos - self.origin;
756
757                 // BUG RIGS: align the move to the surface instead of doing collision testing
758                 // can we move?
759                 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos + rigvel_xy * PHYS_INPUT_TIMELENGTH, mt, self);
760
761                 // align to surface
762                 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos - up + '0 0 1' * rigvel_z * PHYS_INPUT_TIMELENGTH, mt, self);
763
764                 if (trace_fraction < 0.5)
765                 {
766                         trace_fraction = 1;
767                         neworigin = self.origin;
768                 }
769                 else
770                         neworigin = trace_endpos;
771
772                 if (trace_fraction < 1)
773                 {
774                         // now set angles_x so that the car points parallel to the surface
775                         self.angles = vectoangles(
776                                         '1 0 0' * v_forward_x * trace_plane_normal_z
777                                         +
778                                         '0 1 0' * v_forward_y * trace_plane_normal_z
779                                         +
780                                         '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y)
781                                         );
782                         SET_ONGROUND(self);
783                 }
784                 else
785                 {
786                         // now set angles_x so that the car points forward, but is tilted in velocity direction
787                         UNSET_ONGROUND(self);
788                 }
789
790                 self.velocity = (neworigin - self.origin) * (1.0 / PHYS_INPUT_TIMELENGTH);
791                 self.movetype = MOVETYPE_NOCLIP;
792         }
793         else
794         {
795                 rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY; // 4x gravity plays better
796                 self.velocity = rigvel;
797                 self.movetype = MOVETYPE_FLY;
798         }
799
800         trace_fraction = 1;
801         tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 4', MOVE_NORMAL, self);
802         if (trace_fraction != 1)
803         {
804                 self.angles = vectoangles2(
805                                 '1 0 0' * v_forward_x * trace_plane_normal_z
806                                 +
807                                 '0 1 0' * v_forward_y * trace_plane_normal_z
808                                 +
809                                 '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y),
810                                 trace_plane_normal
811                                 );
812         }
813         else
814         {
815                 vector vel_local;
816
817                 vel_local_x = v_forward * self.velocity;
818                 vel_local_y = v_right * self.velocity;
819                 vel_local_z = v_up * self.velocity;
820
821                 self.angles_x = racecar_angle(vel_local_x, vel_local_z);
822                 self.angles_z = racecar_angle(-vel_local_y, vel_local_z);
823         }
824
825         // smooth the angles
826         vector vf1, vu1, smoothangles;
827         makevectors(self.angles);
828         float f = bound(0, PHYS_INPUT_TIMELENGTH * g_bugrigs_angle_smoothing, 1);
829         if (f == 0)
830                 f = 1;
831         vf1 = v_forward * f;
832         vu1 = v_up * f;
833         makevectors(angles_save);
834         vf1 = vf1 + v_forward * (1 - f);
835         vu1 = vu1 + v_up * (1 - f);
836         smoothangles = vectoangles2(vf1, vu1);
837         self.angles_x = -smoothangles_x;
838         self.angles_z =  smoothangles_z;
839 #endif
840 }
841
842 string specialcommand = "xwxwxsxsxaxdxaxdx1x ";
843 .float specialcommand_pos;
844 void SpecialCommand()
845 {
846 #ifdef SVQC
847 #ifdef TETRIS
848         TetrisImpulse();
849 #else
850         if (!CheatImpulse(99))
851                 print("A hollow voice says \"Plugh\".\n");
852 #endif
853 #endif
854 }
855
856 float PM_check_keepaway(void)
857 {
858 #ifdef SVQC
859         return (self.ballcarried && g_keepaway) ? autocvar_g_keepaway_ballcarrier_highspeed : 1;
860 #else
861         return 1;
862 #endif
863 }
864
865 void PM_check_race_movetime(void)
866 {
867 #ifdef SVQC
868         self.race_movetime_frac += PHYS_INPUT_TIMELENGTH;
869         float f = floor(self.race_movetime_frac);
870         self.race_movetime_frac -= f;
871         self.race_movetime_count += f;
872         self.race_movetime = self.race_movetime_frac + self.race_movetime_count;
873 #endif
874 }
875
876 float PM_check_specialcommand(float buttons)
877 {
878 #ifdef SVQC
879         string c;
880         if (!buttons)
881                 c = "x";
882         else if (buttons == 1)
883                 c = "1";
884         else if (buttons == 2)
885                 c = " ";
886         else if (buttons == 128)
887                 c = "s";
888         else if (buttons == 256)
889                 c = "w";
890         else if (buttons == 512)
891                 c = "a";
892         else if (buttons == 1024)
893                 c = "d";
894         else
895                 c = "?";
896
897         if (c == substring(specialcommand, self.specialcommand_pos, 1))
898         {
899                 self.specialcommand_pos += 1;
900                 if (self.specialcommand_pos >= strlen(specialcommand))
901                 {
902                         self.specialcommand_pos = 0;
903                         SpecialCommand();
904                         return true;
905                 }
906         }
907         else if (self.specialcommand_pos && (c != substring(specialcommand, self.specialcommand_pos - 1, 1)))
908                 self.specialcommand_pos = 0;
909 #endif
910         return false;
911 }
912
913 void PM_check_nickspam(void)
914 {
915 #ifdef SVQC
916         if (time >= self.nickspamtime)
917                 return;
918         if (self.nickspamcount >= autocvar_g_nick_flood_penalty_yellow)
919         {
920                 // slight annoyance for nick change scripts
921                 PHYS_INPUT_MOVEVALUES(self) = -1 * PHYS_INPUT_MOVEVALUES(self);
922                 self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = self.BUTTON_ZOOM = self.BUTTON_CROUCH = self.BUTTON_HOOK = self.BUTTON_USE = 0;
923
924                 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!
925                 {
926                         PHYS_INPUT_ANGLES(self)_x = random() * 360;
927                         PHYS_INPUT_ANGLES(self)_y = random() * 360;
928                         // at least I'm not forcing retardedview by also assigning to angles_z
929                         self.fixangle = true;
930                 }
931         }
932 #endif
933 }
934
935 void PM_check_punch()
936 {
937 #ifdef SVQC
938         if (self.punchangle != '0 0 0')
939         {
940                 float f = vlen(self.punchangle) - 10 * PHYS_INPUT_TIMELENGTH;
941                 if (f > 0)
942                         self.punchangle = normalize(self.punchangle) * f;
943                 else
944                         self.punchangle = '0 0 0';
945         }
946
947         if (self.punchvector != '0 0 0')
948         {
949                 float f = vlen(self.punchvector) - 30 * PHYS_INPUT_TIMELENGTH;
950                 if (f > 0)
951                         self.punchvector = normalize(self.punchvector) * f;
952                 else
953                         self.punchvector = '0 0 0';
954         }
955 #endif
956 }
957
958 void PM_check_spider(void)
959 {
960 #ifdef SVQC
961         if (time >= self.spider_slowness)
962                 return;
963         PHYS_MAXSPEED(self) *= 0.5; // half speed while slow from spider
964         self.stat_sv_airspeedlimit_nonqw *= 0.5;
965 #endif
966 }
967
968 // predict frozen movement, as frozen players CAN move in some cases
969 void PM_check_frozen(void)
970 {
971         if (!PHYS_FROZEN(self))
972                 return;
973         if (PHYS_DODGING_FROZEN
974 #ifdef SVQC
975         && IS_REAL_CLIENT(self)
976 #endif
977         )
978         {
979                 PHYS_INPUT_MOVEVALUES(self)_x = bound(-5, PHYS_INPUT_MOVEVALUES(self).x, 5);
980                 PHYS_INPUT_MOVEVALUES(self)_y = bound(-5, PHYS_INPUT_MOVEVALUES(self).y, 5);
981                 PHYS_INPUT_MOVEVALUES(self)_z = bound(-5, PHYS_INPUT_MOVEVALUES(self).z, 5);
982         }
983         else
984                 PHYS_INPUT_MOVEVALUES(self) = '0 0 0';
985
986         vector midpoint = ((self.absmin + self.absmax) * 0.5);
987         if (pointcontents(midpoint) == CONTENT_WATER)
988         {
989                 self.velocity = self.velocity * 0.5;
990
991                 if (pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
992                         self.velocity_z = 200;
993         }
994 }
995
996 void PM_check_hitground()
997 {
998 #ifdef SVQC
999         if (IS_ONGROUND(self))
1000         if (IS_PLAYER(self)) // no fall sounds for observers thank you very much
1001         if (self.wasFlying)
1002         {
1003                 self.wasFlying = 0;
1004                 if (self.waterlevel < WATERLEVEL_SWIMMING)
1005                 if (time >= self.ladder_time)
1006                 if (!self.hook)
1007                 {
1008                         self.nextstep = time + 0.3 + random() * 0.1;
1009                         trace_dphitq3surfaceflags = 0;
1010                         tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
1011                         if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS))
1012                         {
1013                                 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
1014                                         GlobalSound(globalsound_metalfall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
1015                                 else
1016                                         GlobalSound(globalsound_fall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
1017                         }
1018                 }
1019         }
1020 #endif
1021 }
1022
1023 void PM_check_blocked(void)
1024 {
1025 #ifdef SVQC
1026         if (!self.player_blocked)
1027                 return;
1028         PHYS_INPUT_MOVEVALUES(self) = '0 0 0';
1029         self.disableclientprediction = 1;
1030 #endif
1031 }
1032
1033 #ifdef SVQC
1034 float speedaward_lastsent;
1035 float speedaward_lastupdate;
1036 #endif
1037 void PM_check_race(void)
1038 {
1039 #ifdef SVQC
1040         if(!(g_cts || g_race))
1041                 return;
1042         if (vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed)
1043         {
1044                 speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1');
1045                 speedaward_holder = self.netname;
1046                 speedaward_uid = self.crypto_idfp;
1047                 speedaward_lastupdate = time;
1048         }
1049         if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
1050         {
1051                 string rr = (g_cts) ? CTS_RECORD : RACE_RECORD;
1052                 race_send_speedaward(MSG_ALL);
1053                 speedaward_lastsent = speedaward_speed;
1054                 if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
1055                 {
1056                         speedaward_alltimebest = speedaward_speed;
1057                         speedaward_alltimebest_holder = speedaward_holder;
1058                         speedaward_alltimebest_uid = speedaward_uid;
1059                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
1060                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
1061                         race_send_speedaward_alltimebest(MSG_ALL);
1062                 }
1063         }
1064 #endif
1065 }
1066
1067 void PM_check_vortex(void)
1068 {
1069 #ifdef SVQC
1070         // WEAPONTODO
1071         float xyspeed = vlen(vec2(self.velocity));
1072         if (self.weapon == WEP_VORTEX && WEP_CVAR(vortex, charge) && WEP_CVAR(vortex, charge_velocity_rate) && xyspeed > WEP_CVAR(vortex, charge_minspeed))
1073         {
1074                 // add a maximum of charge_velocity_rate when going fast (f = 1), gradually increasing from minspeed (f = 0) to maxspeed
1075                 xyspeed = min(xyspeed, WEP_CVAR(vortex, charge_maxspeed));
1076                 float f = (xyspeed - WEP_CVAR(vortex, charge_minspeed)) / (WEP_CVAR(vortex, charge_maxspeed) - WEP_CVAR(vortex, charge_minspeed));
1077                 // add the extra charge
1078                 self.vortex_charge = min(1, self.vortex_charge + WEP_CVAR(vortex, charge_velocity_rate) * f * PHYS_INPUT_TIMELENGTH);
1079         }
1080 #endif
1081 }
1082
1083 void PM_fly(float maxspd_mod)
1084 {
1085         // noclipping or flying
1086         UNSET_ONGROUND(self);
1087
1088         self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
1089         makevectors(PHYS_INPUT_ANGLES(self));
1090         //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
1091         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1092                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y
1093                                         + '0 0 1' * PHYS_INPUT_MOVEVALUES(self).z;
1094         // acceleration
1095         vector wishdir = normalize(wishvel);
1096         float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
1097         if (time >= self.teleport_time)
1098                 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
1099 }
1100
1101 void PM_swim(float maxspd_mod)
1102 {
1103         // swimming
1104         UNSET_ONGROUND(self);
1105
1106         float jump = PHYS_INPUT_BUTTON_JUMP(self);
1107         // water jump only in certain situations
1108         // this mimics quakeworld code
1109         if (jump && self.waterlevel == WATERLEVEL_SWIMMING && self.velocity_z >= -180)
1110         {
1111                 vector yawangles = '0 1 0' * PHYS_INPUT_ANGLES(self).y;
1112                 makevectors(yawangles);
1113                 vector forward = v_forward;
1114                 vector spot = self.origin + 24 * forward;
1115                 spot_z += 8;
1116                 traceline(spot, spot, MOVE_NOMONSTERS, self);
1117                 if (trace_startsolid)
1118                 {
1119                         spot_z += 24;
1120                         traceline(spot, spot, MOVE_NOMONSTERS, self);
1121                         if (!trace_startsolid)
1122                         {
1123                                 self.velocity = forward * 50;
1124                                 self.velocity_z = 310;
1125                                 pmove_waterjumptime = 2;
1126                                 UNSET_ONGROUND(self);
1127                                 SET_JUMP_HELD(self);
1128                         }
1129                 }
1130         }
1131         makevectors(PHYS_INPUT_ANGLES(self));
1132         //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
1133         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1134                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y
1135                                         + '0 0 1' * PHYS_INPUT_MOVEVALUES(self).z;
1136         if (wishvel == '0 0 0')
1137                 wishvel = '0 0 -60'; // drift towards bottom
1138
1139         vector wishdir = normalize(wishvel);
1140         float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod) * 0.7;
1141
1142         if (IS_DUCKED(self))
1143         wishspeed *= 0.5;
1144
1145 //      if (pmove_waterjumptime <= 0) // TODO: use
1146     {
1147                 // water friction
1148                 float f = 1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION;
1149                 f = min(max(0, f), 1);
1150                 self.velocity *= f;
1151
1152                 f = wishspeed - self.velocity * wishdir;
1153                 if (f > 0)
1154                 {
1155                         float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, f);
1156                         self.velocity += accelspeed * wishdir;
1157                 }
1158
1159                 // holding jump button swims upward slowly
1160                 if (jump)
1161                 {
1162 #if 0
1163                         if (self.watertype & CONTENT_LAVA)
1164                                 self.velocity_z =  50;
1165                         else if (self.watertype & CONTENT_SLIME)
1166                                 self.velocity_z =  80;
1167                         else
1168                         {
1169                                 if (IS_NEXUIZ_DERIVED(gamemode))
1170 #endif
1171                                         self.velocity_z = 200;
1172 #if 0
1173                                 else
1174                                         self.velocity_z = 100;
1175                         }
1176 #endif
1177                 }
1178         }
1179         PM_ClientMovement_Move();
1180         // water acceleration
1181         PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
1182 }
1183
1184 void PM_ladder(float maxspd_mod)
1185 {
1186         // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
1187         UNSET_ONGROUND(self);
1188
1189         float g;
1190         g = PHYS_GRAVITY * PHYS_INPUT_TIMELENGTH;
1191         if (PHYS_ENTGRAVITY(self))
1192                 g *= PHYS_ENTGRAVITY(self);
1193         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1194         {
1195                 g *= 0.5;
1196                 self.velocity_z += g;
1197         }
1198
1199         self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
1200         makevectors(PHYS_INPUT_ANGLES(self));
1201         //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
1202         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self)_x
1203                                         + v_right * PHYS_INPUT_MOVEVALUES(self)_y
1204                                         + '0 0 1' * PHYS_INPUT_MOVEVALUES(self)_z;
1205         self.velocity_z += g;
1206         if (self.ladder_entity.classname == "func_water")
1207         {
1208                 float f = vlen(wishvel);
1209                 if (f > self.ladder_entity.speed)
1210                         wishvel *= (self.ladder_entity.speed / f);
1211
1212                 self.watertype = self.ladder_entity.skin;
1213                 f = self.ladder_entity.origin_z + self.ladder_entity.maxs_z;
1214                 if ((self.origin_z + self.view_ofs_z) < f)
1215                         self.waterlevel = WATERLEVEL_SUBMERGED;
1216                 else if ((self.origin_z + (self.mins_z + self.maxs_z) * 0.5) < f)
1217                         self.waterlevel = WATERLEVEL_SWIMMING;
1218                 else if ((self.origin_z + self.mins_z + 1) < f)
1219                         self.waterlevel = WATERLEVEL_WETFEET;
1220                 else
1221                 {
1222                         self.waterlevel = WATERLEVEL_NONE;
1223                         self.watertype = CONTENT_EMPTY;
1224                 }
1225         }
1226         // acceleration
1227         vector wishdir = normalize(wishvel);
1228         float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
1229         PM_ClientMovement_Move();
1230 #ifdef SVQC
1231         if (time >= self.teleport_time)
1232 #endif
1233                 // water acceleration
1234                 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE*maxspd_mod, 1, 0, 0, 0);
1235 }
1236
1237 void PM_jetpack(float maxspd_mod)
1238 {
1239         //makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
1240         makevectors(PHYS_INPUT_ANGLES(self));
1241         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self)_x
1242                                         + v_right * PHYS_INPUT_MOVEVALUES(self)_y;
1243         // add remaining speed as Z component
1244         float maxairspd = PHYS_MAXAIRSPEED * max(1, maxspd_mod);
1245         // fix speedhacks :P
1246         wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxairspd);
1247         // add the unused velocity as up component
1248         wishvel_z = 0;
1249
1250         // if (self.BUTTON_JUMP)
1251                 wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
1252
1253         // it is now normalized, so...
1254         float a_side = PHYS_JETPACK_ACCEL_SIDE;
1255         float a_up = PHYS_JETPACK_ACCEL_UP;
1256         float a_add = PHYS_JETPACK_ANTIGRAVITY * PHYS_GRAVITY;
1257
1258         wishvel_x *= a_side;
1259         wishvel_y *= a_side;
1260         wishvel_z *= a_up;
1261         wishvel_z += a_add;
1262
1263         float best = 0;
1264         //////////////////////////////////////////////////////////////////////////////////////
1265         // finding the maximum over all vectors of above form
1266         // with wishvel having an absolute value of 1
1267         //////////////////////////////////////////////////////////////////////////////////////
1268         // we're finding the maximum over
1269         //   f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
1270         // for z in the range from -1 to 1
1271         //////////////////////////////////////////////////////////////////////////////////////
1272         // maximum is EITHER attained at the single extreme point:
1273         float a_diff = a_side * a_side - a_up * a_up;
1274         float f;
1275         if (a_diff != 0)
1276         {
1277                 f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
1278                 if (f > -1 && f < 1) // can it be attained?
1279                 {
1280                         best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
1281                         //print("middle\n");
1282                 }
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("top\n");
1290         }
1291         // OR attained at z = -1:
1292         f = (a_up - a_add) * (a_up - a_add);
1293         if (f > best)
1294         {
1295                 best = f;
1296                 //print("bottom\n");
1297         }
1298         best = sqrt(best);
1299         //////////////////////////////////////////////////////////////////////////////////////
1300
1301         //print("best possible acceleration: ", ftos(best), "\n");
1302
1303         float fxy, fz;
1304         fxy = bound(0, 1 - (self.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / PHYS_JETPACK_MAXSPEED_SIDE, 1);
1305         if (wishvel_z - PHYS_GRAVITY > 0)
1306                 fz = bound(0, 1 - self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1307         else
1308                 fz = bound(0, 1 + self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1309
1310         float fvel;
1311         fvel = vlen(wishvel);
1312         wishvel_x *= fxy;
1313         wishvel_y *= fxy;
1314         wishvel_z = (wishvel_z - PHYS_GRAVITY) * fz + PHYS_GRAVITY;
1315
1316         fvel = min(1, vlen(wishvel) / best);
1317         if (PHYS_JETPACK_FUEL && !(ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO))
1318                 f = min(1, PHYS_AMMO_FUEL(self) / (PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel));
1319         else
1320                 f = 1;
1321
1322         //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
1323
1324         if (f > 0 && wishvel != '0 0 0')
1325         {
1326                 self.velocity = self.velocity + wishvel * f * PHYS_INPUT_TIMELENGTH;
1327                 UNSET_ONGROUND(self);
1328
1329 #ifdef SVQC
1330                 if (!(ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO))
1331                         self.ammo_fuel -= PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel * f;
1332
1333                 ITEMS(self) |= IT_USING_JETPACK;
1334
1335                 // jetpack also inhibits health regeneration, but only for 1 second
1336                 self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
1337 #endif
1338         }
1339
1340 #ifdef CSQC
1341         float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1342         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1343                 self.velocity_z -= g * 0.5;
1344         else
1345                 self.velocity_z -= g;
1346         PM_ClientMovement_Move();
1347         if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1348                 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1349                         self.velocity_z -= g * 0.5;
1350 #endif
1351 }
1352 #ifdef SVQC
1353 void SV_WalkMove ()
1354 {
1355         // if PHYS_INPUT_TIMELENGTH is 0 (due to client sending the same timestamp twice),
1356         // don't move
1357         if (PHYS_INPUT_TIMELENGTH <= 0)
1358                 return;
1359
1360 //      if (autocvar_sv_gameplayfix_unstickplayers)
1361 //              SV_CheckStuck (self);
1362
1363 //      applygravity = !SV_CheckWater(self) && self.movetype == MOVETYPE_WALK && !(self.flags & FL_WATERJUMP);
1364
1365 //      hitsupercontentsmask = SV_GenericHitSuperContentsMask(self);
1366
1367 //      SV_CheckVelocity(self);
1368
1369         // do a regular slide move unless it looks like you ran into a step
1370 //      float oldonground = self.flags & FL_ONGROUND;
1371
1372         vector start_origin = self.origin;
1373         vector start_velocity = self.velocity;
1374
1375         float clip = 0;
1376 //      clip = SV_FlyMove (self, PHYS_INPUT_TIMELENGTH, applygravity, NULL, hitsupercontentsmask, sv_gameplayfix_stepmultipletimes.integer ? sv_stepheight.value : 0);
1377
1378 //      if(sv_gameplayfix_downtracesupportsongroundflag.integer)
1379 //      if(!(clip & 1))
1380         {
1381                 // only try this if there was no floor in the way in the trace (no,
1382                 // this check seems to be not REALLY necessary, because if clip & 1,
1383                 // our trace will hit that thing too)
1384                 vector upmove = self.origin;
1385                 upmove_z++;
1386                 vector downmove = self.origin;
1387                 upmove_z--;
1388                 float type;
1389                 if (self.movetype == MOVETYPE_FLYMISSILE)
1390                         type = MOVE_MISSILE;
1391                 else if (self.movetype == MOVETYPE_FLY_WORLDONLY)
1392                         type = MOVE_WORLDONLY;
1393                 else if (self.solid == SOLID_TRIGGER || self.solid == SOLID_NOT)
1394                         type = MOVE_NOMONSTERS; // only clip against bmodels
1395                 else
1396                         type = MOVE_NORMAL;
1397                 vector entmins = self.mins;
1398                 vector entmaxs = self.maxs;
1399                 tracebox(upmove, entmins, entmaxs, downmove, type, self);
1400                 if(trace_fraction < 1 && trace_plane_normal_z > 0.7)
1401                         clip |= 1; // but we HAVE found a floor
1402         }
1403
1404         // if the move did not hit the ground at any point, we're not on ground
1405 //      if(!(clip & 1))
1406 //              self.flags = self.flags & ~FL_ONGROUND;
1407
1408 //      SV_CheckVelocity(self);
1409 //      SV_LinkEdict(self);
1410 //      SV_LinkEdict_TouchAreaGrid(self);
1411
1412         if(clip & 8) // teleport
1413                 return;
1414
1415         if (self.flags & FL_WATERJUMP)
1416                 return;
1417
1418 //      if (autocvar_sv_nostep)
1419 //              return;
1420
1421         vector originalmove_origin = self.origin;
1422         vector originalmove_velocity = self.velocity;
1423         float originalmove_flags = self.flags;
1424         entity originalmove_groundentity = self.groundentity;
1425
1426         // if move didn't block on a step, return
1427         if (clip & 2)
1428         {
1429                 // if move was not trying to move into the step, return
1430                 if (fabs(start_velocity_x) < 0.03125 && fabs(start_velocity_y) < 0.03125)
1431                         return;
1432
1433                 if (self.movetype != MOVETYPE_FLY)
1434                 {
1435                         // return if gibbed by a trigger
1436                         if (self.movetype != MOVETYPE_WALK)
1437                                 return;
1438
1439                         // return if attempting to jump while airborn (unless sv_jumpstep)
1440 //                      if (!autocvar_sv_jumpstep)
1441 //                              if (!oldonground && PRVM_serveredictfloat(self, waterlevel) == 0)
1442 //                                      return;
1443                 }
1444
1445                 // try moving up and forward to go up a step
1446                 // back to start pos
1447                 setorigin(self, start_origin);
1448                 self.velocity = start_velocity;
1449
1450                 // move up
1451                 vector upmove = '0 0 0';
1452                 upmove_z = autocvar_sv_stepheight;
1453 //              if(!SV_PushEntity(&trace, self, upmove, true))
1454 //              {
1455 //                      // we got teleported when upstepping... must abort the move
1456 //                      return;
1457 //              }
1458
1459                 // move forward
1460                 self.velocity_z = 0;
1461 //              clip = SV_FlyMove (self, PHYS_INPUT_TIMELENGTH, applygravity, stepnormal, hitsupercontentsmask, 0);
1462                 self.velocity_z += start_velocity_z;
1463 //              if(clip & 8)
1464 //              {
1465 //                      // we got teleported when upstepping... must abort the move
1466 //                      // note that z velocity handling may not be what QC expects here, but we cannot help it
1467 //                      return;
1468 //              }
1469
1470 //              SV_CheckVelocity(self);
1471 //              SV_LinkEdict(self);
1472 //              SV_LinkEdict_TouchAreaGrid(self);
1473
1474                 // check for stuckness, possibly due to the limited precision of floats
1475                 // in the clipping hulls
1476                 if (clip
1477                  && fabs(originalmove_origin_y - self.origin_y < 0.03125)
1478                  && fabs(originalmove_origin_x - self.origin_x < 0.03125))
1479                 {
1480                         //Con_Printf("wall\n");
1481                         // stepping up didn't make any progress, revert to original move
1482                         setorigin(self, originalmove_origin);
1483                         self.velocity = originalmove_velocity;
1484                         self.flags = originalmove_flags;
1485                         self.groundentity = originalmove_groundentity;
1486                         return;
1487                 }
1488
1489                 //Con_Printf("step - ");
1490
1491                 // extra friction based on view angle
1492 //              if (clip & 2 && sv_wallfriction.integer)
1493 //                      SV_WallFriction (self, stepnormal);
1494         }
1495         // don't do the down move if stepdown is disabled, moving upward, not in water, or the move started offground or ended onground
1496 //      else if (!autocvar_sv_gameplayfix_stepdown || self.waterlevel >= 3 || start_velocity_z >= (1.0 / 32.0) || !oldonground || (self.flags & FL_ONGROUND))
1497 //              return;
1498
1499         // move down
1500         vector downmove = '0 0 0';
1501         downmove_z = -autocvar_sv_stepheight + start_velocity_z*PHYS_INPUT_TIMELENGTH;
1502 //      if(!SV_PushEntity (&downtrace, self, downmove, true))
1503 //      {
1504 //              // we got teleported when downstepping... must abort the move
1505 //              return;
1506 //      }
1507
1508         if (trace_fraction < 1 && trace_plane_normal_z > 0.7)
1509         {
1510                 // this has been disabled so that you can't jump when you are stepping
1511                 // up while already jumping (also known as the Quake2 double jump bug)
1512         }
1513         else
1514         {
1515                 //Con_Printf("slope\n");
1516                 // if the push down didn't end up on good ground, use the move without
1517                 // the step up.  This happens near wall / slope combinations, and can
1518                 // cause the player to hop up higher on a slope too steep to climb
1519                 setorigin(self, originalmove_origin);
1520                 self.velocity = originalmove_velocity;
1521                 self.flags = originalmove_flags;
1522                 self.groundentity = originalmove_groundentity;
1523         }
1524
1525 //      SV_CheckVelocity(self);
1526 //      SV_LinkEdict(self);
1527 //      SV_LinkEdict_TouchAreaGrid(self);
1528 }
1529 #endif
1530 void PM_walk(float buttons_prev, float maxspd_mod)
1531 {
1532         if (!WAS_ONGROUND(self))
1533         {
1534                 if (autocvar_speedmeter)
1535                         dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
1536                 if (self.lastground < time - 0.3)
1537                         self.velocity *= (1 - PHYS_FRICTION_ONLAND);
1538                 if (self.jumppadcount > 1)
1539                         dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
1540                 self.jumppadcount = 0;
1541         }
1542         // walking
1543         makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
1544         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1545                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y;
1546         // acceleration
1547         vector wishdir = normalize(wishvel);
1548         float wishspeed = vlen(wishvel);
1549
1550         wishspeed = min(wishspeed, PHYS_MAXSPEED(self) * maxspd_mod);
1551         if (IS_DUCKED(self))
1552                 wishspeed *= 0.5;
1553
1554         // apply edge friction
1555         float f = vlen(vec2(self.velocity));
1556         if (f > 0)
1557         {
1558                 float realfriction;
1559                 trace_dphitq3surfaceflags = 0;
1560                 tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
1561                 // TODO: apply edge friction
1562                 // apply ground friction
1563                 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)
1564                         realfriction = PHYS_FRICTION_SLICK;
1565                 else
1566                         realfriction = PHYS_FRICTION;
1567
1568                 f = 1 - PHYS_INPUT_TIMELENGTH * realfriction * ((f < PHYS_STOPSPEED) ? (PHYS_STOPSPEED / f) : 1);
1569                 f = max(0, f);
1570                 self.velocity *= f;
1571                 /*
1572                    Mathematical analysis time!
1573
1574                    Our goal is to invert this mess.
1575
1576                    For the two cases we get:
1577                         v = v0 * (1 - PHYS_INPUT_TIMELENGTH * (PHYS_STOPSPEED / v0) * PHYS_FRICTION)
1578                           = v0 - PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1579                         v0 = v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1580                    and
1581                         v = v0 * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1582                         v0 = v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1583
1584                    These cases would be chosen ONLY if:
1585                         v0 < PHYS_STOPSPEED
1586                         v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION < PHYS_STOPSPEED
1587                         v < PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1588                    and, respectively:
1589                         v0 >= PHYS_STOPSPEED
1590                         v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION) >= PHYS_STOPSPEED
1591                         v >= PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1592                  */
1593         }
1594         float addspeed = wishspeed - self.velocity * wishdir;
1595         if (addspeed > 0)
1596         {
1597                 float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
1598                 self.velocity += accelspeed * wishdir;
1599         }
1600         float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1601         if (!(GAMEPLAYFIX_NOGRAVITYONGROUND))
1602                 self.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
1603         if (self.velocity * self.velocity)
1604                 PM_ClientMovement_Move();
1605         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1606                 if (!IS_ONGROUND(self) || !GAMEPLAYFIX_NOGRAVITYONGROUND)
1607                         self.velocity_z -= g * 0.5;
1608 }
1609
1610 void PM_air(float buttons_prev, float maxspd_mod)
1611 {
1612         makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
1613         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1614                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y;
1615         // acceleration
1616         vector wishdir = normalize(wishvel);
1617         float wishspeed = vlen(wishvel);
1618
1619 #ifdef SVQC
1620         if (time >= self.teleport_time)
1621 #else
1622         if (pmove_waterjumptime <= 0)
1623 #endif
1624         {
1625                 float maxairspd = PHYS_MAXAIRSPEED * min(maxspd_mod, 1);
1626
1627                 // apply air speed limit
1628                 float airaccelqw = PHYS_AIRACCEL_QW(self);
1629                 float wishspeed0 = wishspeed;
1630                 wishspeed = min(wishspeed, maxairspd);
1631                 if (IS_DUCKED(self))
1632                         wishspeed *= 0.5;
1633                 float airaccel = PHYS_AIRACCELERATE * min(maxspd_mod, 1);
1634
1635                 float accelerating = (self.velocity * wishdir > 0);
1636                 float wishspeed2 = wishspeed;
1637
1638                 // CPM: air control
1639                 if (PHYS_AIRSTOPACCELERATE)
1640                 {
1641                         vector curdir = normalize(vec2(self.velocity));
1642                         airaccel += (PHYS_AIRSTOPACCELERATE*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
1643                 }
1644                 // note that for straight forward jumping:
1645                 // step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
1646                 // accel  = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
1647                 // -->
1648                 // dv/dt = accel * maxspeed (when slow)
1649                 // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
1650                 // log dv/dt = logaccel + logmaxspeed (when slow)
1651                 // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
1652                 float strafity = IsMoveInDirection(PHYS_INPUT_MOVEVALUES(self), -90) + IsMoveInDirection(PHYS_INPUT_MOVEVALUES(self), +90); // if one is nonzero, other is always zero
1653                 if (PHYS_MAXAIRSTRAFESPEED)
1654                         wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED*maxspd_mod, strafity, PHYS_MAXAIRSTRAFESPEED*maxspd_mod));
1655                 if (PHYS_AIRSTRAFEACCELERATE)
1656                         airaccel = GeomLerp(airaccel, strafity, PHYS_AIRSTRAFEACCELERATE*maxspd_mod);
1657                 if (PHYS_AIRSTRAFEACCEL_QW(self))
1658                         airaccelqw =
1659                 (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW(self) : PHYS_AIRACCEL_QW(self)) >= 0) ? +1 : -1)
1660                 *
1661                 (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW(self)), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW(self))));
1662                 // !CPM
1663
1664                 if (PHYS_WARSOWBUNNY_TURNACCEL && accelerating && PHYS_INPUT_MOVEVALUES(self).y == 0 && PHYS_INPUT_MOVEVALUES(self).x != 0)
1665                         PM_AirAccelerate(wishdir, wishspeed2);
1666                 else
1667                         PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR(self), PHYS_AIRACCEL_SIDEWAYS_FRICTION / maxairspd, PHYS_AIRSPEEDLIMIT_NONQW(self));
1668
1669                 if (PHYS_AIRCONTROL)
1670                         CPM_PM_Aircontrol(wishdir, wishspeed2);
1671         }
1672         float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1673         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1674                 self.velocity_z -= g * 0.5;
1675         else
1676                 self.velocity_z -= g;
1677         PM_ClientMovement_Move();
1678         if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1679                 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1680                         self.velocity_z -= g * 0.5;
1681 }
1682
1683 // used for calculating airshots
1684 bool IsFlying(entity a)
1685 {
1686         if(IS_ONGROUND(a))
1687                 return false;
1688         if(a.waterlevel >= WATERLEVEL_SWIMMING)
1689                 return false;
1690         traceline(a.origin, a.origin - '0 0 48', MOVE_NORMAL, a);
1691         if(trace_fraction < 1)
1692                 return false;
1693         return true;
1694 }
1695
1696 void PM_Main()
1697 {
1698         float buttons = PHYS_INPUT_BUTTON_MASK(self);
1699 #ifdef CSQC
1700         self.team = myteam + 1; // is this correct?
1701         if (!(PHYS_INPUT_BUTTON_JUMP(self))) // !jump
1702                 UNSET_JUMP_HELD(self); // canjump = true
1703         pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH;
1704
1705         PM_ClientMovement_UpdateStatus(true);
1706 #endif
1707         
1708
1709 #ifdef SVQC
1710         WarpZone_PlayerPhysics_FixVAngle();
1711 #endif
1712         float maxspeed_mod = 1;
1713         maxspeed_mod *= PM_check_keepaway();
1714         maxspeed_mod *= PHYS_HIGHSPEED;
1715
1716 #ifdef SVQC
1717         Physics_UpdateStats(maxspeed_mod);
1718
1719         if (self.PlayerPhysplug)
1720                 if (self.PlayerPhysplug())
1721                         return;
1722 #endif
1723
1724         PM_check_race_movetime();
1725 #ifdef SVQC
1726         anticheat_physics();
1727 #endif
1728
1729         if (PM_check_specialcommand(buttons))
1730                 return;
1731 #ifdef SVQC
1732         if (sv_maxidle > 0)
1733         {
1734                 if (buttons != self.buttons_old || PHYS_INPUT_MOVEVALUES(self) != self.movement_old || PHYS_INPUT_ANGLES(self) != self.v_angle_old)
1735                         self.parm_idlesince = time;
1736         }
1737 #endif
1738         float buttons_prev = self.buttons_old;
1739         self.buttons_old = buttons;
1740         self.movement_old = PHYS_INPUT_MOVEVALUES(self);
1741         self.v_angle_old = PHYS_INPUT_ANGLES(self);
1742
1743         PM_check_nickspam();
1744
1745         PM_check_punch();
1746 #ifdef SVQC
1747         if (IS_BOT_CLIENT(self))
1748         {
1749                 if (playerdemo_read())
1750                         return;
1751                 bot_think();
1752         }
1753
1754         if (IS_PLAYER(self))
1755 #endif
1756         {
1757 #ifdef SVQC
1758                 if (self.race_penalty)
1759                         if (time > self.race_penalty)
1760                                 self.race_penalty = 0;
1761 #endif
1762
1763                 float not_allowed_to_move = 0;
1764 #ifdef SVQC
1765                 if (self.race_penalty)
1766                         not_allowed_to_move = 1;
1767 #endif
1768 #ifdef SVQC
1769                 if (time < game_starttime)
1770                         not_allowed_to_move = 1;
1771 #endif
1772
1773                 if (not_allowed_to_move)
1774                 {
1775                         self.velocity = '0 0 0';
1776                         self.movetype = MOVETYPE_NONE;
1777 #ifdef SVQC
1778                         self.disableclientprediction = 2;
1779 #endif
1780                 }
1781 #ifdef SVQC
1782                 else if (self.disableclientprediction == 2)
1783                 {
1784                         if (self.movetype == MOVETYPE_NONE)
1785                                 self.movetype = MOVETYPE_WALK;
1786                         self.disableclientprediction = 0;
1787                 }
1788 #endif
1789         }
1790
1791 #ifdef SVQC
1792         if (self.movetype == MOVETYPE_NONE)
1793                 return;
1794
1795         // when we get here, disableclientprediction cannot be 2
1796         self.disableclientprediction = 0;
1797 #endif
1798
1799         PM_check_spider();
1800
1801         PM_check_frozen();
1802
1803         PM_check_blocked();
1804
1805         maxspeed_mod = 1;
1806
1807         if (self.in_swamp)
1808                 maxspeed_mod *= self.swamp_slowdown; //cvar("g_balance_swamp_moverate");
1809
1810         // conveyors: first fix velocity
1811         if (self.conveyor.state)
1812                 self.velocity -= self.conveyor.movedir;
1813
1814 #ifdef SVQC
1815         MUTATOR_CALLHOOK(PlayerPhysics);
1816 #endif
1817 #ifdef CSQC
1818         PM_multijump();
1819 #endif
1820
1821 //      float forcedodge = 1;
1822 //      if(forcedodge) {
1823 //#ifdef CSQC
1824 //              PM_dodging_checkpressedkeys();
1825 //#endif
1826 //              PM_dodging();
1827 //              PM_ClientMovement_Move();
1828 //              return;
1829 //      }
1830
1831 #ifdef SVQC
1832         if (!IS_PLAYER(self))
1833         {
1834                 maxspeed_mod *= autocvar_sv_spectator_speed_multiplier;
1835                 if (!self.spectatorspeed)
1836                         self.spectatorspeed = maxspeed_mod;
1837                 if (self.impulse && self.impulse <= 19 || (self.impulse >= 200 && self.impulse <= 209) || (self.impulse >= 220 && self.impulse <= 229))
1838                 {
1839                         if (self.lastclassname != "player")
1840                         {
1841                                 if (self.impulse == 10 || self.impulse == 15 || self.impulse == 18 || (self.impulse >= 200 && self.impulse <= 209))
1842                                         self.spectatorspeed = bound(1, self.spectatorspeed + 0.5, 5);
1843                                 else if (self.impulse == 11)
1844                                         self.spectatorspeed = maxspeed_mod;
1845                                 else if (self.impulse == 12 || self.impulse == 16  || self.impulse == 19 || (self.impulse >= 220 && self.impulse <= 229))
1846                                         self.spectatorspeed = bound(1, self.spectatorspeed - 0.5, 5);
1847                                 else if (self.impulse >= 1 && self.impulse <= 9)
1848                                         self.spectatorspeed = 1 + 0.5 * (self.impulse - 1);
1849                         } // otherwise just clear
1850                         self.impulse = 0;
1851                 }
1852                 maxspeed_mod *= self.spectatorspeed;
1853         }
1854 #endif
1855
1856         if(PHYS_DEAD(self))
1857                 goto end;
1858
1859 #ifdef SVQC
1860         if (!self.fixangle && !g_bugrigs)
1861                 self.angles = '0 1 0' * PHYS_INPUT_ANGLES(self).y;
1862 #endif
1863
1864         PM_check_hitground();
1865
1866         if(IsFlying(self))
1867                 self.wasFlying = 1;
1868
1869         if (IS_PLAYER(self))
1870                 CheckPlayerJump();
1871
1872         if (self.flags & /* FL_WATERJUMP */ 2048)
1873         {
1874                 self.velocity_x = self.movedir_x;
1875                 self.velocity_y = self.movedir_y;
1876                 if (time > self.teleport_time || self.waterlevel == WATERLEVEL_NONE)
1877                 {
1878                         self.flags &= ~/* FL_WATERJUMP */ 2048;
1879                         self.teleport_time = 0;
1880                 }
1881         }
1882
1883 #ifdef SVQC
1884         else if (g_bugrigs && IS_PLAYER(self))
1885                 RaceCarPhysics();
1886 #endif
1887
1888         else if (self.movetype == MOVETYPE_NOCLIP || self.movetype == MOVETYPE_FLY || self.movetype == MOVETYPE_FLY_WORLDONLY)
1889                 PM_fly(maxspeed_mod);
1890
1891         else if (self.waterlevel >= WATERLEVEL_SWIMMING)
1892                 PM_swim(maxspeed_mod);
1893
1894         else if (time < self.ladder_time)
1895                 PM_ladder(maxspeed_mod);
1896
1897         else if (ITEMS(self) & IT_USING_JETPACK)
1898                 PM_jetpack(maxspeed_mod);
1899
1900         else if (IS_ONGROUND(self))
1901                 PM_walk(buttons_prev, maxspeed_mod);
1902
1903         else
1904                 PM_air(buttons_prev, maxspeed_mod);
1905
1906 #ifdef SVQC
1907         if (!IS_OBSERVER(self))
1908                 PM_check_race();
1909 #endif
1910         PM_check_vortex();
1911
1912 :end
1913         if (IS_ONGROUND(self))
1914                 self.lastground = time;
1915
1916         // conveyors: then break velocity again
1917         if(self.conveyor.state)
1918                 self.velocity += self.conveyor.movedir;
1919
1920         self.lastflags = FLAGS(self);
1921
1922         self.lastclassname = self.classname;
1923 }
1924
1925 #ifdef SVQC
1926 void SV_PlayerPhysics(void)
1927 #elif defined(CSQC)
1928 void CSQC_ClientMovement_PlayerMove_Frame(void)
1929 #endif
1930 {
1931         PM_Main();
1932
1933 #ifdef CSQC
1934         pmove_org = self.origin;
1935         pmove_vel = self.velocity;
1936 #endif
1937 }