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