redesigned input networking code (still same protocol) and did a lot of cleaning...
authorhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Mon, 5 Feb 2007 17:45:59 +0000 (17:45 +0000)
committerhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Mon, 5 Feb 2007 17:45:59 +0000 (17:45 +0000)
added cl_netinputpacketlosstolerance cvar (default 4, qw used 2, can be up to 16) to repeat old input messages in each packet so that they can only be lost by heavy packet loss
changed sv_clmovement_waitforinput default to 16 to match this capability
reworked a lot of timing code related to input networking
fixed some bugs when frametime is 0 in player physics

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@6796 d7cf8633-e32d-0410-b094-e92efae38249

cl_input.c
cl_main.c
cl_parse.c
client.h
csprogs.c
csprogs.h
host.c
sv_phys.c
sv_user.c
todo

index 60144dd..daf8638 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)"};
@@ -343,6 +343,7 @@ cvar_t in_pitch_max = {0, "in_pitch_max", "90", "how far upward you can aim (qua
 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"};
 
@@ -559,10 +560,15 @@ 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_InputQW(qw_usercmd_t *cmd)
+void CL_ClientMovement_Replay(void);
+
+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;
@@ -578,24 +584,26 @@ 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;
+       CL_ClientMovement_Replay();
 }
 
 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;
@@ -613,19 +621,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].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;
@@ -634,7 +642,7 @@ void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch)
                cl.movement_queue[cl.movement_numqueue].crouch = buttoncrouch;
                cl.movement_numqueue++;
        }
-       cl.movement_replay = true;
+       CL_ClientMovement_Replay();
 }
 
 typedef enum waterlevel_e
@@ -1091,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);
@@ -1146,7 +1150,7 @@ void CL_ClientMovement_Replay(void)
        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);
        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
@@ -1168,49 +1172,53 @@ 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);
-
-       // 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)
-               cl.onground = true;
 
-       // react to onground state changes (for gun bob)
-       if (cl.onground)
+       // store replay location
+       if (cl.movecmd[0].time > cl.movecmd[1].time)
        {
-               if (!cl.oldonground)
-                       cl.hitgroundtime = cl.time;
-               cl.lastongroundtime = cl.time;
+               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);
+               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)
+                       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;
        }
-       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;
@@ -1225,11 +1233,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)
@@ -1248,33 +1256,22 @@ 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 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 0
        if (cl_movement.integer && cls.signon == SIGNONS && cls.protocol != PROTOCOL_QUAKEWORLD)
        {
                if (!cl.movement_needupdate)
@@ -1282,6 +1279,7 @@ void CL_SendMove(void)
                cl.movement_needupdate = false;
        }
        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
@@ -1291,29 +1289,16 @@ void CL_SendMove(void)
                // (such is the case when framerate is too low for instance)
                lastsendtime = max(lastsendtime + packettime, realtime);
        }
-#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();
+       cl.cmd.sequence = 0;
 
-       buf.maxsize = 128;
-       buf.cursize = 0;
-       buf.data = data;
+       cl.cmd.time = cls.protocol == PROTOCOL_QUAKEWORLD ? realtime : cl.timenonlerp;
+       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;
 
        // set button bits
        // LordHavoc: added 6 new buttons and use and chat buttons, and prydon cursor active button
@@ -1343,210 +1328,214 @@ void CL_SendMove(void)
        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;
 
-       impulse = in_impulse;
+       // set impulse
+       cl.cmd.impulse = in_impulse;
        in_impulse = 0;
 
-       csqc_buttons = bits;
+       // movement is set by input code (forwardmove/sidemove/upmove)
+
+       // set viewangles
+       VectorCopy(cl.viewangles, cl.cmd.viewangles);
+
+       buf.maxsize = sizeof(data);
+       buf.cursize = 0;
+       buf.data = data;
 
-       if (cls.signon == SIGNONS)
+       // always dump the first two messages, because they may contain leftover inputs from the last level
+       if (cls.signon == SIGNONS && ++cl.movemessages >= 2)
        {
-               // always dump the first two messages, because they may contain leftover inputs from the last level
-               if (++cl.movemessages >= 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 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)
+
+               cl.movesequence++;
+               if (cl_movement.integer)
+                       cl.cmd.sequence = cl.movesequence;
+
+               // set prydon cursor info
+               CL_UpdatePrydonCursor();
+
+               // 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)
                {
-                       // 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)
-                       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);
-
-                               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
-                               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);
-                               // 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)
-                       {
-                               // 5 bytes
-                               MSG_WriteByte (&buf, clc_move);
-                               MSG_WriteFloat (&buf, cl.mtime[0]);     // so server can get ping times
-                               // 3 bytes
-                               for (i = 0;i < 3;i++)
-                                       MSG_WriteAngle8i (&buf, cl.viewangles[i]);
-                               // 6 bytes
-                               MSG_WriteCoord16i (&buf, forwardmove);
-                               MSG_WriteCoord16i (&buf, sidemove);
-                               MSG_WriteCoord16i (&buf, upmove);
-                               // 2 bytes
-                               MSG_WriteByte (&buf, bits);
-                               MSG_WriteByte (&buf, impulse);
-
-                               CL_ClientMovement_Input((bits & 2) != 0, false);
-                       }
-                       else if (cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3)
-                       {
-                               // 5 bytes
-                               MSG_WriteByte (&buf, clc_move);
-                               MSG_WriteFloat (&buf, cl.mtime[0]);     // so server can get ping times
-                               // 12 bytes
-                               for (i = 0;i < 3;i++)
-                                       MSG_WriteAngle32f (&buf, cl.viewangles[i]);
-                               // 6 bytes
-                               MSG_WriteCoord16i (&buf, forwardmove);
-                               MSG_WriteCoord16i (&buf, sidemove);
-                               MSG_WriteCoord16i (&buf, upmove);
-                               // 2 bytes
-                               MSG_WriteByte (&buf, bits);
-                               MSG_WriteByte (&buf, impulse);
-
-                               CL_ClientMovement_Input((bits & 2) != 0, false);
-                       }
-                       else if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5)
+                       // qw uses exactly 3 moves
+                       maxusercmds = 3;
+               }
+               else
+               {
+                       // configurable number of unacknowledged moves
+                       maxusercmds = bound(1, cl_netinputpacketlosstolerance.integer + 1, CL_MAX_USERCMDS);
+               }
+
+               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)
                        {
-                               // 5 bytes
-                               MSG_WriteByte (&buf, clc_move);
-                               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);
-                               // 2 bytes
-                               MSG_WriteByte (&buf, bits);
-                               MSG_WriteByte (&buf, impulse);
-
-                               CL_ClientMovement_Input((bits & 2) != 0, false);
+                               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)
+               {
+                       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
+                       // 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)
+               {
+                       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]);
+                       // 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);
+
+                       // 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--)
                        {
-                               // 5 bytes
+                               // 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)
-                               {
-                                       if (cl_movement.integer)
-                                       {
-                                               cl.movesequence++;
-                                               MSG_WriteLong (&buf, cl.movesequence);
-                                       }
-                                       else
-                                               MSG_WriteLong (&buf, 0);
-                               }
-                               MSG_WriteFloat (&buf, cl.mtime[0]);     // so server can get ping times
+                                       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, cl.viewangles[i]);
+                                       MSG_WriteAngle16i (&buf, cmd->viewangles[i]);
                                // 6 bytes
-                               MSG_WriteCoord16i (&buf, forwardmove);
-                               MSG_WriteCoord16i (&buf, sidemove);
-                               MSG_WriteCoord16i (&buf, upmove);
+                               MSG_WriteCoord16i (&buf, cmd->forwardmove);
+                               MSG_WriteCoord16i (&buf, cmd->sidemove);
+                               MSG_WriteCoord16i (&buf, cmd->upmove);
                                // 5 bytes
-                               MSG_WriteLong (&buf, bits);
-                               MSG_WriteByte (&buf, impulse);
+                               MSG_WriteLong (&buf, cmd->buttons);
+                               MSG_WriteByte (&buf, cmd->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);
+                               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)
+       {
+               // 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
@@ -1670,6 +1659,7 @@ void CL_InitInput (void)
        Cvar_RegisterVariable(&m_filter);
 
        Cvar_RegisterVariable(&cl_netinputpacketspersecond);
+       Cvar_RegisterVariable(&cl_netinputpacketlosstolerance);
 
        Cvar_RegisterVariable(&cl_nodelta);
 }
index d9c7009..d3d41fe 100644 (file)
--- a/cl_main.c
+++ b/cl_main.c
@@ -884,8 +884,10 @@ void CL_UpdateNetworkEntity(entity_t *e)
        // if it's the player entity, update according to client movement
        if (e == cl.entities + cl.playerentity && cl.movement_predicted)
        {
-               lerp = (cl.time - cl.movement_time[1]) / (cl.movement_time[0] - cl.movement_time[1]);
+               lerp = (cl.timenonlerp - cl.movement_time[2]) / (cl.movement_time[0] - cl.movement_time[1]);
                lerp = bound(0, lerp, 1);
+               if (cl_nolerp.integer)
+                       lerp = 1;
                VectorLerp(cl.movement_oldorigin, lerp, cl.movement_origin, origin);
                VectorSet(angles, 0, cl.viewangles[1], 0);
        }
@@ -1529,7 +1531,6 @@ CL_ReadFromServer
 Read all incoming data from the server
 ===============
 */
-extern void CL_ClientMovement_Replay(void);
 extern void CL_StairSmoothing(void);//view.c
 
 int CL_ReadFromServer(void)
@@ -1555,9 +1556,6 @@ int CL_ReadFromServer(void)
                CL_MoveParticles();
                R_MoveExplosions();
 
-               // predict current player location
-               CL_ClientMovement_Replay();
-
                cl.num_brushmodel_entities = 0;
                // process network entities
                // first link the player
index d122964..4c1bc80 100644 (file)
@@ -1737,9 +1737,6 @@ void CL_ParseClientdata (void)
 
        // viewzoom interpolation
        cl.mviewzoom[0] = (float) max(cl.stats[STAT_VIEWZOOM], 2) * (1.0f / 255.0f);
-
-       // force a recalculation of the player prediction
-       cl.movement_replay = true;
 }
 
 /*
@@ -2550,7 +2547,7 @@ void CL_ParseServerMessage(void)
        {
                cl.mtime[1] = cl.mtime[0];
                cl.mtime[0] = realtime; // qw has no clock
-               cl.movement_needupdate = true;
+               cl.timenonlerp = bound(cl.mtime[1], cl.timenonlerp, cl.mtime[0]);
                cl.onground = false; // since there's no clientdata parsing, clear the onground flag here
                // if true the cl.viewangles are interpolated from cl.mviewangles[]
                // during this frame
@@ -2560,9 +2557,6 @@ void CL_ParseServerMessage(void)
                if (!cls.demoplayback)
                        VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
 
-               // force a recalculation of the player prediction
-               cl.movement_replay = true;
-
                // slightly kill qw player entities each frame
                for (i = 1;i < cl.maxclients;i++)
                        cl.entities_active[i] = false;
@@ -2974,6 +2968,7 @@ void CL_ParseServerMessage(void)
                        case svc_time:
                                cl.mtime[1] = cl.mtime[0];
                                cl.mtime[0] = MSG_ReadFloat ();
+                               cl.timenonlerp = bound(cl.mtime[1], cl.timenonlerp, cl.mtime[0]);
                                cl.movement_needupdate = true;
                                // if true the cl.viewangles are interpolated from cl.mviewangles[]
                                // during this frame
index cb69cea..699a9c8 100644 (file)
--- a/client.h
+++ b/client.h
@@ -27,6 +27,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 // LordHavoc: 256 dynamic lights
 #define MAX_DLIGHTS 256
 
+// this is the maximum number of input packets that can be lost without a
+// misprediction
+#define CL_MAX_USERCMDS 16
+
 // flags for rtlight rendering
 #define LIGHTFLAG_NORMALMODE 1
 #define LIGHTFLAG_REALTIMEMODE 2
@@ -332,6 +336,7 @@ typedef struct usercmd_s
 
        double time;
        double receivetime;
+       int msec; // for qw moves
        int buttons;
        int impulse;
        int sequence;
@@ -566,18 +571,6 @@ typedef struct
        qboolean drawcrosshair;
 }csqc_vidvars_t;
 
-typedef struct qw_usercmd_s
-{
-       vec3_t angles;
-       short forwardmove, sidemove, upmove;
-       unsigned char padding1[2];
-       unsigned char msec;
-       unsigned char buttons;
-       unsigned char impulse;
-       unsigned char padding2;
-}
-qw_usercmd_t;
-
 typedef enum
 {
        PARTICLE_BILLBOARD = 0,
@@ -658,8 +651,10 @@ typedef struct client_state_s
        // send a clc_nop periodically until connected
        float sendnoptime;
 
-       // current input to send to the server
+       // current input being accumulated by mouse/joystick/etc input
        usercmd_t cmd;
+       // latest moves sent to the server that have not been confirmed yet
+       usercmd_t movecmd[CL_MAX_USERCMDS];
 
 // information for local display
        // health, etc
@@ -712,10 +707,8 @@ typedef struct client_state_s
        // this is set true by svc_time parsing and causes a new movement to be
        // queued for prediction purposes
        qboolean movement_needupdate;
-       // indicates the queue has been updated and should be replayed
-       qboolean movement_replay;
        // timestamps of latest two predicted moves for interpolation
-       double movement_time[2];
+       double movement_time[4];
        // simulated data (this is valid even if cl.movement is false)
        vec3_t movement_origin;
        vec3_t movement_oldorigin;
@@ -762,6 +755,12 @@ typedef struct client_state_s
        // the timestamp of the last two messages
        double mtime[2];
 
+       // similar to cl.time but this can go past cl.mtime[0] when packets are
+       // not being received, it is still clamped to the cl.mtime[1] to
+       // cl.mtime[0] range whenever a packet is received, it just does not stop
+       // when interpolation finishes
+       double timenonlerp;
+
        // clients view of time, time should be between mtime[0] and mtime[1] to
        // generate a lerp point for other data, oldtime is the previous frame's
        // value of time, frametime is the difference between time and oldtime
@@ -939,8 +938,6 @@ typedef struct client_state_s
 
        int qw_validsequence;
 
-       qw_usercmd_t qw_moves[QW_UPDATE_BACKUP];
-
        int qw_deltasequence[QW_UPDATE_BACKUP];
 }
 client_state_t;
index edb13bc..96ac42d 100644 (file)
--- a/csprogs.c
+++ b/csprogs.c
@@ -67,7 +67,6 @@ qboolean csqc_loaded = false;
 
 vec3_t csqc_origin, csqc_angles;
 static double csqc_frametime = 0;
-int csqc_buttons;
 
 static mempool_t *csqc_mempool;
 
@@ -153,10 +152,10 @@ static void CSQC_SetGlobals (void)
                prog->globals.client->frametime = cl.time - csqc_frametime;
                csqc_frametime = cl.time;
                prog->globals.client->servercommandframe = cl.servermovesequence;
-               prog->globals.client->clientcommandframe = cl.movemessages;
+               prog->globals.client->clientcommandframe = cl.movesequence;
                VectorCopy(cl.viewangles, prog->globals.client->input_angles);
                VectorCopy(cl.viewangles, csqc_angles);
-               prog->globals.client->input_buttons = csqc_buttons;
+               prog->globals.client->input_buttons = cl.cmd.buttons;
                VectorSet(prog->globals.client->input_movevalues, cl.cmd.forwardmove, cl.cmd.sidemove, cl.cmd.upmove);
                //VectorCopy(cl.movement_origin, csqc_origin);
                Matrix4x4_OriginFromMatrix(&cl.entities[cl.viewentity].render.matrix, csqc_origin);
index ffcb5cb..1a9cfe1 100644 (file)
--- a/csprogs.h
+++ b/csprogs.h
@@ -48,7 +48,6 @@
                                                                // Note that to use this properly, you'll NEED to use the predraw function to set the globals.
 //#define RF_DOUBLESIDED               32
 
-extern int csqc_buttons;
 extern qboolean csqc_loaded;
 extern vec3_t csqc_origin, csqc_angles;
 extern int csqc_fieldoff_scale;
diff --git a/host.c b/host.c
index 1a8b0dd..6ec645a 100644 (file)
--- a/host.c
+++ b/host.c
@@ -799,6 +799,7 @@ void Host_Main(void)
 
                                cl.oldtime = cl.time;
                                cl.time += frametime;
+                               cl.timenonlerp += frametime;
 
                                // Collect input into cmd
                                CL_Move();
index b927bb1..f44932f 100644 (file)
--- a/sv_phys.c
+++ b/sv_phys.c
@@ -96,12 +96,19 @@ static int SV_TestEntityPosition (prvm_edict_t *ent)
                        // a hull size it is incorrectly tested, so this code tries to
                        // 'fix' it slightly...
                        int i;
-                       vec3_t v;
+                       vec3_t v, m1, m2, s;
+                       VectorAdd(ent->fields.server->origin, ent->fields.server->mins, m1);
+                       VectorAdd(ent->fields.server->origin, ent->fields.server->maxs, m2);
+                       VectorSubtract(m2, m1, s);
+#define EPSILON (1.0f / 32.0f)
+                       if (s[0] >= EPSILON*2) {m1[0] += EPSILON;m2[0] -= EPSILON;}
+                       if (s[1] >= EPSILON*2) {m1[1] += EPSILON;m2[1] -= EPSILON;}
+                       if (s[2] >= EPSILON*2) {m1[2] += EPSILON;m2[2] -= EPSILON;}
                        for (i = 0;i < 8;i++)
                        {
-                               v[0] = ent->fields.server->origin[0] + ((i & 1) ? ent->fields.server->maxs[0] : ent->fields.server->mins[0]);
-                               v[1] = ent->fields.server->origin[1] + ((i & 2) ? ent->fields.server->maxs[1] : ent->fields.server->mins[1]);
-                               v[2] = ent->fields.server->origin[2] + ((i & 4) ? ent->fields.server->maxs[2] : ent->fields.server->mins[2]);
+                               v[0] = (i & 1) ? m2[0] : m1[0];
+                               v[1] = (i & 2) ? m2[1] : m1[1];
+                               v[2] = (i & 4) ? m2[2] : m1[2];
                                if (SV_PointSuperContents(v) & SUPERCONTENTS_SOLID)
                                        return true;
                        }
@@ -368,6 +375,8 @@ int SV_FlyMove (prvm_edict_t *ent, float time, float *stepnormal)
        float d, time_left;
        vec3_t dir, end, planes[MAX_CLIP_PLANES], primal_velocity, original_velocity, new_velocity;
        trace_t trace;
+       if (time <= 0)
+               return 0;
        blocked = 0;
        VectorCopy(ent->fields.server->velocity, original_velocity);
        VectorCopy(ent->fields.server->velocity, primal_velocity);
@@ -960,13 +969,6 @@ void SV_CheckStuck (prvm_edict_t *ent)
        }
 
        VectorCopy (ent->fields.server->origin, org);
-       VectorCopy (ent->fields.server->oldorigin, ent->fields.server->origin);
-       if (!SV_TestEntityPosition(ent))
-       {
-               Con_DPrintf("Unstuck player entity %i (classname \"%s\") by restoring oldorigin.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(ent->fields.server->classname));
-               SV_LinkEdict (ent, true);
-               return;
-       }
 
        for (z=-1 ; z< 18 ; z++)
                for (i=-1 ; i <= 1 ; i++)
@@ -983,6 +985,14 @@ void SV_CheckStuck (prvm_edict_t *ent)
                                }
                        }
 
+       VectorCopy (ent->fields.server->oldorigin, ent->fields.server->origin);
+       if (!SV_TestEntityPosition(ent))
+       {
+               Con_DPrintf("Unstuck player entity %i (classname \"%s\") by restoring oldorigin.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(ent->fields.server->classname));
+               SV_LinkEdict (ent, true);
+               return;
+       }
+
        VectorCopy (org, ent->fields.server->origin);
        Con_DPrintf("Stuck player entity %i (classname \"%s\").\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(ent->fields.server->classname));
 }
@@ -1092,6 +1102,7 @@ void SV_WallFriction (prvm_edict_t *ent, float *stepnormal)
        }
 }
 
+#if 0
 /*
 =====================
 SV_TryUnstick
@@ -1151,6 +1162,7 @@ int SV_TryUnstick (prvm_edict_t *ent, vec3_t oldvel)
        Con_DPrint("TryUnstick - failure.\n");
        return 7;
 }
+#endif
 
 /*
 =====================
@@ -1165,6 +1177,11 @@ void SV_WalkMove (prvm_edict_t *ent)
        vec3_t upmove, downmove, start_origin, start_velocity, stepnormal, originalmove_origin, originalmove_velocity;
        trace_t downtrace;
 
+       // if frametime is 0 (due to client sending the same timestamp twice),
+       // don't move
+       if (sv.frametime <= 0)
+               return;
+
        SV_CheckVelocity(ent);
 
        // do a regular slide move unless it looks like you ran into a step
index f64bf06..4855329 100644 (file)
--- a/sv_user.c
+++ b/sv_user.c
@@ -31,7 +31,7 @@ cvar_t sv_wateraccelerate = {0, "sv_wateraccelerate", "-1", "rate at which a pla
 cvar_t sv_clmovement_enable = {0, "sv_clmovement_enable", "1", "whether to allow clients to use cl_movement prediction, which can cause choppy movement on the server which may annoy other players"};
 cvar_t sv_clmovement_minping = {0, "sv_clmovement_minping", "0", "if client ping is below this time in milliseconds, then their ability to use cl_movement prediction is disabled for a while (as they don't need it)"};
 cvar_t sv_clmovement_minping_disabletime = {0, "sv_clmovement_minping_disabletime", "1000", "when client falls below minping, disable their prediction for this many milliseconds (should be at least 1000 or else their prediction may turn on/off frequently)"};
-cvar_t sv_clmovement_waitforinput = {0, "sv_clmovement_waitforinput", "5", "when a client does not send input for this many frames, force them to move anyway (unlike QuakeWorld)"};
+cvar_t sv_clmovement_waitforinput = {0, "sv_clmovement_waitforinput", "16", "when a client does not send input for this many frames, force them to move anyway (unlike QuakeWorld)"};
 
 static usercmd_t cmd;
 
@@ -441,17 +441,14 @@ qboolean SV_ReadClientMove (void)
 {
        qboolean kickplayer = false;
        int i;
-       double oldmovetime;
 #ifdef NUM_PING_TIMES
        double total;
 #endif
-       usercmd_t *move = &host_client->cmd;
-
-       oldmovetime = move->time;
+       double moveframetime;
+       usercmd_t newmove;
+       usercmd_t *move = &newmove;
 
-       // if this move has been applied, clear it, and start accumulating new data
-       if (move->applied)
-               memset(move, 0, sizeof(*move));
+       memset(move, 0, sizeof(*move));
 
        if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__);
 
@@ -462,15 +459,9 @@ qboolean SV_ReadClientMove (void)
        if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__);
        move->receivetime = (float)sv.time;
 
-       // calculate average ping time
-       host_client->ping = move->receivetime - move->time;
-#ifdef NUM_PING_TIMES
-       host_client->ping_times[host_client->num_pings % NUM_PING_TIMES] = move->receivetime - move->time;
-       host_client->num_pings++;
-       for (i=0, total = 0;i < NUM_PING_TIMES;i++)
-               total += host_client->ping_times[i];
-       host_client->ping = total / NUM_PING_TIMES;
-#endif
+       // limit reported time to current time
+       // (incase the client is trying to cheat)
+       move->time = min(move->time, move->receivetime);
 
        // read current angles
        for (i = 0;i < 3;i++)
@@ -496,15 +487,13 @@ qboolean SV_ReadClientMove (void)
        // be sure to bitwise OR them into the move->buttons because we want to
        // accumulate button presses from multiple packets per actual move
        if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5)
-               move->buttons |= MSG_ReadByte ();
+               move->buttons = MSG_ReadByte ();
        else
-               move->buttons |= MSG_ReadLong ();
+               move->buttons = MSG_ReadLong ();
        if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__);
 
        // read impulse
-       i = MSG_ReadByte ();
-       if (i)
-               move->impulse = i;
+       move->impulse = MSG_ReadByte ();
        if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__);
 
        // PRYDON_CLIENTCURSOR
@@ -532,45 +521,66 @@ qboolean SV_ReadClientMove (void)
                if (msg_badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__);
        }
 
+       if (move->sequence && move->sequence <= host_client->movesequence)
+       {
+               // repeat of old input (to fight packet loss)
+               return kickplayer;
+       }
+
+       // if the previous move has not been applied yet, we need to accumulate
+       // the impulse/buttons from it
+       if (!host_client->cmd.applied)
+       {
+               if (!move->impulse)
+                       move->impulse = host_client->cmd.impulse;
+               move->buttons |= host_client->cmd.impulse;
+       }
+
+       moveframetime = bound(0, move->time - host_client->cmd.time, 0.1);
+       Con_Printf("movesequence = %i (%i lost), moveframetime = %f\n", move->sequence, move->sequence ? move->sequence - host_client->movesequence - 1 : 0, moveframetime);
+
        // disable clientside movement prediction in some cases
        if (ceil((move->receivetime - move->time) * 1000.0) < sv_clmovement_minping.integer)
                host_client->clmovement_disabletimeout = realtime + sv_clmovement_minping_disabletime.value / 1000.0;
        if (!sv_clmovement_enable.integer || host_client->clmovement_disabletimeout > realtime)
                move->sequence = 0;
 
-       if (!host_client->spawned)
-               memset(move, 0, sizeof(*move));
-       else if (move->sequence && (float)move->time > (float)sv.time + 0.125f) // add a little fuzz factor due to float precision issues
-       {
-               Con_DPrintf("client move->time %f > sv.time %f, kicking\n", (float)move->time, (float)sv.time);
-               // if the client is lying about time, we have definitively detected a
-               // speed cheat attempt of the worst sort, and we can immediately kick
-               // the offending player off.
-               // this fixes the timestamp to prevent a speed cheat from working
-               move->time = sv.time;
-               // but we kick the player for good measure
-               kickplayer = true;
-       }
-       else
+       // calculate average ping time
+       host_client->ping = move->receivetime - move->time;
+#ifdef NUM_PING_TIMES
+       host_client->ping_times[host_client->num_pings % NUM_PING_TIMES] = move->receivetime - move->time;
+       host_client->num_pings++;
+       for (i=0, total = 0;i < NUM_PING_TIMES;i++)
+               total += host_client->ping_times[i];
+       host_client->ping = total / NUM_PING_TIMES;
+#endif
+
+       // only start accepting input once the player is spawned
+       if (host_client->spawned)
        {
-               // apply the latest accepted move to the entity fields
+               // at this point we know this input is new and should be stored
+               host_client->cmd = *move;
                host_client->movesequence = move->sequence;
-               if (host_client->movesequence && sv_clmovement_waitforinput.integer > 0)
+               // if using prediction, we need to perform moves when packets are
+               // received, even if multiple occur in one frame
+               // (they can't go beyond the current time so there is no cheat issue
+               //  with this approach, and if they don't send input for a while they
+               //  start moving anyway, so the longest 'lagaport' possible is
+               //  determined by the sv_clmovement_waitforinput cvar)
+               if (host_client->movesequence && sv_clmovement_waitforinput.integer > 0 && moveframetime > 0)
                {
-                       double frametime = bound(0, move->time - oldmovetime, 0.1);
                        double oldframetime = prog->globals.server->frametime;
                        double oldframetime2 = sv.frametime;
-                       //if (move->time - oldmovetime >= 0.1001)
-                       //      Con_DPrintf("client move exceeds 100ms!  (time %f -> time %f)\n", oldmovetime, move->time);
                        // the server and qc frametime values must be changed temporarily
-                       sv.frametime = frametime;
-                       prog->globals.server->frametime = frametime;
+                       sv.frametime = moveframetime;
+                       prog->globals.server->frametime = moveframetime;
                        SV_Physics_ClientEntity(host_client->edict);
                        sv.frametime = oldframetime2;
                        prog->globals.server->frametime = oldframetime;
                        host_client->clmovement_skipphysicsframes = sv_clmovement_waitforinput.integer;
                }
        }
+
        return kickplayer;
 }
 
diff --git a/todo b/todo
index 9144da8..4ec5dc4 100644 (file)
--- a/todo
+++ b/todo
 0 feature darkplaces protocol: add lava-steam particle puff effect for bursting lava bubbles (Zombie)
 0 feature darkplaces protocol: add support for .float corona and corona_radius to control corona intensity and radius on dlights
 0 feature darkplaces prvm: if developer is >= 100, list unfreed strzone strings when level ends (div0)
+0 feature darkplaces qc: add FTE_STRINGS extension, and specifically support -1 for strncmp/strncasecmp to compare whole string (div0)
 0 feature darkplaces quakec: DP_QC_STRFTIME extension providing strftime function to find out what the current time is with a format string (FrikaC)
 0 feature darkplaces quakec: add a DP_QC_STRCATREPEAT extension providing string(float atimes, string a[, float btimes, string b, [float ctimes, string c, [float dtimes, string d]]]) strcatrepeat = #???; which repeats the given strings a given number of times and concatenates them together (like many strcat calls), can be given 2, 4, 6, or 8 parameters, stores it into a temp buffer, and returns the temp buffer (FA-Zalon)
 0 feature darkplaces readme: add documentation about r_lockpvs, r_lockvisibility, r_useportalculling, r_drawportals, r_drawcollisionbrushes, r_showtris, r_speeds, r_shadow_visiblevolumes, and r_shadow_visiblelighting.
 2 feature darkplaces client: qw skin loading/rendering
 2 feature darkplaces image: add scaling capabilities to Image_CopyMux
 2 feature darkplaces loader: add support for fuhquake naming of map textures (textures/start/quake.tga style)
+2 feature darkplaces loader: implement vertex cache optimization of models during loading, see this paper: http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html (Dresk)
 2 feature darkplaces menu: add some basic graphics/effects options profiles so that people can choose profiles like "Classic", "Modern", "Excessive", "Realistic", or any other profiles that make sense, may also need to reorganize the graphics/effects options menus to be a bit less confusing (Tron)
 2 feature darkplaces menu: implement menu_clearkeyconfig and menu_keyconfig and the corresponding menu (diGGer)
 2 feature darkplaces menu: new game custom level/mod menu, which allows you to choose a mod and browse through maps and choose starting skill, by default it would have the current mod selected and start selected (Jago)
@@ -540,6 +542,7 @@ d bug darkplaces console: when cursoring up and down through command history, sh
 d bug darkplaces csqc: engine-based rocket entities have a trail but they don't glow if csqc is used
 d bug darkplaces csqc: it's broken!
 d bug darkplaces csqc: network entity positions seem to be incorrectly updated while csqc is active, this is best tested with cl_nolerp 1 on a sys_ticrate 0.1 server, which makes the jumps in rocket movement quite noticable
+d bug darkplaces csqc: when playing back a demo, the csqc does not seem to be getting the cl.viewangles right
 d bug darkplaces general: make all text parsing routines support Mac newlines; \r with no \n (Zenex)
 d bug darkplaces hud: sometimes texture borders wrap, causing annoying seams at the edges of pics, use TEXF_CLAMP
 d bug darkplaces init: only print "Playing shareware version." notice if running GAME_QUAKE (MrBIOS)
@@ -1224,6 +1227,7 @@ d feature darkplaces renderer: add q3bsp water rendering, both scrolling and wat
 d feature darkplaces renderer: add r_shadow_visiblelighting cvar which draws redish orange polygons similar to visiblevolumes for measuring number of light passes per pixel (Harbish)
 d feature darkplaces renderer: v_hwgamma 2 should force use of hardware gamma, ignoring failure return values, this might make fancy gamma ramps work on windows if the driver can bypass windows limitations
 d feature darkplaces server: add DP_QC_WRITEUNTERMINATEDSTRING extension (shadowalker)
+d feature darkplaces server: add DP_SV_PRINT extension
 d feature darkplaces server: add filename/line number reporting to progs stack and opcode printouts (Spike)
 d feature darkplaces server: automatically choose a server port if the bind fails, just keep incrementing the port until it finds an available port (tell Spike)
 d feature darkplaces server: finish DP_QC_BOTCLIENT extension docs and implement it (MauveBib, Supajoe)