]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_input.c
reenabled R_UpdateAllTextureInfo calls when lighting the world model
[xonotic/darkplaces.git] / cl_input.c
index ad392f688dd974cd6fcf579e69b552036cc6c19f..f6e6165a8d2e7d8bb54e78c926c92840a32f185b 100644 (file)
@@ -197,31 +197,76 @@ void IN_JumpUp (void) {KeyUp(&in_jump);}
 
 void IN_Impulse (void) {in_impulse=atoi(Cmd_Argv(1));}
 
-struct
+in_bestweapon_info_t in_bestweapon_info[IN_BESTWEAPON_MAX];
+
+void IN_BestWeapon_Register(const char *name, int impulse, int weaponbit, int activeweaponcode, int ammostat, int ammomin)
 {
-       const char *name;
-       int impulse;
-       int weaponbit;
-       int ammostat;
-       int ammomin;
+       int i;
+       for(i = 0; i < IN_BESTWEAPON_MAX && in_bestweapon_info[i].impulse; ++i)
+               if(in_bestweapon_info[i].impulse == impulse)
+                       break;
+       if(i >= IN_BESTWEAPON_MAX)
+       {
+               Con_Printf("no slot left for weapon definition; increase IN_BESTWEAPON_MAX\n");
+               return; // sorry
+       }
+       strlcpy(in_bestweapon_info[i].name, name, sizeof(in_bestweapon_info[i].name));
+       in_bestweapon_info[i].impulse = impulse;
+       if(weaponbit != -1)
+               in_bestweapon_info[i].weaponbit = weaponbit;
+       if(activeweaponcode != -1)
+               in_bestweapon_info[i].activeweaponcode = activeweaponcode;
+       if(ammostat != -1)
+               in_bestweapon_info[i].ammostat = ammostat;
+       if(ammomin != -1)
+               in_bestweapon_info[i].ammomin = ammomin;
 }
-in_bestweapon_info[] =
+
+void IN_BestWeapon_ResetData (void)
 {
-       {"1", 1, IT_AXE, STAT_SHELLS, 0},
-       {"2", 2, IT_SHOTGUN, STAT_SHELLS, 1},
-       {"3", 3, IT_SUPER_SHOTGUN, STAT_SHELLS, 1},
-       {"4", 4, IT_NAILGUN, STAT_NAILS, 1},
-       {"5", 5, IT_SUPER_NAILGUN, STAT_NAILS, 1},
-       {"6", 6, IT_GRENADE_LAUNCHER, STAT_ROCKETS, 1},
-       {"7", 7, IT_ROCKET_LAUNCHER, STAT_ROCKETS, 1},
-       {"8", 8, IT_LIGHTNING, STAT_CELLS, 1},
-       {"9", 9, 128, STAT_CELLS, 1}, // generic energy weapon for mods
-       {"p", 209, 128, STAT_CELLS, 1}, // dpmod plasma gun
-       {"w", 210, 8388608, STAT_CELLS, 1}, // dpmod plasma wave cannon
-       {"l", 225, HIT_LASER_CANNON, STAT_CELLS, 1}, // hipnotic laser cannon
-       {"h", 226, HIT_MJOLNIR, STAT_CELLS, 0}, // hipnotic mjolnir hammer
-       {NULL, 0, 0, 0, 0}
-};
+       memset(in_bestweapon_info, 0, sizeof(in_bestweapon_info));
+       IN_BestWeapon_Register("1", 1, IT_AXE, IT_AXE, STAT_SHELLS, 0);
+       IN_BestWeapon_Register("2", 2, IT_SHOTGUN, IT_SHOTGUN, STAT_SHELLS, 1);
+       IN_BestWeapon_Register("3", 3, IT_SUPER_SHOTGUN, IT_SUPER_SHOTGUN, STAT_SHELLS, 1);
+       IN_BestWeapon_Register("4", 4, IT_NAILGUN, IT_NAILGUN, STAT_NAILS, 1);
+       IN_BestWeapon_Register("5", 5, IT_SUPER_NAILGUN, IT_SUPER_NAILGUN, STAT_NAILS, 1);
+       IN_BestWeapon_Register("6", 6, IT_GRENADE_LAUNCHER, IT_GRENADE_LAUNCHER, STAT_ROCKETS, 1);
+       IN_BestWeapon_Register("7", 7, IT_ROCKET_LAUNCHER, IT_ROCKET_LAUNCHER, STAT_ROCKETS, 1);
+       IN_BestWeapon_Register("8", 8, IT_LIGHTNING, IT_LIGHTNING, STAT_CELLS, 1);
+       IN_BestWeapon_Register("9", 9, 128, 128, STAT_CELLS, 1); // generic energy weapon for mods
+       IN_BestWeapon_Register("p", 209, 128, 128, STAT_CELLS, 1); // dpmod plasma gun
+       IN_BestWeapon_Register("w", 210, 8388608, 8388608, STAT_CELLS, 1); // dpmod plasma wave cannon
+       IN_BestWeapon_Register("l", 225, HIT_LASER_CANNON, HIT_LASER_CANNON, STAT_CELLS, 1); // hipnotic laser cannon
+       IN_BestWeapon_Register("h", 226, HIT_MJOLNIR, HIT_MJOLNIR, STAT_CELLS, 0); // hipnotic mjolnir hammer
+}
+
+void IN_BestWeapon_Register_f (void)
+{
+       if(Cmd_Argc() == 7)
+       {
+               IN_BestWeapon_Register(
+                       Cmd_Argv(1),
+                       atoi(Cmd_Argv(2)),
+                       atoi(Cmd_Argv(3)),
+                       atoi(Cmd_Argv(4)),
+                       atoi(Cmd_Argv(5)),
+                       atoi(Cmd_Argv(6))
+               );
+       }
+       else if(Cmd_Argc() == 2 && !strcmp(Cmd_Argv(1), "clear"))
+       {
+               memset(in_bestweapon_info, 0, sizeof(in_bestweapon_info));
+       }
+       else if(Cmd_Argc() == 2 && !strcmp(Cmd_Argv(1), "quake"))
+       {
+               IN_BestWeapon_ResetData();
+       }
+       else
+       {
+               Con_Printf("Usage: %s weaponshortname impulse itemcode activeweaponcode ammostat ammomin; %s clear; %s quake\n", Cmd_Argv(0), Cmd_Argv(0), Cmd_Argv(0));
+       }
+}
+
 void IN_BestWeapon (void)
 {
        int i, n;
@@ -235,7 +280,7 @@ void IN_BestWeapon (void)
        {
                t = Cmd_Argv(i);
                // figure out which weapon this character refers to
-               for (n = 0;in_bestweapon_info[n].name;n++)
+               for (n = 0;n < IN_BESTWEAPON_MAX && in_bestweapon_info[n].impulse;n++)
                {
                        if (!strcmp(in_bestweapon_info[n].name, t))
                        {
@@ -256,6 +301,53 @@ void IN_BestWeapon (void)
        // if we couldn't find any of the weapons, there's nothing more we can do...
 }
 
+void IN_CycleWeapon (void)
+{
+       int i, n;
+       int first = -1;
+       qboolean found = false;
+       const char *t;
+       if (Cmd_Argc() < 2)
+       {
+               Con_Printf("bestweapon requires 1 or more parameters\n");
+               return;
+       }
+       for (i = 1;i < Cmd_Argc();i++)
+       {
+               t = Cmd_Argv(i);
+               // figure out which weapon this character refers to
+               for (n = 0;n < IN_BESTWEAPON_MAX && in_bestweapon_info[n].impulse;n++)
+               {
+                       if (!strcmp(in_bestweapon_info[n].name, t))
+                       {
+                               // we found out what weapon this character refers to
+                               // check if the inventory contains the weapon and enough ammo
+                               if ((cl.stats[STAT_ITEMS] & in_bestweapon_info[n].weaponbit) && (cl.stats[in_bestweapon_info[n].ammostat] >= in_bestweapon_info[n].ammomin))
+                               {
+                                       // we found one of the weapons the player wanted
+                                       if(first == -1)
+                                               first = n;
+                                       if(found)
+                                       {
+                                               in_impulse = in_bestweapon_info[n].impulse;
+                                               return;
+                                       }
+                                       if(cl.stats[STAT_ACTIVEWEAPON] == in_bestweapon_info[n].activeweaponcode)
+                                               found = true;
+                               }
+                               break;
+                       }
+               }
+               // if we couldn't identify the weapon we just ignore it and continue checking for other weapons
+       }
+       if(first != -1)
+       {
+               in_impulse = in_bestweapon_info[first].impulse;
+               return;
+       }
+       // if we couldn't find any of the weapons, there's nothing more we can do...
+}
+
 /*
 ===============
 CL_KeyState
@@ -329,32 +421,35 @@ cvar_t cl_anglespeedkey = {CVAR_SAVE, "cl_anglespeedkey","1.5","how much +speed
 
 cvar_t cl_movement = {CVAR_SAVE, "cl_movement", "0", "enables clientside prediction of your player movement"};
 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_waterfriction = {0, "cl_movement_waterfriction", "-1", "how fast you slow down (should match sv_friction), if less than 0 the cl_movement_friction variable is used instead"};
+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_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 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_gravity = {0, "cl_gravity", "800", "how much gravity to apply in client physics (should match sv_gravity)"};
-cvar_t cl_slowmo = {0, "cl_slowmo", "1", "speed of game time (should match slowmo)"};
 
 cvar_t in_pitch_min = {0, "in_pitch_min", "-90", "how far downward you can aim (quake used -70"};
 cvar_t in_pitch_max = {0, "in_pitch_max", "90", "how far upward you can aim (quake used 80"};
 
 cvar_t m_filter = {CVAR_SAVE, "m_filter","0", "smoothes mouse movement, less responsive but smoother aiming"};
 
-cvar_t cl_netinputpacketspersecond = {CVAR_SAVE, "cl_netinputpacketspersecond","50", "how many input packets to send to server each second"};
-cvar_t cl_netinputpacketlosstolerance = {CVAR_SAVE, "cl_netinputpacketlosstolerance", "1", "how many packets in a row can be lost without movement issues when using cl_movement (technically how many input messages to repeat in each packet that have not yet been acknowledged by the server), only affects DP7 servers (Quake uses 0, QuakeWorld uses 2, and just for comparison Quake3 uses 1)"};
+cvar_t cl_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_nodelta = {0, "cl_nodelta", "0", "disables delta compression of non-player entities in QW network protocol"};
 
+extern cvar_t v_flipped;
 
 /*
 ================
@@ -425,6 +520,9 @@ void CL_Input (void)
        // clamp before the move to prevent starting with bad angles
        CL_AdjustAngles ();
 
+       if(v_flipped.integer)
+               cl.viewangles[YAW] = -cl.viewangles[YAW];
+
        // reset some of the command fields
        cl.cmd.forwardmove = 0;
        cl.cmd.sidemove = 0;
@@ -485,41 +583,48 @@ void CL_Input (void)
        // if not in menu, apply mouse move to viewangles/movement
        if (!cl.csqc_wantsmousemove && in_client_mouse)
        {
+               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 * 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;
                }
        }
 
+       if(v_flipped.integer)
+       {
+               cl.viewangles[YAW] = -cl.viewangles[YAW];
+               cl.cmd.sidemove = -cl.cmd.sidemove;
+       }
+
        // clamp after the move to prevent rendering with bad angles
        CL_AdjustAngles ();
 }
@@ -562,7 +667,7 @@ void CL_UpdatePrydonCursor(void)
        // calculate current view matrix
        Matrix4x4_OriginFromMatrix(&r_view.matrix, cl.cmd.cursor_start);
        // calculate direction vector of cursor in viewspace by using frustum slopes
-       VectorSet(temp, cl.cmd.cursor_screen[2] * 1000000, cl.cmd.cursor_screen[0] * -r_view.frustum_x * 1000000, cl.cmd.cursor_screen[1] * -r_view.frustum_y * 1000000);
+       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);
        // 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);
@@ -640,24 +745,6 @@ typedef struct cl_clientmovement_state_s
        // (this is in seconds and counts down to 0)
        float waterjumptime;
 
-       // movement parameters for physics code
-       float movevars_gravity;
-       float movevars_stopspeed;
-       float movevars_maxspeed;
-       float movevars_spectatormaxspeed;
-       float movevars_accelerate;
-       float movevars_airaccelerate;
-       float movevars_wateraccelerate;
-       float movevars_friction;
-       float movevars_waterfriction;
-       float movevars_entgravity;
-       float movevars_jumpvelocity;
-       float movevars_edgefriction;
-       float movevars_maxairspeed;
-       float movevars_stepheight;
-       float movevars_airaccel_qw;
-       float movevars_airaccel_sideways_friction;
-
        // user command
        client_movementqueue_t q;
 }
@@ -791,8 +878,8 @@ void CL_ClientMovement_Move(cl_clientmovement_state_t *s)
                {
                        // may be a step or wall, try stepping up
                        // first move forward at a higher level
-                       VectorSet(currentorigin2, s->origin[0], s->origin[1], s->origin[2] + s->movevars_stepheight);
-                       VectorSet(neworigin2, neworigin[0], neworigin[1], s->origin[2] + s->movevars_stepheight);
+                       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);
                        if (!trace2.startsolid)
                        {
@@ -886,7 +973,7 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
                VectorScale(wishvel, 1 / wishspeed, wishdir);
        else
                VectorSet( wishdir, 0.0, 0.0, 0.0 );
-       wishspeed = min(wishspeed, s->movevars_maxspeed) * 0.7;
+       wishspeed = min(wishspeed, cl.movevars_maxspeed) * 0.7;
 
        if (s->crouched)
                wishspeed *= 0.5;
@@ -894,7 +981,7 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
        if (s->waterjumptime <= 0)
        {
                // water friction
-               f = 1 - s->q.frametime * s->movevars_waterfriction * s->waterlevel;
+               f = 1 - s->q.frametime * cl.movevars_waterfriction * s->waterlevel;
                f = bound(0, f, 1);
                VectorScale(s->velocity, f, s->velocity);
 
@@ -902,7 +989,7 @@ void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
                f = wishspeed - DotProduct(s->velocity, wishdir);
                if (f > 0)
                {
-                       f = min(s->movevars_wateraccelerate * s->q.frametime * wishspeed, f);
+                       f = min(cl.movevars_wateraccelerate * s->q.frametime * wishspeed, f);
                        VectorMA(s->velocity, f, wishdir, s->velocity);
                }
 
@@ -945,9 +1032,9 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
        // released at least once since the last jump
        if (s->q.jump)
        {
-               if (s->onground && s->q.canjump)
+               if (s->onground && (s->q.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] += s->movevars_jumpvelocity;
+                       s->velocity[2] += cl.movevars_jumpvelocity;
                        s->onground = false;
                        s->q.canjump = false;
                }
@@ -966,7 +1053,7 @@ 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, s->movevars_maxspeed);
+       wishspeed = min(wishspeed, cl.movevars_maxspeed);
        if (s->crouched)
                wishspeed *= 0.5;
 
@@ -975,8 +1062,8 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
        {
                // apply edge friction
                f = sqrt(s->velocity[0] * s->velocity[0] + s->velocity[1] * s->velocity[1]);
-               friction = s->movevars_friction;
-               if (f > 0 && s->movevars_edgefriction != 1)
+               friction = cl.movevars_friction;
+               if (f > 0 && cl.movevars_edgefriction != 1)
                {
                        vec3_t neworigin2;
                        vec3_t neworigin3;
@@ -990,19 +1077,19 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
                        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 *= s->movevars_edgefriction;
+                               friction *= cl.movevars_edgefriction;
                }
                // apply ground friction
-               f = 1 - s->q.frametime * friction * ((f < s->movevars_stopspeed) ? (s->movevars_stopspeed / f) : 1);
+               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(s->movevars_accelerate * s->q.frametime * wishspeed, addspeed);
+                       accelspeed = min(cl.movevars_accelerate * s->q.frametime * wishspeed, addspeed);
                        VectorMA(s->velocity, accelspeed, wishdir, s->velocity);
                }
-               s->velocity[2] -= cl_gravity.value * s->q.frametime;
+               s->velocity[2] -= cl.movevars_gravity * cl.movevars_entgravity * s->q.frametime;
                if (cls.protocol == PROTOCOL_QUAKEWORLD)
                        s->velocity[2] = 0;
                if (VectorLength2(s->velocity))
@@ -1018,13 +1105,13 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
                        vec3_t vel_perpend;
 
                        // apply air speed limit
-                       wishspeed = min(wishspeed, s->movevars_maxairspeed);
+                       wishspeed = min(wishspeed, cl.movevars_maxairspeed);
 
                        /*
                        addspeed = wishspeed - DotProduct(s->velocity, wishdir);
                        if (addspeed > 0)
                        {
-                               accelspeed = min(s->movevars_accelerate * s->q.frametime * wishspeed, addspeed);
+                               accelspeed = min(cl.movevars_accelerate * s->q.frametime * wishspeed, addspeed);
                                VectorMA(s->velocity, accelspeed, wishdir, s->velocity);
                        }
                        */
@@ -1036,16 +1123,16 @@ void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
 
                        f = wishspeed - vel_straight;
                        if(f > 0)
-                               vel_straight += min(f, s->movevars_accelerate * s->q.frametime * wishspeed) * s->movevars_airaccel_qw;
+                               vel_straight += min(f, cl.movevars_airaccelerate * s->q.frametime * wishspeed) * cl.movevars_airaccel_qw;
                        if(wishspeed > 0)
-                               vel_straight += min(wishspeed, s->movevars_accelerate * s->q.frametime * wishspeed) * (1 - s->movevars_airaccel_qw);
+                               vel_straight += min(wishspeed, cl.movevars_airaccelerate * s->q.frametime * wishspeed) * (1 - cl.movevars_airaccel_qw);
 
-                       VectorM(1 - (s->q.frametime * (wishspeed / s->movevars_maxairspeed) * s->movevars_airaccel_sideways_friction), vel_perpend, vel_perpend);
+                       VectorM(1 - (s->q.frametime * (wishspeed / cl.movevars_maxairspeed) * cl.movevars_airaccel_sideways_friction), vel_perpend, vel_perpend);
 
                        VectorMA(vel_perpend, vel_straight, wishdir, s->velocity);
                        s->velocity[2] += vel_z;
                }
-               s->velocity[2] -= cl_gravity.value * s->q.frametime;
+               s->velocity[2] -= cl.movevars_gravity * cl.movevars_entgravity * s->q.frametime;
                CL_ClientMovement_Move(s);
        }
 }
@@ -1063,6 +1150,57 @@ void CL_ClientMovement_PlayerMove(cl_clientmovement_state_t *s)
                CL_ClientMovement_Physics_Walk(s);
 }
 
+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);
+       else if (cl.stats[STAT_MOVEVARS_TICRATE])
+       {
+               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] ;
+               cl.movevars_maxspeed = cl.statsf[STAT_MOVEVARS_MAXSPEED];
+               cl.movevars_spectatormaxspeed = cl.statsf[STAT_MOVEVARS_SPECTATORMAXSPEED];
+               cl.movevars_accelerate = cl.statsf[STAT_MOVEVARS_ACCELERATE];
+               cl.movevars_airaccelerate = cl.statsf[STAT_MOVEVARS_AIRACCELERATE];
+               cl.movevars_wateraccelerate = cl.statsf[STAT_MOVEVARS_WATERACCELERATE];
+               cl.movevars_entgravity = cl.statsf[STAT_MOVEVARS_ENTGRAVITY];
+               cl.movevars_jumpvelocity = cl.statsf[STAT_MOVEVARS_JUMPVELOCITY];
+               cl.movevars_edgefriction = cl.statsf[STAT_MOVEVARS_EDGEFRICTION];
+               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_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];
+               cl.movevars_waterfriction = cl.statsf[STAT_MOVEVARS_WATERFRICTION];
+       }
+       else
+       {
+               cl.movevars_ticrate = 1.0 / bound(1, cl_netinputpacketspersecond.value, 100);
+               cl.movevars_timescale = slowmo.value;
+               cl.movevars_gravity = sv_gravity.value;
+               cl.movevars_stopspeed = cl_movement_stopspeed.value;
+               cl.movevars_maxspeed = cl_movement_maxspeed.value;
+               cl.movevars_spectatormaxspeed = cl_movement_maxspeed.value;
+               cl.movevars_accelerate = cl_movement_accelerate.value;
+               cl.movevars_airaccelerate = cl_movement_airaccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_airaccelerate.value;
+               cl.movevars_wateraccelerate = cl_movement_wateraccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_wateraccelerate.value;
+               cl.movevars_friction = cl_movement_friction.value;
+               cl.movevars_wallfriction = cl_movement_wallfriction.value;
+               cl.movevars_waterfriction = cl_movement_waterfriction.value < 0 ? cl_movement_friction.value : cl_movement_waterfriction.value;
+               cl.movevars_entgravity = 1;
+               cl.movevars_jumpvelocity = cl_movement_jumpvelocity.value;
+               cl.movevars_edgefriction = cl_movement_edgefriction.value;
+               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_sideways_friction = cl_movement_airaccel_sideways_friction.value;
+       }
+}
+
 void CL_ClientMovement_Replay(void)
 {
        int i;
@@ -1079,46 +1217,6 @@ 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]);
 
-       // set up movement variables
-       if (cls.protocol == PROTOCOL_QUAKEWORLD)
-       {
-               s.movevars_gravity = cl.qw_movevars_gravity;
-               s.movevars_stopspeed = cl.qw_movevars_stopspeed;
-               s.movevars_maxspeed = cl.qw_movevars_maxspeed;
-               s.movevars_spectatormaxspeed = cl.qw_movevars_spectatormaxspeed;
-               s.movevars_accelerate = cl.qw_movevars_accelerate;
-               s.movevars_airaccelerate = cl.qw_movevars_airaccelerate;
-               s.movevars_wateraccelerate = cl.qw_movevars_wateraccelerate;
-               s.movevars_friction = cl.qw_movevars_friction;
-               s.movevars_waterfriction = cl.qw_movevars_waterfriction;
-               s.movevars_entgravity = cl.qw_movevars_entgravity;
-               s.movevars_jumpvelocity = cl_movement_jumpvelocity.value;
-               s.movevars_edgefriction = cl_movement_edgefriction.value;
-               s.movevars_maxairspeed = cl_movement_maxairspeed.value;
-               s.movevars_stepheight = cl_movement_stepheight.value;
-               s.movevars_airaccel_qw = 1.0;
-               s.movevars_airaccel_sideways_friction = 0.0;
-       }
-       else
-       {
-               s.movevars_gravity = sv_gravity.value;
-               s.movevars_stopspeed = cl_movement_stopspeed.value;
-               s.movevars_maxspeed = cl_movement_maxspeed.value;
-               s.movevars_spectatormaxspeed = cl_movement_maxspeed.value;
-               s.movevars_accelerate = cl_movement_accelerate.value;
-               s.movevars_airaccelerate = cl_movement_airaccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_airaccelerate.value;
-               s.movevars_wateraccelerate = cl_movement_wateraccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_wateraccelerate.value;
-               s.movevars_friction = cl_movement_friction.value;
-               s.movevars_waterfriction = cl_movement_waterfriction.value < 0 ? cl_movement_friction.value : cl_movement_waterfriction.value;
-               s.movevars_entgravity = 1;
-               s.movevars_jumpvelocity = cl_movement_jumpvelocity.value;
-               s.movevars_edgefriction = cl_movement_edgefriction.value;
-               s.movevars_maxairspeed = cl_movement_maxairspeed.value;
-               s.movevars_stepheight = cl_movement_stepheight.value;
-               s.movevars_airaccel_qw = cl_movement_airaccel_qw.value;
-               s.movevars_airaccel_sideways_friction = cl_movement_airaccel_sideways_friction.value;
-       }
-
        totalmovetime = 0;
        for (i = 0;i < cl.movement_numqueue - 1;i++)
                totalmovetime += cl.movement_queue[i].frametime;
@@ -1268,37 +1366,29 @@ void CL_SendMove(void)
                return;
 
        // don't send too often or else network connections can get clogged by a high renderer framerate
-       if (cl_netinputpacketspersecond.value >= 1)
-               packettime = 1.0 / bound(1, cl_netinputpacketspersecond.value, 1000);
-       else
+       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.time = realtime;
                cl.cmd.sequence = cls.netcon->qw.outgoing_sequence;
        }
-       else if (cl.movement_predicted)
-       {
-               if (realtime < lastsendtime + packettime)
-                       return;
-               cl.cmd.time = cl.time;
-               cl.cmd.sequence = cls.movesequence;
-       }
        else
        {
-               // unpredicted movement should be sent immediately whenever a server
+               // movement should be sent immediately whenever a server
                // packet is received, to minimize ping times
                if (!cl.movement_needupdate && realtime < lastsendtime + packettime)
                        return;
-               if (cl.mtime[0] == cl.movecmd[0].time && cl.mtime[1] != cl.mtime[0])
-                       return;
-               cl.cmd.time = cl.mtime[0];
-               cl.cmd.sequence = cls.movesequence;
        }
 
        // don't let it fall behind if CL_SendMove hasn't been called recently
@@ -1317,7 +1407,7 @@ void CL_SendMove(void)
        // 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))
+       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
@@ -1432,8 +1522,10 @@ void CL_SendMove(void)
        }
        else if (cls.signon == SIGNONS)
        {
-               if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE)
+               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)
                {
+                       CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
+
                        // 5 bytes
                        MSG_WriteByte (&buf, clc_move);
                        MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
@@ -1450,6 +1542,8 @@ void CL_SendMove(void)
                }
                else if (cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3)
                {
+                       CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
+
                        // 5 bytes
                        MSG_WriteByte (&buf, clc_move);
                        MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
@@ -1466,6 +1560,8 @@ void CL_SendMove(void)
                }
                else if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5)
                {
+                       CL_ClientMovement_Input((cl.movecmd[0].buttons & 2) != 0, false);
+
                        // 5 bytes
                        MSG_WriteByte (&buf, clc_move);
                        MSG_WriteFloat (&buf, cl.movecmd[0].time); // last server packet time
@@ -1566,7 +1662,7 @@ void CL_SendMove(void)
 
        // send the reliable message (forwarded commands) if there is one
        if (buf.cursize || cls.netcon->message.cursize)
-               NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, max(20*(buf.cursize+40), cl_rate.integer));
+               NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, max(20*(buf.cursize+40), cl_rate.integer), false);
 
        if (cls.netcon->message.overflowed)
        {
@@ -1653,28 +1749,34 @@ 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)");
+       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");
+       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_movement);
        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);
        Cvar_RegisterVariable(&cl_movement_friction);
+       Cvar_RegisterVariable(&cl_movement_wallfriction);
+       Cvar_RegisterVariable(&cl_movement_waterfriction);
        Cvar_RegisterVariable(&cl_movement_edgefriction);
        Cvar_RegisterVariable(&cl_movement_stepheight);
-       Cvar_RegisterVariable(&cl_movement_airaccelerate);
        Cvar_RegisterVariable(&cl_movement_accelerate);
+       Cvar_RegisterVariable(&cl_movement_airaccelerate);
+       Cvar_RegisterVariable(&cl_movement_wateraccelerate);
        Cvar_RegisterVariable(&cl_movement_jumpvelocity);
        Cvar_RegisterVariable(&cl_movement_airaccel_qw);
        Cvar_RegisterVariable(&cl_movement_airaccel_sideways_friction);
-       Cvar_RegisterVariable(&cl_gravity);
-       Cvar_RegisterVariable(&cl_slowmo);
 
        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(&cl_nodelta);