X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;f=cl_input.c;h=9cf08eadcd824d20762288f208276bbfb1b854f1;hb=8663634a9ee1b8392f1edf9dd5e2861af9a80443;hp=53701d4a1f418635c5bdb764727c3d8b4667820b;hpb=7654bd9684b667c0e755205846fbd62948bd1098;p=xonotic%2Fdarkplaces.git diff --git a/cl_input.c b/cl_input.c index 53701d4a..9cf08ead 100644 --- a/cl_input.c +++ b/cl_input.c @@ -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"}; @@ -403,12 +404,12 @@ qboolean cl_ignoremousemove = false; /* ================ -CL_Move +CL_Input Send the intended movement message to the server ================ */ -void CL_Move (void) +void CL_Input (void) { float mx, my; static float old_mouse_x = 0, old_mouse_y = 0; @@ -517,7 +518,6 @@ void CL_Move (void) #include "cl_collision.h" -extern void V_CalcRefdef(void); void CL_UpdatePrydonCursor(void) { vec3_t temp; @@ -552,19 +552,23 @@ void CL_UpdatePrydonCursor(void) cl.cmd.cursor_screen[2] = 1; // calculate current view matrix - //V_CalcRefdef(); Matrix4x4_OriginFromMatrix(&r_view.matrix, cl.cmd.cursor_start); // calculate direction vector of cursor in viewspace by using frustum slopes VectorSet(temp, cl.cmd.cursor_screen[2] * 1000000, cl.cmd.cursor_screen[0] * -r_view.frustum_x * 1000000, cl.cmd.cursor_screen[1] * -r_view.frustum_y * 1000000); Matrix4x4_Transform(&r_view.matrix, temp, cl.cmd.cursor_end); // trace from view origin to the cursor - cl.cmd.cursor_fraction = CL_SelectTraceLine(cl.cmd.cursor_start, cl.cmd.cursor_end, cl.cmd.cursor_impact, cl.cmd.cursor_normal, &cl.cmd.cursor_entitynumber, (chase_active.integer || cl.intermission) ? &cl.entities[cl.playerentity].render : NULL, false); + cl.cmd.cursor_fraction = CL_SelectTraceLine(cl.cmd.cursor_start, cl.cmd.cursor_end, cl.cmd.cursor_impact, cl.cmd.cursor_normal, &cl.cmd.cursor_entitynumber, (chase_active.integer || cl.intermission) ? &cl.entities[cl.playerentity].render : NULL); } -void CL_ClientMovement_InputQW(qw_usercmd_t *cmd) +void CL_ClientMovement_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; @@ -580,32 +584,34 @@ 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; - if (cl.servermovesequence) + if (cls.servermovesequence) { for (i = 0;i < n;i++) { - if (cl.movement_queue[i].sequence > cl.servermovesequence) + if (cl.movement_queue[i].sequence > cls.servermovesequence) cl.movement_queue[cl.movement_numqueue++] = cl.movement_queue[i]; else if (i == 0) cl.movement_replay_canjump = !cl.movement_queue[i].jump; // FIXME: this logic is quite broken @@ -615,19 +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].sequence = cls.movesequence; + cl.movement_queue[cl.movement_numqueue].time = cl.movecmd[0].time; + cl.movement_queue[cl.movement_numqueue].frametime = bound(0, cl.movecmd[0].time - cl.movecmd[1].time, 0.1); VectorCopy(cl.viewangles, cl.movement_queue[cl.movement_numqueue].viewangles); cl.movement_queue[cl.movement_numqueue].move[0] = cl.cmd.forwardmove; cl.movement_queue[cl.movement_numqueue].move[1] = cl.cmd.sidemove; @@ -636,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 @@ -724,7 +730,7 @@ qboolean CL_ClientMovement_Unstick(cl_clientmovement_state_t *s) for (i = 0;i < NUMOFFSETS;i++) { VectorAdd(offsets[i], s->origin, neworigin); - if (!CL_TraceBox(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true).startsolid) + if (!CL_Move(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false).startsolid) { VectorCopy(neworigin, s->origin); return true; @@ -755,7 +761,7 @@ void CL_ClientMovement_UpdateStatus(cl_clientmovement_state_t *s) // low ceiling first if (s->crouched) { - trace = CL_TraceBox(s->origin, cl.playerstandmins, cl.playerstandmaxs, s->origin, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true); + trace = CL_Move(s->origin, cl.playerstandmins, cl.playerstandmaxs, s->origin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); if (!trace.startsolid) s->crouched = false; } @@ -774,22 +780,22 @@ void CL_ClientMovement_UpdateStatus(cl_clientmovement_state_t *s) // set onground VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + 1); VectorSet(origin2, s->origin[0], s->origin[1], s->origin[2] - 2); - trace = CL_TraceBox(origin1, s->mins, s->maxs, origin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true); - s->onground = trace.fraction < 1 && trace.plane.normal[2] > 0.7; + 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 && s->velocity[2] < cl_gravity.value * s->q.frametime; // set watertype/waterlevel VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + s->mins[2] + 1); s->waterlevel = WATERLEVEL_NONE; - s->watertype = CL_TraceBox(origin1, vec3_origin, vec3_origin, origin1, true, NULL, 0, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK; + s->watertype = CL_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK; if (s->watertype) { s->waterlevel = WATERLEVEL_WETFEET; origin1[2] = s->origin[2] + (s->mins[2] + s->maxs[2]) * 0.5f; - if (CL_TraceBox(origin1, vec3_origin, vec3_origin, origin1, true, NULL, 0, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) + if (CL_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) { s->waterlevel = WATERLEVEL_SWIMMING; origin1[2] = s->origin[2] + 22; - if (CL_TraceBox(origin1, vec3_origin, vec3_origin, origin1, true, NULL, 0, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) + if (CL_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) s->waterlevel = WATERLEVEL_SUBMERGED; } } @@ -816,20 +822,20 @@ void CL_ClientMovement_Move(cl_clientmovement_state_t *s) for (bump = 0, t = s->q.frametime;bump < 8 && VectorLength2(s->velocity) > 0;bump++) { VectorMA(s->origin, t, s->velocity, neworigin); - trace = CL_TraceBox(s->origin, s->mins, s->maxs, neworigin, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true); + trace = CL_Move(s->origin, s->mins, s->maxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); if (trace.fraction < 1 && trace.plane.normal[2] == 0) { // may be a step or wall, try stepping up // first move forward at a higher level VectorSet(currentorigin2, s->origin[0], s->origin[1], s->origin[2] + s->movevars_stepheight); VectorSet(neworigin2, neworigin[0], neworigin[1], s->origin[2] + s->movevars_stepheight); - trace2 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true); + trace2 = CL_Move(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); if (!trace2.startsolid) { // then move down from there VectorCopy(trace2.endpos, currentorigin2); VectorSet(neworigin2, trace2.endpos[0], trace2.endpos[1], s->origin[2]); - trace3 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true); + trace3 = CL_Move(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); //Con_Printf("%f %f %f %f : %f %f %f %f : %f %f %f %f\n", trace.fraction, trace.endpos[0], trace.endpos[1], trace.endpos[2], trace2.fraction, trace2.endpos[0], trace2.endpos[1], trace2.endpos[2], trace3.fraction, trace3.endpos[0], trace3.endpos[1], trace3.endpos[2]); // accept the new trace if it made some progress if (fabs(trace3.endpos[0] - trace.endpos[0]) >= 0.03125 || fabs(trace3.endpos[1] - trace.endpos[1]) >= 0.03125) @@ -879,10 +885,10 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s) AngleVectors(yawangles, forward, NULL, NULL); VectorMA(s->origin, 24, forward, spot); spot[2] += 8; - if (CL_TraceBox(spot, vec3_origin, vec3_origin, spot, true, NULL, 0, false).startsolid) + if (CL_Move(spot, vec3_origin, vec3_origin, spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid) { spot[2] += 24; - if (!CL_TraceBox(spot, vec3_origin, vec3_origin, spot, true, NULL, 0, false).startsolid) + if (!CL_Move(spot, vec3_origin, vec3_origin, spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid) { VectorScale(forward, 50, s->velocity); s->velocity[2] = 310; @@ -1011,9 +1017,9 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s) VectorSet(neworigin2, s->origin[0] + s->velocity[0]*(16/f), s->origin[1] + s->velocity[1]*(16/f), s->origin[2] + s->mins[2]); VectorSet(neworigin3, neworigin2[0], neworigin2[1], neworigin2[2] - 34); if (cls.protocol == PROTOCOL_QUAKEWORLD) - trace = CL_TraceBox(neworigin2, s->mins, s->maxs, neworigin3, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true); + trace = CL_Move(neworigin2, s->mins, s->maxs, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); else - trace = CL_TraceBox(neworigin2, vec3_origin, vec3_origin, neworigin3, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true); + trace = CL_Move(neworigin2, vec3_origin, vec3_origin, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false); if (trace.fraction == 1 && !trace.startsolid) friction *= s->movevars_edgefriction; } @@ -1093,10 +1099,6 @@ void CL_ClientMovement_Replay(void) int i; cl_clientmovement_state_t s; - if (!cl.movement_replay) - return; - cl.movement_replay = false; - // set up starting state for the series of moves memset(&s, 0, sizeof(s)); VectorCopy(cl.entities[cl.playerentity].state_current.origin, s.origin); @@ -1145,10 +1147,10 @@ void CL_ClientMovement_Replay(void) s.movevars_airaccel_sideways_friction = cl_movement_airaccel_sideways_friction.value; } - cl.movement_predicted = (cl_movement.integer && !cls.demoplayback && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission) && ((cls.protocol != PROTOCOL_DARKPLACES6 && cls.protocol != PROTOCOL_DARKPLACES7) || cl.servermovesequence); + cl.movement_predicted = (cl_movement.integer && !cls.demoplayback && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission) && ((cls.protocol != PROTOCOL_DARKPLACES6 && cls.protocol != PROTOCOL_DARKPLACES7) || cls.servermovesequence); if (cl.movement_predicted) { - //Con_Printf("%f: ", cl.mtime[0]); + //Con_Printf("%f: ", cl.movecmd[0].time); // replay the input queue to predict current location // note: this relies on the fact there's always one queue item at the end @@ -1170,49 +1172,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.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) + { + 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; @@ -1227,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) @@ -1250,152 +1256,161 @@ void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, qw_usercmd_t *from, qw_usercmd_t * CL_SendMove ============== */ -extern cvar_t cl_netinputpacketspersecond; +usercmd_t nullcmd; // for delta compression of qw moves void CL_SendMove(void) { - int i, j, packetloss; + int i, j, packetloss, maxusercmds; int bits; - int impulse; sizebuf_t buf; - unsigned char data[128]; + unsigned char data[1024]; static double lastsendtime = 0; -#define MOVEAVERAGING 0 -#if MOVEAVERAGING - static float accumforwardmove = 0, accumsidemove = 0, accumupmove = 0, accumtotal = 0; // accumulation -#endif - float forwardmove, sidemove, upmove; + double packettime; + double msectime; + static double oldmsectime; // if playing a demo, do nothing if (!cls.netcon) return; -#if MOVEAVERAGING - // accumulate changes between messages - accumforwardmove += cl.cmd.forwardmove; - accumsidemove += cl.cmd.sidemove; - accumupmove += cl.cmd.upmove; - accumtotal++; -#endif - - if (cl_movement.integer && cls.signon == SIGNONS && cls.protocol != PROTOCOL_QUAKEWORLD) + packettime = 1.0 / bound(10, cl_netinputpacketspersecond.value, 100); + // on quakeworld servers the server replies to client input, so we send + // packets whenever we want to + // on non-quakeworld servers the client replies to server updates + if (cls.protocol == PROTOCOL_QUAKEWORLD) { - if (!cl.movement_needupdate) + // don't send too often or else network connections can get clogged by a high renderer framerate + if (realtime < lastsendtime + packettime) return; - cl.movement_needupdate = false; + cl.cmd.time = realtime; } - else + else if (cl.movement_predicted || cls.signon < SIGNONS) { - 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 = cls.protocol == PROTOCOL_QUAKEWORLD ? realtime : cl.time; } -#if MOVEAVERAGING - // average the accumulated changes - accumtotal = 1.0f / accumtotal; - forwardmove = accumforwardmove * accumtotal; - sidemove = accumsidemove * accumtotal; - upmove = accumupmove * accumtotal; - accumforwardmove = 0; - accumsidemove = 0; - accumupmove = 0; - accumtotal = 0; -#else - // use the latest values - forwardmove = cl.cmd.forwardmove; - sidemove = cl.cmd.sidemove; - upmove = cl.cmd.upmove; -#endif - - if (cls.signon == SIGNONS) - CL_UpdatePrydonCursor(); + else + { + // if not predicted, we should just reply to server packets, and + // report the real latest packet time rather than our interpolated + // time + if (!cl.movement_needupdate && realtime < lastsendtime + packettime) + return; + cl.cmd.time = cls.protocol == PROTOCOL_QUAKEWORLD ? realtime : 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); + // clear the note down that we sent a packet recently + cl.movement_needupdate = false; - buf.maxsize = 128; + + buf.maxsize = sizeof(data); buf.cursize = 0; buf.data = data; - // set button bits - // LordHavoc: added 6 new buttons and use and chat buttons, and prydon cursor active button - bits = 0; - if (in_attack.state & 3) bits |= 1;in_attack.state &= ~2; - if (in_jump.state & 3) bits |= 2;in_jump.state &= ~2; - if (in_button3.state & 3) bits |= 4;in_button3.state &= ~2; - if (in_button4.state & 3) bits |= 8;in_button4.state &= ~2; - if (in_button5.state & 3) bits |= 16;in_button5.state &= ~2; - if (in_button6.state & 3) bits |= 32;in_button6.state &= ~2; - if (in_button7.state & 3) bits |= 64;in_button7.state &= ~2; - if (in_button8.state & 3) bits |= 128;in_button8.state &= ~2; - if (in_use.state & 3) bits |= 256;in_use.state &= ~2; - if (key_dest != key_game || key_consoleactive) bits |= 512; - if (cl_prydoncursor.integer) bits |= 1024; - if (in_button9.state & 3) bits |= 2048;in_button9.state &= ~2; - if (in_button10.state & 3) bits |= 4096;in_button10.state &= ~2; - if (in_button11.state & 3) bits |= 8192;in_button11.state &= ~2; - if (in_button12.state & 3) bits |= 16384;in_button12.state &= ~2; - if (in_button13.state & 3) bits |= 32768;in_button13.state &= ~2; - if (in_button14.state & 3) bits |= 65536;in_button14.state &= ~2; - if (in_button15.state & 3) bits |= 131072;in_button15.state &= ~2; - if (in_button16.state & 3) bits |= 262144;in_button16.state &= ~2; - // button bits 19-31 unused currently - // rotate/zoom view serverside if PRYDON_CLIENTCURSOR cursor is at edge of screen - if (cl.cmd.cursor_screen[0] <= -1) bits |= 8; - if (cl.cmd.cursor_screen[0] >= 1) bits |= 16; - if (cl.cmd.cursor_screen[1] <= -1) bits |= 32; - if (cl.cmd.cursor_screen[1] >= 1) bits |= 64; - - impulse = in_impulse; - in_impulse = 0; - - csqc_buttons = bits; - - if (cls.signon == SIGNONS) + // conditions for sending a move: + // if the move advances time or if the game is paused (in which case time + // is not advancing) + if ((cl.cmd.time > cl.movecmd[0].time || cl.mtime[0] <= cl.mtime[1]) && cls.signon == SIGNONS) { + // send the movement message + // PROTOCOL_QUAKE clc_move = 16 bytes total + // PROTOCOL_QUAKEDP clc_move = 16 bytes total + // PROTOCOL_NEHAHRAMOVIE clc_move = 16 bytes total + // PROTOCOL_DARKPLACES1 clc_move = 19 bytes total + // PROTOCOL_DARKPLACES2 clc_move = 25 bytes total + // PROTOCOL_DARKPLACES3 clc_move = 25 bytes total + // PROTOCOL_DARKPLACES4 clc_move = 19 bytes total + // PROTOCOL_DARKPLACES5 clc_move = 19 bytes total + // PROTOCOL_DARKPLACES6 clc_move = 52 bytes total + // PROTOCOL_DARKPLACES7 clc_move = 56 bytes total per move (can be up to 16 moves) + // PROTOCOL_QUAKEWORLD clc_move = 34 bytes total (typically, but can reach 43 bytes, or even 49 bytes with roll) + + // set button bits + // LordHavoc: added 6 new buttons and use and chat buttons, and prydon cursor active button + bits = 0; + if (in_attack.state & 3) bits |= 1;in_attack.state &= ~2; + if (in_jump.state & 3) bits |= 2;in_jump.state &= ~2; + if (in_button3.state & 3) bits |= 4;in_button3.state &= ~2; + if (in_button4.state & 3) bits |= 8;in_button4.state &= ~2; + if (in_button5.state & 3) bits |= 16;in_button5.state &= ~2; + if (in_button6.state & 3) bits |= 32;in_button6.state &= ~2; + if (in_button7.state & 3) bits |= 64;in_button7.state &= ~2; + if (in_button8.state & 3) bits |= 128;in_button8.state &= ~2; + if (in_use.state & 3) bits |= 256;in_use.state &= ~2; + if (key_dest != key_game || key_consoleactive) bits |= 512; + if (cl_prydoncursor.integer) bits |= 1024; + if (in_button9.state & 3) bits |= 2048;in_button9.state &= ~2; + if (in_button10.state & 3) bits |= 4096;in_button10.state &= ~2; + if (in_button11.state & 3) bits |= 8192;in_button11.state &= ~2; + if (in_button12.state & 3) bits |= 16384;in_button12.state &= ~2; + if (in_button13.state & 3) bits |= 32768;in_button13.state &= ~2; + if (in_button14.state & 3) bits |= 65536;in_button14.state &= ~2; + if (in_button15.state & 3) bits |= 131072;in_button15.state &= ~2; + if (in_button16.state & 3) bits |= 262144;in_button16.state &= ~2; + // button bits 19-31 unused currently + // rotate/zoom view serverside if PRYDON_CLIENTCURSOR cursor is at edge of screen + if (cl.cmd.cursor_screen[0] <= -1) bits |= 8; + if (cl.cmd.cursor_screen[0] >= 1) bits |= 16; + if (cl.cmd.cursor_screen[1] <= -1) bits |= 32; + if (cl.cmd.cursor_screen[1] >= 1) bits |= 64; + cl.cmd.buttons = bits; + + // set impulse + cl.cmd.impulse = in_impulse; + in_impulse = 0; + + // movement is set by input code (forwardmove/sidemove/upmove) + + // set viewangles + VectorCopy(cl.viewangles, cl.cmd.viewangles); + + msectime = floor(cl.cmd.time * 1000); + cl.cmd.msec = (unsigned char)bound(0, msectime - oldmsectime, 255); + // ridiculous value rejection (matches qw) + if (cl.cmd.msec > 250) + cl.cmd.msec = 100; + oldmsectime = msectime; + + cls.movesequence++; + if (cl_movement.integer) + cl.cmd.sequence = cls.movesequence; + else + cl.cmd.sequence = 0; + + // set prydon cursor info + CL_UpdatePrydonCursor(); + // always dump the first two messages, because they may contain leftover inputs from the last level - if (++cl.movemessages >= 2) + if (cls.movesequence > 2) { - // send the movement message - // PROTOCOL_QUAKE clc_move = 16 bytes total - // PROTOCOL_QUAKEDP clc_move = 16 bytes total - // PROTOCOL_NEHAHRAMOVIE clc_move = 16 bytes total - // PROTOCOL_DARKPLACES1 clc_move = 19 bytes total - // PROTOCOL_DARKPLACES2 clc_move = 25 bytes total - // PROTOCOL_DARKPLACES3 clc_move = 25 bytes total - // PROTOCOL_DARKPLACES4 clc_move = 19 bytes total - // PROTOCOL_DARKPLACES5 clc_move = 19 bytes total - // PROTOCOL_DARKPLACES6 clc_move = 52 bytes total - // PROTOCOL_DARKPLACES7 clc_move = 56 bytes total - // PROTOCOL_QUAKEWORLD clc_move = 34 bytes total (typically, but can reach 43 bytes, or even 49 bytes with roll) + // update the cl.movecmd array which holds the most recent moves + for (i = CL_MAX_USERCMDS - 1;i >= 1;i--) + cl.movecmd[i] = cl.movecmd[i-1]; + cl.movecmd[0] = cl.cmd; + + // set the maxusercmds variable to limit how many should be sent + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + // qw uses exactly 3 moves + maxusercmds = 3; + } + else + { + // configurable number of unacknowledged moves + maxusercmds = bound(1, cl_netinputpacketlosstolerance.integer + 1, CL_MAX_USERCMDS); + // when movement prediction is off, there's not much point in repeating old input as it will just be ignored + if (!cl.movement_predicted) + maxusercmds = 1; + } + if (cls.protocol == PROTOCOL_QUAKEWORLD) { int checksumindex; - double msectime; - static double oldmsectime; - qw_usercmd_t *cmd, *oldcmd; - qw_usercmd_t nullcmd; - - //Con_Printf("code qw_clc_move\n"); - - i = cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK; - cmd = &cl.qw_moves[i]; - memset(&nullcmd, 0, sizeof(nullcmd)); - memset(cmd, 0, sizeof(*cmd)); - cmd->buttons = bits; - cmd->impulse = impulse; - cmd->forwardmove = (short)bound(-32768, forwardmove, 32767); - cmd->sidemove = (short)bound(-32768, sidemove, 32767); - cmd->upmove = (short)bound(-32768, upmove, 32767); - VectorCopy(cl.viewangles, cmd->angles); - msectime = realtime * 1000; - cmd->msec = (unsigned char)bound(0, msectime - oldmsectime, 255); - // ridiculous value rejection (matches qw) - if (cmd->msec > 250) - cmd->msec = 100; - oldmsectime = msectime; - - CL_ClientMovement_InputQW(cmd); + + CL_ClientMovement_InputQW(); MSG_WriteByte(&buf, qw_clc_move); // save the position for a checksum byte @@ -1406,17 +1421,9 @@ void CL_SendMove(void) packetloss += cls.netcon->packetlost[j]; MSG_WriteByte(&buf, packetloss); // write most recent 3 moves - i = (cls.netcon->qw.outgoing_sequence-2) & QW_UPDATE_MASK; - cmd = &cl.qw_moves[i]; - QW_MSG_WriteDeltaUsercmd(&buf, &nullcmd, cmd); - oldcmd = cmd; - i = (cls.netcon->qw.outgoing_sequence-1) & QW_UPDATE_MASK; - cmd = &cl.qw_moves[i]; - QW_MSG_WriteDeltaUsercmd(&buf, oldcmd, cmd); - oldcmd = cmd; - i = cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK; - cmd = &cl.qw_moves[i]; - QW_MSG_WriteDeltaUsercmd(&buf, oldcmd, cmd); + QW_MSG_WriteDeltaUsercmd(&buf, &nullcmd, &cl.movecmd[2]); + QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[2], &cl.movecmd[1]); + QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[1], &cl.movecmd[0]); // calculate the checksum buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->qw.outgoing_sequence); // if delta compression history overflows, request no delta @@ -1434,121 +1441,125 @@ void CL_SendMove(void) } else if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE) { + CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false); + // 5 bytes MSG_WriteByte (&buf, clc_move); - MSG_WriteFloat (&buf, cl.mtime[0]); // so server can get ping times + MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time // 3 bytes for (i = 0;i < 3;i++) - MSG_WriteAngle8i (&buf, cl.viewangles[i]); + MSG_WriteAngle8i (&buf, cl.movecmd[0].viewangles[i]); // 6 bytes - MSG_WriteCoord16i (&buf, forwardmove); - MSG_WriteCoord16i (&buf, sidemove); - MSG_WriteCoord16i (&buf, upmove); + MSG_WriteCoord16i (&buf, cl.movecmd[0].forwardmove); + MSG_WriteCoord16i (&buf, cl.movecmd[0].sidemove); + MSG_WriteCoord16i (&buf, cl.movecmd[0].upmove); // 2 bytes - MSG_WriteByte (&buf, bits); - MSG_WriteByte (&buf, impulse); - - CL_ClientMovement_Input((bits & 2) != 0, false); + MSG_WriteByte (&buf, cl.movecmd[0].buttons); + MSG_WriteByte (&buf, cl.movecmd[0].impulse); } else if (cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) { + CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false); + // 5 bytes MSG_WriteByte (&buf, clc_move); - MSG_WriteFloat (&buf, cl.mtime[0]); // so server can get ping times + MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time // 12 bytes for (i = 0;i < 3;i++) - MSG_WriteAngle32f (&buf, cl.viewangles[i]); + MSG_WriteAngle32f (&buf, cl.movecmd[0].viewangles[i]); // 6 bytes - MSG_WriteCoord16i (&buf, forwardmove); - MSG_WriteCoord16i (&buf, sidemove); - MSG_WriteCoord16i (&buf, upmove); + MSG_WriteCoord16i (&buf, cl.movecmd[0].forwardmove); + MSG_WriteCoord16i (&buf, cl.movecmd[0].sidemove); + MSG_WriteCoord16i (&buf, cl.movecmd[0].upmove); // 2 bytes - MSG_WriteByte (&buf, bits); - MSG_WriteByte (&buf, impulse); - - CL_ClientMovement_Input((bits & 2) != 0, false); + MSG_WriteByte (&buf, cl.movecmd[0].buttons); + MSG_WriteByte (&buf, cl.movecmd[0].impulse); } else if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5) { + CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false); + // 5 bytes MSG_WriteByte (&buf, clc_move); - MSG_WriteFloat (&buf, cl.mtime[0]); // so server can get ping times + MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time // 6 bytes for (i = 0;i < 3;i++) - MSG_WriteAngle16i (&buf, cl.viewangles[i]); + MSG_WriteAngle16i (&buf, cl.movecmd[0].viewangles[i]); // 6 bytes - MSG_WriteCoord16i (&buf, forwardmove); - MSG_WriteCoord16i (&buf, sidemove); - MSG_WriteCoord16i (&buf, upmove); + MSG_WriteCoord16i (&buf, cl.movecmd[0].forwardmove); + MSG_WriteCoord16i (&buf, cl.movecmd[0].sidemove); + MSG_WriteCoord16i (&buf, cl.movecmd[0].upmove); // 2 bytes - MSG_WriteByte (&buf, bits); - MSG_WriteByte (&buf, impulse); - - CL_ClientMovement_Input((bits & 2) != 0, false); + MSG_WriteByte (&buf, cl.movecmd[0].buttons); + MSG_WriteByte (&buf, cl.movecmd[0].impulse); } else { - // 5 bytes - MSG_WriteByte (&buf, clc_move); - if (cls.protocol != PROTOCOL_DARKPLACES6) + usercmd_t *cmd; + // FIXME: cl.movecmd[0].buttons & 16 is +button5, Nexuiz specific + CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, (cl.movecmd[0].buttons & 16) != 0); + + // send the latest moves in order, the old ones will be + // ignored by the server harmlessly, however if the previous + // packets were lost these moves will be used + // + // this reduces packet loss impact on gameplay. + for (j = 0, cmd = &cl.movecmd[maxusercmds-1];j < maxusercmds;j++, cmd--) { - if (cl_movement.integer) - { - cl.movesequence++; - MSG_WriteLong (&buf, cl.movesequence); - } - else - MSG_WriteLong (&buf, 0); + // don't repeat any stale moves + if (cmd->sequence && cmd->sequence < cls.servermovesequence) + continue; + // 5/9 bytes + MSG_WriteByte (&buf, clc_move); + if (cls.protocol != PROTOCOL_DARKPLACES6) + MSG_WriteLong (&buf, cmd->sequence); + MSG_WriteFloat (&buf, cmd->time); // last server packet time + // 6 bytes + for (i = 0;i < 3;i++) + MSG_WriteAngle16i (&buf, cmd->viewangles[i]); + // 6 bytes + MSG_WriteCoord16i (&buf, cmd->forwardmove); + MSG_WriteCoord16i (&buf, cmd->sidemove); + MSG_WriteCoord16i (&buf, cmd->upmove); + // 5 bytes + MSG_WriteLong (&buf, cmd->buttons); + MSG_WriteByte (&buf, cmd->impulse); + // PRYDON_CLIENTCURSOR + // 30 bytes + MSG_WriteShort (&buf, (short)(cmd->cursor_screen[0] * 32767.0f)); + MSG_WriteShort (&buf, (short)(cmd->cursor_screen[1] * 32767.0f)); + MSG_WriteFloat (&buf, cmd->cursor_start[0]); + MSG_WriteFloat (&buf, cmd->cursor_start[1]); + MSG_WriteFloat (&buf, cmd->cursor_start[2]); + MSG_WriteFloat (&buf, cmd->cursor_impact[0]); + MSG_WriteFloat (&buf, cmd->cursor_impact[1]); + MSG_WriteFloat (&buf, cmd->cursor_impact[2]); + MSG_WriteShort (&buf, cmd->cursor_entitynumber); } - MSG_WriteFloat (&buf, cl.mtime[0]); // so server can get ping times - // 6 bytes - for (i = 0;i < 3;i++) - MSG_WriteAngle16i (&buf, cl.viewangles[i]); - // 6 bytes - MSG_WriteCoord16i (&buf, forwardmove); - MSG_WriteCoord16i (&buf, sidemove); - MSG_WriteCoord16i (&buf, upmove); - // 5 bytes - MSG_WriteLong (&buf, bits); - MSG_WriteByte (&buf, impulse); - // PRYDON_CLIENTCURSOR - // 30 bytes - MSG_WriteShort (&buf, (short)(cl.cmd.cursor_screen[0] * 32767.0f)); - MSG_WriteShort (&buf, (short)(cl.cmd.cursor_screen[1] * 32767.0f)); - MSG_WriteFloat (&buf, cl.cmd.cursor_start[0]); - MSG_WriteFloat (&buf, cl.cmd.cursor_start[1]); - MSG_WriteFloat (&buf, cl.cmd.cursor_start[2]); - MSG_WriteFloat (&buf, cl.cmd.cursor_impact[0]); - MSG_WriteFloat (&buf, cl.cmd.cursor_impact[1]); - MSG_WriteFloat (&buf, cl.cmd.cursor_impact[2]); - MSG_WriteShort (&buf, cl.cmd.cursor_entitynumber); - - // FIXME: bits & 16 is +button5, Nexuiz specific - CL_ClientMovement_Input((bits & 2) != 0, (bits & 16) != 0); } } + } - if (cls.protocol != PROTOCOL_QUAKEWORLD) + if (cls.protocol != PROTOCOL_QUAKEWORLD) + { + // ack the last few frame numbers + // (redundent to improve handling of client->server packet loss) + // for LATESTFRAMENUMS == 3 case this is 15 bytes + for (i = 0;i < LATESTFRAMENUMS;i++) { - // ack the last few frame numbers - // (redundent to improve handling of client->server packet loss) - // for LATESTFRAMENUMS == 3 case this is 15 bytes - for (i = 0;i < LATESTFRAMENUMS;i++) + if (cl.latestframenums[i] > 0) { - if (cl.latestframenums[i] > 0) - { - if (developer_networkentities.integer >= 1) - Con_Printf("send clc_ackframe %i\n", cl.latestframenums[i]); - MSG_WriteByte(&buf, clc_ackframe); - MSG_WriteLong(&buf, cl.latestframenums[i]); - } + if (developer_networkentities.integer >= 1) + Con_Printf("send clc_ackframe %i\n", cl.latestframenums[i]); + MSG_WriteByte(&buf, clc_ackframe); + MSG_WriteLong(&buf, cl.latestframenums[i]); } } - - // PROTOCOL_DARKPLACES6 = 67 bytes per packet - // PROTOCOL_DARKPLACES7 = 71 bytes per packet } + // PROTOCOL_DARKPLACES6 = 67 bytes per packet + // PROTOCOL_DARKPLACES7 = 71 bytes per packet + if (cls.protocol != PROTOCOL_QUAKEWORLD) { // acknowledge any recently received data blocks @@ -1672,6 +1683,7 @@ void CL_InitInput (void) Cvar_RegisterVariable(&m_filter); Cvar_RegisterVariable(&cl_netinputpacketspersecond); + Cvar_RegisterVariable(&cl_netinputpacketlosstolerance); Cvar_RegisterVariable(&cl_nodelta); }