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