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