]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_input.c
removed trailing comments on cvar declarations and Cmd_AddCommand calls
[xonotic/darkplaces.git] / cl_input.c
index 9ac1fa0caab98861b8ddbf72e541f39b2dafe42e..569740bb829dcc79f05875acd3ab403a1d761b92 100644 (file)
@@ -320,7 +320,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_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"};
+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_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)"};
@@ -337,12 +337,13 @@ cvar_t cl_movement_airaccel_sideways_friction = {0, "cl_movement_airaccel_sidewa
 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_nodelta = {0, "cl_nodelta", "0", "disables delta compression of non-player entities in QW network protocol"};
 
@@ -403,12 +404,12 @@ qboolean cl_ignoremousemove = false;
 
 /*
 ================
-CL_Move
+CL_Input
 
 Send the intended movement message to the server
 ================
 */
-void CL_Move (void)
+void CL_Input (void)
 {
        float mx, my;
        static float old_mouse_x = 0, old_mouse_y = 0;
@@ -517,7 +518,6 @@ void CL_Move (void)
 
 #include "cl_collision.h"
 
-extern void V_CalcRefdef(void);
 void CL_UpdatePrydonCursor(void)
 {
        vec3_t temp;
@@ -552,19 +552,21 @@ void CL_UpdatePrydonCursor(void)
        cl.cmd.cursor_screen[2] = 1;
 
        // calculate current view matrix
-       //V_CalcRefdef();
        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);
        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, false);
+       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_InputQW(qw_usercmd_t *cmd)
+void CL_ClientMovement_InputQW(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;
@@ -580,16 +582,17 @@ void CL_ClientMovement_InputQW(qw_usercmd_t *cmd)
        {
                // add to input queue
                cl.movement_queue[cl.movement_numqueue].sequence = cls.netcon->qw.outgoing_sequence;
-               cl.movement_queue[cl.movement_numqueue].time = realtime;
-               cl.movement_queue[cl.movement_numqueue].frametime = cmd->msec / 1000.0;
-               VectorCopy(cmd->angles, cl.movement_queue[cl.movement_numqueue].viewangles);
-               cl.movement_queue[cl.movement_numqueue].move[0] = cmd->forwardmove;
-               cl.movement_queue[cl.movement_numqueue].move[1] = cmd->sidemove;
-               cl.movement_queue[cl.movement_numqueue].move[2] = cmd->upmove;
-               cl.movement_queue[cl.movement_numqueue].jump = (cmd->buttons & 2) != 0;
+               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++;
        }
+
        cl.movement_replay = true;
 }
 
@@ -597,15 +600,17 @@ void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch)
 {
        int i;
        int n;
-       double lasttime = (cls.protocol == PROTOCOL_DARKPLACES6 || cls.protocol == PROTOCOL_DARKPLACES7) ? cl.mtime[1] : (cl.movement_numqueue >= 0 ? cl.movement_queue[cl.movement_numqueue - 1].time : 0);
+       // 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;
-       if (cl.servermovesequence)
+       if (cls.servermovesequence)
        {
                for (i = 0;i < n;i++)
                {
-                       if (cl.movement_queue[i].sequence > cl.servermovesequence)
+                       if (cl.movement_queue[i].sequence > cls.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
@@ -615,19 +620,19 @@ void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch)
        {
                for (i = 0;i < n;i++)
                {
-                       if (cl.movement_queue[i].time >= cl.mtime[0] - cl_movement_latency.value / 1000.0 && cl.movement_queue[i].time <= cl.mtime[0])
+                       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])) && cl.mtime[0] > cl.mtime[1])
+       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].time = cl.mtime[0];
-               cl.movement_queue[cl.movement_numqueue].frametime = bound(0, cl.mtime[0] - lasttime, 0.1);
+               cl.movement_queue[cl.movement_numqueue].sequence = cls.movesequence;
+               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;
@@ -636,6 +641,7 @@ void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch)
                cl.movement_queue[cl.movement_numqueue].crouch = buttoncrouch;
                cl.movement_numqueue++;
        }
+
        cl.movement_replay = true;
 }
 
@@ -724,7 +730,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;
@@ -755,7 +761,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;
                }
@@ -774,22 +780,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);
+       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;
                }
        }
@@ -816,20 +822,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);
+                       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)
@@ -879,10 +885,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_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;
@@ -1011,9 +1017,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_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;
                }
@@ -1093,10 +1099,6 @@ void CL_ClientMovement_Replay(void)
        int i;
        cl_clientmovement_state_t s;
 
-       if (!cl.movement_replay)
-               return;
-       cl.movement_replay = false;
-
        // set up starting state for the series of moves
        memset(&s, 0, sizeof(s));
        VectorCopy(cl.entities[cl.playerentity].state_current.origin, s.origin);
@@ -1145,10 +1147,10 @@ void CL_ClientMovement_Replay(void)
                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);
+       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) || cls.servermovesequence);
        if (cl.movement_predicted)
        {
-               //Con_Printf("%f: ", cl.mtime[0]);
+               //Con_Printf("%f: ", cl.movecmd[0].time);
 
                // replay the input queue to predict current location
                // note: this relies on the fact there's always one queue item at the end
@@ -1170,49 +1172,68 @@ void CL_ClientMovement_Replay(void)
                // get the first movement queue entry to know whether to crouch and such
                s.q = cl.movement_queue[0];
        }
-
-       // store replay location
        CL_ClientMovement_UpdateStatus(&s);
-       cl.movement_time[1] = cl.movement_time[0];
-       cl.movement_time[0] = cl.movement_queue[cl.movement_numqueue-1].time;
-       VectorCopy(cl.movement_origin, cl.movement_oldorigin);
-       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);
+
+       if (cl.movement_replay)
+       {
+               cl.movement_replay = false;
+               // update interpolation timestamps if time has passed
+               if (cl.movecmd[0].time != cl.movecmd[1].time)
+               {
+                       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);
+                       //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);
+       }
 
        // 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)
+       {
+               // when predicted we simply set the flag according to the UpdateStatus
                cl.onground = s.onground;
-       else if (s.onground)
-               cl.onground = true;
+       }
+       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.time;
-               cl.lastongroundtime = cl.time;
+                       cl.hitgroundtime = cl.movecmd[0].time;
+               cl.lastongroundtime = cl.movecmd[0].time;
        }
        cl.oldonground = cl.onground;
 }
 
-void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, qw_usercmd_t *from, qw_usercmd_t *to)
+void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, usercmd_t *from, usercmd_t *to)
 {
        int bits;
 
        bits = 0;
-       if (to->angles[0] != from->angles[0])
+       if (to->viewangles[0] != from->viewangles[0])
                bits |= QW_CM_ANGLE1;
-       if (to->angles[1] != from->angles[1])
+       if (to->viewangles[1] != from->viewangles[1])
                bits |= QW_CM_ANGLE2;
-       if (to->angles[2] != from->angles[2])
+       if (to->viewangles[2] != from->viewangles[2])
                bits |= QW_CM_ANGLE3;
        if (to->forwardmove != from->forwardmove)
                bits |= QW_CM_FORWARD;
@@ -1227,11 +1248,11 @@ void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, qw_usercmd_t *from, qw_usercmd_t *
 
        MSG_WriteByte(buf, bits);
        if (bits & QW_CM_ANGLE1)
-               MSG_WriteAngle16i(buf, to->angles[0]);
+               MSG_WriteAngle16i(buf, to->viewangles[0]);
        if (bits & QW_CM_ANGLE2)
-               MSG_WriteAngle16i(buf, to->angles[1]);
+               MSG_WriteAngle16i(buf, to->viewangles[1]);
        if (bits & QW_CM_ANGLE3)
-               MSG_WriteAngle16i(buf, to->angles[2]);
+               MSG_WriteAngle16i(buf, to->viewangles[2]);
        if (bits & QW_CM_FORWARD)
                MSG_WriteShort(buf, to->forwardmove);
        if (bits & QW_CM_SIDE)
@@ -1250,152 +1271,159 @@ void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, qw_usercmd_t *from, qw_usercmd_t *
 CL_SendMove
 ==============
 */
-extern cvar_t cl_netinputpacketspersecond;
+usercmd_t nullcmd; // for delta compression of qw moves
 void CL_SendMove(void)
 {
-       int i, j, packetloss;
+       int i, j, packetloss, maxusercmds;
        int bits;
-       int impulse;
        sizebuf_t buf;
-       unsigned char data[128];
+       unsigned char data[1024];
        static double lastsendtime = 0;
-#define MOVEAVERAGING 0
-#if MOVEAVERAGING
-       static float accumforwardmove = 0, accumsidemove = 0, accumupmove = 0, accumtotal = 0; // accumulation
-#endif
-       float forwardmove, sidemove, upmove;
+       double packettime;
+       double msectime;
+       static double oldmsectime;
 
        // if playing a demo, do nothing
        if (!cls.netcon)
                return;
 
-#if MOVEAVERAGING
-       // accumulate changes between messages
-       accumforwardmove += cl.cmd.forwardmove;
-       accumsidemove += cl.cmd.sidemove;
-       accumupmove += cl.cmd.upmove;
-       accumtotal++;
-#endif
-
-       if (cl_movement.integer && 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 = 1.0 / bound(10, cl_netinputpacketspersecond.value, 100);
+       // quakeworld servers take only frametimes
+       // predicted dp7 servers take current interpolation time
+       // unpredicted servers take an echo of the latest server timestamp
+       if (cls.protocol == PROTOCOL_QUAKEWORLD)
        {
-               if (!cl.movement_needupdate)
+               if (realtime < lastsendtime + packettime)
                        return;
-               cl.movement_needupdate = false;
+               cl.cmd.time = realtime;
        }
-       else
+       else if (cl.movement_predicted)
        {
-               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)
                        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 = cl.time;
        }
-#if MOVEAVERAGING
-       // average the accumulated changes
-       accumtotal = 1.0f / accumtotal;
-       forwardmove = accumforwardmove * accumtotal;
-       sidemove = accumsidemove * accumtotal;
-       upmove = accumupmove * accumtotal;
-       accumforwardmove = 0;
-       accumsidemove = 0;
-       accumupmove = 0;
-       accumtotal = 0;
-#else
-       // use the latest values
-       forwardmove = cl.cmd.forwardmove;
-       sidemove = cl.cmd.sidemove;
-       upmove = cl.cmd.upmove;
-#endif
-
-       if (cls.signon == SIGNONS)
-               CL_UpdatePrydonCursor();
+       else
+       {
+               // unpredicted movement should be sent immediately whenever a server
+               // packet is received, to minimize ping times
+               if (!cl.movement_needupdate && realtime < lastsendtime + packettime)
+                       return;
+               cl.cmd.time = cl.mtime[0];
+       }
+       // 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 = 128;
+       buf.maxsize = sizeof(data);
        buf.cursize = 0;
        buf.data = data;
 
-       // 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;
-
-       impulse = in_impulse;
-       in_impulse = 0;
-
-       csqc_buttons = bits;
-
-       if (cls.signon == SIGNONS)
+       // 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;
+
+               cls.movesequence++;
+               if (cl_movement.integer)
+                       cl.cmd.sequence = cls.movesequence;
+               else
+                       cl.cmd.sequence = 0;
+
+               // set prydon cursor info
+               CL_UpdatePrydonCursor();
+
                // always dump the first two messages, because they may contain leftover inputs from the last level
-               if (++cl.movemessages >= 2)
+               if (cls.movesequence > 2)
                {
-                       // 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
-                       // PROTOCOL_QUAKEWORLD   clc_move = 34 bytes total (typically, but can reach 43 bytes, or even 49 bytes with roll)
+                       // 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;
+
+                       // 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;
+                       }
+
                        if (cls.protocol == PROTOCOL_QUAKEWORLD)
                        {
                                int checksumindex;
-                               double msectime;
-                               static double oldmsectime;
-                               qw_usercmd_t *cmd, *oldcmd;
-                               qw_usercmd_t nullcmd;
-
-                               //Con_Printf("code qw_clc_move\n");
-
-                               i = cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK;
-                               cmd = &cl.qw_moves[i];
-                               memset(&nullcmd, 0, sizeof(nullcmd));
-                               memset(cmd, 0, sizeof(*cmd));
-                               cmd->buttons = bits;
-                               cmd->impulse = impulse;
-                               cmd->forwardmove = (short)bound(-32768, forwardmove, 32767);
-                               cmd->sidemove = (short)bound(-32768, sidemove, 32767);
-                               cmd->upmove = (short)bound(-32768, upmove, 32767);
-                               VectorCopy(cl.viewangles, cmd->angles);
-                               msectime = realtime * 1000;
-                               cmd->msec = (unsigned char)bound(0, msectime - oldmsectime, 255);
-                               // ridiculous value rejection (matches qw)
-                               if (cmd->msec > 250)
-                                       cmd->msec = 100;
-                               oldmsectime = msectime;
-
-                               CL_ClientMovement_InputQW(cmd);
+
+                               CL_ClientMovement_InputQW();
 
                                MSG_WriteByte(&buf, qw_clc_move);
                                // save the position for a checksum byte
@@ -1406,17 +1434,9 @@ void CL_SendMove(void)
                                        packetloss += cls.netcon->packetlost[j];
                                MSG_WriteByte(&buf, packetloss);
                                // write most recent 3 moves
-                               i = (cls.netcon->qw.outgoing_sequence-2) & QW_UPDATE_MASK;
-                               cmd = &cl.qw_moves[i];
-                               QW_MSG_WriteDeltaUsercmd(&buf, &nullcmd, cmd);
-                               oldcmd = cmd;
-                               i = (cls.netcon->qw.outgoing_sequence-1) & QW_UPDATE_MASK;
-                               cmd = &cl.qw_moves[i];
-                               QW_MSG_WriteDeltaUsercmd(&buf, oldcmd, cmd);
-                               oldcmd = cmd;
-                               i = cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK;
-                               cmd = &cl.qw_moves[i];
-                               QW_MSG_WriteDeltaUsercmd(&buf, oldcmd, cmd);
+                               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
@@ -1434,119 +1454,136 @@ void CL_SendMove(void)
                        }
                        else if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE)
                        {
+                               CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
+
                                // 5 bytes
                                MSG_WriteByte (&buf, clc_move);
-                               MSG_WriteFloat (&buf, cl.mtime[0]);     // so server can get ping times
+                               MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
                                // 3 bytes
                                for (i = 0;i < 3;i++)
-                                       MSG_WriteAngle8i (&buf, cl.viewangles[i]);
+                                       MSG_WriteAngle8i (&buf, cl.movecmd[0].viewangles[i]);
                                // 6 bytes
-                               MSG_WriteCoord16i (&buf, forwardmove);
-                               MSG_WriteCoord16i (&buf, sidemove);
-                               MSG_WriteCoord16i (&buf, upmove);
+                               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, bits);
-                               MSG_WriteByte (&buf, impulse);
-
-                               CL_ClientMovement_Input((bits & 2) != 0, false);
+                               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.mtime[0]);     // so server can get ping times
+                               MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
                                // 12 bytes
                                for (i = 0;i < 3;i++)
-                                       MSG_WriteAngle32f (&buf, cl.viewangles[i]);
+                                       MSG_WriteAngle32f (&buf, cl.movecmd[0].viewangles[i]);
                                // 6 bytes
-                               MSG_WriteCoord16i (&buf, forwardmove);
-                               MSG_WriteCoord16i (&buf, sidemove);
-                               MSG_WriteCoord16i (&buf, upmove);
+                               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, bits);
-                               MSG_WriteByte (&buf, impulse);
-
-                               CL_ClientMovement_Input((bits & 2) != 0, false);
+                               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);
+
                                // 5 bytes
                                MSG_WriteByte (&buf, clc_move);
-                               MSG_WriteFloat (&buf, cl.mtime[0]);     // so server can get ping times
+                               MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
                                // 6 bytes
                                for (i = 0;i < 3;i++)
-                                       MSG_WriteAngle16i (&buf, cl.viewangles[i]);
+                                       MSG_WriteAngle16i (&buf, cl.movecmd[0].viewangles[i]);
                                // 6 bytes
-                               MSG_WriteCoord16i (&buf, forwardmove);
-                               MSG_WriteCoord16i (&buf, sidemove);
-                               MSG_WriteCoord16i (&buf, upmove);
+                               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, bits);
-                               MSG_WriteByte (&buf, impulse);
-
-                               CL_ClientMovement_Input((bits & 2) != 0, false);
+                               MSG_WriteByte (&buf, cl.movecmd[0].buttons);
+                               MSG_WriteByte (&buf, cl.movecmd[0].impulse);
                        }
                        else
                        {
-                               // 5 bytes
-                               MSG_WriteByte (&buf, clc_move);
-                               if (cls.protocol != PROTOCOL_DARKPLACES6)
+                               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--)
                                {
-                                       if (cl_movement.integer)
-                                       {
-                                               cl.movesequence++;
-                                               MSG_WriteLong (&buf, cl.movesequence);
-                                       }
-                                       else
-                                               MSG_WriteLong (&buf, 0);
+                                       // don't repeat any stale moves
+                                       if (cmd->sequence && cmd->sequence < cls.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_WriteFloat (&buf, cl.mtime[0]);     // so server can get ping times
-                               // 6 bytes
-                               for (i = 0;i < 3;i++)
-                                       MSG_WriteAngle16i (&buf, cl.viewangles[i]);
-                               // 6 bytes
-                               MSG_WriteCoord16i (&buf, forwardmove);
-                               MSG_WriteCoord16i (&buf, sidemove);
-                               MSG_WriteCoord16i (&buf, upmove);
-                               // 5 bytes
-                               MSG_WriteLong (&buf, bits);
-                               MSG_WriteByte (&buf, impulse);
-                               // PRYDON_CLIENTCURSOR
-                               // 30 bytes
-                               MSG_WriteShort (&buf, (short)(cl.cmd.cursor_screen[0] * 32767.0f));
-                               MSG_WriteShort (&buf, (short)(cl.cmd.cursor_screen[1] * 32767.0f));
-                               MSG_WriteFloat (&buf, cl.cmd.cursor_start[0]);
-                               MSG_WriteFloat (&buf, cl.cmd.cursor_start[1]);
-                               MSG_WriteFloat (&buf, cl.cmd.cursor_start[2]);
-                               MSG_WriteFloat (&buf, cl.cmd.cursor_impact[0]);
-                               MSG_WriteFloat (&buf, cl.cmd.cursor_impact[1]);
-                               MSG_WriteFloat (&buf, cl.cmd.cursor_impact[2]);
-                               MSG_WriteShort (&buf, cl.cmd.cursor_entitynumber);
-
-                               // FIXME: bits & 16 is +button5, Nexuiz specific
-                               CL_ClientMovement_Input((bits & 2) != 0, (bits & 16) != 0);
                        }
                }
+       }
 
-               if (cls.protocol != PROTOCOL_QUAKEWORLD)
+       if (cls.protocol != PROTOCOL_QUAKEWORLD)
+       {
+               // ack the last few frame numbers
+               // (redundent to improve handling of client->server packet loss)
+               // for LATESTFRAMENUMS == 3 case this is 15 bytes
+               for (i = 0;i < LATESTFRAMENUMS;i++)
                {
-                       // ack the last few frame numbers
-                       // (redundent to improve handling of client->server packet loss)
-                       // for LATESTFRAMENUMS == 3 case this is 15 bytes
-                       for (i = 0;i < LATESTFRAMENUMS;i++)
+                       if (cl.latestframenums[i] > 0)
                        {
-                               if (cl.latestframenums[i] > 0)
-                               {
-                                       if (developer_networkentities.integer >= 1)
-                                               Con_Printf("send clc_ackframe %i\n", cl.latestframenums[i]);
-                                       MSG_WriteByte(&buf, clc_ackframe);
-                                       MSG_WriteLong(&buf, cl.latestframenums[i]);
-                               }
+                               if (developer_networkentities.integer >= 1)
+                                       Con_Printf("send clc_ackframe %i\n", cl.latestframenums[i]);
+                               MSG_WriteByte(&buf, clc_ackframe);
+                               MSG_WriteLong(&buf, cl.latestframenums[i]);
                        }
                }
+       }
 
-               // PROTOCOL_DARKPLACES6 = 67 bytes per packet
-               // PROTOCOL_DARKPLACES7 = 71 bytes per packet
+       // 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++)
+               {
+                       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
@@ -1583,7 +1620,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");
@@ -1659,6 +1696,7 @@ void CL_InitInput (void)
        Cvar_RegisterVariable(&m_filter);
 
        Cvar_RegisterVariable(&cl_netinputpacketspersecond);
+       Cvar_RegisterVariable(&cl_netinputpacketlosstolerance);
 
        Cvar_RegisterVariable(&cl_nodelta);
 }