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