+ float f = vlen(self.punchvector) - 30 * PHYS_INPUT_TIMELENGTH;
+ if (f > 0)
+ self.punchvector = normalize(self.punchvector) * f;
+ else
+ self.punchvector = '0 0 0';
+ }
+#endif
+}
+
+void PM_check_spider(void)
+{
+#ifdef SVQC
+ if (time >= self.spider_slowness)
+ return;
+ PHYS_MAXSPEED(self) *= 0.5; // half speed while slow from spider
+ PHYS_MAXAIRSPEED(self) *= 0.5;
+ PHYS_AIRSPEEDLIMIT_NONQW(self) *= 0.5;
+ PHYS_AIRSTRAFEACCELERATE(self) *= 0.5;
+#endif
+}
+
+// predict frozen movement, as frozen players CAN move in some cases
+void PM_check_frozen(void)
+{
+ if (!PHYS_FROZEN(self))
+ return;
+ if (PHYS_DODGING_FROZEN
+#ifdef SVQC
+ && IS_REAL_CLIENT(self)
+#endif
+ )
+ {
+ self.movement_x = bound(-5, self.movement.x, 5);
+ self.movement_y = bound(-5, self.movement.y, 5);
+ self.movement_z = bound(-5, self.movement.z, 5);
+ }
+ else
+ self.movement = '0 0 0';
+
+ vector midpoint = ((self.absmin + self.absmax) * 0.5);
+ if (pointcontents(midpoint) == CONTENT_WATER)
+ {
+ self.velocity = self.velocity * 0.5;
+
+ if (pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
+ self.velocity_z = 200;
+ }
+}
+
+void PM_check_hitground()
+{
+#ifdef SVQC
+ if (IS_ONGROUND(self))
+ if (IS_PLAYER(self)) // no fall sounds for observers thank you very much
+ if (self.wasFlying)
+ {
+ self.wasFlying = 0;
+ if (self.waterlevel < WATERLEVEL_SWIMMING)
+ if (time >= self.ladder_time)
+ if (!self.hook)
+ {
+ self.nextstep = time + 0.3 + random() * 0.1;
+ trace_dphitq3surfaceflags = 0;
+ tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
+ if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS))
+ {
+ if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
+ GlobalSound(globalsound_metalfall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
+ else
+ GlobalSound(globalsound_fall, CH_PLAYER, VOICETYPE_PLAYERSOUND);
+ }
+ }
+ }
+#endif
+}
+
+void PM_check_blocked(void)
+{
+#ifdef SVQC
+ if (!self.player_blocked)
+ return;
+ self.movement = '0 0 0';
+ self.disableclientprediction = 1;
+#endif
+}
+
+#ifdef SVQC
+float speedaward_lastsent;
+float speedaward_lastupdate;
+#endif
+void PM_check_race(void)
+{
+#ifdef SVQC
+ if(!(g_cts || g_race))
+ return;
+ if (vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed)
+ {
+ speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1');
+ speedaward_holder = self.netname;
+ speedaward_uid = self.crypto_idfp;
+ speedaward_lastupdate = time;
+ }
+ if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
+ {
+ string rr = (g_cts) ? CTS_RECORD : RACE_RECORD;
+ race_send_speedaward(MSG_ALL);
+ speedaward_lastsent = speedaward_speed;
+ if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
+ {
+ speedaward_alltimebest = speedaward_speed;
+ speedaward_alltimebest_holder = speedaward_holder;
+ speedaward_alltimebest_uid = speedaward_uid;
+ db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
+ db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
+ race_send_speedaward_alltimebest(MSG_ALL);
+ }
+ }
+#endif
+}
+
+void PM_check_vortex(void)
+{
+#ifdef SVQC
+ // WEAPONTODO
+ float xyspeed = vlen(vec2(self.velocity));
+ if (self.weapon == WEP_VORTEX && WEP_CVAR(vortex, charge) && WEP_CVAR(vortex, charge_velocity_rate) && xyspeed > WEP_CVAR(vortex, charge_minspeed))
+ {
+ // add a maximum of charge_velocity_rate when going fast (f = 1), gradually increasing from minspeed (f = 0) to maxspeed
+ xyspeed = min(xyspeed, WEP_CVAR(vortex, charge_maxspeed));
+ float f = (xyspeed - WEP_CVAR(vortex, charge_minspeed)) / (WEP_CVAR(vortex, charge_maxspeed) - WEP_CVAR(vortex, charge_minspeed));
+ // add the extra charge
+ self.vortex_charge = min(1, self.vortex_charge + WEP_CVAR(vortex, charge_velocity_rate) * f * PHYS_INPUT_TIMELENGTH);
+ }
+#endif
+}
+
+void PM_fly(float maxspd_mod)
+{
+ // noclipping or flying
+ UNSET_ONGROUND(self);
+
+ self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
+ makevectors(self.v_angle);
+ //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
+ vector wishvel = v_forward * self.movement.x
+ + v_right * self.movement.y
+ + '0 0 1' * self.movement.z;
+ // acceleration
+ vector wishdir = normalize(wishvel);
+ float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
+#ifdef SVQC
+ if (time >= self.teleport_time)
+#endif
+ PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
+ PM_ClientMovement_Move();
+}
+
+void PM_swim(float maxspd_mod)
+{
+ // swimming
+ UNSET_ONGROUND(self);
+
+ float jump = PHYS_INPUT_BUTTON_JUMP(self);
+ // water jump only in certain situations
+ // this mimics quakeworld code
+ if (jump && self.waterlevel == WATERLEVEL_SWIMMING && self.velocity_z >= -180)
+ {
+ vector yawangles = '0 1 0' * self.v_angle.y;
+ makevectors(yawangles);
+ vector forward = v_forward;
+ vector spot = self.origin + 24 * forward;
+ spot_z += 8;
+ traceline(spot, spot, MOVE_NOMONSTERS, self);
+ if (trace_startsolid)
+ {
+ spot_z += 24;
+ traceline(spot, spot, MOVE_NOMONSTERS, self);
+ if (!trace_startsolid)
+ {
+ self.velocity = forward * 50;
+ self.velocity_z = 310;
+ pmove_waterjumptime = 2;
+ UNSET_ONGROUND(self);
+ SET_JUMP_HELD(self);
+ }
+ }
+ }
+ makevectors(self.v_angle);
+ //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
+ vector wishvel = v_forward * self.movement.x
+ + v_right * self.movement.y
+ + '0 0 1' * self.movement.z;
+ if (wishvel == '0 0 0')
+ wishvel = '0 0 -60'; // drift towards bottom
+
+ vector wishdir = normalize(wishvel);
+ float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod) * 0.7;
+
+ if (IS_DUCKED(self))
+ wishspeed *= 0.5;
+
+// if (pmove_waterjumptime <= 0) // TODO: use
+ {
+ // water friction
+ float f = 1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION;
+ f = min(max(0, f), 1);
+ self.velocity *= f;
+
+ f = wishspeed - self.velocity * wishdir;
+ if (f > 0)
+ {
+ float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, f);
+ self.velocity += accelspeed * wishdir;
+ }
+
+ // holding jump button swims upward slowly
+ if (jump)
+ {
+#if 0
+ if (self.watertype & CONTENT_LAVA)
+ self.velocity_z = 50;
+ else if (self.watertype & CONTENT_SLIME)
+ self.velocity_z = 80;
+ else
+ {
+ if (IS_NEXUIZ_DERIVED(gamemode))
+#endif
+ self.velocity_z = 200;
+#if 0
+ else
+ self.velocity_z = 100;
+ }
+#endif
+ }
+ }
+ // water acceleration
+ PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE * maxspd_mod, 1, 0, 0, 0);
+ PM_ClientMovement_Move();
+}
+
+void PM_ladder(float maxspd_mod)
+{
+ // on a spawnfunc_func_ladder or swimming in spawnfunc_func_water
+ UNSET_ONGROUND(self);
+
+ float g;
+ g = PHYS_GRAVITY * PHYS_INPUT_TIMELENGTH;
+ if (PHYS_ENTGRAVITY(self))
+ g *= PHYS_ENTGRAVITY(self);
+ if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+ {
+ g *= 0.5;
+ self.velocity_z += g;
+ }
+
+ self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION);
+ makevectors(self.v_angle);
+ //wishvel = v_forward * self.movement.x + v_right * self.movement.y + v_up * self.movement.z;
+ vector wishvel = v_forward * self.movement_x
+ + v_right * self.movement_y
+ + '0 0 1' * self.movement_z;
+ self.velocity_z += g;
+ if (self.ladder_entity.classname == "func_water")
+ {
+ float f = vlen(wishvel);
+ if (f > self.ladder_entity.speed)
+ wishvel *= (self.ladder_entity.speed / f);
+
+ self.watertype = self.ladder_entity.skin;
+ f = self.ladder_entity.origin_z + self.ladder_entity.maxs_z;
+ if ((self.origin_z + self.view_ofs_z) < f)
+ self.waterlevel = WATERLEVEL_SUBMERGED;
+ else if ((self.origin_z + (self.mins_z + self.maxs_z) * 0.5) < f)
+ self.waterlevel = WATERLEVEL_SWIMMING;
+ else if ((self.origin_z + self.mins_z + 1) < f)
+ self.waterlevel = WATERLEVEL_WETFEET;
+ else
+ {
+ self.waterlevel = WATERLEVEL_NONE;
+ self.watertype = CONTENT_EMPTY;
+ }
+ }
+ // acceleration
+ vector wishdir = normalize(wishvel);
+ float wishspeed = min(vlen(wishvel), PHYS_MAXSPEED(self) * maxspd_mod);
+#ifdef SVQC
+ if (time >= self.teleport_time)
+#endif
+ // water acceleration
+ PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE*maxspd_mod, 1, 0, 0, 0);
+ PM_ClientMovement_Move();
+}
+
+void PM_jetpack(float maxspd_mod)
+{
+ //makevectors(self.v_angle.y * '0 1 0');
+ makevectors(self.v_angle);
+ vector wishvel = v_forward * self.movement_x
+ + v_right * self.movement_y;
+ // add remaining speed as Z component
+ float maxairspd = PHYS_MAXAIRSPEED(self) * max(1, maxspd_mod);
+ // fix speedhacks :P
+ wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxairspd);
+ // add the unused velocity as up component
+ wishvel_z = 0;
+
+ // if (self.BUTTON_JUMP)
+ wishvel_z = sqrt(max(0, 1 - wishvel * wishvel));
+
+ // it is now normalized, so...
+ float a_side = PHYS_JETPACK_ACCEL_SIDE;
+ float a_up = PHYS_JETPACK_ACCEL_UP;
+ float a_add = PHYS_JETPACK_ANTIGRAVITY * PHYS_GRAVITY;
+
+ wishvel_x *= a_side;
+ wishvel_y *= a_side;
+ wishvel_z *= a_up;
+ wishvel_z += a_add;
+
+ float best = 0;
+ //////////////////////////////////////////////////////////////////////////////////////
+ // finding the maximum over all vectors of above form
+ // with wishvel having an absolute value of 1
+ //////////////////////////////////////////////////////////////////////////////////////
+ // we're finding the maximum over
+ // f(a_side, a_up, a_add, z) := a_side * (1 - z^2) + (a_add + a_up * z)^2;
+ // for z in the range from -1 to 1
+ //////////////////////////////////////////////////////////////////////////////////////
+ // maximum is EITHER attained at the single extreme point:
+ float a_diff = a_side * a_side - a_up * a_up;
+ float f;
+ if (a_diff != 0)
+ {
+ f = a_add * a_up / a_diff; // this is the zero of diff(f(a_side, a_up, a_add, z), z)
+ if (f > -1 && f < 1) // can it be attained?
+ {
+ best = (a_diff + a_add * a_add) * (a_diff + a_up * a_up) / a_diff;
+ //print("middle\n");
+ }
+ }
+ // OR attained at z = 1:
+ f = (a_up + a_add) * (a_up + a_add);
+ if (f > best)
+ {
+ best = f;
+ //print("top\n");
+ }
+ // OR attained at z = -1:
+ f = (a_up - a_add) * (a_up - a_add);
+ if (f > best)
+ {
+ best = f;
+ //print("bottom\n");
+ }
+ best = sqrt(best);
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ //print("best possible acceleration: ", ftos(best), "\n");
+
+ float fxy, fz;
+ fxy = bound(0, 1 - (self.velocity * normalize(wishvel_x * '1 0 0' + wishvel_y * '0 1 0')) / PHYS_JETPACK_MAXSPEED_SIDE, 1);
+ if (wishvel_z - PHYS_GRAVITY > 0)
+ fz = bound(0, 1 - self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
+ else
+ fz = bound(0, 1 + self.velocity_z / PHYS_JETPACK_MAXSPEED_UP, 1);
+
+ float fvel;
+ fvel = vlen(wishvel);
+ wishvel_x *= fxy;
+ wishvel_y *= fxy;
+ wishvel_z = (wishvel_z - PHYS_GRAVITY) * fz + PHYS_GRAVITY;
+
+ fvel = min(1, vlen(wishvel) / best);
+ if (PHYS_JETPACK_FUEL && !(ITEMS_STAT(self) & IT_UNLIMITED_WEAPON_AMMO))
+ f = min(1, PHYS_AMMO_FUEL(self) / (PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel));
+ else
+ f = 1;
+
+ //print("this acceleration: ", ftos(vlen(wishvel) * f), "\n");
+
+ if (f > 0 && wishvel != '0 0 0')
+ {
+ self.velocity = self.velocity + wishvel * f * PHYS_INPUT_TIMELENGTH;
+ UNSET_ONGROUND(self);
+
+#ifdef SVQC
+ if (!(ITEMS_STAT(self) & IT_UNLIMITED_WEAPON_AMMO))
+ self.ammo_fuel -= PHYS_JETPACK_FUEL * PHYS_INPUT_TIMELENGTH * fvel * f;
+
+ ITEMS_STAT(self) |= IT_USING_JETPACK;
+
+ // jetpack also inhibits health regeneration, but only for 1 second
+ self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
+#endif
+ }
+
+#ifdef CSQC
+ float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
+ if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+ self.velocity_z -= g * 0.5;
+ else
+ self.velocity_z -= g;
+ PM_ClientMovement_Move();
+ if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
+ if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+ self.velocity_z -= g * 0.5;
+#endif
+}
+
+void PM_walk(float buttons_prev, float maxspd_mod)
+{
+ if (!WAS_ONGROUND(self))
+ {
+#ifdef SVQC
+ if (autocvar_speedmeter)
+ dprint(strcat("landing velocity: ", vtos(self.velocity), " (abs: ", ftos(vlen(self.velocity)), ")\n"));
+#endif
+ if (self.lastground < time - 0.3)
+ self.velocity *= (1 - PHYS_FRICTION_ONLAND);
+#ifdef SVQC
+ if (self.jumppadcount > 1)
+ dprint(strcat(ftos(self.jumppadcount), "x jumppad combo\n"));
+ self.jumppadcount = 0;
+#endif
+ }
+
+ // walking
+ makevectors(self.v_angle.y * '0 1 0');
+ vector wishvel = v_forward * self.movement.x
+ + v_right * self.movement.y;
+ // acceleration
+ vector wishdir = normalize(wishvel);
+ float wishspeed = vlen(wishvel);
+
+ wishspeed = min(wishspeed, PHYS_MAXSPEED(self) * maxspd_mod);
+ if (IS_DUCKED(self))
+ wishspeed *= 0.5;
+
+ // apply edge friction
+ float f = vlen(vec2(self.velocity));
+ if (f > 0)
+ {
+ float realfriction;
+ trace_dphitq3surfaceflags = 0;
+ tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 1', MOVE_NOMONSTERS, self);
+ // TODO: apply edge friction
+ // apply ground friction
+ if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK)
+ realfriction = PHYS_FRICTION_SLICK;
+ else
+ realfriction = PHYS_FRICTION;
+
+ f = 1 - PHYS_INPUT_TIMELENGTH * realfriction * ((f < PHYS_STOPSPEED) ? (PHYS_STOPSPEED / f) : 1);
+ f = max(0, f);
+ self.velocity *= f;
+ /*
+ Mathematical analysis time!
+
+ Our goal is to invert this mess.
+
+ For the two cases we get:
+ v = v0 * (1 - PHYS_INPUT_TIMELENGTH * (PHYS_STOPSPEED / v0) * PHYS_FRICTION)
+ = v0 - PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
+ v0 = v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION
+ and
+ v = v0 * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
+ v0 = v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
+
+ These cases would be chosen ONLY if:
+ v0 < PHYS_STOPSPEED
+ v + PHYS_INPUT_TIMELENGTH * PHYS_STOPSPEED * PHYS_FRICTION < PHYS_STOPSPEED
+ v < PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
+ and, respectively:
+ v0 >= PHYS_STOPSPEED
+ v / (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION) >= PHYS_STOPSPEED
+ v >= PHYS_STOPSPEED * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION)
+ */
+ }
+ float addspeed = wishspeed - self.velocity * wishdir;
+ if (addspeed > 0)
+ {
+ float accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed);
+ self.velocity += accelspeed * wishdir;
+ }
+ float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
+ if (!(GAMEPLAYFIX_NOGRAVITYONGROUND))
+ self.velocity_z -= g * (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE ? 0.5 : 1);
+ if (self.velocity * self.velocity)
+ PM_ClientMovement_Move();
+ if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+ if (!IS_ONGROUND(self) || !GAMEPLAYFIX_NOGRAVITYONGROUND)
+ self.velocity_z -= g * 0.5;
+}
+
+void PM_air(float buttons_prev, float maxspd_mod)
+{
+ makevectors(self.v_angle.y * '0 1 0');
+ vector wishvel = v_forward * self.movement.x
+ + v_right * self.movement.y;
+ // acceleration
+ vector wishdir = normalize(wishvel);
+ float wishspeed = vlen(wishvel);
+
+#ifdef SVQC
+ if (time >= self.teleport_time)
+#else
+ if (pmove_waterjumptime <= 0)
+#endif
+ {
+ float maxairspd = PHYS_MAXAIRSPEED(self) * min(maxspd_mod, 1);
+
+ // apply air speed limit
+ float airaccelqw = PHYS_AIRACCEL_QW(self);
+ float wishspeed0 = wishspeed;
+ wishspeed = min(wishspeed, maxairspd);
+ if (IS_DUCKED(self))
+ wishspeed *= 0.5;
+ float airaccel = PHYS_AIRACCELERATE * min(maxspd_mod, 1);
+
+ float accelerating = (self.velocity * wishdir > 0);
+ float wishspeed2 = wishspeed;
+
+ // CPM: air control
+ if (PHYS_AIRSTOPACCELERATE)
+ {
+ vector curdir = normalize(vec2(self.velocity));
+ airaccel += (PHYS_AIRSTOPACCELERATE*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
+ }
+ // note that for straight forward jumping:
+ // step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0;
+ // accel = bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
+ // -->
+ // dv/dt = accel * maxspeed (when slow)
+ // dv/dt = accel * maxspeed * (1 - accelqw) (when fast)
+ // log dv/dt = logaccel + logmaxspeed (when slow)
+ // log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
+ float strafity = IsMoveInDirection(self.movement, -90) + IsMoveInDirection(self.movement, +90); // if one is nonzero, other is always zero
+ if (PHYS_MAXAIRSTRAFESPEED)
+ wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED(self)*maxspd_mod, strafity, PHYS_MAXAIRSTRAFESPEED*maxspd_mod));
+ if (PHYS_AIRSTRAFEACCELERATE(self))
+ airaccel = GeomLerp(airaccel, strafity, PHYS_AIRSTRAFEACCELERATE(self)*maxspd_mod);
+ if (PHYS_AIRSTRAFEACCEL_QW(self))
+ airaccelqw =
+ (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW(self) : PHYS_AIRACCEL_QW(self)) >= 0) ? +1 : -1)
+ *
+ (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW(self)), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW(self))));
+ // !CPM
+
+ if (PHYS_WARSOWBUNNY_TURNACCEL && accelerating && self.movement.y == 0 && self.movement.x != 0)
+ PM_AirAccelerate(wishdir, wishspeed2);
+ else
+ PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR(self), PHYS_AIRACCEL_SIDEWAYS_FRICTION / maxairspd, PHYS_AIRSPEEDLIMIT_NONQW(self));
+
+ if (PHYS_AIRCONTROL)
+ CPM_PM_Aircontrol(wishdir, wishspeed2);
+ }
+ float g = PHYS_GRAVITY * PHYS_ENTGRAVITY(self) * PHYS_INPUT_TIMELENGTH;
+ if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+ self.velocity_z -= g * 0.5;
+ else
+ self.velocity_z -= g;
+ PM_ClientMovement_Move();
+ if (!IS_ONGROUND(self) || !(GAMEPLAYFIX_NOGRAVITYONGROUND))
+ if (GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE)
+ self.velocity_z -= g * 0.5;
+}
+
+// used for calculating airshots
+bool IsFlying(entity a)
+{
+ if(IS_ONGROUND(a))
+ return false;
+ if(a.waterlevel >= WATERLEVEL_SWIMMING)
+ return false;
+ traceline(a.origin, a.origin - '0 0 48', MOVE_NORMAL, a);
+ if(trace_fraction < 1)
+ return false;
+ return true;
+}
+
+void PM_Main()
+{
+ int buttons = PHYS_INPUT_BUTTON_MASK(self);
+#ifdef CSQC
+ self.items = getstati(STAT_ITEMS, 0, 24);
+
+ self.movement = PHYS_INPUT_MOVEVALUES(self);
+
+ vector oldv_angle = self.v_angle;
+ vector oldangles = self.angles; // we need to save these, as they're abused by other code
+ self.v_angle = PHYS_INPUT_ANGLES(self);
+ self.angles = PHYS_WORLD_ANGLES(self);
+
+ self.team = myteam + 1; // is this correct?
+ if (!(PHYS_INPUT_BUTTON_JUMP(self))) // !jump
+ UNSET_JUMP_HELD(self); // canjump = true
+ pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH;
+
+ PM_ClientMovement_UpdateStatus(true);
+#endif
+
+
+#ifdef SVQC
+ WarpZone_PlayerPhysics_FixVAngle();
+#endif
+ float maxspeed_mod = 1;
+ maxspeed_mod *= PM_check_keepaway();
+ maxspeed_mod *= PHYS_HIGHSPEED;
+
+#ifdef SVQC
+ Physics_UpdateStats(maxspeed_mod);
+
+ if (self.PlayerPhysplug)
+ if (self.PlayerPhysplug())
+ return;
+#endif
+
+ PM_check_race_movetime();
+#ifdef SVQC
+ anticheat_physics();
+#endif
+
+ if (PM_check_specialcommand(buttons))
+ return;
+#ifdef SVQC
+ if (sv_maxidle > 0)
+ {
+ if (buttons != self.buttons_old || self.movement != self.movement_old || self.v_angle != self.v_angle_old)
+ self.parm_idlesince = time;
+ }
+#endif
+ int buttons_prev = self.buttons_old;
+ self.buttons_old = buttons;
+ self.movement_old = self.movement;
+ self.v_angle_old = self.v_angle;
+
+ PM_check_nickspam();
+
+ PM_check_punch();
+#ifdef SVQC
+ if (IS_BOT_CLIENT(self))
+ {
+ if (playerdemo_read())
+ return;
+ bot_think();
+ }
+
+ if (IS_PLAYER(self))
+#endif
+ {
+#ifdef SVQC
+ if (self.race_penalty)
+ if (time > self.race_penalty)
+ self.race_penalty = 0;
+#endif
+
+ bool not_allowed_to_move = false;
+#ifdef SVQC
+ if (self.race_penalty)
+ not_allowed_to_move = true;
+#endif
+#ifdef SVQC
+ if (time < game_starttime)
+ not_allowed_to_move = true;
+#endif
+
+ if (not_allowed_to_move)
+ {
+ self.velocity = '0 0 0';
+ self.movetype = MOVETYPE_NONE;
+#ifdef SVQC
+ self.disableclientprediction = 2;
+#endif
+ }
+#ifdef SVQC
+ else if (self.disableclientprediction == 2)
+ {
+ if (self.movetype == MOVETYPE_NONE)
+ self.movetype = MOVETYPE_WALK;
+ self.disableclientprediction = 0;
+ }
+#endif
+ }
+
+#ifdef SVQC
+ if (self.movetype == MOVETYPE_NONE)
+ return;
+
+ // when we get here, disableclientprediction cannot be 2
+ self.disableclientprediction = 0;
+#endif
+
+ PM_check_spider();
+
+ PM_check_frozen();
+
+ PM_check_blocked();
+
+ maxspeed_mod = 1;
+
+ if (self.in_swamp)
+ maxspeed_mod *= self.swamp_slowdown; //cvar("g_balance_swamp_moverate");
+
+ // conveyors: first fix velocity
+ if (self.conveyor.state)
+ self.velocity -= self.conveyor.movedir;
+
+#ifdef SVQC
+ MUTATOR_CALLHOOK(PlayerPhysics);
+#endif
+#ifdef CSQC
+ PM_multijump();
+#endif
+
+// float forcedodge = 1;
+// if(forcedodge) {
+//#ifdef CSQC
+// PM_dodging_checkpressedkeys();
+//#endif
+// PM_dodging();
+// PM_ClientMovement_Move();
+// return;
+// }
+
+#ifdef SVQC
+ if (!IS_PLAYER(self))
+ {
+ maxspeed_mod = autocvar_sv_spectator_speed_multiplier;
+ if (!self.spectatorspeed)
+ self.spectatorspeed = maxspeed_mod;
+ if (self.impulse && self.impulse <= 19 || (self.impulse >= 200 && self.impulse <= 209) || (self.impulse >= 220 && self.impulse <= 229))
+ {
+ if (self.lastclassname != "player")
+ {
+ if (self.impulse == 10 || self.impulse == 15 || self.impulse == 18 || (self.impulse >= 200 && self.impulse <= 209))
+ self.spectatorspeed = bound(1, self.spectatorspeed + 0.5, 5);
+ else if (self.impulse == 11)
+ self.spectatorspeed = maxspeed_mod;
+ else if (self.impulse == 12 || self.impulse == 16 || self.impulse == 19 || (self.impulse >= 220 && self.impulse <= 229))
+ self.spectatorspeed = bound(1, self.spectatorspeed - 0.5, 5);
+ else if (self.impulse >= 1 && self.impulse <= 9)
+ self.spectatorspeed = 1 + 0.5 * (self.impulse - 1);
+ } // otherwise just clear
+ self.impulse = 0;
+ }
+ maxspeed_mod = self.spectatorspeed;
+ }
+
+ float spd = max(PHYS_MAXSPEED(self), PHYS_MAXAIRSPEED(self)) * maxspeed_mod;
+ if(self.speed != spd)
+ {
+ self.speed = spd;
+ string temps = ftos(spd);
+ stuffcmd(self, strcat("cl_forwardspeed ", temps, "\n"));
+ stuffcmd(self, strcat("cl_backspeed ", temps, "\n"));
+ stuffcmd(self, strcat("cl_sidespeed ", temps, "\n"));
+ stuffcmd(self, strcat("cl_upspeed ", temps, "\n"));
+ }
+#endif
+
+ if(PHYS_DEAD(self))
+ {
+ // handle water here
+ vector midpoint = ((self.absmin + self.absmax) * 0.5);
+ if(pointcontents(midpoint) == CONTENT_WATER)
+ {
+ self.velocity = self.velocity * 0.5;
+
+ // do we want this?
+ //if(pointcontents(midpoint + '0 0 2') == CONTENT_WATER)
+ //{ self.velocity_z = 70; }
+ }
+ goto end;
+ }
+
+#ifdef SVQC
+ if (!self.fixangle && !g_bugrigs)
+ self.angles = '0 1 0' * self.v_angle.y;
+#endif
+
+ PM_check_hitground();
+
+ if(IsFlying(self))
+ self.wasFlying = 1;
+
+ if (IS_PLAYER(self))
+ CheckPlayerJump();
+
+ if (self.flags & FL_WATERJUMP)
+ {
+ self.velocity_x = self.movedir_x;
+ self.velocity_y = self.movedir_y;
+ if (time > self.teleport_time || self.waterlevel == WATERLEVEL_NONE)
+ {
+ self.flags &= ~FL_WATERJUMP;
+ self.teleport_time = 0;
+ }
+ }
+
+#ifdef SVQC
+ else if (g_bugrigs && IS_PLAYER(self))
+ RaceCarPhysics();
+#endif
+
+ else if (self.movetype == MOVETYPE_NOCLIP || self.movetype == MOVETYPE_FLY || self.movetype == MOVETYPE_FLY_WORLDONLY || (BUFFS(self) & BUFF_FLIGHT))
+ PM_fly(maxspeed_mod);
+
+ else if (self.waterlevel >= WATERLEVEL_SWIMMING)
+ PM_swim(maxspeed_mod);
+
+ else if (time < self.ladder_time)
+ PM_ladder(maxspeed_mod);
+
+ else if (ITEMS_STAT(self) & IT_USING_JETPACK)
+ PM_jetpack(maxspeed_mod);
+
+ else if (IS_ONGROUND(self))
+ PM_walk(buttons_prev, maxspeed_mod);
+
+ else
+ PM_air(buttons_prev, maxspeed_mod);
+
+#ifdef SVQC
+ if (!IS_OBSERVER(self))
+ PM_check_race();
+#endif
+ PM_check_vortex();
+
+:end
+ if (IS_ONGROUND(self))
+ self.lastground = time;
+
+ // conveyors: then break velocity again
+ if(self.conveyor.state)
+ self.velocity += self.conveyor.movedir;
+
+ self.lastflags = self.flags;
+
+ self.lastclassname = self.classname;
+
+#ifdef CSQC
+ self.v_angle = oldv_angle;
+ self.angles = oldangles;
+#endif
+}
+
+#ifdef SVQC
+void SV_PlayerPhysics(void)
+#elif defined(CSQC)
+void CSQC_ClientMovement_PlayerMove_Frame(void)
+#endif
+{
+ PM_Main();
+
+#ifdef CSQC
+ self.pmove_flags =
+ ((self.flags & FL_DUCKED) ? PMF_DUCKED : 0) |
+ (!(self.flags & FL_JUMPRELEASED) ? 0 : PMF_JUMP_HELD) |
+ ((self.flags & FL_ONGROUND) ? PMF_ONGROUND : 0);
+#endif
+}