]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_input.c
turn font size snapping into a float
[xonotic/darkplaces.git] / cl_input.c
index 4920784ecc587137a965fa633725fbd90e658566..57f403660b79a3c5468658db1cb12f0f4db34eb6 100644 (file)
@@ -301,6 +301,7 @@ void IN_BestWeapon (void)
        // if we couldn't find any of the weapons, there's nothing more we can do...
 }
 
+#if 0
 void IN_CycleWeapon (void)
 {
        int i, n;
@@ -347,6 +348,7 @@ void IN_CycleWeapon (void)
        }
        // if we couldn't find any of the weapons, there's nothing more we can do...
 }
+#endif
 
 /*
 ===============
@@ -363,9 +365,9 @@ float CL_KeyState (kbutton_t *key)
        float           val;
        qboolean        impulsedown, impulseup, down;
 
-       impulsedown = key->state & 2;
-       impulseup = key->state & 4;
-       down = key->state & 1;
+       impulsedown = (key->state & 2) != 0;
+       impulseup = (key->state & 4) != 0;
+       down = (key->state & 1) != 0;
        val = 0;
 
        if (impulsedown && !impulseup)
@@ -413,6 +415,7 @@ cvar_t cl_backspeed = {CVAR_SAVE, "cl_backspeed","400","backward movement speed"
 cvar_t cl_sidespeed = {CVAR_SAVE, "cl_sidespeed","350","strafe movement speed"};
 
 cvar_t cl_movespeedkey = {CVAR_SAVE, "cl_movespeedkey","2.0","how much +speed multiplies keyboard movement speed"};
+cvar_t cl_movecliptokeyboard = {0, "cl_movecliptokeyboard", "0", "if set to 1, any move is clipped to the nine keyboard states; if set to 2, only the direction is clipped, not the amount"};
 
 cvar_t cl_yawspeed = {CVAR_SAVE, "cl_yawspeed","140","keyboard yaw turning speed"};
 cvar_t cl_pitchspeed = {CVAR_SAVE, "cl_pitchspeed","150","keyboard pitch turning speed"};
@@ -420,6 +423,7 @@ cvar_t cl_pitchspeed = {CVAR_SAVE, "cl_pitchspeed","150","keyboard pitch turning
 cvar_t cl_anglespeedkey = {CVAR_SAVE, "cl_anglespeedkey","1.5","how much +speed multiplies keyboard turning speed"};
 
 cvar_t cl_movement = {CVAR_SAVE, "cl_movement", "0", "enables clientside prediction of your player movement"};
+cvar_t cl_movement_nettimeout = {CVAR_SAVE, "cl_movement_nettimeout", "0.3", "stops predicting moves when server is lagging badly (avoids major performance problems), timeout in seconds"};
 cvar_t cl_movement_minping = {CVAR_SAVE, "cl_movement_minping", "0", "whether to use prediction when ping is lower than this value in milliseconds"};
 cvar_t cl_movement_track_canjump = {CVAR_SAVE, "cl_movement_track_canjump", "1", "track if the player released the jump key between two jumps to decide if he is able to jump or not; when off, this causes some \"sliding\" slightly above the floor when the jump key is held too long; if the mod allows repeated jumping by holding space all the time, this has to be set to zero too"};
 cvar_t cl_movement_maxspeed = {0, "cl_movement_maxspeed", "320", "how fast you can move (should match sv_maxspeed)"};
@@ -666,6 +670,71 @@ void CL_Input (void)
 
        // clamp after the move to prevent rendering with bad angles
        CL_AdjustAngles ();
+
+       if(cl_movecliptokeyboard.integer)
+       {
+               vec_t f = 1;
+               if (in_speed.state & 1)
+                       f *= cl_movespeedkey.value;
+               if(cl_movecliptokeyboard.integer == 2)
+               {
+                       // digital direction, analog amount
+                       vec_t wishvel_x, wishvel_y;
+                       f *= max(cl_sidespeed.value, max(cl_forwardspeed.value, cl_backspeed.value));
+                       wishvel_x = fabs(cl.cmd.forwardmove);
+                       wishvel_y = fabs(cl.cmd.sidemove);
+                       if(wishvel_x != 0 && wishvel_y != 0 && wishvel_x != wishvel_y)
+                       {
+                               vec_t wishspeed = sqrt(wishvel_x * wishvel_x + wishvel_y * wishvel_y);
+                               if(wishvel_x >= 2 * wishvel_y)
+                               {
+                                       // pure X motion
+                                       if(cl.cmd.forwardmove > 0)
+                                               cl.cmd.forwardmove = wishspeed;
+                                       else
+                                               cl.cmd.forwardmove = -wishspeed;
+                                       cl.cmd.sidemove = 0;
+                               }
+                               else if(wishvel_y >= 2 * wishvel_x)
+                               {
+                                       // pure Y motion
+                                       cl.cmd.forwardmove = 0;
+                                       if(cl.cmd.sidemove > 0)
+                                               cl.cmd.sidemove = wishspeed;
+                                       else
+                                               cl.cmd.sidemove = -wishspeed;
+                               }
+                               else
+                               {
+                                       // diagonal
+                                       if(cl.cmd.forwardmove > 0)
+                                               cl.cmd.forwardmove = 0.70710678118654752440 * wishspeed;
+                                       else
+                                               cl.cmd.forwardmove = -0.70710678118654752440 * wishspeed;
+                                       if(cl.cmd.sidemove > 0)
+                                               cl.cmd.sidemove = 0.70710678118654752440 * wishspeed;
+                                       else
+                                               cl.cmd.sidemove = -0.70710678118654752440 * wishspeed;
+                               }
+                       }
+               }
+               else if(cl_movecliptokeyboard.integer)
+               {
+                       // digital direction, digital amount
+                       if(cl.cmd.sidemove >= cl_sidespeed.value * f * 0.5)
+                               cl.cmd.sidemove = cl_sidespeed.value * f;
+                       else if(cl.cmd.sidemove <= -cl_sidespeed.value * f * 0.5)
+                               cl.cmd.sidemove = -cl_sidespeed.value * f;
+                       else
+                               cl.cmd.sidemove = 0;
+                       if(cl.cmd.forwardmove >= cl_forwardspeed.value * f * 0.5)
+                               cl.cmd.forwardmove = cl_forwardspeed.value * f;
+                       else if(cl.cmd.forwardmove <= -cl_backspeed.value * f * 0.5)
+                               cl.cmd.forwardmove = -cl_backspeed.value * f;
+                       else
+                               cl.cmd.forwardmove = 0;
+               }
+       }
 }
 
 #include "cl_collision.h"
@@ -777,7 +846,7 @@ qboolean CL_ClientMovement_Unstick(cl_clientmovement_state_t *s)
        for (i = 0;i < NUMOFFSETS;i++)
        {
                VectorAdd(offsets[i], s->origin, neworigin);
-               if (!CL_Move(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false).startsolid)
+               if (!CL_TraceBox(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false).startsolid)
                {
                        VectorCopy(neworigin, s->origin);
                        return true;
@@ -808,7 +877,7 @@ void CL_ClientMovement_UpdateStatus(cl_clientmovement_state_t *s)
                // low ceiling first
                if (s->crouched)
                {
-                       trace = CL_Move(s->origin, cl.playerstandmins, cl.playerstandmaxs, s->origin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                       trace = CL_TraceBox(s->origin, cl.playerstandmins, cl.playerstandmaxs, s->origin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
                        if (!trace.startsolid)
                                s->crouched = false;
                }
@@ -826,23 +895,23 @@ void CL_ClientMovement_UpdateStatus(cl_clientmovement_state_t *s)
 
        // set onground
        VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + 1);
-       VectorSet(origin2, s->origin[0], s->origin[1], s->origin[2] - 2);
-       trace = CL_Move(origin1, s->mins, s->maxs, origin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+       VectorSet(origin2, s->origin[0], s->origin[1], s->origin[2] - 1); // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :)
+       trace = CL_TraceBox(origin1, s->mins, s->maxs, origin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
        s->onground = trace.fraction < 1 && trace.plane.normal[2] > 0.7;
 
        // set watertype/waterlevel
        VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + s->mins[2] + 1);
        s->waterlevel = WATERLEVEL_NONE;
-       s->watertype = CL_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK;
+       s->watertype = CL_TracePoint(origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK;
        if (s->watertype)
        {
                s->waterlevel = WATERLEVEL_WETFEET;
                origin1[2] = s->origin[2] + (s->mins[2] + s->maxs[2]) * 0.5f;
-               if (CL_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
+               if (CL_TracePoint(origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
                {
                        s->waterlevel = WATERLEVEL_SWIMMING;
                        origin1[2] = s->origin[2] + 22;
-                       if (CL_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
+                       if (CL_TracePoint(origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
                                s->waterlevel = WATERLEVEL_SUBMERGED;
                }
        }
@@ -869,20 +938,20 @@ void CL_ClientMovement_Move(cl_clientmovement_state_t *s)
        for (bump = 0, t = s->cmd.frametime;bump < 8 && VectorLength2(s->velocity) > 0;bump++)
        {
                VectorMA(s->origin, t, s->velocity, neworigin);
-               trace = CL_Move(s->origin, s->mins, s->maxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+               trace = CL_TraceBox(s->origin, s->mins, s->maxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
                if (trace.fraction < 1 && trace.plane.normal[2] == 0)
                {
                        // may be a step or wall, try stepping up
                        // first move forward at a higher level
                        VectorSet(currentorigin2, s->origin[0], s->origin[1], s->origin[2] + cl.movevars_stepheight);
                        VectorSet(neworigin2, neworigin[0], neworigin[1], s->origin[2] + cl.movevars_stepheight);
-                       trace2 = CL_Move(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                       trace2 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
                        if (!trace2.startsolid)
                        {
                                // then move down from there
                                VectorCopy(trace2.endpos, currentorigin2);
                                VectorSet(neworigin2, trace2.endpos[0], trace2.endpos[1], s->origin[2]);
-                               trace3 = CL_Move(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                               trace3 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
                                //Con_Printf("%f %f %f %f : %f %f %f %f : %f %f %f %f\n", trace.fraction, trace.endpos[0], trace.endpos[1], trace.endpos[2], trace2.fraction, trace2.endpos[0], trace2.endpos[1], trace2.endpos[2], trace3.fraction, trace3.endpos[0], trace3.endpos[1], trace3.endpos[2]);
                                // accept the new trace if it made some progress
                                if (fabs(trace3.endpos[0] - trace.endpos[0]) >= 0.03125 || fabs(trace3.endpos[1] - trace.endpos[1]) >= 0.03125)
@@ -932,10 +1001,10 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
                AngleVectors(yawangles, forward, NULL, NULL);
                VectorMA(s->origin, 24, forward, spot);
                spot[2] += 8;
-               if (CL_Move(spot, vec3_origin, vec3_origin, spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
+               if (CL_TracePoint(spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
                {
                        spot[2] += 24;
-                       if (!CL_Move(spot, vec3_origin, vec3_origin, spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
+                       if (!CL_TracePoint(spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
                        {
                                VectorScale(forward, 50, s->velocity);
                                s->velocity[2] = 310;
@@ -1033,11 +1102,17 @@ void CL_ClientMovement_Physics_CPM_PM_Aircontrol(cl_clientmovement_state_t *s, v
        s->velocity[2] = zspeed;
 }
 
-void CL_ClientMovement_Physics_PM_Accelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed, vec_t accel, vec_t accelqw, vec_t sidefric)
+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 sidefric)
 {
        vec_t vel_straight, vel_z;
        vec3_t vel_perpend;
        vec_t addspeed;
+       vec_t savespeed;
+
+       if(cl.moveflags & MOVEFLAG_Q2AIRACCELERATE)
+               wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
+
+       savespeed = VectorLength2(s->velocity);
 
        vel_straight = DotProduct(s->velocity, wishdir);
        vel_z = s->velocity[2];
@@ -1045,11 +1120,22 @@ void CL_ClientMovement_Physics_PM_Accelerate(cl_clientmovement_state_t *s, vec3_
 
        addspeed = wishspeed - vel_straight;
        if(addspeed > 0)
-               vel_straight = vel_straight + min(addspeed, accel * s->cmd.frametime * wishspeed) * accelqw;
+               vel_straight = vel_straight + min(addspeed, accel * s->cmd.frametime * wishspeed0) * accelqw;
        if(wishspeed > 0)
-               vel_straight = vel_straight + min(wishspeed, accel * s->cmd.frametime * wishspeed) * (1 - accelqw);
+               vel_straight = vel_straight + min(wishspeed, accel * s->cmd.frametime * wishspeed0) * (1 - accelqw);
        
-       VectorScale(vel_perpend, 1 - s->cmd.frametime * wishspeed * sidefric, vel_perpend);
+       if(sidefric < 0 && VectorLength2(vel_perpend))
+       {
+               vec_t f, fmin;
+               f = 1 + s->cmd.frametime * wishspeed * sidefric;
+               fmin = (savespeed - vel_straight*vel_straight) / VectorLength2(vel_perpend);
+               if(fmin <= 0)
+                       VectorScale(vel_perpend, f, vel_perpend);
+               else
+                       VectorScale(vel_perpend, min(1.0f, max(fmin, f)), vel_perpend);
+       }
+       else
+               VectorScale(vel_perpend, 1 - s->cmd.frametime * wishspeed * sidefric, vel_perpend);
 
        VectorMA(vel_perpend, vel_straight, wishdir, s->velocity);
        s->velocity[2] += vel_z;
@@ -1168,9 +1254,9 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
                                VectorSet(neworigin2, s->origin[0] + s->velocity[0]*(16/f), s->origin[1] + s->velocity[1]*(16/f), s->origin[2] + s->mins[2]);
                                VectorSet(neworigin3, neworigin2[0], neworigin2[1], neworigin2[2] - 34);
                                if (cls.protocol == PROTOCOL_QUAKEWORLD)
-                                       trace = CL_Move(neworigin2, s->mins, s->maxs, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                                       trace = CL_TraceBox(neworigin2, s->mins, s->maxs, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
                                else
-                                       trace = CL_Move(neworigin2, vec3_origin, vec3_origin, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                                       trace = CL_TraceLine(neworigin2, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
                                if (trace.fraction == 1 && !trace.startsolid)
                                        friction *= cl.movevars_edgefriction;
                        }
@@ -1196,9 +1282,11 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
                if (s->waterjumptime <= 0)
                {
                        // apply air speed limit
-                       vec_t accel, wishspeed2;
+                       vec_t accel, wishspeed0, wishspeed2, accelqw;
                        qboolean accelerating;
 
+                       accelqw = cl.movevars_airaccel_qw;
+                       wishspeed0 = wishspeed;
                        wishspeed = min(wishspeed, cl.movevars_maxairspeed);
                        if (s->crouched)
                                wishspeed *= 0.5;
@@ -1214,17 +1302,31 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
                        if(s->cmd.forwardmove == 0 && s->cmd.sidemove != 0)
                        {
                                if(cl.movevars_maxairstrafespeed)
+                               {
                                        if(wishspeed > cl.movevars_maxairstrafespeed)
                                                wishspeed = cl.movevars_maxairstrafespeed;
+                                       if(cl.movevars_maxairstrafespeed < cl.movevars_maxairspeed)
+                                               accelqw = 1;
+                                               // otherwise, CPMA-style air acceleration misbehaves a lot
+                                               // if partially non-QW acceleration is used (as in, strafing
+                                               // would get faster than moving forward straight)
+                               }
                                if(cl.movevars_airstrafeaccelerate)
+                               {
                                        accel = cl.movevars_airstrafeaccelerate;
+                                       if(cl.movevars_airstrafeaccelerate > cl.movevars_airaccelerate)
+                                               accelqw = 1;
+                                               // otherwise, CPMA-style air acceleration misbehaves a lot
+                                               // if partially non-QW acceleration is used (as in, strafing
+                                               // would get faster than moving forward straight)
+                               }
                        }
                        // !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, accel, cl.movevars_airaccel_qw, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed);
+                               CL_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed0, accel, accelqw, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed);
 
                        if(cl.movevars_aircontrol)
                                CL_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
@@ -1252,9 +1354,12 @@ 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] ;
@@ -1285,6 +1390,8 @@ void CL_UpdateMoveVars(void)
        }
        else
        {
+               cl.moveflags = 0;
+               cl.movevars_ticrate = slowmo.value / bound(1.0f, cl_netfps.value, 1000.0f);
                cl.movevars_timescale = slowmo.value;
                cl.movevars_gravity = sv_gravity.value;
                cl.movevars_stopspeed = cl_movement_stopspeed.value;
@@ -1313,6 +1420,12 @@ void CL_UpdateMoveVars(void)
                cl.movevars_warsowbunny_turnaccel = 0;
                cl.movevars_warsowbunny_backtosideratio = 0;
        }
+
+       if(!(cl.moveflags & MOVEFLAG_VALID))
+       {
+               if(gamemode == GAME_NEXUIZ)
+                       cl.moveflags = MOVEFLAG_Q2AIRACCELERATE;
+       }
 }
 
 void CL_ClientMovement_Replay(void)
@@ -1356,22 +1469,25 @@ void CL_ClientMovement_Replay(void)
                                s.cmd.canjump = cl.movecmd[i+1].canjump;
                        // 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.05)
+                       if(s.cmd.frametime > 0.0005)
                        {
-                               s.cmd.frametime /= 2;
+                               if (s.cmd.frametime > 0.05)
+                               {
+                                       s.cmd.frametime /= 2;
+                                       CL_ClientMovement_PlayerMove(&s);
+                               }
                                CL_ClientMovement_PlayerMove(&s);
+                               cl.movecmd[i].canjump = s.cmd.canjump;
                        }
-                       CL_ClientMovement_PlayerMove(&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];
        }
-       CL_ClientMovement_UpdateStatus(&s);
 
        if (cls.demoplayback) // for bob, speedometer
                VectorCopy(cl.mvelocity[0], cl.movement_velocity);
@@ -1473,14 +1589,19 @@ void CL_SendMove(void)
        unsigned char data[1024];
        double packettime;
        int msecdelta;
+       qboolean quemove;
+       qboolean important;
 
        // if playing a demo, do nothing
        if (!cls.netcon)
                return;
 
-       // we build up cl.movecmd[0] and then decide whether to send or not
-       // the prediction code will use this command even though it has not been
-       // sent yet
+       // we don't que moves during a lag spike (potential network timeout)
+       quemove = realtime - cl.last_received_message < cl_movement_nettimeout.value;
+
+       // we build up cl.cmd and then decide whether to send or not
+       // we store this into cl.movecmd[0] for prediction each frame even if we
+       // do not send, to make sure that prediction is instant
        cl.cmd.time = cl.time;
        cl.cmd.sequence = cls.netcon->outgoing_unreliable_sequence;
 
@@ -1527,7 +1648,7 @@ void CL_SendMove(void)
                cl.cmd.msec = 100;
        cl.cmd.frametime = cl.cmd.msec * (1.0 / 1000.0);
 
-       cl.cmd.predicted = cl_movement.integer;
+       cl.cmd.predicted = cl_movement.integer != 0;
 
        // movement is set by input code (forwardmove/sidemove/upmove)
        // always dump the first two moves, because they may contain leftover inputs from the last level
@@ -1553,14 +1674,15 @@ void CL_SendMove(void)
                break;
        case PROTOCOL_DARKPLACES6:
        case PROTOCOL_DARKPLACES7:
-               // FIXME: cl.movecmd[0].buttons & 16 is +button5, Nexuiz specific
+               // FIXME: cl.cmd.buttons & 16 is +button5, Nexuiz specific
                cl.cmd.crouch = (cl.cmd.buttons & 16) != 0;
                break;
        case PROTOCOL_UNKNOWN:
                break;
        }
 
-       cl.movecmd[0] = cl.cmd;
+       if (quemove)
+               cl.movecmd[0] = cl.cmd;
 
        // don't predict more than 200fps
        if (realtime >= cl.lastpackettime + 0.005)
@@ -1572,20 +1694,28 @@ void CL_SendMove(void)
        // don't send too often or else network connections can get clogged by a
        // high renderer framerate
        packettime = 1.0 / bound(1, cl_netfps.value, 1000);
-       // send input every frame in singleplayer
-       if (cl.islocalgame)
-               packettime = 0;
-       // always send if buttons changed or an impulse is pending
-       // even if it violates the rate limit!
-       if (!cl.movecmd[0].impulse && (!cl_netimmediatebuttons.integer || cl.movecmd[0].buttons == cl.movecmd[1].buttons))
+       if (cl.movevars_timescale && cl.movevars_ticrate)
        {
-               // don't choke the connection with packets (obey rate limit)
-               if ((cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS) && !NetConn_CanSend(cls.netcon) && !cl.islocalgame)
-                       return;
-               // don't send too often (cl_netfps)
-               if (realtime < cl.lastpackettime + packettime)
-                       return;
+               float maxtic = cl.movevars_ticrate / cl.movevars_timescale;
+               packettime = min(packettime, maxtic);
        }
+
+       // do not send 0ms packets because they mess up physics
+       if(cl.cmd.msec == 0 && cl.time > cl.oldtime && (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS))
+               return;
+       // always send if buttons changed or an impulse is pending
+       // even if it violates the rate limit!
+       important = (cl.cmd.impulse || (cl_netimmediatebuttons.integer && cl.cmd.buttons != cl.movecmd[1].buttons));
+       // don't send too often (cl_netfps)
+       if (!important && realtime < cl.lastpackettime + packettime)
+               return;
+       // don't choke the connection with packets (obey rate limit)
+       // it is important that this check be last, because it adds a new
+       // frame to the shownetgraph output and any cancelation after this
+       // will produce a nasty spike-like look to the netgraph
+       // we also still send if it is important
+       if (!NetConn_CanSend(cls.netcon) && !important)
+               return;
        // try to round off the lastpackettime to a multiple of the packet interval
        // (this causes it to emit packets at a steady beat)
        if (packettime > 0)
@@ -1624,14 +1754,14 @@ void CL_SendMove(void)
                        MSG_WriteByte(&buf, 0);
                        // packet loss percentage
                        for (j = 0, packetloss = 0;j < NETGRAPH_PACKETS;j++)
-                               if (cls.netcon->incoming_unreliablesize[j] == NETGRAPH_LOSTPACKET)
+                               if (cls.netcon->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
                                        packetloss++;
                        packetloss = packetloss * 100 / NETGRAPH_PACKETS;
                        MSG_WriteByte(&buf, packetloss);
                        // write most recent 3 moves
                        QW_MSG_WriteDeltaUsercmd(&buf, &nullcmd, &cl.movecmd[2]);
                        QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[2], &cl.movecmd[1]);
-                       QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[1], &cl.movecmd[0]);
+                       QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[1], &cl.cmd);
                        // calculate the checksum
                        buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->outgoing_unreliable_sequence);
                        // if delta compression history overflows, request no delta
@@ -1655,54 +1785,54 @@ void CL_SendMove(void)
                case PROTOCOL_NEHAHRABJP3:
                        // 5 bytes
                        MSG_WriteByte (&buf, clc_move);
-                       MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
+                       MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time
                        // 3 bytes
                        for (i = 0;i < 3;i++)
-                               MSG_WriteAngle8i (&buf, cl.movecmd[0].viewangles[i]);
+                               MSG_WriteAngle8i (&buf, cl.cmd.viewangles[i]);
                        // 6 bytes
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].forwardmove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].sidemove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].upmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.forwardmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.sidemove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.upmove);
                        // 2 bytes
-                       MSG_WriteByte (&buf, cl.movecmd[0].buttons);
-                       MSG_WriteByte (&buf, cl.movecmd[0].impulse);
+                       MSG_WriteByte (&buf, cl.cmd.buttons);
+                       MSG_WriteByte (&buf, cl.cmd.impulse);
                        break;
                case PROTOCOL_DARKPLACES2:
                case PROTOCOL_DARKPLACES3:
                        // 5 bytes
                        MSG_WriteByte (&buf, clc_move);
-                       MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
+                       MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time
                        // 12 bytes
                        for (i = 0;i < 3;i++)
-                               MSG_WriteAngle32f (&buf, cl.movecmd[0].viewangles[i]);
+                               MSG_WriteAngle32f (&buf, cl.cmd.viewangles[i]);
                        // 6 bytes
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].forwardmove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].sidemove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].upmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.forwardmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.sidemove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.upmove);
                        // 2 bytes
-                       MSG_WriteByte (&buf, cl.movecmd[0].buttons);
-                       MSG_WriteByte (&buf, cl.movecmd[0].impulse);
+                       MSG_WriteByte (&buf, cl.cmd.buttons);
+                       MSG_WriteByte (&buf, cl.cmd.impulse);
                        break;
                case PROTOCOL_DARKPLACES1:
                case PROTOCOL_DARKPLACES4:
                case PROTOCOL_DARKPLACES5:
                        // 5 bytes
                        MSG_WriteByte (&buf, clc_move);
-                       MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
+                       MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time
                        // 6 bytes
                        for (i = 0;i < 3;i++)
-                               MSG_WriteAngle16i (&buf, cl.movecmd[0].viewangles[i]);
+                               MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]);
                        // 6 bytes
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].forwardmove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].sidemove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].upmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.forwardmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.sidemove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.upmove);
                        // 2 bytes
-                       MSG_WriteByte (&buf, cl.movecmd[0].buttons);
-                       MSG_WriteByte (&buf, cl.movecmd[0].impulse);
+                       MSG_WriteByte (&buf, cl.cmd.buttons);
+                       MSG_WriteByte (&buf, cl.cmd.impulse);
                case PROTOCOL_DARKPLACES6:
                case PROTOCOL_DARKPLACES7:
                        // set the maxusercmds variable to limit how many should be sent
-                       maxusercmds = bound(1, cl_netrepeatinput.integer + 1, CL_MAX_USERCMDS);
+                       maxusercmds = bound(1, cl_netrepeatinput.integer + 1, min(3, CL_MAX_USERCMDS));
                        // when movement prediction is off, there's not much point in repeating old input as it will just be ignored
                        if (!cl.cmd.predicted)
                                maxusercmds = 1;
@@ -1784,12 +1914,15 @@ void CL_SendMove(void)
        if (buf.cursize || cls.netcon->message.cursize)
                NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, max(20*(buf.cursize+40), cl_rate.integer), false);
 
-       // update the cl.movecmd array which holds the most recent moves,
-       // because we now need a new slot for the next input
-       for (i = CL_MAX_USERCMDS - 1;i >= 1;i--)
-               cl.movecmd[i] = cl.movecmd[i-1];
-       cl.movecmd[0].msec = 0;
-       cl.movecmd[0].frametime = 0;
+       if (quemove)
+       {
+               // update the cl.movecmd array which holds the most recent moves,
+               // because we now need a new slot for the next input
+               for (i = CL_MAX_USERCMDS - 1;i >= 1;i--)
+                       cl.movecmd[i] = cl.movecmd[i-1];
+               cl.movecmd[0].msec = 0;
+               cl.movecmd[0].frametime = 0;
+       }
 
        // clear button 'click' states
        in_attack.state  &= ~2;
@@ -1897,10 +2030,14 @@ void CL_InitInput (void)
 
        // LordHavoc: added bestweapon command
        Cmd_AddCommand ("bestweapon", IN_BestWeapon, "send an impulse number to server to select the first usable weapon out of several (example: 8 7 6 5 4 3 2 1)");
+#if 0
        Cmd_AddCommand ("cycleweapon", IN_CycleWeapon, "send an impulse number to server to select the next usable weapon out of several (example: 9 4 8) if you are holding one of these, and choose the first one if you are holding none of these");
+#endif
        Cmd_AddCommand ("register_bestweapon", IN_BestWeapon_Register_f, "(for QC usage only) change weapon parameters to be used by bestweapon; stuffcmd this in ClientConnect");
 
+       Cvar_RegisterVariable(&cl_movecliptokeyboard);
        Cvar_RegisterVariable(&cl_movement);
+       Cvar_RegisterVariable(&cl_movement_nettimeout);
        Cvar_RegisterVariable(&cl_movement_minping);
        Cvar_RegisterVariable(&cl_movement_track_canjump);
        Cvar_RegisterVariable(&cl_movement_maxspeed);