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