]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_input.c
EF_LOWPRECISION is now default on all entities except the player, this
[xonotic/darkplaces.git] / cl_input.c
index 95321d6743f53d37c7b454e063a6a6d3ae6c6791..a1a7aad12a5ad0c665592544ac7a14be7a063ae3 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,7 +328,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 or quakeworld servers"};
+cvar_t cl_movement_minping = {CVAR_SAVE, "cl_movement_minping", "50", "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)"};
@@ -337,13 +345,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_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 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"};
 
@@ -404,12 +412,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;
@@ -560,8 +568,6 @@ void CL_UpdatePrydonCursor(void)
        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)
 {
        int i;
@@ -594,7 +600,8 @@ void CL_ClientMovement_InputQW(void)
                cl.movement_queue[cl.movement_numqueue].crouch = false;
                cl.movement_numqueue++;
        }
-       CL_ClientMovement_Replay();
+
+       cl.movement_replay = true;
 }
 
 void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch)
@@ -607,21 +614,11 @@ void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch)
        // 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
+       if (cls.servermovesequence)
        {
                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)
+                       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
@@ -631,7 +628,7 @@ void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch)
        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 = 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);
@@ -642,7 +639,8 @@ void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch)
                cl.movement_queue[cl.movement_numqueue].crouch = buttoncrouch;
                cl.movement_numqueue++;
        }
-       CL_ClientMovement_Replay();
+
+       cl.movement_replay = true;
 }
 
 typedef enum waterlevel_e
@@ -730,7 +728,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 +759,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 +778,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 +820,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)
@@ -885,10 +883,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;
@@ -1017,9 +1015,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;
                }
@@ -1097,6 +1095,7 @@ void CL_ClientMovement_PlayerMove(cl_clientmovement_state_t *s)
 void CL_ClientMovement_Replay(void)
 {
        int i;
+       double totalmovetime;
        cl_clientmovement_state_t s;
 
        // set up starting state for the series of moves
@@ -1147,7 +1146,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);
+       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);
        if (cl.movement_predicted)
        {
                //Con_Printf("%f: ", cl.movecmd[0].time);
@@ -1174,39 +1176,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.timenonlerp;
-               VectorCopy(cl.movement_origin, cl.movement_oldorigin);
+               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);
-               //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)
@@ -1264,6 +1281,7 @@ void CL_SendMove(void)
        sizebuf_t buf;
        unsigned char data[1024];
        static double lastsendtime = 0;
+       double packettime;
        double msectime;
        static double oldmsectime;
 
@@ -1271,26 +1289,37 @@ void CL_SendMove(void)
        if (!cls.netcon)
                return;
 
-#if 0
-       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
-#endif
+       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;
+       }
+       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;
 
-       cl.cmd.time = cls.protocol == PROTOCOL_QUAKEWORLD ? realtime : cl.timenonlerp;
 
        buf.maxsize = sizeof(data);
        buf.cursize = 0;
@@ -1299,7 +1328,7 @@ 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)
+       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
@@ -1360,9 +1389,9 @@ void CL_SendMove(void)
                        cl.cmd.msec = 100;
                oldmsectime = msectime;
 
-               cl.movesequence++;
+               cls.movesequence++;
                if (cl_movement.integer)
-                       cl.cmd.sequence = cl.movesequence;
+                       cl.cmd.sequence = cls.movesequence;
                else
                        cl.cmd.sequence = 0;
 
@@ -1370,7 +1399,7 @@ void CL_SendMove(void)
                CL_UpdatePrydonCursor();
 
                // always dump the first two messages, because they may contain leftover inputs from the last level
-               if (cl.movesequence > 2)
+               if (cls.movesequence > 2)
                {
                        // update the cl.movecmd array which holds the most recent moves
                        for (i = CL_MAX_USERCMDS - 1;i >= 1;i--)
@@ -1388,7 +1417,7 @@ void CL_SendMove(void)
                                // 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.integer)
+                               if (!cl.cmd.sequence)
                                        maxusercmds = 1;
                        }
 
@@ -1493,7 +1522,7 @@ void CL_SendMove(void)
                                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)
+                                       if (cmd->sequence && cmd->sequence < cls.servermovesequence)
                                                continue;
                                        // 5/9 bytes
                                        MSG_WriteByte (&buf, clc_move);
@@ -1593,7 +1622,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,10 +1675,10 @@ 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);