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