+
+ // check if it moved at all
+ if (trace.fraction >= 0.001)
+ VectorCopy(trace.endpos, s->origin);
+
+ // check if it moved all the way
+ if (trace.fraction == 1)
+ break;
+
+ // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate
+ // <LordHavoc> I'm pretty sure I commented it out solely because it seemed redundant
+ // this got commented out in a change that supposedly makes the code match QW better
+ // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block
+ if (trace.plane.normal[2] > 0.7)
+ s->onground = true;
+
+ t -= t * trace.fraction;
+
+ f = DotProduct(s->velocity, trace.plane.normal);
+ VectorMA(s->velocity, -f, trace.plane.normal, s->velocity);
+ }
+ if (s->waterjumptime > 0)
+ VectorCopy(primalvelocity, s->velocity);
+}
+
+
+static void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
+{
+ vec_t wishspeed;
+ vec_t f;
+ vec3_t wishvel;
+ vec3_t wishdir;
+
+ // water jump only in certain situations
+ // this mimics quakeworld code
+ if (s->cmd.jump && s->waterlevel == 2 && s->velocity[2] >= -180)
+ {
+ vec3_t forward;
+ vec3_t yawangles;
+ vec3_t spot;
+ VectorSet(yawangles, 0, s->cmd.viewangles[1], 0);
+ AngleVectors(yawangles, forward, NULL, NULL);
+ VectorMA(s->origin, 24, forward, spot);
+ spot[2] += 8;
+ if (CL_TracePoint(spot, MOVE_NOMONSTERS, s->self, 0, 0, true, false, NULL, false).startsolid)
+ {
+ spot[2] += 24;
+ if (!CL_TracePoint(spot, MOVE_NOMONSTERS, s->self, 0, 0, true, false, NULL, false).startsolid)
+ {
+ VectorScale(forward, 50, s->velocity);
+ s->velocity[2] = 310;
+ s->waterjumptime = 2;
+ s->onground = false;
+ s->cmd.canjump = false;
+ }
+ }
+ }
+
+ if (!(s->cmd.forwardmove*s->cmd.forwardmove + s->cmd.sidemove*s->cmd.sidemove + s->cmd.upmove*s->cmd.upmove))
+ {
+ // drift towards bottom
+ VectorSet(wishvel, 0, 0, -60);
+ }
+ else
+ {
+ // swim
+ vec3_t forward;
+ vec3_t right;
+ vec3_t up;
+ // calculate movement vector
+ AngleVectors(s->cmd.viewangles, forward, right, up);
+ VectorSet(up, 0, 0, 1);
+ VectorMAMAM(s->cmd.forwardmove, forward, s->cmd.sidemove, right, s->cmd.upmove, up, wishvel);
+ }
+
+ // split wishvel into wishspeed and wishdir
+ wishspeed = VectorLength(wishvel);
+ if (wishspeed)
+ VectorScale(wishvel, 1 / wishspeed, wishdir);
+ else
+ VectorSet( wishdir, 0.0, 0.0, 0.0 );
+ wishspeed = min(wishspeed, cl.movevars_maxspeed) * 0.7;
+
+ if (s->crouched)
+ wishspeed *= 0.5;
+
+ if (s->waterjumptime <= 0)
+ {
+ // water friction
+ f = 1 - s->cmd.frametime * cl.movevars_waterfriction * (cls.protocol == PROTOCOL_QUAKEWORLD ? s->waterlevel : 1);
+ f = bound(0, f, 1);
+ VectorScale(s->velocity, f, s->velocity);
+
+ // water acceleration
+ f = wishspeed - DotProduct(s->velocity, wishdir);
+ if (f > 0)
+ {
+ f = min(cl.movevars_wateraccelerate * s->cmd.frametime * wishspeed, f);
+ VectorMA(s->velocity, f, wishdir, s->velocity);
+ }
+
+ // holding jump button swims upward slowly
+ if (s->cmd.jump)
+ {
+ if (s->watertype & SUPERCONTENTS_LAVA)
+ s->velocity[2] = 50;
+ else if (s->watertype & SUPERCONTENTS_SLIME)
+ s->velocity[2] = 80;
+ else
+ {
+ if (IS_NEXUIZ_DERIVED(gamemode))
+ s->velocity[2] = 200;
+ else
+ s->velocity[2] = 100;
+ }
+ }
+ }
+
+ CL_ClientMovement_Move(s);
+}
+
+static vec_t CL_IsMoveInDirection(vec_t forward, vec_t side, vec_t angle)
+{
+ if(forward == 0 && side == 0)
+ return 0; // avoid division by zero
+ angle -= RAD2DEG(atan2(side, forward));
+ angle = (ANGLEMOD(angle + 180) - 180) / 45;
+ if(angle > 1)
+ return 0;
+ if(angle < -1)
+ return 0;
+ return 1 - fabs(angle);
+}
+
+static vec_t CL_GeomLerp(vec_t a, vec_t lerp, vec_t b)
+{
+ if(a == 0)
+ {
+ if(lerp < 1)
+ return 0;
+ else
+ return b;
+ }
+ if(b == 0)
+ {
+ if(lerp > 0)
+ return 0;
+ else
+ return a;
+ }
+ return a * pow(fabs(b / a), lerp);
+}
+
+static void CL_ClientMovement_Physics_CPM_PM_Aircontrol(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed)
+{
+ vec_t zspeed, speed, dot, k;
+
+#if 0
+ // this doesn't play well with analog input
+ if(s->cmd.forwardmove == 0 || s->cmd.sidemove != 0)
+ return;
+ k = 32;
+#else
+ k = 32 * (2 * CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, 0) - 1);
+ if(k <= 0)
+ return;
+#endif
+
+ k *= bound(0, wishspeed / cl.movevars_maxairspeed, 1);
+
+ zspeed = s->velocity[2];
+ s->velocity[2] = 0;
+ speed = VectorNormalizeLength(s->velocity);
+
+ dot = DotProduct(s->velocity, wishdir);
+
+ if(dot > 0) { // we can't change direction while slowing down
+ k *= pow(dot, cl.movevars_aircontrol_power)*s->cmd.frametime;
+ speed = max(0, speed - cl.movevars_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32);
+ k *= cl.movevars_aircontrol;
+ VectorMAM(speed, s->velocity, k, wishdir, s->velocity);
+ VectorNormalize(s->velocity);
+ }
+
+ VectorScale(s->velocity, speed, s->velocity);
+ s->velocity[2] = zspeed;
+}
+
+static float CL_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor)
+{
+ return
+ (accelqw < 0 ? -1 : +1)
+ *
+ bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1);
+}
+
+static void CL_ClientMovement_Physics_PM_Accelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed, vec_t wishspeed0, vec_t accel, vec_t accelqw, vec_t stretchfactor, vec_t sidefric, vec_t speedlimit)
+{
+ vec_t vel_straight;
+ vec_t vel_z;
+ vec3_t vel_perpend;
+ vec_t step;
+ vec3_t vel_xy;
+ vec_t vel_xy_current;
+ vec_t vel_xy_backward, vel_xy_forward;
+ vec_t speedclamp;
+
+ if(stretchfactor > 0)
+ speedclamp = stretchfactor;
+ else if(accelqw < 0)
+ speedclamp = 1;
+ else
+ speedclamp = -1; // no clamping
+
+ if(accelqw < 0)
+ accelqw = -accelqw;
+
+ if(cl.moveflags & MOVEFLAG_Q2AIRACCELERATE)
+ wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
+
+ vel_straight = DotProduct(s->velocity, wishdir);
+ vel_z = s->velocity[2];
+ VectorCopy(s->velocity, vel_xy); vel_xy[2] -= vel_z;
+ VectorMA(vel_xy, -vel_straight, wishdir, vel_perpend);
+
+ step = accel * s->cmd.frametime * wishspeed0;
+
+ vel_xy_current = VectorLength(vel_xy);
+ if(speedlimit > 0)
+ accelqw = CL_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
+ vel_xy_forward = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
+ vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
+ if(vel_xy_backward < 0)
+ vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards
+
+ vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw);
+
+ if(sidefric < 0 && VectorLength2(vel_perpend))
+ // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
+ {
+ vec_t f, fmin;
+ f = max(0, 1 + s->cmd.frametime * wishspeed * sidefric);
+ fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / VectorLength2(vel_perpend);
+ // assume: fmin > 1
+ // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
+ // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
+ // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
+ // obviously, this cannot be
+ if(fmin <= 0)
+ VectorScale(vel_perpend, f, vel_perpend);