+ strafity = CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, -90) + CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, +90); // if one is nonzero, other is always zero
+ if(cl.movevars_maxairstrafespeed)
+ wishspeed = min(wishspeed, CL_GeomLerp(cl.movevars_maxairspeed, strafity, cl.movevars_maxairstrafespeed));
+ if(cl.movevars_airstrafeaccelerate)
+ accel = CL_GeomLerp(cl.movevars_airaccelerate, strafity, cl.movevars_airstrafeaccelerate);
+ if(cl.movevars_airstrafeaccel_qw)
+ accelqw =
+ (((strafity > 0.5 ? cl.movevars_airstrafeaccel_qw : cl.movevars_airaccel_qw) >= 0) ? +1 : -1)
+ *
+ (1 - CL_GeomLerp(1 - fabs(cl.movevars_airaccel_qw), strafity, 1 - fabs(cl.movevars_airstrafeaccel_qw)));
+ // !CPM
+
+ if(cl.movevars_warsowbunny_turnaccel && accelerating && s->cmd.sidemove == 0 && s->cmd.forwardmove != 0)
+ CL_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2);
+ else
+ CL_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed0, accel, accelqw, cl.movevars_airaccel_qw_stretchfactor, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed, cl.movevars_airspeedlimit_nonqw);
+
+ if(cl.movevars_aircontrol)
+ CL_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
+ }
+ gravity = cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime;
+ if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+ s->velocity[2] -= gravity * 0.5f;
+ else
+ s->velocity[2] -= gravity;
+ CL_ClientMovement_Move(s);
+ if(!(cl.moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !s->onground)
+ {
+ if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+ s->velocity[2] -= gravity * 0.5f;
+ }
+ }
+}
+
+static void CL_ClientMovement_PlayerMove(cl_clientmovement_state_t *s)
+{
+ //Con_Printf(" %f", frametime);
+ if (!s->cmd.jump)
+ s->cmd.canjump = true;
+ s->waterjumptime -= s->cmd.frametime;
+ CL_ClientMovement_UpdateStatus(s);
+ if (s->waterlevel >= WATERLEVEL_SWIMMING)
+ CL_ClientMovement_Physics_Swim(s);
+ else
+ CL_ClientMovement_Physics_Walk(s);
+}
+
+extern cvar_t slowmo;
+void CL_UpdateMoveVars(void)
+{
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ {
+ cl.moveflags = 0;
+ }
+ else if (cl.stats[STAT_MOVEVARS_TICRATE])
+ {
+ cl.moveflags = cl.stats[STAT_MOVEFLAGS];
+ cl.movevars_ticrate = cl.statsf[STAT_MOVEVARS_TICRATE];
+ cl.movevars_timescale = cl.statsf[STAT_MOVEVARS_TIMESCALE];
+ cl.movevars_gravity = cl.statsf[STAT_MOVEVARS_GRAVITY];
+ cl.movevars_stopspeed = cl.statsf[STAT_MOVEVARS_STOPSPEED] ;
+ cl.movevars_maxspeed = cl.statsf[STAT_MOVEVARS_MAXSPEED];
+ cl.movevars_spectatormaxspeed = cl.statsf[STAT_MOVEVARS_SPECTATORMAXSPEED];
+ cl.movevars_accelerate = cl.statsf[STAT_MOVEVARS_ACCELERATE];
+ cl.movevars_airaccelerate = cl.statsf[STAT_MOVEVARS_AIRACCELERATE];
+ cl.movevars_wateraccelerate = cl.statsf[STAT_MOVEVARS_WATERACCELERATE];
+ cl.movevars_entgravity = cl.statsf[STAT_MOVEVARS_ENTGRAVITY];
+ cl.movevars_jumpvelocity = cl.statsf[STAT_MOVEVARS_JUMPVELOCITY];
+ cl.movevars_edgefriction = cl.statsf[STAT_MOVEVARS_EDGEFRICTION];
+ cl.movevars_maxairspeed = cl.statsf[STAT_MOVEVARS_MAXAIRSPEED];
+ cl.movevars_stepheight = cl.statsf[STAT_MOVEVARS_STEPHEIGHT];
+ cl.movevars_airaccel_qw = cl.statsf[STAT_MOVEVARS_AIRACCEL_QW];
+ cl.movevars_airaccel_qw_stretchfactor = cl.statsf[STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR];
+ cl.movevars_airaccel_sideways_friction = cl.statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION];
+ cl.movevars_friction = cl.statsf[STAT_MOVEVARS_FRICTION];
+ cl.movevars_wallfriction = cl.statsf[STAT_MOVEVARS_WALLFRICTION];
+ cl.movevars_waterfriction = cl.statsf[STAT_MOVEVARS_WATERFRICTION];
+ cl.movevars_airstopaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTOPACCELERATE];
+ cl.movevars_airstrafeaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCELERATE];
+ cl.movevars_maxairstrafespeed = cl.statsf[STAT_MOVEVARS_MAXAIRSTRAFESPEED];
+ cl.movevars_airstrafeaccel_qw = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCEL_QW];
+ cl.movevars_aircontrol = cl.statsf[STAT_MOVEVARS_AIRCONTROL];
+ cl.movevars_aircontrol_power = cl.statsf[STAT_MOVEVARS_AIRCONTROL_POWER];
+ cl.movevars_aircontrol_penalty = cl.statsf[STAT_MOVEVARS_AIRCONTROL_PENALTY];
+ cl.movevars_warsowbunny_airforwardaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL];
+ cl.movevars_warsowbunny_accel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_ACCEL];
+ cl.movevars_warsowbunny_topspeed = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED];
+ cl.movevars_warsowbunny_turnaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL];
+ cl.movevars_warsowbunny_backtosideratio = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO];
+ cl.movevars_airspeedlimit_nonqw = cl.statsf[STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW];
+ }
+ else
+ {
+ cl.moveflags = 0;
+ cl.movevars_ticrate = (cls.demoplayback ? 1.0f : slowmo.value) / bound(1.0f, cl_netfps.value, 1000.0f);
+ cl.movevars_timescale = (cls.demoplayback ? 1.0f : slowmo.value);
+ cl.movevars_gravity = sv_gravity.value;
+ cl.movevars_stopspeed = cl_movement_stopspeed.value;
+ cl.movevars_maxspeed = cl_movement_maxspeed.value;
+ cl.movevars_spectatormaxspeed = cl_movement_maxspeed.value;
+ cl.movevars_accelerate = cl_movement_accelerate.value;
+ cl.movevars_airaccelerate = cl_movement_airaccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_airaccelerate.value;
+ cl.movevars_wateraccelerate = cl_movement_wateraccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_wateraccelerate.value;
+ cl.movevars_friction = cl_movement_friction.value;
+ cl.movevars_wallfriction = cl_movement_wallfriction.value;
+ cl.movevars_waterfriction = cl_movement_waterfriction.value < 0 ? cl_movement_friction.value : cl_movement_waterfriction.value;
+ cl.movevars_entgravity = 1;
+ cl.movevars_jumpvelocity = cl_movement_jumpvelocity.value;
+ cl.movevars_edgefriction = cl_movement_edgefriction.value;
+ cl.movevars_maxairspeed = cl_movement_maxairspeed.value;
+ cl.movevars_stepheight = cl_movement_stepheight.value;
+ cl.movevars_airaccel_qw = cl_movement_airaccel_qw.value;
+ cl.movevars_airaccel_qw_stretchfactor = 0;
+ cl.movevars_airaccel_sideways_friction = cl_movement_airaccel_sideways_friction.value;
+ cl.movevars_airstopaccelerate = 0;
+ cl.movevars_airstrafeaccelerate = 0;
+ cl.movevars_maxairstrafespeed = 0;
+ cl.movevars_airstrafeaccel_qw = 0;
+ cl.movevars_aircontrol = 0;
+ cl.movevars_aircontrol_power = 2;
+ cl.movevars_aircontrol_penalty = 0;
+ cl.movevars_warsowbunny_airforwardaccel = 0;
+ cl.movevars_warsowbunny_accel = 0;
+ cl.movevars_warsowbunny_topspeed = 0;
+ cl.movevars_warsowbunny_turnaccel = 0;
+ cl.movevars_warsowbunny_backtosideratio = 0;
+ cl.movevars_airspeedlimit_nonqw = 0;
+ }
+
+ if(!(cl.moveflags & MOVEFLAG_VALID))
+ {
+ if(gamemode == GAME_NEXUIZ) // Legacy hack to work with old servers of Nexuiz.
+ cl.moveflags = MOVEFLAG_Q2AIRACCELERATE;
+ }
+
+ if(cl.movevars_aircontrol_power <= 0)
+ cl.movevars_aircontrol_power = 2; // CPMA default
+}
+
+void CL_ClientMovement_PlayerMove_Frame(cl_clientmovement_state_t *s)
+{
+ // if a move is more than 50ms, do it as two moves (matching qwsv)
+ //Con_Printf("%i ", s.cmd.msec);
+ if(s->cmd.frametime > 0.0005)
+ {
+ if (s->cmd.frametime > 0.05)
+ {
+ s->cmd.frametime /= 2;
+ CL_ClientMovement_PlayerMove(s);
+ }
+ CL_ClientMovement_PlayerMove(s);
+ }
+ else
+ {
+ // we REALLY need this handling to happen, even if the move is not executed
+ if (!s->cmd.jump)
+ s->cmd.canjump = true;
+ }
+}
+
+void CL_ClientMovement_Replay(void)
+{
+ int i;
+ double totalmovemsec;
+ cl_clientmovement_state_t s;
+
+ VectorCopy(cl.mvelocity[0], cl.movement_velocity);
+
+ if (cl.movement_predicted && !cl.movement_replay)
+ return;
+
+ if (!cl_movement_replay.integer)
+ return;
+
+ // set up starting state for the series of moves
+ memset(&s, 0, sizeof(s));
+ VectorCopy(cl.entities[cl.playerentity].state_current.origin, s.origin);
+ VectorCopy(cl.mvelocity[0], s.velocity);
+ s.crouched = true; // will be updated on first move
+ //Con_Printf("movement replay starting org %f %f %f vel %f %f %f\n", s.origin[0], s.origin[1], s.origin[2], s.velocity[0], s.velocity[1], s.velocity[2]);
+
+ totalmovemsec = 0;
+ for (i = 0;i < CL_MAX_USERCMDS;i++)
+ if (cl.movecmd[i].sequence > cls.servermovesequence)
+ totalmovemsec += cl.movecmd[i].msec;
+ cl.movement_predicted = totalmovemsec >= cl_movement_minping.value && cls.servermovesequence && (cl_movement.integer && !cls.demoplayback && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission);
+ //Con_Printf("%i = %.0f >= %.0f && %u && (%i && %i && %i == %i && %i > 0 && %i\n", cl.movement_predicted, totalmovemsec, cl_movement_minping.value, cls.servermovesequence, cl_movement.integer, !cls.demoplayback, cls.signon, SIGNONS, cl.stats[STAT_HEALTH], !cl.intermission);
+ if (cl.movement_predicted)
+ {
+ //Con_Printf("%ims\n", cl.movecmd[0].msec);
+
+ // replay the input queue to predict current location
+ // note: this relies on the fact there's always one queue item at the end
+
+ // find how many are still valid
+ for (i = 0;i < CL_MAX_USERCMDS;i++)
+ if (cl.movecmd[i].sequence <= cls.servermovesequence)
+ break;
+ // now walk them in oldest to newest order
+ for (i--;i >= 0;i--)
+ {
+ s.cmd = cl.movecmd[i];
+ if (i < CL_MAX_USERCMDS - 1)
+ s.cmd.canjump = cl.movecmd[i+1].canjump;
+
+ CL_ClientMovement_PlayerMove_Frame(&s);
+
+ cl.movecmd[i].canjump = s.cmd.canjump;
+ }
+ //Con_Printf("\n");
+ CL_ClientMovement_UpdateStatus(&s);
+ }
+ else
+ {
+ // get the first movement queue entry to know whether to crouch and such
+ s.cmd = cl.movecmd[0];
+ }
+
+ if (!cls.demoplayback) // for bob, speedometer
+ {
+ cl.movement_replay = false;
+ // update the interpolation target position and velocity
+ VectorCopy(s.origin, cl.movement_origin);
+ VectorCopy(s.velocity, cl.movement_velocity);
+ }
+
+ // update the onground flag if appropriate
+ if (cl.movement_predicted)
+ {
+ // when predicted we simply set the flag according to the UpdateStatus
+ cl.onground = s.onground;
+ }
+ else
+ {
+ // when not predicted, cl.onground is cleared by cl_parse.c each time
+ // an update packet is received, but can be forced on here to hide
+ // server inconsistencies in the onground flag
+ // (which mostly occur when stepping up stairs at very high framerates
+ // where after the step up the move continues forward and not
+ // downward so the ground is not detected)
+ //
+ // such onground inconsistencies can cause jittery gun bobbing and
+ // stair smoothing, so we set onground if UpdateStatus says so
+ if (s.onground)
+ cl.onground = true;
+ }
+}
+
+static void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, usercmd_t *from, usercmd_t *to)
+{
+ int bits;
+
+ bits = 0;
+ if (to->viewangles[0] != from->viewangles[0])
+ bits |= QW_CM_ANGLE1;
+ if (to->viewangles[1] != from->viewangles[1])
+ bits |= QW_CM_ANGLE2;
+ if (to->viewangles[2] != from->viewangles[2])
+ bits |= QW_CM_ANGLE3;
+ if (to->forwardmove != from->forwardmove)
+ bits |= QW_CM_FORWARD;
+ if (to->sidemove != from->sidemove)
+ bits |= QW_CM_SIDE;
+ if (to->upmove != from->upmove)
+ bits |= QW_CM_UP;
+ if (to->buttons != from->buttons)
+ bits |= QW_CM_BUTTONS;
+ if (to->impulse != from->impulse)
+ bits |= QW_CM_IMPULSE;
+
+ MSG_WriteByte(buf, bits);
+ if (bits & QW_CM_ANGLE1)
+ MSG_WriteAngle16i(buf, to->viewangles[0]);
+ if (bits & QW_CM_ANGLE2)
+ MSG_WriteAngle16i(buf, to->viewangles[1]);
+ if (bits & QW_CM_ANGLE3)
+ MSG_WriteAngle16i(buf, to->viewangles[2]);
+ if (bits & QW_CM_FORWARD)
+ MSG_WriteShort(buf, (short) to->forwardmove);
+ if (bits & QW_CM_SIDE)
+ MSG_WriteShort(buf, (short) to->sidemove);
+ if (bits & QW_CM_UP)
+ MSG_WriteShort(buf, (short) to->upmove);
+ if (bits & QW_CM_BUTTONS)
+ MSG_WriteByte(buf, to->buttons);
+ if (bits & QW_CM_IMPULSE)
+ MSG_WriteByte(buf, to->impulse);
+ MSG_WriteByte(buf, to->msec);
+}
+
+void CL_NewFrameReceived(int num)
+{
+ if (developer_networkentities.integer >= 10)
+ Con_Printf("recv: svc_entities %i\n", num);
+ cl.latestframenums[cl.latestframenumsposition] = num;
+ cl.latestsendnums[cl.latestframenumsposition] = cl.cmd.sequence;
+ cl.latestframenumsposition = (cl.latestframenumsposition + 1) % LATESTFRAMENUMS;
+}
+
+void CL_RotateMoves(const matrix4x4_t *m)
+{
+ // rotate viewangles in all previous moves
+ vec3_t v;
+ vec3_t f, r, u;
+ int i;
+ for (i = 0;i < CL_MAX_USERCMDS;i++)
+ {
+ if (cl.movecmd[i].sequence > cls.servermovesequence)
+ {
+ usercmd_t *c = &cl.movecmd[i];
+ AngleVectors(c->viewangles, f, r, u);
+ Matrix4x4_Transform(m, f, v); VectorCopy(v, f);
+ Matrix4x4_Transform(m, u, v); VectorCopy(v, u);
+ AnglesFromVectors(c->viewangles, f, u, false);