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