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