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