]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_input.c
add a FIXME comment regarding canjump, will check that later
[xonotic/darkplaces.git] / cl_input.c
index be77aaafe42de23ea79d83fc2e2df28059a6a58e..d357c115cffdf6d2f0d46d4d97053fb915325ebb 100644 (file)
@@ -365,9 +365,9 @@ float CL_KeyState (kbutton_t *key)
        float           val;
        qboolean        impulsedown, impulseup, down;
 
-       impulsedown = key->state & 2;
-       impulseup = key->state & 4;
-       down = key->state & 1;
+       impulsedown = (key->state & 2) != 0;
+       impulseup = (key->state & 4) != 0;
+       down = (key->state & 1) != 0;
        val = 0;
 
        if (impulsedown && !impulseup)
@@ -423,6 +423,8 @@ 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_replay = {0, "cl_movement_replay", "1", "use engine prediction"};
+cvar_t cl_movement_nettimeout = {CVAR_SAVE, "cl_movement_nettimeout", "0.3", "stops predicting moves when server is lagging badly (avoids major performance problems), timeout in seconds"};
 cvar_t cl_movement_minping = {CVAR_SAVE, "cl_movement_minping", "0", "whether to use prediction when ping is lower than this value in milliseconds"};
 cvar_t cl_movement_track_canjump = {CVAR_SAVE, "cl_movement_track_canjump", "1", "track if the player released the jump key between two jumps to decide if he is able to jump or not; when off, this causes some \"sliding\" slightly above the floor when the jump key is held too long; if the mod allows repeated jumping by holding space all the time, this has to be set to zero too"};
 cvar_t cl_movement_maxspeed = {0, "cl_movement_maxspeed", "320", "how fast you can move (should match sv_maxspeed)"};
@@ -437,8 +439,8 @@ cvar_t cl_movement_accelerate = {0, "cl_movement_accelerate", "10", "how fast yo
 cvar_t cl_movement_airaccelerate = {0, "cl_movement_airaccelerate", "-1", "how fast you accelerate while in the air (should match sv_airaccelerate), if less than 0 the cl_movement_accelerate variable is used instead"};
 cvar_t cl_movement_wateraccelerate = {0, "cl_movement_wateraccelerate", "-1", "how fast you accelerate while in water (should match sv_wateraccelerate), if less than 0 the cl_movement_accelerate variable is used instead"};
 cvar_t cl_movement_jumpvelocity = {0, "cl_movement_jumpvelocity", "270", "how fast you move upward when you begin a jump (should match the quakec code)"};
-cvar_t cl_movement_airaccel_qw = {0, "cl_movement_airaccel_qw", "1", "ratio of QW-style air control as opposed to simple acceleration (should match sv_airaccel_qw)"};
-cvar_t cl_movement_airaccel_sideways_friction = {0, "cl_movement_airaccel_sideways_friction", "0", "anti-sideways movement stabilization (should match sv_airaccel_sideways_friction)"};
+cvar_t cl_movement_airaccel_qw = {0, "cl_movement_airaccel_qw", "1", "ratio of QW-style air control as opposed to simple acceleration (reduces speed gain when zigzagging) (should match sv_airaccel_qw); when < 0, the speed is clamped against the maximum allowed forward speed after the move"};
+cvar_t cl_movement_airaccel_sideways_friction = {0, "cl_movement_airaccel_sideways_friction", "0", "anti-sideways movement stabilization (should match sv_airaccel_sideways_friction); when < 0, only so much friction is applied that braking (by accelerating backwards) cannot be stronger"};
 
 cvar_t in_pitch_min = {0, "in_pitch_min", "-90", "how far downward you can aim (quake used -70"};
 cvar_t in_pitch_max = {0, "in_pitch_max", "90", "how far upward you can aim (quake used 80"};
@@ -449,12 +451,14 @@ cvar_t m_accelerate_minspeed = {CVAR_SAVE, "m_accelerate_minspeed","5000", "belo
 cvar_t m_accelerate_maxspeed = {CVAR_SAVE, "m_accelerate_maxspeed","10000", "above this speed, full acceleration is done"};
 cvar_t m_accelerate_filter = {CVAR_SAVE, "m_accelerate_filter","0.1", "mouse acceleration factor filtering"};
 
-cvar_t cl_netfps = {CVAR_SAVE, "cl_netfps","20", "how many input packets to send to server each second"};
+cvar_t cl_netfps = {CVAR_SAVE, "cl_netfps","72", "how many input packets to send to server each second"};
 cvar_t cl_netrepeatinput = {CVAR_SAVE, "cl_netrepeatinput", "1", "how many packets in a row can be lost without movement issues when using cl_movement (technically how many input messages to repeat in each packet that have not yet been acknowledged by the server), only affects DP7 and later servers (Quake uses 0, QuakeWorld uses 2, and just for comparison Quake3 uses 1)"};
 cvar_t cl_netimmediatebuttons = {CVAR_SAVE, "cl_netimmediatebuttons", "1", "sends extra packets whenever your buttons change or an impulse is used (basically: whenever you click fire or change weapon)"};
 
 cvar_t cl_nodelta = {0, "cl_nodelta", "0", "disables delta compression of non-player entities in QW network protocol"};
 
+cvar_t cl_csqc_generatemousemoveevents = {0, "cl_csqc_generatemousemoveevents", "1", "enables calls to CSQC_InputEvent with type 2, for compliance with EXT_CSQC spec"};
+
 extern cvar_t v_flipped;
 
 /*
@@ -514,6 +518,7 @@ CL_Input
 Send the intended movement message to the server
 ================
 */
+extern qboolean CL_VM_InputEvent (int eventtype, int x, int y);
 void CL_Input (void)
 {
        float mx, my;
@@ -560,6 +565,27 @@ void CL_Input (void)
        // allow mice or other external controllers to add to the move
        IN_Move ();
 
+       // send mouse move to csqc
+       if (cl.csqc_loaded && cl_csqc_generatemousemoveevents.integer)
+       {
+               if (cl.csqc_wantsmousemove)
+               {
+                       // event type 3 is a DP_CSQC thing
+                       static int oldwindowmouse[2];
+                       if (oldwindowmouse[0] != in_windowmouse_x || oldwindowmouse[1] != in_windowmouse_y)
+                       {
+                               CL_VM_InputEvent(3, in_windowmouse_x * vid_conwidth.integer / vid.width, in_windowmouse_y * vid_conheight.integer / vid.height);
+                               oldwindowmouse[0] = in_windowmouse_x;
+                               oldwindowmouse[1] = in_windowmouse_y;
+                       }
+               }
+               else
+               {
+                       if (in_mouse_x || in_mouse_y)
+                               CL_VM_InputEvent(2, in_mouse_x * vid_conwidth.integer / vid.width, in_mouse_y * vid_conheight.integer / vid.height);
+               }
+       }
+
        // apply m_accelerate if it is on
        if(m_accelerate.value > 1)
        {
@@ -586,14 +612,7 @@ void CL_Input (void)
                }
                else
                {
-                       /*
-                       f = log(averagespeed);
-                       mi = log(mi);
-                       ma = log(ma);
-                       */
                        f = averagespeed;
-                       mi = mi;
-                       ma = ma;
                        f = (f - mi) / (ma - mi) * (m_accelerate.value - 1) + 1;
                }
 
@@ -621,17 +640,10 @@ void CL_Input (void)
        }
 
        // if not in menu, apply mouse move to viewangles/movement
-       if (!key_consoleactive && key_dest == key_game && !cl.csqc_wantsmousemove)
+       if (!key_consoleactive && key_dest == key_game && !cl.csqc_wantsmousemove && cl_prydoncursor.integer <= 0)
        {
                float modulatedsensitivity = sensitivity.value * cl.sensitivityscale;
-               if (cl_prydoncursor.integer)
-               {
-                       // mouse interacting with the scene, mostly stationary view
-                       V_StopPitchDrift();
-                       cl.cmd.cursor_screen[0] += in_mouse_x * modulatedsensitivity / vid.width;
-                       cl.cmd.cursor_screen[1] += in_mouse_y * modulatedsensitivity / vid.height;
-               }
-               else if (in_strafe.state & 1)
+               if (in_strafe.state & 1)
                {
                        // strafing mode, all looking is movement
                        V_StopPitchDrift();
@@ -659,7 +671,13 @@ void CL_Input (void)
                }
        }
        else // don't pitch drift when csqc is controlling the mouse
+       {
+               // mouse interacting with the scene, mostly stationary view
                V_StopPitchDrift();
+               // update prydon cursor
+               cl.cmd.cursor_screen[0] = in_windowmouse_x * 2.0 / vid.width - 1.0;
+               cl.cmd.cursor_screen[1] = in_windowmouse_y * 2.0 / vid.height - 1.0;
+       }
 
        if(v_flipped.integer)
        {
@@ -742,7 +760,7 @@ void CL_UpdatePrydonCursor(void)
 {
        vec3_t temp;
 
-       if (!cl_prydoncursor.integer)
+       if (cl_prydoncursor.integer <= 0)
                VectorClear(cl.cmd.cursor_screen);
 
        /*
@@ -777,7 +795,15 @@ void CL_UpdatePrydonCursor(void)
        VectorSet(temp, cl.cmd.cursor_screen[2] * 1000000, (v_flipped.integer ? -1 : 1) * cl.cmd.cursor_screen[0] * -r_refdef.view.frustum_x * 1000000, cl.cmd.cursor_screen[1] * -r_refdef.view.frustum_y * 1000000);
        Matrix4x4_Transform(&r_refdef.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);
+       if (cl_prydoncursor_notrace.integer)
+       {
+               cl.cmd.cursor_fraction = 1.0f;
+               VectorCopy(cl.cmd.cursor_end, cl.cmd.cursor_impact);
+               VectorClear(cl.cmd.cursor_normal);
+               cl.cmd.cursor_entitynumber = 0;
+       }
+       else
+               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);
 }
 
 typedef enum waterlevel_e
@@ -845,7 +871,7 @@ qboolean CL_ClientMovement_Unstick(cl_clientmovement_state_t *s)
        for (i = 0;i < NUMOFFSETS;i++)
        {
                VectorAdd(offsets[i], s->origin, neworigin);
-               if (!CL_Move(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false).startsolid)
+               if (!CL_TraceBox(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false).startsolid)
                {
                        VectorCopy(neworigin, s->origin);
                        return true;
@@ -876,7 +902,7 @@ void CL_ClientMovement_UpdateStatus(cl_clientmovement_state_t *s)
                // low ceiling first
                if (s->crouched)
                {
-                       trace = CL_Move(s->origin, cl.playerstandmins, cl.playerstandmaxs, s->origin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                       trace = CL_TraceBox(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;
                }
@@ -895,22 +921,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] - 1); // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :)
-       trace = CL_Move(origin1, s->mins, s->maxs, origin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+       trace = CL_TraceBox(origin1, s->mins, s->maxs, origin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
        s->onground = trace.fraction < 1 && trace.plane.normal[2] > 0.7;
 
        // set watertype/waterlevel
        VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + s->mins[2] + 1);
        s->waterlevel = WATERLEVEL_NONE;
-       s->watertype = CL_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK;
+       s->watertype = CL_TracePoint(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_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
+               if (CL_TracePoint(origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
                {
                        s->waterlevel = WATERLEVEL_SWIMMING;
                        origin1[2] = s->origin[2] + 22;
-                       if (CL_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
+                       if (CL_TracePoint(origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
                                s->waterlevel = WATERLEVEL_SUBMERGED;
                }
        }
@@ -937,20 +963,20 @@ void CL_ClientMovement_Move(cl_clientmovement_state_t *s)
        for (bump = 0, t = s->cmd.frametime;bump < 8 && VectorLength2(s->velocity) > 0;bump++)
        {
                VectorMA(s->origin, t, s->velocity, neworigin);
-               trace = CL_Move(s->origin, s->mins, s->maxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+               trace = CL_TraceBox(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] + cl.movevars_stepheight);
                        VectorSet(neworigin2, neworigin[0], neworigin[1], s->origin[2] + cl.movevars_stepheight);
-                       trace2 = CL_Move(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                       trace2 = CL_TraceBox(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_Move(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                               trace3 = CL_TraceBox(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)
@@ -1000,10 +1026,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_Move(spot, vec3_origin, vec3_origin, spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
+               if (CL_TracePoint(spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
                {
                        spot[2] += 24;
-                       if (!CL_Move(spot, vec3_origin, vec3_origin, spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
+                       if (!CL_TracePoint(spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
                        {
                                VectorScale(forward, 50, s->velocity);
                                s->velocity[2] = 310;
@@ -1066,7 +1092,7 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
                                s->velocity[2] =  80;
                        else
                        {
-                               if (gamemode == GAME_NEXUIZ)
+                               if (gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
                                        s->velocity[2] = 200;
                                else
                                        s->velocity[2] = 100;
@@ -1077,22 +1103,65 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
        CL_ClientMovement_Move(s);
 }
 
+static vec_t CL_IsMoveInDirection(vec_t forward, vec_t side, vec_t angle)
+{
+       if(forward == 0 && side == 0)
+               return 0; // avoid division by zero
+       angle -= RAD2DEG(atan2(side, forward));
+       angle = (ANGLEMOD(angle + 180) - 180) / 45;
+       if(angle >  1)
+               return 0;
+       if(angle < -1)
+               return 0;
+       return 1 - fabs(angle);
+}
+
+static vec_t CL_GeomLerp(vec_t a, vec_t lerp, vec_t b)
+{
+       if(a == 0)
+       {
+               if(lerp < 1)
+                       return 0;
+               else
+                       return b;
+       }
+       if(b == 0)
+       {
+               if(lerp > 0)
+                       return 0;
+               else
+                       return a;
+       }
+       return a * pow(fabs(b / a), lerp);
+}
+
 void CL_ClientMovement_Physics_CPM_PM_Aircontrol(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed)
 {
        vec_t zspeed, speed, dot, k;
 
+#if 0
+       // this doesn't play well with analog input
        if(s->cmd.forwardmove == 0 || s->cmd.sidemove != 0)
                return;
-       
+       k = 32;
+#else
+       k = 32 * (2 * CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, 0) - 1);
+       if(k <= 0)
+               return;
+#endif
+
+       k *= bound(0, wishspeed / cl.movevars_maxairspeed, 1);
+
        zspeed = s->velocity[2];
        s->velocity[2] = 0;
        speed = VectorNormalizeLength(s->velocity);
 
        dot = DotProduct(s->velocity, wishdir);
-       k = 32;
-       k *= cl.movevars_aircontrol*dot*dot*s->cmd.frametime;
 
        if(dot > 0) { // we can't change direction while slowing down
+               k *= pow(dot, cl.movevars_aircontrol_power)*s->cmd.frametime;
+               speed = max(0, speed - cl.movevars_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32);
+               k *= cl.movevars_aircontrol;
                VectorMAM(speed, s->velocity, k, wishdir, s->velocity);
                VectorNormalize(s->velocity);
        }
@@ -1101,25 +1170,91 @@ void CL_ClientMovement_Physics_CPM_PM_Aircontrol(cl_clientmovement_state_t *s, v
        s->velocity[2] = zspeed;
 }
 
-void CL_ClientMovement_Physics_PM_Accelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed, vec_t accel, vec_t accelqw, vec_t sidefric)
+float CL_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor)
 {
-       vec_t vel_straight, vel_z;
+       return
+               (accelqw < 0 ? -1 : +1)
+               *
+               bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1);
+}
+
+void CL_ClientMovement_Physics_PM_Accelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed, vec_t wishspeed0, vec_t accel, vec_t accelqw, vec_t stretchfactor, vec_t sidefric, vec_t speedlimit)
+{
+       vec_t vel_straight;
+       vec_t vel_z;
        vec3_t vel_perpend;
-       vec_t addspeed;
+       vec_t step;
+       vec3_t vel_xy;
+       vec_t vel_xy_current;
+       vec_t vel_xy_backward, vel_xy_forward;
+       vec_t speedclamp;
+
+       if(stretchfactor > 0)
+               speedclamp = stretchfactor;
+       else if(accelqw < 0)
+               speedclamp = 1;
+       else
+               speedclamp = -1; // no clamping
+
+       if(accelqw < 0)
+               accelqw = -accelqw;
+
+       if(cl.moveflags & MOVEFLAG_Q2AIRACCELERATE)
+               wishspeed0 = wishspeed; // don't need to emulate this Q1 bug
 
        vel_straight = DotProduct(s->velocity, wishdir);
        vel_z = s->velocity[2];
-       VectorMA(s->velocity, -vel_straight, wishdir, vel_perpend); vel_perpend[2] -= vel_z;
+       VectorCopy(s->velocity, vel_xy); vel_xy[2] -= vel_z;
+       VectorMA(vel_xy, -vel_straight, wishdir, vel_perpend);
+
+       step = accel * s->cmd.frametime * wishspeed0;
 
-       addspeed = wishspeed - vel_straight;
-       if(addspeed > 0)
-               vel_straight = vel_straight + min(addspeed, accel * s->cmd.frametime * wishspeed) * accelqw;
-       if(wishspeed > 0)
-               vel_straight = vel_straight + min(wishspeed, accel * s->cmd.frametime * wishspeed) * (1 - accelqw);
-       
-       VectorScale(vel_perpend, 1 - s->cmd.frametime * wishspeed * sidefric, vel_perpend);
+       vel_xy_current  = VectorLength(vel_xy);
+       if(speedlimit > 0)
+               accelqw = CL_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed));
+       vel_xy_forward  = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw);
+       vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw);
+       if(vel_xy_backward < 0)
+               vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards
+
+       vel_straight    = vel_straight   + bound(0, wishspeed - vel_straight,   step) * accelqw + step * (1 - accelqw);
+
+       if(sidefric < 0 && VectorLength2(vel_perpend))
+               // negative: only apply so much sideways friction to stay below the speed you could get by "braking"
+       {
+               vec_t f, fmin;
+               f = max(0, 1 + s->cmd.frametime * wishspeed * sidefric);
+               fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / VectorLength2(vel_perpend);
+               // assume: fmin > 1
+               // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend
+               // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend
+               // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy
+               // obviously, this cannot be
+               if(fmin <= 0)
+                       VectorScale(vel_perpend, f, vel_perpend);
+               else
+               {
+                       fmin = sqrt(fmin);
+                       VectorScale(vel_perpend, max(fmin, f), vel_perpend);
+               }
+       }
+       else
+               VectorScale(vel_perpend, max(0, 1 - s->cmd.frametime * wishspeed * sidefric), vel_perpend);
 
        VectorMA(vel_perpend, vel_straight, wishdir, s->velocity);
+
+       if(speedclamp >= 0)
+       {
+               vec_t vel_xy_preclamp;
+               vel_xy_preclamp = VectorLength(s->velocity);
+               if(vel_xy_preclamp > 0) // prevent division by zero
+               {
+                       vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp;
+                       if(vel_xy_current < vel_xy_preclamp)
+                               VectorScale(s->velocity, (vel_xy_current / vel_xy_preclamp), s->velocity);
+               }
+       }
+
        s->velocity[2] += vel_z;
 }
 
@@ -1181,6 +1316,7 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
        vec_t addspeed;
        vec_t accelspeed;
        vec_t f;
+       vec_t gravity;
        vec3_t forward;
        vec3_t right;
        vec3_t up;
@@ -1236,9 +1372,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_Move(neworigin2, s->mins, s->maxs, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                                       trace = CL_TraceBox(neworigin2, s->mins, s->maxs, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
                                else
-                                       trace = CL_Move(neworigin2, vec3_origin, vec3_origin, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                                       trace = CL_TraceLine(neworigin2, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false, false);
                                if (trace.fraction == 1 && !trace.startsolid)
                                        friction *= cl.movevars_edgefriction;
                        }
@@ -1253,21 +1389,31 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
                        accelspeed = min(cl.movevars_accelerate * s->cmd.frametime * wishspeed, addspeed);
                        VectorMA(s->velocity, accelspeed, wishdir, s->velocity);
                }
-               s->velocity[2] -= cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime;
+               if(cl.moveflags & MOVEFLAG_NOGRAVITYONGROUND)
+                       gravity = 0;
+               else
+                       gravity = cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime;
+               if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+                       s->velocity[2] -= gravity * 0.5f;
+               else
+                       s->velocity[2] -= gravity;
                if (cls.protocol == PROTOCOL_QUAKEWORLD)
                        s->velocity[2] = 0;
                if (VectorLength2(s->velocity))
                        CL_ClientMovement_Move(s);
+               if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+                       s->velocity[2] -= gravity * 0.5f;
        }
        else
        {
                if (s->waterjumptime <= 0)
                {
                        // apply air speed limit
-                       vec_t accel, wishspeed2, accelqw;
+                       vec_t accel, wishspeed0, wishspeed2, accelqw, strafity;
                        qboolean accelerating;
 
                        accelqw = cl.movevars_airaccel_qw;
+                       wishspeed0 = wishspeed;
                        wishspeed = min(wishspeed, cl.movevars_maxairspeed);
                        if (s->crouched)
                                wishspeed *= 0.5;
@@ -1278,42 +1424,42 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
 
                        // CPM: air control
                        if(cl.movevars_airstopaccelerate != 0)
-                               if(DotProduct(s->velocity, wishdir) < 0)
-                                       accel = cl.movevars_airstopaccelerate;
-                       if(s->cmd.forwardmove == 0 && s->cmd.sidemove != 0)
                        {
-                               if(cl.movevars_maxairstrafespeed)
-                               {
-                                       if(wishspeed > cl.movevars_maxairstrafespeed)
-                                               wishspeed = cl.movevars_maxairstrafespeed;
-                                       if(cl.movevars_maxairstrafespeed < cl.movevars_maxairspeed)
-                                               accelqw = 1;
-                                               // otherwise, CPMA-style air acceleration misbehaves a lot
-                                               // if partially non-QW acceleration is used (as in, strafing
-                                               // would get faster than moving forward straight)
-                               }
-                               if(cl.movevars_airstrafeaccelerate)
-                               {
-                                       accel = cl.movevars_airstrafeaccelerate;
-                                       if(cl.movevars_airstrafeaccelerate > cl.movevars_airaccelerate)
-                                               accelqw = 1;
-                                               // otherwise, CPMA-style air acceleration misbehaves a lot
-                                               // if partially non-QW acceleration is used (as in, strafing
-                                               // would get faster than moving forward straight)
-                               }
+                               vec3_t curdir;
+                               curdir[0] = s->velocity[0];
+                               curdir[1] = s->velocity[1];
+                               curdir[2] = 0;
+                               VectorNormalize(curdir);
+                               accel = accel + (cl.movevars_airstopaccelerate - accel) * max(0, -DotProduct(curdir, wishdir));
                        }
+                       strafity = CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, -90) + CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, +90); // if one is nonzero, other is always zero
+                       if(cl.movevars_maxairstrafespeed)
+                               wishspeed = min(wishspeed, CL_GeomLerp(cl.movevars_maxairspeed, strafity, cl.movevars_maxairstrafespeed));
+                       if(cl.movevars_airstrafeaccelerate)
+                               accel = CL_GeomLerp(cl.movevars_airaccelerate, strafity, cl.movevars_airstrafeaccelerate);
+                       if(cl.movevars_airstrafeaccel_qw)
+                               accelqw =
+                                       (((strafity > 0.5 ? cl.movevars_airstrafeaccel_qw : cl.movevars_airaccel_qw) >= 0) ? +1 : -1)
+                                       *
+                                       (1 - CL_GeomLerp(1 - fabs(cl.movevars_airaccel_qw), strafity, 1 - fabs(cl.movevars_airstrafeaccel_qw)));
                        // !CPM
 
                        if(cl.movevars_warsowbunny_turnaccel && accelerating && s->cmd.sidemove == 0 && s->cmd.forwardmove != 0)
                                CL_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2);
                        else
-                               CL_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, accel, accelqw, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed);
+                               CL_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed0, accel, accelqw, cl.movevars_airaccel_qw_stretchfactor, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed, cl.movevars_airspeedlimit_nonqw);
 
                        if(cl.movevars_aircontrol)
                                CL_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
                }
-               s->velocity[2] -= cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime;
+               gravity = cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime;
+               if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+                       s->velocity[2] -= gravity * 0.5f;
+               else
+                       s->velocity[2] -= gravity;
                CL_ClientMovement_Move(s);
+               if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+                       s->velocity[2] -= gravity * 0.5f;
        }
 }
 
@@ -1335,9 +1481,12 @@ void CL_UpdateMoveVars(void)
 {
        if (cls.protocol == PROTOCOL_QUAKEWORLD)
        {
+               cl.moveflags = 0;
        }
        else if (cl.stats[STAT_MOVEVARS_TICRATE])
        {
+               cl.moveflags = cl.stats[STAT_MOVEFLAGS];
+               cl.movevars_ticrate = cl.statsf[STAT_MOVEVARS_TICRATE];
                cl.movevars_timescale = cl.statsf[STAT_MOVEVARS_TIMESCALE];
                cl.movevars_gravity = cl.statsf[STAT_MOVEVARS_GRAVITY];
                cl.movevars_stopspeed = cl.statsf[STAT_MOVEVARS_STOPSPEED] ;
@@ -1352,6 +1501,7 @@ void CL_UpdateMoveVars(void)
                cl.movevars_maxairspeed = cl.statsf[STAT_MOVEVARS_MAXAIRSPEED];
                cl.movevars_stepheight = cl.statsf[STAT_MOVEVARS_STEPHEIGHT];
                cl.movevars_airaccel_qw = cl.statsf[STAT_MOVEVARS_AIRACCEL_QW];
+               cl.movevars_airaccel_qw_stretchfactor = cl.statsf[STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR];
                cl.movevars_airaccel_sideways_friction = cl.statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION];
                cl.movevars_friction = cl.statsf[STAT_MOVEVARS_FRICTION];
                cl.movevars_wallfriction = cl.statsf[STAT_MOVEVARS_WALLFRICTION];
@@ -1359,15 +1509,21 @@ void CL_UpdateMoveVars(void)
                cl.movevars_airstopaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTOPACCELERATE];
                cl.movevars_airstrafeaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCELERATE];
                cl.movevars_maxairstrafespeed = cl.statsf[STAT_MOVEVARS_MAXAIRSTRAFESPEED];
+               cl.movevars_airstrafeaccel_qw = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCEL_QW];
                cl.movevars_aircontrol = cl.statsf[STAT_MOVEVARS_AIRCONTROL];
+               cl.movevars_aircontrol_power = cl.statsf[STAT_MOVEVARS_AIRCONTROL_POWER];
+               cl.movevars_aircontrol_penalty = cl.statsf[STAT_MOVEVARS_AIRCONTROL_PENALTY];
                cl.movevars_warsowbunny_airforwardaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL];
                cl.movevars_warsowbunny_accel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_ACCEL];
                cl.movevars_warsowbunny_topspeed = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED];
                cl.movevars_warsowbunny_turnaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL];
                cl.movevars_warsowbunny_backtosideratio = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO];
+               cl.movevars_airspeedlimit_nonqw = cl.statsf[STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW];
        }
        else
        {
+               cl.moveflags = 0;
+               cl.movevars_ticrate = slowmo.value / bound(1.0f, cl_netfps.value, 1000.0f);
                cl.movevars_timescale = slowmo.value;
                cl.movevars_gravity = sv_gravity.value;
                cl.movevars_stopspeed = cl_movement_stopspeed.value;
@@ -1385,17 +1541,31 @@ void CL_UpdateMoveVars(void)
                cl.movevars_maxairspeed = cl_movement_maxairspeed.value;
                cl.movevars_stepheight = cl_movement_stepheight.value;
                cl.movevars_airaccel_qw = cl_movement_airaccel_qw.value;
+               cl.movevars_airaccel_qw_stretchfactor = 0;
                cl.movevars_airaccel_sideways_friction = cl_movement_airaccel_sideways_friction.value;
                cl.movevars_airstopaccelerate = 0;
                cl.movevars_airstrafeaccelerate = 0;
                cl.movevars_maxairstrafespeed = 0;
+               cl.movevars_airstrafeaccel_qw = 0;
                cl.movevars_aircontrol = 0;
+               cl.movevars_aircontrol_power = 2;
+               cl.movevars_aircontrol_penalty = 0;
                cl.movevars_warsowbunny_airforwardaccel = 0;
                cl.movevars_warsowbunny_accel = 0;
                cl.movevars_warsowbunny_topspeed = 0;
                cl.movevars_warsowbunny_turnaccel = 0;
                cl.movevars_warsowbunny_backtosideratio = 0;
+               cl.movevars_airspeedlimit_nonqw = 0;
+       }
+
+       if(!(cl.moveflags & MOVEFLAG_VALID))
+       {
+               if(gamemode == GAME_NEXUIZ)
+                       cl.moveflags = MOVEFLAG_Q2AIRACCELERATE;
        }
+
+       if(cl.movevars_aircontrol_power <= 0)
+               cl.movevars_aircontrol_power = 2; // CPMA default
 }
 
 void CL_ClientMovement_Replay(void)
@@ -1407,6 +1577,9 @@ void CL_ClientMovement_Replay(void)
        if (cl.movement_predicted && !cl.movement_replay)
                return;
 
+       if (!cl_movement_replay.integer)
+               return;
+
        // set up starting state for the series of moves
        memset(&s, 0, sizeof(s));
        VectorCopy(cl.entities[cl.playerentity].state_current.origin, s.origin);
@@ -1437,24 +1610,29 @@ void CL_ClientMovement_Replay(void)
                        s.cmd = cl.movecmd[i];
                        if (i < CL_MAX_USERCMDS - 1)
                                s.cmd.canjump = cl.movecmd[i+1].canjump;
+                               // FIXME doesn't this read from unused slots? shouldn't this rather be limited to the initial value of i?
+
                        // if a move is more than 50ms, do it as two moves (matching qwsv)
                        //Con_Printf("%i ", s.cmd.msec);
-                       if (s.cmd.frametime > 0.05)
+                       if(s.cmd.frametime > 0.0005)
                        {
-                               s.cmd.frametime /= 2;
+                               if (s.cmd.frametime > 0.05)
+                               {
+                                       s.cmd.frametime /= 2;
+                                       CL_ClientMovement_PlayerMove(&s);
+                               }
                                CL_ClientMovement_PlayerMove(&s);
+                               cl.movecmd[i].canjump = s.cmd.canjump;
                        }
-                       CL_ClientMovement_PlayerMove(&s);
-                       cl.movecmd[i].canjump = s.cmd.canjump;
                }
                //Con_Printf("\n");
+               CL_ClientMovement_UpdateStatus(&s);
        }
        else
        {
                // get the first movement queue entry to know whether to crouch and such
                s.cmd = cl.movecmd[0];
        }
-       CL_ClientMovement_UpdateStatus(&s);
 
        if (cls.demoplayback) // for bob, speedometer
                VectorCopy(cl.mvelocity[0], cl.movement_velocity);
@@ -1539,6 +1717,34 @@ void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, usercmd_t *from, usercmd_t *to)
        MSG_WriteByte(buf, to->msec);
 }
 
+void CL_NewFrameReceived(int num)
+{
+       if (developer_networkentities.integer >= 10)
+               Con_Printf("recv: svc_entities %i\n", num);
+       cl.latestframenums[cl.latestframenumsposition] = num;
+       cl.latestsendnums[cl.latestframenumsposition] = cl.cmd.sequence;
+       cl.latestframenumsposition = (cl.latestframenumsposition + 1) % LATESTFRAMENUMS;
+}
+
+void CL_RotateMoves(const matrix4x4_t *m)
+{
+       // rotate viewangles in all previous moves
+       vec3_t v;
+       vec3_t f, r, u;
+       int i;
+       for (i = 0;i < CL_MAX_USERCMDS;i++)
+       {
+               if (cl.movecmd[i].sequence > cls.servermovesequence)
+               {
+                       usercmd_t *c = &cl.movecmd[i];
+                       AngleVectors(c->viewangles, f, r, u);
+                       Matrix4x4_Transform(m, f, v); VectorCopy(v, f);
+                       Matrix4x4_Transform(m, u, v); VectorCopy(v, u);
+                       AnglesFromVectors(c->viewangles, f, u, false);
+               }
+       }
+}
+
 /*
 ==============
 CL_SendMove
@@ -1556,14 +1762,19 @@ void CL_SendMove(void)
        unsigned char data[1024];
        double packettime;
        int msecdelta;
+       qboolean quemove;
+       qboolean important;
 
        // if playing a demo, do nothing
        if (!cls.netcon)
                return;
 
-       // we build up cl.movecmd[0] and then decide whether to send or not
-       // the prediction code will use this command even though it has not been
-       // sent yet
+       // we don't que moves during a lag spike (potential network timeout)
+       quemove = realtime - cl.last_received_message < cl_movement_nettimeout.value;
+
+       // we build up cl.cmd and then decide whether to send or not
+       // we store this into cl.movecmd[0] for prediction each frame even if we
+       // do not send, to make sure that prediction is instant
        cl.cmd.time = cl.time;
        cl.cmd.sequence = cls.netcon->outgoing_unreliable_sequence;
 
@@ -1580,7 +1791,7 @@ void CL_SendMove(void)
        if (in_button8.state  & 3) bits |= 128;
        if (in_use.state      & 3) bits |= 256;
        if (key_dest != key_game || key_consoleactive) bits |= 512;
-       if (cl_prydoncursor.integer) bits |= 1024;
+       if (cl_prydoncursor.integer > 0) bits |= 1024;
        if (in_button9.state  & 3)  bits |=   2048;
        if (in_button10.state  & 3) bits |=   4096;
        if (in_button11.state  & 3) bits |=   8192;
@@ -1591,10 +1802,13 @@ void CL_SendMove(void)
        if (in_button16.state  & 3) bits |= 262144;
        // 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;
+       if(cl_prydoncursor.integer > 0)
+       {
+               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;
+       }
 
        // set buttons and impulse
        cl.cmd.buttons = bits;
@@ -1610,7 +1824,7 @@ void CL_SendMove(void)
                cl.cmd.msec = 100;
        cl.cmd.frametime = cl.cmd.msec * (1.0 / 1000.0);
 
-       cl.cmd.predicted = cl_movement.integer;
+       cl.cmd.predicted = cl_movement.integer != 0;
 
        // movement is set by input code (forwardmove/sidemove/upmove)
        // always dump the first two moves, because they may contain leftover inputs from the last level
@@ -1636,14 +1850,15 @@ void CL_SendMove(void)
                break;
        case PROTOCOL_DARKPLACES6:
        case PROTOCOL_DARKPLACES7:
-               // FIXME: cl.movecmd[0].buttons & 16 is +button5, Nexuiz specific
+               // FIXME: cl.cmd.buttons & 16 is +button5, Nexuiz/Xonotic specific
                cl.cmd.crouch = (cl.cmd.buttons & 16) != 0;
                break;
        case PROTOCOL_UNKNOWN:
                break;
        }
 
-       cl.movecmd[0] = cl.cmd;
+       if (quemove)
+               cl.movecmd[0] = cl.cmd;
 
        // don't predict more than 200fps
        if (realtime >= cl.lastpackettime + 0.005)
@@ -1655,20 +1870,28 @@ void CL_SendMove(void)
        // don't send too often or else network connections can get clogged by a
        // high renderer framerate
        packettime = 1.0 / bound(1, cl_netfps.value, 1000);
-       // send input every frame in singleplayer
-       if (cl.islocalgame)
-               packettime = 0;
-       // always send if buttons changed or an impulse is pending
-       // even if it violates the rate limit!
-       if (!cl.movecmd[0].impulse && (!cl_netimmediatebuttons.integer || cl.movecmd[0].buttons == cl.movecmd[1].buttons))
+       if (cl.movevars_timescale && cl.movevars_ticrate)
        {
-               // don't choke the connection with packets (obey rate limit)
-               if ((cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS) && !NetConn_CanSend(cls.netcon) && !cl.islocalgame)
-                       return;
-               // don't send too often (cl_netfps)
-               if (realtime < cl.lastpackettime + packettime)
-                       return;
+               float maxtic = cl.movevars_ticrate / cl.movevars_timescale;
+               packettime = min(packettime, maxtic);
        }
+
+       // do not send 0ms packets because they mess up physics
+       if(cl.cmd.msec == 0 && cl.time > cl.oldtime && (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS))
+               return;
+       // always send if buttons changed or an impulse is pending
+       // even if it violates the rate limit!
+       important = (cl.cmd.impulse || (cl_netimmediatebuttons.integer && cl.cmd.buttons != cl.movecmd[1].buttons));
+       // don't send too often (cl_netfps)
+       if (!important && realtime < cl.lastpackettime + packettime)
+               return;
+       // don't choke the connection with packets (obey rate limit)
+       // it is important that this check be last, because it adds a new
+       // frame to the shownetgraph output and any cancelation after this
+       // will produce a nasty spike-like look to the netgraph
+       // we also still send if it is important
+       if (!NetConn_CanSend(cls.netcon) && !important)
+               return;
        // try to round off the lastpackettime to a multiple of the packet interval
        // (this causes it to emit packets at a steady beat)
        if (packettime > 0)
@@ -1707,14 +1930,14 @@ void CL_SendMove(void)
                        MSG_WriteByte(&buf, 0);
                        // packet loss percentage
                        for (j = 0, packetloss = 0;j < NETGRAPH_PACKETS;j++)
-                               if (cls.netcon->incoming_unreliablesize[j] == NETGRAPH_LOSTPACKET)
+                               if (cls.netcon->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
                                        packetloss++;
                        packetloss = packetloss * 100 / NETGRAPH_PACKETS;
                        MSG_WriteByte(&buf, packetloss);
                        // write most recent 3 moves
                        QW_MSG_WriteDeltaUsercmd(&buf, &nullcmd, &cl.movecmd[2]);
                        QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[2], &cl.movecmd[1]);
-                       QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[1], &cl.movecmd[0]);
+                       QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[1], &cl.cmd);
                        // calculate the checksum
                        buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->outgoing_unreliable_sequence);
                        // if delta compression history overflows, request no delta
@@ -1738,50 +1961,58 @@ void CL_SendMove(void)
                case PROTOCOL_NEHAHRABJP3:
                        // 5 bytes
                        MSG_WriteByte (&buf, clc_move);
-                       MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
-                       // 3 bytes
-                       for (i = 0;i < 3;i++)
-                               MSG_WriteAngle8i (&buf, cl.movecmd[0].viewangles[i]);
+                       MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time
+                       // 3 bytes (6 bytes in proquake)
+                       if (cls.proquake_servermod == 1) // MOD_PROQUAKE
+                       {
+                               for (i = 0;i < 3;i++)
+                                       MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]);
+                       }
+                       else
+                       {
+                               for (i = 0;i < 3;i++)
+                                       MSG_WriteAngle8i (&buf, cl.cmd.viewangles[i]);
+                       }
                        // 6 bytes
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].forwardmove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].sidemove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].upmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.forwardmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.sidemove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.upmove);
                        // 2 bytes
-                       MSG_WriteByte (&buf, cl.movecmd[0].buttons);
-                       MSG_WriteByte (&buf, cl.movecmd[0].impulse);
+                       MSG_WriteByte (&buf, cl.cmd.buttons);
+                       MSG_WriteByte (&buf, cl.cmd.impulse);
                        break;
                case PROTOCOL_DARKPLACES2:
                case PROTOCOL_DARKPLACES3:
                        // 5 bytes
                        MSG_WriteByte (&buf, clc_move);
-                       MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
+                       MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time
                        // 12 bytes
                        for (i = 0;i < 3;i++)
-                               MSG_WriteAngle32f (&buf, cl.movecmd[0].viewangles[i]);
+                               MSG_WriteAngle32f (&buf, cl.cmd.viewangles[i]);
                        // 6 bytes
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].forwardmove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].sidemove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].upmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.forwardmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.sidemove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.upmove);
                        // 2 bytes
-                       MSG_WriteByte (&buf, cl.movecmd[0].buttons);
-                       MSG_WriteByte (&buf, cl.movecmd[0].impulse);
+                       MSG_WriteByte (&buf, cl.cmd.buttons);
+                       MSG_WriteByte (&buf, cl.cmd.impulse);
                        break;
                case PROTOCOL_DARKPLACES1:
                case PROTOCOL_DARKPLACES4:
                case PROTOCOL_DARKPLACES5:
                        // 5 bytes
                        MSG_WriteByte (&buf, clc_move);
-                       MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
+                       MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time
                        // 6 bytes
                        for (i = 0;i < 3;i++)
-                               MSG_WriteAngle16i (&buf, cl.movecmd[0].viewangles[i]);
+                               MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]);
                        // 6 bytes
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].forwardmove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].sidemove);
-                       MSG_WriteCoord16i (&buf, cl.movecmd[0].upmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.forwardmove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.sidemove);
+                       MSG_WriteCoord16i (&buf, cl.cmd.upmove);
                        // 2 bytes
-                       MSG_WriteByte (&buf, cl.movecmd[0].buttons);
-                       MSG_WriteByte (&buf, cl.movecmd[0].impulse);
+                       MSG_WriteByte (&buf, cl.cmd.buttons);
+                       MSG_WriteByte (&buf, cl.cmd.impulse);
                case PROTOCOL_DARKPLACES6:
                case PROTOCOL_DARKPLACES7:
                        // set the maxusercmds variable to limit how many should be sent
@@ -1835,17 +2066,24 @@ void CL_SendMove(void)
 
        if (cls.protocol != PROTOCOL_QUAKEWORLD && buf.cursize)
        {
-               // ack the last few frame numbers
+               // ack entity frame numbers received since the last input was sent
                // (redundent to improve handling of client->server packet loss)
-               // for LATESTFRAMENUMS == 3 case this is 15 bytes
+               // if cl_netrepeatinput is 1 and client framerate matches server
+               // framerate, this is 10 bytes, if client framerate is lower this
+               // will be more...
+               int i, j;
+               int oldsequence = cl.cmd.sequence - bound(1, cl_netrepeatinput.integer + 1, 3);
+               if (oldsequence < 1)
+                       oldsequence = 1;
                for (i = 0;i < LATESTFRAMENUMS;i++)
                {
-                       if (cl.latestframenums[i] > 0)
+                       j = (cl.latestframenumsposition + i) % LATESTFRAMENUMS;
+                       if (cl.latestsendnums[j] >= oldsequence)
                        {
                                if (developer_networkentities.integer >= 10)
-                                       Con_Printf("send clc_ackframe %i\n", cl.latestframenums[i]);
+                                       Con_Printf("send clc_ackframe %i\n", cl.latestframenums[j]);
                                MSG_WriteByte(&buf, clc_ackframe);
-                               MSG_WriteLong(&buf, cl.latestframenums[i]);
+                               MSG_WriteLong(&buf, cl.latestframenums[j]);
                        }
                }
        }
@@ -1867,12 +2105,15 @@ void CL_SendMove(void)
        if (buf.cursize || cls.netcon->message.cursize)
                NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, max(20*(buf.cursize+40), cl_rate.integer), false);
 
-       // update the cl.movecmd array which holds the most recent moves,
-       // because we now need a new slot for the next input
-       for (i = CL_MAX_USERCMDS - 1;i >= 1;i--)
-               cl.movecmd[i] = cl.movecmd[i-1];
-       cl.movecmd[0].msec = 0;
-       cl.movecmd[0].frametime = 0;
+       if (quemove)
+       {
+               // update the cl.movecmd array which holds the most recent moves,
+               // because we now need a new slot for the next input
+               for (i = CL_MAX_USERCMDS - 1;i >= 1;i--)
+                       cl.movecmd[i] = cl.movecmd[i-1];
+               cl.movecmd[0].msec = 0;
+               cl.movecmd[0].frametime = 0;
+       }
 
        // clear button 'click' states
        in_attack.state  &= ~2;
@@ -1987,6 +2228,8 @@ void CL_InitInput (void)
 
        Cvar_RegisterVariable(&cl_movecliptokeyboard);
        Cvar_RegisterVariable(&cl_movement);
+       Cvar_RegisterVariable(&cl_movement_replay);
+       Cvar_RegisterVariable(&cl_movement_nettimeout);
        Cvar_RegisterVariable(&cl_movement_minping);
        Cvar_RegisterVariable(&cl_movement_track_canjump);
        Cvar_RegisterVariable(&cl_movement_maxspeed);
@@ -2017,5 +2260,7 @@ void CL_InitInput (void)
        Cvar_RegisterVariable(&cl_netimmediatebuttons);
 
        Cvar_RegisterVariable(&cl_nodelta);
+
+       Cvar_RegisterVariable(&cl_csqc_generatemousemoveevents);
 }