]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - sv_user.c
test alpha
[xonotic/darkplaces.git] / sv_user.c
index 0cce2eb9d84129266dd4b48e5ffbafd4f787ee2f..f3dc9c01201eb537fdec7886eabcfd67c5b881e9 100644 (file)
--- a/sv_user.c
+++ b/sv_user.c
@@ -23,8 +23,212 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "sv_demo.h"
 #define DEBUGMOVES 0
 
-static usercmd_t cmd;
+static usercmd_t usercmd;
 extern cvar_t sv_autodemo_perclient;
+extern cvar_t sv_rollangle;
+extern cvar_t sv_rollspeed;
+
+/*
+==================
+SV_PreSpawn_f
+==================
+*/
+void SV_PreSpawn_f(cmd_state_t *cmd)
+{
+       if (host_client->prespawned)
+       {
+               Con_Print("prespawn not valid -- already prespawned\n");
+               return;
+       }
+       host_client->prespawned = true;
+
+       if (host_client->netconnection)
+       {
+               SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
+               MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
+               MSG_WriteByte (&host_client->netconnection->message, 2);
+               host_client->sendsignon = 0;            // enable unlimited sends again
+       }
+
+       // reset the name change timer because the client will send name soon
+       host_client->nametime = 0;
+}
+
+/*
+==================
+SV_Spawn_f
+==================
+*/
+void SV_Spawn_f(cmd_state_t *cmd)
+{
+       prvm_prog_t *prog = SVVM_prog;
+       int i;
+       client_t *client;
+       int stats[MAX_CL_STATS];
+
+       if (!host_client->prespawned)
+       {
+               Con_Print("Spawn not valid -- not yet prespawned\n");
+               return;
+       }
+       if (host_client->spawned)
+       {
+               Con_Print("Spawn not valid -- already spawned\n");
+               return;
+       }
+       host_client->spawned = true;
+
+       // reset name change timer again because they might want to change name
+       // again in the first 5 seconds after connecting
+       host_client->nametime = 0;
+
+       // LadyHavoc: moved this above the QC calls at FrikaC's request
+       // LadyHavoc: commented this out
+       //if (host_client->netconnection)
+       //      SZ_Clear (&host_client->netconnection->message);
+
+       // run the entrance script
+       if (sv.loadgame)
+       {
+               // loaded games are fully initialized already
+               if (PRVM_serverfunction(RestoreGame))
+               {
+                       Con_DPrint("Calling RestoreGame\n");
+                       PRVM_serverglobalfloat(time) = sv.time;
+                       PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
+                       prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
+               }
+       }
+       else
+       {
+               //Con_Printf("SV_Spawn_f: host_client->edict->netname = %s, host_client->edict->netname = %s, host_client->name = %s\n", PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), host_client->name);
+
+               // copy spawn parms out of the client_t
+               for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
+                       (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
+
+               // call the spawn function
+               host_client->clientconnectcalled = true;
+               PRVM_serverglobalfloat(time) = sv.time;
+               PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
+               prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
+
+               Con_Printf("%s connected\n", host_client->name);
+
+               PRVM_serverglobalfloat(time) = sv.time;
+               prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
+       }
+
+       if (!host_client->netconnection)
+               return;
+
+       // send time of update
+       MSG_WriteByte (&host_client->netconnection->message, svc_time);
+       MSG_WriteFloat (&host_client->netconnection->message, sv.time);
+
+       // send all current names, colors, and frag counts
+       for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
+       {
+               if (!client->active)
+                       continue;
+               MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
+               MSG_WriteByte (&host_client->netconnection->message, i);
+               MSG_WriteString (&host_client->netconnection->message, client->name);
+               MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
+               MSG_WriteByte (&host_client->netconnection->message, i);
+               MSG_WriteShort (&host_client->netconnection->message, client->frags);
+               MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
+               MSG_WriteByte (&host_client->netconnection->message, i);
+               MSG_WriteByte (&host_client->netconnection->message, client->colors);
+       }
+
+       // send all current light styles
+       for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
+       {
+               if (sv.lightstyles[i][0])
+               {
+                       MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
+                       MSG_WriteByte (&host_client->netconnection->message, (char)i);
+                       MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
+               }
+       }
+
+       // send some stats
+       MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
+       MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
+       MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
+
+       MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
+       MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
+       MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
+
+       MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
+       MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
+       MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
+
+       MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
+       MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
+       MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
+
+       // send a fixangle
+       // Never send a roll angle, because savegames can catch the server
+       // in a state where it is expecting the client to correct the angle
+       // and it won't happen if the game was just loaded, so you wind up
+       // with a permanent head tilt
+       if (sv.loadgame)
+       {
+               MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
+               MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
+               MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
+               MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
+       }
+       else
+       {
+               MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
+               MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
+               MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
+               MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
+       }
+
+       SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
+
+       MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
+       MSG_WriteByte (&host_client->netconnection->message, 3);
+}
+
+/*
+==================
+SV_Begin_f
+==================
+*/
+void SV_Begin_f(cmd_state_t *cmd)
+{
+       if (!host_client->spawned)
+       {
+               Con_Print("Begin not valid -- not yet spawned\n");
+               return;
+       }
+       if (host_client->begun)
+       {
+               Con_Print("Begin not valid -- already begun\n");
+               return;
+       }
+       host_client->begun = true;
+
+       // LadyHavoc: note: this code also exists in SV_DropClient
+       if (sv.loadgame)
+       {
+               int i;
+               for (i = 0;i < svs.maxclients;i++)
+                       if (svs.clients[i].active && !svs.clients[i].spawned)
+                               break;
+               if (i == svs.maxclients)
+               {
+                       Con_Printf("Loaded game, everyone rejoined - unpausing\n");
+                       sv.paused = sv.loadgame = false; // we're basically done with loading now
+               }
+       }
+}
 
 /*
 ===============
@@ -59,7 +263,7 @@ void SV_SetIdealPitch (void)
                bottom[1] = top[1];
                bottom[2] = top[2] - 160;
 
-               tr = SV_TraceLine(top, bottom, MOVE_NOMONSTERS, host_client->edict, SUPERCONTENTS_SOLID, collision_extendmovelength.value);
+               tr = SV_TraceLine(top, bottom, MOVE_NOMONSTERS, host_client->edict, SUPERCONTENTS_SOLID, 0, 0, collision_extendmovelength.value);
                // if looking at a wall, leave ideal the way is was
                if (tr.startsolid)
                        return;
@@ -101,7 +305,7 @@ void SV_SetIdealPitch (void)
 static vec3_t wishdir, forward, right, up;
 static float wishspeed;
 
-static qboolean onground;
+static qbool onground;
 
 /*
 ==================
@@ -126,7 +330,7 @@ static void SV_UserFriction (void)
        start[2] = PRVM_serveredictvector(host_client->edict, origin)[2] + PRVM_serveredictvector(host_client->edict, mins)[2];
        stop[2] = start[2] - 34;
 
-       trace = SV_TraceLine(start, stop, MOVE_NOMONSTERS, host_client->edict, SV_GenericHitSuperContentsMask(host_client->edict), collision_extendmovelength.value);
+       trace = SV_TraceLine(start, stop, MOVE_NOMONSTERS, host_client->edict, SV_GenericHitSuperContentsMask(host_client->edict), 0, 0, collision_extendmovelength.value);
 
        if (trace.fraction == 1.0)
                friction = sv_friction.value*sv_edgefriction.value;
@@ -233,28 +437,28 @@ static void SV_WaterMove (void)
        prvm_prog_t *prog = SVVM_prog;
        int i;
        vec3_t wishvel, v_angle;
-       vec_t speed, newspeed, wishspeed, addspeed, accelspeed, temp;
+       vec_t speed, newspeed, fwishspeed, addspeed, accelspeed, temp;
 
        // user intentions
        VectorCopy(PRVM_serveredictvector(host_client->edict, v_angle), v_angle);
        AngleVectors(v_angle, forward, right, up);
 
        for (i=0 ; i<3 ; i++)
-               wishvel[i] = forward[i]*cmd.forwardmove + right[i]*cmd.sidemove;
+               wishvel[i] = forward[i]*usercmd.forwardmove + right[i]*usercmd.sidemove;
 
-       if (!cmd.forwardmove && !cmd.sidemove && !cmd.upmove)
+       if (!usercmd.forwardmove && !usercmd.sidemove && !usercmd.upmove)
                wishvel[2] -= 60;               // drift towards bottom
        else
-               wishvel[2] += cmd.upmove;
+               wishvel[2] += usercmd.upmove;
 
-       wishspeed = VectorLength(wishvel);
-       if (wishspeed > sv_maxspeed.value)
+       fwishspeed = VectorLength(wishvel);
+       if (fwishspeed > sv_maxspeed.value)
        {
-               temp = sv_maxspeed.value/wishspeed;
+               temp = sv_maxspeed.value/fwishspeed;
                VectorScale (wishvel, temp, wishvel);
-               wishspeed = sv_maxspeed.value;
+               fwishspeed = sv_maxspeed.value;
        }
-       wishspeed *= 0.7;
+       fwishspeed *= 0.7;
 
        // water friction
        speed = VectorLength(PRVM_serveredictvector(host_client->edict, velocity));
@@ -270,15 +474,15 @@ static void SV_WaterMove (void)
                newspeed = 0;
 
        // water acceleration
-       if (!wishspeed)
+       if (!fwishspeed)
                return;
 
-       addspeed = wishspeed - newspeed;
+       addspeed = fwishspeed - newspeed;
        if (addspeed <= 0)
                return;
 
        VectorNormalize (wishvel);
-       accelspeed = (sv_wateraccelerate.value < 0 ? sv_accelerate.value : sv_wateraccelerate.value) * wishspeed * sv.frametime;
+       accelspeed = (sv_wateraccelerate.value < 0 ? sv_accelerate.value : sv_wateraccelerate.value) * fwishspeed * sv.frametime;
        if (accelspeed > addspeed)
                accelspeed = addspeed;
 
@@ -312,13 +516,13 @@ static void SV_AirMove (void)
        vec3_t wishvel;
        float fmove, smove, temp;
 
-       // LordHavoc: correct quake movement speed bug when looking up/down
+       // LadyHavoc: correct quake movement speed bug when looking up/down
        wishvel[0] = wishvel[2] = 0;
        wishvel[1] = PRVM_serveredictvector(host_client->edict, angles)[1];
        AngleVectors (wishvel, forward, right, up);
 
-       fmove = cmd.forwardmove;
-       smove = cmd.sidemove;
+       fmove = usercmd.forwardmove;
+       smove = usercmd.sidemove;
 
 // hack to not let you back into teleporter
        if (sv.time < PRVM_serveredictfloat(host_client->edict, teleport_time) && fmove < 0)
@@ -328,7 +532,7 @@ static void SV_AirMove (void)
                wishvel[i] = forward[i]*fmove + right[i]*smove;
 
        if ((int)PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_WALK)
-               wishvel[2] += cmd.upmove;
+               wishvel[2] += usercmd.upmove;
 
        VectorCopy (wishvel, wishdir);
        wishspeed = VectorNormalizeLength(wishdir);
@@ -358,13 +562,13 @@ static void SV_AirMove (void)
 
 /*
 ===================
-SV_ClientThink
+SV_PlayerPhysics
 
 the move fields specify an intended velocity in pix/sec
 the angle fields specify an exact angular motion in degrees
 ===================
 */
-void SV_ClientThink (void)
+void SV_PlayerPhysics (void)
 {
        prvm_prog_t *prog = SVVM_prog;
        vec3_t v_angle, angles, velocity;
@@ -375,7 +579,7 @@ void SV_ClientThink (void)
        // make sure the velocity is sane (not a NaN)
        SV_CheckVelocity(host_client->edict);
 
-       // LordHavoc: QuakeC replacement for SV_ClientThink (player movement)
+       // LadyHavoc: QuakeC replacement for SV_PlayerPhysics (player movement)
        if (PRVM_serverfunction(SV_PlayerPhysics) && sv_playerphysicsqc.integer)
        {
                PRVM_serverglobalfloat(time) = sv.time;
@@ -396,14 +600,14 @@ void SV_ClientThink (void)
        if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
                return;
 
-       cmd = host_client->cmd;
+       usercmd = host_client->cmd;
 
        // angles
        // show 1/3 the pitch angle and all the roll angle
        VectorAdd (PRVM_serveredictvector(host_client->edict, v_angle), PRVM_serveredictvector(host_client->edict, punchangle), v_angle);
        VectorCopy(PRVM_serveredictvector(host_client->edict, angles), angles);
        VectorCopy(PRVM_serveredictvector(host_client->edict, velocity), velocity);
-       PRVM_serveredictvector(host_client->edict, angles)[ROLL] = V_CalcRoll (angles, velocity)*4;
+       PRVM_serveredictvector(host_client->edict, angles)[ROLL] = Com_CalcRoll (angles, velocity, sv_rollangle.value, sv_rollspeed.value)*4;
        if (!PRVM_serveredictfloat(host_client->edict, fixangle))
        {
                PRVM_serveredictvector(host_client->edict, angles)[PITCH] = -v_angle[PITCH]/3;
@@ -417,16 +621,6 @@ void SV_ClientThink (void)
                return;
        }
 
-       /*
-       // Player is (somehow) outside of the map, or flying, or noclipping
-       if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP && (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_FLY || SV_TestEntityPosition (host_client->edict)))
-       //if (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_NOCLIP || PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_FLY || SV_TestEntityPosition (host_client->edict))
-       {
-               SV_FreeMove ();
-               return;
-       }
-       */
-
        // walk
        if ((PRVM_serveredictfloat(host_client->edict, waterlevel) >= 2) && (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP))
        {
@@ -460,16 +654,16 @@ static void SV_ReadClientMove (void)
        // read ping time
        if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3 && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5 && sv.protocol != PROTOCOL_DARKPLACES6)
                move->sequence = MSG_ReadLong(&sv_message);
-       move->time = move->clienttime = MSG_ReadFloat(&sv_message);
+       move->time = MSG_ReadFloat(&sv_message);
        if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__);
        move->receivetime = (float)sv.time;
 
 #if DEBUGMOVES
-       Con_Printf("%s move%i #%i %ims (%ims) %i %i '%i %i %i' '%i %i %i'\n", move->time > move->receivetime ? "^3read future" : "^4read normal", sv_numreadmoves + 1, move->sequence, (int)floor((move->time - host_client->cmd.time) * 1000.0 + 0.5), (int)floor(move->time * 1000.0 + 0.5), move->impulse, move->buttons, (int)move->viewangles[0], (int)move->viewangles[1], (int)move->viewangles[2], (int)move->forwardmove, (int)move->sidemove, (int)move->upmove);
+       Con_Printf("%s move%i #%u %ims (%ims) %i %i '%i %i %i' '%i %i %i'\n", move->time > move->receivetime ? "^3read future" : "^4read normal", sv_numreadmoves + 1, move->sequence, (int)floor((move->time - host_client->cmd.time) * 1000.0 + 0.5), (int)floor(move->time * 1000.0 + 0.5), move->impulse, move->buttons, (int)move->viewangles[0], (int)move->viewangles[1], (int)move->viewangles[2], (int)move->forwardmove, (int)move->sidemove, (int)move->upmove);
 #endif
        // limit reported time to current time
        // (incase the client is trying to cheat)
-       move->time = min(move->time, move->receivetime + sv.frametime);
+       move->time = min(move->time, sv.time + sv.frametime);
 
        // read current angles
        for (i = 0;i < 3;i++)
@@ -544,16 +738,21 @@ static void SV_ReadClientMove (void)
                sv_readmoves[sv_numreadmoves++] = *move;
 
        // movement packet loss tracking
-       if(move->sequence)
+       // bones_was_here: checking begun prevents heavy loss detection right after a map change
+       if(move->sequence && host_client->begun)
        {
                if(move->sequence > host_client->movement_highestsequence_seen)
                {
                        if(host_client->movement_highestsequence_seen)
                        {
                                // mark moves in between as lost
-                               if(move->sequence - host_client->movement_highestsequence_seen - 1 < NETGRAPH_PACKETS)
-                                       for(i = host_client->movement_highestsequence_seen + 1; i < move->sequence; ++i)
-                                               host_client->movement_count[i % NETGRAPH_PACKETS] = -1;
+                               unsigned int delta = move->sequence - host_client->movement_highestsequence_seen - 1;
+                               if(delta < NETGRAPH_PACKETS)
+                               {
+                                       unsigned int u;
+                                       for(u = 0; u < delta; ++u)
+                                               host_client->movement_count[(host_client->movement_highestsequence_seen + 1 + u) % NETGRAPH_PACKETS] = -1;
+                               }
                                else
                                        memset(host_client->movement_count, -1, sizeof(host_client->movement_count));
                        }
@@ -573,16 +772,224 @@ static void SV_ReadClientMove (void)
        }
 }
 
+static void SV_ExecuteAsyncMove(usercmd_t *move)
+{
+       prvm_prog_t *prog = SVVM_prog;
+
+       double qcframetime = PRVM_serverglobalfloat(frametime);
+       double svframetime = sv.frametime;
+
+       float timeout = min(0.1, sys_ticrate.value > 0.0 && sv.frametime > 0.0 ? sv.frametime * ceil(sv_clmovement_inputtimeout.value / sv.frametime) : sv_clmovement_inputtimeout.value);
+
+       float newping = (sv_clmovement_buffer.integer ? sv.time : host_client->cmd.receivetime) - move->time; // include buffering latency if applicable
+
+       float latencydelta = max(0.0f, newping - host_client->ping);
+
+       if (sv_clmovement_inputtimeout_strict.integer && host_client->clmovement_inputtimeout_accum)
+       {
+               // latency comp for strict mode
+               host_client->cmd.time -= latencydelta;
+               host_client->clmovement_inputtimeout_accum = 0.0f;
+       }
+
+       // support splitting by not resetting frametime when some remains to be executed
+       if (move->frametime <= 0.0)
+               move->frametime = move->time - host_client->cmd.time;
+
+       //Con_Printf("^2newping: %f\toldping: %f\tframetime: %f\n", newping, host_client->ping, move->frametime);
+       // TODO should we check sv.frametime > 0.0 ?
+       if (sv_clmovement_inputtimeout_correct.integer && host_client->clmovement_inputtimeout_accum)
+       {
+               Con_Printf("inputtimeout_accum: %f\n", host_client->clmovement_inputtimeout_accum);
+               // this condition is WIP lol
+               // no condition at all works well for ping spikes and big fps changes, but regresses the improvements for 60fps inputtimeout 0.015625 ticrate 0.0078125
+               //if (move->frametime - host_client->clmovement_inputtimeout_accum < 0.0005) // only if we will drop a move, might let accumulator get larger than would be ideal
+               //if (host_client->clmovement_inputtimeout_accum > timeout)
+               //if (host_client->clmovement_inputtimeout_accum > sv.frametime) // seems about as good as no condition, for fps and ping spikes :)
+               if ((sv_clmovement_inputtimeout_correct.integer == 1 && host_client->clmovement_inputtimeout_accum > sv.frametime && sv.frametime > 0.0) || (sv_clmovement_inputtimeout_correct.integer == 2 && host_client->clmovement_inputtimeout_accum > timeout) || (sv_clmovement_inputtimeout_correct.integer == 3 && move->frametime - host_client->clmovement_inputtimeout_accum < 0.0005) || sv_clmovement_inputtimeout_correct.integer == 4)
+               {
+                       //Con_Printf("latencydelta: %f\taccum: %f\n", latencydelta, host_client->clmovement_inputtimeout_accum);
+                       // this seems very well behaved in the sudden latency increase case (eg cl_maxidlefps)
+                       host_client->clmovement_inputtimeout_accum = max(0.0f, host_client->clmovement_inputtimeout_accum - latencydelta);
+               }
+
+               //if (move->frametime > host_client->clmovement_inputtimeout_accum || (sv_clmovement_inputtimeout_correct.integer & 2 && move->frametime > timeout) || (sv_clmovement_inputtimeout_correct.integer & 4 && sv.frametime && move->frametime > sv.frametime) || sv_clmovement_inputtimeout_correct.integer & 8)
+               //anything more aggressive than (move->frametime > host_client->clmovement_inputtimeout_accum) requires move splitting to prevent some discards after frametimes jump well past inputtimeout
+//             if (host_client->clmovement_inputtimeout_accum > 0.0f)
+//             {
+                       // FIXME sys_ticrate 0.0078125 inputtimeout 0.0078125 125fps = a few moves dropped (frametimes between 0 and 0.0005)
+                       // FIXME sys_ticrate 0.0078125 inputtimeout 0.015625 63fps = choppy but nothign is dropped, and its perfectly smooth at 60fps
+                       double ft = move->frametime;
+                       move->frametime -= host_client->clmovement_inputtimeout_accum;
+                       //Con_Printf("Shortening a move after %f inputtimeout, was %f, now %f\n", host_client->clmovement_inputtimeout_accum, ft, move->frametime);
+                       host_client->clmovement_inputtimeout_accum = max(0.0f, host_client->clmovement_inputtimeout_accum - ft);
+//             }
+//             else
+//             {
+//                     Con_Printf("^1Bad stuff!\n");
+                       //Con_Printf("Discarding a SHORT move after %f inputtimeout, was %f, now 0.0\n", host_client->clmovement_inputtimeout_accum, move->frametime);
+                       //host_client->clmovement_inputtimeout_accum -= move->frametime;
+                       //move->frametime = 0.0;
+//                     host_client->clmovement_inputtimeout_accum = 0.0f;
+//             }
+
+               //Con_Printf("Final frametime: %f\tLatency delta: %f\n", move->frametime, latencydelta);
+       }
+
+       // bones_was_here: limit moveframetime to a multiple of sv.frametime to match inputtimeout behaviour
+       // very short inputtimeout requires NOT limiting to inputtimeout (as that could be shorter than client's frametime),
+       // and ideally buffered mode's move splitting (to prevent inputtimeouts and sync physics)
+       // but, just using _correct mode is a big improvement for that case
+       // TODO: correct mode: since we already truncated, we could bound tighter here, but should we? probably smoother not to...
+       if (!sv_clmovement_inputtimeout_correct.integer && !sv_clmovement_buffer_split.integer)
+               move->frametime = min(move->frametime, timeout);
+
+       // FIXME code duplication; would only work with sv_clmovement_alwayscopy >= 1
+       // if the previous move has not been applied yet, we need to accumulate
+       // the impulse/buttons from it
+       if (!host_client->cmd.applied)
+       {
+               if (!move->impulse)
+                       move->impulse = host_client->cmd.impulse;
+               move->buttons |= host_client->cmd.buttons;
+       }
+
+       // discard (treat like lost) moves with too low distance from
+       // the previous one to prevent hacks using float inaccuracy
+       // clients will see this as packet loss in the netgraph
+       // this should also apply if a move cannot get
+       // executed because it came too late and
+       // already was performed serverside
+       if(move->frametime < 0.0005)
+       {
+               // count the move as LOST if we don't
+               // execute it but it has higher
+               // sequence count
+               if(host_client->movesequence && sv_clmovement_noisy.integer && (move->time - host_client->cmd.time > 0.0005 || sv_clmovement_noisy.integer & 2))
+                       if(move->sequence > host_client->movesequence)
+                               host_client->movement_count[(move->sequence) % NETGRAPH_PACKETS] = -1;
+
+               // bones_was_here: reliability improved by copying the move even when we don't execute it asynchronously
+               // (legit DP clients often hit frametime < 0.0005 due to time synchronisation)
+               // if we hit the inputtimeout, this move will be executed synchronously (so player can still move even with 100% move discard rate)
+               // if we don't hit the inputtimeout, this move will not be marked as applied,
+               // so the impulse and buttons will be accumulated into the next move parsed by SV_ReadClientMove
+               // or into the next move in the buffer after this one (see above)
+               // (previously this only happened in synchronous movement)
+               if (sv_clmovement_alwayscopy.integer)
+               {
+                       // latencydelta will need to know this move's latency if there's still some inputtimeout_accum
+                       //if (sv_clmovement_inputtimeout_correct.integer)
+                       host_client->ping = (sv_clmovement_buffer.integer ? sv.time : move->receivetime) - move->time; // include buffering latency if applicable
+
+                       //if (move->time < host_client->cmd.time)
+                               //Con_Printf("^3Time could step back by %f\n", host_client->cmd.time - move->time);
+
+                       // should move frametimes increase after discards (similar to PL) ? NO !
+                       // problem with that in _correct mode: we discard x ms of frametime, but add that many to the next move, and since we already decremented the accumulator, client can lagaport
+                       if (sv_clmovement_inputtimeout_correct.integer)
+                       {
+                               // allow small backsteps ONLY, to prevent corner cases where we discard far too many moves
+                               // one sv.frametime is plenty for _correct mode and makes it much more robust with big ping spikes
+                               move->time = max(move->time, host_client->cmd.time - sv.frametime); // prevent backstepping of time
+                       }
+                       else if (sv_clmovement_inputtimeout_strict.integer)
+                       {
+                               // prevent updating of time (HUGE frametimes! like PL, old behaviour, breaks burst discards after inputtimeout)
+                               // strict mode needs this to work properly
+                               move->time = host_client->cmd.time;
+                       }
+
+                       //if (move->time < host_client->cmd.time)
+                               //Con_Printf("^1  Time did step back by %f\n", host_client->cmd.time - move->time);
+
+                       host_client->cmd = *move;
+               }
+
+               // TODO move this out of this block if removing buffer_split
+               if (sv_clmovement_buffer.integer)
+                       host_client->mbuf_r++;
+
+               //Con_Printf("Discarding move with frametime %f\n", move->frametime);
+               return;
+       }
+
+       host_client->cmd = *move;
+       host_client->movesequence = move->sequence;
+
+       if (sv_clmovement_buffer.integer)
+       {
+               // TODO can this break when paused? should we use sys_ticrate.value instead or just check sv.frametime?
+               // FIXME if the client has exactly half the server's framerate and never fluctuates at all
+               // the number of buffered moves never decreases
+               // happens when enabling buffer during a match with a client already running exactly half hz
+               // hack: could do a small offset so its running slightly more frametime on the first one ?
+               // or just use a small buffer (4) lol, should be -ok- since long moves are split without using additional buffer slots
+               if ((sv_clmovement_buffer_split.integer == 1 && sv.frametime > 0.0 && move->frametime >= 2.0 * sv.frametime) || (sv_clmovement_buffer_split.integer == 2 && move->frametime > timeout))
+               {
+                       //Con_Printf("Splitting a move in %d! ", (int)floor(move->frametime / sv.frametime));
+                       host_client->cmd.frametime /= floor(move->frametime / sv.frametime);
+                       move->frametime -= host_client->cmd.frametime;
+                       move->msec = 1;
+                       //Con_Printf("shortened frametime: %f stored remainder: %f\n", host_client->cmd.frametime, move->frametime);
+               }
+               else
+               {
+                       host_client->mbuf_r++;
+                       move->msec = 0;
+                       move->frametime = 0.0; // when reusing this buffer slot, frametime is only calculated if not already set
+               }
+
+               host_client->frametime_accum += host_client->cmd.frametime;
+       }
+
+       // if using prediction, we need to perform moves when packets are
+       // received, even if multiple occur in one frame
+       // (they can't go beyond the current time so there is no cheat issue
+       //  with this approach, and if they don't send input for a while they
+       //  start moving anyway, so the longest 'lagaport' possible is
+       //  determined by the sv_clmovement_inputtimeout cvar)
+
+       // update ping time for qc to see while executing this move
+       host_client->ping = (sv_clmovement_buffer.integer ? sv.time : host_client->cmd.receivetime) - host_client->cmd.time; // include buffering latency if applicable
+       //FIXME why do need to keep receivetime? to support listen servers running multiple physics frames per frame? no, because no move processing happens between physics frames
+       // the server and qc frametime values must be changed temporarily
+       PRVM_serverglobalfloat(frametime) = sv.frametime = host_client->cmd.frametime;
+       // if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction)
+       if (sv.frametime > 0.05)
+       {
+               PRVM_serverglobalfloat(frametime) = sv.frametime = host_client->cmd.frametime * 0.5;
+               SV_Physics_ClientMove();
+       }
+       SV_Physics_ClientMove();
+       sv.frametime = svframetime;
+       PRVM_serverglobalfloat(frametime) = qcframetime;
+       host_client->clmovement_inputtimeout = min(0.1, sv_clmovement_inputtimeout.value);
+}
+
+inline void SV_ExecuteBufferedAsyncMoves(void)
+{
+       unsigned char i = 0;
+       for (host_client = &svs.clients[0]; i < svs.maxclients; i++, host_client++)
+       {
+               if (host_client->begun)
+                       host_client->mbuf_s_max = max(host_client->mbuf_s_max, host_client->mbuf_w - host_client->mbuf_r); // the implicit cast to unsigned char is part of the calculation
+                       //Con_Printf("Buffered moves: %d\n", (unsigned char)(host_client->mvbuf_w - host_client->mvbuf_r));
+
+               host_client->frametime_accum = 0.0;
+               while (host_client->frametime_accum < sv.frametime && host_client->mbuf_w != host_client->mbuf_r)
+                       SV_ExecuteAsyncMove(&host_client->mbuf[MBUF_MASK(host_client->mbuf_r)]);
+       }
+}
+
 static void SV_ExecuteClientMoves(void)
 {
        prvm_prog_t *prog = SVVM_prog;
        int moveindex;
-       float moveframetime;
-       double oldframetime;
-       double oldframetime2;
-#ifdef NUM_PING_TIMES
-       double total;
-#endif
+       //double moveframetime;
+       //double oldframetime;
+       //double oldframetime2;
+
        if (sv_numreadmoves < 1)
                return;
        // only start accepting input once the player is spawned
@@ -591,11 +998,12 @@ static void SV_ExecuteClientMoves(void)
 #if DEBUGMOVES
        Con_Printf("SV_ExecuteClientMoves: read %i moves at sv.time %f\n", sv_numreadmoves, (float)sv.time);
 #endif
+
        // disable clientside movement prediction in some cases
-       if (ceil(max(sv_readmoves[sv_numreadmoves-1].receivetime - sv_readmoves[sv_numreadmoves-1].time, 0) * 1000.0) < sv_clmovement_minping.integer)
-               host_client->clmovement_disabletimeout = realtime + sv_clmovement_minping_disabletime.value / 1000.0;
+       if (host_client->ping * 1000.0 < sv_clmovement_minping.value || host_client->ping > 1)
+               host_client->clmovement_disabletimeout = host.realtime + sv_clmovement_minping_disabletime.value / 1000.0;
        // several conditions govern whether clientside movement prediction is allowed
-       if (sv_readmoves[sv_numreadmoves-1].sequence && sv_clmovement_enable.integer && sv_clmovement_inputtimeout.value > 0 && host_client->clmovement_disabletimeout <= realtime && PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_WALK && (!PRVM_serveredictfloat(host_client->edict, disableclientprediction)))
+       if (sv_readmoves[sv_numreadmoves-1].sequence && sv_clmovement_enable.integer && sv_clmovement_inputtimeout.value > 0 && host_client->clmovement_disabletimeout <= host.realtime && (PRVM_serveredictfloat(host_client->edict, disableclientprediction) == -1 || (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_WALK && (!PRVM_serveredictfloat(host_client->edict, disableclientprediction)))))
        {
                // process the moves in order and ignore old ones
                // but always trust the latest move
@@ -605,16 +1013,79 @@ static void SV_ExecuteClientMoves(void)
                for (moveindex = 0;moveindex < sv_numreadmoves;moveindex++)
                {
                        usercmd_t *move = sv_readmoves + moveindex;
-                       if (host_client->movesequence < move->sequence || moveindex == sv_numreadmoves - 1)
+                       if (max(host_client->movesequence, host_client->mbuf[MBUF_MASK(host_client->mbuf_w - 1)].sequence) < move->sequence || moveindex == sv_numreadmoves - 1)
                        {
 #if DEBUGMOVES
-                               Con_Printf("%smove #%i %ims (%ims) %i %i '%i %i %i' '%i %i %i'\n", (move->time - host_client->cmd.time) > sv.frametime * 1.01 ? "^1" : "^2", move->sequence, (int)floor((move->time - host_client->cmd.time) * 1000.0 + 0.5), (int)floor(move->time * 1000.0 + 0.5), move->impulse, move->buttons, (int)move->viewangles[0], (int)move->viewangles[1], (int)move->viewangles[2], (int)move->forwardmove, (int)move->sidemove, (int)move->upmove);
+                               Con_Printf("%smove #%u %ims (%ims) %i %i '%i %i %i' '%i %i %i'\n", (move->time - host_client->cmd.time) > sv.frametime * 1.01 ? "^1" : "^2", move->sequence, (int)floor((move->time - host_client->cmd.time) * 1000.0 + 0.5), (int)floor(move->time * 1000.0 + 0.5), move->impulse, move->buttons, (int)move->viewangles[0], (int)move->viewangles[1], (int)move->viewangles[2], (int)move->forwardmove, (int)move->sidemove, (int)move->upmove);
 #endif
+/*
+                               usercmd_t *prevmove;
+                               if (sv_clmovement_buffer.integer && host_client->movesequence && host_client->movesequence < host_client->mbuf[MBUF_MASK(host_client->mbuf_w - 1)].sequence)
+                                        prevmove = &host_client->mbuf[MBUF_MASK(host_client->mbuf_w - 1)];
+                               else
+                                        prevmove = &host_client->cmd;
+                               if (move->time > sv.time)
+                                       Con_Printf("move->time exceeded sv.time by %f\n", move->time - sv.time);
+                               if (move->time < prevmove->time)
+                                       Con_Printf("time stepped back by %f\n", prevmove->time - move->time);
+*/
+
                                // this is a new move
+/*
+                               // we limit to sv.time + sv.frametime in SV_ReadClientMove and fall back to sync phys if too old
+                               // prevent backstepping of time also not required: move->frametime will be < 0 and the move will be discarded
                                move->time = bound(sv.time - 1, move->time, sv.time); // prevent slowhack/speedhack combos
                                move->time = max(move->time, host_client->cmd.time); // prevent backstepping of time
-                               moveframetime = bound(0, move->time - host_client->cmd.time, min(0.1, sv_clmovement_inputtimeout.value));
+*/
+/*
+                               move->frametime = move->time - max(host_client->cmd.time, host_client->mvbuf[MVBUF_MASK(host_client->mvbuf_w - 1)].time);
+
+                               //if (move->frametime > 0.05)
+                                       //Con_Printf("Long move frametime: %f\n", move->frametime);
+
+                               if (sv_clmovement_inputtimeout_correct.integer && host_client->clmovement_inputtimeout_accum)
+                               {
+                                       if (move->frametime > host_client->clmovement_inputtimeout_accum)
+                                       {
+                                               //Con_Printf("Shortening move frametime after %f inputtimeout, was %f, now %f\n", host_client->clmovement_inputtimeout_accum, move->frametime, move->frametime - host_client->clmovement_inputtimeout_accum);
+                                               move->frametime -= host_client->clmovement_inputtimeout_accum;
+                                               host_client->clmovement_inputtimeout_accum = 0.0f;
+                                       }
+                                       else
+                                       {
+                                               //Con_Printf("Shortening move frametime after %f inputtimeout, was %f, now 0.0\n", host_client->clmovement_inputtimeout_accum, move->frametime);
+                                               host_client->clmovement_inputtimeout_accum -= move->frametime;
+                                               move->frametime = 0.0;
+                                       }
+                               }
 
+                               // bones_was_here: limit moveframetime to a multiple of sv.frametime to match inputtimeout behaviour
+                               // very short inputtimeout requires NOT limiting to inputtimeout (as that could be shorter than client's frametime),
+                               // and ideally buffered mode's move splitting (to prevent inputtimeouts and sync physics)
+                               // but, just using _correct mode is a big improvement for that case
+                               if (sv_clmovement_inputtimeout_correct.integer || sv_clmovement_buffer_split.integer)
+                                       move->frametime = min(move->frametime, 0.1);
+                               else
+                                       move->frametime = min(move->frametime, min(0.1, sys_ticrate.value > 0.0 && sv.frametime > 0.0 ? sv.frametime * ceil(sv_clmovement_inputtimeout.value / sv.frametime) : sv_clmovement_inputtimeout.value));
+*/
+                               if (sv_clmovement_buffer.integer)
+                               {
+                                       // if the buffer is full we are about to overwrite the oldest move
+                                       // log it as lost and increment the read index
+                                       if ((unsigned char)(host_client->mbuf_w - host_client->mbuf_r) >= MBUF_SIZE)
+                                               host_client->movement_count[(host_client->mbuf[MBUF_MASK(host_client->mbuf_r++)].sequence) % NETGRAPH_PACKETS] = -1;
+
+                                       // store the move and increment the write index
+                                       host_client->mbuf[MBUF_MASK(host_client->mbuf_w++)] = *move;
+
+                                       // if possible, execute the move now
+                                       if (host_client->frametime_accum < sv.frametime)
+                                               SV_ExecuteAsyncMove(&host_client->mbuf[MBUF_MASK(host_client->mbuf_r)]);
+                               }
+                               else
+                                       SV_ExecuteAsyncMove(move);
+
+/*
                                // discard (treat like lost) moves with too low distance from
                                // the previous one to prevent hacks using float inaccuracy
                                // clients will see this as packet loss in the netgraph
@@ -642,8 +1113,6 @@ static void SV_ExecuteClientMoves(void)
                                //  with this approach, and if they don't send input for a while they
                                //  start moving anyway, so the longest 'lagaport' possible is
                                //  determined by the sv_clmovement_inputtimeout cvar)
-                               if (moveframetime <= 0)
-                                       continue;
                                oldframetime = PRVM_serverglobalfloat(frametime);
                                oldframetime2 = sv.frametime;
                                // update ping time for qc to see while executing this move
@@ -659,7 +1128,8 @@ static void SV_ExecuteClientMoves(void)
                                SV_Physics_ClientMove();
                                sv.frametime = oldframetime2;
                                PRVM_serverglobalfloat(frametime) = oldframetime;
-                               host_client->clmovement_inputtimeout = sv_clmovement_inputtimeout.value;
+                               host_client->clmovement_inputtimeout = min(0.1, sv_clmovement_inputtimeout.value);
+*/
                        }
                }
        }
@@ -679,7 +1149,7 @@ static void SV_ExecuteClientMoves(void)
                }
                // now copy the new move
                host_client->cmd = sv_readmoves[sv_numreadmoves-1];
-               host_client->cmd.time = max(host_client->cmd.time, sv.time);
+               //host_client->cmd.time = max(host_client->cmd.time, sv.time);
                        // physics will run up to sv.time, so allow no predicted moves
                        // before that otherwise, there is a speedhack by turning
                        // prediction on and off repeatedly on client side because the
@@ -688,17 +1158,11 @@ static void SV_ExecuteClientMoves(void)
                host_client->movesequence = 0;
                // make sure that normal physics takes over immediately
                host_client->clmovement_inputtimeout = 0;
+               // clear the async move buffer
+               host_client->mbuf_r = host_client->mbuf_w = 0;
+               // update ping time
+               host_client->ping = host_client->cmd.receivetime - sv_readmoves[sv_numreadmoves-1].time;
        }
-
-       // calculate average ping time
-       host_client->ping = host_client->cmd.receivetime - host_client->cmd.clienttime;
-#ifdef NUM_PING_TIMES
-       host_client->ping_times[host_client->num_pings % NUM_PING_TIMES] = host_client->cmd.receivetime - host_client->cmd.clienttime;
-       host_client->num_pings++;
-       for (i=0, total = 0;i < NUM_PING_TIMES;i++)
-               total += host_client->ping_times[i];
-       host_client->ping = total / NUM_PING_TIMES;
-#endif
 }
 
 void SV_ApplyClientMove (void)
@@ -764,7 +1228,7 @@ void SV_ApplyClientMove (void)
        PRVM_serveredictfloat(host_client->edict, ping_movementloss) = movementloss / (float) NETGRAPH_PACKETS;
 }
 
-static qboolean SV_FrameLost(int framenum)
+static qbool SV_FrameLost(int framenum)
 {
        if (host_client->entitydatabase5)
        {
@@ -796,7 +1260,7 @@ SV_ReadClientMessage
 void SV_ReadClientMessage(void)
 {
        prvm_prog_t *prog = SVVM_prog;
-       int cmd, num, start;
+       int netcmd, num, start;
        char *s, *p, *q;
 
        if(sv_autodemo_perclient.integer >= 2)
@@ -821,8 +1285,8 @@ void SV_ReadClientMessage(void)
                        return;
                }
 
-               cmd = MSG_ReadByte(&sv_message);
-               if (cmd == -1)
+               netcmd = MSG_ReadByte(&sv_message);
+               if (netcmd == -1)
                {
                        // end of message
                        // apply the moves that were read this frame
@@ -830,10 +1294,10 @@ void SV_ReadClientMessage(void)
                        break;
                }
 
-               switch (cmd)
+               switch (netcmd)
                {
                default:
-                       Con_Printf("SV_ReadClientMessage: unknown command char %i (at offset 0x%x)\n", cmd, sv_message.readcount);
+                       Con_Printf("SV_ReadClientMessage: unknown command char %i (at offset 0x%x)\n", netcmd, sv_message.readcount);
                        if (developer_networking.integer)
                                Com_HexDumpToConsole(sv_message.data, sv_message.cursize);
                        SV_DropClient (false);
@@ -865,7 +1329,7 @@ void SV_ReadClientMessage(void)
                        if (strncasecmp(s, "spawn", 5) == 0
                         || strncasecmp(s, "begin", 5) == 0
                         || strncasecmp(s, "prespawn", 8) == 0)
-                               Cmd_ExecuteString (s, src_client, true);
+                               Cmd_ExecuteString (cmd_serverfromclient, s, src_client, true);
                        else if (PRVM_serverfunction(SV_ParseClientCommand))
                        {
                                int restorevm_tempstringsbuf_cursize;
@@ -877,7 +1341,7 @@ void SV_ReadClientMessage(void)
                                prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
                        }
                        else
-                               Cmd_ExecuteString (s, src_client, true);
+                               Cmd_ExecuteString (cmd_serverfromclient, s, src_client, true);
                        break;
 
 clc_stringcmd_invalid:
@@ -928,7 +1392,7 @@ clc_stringcmd_invalid:
                                                Mem_Free(temp);
                                                // calculated crc, send the file info to the client
                                                // (so that it can verify the data)
-                                               Host_ClientCommands("\ncl_downloadfinished %i %i %s\n", size, crc, host_client->download_name);
+                                               SV_ClientCommands("\ncl_downloadfinished %i %i %s\n", size, crc, host_client->download_name);
                                                Con_DPrintf("Download of %s by %s has finished\n", host_client->download_name, host_client->name);
                                                FS_Close(host_client->download_file);
                                                host_client->download_file = NULL;