]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_input.c
added support for arguire's strange protocols that expand
[xonotic/darkplaces.git] / cl_input.c
index 8bdec9a84de9dd6360bd5c649967faecee880e92..2e57b14c265848d3fc04500778a845dd730ffc08 100644 (file)
@@ -197,47 +197,55 @@ void IN_JumpUp (void) {KeyUp(&in_jump);}
 
 void IN_Impulse (void) {in_impulse=atoi(Cmd_Argv(1));}
 
-int in_bestweapon_info[][5] =
+struct
 {
-       {'1', 1, IT_AXE, STAT_SHELLS, 0},
-       {'2', 2, IT_SHOTGUN, STAT_SHELLS, 1},
-       {'3', 3, IT_SUPER_SHOTGUN, STAT_SHELLS, 1},
-       {'4', 4, IT_NAILGUN, STAT_NAILS, 1},
-       {'5', 5, IT_SUPER_NAILGUN, STAT_NAILS, 1},
-       {'6', 6, IT_GRENADE_LAUNCHER, STAT_ROCKETS, 1},
-       {'7', 7, IT_ROCKET_LAUNCHER, STAT_ROCKETS, 1},
-       {'8', 8, IT_LIGHTNING, STAT_CELLS, 1},
-       {'9', 9, 128, STAT_CELLS, 1}, // generic energy weapon for mods
-       {'p', 209, 128, STAT_CELLS, 1}, // dpmod plasma gun
-       {'w', 210, 8388608, STAT_CELLS, 1}, // dpmod plasma wave cannon
-       {'l', 225, HIT_LASER_CANNON, STAT_CELLS, 1}, // hipnotic laser cannon
-       {'h', 226, HIT_MJOLNIR, STAT_CELLS, 0}, // hipnotic mjolnir hammer
-       {-1, 0, 0, 0, 0}
+       const char *name;
+       int impulse;
+       int weaponbit;
+       int ammostat;
+       int ammomin;
+}
+in_bestweapon_info[] =
+{
+       {"1", 1, IT_AXE, STAT_SHELLS, 0},
+       {"2", 2, IT_SHOTGUN, STAT_SHELLS, 1},
+       {"3", 3, IT_SUPER_SHOTGUN, STAT_SHELLS, 1},
+       {"4", 4, IT_NAILGUN, STAT_NAILS, 1},
+       {"5", 5, IT_SUPER_NAILGUN, STAT_NAILS, 1},
+       {"6", 6, IT_GRENADE_LAUNCHER, STAT_ROCKETS, 1},
+       {"7", 7, IT_ROCKET_LAUNCHER, STAT_ROCKETS, 1},
+       {"8", 8, IT_LIGHTNING, STAT_CELLS, 1},
+       {"9", 9, 128, STAT_CELLS, 1}, // generic energy weapon for mods
+       {"p", 209, 128, STAT_CELLS, 1}, // dpmod plasma gun
+       {"w", 210, 8388608, STAT_CELLS, 1}, // dpmod plasma wave cannon
+       {"l", 225, HIT_LASER_CANNON, STAT_CELLS, 1}, // hipnotic laser cannon
+       {"h", 226, HIT_MJOLNIR, STAT_CELLS, 0}, // hipnotic mjolnir hammer
+       {NULL, 0, 0, 0, 0}
 };
 void IN_BestWeapon (void)
 {
        int i, n;
-       const char *s;
-       if (Cmd_Argc() != 2)
+       const char *t;
+       if (Cmd_Argc() < 2)
        {
-               Con_Printf("bestweapon requires 1 parameter\n");
+               Con_Printf("bestweapon requires 1 or more parameters\n");
                return;
        }
-       s = Cmd_Argv(1);
-       for (i = 0;s[i];i++)
+       for (i = 1;i < Cmd_Argc();i++)
        {
+               t = Cmd_Argv(i);
                // figure out which weapon this character refers to
-               for (n = 0;in_bestweapon_info[n][0] >= 0;n++)
+               for (n = 0;in_bestweapon_info[n].name;n++)
                {
-                       if (in_bestweapon_info[n][0] == s[i])
+                       if (!strcmp(in_bestweapon_info[n].name, t))
                        {
                                // we found out what weapon this character refers to
                                // check if the inventory contains the weapon and enough ammo
-                               if ((cl.stats[STAT_ITEMS] & in_bestweapon_info[n][2]) && (cl.stats[in_bestweapon_info[n][3]] >= in_bestweapon_info[n][4]))
+                               if ((cl.stats[STAT_ITEMS] & in_bestweapon_info[n].weaponbit) && (cl.stats[in_bestweapon_info[n].ammostat] >= in_bestweapon_info[n].ammomin))
                                {
                                        // we found one of the weapons the player wanted
                                        // send an impulse to switch to it
-                                       in_impulse = in_bestweapon_info[n][1];
+                                       in_impulse = in_bestweapon_info[n].impulse;
                                        return;
                                }
                                break;
@@ -320,33 +328,35 @@ 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_latency = {0, "cl_movement_latency", "0", "compensates for this much latency (ping time) on quake servers which do not really support prediction, no effect on darkplaces7 protocol servers or quakeworld servers"};
+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_maxspeed = {0, "cl_movement_maxspeed", "320", "how fast you can move (should match sv_maxspeed)"};
 cvar_t cl_movement_maxairspeed = {0, "cl_movement_maxairspeed", "30", "how fast you can move while in the air (should match sv_maxairspeed)"};
 cvar_t cl_movement_stopspeed = {0, "cl_movement_stopspeed", "100", "speed below which you will be slowed rapidly to a stop rather than sliding endlessly (should match sv_stopspeed)"};
 cvar_t cl_movement_friction = {0, "cl_movement_friction", "4", "how fast you slow down (should match sv_friction)"};
-cvar_t cl_movement_waterfriction = {0, "cl_movement_waterfriction", "-1", "how fast you slow down (should match sv_friction), if less than 0 the cl_movement_friction variable is used instead"};
+cvar_t cl_movement_wallfriction = {0, "cl_movement_wallfriction", "1", "how fast you slow down while sliding along a wall (should match sv_wallfriction)"};
+cvar_t cl_movement_waterfriction = {0, "cl_movement_waterfriction", "-1", "how fast you slow down (should match sv_waterfriction), if less than 0 the cl_movement_friction variable is used instead"};
 cvar_t cl_movement_edgefriction = {0, "cl_movement_edgefriction", "2", "how much to slow down when you may be about to fall off a ledge (should match edgefriction)"};
 cvar_t cl_movement_stepheight = {0, "cl_movement_stepheight", "18", "how tall a step you can step in one instant (should match sv_stepheight)"};
 cvar_t cl_movement_accelerate = {0, "cl_movement_accelerate", "10", "how fast you accelerate (should match sv_accelerate)"};
 cvar_t cl_movement_airaccelerate = {0, "cl_movement_airaccelerate", "-1", "how fast you accelerate while in the air (should match sv_airaccelerate), if less than 0 the cl_movement_accelerate variable is used instead"};
-cvar_t cl_movement_wateraccelerate = {0, "cl_movement_wateraccelerate", "-1", "how fast you accelerate while in the air (should match sv_airaccelerate), if less than 0 the cl_movement_accelerate variable is used instead"};
+cvar_t cl_movement_wateraccelerate = {0, "cl_movement_wateraccelerate", "-1", "how fast you accelerate while in water (should match sv_wateraccelerate), if less than 0 the cl_movement_accelerate variable is used instead"};
 cvar_t cl_movement_jumpvelocity = {0, "cl_movement_jumpvelocity", "270", "how fast you move upward when you begin a jump (should match the quakec code)"};
 cvar_t cl_movement_airaccel_qw = {0, "cl_movement_airaccel_qw", "1", "ratio of QW-style air control as opposed to simple acceleration (should match sv_airaccel_qw)"};
 cvar_t cl_movement_airaccel_sideways_friction = {0, "cl_movement_airaccel_sideways_friction", "0", "anti-sideways movement stabilization (should match sv_airaccel_sideways_friction)"};
-cvar_t cl_gravity = {0, "cl_gravity", "800", "how much gravity to apply in client physics (should match sv_gravity)"};
-cvar_t cl_slowmo = {0, "cl_slowmo", "1", "speed of game time (should match slowmo)"};
 
-cvar_t in_pitch_min = {0, "in_pitch_min", "-90", "how far downward you can aim (quake used -70"}; // quake used -70
-cvar_t in_pitch_max = {0, "in_pitch_max", "90", "how far upward you can aim (quake used 80"}; // quake used 80
+cvar_t in_pitch_min = {0, "in_pitch_min", "-90", "how far downward you can aim (quake used -70"};
+cvar_t in_pitch_max = {0, "in_pitch_max", "90", "how far upward you can aim (quake used 80"};
 
 cvar_t m_filter = {CVAR_SAVE, "m_filter","0", "smoothes mouse movement, less responsive but smoother aiming"};
 
-cvar_t cl_netinputpacketspersecond = {CVAR_SAVE, "cl_netinputpacketspersecond","50", "how many input packets to send to server each second"};
-cvar_t cl_netinputpacketlosstolerance = {CVAR_SAVE, "cl_netinputpacketlosstolerance", "4", "how many packets in a row can be lost without movement issues when using cl_movement (technically how many input messages to repeat in each packet that have not yet been acknowledged by the server)"};
+cvar_t cl_netinputpacketsperserverpacket = {CVAR_SAVE, "cl_netinputpacketsperserverpacket", "1.0", "send this many input packets per server packet received"};
+cvar_t cl_netinputpacketspersecond = {CVAR_SAVE, "cl_netinputpacketspersecond","20", "how many input packets to send to server each second (only used on old servers, and note this is multiplied by cl_netinputpacketsperserverpacket)"};
+cvar_t cl_netinputpacketspersecond_qw = {CVAR_SAVE, "cl_netinputpacketspersecond","72", "how many input packets to send to a qw server each second (only used on qw servers)"};
+cvar_t cl_netinputpacketlosstolerance = {CVAR_SAVE, "cl_netinputpacketlosstolerance", "1", "how many packets in a row can be lost without movement issues when using cl_movement (technically how many input messages to repeat in each packet that have not yet been acknowledged by the server), only affects DP7 and later servers (Quake uses 0, QuakeWorld uses 2, and just for comparison Quake3 uses 1)"};
 
 cvar_t cl_nodelta = {0, "cl_nodelta", "0", "disables delta compression of non-player entities in QW network protocol"};
 
+extern cvar_t v_flipped;
 
 /*
 ================
@@ -417,6 +427,9 @@ void CL_Input (void)
        // clamp before the move to prevent starting with bad angles
        CL_AdjustAngles ();
 
+       if(v_flipped.integer)
+               cl.viewangles[YAW] = -cl.viewangles[YAW];
+
        // reset some of the command fields
        cl.cmd.forwardmove = 0;
        cl.cmd.sidemove = 0;
@@ -512,6 +525,12 @@ void CL_Input (void)
                }
        }
 
+       if(v_flipped.integer)
+       {
+               cl.viewangles[YAW] = -cl.viewangles[YAW];
+               cl.cmd.sidemove = -cl.cmd.sidemove;
+       }
+
        // clamp after the move to prevent rendering with bad angles
        CL_AdjustAngles ();
 }
@@ -554,95 +573,53 @@ void CL_UpdatePrydonCursor(void)
        // calculate current view matrix
        Matrix4x4_OriginFromMatrix(&r_view.matrix, cl.cmd.cursor_start);
        // calculate direction vector of cursor in viewspace by using frustum slopes
-       VectorSet(temp, cl.cmd.cursor_screen[2] * 1000000, cl.cmd.cursor_screen[0] * -r_view.frustum_x * 1000000, cl.cmd.cursor_screen[1] * -r_view.frustum_y * 1000000);
+       VectorSet(temp, cl.cmd.cursor_screen[2] * 1000000, (v_flipped.integer ? -1 : 1) * cl.cmd.cursor_screen[0] * -r_view.frustum_x * 1000000, cl.cmd.cursor_screen[1] * -r_view.frustum_y * 1000000);
        Matrix4x4_Transform(&r_view.matrix, temp, cl.cmd.cursor_end);
        // trace from view origin to the cursor
        cl.cmd.cursor_fraction = CL_SelectTraceLine(cl.cmd.cursor_start, cl.cmd.cursor_end, cl.cmd.cursor_impact, cl.cmd.cursor_normal, &cl.cmd.cursor_entitynumber, (chase_active.integer || cl.intermission) ? &cl.entities[cl.playerentity].render : NULL);
 }
 
-void CL_ClientMovement_Replay(void);
-
-void CL_ClientMovement_InputQW(void)
+void CL_ClientMovement_ExpireOldMoves(void)
 {
        int i;
        int n;
-       // if time has not advanced, do nothing
-       if (cl.movecmd[0].time <= cl.movecmd[1].time)
-               return;
        // remove stale queue items
        n = cl.movement_numqueue;
        cl.movement_numqueue = 0;
-       for (i = 0;i < n;i++)
+       if (cls.servermovesequence)
        {
-               if (cl.movement_queue[i].sequence > cls.netcon->qw.incoming_sequence)
-                       cl.movement_queue[cl.movement_numqueue++] = cl.movement_queue[i];
-               else if (i == 0)
-                       cl.movement_replay_canjump = !cl.movement_queue[i].jump; // FIXME: this logic is quite broken
-       }
-       // add to input queue if there is room
-       if (cl.movement_numqueue < (int)(sizeof(cl.movement_queue)/sizeof(cl.movement_queue[0])))
-       {
-               // add to input queue
-               cl.movement_queue[cl.movement_numqueue].sequence = cls.netcon->qw.outgoing_sequence;
-               cl.movement_queue[cl.movement_numqueue].time = cl.movecmd[0].time;
-               cl.movement_queue[cl.movement_numqueue].frametime = cl.cmd.msec / 1000.0;
-               VectorCopy(cl.cmd.viewangles, cl.movement_queue[cl.movement_numqueue].viewangles);
-               cl.movement_queue[cl.movement_numqueue].move[0] = cl.cmd.forwardmove;
-               cl.movement_queue[cl.movement_numqueue].move[1] = cl.cmd.sidemove;
-               cl.movement_queue[cl.movement_numqueue].move[2] = cl.cmd.upmove;
-               cl.movement_queue[cl.movement_numqueue].jump = (cl.cmd.buttons & 2) != 0;
-               cl.movement_queue[cl.movement_numqueue].crouch = false;
-               cl.movement_numqueue++;
+               for (i = 0;i < n;i++)
+               {
+                       if (cl.movement_queue[i].sequence > cls.servermovesequence)
+                               cl.movement_queue[cl.movement_numqueue++] = cl.movement_queue[i];
+                       else
+                               cl.movement_replay_canjump = cl.movement_queue[i].canjump;
+               }
        }
-       CL_ClientMovement_Replay();
 }
 
 void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch)
 {
-       int i;
-       int n;
        // if time has not advanced, do nothing
-       if (cl.movecmd[0].time <= cl.movecmd[1].time)
+       if (cl.movecmd[0].msec <= 0)
                return;
-       // remove stale queue items
-       n = cl.movement_numqueue;
-       cl.movement_numqueue = 0;
-       if (cl.servermovesequence)
-       {
-               for (i = 0;i < n;i++)
-               {
-                       if (cl.movement_queue[i].sequence > cl.servermovesequence)
-                               cl.movement_queue[cl.movement_numqueue++] = cl.movement_queue[i];
-                       else if (i == 0)
-                               cl.movement_replay_canjump = !cl.movement_queue[i].jump; // FIXME: this logic is quite broken
-               }
-       }
-       else
-       {
-               for (i = 0;i < n;i++)
-               {
-                       if (cl.movement_queue[i].time >= cl.movecmd[0].time - cl_movement_latency.value / 1000.0 && cl.movement_queue[i].time <= cl.movecmd[0].time)
-                               cl.movement_queue[cl.movement_numqueue++] = cl.movement_queue[i];
-                       else if (i == 0)
-                               cl.movement_replay_canjump = !cl.movement_queue[i].jump; // FIXME: this logic is quite broken
-               }
-       }
        // add to input queue if there is room
        if (cl.movement_numqueue < (int)(sizeof(cl.movement_queue)/sizeof(cl.movement_queue[0])))
        {
                // add to input queue
-               cl.movement_queue[cl.movement_numqueue].sequence = cl.movesequence;
+               cl.movement_queue[cl.movement_numqueue].sequence = cl.movecmd[0].sequence;
                cl.movement_queue[cl.movement_numqueue].time = cl.movecmd[0].time;
-               cl.movement_queue[cl.movement_numqueue].frametime = bound(0, cl.movecmd[0].time - cl.movecmd[1].time, 0.1);
-               VectorCopy(cl.viewangles, cl.movement_queue[cl.movement_numqueue].viewangles);
-               cl.movement_queue[cl.movement_numqueue].move[0] = cl.cmd.forwardmove;
-               cl.movement_queue[cl.movement_numqueue].move[1] = cl.cmd.sidemove;
-               cl.movement_queue[cl.movement_numqueue].move[2] = cl.cmd.upmove;
+               cl.movement_queue[cl.movement_numqueue].frametime = cl.movecmd[0].msec * 0.001;
+               VectorCopy(cl.movecmd[0].viewangles, cl.movement_queue[cl.movement_numqueue].viewangles);
+               cl.movement_queue[cl.movement_numqueue].move[0] = cl.movecmd[0].forwardmove;
+               cl.movement_queue[cl.movement_numqueue].move[1] = cl.movecmd[0].sidemove;
+               cl.movement_queue[cl.movement_numqueue].move[2] = cl.movecmd[0].upmove;
                cl.movement_queue[cl.movement_numqueue].jump = buttonjump;
                cl.movement_queue[cl.movement_numqueue].crouch = buttoncrouch;
                cl.movement_numqueue++;
        }
-       CL_ClientMovement_Replay();
+
+       cl.movement_replay = true;
 }
 
 typedef enum waterlevel_e
@@ -666,8 +643,6 @@ typedef struct cl_clientmovement_state_s
        qboolean onground;
        // currently crouching
        qboolean crouched;
-       // whether jump button has been released since last jump
-       qboolean canjump;
        // what kind of water (SUPERCONTENTS_LAVA for instance)
        int watertype;
        // how deep
@@ -676,24 +651,6 @@ typedef struct cl_clientmovement_state_s
        // (this is in seconds and counts down to 0)
        float waterjumptime;
 
-       // movement parameters for physics code
-       float movevars_gravity;
-       float movevars_stopspeed;
-       float movevars_maxspeed;
-       float movevars_spectatormaxspeed;
-       float movevars_accelerate;
-       float movevars_airaccelerate;
-       float movevars_wateraccelerate;
-       float movevars_friction;
-       float movevars_waterfriction;
-       float movevars_entgravity;
-       float movevars_jumpvelocity;
-       float movevars_edgefriction;
-       float movevars_maxairspeed;
-       float movevars_stepheight;
-       float movevars_airaccel_qw;
-       float movevars_airaccel_sideways_friction;
-
        // user command
        client_movementqueue_t q;
 }
@@ -730,7 +687,7 @@ qboolean CL_ClientMovement_Unstick(cl_clientmovement_state_t *s)
        for (i = 0;i < NUMOFFSETS;i++)
        {
                VectorAdd(offsets[i], s->origin, neworigin);
-               if (!CL_TraceBox(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true).startsolid)
+               if (!CL_Move(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false).startsolid)
                {
                        VectorCopy(neworigin, s->origin);
                        return true;
@@ -761,7 +718,7 @@ void CL_ClientMovement_UpdateStatus(cl_clientmovement_state_t *s)
                // low ceiling first
                if (s->crouched)
                {
-                       trace = CL_TraceBox(s->origin, cl.playerstandmins, cl.playerstandmaxs, s->origin, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true);
+                       trace = CL_Move(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;
                }
@@ -780,22 +737,22 @@ 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_TraceBox(origin1, s->mins, s->maxs, origin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true);
-       s->onground = trace.fraction < 1 && trace.plane.normal[2] > 0.7 && s->velocity[2] < cl_gravity.value * s->q.frametime;
+       trace = CL_Move(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_TraceBox(origin1, vec3_origin, vec3_origin, origin1, true, NULL, 0, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK;
+       s->watertype = CL_Move(origin1, vec3_origin, vec3_origin, 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_TraceBox(origin1, vec3_origin, vec3_origin, origin1, true, NULL, 0, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
+               if (CL_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
                {
                        s->waterlevel = WATERLEVEL_SWIMMING;
                        origin1[2] = s->origin[2] + 22;
-                       if (CL_TraceBox(origin1, vec3_origin, vec3_origin, origin1, true, NULL, 0, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
+                       if (CL_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
                                s->waterlevel = WATERLEVEL_SUBMERGED;
                }
        }
@@ -822,20 +779,20 @@ void CL_ClientMovement_Move(cl_clientmovement_state_t *s)
        for (bump = 0, t = s->q.frametime;bump < 8 && VectorLength2(s->velocity) > 0;bump++)
        {
                VectorMA(s->origin, t, s->velocity, neworigin);
-               trace = CL_TraceBox(s->origin, s->mins, s->maxs, neworigin, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true);
+               trace = CL_Move(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] + s->movevars_stepheight);
-                       VectorSet(neworigin2, neworigin[0], neworigin[1], s->origin[2] + s->movevars_stepheight);
-                       trace2 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true);
+                       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);
                        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_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true);
+                               trace3 = CL_Move(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)
@@ -885,16 +842,16 @@ 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_TraceBox(spot, vec3_origin, vec3_origin, spot, true, NULL, 0, false).startsolid)
+               if (CL_Move(spot, vec3_origin, vec3_origin, spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
                {
                        spot[2] += 24;
-                       if (!CL_TraceBox(spot, vec3_origin, vec3_origin, spot, true, NULL, 0, false).startsolid)
+                       if (!CL_Move(spot, vec3_origin, vec3_origin, spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
                        {
                                VectorScale(forward, 50, s->velocity);
                                s->velocity[2] = 310;
                                s->waterjumptime = 2;
                                s->onground = false;
-                               s->canjump = false;
+                               s->q.canjump = false;
                        }
                }
        }
@@ -922,7 +879,7 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
                VectorScale(wishvel, 1 / wishspeed, wishdir);
        else
                VectorSet( wishdir, 0.0, 0.0, 0.0 );
-       wishspeed = min(wishspeed, s->movevars_maxspeed) * 0.7;
+       wishspeed = min(wishspeed, cl.movevars_maxspeed) * 0.7;
 
        if (s->crouched)
                wishspeed *= 0.5;
@@ -930,7 +887,7 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
        if (s->waterjumptime <= 0)
        {
                // water friction
-               f = 1 - s->q.frametime * s->movevars_waterfriction * s->waterlevel;
+               f = 1 - s->q.frametime * cl.movevars_waterfriction * s->waterlevel;
                f = bound(0, f, 1);
                VectorScale(s->velocity, f, s->velocity);
 
@@ -938,7 +895,7 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
                f = wishspeed - DotProduct(s->velocity, wishdir);
                if (f > 0)
                {
-                       f = min(s->movevars_wateraccelerate * s->q.frametime * wishspeed, f);
+                       f = min(cl.movevars_wateraccelerate * s->q.frametime * wishspeed, f);
                        VectorMA(s->velocity, f, wishdir, s->velocity);
                }
 
@@ -979,12 +936,17 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
 
        // jump if on ground with jump button pressed but only if it has been
        // released at least once since the last jump
-       if (s->q.jump && s->onground)// && s->canjump) // FIXME: canjump doesn't work properly
+       if (s->q.jump)
        {
-               s->velocity[2] += s->movevars_jumpvelocity;
-               s->onground = false;
-               s->canjump = false;
+               if (s->onground && s->q.canjump)
+               {
+                       s->velocity[2] += cl.movevars_jumpvelocity;
+                       s->onground = false;
+                       s->q.canjump = false;
+               }
        }
+       else
+               s->q.canjump = true;
 
        // calculate movement vector
        VectorSet(yawangles, 0, s->q.viewangles[1], 0);
@@ -997,7 +959,7 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
                VectorScale(wishvel, 1 / wishspeed, wishdir);
        else
                VectorSet( wishdir, 0.0, 0.0, 0.0 );
-       wishspeed = min(wishspeed, s->movevars_maxspeed);
+       wishspeed = min(wishspeed, cl.movevars_maxspeed);
        if (s->crouched)
                wishspeed *= 0.5;
 
@@ -1006,8 +968,8 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
        {
                // apply edge friction
                f = sqrt(s->velocity[0] * s->velocity[0] + s->velocity[1] * s->velocity[1]);
-               friction = s->movevars_friction;
-               if (f > 0 && s->movevars_edgefriction != 1)
+               friction = cl.movevars_friction;
+               if (f > 0 && cl.movevars_edgefriction != 1)
                {
                        vec3_t neworigin2;
                        vec3_t neworigin3;
@@ -1017,23 +979,23 @@ 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_TraceBox(neworigin2, s->mins, s->maxs, neworigin3, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true);
+                               trace = CL_Move(neworigin2, s->mins, s->maxs, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
                        else
-                               trace = CL_TraceBox(neworigin2, vec3_origin, vec3_origin, neworigin3, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true);
+                               trace = CL_Move(neworigin2, vec3_origin, vec3_origin, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
                        if (trace.fraction == 1 && !trace.startsolid)
-                               friction *= s->movevars_edgefriction;
+                               friction *= cl.movevars_edgefriction;
                }
                // apply ground friction
-               f = 1 - s->q.frametime * friction * ((f < s->movevars_stopspeed) ? (s->movevars_stopspeed / f) : 1);
+               f = 1 - s->q.frametime * friction * ((f < cl.movevars_stopspeed) ? (cl.movevars_stopspeed / f) : 1);
                f = max(f, 0);
                VectorScale(s->velocity, f, s->velocity);
                addspeed = wishspeed - DotProduct(s->velocity, wishdir);
                if (addspeed > 0)
                {
-                       accelspeed = min(s->movevars_accelerate * s->q.frametime * wishspeed, addspeed);
+                       accelspeed = min(cl.movevars_accelerate * s->q.frametime * wishspeed, addspeed);
                        VectorMA(s->velocity, accelspeed, wishdir, s->velocity);
                }
-               s->velocity[2] -= cl_gravity.value * s->q.frametime;
+               s->velocity[2] -= cl.movevars_gravity * cl.movevars_entgravity * s->q.frametime;
                if (cls.protocol == PROTOCOL_QUAKEWORLD)
                        s->velocity[2] = 0;
                if (VectorLength2(s->velocity))
@@ -1049,13 +1011,13 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
                        vec3_t vel_perpend;
 
                        // apply air speed limit
-                       wishspeed = min(wishspeed, s->movevars_maxairspeed);
+                       wishspeed = min(wishspeed, cl.movevars_maxairspeed);
 
                        /*
                        addspeed = wishspeed - DotProduct(s->velocity, wishdir);
                        if (addspeed > 0)
                        {
-                               accelspeed = min(s->movevars_accelerate * s->q.frametime * wishspeed, addspeed);
+                               accelspeed = min(cl.movevars_accelerate * s->q.frametime * wishspeed, addspeed);
                                VectorMA(s->velocity, accelspeed, wishdir, s->velocity);
                        }
                        */
@@ -1067,16 +1029,16 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
 
                        f = wishspeed - vel_straight;
                        if(f > 0)
-                               vel_straight += min(f, s->movevars_accelerate * s->q.frametime * wishspeed) * s->movevars_airaccel_qw;
+                               vel_straight += min(f, cl.movevars_airaccelerate * s->q.frametime * wishspeed) * cl.movevars_airaccel_qw;
                        if(wishspeed > 0)
-                               vel_straight += min(wishspeed, s->movevars_accelerate * s->q.frametime * wishspeed) * (1 - s->movevars_airaccel_qw);
+                               vel_straight += min(wishspeed, cl.movevars_airaccelerate * s->q.frametime * wishspeed) * (1 - cl.movevars_airaccel_qw);
 
-                       VectorM(1 - (s->q.frametime * (wishspeed / s->movevars_maxairspeed) * s->movevars_airaccel_sideways_friction), vel_perpend, vel_perpend);
+                       VectorM(1 - (s->q.frametime * (wishspeed / cl.movevars_maxairspeed) * cl.movevars_airaccel_sideways_friction), vel_perpend, vel_perpend);
 
                        VectorMA(vel_perpend, vel_straight, wishdir, s->velocity);
                        s->velocity[2] += vel_z;
                }
-               s->velocity[2] -= cl_gravity.value * s->q.frametime;
+               s->velocity[2] -= cl.movevars_gravity * cl.movevars_entgravity * s->q.frametime;
                CL_ClientMovement_Move(s);
        }
 }
@@ -1085,7 +1047,7 @@ void CL_ClientMovement_PlayerMove(cl_clientmovement_state_t *s)
 {
        //Con_Printf(" %f", frametime);
        if (!s->q.jump)
-               s->canjump = true;
+               s->q.canjump = true;
        s->waterjumptime -= s->q.frametime;
        CL_ClientMovement_UpdateStatus(s);
        if (s->waterlevel >= WATERLEVEL_SWIMMING)
@@ -1094,70 +1056,90 @@ void CL_ClientMovement_PlayerMove(cl_clientmovement_state_t *s)
                CL_ClientMovement_Physics_Walk(s);
 }
 
+extern cvar_t slowmo;
+void CL_UpdateMoveVars(void)
+{
+       if (cls.protocol == PROTOCOL_QUAKEWORLD)
+               cl.movevars_ticrate = 1.0 / bound(1, cl_netinputpacketspersecond_qw.value, 100);
+       else if (cl.stats[STAT_MOVEVARS_TICRATE])
+       {
+               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_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];
+       }
+       else
+       {
+               cl.movevars_ticrate = 1.0 / bound(1, cl_netinputpacketspersecond.value, 100);
+               cl.movevars_timescale = 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_sideways_friction = cl_movement_airaccel_sideways_friction.value;
+       }
+}
+
 void CL_ClientMovement_Replay(void)
 {
        int i;
+       qboolean canjump;
+       double totalmovetime;
        cl_clientmovement_state_t s;
 
+       CL_ClientMovement_ExpireOldMoves();
+
        // 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
-       s.canjump = cl.movement_replay_canjump;
        //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]);
 
-       // set up movement variables
-       if (cls.protocol == PROTOCOL_QUAKEWORLD)
-       {
-               s.movevars_gravity = cl.qw_movevars_gravity;
-               s.movevars_stopspeed = cl.qw_movevars_stopspeed;
-               s.movevars_maxspeed = cl.qw_movevars_maxspeed;
-               s.movevars_spectatormaxspeed = cl.qw_movevars_spectatormaxspeed;
-               s.movevars_accelerate = cl.qw_movevars_accelerate;
-               s.movevars_airaccelerate = cl.qw_movevars_airaccelerate;
-               s.movevars_wateraccelerate = cl.qw_movevars_wateraccelerate;
-               s.movevars_friction = cl.qw_movevars_friction;
-               s.movevars_waterfriction = cl.qw_movevars_waterfriction;
-               s.movevars_entgravity = cl.qw_movevars_entgravity;
-               s.movevars_jumpvelocity = cl_movement_jumpvelocity.value;
-               s.movevars_edgefriction = cl_movement_edgefriction.value;
-               s.movevars_maxairspeed = cl_movement_maxairspeed.value;
-               s.movevars_stepheight = cl_movement_stepheight.value;
-               s.movevars_airaccel_qw = 1.0;
-               s.movevars_airaccel_sideways_friction = 0.0;
-       }
-       else
-       {
-               s.movevars_gravity = sv_gravity.value;
-               s.movevars_stopspeed = cl_movement_stopspeed.value;
-               s.movevars_maxspeed = cl_movement_maxspeed.value;
-               s.movevars_spectatormaxspeed = cl_movement_maxspeed.value;
-               s.movevars_accelerate = cl_movement_accelerate.value;
-               s.movevars_airaccelerate = cl_movement_airaccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_airaccelerate.value;
-               s.movevars_wateraccelerate = cl_movement_wateraccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_wateraccelerate.value;
-               s.movevars_friction = cl_movement_friction.value;
-               s.movevars_waterfriction = cl_movement_waterfriction.value < 0 ? cl_movement_friction.value : cl_movement_waterfriction.value;
-               s.movevars_entgravity = 1;
-               s.movevars_jumpvelocity = cl_movement_jumpvelocity.value;
-               s.movevars_edgefriction = cl_movement_edgefriction.value;
-               s.movevars_maxairspeed = cl_movement_maxairspeed.value;
-               s.movevars_stepheight = cl_movement_stepheight.value;
-               s.movevars_airaccel_qw = cl_movement_airaccel_qw.value;
-               s.movevars_airaccel_sideways_friction = cl_movement_airaccel_sideways_friction.value;
-       }
-
-       cl.movement_predicted = (cl_movement.integer && !cls.demoplayback && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission) && ((cls.protocol != PROTOCOL_DARKPLACES6 && cls.protocol != PROTOCOL_DARKPLACES7) || cl.servermovesequence);
+       totalmovetime = 0;
+       for (i = 0;i < cl.movement_numqueue - 1;i++)
+               totalmovetime += cl.movement_queue[i].frametime;
+       cl.movement_predicted = totalmovetime * 1000.0 >= 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 && %i && (%i && %i && %i == %i && %i > 0 && %i\n", cl.movement_predicted, totalmovetime * 1000.0, 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("%f: ", cl.movecmd[0].time);
+               //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
 
+               canjump = cl.movement_replay_canjump;
                for (i = 0;i < cl.movement_numqueue;i++)
                {
                        s.q = cl.movement_queue[i];
+                       s.q.canjump = canjump;
                        // if a move is more than 50ms, do it as two moves (matching qwsv)
                        if (s.q.frametime > 0.05)
                        {
@@ -1165,6 +1147,7 @@ void CL_ClientMovement_Replay(void)
                                CL_ClientMovement_PlayerMove(&s);
                        }
                        CL_ClientMovement_PlayerMove(&s);
+                       canjump = cl.movement_queue[i].canjump = s.q.canjump;
                }
        }
        else
@@ -1174,39 +1157,54 @@ void CL_ClientMovement_Replay(void)
        }
        CL_ClientMovement_UpdateStatus(&s);
 
-       // store replay location
-       if (cl.movecmd[0].time > cl.movecmd[1].time)
+       if (cl.movement_replay)
        {
-               cl.movement_time[1] = cl.movecmd[1].time;
-               cl.movement_time[0] = cl.movecmd[0].time;
-               cl.movement_time[2] = cl.time;
-               VectorCopy(cl.movement_origin, cl.movement_oldorigin);
+               cl.movement_replay = false;
+               // update interpolation timestamps if time has passed
+               if (cl.movement_time[0] != cl.movecmd[0].time)
+               {
+                       cl.movement_time[1] = cl.movement_time[0];
+                       cl.movement_time[0] = cl.movecmd[0].time;
+                       cl.movement_time[2] = cl.time;
+                       VectorCopy(cl.movement_origin, cl.movement_oldorigin);
+                       //VectorCopy(s.origin, cl.entities[cl.playerentity].state_current.origin);
+                       //VectorSet(cl.entities[cl.playerentity].state_current.angles, 0, cl.viewangles[1], 0);
+               }
+
+               // update the interpolation target position and velocity
                VectorCopy(s.origin, cl.movement_origin);
                VectorCopy(s.velocity, cl.movement_velocity);
-               //VectorCopy(s.origin, cl.entities[cl.playerentity].state_current.origin);
-               //VectorSet(cl.entities[cl.playerentity].state_current.angles, 0, cl.viewangles[1], 0);
-
-               // update the onground flag if appropriate
-               // when not predicted, cl.onground is only cleared by cl_parse.c, but can
-               // be set forcefully here to hide server inconsistencies in the onground
-               // flag (such as when stepping up stairs, the onground flag tends to turn
-               // off briefly due to precision errors, particularly at high framerates),
-               // such inconsistencies can mess up the gun bobbing and stair smoothing,
-               // so they must be avoided.
-               if (cl.movement_predicted)
-                       cl.onground = s.onground;
-               else if (s.onground)
+       }
+
+       // 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;
+       }
 
-               // react to onground state changes (for gun bob)
-               if (cl.onground)
-               {
-                       if (!cl.oldonground)
-                               cl.hitgroundtime = cl.movecmd[0].time;
-                       cl.lastongroundtime = cl.movecmd[0].time;
-               }
-               cl.oldonground = cl.onground;
+       // react to onground state changes (for gun bob)
+       if (cl.onground)
+       {
+               if (!cl.oldonground)
+                       cl.hitgroundtime = cl.movecmd[0].time;
+               cl.lastongroundtime = cl.movecmd[0].time;
        }
+       cl.oldonground = cl.onground;
 }
 
 void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, usercmd_t *from, usercmd_t *to)
@@ -1259,38 +1257,51 @@ CL_SendMove
 usercmd_t nullcmd; // for delta compression of qw moves
 void CL_SendMove(void)
 {
-       int i, j, packetloss, maxusercmds;
+       int i, j, packetloss;
        int bits;
        sizebuf_t buf;
        unsigned char data[1024];
        static double lastsendtime = 0;
-       double msectime;
-       static double oldmsectime;
+       double packettime;
+       int msecdelta;
+
+       CL_ClientMovement_ExpireOldMoves();
 
        // if playing a demo, do nothing
        if (!cls.netcon)
                return;
 
-#if 0
-       if (cl.movement_predicted && cls.signon == SIGNONS && cls.protocol != PROTOCOL_QUAKEWORLD)
+       // don't send too often or else network connections can get clogged by a high renderer framerate
+       packettime = cl.movevars_ticrate;
+       if (cls.protocol != PROTOCOL_QUAKEWORLD)
+               packettime /= (double)bound(1, cl_netinputpacketsperserverpacket.value, 10);
+       // send input every frame in singleplayer
+       if (cl.islocalgame)
+               packettime = 0;
+       // quakeworld servers take only frametimes
+       // predicted dp7 servers take current interpolation time
+       // unpredicted servers take an echo of the latest server timestamp
+       cl.cmd.time = cl.time;
+       cl.cmd.sequence = cls.movesequence;
+       if (cls.protocol == PROTOCOL_QUAKEWORLD)
        {
-               if (!cl.movement_needupdate)
+               if (realtime < lastsendtime + packettime)
                        return;
-               cl.movement_needupdate = false;
+               cl.cmd.sequence = cls.netcon->qw.outgoing_sequence;
        }
        else
-#endif
        {
-               double packettime = 1.0 / bound(10, cl_netinputpacketspersecond.value, 100);
-               // don't send too often or else network connections can get clogged by a high renderer framerate
-               if (realtime < lastsendtime + packettime)
+               // movement should be sent immediately whenever a server
+               // packet is received, to minimize ping times
+               if (!cl.movement_needupdate && realtime < lastsendtime + packettime)
                        return;
-               // don't let it fall behind if CL_SendMove hasn't been called recently
-               // (such is the case when framerate is too low for instance)
-               lastsendtime = max(lastsendtime + packettime, realtime);
        }
 
-       cl.cmd.time = cls.protocol == PROTOCOL_QUAKEWORLD ? realtime : cl.time;
+       // don't let it fall behind if CL_SendMove hasn't been called recently
+       // (such is the case when framerate is too low for instance)
+       lastsendtime = bound(realtime, lastsendtime + packettime, realtime + packettime);
+       // set the flag indicating that we sent a packet recently
+       cl.movement_needupdate = false;
 
        buf.maxsize = sizeof(data);
        buf.cursize = 0;
@@ -1299,234 +1310,227 @@ void CL_SendMove(void)
        // conditions for sending a move:
        // if the move advances time or if the game is paused (in which case time
        // is not advancing)
-       if ((cl.cmd.time > cl.movecmd[0].time || cl.mtime[0] <= cl.mtime[1]) && cls.signon == SIGNONS)
-       {
-               // send the movement message
-               // PROTOCOL_QUAKE        clc_move = 16 bytes total
-               // PROTOCOL_QUAKEDP      clc_move = 16 bytes total
-               // PROTOCOL_NEHAHRAMOVIE clc_move = 16 bytes total
-               // PROTOCOL_DARKPLACES1  clc_move = 19 bytes total
-               // PROTOCOL_DARKPLACES2  clc_move = 25 bytes total
-               // PROTOCOL_DARKPLACES3  clc_move = 25 bytes total
-               // PROTOCOL_DARKPLACES4  clc_move = 19 bytes total
-               // PROTOCOL_DARKPLACES5  clc_move = 19 bytes total
-               // PROTOCOL_DARKPLACES6  clc_move = 52 bytes total
-               // PROTOCOL_DARKPLACES7  clc_move = 56 bytes total per move (can be up to 16 moves)
-               // PROTOCOL_QUAKEWORLD   clc_move = 34 bytes total (typically, but can reach 43 bytes, or even 49 bytes with roll)
-
-               // set button bits
-               // LordHavoc: added 6 new buttons and use and chat buttons, and prydon cursor active button
-               bits = 0;
-               if (in_attack.state   & 3) bits |=   1;in_attack.state  &= ~2;
-               if (in_jump.state     & 3) bits |=   2;in_jump.state    &= ~2;
-               if (in_button3.state  & 3) bits |=   4;in_button3.state &= ~2;
-               if (in_button4.state  & 3) bits |=   8;in_button4.state &= ~2;
-               if (in_button5.state  & 3) bits |=  16;in_button5.state &= ~2;
-               if (in_button6.state  & 3) bits |=  32;in_button6.state &= ~2;
-               if (in_button7.state  & 3) bits |=  64;in_button7.state &= ~2;
-               if (in_button8.state  & 3) bits |= 128;in_button8.state &= ~2;
-               if (in_use.state      & 3) bits |= 256;in_use.state     &= ~2;
-               if (key_dest != key_game || key_consoleactive) bits |= 512;
-               if (cl_prydoncursor.integer) bits |= 1024;
-               if (in_button9.state  & 3)  bits |=   2048;in_button9.state  &= ~2;
-               if (in_button10.state  & 3) bits |=   4096;in_button10.state &= ~2;
-               if (in_button11.state  & 3) bits |=   8192;in_button11.state &= ~2;
-               if (in_button12.state  & 3) bits |=  16384;in_button12.state &= ~2;
-               if (in_button13.state  & 3) bits |=  32768;in_button13.state &= ~2;
-               if (in_button14.state  & 3) bits |=  65536;in_button14.state &= ~2;
-               if (in_button15.state  & 3) bits |= 131072;in_button15.state &= ~2;
-               if (in_button16.state  & 3) bits |= 262144;in_button16.state &= ~2;
-               // button bits 19-31 unused currently
-               // rotate/zoom view serverside if PRYDON_CLIENTCURSOR cursor is at edge of screen
-               if (cl.cmd.cursor_screen[0] <= -1) bits |= 8;
-               if (cl.cmd.cursor_screen[0] >=  1) bits |= 16;
-               if (cl.cmd.cursor_screen[1] <= -1) bits |= 32;
-               if (cl.cmd.cursor_screen[1] >=  1) bits |= 64;
-               cl.cmd.buttons = bits;
-
-               // set impulse
-               cl.cmd.impulse = in_impulse;
-               in_impulse = 0;
-
-               // movement is set by input code (forwardmove/sidemove/upmove)
-
-               // set viewangles
-               VectorCopy(cl.viewangles, cl.cmd.viewangles);
-
-               msectime = floor(cl.cmd.time * 1000);
-               cl.cmd.msec = (unsigned char)bound(0, msectime - oldmsectime, 255);
-               // ridiculous value rejection (matches qw)
-               if (cl.cmd.msec > 250)
-                       cl.cmd.msec = 100;
-               oldmsectime = msectime;
-
-               cl.movesequence++;
-               if (cl_movement.integer)
-                       cl.cmd.sequence = cl.movesequence;
-               else
-                       cl.cmd.sequence = 0;
+       // don't send a new input packet if the connection is still saturated from
+       // the last one (or chat messages, etc)
+       // note: this behavior comes from QW
+       if ((cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS) && !NetConn_CanSend(cls.netcon) && !cl.islocalgame)
+               return;
 
-               // set prydon cursor info
-               CL_UpdatePrydonCursor();
+       // increase the move counter since we intend to send a move
+       cls.movesequence++;
+
+       // send the movement message
+       // PROTOCOL_QUAKE        clc_move = 16 bytes total
+       // PROTOCOL_QUAKEDP      clc_move = 16 bytes total
+       // PROTOCOL_NEHAHRAMOVIE clc_move = 16 bytes total
+       // PROTOCOL_DARKPLACES1  clc_move = 19 bytes total
+       // PROTOCOL_DARKPLACES2  clc_move = 25 bytes total
+       // PROTOCOL_DARKPLACES3  clc_move = 25 bytes total
+       // PROTOCOL_DARKPLACES4  clc_move = 19 bytes total
+       // PROTOCOL_DARKPLACES5  clc_move = 19 bytes total
+       // PROTOCOL_DARKPLACES6  clc_move = 52 bytes total
+       // PROTOCOL_DARKPLACES7  clc_move = 56 bytes total per move (can be up to 16 moves)
+       // PROTOCOL_QUAKEWORLD   clc_move = 34 bytes total (typically, but can reach 43 bytes, or even 49 bytes with roll)
+
+       // set prydon cursor info
+       CL_UpdatePrydonCursor();
+
+       // set button bits
+       // LordHavoc: added 6 new buttons and use and chat buttons, and prydon cursor active button
+       bits = 0;
+       if (in_attack.state   & 3) bits |=   1;in_attack.state  &= ~2;
+       if (in_jump.state     & 3) bits |=   2;in_jump.state    &= ~2;
+       if (in_button3.state  & 3) bits |=   4;in_button3.state &= ~2;
+       if (in_button4.state  & 3) bits |=   8;in_button4.state &= ~2;
+       if (in_button5.state  & 3) bits |=  16;in_button5.state &= ~2;
+       if (in_button6.state  & 3) bits |=  32;in_button6.state &= ~2;
+       if (in_button7.state  & 3) bits |=  64;in_button7.state &= ~2;
+       if (in_button8.state  & 3) bits |= 128;in_button8.state &= ~2;
+       if (in_use.state      & 3) bits |= 256;in_use.state     &= ~2;
+       if (key_dest != key_game || key_consoleactive) bits |= 512;
+       if (cl_prydoncursor.integer) bits |= 1024;
+       if (in_button9.state  & 3)  bits |=   2048;in_button9.state  &= ~2;
+       if (in_button10.state  & 3) bits |=   4096;in_button10.state &= ~2;
+       if (in_button11.state  & 3) bits |=   8192;in_button11.state &= ~2;
+       if (in_button12.state  & 3) bits |=  16384;in_button12.state &= ~2;
+       if (in_button13.state  & 3) bits |=  32768;in_button13.state &= ~2;
+       if (in_button14.state  & 3) bits |=  65536;in_button14.state &= ~2;
+       if (in_button15.state  & 3) bits |= 131072;in_button15.state &= ~2;
+       if (in_button16.state  & 3) bits |= 262144;in_button16.state &= ~2;
+       // button bits 19-31 unused currently
+       // rotate/zoom view serverside if PRYDON_CLIENTCURSOR cursor is at edge of screen
+       if (cl.cmd.cursor_screen[0] <= -1) bits |= 8;
+       if (cl.cmd.cursor_screen[0] >=  1) bits |= 16;
+       if (cl.cmd.cursor_screen[1] <= -1) bits |= 32;
+       if (cl.cmd.cursor_screen[1] >=  1) bits |= 64;
+       cl.cmd.buttons = bits;
+
+       // set impulse
+       cl.cmd.impulse = in_impulse;
+       in_impulse = 0;
+
+       // movement is set by input code (forwardmove/sidemove/upmove)
+
+       // set viewangles
+       VectorCopy(cl.viewangles, cl.cmd.viewangles);
+
+       msecdelta = (int)(floor(cl.cmd.time * 1000) - floor(cl.movecmd[0].time * 1000));
+       cl.cmd.msec = (unsigned char)bound(0, msecdelta, 255);
+       // ridiculous value rejection (matches qw)
+       if (cl.cmd.msec > 250)
+               cl.cmd.msec = 100;
+
+       cl.cmd.predicted = cl_movement.integer;
+
+       // always dump the first two messages, because they may contain leftover inputs from the last level
+       if (cl.cmd.sequence <= 2)
+               cl.cmd.forwardmove = cl.cmd.sidemove = cl.cmd.upmove = cl.cmd.impulse = cl.cmd.buttons = 0;
+
+       // update the cl.movecmd array which holds the most recent moves
+       for (i = CL_MAX_USERCMDS - 1;i >= 1;i--)
+               cl.movecmd[i] = cl.movecmd[i-1];
+       cl.movecmd[0] = cl.cmd;
 
-               // always dump the first two messages, because they may contain leftover inputs from the last level
-               if (cl.movesequence > 2)
+       if (cls.protocol == PROTOCOL_QUAKEWORLD)
+       {
+               int checksumindex;
+
+               CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
+
+               MSG_WriteByte(&buf, qw_clc_move);
+               // save the position for a checksum byte
+               checksumindex = buf.cursize;
+               MSG_WriteByte(&buf, 0);
+               // packet loss percentage
+               for (j = 0, packetloss = 0;j < NETGRAPH_PACKETS;j++)
+                       if (cls.netcon->incoming_unreliablesize[j] == 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]);
+               // calculate the checksum
+               buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->qw.outgoing_sequence);
+               // if delta compression history overflows, request no delta
+               if (cls.netcon->qw.outgoing_sequence - cl.qw_validsequence >= QW_UPDATE_BACKUP-1)
+                       cl.qw_validsequence = 0;
+               // request delta compression if appropriate
+               if (cl.qw_validsequence && !cl_nodelta.integer && cls.state == ca_connected && !cls.demorecording)
                {
-                       // update the cl.movecmd array which holds the most recent moves
-                       for (i = CL_MAX_USERCMDS - 1;i >= 1;i--)
-                               cl.movecmd[i] = cl.movecmd[i-1];
-                       cl.movecmd[0] = cl.cmd;
+                       cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = cl.qw_validsequence;
+                       MSG_WriteByte(&buf, qw_clc_delta);
+                       MSG_WriteByte(&buf, cl.qw_validsequence & 255);
+               }
+               else
+                       cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = -1;
+       }
+       else if (cls.signon == SIGNONS)
+       {
+               if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3)
+               {
+                       // 5 bytes
+                       MSG_WriteByte (&buf, clc_move);
+                       MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
+                       // 3 bytes
+                       for (i = 0;i < 3;i++)
+                               MSG_WriteAngle8i (&buf, cl.movecmd[0].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);
+                       // 2 bytes
+                       MSG_WriteByte (&buf, cl.movecmd[0].buttons);
+                       MSG_WriteByte (&buf, cl.movecmd[0].impulse);
+               }
+               else if (cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3)
+               {
+                       // 5 bytes
+                       MSG_WriteByte (&buf, clc_move);
+                       MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
+                       // 12 bytes
+                       for (i = 0;i < 3;i++)
+                               MSG_WriteAngle32f (&buf, cl.movecmd[0].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);
+                       // 2 bytes
+                       MSG_WriteByte (&buf, cl.movecmd[0].buttons);
+                       MSG_WriteByte (&buf, cl.movecmd[0].impulse);
+               }
+               else if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5)
+               {
+                       // 5 bytes
+                       MSG_WriteByte (&buf, clc_move);
+                       MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
+                       // 6 bytes
+                       for (i = 0;i < 3;i++)
+                               MSG_WriteAngle16i (&buf, cl.movecmd[0].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);
+                       // 2 bytes
+                       MSG_WriteByte (&buf, cl.movecmd[0].buttons);
+                       MSG_WriteByte (&buf, cl.movecmd[0].impulse);
+               }
+               else if (cls.signon == SIGNONS)
+               {
+                       int maxusercmds;
+                       usercmd_t *cmd;
 
-                       // set the maxusercmds variable to limit how many should be sent
-                       if (cls.protocol == PROTOCOL_QUAKEWORLD)
-                       {
-                               // qw uses exactly 3 moves
-                               maxusercmds = 3;
-                       }
-                       else
-                       {
-                               // configurable number of unacknowledged moves
-                               maxusercmds = bound(1, cl_netinputpacketlosstolerance.integer + 1, 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.movement_predicted)
-                                       maxusercmds = 1;
-                       }
+                       // FIXME: cl.movecmd[0].buttons & 16 is +button5, Nexuiz specific
+                       CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, (cl.movecmd[0].buttons & 16) != 0);
 
-                       if (cls.protocol == PROTOCOL_QUAKEWORLD)
-                       {
-                               int checksumindex;
-
-                               CL_ClientMovement_InputQW();
-
-                               MSG_WriteByte(&buf, qw_clc_move);
-                               // save the position for a checksum byte
-                               checksumindex = buf.cursize;
-                               MSG_WriteByte(&buf, 0);
-                               // packet loss percentage
-                               for (j = 0, packetloss = 0;j < 100;j++)
-                                       packetloss += cls.netcon->packetlost[j];
-                               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]);
-                               // calculate the checksum
-                               buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->qw.outgoing_sequence);
-                               // if delta compression history overflows, request no delta
-                               if (cls.netcon->qw.outgoing_sequence - cl.qw_validsequence >= QW_UPDATE_BACKUP-1)
-                                       cl.qw_validsequence = 0;
-                               // request delta compression if appropriate
-                               if (cl.qw_validsequence && !cl_nodelta.integer && cls.state == ca_connected && !cls.demorecording)
-                               {
-                                       cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = cl.qw_validsequence;
-                                       MSG_WriteByte(&buf, qw_clc_delta);
-                                       MSG_WriteByte(&buf, cl.qw_validsequence & 255);
-                               }
-                               else
-                                       cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = -1;
-                       }
-                       else if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE)
+                       // set the maxusercmds variable to limit how many should be sent
+                       maxusercmds = bound(1, cl_netinputpacketlosstolerance.integer + 1, 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;
+
+                       // send the latest moves in order, the old ones will be
+                       // ignored by the server harmlessly, however if the previous
+                       // packets were lost these moves will be used
+                       //
+                       // this reduces packet loss impact on gameplay.
+                       for (j = 0, cmd = &cl.movecmd[maxusercmds-1];j < maxusercmds;j++, cmd--)
                        {
-                               CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
-
-                               // 5 bytes
+                               // don't repeat any stale moves
+                               if (cmd->sequence && cmd->sequence < cls.servermovesequence)
+                                       continue;
+                               // 5/9 bytes
                                MSG_WriteByte (&buf, clc_move);
-                               MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
-                               // 3 bytes
-                               for (i = 0;i < 3;i++)
-                                       MSG_WriteAngle8i (&buf, cl.movecmd[0].viewangles[i]);
+                               if (cls.protocol != PROTOCOL_DARKPLACES6)
+                                       MSG_WriteLong (&buf, cmd->predicted ? cmd->sequence : 0);
+                               MSG_WriteFloat (&buf, cmd->time); // last server packet time
                                // 6 bytes
-                               MSG_WriteCoord16i (&buf, cl.movecmd[0].forwardmove);
-                               MSG_WriteCoord16i (&buf, cl.movecmd[0].sidemove);
-                               MSG_WriteCoord16i (&buf, cl.movecmd[0].upmove);
-                               // 2 bytes
-                               MSG_WriteByte (&buf, cl.movecmd[0].buttons);
-                               MSG_WriteByte (&buf, cl.movecmd[0].impulse);
-                       }
-                       else if (cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3)
-                       {
-                               CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
-
-                               // 5 bytes
-                               MSG_WriteByte (&buf, clc_move);
-                               MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
-                               // 12 bytes
                                for (i = 0;i < 3;i++)
-                                       MSG_WriteAngle32f (&buf, cl.movecmd[0].viewangles[i]);
+                                       MSG_WriteAngle16i (&buf, 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);
-                               // 2 bytes
-                               MSG_WriteByte (&buf, cl.movecmd[0].buttons);
-                               MSG_WriteByte (&buf, cl.movecmd[0].impulse);
-                       }
-                       else if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5)
-                       {
-                               CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
-
+                               MSG_WriteCoord16i (&buf, cmd->forwardmove);
+                               MSG_WriteCoord16i (&buf, cmd->sidemove);
+                               MSG_WriteCoord16i (&buf, cmd->upmove);
                                // 5 bytes
-                               MSG_WriteByte (&buf, clc_move);
-                               MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
-                               // 6 bytes
-                               for (i = 0;i < 3;i++)
-                                       MSG_WriteAngle16i (&buf, cl.movecmd[0].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);
-                               // 2 bytes
-                               MSG_WriteByte (&buf, cl.movecmd[0].buttons);
-                               MSG_WriteByte (&buf, cl.movecmd[0].impulse);
-                       }
-                       else
-                       {
-                               usercmd_t *cmd;
-                               // FIXME: cl.movecmd[0].buttons & 16 is +button5, Nexuiz specific
-                               CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, (cl.movecmd[0].buttons & 16) != 0);
-
-                               // send the latest moves in order, the old ones will be
-                               // ignored by the server harmlessly, however if the previous
-                               // packets were lost these moves will be used
-                               //
-                               // this reduces packet loss impact on gameplay.
-                               for (j = 0, cmd = &cl.movecmd[maxusercmds-1];j < maxusercmds;j++, cmd--)
-                               {
-                                       // don't repeat any stale moves
-                                       if (cmd->sequence && cmd->sequence < cl.servermovesequence)
-                                               continue;
-                                       // 5/9 bytes
-                                       MSG_WriteByte (&buf, clc_move);
-                                       if (cls.protocol != PROTOCOL_DARKPLACES6)
-                                               MSG_WriteLong (&buf, cmd->sequence);
-                                       MSG_WriteFloat (&buf, cmd->time); // last server packet time
-                                       // 6 bytes
-                                       for (i = 0;i < 3;i++)
-                                               MSG_WriteAngle16i (&buf, cmd->viewangles[i]);
-                                       // 6 bytes
-                                       MSG_WriteCoord16i (&buf, cmd->forwardmove);
-                                       MSG_WriteCoord16i (&buf, cmd->sidemove);
-                                       MSG_WriteCoord16i (&buf, cmd->upmove);
-                                       // 5 bytes
-                                       MSG_WriteLong (&buf, cmd->buttons);
-                                       MSG_WriteByte (&buf, cmd->impulse);
-                                       // PRYDON_CLIENTCURSOR
-                                       // 30 bytes
-                                       MSG_WriteShort (&buf, (short)(cmd->cursor_screen[0] * 32767.0f));
-                                       MSG_WriteShort (&buf, (short)(cmd->cursor_screen[1] * 32767.0f));
-                                       MSG_WriteFloat (&buf, cmd->cursor_start[0]);
-                                       MSG_WriteFloat (&buf, cmd->cursor_start[1]);
-                                       MSG_WriteFloat (&buf, cmd->cursor_start[2]);
-                                       MSG_WriteFloat (&buf, cmd->cursor_impact[0]);
-                                       MSG_WriteFloat (&buf, cmd->cursor_impact[1]);
-                                       MSG_WriteFloat (&buf, cmd->cursor_impact[2]);
-                                       MSG_WriteShort (&buf, cmd->cursor_entitynumber);
-                               }
+                               MSG_WriteLong (&buf, cmd->buttons);
+                               MSG_WriteByte (&buf, cmd->impulse);
+                               // PRYDON_CLIENTCURSOR
+                               // 30 bytes
+                               MSG_WriteShort (&buf, (short)(cmd->cursor_screen[0] * 32767.0f));
+                               MSG_WriteShort (&buf, (short)(cmd->cursor_screen[1] * 32767.0f));
+                               MSG_WriteFloat (&buf, cmd->cursor_start[0]);
+                               MSG_WriteFloat (&buf, cmd->cursor_start[1]);
+                               MSG_WriteFloat (&buf, cmd->cursor_start[2]);
+                               MSG_WriteFloat (&buf, cmd->cursor_impact[0]);
+                               MSG_WriteFloat (&buf, cmd->cursor_impact[1]);
+                               MSG_WriteFloat (&buf, cmd->cursor_impact[2]);
+                               MSG_WriteShort (&buf, cmd->cursor_entitynumber);
                        }
                }
        }
 
-       if (cls.protocol != PROTOCOL_QUAKEWORLD)
+       if (cls.protocol != PROTOCOL_QUAKEWORLD && buf.cursize)
        {
                // ack the last few frame numbers
                // (redundent to improve handling of client->server packet loss)
@@ -1535,7 +1539,7 @@ void CL_SendMove(void)
                {
                        if (cl.latestframenums[i] > 0)
                        {
-                               if (developer_networkentities.integer >= 1)
+                               if (developer_networkentities.integer >= 10)
                                        Con_Printf("send clc_ackframe %i\n", cl.latestframenums[i]);
                                MSG_WriteByte(&buf, clc_ackframe);
                                MSG_WriteLong(&buf, cl.latestframenums[i]);
@@ -1546,21 +1550,19 @@ void CL_SendMove(void)
        // PROTOCOL_DARKPLACES6 = 67 bytes per packet
        // PROTOCOL_DARKPLACES7 = 71 bytes per packet
 
-       if (cls.protocol != PROTOCOL_QUAKEWORLD)
+       // acknowledge any recently received data blocks
+       for (i = 0;i < CL_MAX_DOWNLOADACKS && (cls.dp_downloadack[i].start || cls.dp_downloadack[i].size);i++)
        {
-               // acknowledge any recently received data blocks
-               for (i = 0;i < CL_MAX_DOWNLOADACKS && (cls.dp_downloadack[i].start || cls.dp_downloadack[i].size);i++)
-               {
-                       MSG_WriteByte(&buf, clc_ackdownloaddata);
-                       MSG_WriteLong(&buf, cls.dp_downloadack[i].start);
-                       MSG_WriteShort(&buf, cls.dp_downloadack[i].size);
-                       cls.dp_downloadack[i].start = 0;
-                       cls.dp_downloadack[i].size = 0;
-               }
+               MSG_WriteByte(&buf, clc_ackdownloaddata);
+               MSG_WriteLong(&buf, cls.dp_downloadack[i].start);
+               MSG_WriteShort(&buf, cls.dp_downloadack[i].size);
+               cls.dp_downloadack[i].start = 0;
+               cls.dp_downloadack[i].size = 0;
        }
 
        // send the reliable message (forwarded commands) if there is one
-       NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol);
+       if (buf.cursize || cls.netcon->message.cursize)
+               NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, max(20*(buf.cursize+40), cl_rate.integer), false);
 
        if (cls.netcon->message.overflowed)
        {
@@ -1593,7 +1595,7 @@ void CL_InitInput (void)
        Cmd_AddCommand ("-lookup", IN_LookupUp, "stop looking upward");
        Cmd_AddCommand ("+lookdown", IN_LookdownDown, "look downward");
        Cmd_AddCommand ("-lookdown", IN_LookdownUp, "stop looking downward");
-       Cmd_AddCommand ("+strafe", IN_StrafeDown, "activate strafing mode (move instead of turn)\n");
+       Cmd_AddCommand ("+strafe", IN_StrafeDown, "activate strafing mode (move instead of turn)");
        Cmd_AddCommand ("-strafe", IN_StrafeUp, "deactivate strafing mode");
        Cmd_AddCommand ("+moveleft", IN_MoveleftDown, "strafe left");
        Cmd_AddCommand ("-moveleft", IN_MoveleftUp, "stop strafing left");
@@ -1646,29 +1648,32 @@ void CL_InitInput (void)
        Cmd_AddCommand ("-button16", IN_Button16Up, "deactivate button16");
 
        // 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: 87654321)");
+       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)");
 
        Cvar_RegisterVariable(&cl_movement);
-       Cvar_RegisterVariable(&cl_movement_latency);
+       Cvar_RegisterVariable(&cl_movement_minping);
        Cvar_RegisterVariable(&cl_movement_maxspeed);
        Cvar_RegisterVariable(&cl_movement_maxairspeed);
        Cvar_RegisterVariable(&cl_movement_stopspeed);
        Cvar_RegisterVariable(&cl_movement_friction);
+       Cvar_RegisterVariable(&cl_movement_wallfriction);
+       Cvar_RegisterVariable(&cl_movement_waterfriction);
        Cvar_RegisterVariable(&cl_movement_edgefriction);
        Cvar_RegisterVariable(&cl_movement_stepheight);
-       Cvar_RegisterVariable(&cl_movement_airaccelerate);
        Cvar_RegisterVariable(&cl_movement_accelerate);
+       Cvar_RegisterVariable(&cl_movement_airaccelerate);
+       Cvar_RegisterVariable(&cl_movement_wateraccelerate);
        Cvar_RegisterVariable(&cl_movement_jumpvelocity);
        Cvar_RegisterVariable(&cl_movement_airaccel_qw);
        Cvar_RegisterVariable(&cl_movement_airaccel_sideways_friction);
-       Cvar_RegisterVariable(&cl_gravity);
-       Cvar_RegisterVariable(&cl_slowmo);
 
        Cvar_RegisterVariable(&in_pitch_min);
        Cvar_RegisterVariable(&in_pitch_max);
        Cvar_RegisterVariable(&m_filter);
 
+       Cvar_RegisterVariable(&cl_netinputpacketsperserverpacket);
        Cvar_RegisterVariable(&cl_netinputpacketspersecond);
+       Cvar_RegisterVariable(&cl_netinputpacketspersecond_qw);
        Cvar_RegisterVariable(&cl_netinputpacketlosstolerance);
 
        Cvar_RegisterVariable(&cl_nodelta);