mostly fix prediction in minstagib speed powerup
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / cl_physics.qc
1 .float race_penalty;
2 .float restart_jump;
3
4 float sv_accelerate;
5 float sv_friction;
6 float sv_maxspeed;
7 float sv_airaccelerate;
8 float sv_maxairspeed;
9 float sv_stopspeed;
10 float sv_gravity;
11 float sv_airaccel_sideways_friction;
12 float sv_airaccel_qw;
13 float sv_airstopaccelerate;
14 float sv_airstrafeaccelerate;
15 float sv_maxairstrafespeed;
16 float sv_airstrafeaccel_qw;
17 float sv_aircontrol;
18 float sv_aircontrol_power;
19 float sv_aircontrol_penalty;
20 float sv_warsowbunny_airforwardaccel;
21 float sv_warsowbunny_accel;
22 float sv_warsowbunny_topspeed;
23 float sv_warsowbunny_turnaccel;
24 float sv_warsowbunny_backtosideratio;
25 float sv_airspeedlimit_nonqw;
26
27 .float ladder_time;
28 .entity ladder_entity;
29 .float gravity;
30 .float swamp_slowdown;
31 .float lastflags;
32 .float lastground;
33 .float wasFlying;
34 .float spectatorspeed;
35
36 .float multijump_count;
37 .float multijump_ready;
38 .float prevjumpbutton;
39
40 /*
41 =============
42 PlayerJump
43
44 When you press the jump key
45 =============
46 */
47 void PlayerJump (void)
48 {
49         float mjumpheight;
50         float doublejump;
51
52         doublejump = FALSE;
53         if (sv_doublejump)
54         {
55                 tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
56                 if (trace_fraction < 1 && trace_plane_normal_z > 0.7)
57                         doublejump = TRUE;
58         }
59
60         mjumpheight = cvar("sv_jumpvelocity");
61         if (self.waterlevel >= WATERLEVEL_SWIMMING)
62         {
63                 if (self.watertype == CONTENT_WATER)
64                         self.velocity_z = 200;
65                 else if (self.watertype == CONTENT_SLIME)
66                         self.velocity_z = 80;
67                 else
68                         self.velocity_z = 50;
69
70                 return;
71         }
72
73         if (cvar("g_multijump"))
74         {
75                 if (self.prevjumpbutton == FALSE && !(self.flags & FL_ONGROUND)) // jump button pressed this frame and we are in midair
76                         self.multijump_ready = TRUE;  // this is necessary to check that we released the jump button and pressed it again
77                 else
78                         self.multijump_ready = FALSE;
79         }
80
81         if(!doublejump && self.multijump_ready && self.multijump_count < cvar("g_multijump") && self.velocity_z > cvar("g_multijump_speed"))
82         {
83                 // doublejump = FALSE; // checked above in the if
84                 if (cvar("g_multijump") > 0)
85                 {
86                         if (cvar("g_multijump_add") == 0) // in this case we make the z velocity == jumpvelocity
87                         {
88                                 if (self.velocity_z < mjumpheight)
89                                 {
90                                         doublejump = TRUE;
91                                         self.velocity_z = 0;
92                                 }
93                         }
94                         else
95                                 doublejump = TRUE;
96
97                         if(doublejump)
98                         {
99                                 if(self.movement_x != 0 || self.movement_y != 0) // don't remove all speed if player isnt pressing any movement keys
100                                 {
101                                         float curspeed;
102                                         vector wishvel, wishdir;
103
104                                         curspeed = max(
105                                                 vlen(vec2(self.velocity)), // current xy speed
106                                                 vlen(vec2(antilag_takebackavgvelocity(self, max(self.lastteleporttime + sys_frametime, time - 0.25), time))) // average xy topspeed over the last 0.25 secs
107                                         );
108                                         makevectors(self.v_angle_y * '0 1 0');
109                                         wishvel = v_forward * self.movement_x + v_right * self.movement_y;
110                                         wishdir = normalize(wishvel);
111
112                                         self.velocity_x = wishdir_x * curspeed; // allow "dodging" at a multijump
113                                         self.velocity_y = wishdir_y * curspeed;
114                                         // keep velocity_z unchanged!
115                                 }
116                                 self.multijump_count += 1;
117                         }
118                 }
119                 self.multijump_ready = FALSE; // require releasing and pressing the jump button again for the next jump
120         }
121
122         if (!doublejump)
123                 if (!(self.flags & FL_ONGROUND))
124                         return;
125
126         if(!sv_pogostick)
127                 if (!(self.flags & FL_JUMPRELEASED))
128                         return;
129
130         if(self.health <= g_bloodloss)
131                 return;
132
133         if(g_runematch)
134         {
135                 if(self.runes & RUNE_SPEED)
136                 {
137                         if(self.runes & CURSE_SLOW)
138                                 mjumpheight = mjumpheight * cvar("g_balance_rune_speed_combo_jumpheight");
139                         else
140                                 mjumpheight = mjumpheight * cvar("g_balance_rune_speed_jumpheight");
141                 }
142                 else if(self.runes & CURSE_SLOW)
143                 {
144                         mjumpheight = mjumpheight * cvar("g_balance_curse_slow_jumpheight");
145                 }
146         }
147
148         // sv_jumpspeedcap_min/sv_jumpspeedcap_max act as baseline
149         // velocity bounds.  Final velocity is bound between (jumpheight *
150         // min + jumpheight) and (jumpheight * max + jumpheight);
151
152         if(cvar_string("sv_jumpspeedcap_min") != "")
153         {
154                 float minjumpspeed;
155
156                 minjumpspeed = mjumpheight * cvar("sv_jumpspeedcap_min");
157
158                 if (self.velocity_z < minjumpspeed)
159                         mjumpheight += minjumpspeed - self.velocity_z;
160         }
161
162         if(cvar_string("sv_jumpspeedcap_max") != "")
163         {
164                 // don't do jump speedcaps on ramps to preserve old xonotic ramjump style
165                 tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
166
167                 if(!(trace_fraction < 1 && trace_plane_normal_z < 0.98 && cvar("sv_jumpspeedcap_max_disable_on_ramps")))
168                 {
169                         float maxjumpspeed;
170
171                         maxjumpspeed = mjumpheight * cvar("sv_jumpspeedcap_max");
172
173                         if (self.velocity_z > maxjumpspeed)
174                                 mjumpheight -= self.velocity_z - maxjumpspeed;
175                 }
176         }
177
178         if(!(self.lastflags & FL_ONGROUND))
179         {
180                 if(cvar("speedmeter"))
181                         dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
182                 if(self.lastground < time - 0.3)
183                 {
184                         self.velocity_x *= (1 - cvar("sv_friction_on_land"));
185                         self.velocity_y *= (1 - cvar("sv_friction_on_land"));
186                 }
187                 if(self.jumppadcount > 1)
188                         dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
189                 self.jumppadcount = 0;
190         }
191
192         self.velocity_z = self.velocity_z + mjumpheight;
193         self.oldvelocity_z = self.velocity_z;
194
195         self.flags &~= FL_ONGROUND;
196         self.flags &~= FL_JUMPRELEASED;
197
198         if (self.crouch)
199                 setanim(self, self.anim_duckjump, FALSE, TRUE, TRUE);
200         else
201                 setanim(self, self.anim_jump, FALSE, TRUE, TRUE);
202
203         if(g_jump_grunt)
204                 PlayerSound(playersound_jump, CHAN_PLAYER, VOICETYPE_PLAYERSOUND);
205
206         self.restart_jump = -1; // restart jump anim next time
207         // value -1 is used to not use the teleport bit (workaround for tiny hitch when re-jumping)
208 }
209
210 void CheckWaterJump()
211 {
212         local vector start, end;
213
214 // check for a jump-out-of-water
215         makevectors (self.angles);
216         start = self.origin;
217         start_z = start_z + 8;
218         v_forward_z = 0;
219         normalize(v_forward);
220         end = start + v_forward*24;
221         traceline (start, end, TRUE, self);
222         if (trace_fraction < 1)
223         {       // solid at waist
224                 start_z = start_z + self.maxs_z - 8;
225                 end = start + v_forward*24;
226                 self.movedir = trace_plane_normal * -50;
227                 traceline (start, end, TRUE, self);
228                 if (trace_fraction == 1)
229                 {       // open at eye level
230                         self.flags |= FL_WATERJUMP;
231                         self.velocity_z = 225;
232                         self.flags &~= FL_JUMPRELEASED;
233                         self.teleport_time = time + 2;  // safety net
234                         return;
235                 }
236         }
237 };
238
239 float racecar_angle(float forward, float down)
240 {
241         float ret, angle_mult;
242
243         if(forward < 0)
244         {
245                 forward = -forward;
246                 down = -down;
247         }
248
249         ret = vectoyaw('0 1 0' * down + '1 0 0' * forward);
250
251         angle_mult = forward / (800 + forward);
252
253         if(ret > 180)
254                 return ret * angle_mult + 360 * (1 - angle_mult);
255         else
256                 return ret * angle_mult;
257 }
258
259 void RaceCarPhysics()
260 {
261         // using this move type for "big rigs"
262         // the engine does not push the entity!
263
264         float accel, steer, f, myspeed, steerfactor;
265         vector angles_save, rigvel;
266
267         angles_save = self.angles;
268         accel = bound(-1, self.movement_x / sv_maxspeed, 1);
269         steer = bound(-1, self.movement_y / sv_maxspeed, 1);
270
271         if(g_bugrigs_reverse_speeding)
272         {
273                 if(accel < 0)
274                 {
275                         // back accel is DIGITAL
276                         // to prevent speedhack
277                         if(accel < -0.5)
278                                 accel = -1;
279                         else
280                                 accel = 0;
281                 }
282         }
283
284         self.angles_x = 0;
285         self.angles_z = 0;
286         makevectors(self.angles); // new forward direction!
287
288         if(self.flags & FL_ONGROUND || g_bugrigs_air_steering)
289         {
290                 float upspeed, accelfactor;
291
292                 myspeed = self.velocity * v_forward;
293                 upspeed = self.velocity * v_up;
294
295                 // responsiveness factor for steering and acceleration
296                 f = 1 / (1 + pow(max(-myspeed, myspeed) / g_bugrigs_speed_ref, g_bugrigs_speed_pow));
297                 //MAXIMA: f(v) := 1 / (1 + (v / g_bugrigs_speed_ref) ^ g_bugrigs_speed_pow);
298
299                 if(myspeed < 0 && g_bugrigs_reverse_spinning)
300                         steerfactor = -myspeed * g_bugrigs_steer;
301                 else
302                         steerfactor = -myspeed * f * g_bugrigs_steer;
303
304                 if(myspeed < 0 && g_bugrigs_reverse_speeding)
305                         accelfactor = g_bugrigs_accel;
306                 else
307                         accelfactor = f * g_bugrigs_accel;
308                 //MAXIMA: accel(v) := f(v) * g_bugrigs_accel;
309
310                 if(accel < 0)
311                 {
312                         if(myspeed > 0)
313                         {
314                                 myspeed = max(0, myspeed - frametime * (g_bugrigs_friction_floor - g_bugrigs_friction_brake * accel));
315                         }
316                         else
317                         {
318                                 if(!g_bugrigs_reverse_speeding)
319                                         myspeed = min(0, myspeed + frametime * g_bugrigs_friction_floor);
320                         }
321                 }
322                 else
323                 {
324                         if(myspeed >= 0)
325                         {
326                                 myspeed = max(0, myspeed - frametime * g_bugrigs_friction_floor);
327                         }
328                         else
329                         {
330                                 if(g_bugrigs_reverse_stopping)
331                                         myspeed = 0;
332                                 else
333                                         myspeed = min(0, myspeed + frametime * (g_bugrigs_friction_floor + g_bugrigs_friction_brake * accel));
334                         }
335                 }
336                 // terminal velocity = velocity at which 50 == accelfactor, that is, 1549 units/sec
337                 //MAXIMA: friction(v) := g_bugrigs_friction_floor;
338
339                 self.angles_y += steer * frametime * steerfactor; // apply steering
340                 makevectors(self.angles); // new forward direction!
341
342                 myspeed += accel * accelfactor * frametime;
343
344                 rigvel = myspeed * v_forward + '0 0 1' * upspeed;
345         }
346         else
347         {
348                 myspeed = vlen(self.velocity);
349
350                 // responsiveness factor for steering and acceleration
351                 f = 1 / (1 + pow(max(0, myspeed / g_bugrigs_speed_ref), g_bugrigs_speed_pow));
352                 steerfactor = -myspeed * f;
353                 self.angles_y += steer * frametime * steerfactor; // apply steering
354
355                 rigvel = self.velocity;
356                 makevectors(self.angles); // new forward direction!
357         }
358
359         rigvel = rigvel * max(0, 1 - vlen(rigvel) * g_bugrigs_friction_air * frametime);
360         //MAXIMA: airfriction(v) := v * v * g_bugrigs_friction_air;
361         //MAXIMA: total_acceleration(v) := accel(v) - friction(v) - airfriction(v);
362         //MAXIMA: solve(total_acceleration(v) = 0, v);
363
364         if(g_bugrigs_planar_movement)
365         {
366                 vector rigvel_xy, neworigin, up;
367                 float mt;
368
369                 rigvel_z -= frametime * sv_gravity; // 4x gravity plays better
370                 rigvel_xy = vec2(rigvel);
371
372                 if(g_bugrigs_planar_movement_car_jumping && !g_touchexplode) // touchexplode is a better way to handle collisions
373                         mt = MOVE_NORMAL;
374                 else
375                         mt = MOVE_NOMONSTERS;
376
377                 tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 1024', mt, self);
378                 up = trace_endpos - self.origin;
379
380                 // BUG RIGS: align the move to the surface instead of doing collision testing
381                 // can we move?
382                 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos + rigvel_xy * frametime, mt, self);
383
384                 // align to surface
385                 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos - up + '0 0 1' * rigvel_z * frametime, mt, self);
386
387                 if(trace_fraction < 0.5)
388                 {
389                         trace_fraction = 1;
390                         neworigin = self.origin;
391                 }
392                 else
393                         neworigin = trace_endpos;
394
395                 if(trace_fraction < 1)
396                 {
397                         // now set angles_x so that the car points parallel to the surface
398                         self.angles = vectoangles(
399                                         '1 0 0' * v_forward_x * trace_plane_normal_z
400                                         +
401                                         '0 1 0' * v_forward_y * trace_plane_normal_z
402                                         +
403                                         '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y)
404                                         );
405                         self.flags |= FL_ONGROUND;
406                 }
407                 else
408                 {
409                         // now set angles_x so that the car points forward, but is tilted in velocity direction
410                         self.flags &~= FL_ONGROUND;
411                 }
412
413                 self.velocity = (neworigin - self.origin) * (1.0 / frametime);
414                 self.movetype = MOVETYPE_NOCLIP;
415         }
416         else
417         {
418                 rigvel_z -= frametime * sv_gravity; // 4x gravity plays better
419                 self.velocity = rigvel;
420                 self.movetype = MOVETYPE_FLY;
421         }
422
423         trace_fraction = 1;
424         tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 4', MOVE_NORMAL, self);
425         if(trace_fraction != 1)
426         {
427                 self.angles = vectoangles2(
428                                 '1 0 0' * v_forward_x * trace_plane_normal_z
429                                 +
430                                 '0 1 0' * v_forward_y * trace_plane_normal_z
431                                 +
432                                 '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y),
433                                 trace_plane_normal
434                                 );
435         }
436         else
437         {
438                 vector vel_local;
439
440                 vel_local_x = v_forward * self.velocity;
441                 vel_local_y = v_right * self.velocity;
442                 vel_local_z = v_up * self.velocity;
443
444                 self.angles_x = racecar_angle(vel_local_x, vel_local_z);
445                 self.angles_z = racecar_angle(-vel_local_y, vel_local_z);
446         }
447
448         // smooth the angles
449         vector vf1, vu1, smoothangles;
450         makevectors(self.angles);
451         f = bound(0, frametime * g_bugrigs_angle_smoothing, 1);
452         if(f == 0)
453                 f = 1;
454         vf1 = v_forward * f;
455         vu1 = v_up * f;
456         makevectors(angles_save);
457         vf1 = vf1 + v_forward * (1 - f);
458         vu1 = vu1 + v_up * (1 - f);
459         smoothangles = vectoangles2(vf1, vu1);
460         self.angles_x = -smoothangles_x;
461         self.angles_z =  smoothangles_z;
462 }
463
464 float IsMoveInDirection(vector mv, float angle) // key mix factor
465 {
466         if(mv_x == 0 && mv_y == 0)
467                 return 0; // avoid division by zero
468         angle -= RAD2DEG * atan2(mv_y, mv_x);
469         angle = remainder(angle, 360) / 45;
470         if(angle >  1)
471                 return 0;
472         if(angle < -1)
473                 return 0;
474         return 1 - fabs(angle);
475 }
476
477 float GeomLerp(float a, float lerp, float b)
478 {
479         if(a == 0)
480         {
481                 if(lerp < 1)
482                         return 0;
483                 else
484                         return b;
485         }
486         if(b == 0)
487         {
488                 if(lerp > 0)
489                         return 0;
490                 else
491                         return a;
492         }
493         return a * pow(fabs(b / a), lerp);
494 }
495
496 void CPM_PM_Aircontrol(vector wishdir, float wishspeed)
497 {
498         float zspeed, xyspeed, dot, k;
499
500 #if 0
501         // this doesn't play well with analog input
502         if(self.movement_x == 0 || self.movement_y != 0)
503                 return; // can't control movement if not moving forward or backward
504         k = 32;
505 #else
506         k = 32 * (2 * IsMoveInDirection(self.movement, 0) - 1);
507         if(k <= 0)
508                 return;
509 #endif
510
511         k *= bound(0, wishspeed / sv_maxairspeed, 1);
512
513         zspeed = self.velocity_z;
514         self.velocity_z = 0;
515         xyspeed = vlen(self.velocity); self.velocity = normalize(self.velocity);
516
517         dot = self.velocity * wishdir;
518
519         if(dot > 0) // we can't change direction while slowing down
520         {
521                 k *= pow(dot, sv_aircontrol_power)*frametime;
522                 xyspeed = max(0, xyspeed - sv_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32);
523                 k *= sv_aircontrol;
524                 self.velocity = normalize(self.velocity * xyspeed + wishdir * k);
525         }
526
527         self.velocity = self.velocity * xyspeed;
528         self.velocity_z = zspeed;
529 }
530
531 float AdjustAirAccelQW(float accelqw, float factor)
532 {
533         return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
534 }
535
536 // example config for alternate speed clamping:
537 //   sv_airaccel_qw 0.8
538 //   sv_airaccel_sideways_friction 0
539 //   prvm_globalset server speedclamp_mode 1
540 //     (or 2)
541 void PM_Accelerate(vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float sidefric, float speedlimit)
542 {
543         float vel_straight;
544         float vel_z;
545         vector vel_perpend;
546         float step;
547
548         vector vel_xy;
549         float vel_xy_current;
550         float vel_xy_backward, vel_xy_forward;
551         float speedclamp;
552
553         speedclamp = (accelqw < 0);
554         if(speedclamp)
555                 accelqw = -accelqw;
556
557         if(cvar("sv_gameplayfix_q2airaccelerate"))
558                 wishspeed0 = wishspeed;
559
560         vel_straight = self.velocity * wishdir;
561         vel_z = self.velocity_z;
562         vel_xy = vec2(self.velocity);
563         vel_perpend = vel_xy - vel_straight * wishdir;
564
565         step = accel * frametime * wishspeed0;
566
567         vel_xy_current  = vlen(vel_xy);
568         if(speedlimit)
569                 accelqw = AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
570         vel_xy_forward  = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
571         vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
572         if(vel_xy_backward < 0)
573                 vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards
574
575         vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw);
576
577         if(sidefric < 0 && (vel_perpend*vel_perpend))
578                 // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
579         {
580                 float f, fminimum;
581                 f = max(0, 1 + frametime * wishspeed * sidefric);
582                 fminimum = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / (vel_perpend*vel_perpend);
583                 // this cannot be > 1
584                 if(fminimum <= 0)
585                         vel_perpend = vel_perpend * max(0, f);
586                 else
587                 {
588                         fminimum = sqrt(fminimum);
589                         vel_perpend = vel_perpend * max(fminimum, f);
590                 }
591         }
592         else
593                 vel_perpend = vel_perpend * max(0, 1 - frametime * wishspeed * sidefric);
594         
595         vel_xy = vel_straight * wishdir + vel_perpend;
596         
597         if(speedclamp)
598         {
599                 // ensure we don't get too fast or decelerate faster than we should
600                 vel_xy_current = min(vlen(vel_xy), vel_xy_forward);
601                 if(vel_xy_current > 0) // prevent division by zero
602                         vel_xy = normalize(vel_xy) * vel_xy_current;
603         }
604
605         self.velocity = vel_xy + vel_z * '0 0 1';
606 }
607
608 void PM_AirAccelerate(vector wishdir, float wishspeed)
609 {
610         vector curvel, wishvel, acceldir, curdir;
611         float addspeed, accelspeed, curspeed, f;
612         float dot;
613
614         if(wishspeed == 0)
615                 return;
616
617         curvel = self.velocity;
618         curvel_z = 0;
619         curspeed = vlen(curvel);
620
621         if(wishspeed > curspeed * 1.01)
622         {
623                 wishspeed = min(wishspeed, curspeed + sv_warsowbunny_airforwardaccel * sv_maxspeed * frametime);
624         }
625         else
626         {
627                 f = max(0, (sv_warsowbunny_topspeed - curspeed) / (sv_warsowbunny_topspeed - sv_maxspeed));
628                 wishspeed = max(curspeed, sv_maxspeed) + sv_warsowbunny_accel * f * sv_maxspeed * frametime;
629         }
630         wishvel = wishdir * wishspeed;
631         acceldir = wishvel - curvel;
632         addspeed = vlen(acceldir);
633         acceldir = normalize(acceldir);
634
635         accelspeed = min(addspeed, sv_warsowbunny_turnaccel * sv_maxspeed * frametime);
636
637         if(sv_warsowbunny_backtosideratio < 1)
638         {
639                 curdir = normalize(curvel);
640                 dot = acceldir * curdir;
641                 if(dot < 0)
642                         acceldir = acceldir - (1 - sv_warsowbunny_backtosideratio) * dot * curdir;
643         }
644
645         self.velocity += accelspeed * acceldir;
646 }
647
648 .vector movement_old;
649 .float buttons_old;
650 .vector v_angle_old;
651 .string lastclassname;
652
653 .float() PlayerPhysplug;
654
655 string specialcommand = "xwxwxsxsxaxdxaxdx1x ";
656 .float specialcommand_pos;
657 void SpecialCommand()
658 {
659 #ifdef TETRIS
660         TetrisImpulse();
661 #else
662         if(!CheatImpulse(99))
663                 print("A hollow voice says \"Plugh\".\n");
664 #endif
665 }
666
667 float speedaward_speed;
668 string speedaward_holder;
669 string speedaward_uid;
670 void race_send_speedaward(float msg)
671 {
672         // send the best speed of the round
673         WriteByte(msg, SVC_TEMPENTITY);
674         WriteByte(msg, TE_CSQC_RACE);
675         WriteByte(msg, RACE_NET_SPEED_AWARD);
676         WriteInt24_t(msg, floor(speedaward_speed+0.5));
677         WriteString(msg, speedaward_holder);
678 }
679
680 float speedaward_alltimebest;
681 string speedaward_alltimebest_holder;
682 string speedaward_alltimebest_uid;
683 void race_send_speedaward_alltimebest(float msg)
684 {
685         // send the best speed
686         WriteByte(msg, SVC_TEMPENTITY);
687         WriteByte(msg, TE_CSQC_RACE);
688         WriteByte(msg, RACE_NET_SPEED_AWARD_BEST);
689         WriteInt24_t(msg, floor(speedaward_alltimebest+0.5));
690         WriteString(msg, speedaward_alltimebest_holder);
691 }
692
693 string GetMapname(void);
694 float speedaward_lastupdate;
695 float speedaward_lastsent;
696 void SV_PlayerPhysics()
697 {
698         local vector wishvel, wishdir, v;
699         local float wishspeed, f, maxspd_mod, spd, maxairspd, airaccel, swampspd_mod, buttons;
700         string temps;
701         float buttons_prev;
702         float not_allowed_to_move;
703         string c;
704         
705         maxspd_mod = 1;
706         if(g_minstagib && (self.items & IT_INVINCIBLE))
707                 maxspd_mod *= cvar("g_minstagib_speed_highspeed");
708         if(g_nexball && self.ballcarried)
709                 maxspd_mod *= cvar("g_nexball_basketball_carrier_highspeed");
710
711         // fix physics stats for g_movement_highspeed
712         self.stat_sv_airaccel_qw = AdjustAirAccelQW(sv_airaccel_qw, autocvar_g_movement_highspeed * maxspd_mod);
713
714         if(sv_airstrafeaccel_qw)
715                 self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(sv_airstrafeaccel_qw, autocvar_g_movement_highspeed * maxspd_mod);
716         else
717                 self.stat_sv_airstrafeaccel_qw = 0;
718
719         self.stat_sv_airspeedlimit_nonqw = sv_airspeedlimit_nonqw * autocvar_g_movement_highspeed * maxspd_mod;
720
721     if(self.PlayerPhysplug)
722         if(self.PlayerPhysplug())
723             return;
724
725         self.race_movetime_frac += frametime;
726         f = floor(self.race_movetime_frac);
727         self.race_movetime_frac -= f;
728         self.race_movetime_count += f;
729         self.race_movetime = self.race_movetime_frac + self.race_movetime_count;
730
731         anticheat_physics();
732
733         buttons = self.BUTTON_ATCK + 2 * self.BUTTON_JUMP + 4 * self.BUTTON_ATCK2 + 8 * self.BUTTON_ZOOM + 16 * self.BUTTON_CROUCH + 32 * self.BUTTON_HOOK + 64 * self.BUTTON_USE + 128 * (self.movement_x < 0) + 256 * (self.movement_x > 0) + 512 * (self.movement_y < 0) + 1024 * (self.movement_y > 0);
734
735         if(!buttons)
736                 c = "x";
737         else if(buttons == 1)
738                 c = "1";
739         else if(buttons == 2)
740                 c = " ";
741         else if(buttons == 128)
742                 c = "s";
743         else if(buttons == 256)
744                 c = "w";
745         else if(buttons == 512)
746                 c = "a";
747         else if(buttons == 1024)
748                 c = "d";
749         else
750                 c = "?";
751
752         if(c == substring(specialcommand, self.specialcommand_pos, 1))
753         {
754                 self.specialcommand_pos += 1;
755                 if(self.specialcommand_pos >= strlen(specialcommand))
756                 {
757                         self.specialcommand_pos = 0;
758                         SpecialCommand();
759                         return;
760                 }
761         }
762         else if(self.specialcommand_pos && (c != substring(specialcommand, self.specialcommand_pos - 1, 1)))
763                 self.specialcommand_pos = 0;
764
765         if(!sv_maxidle_spectatorsareidle || self.movetype == MOVETYPE_WALK)
766         {
767                 if(buttons != self.buttons_old || self.movement != self.movement_old || self.v_angle != self.v_angle_old)
768                         self.parm_idlesince = time;
769         }
770         buttons_prev = self.buttons_old;
771         self.buttons_old = buttons;
772         self.movement_old = self.movement;
773         self.v_angle_old = self.v_angle;
774
775         if(time < self.nickspamtime)
776         if(self.nickspamcount >= cvar("g_nick_flood_penalty_yellow"))
777         {
778                 // slight annoyance for nick change scripts
779                 self.movement = -1 * self.movement;
780                 self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = self.BUTTON_ZOOM = self.BUTTON_CROUCH = self.BUTTON_HOOK = self.BUTTON_USE = 0;
781
782                 if(self.nickspamcount >= cvar("g_nick_flood_penalty_red")) // if you are persistent and the slight annoyance above does not stop you, I'll show you!
783                 {
784                         self.angles_x = random() * 360;
785                         self.angles_y = random() * 360;
786                         // at least I'm not forcing retardedview by also assigning to angles_z
787                         self.fixangle = 1;
788                 }
789         }
790
791         if (self.punchangle != '0 0 0')
792         {
793                 f = vlen(self.punchangle) - 10 * frametime;
794                 if (f > 0)
795                         self.punchangle = normalize(self.punchangle) * f;
796                 else
797                         self.punchangle = '0 0 0';
798         }
799
800         if (self.punchvector != '0 0 0')
801         {
802                 f = vlen(self.punchvector) - 30 * frametime;
803                 if (f > 0)
804                         self.punchvector = normalize(self.punchvector) * f;
805                 else
806                         self.punchvector = '0 0 0';
807         }
808
809         if (clienttype(self) == CLIENTTYPE_BOT)
810         {
811                 if(playerdemo_read())
812                         return;
813                 bot_think();
814         }
815         
816         MUTATOR_CALLHOOK(PlayerPhysics);
817
818         self.items &~= IT_USING_JETPACK;
819
820         if(self.classname == "player")
821         {
822                 if(self.race_penalty)
823                         if(time > self.race_penalty)
824                                 self.race_penalty = 0;
825
826                 not_allowed_to_move = 0;
827                 if(self.race_penalty)
828                         not_allowed_to_move = 1;
829                 if(!cvar("sv_ready_restart_after_countdown"))
830                 if(time < game_starttime)
831                         not_allowed_to_move = 1;
832
833                 if(not_allowed_to_move)
834                 {
835                         self.velocity = '0 0 0';
836                         self.movetype = MOVETYPE_NONE;
837                         self.disableclientprediction = 2;
838                 }
839                 else if(self.disableclientprediction == 2)
840                 {
841                         if(self.movetype == MOVETYPE_NONE)
842                                 self.movetype = MOVETYPE_WALK;
843                         self.disableclientprediction = 0;
844                 }
845         }
846
847         if (self.movetype == MOVETYPE_NONE)
848                 return;
849
850         maxspd_mod = 1;
851
852         if(g_runematch)
853         {
854                 if(self.runes & RUNE_SPEED)
855                 {
856                         if(self.runes & CURSE_SLOW)
857                                 maxspd_mod = maxspd_mod * cvar("g_balance_rune_speed_combo_moverate");
858                         else
859                                 maxspd_mod = maxspd_mod * cvar("g_balance_rune_speed_moverate");
860                 }
861                 else if(self.runes & CURSE_SLOW)
862                 {
863                         maxspd_mod = maxspd_mod * cvar("g_balance_curse_slow_moverate");
864                 }
865         }
866
867         swampspd_mod = 1;
868         if(self.in_swamp) {
869                 swampspd_mod = self.swamp_slowdown; //cvar("g_balance_swamp_moverate");
870         }
871
872         if(self.classname != "player")
873         {
874                 maxspd_mod = cvar("sv_spectator_speed_multiplier");
875                 if(!self.spectatorspeed)
876                         self.spectatorspeed = maxspd_mod;
877                 if(self.impulse && self.impulse <= 19)
878                 {
879                         if(self.lastclassname != "player")
880                         {
881                                 if(self.impulse == 10 || self.impulse == 15 || self.impulse == 18)
882                                         self.spectatorspeed = bound(1, self.spectatorspeed + 0.5, 5);
883                                 else if(self.impulse == 11)
884                                         self.spectatorspeed = maxspd_mod;
885                                 else if(self.impulse == 12 || self.impulse == 16  || self.impulse == 19)
886                                         self.spectatorspeed = bound(1, self.spectatorspeed - 0.5, 5);
887                                 else if(self.impulse >= 1 && self.impulse <= 9)
888                                         self.spectatorspeed = 1 + 0.5 * (self.impulse - 1);
889                         } // otherwise just clear
890                         self.impulse = 0;
891                 }
892                 maxspd_mod = self.spectatorspeed;
893         }
894
895         spd = max(sv_maxspeed, sv_maxairspeed) * maxspd_mod * swampspd_mod;
896         if(self.speed != spd)
897         {
898                 self.speed = spd;
899                 temps = ftos(spd);
900                 stuffcmd(self, strcat("cl_forwardspeed ", temps, "\n"));
901                 stuffcmd(self, strcat("cl_backspeed ", temps, "\n"));
902                 stuffcmd(self, strcat("cl_sidespeed ", temps, "\n"));
903                 stuffcmd(self, strcat("cl_upspeed ", temps, "\n"));
904         }
905
906         maxspd_mod *= swampspd_mod; // only one common speed modder please!
907         swampspd_mod = 1;
908
909         // if dead, behave differently
910         if (self.deadflag)
911                 goto end;
912
913         if (!self.fixangle && !g_bugrigs)
914         {
915                 self.angles_x = 0;
916                 self.angles_y = self.v_angle_y;
917                 self.angles_z = 0;
918         }
919
920         if(self.flags & FL_ONGROUND)
921         if(self.wasFlying)
922         {
923                 self.wasFlying = 0;
924
925                 if(self.waterlevel < WATERLEVEL_SWIMMING)
926                 if(time >= self.ladder_time)
927                 if not(self.hook)
928                 {
929                         self.nextstep = time + 0.3 + random() * 0.1;
930                         trace_dphitq3surfaceflags = 0;
931                         tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
932                         if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS)
933                         {
934                                 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
935                                         GlobalSound(globalsound_metalfall, CHAN_PLAYER, VOICETYPE_PLAYERSOUND);
936                                 else
937                                         GlobalSound(globalsound_fall, CHAN_PLAYER, VOICETYPE_PLAYERSOUND);
938                         }
939                 }
940         }
941
942         if(IsFlying(self))
943                 self.wasFlying = 1;
944
945         if(self.classname == "player")
946         {
947                 if(self.flags & FL_ONGROUND)
948                 {
949                         if (cvar("g_multijump") > 0)
950                                 self.multijump_count = 0;
951                         else
952                                 self.multijump_count = -2; // the cvar value for infinite jumps is -1, so this needs to be smaller
953                 }
954
955                 if (self.BUTTON_JUMP)
956                         PlayerJump ();
957                 else
958                         self.flags |= FL_JUMPRELEASED;
959
960                 if (self.waterlevel == WATERLEVEL_SWIMMING)
961                         CheckWaterJump ();
962                 self.prevjumpbutton = self.BUTTON_JUMP;
963         }
964
965         if (self.flags & FL_WATERJUMP )
966         {
967                 self.velocity_x = self.movedir_x;
968                 self.velocity_y = self.movedir_y;
969                 if (time > self.teleport_time || self.waterlevel == WATERLEVEL_NONE)
970                 {
971                         self.flags &~= FL_WATERJUMP;
972                         self.teleport_time = 0;
973                 }
974         }
975         else if (g_bugrigs && self.classname == "player")
976         {
977                 RaceCarPhysics();
978         }
979         else if (self.movetype == MOVETYPE_NOCLIP || self.movetype == MOVETYPE_FLY)
980         {
981                 // noclipping or flying
982                 self.flags &~= FL_ONGROUND;
983
984                 self.velocity = self.velocity * (1 - frametime * sv_friction);
985                 makevectors(self.v_angle);
986                 //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
987                 wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;
988                 // acceleration
989                 wishdir = normalize(wishvel);
990                 wishspeed = vlen(wishvel);
991                 if (wishspeed > sv_maxspeed*maxspd_mod)
992                         wishspeed = sv_maxspeed*maxspd_mod;
993                 if (time >= self.teleport_time)
994                         PM_Accelerate(wishdir, wishspeed, wishspeed, sv_accelerate*maxspd_mod, 1, 0, 0);
995         }
996         else if (self.waterlevel >= WATERLEVEL_SWIMMING)
997         {
998                 // swimming
999                 self.flags &~= FL_ONGROUND;
1000
1001                 makevectors(self.v_angle);
1002                 //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
1003                 wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;
1004                 if (wishvel == '0 0 0')
1005                         wishvel = '0 0 -60'; // drift towards bottom
1006
1007                 wishdir = normalize(wishvel);
1008                 wishspeed = vlen(wishvel);
1009                 if (wishspeed > sv_maxspeed*maxspd_mod)
1010                         wishspeed = sv_maxspeed*maxspd_mod;
1011                 wishspeed = wishspeed * 0.7;
1012
1013                 // water friction
1014                 self.velocity = self.velocity * (1 - frametime * sv_friction);
1015
1016                 // water acceleration
1017                 PM_Accelerate(wishdir, wishspeed, wishspeed, sv_accelerate*maxspd_mod, 1, 0, 0);
1018         }
1019         else if (time < self.ladder_time)
1020         {
1021                 // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
1022                 self.flags &~= FL_ONGROUND;
1023
1024                 self.velocity = self.velocity * (1 - frametime * sv_friction);
1025                 makevectors(self.v_angle);
1026                 //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
1027                 wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;
1028                 if (self.gravity)
1029                         self.velocity_z = self.velocity_z + self.gravity * sv_gravity * frametime;
1030                 else
1031                         self.velocity_z = self.velocity_z + sv_gravity * frametime;
1032                 if (self.ladder_entity.classname == "func_water")
1033                 {
1034                         f = vlen(wishvel);
1035                         if (f > self.ladder_entity.speed)
1036                                 wishvel = wishvel * (self.ladder_entity.speed / f);
1037
1038                         self.watertype = self.ladder_entity.skin;
1039                         f = self.ladder_entity.origin_z + self.ladder_entity.maxs_z;
1040                         if ((self.origin_z + self.view_ofs_z) < f)
1041                                 self.waterlevel = WATERLEVEL_SUBMERGED;
1042                         else if ((self.origin_z + (self.mins_z + self.maxs_z) * 0.5) < f)
1043                                 self.waterlevel = WATERLEVEL_SWIMMING;
1044                         else if ((self.origin_z + self.mins_z + 1) < f)
1045                                 self.waterlevel = WATERLEVEL_WETFEET;
1046                         else
1047                         {
1048                                 self.waterlevel = WATERLEVEL_NONE;
1049                                 self.watertype = CONTENT_EMPTY;
1050                         }
1051                 }
1052                 // acceleration
1053                 wishdir = normalize(wishvel);
1054                 wishspeed = vlen(wishvel);
1055                 if (wishspeed > sv_maxspeed*maxspd_mod)
1056                         wishspeed = sv_maxspeed*maxspd_mod;
1057                 if (time >= self.teleport_time)
1058                 {
1059                         // water acceleration
1060                         PM_Accelerate(wishdir, wishspeed, wishspeed, sv_accelerate*maxspd_mod, 1, 0, 0);
1061                 }
1062         }
1063         else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!cvar("g_jetpack_fuel") || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO))
1064         {
1065                 //makevectors(self.v_angle_y * '0 1 0');
1066                 makevectors(self.v_angle);
1067                 wishvel = v_forward * self.movement_x + v_right * self.movement_y;
1068                 // add remaining speed as Z component
1069                 maxairspd = sv_maxairspeed*max(1, maxspd_mod);
1070                 // fix speedhacks :P
1071                 wishvel = normalize(wishvel) * min(vlen(wishvel) / maxairspd, 1);
1072                 // add the unused velocity as up component
1073                 wishvel_z = 0;
1074
1075                 // if(self.BUTTON_JUMP)
1076                         wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
1077
1078                 // it is now normalized, so...
1079                 float a_side, a_up, a_add, a_diff;
1080                 a_side = cvar("g_jetpack_acceleration_side");
1081                 a_up = cvar("g_jetpack_acceleration_up");
1082                 a_add = cvar("g_jetpack_antigravity") * sv_gravity;
1083
1084                 wishvel_x *= a_side;
1085                 wishvel_y *= a_side;
1086                 wishvel_z *= a_up;
1087                 wishvel_z += a_add;
1088
1089                 float best;
1090                 best = 0;
1091                 //////////////////////////////////////////////////////////////////////////////////////
1092                 // finding the maximum over all vectors of above form
1093                 // with wishvel having an absolute value of 1
1094                 //////////////////////////////////////////////////////////////////////////////////////
1095                 // we're finding the maximum over
1096                 //   f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
1097                 // for z in the range from -1 to 1
1098                 //////////////////////////////////////////////////////////////////////////////////////
1099                 // maximum is EITHER attained at the single extreme point:
1100                 a_diff = a_side * a_side - a_up * a_up;
1101                 if(a_diff != 0)
1102                 {
1103                         f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
1104                         if(f > -1 && f < 1) // can it be attained?
1105                         {
1106                                 best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
1107                                 //print("middle\n");
1108                         }
1109                 }
1110                 // OR attained at z = 1:
1111                 f = (a_up + a_add) * (a_up + a_add);
1112                 if(f > best)
1113                 {
1114                         best = f;
1115                         //print("top\n");
1116                 }
1117                 // OR attained at z = -1:
1118                 f = (a_up - a_add) * (a_up - a_add);
1119                 if(f > best)
1120                 {
1121                         best = f;
1122                         //print("bottom\n");
1123                 }
1124                 best = sqrt(best);
1125                 //////////////////////////////////////////////////////////////////////////////////////
1126
1127                 //print("best possible acceleration: ", ftos(best), "\n");
1128
1129                 float fxy, fz;
1130                 fxy = bound(0, 1 - (self.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / cvar("g_jetpack_maxspeed_side"), 1);
1131                 if(wishvel_z - sv_gravity > 0)
1132                         fz = bound(0, 1 - self.velocity_z / cvar("g_jetpack_maxspeed_up"), 1);
1133                 else
1134                         fz = bound(0, 1 + self.velocity_z / cvar("g_jetpack_maxspeed_up"), 1);
1135
1136                 float fvel;
1137                 fvel = vlen(wishvel);
1138                 wishvel_x *= fxy;
1139                 wishvel_y *= fxy;
1140                 wishvel_z = (wishvel_z - sv_gravity) * fz + sv_gravity;
1141
1142                 fvel = min(1, vlen(wishvel) / best);
1143                 if(cvar("g_jetpack_fuel") && !(self.items & IT_UNLIMITED_WEAPON_AMMO))
1144                         f = min(1, self.ammo_fuel / (cvar("g_jetpack_fuel") * frametime * fvel));
1145                 else
1146                         f = 1;
1147
1148                 //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
1149
1150                 if (f > 0 && wishvel != '0 0 0')
1151                 {
1152                         self.velocity = self.velocity + wishvel * f * frametime;
1153                         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
1154                                 self.ammo_fuel -= cvar("g_jetpack_fuel") * frametime * fvel * f;
1155                         self.flags &~= FL_ONGROUND;
1156                         self.items |= IT_USING_JETPACK;
1157
1158                         // jetpack also inhibits health regeneration, but only for 1 second
1159                         self.pauseregen_finished = max(self.pauseregen_finished, time + cvar("g_balance_pause_fuel_regen"));
1160                 }
1161         }
1162         else if (self.flags & FL_ONGROUND)
1163         {
1164                 // we get here if we ran out of ammo
1165                 if((self.items & IT_JETPACK) && self.BUTTON_HOOK && !(buttons_prev & 32))
1166                         sprint(self, "You don't have any fuel for the ^2Jetpack\n");
1167
1168                 // walking
1169                 makevectors(self.v_angle_y * '0 1 0');
1170                 wishvel = v_forward * self.movement_x + v_right * self.movement_y;
1171
1172                 if(!(self.lastflags & FL_ONGROUND))
1173                 {
1174                         if(cvar("speedmeter"))
1175                                 dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
1176                         if(self.lastground < time - 0.3)
1177                                 self.velocity = self.velocity * (1 - cvar("sv_friction_on_land"));
1178                         if(self.jumppadcount > 1)
1179                                 dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
1180                         self.jumppadcount = 0;
1181                 }
1182
1183 #ifdef LETS_TEST_FTEQCC
1184                 if(self.velocity_x || self.velocity_y)
1185                 {
1186                         // good
1187                 }
1188                 else
1189                 {
1190                         if(self.velocity_x)
1191                                 checkclient();
1192                         if(self.velocity_y)
1193                                 checkclient();
1194                 }
1195 #endif
1196
1197                 v = self.velocity;
1198                 v_z = 0;
1199                 f = vlen(v);
1200                 if(f > 0)
1201                 {
1202                         if (f < sv_stopspeed)
1203                                 f = 1 - frametime * (sv_stopspeed / f) * sv_friction;
1204                         else
1205                                 f = 1 - frametime * sv_friction;
1206                         if (f > 0)
1207                                 self.velocity = self.velocity * f;
1208                         else
1209                                 self.velocity = '0 0 0';
1210                 }
1211
1212                 // acceleration
1213                 wishdir = normalize(wishvel);
1214                 wishspeed = vlen(wishvel);
1215                 if (wishspeed > sv_maxspeed*maxspd_mod)
1216                         wishspeed = sv_maxspeed*maxspd_mod;
1217                 if (self.crouch)
1218                         wishspeed = wishspeed * 0.5;
1219                 if (time >= self.teleport_time)
1220                         PM_Accelerate(wishdir, wishspeed, wishspeed, sv_accelerate*maxspd_mod, 1, 0, 0);
1221         }
1222         else
1223         {
1224                 float wishspeed0;
1225                 // we get here if we ran out of ammo
1226                 if((self.items & IT_JETPACK) && self.BUTTON_HOOK && !(buttons_prev & 32))
1227                         sprint(self, "You don't have any fuel for the ^2Jetpack\n");
1228
1229                 if(maxspd_mod < 1)
1230                 {
1231                         maxairspd = sv_maxairspeed*maxspd_mod;
1232                         airaccel = sv_airaccelerate*maxspd_mod;
1233                 }
1234                 else
1235                 {
1236                         maxairspd = sv_maxairspeed;
1237                         airaccel = sv_airaccelerate;
1238                 }
1239                 // airborn
1240                 makevectors(self.v_angle_y * '0 1 0');
1241                 wishvel = v_forward * self.movement_x + v_right * self.movement_y;
1242                 // acceleration
1243                 wishdir = normalize(wishvel);
1244                 wishspeed = wishspeed0 = vlen(wishvel);
1245                 if (wishspeed0 > sv_maxspeed*maxspd_mod)
1246                         wishspeed0 = sv_maxspeed*maxspd_mod;
1247                 if (wishspeed > maxairspd)
1248                         wishspeed = maxairspd;
1249                 if (self.crouch)
1250                         wishspeed = wishspeed * 0.5;
1251                 if (time >= self.teleport_time)
1252                 {
1253                         float accelerating;
1254                         float wishspeed2;
1255                         float airaccelqw;
1256                         float strafity;
1257
1258                         airaccelqw = self.stat_sv_airaccel_qw;
1259                         accelerating = (self.velocity * wishdir > 0);
1260                         wishspeed2 = wishspeed;
1261
1262                         // CPM
1263                         if(sv_airstopaccelerate)
1264                         {
1265                                 vector curdir;
1266                                 curdir = self.velocity;
1267                                 curdir_z = 0;
1268                                 curdir = normalize(curdir);
1269                                 airaccel = airaccel + (sv_airstopaccelerate*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
1270                         }
1271                         // note that for straight forward jumping:
1272                         // step = accel * frametime * wishspeed0;
1273                         // accel  = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
1274                         // -->
1275                         // dv/dt = accel * maxspeed (when slow)
1276                         // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
1277                         // log dv/dt = logaccel + logmaxspeed (when slow)
1278                         // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
1279                         strafity = IsMoveInDirection(self.movement, -90) + IsMoveInDirection(self.movement, +90); // if one is nonzero, other is always zero
1280                         if(sv_maxairstrafespeed)
1281                                 wishspeed = min(wishspeed, GeomLerp(sv_maxairspeed*maxspd_mod, strafity, sv_maxairstrafespeed*maxspd_mod));
1282                         if(sv_airstrafeaccelerate)
1283                                 airaccel = GeomLerp(airaccel, strafity, sv_airstrafeaccelerate*maxspd_mod);
1284                         if(self.stat_sv_airstrafeaccel_qw)
1285                                 airaccelqw = copysign(1-GeomLerp(1-fabs(self.stat_sv_airaccel_qw), strafity, 1-fabs(self.stat_sv_airstrafeaccel_qw)), ((strafity > 0.5) ? self.stat_sv_airstrafeaccel_qw : self.stat_sv_airaccel_qw));
1286                         // !CPM
1287
1288                         if(sv_warsowbunny_turnaccel && accelerating && self.movement_y == 0 && self.movement_x != 0)
1289                                 PM_AirAccelerate(wishdir, wishspeed);
1290                         else
1291                                 PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, sv_airaccel_sideways_friction / maxairspd, self.stat_sv_airspeedlimit_nonqw);
1292
1293                         if(sv_aircontrol)
1294                                 CPM_PM_Aircontrol(wishdir, wishspeed2);
1295                 }
1296         }
1297
1298         if((g_cts || g_race) && self.classname != "observer") {
1299                 if(vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed) {
1300                         speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1');
1301                         speedaward_holder = self.netname;
1302                         speedaward_uid = self.crypto_idfp;
1303                         speedaward_lastupdate = time;
1304                 }
1305                 if(speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1) {
1306                         string rr;
1307                         if(g_cts)
1308                                 rr = CTS_RECORD;
1309                         else
1310                                 rr = RACE_RECORD;
1311                         race_send_speedaward(MSG_ALL);
1312                         speedaward_lastsent = speedaward_speed;
1313                         if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "") {
1314                                 speedaward_alltimebest = speedaward_speed;
1315                                 speedaward_alltimebest_holder = speedaward_holder;
1316                                 speedaward_alltimebest_uid = speedaward_uid;
1317                                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
1318                                 db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
1319                                 race_send_speedaward_alltimebest(MSG_ALL);
1320                         }
1321                 }
1322         }
1323
1324         float xyspeed;
1325         xyspeed = vlen('1 0 0' * self.velocity_x + '0 1 0' * self.velocity_y);
1326         if(self.weapon == WEP_NEX && cvar("g_balance_nex_charge") && cvar("g_balance_nex_charge_velocity_rate") && xyspeed > cvar("g_balance_nex_charge_minspeed"))
1327         {
1328                 // add a maximum of charge_velocity_rate when going fast (f = 1), gradually increasing from minspeed (f = 0) to maxspeed
1329                 xyspeed = min(xyspeed, cvar("g_balance_nex_charge_maxspeed"));
1330                 f = (xyspeed - cvar("g_balance_nex_charge_minspeed")) / (cvar("g_balance_nex_charge_maxspeed") - cvar("g_balance_nex_charge_minspeed"));
1331                 // add the extra charge
1332                 self.nex_charge = min(1, self.nex_charge + cvar("g_balance_nex_charge_velocity_rate") * f * frametime);
1333         }
1334 :end
1335         if(self.flags & FL_ONGROUND)
1336                 self.lastground = time;
1337
1338         self.lastflags = self.flags;
1339         self.lastclassname = self.classname;
1340 };