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