]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_input.c
sv_aircontrol_power - customizing the response curve of CPMA style air control
[xonotic/darkplaces.git] / cl_input.c
index d65d60fc36ee5fc5b2e1ae18807c0b5eaebcd5bb..aacdfcc5febea6a8eb01f96a559db26a80b603e3 100644 (file)
@@ -301,6 +301,7 @@ void IN_BestWeapon (void)
        // if we couldn't find any of the weapons, there's nothing more we can do...
 }
 
+#if 0
 void IN_CycleWeapon (void)
 {
        int i, n;
@@ -347,6 +348,7 @@ void IN_CycleWeapon (void)
        }
        // if we couldn't find any of the weapons, there's nothing more we can do...
 }
+#endif
 
 /*
 ===============
@@ -363,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)
@@ -413,6 +415,7 @@ cvar_t cl_backspeed = {CVAR_SAVE, "cl_backspeed","400","backward movement speed"
 cvar_t cl_sidespeed = {CVAR_SAVE, "cl_sidespeed","350","strafe movement speed"};
 
 cvar_t cl_movespeedkey = {CVAR_SAVE, "cl_movespeedkey","2.0","how much +speed multiplies keyboard movement speed"};
+cvar_t cl_movecliptokeyboard = {0, "cl_movecliptokeyboard", "0", "if set to 1, any move is clipped to the nine keyboard states; if set to 2, only the direction is clipped, not the amount"};
 
 cvar_t cl_yawspeed = {CVAR_SAVE, "cl_yawspeed","140","keyboard yaw turning speed"};
 cvar_t cl_pitchspeed = {CVAR_SAVE, "cl_pitchspeed","150","keyboard pitch turning speed"};
@@ -420,31 +423,36 @@ 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_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)"};
 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)"};
 cvar_t cl_movement_friction = {0, "cl_movement_friction", "4", "how fast you slow down (should match sv_friction)"};
 cvar_t cl_movement_wallfriction = {0, "cl_movement_wallfriction", "1", "how fast you slow down while sliding along a wall (should match sv_wallfriction)"};
 cvar_t cl_movement_waterfriction = {0, "cl_movement_waterfriction", "-1", "how fast you slow down (should match sv_waterfriction), if less than 0 the cl_movement_friction variable is used instead"};
-cvar_t cl_movement_edgefriction = {0, "cl_movement_edgefriction", "2", "how much to slow down when you may be about to fall off a ledge (should match edgefriction)"};
+cvar_t cl_movement_edgefriction = {0, "cl_movement_edgefriction", "1", "how much to slow down when you may be about to fall off a ledge (should match edgefriction)"};
 cvar_t cl_movement_stepheight = {0, "cl_movement_stepheight", "18", "how tall a step you can step in one instant (should match sv_stepheight)"};
 cvar_t cl_movement_accelerate = {0, "cl_movement_accelerate", "10", "how fast you accelerate (should match sv_accelerate)"};
 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"};
 
 cvar_t m_filter = {CVAR_SAVE, "m_filter","0", "smoothes mouse movement, less responsive but smoother aiming"};
+cvar_t m_accelerate = {CVAR_SAVE, "m_accelerate","1", "mouse acceleration factor (try 2)"};
+cvar_t m_accelerate_minspeed = {CVAR_SAVE, "m_accelerate_minspeed","5000", "below this speed, no acceleration is done"};
+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_netinputpacketsperserverpacket = {CVAR_SAVE, "cl_netinputpacketsperserverpacket", "1.0", "send this many input packets per server packet received"};
-cvar_t cl_netinputpacketspersecond = {CVAR_SAVE, "cl_netinputpacketspersecond","20", "how many input packets to send to server each second (only used on old servers, and note this is multiplied by cl_netinputpacketsperserverpacket)"};
-cvar_t cl_netinputpacketspersecond_qw = {CVAR_SAVE, "cl_netinputpacketspersecond_qw","72", "how many input packets to send to a qw server each second (only used on qw servers)"};
-cvar_t cl_netinputpacketlosstolerance = {CVAR_SAVE, "cl_netinputpacketlosstolerance", "1", "how many packets in a row can be lost without movement issues when using cl_movement (technically how many input messages to repeat in each packet that have not yet been acknowledged by the server), only affects DP7 and later servers (Quake uses 0, QuakeWorld uses 2, and just for comparison Quake3 uses 1)"};
+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"};
 
@@ -490,19 +498,15 @@ void CL_AdjustAngles (void)
 
        cl.viewangles[YAW] = ANGLEMOD(cl.viewangles[YAW]);
        cl.viewangles[PITCH] = ANGLEMOD(cl.viewangles[PITCH]);
-       cl.viewangles[ROLL] = ANGLEMOD(cl.viewangles[ROLL]);
        if (cl.viewangles[YAW] >= 180)
                cl.viewangles[YAW] -= 360;
        if (cl.viewangles[PITCH] >= 180)
                cl.viewangles[PITCH] -= 360;
-       if (cl.viewangles[ROLL] >= 180)
-               cl.viewangles[ROLL] -= 360;
-
-       cl.viewangles[PITCH] = bound (in_pitch_min.value, cl.viewangles[PITCH], in_pitch_max.value);
-       cl.viewangles[ROLL] = bound(-50, cl.viewangles[ROLL], 50);
+       cl.viewangles[PITCH] = bound(in_pitch_min.value, cl.viewangles[PITCH], in_pitch_max.value);
+       cl.viewangles[ROLL] = bound(-180, cl.viewangles[ROLL], 180);
 }
 
-qboolean cl_ignoremousemove = false;
+int cl_ignoremousemoves = 2;
 
 /*
 ================
@@ -554,18 +558,48 @@ void CL_Input (void)
                cl.cmd.upmove *= cl_movespeedkey.value;
        }
 
-       in_mouse_x = 0;
-       in_mouse_y = 0;
-
        // allow mice or other external controllers to add to the move
        IN_Move ();
 
-       // ignore a mouse move if mouse was activated/deactivated this frame
-       if (cl_ignoremousemove)
+       // apply m_accelerate if it is on
+       if(m_accelerate.value > 1)
        {
-               cl_ignoremousemove = false;
-               in_mouse_x = 0;
-               in_mouse_y = 0;
+               static float averagespeed = 0;
+               float speed, f, mi, ma;
+
+               speed = sqrt(in_mouse_x * in_mouse_x + in_mouse_y * in_mouse_y) / cl.realframetime;
+               if(m_accelerate_filter.value > 0)
+                       f = bound(0, cl.realframetime / m_accelerate_filter.value, 1);
+               else
+                       f = 1;
+               averagespeed = speed * f + averagespeed * (1 - f);
+
+               mi = max(1, m_accelerate_minspeed.value);
+               ma = max(m_accelerate_minspeed.value + 1, m_accelerate_maxspeed.value);
+
+               if(averagespeed <= mi)
+               {
+                       f = 1;
+               }
+               else if(averagespeed >= ma)
+               {
+                       f = m_accelerate.value;
+               }
+               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;
+               }
+
+               in_mouse_x *= f;
+               in_mouse_y *= f;
        }
 
        // apply m_filter if it is on
@@ -579,43 +613,54 @@ void CL_Input (void)
        old_mouse_x = mx;
        old_mouse_y = my;
 
+       // ignore a mouse move if mouse was activated/deactivated this frame
+       if (cl_ignoremousemoves)
+       {
+               cl_ignoremousemoves--;
+               in_mouse_x = old_mouse_x = 0;
+               in_mouse_y = old_mouse_y = 0;
+       }
+
        // if not in menu, apply mouse move to viewangles/movement
-       if (!cl.csqc_wantsmousemove && in_client_mouse)
+       if (!key_consoleactive && key_dest == key_game && !cl.csqc_wantsmousemove)
        {
-               if (cl_prydoncursor.integer)
+               float modulatedsensitivity = sensitivity.value * cl.sensitivityscale;
+               if (cl_prydoncursor.integer > 0)
                {
                        // mouse interacting with the scene, mostly stationary view
                        V_StopPitchDrift();
-                       cl.cmd.cursor_screen[0] += in_mouse_x * sensitivity.value / vid.width;
-                       cl.cmd.cursor_screen[1] += in_mouse_y * sensitivity.value / vid.height;
+                       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)
                {
                        // strafing mode, all looking is movement
                        V_StopPitchDrift();
-                       cl.cmd.sidemove += m_side.value * in_mouse_x * sensitivity.value;
+                       cl.cmd.sidemove += m_side.value * in_mouse_x * modulatedsensitivity;
                        if (noclip_anglehack)
-                               cl.cmd.upmove -= m_forward.value * in_mouse_y * sensitivity.value;
+                               cl.cmd.upmove -= m_forward.value * in_mouse_y * modulatedsensitivity;
                        else
-                               cl.cmd.forwardmove -= m_forward.value * in_mouse_y * sensitivity.value;
+                               cl.cmd.forwardmove -= m_forward.value * in_mouse_y * modulatedsensitivity;
                }
                else if ((in_mlook.state & 1) || freelook.integer)
                {
                        // mouselook, lookstrafe causes turning to become strafing
                        V_StopPitchDrift();
                        if (lookstrafe.integer)
-                               cl.cmd.sidemove += m_side.value * in_mouse_x * sensitivity.value;
+                               cl.cmd.sidemove += m_side.value * in_mouse_x * modulatedsensitivity;
                        else
-                               cl.viewangles[YAW] -= m_yaw.value * in_mouse_x * sensitivity.value * cl.viewzoom;
-                       cl.viewangles[PITCH] += m_pitch.value * in_mouse_y * sensitivity.value * cl.viewzoom;
+                               cl.viewangles[YAW] -= m_yaw.value * in_mouse_x * modulatedsensitivity * cl.viewzoom;
+                       cl.viewangles[PITCH] += m_pitch.value * in_mouse_y * modulatedsensitivity * cl.viewzoom;
                }
                else
                {
                        // non-mouselook, yaw turning and forward/back movement
-                       cl.viewangles[YAW] -= m_yaw.value * in_mouse_x * sensitivity.value * cl.viewzoom;
-                       cl.cmd.forwardmove -= m_forward.value * in_mouse_y * sensitivity.value;
+                       cl.viewangles[YAW] -= m_yaw.value * in_mouse_x * modulatedsensitivity * cl.viewzoom;
+                       cl.cmd.forwardmove -= m_forward.value * in_mouse_y * modulatedsensitivity;
                }
        }
+       else // don't pitch drift when csqc is controlling the mouse
+               V_StopPitchDrift();
 
        if(v_flipped.integer)
        {
@@ -625,6 +670,71 @@ void CL_Input (void)
 
        // clamp after the move to prevent rendering with bad angles
        CL_AdjustAngles ();
+
+       if(cl_movecliptokeyboard.integer)
+       {
+               vec_t f = 1;
+               if (in_speed.state & 1)
+                       f *= cl_movespeedkey.value;
+               if(cl_movecliptokeyboard.integer == 2)
+               {
+                       // digital direction, analog amount
+                       vec_t wishvel_x, wishvel_y;
+                       f *= max(cl_sidespeed.value, max(cl_forwardspeed.value, cl_backspeed.value));
+                       wishvel_x = fabs(cl.cmd.forwardmove);
+                       wishvel_y = fabs(cl.cmd.sidemove);
+                       if(wishvel_x != 0 && wishvel_y != 0 && wishvel_x != wishvel_y)
+                       {
+                               vec_t wishspeed = sqrt(wishvel_x * wishvel_x + wishvel_y * wishvel_y);
+                               if(wishvel_x >= 2 * wishvel_y)
+                               {
+                                       // pure X motion
+                                       if(cl.cmd.forwardmove > 0)
+                                               cl.cmd.forwardmove = wishspeed;
+                                       else
+                                               cl.cmd.forwardmove = -wishspeed;
+                                       cl.cmd.sidemove = 0;
+                               }
+                               else if(wishvel_y >= 2 * wishvel_x)
+                               {
+                                       // pure Y motion
+                                       cl.cmd.forwardmove = 0;
+                                       if(cl.cmd.sidemove > 0)
+                                               cl.cmd.sidemove = wishspeed;
+                                       else
+                                               cl.cmd.sidemove = -wishspeed;
+                               }
+                               else
+                               {
+                                       // diagonal
+                                       if(cl.cmd.forwardmove > 0)
+                                               cl.cmd.forwardmove = 0.70710678118654752440 * wishspeed;
+                                       else
+                                               cl.cmd.forwardmove = -0.70710678118654752440 * wishspeed;
+                                       if(cl.cmd.sidemove > 0)
+                                               cl.cmd.sidemove = 0.70710678118654752440 * wishspeed;
+                                       else
+                                               cl.cmd.sidemove = -0.70710678118654752440 * wishspeed;
+                               }
+                       }
+               }
+               else if(cl_movecliptokeyboard.integer)
+               {
+                       // digital direction, digital amount
+                       if(cl.cmd.sidemove >= cl_sidespeed.value * f * 0.5)
+                               cl.cmd.sidemove = cl_sidespeed.value * f;
+                       else if(cl.cmd.sidemove <= -cl_sidespeed.value * f * 0.5)
+                               cl.cmd.sidemove = -cl_sidespeed.value * f;
+                       else
+                               cl.cmd.sidemove = 0;
+                       if(cl.cmd.forwardmove >= cl_forwardspeed.value * f * 0.5)
+                               cl.cmd.forwardmove = cl_forwardspeed.value * f;
+                       else if(cl.cmd.forwardmove <= -cl_backspeed.value * f * 0.5)
+                               cl.cmd.forwardmove = -cl_backspeed.value * f;
+                       else
+                               cl.cmd.forwardmove = 0;
+               }
+       }
 }
 
 #include "cl_collision.h"
@@ -633,7 +743,7 @@ void CL_UpdatePrydonCursor(void)
 {
        vec3_t temp;
 
-       if (!cl_prydoncursor.integer)
+       if (cl_prydoncursor.integer <= 0)
                VectorClear(cl.cmd.cursor_screen);
 
        /*
@@ -663,55 +773,20 @@ void CL_UpdatePrydonCursor(void)
        cl.cmd.cursor_screen[2] = 1;
 
        // calculate current view matrix
-       Matrix4x4_OriginFromMatrix(&r_view.matrix, cl.cmd.cursor_start);
+       Matrix4x4_OriginFromMatrix(&r_refdef.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, (v_flipped.integer ? -1 : 1) * 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);
+       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);
-}
-
-void CL_ClientMovement_ExpireOldMoves(void)
-{
-       int i;
-       int n;
-       // remove stale queue items
-       n = cl.movement_numqueue;
-       cl.movement_numqueue = 0;
-       if (cls.servermovesequence)
+       if (cl_prydoncursor_notrace.integer)
        {
-               for (i = 0;i < n;i++)
-               {
-                       if (cl.movement_queue[i].sequence > cls.servermovesequence)
-                               cl.movement_queue[cl.movement_numqueue++] = cl.movement_queue[i];
-                       else
-                               cl.movement_replay_canjump = cl.movement_queue[i].canjump;
-               }
+               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;
        }
-}
-
-void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch)
-{
-       // if time has not advanced, do nothing
-       if (cl.movecmd[0].msec <= 0)
-               return;
-       // add to input queue if there is room
-       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.movecmd[0].sequence;
-               cl.movement_queue[cl.movement_numqueue].time = cl.movecmd[0].time;
-               cl.movement_queue[cl.movement_numqueue].frametime = cl.movecmd[0].msec * 0.001;
-               VectorCopy(cl.movecmd[0].viewangles, cl.movement_queue[cl.movement_numqueue].viewangles);
-               cl.movement_queue[cl.movement_numqueue].move[0] = cl.movecmd[0].forwardmove;
-               cl.movement_queue[cl.movement_numqueue].move[1] = cl.movecmd[0].sidemove;
-               cl.movement_queue[cl.movement_numqueue].move[2] = cl.movecmd[0].upmove;
-               cl.movement_queue[cl.movement_numqueue].jump = buttonjump;
-               cl.movement_queue[cl.movement_numqueue].crouch = buttoncrouch;
-               cl.movement_numqueue++;
-       }
-
-       cl.movement_replay = true;
+       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
@@ -744,7 +819,7 @@ typedef struct cl_clientmovement_state_s
        float waterjumptime;
 
        // user command
-       client_movementqueue_t q;
+       usercmd_t cmd;
 }
 cl_clientmovement_state_t;
 
@@ -779,7 +854,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;
@@ -798,7 +873,7 @@ void CL_ClientMovement_UpdateStatus(cl_clientmovement_state_t *s)
        CL_ClientMovement_Unstick(s);
 
        // set crouched
-       if (s->q.crouch)
+       if (s->cmd.crouch)
        {
                // wants to crouch, this always works..
                if (!s->crouched)
@@ -810,7 +885,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;
                }
@@ -828,23 +903,23 @@ 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_Move(origin1, s->mins, s->maxs, origin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+       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_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;
                }
        }
@@ -868,23 +943,23 @@ void CL_ClientMovement_Move(cl_clientmovement_state_t *s)
        trace_t trace3;
        CL_ClientMovement_UpdateStatus(s);
        VectorCopy(s->velocity, primalvelocity);
-       for (bump = 0, t = s->q.frametime;bump < 8 && VectorLength2(s->velocity) > 0;bump++)
+       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)
@@ -925,30 +1000,30 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
 
        // water jump only in certain situations
        // this mimics quakeworld code
-       if (s->q.jump && s->waterlevel == 2 && s->velocity[2] >= -180)
+       if (s->cmd.jump && s->waterlevel == 2 && s->velocity[2] >= -180)
        {
                vec3_t forward;
                vec3_t yawangles;
                vec3_t spot;
-               VectorSet(yawangles, 0, s->q.viewangles[1], 0);
+               VectorSet(yawangles, 0, s->cmd.viewangles[1], 0);
                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;
                                s->waterjumptime = 2;
                                s->onground = false;
-                               s->q.canjump = false;
+                               s->cmd.canjump = false;
                        }
                }
        }
 
-       if (!VectorLength2(s->q.move))
+       if (!(s->cmd.forwardmove*s->cmd.forwardmove + s->cmd.sidemove*s->cmd.sidemove + s->cmd.upmove*s->cmd.upmove))
        {
                // drift towards bottom
                VectorSet(wishvel, 0, 0, -60);
@@ -960,9 +1035,9 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
                vec3_t right;
                vec3_t up;
                // calculate movement vector
-               AngleVectors(s->q.viewangles, forward, right, up);
+               AngleVectors(s->cmd.viewangles, forward, right, up);
                VectorSet(up, 0, 0, 1);
-               VectorMAMAM(s->q.move[0], forward, s->q.move[1], right, s->q.move[2], up, wishvel);
+               VectorMAMAM(s->cmd.forwardmove, forward, s->cmd.sidemove, right, s->cmd.upmove, up, wishvel);
        }
 
        // split wishvel into wishspeed and wishdir
@@ -979,7 +1054,7 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
        if (s->waterjumptime <= 0)
        {
                // water friction
-               f = 1 - s->q.frametime * cl.movevars_waterfriction * s->waterlevel;
+               f = 1 - s->cmd.frametime * cl.movevars_waterfriction * (cls.protocol == PROTOCOL_QUAKEWORLD ? s->waterlevel : 1);
                f = bound(0, f, 1);
                VectorScale(s->velocity, f, s->velocity);
 
@@ -987,12 +1062,12 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
                f = wishspeed - DotProduct(s->velocity, wishdir);
                if (f > 0)
                {
-                       f = min(cl.movevars_wateraccelerate * s->q.frametime * wishspeed, f);
+                       f = min(cl.movevars_wateraccelerate * s->cmd.frametime * wishspeed, f);
                        VectorMA(s->velocity, f, wishdir, s->velocity);
                }
 
                // holding jump button swims upward slowly
-               if (s->q.jump)
+               if (s->cmd.jump)
                {
                        if (s->watertype & SUPERCONTENTS_LAVA)
                                s->velocity[2] =  50;
@@ -1011,6 +1086,173 @@ 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);
+}
+
+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);
+
+       if(dot > 0) { // we can't change direction while slowing down
+               k *= cl.movevars_aircontrol*pow(dot, cl.movevars_aircontrol_power)*s->cmd.frametime;
+               VectorMAM(speed, s->velocity, k, wishdir, s->velocity);
+               VectorNormalize(s->velocity);
+       }
+
+       VectorScale(s->velocity, speed, s->velocity);
+       s->velocity[2] = zspeed;
+}
+
+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 sidefric)
+{
+       vec_t vel_straight;
+       vec_t vel_z;
+       vec3_t vel_perpend;
+       vec_t step;
+       vec3_t vel_xy;
+       vec_t vel_xy_current;
+       vec_t vel_xy_backward, vel_xy_forward;
+       qboolean speedclamp;
+
+       speedclamp = (accelqw < 0);
+       if(speedclamp)
+               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];
+       VectorCopy(s->velocity, vel_xy); vel_xy[2] -= vel_z;
+       VectorMA(vel_xy, -vel_straight, wishdir, vel_perpend);
+
+       step = accel * s->cmd.frametime * wishspeed0;
+
+       vel_xy_current  = VectorLength(vel_xy);
+       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)
+       {
+               vel_xy_current = min(VectorLength(s->velocity), vel_xy_forward);
+               if(vel_xy_current > 0) // prevent division by zero
+               {
+                       VectorNormalize(s->velocity);
+                       VectorScale(s->velocity, vel_xy_current, s->velocity);
+               }
+       }
+
+       s->velocity[2] += vel_z;
+}
+
+void CL_ClientMovement_Physics_PM_AirAccelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed)
+{
+    vec3_t curvel, wishvel, acceldir, curdir;
+    float addspeed, accelspeed, curspeed;
+    float dot;
+
+    float airforwardaccel = cl.movevars_warsowbunny_airforwardaccel;
+    float bunnyaccel = cl.movevars_warsowbunny_accel;
+    float bunnytopspeed = cl.movevars_warsowbunny_topspeed;
+    float turnaccel = cl.movevars_warsowbunny_turnaccel;
+    float backtosideratio = cl.movevars_warsowbunny_backtosideratio;
+
+    if( !wishspeed )
+        return;
+
+    VectorCopy( s->velocity, curvel );
+    curvel[2] = 0;
+    curspeed = VectorLength( curvel );
+
+    if( wishspeed > curspeed * 1.01f )
+    {
+        float accelspeed = curspeed + airforwardaccel * cl.movevars_maxairspeed * s->cmd.frametime;
+        if( accelspeed < wishspeed )
+            wishspeed = accelspeed;
+    }
+    else
+    {
+        float f = ( bunnytopspeed - curspeed ) / ( bunnytopspeed - cl.movevars_maxairspeed );
+        if( f < 0 )
+            f = 0;
+        wishspeed = max( curspeed, cl.movevars_maxairspeed ) + bunnyaccel * f * cl.movevars_maxairspeed * s->cmd.frametime;
+    }
+    VectorScale( wishdir, wishspeed, wishvel );
+    VectorSubtract( wishvel, curvel, acceldir );
+    addspeed = VectorNormalizeLength( acceldir );
+
+    accelspeed = turnaccel * cl.movevars_maxairspeed /* wishspeed */ * s->cmd.frametime;
+    if( accelspeed > addspeed )
+        accelspeed = addspeed;
+
+    if( backtosideratio < 1.0f )
+    {
+        VectorNormalize2( curvel, curdir );
+        dot = DotProduct( acceldir, curdir );
+        if( dot < 0 )
+            VectorMA( acceldir, -( 1.0f - backtosideratio ) * dot, curdir, acceldir );
+    }
+
+    VectorMA( s->velocity, accelspeed, acceldir, s->velocity );
+}
+
 void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
 {
        vec_t friction;
@@ -1018,6 +1260,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;
@@ -1028,22 +1271,22 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
 
        // jump if on ground with jump button pressed but only if it has been
        // released at least once since the last jump
-       if (s->q.jump)
+       if (s->cmd.jump)
        {
-               if (s->onground && s->q.canjump)
+               if (s->onground && (s->cmd.canjump || !cl_movement_track_canjump.integer)) // FIXME remove this cvar again when canjump logic actually works, or maybe keep it for mods that allow "pogo-ing"
                {
                        s->velocity[2] += cl.movevars_jumpvelocity;
                        s->onground = false;
-                       s->q.canjump = false;
+                       s->cmd.canjump = false;
                }
        }
        else
-               s->q.canjump = true;
+               s->cmd.canjump = true;
 
        // calculate movement vector
-       VectorSet(yawangles, 0, s->q.viewangles[1], 0);
+       VectorSet(yawangles, 0, s->cmd.viewangles[1], 0);
        AngleVectors(yawangles, forward, right, up);
-       VectorMAM(s->q.move[0], forward, s->q.move[1], right, wishvel);
+       VectorMAM(s->cmd.forwardmove, forward, s->cmd.sidemove, right, wishvel);
 
        // split wishvel into wishspeed and wishdir
        wishspeed = VectorLength(wishvel);
@@ -1051,96 +1294,134 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
                VectorScale(wishvel, 1 / wishspeed, wishdir);
        else
                VectorSet( wishdir, 0.0, 0.0, 0.0 );
-       wishspeed = min(wishspeed, cl.movevars_maxspeed);
-       if (s->crouched)
-               wishspeed *= 0.5;
-
        // check if onground
        if (s->onground)
        {
+               wishspeed = min(wishspeed, cl.movevars_maxspeed);
+               if (s->crouched)
+                       wishspeed *= 0.5;
+
                // apply edge friction
                f = sqrt(s->velocity[0] * s->velocity[0] + s->velocity[1] * s->velocity[1]);
-               friction = cl.movevars_friction;
-               if (f > 0 && cl.movevars_edgefriction != 1)
+               if (f > 0)
                {
-                       vec3_t neworigin2;
-                       vec3_t neworigin3;
-                       // note: QW uses the full player box for the trace, and yet still
-                       // uses s->origin[2] + s->mins[2], which is clearly an bug, but
-                       // this mimics it for compatibility
-                       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);
-                       else
-                               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 *= cl.movevars_edgefriction;
+                       friction = cl.movevars_friction;
+                       if (cl.movevars_edgefriction != 1)
+                       {
+                               vec3_t neworigin2;
+                               vec3_t neworigin3;
+                               // note: QW uses the full player box for the trace, and yet still
+                               // uses s->origin[2] + s->mins[2], which is clearly an bug, but
+                               // this mimics it for compatibility
+                               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, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                               else
+                                       trace = CL_TraceLine(neworigin2, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+                               if (trace.fraction == 1 && !trace.startsolid)
+                                       friction *= cl.movevars_edgefriction;
+                       }
+                       // apply ground friction
+                       f = 1 - s->cmd.frametime * friction * ((f < cl.movevars_stopspeed) ? (cl.movevars_stopspeed / f) : 1);
+                       f = max(f, 0);
+                       VectorScale(s->velocity, f, s->velocity);
                }
-               // apply ground friction
-               f = 1 - s->q.frametime * friction * ((f < cl.movevars_stopspeed) ? (cl.movevars_stopspeed / f) : 1);
-               f = max(f, 0);
-               VectorScale(s->velocity, f, s->velocity);
                addspeed = wishspeed - DotProduct(s->velocity, wishdir);
                if (addspeed > 0)
                {
-                       accelspeed = min(cl.movevars_accelerate * s->q.frametime * wishspeed, addspeed);
+                       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->q.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)
                {
-                       vec_t f;
-                       vec_t vel_straight;
-                       vec_t vel_z;
-                       vec3_t vel_perpend;
-
                        // apply air speed limit
-                       wishspeed = min(wishspeed, cl.movevars_maxairspeed);
+                       vec_t accel, wishspeed0, wishspeed2, accelqw;
+                       qboolean accelerating;
 
-                       /*
-                       addspeed = wishspeed - DotProduct(s->velocity, wishdir);
-                       if (addspeed > 0)
+                       accelqw = cl.movevars_airaccel_qw;
+                       wishspeed0 = wishspeed;
+                       wishspeed = min(wishspeed, cl.movevars_maxairspeed);
+                       if (s->crouched)
+                               wishspeed *= 0.5;
+                       accel = cl.movevars_airaccelerate;
+
+                       accelerating = (DotProduct(s->velocity, wishdir) > 0);
+                       wishspeed2 = wishspeed;
+
+                       // CPM: air control
+                       if(cl.movevars_airstopaccelerate != 0)
+                               if(DotProduct(s->velocity, wishdir) < 0)
+                                       accel = cl.movevars_airstopaccelerate;
+                       // this doesn't play well with analog input, but can't really be
+                       // fixed like the AirControl can. So, don't set the maxairstrafe*
+                       // cvars when you want to support analog input.
+                       if(s->cmd.forwardmove == 0 && s->cmd.sidemove != 0)
                        {
-                               accelspeed = min(cl.movevars_accelerate * s->q.frametime * wishspeed, addspeed);
-                               VectorMA(s->velocity, accelspeed, wishdir, s->velocity);
+                               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)
+                               }
                        }
-                       */
-
-                       vel_straight = DotProduct(s->velocity, wishdir);
-                       vel_z = s->velocity[2];
-                       VectorMA(s->velocity, -vel_straight, wishdir, vel_perpend);
-                       vel_perpend[2] -= vel_z;
+                       // !CPM
 
-                       f = wishspeed - vel_straight;
-                       if(f > 0)
-                               vel_straight += min(f, cl.movevars_airaccelerate * s->q.frametime * wishspeed) * cl.movevars_airaccel_qw;
-                       if(wishspeed > 0)
-                               vel_straight += min(wishspeed, cl.movevars_airaccelerate * s->q.frametime * wishspeed) * (1 - cl.movevars_airaccel_qw);
-
-                       VectorM(1 - (s->q.frametime * (wishspeed / cl.movevars_maxairspeed) * cl.movevars_airaccel_sideways_friction), vel_perpend, vel_perpend);
+                       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, wishspeed0, accel, accelqw, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed);
 
-                       VectorMA(vel_perpend, vel_straight, wishdir, s->velocity);
-                       s->velocity[2] += vel_z;
+                       if(cl.movevars_aircontrol)
+                               CL_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
                }
-               s->velocity[2] -= cl.movevars_gravity * cl.movevars_entgravity * s->q.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;
        }
 }
 
 void CL_ClientMovement_PlayerMove(cl_clientmovement_state_t *s)
 {
        //Con_Printf(" %f", frametime);
-       if (!s->q.jump)
-               s->q.canjump = true;
-       s->waterjumptime -= s->q.frametime;
+       if (!s->cmd.jump)
+               s->cmd.canjump = true;
+       s->waterjumptime -= s->cmd.frametime;
        CL_ClientMovement_UpdateStatus(s);
        if (s->waterlevel >= WATERLEVEL_SWIMMING)
                CL_ClientMovement_Physics_Swim(s);
@@ -1152,9 +1433,12 @@ extern cvar_t slowmo;
 void CL_UpdateMoveVars(void)
 {
        if (cls.protocol == PROTOCOL_QUAKEWORLD)
-               cl.movevars_ticrate = 1.0 / bound(1, cl_netinputpacketspersecond_qw.value, 100);
+       {
+               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];
@@ -1174,10 +1458,21 @@ void CL_UpdateMoveVars(void)
                cl.movevars_friction = cl.statsf[STAT_MOVEVARS_FRICTION];
                cl.movevars_wallfriction = cl.statsf[STAT_MOVEVARS_WALLFRICTION];
                cl.movevars_waterfriction = cl.statsf[STAT_MOVEVARS_WATERFRICTION];
+               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_aircontrol = cl.statsf[STAT_MOVEVARS_AIRCONTROL];
+               cl.movevars_aircontrol_power = cl.statsf[STAT_MOVEVARS_AIRCONTROL_POWER];
+               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];
        }
        else
        {
-               cl.movevars_ticrate = 1.0 / bound(1, cl_netinputpacketspersecond.value, 100);
+               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;
@@ -1196,17 +1491,36 @@ void CL_UpdateMoveVars(void)
                cl.movevars_stepheight = cl_movement_stepheight.value;
                cl.movevars_airaccel_qw = cl_movement_airaccel_qw.value;
                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_aircontrol = 0;
+               cl.movevars_aircontrol_power = 2;
+               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;
        }
+
+       if(!(cl.moveflags & MOVEFLAG_VALID))
+       {
+               if(gamemode == GAME_NEXUIZ)
+                       cl.moveflags = MOVEFLAG_Q2AIRACCELERATE;
+       }
+
+       if(cl.movevars_aircontrol_power <= 0)
+               cl.movevars_aircontrol = 2; // CPMA default
 }
 
 void CL_ClientMovement_Replay(void)
 {
        int i;
-       qboolean canjump;
-       double totalmovetime;
+       double totalmovemsec;
        cl_clientmovement_state_t s;
 
-       CL_ClientMovement_ExpireOldMoves();
+       if (cl.movement_predicted && !cl.movement_replay)
+               return;
 
        // set up starting state for the series of moves
        memset(&s, 0, sizeof(s));
@@ -1215,11 +1529,12 @@ void CL_ClientMovement_Replay(void)
        s.crouched = true; // will be updated on first move
        //Con_Printf("movement replay starting org %f %f %f vel %f %f %f\n", s.origin[0], s.origin[1], s.origin[2], s.velocity[0], s.velocity[1], s.velocity[2]);
 
-       totalmovetime = 0;
-       for (i = 0;i < cl.movement_numqueue - 1;i++)
-               totalmovetime += cl.movement_queue[i].frametime;
-       cl.movement_predicted = totalmovetime * 1000.0 >= cl_movement_minping.value && cls.servermovesequence && (cl_movement.integer && !cls.demoplayback && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission);
-       //Con_Printf("%i = %.0f >= %.0f && %i && (%i && %i && %i == %i && %i > 0 && %i\n", cl.movement_predicted, totalmovetime * 1000.0, cl_movement_minping.value, cls.servermovesequence, cl_movement.integer, !cls.demoplayback, cls.signon, SIGNONS, cl.stats[STAT_HEALTH], !cl.intermission);
+       totalmovemsec = 0;
+       for (i = 0;i < CL_MAX_USERCMDS;i++)
+               if (cl.movecmd[i].sequence > cls.servermovesequence)
+                       totalmovemsec += cl.movecmd[i].msec;
+       cl.movement_predicted = totalmovemsec >= cl_movement_minping.value && cls.servermovesequence && (cl_movement.integer && !cls.demoplayback && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission);
+       //Con_Printf("%i = %.0f >= %.0f && %i && (%i && %i && %i == %i && %i > 0 && %i\n", cl.movement_predicted, totalmovemsec, cl_movement_minping.value, cls.servermovesequence, cl_movement.integer, !cls.demoplayback, cls.signon, SIGNONS, cl.stats[STAT_HEALTH], !cl.intermission);
        if (cl.movement_predicted)
        {
                //Con_Printf("%ims\n", cl.movecmd[0].msec);
@@ -1227,42 +1542,43 @@ void CL_ClientMovement_Replay(void)
                // replay the input queue to predict current location
                // note: this relies on the fact there's always one queue item at the end
 
-               canjump = cl.movement_replay_canjump;
-               for (i = 0;i < cl.movement_numqueue;i++)
+               // find how many are still valid
+               for (i = 0;i < CL_MAX_USERCMDS;i++)
+                       if (cl.movecmd[i].sequence <= cls.servermovesequence)
+                               break;
+               // now walk them in oldest to newest order
+               for (i--;i >= 0;i--)
                {
-                       s.q = cl.movement_queue[i];
-                       s.q.canjump = canjump;
+                       s.cmd = cl.movecmd[i];
+                       if (i < CL_MAX_USERCMDS - 1)
+                               s.cmd.canjump = cl.movecmd[i+1].canjump;
                        // if a move is more than 50ms, do it as two moves (matching qwsv)
-                       if (s.q.frametime > 0.05)
+                       //Con_Printf("%i ", s.cmd.msec);
+                       if(s.cmd.frametime > 0.0005)
                        {
-                               s.q.frametime *= 0.5;
+                               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);
-                       canjump = cl.movement_queue[i].canjump = s.q.canjump;
                }
+               //Con_Printf("\n");
+               CL_ClientMovement_UpdateStatus(&s);
        }
        else
        {
                // get the first movement queue entry to know whether to crouch and such
-               s.q = cl.movement_queue[0];
+               s.cmd = cl.movecmd[0];
        }
-       CL_ClientMovement_UpdateStatus(&s);
 
-       if (cl.movement_replay)
+       if (cls.demoplayback) // for bob, speedometer
+               VectorCopy(cl.mvelocity[0], cl.movement_velocity);
+       else
        {
                cl.movement_replay = false;
-               // update interpolation timestamps if time has passed
-               if (cl.movement_time[0] != cl.movecmd[0].time)
-               {
-                       cl.movement_time[1] = cl.movement_time[0];
-                       cl.movement_time[0] = cl.movecmd[0].time;
-                       cl.movement_time[2] = cl.time;
-                       VectorCopy(cl.movement_origin, cl.movement_oldorigin);
-                       //VectorCopy(s.origin, cl.entities[cl.playerentity].state_current.origin);
-                       //VectorSet(cl.entities[cl.playerentity].state_current.angles, 0, cl.viewangles[1], 0);
-               }
-
                // update the interpolation target position and velocity
                VectorCopy(s.origin, cl.movement_origin);
                VectorCopy(s.velocity, cl.movement_velocity);
@@ -1329,11 +1645,11 @@ void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, usercmd_t *from, usercmd_t *to)
        if (bits & QW_CM_ANGLE3)
                MSG_WriteAngle16i(buf, to->viewangles[2]);
        if (bits & QW_CM_FORWARD)
-               MSG_WriteShort(buf, to->forwardmove);
+               MSG_WriteShort(buf, (short) to->forwardmove);
        if (bits & QW_CM_SIDE)
-               MSG_WriteShort(buf, to->sidemove);
+               MSG_WriteShort(buf, (short) to->sidemove);
        if (bits & QW_CM_UP)
-               MSG_WriteShort(buf, to->upmove);
+               MSG_WriteShort(buf, (short) to->upmove);
        if (bits & QW_CM_BUTTONS)
                MSG_WriteByte(buf, to->buttons);
        if (bits & QW_CM_IMPULSE)
@@ -1341,6 +1657,15 @@ 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;
+}
+
 /*
 ==============
 CL_SendMove
@@ -1350,240 +1675,258 @@ usercmd_t nullcmd; // for delta compression of qw moves
 void CL_SendMove(void)
 {
        int i, j, packetloss;
+       int checksumindex;
        int bits;
+       int maxusercmds;
+       usercmd_t *cmd;
        sizebuf_t buf;
        unsigned char data[1024];
-       static double lastsendtime = 0;
        double packettime;
        int msecdelta;
-
-       CL_ClientMovement_ExpireOldMoves();
+       qboolean quemove;
+       qboolean important;
 
        // if playing a demo, do nothing
        if (!cls.netcon)
                return;
 
-       // don't send too often or else network connections can get clogged by a high renderer framerate
-       packettime = cl.movevars_ticrate;
-       if (cls.protocol != PROTOCOL_QUAKEWORLD)
-               packettime /= (double)bound(1, cl_netinputpacketsperserverpacket.value, 10);
-       // send input every frame in singleplayer
-       if (cl.islocalgame)
-               packettime = 0;
-       // quakeworld servers take only frametimes
-       // predicted dp7 servers take current interpolation time
-       // unpredicted servers take an echo of the latest server timestamp
-       cl.cmd.time = cl.time;
-       cl.cmd.sequence = cls.movesequence;
-       if (cls.protocol == PROTOCOL_QUAKEWORLD)
-       {
-               if (realtime < lastsendtime + packettime)
-                       return;
-               cl.cmd.sequence = cls.netcon->qw.outgoing_sequence;
-       }
-       else
-       {
-               // movement should be sent immediately whenever a server
-               // packet is received, to minimize ping times
-               if (!cl.movement_needupdate && 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 = bound(realtime, lastsendtime + packettime, realtime + packettime);
-       // set the flag indicating that we sent a packet recently
-       cl.movement_needupdate = false;
+       // we don't que moves during a lag spike (potential network timeout)
+       quemove = realtime - cl.last_received_message < cl_movement_nettimeout.value;
 
-       buf.maxsize = sizeof(data);
-       buf.cursize = 0;
-       buf.data = data;
-
-       // conditions for sending a move:
-       // if the move advances time or if the game is paused (in which case time
-       // is not advancing)
-       // don't send a new input packet if the connection is still saturated from
-       // the last one (or chat messages, etc)
-       // note: this behavior comes from QW
-       if ((cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS) && !NetConn_CanSend(cls.netcon) && !cl.islocalgame)
-               return;
-
-       // increase the move counter since we intend to send a move
-       cls.movesequence++;
-
-       // 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 prydon cursor info
-       CL_UpdatePrydonCursor();
+       // 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;
 
        // 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 (in_attack.state   & 3) bits |=   1;
+       if (in_jump.state     & 3) bits |=   2;
+       if (in_button3.state  & 3) bits |=   4;
+       if (in_button4.state  & 3) bits |=   8;
+       if (in_button5.state  & 3) bits |=  16;
+       if (in_button6.state  & 3) bits |=  32;
+       if (in_button7.state  & 3) bits |=  64;
+       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 (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;
+       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;
+       if (in_button12.state  & 3) bits |=  16384;
+       if (in_button13.state  & 3) bits |=  32768;
+       if (in_button14.state  & 3) bits |=  65536;
+       if (in_button15.state  & 3) bits |= 131072;
+       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;
-       cl.cmd.buttons = bits;
 
-       // set impulse
+       // set buttons and impulse
+       cl.cmd.buttons = bits;
        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);
 
-       msecdelta = (int)(floor(cl.cmd.time * 1000) - floor(cl.movecmd[0].time * 1000));
+       msecdelta = (int)(floor(cl.cmd.time * 1000) - floor(cl.movecmd[1].time * 1000));
        cl.cmd.msec = (unsigned char)bound(0, msecdelta, 255);
        // ridiculous value rejection (matches qw)
        if (cl.cmd.msec > 250)
                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;
 
-       // always dump the first two messages, because they may contain leftover inputs from the last level
+       // 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
        if (cl.cmd.sequence <= 2)
                cl.cmd.forwardmove = cl.cmd.sidemove = cl.cmd.upmove = cl.cmd.impulse = cl.cmd.buttons = 0;
 
-       // 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;
+       cl.cmd.jump = (cl.cmd.buttons & 2) != 0;
+       cl.cmd.crouch = 0;
+       switch (cls.protocol)
+       {
+       case PROTOCOL_QUAKEWORLD:
+       case PROTOCOL_QUAKE:
+       case PROTOCOL_QUAKEDP:
+       case PROTOCOL_NEHAHRAMOVIE:
+       case PROTOCOL_NEHAHRABJP:
+       case PROTOCOL_NEHAHRABJP2:
+       case PROTOCOL_NEHAHRABJP3:
+       case PROTOCOL_DARKPLACES1:
+       case PROTOCOL_DARKPLACES2:
+       case PROTOCOL_DARKPLACES3:
+       case PROTOCOL_DARKPLACES4:
+       case PROTOCOL_DARKPLACES5:
+               break;
+       case PROTOCOL_DARKPLACES6:
+       case PROTOCOL_DARKPLACES7:
+               // FIXME: cl.cmd.buttons & 16 is +button5, Nexuiz specific
+               cl.cmd.crouch = (cl.cmd.buttons & 16) != 0;
+               break;
+       case PROTOCOL_UNKNOWN:
+               break;
+       }
 
-       if (cls.protocol == PROTOCOL_QUAKEWORLD)
+       if (quemove)
+               cl.movecmd[0] = cl.cmd;
+
+       // don't predict more than 200fps
+       if (realtime >= cl.lastpackettime + 0.005)
+               cl.movement_replay = true; // redo the prediction
+
+       // now decide whether to actually send this move
+       // (otherwise it is only for prediction)
+
+       // 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);
+       if (cl.movevars_timescale && cl.movevars_ticrate)
        {
-               int checksumindex;
-
-               CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
-
-               MSG_WriteByte(&buf, qw_clc_move);
-               // save the position for a checksum byte
-               checksumindex = buf.cursize;
-               MSG_WriteByte(&buf, 0);
-               // packet loss percentage
-               for (j = 0, packetloss = 0;j < NETGRAPH_PACKETS;j++)
-                       if (cls.netcon->incoming_unreliablesize[j] == 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]);
-               // calculate the checksum
-               buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->qw.outgoing_sequence);
-               // if delta compression history overflows, request no delta
-               if (cls.netcon->qw.outgoing_sequence - cl.qw_validsequence >= QW_UPDATE_BACKUP-1)
-                       cl.qw_validsequence = 0;
-               // request delta compression if appropriate
-               if (cl.qw_validsequence && !cl_nodelta.integer && cls.state == ca_connected && !cls.demorecording)
-               {
-                       cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = cl.qw_validsequence;
-                       MSG_WriteByte(&buf, qw_clc_delta);
-                       MSG_WriteByte(&buf, cl.qw_validsequence & 255);
-               }
-               else
-                       cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = -1;
+               float maxtic = cl.movevars_ticrate / cl.movevars_timescale;
+               packettime = min(packettime, maxtic);
        }
-       else if (cls.signon == SIGNONS)
+
+       // 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)
+               cl.lastpackettime = floor(realtime / packettime) * packettime;
+       else
+               cl.lastpackettime = realtime;
+
+       buf.maxsize = sizeof(data);
+       buf.cursize = 0;
+       buf.data = data;
+
+       // 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 prydon cursor info
+       CL_UpdatePrydonCursor();
+
+       if (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS)
        {
-               if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3)
+               switch (cls.protocol)
                {
-                       CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
-
+               case PROTOCOL_QUAKEWORLD:
+                       MSG_WriteByte(&buf, qw_clc_move);
+                       // save the position for a checksum byte
+                       checksumindex = buf.cursize;
+                       MSG_WriteByte(&buf, 0);
+                       // packet loss percentage
+                       for (j = 0, packetloss = 0;j < NETGRAPH_PACKETS;j++)
+                               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.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
+                       if (cls.netcon->outgoing_unreliable_sequence - cl.qw_validsequence >= QW_UPDATE_BACKUP-1)
+                               cl.qw_validsequence = 0;
+                       // request delta compression if appropriate
+                       if (cl.qw_validsequence && !cl_nodelta.integer && cls.state == ca_connected && !cls.demorecording)
+                       {
+                               cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = cl.qw_validsequence;
+                               MSG_WriteByte(&buf, qw_clc_delta);
+                               MSG_WriteByte(&buf, cl.qw_validsequence & 255);
+                       }
+                       else
+                               cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = -1;
+                       break;
+               case PROTOCOL_QUAKE:
+               case PROTOCOL_QUAKEDP:
+               case PROTOCOL_NEHAHRAMOVIE:
+               case PROTOCOL_NEHAHRABJP:
+               case PROTOCOL_NEHAHRABJP2:
+               case PROTOCOL_NEHAHRABJP3:
                        // 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
                        // 3 bytes
                        for (i = 0;i < 3;i++)
-                               MSG_WriteAngle8i (&buf, cl.movecmd[0].viewangles[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);
-               }
-               else if (cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3)
-               {
-                       CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
-
+                       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);
-               }
-               else if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5)
-               {
-                       CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
-
+                       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);
-               }
-               else if (cls.signon == SIGNONS)
-               {
-                       int maxusercmds;
-                       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);
-
+                       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
-                       maxusercmds = bound(1, cl_netinputpacketlosstolerance.integer + 1, CL_MAX_USERCMDS);
+                       maxusercmds = bound(1, cl_netrepeatinput.integer + 1, min(3, 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.cmd.predicted)
                                maxusercmds = 1;
@@ -1625,22 +1968,32 @@ void CL_SendMove(void)
                                MSG_WriteFloat (&buf, cmd->cursor_impact[2]);
                                MSG_WriteShort (&buf, cmd->cursor_entitynumber);
                        }
+                       break;
+               case PROTOCOL_UNKNOWN:
+                       break;
                }
        }
 
        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]);
                        }
                }
        }
@@ -1662,6 +2015,37 @@ 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);
 
+       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;
+       in_jump.state    &= ~2;
+       in_button3.state &= ~2;
+       in_button4.state &= ~2;
+       in_button5.state &= ~2;
+       in_button6.state &= ~2;
+       in_button7.state &= ~2;
+       in_button8.state &= ~2;
+       in_use.state     &= ~2;
+       in_button9.state  &= ~2;
+       in_button10.state &= ~2;
+       in_button11.state &= ~2;
+       in_button12.state &= ~2;
+       in_button13.state &= ~2;
+       in_button14.state &= ~2;
+       in_button15.state &= ~2;
+       in_button16.state &= ~2;
+       // clear impulse
+       in_impulse = 0;
+
        if (cls.netcon->message.overflowed)
        {
                Con_Print("CL_SendMove: lost server connection\n");
@@ -1747,11 +2131,16 @@ void CL_InitInput (void)
 
        // LordHavoc: added bestweapon command
        Cmd_AddCommand ("bestweapon", IN_BestWeapon, "send an impulse number to server to select the first usable weapon out of several (example: 8 7 6 5 4 3 2 1)");
+#if 0
        Cmd_AddCommand ("cycleweapon", IN_CycleWeapon, "send an impulse number to server to select the next usable weapon out of several (example: 9 4 8) if you are holding one of these, and choose the first one if you are holding none of these");
+#endif
        Cmd_AddCommand ("register_bestweapon", IN_BestWeapon_Register_f, "(for QC usage only) change weapon parameters to be used by bestweapon; stuffcmd this in ClientConnect");
 
+       Cvar_RegisterVariable(&cl_movecliptokeyboard);
        Cvar_RegisterVariable(&cl_movement);
+       Cvar_RegisterVariable(&cl_movement_nettimeout);
        Cvar_RegisterVariable(&cl_movement_minping);
+       Cvar_RegisterVariable(&cl_movement_track_canjump);
        Cvar_RegisterVariable(&cl_movement_maxspeed);
        Cvar_RegisterVariable(&cl_movement_maxairspeed);
        Cvar_RegisterVariable(&cl_movement_stopspeed);
@@ -1770,11 +2159,14 @@ void CL_InitInput (void)
        Cvar_RegisterVariable(&in_pitch_min);
        Cvar_RegisterVariable(&in_pitch_max);
        Cvar_RegisterVariable(&m_filter);
-
-       Cvar_RegisterVariable(&cl_netinputpacketsperserverpacket);
-       Cvar_RegisterVariable(&cl_netinputpacketspersecond);
-       Cvar_RegisterVariable(&cl_netinputpacketspersecond_qw);
-       Cvar_RegisterVariable(&cl_netinputpacketlosstolerance);
+       Cvar_RegisterVariable(&m_accelerate);
+       Cvar_RegisterVariable(&m_accelerate_minspeed);
+       Cvar_RegisterVariable(&m_accelerate_maxspeed);
+       Cvar_RegisterVariable(&m_accelerate_filter);
+
+       Cvar_RegisterVariable(&cl_netfps);
+       Cvar_RegisterVariable(&cl_netrepeatinput);
+       Cvar_RegisterVariable(&cl_netimmediatebuttons);
 
        Cvar_RegisterVariable(&cl_nodelta);
 }