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