]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - sv_phys.c
fix two bugs introduced in r5828 (2005-11-25) that made monsters and
[xonotic/darkplaces.git] / sv_phys.c
index 91fbbccad2e85c15bc27ef2fb4bfff384dd437eb..bd8b50ce30262ed9a36b8092fd8f49a009552867 100644 (file)
--- a/sv_phys.c
+++ b/sv_phys.c
@@ -43,6 +43,25 @@ solid_edge items only clip against bsp models.
 
 void SV_Physics_Toss (prvm_edict_t *ent);
 
+int SV_GetPitchSign(prvm_edict_t *ent)
+{
+       dp_model_t *model;
+       int modelindex;
+       if (
+                       ((modelindex = (int)ent->fields.server->modelindex) >= 1 && modelindex < MAX_MODELS && (model = sv.models[modelindex]))
+                       ?
+                       model->type == mod_alias
+                       :
+                       (
+                        (((unsigned char)PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.pflags)->_float) & PFLAGS_FULLDYNAMIC)
+                        ||
+                        ((gamemode == GAME_TENEBRAE) && ((unsigned int)ent->fields.server->effects & (16 | 32)))
+                       )
+          )
+               return -1;
+       return 1;
+}
+
 /*
 ===============================================================================
 
@@ -199,19 +218,7 @@ trace_t SV_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int
                        // if the modelindex is 0, it shouldn't be SOLID_BSP!
                        if (modelindex > 0 && modelindex < MAX_MODELS)
                                model = sv.models[(int)touch->fields.server->modelindex];
-                       //pitchsign = 1;
-                       if (
-                               ((modelindex = (int)touch->fields.server->modelindex) >= 1 && modelindex < MAX_MODELS && (model = sv.models[(int)touch->fields.server->modelindex]))
-                               ?
-                                       model->type == mod_alias
-                               :
-                                       (
-                                               (((unsigned char)PRVM_EDICTFIELDVALUE(touch, prog->fieldoffsets.pflags)->_float) & PFLAGS_FULLDYNAMIC)
-                                               ||
-                                               ((gamemode == GAME_TENEBRAE) && ((unsigned int)touch->fields.server->effects & (16 | 32)))
-                                       )
-                       )
-                               pitchsign = -1;
+                       pitchsign = SV_GetPitchSign(touch);
                }
                if (model)
                        Matrix4x4_CreateFromQuakeEntity(&matrix, touch->fields.server->origin[0], touch->fields.server->origin[1], touch->fields.server->origin[2], pitchsign * touch->fields.server->angles[0], touch->fields.server->angles[1], touch->fields.server->angles[2], 1);
@@ -374,19 +381,7 @@ trace_t SV_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_
                        // if the modelindex is 0, it shouldn't be SOLID_BSP!
                        if (modelindex > 0 && modelindex < MAX_MODELS)
                                model = sv.models[(int)touch->fields.server->modelindex];
-                       //pitchsign = 1;
-                       if (
-                               ((modelindex = (int)touch->fields.server->modelindex) >= 1 && modelindex < MAX_MODELS && (model = sv.models[(int)touch->fields.server->modelindex]))
-                               ?
-                                       model->type == mod_alias
-                               :
-                                       (
-                                               (((unsigned char)PRVM_EDICTFIELDVALUE(touch, prog->fieldoffsets.pflags)->_float) & PFLAGS_FULLDYNAMIC)
-                                               ||
-                                               ((gamemode == GAME_TENEBRAE) && ((unsigned int)touch->fields.server->effects & (16 | 32)))
-                                       )
-                       )
-                               pitchsign = -1;
+                       pitchsign = SV_GetPitchSign(touch);
                }
                if (model)
                        Matrix4x4_CreateFromQuakeEntity(&matrix, touch->fields.server->origin[0], touch->fields.server->origin[1], touch->fields.server->origin[2], pitchsign * touch->fields.server->angles[0], touch->fields.server->angles[1], touch->fields.server->angles[2], 1);
@@ -587,18 +582,7 @@ trace_t SV_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, co
                        if (modelindex > 0 && modelindex < MAX_MODELS)
                                model = sv.models[(int)touch->fields.server->modelindex];
                        //pitchsign = 1;
-                       if (
-                               ((modelindex = (int)touch->fields.server->modelindex) >= 1 && modelindex < MAX_MODELS && (model = sv.models[(int)touch->fields.server->modelindex]))
-                               ?
-                                       model->type == mod_alias
-                               :
-                                       (
-                                               (((unsigned char)PRVM_EDICTFIELDVALUE(touch, prog->fieldoffsets.pflags)->_float) & PFLAGS_FULLDYNAMIC)
-                                               ||
-                                               ((gamemode == GAME_TENEBRAE) && ((unsigned int)touch->fields.server->effects & (16 | 32)))
-                                       )
-                       )
-                               pitchsign = -1;
+                       pitchsign = SV_GetPitchSign(touch);
                }
                if (model)
                        Matrix4x4_CreateFromQuakeEntity(&matrix, touch->fields.server->origin[0], touch->fields.server->origin[1], touch->fields.server->origin[2], pitchsign * touch->fields.server->angles[0], touch->fields.server->angles[1], touch->fields.server->angles[2], 1);
@@ -706,6 +690,32 @@ Linking entities into the world culling system
 ===============================================================================
 */
 
+void SV_LinkEdict_TouchAreaGrid_Call(prvm_edict_t *touch, prvm_edict_t *ent)
+{
+       prvm_eval_t *val;
+       prog->globals.server->self = PRVM_EDICT_TO_PROG(touch);
+       prog->globals.server->other = PRVM_EDICT_TO_PROG(ent);
+       prog->globals.server->time = sv.time;
+       prog->globals.server->trace_allsolid = false;
+       prog->globals.server->trace_startsolid = false;
+       prog->globals.server->trace_fraction = 1;
+       prog->globals.server->trace_inwater = false;
+       prog->globals.server->trace_inopen = true;
+       VectorCopy (touch->fields.server->origin, prog->globals.server->trace_endpos);
+       VectorSet (prog->globals.server->trace_plane_normal, 0, 0, 1);
+       prog->globals.server->trace_plane_dist = 0;
+       prog->globals.server->trace_ent = PRVM_EDICT_TO_PROG(ent);
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dpstartcontents)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dphitcontents)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dphitq3surfaceflags)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dphittexturename)))
+               val->string = 0;
+       PRVM_ExecuteProgram (touch->fields.server->touch, "QC function self.touch is missing");
+}
+
 void SV_LinkEdict_TouchAreaGrid(prvm_edict_t *ent)
 {
        int i, numtouchedicts, old_self, old_other;
@@ -737,34 +747,44 @@ void SV_LinkEdict_TouchAreaGrid(prvm_edict_t *ent)
                touch = touchedicts[i];
                if (touch != ent && (int)touch->fields.server->solid == SOLID_TRIGGER && touch->fields.server->touch)
                {
-                       prvm_eval_t *val;
-                       prog->globals.server->self = PRVM_EDICT_TO_PROG(touch);
-                       prog->globals.server->other = PRVM_EDICT_TO_PROG(ent);
-                       prog->globals.server->time = sv.time;
-                       prog->globals.server->trace_allsolid = false;
-                       prog->globals.server->trace_startsolid = false;
-                       prog->globals.server->trace_fraction = 1;
-                       prog->globals.server->trace_inwater = false;
-                       prog->globals.server->trace_inopen = true;
-                       VectorCopy (touch->fields.server->origin, prog->globals.server->trace_endpos);
-                       VectorSet (prog->globals.server->trace_plane_normal, 0, 0, 1);
-                       prog->globals.server->trace_plane_dist = 0;
-                       prog->globals.server->trace_ent = PRVM_EDICT_TO_PROG(ent);
-                       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dpstartcontents)))
-                               val->_float = 0;
-                       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dphitcontents)))
-                               val->_float = 0;
-                       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dphitq3surfaceflags)))
-                               val->_float = 0;
-                       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dphittexturename)))
-                               val->string = 0;
-                       PRVM_ExecuteProgram (touch->fields.server->touch, "QC function self.touch is missing");
+                       SV_LinkEdict_TouchAreaGrid_Call(touch, ent);
                }
        }
        prog->globals.server->self = old_self;
        prog->globals.server->other = old_other;
 }
 
+static void RotateBBox(const vec3_t mins, const vec3_t maxs, const vec3_t angles, vec3_t rotatedmins, vec3_t rotatedmaxs)
+{
+       vec3_t v, u;
+       matrix4x4_t m;
+       Matrix4x4_CreateFromQuakeEntity(&m, 0, 0, 0, angles[PITCH], angles[YAW], angles[ROLL], 1.0);
+
+       v[0] = mins[0]; v[1] = mins[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u);
+               VectorCopy(u, rotatedmins); VectorCopy(u, rotatedmaxs);
+       v[0] = maxs[0]; v[1] = mins[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u);
+               if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2];
+               if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2];
+       v[0] = mins[0]; v[1] = maxs[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u);
+               if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2];
+               if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2];
+       v[0] = maxs[0]; v[1] = maxs[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u);
+               if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2];
+               if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2];
+       v[0] = mins[0]; v[1] = mins[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u);
+               if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2];
+               if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2];
+       v[0] = maxs[0]; v[1] = mins[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u);
+               if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2];
+               if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2];
+       v[0] = mins[0]; v[1] = maxs[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u);
+               if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2];
+               if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2];
+       v[0] = maxs[0]; v[1] = maxs[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u);
+               if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2];
+               if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2];
+}
+
 /*
 ===============
 SV_LinkEdict
@@ -784,7 +804,15 @@ void SV_LinkEdict (prvm_edict_t *ent)
 
 // set the abs box
 
-       if (ent->fields.server->solid == SOLID_BSP)
+       if (ent->fields.server->movetype == MOVETYPE_PHYSICS)
+       {
+               // TODO maybe should do this for rotating SOLID_BSP too? Would behave better with rotating doors
+               // TODO special handling for spheres?
+               RotateBBox(ent->fields.server->mins, ent->fields.server->maxs, ent->fields.server->angles, mins, maxs);
+               VectorAdd(ent->fields.server->origin, mins, mins);
+               VectorAdd(ent->fields.server->origin, maxs, maxs);
+       }
+       else if (ent->fields.server->solid == SOLID_BSP)
        {
                int modelindex = (int)ent->fields.server->modelindex;
                if (modelindex < 0 || modelindex >= MAX_MODELS)
@@ -1025,6 +1053,12 @@ void SV_CheckVelocity (prvm_edict_t *ent)
                }
        }
 
+       // LordHavoc: a hack to ensure that the (rather silly) id1 quakec
+       // player_run/player_stand1 does not horribly malfunction if the
+       // velocity becomes a denormalized float
+       if (VectorLength2(ent->fields.server->velocity) < 0.0001)
+               VectorClear(ent->fields.server->velocity);
+
        // LordHavoc: max velocity fix, inspired by Maddes's source fixes, but this is faster
        wishspeed = DotProduct(ent->fields.server->velocity, ent->fields.server->velocity);
        if (wishspeed > sv_maxvelocity.value * sv_maxvelocity.value)
@@ -1448,8 +1482,11 @@ Returns true if the push did not result in the entity being teleported by QC cod
 static qboolean SV_PushEntity (trace_t *trace, prvm_edict_t *ent, vec3_t push, qboolean failonbmodelstartsolid, qboolean dolink)
 {
        int type;
+       int bump;
+       vec3_t original;
        vec3_t end;
 
+       VectorCopy(ent->fields.server->origin, original);
        VectorAdd (ent->fields.server->origin, push, end);
 
        if (ent->fields.server->movetype == MOVETYPE_FLYMISSILE)
@@ -1460,9 +1497,23 @@ static qboolean SV_PushEntity (trace_t *trace, prvm_edict_t *ent, vec3_t push, q
                type = MOVE_NORMAL;
 
        *trace = SV_TraceBox(ent->fields.server->origin, ent->fields.server->mins, ent->fields.server->maxs, end, type, ent, SV_GenericHitSuperContentsMask(ent));
+       bump = 0;
+       while (trace->startsolid && sv_gameplayfix_nudgeoutofsolid.integer)
+       {
+               vec_t nudge = -trace->startdepth + sv_gameplayfix_nudgeoutofsolid_bias.value;
+               VectorMA(ent->fields.server->origin, nudge, trace->startdepthnormal, ent->fields.server->origin);
+               *trace = SV_TraceBox(ent->fields.server->origin, ent->fields.server->mins, ent->fields.server->maxs, end, type, ent, SV_GenericHitSuperContentsMask(ent));
+               bump++;
+               if (bump > 10)
+               {
+                       VectorCopy(original, ent->fields.server->origin);
+                       break;
+               }
+       }
        if (trace->bmodelstartsolid && failonbmodelstartsolid)
                return true;
 
+
        VectorCopy (trace->endpos, ent->fields.server->origin);
        SV_LinkEdict(ent);
 
@@ -1628,12 +1679,18 @@ void SV_PushMove (prvm_edict_t *pusher, float movetime)
        for (e = 0;e < numcheckentities;e++)
        {
                prvm_edict_t *check = checkentities[e];
-               if (check->fields.server->movetype == MOVETYPE_NONE
-                || check->fields.server->movetype == MOVETYPE_PUSH
-                || check->fields.server->movetype == MOVETYPE_FOLLOW
-                || check->fields.server->movetype == MOVETYPE_NOCLIP
-                || check->fields.server->movetype == MOVETYPE_FAKEPUSH)
+               int movetype = (int)check->fields.server->movetype;
+               switch(movetype)
+               {
+               case MOVETYPE_NONE:
+               case MOVETYPE_PUSH:
+               case MOVETYPE_FOLLOW:
+               case MOVETYPE_NOCLIP:
+               case MOVETYPE_FAKEPUSH:
                        continue;
+               default:
+                       break;
+               }
 
                if (check->fields.server->owner == pusherprog)
                        continue;
@@ -1681,6 +1738,15 @@ void SV_PushMove (prvm_edict_t *pusher, float movetime)
                VectorCopy (check->fields.server->angles, check->priv.server->moved_fromangles);
                moved_edicts[num_moved++] = PRVM_NUM_FOR_EDICT(check);
 
+               // physics objects need better collisions than this code can do
+               if (movetype == MOVETYPE_PHYSICS)
+               {
+                       VectorAdd(check->fields.server->origin, move, check->fields.server->origin);
+                       SV_LinkEdict(check);
+                       SV_LinkEdict_TouchAreaGrid(check);
+                       continue;
+               }
+
                // try moving the contacted entity
                pusher->fields.server->solid = SOLID_NOT;
                if(!SV_PushEntity (&trace, check, move, true, true))
@@ -2368,10 +2434,12 @@ void SV_Physics_Toss (prvm_edict_t *ent)
        vec3_t move;
        vec_t movetime;
        int bump;
+       prvm_edict_t *groundentity;
 
 // if onground, return without moving
        if ((int)ent->fields.server->flags & FL_ONGROUND)
        {
+               groundentity = PRVM_PROG_TO_EDICT(ent->fields.server->groundentity);
                if (ent->fields.server->velocity[2] >= (1.0 / 32.0) && sv_gameplayfix_upwardvelocityclearsongroundflag.integer)
                {
                        // don't stick to ground if onground and moving upward
@@ -2382,7 +2450,7 @@ void SV_Physics_Toss (prvm_edict_t *ent)
                        // we can trust FL_ONGROUND if groundentity is world because it never moves
                        return;
                }
-               else if (ent->priv.server->suspendedinairflag && PRVM_PROG_TO_EDICT(ent->fields.server->groundentity)->priv.server->free)
+               else if (ent->priv.server->suspendedinairflag && groundentity->priv.server->free)
                {
                        // if ent was supported by a brush model on previous frame,
                        // and groundentity is now freed, set groundentity to 0 (world)
@@ -2391,6 +2459,11 @@ void SV_Physics_Toss (prvm_edict_t *ent)
                        if (sv_gameplayfix_noairborncorpse_allowsuspendeditems.integer)
                                return;
                }
+               else if (BoxesOverlap(ent->priv.server->cullmins, ent->priv.server->cullmaxs, groundentity->priv.server->cullmins, groundentity->priv.server->cullmaxs))
+               {
+                       // don't slide if still touching the groundentity
+                       return;
+               }
        }
        ent->priv.server->suspendedinairflag = false;
 
@@ -2426,7 +2499,13 @@ void SV_Physics_Toss (prvm_edict_t *ent)
                movetime *= 1 - min(1, trace.fraction);
                if (ent->fields.server->movetype == MOVETYPE_BOUNCEMISSILE)
                {
-                       ClipVelocity (ent->fields.server->velocity, trace.plane.normal, ent->fields.server->velocity, 2.0);
+                       prvm_eval_t *val;
+                       float bouncefactor = 1.0f;
+                       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.bouncefactor);
+                       if (val!=0 && val->_float)
+                               bouncefactor = val->_float;
+
+                       ClipVelocity (ent->fields.server->velocity, trace.plane.normal, ent->fields.server->velocity, 1 + bouncefactor);
                        ent->fields.server->flags = (int)ent->fields.server->flags & ~FL_ONGROUND;
                }
                else if (ent->fields.server->movetype == MOVETYPE_BOUNCE)
@@ -2536,7 +2615,7 @@ void SV_Physics_Step (prvm_edict_t *ent)
                {
                        // freefall if onground and moving upward
                        // freefall if not standing on a world surface (it may be a lift or trap door)
-                       if ((ent->fields.server->velocity[2] >= (1.0 / 32.0) && sv_gameplayfix_upwardvelocityclearsongroundflag.integer) || ent->fields.server->groundentity)
+                       if (ent->fields.server->velocity[2] >= (1.0 / 32.0) && sv_gameplayfix_upwardvelocityclearsongroundflag.integer)
                        {
                                ent->fields.server->flags -= FL_ONGROUND;
                                SV_CheckVelocity(ent);
@@ -2648,6 +2727,13 @@ static void SV_Physics_Entity (prvm_edict_t *ent)
                if (SV_RunThink (ent))
                        SV_Physics_Toss (ent);
                break;
+       case MOVETYPE_PHYSICS:
+               if (SV_RunThink(ent))
+               {
+                       SV_LinkEdict(ent);
+                       SV_LinkEdict_TouchAreaGrid(ent);
+               }
+               break;
        default:
                Con_Printf ("SV_Physics: bad movetype %i\n", (int)ent->fields.server->movetype);
                break;
@@ -2672,12 +2758,6 @@ void SV_Physics_ClientMove(void)
 
        // make sure the velocity is sane (not a NaN)
        SV_CheckVelocity(ent);
-       // LordHavoc: a hack to ensure that the (rather silly) id1 quakec
-       // player_run/player_stand1 does not horribly malfunction if the
-       // velocity becomes a number that is both == 0 and != 0
-       // (sounds to me like NaN but to be absolutely safe...)
-       if (DotProduct(ent->fields.server->velocity, ent->fields.server->velocity) < 0.0001)
-               VectorClear(ent->fields.server->velocity);
 
        // perform MOVETYPE_WALK behavior
        SV_WalkMove (ent);
@@ -2701,14 +2781,14 @@ void SV_Physics_ClientMove(void)
        }
 }
 
-void SV_Physics_ClientEntity(prvm_edict_t *ent)
+static void SV_Physics_ClientEntity_PreThink(prvm_edict_t *ent)
 {
        // don't do physics on disconnected clients, FrikBot relies on this
        if (!host_client->spawned)
-       {
-               memset(&host_client->cmd, 0, sizeof(host_client->cmd));
                return;
-       }
+
+       // make sure the velocity is sane (not a NaN)
+       SV_CheckVelocity(ent);
 
        // don't run physics here if running asynchronously
        if (host_client->clmovement_inputtimeout <= 0)
@@ -2717,20 +2797,65 @@ void SV_Physics_ClientEntity(prvm_edict_t *ent)
                //host_client->cmd.time = max(host_client->cmd.time, sv.time);
        }
 
-       // make sure the velocity is sane (not a NaN)
+       // make sure the velocity is still sane (not a NaN)
        SV_CheckVelocity(ent);
-       // LordHavoc: a hack to ensure that the (rather silly) id1 quakec
-       // player_run/player_stand1 does not horribly malfunction if the
-       // velocity becomes a number that is both == 0 and != 0
-       // (sounds to me like NaN but to be absolutely safe...)
-       if (DotProduct(ent->fields.server->velocity, ent->fields.server->velocity) < 0.0001)
-               VectorClear(ent->fields.server->velocity);
 
        // call standard client pre-think
        prog->globals.server->time = sv.time;
        prog->globals.server->self = PRVM_EDICT_TO_PROG(ent);
-       PRVM_ExecuteProgram (prog->globals.server->PlayerPreThink, "QC function PlayerPreThink is missing");
-       SV_CheckVelocity (ent);
+       PRVM_ExecuteProgram(prog->globals.server->PlayerPreThink, "QC function PlayerPreThink is missing");
+
+       // make sure the velocity is still sane (not a NaN)
+       SV_CheckVelocity(ent);
+}
+
+static void SV_Physics_ClientEntity_PostThink(prvm_edict_t *ent)
+{
+       // don't do physics on disconnected clients, FrikBot relies on this
+       if (!host_client->spawned)
+               return;
+
+       // make sure the velocity is sane (not a NaN)
+       SV_CheckVelocity(ent);
+
+       // call standard player post-think
+       prog->globals.server->time = sv.time;
+       prog->globals.server->self = PRVM_EDICT_TO_PROG(ent);
+       PRVM_ExecuteProgram(prog->globals.server->PlayerPostThink, "QC function PlayerPostThink is missing");
+
+       // make sure the velocity is still sane (not a NaN)
+       SV_CheckVelocity(ent);
+
+       if(ent->fields.server->fixangle)
+       {
+               // angle fixing was requested by physics code...
+               // so store the current angles for later use
+               memcpy(host_client->fixangle_angles, ent->fields.server->angles, sizeof(host_client->fixangle_angles));
+               host_client->fixangle_angles_set = TRUE;
+
+               // and clear fixangle for the next frame
+               ent->fields.server->fixangle = 0;
+       }
+
+       // decrement the countdown variable used to decide when to go back to
+       // synchronous physics
+       if (host_client->clmovement_inputtimeout > sv.frametime)
+               host_client->clmovement_inputtimeout -= sv.frametime;
+       else
+               host_client->clmovement_inputtimeout = 0;
+}
+
+static void SV_Physics_ClientEntity(prvm_edict_t *ent)
+{
+       // don't do physics on disconnected clients, FrikBot relies on this
+       if (!host_client->spawned)
+       {
+               memset(&host_client->cmd, 0, sizeof(host_client->cmd));
+               return;
+       }
+
+       // make sure the velocity is sane (not a NaN)
+       SV_CheckVelocity(ent);
 
        switch ((int) ent->fields.server->movetype)
        {
@@ -2773,40 +2898,20 @@ void SV_Physics_ClientEntity(prvm_edict_t *ent)
                SV_RunThink (ent);
                SV_WalkMove (ent);
                break;
+       case MOVETYPE_PHYSICS:
+               SV_RunThink (ent);
+               break;
        default:
                Con_Printf ("SV_Physics_ClientEntity: bad movetype %i\n", (int)ent->fields.server->movetype);
                break;
        }
 
-       // decrement the countdown variable used to decide when to go back to
-       // synchronous physics
-       if (host_client->clmovement_inputtimeout > sv.frametime)
-               host_client->clmovement_inputtimeout -= sv.frametime;
-       else
-               host_client->clmovement_inputtimeout = 0;
-
        SV_CheckVelocity (ent);
 
        SV_LinkEdict(ent);
        SV_LinkEdict_TouchAreaGrid(ent);
 
        SV_CheckVelocity (ent);
-
-       // call standard player post-think
-       prog->globals.server->time = sv.time;
-       prog->globals.server->self = PRVM_EDICT_TO_PROG(ent);
-       PRVM_ExecuteProgram (prog->globals.server->PlayerPostThink, "QC function PlayerPostThink is missing");
-
-       if(ent->fields.server->fixangle)
-       {
-               // angle fixing was requested by physics code...
-               // so store the current angles for later use
-               memcpy(host_client->fixangle_angles, ent->fields.server->angles, sizeof(host_client->fixangle_angles));
-               host_client->fixangle_angles_set = TRUE;
-
-               // and clear fixangle for the next frame
-               ent->fields.server->fixangle = 0;
-       }
 }
 
 /*
@@ -2827,6 +2932,9 @@ void SV_Physics (void)
        prog->globals.server->frametime = sv.frametime;
        PRVM_ExecuteProgram (prog->globals.server->StartFrame, "QC function StartFrame is missing");
 
+       // run physics engine
+       World_Physics_Frame(&sv.world, sv.frametime, sv_gravity.value);
+
 //
 // treat each object in turn
 //
@@ -2837,11 +2945,35 @@ void SV_Physics (void)
                        if (!ent->priv.server->free)
                                SV_LinkEdict_TouchAreaGrid(ent); // force retouch even for stationary
 
-       // run physics on the client entities
-       for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++)
-               if (!ent->priv.server->free)
+       if (sv_gameplayfix_consistentplayerprethink.integer)
+       {
+               // run physics on the client entities in 3 stages
+               for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++)
+                       if (!ent->priv.server->free)
+                               SV_Physics_ClientEntity_PreThink(ent);
+
+               for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++)
+                       if (!ent->priv.server->free)
                                SV_Physics_ClientEntity(ent);
 
+               for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++)
+                       if (!ent->priv.server->free)
+                               SV_Physics_ClientEntity_PostThink(ent);
+       }
+       else
+       {
+               // run physics on the client entities
+               for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++)
+               {
+                       if (!ent->priv.server->free)
+                       {
+                               SV_Physics_ClientEntity_PreThink(ent);
+                               SV_Physics_ClientEntity(ent);
+                               SV_Physics_ClientEntity_PostThink(ent);
+                       }
+               }
+       }
+
        // run physics on all the non-client entities
        if (!sv_freezenonclients.integer)
        {