Index: src/game/g_local.h =================================================================== --- src/game/g_local.h (revision 866) +++ src/game/g_local.h (working copy) @@ -426,6 +426,10 @@ gentity_t *hovel; + int lastUpdateFrame; + qboolean warping; + qboolean warped; + int lastPoisonTime; int poisonImmunityTime; gentity_t *lastPoisonClient; @@ -1136,6 +1140,9 @@ extern vmCvar_t g_shove; +extern vmCvar_t g_maxWarp; +extern vmCvar_t g_skipCorrection; + extern vmCvar_t g_mapConfigs; extern vmCvar_t g_admin; Index: src/game/g_active.c =================================================================== --- src/game/g_active.c (revision 866) +++ src/game/g_active.c (working copy) @@ -1269,6 +1269,24 @@ // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; + if( g_maxWarp.integer > 0 && client->warping ) + { + int frames = ( level.framenum - client->lastUpdateFrame ); + int maxtime = 0; + + if( frames > g_maxWarp.integer ) + frames = g_maxWarp.integer; + + maxtime = ( frames * ( level.time - level.previousTime ) ); + //ucmd->serverTime = client->ps.commandTime + maxtime; + ucmd->serverTime = level.previousTime; + client->ps.commandTime = level.previousTime - maxtime; + client->warped = qtrue; + } + client->warping = qfalse; + client->lastUpdateFrame = level.framenum; + + // sanity check the command time to prevent speedup cheating if( ucmd->serverTime > level.time + 200 ) { @@ -1768,6 +1786,58 @@ /* ============== + G_PredictPmove + + Make use of the 'Pmove' functions to figure out where a player + would have moved on their present course in frametime seconds. + Link them at this predicted location and send it out in the next + snapshot, BUT don't alter their location so subsequent cmds from + this client process as though nothing has happened. +============== +*/ +static void G_PredictPmove( gentity_t *ent, float frametime ) +{ + pmove_t pm; + pmoveExt_t pmext; + playerState_t ps; + vec3_t currentOrigin; + + if( !ent || !ent->inuse || !ent->r.linked || !ent->client || + ent->health < 1 || ent->client->ps.pm_type != PM_NORMAL || + ent->waterlevel > 1 ) + { + return; + } + + memset( &pm, 0, sizeof( pm ) ); + memcpy( &ps, &ent->client->ps, sizeof( ps ) ); + memcpy( &pmext, &ent->client->pmext, sizeof( pmext ) ); + pm.ps = &ps; + pm.pmext = &pmext; + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + pm.tracemask = MASK_PLAYERSOLID; + VectorCopy( ent->r.mins, pm.mins ); + VectorCopy( ent->r.maxs, pm.maxs ); + + PmovePredict( &pm, frametime ); + SnapVector( ps.origin ); + SnapVector( ps.velocity ); + + // link the entity at this new location so they can be hit there but don't + // really move them since it will interfere with movement + VectorCopy( ent->r.currentOrigin , currentOrigin ); + VectorCopy( ps.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + VectorCopy( currentOrigin, ent->r.currentOrigin ); + + // send this new origin/velocity in the next snapshot + VectorCopy( ps.origin, ent->s.pos.trBase ); + VectorCopy( ps.velocity, ent->s.pos.trDelta ); +} + +/* +============== ClientEndFrame Called at the end of each server frame for each connected client @@ -1778,6 +1848,7 @@ void ClientEndFrame( gentity_t *ent ) { clientPersistant_t *pers; + int frames; if( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { @@ -1817,6 +1888,28 @@ BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); SendPendingPredictableEvents( &ent->client->ps ); + + // see how many frames the client has missed + frames = level.framenum - ent->client->lastUpdateFrame - 1; + + if( g_maxWarp.integer > 0 ) + { + if( frames > g_maxWarp.integer ) + { + ent->client->warping = qtrue; + frames = g_maxWarp.integer; + } + } + else if( frames > 3 ) + frames = 3; + + if( g_skipCorrection.integer && !ent->client->warped && frames > 0 ) + { + float pmtime = 0.001f * ( frames * ( level.time - level.previousTime ) ); + + G_PredictPmove( ent, pmtime ); + } + ent->client->warped = qfalse; } Index: src/game/g_main.c =================================================================== --- src/game/g_main.c (revision 866) +++ src/game/g_main.c (working copy) @@ -116,6 +116,9 @@ vmCvar_t g_shove; +vmCvar_t g_maxWarp; +vmCvar_t g_skipCorrection; + vmCvar_t g_mapConfigs; vmCvar_t g_chatTeamPrefix; @@ -226,6 +229,10 @@ { &g_currentMap, "g_currentMap", "0", 0, 0, qfalse }, { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse }, { &g_shove, "g_shove", "0.0", CVAR_ARCHIVE, 0, qfalse }, + + { &g_maxWarp, "g_maxWarp", "3", CVAR_ARCHIVE, 0, qfalse }, + { &g_skipCorrection, "g_skipCorrection", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse }, { NULL, "g_mapConfigsLoaded", "0", CVAR_ROM, 0, qfalse }, Index: src/game/bg_pmove.c =================================================================== --- src/game/bg_pmove.c (revision 866) +++ src/game/bg_pmove.c (working copy) @@ -3504,3 +3504,23 @@ pmove->cmd.upmove = 20; } } + +/* +================ + PmovePredict + + Used by the server to move a client along when they miss a server frame +================ +*/ + +void PmovePredict( pmove_t *pmove, float frametime ) +{ + pm = pmove; + memset( &pml, 0, sizeof( pml ) ); + pml.frametime = frametime; + PM_GroundTrace( ); + if( pml.groundPlane ) + PM_StepSlideMove( qfalse, qtrue ); + else + PM_StepSlideMove( qtrue, qtrue ); +} Index: src/game/bg_public.h =================================================================== --- src/game/bg_public.h (revision 866) +++ src/game/bg_public.h (working copy) @@ -197,6 +197,7 @@ // if a full pmove isn't done on the client, you can just update the angles void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); void Pmove( pmove_t *pmove ); +void PmovePredict( pmove_t *pmove, float frametime ); //===================================================================================