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