2 #include "triggers/include.qh"
7 #include "../server/miscfunctions.qh"
8 #include "triggers/trigger/viewloc.qh"
10 // client side physics
11 bool Physics_Valid(string thecvar)
13 return autocvar_g_physics_clientselect && strhasword(autocvar_g_physics_clientselect_options, thecvar);
16 float Physics_ClientOption(entity pl, string option)
18 if(Physics_Valid(pl.cvar_cl_physics))
20 string s = sprintf("g_physics_%s_%s", pl.cvar_cl_physics, option);
21 if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
24 if(autocvar_g_physics_clientselect && autocvar_g_physics_clientselect_default)
26 string s = sprintf("g_physics_%s_%s", autocvar_g_physics_clientselect_default, option);
27 if(cvar_type(s) & CVAR_TYPEFLAG_EXISTS)
30 return cvar(strcat("sv_", option));
33 void Physics_UpdateStats(entity this, float maxspd_mod)
35 STAT(MOVEVARS_AIRACCEL_QW, this) = AdjustAirAccelQW(Physics_ClientOption(this, "airaccel_qw"), maxspd_mod);
36 STAT(MOVEVARS_AIRSTRAFEACCEL_QW, this) = (Physics_ClientOption(this, "airstrafeaccel_qw"))
37 ? AdjustAirAccelQW(Physics_ClientOption(this, "airstrafeaccel_qw"), maxspd_mod)
39 STAT(MOVEVARS_AIRSPEEDLIMIT_NONQW, this) = Physics_ClientOption(this, "airspeedlimit_nonqw") * maxspd_mod;
40 STAT(MOVEVARS_MAXSPEED, this) = Physics_ClientOption(this, "maxspeed") * maxspd_mod; // also slow walking
43 // fix some new settings
44 STAT(MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, this) = Physics_ClientOption(this, "airaccel_qw_stretchfactor");
45 STAT(MOVEVARS_MAXAIRSTRAFESPEED, this) = Physics_ClientOption(this, "maxairstrafespeed");
46 STAT(MOVEVARS_MAXAIRSPEED, this) = Physics_ClientOption(this, "maxairspeed");
47 STAT(MOVEVARS_AIRSTRAFEACCELERATE, this) = Physics_ClientOption(this, "airstrafeaccelerate");
48 STAT(MOVEVARS_WARSOWBUNNY_TURNACCEL, this) = Physics_ClientOption(this, "warsowbunny_turnaccel");
49 STAT(MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, this) = Physics_ClientOption(this, "airaccel_sideways_friction");
50 STAT(MOVEVARS_AIRCONTROL, this) = Physics_ClientOption(this, "aircontrol");
51 STAT(MOVEVARS_AIRCONTROL_POWER, this) = Physics_ClientOption(this, "aircontrol_power");
52 STAT(MOVEVARS_AIRCONTROL_PENALTY, this) = Physics_ClientOption(this, "aircontrol_penalty");
53 STAT(MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, this) = Physics_ClientOption(this, "warsowbunny_airforwardaccel");
54 STAT(MOVEVARS_WARSOWBUNNY_TOPSPEED, this) = Physics_ClientOption(this, "warsowbunny_topspeed");
55 STAT(MOVEVARS_WARSOWBUNNY_ACCEL, this) = Physics_ClientOption(this, "warsowbunny_accel");
56 STAT(MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, this) = Physics_ClientOption(this, "warsowbunny_backtosideratio");
57 STAT(MOVEVARS_FRICTION, this) = Physics_ClientOption(this, "friction");
58 STAT(MOVEVARS_ACCELERATE, this) = Physics_ClientOption(this, "accelerate");
59 STAT(MOVEVARS_STOPSPEED, this) = Physics_ClientOption(this, "stopspeed");
60 STAT(MOVEVARS_AIRACCELERATE, this) = Physics_ClientOption(this, "airaccelerate");
61 STAT(MOVEVARS_AIRSTOPACCELERATE, this) = Physics_ClientOption(this, "airstopaccelerate");
62 STAT(MOVEVARS_JUMPVELOCITY, this) = Physics_ClientOption(this, "jumpvelocity");
63 STAT(MOVEVARS_TRACK_CANJUMP, this) = Physics_ClientOption(this, "track_canjump");
67 float IsMoveInDirection(vector mv, float ang) // key mix factor
69 if (mv_x == 0 && mv_y == 0)
70 return 0; // avoid division by zero
71 ang -= RAD2DEG * atan2(mv_y, mv_x);
72 ang = remainder(ang, 360) / 45;
73 return ang > 1 ? 0 : ang < -1 ? 0 : 1 - fabs(ang);
76 float GeomLerp(float a, float lerp, float b)
78 return a == 0 ? (lerp < 1 ? 0 : b)
79 : b == 0 ? (lerp > 0 ? 0 : a)
80 : a * pow(fabs(b / a), lerp);
83 noref float pmove_waterjumptime;
85 const float unstick_count = 27;
86 vector unstick_offsets[unstick_count] =
88 // 1 no nudge (just return the original if this test passes)
91 ' 0.000 0.000 0.125', '0.000 0.000 -0.125',
92 '-0.125 0.000 0.000', '0.125 0.000 0.000',
93 ' 0.000 -0.125 0.000', '0.000 0.125 0.000',
94 // 4 diagonal flat nudges
95 '-0.125 -0.125 0.000', '0.125 -0.125 0.000',
96 '-0.125 0.125 0.000', '0.125 0.125 0.000',
97 // 8 diagonal upward nudges
98 '-0.125 0.000 0.125', '0.125 0.000 0.125',
99 ' 0.000 -0.125 0.125', '0.000 0.125 0.125',
100 '-0.125 -0.125 0.125', '0.125 -0.125 0.125',
101 '-0.125 0.125 0.125', '0.125 0.125 0.125',
102 // 8 diagonal downward nudges
103 '-0.125 0.000 -0.125', '0.125 0.000 -0.125',
104 ' 0.000 -0.125 -0.125', '0.000 0.125 -0.125',
105 '-0.125 -0.125 -0.125', '0.125 -0.125 -0.125',
106 '-0.125 0.125 -0.125', '0.125 0.125 -0.125',
109 void PM_ClientMovement_Unstick()
112 for (i = 0; i < unstick_count; i++)
114 vector neworigin = unstick_offsets[i] + self.origin;
115 tracebox(neworigin, PL_CROUCH_MIN, PL_CROUCH_MAX, neworigin, MOVE_NORMAL, self);
116 if (!trace_startsolid)
118 setorigin(self, neworigin);
124 void PM_ClientMovement_UpdateStatus(bool ground)
126 // make sure player is not stuck
127 PM_ClientMovement_Unstick();
130 if (PHYS_INPUT_BUTTON_CROUCH(self))
132 // wants to crouch, this always works..
133 if (!IS_DUCKED(self))
138 // wants to stand, if currently crouching we need to check for a
142 tracebox(self.origin, PL_MIN, PL_MAX, self.origin, MOVE_NORMAL, self);
143 if (!trace_startsolid)
149 vector origin1 = self.origin + '0 0 1';
150 vector origin2 = self.origin - '0 0 1';
154 tracebox(origin1, self.mins, self.maxs, origin2, MOVE_NORMAL, self);
155 if (trace_fraction < 1.0 && trace_plane_normal_z > 0.7)
159 // this code actually "predicts" an impact; so let's clip velocity first
160 float f = self.velocity * trace_plane_normal;
161 self.velocity -= f * trace_plane_normal;
164 UNSET_ONGROUND(self);
167 // set watertype/waterlevel
168 origin1 = self.origin;
169 origin1_z += self.mins_z + 1;
170 self.waterlevel = WATERLEVEL_NONE;
172 int thepoint = pointcontents(origin1);
174 self.watertype = (thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME);
178 self.waterlevel = WATERLEVEL_WETFEET;
179 origin1_z = self.origin_z + (self.mins_z + self.maxs_z) * 0.5;
180 thepoint = pointcontents(origin1);
181 if(thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME)
183 self.waterlevel = WATERLEVEL_SWIMMING;
184 origin1_z = self.origin_z + 22;
185 thepoint = pointcontents(origin1);
186 if(thepoint == CONTENT_WATER || thepoint == CONTENT_LAVA || thepoint == CONTENT_SLIME)
187 self.waterlevel = WATERLEVEL_SUBMERGED;
191 if(IS_ONGROUND(self) || self.velocity_z <= 0 || pmove_waterjumptime <= 0)
192 pmove_waterjumptime = 0;
195 void PM_ClientMovement_Move()
202 vector currentorigin2;
204 vector primalvelocity;
206 vector trace1_endpos = '0 0 0';
207 vector trace2_endpos = '0 0 0';
208 vector trace3_endpos = '0 0 0';
209 float trace1_fraction = 0;
210 float trace2_fraction = 0;
211 float trace3_fraction = 0;
212 vector trace1_plane_normal = '0 0 0';
213 vector trace2_plane_normal = '0 0 0';
214 vector trace3_plane_normal = '0 0 0';
217 PM_ClientMovement_UpdateStatus(false);
218 primalvelocity = self.velocity;
219 for(bump = 0, t = PHYS_INPUT_TIMELENGTH; bump < 8 && (self.velocity * self.velocity) > 0; bump++)
221 neworigin = self.origin + t * self.velocity;
222 tracebox(self.origin, self.mins, self.maxs, neworigin, MOVE_NORMAL, self);
223 trace1_endpos = trace_endpos;
224 trace1_fraction = trace_fraction;
225 trace1_plane_normal = trace_plane_normal;
226 if(trace1_fraction < 1 && trace1_plane_normal_z == 0)
228 // may be a step or wall, try stepping up
229 // first move forward at a higher level
230 currentorigin2 = self.origin;
231 currentorigin2_z += PHYS_STEPHEIGHT;
232 neworigin2 = neworigin;
233 neworigin2_z += PHYS_STEPHEIGHT;
234 tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
235 trace2_endpos = trace_endpos;
236 trace2_fraction = trace_fraction;
237 trace2_plane_normal = trace_plane_normal;
238 if(!trace_startsolid)
240 // then move down from there
241 currentorigin2 = trace2_endpos;
242 neworigin2 = trace2_endpos;
243 neworigin2_z = self.origin_z;
244 tracebox(currentorigin2, self.mins, self.maxs, neworigin2, MOVE_NORMAL, self);
245 trace3_endpos = trace_endpos;
246 trace3_fraction = trace_fraction;
247 trace3_plane_normal = trace_plane_normal;
248 // accept the new trace if it made some progress
249 if(fabs(trace3_endpos_x - trace1_endpos_x) >= 0.03125 || fabs(trace3_endpos_y - trace1_endpos_y) >= 0.03125)
251 trace1_endpos = trace2_endpos;
252 trace1_fraction = trace2_fraction;
253 trace1_plane_normal = trace2_plane_normal;
254 trace1_endpos = trace3_endpos;
259 // check if it moved at all
260 if(trace1_fraction >= 0.001)
261 setorigin(self, trace1_endpos);
263 // check if it moved all the way
264 if(trace1_fraction == 1)
267 // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
268 // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
269 // this got commented out in a change that supposedly makes the code match QW better
270 // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block
271 if(trace1_plane_normal_z > 0.7)
274 t -= t * trace1_fraction;
276 f = (self.velocity * trace1_plane_normal);
277 self.velocity = self.velocity + -f * trace1_plane_normal;
279 if(pmove_waterjumptime > 0)
280 self.velocity = primalvelocity;
284 void CPM_PM_Aircontrol(vector wishdir, float wishspeed)
286 float k = 32 * (2 * IsMoveInDirection(self.movement, 0) - 1);
290 k *= bound(0, wishspeed / PHYS_MAXAIRSPEED(self), 1);
292 float zspeed = self.velocity_z;
294 float xyspeed = vlen(self.velocity);
295 self.velocity = normalize(self.velocity);
297 float dot = self.velocity * wishdir;
299 if (dot > 0) // we can't change direction while slowing down
301 k *= pow(dot, PHYS_AIRCONTROL_POWER) * PHYS_INPUT_TIMELENGTH;
302 xyspeed = max(0, xyspeed - PHYS_AIRCONTROL_PENALTY * sqrt(max(0, 1 - dot*dot)) * k/32);
303 k *= PHYS_AIRCONTROL;
304 self.velocity = normalize(self.velocity * xyspeed + wishdir * k);
307 self.velocity = self.velocity * xyspeed;
308 self.velocity_z = zspeed;
311 float AdjustAirAccelQW(float accelqw, float factor)
313 return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw);
316 // example config for alternate speed clamping:
317 // sv_airaccel_qw 0.8
318 // sv_airaccel_sideways_friction 0
319 // prvm_globalset server speedclamp_mode 1
321 void PM_Accelerate(vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit)
323 float speedclamp = stretchfactor > 0 ? stretchfactor
324 : accelqw < 0 ? 1 // full clamping, no stretch
327 accelqw = fabs(accelqw);
329 if (GAMEPLAYFIX_Q2AIRACCELERATE)
330 wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
332 float vel_straight = self.velocity * wishdir;
333 float vel_z = self.velocity_z;
334 vector vel_xy = vec2(self.velocity);
335 vector vel_perpend = vel_xy - vel_straight * wishdir;
337 float step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
339 float vel_xy_current = vlen(vel_xy);
341 accelqw = AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
342 float vel_xy_forward = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
343 float vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
344 vel_xy_backward = max(0, vel_xy_backward); // not that it REALLY occurs that this would cause wrong behaviour afterwards
345 vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw);
347 if (sidefric < 0 && (vel_perpend*vel_perpend))
348 // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
350 float f = max(0, 1 + PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
351 float fmin = (vel_xy_backward * vel_xy_backward - vel_straight * vel_straight) / (vel_perpend * vel_perpend);
353 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
354 // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
355 // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
356 // obviously, this cannot be
362 vel_perpend *= max(fmin, f);
366 vel_perpend *= max(0, 1 - PHYS_INPUT_TIMELENGTH * wishspeed * sidefric);
368 vel_xy = vel_straight * wishdir + vel_perpend;
372 float vel_xy_preclamp;
373 vel_xy_preclamp = vlen(vel_xy);
374 if (vel_xy_preclamp > 0) // prevent division by zero
376 vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
377 if (vel_xy_current < vel_xy_preclamp)
378 vel_xy *= (vel_xy_current / vel_xy_preclamp);
382 self.velocity = vel_xy + vel_z * '0 0 1';
385 void PM_AirAccelerate(vector wishdir, float wishspeed)
390 vector curvel = self.velocity;
392 float curspeed = vlen(curvel);
394 if (wishspeed > curspeed * 1.01)
395 wishspeed = min(wishspeed, curspeed + PHYS_WARSOWBUNNY_AIRFORWARDACCEL * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH);
398 float f = max(0, (PHYS_WARSOWBUNNY_TOPSPEED - curspeed) / (PHYS_WARSOWBUNNY_TOPSPEED - PHYS_MAXSPEED(self)));
399 wishspeed = max(curspeed, PHYS_MAXSPEED(self)) + PHYS_WARSOWBUNNY_ACCEL * f * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH;
401 vector wishvel = wishdir * wishspeed;
402 vector acceldir = wishvel - curvel;
403 float addspeed = vlen(acceldir);
404 acceldir = normalize(acceldir);
406 float accelspeed = min(addspeed, PHYS_WARSOWBUNNY_TURNACCEL * PHYS_MAXSPEED(self) * PHYS_INPUT_TIMELENGTH);
408 if (PHYS_WARSOWBUNNY_BACKTOSIDERATIO < 1)
410 vector curdir = normalize(curvel);
411 float dot = acceldir * curdir;
413 acceldir -= (1 - PHYS_WARSOWBUNNY_BACKTOSIDERATIO) * dot * curdir;
416 self.velocity += accelspeed * acceldir;
424 When you press the jump key
425 returns true if handled
430 if (PHYS_FROZEN(self))
431 return true; // no jumping in freezetag when frozen
434 if (self.player_blocked)
435 return true; // no jumping while blocked
438 bool doublejump = false;
439 float mjumpheight = PHYS_JUMPVELOCITY;
441 player_multijump = doublejump;
442 player_jumpheight = mjumpheight;
445 if (MUTATOR_CALLHOOK(PlayerJump, doublejump, mjumpheight)
447 || PM_multijump_checkjump()
451 doublejump = player_multijump;
452 mjumpheight = player_jumpheight;
456 tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
457 if (trace_fraction < 1 && trace_plane_normal_z > 0.7)
461 // we MUST clip velocity here!
463 f = self.velocity * trace_plane_normal;
465 self.velocity -= f * trace_plane_normal;
469 if (self.waterlevel >= WATERLEVEL_SWIMMING)
471 self.velocity_z = PHYS_MAXSPEED(self) * 0.7;
476 if (!IS_ONGROUND(self))
477 return IS_JUMP_HELD(self);
479 bool track_jump = PHYS_CL_TRACK_CANJUMP(self);
480 if(PHYS_TRACK_CANJUMP(self))
484 if (IS_JUMP_HELD(self))
487 // sv_jumpspeedcap_min/sv_jumpspeedcap_max act as baseline
488 // velocity bounds. Final velocity is bound between (jumpheight *
489 // min + jumpheight) and (jumpheight * max + jumpheight);
491 if(PHYS_JUMPSPEEDCAP_MIN != "")
493 float minjumpspeed = mjumpheight * stof(PHYS_JUMPSPEEDCAP_MIN);
495 if (self.velocity_z < minjumpspeed)
496 mjumpheight += minjumpspeed - self.velocity_z;
499 if(PHYS_JUMPSPEEDCAP_MAX != "")
501 // don't do jump speedcaps on ramps to preserve old xonotic ramjump style
502 tracebox(self.origin + '0 0 0.01', self.mins, self.maxs, self.origin - '0 0 0.01', MOVE_NORMAL, self);
504 if (!(trace_fraction < 1 && trace_plane_normal_z < 0.98 && PHYS_JUMPSPEEDCAP_DISABLE_ONRAMPS))
506 float maxjumpspeed = mjumpheight * stof(PHYS_JUMPSPEEDCAP_MAX);
508 if (self.velocity_z > maxjumpspeed)
509 mjumpheight -= self.velocity_z - maxjumpspeed;
513 if (!WAS_ONGROUND(self))
516 if(autocvar_speedmeter)
517 LOG_TRACE(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
519 if(self.lastground < time - 0.3)
521 self.velocity_x *= (1 - PHYS_FRICTION_ONLAND);
522 self.velocity_y *= (1 - PHYS_FRICTION_ONLAND);
525 if(self.jumppadcount > 1)
526 LOG_TRACE(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
527 self.jumppadcount = 0;
531 self.velocity_z += mjumpheight;
533 UNSET_ONGROUND(self);
538 self.oldvelocity_z = self.velocity_z;
540 animdecide_setaction(self, ANIMACTION_JUMP, true);
542 if (autocvar_g_jump_grunt)
543 PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND);
548 void CheckWaterJump()
550 // check for a jump-out-of-water
551 makevectors(self.v_angle);
552 vector start = self.origin;
555 normalize(v_forward);
556 vector end = start + v_forward*24;
557 traceline (start, end, true, self);
558 if (trace_fraction < 1)
560 start_z = start_z + self.maxs_z - 8;
561 end = start + v_forward*24;
562 self.movedir = trace_plane_normal * -50;
563 traceline(start, end, true, self);
564 if (trace_fraction == 1)
565 { // open at eye level
566 self.velocity_z = 225;
567 self.flags |= FL_WATERJUMP;
570 self.teleport_time = time + 2; // safety net
572 pmove_waterjumptime = time + 2;
580 #define JETPACK_JUMP(s) s.cvar_cl_jetpack_jump
582 float autocvar_cl_jetpack_jump;
583 #define JETPACK_JUMP(s) autocvar_cl_jetpack_jump
585 .float jetpack_stopped;
586 // Hack: shouldn't need to know about this
587 .float multijump_count;
588 void CheckPlayerJump()
591 float was_flying = ITEMS_STAT(self) & IT_USING_JETPACK;
593 if (JETPACK_JUMP(self) < 2)
594 ITEMS_STAT(self) &= ~IT_USING_JETPACK;
596 if(PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self))
598 float air_jump = !PlayerJump() || self.multijump_count > 0; // PlayerJump() has important side effects
599 float activate = JETPACK_JUMP(self) && air_jump && PHYS_INPUT_BUTTON_JUMP(self) || PHYS_INPUT_BUTTON_JETPACK(self);
600 float has_fuel = !PHYS_JETPACK_FUEL || PHYS_AMMO_FUEL(self) || ITEMS_STAT(self) & IT_UNLIMITED_WEAPON_AMMO;
602 if (!(ITEMS_STAT(self) & ITEM_Jetpack.m_itemid)) { }
603 else if (self.jetpack_stopped) { }
607 if (was_flying) // TODO: ran out of fuel message
608 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
610 Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_JETPACK_NOFUEL);
612 self.jetpack_stopped = true;
613 ITEMS_STAT(self) &= ~IT_USING_JETPACK;
615 else if (activate && !PHYS_FROZEN(self))
616 ITEMS_STAT(self) |= IT_USING_JETPACK;
620 self.jetpack_stopped = false;
621 ITEMS_STAT(self) &= ~IT_USING_JETPACK;
623 if (!PHYS_INPUT_BUTTON_JUMP(self))
624 UNSET_JUMP_HELD(self);
626 if (self.waterlevel == WATERLEVEL_SWIMMING)
630 float racecar_angle(float forward, float down)
638 float ret = vectoyaw('0 1 0' * down + '1 0 0' * forward);
640 float angle_mult = forward / (800 + forward);
643 return ret * angle_mult + 360 * (1 - angle_mult);
645 return ret * angle_mult;
648 void RaceCarPhysics()
651 // using this move type for "big rigs"
652 // the engine does not push the entity!
656 vector angles_save = self.angles;
657 float accel = bound(-1, self.movement.x / PHYS_MAXSPEED(self), 1);
658 float steer = bound(-1, self.movement.y / PHYS_MAXSPEED(self), 1);
660 if (g_bugrigs_reverse_speeding)
664 // back accel is DIGITAL
665 // to prevent speedhack
675 makevectors(self.angles); // new forward direction!
677 if (IS_ONGROUND(self) || g_bugrigs_air_steering)
679 float myspeed = self.velocity * v_forward;
680 float upspeed = self.velocity * v_up;
682 // responsiveness factor for steering and acceleration
683 float f = 1 / (1 + pow(max(-myspeed, myspeed) / g_bugrigs_speed_ref, g_bugrigs_speed_pow));
684 //MAXIMA: f(v) := 1 / (1 + (v / g_bugrigs_speed_ref) ^ g_bugrigs_speed_pow);
687 if (myspeed < 0 && g_bugrigs_reverse_spinning)
688 steerfactor = -myspeed * g_bugrigs_steer;
690 steerfactor = -myspeed * f * g_bugrigs_steer;
693 if (myspeed < 0 && g_bugrigs_reverse_speeding)
694 accelfactor = g_bugrigs_accel;
696 accelfactor = f * g_bugrigs_accel;
697 //MAXIMA: accel(v) := f(v) * g_bugrigs_accel;
703 myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor - g_bugrigs_friction_brake * accel));
707 if (!g_bugrigs_reverse_speeding)
708 myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
715 myspeed = max(0, myspeed - PHYS_INPUT_TIMELENGTH * g_bugrigs_friction_floor);
719 if (g_bugrigs_reverse_stopping)
722 myspeed = min(0, myspeed + PHYS_INPUT_TIMELENGTH * (g_bugrigs_friction_floor + g_bugrigs_friction_brake * accel));
725 // terminal velocity = velocity at which 50 == accelfactor, that is, 1549 units/sec
726 //MAXIMA: friction(v) := g_bugrigs_friction_floor;
728 self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
729 makevectors(self.angles); // new forward direction!
731 myspeed += accel * accelfactor * PHYS_INPUT_TIMELENGTH;
733 rigvel = myspeed * v_forward + '0 0 1' * upspeed;
737 float myspeed = vlen(self.velocity);
739 // responsiveness factor for steering and acceleration
740 float f = 1 / (1 + pow(max(0, myspeed / g_bugrigs_speed_ref), g_bugrigs_speed_pow));
741 float steerfactor = -myspeed * f;
742 self.angles_y += steer * PHYS_INPUT_TIMELENGTH * steerfactor; // apply steering
744 rigvel = self.velocity;
745 makevectors(self.angles); // new forward direction!
748 rigvel *= max(0, 1 - vlen(rigvel) * g_bugrigs_friction_air * PHYS_INPUT_TIMELENGTH);
749 //MAXIMA: airfriction(v) := v * v * g_bugrigs_friction_air;
750 //MAXIMA: total_acceleration(v) := accel(v) - friction(v) - airfriction(v);
751 //MAXIMA: solve(total_acceleration(v) = 0, v);
753 if (g_bugrigs_planar_movement)
755 vector rigvel_xy, neworigin, up;
758 rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY(this); // 4x gravity plays better
759 rigvel_xy = vec2(rigvel);
761 if (g_bugrigs_planar_movement_car_jumping)
764 mt = MOVE_NOMONSTERS;
766 tracebox(self.origin, self.mins, self.maxs, self.origin + '0 0 1024', mt, self);
767 up = trace_endpos - self.origin;
769 // BUG RIGS: align the move to the surface instead of doing collision testing
771 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos + rigvel_xy * PHYS_INPUT_TIMELENGTH, mt, self);
774 tracebox(trace_endpos, self.mins, self.maxs, trace_endpos - up + '0 0 1' * rigvel_z * PHYS_INPUT_TIMELENGTH, mt, self);
776 if (trace_fraction < 0.5)
779 neworigin = self.origin;
782 neworigin = trace_endpos;
784 if (trace_fraction < 1)
786 // now set angles_x so that the car points parallel to the surface
787 self.angles = vectoangles(
788 '1 0 0' * v_forward_x * trace_plane_normal_z
790 '0 1 0' * v_forward_y * trace_plane_normal_z
792 '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y)
798 // now set angles_x so that the car points forward, but is tilted in velocity direction
799 UNSET_ONGROUND(self);
802 self.velocity = (neworigin - self.origin) * (1.0 / PHYS_INPUT_TIMELENGTH);
803 self.movetype = MOVETYPE_NOCLIP;
807 rigvel_z -= PHYS_INPUT_TIMELENGTH * PHYS_GRAVITY(this); // 4x gravity plays better
808 self.velocity = rigvel;
809 self.movetype = MOVETYPE_FLY;
813 tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 4', MOVE_NORMAL, self);
814 if (trace_fraction != 1)
816 self.angles = vectoangles2(
817 '1 0 0' * v_forward_x * trace_plane_normal_z
819 '0 1 0' * v_forward_y * trace_plane_normal_z
821 '0 0 1' * -(v_forward_x * trace_plane_normal_x + v_forward_y * trace_plane_normal_y),
829 vel_local_x = v_forward * self.velocity;
830 vel_local_y = v_right * self.velocity;
831 vel_local_z = v_up * self.velocity;
833 self.angles_x = racecar_angle(vel_local_x, vel_local_z);
834 self.angles_z = racecar_angle(-vel_local_y, vel_local_z);
838 vector vf1, vu1, smoothangles;
839 makevectors(self.angles);
840 float f = bound(0, PHYS_INPUT_TIMELENGTH * g_bugrigs_angle_smoothing, 1);
845 makevectors(angles_save);
846 vf1 = vf1 + v_forward * (1 - f);
847 vu1 = vu1 + v_up * (1 - f);
848 smoothangles = vectoangles2(vf1, vu1);
849 self.angles_x = -smoothangles_x;
850 self.angles_z = smoothangles_z;
854 string specialcommand = "xwxwxsxsxaxdxaxdx1x ";
855 .float specialcommand_pos;
856 void SpecialCommand()
862 if (!CheatImpulse(99))
863 LOG_INFO("A hollow voice says \"Plugh\".\n");
868 float PM_check_specialcommand(float buttons)
874 else if (buttons == 1)
876 else if (buttons == 2)
878 else if (buttons == 128)
880 else if (buttons == 256)
882 else if (buttons == 512)
884 else if (buttons == 1024)
889 if (c == substring(specialcommand, self.specialcommand_pos, 1))
891 self.specialcommand_pos += 1;
892 if (self.specialcommand_pos >= strlen(specialcommand))
894 self.specialcommand_pos = 0;
899 else if (self.specialcommand_pos && (c != substring(specialcommand, self.specialcommand_pos - 1, 1)))
900 self.specialcommand_pos = 0;
905 void PM_check_nickspam()
908 if (time >= self.nickspamtime)
910 if (self.nickspamcount >= autocvar_g_nick_flood_penalty_yellow)
912 // slight annoyance for nick change scripts
913 self.movement = -1 * self.movement;
914 self.BUTTON_ATCK = self.BUTTON_JUMP = self.BUTTON_ATCK2 = self.BUTTON_ZOOM = self.BUTTON_CROUCH = self.BUTTON_HOOK = self.BUTTON_USE = 0;
916 if (self.nickspamcount >= autocvar_g_nick_flood_penalty_red) // if you are persistent and the slight annoyance above does not stop you, I'll show you!
918 self.v_angle_x = random() * 360;
919 self.v_angle_y = random() * 360;
920 // at least I'm not forcing retardedview by also assigning to angles_z
921 self.fixangle = true;
927 void PM_check_punch()
930 if (self.punchangle != '0 0 0')
932 float f = vlen(self.punchangle) - 10 * PHYS_INPUT_TIMELENGTH;
934 self.punchangle = normalize(self.punchangle) * f;
936 self.punchangle = '0 0 0';
939 if (self.punchvector != '0 0 0')
941 float f = vlen(self.punchvector) - 30 * PHYS_INPUT_TIMELENGTH;
943 self.punchvector = normalize(self.punchvector) * f;
945 self.punchvector = '0 0 0';
950 void PM_check_spider()
953 if (time >= self.spider_slowness)
955 PHYS_MAXSPEED(self) *= 0.5; // half speed while slow from spider
956 PHYS_MAXAIRSPEED(self) *= 0.5;
957 PHYS_AIRSPEEDLIMIT_NONQW(self) *= 0.5;
958 PHYS_AIRSTRAFEACCELERATE(self) *= 0.5;
962 // predict frozen movement, as frozen players CAN move in some cases
963 void PM_check_frozen()
965 if (!PHYS_FROZEN(self))
967 if (PHYS_DODGING_FROZEN
969 && IS_REAL_CLIENT(self)
973 self.movement_x = bound(-5, self.movement.x, 5);
974 self.movement_y = bound(-5, self.movement.y, 5);
975 self.movement_z = bound(-5, self.movement.z, 5);
978 self.movement = '0 0 0';
980 vector midpoint = ((self.absmin + self.absmax) * 0.5);
981 if (pointcontents(midpoint) == CONTENT_WATER)
983 self.velocity = self.velocity * 0.5;
985 if (pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
986 self.velocity_z = 200;
990 void PM_check_hitground()
993 if (IS_ONGROUND(self))
994 if (IS_PLAYER(self)) // no fall sounds for observers thank you very much
998 if (self.waterlevel < WATERLEVEL_SWIMMING)
999 if (time >= self.ladder_time)
1002 self.nextstep = time + 0.3 + random() * 0.1;
1003 trace_dphitq3surfaceflags = 0;
1004 tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
1005 if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS))
1007 if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
1008 GlobalSound(globalsound_metalfall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
1010 GlobalSound(globalsound_fall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
1017 void PM_check_blocked()
1020 if (!self.player_blocked)
1022 self.movement = '0 0 0';
1023 self.disableclientprediction = 1;
1027 void PM_check_vortex()
1031 float xyspeed = vlen(vec2(self.velocity));
1032 if (self.weapon == WEP_VORTEX.m_id && WEP_CVAR(vortex, charge) && WEP_CVAR(vortex, charge_velocity_rate) && xyspeed > WEP_CVAR(vortex, charge_minspeed))
1034 // add a maximum of charge_velocity_rate when going fast (f = 1), gradually increasing from minspeed (f = 0) to maxspeed
1035 xyspeed = min(xyspeed, WEP_CVAR(vortex, charge_maxspeed));
1036 float f = (xyspeed - WEP_CVAR(vortex, charge_minspeed)) / (WEP_CVAR(vortex, charge_maxspeed) - WEP_CVAR(vortex, charge_minspeed));
1037 // add the extra charge
1038 self.vortex_charge = min(1, self.vortex_charge + WEP_CVAR(vortex, charge_velocity_rate) * f * PHYS_INPUT_TIMELENGTH);
1043 void PM_fly(float maxspd_mod)
1045 // noclipping or flying
1046 UNSET_ONGROUND(self);
1048 self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
1049 makevectors(self.v_angle);
1050 //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
1051 vector wishvel = v_forward * self.movement.x
1052 + v_right * self.movement.y
1053 + '0 0 1' * self.movement.z;
1055 vector wishdir = normalize(wishvel);
1056 float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
1058 if (time >= self.teleport_time)
1060 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
1061 PM_ClientMovement_Move();
1064 void PM_swim(float maxspd_mod)
1067 UNSET_ONGROUND(self);
1069 float jump = PHYS_INPUT_BUTTON_JUMP(self);
1070 // water jump only in certain situations
1071 // this mimics quakeworld code
1072 if (jump && self.waterlevel == WATERLEVEL_SWIMMING && self.velocity_z >= -180)
1074 vector yawangles = '0 1 0' * self.v_angle.y;
1075 makevectors(yawangles);
1076 vector forward = v_forward;
1077 vector spot = self.origin + 24 * forward;
1079 traceline(spot, spot, MOVE_NOMONSTERS, self);
1080 if (trace_startsolid)
1083 traceline(spot, spot, MOVE_NOMONSTERS, self);
1084 if (!trace_startsolid)
1086 self.velocity = forward * 50;
1087 self.velocity_z = 310;
1088 pmove_waterjumptime = 2;
1089 UNSET_ONGROUND(self);
1090 SET_JUMP_HELD(self);
1094 makevectors(self.v_angle);
1095 //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
1096 vector wishvel = v_forward * self.movement.x
1097 + v_right * self.movement.y
1098 + '0 0 1' * self.movement.z;
1099 if (wishvel == '0 0 0')
1100 wishvel = '0 0 -60'; // drift towards bottom
1102 vector wishdir = normalize(wishvel);
1103 float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod) * 0.7;
1105 if (IS_DUCKED(self))
1108 // if (pmove_waterjumptime <= 0) // TODO: use
1111 float f = 1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION;
1112 f = min(max(0, f), 1);
1115 f = wishspeed - self.velocity * wishdir;
1118 float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, f);
1119 self.velocity += accelspeed * wishdir;
1122 // holding jump button swims upward slowly
1126 if (self.watertype & CONTENT_LAVA)
1127 self.velocity_z = 50;
1128 else if (self.watertype & CONTENT_SLIME)
1129 self.velocity_z = 80;
1132 if (IS_NEXUIZ_DERIVED(gamemode))
1134 self.velocity_z = 200;
1137 self.velocity_z = 100;
1142 // water acceleration
1143 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
1144 PM_ClientMovement_Move();
1147 void PM_ladder(float maxspd_mod)
1149 // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
1150 UNSET_ONGROUND(self);
1153 g = PHYS_GRAVITY(this) * PHYS_INPUT_TIMELENGTH;
1154 if (PHYS_ENTGRAVITY(self))
1155 g *= PHYS_ENTGRAVITY(self);
1156 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1159 self.velocity_z += g;
1162 self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
1163 makevectors(self.v_angle);
1164 //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
1165 vector wishvel = v_forward * self.movement_x
1166 + v_right * self.movement_y
1167 + '0 0 1' * self.movement_z;
1168 self.velocity_z += g;
1169 if (self.ladder_entity.classname == "func_water")
1171 float f = vlen(wishvel);
1172 if (f > self.ladder_entity.speed)
1173 wishvel *= (self.ladder_entity.speed / f);
1175 self.watertype = self.ladder_entity.skin;
1176 f = self.ladder_entity.origin_z + self.ladder_entity.maxs_z;
1177 if ((self.origin_z + self.view_ofs_z) < f)
1178 self.waterlevel = WATERLEVEL_SUBMERGED;
1179 else if ((self.origin_z + (self.mins_z + self.maxs_z) * 0.5) < f)
1180 self.waterlevel = WATERLEVEL_SWIMMING;
1181 else if ((self.origin_z + self.mins_z + 1) < f)
1182 self.waterlevel = WATERLEVEL_WETFEET;
1185 self.waterlevel = WATERLEVEL_NONE;
1186 self.watertype = CONTENT_EMPTY;
1190 vector wishdir = normalize(wishvel);
1191 float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
1193 if (time >= self.teleport_time)
1195 // water acceleration
1196 PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE*maxspd_mod, 1, 0, 0, 0);
1197 PM_ClientMovement_Move();
1200 void PM_jetpack(float maxspd_mod)
1202 //makevectors(self.v_angle.y * '0 1 0');
1203 makevectors(self.v_angle);
1204 vector wishvel = v_forward * self.movement_x
1205 + v_right * self.movement_y;
1206 // add remaining speed as Z component
1207 float maxairspd = PHYS_MAXAIRSPEED(self) * max(1, maxspd_mod);
1208 // fix speedhacks :P
1209 wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxairspd);
1210 // add the unused velocity as up component
1213 // if (self.BUTTON_JUMP)
1214 wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
1216 // it is now normalized, so...
1217 float a_side = PHYS_JETPACK_ACCEL_SIDE;
1218 float a_up = PHYS_JETPACK_ACCEL_UP;
1219 float a_add = PHYS_JETPACK_ANTIGRAVITY * PHYS_GRAVITY(this);
1221 wishvel_x *= a_side;
1222 wishvel_y *= a_side;
1227 //////////////////////////////////////////////////////////////////////////////////////
1228 // finding the maximum over all vectors of above form
1229 // with wishvel having an absolute value of 1
1230 //////////////////////////////////////////////////////////////////////////////////////
1231 // we're finding the maximum over
1232 // f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
1233 // for z in the range from -1 to 1
1234 //////////////////////////////////////////////////////////////////////////////////////
1235 // maximum is EITHER attained at the single extreme point:
1236 float a_diff = a_side * a_side - a_up * a_up;
1240 f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
1241 if (f > -1 && f < 1) // can it be attained?
1243 best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
1244 //print("middle\n");
1247 // OR attained at z = 1:
1248 f = (a_up + a_add) * (a_up + a_add);
1254 // OR attained at z = -1:
1255 f = (a_up - a_add) * (a_up - a_add);
1259 //print("bottom\n");
1262 //////////////////////////////////////////////////////////////////////////////////////
1264 //print("best possible acceleration: ", ftos(best), "\n");
1267 fxy = bound(0, 1 - (self.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / PHYS_JETPACK_MAXSPEED_SIDE, 1);
1268 if (wishvel_z - PHYS_GRAVITY(this) > 0)
1269 fz = bound(0, 1 - self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1271 fz = bound(0, 1 + self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
1274 fvel = vlen(wishvel);
1277 wishvel_z = (wishvel_z - PHYS_GRAVITY(this)) * fz + PHYS_GRAVITY(this);
1279 fvel = min(1, vlen(wishvel) / best);
1280 if (PHYS_JETPACK_FUEL && !(ITEMS_STAT(self) & IT_UNLIMITED_WEAPON_AMMO))
1281 f = min(1, PHYS_AMMO_FUEL(self) / (PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel));
1285 //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
1287 if (f > 0 && wishvel != '0 0 0')
1289 self.velocity = self.velocity + wishvel * f * PHYS_INPUT_TIMELENGTH;
1290 UNSET_ONGROUND(self);
1293 if (!(ITEMS_STAT(self) & IT_UNLIMITED_WEAPON_AMMO))
1294 self.ammo_fuel -= PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel * f;
1296 ITEMS_STAT(self) |= IT_USING_JETPACK;
1298 // jetpack also inhibits health regeneration, but only for 1 second
1299 self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
1304 float g = PHYS_GRAVITY(this) * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1305 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1306 self.velocity_z -= g * 0.5;
1308 self.velocity_z -= g;
1309 PM_ClientMovement_Move();
1310 if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1311 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1312 self.velocity_z -= g * 0.5;
1316 void PM_walk(float buttons_prev, float maxspd_mod)
1318 if (!WAS_ONGROUND(self))
1321 if (autocvar_speedmeter)
1322 LOG_TRACE(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
1324 if (self.lastground < time - 0.3)
1325 self.velocity *= (1 - PHYS_FRICTION_ONLAND);
1327 if (self.jumppadcount > 1)
1328 LOG_TRACE(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
1329 self.jumppadcount = 0;
1334 makevectors(self.v_angle.y * '0 1 0');
1335 vector wishvel = v_forward * self.movement.x
1336 + v_right * self.movement.y;
1338 vector wishdir = normalize(wishvel);
1339 float wishspeed = vlen(wishvel);
1341 wishspeed = min(wishspeed, PHYS_MAXSPEED(self) * maxspd_mod);
1342 if (IS_DUCKED(self))
1345 // apply edge friction
1346 float f = vlen(vec2(self.velocity));
1350 trace_dphitq3surfaceflags = 0;
1351 tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
1352 // TODO: apply edge friction
1353 // apply ground friction
1354 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)
1355 realfriction = PHYS_FRICTION_SLICK;
1357 realfriction = PHYS_FRICTION;
1359 f = 1 - PHYS_INPUT_TIMELENGTH * realfriction * ((f < PHYS_STOPSPEED) ? (PHYS_STOPSPEED / f) : 1);
1363 Mathematical analysis time!
1365 Our goal is to invert this mess.
1367 For the two cases we get:
1368 v = v0 * (1 - PHYS_INPUT_TIMELENGTH * (PHYS_STOPSPEED / v0) * PHYS_FRICTION)
1369 = v0 - PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1370 v0 = v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
1372 v = v0 * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1373 v0 = v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1375 These cases would be chosen ONLY if:
1377 v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION < PHYS_STOPSPEED
1378 v < PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1380 v0 >= PHYS_STOPSPEED
1381 v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION) >= PHYS_STOPSPEED
1382 v >= PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
1385 float addspeed = wishspeed - self.velocity * wishdir;
1388 float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
1389 self.velocity += accelspeed * wishdir;
1391 float g = PHYS_GRAVITY(this) * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1392 if (!(GAMEPLAYFIX_NOGRAVITYONGROUND))
1393 self.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
1394 if (self.velocity * self.velocity)
1395 PM_ClientMovement_Move();
1396 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1397 if (!IS_ONGROUND(self) || !GAMEPLAYFIX_NOGRAVITYONGROUND)
1398 self.velocity_z -= g * 0.5;
1401 void PM_air(float buttons_prev, float maxspd_mod)
1403 makevectors(self.v_angle.y * '0 1 0');
1404 vector wishvel = v_forward * self.movement.x
1405 + v_right * self.movement.y;
1407 vector wishdir = normalize(wishvel);
1408 float wishspeed = vlen(wishvel);
1411 if (time >= self.teleport_time)
1413 if (pmove_waterjumptime <= 0)
1416 float maxairspd = PHYS_MAXAIRSPEED(self) * min(maxspd_mod, 1);
1418 // apply air speed limit
1419 float airaccelqw = PHYS_AIRACCEL_QW(self);
1420 float wishspeed0 = wishspeed;
1421 wishspeed = min(wishspeed, maxairspd);
1422 if (IS_DUCKED(self))
1424 float airaccel = PHYS_AIRACCELERATE * min(maxspd_mod, 1);
1426 float accelerating = (self.velocity * wishdir > 0);
1427 float wishspeed2 = wishspeed;
1430 if (PHYS_AIRSTOPACCELERATE)
1432 vector curdir = normalize(vec2(self.velocity));
1433 airaccel += (PHYS_AIRSTOPACCELERATE*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
1435 // note that for straight forward jumping:
1436 // step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
1437 // accel = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
1439 // dv/dt = accel * maxspeed (when slow)
1440 // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
1441 // log dv/dt = logaccel + logmaxspeed (when slow)
1442 // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
1443 float strafity = IsMoveInDirection(self.movement, -90) + IsMoveInDirection(self.movement, +90); // if one is nonzero, other is always zero
1444 if (PHYS_MAXAIRSTRAFESPEED)
1445 wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED(self)*maxspd_mod, strafity, PHYS_MAXAIRSTRAFESPEED*maxspd_mod));
1446 if (PHYS_AIRSTRAFEACCELERATE(self))
1447 airaccel = GeomLerp(airaccel, strafity, PHYS_AIRSTRAFEACCELERATE(self)*maxspd_mod);
1448 if (PHYS_AIRSTRAFEACCEL_QW(self))
1450 (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW(self) : PHYS_AIRACCEL_QW(self)) >= 0) ? +1 : -1)
1452 (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW(self)), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW(self))));
1455 if (PHYS_WARSOWBUNNY_TURNACCEL && accelerating && self.movement.y == 0 && self.movement.x != 0)
1456 PM_AirAccelerate(wishdir, wishspeed2);
1458 PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR(self), PHYS_AIRACCEL_SIDEWAYS_FRICTION / maxairspd, PHYS_AIRSPEEDLIMIT_NONQW(self));
1460 if (PHYS_AIRCONTROL)
1461 CPM_PM_Aircontrol(wishdir, wishspeed2);
1463 float g = PHYS_GRAVITY(this) * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
1464 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1465 self.velocity_z -= g * 0.5;
1467 self.velocity_z -= g;
1468 PM_ClientMovement_Move();
1469 if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
1470 if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
1471 self.velocity_z -= g * 0.5;
1474 // used for calculating airshots
1475 bool IsFlying(entity a)
1479 if(a.waterlevel >= WATERLEVEL_SWIMMING)
1481 traceline(a.origin, a.origin - '0 0 48', MOVE_NORMAL, a);
1482 if(trace_fraction < 1)
1489 int buttons = PHYS_INPUT_BUTTON_MASK(self);
1491 self.items = getstati(STAT_ITEMS, 0, 24);
1493 self.movement = PHYS_INPUT_MOVEVALUES(self);
1495 vector oldv_angle = self.v_angle;
1496 vector oldangles = self.angles; // we need to save these, as they're abused by other code
1497 self.v_angle = PHYS_INPUT_ANGLES(self);
1498 self.angles = PHYS_WORLD_ANGLES(self);
1500 self.team = myteam + 1; // is this correct?
1501 if (!(PHYS_INPUT_BUTTON_JUMP(self))) // !jump
1502 UNSET_JUMP_HELD(self); // canjump = true
1503 pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH;
1505 PM_ClientMovement_UpdateStatus(true);
1510 WarpZone_PlayerPhysics_FixVAngle();
1512 float maxspeed_mod = 1;
1513 maxspeed_mod *= PHYS_HIGHSPEED;
1516 Physics_UpdateStats(this, maxspeed_mod);
1518 if (self.PlayerPhysplug)
1519 if (self.PlayerPhysplug())
1524 anticheat_physics();
1527 if (PM_check_specialcommand(buttons))
1532 if (buttons != self.buttons_old || self.movement != self.movement_old || self.v_angle != self.v_angle_old)
1533 self.parm_idlesince = time;
1536 int buttons_prev = self.buttons_old;
1537 self.buttons_old = buttons;
1538 self.movement_old = self.movement;
1539 self.v_angle_old = self.v_angle;
1541 PM_check_nickspam();
1545 if (IS_BOT_CLIENT(self))
1547 if (playerdemo_read())
1552 if (IS_PLAYER(self))
1555 bool not_allowed_to_move = false;
1557 if (time < game_starttime)
1558 not_allowed_to_move = true;
1561 if (not_allowed_to_move)
1563 self.velocity = '0 0 0';
1564 self.movetype = MOVETYPE_NONE;
1566 self.disableclientprediction = 2;
1570 else if (self.disableclientprediction == 2)
1572 if (self.movetype == MOVETYPE_NONE)
1573 self.movetype = MOVETYPE_WALK;
1574 self.disableclientprediction = 0;
1580 if (self.movetype == MOVETYPE_NONE)
1583 // when we get here, disableclientprediction cannot be 2
1584 self.disableclientprediction = 0;
1587 viewloc_PlayerPhysics();
1598 maxspeed_mod *= self.swamp_slowdown; //cvar("g_balance_swamp_moverate");
1600 // conveyors: first fix velocity
1601 if (self.conveyor.state)
1602 self.velocity -= self.conveyor.movedir;
1605 MUTATOR_CALLHOOK(PlayerPhysics);
1611 // float forcedodge = 1;
1614 // PM_dodging_checkpressedkeys();
1617 // PM_ClientMovement_Move();
1622 if (!IS_PLAYER(self))
1624 maxspeed_mod = autocvar_sv_spectator_speed_multiplier;
1625 if (!self.spectatorspeed)
1626 self.spectatorspeed = maxspeed_mod;
1627 if (self.impulse && self.impulse <= 19 || (self.impulse >= 200 && self.impulse <= 209) || (self.impulse >= 220 && self.impulse <= 229))
1629 if (self.lastclassname != "player")
1631 if (self.impulse == 10 || self.impulse == 15 || self.impulse == 18 || (self.impulse >= 200 && self.impulse <= 209))
1632 self.spectatorspeed = bound(1, self.spectatorspeed + 0.5, 5);
1633 else if (self.impulse == 11)
1634 self.spectatorspeed = maxspeed_mod;
1635 else if (self.impulse == 12 || self.impulse == 16 || self.impulse == 19 || (self.impulse >= 220 && self.impulse <= 229))
1636 self.spectatorspeed = bound(1, self.spectatorspeed - 0.5, 5);
1637 else if (self.impulse >= 1 && self.impulse <= 9)
1638 self.spectatorspeed = 1 + 0.5 * (self.impulse - 1);
1639 } // otherwise just clear
1642 maxspeed_mod = self.spectatorspeed;
1645 float spd = max(PHYS_MAXSPEED(self), PHYS_MAXAIRSPEED(self)) * maxspeed_mod;
1646 if(self.speed != spd)
1649 string temps = ftos(spd);
1650 stuffcmd(self, strcat("cl_forwardspeed ", temps, "\n"));
1651 stuffcmd(self, strcat("cl_backspeed ", temps, "\n"));
1652 stuffcmd(self, strcat("cl_sidespeed ", temps, "\n"));
1653 stuffcmd(self, strcat("cl_upspeed ", temps, "\n"));
1656 if(self.stat_jumpspeedcap_min != PHYS_JUMPSPEEDCAP_MIN)
1658 self.stat_jumpspeedcap_min = PHYS_JUMPSPEEDCAP_MIN;
1659 stuffcmd(self, strcat("cl_jumpspeedcap_min ", PHYS_JUMPSPEEDCAP_MIN, "\n"));
1661 if(self.stat_jumpspeedcap_max != PHYS_JUMPSPEEDCAP_MAX)
1663 self.stat_jumpspeedcap_min = PHYS_JUMPSPEEDCAP_MAX;
1664 stuffcmd(self, strcat("cl_jumpspeedcap_max ", PHYS_JUMPSPEEDCAP_MAX, "\n"));
1670 // handle water here
1671 vector midpoint = ((self.absmin + self.absmax) * 0.5);
1672 if(pointcontents(midpoint) == CONTENT_WATER)
1674 self.velocity = self.velocity * 0.5;
1677 //if(pointcontents(midpoint + '0 0 2') == CONTENT_WATER)
1678 //{ self.velocity_z = 70; }
1684 if (!self.fixangle && !g_bugrigs)
1685 self.angles = '0 1 0' * self.v_angle.y;
1688 PM_check_hitground();
1693 if (IS_PLAYER(self))
1696 if (self.flags & FL_WATERJUMP)
1698 self.velocity_x = self.movedir_x;
1699 self.velocity_y = self.movedir_y;
1700 if (time > self.teleport_time || self.waterlevel == WATERLEVEL_NONE)
1702 self.flags &= ~FL_WATERJUMP;
1703 self.teleport_time = 0;
1708 else if (g_bugrigs && IS_PLAYER(self))
1712 else if (self.movetype == MOVETYPE_NOCLIP || self.movetype == MOVETYPE_FLY || self.movetype == MOVETYPE_FLY_WORLDONLY || (BUFFS_STAT(self) & BUFF_FLIGHT.m_itemid))
1713 PM_fly(maxspeed_mod);
1715 else if (self.waterlevel >= WATERLEVEL_SWIMMING)
1716 PM_swim(maxspeed_mod);
1718 else if (time < self.ladder_time)
1719 PM_ladder(maxspeed_mod);
1721 else if (ITEMS_STAT(self) & IT_USING_JETPACK)
1722 PM_jetpack(maxspeed_mod);
1724 else if (IS_ONGROUND(self))
1725 PM_walk(buttons_prev, maxspeed_mod);
1728 PM_air(buttons_prev, maxspeed_mod);
1733 if (IS_ONGROUND(self))
1734 self.lastground = time;
1736 // conveyors: then break velocity again
1737 if(self.conveyor.state)
1738 self.velocity += self.conveyor.movedir;
1740 self.lastflags = self.flags;
1742 self.lastclassname = self.classname;
1745 self.v_angle = oldv_angle;
1746 self.angles = oldangles;
1751 void SV_PlayerPhysics()
1753 void CSQC_ClientMovement_PlayerMove_Frame()
1760 ((self.flags & FL_DUCKED) ? PMF_DUCKED : 0) |
1761 (!(self.flags & FL_JUMPRELEASED) ? 0 : PMF_JUMP_HELD) |
1762 ((self.flags & FL_ONGROUND) ? PMF_ONGROUND : 0);