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