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