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