]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/cl_physics.qc
new gib types: alien and robot_shiny, and blood types, depending on playermodel
[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 void SV_PlayerPhysics()
466 {
467         local vector wishvel, wishdir, v;
468         local float wishspeed, f, maxspd_mod, spd, maxairspd, airaccel, swampspd_mod, shtest_score, buttons;
469         string temps;
470         float buttons_prev;
471
472         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;
473         if(!sv_maxidle_spectatorsareidle || self.movetype == MOVETYPE_WALK)
474         {
475                 if(buttons != self.buttons_old || self.movement != self.movement_old || self.v_angle != self.v_angle_old)
476                         self.parm_idlesince = time;
477         }
478         buttons_prev = self.buttons_old;
479         self.buttons_old = buttons;
480         self.movement_old = self.movement;
481         self.v_angle_old = self.v_angle;
482
483         if(time < self.nickspamtime)
484         if(self.nickspamcount >= cvar("g_nick_flood_penalty_yellow"))
485         {
486                 // slight annoyance for nick change scripts
487                 self.movement = -1 * self.movement;
488                 self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = self.BUTTON_ZOOM = self.BUTTON_CROUCH = self.BUTTON_HOOK = self.BUTTON_USE = 0;
489
490                 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!
491                 {
492                         self.angles_x = random() * 360;
493                         self.angles_y = random() * 360;
494                         // at least I'm not forcing retardedview by also assigning to angles_z
495                         self.fixangle = 1;
496                 }
497         }
498
499         if(time > self.shtest_next)
500         {
501                 if(self.shtest_next > 0)
502                 {
503                         // self.shtest_accumulator:
504                         //   started at time - SHTEST_DELTA
505                         //   should be at SHTEST_DELTA
506                         shtest_score = self.shtest_accumulator / (SHTEST_DELTA + time - self.shtest_next);
507                         if(shtest_score > SHTEST_THRESHOLD)
508                                 print("TIME PARADOX: shtest for ", self.netname, " said ", ftos(shtest_score), "\n");
509                         else if(cvar("developer_shtest"))
510                                 dprint("okay: shtest for ", self.netname, " said ", ftos(shtest_score), "\n");
511                 }
512                 self.shtest_next = time + SHTEST_DELTA;
513                 self.shtest_accumulator = 0;
514         }
515         self.shtest_accumulator += frametime;
516
517         if (clienttype(self) == CLIENTTYPE_BOT)
518                 bot_think();
519
520         self.items &~= IT_USING_JETPACK;
521
522         if (self.movetype == MOVETYPE_NONE && self.disableclientprediction != 2)
523                 return;
524
525         if (self.punchangle != '0 0 0')
526         {
527                 f = vlen(self.punchangle) - 10 * frametime;
528                 if (f > 0)
529                         self.punchangle = normalize(self.punchangle) * f;
530                 else
531                         self.punchangle = '0 0 0';
532         }
533
534         if (self.punchvector != '0 0 0')
535         {
536                 f = vlen(self.punchvector) - 30 * frametime;
537                 if (f > 0)
538                         self.punchvector = normalize(self.punchvector) * f;
539                 else
540                         self.punchvector = '0 0 0';
541         }
542
543         maxspd_mod = 1;
544
545         if(g_runematch)
546         {
547                 if(self.runes & RUNE_SPEED)
548                 {
549                         if(self.runes & CURSE_SLOW)
550                                 maxspd_mod = maxspd_mod * cvar("g_balance_rune_speed_combo_moverate");
551                         else
552                                 maxspd_mod = maxspd_mod * cvar("g_balance_rune_speed_moverate");
553                 }
554                 else if(self.runes & CURSE_SLOW)
555                 {
556                         maxspd_mod = maxspd_mod * cvar("g_balance_curse_slow_moverate");
557                 }
558         }
559
560         if(g_minstagib && (self.items & IT_INVINCIBLE))
561         {
562                 maxspd_mod = cvar("g_minstagib_speed_moverate");
563         }
564
565         if(g_nexball && self.ballcarried)
566         {
567                 maxspd_mod = cvar("g_nexball_basketball_carrier_speed");
568         }
569
570         swampspd_mod = 1;
571         if(self.in_swamp) {
572                 swampspd_mod = self.swamp_slowdown; //cvar("g_balance_swamp_moverate");
573         }
574
575         if(self.classname != "player")
576         {
577                 maxspd_mod = cvar("sv_spectator_speed_multiplier");
578                 if(!self.spectatorspeed)
579                         self.spectatorspeed = maxspd_mod;
580                 if(self.impulse && self.impulse <= 19)
581                 {
582                         if(self.lastclassname != "player")
583                         {
584                                 if(self.impulse == 10 || self.impulse == 15 || self.impulse == 18)
585                                         self.spectatorspeed = bound(1, self.spectatorspeed + 0.5, 5);
586                                 else if(self.impulse == 11)
587                                         self.spectatorspeed = maxspd_mod;
588                                 else if(self.impulse == 12 || self.impulse == 16  || self.impulse == 19)
589                                         self.spectatorspeed = bound(1, self.spectatorspeed - 0.5, 5);
590                                 else if(self.impulse >= 1 && self.impulse <= 9)
591                                         self.spectatorspeed = 1 + 0.5 * (self.impulse - 1);
592                         } // otherwise just clear
593                         self.impulse = 0;
594                 }
595                 maxspd_mod = self.spectatorspeed;
596         }
597
598         spd = max(sv_maxspeed, sv_maxairspeed) * maxspd_mod * swampspd_mod;
599         if(self.speed != spd)
600         {
601                 self.speed = spd;
602                 temps = ftos(spd);
603                 stuffcmd(self, strcat("cl_forwardspeed ", temps, "\n"));
604                 stuffcmd(self, strcat("cl_backspeed ", temps, "\n"));
605                 stuffcmd(self, strcat("cl_sidespeed ", temps, "\n"));
606                 stuffcmd(self, strcat("cl_upspeed ", temps, "\n"));
607         }
608
609         maxspd_mod *= swampspd_mod; // only one common speed modder please!
610         swampspd_mod = 1;
611
612         // if dead, behave differently
613         if (self.deadflag)
614                 goto end;
615
616         if (!self.fixangle && !g_bugrigs)
617         {
618                 self.angles_x = 0;
619                 self.angles_y = self.v_angle_y;
620                 self.angles_z = 0;
621         }
622
623         if(self.flags & FL_ONGROUND)
624         if(self.wasFlying)
625         {
626                 self.wasFlying = 0;
627
628                 if(self.waterlevel < WATERLEVEL_SWIMMING)
629                 if(time >= self.ladder_time)
630                 if not(self.hook)
631                 {
632                         self.nextstep = time + 0.3 + random() * 0.1;
633                         trace_dphitq3surfaceflags = 0;
634                         tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
635                         if not(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS)
636                         {
637                                 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
638                                         GlobalSound(globalsound_metalfall, CHAN_PLAYER, VOICETYPE_PLAYERSOUND);
639                                 else
640                                         GlobalSound(globalsound_fall, CHAN_PLAYER, VOICETYPE_PLAYERSOUND);
641                         }
642                 }
643         }
644
645         if(IsFlying(self))
646                 self.wasFlying = 1;
647
648         if(self.classname == "player")
649         {
650                 if(sv_doublejump)
651                 {
652                         self.flags &~= FL_ONGROUND;
653                         tracebox(self.origin + '0 0 1', self.mins, self.maxs, self.origin - '0 0 2', MOVE_NORMAL, self);
654                         if(trace_fraction < 1 && trace_plane_normal_z > 0.7)
655                                 self.flags |= FL_ONGROUND;
656                 }
657
658                 if (self.BUTTON_JUMP)
659                         PlayerJump ();
660                 else
661                         self.flags |= FL_JUMPRELEASED;
662
663                 if (self.waterlevel == WATERLEVEL_SWIMMING)
664                         CheckWaterJump ();
665         }
666
667         if (self.flags & FL_WATERJUMP )
668         {
669                 self.velocity_x = self.movedir_x;
670                 self.velocity_y = self.movedir_y;
671                 if (time > self.teleport_time || self.waterlevel == WATERLEVEL_NONE)
672                 {
673                         self.flags &~= FL_WATERJUMP;
674                         self.teleport_time = 0;
675                 }
676         }
677         else if (g_bugrigs && self.classname == "player")
678         {
679                 RaceCarPhysics();
680         }
681         else if (self.movetype == MOVETYPE_NOCLIP || self.movetype == MOVETYPE_FLY)
682         {
683                 // noclipping or flying
684                 self.flags &~= FL_ONGROUND;
685
686                 self.velocity = self.velocity * (1 - frametime * sv_friction);
687                 makevectors(self.v_angle);
688                 //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
689                 wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;
690                 // acceleration
691                 wishdir = normalize(wishvel);
692                 wishspeed = vlen(wishvel);
693                 if (wishspeed > sv_maxspeed*maxspd_mod)
694                         wishspeed = sv_maxspeed*maxspd_mod;
695                 if (time >= self.teleport_time)
696                         PM_Accelerate(wishdir, wishspeed, sv_accelerate*maxspd_mod, 1, 0);
697         }
698         else if (self.waterlevel >= WATERLEVEL_SWIMMING)
699         {
700                 // swimming
701                 self.flags &~= FL_ONGROUND;
702
703                 makevectors(self.v_angle);
704                 //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
705                 wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;
706                 if (wishvel == '0 0 0')
707                         wishvel = '0 0 -60'; // drift towards bottom
708
709                 wishdir = normalize(wishvel);
710                 wishspeed = vlen(wishvel);
711                 if (wishspeed > sv_maxspeed*maxspd_mod)
712                         wishspeed = sv_maxspeed*maxspd_mod;
713                 wishspeed = wishspeed * 0.7;
714
715                 // water friction
716                 self.velocity = self.velocity * (1 - frametime * sv_friction);
717
718                 // water acceleration
719                 PM_Accelerate(wishdir, wishspeed, sv_accelerate*maxspd_mod, 1, 0);
720         }
721         else if (time < self.ladder_time)
722         {
723                 // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
724                 self.flags &~= FL_ONGROUND;
725
726                 self.velocity = self.velocity * (1 - frametime * sv_friction);
727                 makevectors(self.v_angle);
728                 //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
729                 wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;
730                 if (self.gravity)
731                         self.velocity_z = self.velocity_z + self.gravity * sv_gravity * frametime;
732                 else
733                         self.velocity_z = self.velocity_z + sv_gravity * frametime;
734                 if (self.ladder_entity.classname == "func_water")
735                 {
736                         f = vlen(wishvel);
737                         if (f > self.ladder_entity.speed)
738                                 wishvel = wishvel * (self.ladder_entity.speed / f);
739
740                         self.watertype = self.ladder_entity.skin;
741                         f = self.ladder_entity.origin_z + self.ladder_entity.maxs_z;
742                         if ((self.origin_z + self.view_ofs_z) < f)
743                                 self.waterlevel = WATERLEVEL_SUBMERGED;
744                         else if ((self.origin_z + (self.mins_z + self.maxs_z) * 0.5) < f)
745                                 self.waterlevel = WATERLEVEL_SWIMMING;
746                         else if ((self.origin_z + self.mins_z + 1) < f)
747                                 self.waterlevel = WATERLEVEL_WETFEET;
748                         else
749                         {
750                                 self.waterlevel = WATERLEVEL_NONE;
751                                 self.watertype = CONTENT_EMPTY;
752                         }
753                 }
754                 // acceleration
755                 wishdir = normalize(wishvel);
756                 wishspeed = vlen(wishvel);
757                 if (wishspeed > sv_maxspeed*maxspd_mod)
758                         wishspeed = sv_maxspeed*maxspd_mod;
759                 if (time >= self.teleport_time)
760                 {
761                         // water acceleration
762                         PM_Accelerate(wishdir, wishspeed, sv_accelerate*maxspd_mod, 1, 0);
763                 }
764         }
765         else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!cvar("g_jetpack_fuel") || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO))
766         {
767                 //makevectors(self.v_angle_y * '0 1 0');
768                 makevectors(self.v_angle);
769                 wishvel = v_forward * self.movement_x + v_right * self.movement_y;
770                 // add remaining speed as Z component
771                 maxairspd = sv_maxairspeed*max(1, maxspd_mod);
772                 // fix speedhacks :P
773                 wishvel = normalize(wishvel) * min(vlen(wishvel) / maxairspd, 1);
774                 // add the unused velocity as up component
775                 wishvel_z = 0;
776
777                 // if(self.BUTTON_JUMP)
778                         wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
779
780                 // it is now normalized, so...
781                 float a_side, a_up, a_add, a_diff;
782                 a_side = cvar("g_jetpack_acceleration_side");
783                 a_up = cvar("g_jetpack_acceleration_up");
784                 a_add = cvar("g_jetpack_antigravity") * sv_gravity;
785
786                 wishvel_x *= a_side;
787                 wishvel_y *= a_side;
788                 wishvel_z *= a_up;
789                 wishvel_z += a_add;
790
791                 float best;
792                 best = 0;
793                 //////////////////////////////////////////////////////////////////////////////////////
794                 // finding the maximum over all vectors of above form
795                 // with wishvel having an absolute value of 1
796                 //////////////////////////////////////////////////////////////////////////////////////
797                 // we're finding the maximum over
798                 //   f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
799                 // for z in the range from -1 to 1
800                 //////////////////////////////////////////////////////////////////////////////////////
801                 // maximum is EITHER attained at the single extreme point:
802                 a_diff = a_side * a_side - a_up * a_up;
803                 if(a_diff != 0)
804                 {
805                         f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
806                         if(f > -1 && f < 1) // can it be attained?
807                         {
808                                 best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
809                                 //print("middle\n");
810                         }
811                 }
812                 // OR attained at z = 1:
813                 f = (a_up + a_add) * (a_up + a_add);
814                 if(f > best)
815                 {
816                         best = f;
817                         //print("top\n");
818                 }
819                 // OR attained at z = -1:
820                 f = (a_up - a_add) * (a_up - a_add);
821                 if(f > best)
822                 {
823                         best = f;
824                         //print("bottom\n");
825                 }
826                 best = sqrt(best);
827                 //////////////////////////////////////////////////////////////////////////////////////
828
829                 //print("best possible acceleration: ", ftos(best), "\n");
830
831                 float fxy, fz;
832                 fxy = bound(0, 1 - (self.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / cvar("g_jetpack_maxspeed_side"), 1);
833                 if(wishvel_z - sv_gravity > 0)
834                         fz = bound(0, 1 - self.velocity_z / cvar("g_jetpack_maxspeed_up"), 1);
835                 else
836                         fz = bound(0, 1 + self.velocity_z / cvar("g_jetpack_maxspeed_up"), 1);
837
838                 float fvel;
839                 fvel = vlen(wishvel);
840                 wishvel_x *= fxy;
841                 wishvel_y *= fxy;
842                 wishvel_z = (wishvel_z - sv_gravity) * fz + sv_gravity;
843
844                 fvel = min(1, vlen(wishvel) / best);
845                 if(cvar("g_jetpack_fuel") && !(self.items & IT_UNLIMITED_WEAPON_AMMO))
846                         f = min(1, self.ammo_fuel / (cvar("g_jetpack_fuel") * frametime * fvel));
847                 else
848                         f = 1;
849
850                 //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
851
852                 if (f > 0 && wishvel != '0 0 0')
853                 {
854                         self.velocity = self.velocity + wishvel * f * frametime;
855                         if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
856                                 self.ammo_fuel -= cvar("g_jetpack_fuel") * frametime * fvel * f;
857                         self.flags &~= FL_ONGROUND;
858                         self.items |= IT_USING_JETPACK;
859
860                         // jetpack also inhibits health regeneration, but only for 1 second
861                         self.pauseregen_finished = max(self.pauseregen_finished, time + cvar("g_balance_pause_fuel_regen"));
862                 }
863         }
864         else if (self.flags & FL_ONGROUND)
865         {
866                 // we get here if we ran out of ammo
867                 if((self.items & IT_JETPACK) && self.BUTTON_HOOK && !(buttons_prev & 32))
868                         sprint(self, "You don't have any fuel for the ^2Jetpack\n");
869
870                 // walking
871                 makevectors(self.v_angle_y * '0 1 0');
872                 wishvel = v_forward * self.movement_x + v_right * self.movement_y;
873
874                 if(!(self.lastflags & FL_ONGROUND))
875                 {
876                         if(cvar("speedmeter"))
877                                 dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
878                         if(self.lastground < time - 0.3)
879                                 self.velocity = self.velocity * (1 - cvar("sv_friction_on_land"));
880                         if(self.jumppadcount > 1)
881                                 dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
882                         self.jumppadcount = 0;
883                 }
884
885                 if (self.velocity_x || self.velocity_y)
886                 if (!(self.flags & FL_JUMPRELEASED) || !self.BUTTON_JUMP)
887                 {
888                         v = self.velocity;
889                         v_z = 0;
890                         f = vlen(v);
891                         if (f < sv_stopspeed)
892                                 f = 1 - frametime * (sv_stopspeed / f) * sv_friction;
893                         else
894                                 f = 1 - frametime * sv_friction;
895                         if (f > 0)
896                                 self.velocity = self.velocity * f;
897                         else
898                                 self.velocity = '0 0 0';
899                 }
900                 // acceleration
901                 wishdir = normalize(wishvel);
902                 wishspeed = vlen(wishvel);
903                 if (wishspeed > sv_maxspeed*maxspd_mod)
904                         wishspeed = sv_maxspeed*maxspd_mod;
905                 if (self.crouch)
906                         wishspeed = wishspeed * 0.5;
907                 if (time >= self.teleport_time)
908                         PM_Accelerate(wishdir, wishspeed, sv_accelerate*maxspd_mod, 1, 0);
909         }
910         else
911         {
912                 // we get here if we ran out of ammo
913                 if((self.items & IT_JETPACK) && self.BUTTON_HOOK && !(buttons_prev & 32))
914                         sprint(self, "You don't have any fuel for the ^2Jetpack\n");
915
916                 if(maxspd_mod < 1)
917                 {
918                         maxairspd = sv_maxairspeed*maxspd_mod;
919                         airaccel = sv_airaccelerate*maxspd_mod;
920                 }
921                 else
922                 {
923                         maxairspd = sv_maxairspeed;
924                         airaccel = sv_airaccelerate;
925                 }
926                 // airborn
927                 makevectors(self.v_angle_y * '0 1 0');
928                 wishvel = v_forward * self.movement_x + v_right * self.movement_y;
929                 // acceleration
930                 wishdir = normalize(wishvel);
931                 wishspeed = vlen(wishvel);
932                 if (wishspeed > maxairspd)
933                         wishspeed = maxairspd;
934                 if (self.crouch)
935                         wishspeed = wishspeed * 0.5;
936                 if (time >= self.teleport_time)
937                 {
938                         float accelerating;
939                         float wishspeed2;
940
941                         accelerating = (self.velocity * wishdir > 0);
942                         wishspeed2 = wishspeed;
943
944                         // CPM
945                         if(sv_airstopaccelerate)
946                                 if(self.velocity * wishdir < 0)
947                                         airaccel = sv_airstopaccelerate;
948                         if(self.movement_x == 0 && self.movement_y != 0)
949                         {
950                                 if(sv_maxairstrafespeed)
951                                         wishspeed = min(wishspeed, sv_maxairstrafespeed);
952                                 if(sv_airstrafeaccelerate)
953                                         airaccel = sv_airstrafeaccelerate;
954                         }
955                         // !CPM
956
957                         if(sv_warsowbunny_turnaccel && accelerating && self.movement_y == 0 && self.movement_x != 0)
958                                 PM_AirAccelerate(wishdir, wishspeed);
959                         else
960                                 PM_Accelerate(wishdir, wishspeed, airaccel, sv_airaccel_qw, sv_airaccel_sideways_friction / maxairspd);
961
962                         if(sv_aircontrol)
963                                 CPM_PM_Aircontrol(wishdir, wishspeed2);
964                 }
965         }
966
967 :end
968         if(self.flags & FL_ONGROUND)
969                 self.lastground = time;
970
971         self.lastflags = self.flags;
972         self.lastclassname = self.classname;
973 };