]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/physics.qc
Fix water movement and add a networked stat for the tiny movement while frozen
[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 #ifdef SVQC
617
618 // check for a jump-out-of-water
619         makevectors(self.angles);
620         vector start = self.origin;
621         start_z += 8;
622         v_forward_z = 0;
623         normalize(v_forward);
624         vector end = start + v_forward*24;
625         traceline (start, end, TRUE, self);
626         if (trace_fraction < 1)
627         {       // solid at waist
628                 start_z = start_z + self.maxs_z - 8;
629                 end = start + v_forward*24;
630                 self.movedir = trace_plane_normal * -50;
631                 traceline(start, end, TRUE, self);
632                 if (trace_fraction == 1)
633                 {       // open at eye level
634                         self.flags |= FL_WATERJUMP;
635                         self.velocity_z = 225;
636                         self.flags &= ~FL_JUMPRELEASED;
637                         self.teleport_time = time + 2;  // safety net
638                 }
639         }
640 #endif
641 }
642
643 void CheckPlayerJump()
644 {
645 #ifdef SVQC
646         if (self.BUTTON_JUMP)
647                 PlayerJump();
648         else
649                 self.flags |= FL_JUMPRELEASED;
650
651         if (self.waterlevel == WATERLEVEL_SWIMMING)
652                 CheckWaterJump();
653 #endif
654 }
655
656 float racecar_angle(float forward, float down)
657 {
658         if (forward < 0)
659         {
660                 forward = -forward;
661                 down = -down;
662         }
663
664         float ret = vectoyaw('0 1 0' * down + '1 0 0' * forward);
665
666         float angle_mult = forward / (800 + forward);
667
668         if (ret > 180)
669                 return ret * angle_mult + 360 * (1 - angle_mult);
670         else
671                 return ret * angle_mult;
672 }
673
674 void RaceCarPhysics()
675 {
676 #ifdef SVQC
677         // using this move type for "big rigs"
678         // the engine does not push the entity!
679
680         vector rigvel;
681
682         vector angles_save = self.angles;
683         float accel = bound(-1, PHYS_INPUT_MOVEVALUES(self).x / self.stat_sv_maxspeed, 1);
684         float steer = bound(-1, PHYS_INPUT_MOVEVALUES(self).y / self.stat_sv_maxspeed, 1);
685
686         if (g_bugrigs_reverse_speeding)
687         {
688                 if (accel < 0)
689                 {
690                         // back accel is DIGITAL
691                         // to prevent speedhack
692                         if (accel < -0.5)
693                                 accel = -1;
694                         else
695                                 accel = 0;
696                 }
697         }
698
699         self.angles_x = 0;
700         self.angles_z = 0;
701         makevectors(self.angles); // new forward direction!
702
703         if (self.flags & FL_ONGROUND || g_bugrigs_air_steering)
704         {
705                 float myspeed = self.velocity * v_forward;
706                 float upspeed = self.velocity * v_up;
707
708                 // responsiveness factor for steering and acceleration
709                 float f = 1 / (1 + pow(max(-myspeed, myspeed) / g_bugrigs_speed_ref, g_bugrigs_speed_pow));
710                 //MAXIMA: f(v) := 1 / (1 + (v / g_bugrigs_speed_ref) ^ g_bugrigs_speed_pow);
711
712                 float steerfactor;
713                 if (myspeed < 0 && g_bugrigs_reverse_spinning)
714                         steerfactor = -myspeed * g_bugrigs_steer;
715                 else
716                         steerfactor = -myspeed * f * g_bugrigs_steer;
717
718                 float accelfactor;
719                 if (myspeed < 0 && g_bugrigs_reverse_speeding)
720                         accelfactor = g_bugrigs_accel;
721                 else
722                         accelfactor = f * g_bugrigs_accel;
723                 //MAXIMA: accel(v) := f(v) * g_bugrigs_accel;
724
725                 if (accel < 0)
726                 {
727                         if (myspeed > 0)
728                         {
729                                 myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor - g_bugrigs_friction_brake * accel));
730                         }
731                         else
732                         {
733                                 if (!g_bugrigs_reverse_speeding)
734                                         myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
735                         }
736                 }
737                 else
738                 {
739                         if (myspeed >= 0)
740                         {
741                                 myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
742                         }
743                         else
744                         {
745                                 if (g_bugrigs_reverse_stopping)
746                                         myspeed = 0;
747                                 else
748                                         myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor + g_bugrigs_friction_brake * accel));
749                         }
750                 }
751                 // terminal velocity = velocity at which 50 == accelfactor, that is, 1549 units/sec
752                 //MAXIMA: friction(v) := g_bugrigs_friction_floor;
753
754                 self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
755                 makevectors(self.angles); // new forward direction!
756
757                 myspeed += accel * accelfactor * PHYS_INPUT_TIMELENGTH;
758
759                 rigvel = myspeed * v_forward + '0 0 1' * upspeed;
760         }
761         else
762         {
763                 float myspeed = vlen(self.velocity);
764
765                 // responsiveness factor for steering and acceleration
766                 float f = 1 / (1 + pow(max(0, myspeed / g_bugrigs_speed_ref), g_bugrigs_speed_pow));
767                 float steerfactor = -myspeed * f;
768                 self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
769
770                 rigvel = self.velocity;
771                 makevectors(self.angles); // new forward direction!
772         }
773
774         rigvel *= max(0, 1 - vlen(rigvel) * g_bugrigs_friction_air * PHYS_INPUT_TIMELENGTH);
775         //MAXIMA: airfriction(v) := v * v * g_bugrigs_friction_air;
776         //MAXIMA: total_acceleration(v) := accel(v) - friction(v) - airfriction(v);
777         //MAXIMA: solve(total_acceleration(v) = 0, v);
778
779         if (g_bugrigs_planar_movement)
780         {
781                 vector rigvel_xy, neworigin, up;
782                 float mt;
783
784                 rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY; // 4x gravity plays better
785                 rigvel_xy = vec2(rigvel);
786
787                 if (g_bugrigs_planar_movement_car_jumping)
788                         mt = MOVE_NORMAL;
789                 else
790                         mt = MOVE_NOMONSTERS;
791
792                 tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 1024', mt, self);
793                 up = trace_endpos - self.origin;
794
795                 // BUG RIGS: align the move to the surface instead of doing collision testing
796                 // can we move?
797                 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos + rigvel_xy * PHYS_INPUT_TIMELENGTH, mt, self);
798
799                 // align to surface
800                 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos - up + '0 0 1' * rigvel_z * PHYS_INPUT_TIMELENGTH, mt, self);
801
802                 if (trace_fraction < 0.5)
803                 {
804                         trace_fraction = 1;
805                         neworigin = self.origin;
806                 }
807                 else
808                         neworigin = trace_endpos;
809
810                 if (trace_fraction < 1)
811                 {
812                         // now set angles_x so that the car points parallel to the surface
813                         self.angles = vectoangles(
814                                         '1 0 0' * v_forward_x * trace_plane_normal_z
815                                         +
816                                         '0 1 0' * v_forward_y * trace_plane_normal_z
817                                         +
818                                         '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y)
819                                         );
820                         self.flags |= FL_ONGROUND;
821                 }
822                 else
823                 {
824                         // now set angles_x so that the car points forward, but is tilted in velocity direction
825                         self.flags &= ~FL_ONGROUND;
826                 }
827
828                 self.velocity = (neworigin - self.origin) * (1.0 / PHYS_INPUT_TIMELENGTH);
829                 self.movetype = MOVETYPE_NOCLIP;
830         }
831         else
832         {
833                 rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY; // 4x gravity plays better
834                 self.velocity = rigvel;
835                 self.movetype = MOVETYPE_FLY;
836         }
837
838         trace_fraction = 1;
839         tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 4', MOVE_NORMAL, self);
840         if (trace_fraction != 1)
841         {
842                 self.angles = vectoangles2(
843                                 '1 0 0' * v_forward_x * trace_plane_normal_z
844                                 +
845                                 '0 1 0' * v_forward_y * trace_plane_normal_z
846                                 +
847                                 '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y),
848                                 trace_plane_normal
849                                 );
850         }
851         else
852         {
853                 vector vel_local;
854
855                 vel_local_x = v_forward * self.velocity;
856                 vel_local_y = v_right * self.velocity;
857                 vel_local_z = v_up * self.velocity;
858
859                 self.angles_x = racecar_angle(vel_local_x, vel_local_z);
860                 self.angles_z = racecar_angle(-vel_local_y, vel_local_z);
861         }
862
863         // smooth the angles
864         vector vf1, vu1, smoothangles;
865         makevectors(self.angles);
866         float f = bound(0, PHYS_INPUT_TIMELENGTH * g_bugrigs_angle_smoothing, 1);
867         if (f == 0)
868                 f = 1;
869         vf1 = v_forward * f;
870         vu1 = v_up * f;
871         makevectors(angles_save);
872         vf1 = vf1 + v_forward * (1 - f);
873         vu1 = vu1 + v_up * (1 - f);
874         smoothangles = vectoangles2(vf1, vu1);
875         self.angles_x = -smoothangles_x;
876         self.angles_z =  smoothangles_z;
877 #endif
878 }
879
880 string specialcommand = "xwxwxsxsxaxdxaxdx1x ";
881 .float specialcommand_pos;
882 void SpecialCommand()
883 {
884 #ifdef SVQC
885 #ifdef TETRIS
886         TetrisImpulse();
887 #else
888         if (!CheatImpulse(99))
889                 print("A hollow voice says \"Plugh\".\n");
890 #endif
891 #endif
892 }
893
894 #ifdef SVQC
895 float speedaward_speed;
896 string speedaward_holder;
897 string speedaward_uid;
898 #endif
899 void race_send_speedaward(float msg)
900 {
901 #ifdef SVQC
902         // send the best speed of the round
903         WriteByte(msg, SVC_TEMPENTITY);
904         WriteByte(msg, TE_CSQC_RACE);
905         WriteByte(msg, RACE_NET_SPEED_AWARD);
906         WriteInt24_t(msg, floor(speedaward_speed+0.5));
907         WriteString(msg, speedaward_holder);
908 #endif
909 }
910
911 #ifdef SVQC
912 float speedaward_alltimebest;
913 string speedaward_alltimebest_holder;
914 string speedaward_alltimebest_uid;
915 #endif
916 void race_send_speedaward_alltimebest(float msg)
917 {
918 #ifdef SVQC
919         // send the best speed
920         WriteByte(msg, SVC_TEMPENTITY);
921         WriteByte(msg, TE_CSQC_RACE);
922         WriteByte(msg, RACE_NET_SPEED_AWARD_BEST);
923         WriteInt24_t(msg, floor(speedaward_alltimebest+0.5));
924         WriteString(msg, speedaward_alltimebest_holder);
925 #endif
926 }
927
928 float PM_check_keepaway(void)
929 {
930 #ifdef SVQC
931         return (self.ballcarried && g_keepaway) ? autocvar_g_keepaway_ballcarrier_highspeed : 1;
932 #else
933         return 1;
934 #endif
935 }
936
937 void PM_check_race_movetime(void)
938 {
939 #ifdef SVQC
940         self.race_movetime_frac += PHYS_INPUT_TIMELENGTH;
941         float f = floor(self.race_movetime_frac);
942         self.race_movetime_frac -= f;
943         self.race_movetime_count += f;
944         self.race_movetime = self.race_movetime_frac + self.race_movetime_count;
945 #endif
946 }
947
948 float PM_check_specialcommand(float buttons)
949 {
950 #ifdef SVQC
951         string c;
952         if (!buttons)
953                 c = "x";
954         else if (buttons == 1)
955                 c = "1";
956         else if (buttons == 2)
957                 c = " ";
958         else if (buttons == 128)
959                 c = "s";
960         else if (buttons == 256)
961                 c = "w";
962         else if (buttons == 512)
963                 c = "a";
964         else if (buttons == 1024)
965                 c = "d";
966         else
967                 c = "?";
968
969         if (c == substring(specialcommand, self.specialcommand_pos, 1))
970         {
971                 self.specialcommand_pos += 1;
972                 if (self.specialcommand_pos >= strlen(specialcommand))
973                 {
974                         self.specialcommand_pos = 0;
975                         SpecialCommand();
976                         return TRUE;
977                 }
978         }
979         else if (self.specialcommand_pos && (c != substring(specialcommand, self.specialcommand_pos - 1, 1)))
980                 self.specialcommand_pos = 0;
981 #endif
982         return FALSE;
983 }
984
985 void PM_check_nickspam(void)
986 {
987 #ifdef SVQC
988         if (time >= self.nickspamtime)
989                 return;
990         if (self.nickspamcount >= autocvar_g_nick_flood_penalty_yellow)
991         {
992                 // slight annoyance for nick change scripts
993                 PHYS_INPUT_MOVEVALUES(self) = -1 * PHYS_INPUT_MOVEVALUES(self);
994                 self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = self.BUTTON_ZOOM = self.BUTTON_CROUCH = self.BUTTON_HOOK = self.BUTTON_USE = 0;
995
996                 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!
997                 {
998                         PHYS_INPUT_ANGLES(self)_x = random() * 360;
999                         PHYS_INPUT_ANGLES(self)_y = random() * 360;
1000                         // at least I'm not forcing retardedview by also assigning to angles_z
1001                         self.fixangle = TRUE;
1002                 }
1003         }
1004 #endif
1005 }
1006
1007 void PM_check_punch()
1008 {
1009 #ifdef SVQC
1010         if (self.punchangle != '0 0 0')
1011         {
1012                 float f = vlen(self.punchangle) - 10 * PHYS_INPUT_TIMELENGTH;
1013                 if (f > 0)
1014                         self.punchangle = normalize(self.punchangle) * f;
1015                 else
1016                         self.punchangle = '0 0 0';
1017         }
1018
1019         if (self.punchvector != '0 0 0')
1020         {
1021                 float f = vlen(self.punchvector) - 30 * PHYS_INPUT_TIMELENGTH;
1022                 if (f > 0)
1023                         self.punchvector = normalize(self.punchvector) * f;
1024                 else
1025                         self.punchvector = '0 0 0';
1026         }
1027 #endif
1028 }
1029
1030 void PM_check_spider(void)
1031 {
1032 #ifdef SVQC
1033         if (time >= self.spider_slowness)
1034                 return;
1035         self.stat_sv_maxspeed *= 0.5; // half speed while slow from spider
1036         self.stat_sv_airspeedlimit_nonqw *= 0.5;
1037 #endif
1038 }
1039
1040 // predict frozen movement, as frozen players CAN move in some cases
1041 void PM_check_frozen(void)
1042 {
1043         if (!PHYS_FROZEN(self))
1044                 return;
1045         if (PHYS_DODGING_FROZEN
1046 #ifdef SVQC
1047         && IS_REAL_CLIENT(self)
1048 #endif
1049         )
1050         {
1051                 PHYS_INPUT_MOVEVALUES(self)_x = bound(-5, PHYS_INPUT_MOVEVALUES(self).x, 5);
1052                 PHYS_INPUT_MOVEVALUES(self)_y = bound(-5, PHYS_INPUT_MOVEVALUES(self).y, 5);
1053                 PHYS_INPUT_MOVEVALUES(self)_z = bound(-5, PHYS_INPUT_MOVEVALUES(self).z, 5);
1054         }
1055         else
1056                 PHYS_INPUT_MOVEVALUES(self) = '0 0 0';
1057
1058         vector midpoint = ((self.absmin + self.absmax) * 0.5);
1059         if (pointcontents(midpoint) == CONTENT_WATER)
1060         {
1061                 self.velocity = self.velocity * 0.5;
1062
1063                 if (pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
1064                         self.velocity_z = 200;
1065         }
1066 }
1067
1068 void PM_check_blocked(void)
1069 {
1070 #ifdef SVQC
1071         if (!self.player_blocked)
1072                 return;
1073         PHYS_INPUT_MOVEVALUES(self) = '0 0 0';
1074         self.disableclientprediction = 1;
1075 #endif
1076 }
1077
1078 #ifdef SVQC
1079 float speedaward_lastsent;
1080 float speedaward_lastupdate;
1081 string GetMapname(void);
1082 #endif
1083 void PM_check_race(void)
1084 {
1085 #ifdef SVQC
1086         if not(g_cts || g_race)
1087                 return;
1088         if (vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed)
1089         {
1090                 speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1');
1091                 speedaward_holder = self.netname;
1092                 speedaward_uid = self.crypto_idfp;
1093                 speedaward_lastupdate = time;
1094         }
1095         if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
1096         {
1097                 string rr = (g_cts) ? CTS_RECORD : RACE_RECORD;
1098                 race_send_speedaward(MSG_ALL);
1099                 speedaward_lastsent = speedaward_speed;
1100                 if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
1101                 {
1102                         speedaward_alltimebest = speedaward_speed;
1103                         speedaward_alltimebest_holder = speedaward_holder;
1104                         speedaward_alltimebest_uid = speedaward_uid;
1105                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
1106                         db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
1107                         race_send_speedaward_alltimebest(MSG_ALL);
1108                 }
1109         }
1110 #endif
1111 }
1112
1113 void PM_check_vortex(void)
1114 {
1115 #ifdef SVQC
1116         float xyspeed = vlen(vec2(self.velocity));
1117         if (self.weapon == WEP_NEX && autocvar_g_balance_nex_charge && autocvar_g_balance_nex_charge_velocity_rate && xyspeed > autocvar_g_balance_nex_charge_minspeed)
1118         {
1119                 // add a maximum of charge_velocity_rate when going fast (f = 1), gradually increasing from minspeed (f = 0) to maxspeed
1120                 xyspeed = min(xyspeed, autocvar_g_balance_nex_charge_maxspeed);
1121                 float f = (xyspeed - autocvar_g_balance_nex_charge_minspeed) / (autocvar_g_balance_nex_charge_maxspeed - autocvar_g_balance_nex_charge_minspeed);
1122                 // add the extra charge
1123                 self.nex_charge = min(1, self.nex_charge + autocvar_g_balance_nex_charge_velocity_rate * f * PHYS_INPUT_TIMELENGTH);
1124         }
1125 #endif
1126 }
1127
1128 void PM_fly(float maxspd_mod)
1129 {
1130         // noclipping or flying
1131         self.flags &= ~FL_ONGROUND;
1132
1133         self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
1134         makevectors(PHYS_INPUT_ANGLES(self));
1135         //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
1136         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1137                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y
1138                                         + '0 0 1' * PHYS_INPUT_MOVEVALUES(self).z;
1139         // acceleration
1140         vector wishdir = normalize(wishvel);
1141         float wishspeed = vlen(wishvel);
1142         if (wishspeed > PHYS_MAXSPEED(self) * maxspd_mod)
1143                 wishspeed = PHYS_MAXSPEED(self) * maxspd_mod;
1144         if (time >= self.teleport_time)
1145                 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
1146 }
1147
1148 void PM_swim(float maxspd_mod)
1149 {
1150         // swimming
1151         UNSET_ONGROUND(self);
1152
1153         makevectors(PHYS_INPUT_ANGLES(self));
1154         //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
1155         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1156                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y
1157                                         + '0 0 1' * PHYS_INPUT_MOVEVALUES(self).z;
1158         if (wishvel == '0 0 0')
1159                 wishvel = '0 0 -60'; // drift towards bottom
1160
1161         vector wishdir = normalize(wishvel);
1162         float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
1163         wishspeed = wishspeed * 0.7;
1164
1165         // water friction
1166         self.velocity *= (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
1167
1168 #ifdef CSQC
1169         float addspeed = wishspeed - dotproduct(self.velocity, wishdir);
1170         if (addspeed > 0)
1171         {
1172                 float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
1173                 self.velocity += accelspeed * wishdir;
1174         }
1175         float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1176         if (!(GAMEPLAYFIX_NOGRAVITYONGROUND))
1177                 self.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
1178         if (self.velocity * self.velocity)
1179                 CSQC_ClientMovement_Move();
1180         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1181                 if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1182                         self.velocity_z -= g * 0.5;
1183 #endif
1184
1185 #ifdef SVQC
1186         // water acceleration
1187         PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
1188 #endif
1189 }
1190
1191 void PM_ladder(float maxspd_mod)
1192 {
1193 #ifdef SVQC
1194         // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
1195         self.flags &= ~FL_ONGROUND;
1196
1197         float g;
1198         g = PHYS_GRAVITY * PHYS_INPUT_TIMELENGTH;
1199         if (PHYS_ENTGRAVITY(self))
1200                 g *= PHYS_ENTGRAVITY(self);
1201         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1202         {
1203                 g *= 0.5;
1204                 self.velocity_z += g;
1205         }
1206
1207         self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
1208         makevectors(PHYS_INPUT_ANGLES(self));
1209         //wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x + v_right * PHYS_INPUT_MOVEVALUES(self).y + v_up * PHYS_INPUT_MOVEVALUES(self).z;
1210         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1211                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y
1212                                         + '0 0 1' * PHYS_INPUT_MOVEVALUES(self).z;
1213         self.velocity_z += g;
1214         if (self.ladder_entity.classname == "func_water")
1215         {
1216                 float f = vlen(wishvel);
1217                 if (f > self.ladder_entity.speed)
1218                         wishvel *= (self.ladder_entity.speed / f);
1219
1220                 self.watertype = self.ladder_entity.skin;
1221                 f = self.ladder_entity.origin_z + self.ladder_entity.maxs_z;
1222                 if ((self.origin_z + self.view_ofs_z) < f)
1223                         self.waterlevel = WATERLEVEL_SUBMERGED;
1224                 else if ((self.origin_z + (self.mins_z + self.maxs_z) * 0.5) < f)
1225                         self.waterlevel = WATERLEVEL_SWIMMING;
1226                 else if ((self.origin_z + self.mins_z + 1) < f)
1227                         self.waterlevel = WATERLEVEL_WETFEET;
1228                 else
1229                 {
1230                         self.waterlevel = WATERLEVEL_NONE;
1231                         self.watertype = CONTENT_EMPTY;
1232                 }
1233         }
1234         // acceleration
1235         vector wishdir = normalize(wishvel);
1236         float wishspeed = min(vlen(wishvel), self.stat_sv_maxspeed * maxspd_mod);
1237         if (time >= self.teleport_time)
1238                 // water acceleration
1239                 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE*maxspd_mod, 1, 0, 0, 0);
1240 #endif
1241 }
1242
1243 void PM_jetpack(float maxspd_mod)
1244 {
1245         //makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
1246         makevectors(PHYS_INPUT_ANGLES(self));
1247         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1248                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y;
1249         // add remaining speed as Z component
1250         float maxairspd = PHYS_MAXAIRSPEED * max(1, maxspd_mod);
1251         // fix speedhacks :P
1252         wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxairspd);
1253         // add the unused velocity as up component
1254         wishvel_z = 0;
1255
1256         // if (self.BUTTON_JUMP)
1257                 wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
1258
1259         // it is now normalized, so...
1260         float a_side = PHYS_JETPACK_ACCEL_SIDE;
1261         float a_up = PHYS_JETPACK_ACCEL_UP;
1262         float a_add = PHYS_JETPACK_ANTIGRAVITY * PHYS_GRAVITY;
1263
1264         wishvel_x *= a_side;
1265         wishvel_y *= a_side;
1266         wishvel_z *= a_up;
1267         wishvel_z += a_add;
1268
1269         float best = 0;
1270         //////////////////////////////////////////////////////////////////////////////////////
1271         // finding the maximum over all vectors of above form
1272         // with wishvel having an absolute value of 1
1273         //////////////////////////////////////////////////////////////////////////////////////
1274         // we're finding the maximum over
1275         //   f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
1276         // for z in the range from -1 to 1
1277         //////////////////////////////////////////////////////////////////////////////////////
1278         // maximum is EITHER attained at the single extreme point:
1279         float a_diff = a_side * a_side - a_up * a_up;
1280         float f;
1281         if (a_diff != 0)
1282         {
1283                 f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
1284                 if (f > -1 && f < 1) // can it be attained?
1285                 {
1286                         best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
1287                         //print("middle\n");
1288                 }
1289         }
1290         // OR attained at z = 1:
1291         f = (a_up + a_add) * (a_up + a_add);
1292         if (f > best)
1293         {
1294                 best = f;
1295                 //print("top\n");
1296         }
1297         // OR attained at z = -1:
1298         f = (a_up - a_add) * (a_up - a_add);
1299         if (f > best)
1300         {
1301                 best = f;
1302                 //print("bottom\n");
1303         }
1304         best = sqrt(best);
1305         //////////////////////////////////////////////////////////////////////////////////////
1306
1307         //print("best possible acceleration: ", ftos(best), "\n");
1308
1309         float fxy, fz;
1310         fxy = bound(0, 1 - (self.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / PHYS_JETPACK_MAXSPEED_SIDE, 1);
1311         if (wishvel_z - PHYS_GRAVITY > 0)
1312                 fz = bound(0, 1 - self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1313         else
1314                 fz = bound(0, 1 + self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1315
1316         float fvel;
1317         fvel = vlen(wishvel);
1318         wishvel_x *= fxy;
1319         wishvel_y *= fxy;
1320         wishvel_z = (wishvel_z - PHYS_GRAVITY) * fz + PHYS_GRAVITY;
1321
1322         fvel = min(1, vlen(wishvel) / best);
1323         if (PHYS_JETPACK_FUEL && !(ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO))
1324                 f = min(1, PHYS_AMMO_FUEL(self) / (PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel));
1325         else
1326                 f = 1;
1327
1328         //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
1329
1330         if (f > 0 && wishvel != '0 0 0')
1331         {
1332                 self.velocity = self.velocity + wishvel * f * PHYS_INPUT_TIMELENGTH;
1333                 UNSET_ONGROUND(self);
1334
1335 #ifdef SVQC
1336                 if (!(ITEMS(self) & IT_UNLIMITED_WEAPON_AMMO))
1337                         self.ammo_fuel -= PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel * f;
1338                 
1339                 self.items |= IT_USING_JETPACK;
1340
1341                 // jetpack also inhibits health regeneration, but only for 1 second
1342                 self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
1343 #endif
1344         }
1345 }
1346
1347 void PM_walk(float buttons_prev, float maxspd_mod)
1348 {
1349 #ifdef SVQC
1350         // we get here if we ran out of ammo
1351         if ((ITEMS(self) & IT_JETPACK) && self.BUTTON_HOOK && !(buttons_prev & 32) && self.ammo_fuel < 0.01)
1352                 sprint(self, "You don't have any fuel for the ^2Jetpack\n");
1353 #endif
1354         // walking
1355         makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
1356         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1357                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y;
1358
1359 #ifdef SVQC
1360         if (!(self.lastflags & FL_ONGROUND))
1361         {
1362                 if (autocvar_speedmeter)
1363                         dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
1364                 if (self.lastground < time - 0.3)
1365                         self.velocity *= (1 - autocvar_sv_friction_on_land);
1366                 if (self.jumppadcount > 1)
1367                         dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
1368                 self.jumppadcount = 0;
1369         }
1370 #endif
1371
1372         vector v = self.velocity;
1373         v_z = 0;
1374         float f = vlen(v);
1375         if (f > 0)
1376         {
1377                 f = 1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION * ((f < PHYS_STOPSPEED) ? (PHYS_STOPSPEED / f) : 1);
1378                 f = max(0, f);
1379                 self.velocity *= f;
1380                 /*
1381                    Mathematical analysis time!
1382
1383                    Our goal is to invert this mess.
1384
1385                    For the two cases we get:
1386                         v = v0 * (1 - PHYS_INPUT_TIMELENGTH * (PHYS_STOPSPEED / v0) * PHYS_FRICTION)
1387                           = v0 - PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1388                         v0 = v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1389                    and
1390                         v = v0 * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1391                         v0 = v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1392
1393                    These cases would be chosen ONLY if:
1394                         v0 < PHYS_STOPSPEED
1395                         v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION < PHYS_STOPSPEED
1396                         v < PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1397                    and, respectively:
1398                         v0 >= PHYS_STOPSPEED
1399                         v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION) >= PHYS_STOPSPEED
1400                         v >= PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1401                  */
1402         }
1403
1404         // acceleration
1405         vector wishdir = normalize(wishvel);
1406         float wishspeed = vlen(wishvel);
1407         wishspeed = min(wishspeed, PHYS_MAXSPEED(self) * maxspd_mod);
1408         if (IS_DUCKED(self))
1409                 wishspeed *= 0.5;
1410 #ifdef SVQC
1411         if (time >= self.teleport_time)
1412                 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
1413 #endif
1414
1415 #ifdef CSQC
1416         float addspeed = wishspeed - dotproduct(self.velocity, wishdir);
1417         if (addspeed > 0)
1418         {
1419                 float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
1420                 self.velocity += accelspeed * wishdir;
1421         }
1422         float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1423         if (!(GAMEPLAYFIX_NOGRAVITYONGROUND))
1424                 self.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
1425         if (self.velocity * self.velocity)
1426                 CSQC_ClientMovement_Move();
1427         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1428                 if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1429                         self.velocity_z -= g * 0.5;
1430 #endif
1431 }
1432
1433 void PM_air(float buttons_prev, float maxspd_mod)
1434 {
1435 #ifdef SVQC
1436         // we get here if we ran out of ammo
1437         if ((ITEMS(self) & IT_JETPACK) && self.BUTTON_HOOK && !(buttons_prev & 32) && PHYS_AMMO_FUEL(self) < 0.01)
1438                 sprint(self, "You don't have any fuel for the ^2Jetpack\n");
1439 #endif
1440         float maxairspd, airaccel;
1441         maxairspd = PHYS_MAXAIRSPEED * min(maxspd_mod, 1);
1442         airaccel = PHYS_AIRACCELERATE * min(maxspd_mod, 1);
1443         // airborn
1444         makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
1445         vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(self).x
1446                                         + v_right * PHYS_INPUT_MOVEVALUES(self).y;
1447         // acceleration
1448         vector wishdir = normalize(wishvel);
1449         float wishspeed = vlen(wishvel);
1450         float wishspeed0 = min(wishspeed, PHYS_MAXSPEED(self) * maxspd_mod);
1451         wishspeed = min(wishspeed, maxairspd);
1452         if (IS_DUCKED(self))
1453                 wishspeed *= 0.5;
1454 #ifdef SVQC
1455         if (time >= self.teleport_time)
1456 #else
1457         if (pmove_waterjumptime <= 0)
1458 #endif
1459         {
1460                 float airaccelqw = PHYS_AIRACCEL_QW(self);
1461                 float accelerating = (self.velocity * wishdir > 0);
1462                 float wishspeed2 = wishspeed;
1463
1464                 // CPM
1465                 if (PHYS_AIRSTOPACCELERATE)
1466                 {
1467                         vector curdir = normalize(vec2(self.velocity));
1468                         airaccel += (PHYS_AIRSTOPACCELERATE*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
1469                 }
1470                 // note that for straight forward jumping:
1471                 // step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
1472                 // accel  = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
1473                 // -->
1474                 // dv/dt = accel * maxspeed (when slow)
1475                 // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
1476                 // log dv/dt = logaccel + logmaxspeed (when slow)
1477                 // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
1478                 float strafity = IsMoveInDirection(PHYS_INPUT_MOVEVALUES(self), -90) + IsMoveInDirection(PHYS_INPUT_MOVEVALUES(self), +90); // if one is nonzero, other is always zero
1479                 if (PHYS_MAXAIRSTRAFESPEED)
1480                         wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED*maxspd_mod, strafity, PHYS_MAXAIRSTRAFESPEED*maxspd_mod));
1481                 if (PHYS_AIRSTRAFEACCELERATE)
1482                         airaccel = GeomLerp(airaccel, strafity, PHYS_AIRSTRAFEACCELERATE*maxspd_mod);
1483                 if (PHYS_AIRSTRAFEACCEL_QW(self))
1484                         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)));
1485                 // !CPM
1486
1487                 if (PHYS_WARSOWBUNNY_TURNACCEL && accelerating && PHYS_INPUT_MOVEVALUES(self).y == 0 && PHYS_INPUT_MOVEVALUES(self).x != 0)
1488                         PM_AirAccelerate(wishdir, wishspeed);
1489                 else
1490                         PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR(self), PHYS_AIRACCEL_SIDEWAYS_FRICTION / maxairspd, PHYS_AIRSPEEDLIMIT_NONQW(self));
1491
1492                 if (PHYS_AIRCONTROL)
1493                         CPM_PM_Aircontrol(wishdir, wishspeed2);
1494         }
1495 #ifdef CSQC
1496         float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1497         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1498                 self.velocity_z -= g * 0.5;
1499         else
1500                 self.velocity_z -= g;
1501         CSQC_ClientMovement_Move();
1502         if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1503                 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1504                         self.velocity_z -= g * 0.5;
1505 #endif
1506 }
1507
1508 // used for calculating airshots
1509 float PM_is_flying()
1510 {
1511         if (IS_ONGROUND(self))
1512                 return 0;
1513         if (self.waterlevel >= WATERLEVEL_SWIMMING)
1514                 return 0;
1515         traceline(self.origin, self.origin - '0 0 48', MOVE_NORMAL, self);
1516         return trace_fraction >= 1;
1517 }
1518
1519 void PM_Main()
1520 {
1521 #ifdef CSQC
1522         //Con_Printf(" %f", PHYS_INPUT_TIMELENGTH);
1523         if (!(PHYS_INPUT_BUTTONS(self) & 2)) // !jump
1524                 UNSET_JUMP_HELD(self); // canjump = true
1525         pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH;
1526         CSQC_ClientMovement_UpdateStatus();
1527 #endif
1528
1529 #ifdef SVQC
1530         WarpZone_PlayerPhysics_FixVAngle();
1531 #endif
1532         float maxspeed_mod = 1;
1533         maxspeed_mod *= PM_check_keepaway();
1534         maxspeed_mod *= PHYS_HIGHSPEED;
1535
1536         // fix physics stats for g_movement_highspeed
1537         // TODO maybe rather use maxairspeed? needs testing
1538 #ifdef SVQC
1539         self.stat_sv_airaccel_qw = AdjustAirAccelQW(autocvar_sv_airaccel_qw, maxspeed_mod);
1540         if (autocvar_sv_airstrafeaccel_qw)
1541                 self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(autocvar_sv_airstrafeaccel_qw, maxspeed_mod);
1542         else
1543                 self.stat_sv_airstrafeaccel_qw = 0;
1544         self.stat_sv_airspeedlimit_nonqw = autocvar_sv_airspeedlimit_nonqw * maxspeed_mod;
1545         self.stat_sv_maxspeed = autocvar_sv_maxspeed * maxspeed_mod; // also slow walking
1546         self.stat_movement_highspeed = autocvar_g_movement_highspeed; // TODO: remove this!
1547
1548         self.stat_jetpack_antigravity = autocvar_g_jetpack_antigravity;
1549         self.stat_jetpack_accel_up = autocvar_g_jetpack_acceleration_up;
1550         self.stat_jetpack_accel_side = autocvar_g_jetpack_acceleration_side;
1551         self.stat_jetpack_maxspeed_side = autocvar_g_jetpack_maxspeed_side;
1552         self.stat_jetpack_maxspeed_up = autocvar_g_jetpack_maxspeed_up;
1553         self.stat_jetpack_fuel = autocvar_g_jetpack_fuel;
1554 #endif
1555 #ifdef SVQC
1556         if (self.PlayerPhysplug)
1557                 if (self.PlayerPhysplug())
1558                         return;
1559 #endif
1560
1561         PM_check_race_movetime();
1562 #ifdef SVQC
1563         anticheat_physics();
1564 #endif
1565         float buttons = PHYS_INPUT_BUTTONS(self);
1566
1567         if (PM_check_specialcommand(buttons))
1568                 return;
1569 #ifdef SVQC
1570         if (sv_maxidle > 0)
1571         {
1572                 if (buttons != self.buttons_old || PHYS_INPUT_MOVEVALUES(self) != self.movement_old || PHYS_INPUT_ANGLES(self) != self.v_angle_old)
1573                         self.parm_idlesince = time;
1574         }
1575 #endif
1576         float buttons_prev = self.buttons_old;
1577         self.buttons_old = buttons;
1578         self.movement_old = PHYS_INPUT_MOVEVALUES(self);
1579         self.v_angle_old = PHYS_INPUT_ANGLES(self);
1580
1581         PM_check_nickspam();
1582
1583         PM_check_punch();
1584 #ifdef SVQC
1585         if (IS_BOT_CLIENT(self))
1586         {
1587                 if (playerdemo_read())
1588                         return;
1589                 bot_think();
1590         }
1591
1592         self.items &= ~IT_USING_JETPACK;
1593
1594         if (IS_PLAYER(self))
1595 #endif
1596         {
1597 #ifdef SVQC
1598                 if (self.race_penalty)
1599                         if (time > self.race_penalty)
1600                                 self.race_penalty = 0;
1601 #endif
1602
1603                 float not_allowed_to_move = 0;
1604 #ifdef SVQC
1605                 if (self.race_penalty)
1606                         not_allowed_to_move = 1;
1607 #endif
1608 #ifdef SVQC
1609                 if (!autocvar_sv_ready_restart_after_countdown)
1610                         if (time < game_starttime)
1611                                 not_allowed_to_move = 1;
1612 #endif
1613
1614                 if (not_allowed_to_move)
1615                 {
1616                         self.velocity = '0 0 0';
1617                         self.movetype = MOVETYPE_NONE;
1618 #ifdef SVQC
1619                         self.disableclientprediction = 2;
1620 #endif
1621                 }
1622 #ifdef SVQC
1623                 else if (self.disableclientprediction == 2)
1624                 {
1625                         if (self.movetype == MOVETYPE_NONE)
1626                                 self.movetype = MOVETYPE_WALK;
1627                         self.disableclientprediction = 0;
1628                 }
1629 #endif
1630         }
1631
1632 #ifdef SVQC
1633         if (self.movetype == MOVETYPE_NONE)
1634                 return;
1635 #endif
1636
1637 #ifdef SVQC
1638         // when we get here, disableclientprediction cannot be 2
1639         self.disableclientprediction = 0;
1640         if (time < self.ladder_time)
1641                 self.disableclientprediction = 1;
1642 #endif
1643
1644         PM_check_spider();
1645
1646         PM_check_frozen();
1647
1648 #ifdef SVQC
1649         MUTATOR_CALLHOOK(PlayerPhysics);
1650 #endif
1651
1652         PM_check_blocked();
1653
1654         maxspeed_mod = 1;
1655
1656 #ifdef SVQC
1657         if (self.in_swamp) {
1658                 maxspeed_mod *= self.swamp_slowdown; //cvar("g_balance_swamp_moverate");
1659         }
1660 #endif
1661
1662 #ifdef SVQC
1663         // conveyors: first fix velocity
1664         if (self.conveyor.state)
1665                 self.velocity -= self.conveyor.movedir;
1666 #endif
1667
1668 #ifdef SVQC
1669         if (!IS_PLAYER(self))
1670         {
1671                 maxspeed_mod *= autocvar_sv_spectator_speed_multiplier;
1672                 if (!self.spectatorspeed)
1673                         self.spectatorspeed = maxspeed_mod;
1674                 if (self.impulse && self.impulse <= 19 || (self.impulse >= 200 && self.impulse <= 209) || (self.impulse >= 220 && self.impulse <= 229))
1675                 {
1676                         if (self.lastclassname != "player")
1677                         {
1678                                 if (self.impulse == 10 || self.impulse == 15 || self.impulse == 18 || (self.impulse >= 200 && self.impulse <= 209))
1679                                         self.spectatorspeed = bound(1, self.spectatorspeed + 0.5, 5);
1680                                 else if (self.impulse == 11)
1681                                         self.spectatorspeed = maxspeed_mod;
1682                                 else if (self.impulse == 12 || self.impulse == 16  || self.impulse == 19 || (self.impulse >= 220 && self.impulse <= 229))
1683                                         self.spectatorspeed = bound(1, self.spectatorspeed - 0.5, 5);
1684                                 else if (self.impulse >= 1 && self.impulse <= 9)
1685                                         self.spectatorspeed = 1 + 0.5 * (self.impulse - 1);
1686                         } // otherwise just clear
1687                         self.impulse = 0;
1688                 }
1689                 maxspeed_mod *= self.spectatorspeed;
1690         }
1691 #endif
1692
1693 #ifdef SVQC
1694         // if dead, behave differently
1695         // in CSQC, physics don't handle dead player
1696         if (self.deadflag)
1697                 goto end;
1698 #endif
1699
1700 #ifdef SVQC
1701         if (!self.fixangle && !g_bugrigs)
1702                 self.angles = '0 1 0' * PHYS_INPUT_ANGLES(self).y;
1703 #endif
1704
1705 #ifdef SVQC
1706         if (self.flags & FL_ONGROUND)
1707         if (IS_PLAYER(self)) // no fall sounds for observers thank you very much
1708         if (self.wasFlying)
1709         {
1710                 self.wasFlying = 0;
1711                 if (self.waterlevel < WATERLEVEL_SWIMMING)
1712                 if (time >= self.ladder_time)
1713                 if (!self.hook)
1714                 {
1715                         self.nextstep = time + 0.3 + random() * 0.1;
1716                         trace_dphitq3surfaceflags = 0;
1717                         tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
1718                         if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS))
1719                         {
1720                                 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
1721                                         GlobalSound(globalsound_metalfall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
1722                                 else
1723                                         GlobalSound(globalsound_fall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
1724                         }
1725                 }
1726         }
1727 #endif
1728
1729         if (PM_is_flying())
1730                 self.wasFlying = 1;
1731
1732 #ifdef SVQC
1733         if (IS_PLAYER(self))
1734                 CheckPlayerJump();
1735 #endif
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