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