]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/cl_physics.qc
try to fixa speccer bug
[divverent/nexuiz.git] / data / qcsrc / server / cl_physics.qc
1 .float race_penalty;
2
3 float sv_accelerate;
4 float sv_friction;
5 float sv_maxspeed;
6 float sv_airaccelerate;
7 float sv_maxairspeed;
8 float sv_stopspeed;
9 float sv_gravity;
10 float sv_airaccel_sideways_friction;
11 float sv_airaccel_qw;
12 float sv_airstopaccelerate;
13 float sv_airstrafeaccelerate;
14 float sv_maxairstrafespeed;
15 float sv_aircontrol;
16 float sv_warsowbunny_airforwardaccel;
17 float sv_warsowbunny_accel;
18 float sv_warsowbunny_topspeed;
19 float sv_warsowbunny_turnaccel;
20 float sv_warsowbunny_backtosideratio;
21
22 .float ladder_time;
23 .entity ladder_entity;
24 .float gravity;
25 .float swamp_slowdown;
26 .float lastflags;
27 .float lastground;
28 .float wasFlying;
29 .float spectatorspeed;
30
31 #define SHTEST_DELTA 10
32 #define SHTEST_THRESHOLD 1.1
33 .float shtest_next;
34 .float shtest_accumulator;
35 .float doublejump_nextjumptime;
36
37 /*
38 =============
39 PlayerJump
40
41 When you press the jump key
42 =============
43 */
44 void PlayerJump (void)
45 {
46         float mjumpheight;
47
48         mjumpheight = cvar("sv_jumpvelocity");
49         if (self.waterlevel >= WATERLEVEL_SWIMMING)
50         {
51                 if (self.watertype == CONTENT_WATER)
52                         self.velocity_z = 200;
53                 else if (self.watertype == CONTENT_SLIME)
54                         self.velocity_z = 80;
55                 else
56                         self.velocity_z = 50;
57
58                 return;
59         }
60
61         if (!(self.flags & FL_ONGROUND))
62                 return;
63
64         if(!sv_pogostick)
65                 if (!(self.flags & FL_JUMPRELEASED))
66                         return;
67
68         if(self.health <= g_bloodloss)
69                 return;
70
71         if(sv_doublejump)
72                 if(time < self.doublejump_nextjumptime)
73                         return;
74
75         if(g_runematch)
76         {
77                 if(self.runes & RUNE_SPEED)
78                 {
79                         if(self.runes & CURSE_SLOW)
80                                 mjumpheight = mjumpheight * cvar("g_balance_rune_speed_combo_jumpheight");
81                         else
82                                 mjumpheight = mjumpheight * cvar("g_balance_rune_speed_jumpheight");
83                 }
84                 else if(self.runes & CURSE_SLOW)
85                 {
86                         mjumpheight = mjumpheight * cvar("g_balance_curse_slow_jumpheight");
87                 }
88         }
89
90         if(g_minstagib && (self.items & IT_INVINCIBLE))
91         {
92                 mjumpheight = mjumpheight * cvar("g_minstagib_speed_jumpheight");
93         }
94
95         self.velocity_z = self.velocity_z + mjumpheight;
96         self.oldvelocity_z = self.velocity_z;
97
98         self.flags &~= FL_ONGROUND;
99         self.flags &~= FL_JUMPRELEASED;
100
101         if (self.crouch)
102                 setanim(self, self.anim_duckjump, FALSE, TRUE, TRUE);
103         else
104                 setanim(self, self.anim_jump, FALSE, TRUE, TRUE);
105
106         if(g_jump_grunt)
107                 PlayerSound(playersound_jump, CHAN_PLAYER, VOICETYPE_PLAYERSOUND);
108
109         if(sv_doublejump)
110         {
111                 // we're moving upwards at self.velocity_z
112                 // only allow jumping after we got 3 units upwards
113                 // so we for sure leave the FL_ONGROUND check
114                 //
115                 // but as this sucks because of factoring in gravity, we'll just do it
116                 // for 4 units, and constant velocity
117                 self.doublejump_nextjumptime = time + 4 / max(40, self.velocity_z); // max 0.1s blocking of jumps
118         }
119 }
120
121 void CheckWaterJump()
122 {
123         local vector start, end;
124
125 // check for a jump-out-of-water
126         makevectors (self.angles);
127         start = self.origin;
128         start_z = start_z + 8;
129         v_forward_z = 0;
130         normalize(v_forward);
131         end = start + v_forward*24;
132         traceline (start, end, TRUE, self);
133         if (trace_fraction < 1)
134         {       // solid at waist
135                 start_z = start_z + self.maxs_z - 8;
136                 end = start + v_forward*24;
137                 self.movedir = trace_plane_normal * -50;
138                 traceline (start, end, TRUE, self);
139                 if (trace_fraction == 1)
140                 {       // open at eye level
141                         self.flags |= FL_WATERJUMP;
142                         self.velocity_z = 225;
143                         self.flags &~= FL_JUMPRELEASED;
144                         self.teleport_time = time + 2;  // safety net
145                         return;
146                 }
147         }
148 };
149
150 float racecar_angle(float forward, float down)
151 {
152         float ret, angle_mult;
153
154         if(forward < 0)
155         {
156                 forward = -forward;
157                 down = -down;
158         }
159
160         ret = vectoyaw('0 1 0' * down + '1 0 0' * forward);
161
162         angle_mult = forward / (800 + forward);
163
164         if(ret > 180)
165                 return ret * angle_mult + 360 * (1 - angle_mult);
166         else
167                 return ret * angle_mult;
168 }
169
170 void RaceCarPhysics()
171 {
172         // using this move type for "big rigs"
173         // the engine does not push the entity!
174
175         float accel, steer, f;
176         vector angles_save, rigvel;
177
178         angles_save = self.angles;
179         accel = bound(-1, self.movement_x / sv_maxspeed, 1);
180         steer = bound(-1, self.movement_y / sv_maxspeed, 1);
181
182         if(g_bugrigs_reverse_speeding)
183         {
184                 if(accel < 0)
185                 {
186                         // back accel is DIGITAL
187                         // to prevent speedhack
188                         if(accel < -0.5)
189                                 accel = -1;
190                         else
191                                 accel = 0;
192                 }
193         }
194
195         self.angles_x = 0;
196         self.angles_z = 0;
197         makevectors(self.angles); // new forward direction!
198
199         if(self.flags & FL_ONGROUND || g_bugrigs_air_steering)
200         {
201                 float myspeed, upspeed, steerfactor, accelfactor;
202
203                 myspeed = self.velocity * v_forward;
204                 upspeed = self.velocity * v_up;
205
206                 // responsiveness factor for steering and acceleration
207                 f = 1 / (1 + pow(max(-myspeed, myspeed) / g_bugrigs_speed_ref, g_bugrigs_speed_pow));
208                 //MAXIMA: f(v) := 1 / (1 + (v / g_bugrigs_speed_ref) ^ g_bugrigs_speed_pow);
209
210                 if(myspeed < 0 && g_bugrigs_reverse_spinning)
211                         steerfactor = -myspeed * g_bugrigs_steer;
212                 else
213                         steerfactor = -myspeed * f * g_bugrigs_steer;
214
215                 if(myspeed < 0 && g_bugrigs_reverse_speeding)
216                         accelfactor = g_bugrigs_accel;
217                 else
218                         accelfactor = f * g_bugrigs_accel;
219                 //MAXIMA: accel(v) := f(v) * g_bugrigs_accel;
220
221                 if(accel < 0)
222                 {
223                         if(myspeed > 0)
224                         {
225                                 myspeed = max(0, myspeed - frametime * (g_bugrigs_friction_floor - g_bugrigs_friction_brake * accel));
226                         }
227                         else
228                         {
229                                 if(!g_bugrigs_reverse_speeding)
230                                         myspeed = min(0, myspeed + frametime * g_bugrigs_friction_floor);
231                         }
232                 }
233                 else
234                 {
235                         if(myspeed >= 0)
236                         {
237                                 myspeed = max(0, myspeed - frametime * g_bugrigs_friction_floor);
238                         }
239                         else
240                         {
241                                 if(g_bugrigs_reverse_stopping)
242                                         myspeed = 0;
243                                 else
244                                         myspeed = min(0, myspeed + frametime * (g_bugrigs_friction_floor + g_bugrigs_friction_brake * accel));
245                         }
246                 }
247                 // terminal velocity = velocity at which 50 == accelfactor, that is, 1549 units/sec
248                 //MAXIMA: friction(v) := g_bugrigs_friction_floor;
249
250                 self.angles_y += steer * frametime * steerfactor; // apply steering
251                 makevectors(self.angles); // new forward direction!
252
253                 myspeed += accel * accelfactor * frametime;
254
255                 rigvel = myspeed * v_forward + '0 0 1' * upspeed;
256         }
257         else
258         {
259                 myspeed = vlen(self.velocity);
260
261                 // responsiveness factor for steering and acceleration
262                 f = 1 / (1 + pow(max(0, myspeed / g_bugrigs_speed_ref), g_bugrigs_speed_pow));
263                 steerfactor = -myspeed * f;
264                 self.angles_y += steer * frametime * steerfactor; // apply steering
265
266                 rigvel = self.velocity;
267                 makevectors(self.angles); // new forward direction!
268         }
269
270         rigvel = rigvel * max(0, 1 - vlen(rigvel) * g_bugrigs_friction_air * frametime);
271         //MAXIMA: airfriction(v) := v * v * g_bugrigs_friction_air;
272         //MAXIMA: total_acceleration(v) := accel(v) - friction(v) - airfriction(v);
273         //MAXIMA: solve(total_acceleration(v) = 0, v);
274
275         if(g_bugrigs_planar_movement)
276         {
277                 vector rigvel_xy, neworigin, up;
278                 float mt;
279
280                 rigvel_z -= frametime * sv_gravity; // 4x gravity plays better
281                 rigvel_xy = rigvel;
282                 rigvel_xy_z = 0;
283
284                 if(g_bugrigs_planar_movement_car_jumping && !g_touchexplode) // touchexplode is a better way to handle collisions
285                         mt = MOVE_NORMAL;
286                 else
287                         mt = MOVE_NOMONSTERS;
288
289                 tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 1024', mt, self);
290                 up = trace_endpos - self.origin;
291
292                 // BUG RIGS: align the move to the surface instead of doing collision testing
293                 // can we move?
294                 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos + rigvel_xy * frametime, mt, self);
295
296                 // align to surface
297                 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos - up + '0 0 1' * rigvel_z * frametime, mt, self);
298
299                 if(trace_fraction < 0.5)
300                 {
301                         trace_fraction = 1;
302                         neworigin = self.origin;
303                 }
304                 else
305                         neworigin = trace_endpos;
306
307                 if(trace_fraction < 1)
308                 {
309                         // now set angles_x so that the car points parallel to the surface
310                         self.angles = vectoangles(
311                                         '1 0 0' * v_forward_x * trace_plane_normal_z
312                                         +
313                                         '0 1 0' * v_forward_y * trace_plane_normal_z
314                                         +
315                                         '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y)
316                                         );
317                         self.flags |= FL_ONGROUND;
318                 }
319                 else
320                 {
321                         // now set angles_x so that the car points forward, but is tilted in velocity direction
322                         self.flags &~= FL_ONGROUND;
323                 }
324
325                 self.velocity = (neworigin - self.origin) * (1.0 / frametime);
326                 self.movetype = MOVETYPE_NOCLIP;
327         }
328         else
329         {
330                 rigvel_z -= frametime * sv_gravity; // 4x gravity plays better
331                 self.velocity = rigvel;
332                 self.movetype = MOVETYPE_FLY;
333         }
334
335         trace_fraction = 1;
336         tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 4', MOVE_NORMAL, self);
337         if(trace_fraction != 1)
338         {
339                 self.angles = vectoangles2(
340                                 '1 0 0' * v_forward_x * trace_plane_normal_z
341                                 +
342                                 '0 1 0' * v_forward_y * trace_plane_normal_z
343                                 +
344                                 '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y),
345                                 trace_plane_normal
346                                 );
347         }
348         else
349         {
350                 vector vel_local;
351
352                 vel_local_x = v_forward * self.velocity;
353                 vel_local_y = v_right * self.velocity;
354                 vel_local_z = v_up * self.velocity;
355
356                 self.angles_x = racecar_angle(vel_local_x, vel_local_z);
357                 self.angles_z = racecar_angle(-vel_local_y, vel_local_z);
358         }
359
360         // smooth the angles
361         vector vf1, vu1, smoothangles;
362         makevectors(self.angles);
363         f = bound(0, frametime * g_bugrigs_angle_smoothing, 1);
364         if(f == 0)
365                 f = 1;
366         vf1 = v_forward * f;
367         vu1 = v_up * f;
368         makevectors(angles_save);
369         vf1 = vf1 + v_forward * (1 - f);
370         vu1 = vu1 + v_up * (1 - f);
371         smoothangles = vectoangles2(vf1, vu1);
372         self.angles_x = -smoothangles_x;
373         self.angles_z =  smoothangles_z;
374 }
375
376 void CPM_PM_Aircontrol(vector wishdir, float wishspeed)
377 {
378         float zspeed, xyspeed, dot, k;
379
380         if(self.movement_x == 0 || self.movement_y != 0)
381                 return; // can't control movement if not moving forward or backward
382
383         zspeed = self.velocity_z;
384         self.velocity_z = 0;
385         xyspeed = vlen(self.velocity);
386         self.velocity = normalize(self.velocity);
387
388         dot = self.velocity * wishdir;
389         k = 32;
390         k *= sv_aircontrol*dot*dot*frametime;
391
392         if(dot > 0) // we can't change direction while slowing down
393                 self.velocity = normalize(self.velocity * xyspeed + wishdir * k);
394
395         self.velocity = self.velocity * xyspeed;
396         self.velocity_z = zspeed;
397 }
398
399 void PM_Accelerate(vector wishdir, float wishspeed, float accel, float accelqw, float sidefric)
400 {
401         float vel_straight;
402         float vel_z;
403         vector vel_perpend;
404         float addspeed;
405
406         vel_straight = self.velocity * wishdir;
407         vel_z = self.velocity_z;
408         vel_perpend = self.velocity - vel_straight * wishdir - vel_z * '0 0 1';
409
410         addspeed = wishspeed - vel_straight;
411         if(addspeed > 0)
412                 vel_straight = vel_straight + min(addspeed, accel * frametime * wishspeed) * accelqw;
413         if(wishspeed > 0)
414                 vel_straight = vel_straight + min(wishspeed, accel * frametime * wishspeed) * (1 - accelqw);
415
416         vel_perpend = vel_perpend * (1 - frametime * wishspeed * sidefric);
417
418         self.velocity = vel_straight * wishdir + vel_z * '0 0 1' + vel_perpend;
419 }
420
421 void PM_AirAccelerate(vector wishdir, float wishspeed)
422 {
423         vector curvel, wishvel, acceldir, curdir;
424         float addspeed, accelspeed, curspeed, f;
425         float dot;
426
427         if(wishspeed == 0)
428                 return;
429
430         curvel = self.velocity;
431         curvel_z = 0;
432         curspeed = vlen(curvel);
433
434         if(wishspeed > curspeed * 1.01)
435         {
436                 wishspeed = min(wishspeed, curspeed + sv_warsowbunny_airforwardaccel * sv_maxspeed * frametime);
437         }
438         else
439         {
440                 f = max(0, (sv_warsowbunny_topspeed - curspeed) / (sv_warsowbunny_topspeed - sv_maxspeed));
441                 wishspeed = max(curspeed, sv_maxspeed) + sv_warsowbunny_accel * f * sv_maxspeed * frametime;
442         }
443         wishvel = wishdir * wishspeed;
444         acceldir = wishvel - curvel;
445         addspeed = vlen(acceldir);
446         acceldir = normalize(acceldir);
447
448         accelspeed = min(addspeed, sv_warsowbunny_turnaccel * sv_maxspeed * frametime);
449
450         if(sv_warsowbunny_backtosideratio < 1)
451         {
452                 curdir = normalize(curvel);
453                 dot = acceldir * curdir;
454                 if(dot < 0)
455                         acceldir = acceldir - (1 - sv_warsowbunny_backtosideratio) * dot * curdir;
456         }
457
458         self.velocity += accelspeed * acceldir;
459 }
460
461 .vector movement_old;
462 .float buttons_old;
463 .vector v_angle_old;
464 .string lastclassname;
465
466 void Nixnex_GiveCurrentWeapon();
467 .float() PlayerPhysplug;
468 void SV_PlayerPhysics()
469 {
470         local vector wishvel, wishdir, v;
471         local float wishspeed, f, maxspd_mod, spd, maxairspd, airaccel, swampspd_mod, shtest_score, buttons;
472         string temps;
473         float buttons_prev;
474         float not_allowed_to_move;
475
476     if(self.PlayerPhysplug)
477         if(self.PlayerPhysplug())
478             return;
479
480         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;
481         if(!sv_maxidle_spectatorsareidle || self.movetype == MOVETYPE_WALK)
482         {
483                 if(buttons != self.buttons_old || self.movement != self.movement_old || self.v_angle != self.v_angle_old)
484                         self.parm_idlesince = time;
485         }
486         buttons_prev = self.buttons_old;
487         self.buttons_old = buttons;
488         self.movement_old = self.movement;
489         self.v_angle_old = self.v_angle;
490
491         if(time < self.nickspamtime)
492         if(self.nickspamcount >= cvar("g_nick_flood_penalty_yellow"))
493         {
494                 // slight annoyance for nick change scripts
495                 self.movement = -1 * self.movement;
496                 self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = self.BUTTON_ZOOM = self.BUTTON_CROUCH = self.BUTTON_HOOK = self.BUTTON_USE = 0;
497
498                 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!
499                 {
500                         self.angles_x = random() * 360;
501                         self.angles_y = random() * 360;
502                         // at least I'm not forcing retardedview by also assigning to angles_z
503                         self.fixangle = 1;
504                 }
505         }
506
507         if(time > self.shtest_next)
508         {
509                 if(self.shtest_next > 0)
510                 {
511                         // self.shtest_accumulator:
512                         //   started at time - SHTEST_DELTA
513                         //   should be at SHTEST_DELTA
514                         shtest_score = self.shtest_accumulator / (SHTEST_DELTA + time - self.shtest_next);
515                         if(shtest_score > SHTEST_THRESHOLD)
516                                 print("TIME PARADOX: shtest for ", self.netname, " said ", ftos(shtest_score), "\n");
517                         else if(cvar("developer_shtest"))
518                                 dprint("okay: shtest for ", self.netname, " said ", ftos(shtest_score), "\n");
519                 }
520                 self.shtest_next = time + SHTEST_DELTA;
521                 self.shtest_accumulator = 0;
522         }
523         self.shtest_accumulator += frametime;
524
525         if (clienttype(self) == CLIENTTYPE_BOT)
526                 bot_think();
527
528         self.items &~= IT_USING_JETPACK;
529
530         if(self.classname == "player")
531         {
532                 if(self.race_penalty)
533                         if(time > self.race_penalty)
534                                 self.race_penalty = 0;
535
536                 not_allowed_to_move = 0;
537                 if(self.race_penalty)
538                         not_allowed_to_move = 1;
539                 if(!cvar("sv_ready_restart_after_countdown"))
540                 if(time < game_starttime)
541                         not_allowed_to_move = 1;
542
543                 if(not_allowed_to_move)
544                 {
545                         self.velocity = '0 0 0';
546                         self.movetype = MOVETYPE_NONE;
547                         self.disableclientprediction = 2;
548                 }
549                 else if(self.disableclientprediction == 2)
550                 {
551                         if(self.movetype == MOVETYPE_NONE)
552                                 self.movetype = MOVETYPE_WALK;
553                         self.disableclientprediction = 0;
554                 }
555         }
556
557         if (self.movetype == MOVETYPE_NONE)
558                 return;
559
560         if (self.punchangle != '0 0 0')
561         {
562                 f = vlen(self.punchangle) - 10 * frametime;
563                 if (f > 0)
564                         self.punchangle = normalize(self.punchangle) * f;
565                 else
566                         self.punchangle = '0 0 0';
567         }
568
569         if (self.punchvector != '0 0 0')
570         {
571                 f = vlen(self.punchvector) - 30 * frametime;
572                 if (f > 0)
573                         self.punchvector = normalize(self.punchvector) * f;
574                 else
575                         self.punchvector = '0 0 0';
576         }
577
578         maxspd_mod = 1;
579
580         if(g_runematch)
581         {
582                 if(self.runes & RUNE_SPEED)
583                 {
584                         if(self.runes & CURSE_SLOW)
585                                 maxspd_mod = maxspd_mod * cvar("g_balance_rune_speed_combo_moverate");
586                         else
587                                 maxspd_mod = maxspd_mod * cvar("g_balance_rune_speed_moverate");
588                 }
589                 else if(self.runes & CURSE_SLOW)
590                 {
591                         maxspd_mod = maxspd_mod * cvar("g_balance_curse_slow_moverate");
592                 }
593         }
594
595         if(g_minstagib && (self.items & IT_INVINCIBLE))
596         {
597                 maxspd_mod = cvar("g_minstagib_speed_moverate");
598         }
599
600         if(g_nexball && self.ballcarried)
601         {
602                 maxspd_mod = cvar("g_nexball_basketball_carrier_speed");
603         }
604
605         swampspd_mod = 1;
606         if(self.in_swamp) {
607                 swampspd_mod = self.swamp_slowdown; //cvar("g_balance_swamp_moverate");
608         }
609
610         if(self.classname != "player")
611         {
612                 maxspd_mod = cvar("sv_spectator_speed_multiplier");
613                 if(!self.spectatorspeed)
614                         self.spectatorspeed = maxspd_mod;
615                 if(self.impulse && self.impulse <= 19)
616                 {
617                         if(self.lastclassname != "player")
618                         {
619                                 if(self.impulse == 10 || self.impulse == 15 || self.impulse == 18)
620                                         self.spectatorspeed = bound(1, self.spectatorspeed + 0.5, 5);
621                                 else if(self.impulse == 11)
622                                         self.spectatorspeed = maxspd_mod;
623                                 else if(self.impulse == 12 || self.impulse == 16  || self.impulse == 19)
624                                         self.spectatorspeed = bound(1, self.spectatorspeed - 0.5, 5);
625                                 else if(self.impulse >= 1 && self.impulse <= 9)
626                                         self.spectatorspeed = 1 + 0.5 * (self.impulse - 1);
627                         } // otherwise just clear
628                         self.impulse = 0;
629                 }
630                 maxspd_mod = self.spectatorspeed;
631         }
632
633         spd = max(sv_maxspeed, sv_maxairspeed) * maxspd_mod * swampspd_mod;
634         if(self.speed != spd)
635         {
636                 self.speed = spd;
637                 temps = ftos(spd);
638                 stuffcmd(self, strcat("cl_forwardspeed ", temps, "\n"));
639                 stuffcmd(self, strcat("cl_backspeed ", temps, "\n"));
640                 stuffcmd(self, strcat("cl_sidespeed ", temps, "\n"));
641                 stuffcmd(self, strcat("cl_upspeed ", temps, "\n"));
642         }
643
644         maxspd_mod *= swampspd_mod; // only one common speed modder please!
645         swampspd_mod = 1;
646
647         // if dead, behave differently
648         if (self.deadflag)
649                 goto end;
650
651         if (!self.fixangle && !g_bugrigs)
652         {
653                 self.angles_x = 0;
654                 self.angles_y = self.v_angle_y;
655                 self.angles_z = 0;
656         }
657
658         if(self.flags & FL_ONGROUND)
659         if(self.wasFlying)
660         {
661                 self.wasFlying = 0;
662
663                 if(self.waterlevel < WATERLEVEL_SWIMMING)
664                 if(time >= self.ladder_time)
665                 if not(self.hook)
666                 {
667                         self.nextstep = time + 0.3 + random() * 0.1;
668                         trace_dphitq3surfaceflags = 0;
669                         tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
670                         if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS)
671                         {
672                                 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
673                                         GlobalSound(globalsound_metalfall, CHAN_PLAYER, VOICETYPE_PLAYERSOUND);
674                                 else
675                                         GlobalSound(globalsound_fall, CHAN_PLAYER, VOICETYPE_PLAYERSOUND);
676                         }
677                 }
678         }
679
680         if(IsFlying(self))
681                 self.wasFlying = 1;
682
683         if(self.classname == "player")
684         {
685                 if(sv_doublejump)
686                 {
687                         self.flags &~= FL_ONGROUND;
688                         tracebox(self.origin + '0 0 1', self.mins, self.maxs, self.origin - '0 0 2', MOVE_NORMAL, self);
689                         if(trace_fraction < 1 && trace_plane_normal_z > 0.7)
690                                 self.flags |= FL_ONGROUND;
691                 }
692
693                 if (self.BUTTON_JUMP)
694                         PlayerJump ();
695                 else
696                         self.flags |= FL_JUMPRELEASED;
697
698                 if (self.waterlevel == WATERLEVEL_SWIMMING)
699                         CheckWaterJump ();
700         }
701
702         if (self.flags & FL_WATERJUMP )
703         {
704                 self.velocity_x = self.movedir_x;
705                 self.velocity_y = self.movedir_y;
706                 if (time > self.teleport_time || self.waterlevel == WATERLEVEL_NONE)
707                 {
708                         self.flags &~= FL_WATERJUMP;
709                         self.teleport_time = 0;
710                 }
711         }
712         else if (g_bugrigs && self.classname == "player")
713         {
714                 RaceCarPhysics();
715         }
716         else if (self.movetype == MOVETYPE_NOCLIP || self.movetype == MOVETYPE_FLY)
717         {
718                 // noclipping or flying
719                 self.flags &~= FL_ONGROUND;
720
721                 self.velocity = self.velocity * (1 - frametime * sv_friction);
722                 makevectors(self.v_angle);
723                 //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
724                 wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;
725                 // acceleration
726                 wishdir = normalize(wishvel);
727                 wishspeed = vlen(wishvel);
728                 if (wishspeed > sv_maxspeed*maxspd_mod)
729                         wishspeed = sv_maxspeed*maxspd_mod;
730                 if (time >= self.teleport_time)
731                         PM_Accelerate(wishdir, wishspeed, sv_accelerate*maxspd_mod, 1, 0);
732         }
733         else if (self.waterlevel >= WATERLEVEL_SWIMMING)
734         {
735                 // swimming
736                 self.flags &~= FL_ONGROUND;
737
738                 makevectors(self.v_angle);
739                 //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
740                 wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;
741                 if (wishvel == '0 0 0')
742                         wishvel = '0 0 -60'; // drift towards bottom
743
744                 wishdir = normalize(wishvel);
745                 wishspeed = vlen(wishvel);
746                 if (wishspeed > sv_maxspeed*maxspd_mod)
747                         wishspeed = sv_maxspeed*maxspd_mod;
748                 wishspeed = wishspeed * 0.7;
749
750                 // water friction
751                 self.velocity = self.velocity * (1 - frametime * sv_friction);
752
753                 // water acceleration
754                 PM_Accelerate(wishdir, wishspeed, sv_accelerate*maxspd_mod, 1, 0);
755         }
756         else if (time < self.ladder_time)
757         {
758                 // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
759                 self.flags &~= FL_ONGROUND;
760
761                 self.velocity = self.velocity * (1 - frametime * sv_friction);
762                 makevectors(self.v_angle);
763                 //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
764                 wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;
765                 if (self.gravity)
766                         self.velocity_z = self.velocity_z + self.gravity * sv_gravity * frametime;
767                 else
768                         self.velocity_z = self.velocity_z + sv_gravity * frametime;
769                 if (self.ladder_entity.classname == "func_water")
770                 {
771                         f = vlen(wishvel);
772                         if (f > self.ladder_entity.speed)
773                                 wishvel = wishvel * (self.ladder_entity.speed / f);
774
775                         self.watertype = self.ladder_entity.skin;
776                         f = self.ladder_entity.origin_z + self.ladder_entity.maxs_z;
777                         if ((self.origin_z + self.view_ofs_z) < f)
778                                 self.waterlevel = WATERLEVEL_SUBMERGED;
779                         else if ((self.origin_z + (self.mins_z + self.maxs_z) * 0.5) < f)
780                                 self.waterlevel = WATERLEVEL_SWIMMING;
781                         else if ((self.origin_z + self.mins_z + 1) < f)
782                                 self.waterlevel = WATERLEVEL_WETFEET;
783                         else
784                         {
785                                 self.waterlevel = WATERLEVEL_NONE;
786                                 self.watertype = CONTENT_EMPTY;
787                         }
788                 }
789                 // acceleration
790                 wishdir = normalize(wishvel);
791                 wishspeed = vlen(wishvel);
792                 if (wishspeed > sv_maxspeed*maxspd_mod)
793                         wishspeed = sv_maxspeed*maxspd_mod;
794                 if (time >= self.teleport_time)
795                 {
796                         // water acceleration
797                         PM_Accelerate(wishdir, wishspeed, sv_accelerate*maxspd_mod, 1, 0);
798                 }
799         }
800         else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!cvar("g_jetpack_fuel") || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO))
801         {
802                 //makevectors(self.v_angle_y * '0 1 0');
803                 makevectors(self.v_angle);
804                 wishvel = v_forward * self.movement_x + v_right * self.movement_y;
805                 // add remaining speed as Z component
806                 maxairspd = sv_maxairspeed*max(1, maxspd_mod);
807                 // fix speedhacks :P
808                 wishvel = normalize(wishvel) * min(vlen(wishvel) / maxairspd, 1);
809                 // add the unused velocity as up component
810                 wishvel_z = 0;
811
812                 // if(self.BUTTON_JUMP)
813                         wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
814
815                 // it is now normalized, so...
816                 float a_side, a_up, a_add, a_diff;
817                 a_side = cvar("g_jetpack_acceleration_side");
818                 a_up = cvar("g_jetpack_acceleration_up");
819                 a_add = cvar("g_jetpack_antigravity") * sv_gravity;
820
821                 wishvel_x *= a_side;
822                 wishvel_y *= a_side;
823                 wishvel_z *= a_up;
824                 wishvel_z += a_add;
825
826                 float best;
827                 best = 0;
828                 //////////////////////////////////////////////////////////////////////////////////////
829                 // finding the maximum over all vectors of above form
830                 // with wishvel having an absolute value of 1
831                 //////////////////////////////////////////////////////////////////////////////////////
832                 // we're finding the maximum over
833                 //   f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
834                 // for z in the range from -1 to 1
835                 //////////////////////////////////////////////////////////////////////////////////////
836                 // maximum is EITHER attained at the single extreme point:
837                 a_diff = a_side * a_side - a_up * a_up;
838                 if(a_diff != 0)
839                 {
840                         f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
841                         if(f > -1 && f < 1) // can it be attained?
842                         {
843                                 best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
844                                 //print("middle\n");
845                         }
846                 }
847                 // OR attained at z = 1:
848                 f = (a_up + a_add) * (a_up + a_add);
849                 if(f > best)
850                 {
851                         best = f;
852                         //print("top\n");
853                 }
854                 // OR attained at z = -1:
855                 f = (a_up - a_add) * (a_up - a_add);
856                 if(f > best)
857                 {
858                         best = f;
859                         //print("bottom\n");
860                 }
861                 best = sqrt(best);
862                 //////////////////////////////////////////////////////////////////////////////////////
863
864                 //print("best possible acceleration: ", ftos(best), "\n");
865
866                 float fxy, fz;
867                 fxy = bound(0, 1 - (self.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / cvar("g_jetpack_maxspeed_side"), 1);
868                 if(wishvel_z - sv_gravity > 0)
869                         fz = bound(0, 1 - self.velocity_z / cvar("g_jetpack_maxspeed_up"), 1);
870                 else
871                         fz = bound(0, 1 + self.velocity_z / cvar("g_jetpack_maxspeed_up"), 1);
872
873                 float fvel;
874                 fvel = vlen(wishvel);
875                 wishvel_x *= fxy;
876                 wishvel_y *= fxy;
877                 wishvel_z = (wishvel_z - sv_gravity) * fz + sv_gravity;
878
879                 fvel = min(1, vlen(wishvel) / best);
880                 if(cvar("g_jetpack_fuel") && !(self.items & IT_UNLIMITED_WEAPON_AMMO))
881                         f = min(1, self.ammo_fuel / (cvar("g_jetpack_fuel") * frametime * fvel));
882                 else
883                         f = 1;
884
885                 //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
886
887                 if (f > 0 && wishvel != '0 0 0')
888                 {
889                         self.velocity = self.velocity + wishvel * f * frametime;
890                         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
891                                 self.ammo_fuel -= cvar("g_jetpack_fuel") * frametime * fvel * f;
892                         self.flags &~= FL_ONGROUND;
893                         self.items |= IT_USING_JETPACK;
894
895                         // jetpack also inhibits health regeneration, but only for 1 second
896                         self.pauseregen_finished = max(self.pauseregen_finished, time + cvar("g_balance_pause_fuel_regen"));
897                 }
898         }
899         else if (self.flags & FL_ONGROUND)
900         {
901                 // we get here if we ran out of ammo
902                 if((self.items & IT_JETPACK) && self.BUTTON_HOOK && !(buttons_prev & 32))
903                         sprint(self, "You don't have any fuel for the ^2Jetpack\n");
904
905                 // walking
906                 makevectors(self.v_angle_y * '0 1 0');
907                 wishvel = v_forward * self.movement_x + v_right * self.movement_y;
908
909                 if(!(self.lastflags & FL_ONGROUND))
910                 {
911                         if(cvar("speedmeter"))
912                                 dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
913                         if(self.lastground < time - 0.3)
914                                 self.velocity = self.velocity * (1 - cvar("sv_friction_on_land"));
915                         if(self.jumppadcount > 1)
916                                 dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
917                         self.jumppadcount = 0;
918                 }
919
920                 if (self.velocity_x || self.velocity_y)
921                 if (!(self.flags & FL_JUMPRELEASED) || !self.BUTTON_JUMP)
922                 {
923                         v = self.velocity;
924                         v_z = 0;
925                         f = vlen(v);
926                         if (f < sv_stopspeed)
927                                 f = 1 - frametime * (sv_stopspeed / f) * sv_friction;
928                         else
929                                 f = 1 - frametime * sv_friction;
930                         if (f > 0)
931                                 self.velocity = self.velocity * f;
932                         else
933                                 self.velocity = '0 0 0';
934                 }
935                 // acceleration
936                 wishdir = normalize(wishvel);
937                 wishspeed = vlen(wishvel);
938                 if (wishspeed > sv_maxspeed*maxspd_mod)
939                         wishspeed = sv_maxspeed*maxspd_mod;
940                 if (self.crouch)
941                         wishspeed = wishspeed * 0.5;
942                 if (time >= self.teleport_time)
943                         PM_Accelerate(wishdir, wishspeed, sv_accelerate*maxspd_mod, 1, 0);
944         }
945         else
946         {
947                 // we get here if we ran out of ammo
948                 if((self.items & IT_JETPACK) && self.BUTTON_HOOK && !(buttons_prev & 32))
949                         sprint(self, "You don't have any fuel for the ^2Jetpack\n");
950
951                 if(maxspd_mod < 1)
952                 {
953                         maxairspd = sv_maxairspeed*maxspd_mod;
954                         airaccel = sv_airaccelerate*maxspd_mod;
955                 }
956                 else
957                 {
958                         maxairspd = sv_maxairspeed;
959                         airaccel = sv_airaccelerate;
960                 }
961                 // airborn
962                 makevectors(self.v_angle_y * '0 1 0');
963                 wishvel = v_forward * self.movement_x + v_right * self.movement_y;
964                 // acceleration
965                 wishdir = normalize(wishvel);
966                 wishspeed = vlen(wishvel);
967                 if (wishspeed > maxairspd)
968                         wishspeed = maxairspd;
969                 if (self.crouch)
970                         wishspeed = wishspeed * 0.5;
971                 if (time >= self.teleport_time)
972                 {
973                         float accelerating;
974                         float wishspeed2;
975                         float airaccelqw;
976
977                         airaccelqw = sv_airaccel_qw;
978                         accelerating = (self.velocity * wishdir > 0);
979                         wishspeed2 = wishspeed;
980
981                         // CPM
982                         if(sv_airstopaccelerate)
983                                 if(self.velocity * wishdir < 0)
984                                         airaccel = sv_airstopaccelerate*maxspd_mod;
985                         if(self.movement_x == 0 && self.movement_y != 0)
986                         {
987                                 if(sv_maxairstrafespeed)
988                                 {
989                                         wishspeed = min(wishspeed, sv_maxairstrafespeed*maxspd_mod);
990                                         if(sv_maxairstrafespeed < sv_maxairspeed)
991                                                 airaccelqw = 1;
992                                 }
993                                 if(sv_airstrafeaccelerate)
994                                 {
995                                         airaccel = sv_airstrafeaccelerate*maxspd_mod;
996                                         if(sv_airstrafeaccelerate > sv_airaccelerate)
997                                                 airaccelqw = 1;
998                                 }
999                         }
1000                         // !CPM
1001
1002                         if(sv_warsowbunny_turnaccel && accelerating && self.movement_y == 0 && self.movement_x != 0)
1003                                 PM_AirAccelerate(wishdir, wishspeed);
1004                         else
1005                                 PM_Accelerate(wishdir, wishspeed, airaccel, airaccelqw, sv_airaccel_sideways_friction / maxairspd);
1006
1007                         if(sv_aircontrol)
1008                                 CPM_PM_Aircontrol(wishdir, wishspeed2);
1009                 }
1010         }
1011
1012 :end
1013         if(self.flags & FL_ONGROUND)
1014                 self.lastground = time;
1015
1016         self.lastflags = self.flags;
1017         self.lastclassname = self.classname;
1018 };