]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/physics.qc
Impulses: migration pathway
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / physics.qc
1 #include "physics.qh"
2 #include "triggers/include.qh"
3 #include "viewloc.qh"
4
5 #ifdef SVQC
6
7 #include "../server/miscfunctions.qh"
8 #include "triggers/trigger/viewloc.qh"
9
10 // client side physics
11 bool Physics_Valid(string thecvar)
12 {
13         return autocvar_g_physics_clientselect && strhasword(autocvar_g_physics_clientselect_options, thecvar);
14 }
15
16 float Physics_ClientOption(entity this, string option)
17 {
18         if(Physics_Valid(this.cvar_cl_physics))
19         {
20                 string s = sprintf("g_physics_%s_%s", this.cvar_cl_physics, option);
21                 if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
22                         return cvar(s);
23         }
24         if(autocvar_g_physics_clientselect && autocvar_g_physics_clientselect_default)
25         {
26                 string s = sprintf("g_physics_%s_%s", autocvar_g_physics_clientselect_default, option);
27                 if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
28                         return cvar(s);
29         }
30         return cvar(strcat("sv_", option));
31 }
32
33 void Physics_UpdateStats(entity this, float maxspd_mod)
34 {
35         STAT(MOVEVARS_AIRACCEL_QW, this) = AdjustAirAccelQW(Physics_ClientOption(this, "airaccel_qw"), maxspd_mod);
36         STAT(MOVEVARS_AIRSTRAFEACCEL_QW, this) = (Physics_ClientOption(this, "airstrafeaccel_qw"))
37                 ? AdjustAirAccelQW(Physics_ClientOption(this, "airstrafeaccel_qw"), maxspd_mod)
38                 : 0;
39         STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW, this) = Physics_ClientOption(this, "airspeedlimit_nonqw") * maxspd_mod;
40         STAT(MOVEVARS_MAXSPEED, this) = Physics_ClientOption(this, "maxspeed") * maxspd_mod; // also slow walking
41
42         // old stats
43         // fix some new settings
44         STAT(MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, this) = Physics_ClientOption(this, "airaccel_qw_stretchfactor");
45         STAT(MOVEVARS_MAXAIRSTRAFESPEED, this) = Physics_ClientOption(this, "maxairstrafespeed");
46         STAT(MOVEVARS_MAXAIRSPEED, this) = Physics_ClientOption(this, "maxairspeed");
47         STAT(MOVEVARS_AIRSTRAFEACCELERATE, this) = Physics_ClientOption(this, "airstrafeaccelerate");
48         STAT(MOVEVARS_WARSOWBUNNY_TURNACCEL, this) = Physics_ClientOption(this, "warsowbunny_turnaccel");
49         STAT(MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, this) = Physics_ClientOption(this, "airaccel_sideways_friction");
50         STAT(MOVEVARS_AIRCONTROL, this) = Physics_ClientOption(this, "aircontrol");
51         STAT(MOVEVARS_AIRCONTROL_POWER, this) = Physics_ClientOption(this, "aircontrol_power");
52         STAT(MOVEVARS_AIRCONTROL_PENALTY, this) = Physics_ClientOption(this, "aircontrol_penalty");
53         STAT(MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, this) = Physics_ClientOption(this, "warsowbunny_airforwardaccel");
54         STAT(MOVEVARS_WARSOWBUNNY_TOPSPEED, this) = Physics_ClientOption(this, "warsowbunny_topspeed");
55         STAT(MOVEVARS_WARSOWBUNNY_ACCEL, this) = Physics_ClientOption(this, "warsowbunny_accel");
56         STAT(MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, this) = Physics_ClientOption(this, "warsowbunny_backtosideratio");
57         STAT(MOVEVARS_FRICTION, this) = Physics_ClientOption(this, "friction");
58         STAT(MOVEVARS_ACCELERATE, this) = Physics_ClientOption(this, "accelerate");
59         STAT(MOVEVARS_STOPSPEED, this) = Physics_ClientOption(this, "stopspeed");
60         STAT(MOVEVARS_AIRACCELERATE, this) = Physics_ClientOption(this, "airaccelerate");
61         STAT(MOVEVARS_AIRSTOPACCELERATE, this) = Physics_ClientOption(this, "airstopaccelerate");
62         STAT(MOVEVARS_JUMPVELOCITY, this) = Physics_ClientOption(this, "jumpvelocity");
63         STAT(MOVEVARS_TRACK_CANJUMP, this) = Physics_ClientOption(this, "track_canjump");
64 }
65 #endif
66
67 float IsMoveInDirection(vector mv, float ang) // key mix factor
68 {
69         if (mv_x == 0 && mv_y == 0)
70                 return 0; // avoid division by zero
71         ang -= RAD2DEG * atan2(mv_y, mv_x);
72         ang = remainder(ang, 360) / 45;
73         return ang > 1 ? 0 : ang < -1 ? 0 : 1 - fabs(ang);
74 }
75
76 float GeomLerp(float a, float lerp, float b)
77 {
78         return a == 0 ? (lerp < 1 ? 0 : b)
79                 : b == 0 ? (lerp > 0 ? 0 : a)
80                 : a * pow(fabs(b / a), lerp);
81 }
82
83 #define unstick_offsets(X) \
84 /* 1 no nudge (just return the original if this test passes) */ \
85         X(' 0.000  0.000  0.000') \
86 /* 6 simple nudges */ \
87         X(' 0.000  0.000  0.125') X('0.000  0.000 -0.125') \
88         X('-0.125  0.000  0.000') X('0.125  0.000  0.000') \
89         X(' 0.000 -0.125  0.000') X('0.000  0.125  0.000') \
90 /* 4 diagonal flat nudges */ \
91         X('-0.125 -0.125  0.000') X('0.125 -0.125  0.000') \
92         X('-0.125  0.125  0.000') X('0.125  0.125  0.000') \
93 /* 8 diagonal upward nudges */ \
94         X('-0.125  0.000  0.125') X('0.125  0.000  0.125') \
95         X(' 0.000 -0.125  0.125') X('0.000  0.125  0.125') \
96         X('-0.125 -0.125  0.125') X('0.125 -0.125  0.125') \
97         X('-0.125  0.125  0.125') X('0.125  0.125  0.125') \
98 /* 8 diagonal downward nudges */ \
99         X('-0.125  0.000 -0.125') X('0.125  0.000 -0.125') \
100         X(' 0.000 -0.125 -0.125') X('0.000  0.125 -0.125') \
101         X('-0.125 -0.125 -0.125') X('0.125 -0.125 -0.125') \
102         X('-0.125  0.125 -0.125') X('0.125  0.125 -0.125') \
103 /**/
104
105 void PM_ClientMovement_Unstick(entity this)
106 {
107         #define X(unstick_offset) \
108         { \
109                 vector neworigin = unstick_offset + this.origin; \
110                 tracebox(neworigin, PL_CROUCH_MIN, PL_CROUCH_MAX, neworigin, MOVE_NORMAL, this); \
111                 if (!trace_startsolid) \
112                 { \
113                         setorigin(this, neworigin); \
114                         return; \
115                 } \
116         }
117         unstick_offsets(X);
118         #undef X
119 }
120
121 void PM_ClientMovement_UpdateStatus(entity this, bool ground)
122 {
123 #ifdef CSQC
124         // make sure player is not stuck
125         if(autocvar_cl_movement != 3)
126                 PM_ClientMovement_Unstick(this);
127
128         // set crouched
129         if (PHYS_INPUT_BUTTON_CROUCH(this))
130         {
131                 // wants to crouch, this always works
132                 if (!IS_DUCKED(this)) SET_DUCKED(this);
133         }
134         else
135         {
136                 // wants to stand, if currently crouching we need to check for a low ceiling first
137                 if (IS_DUCKED(this))
138                 {
139                         tracebox(this.origin, PL_MIN, PL_MAX, this.origin, MOVE_NORMAL, this);
140                         if (!trace_startsolid) UNSET_DUCKED(this);
141                 }
142         }
143
144         // set onground
145         vector origin1 = this.origin + '0 0 1';
146         vector origin2 = this.origin - '0 0 1';
147
148         if (ground && autocvar_cl_movement != 3)
149         {
150                 tracebox(origin1, this.mins, this.maxs, origin2, MOVE_NORMAL, this);
151                 if (trace_fraction < 1.0 && trace_plane_normal.z > 0.7)
152                 {
153                         SET_ONGROUND(this);
154
155                         // this code actually "predicts" an impact; so let's clip velocity first
156                         this.velocity -= this.velocity * trace_plane_normal * trace_plane_normal;
157                 }
158                 else
159                         UNSET_ONGROUND(this);
160         }
161
162         if(autocvar_cl_movement != 3)
163         {
164                 // set watertype/waterlevel
165                 origin1 = this.origin;
166                 origin1.z += this.mins_z + 1;
167                 this.waterlevel = WATERLEVEL_NONE;
168
169                 int thepoint = pointcontents(origin1);
170
171                 this.watertype = (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME);
172
173                 if (this.watertype)
174                 {
175                         this.waterlevel = WATERLEVEL_WETFEET;
176                         origin1.z = this.origin.z + (this.mins.z + this.maxs.z) * 0.5;
177                         thepoint = pointcontents(origin1);
178                         if (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME)
179                         {
180                                 this.waterlevel = WATERLEVEL_SWIMMING;
181                                 origin1.z = this.origin.z + 22;
182                                 thepoint = pointcontents(origin1);
183                                 if (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME)
184                                         this.waterlevel = WATERLEVEL_SUBMERGED;
185                         }
186                 }
187         }
188
189         if (IS_ONGROUND(this) || this.velocity.z <= 0 || pmove_waterjumptime <= 0)
190                 pmove_waterjumptime = 0;
191 #endif
192 }
193
194 void PM_ClientMovement_Move(entity this)
195 {
196 #ifdef CSQC
197
198         PM_ClientMovement_UpdateStatus(this, false);
199         if(autocvar_cl_movement == 3)
200                 return;
201
202         int bump;
203         float t;
204         float f;
205         vector neworigin;
206         vector currentorigin2;
207         vector neworigin2;
208         vector primalvelocity;
209
210         vector trace1_endpos = '0 0 0';
211         vector trace2_endpos = '0 0 0';
212         vector trace3_endpos = '0 0 0';
213         float trace1_fraction = 0;
214         float trace2_fraction = 0;
215         float trace3_fraction = 0;
216         vector trace1_plane_normal = '0 0 0';
217         vector trace2_plane_normal = '0 0 0';
218         vector trace3_plane_normal = '0 0 0';
219
220         primalvelocity = this.velocity;
221         for(bump = 0, t = PHYS_INPUT_TIMELENGTH; bump < 8 && (this.velocity * this.velocity) > 0; bump++)
222         {
223                 neworigin = this.origin + t * this.velocity;
224                 tracebox(this.origin, this.mins, this.maxs, neworigin, MOVE_NORMAL, this);
225                 trace1_endpos = trace_endpos;
226                 trace1_fraction = trace_fraction;
227                 trace1_plane_normal = trace_plane_normal;
228                 if(trace1_fraction < 1 && trace1_plane_normal_z == 0)
229                 {
230                         // may be a step or wall, try stepping up
231                         // first move forward at a higher level
232                         currentorigin2 = this.origin;
233                         currentorigin2_z += PHYS_STEPHEIGHT;
234                         neworigin2 = neworigin;
235                         neworigin2_z += PHYS_STEPHEIGHT;
236                         tracebox(currentorigin2, this.mins, this.maxs, neworigin2, MOVE_NORMAL, this);
237                         trace2_endpos = trace_endpos;
238                         trace2_fraction = trace_fraction;
239                         trace2_plane_normal = trace_plane_normal;
240                         if(!trace_startsolid)
241                         {
242                                 // then move down from there
243                                 currentorigin2 = trace2_endpos;
244                                 neworigin2 = trace2_endpos;
245                                 neworigin2_z = this.origin_z;
246                                 tracebox(currentorigin2, this.mins, this.maxs, neworigin2, MOVE_NORMAL, this);
247                                 trace3_endpos = trace_endpos;
248                                 trace3_fraction = trace_fraction;
249                                 trace3_plane_normal = trace_plane_normal;
250                                 // accept the new trace if it made some progress
251                                 if(fabs(trace3_endpos_x - trace1_endpos_x) >= 0.03125 || fabs(trace3_endpos_y - trace1_endpos_y) >= 0.03125)
252                                 {
253                                         trace1_endpos = trace2_endpos;
254                                         trace1_fraction = trace2_fraction;
255                                         trace1_plane_normal = trace2_plane_normal;
256                                         trace1_endpos = trace3_endpos;
257                                 }
258                         }
259                 }
260
261                 // check if it moved at all
262                 if(trace1_fraction >= 0.001)
263                         setorigin(this, trace1_endpos);
264
265                 // check if it moved all the way
266                 if(trace1_fraction == 1)
267                         break;
268
269                 // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
270                 // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
271                 // this got commented out in a change that supposedly makes the code match QW better
272                 // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block
273                 if(trace1_plane_normal_z > 0.7)
274                         SET_ONGROUND(this);
275
276                 t -= t * trace1_fraction;
277
278                 f = (this.velocity * trace1_plane_normal);
279                 this.velocity = this.velocity + -f * trace1_plane_normal;
280         }
281         if(PHYS_TELEPORT_TIME(this) > 0)
282                 this.velocity = primalvelocity;
283 #endif
284 }
285
286 void CPM_PM_Aircontrol(entity this, vector wishdir, float wishspeed)
287 {
288         float k = 32 * (2 * IsMoveInDirection(this.movement, 0) - 1);
289         if (k <= 0)
290                 return;
291
292         k *= bound(0, wishspeed / PHYS_MAXAIRSPEED(this), 1);
293
294         float zspeed = this.velocity_z;
295         this.velocity_z = 0;
296         float xyspeed = vlen(this.velocity);
297         this.velocity = normalize(this.velocity);
298
299         float dot = this.velocity * wishdir;
300
301         if (dot > 0) // we can't change direction while slowing down
302         {
303                 k *= pow(dot, PHYS_AIRCONTROL_POWER(this)) * PHYS_INPUT_TIMELENGTH;
304                 xyspeed = max(0, xyspeed - PHYS_AIRCONTROL_PENALTY(this) * sqrt(max(0, 1 - dot*dot)) * k/32);
305                 k *= PHYS_AIRCONTROL(this);
306                 this.velocity = normalize(this.velocity * xyspeed + wishdir * k);
307         }
308
309         this.velocity = this.velocity * xyspeed;
310         this.velocity_z = zspeed;
311 }
312
313 float AdjustAirAccelQW(float accelqw, float factor)
314 {
315         return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
316 }
317
318 // example config for alternate speed clamping:
319 //   sv_airaccel_qw 0.8
320 //   sv_airaccel_sideways_friction 0
321 //   prvm_globalset server speedclamp_mode 1
322 //     (or 2)
323 void PM_Accelerate(entity this, vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
324 {
325         float speedclamp = stretchfactor > 0 ? stretchfactor
326         : accelqw < 0 ? 1 // full clamping, no stretch
327         : -1; // no clamping
328
329         accelqw = fabs(accelqw);
330
331         if (GAMEPLAYFIX_Q2AIRACCELERATE)
332                 wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
333
334         float vel_straight = this.velocity * wishdir;
335         float vel_z = this.velocity_z;
336         vector vel_xy = vec2(this.velocity);
337         vector vel_perpend = vel_xy - vel_straight * wishdir;
338
339         float step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
340
341         float vel_xy_current  = vlen(vel_xy);
342         if (speedlimit)
343                 accelqw = AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
344         float vel_xy_forward =  vel_xy_current  + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
345         float vel_xy_backward = vel_xy_current  - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
346         vel_xy_backward = max(0, vel_xy_backward); // not that it REALLY occurs that this would cause wrong behaviour afterwards
347         vel_straight =          vel_straight    + bound(0, wishspeed - vel_straight,   step) * accelqw + step * (1 - accelqw);
348
349         if (sidefric < 0 && (vel_perpend*vel_perpend))
350                 // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
351         {
352                 float f = max(0, 1 + PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
353                 float themin = (vel_xy_backward * vel_xy_backward - vel_straight * vel_straight) / (vel_perpend * vel_perpend);
354                 // assume: themin > 1
355                 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
356                 // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
357                 // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
358                 // obviously, this cannot be
359                 if (themin <= 0)
360                         vel_perpend *= f;
361                 else
362                 {
363                         themin = sqrt(themin);
364                         vel_perpend *= max(themin, f);
365                 }
366         }
367         else
368                 vel_perpend *= max(0, 1 - PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
369
370         vel_xy = vel_straight * wishdir + vel_perpend;
371
372         if (speedclamp >= 0)
373         {
374                 float vel_xy_preclamp;
375                 vel_xy_preclamp = vlen(vel_xy);
376                 if (vel_xy_preclamp > 0) // prevent division by zero
377                 {
378                         vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
379                         if (vel_xy_current < vel_xy_preclamp)
380                                 vel_xy *= (vel_xy_current / vel_xy_preclamp);
381                 }
382         }
383
384         this.velocity = vel_xy + vel_z * '0 0 1';
385 }
386
387 void PM_AirAccelerate(entity this, vector wishdir, float wishspeed)
388 {
389         if (wishspeed == 0)
390                 return;
391
392         vector curvel = this.velocity;
393         curvel_z = 0;
394         float curspeed = vlen(curvel);
395
396         if (wishspeed > curspeed * 1.01)
397                 wishspeed = min(wishspeed, curspeed + PHYS_WARSOWBUNNY_AIRFORWARDACCEL(this) * PHYS_MAXSPEED(this) * PHYS_INPUT_TIMELENGTH);
398         else
399         {
400                 float f = max(0, (PHYS_WARSOWBUNNY_TOPSPEED(this) - curspeed) / (PHYS_WARSOWBUNNY_TOPSPEED(this) - PHYS_MAXSPEED(this)));
401                 wishspeed = max(curspeed, PHYS_MAXSPEED(this)) + PHYS_WARSOWBUNNY_ACCEL(this) * f * PHYS_MAXSPEED(this) * PHYS_INPUT_TIMELENGTH;
402         }
403         vector wishvel = wishdir * wishspeed;
404         vector acceldir = wishvel - curvel;
405         float addspeed = vlen(acceldir);
406         acceldir = normalize(acceldir);
407
408         float accelspeed = min(addspeed, PHYS_WARSOWBUNNY_TURNACCEL(this) * PHYS_MAXSPEED(this) * PHYS_INPUT_TIMELENGTH);
409
410         if (PHYS_WARSOWBUNNY_BACKTOSIDERATIO(this) < 1)
411         {
412                 vector curdir = normalize(curvel);
413                 float dot = acceldir * curdir;
414                 if (dot < 0)
415                         acceldir -= (1 - PHYS_WARSOWBUNNY_BACKTOSIDERATIO(this)) * dot * curdir;
416         }
417
418         this.velocity += accelspeed * acceldir;
419 }
420
421
422 /*
423 =============
424 PlayerJump
425
426 When you press the jump key
427 returns true if handled
428 =============
429 */
430 bool PlayerJump(entity this)
431 {
432         if (PHYS_FROZEN(this))
433                 return true; // no jumping in freezetag when frozen
434
435 #ifdef SVQC
436         if (this.player_blocked)
437                 return true; // no jumping while blocked
438 #endif
439
440         bool doublejump = false;
441         float mjumpheight = PHYS_JUMPVELOCITY(this);
442
443         if (MUTATOR_CALLHOOK(PlayerJump, this, doublejump, mjumpheight))
444                 return true;
445
446         doublejump = player_multijump;
447         mjumpheight = player_jumpheight;
448
449         if (this.waterlevel >= WATERLEVEL_SWIMMING)
450         {
451                 this.velocity_z = PHYS_MAXSPEED(this) * 0.7;
452                 return true;
453         }
454
455         if (!doublejump)
456                 if (!IS_ONGROUND(this))
457                         return IS_JUMP_HELD(this);
458
459         bool track_jump = PHYS_CL_TRACK_CANJUMP(this);
460         if(PHYS_TRACK_CANJUMP(this))
461                 track_jump = true;
462
463         if (track_jump)
464                 if (IS_JUMP_HELD(this))
465                         return true;
466
467         // sv_jumpspeedcap_min/sv_jumpspeedcap_max act as baseline
468         // velocity bounds.  Final velocity is bound between (jumpheight *
469         // min + jumpheight) and (jumpheight * max + jumpheight);
470
471         if(PHYS_JUMPSPEEDCAP_MIN != "")
472         {
473                 float minjumpspeed = mjumpheight * stof(PHYS_JUMPSPEEDCAP_MIN);
474
475                 if (this.velocity_z < minjumpspeed)
476                         mjumpheight += minjumpspeed - this.velocity_z;
477         }
478
479         if(PHYS_JUMPSPEEDCAP_MAX != "")
480         {
481                 // don't do jump speedcaps on ramps to preserve old xonotic ramjump style
482                 tracebox(this.origin + '0 0 0.01', this.mins, this.maxs, this.origin - '0 0 0.01', MOVE_NORMAL, this);
483
484                 if (!(trace_fraction < 1 && trace_plane_normal_z < 0.98 && PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS))
485                 {
486                         float maxjumpspeed = mjumpheight * stof(PHYS_JUMPSPEEDCAP_MAX);
487
488                         if (this.velocity_z > maxjumpspeed)
489                                 mjumpheight -= this.velocity_z - maxjumpspeed;
490                 }
491         }
492
493         if (!WAS_ONGROUND(this))
494         {
495 #ifdef SVQC
496                 if(autocvar_speedmeter)
497                         LOG_TRACE(strcat("landing velocity: ", vtos(this.velocity), " (abs: ", ftos(vlen(this.velocity)), ")\n"));
498 #endif
499                 if(this.lastground < time - 0.3)
500                 {
501                         this.velocity_x *= (1 - PHYS_FRICTION_ONLAND);
502                         this.velocity_y *= (1 - PHYS_FRICTION_ONLAND);
503                 }
504 #ifdef SVQC
505                 if(this.jumppadcount > 1)
506                         LOG_TRACE(strcat(ftos(this.jumppadcount), "x jumppad combo\n"));
507                 this.jumppadcount = 0;
508 #endif
509         }
510
511         this.velocity_z += mjumpheight;
512
513         UNSET_ONGROUND(this);
514         SET_JUMP_HELD(this);
515
516 #ifdef SVQC
517
518         this.oldvelocity_z = this.velocity_z;
519
520         animdecide_setaction(this, ANIMACTION_JUMP, true);
521
522         if (autocvar_g_jump_grunt)
523                 PlayerSound(this, playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
524 #endif
525         return true;
526 }
527
528 void CheckWaterJump(entity this)
529 {
530 // check for a jump-out-of-water
531         makevectors(this.v_angle);
532         vector start = this.origin;
533         start_z += 8;
534         v_forward_z = 0;
535         normalize(v_forward);
536         vector end = start + v_forward*24;
537         traceline (start, end, true, this);
538         if (trace_fraction < 1)
539         {       // solid at waist
540                 start_z = start_z + this.maxs_z - 8;
541                 end = start + v_forward*24;
542                 this.movedir = trace_plane_normal * -50;
543                 traceline(start, end, true, this);
544                 if (trace_fraction == 1)
545                 {       // open at eye level
546                         this.velocity_z = 225;
547                         this.flags |= FL_WATERJUMP;
548                         SET_JUMP_HELD(this);
549                 #ifdef SVQC
550                         PHYS_TELEPORT_TIME(this) = time + 2;    // safety net
551                 #elif defined(CSQC)
552                         pmove_waterjumptime = time + 2;
553                 #endif
554                 }
555         }
556 }
557
558
559 #ifdef SVQC
560         #define JETPACK_JUMP(s) s.cvar_cl_jetpack_jump
561 #elif defined(CSQC)
562         float autocvar_cl_jetpack_jump;
563         #define JETPACK_JUMP(s) autocvar_cl_jetpack_jump
564 #endif
565 .float jetpack_stopped;
566 void CheckPlayerJump(entity this)
567 {
568 #ifdef SVQC
569         float was_flying = ITEMS_STAT(this) & IT_USING_JETPACK;
570 #endif
571         if (JETPACK_JUMP(this) < 2)
572                 ITEMS_STAT(this) &= ~IT_USING_JETPACK;
573
574         if(PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_JETPACK(this))
575         {
576                 float air_jump = !PlayerJump(this) || player_multijump; // PlayerJump() has important side effects
577                 float activate = JETPACK_JUMP(this) && air_jump && PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_JETPACK(this);
578                 float has_fuel = !PHYS_JETPACK_FUEL || PHYS_AMMO_FUEL(this) || ITEMS_STAT(this) & IT_UNLIMITED_WEAPON_AMMO;
579
580                 if (!(ITEMS_STAT(this) & ITEM_Jetpack.m_itemid)) { }
581                 else if (this.jetpack_stopped) { }
582                 else if (!has_fuel)
583                 {
584 #ifdef SVQC
585                         if (was_flying) // TODO: ran out of fuel message
586                                 Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_JETPACK_NOFUEL);
587                         else if (activate)
588                                 Send_Notification(NOTIF_ONE, this, MSG_INFO, INFO_JETPACK_NOFUEL);
589 #endif
590                         this.jetpack_stopped = true;
591                         ITEMS_STAT(this) &= ~IT_USING_JETPACK;
592                 }
593                 else if (activate && !PHYS_FROZEN(this))
594                         ITEMS_STAT(this) |= IT_USING_JETPACK;
595         }
596         else
597         {
598                 this.jetpack_stopped = false;
599                 ITEMS_STAT(this) &= ~IT_USING_JETPACK;
600         }
601         if (!PHYS_INPUT_BUTTON_JUMP(this))
602                 UNSET_JUMP_HELD(this);
603
604         if (this.waterlevel == WATERLEVEL_SWIMMING)
605                 CheckWaterJump(this);
606 }
607
608 float racecar_angle(float forward, float down)
609 {
610         if (forward < 0)
611         {
612                 forward = -forward;
613                 down = -down;
614         }
615
616         float ret = vectoyaw('0 1 0' * down + '1 0 0' * forward);
617
618         float angle_mult = forward / (800 + forward);
619
620         if (ret > 180)
621                 return ret * angle_mult + 360 * (1 - angle_mult);
622         else
623                 return ret * angle_mult;
624 }
625
626 string specialcommand = "xwxwxsxsxaxdxaxdx1x ";
627 .float specialcommand_pos;
628 void SpecialCommand()
629 {
630 #ifdef SVQC
631         if (!CheatImpulse(99))
632                 LOG_INFO("A hollow voice says \"Plugh\".\n");
633 #endif
634 }
635
636 float PM_check_specialcommand(entity this, float buttons)
637 {
638 #ifdef SVQC
639         string c;
640         if (!buttons)
641                 c = "x";
642         else if (buttons == 1)
643                 c = "1";
644         else if (buttons == 2)
645                 c = " ";
646         else if (buttons == 128)
647                 c = "s";
648         else if (buttons == 256)
649                 c = "w";
650         else if (buttons == 512)
651                 c = "a";
652         else if (buttons == 1024)
653                 c = "d";
654         else
655                 c = "?";
656
657         if (c == substring(specialcommand, this.specialcommand_pos, 1))
658         {
659                 this.specialcommand_pos += 1;
660                 if (this.specialcommand_pos >= strlen(specialcommand))
661                 {
662                         this.specialcommand_pos = 0;
663                         SpecialCommand();
664                         return true;
665                 }
666         }
667         else if (this.specialcommand_pos && (c != substring(specialcommand, this.specialcommand_pos - 1, 1)))
668                 this.specialcommand_pos = 0;
669 #endif
670         return false;
671 }
672
673 void PM_check_nickspam(entity this)
674 {
675 #ifdef SVQC
676         if (time >= this.nickspamtime)
677                 return;
678         if (this.nickspamcount >= autocvar_g_nick_flood_penalty_yellow)
679         {
680                 // slight annoyance for nick change scripts
681                 this.movement = -1 * this.movement;
682                 this.BUTTON_ATCK = this.BUTTON_JUMP = this.BUTTON_ATCK2 = this.BUTTON_ZOOM = this.BUTTON_CROUCH = this.BUTTON_HOOK = this.BUTTON_USE = 0;
683
684                 if (this.nickspamcount >= autocvar_g_nick_flood_penalty_red) // if you are persistent and the slight annoyance above does not stop you, I'll show you!
685                 {
686                         this.v_angle_x = random() * 360;
687                         this.v_angle_y = random() * 360;
688                         // at least I'm not forcing retardedview by also assigning to angles_z
689                         this.fixangle = true;
690                 }
691         }
692 #endif
693 }
694
695 void PM_check_punch(entity this)
696 {
697 #ifdef SVQC
698         if (this.punchangle != '0 0 0')
699         {
700                 float f = vlen(this.punchangle) - 10 * PHYS_INPUT_TIMELENGTH;
701                 if (f > 0)
702                         this.punchangle = normalize(this.punchangle) * f;
703                 else
704                         this.punchangle = '0 0 0';
705         }
706
707         if (this.punchvector != '0 0 0')
708         {
709                 float f = vlen(this.punchvector) - 30 * PHYS_INPUT_TIMELENGTH;
710                 if (f > 0)
711                         this.punchvector = normalize(this.punchvector) * f;
712                 else
713                         this.punchvector = '0 0 0';
714         }
715 #endif
716 }
717
718 // predict frozen movement, as frozen players CAN move in some cases
719 void PM_check_frozen(entity this)
720 {
721         if (!PHYS_FROZEN(this))
722                 return;
723         if (PHYS_DODGING_FROZEN
724 #ifdef SVQC
725         && IS_REAL_CLIENT(this)
726 #endif
727         )
728         {
729                 this.movement_x = bound(-5, this.movement.x, 5);
730                 this.movement_y = bound(-5, this.movement.y, 5);
731                 this.movement_z = bound(-5, this.movement.z, 5);
732         }
733         else
734                 this.movement = '0 0 0';
735
736         vector midpoint = ((this.absmin + this.absmax) * 0.5);
737         if (pointcontents(midpoint) == CONTENT_WATER)
738         {
739                 this.velocity = this.velocity * 0.5;
740
741                 if (pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
742                         this.velocity_z = 200;
743         }
744 }
745
746 void PM_check_hitground(entity this)
747 {
748 #ifdef SVQC
749         if (!this.wasFlying) return;
750     this.wasFlying = false;
751     if (this.waterlevel >= WATERLEVEL_SWIMMING) return;
752     if (time < this.ladder_time) return;
753     if (this.hook) return;
754     this.nextstep = time + 0.3 + random() * 0.1;
755     trace_dphitq3surfaceflags = 0;
756     tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
757     if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS) return;
758     entity gs = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
759         ? GS_FALL_METAL
760         : GS_FALL;
761     GlobalSound(this, gs, CH_PLAYER, VOICETYPE_PLAYERSOUND);
762 #endif
763 }
764
765 void PM_Footsteps(entity this)
766 {
767 #ifdef SVQC
768         if (!g_footsteps) return;
769         if (IS_DUCKED(this)) return;
770         if (time >= this.lastground + 0.2) return;
771         if (vdist(this.velocity, <=, autocvar_sv_maxspeed * 0.6)) return;
772         if ((time > this.nextstep) || (time < (this.nextstep - 10.0)))
773         {
774                 this.nextstep = time + 0.3 + random() * 0.1;
775                 trace_dphitq3surfaceflags = 0;
776                 tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
777                 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS) return;
778                 entity gs = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
779                         ? GS_STEP_METAL
780                         : GS_STEP;
781                 GlobalSound(this, gs, CH_PLAYER, VOICETYPE_PLAYERSOUND);
782         }
783 #endif
784 }
785
786 void PM_check_blocked(entity this)
787 {
788 #ifdef SVQC
789         if (!this.player_blocked)
790                 return;
791         this.movement = '0 0 0';
792         this.disableclientprediction = 1;
793 #endif
794 }
795
796 void PM_fly(entity this, float maxspd_mod)
797 {
798         // noclipping or flying
799         UNSET_ONGROUND(this);
800
801         this.velocity = this.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this));
802         makevectors(this.v_angle);
803         //wishvel = v_forward * this.movement.x + v_right * this.movement.y + v_up * this.movement.z;
804         vector wishvel = v_forward * this.movement.x
805                                         + v_right * this.movement.y
806                                         + '0 0 1' * this.movement.z;
807         // acceleration
808         vector wishdir = normalize(wishvel);
809         float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(this) * maxspd_mod);
810 #ifdef SVQC
811         if(time >= PHYS_TELEPORT_TIME(this))
812 #endif
813                 PM_Accelerate(this, wishdir, wishspeed, wishspeed, PHYS_ACCELERATE(this) * maxspd_mod, 1, 0, 0, 0);
814         PM_ClientMovement_Move(this);
815 }
816
817 void PM_swim(entity this, float maxspd_mod)
818 {
819         // swimming
820         UNSET_ONGROUND(this);
821
822         float jump = PHYS_INPUT_BUTTON_JUMP(this);
823         // water jump only in certain situations
824         // this mimics quakeworld code
825         if (jump && this.waterlevel == WATERLEVEL_SWIMMING && this.velocity_z >= -180)
826         {
827                 vector yawangles = '0 1 0' * this.v_angle.y;
828                 makevectors(yawangles);
829                 vector forward = v_forward;
830                 vector spot = this.origin + 24 * forward;
831                 spot_z += 8;
832                 traceline(spot, spot, MOVE_NOMONSTERS, this);
833                 if (trace_startsolid)
834                 {
835                         spot_z += 24;
836                         traceline(spot, spot, MOVE_NOMONSTERS, this);
837                         if (!trace_startsolid)
838                         {
839                                 this.velocity = forward * 50;
840                                 this.velocity_z = 310;
841                         #ifdef CSQC
842                                 pmove_waterjumptime = 2;
843                         #endif
844                                 UNSET_ONGROUND(this);
845                                 SET_JUMP_HELD(this);
846                         }
847                 }
848         }
849         makevectors(this.v_angle);
850         //wishvel = v_forward * this.movement.x + v_right * this.movement.y + v_up * this.movement.z;
851         vector wishvel = v_forward * this.movement.x
852                                         + v_right * this.movement.y
853                                         + '0 0 1' * this.movement.z;
854         if (wishvel == '0 0 0')
855                 wishvel = '0 0 -60'; // drift towards bottom
856
857         vector wishdir = normalize(wishvel);
858         float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(this) * maxspd_mod) * 0.7;
859
860         if (IS_DUCKED(this))
861         wishspeed *= 0.5;
862
863 //      if (pmove_waterjumptime <= 0) // TODO: use
864     {
865                 // water friction
866                 float f = 1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this);
867                 f = min(max(0, f), 1);
868                 this.velocity *= f;
869
870                 f = wishspeed - this.velocity * wishdir;
871                 if (f > 0)
872                 {
873                         float accelspeed = min(PHYS_ACCELERATE(this) * PHYS_INPUT_TIMELENGTH * wishspeed, f);
874                         this.velocity += accelspeed * wishdir;
875                 }
876
877                 // holding jump button swims upward slowly
878                 if (jump)
879                 {
880 #if 0
881                         if (this.watertype & CONTENT_LAVA)
882                                 this.velocity_z =  50;
883                         else if (this.watertype & CONTENT_SLIME)
884                                 this.velocity_z =  80;
885                         else
886                         {
887                                 if (IS_NEXUIZ_DERIVED(gamemode))
888 #endif
889                                         this.velocity_z = 200;
890 #if 0
891                                 else
892                                         this.velocity_z = 100;
893                         }
894 #endif
895                 }
896         }
897         // water acceleration
898         PM_Accelerate(this, wishdir, wishspeed, wishspeed, PHYS_ACCELERATE(this) * maxspd_mod, 1, 0, 0, 0);
899         PM_ClientMovement_Move(this);
900 }
901
902 void PM_ladder(entity this, float maxspd_mod)
903 {
904         // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
905         UNSET_ONGROUND(this);
906
907         float g;
908         g = PHYS_GRAVITY(this) * PHYS_INPUT_TIMELENGTH;
909         if (PHYS_ENTGRAVITY(this))
910                 g *= PHYS_ENTGRAVITY(this);
911         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
912         {
913                 g *= 0.5;
914                 this.velocity_z += g;
915         }
916
917         this.velocity = this.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this));
918         makevectors(this.v_angle);
919         //wishvel = v_forward * this.movement.x + v_right * this.movement.y + v_up * this.movement.z;
920         vector wishvel = v_forward * this.movement_x
921                                         + v_right * this.movement_y
922                                         + '0 0 1' * this.movement_z;
923         this.velocity_z += g;
924         if (this.ladder_entity.classname == "func_water")
925         {
926                 float f = vlen(wishvel);
927                 if (f > this.ladder_entity.speed)
928                         wishvel *= (this.ladder_entity.speed / f);
929
930                 this.watertype = this.ladder_entity.skin;
931                 f = this.ladder_entity.origin_z + this.ladder_entity.maxs_z;
932                 if ((this.origin_z + this.view_ofs_z) < f)
933                         this.waterlevel = WATERLEVEL_SUBMERGED;
934                 else if ((this.origin_z + (this.mins_z + this.maxs_z) * 0.5) < f)
935                         this.waterlevel = WATERLEVEL_SWIMMING;
936                 else if ((this.origin_z + this.mins_z + 1) < f)
937                         this.waterlevel = WATERLEVEL_WETFEET;
938                 else
939                 {
940                         this.waterlevel = WATERLEVEL_NONE;
941                         this.watertype = CONTENT_EMPTY;
942                 }
943         }
944         // acceleration
945         vector wishdir = normalize(wishvel);
946         float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(this) * maxspd_mod);
947         if(time >= PHYS_TELEPORT_TIME(this))
948                 // water acceleration
949                 PM_Accelerate(this, wishdir, wishspeed, wishspeed, PHYS_ACCELERATE(this)*maxspd_mod, 1, 0, 0, 0);
950         PM_ClientMovement_Move(this);
951 }
952
953 void PM_jetpack(entity this, float maxspd_mod)
954 {
955         //makevectors(this.v_angle.y * '0 1 0');
956         makevectors(this.v_angle);
957         vector wishvel = v_forward * this.movement_x
958                                         + v_right * this.movement_y;
959         // add remaining speed as Z component
960         float maxairspd = PHYS_MAXAIRSPEED(this) * max(1, maxspd_mod);
961         // fix speedhacks :P
962         wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxairspd);
963         // add the unused velocity as up component
964         wishvel_z = 0;
965
966         // if (this.BUTTON_JUMP)
967                 wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
968
969         // it is now normalized, so...
970         float a_side = PHYS_JETPACK_ACCEL_SIDE;
971         float a_up = PHYS_JETPACK_ACCEL_UP;
972         float a_add = PHYS_JETPACK_ANTIGRAVITY * PHYS_GRAVITY(this);
973
974         wishvel_x *= a_side;
975         wishvel_y *= a_side;
976         wishvel_z *= a_up;
977         wishvel_z += a_add;
978
979         float best = 0;
980         //////////////////////////////////////////////////////////////////////////////////////
981         // finding the maximum over all vectors of above form
982         // with wishvel having an absolute value of 1
983         //////////////////////////////////////////////////////////////////////////////////////
984         // we're finding the maximum over
985         //   f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
986         // for z in the range from -1 to 1
987         //////////////////////////////////////////////////////////////////////////////////////
988         // maximum is EITHER attained at the single extreme point:
989         float a_diff = a_side * a_side - a_up * a_up;
990         float f;
991         if (a_diff != 0)
992         {
993                 f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
994                 if (f > -1 && f < 1) // can it be attained?
995                 {
996                         best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
997                         //print("middle\n");
998                 }
999         }
1000         // OR attained at z = 1:
1001         f = (a_up + a_add) * (a_up + a_add);
1002         if (f > best)
1003         {
1004                 best = f;
1005                 //print("top\n");
1006         }
1007         // OR attained at z = -1:
1008         f = (a_up - a_add) * (a_up - a_add);
1009         if (f > best)
1010         {
1011                 best = f;
1012                 //print("bottom\n");
1013         }
1014         best = sqrt(best);
1015         //////////////////////////////////////////////////////////////////////////////////////
1016
1017         //print("best possible acceleration: ", ftos(best), "\n");
1018
1019         float fxy, fz;
1020         fxy = bound(0, 1 - (this.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / PHYS_JETPACK_MAXSPEED_SIDE, 1);
1021         if (wishvel_z - PHYS_GRAVITY(this) > 0)
1022                 fz = bound(0, 1 - this.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1023         else
1024                 fz = bound(0, 1 + this.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1025
1026         float fvel;
1027         fvel = vlen(wishvel);
1028         wishvel_x *= fxy;
1029         wishvel_y *= fxy;
1030         wishvel_z = (wishvel_z - PHYS_GRAVITY(this)) * fz + PHYS_GRAVITY(this);
1031
1032         fvel = min(1, vlen(wishvel) / best);
1033         if (PHYS_JETPACK_FUEL && !(ITEMS_STAT(this) & IT_UNLIMITED_WEAPON_AMMO))
1034                 f = min(1, PHYS_AMMO_FUEL(this) / (PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel));
1035         else
1036                 f = 1;
1037
1038         //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
1039
1040         if (f > 0 && wishvel != '0 0 0')
1041         {
1042                 this.velocity = this.velocity + wishvel * f * PHYS_INPUT_TIMELENGTH;
1043                 UNSET_ONGROUND(this);
1044
1045 #ifdef SVQC
1046                 if (!(ITEMS_STAT(this) & IT_UNLIMITED_WEAPON_AMMO))
1047                         this.ammo_fuel -= PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel * f;
1048
1049                 ITEMS_STAT(this) |= IT_USING_JETPACK;
1050
1051                 // jetpack also inhibits health regeneration, but only for 1 second
1052                 this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
1053 #endif
1054         }
1055
1056 #ifdef CSQC
1057         float g = PHYS_GRAVITY(this) * PHYS_ENTGRAVITY(this) * PHYS_INPUT_TIMELENGTH;
1058         if(autocvar_cl_movement != 3)
1059         {
1060                 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1061                         this.velocity_z -= g * 0.5;
1062                 else
1063                         this.velocity_z -= g;
1064         }
1065         PM_ClientMovement_Move(this);
1066         if(autocvar_cl_movement != 3)
1067         {
1068                 if (!IS_ONGROUND(this) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1069                         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1070                                 this.velocity_z -= g * 0.5;
1071         }
1072 #endif
1073 }
1074
1075 void PM_walk(entity this, float maxspd_mod)
1076 {
1077         if (!WAS_ONGROUND(this))
1078         {
1079 #ifdef SVQC
1080                 if (autocvar_speedmeter)
1081                         LOG_TRACE(strcat("landing velocity: ", vtos(this.velocity), " (abs: ", ftos(vlen(this.velocity)), ")\n"));
1082 #endif
1083                 if (this.lastground < time - 0.3)
1084                         this.velocity *= (1 - PHYS_FRICTION_ONLAND);
1085 #ifdef SVQC
1086                 if (this.jumppadcount > 1)
1087                         LOG_TRACE(strcat(ftos(this.jumppadcount), "x jumppad combo\n"));
1088                 this.jumppadcount = 0;
1089 #endif
1090         }
1091
1092         // walking
1093         makevectors(this.v_angle.y * '0 1 0');
1094         const vector wishvel = v_forward * this.movement.x
1095                                                 + v_right * this.movement.y;
1096         // acceleration
1097         const vector wishdir = normalize(wishvel);
1098         float wishspeed = vlen(wishvel);
1099         wishspeed = min(wishspeed, PHYS_MAXSPEED(this) * maxspd_mod);
1100         if (IS_DUCKED(this)) wishspeed *= 0.5;
1101
1102         // apply edge friction
1103         const float f2 = vlen2(vec2(this.velocity));
1104         if (f2 > 0)
1105         {
1106                 trace_dphitq3surfaceflags = 0;
1107                 tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 1', MOVE_NOMONSTERS, this);
1108                 // TODO: apply edge friction
1109                 // apply ground friction
1110                 const int realfriction = (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)
1111                         ? PHYS_FRICTION_SLICK
1112                         : PHYS_FRICTION(this);
1113
1114                 float f = sqrt(f2);
1115                 f = 1 - PHYS_INPUT_TIMELENGTH * realfriction * ((f < PHYS_STOPSPEED(this)) ? (PHYS_STOPSPEED(this) / f) : 1);
1116                 f = max(0, f);
1117                 this.velocity *= f;
1118                 /*
1119                    Mathematical analysis time!
1120
1121                    Our goal is to invert this mess.
1122
1123                    For the two cases we get:
1124                         v = v0 * (1 - PHYS_INPUT_TIMELENGTH * (PHYS_STOPSPEED(this) / v0) * PHYS_FRICTION(this))
1125                           = v0 - PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED(this) * PHYS_FRICTION(this)
1126                         v0 = v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED(this) * PHYS_FRICTION(this)
1127                    and
1128                         v = v0 * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this))
1129                         v0 = v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this))
1130
1131                    These cases would be chosen ONLY if:
1132                         v0 < PHYS_STOPSPEED(this)
1133                         v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED(this) * PHYS_FRICTION(this) < PHYS_STOPSPEED(this)
1134                         v < PHYS_STOPSPEED(this) * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this))
1135                    and, respectively:
1136                         v0 >= PHYS_STOPSPEED(this)
1137                         v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this)) >= PHYS_STOPSPEED(this)
1138                         v >= PHYS_STOPSPEED(this) * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION(this))
1139                  */
1140         }
1141         const float addspeed = wishspeed - this.velocity * wishdir;
1142         if (addspeed > 0)
1143         {
1144                 const float accelspeed = min(PHYS_ACCELERATE(this) * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
1145                 this.velocity += accelspeed * wishdir;
1146         }
1147 #ifdef CSQC
1148         float g = PHYS_GRAVITY(this) * PHYS_ENTGRAVITY(this) * PHYS_INPUT_TIMELENGTH;
1149         if(autocvar_cl_movement != 3)
1150         {
1151                 if (!(GAMEPLAYFIX_NOGRAVITYONGROUND))
1152                         this.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
1153         }
1154         if (vdist(this.velocity, >, 0))
1155                 PM_ClientMovement_Move(this);
1156         if(autocvar_cl_movement != 3)
1157         {
1158                 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1159                         if (!IS_ONGROUND(this) || !GAMEPLAYFIX_NOGRAVITYONGROUND)
1160                                 this.velocity_z -= g * 0.5;
1161         }
1162 #endif
1163 }
1164
1165 void PM_air(entity this, float buttons_prev, float maxspd_mod)
1166 {
1167         makevectors(this.v_angle.y * '0 1 0');
1168         vector wishvel = v_forward * this.movement.x
1169                                         + v_right * this.movement.y;
1170         // acceleration
1171         vector wishdir = normalize(wishvel);
1172         float wishspeed = vlen(wishvel);
1173
1174 #ifdef SVQC
1175         if(time >= PHYS_TELEPORT_TIME(this))
1176 #elif defined(CSQC)
1177         if(pmove_waterjumptime <= 0)
1178 #endif
1179         {
1180                 float maxairspd = PHYS_MAXAIRSPEED(this) * min(maxspd_mod, 1);
1181
1182                 // apply air speed limit
1183                 float airaccelqw = PHYS_AIRACCEL_QW(this);
1184                 float wishspeed0 = wishspeed;
1185                 wishspeed = min(wishspeed, maxairspd);
1186                 if (IS_DUCKED(this))
1187                         wishspeed *= 0.5;
1188                 float airaccel = PHYS_AIRACCELERATE(this) * min(maxspd_mod, 1);
1189
1190                 float accelerating = (this.velocity * wishdir > 0);
1191                 float wishspeed2 = wishspeed;
1192
1193                 // CPM: air control
1194                 if (PHYS_AIRSTOPACCELERATE(this))
1195                 {
1196                         vector curdir = normalize(vec2(this.velocity));
1197                         airaccel += (PHYS_AIRSTOPACCELERATE(this)*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
1198                 }
1199                 // note that for straight forward jumping:
1200                 // step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
1201                 // accel  = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
1202                 // -->
1203                 // dv/dt = accel * maxspeed (when slow)
1204                 // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
1205                 // log dv/dt = logaccel + logmaxspeed (when slow)
1206                 // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
1207                 float strafity = IsMoveInDirection(this.movement, -90) + IsMoveInDirection(this.movement, +90); // if one is nonzero, other is always zero
1208                 if (PHYS_MAXAIRSTRAFESPEED(this))
1209                         wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED(this)*maxspd_mod, strafity, PHYS_MAXAIRSTRAFESPEED(this)*maxspd_mod));
1210                 if (PHYS_AIRSTRAFEACCELERATE(this))
1211                         airaccel = GeomLerp(airaccel, strafity, PHYS_AIRSTRAFEACCELERATE(this)*maxspd_mod);
1212                 if (PHYS_AIRSTRAFEACCEL_QW(this))
1213                         airaccelqw =
1214                 (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW(this) : PHYS_AIRACCEL_QW(this)) >= 0) ? +1 : -1)
1215                 *
1216                 (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW(this)), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW(this))));
1217                 // !CPM
1218
1219                 if (PHYS_WARSOWBUNNY_TURNACCEL(this) && accelerating && this.movement.y == 0 && this.movement.x != 0)
1220                         PM_AirAccelerate(this, wishdir, wishspeed2);
1221                 else
1222                         PM_Accelerate(this, wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR(this), PHYS_AIRACCEL_SIDEWAYS_FRICTION(this) / maxairspd, PHYS_AIRSPEEDLIMIT_NONQW(this));
1223
1224                 if (PHYS_AIRCONTROL(this))
1225                         CPM_PM_Aircontrol(this, wishdir, wishspeed2);
1226         }
1227 #ifdef CSQC
1228         float g = PHYS_GRAVITY(this) * PHYS_ENTGRAVITY(this) * PHYS_INPUT_TIMELENGTH;
1229         if(autocvar_cl_movement != 3)
1230         if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1231                 this.velocity_z -= g * 0.5;
1232         else
1233                 this.velocity_z -= g;
1234 #endif
1235         PM_ClientMovement_Move(this);
1236 #ifdef CSQC
1237         if(autocvar_cl_movement != 3)
1238         if (!IS_ONGROUND(this) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1239                 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1240                         this.velocity_z -= g * 0.5;
1241 #endif
1242 }
1243
1244 // used for calculating airshots
1245 bool IsFlying(entity this)
1246 {
1247         if(IS_ONGROUND(this))
1248                 return false;
1249         if(this.waterlevel >= WATERLEVEL_SWIMMING)
1250                 return false;
1251         traceline(this.origin, this.origin - '0 0 48', MOVE_NORMAL, this);
1252         if(trace_fraction < 1)
1253                 return false;
1254         return true;
1255 }
1256
1257 void PM_Main(entity this)
1258 {
1259         int buttons = PHYS_INPUT_BUTTON_MASK(this);
1260 #ifdef CSQC
1261         this.items = getstati(STAT_ITEMS, 0, 24);
1262
1263         this.movement = PHYS_INPUT_MOVEVALUES(this);
1264
1265         vector oldv_angle = this.v_angle;
1266         vector oldangles = this.angles; // we need to save these, as they're abused by other code
1267         this.v_angle = PHYS_INPUT_ANGLES(this);
1268         this.angles = PHYS_WORLD_ANGLES(this);
1269
1270         this.team = myteam + 1; // is this correct?
1271         if (!(PHYS_INPUT_BUTTON_JUMP(this))) // !jump
1272                 UNSET_JUMP_HELD(this); // canjump = true
1273         pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH;
1274
1275         PM_ClientMovement_UpdateStatus(this, true);
1276 #endif
1277
1278
1279 #ifdef SVQC
1280         WarpZone_PlayerPhysics_FixVAngle();
1281 #endif
1282         float maxspeed_mod = 1;
1283         maxspeed_mod *= PHYS_HIGHSPEED;
1284
1285 #ifdef SVQC
1286         Physics_UpdateStats(this, maxspeed_mod);
1287
1288         if (this.PlayerPhysplug)
1289                 if (this.PlayerPhysplug())
1290                         return;
1291 #endif
1292
1293 #ifdef SVQC
1294         anticheat_physics(this);
1295 #endif
1296
1297         if (PM_check_specialcommand(this, buttons))
1298                 return;
1299 #ifdef SVQC
1300         if (sv_maxidle > 0)
1301         {
1302                 if (buttons != this.buttons_old || this.movement != this.movement_old || this.v_angle != this.v_angle_old)
1303                         this.parm_idlesince = time;
1304         }
1305 #endif
1306         int buttons_prev = this.buttons_old;
1307         this.buttons_old = buttons;
1308         this.movement_old = this.movement;
1309         this.v_angle_old = this.v_angle;
1310
1311         PM_check_nickspam(this);
1312
1313         PM_check_punch(this);
1314 #ifdef SVQC
1315         if (IS_BOT_CLIENT(this))
1316         {
1317                 if (playerdemo_read(this))
1318                         return;
1319                 WITH(entity, self, this, bot_think());
1320         }
1321 #endif
1322
1323 #ifdef SVQC
1324         if (IS_PLAYER(this))
1325         {
1326                 const bool allowed_to_move = (time >= game_starttime);
1327                 if (!allowed_to_move)
1328                 {
1329                         this.velocity = '0 0 0';
1330                         this.movetype = MOVETYPE_NONE;
1331                         this.disableclientprediction = 2;
1332                 }
1333                 else if (this.disableclientprediction == 2)
1334                 {
1335                         if (this.movetype == MOVETYPE_NONE)
1336                                 this.movetype = MOVETYPE_WALK;
1337                         this.disableclientprediction = 0;
1338                 }
1339         }
1340 #endif
1341
1342 #ifdef SVQC
1343         if (this.movetype == MOVETYPE_NONE)
1344                 return;
1345
1346         // when we get here, disableclientprediction cannot be 2
1347         this.disableclientprediction = 0;
1348 #endif
1349
1350         viewloc_PlayerPhysics(this);
1351
1352         PM_check_frozen(this);
1353
1354         PM_check_blocked(this);
1355
1356         maxspeed_mod = 1;
1357
1358         if (this.in_swamp)
1359                 maxspeed_mod *= this.swamp_slowdown; //cvar("g_balance_swamp_moverate");
1360
1361         // conveyors: first fix velocity
1362         if (this.conveyor.state)
1363                 this.velocity -= this.conveyor.movedir;
1364
1365         MUTATOR_CALLHOOK(PlayerPhysics, this);
1366
1367 #ifdef SVQC
1368         if (!IS_PLAYER(this))
1369         {
1370                 maxspeed_mod = autocvar_sv_spectator_speed_multiplier;
1371                 if (!this.spectatorspeed)
1372                         this.spectatorspeed = maxspeed_mod;
1373                 if (this.impulse && this.impulse <= 19 || (this.impulse >= 200 && this.impulse <= 209) || (this.impulse >= 220 && this.impulse <= 229))
1374                 {
1375                         if (this.lastclassname != "player")
1376                         {
1377                                 if (this.impulse == 10 || this.impulse == 15 || this.impulse == 18 || (this.impulse >= 200 && this.impulse <= 209))
1378                                         this.spectatorspeed = bound(1, this.spectatorspeed + 0.5, 5);
1379                                 else if (this.impulse == 11)
1380                                         this.spectatorspeed = maxspeed_mod;
1381                                 else if (this.impulse == 12 || this.impulse == 16  || this.impulse == 19 || (this.impulse >= 220 && this.impulse <= 229))
1382                                         this.spectatorspeed = bound(1, this.spectatorspeed - 0.5, 5);
1383                                 else if (this.impulse >= 1 && this.impulse <= 9)
1384                                         this.spectatorspeed = 1 + 0.5 * (this.impulse - 1);
1385                         } // otherwise just clear
1386                         this.impulse = 0;
1387                 }
1388                 maxspeed_mod = this.spectatorspeed;
1389         }
1390
1391         float spd = max(PHYS_MAXSPEED(this), PHYS_MAXAIRSPEED(this)) * maxspeed_mod;
1392         if(this.speed != spd)
1393         {
1394                 this.speed = spd;
1395                 string temps = ftos(spd);
1396                 stuffcmd(this, strcat("cl_forwardspeed ", temps, "\n"));
1397                 stuffcmd(this, strcat("cl_backspeed ", temps, "\n"));
1398                 stuffcmd(this, strcat("cl_sidespeed ", temps, "\n"));
1399                 stuffcmd(this, strcat("cl_upspeed ", temps, "\n"));
1400         }
1401
1402         if(this.jumpspeedcap_min != autocvar_sv_jumpspeedcap_min)
1403         {
1404                 this.jumpspeedcap_min = autocvar_sv_jumpspeedcap_min;
1405                 stuffcmd(this, sprintf("\ncl_jumpspeedcap_min \"%s\"\n", autocvar_sv_jumpspeedcap_min));
1406         }
1407         if(this.jumpspeedcap_max != autocvar_sv_jumpspeedcap_max)
1408         {
1409                 this.jumpspeedcap_max = autocvar_sv_jumpspeedcap_max;
1410                 stuffcmd(this, sprintf("\ncl_jumpspeedcap_max \"%s\"\n", autocvar_sv_jumpspeedcap_max));
1411         }
1412 #endif
1413
1414         if(PHYS_DEAD(this))
1415         {
1416                 // handle water here
1417                 vector midpoint = ((this.absmin + this.absmax) * 0.5);
1418                 if(pointcontents(midpoint) == CONTENT_WATER)
1419                 {
1420                         this.velocity = this.velocity * 0.5;
1421
1422                         // do we want this?
1423                         //if(pointcontents(midpoint + '0 0 2') == CONTENT_WATER)
1424                                 //{ this.velocity_z = 70; }
1425                 }
1426                 goto end;
1427         }
1428
1429 #ifdef SVQC
1430         if (!this.fixangle)
1431                 this.angles = '0 1 0' * this.v_angle.y;
1432 #endif
1433
1434         if (IS_PLAYER(this) && IS_ONGROUND(this))
1435         {
1436                 PM_check_hitground(this);
1437                 PM_Footsteps(this);
1438         }
1439
1440         if(IsFlying(this))
1441                 this.wasFlying = 1;
1442
1443         if (IS_PLAYER(this))
1444                 CheckPlayerJump(this);
1445
1446         if (this.flags & FL_WATERJUMP)
1447         {
1448                 this.velocity_x = this.movedir.x;
1449                 this.velocity_y = this.movedir.y;
1450                 if (time > PHYS_TELEPORT_TIME(this) || this.waterlevel == WATERLEVEL_NONE)
1451                 {
1452                         this.flags &= ~FL_WATERJUMP;
1453                         PHYS_TELEPORT_TIME(this) = 0;
1454                 }
1455         }
1456
1457         else if (MUTATOR_CALLHOOK(PM_Physics, this, maxspeed_mod))
1458                 { }
1459
1460 #ifdef SVQC
1461         else if (this.movetype == MOVETYPE_NOCLIP || this.movetype == MOVETYPE_FLY || this.movetype == MOVETYPE_FLY_WORLDONLY || MUTATOR_CALLHOOK(IsFlying, this))
1462 #elif defined(CSQC)
1463         else if (this.move_movetype == MOVETYPE_NOCLIP || this.move_movetype == MOVETYPE_FLY || this.move_movetype == MOVETYPE_FLY_WORLDONLY || MUTATOR_CALLHOOK(IsFlying, this))
1464 #endif
1465                 PM_fly(this, maxspeed_mod);
1466
1467         else if (this.waterlevel >= WATERLEVEL_SWIMMING)
1468                 PM_swim(this, maxspeed_mod);
1469
1470         else if (time < this.ladder_time)
1471                 PM_ladder(this, maxspeed_mod);
1472
1473         else if (ITEMS_STAT(this) & IT_USING_JETPACK)
1474                 PM_jetpack(this, maxspeed_mod);
1475
1476         else if (IS_ONGROUND(this))
1477                 PM_walk(this, maxspeed_mod);
1478
1479         else
1480                 PM_air(this, buttons_prev, maxspeed_mod);
1481
1482 :end
1483         if (IS_ONGROUND(this))
1484                 this.lastground = time;
1485
1486         // conveyors: then break velocity again
1487         if(this.conveyor.state)
1488                 this.velocity += this.conveyor.movedir;
1489
1490         this.lastflags = this.flags;
1491
1492         this.lastclassname = this.classname;
1493
1494 #ifdef CSQC
1495         this.v_angle = oldv_angle;
1496         this.angles = oldangles;
1497 #endif
1498 }
1499
1500 #if defined(SVQC)
1501 void SV_PlayerPhysics()
1502 #elif defined(CSQC)
1503 void CSQC_ClientMovement_PlayerMove_Frame(entity this)
1504 #endif
1505 {
1506 #ifdef SVQC
1507         SELFPARAM();
1508 #endif
1509         PM_Main(this);
1510 }