From a3ff3075710c7657dad4579c90fb451736421140 Mon Sep 17 00:00:00 2001 From: Paweł Redman Date: Mon, 14 Aug 2017 13:07:27 +0200 Subject: Implement player extrapolation. If a player doesn't send client frames fast enough then there will be some server frames where they don't move. This is visible as 'warping' and is generally very undesirable. Extrapolation tries to fix the problem by filling in the missing data by continuing player's trajectory. --- src/game/bg_misc.c | 112 -------------------------------------------------- src/game/bg_public.h | 1 - src/game/g_active.c | 113 +++++++++++++++++++++++++++++++++++++++++++++++---- src/game/g_local.h | 12 ++++++ src/game/g_main.c | 2 + 5 files changed, 118 insertions(+), 122 deletions(-) diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c index 4b0d56f..8810c12 100644 --- a/src/game/bg_misc.c +++ b/src/game/bg_misc.c @@ -3416,118 +3416,6 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean s->otherEntityNum = ps->otherEntityNum; } - -/* -======================== -BG_PlayerStateToEntityStateExtraPolate - -This is done after each set of usercmd_t on the server, -and after local prediction on the client -======================== -*/ -void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) -{ - int i; - - if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR || ps->pm_type == PM_FREEZE ) - s->eType = ET_INVISIBLE; - else if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) - s->eType = ET_INVISIBLE; - else - s->eType = ET_PLAYER; - - s->number = ps->clientNum; - - s->pos.trType = TR_LINEAR_STOP; - VectorCopy( ps->origin, s->pos.trBase ); - - if( snap ) - SnapVector( s->pos.trBase ); - - // set the trDelta for flag direction and linear prediction - VectorCopy( ps->velocity, s->pos.trDelta ); - // set the time for linear prediction - s->pos.trTime = time; - // set maximum extra polation time - s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) - - s->apos.trType = TR_INTERPOLATE; - VectorCopy( ps->viewangles, s->apos.trBase ); - if( snap ) - SnapVector( s->apos.trBase ); - - //TA: i need for other things :) - //s->angles2[YAW] = ps->movementDir; - s->time2 = ps->movementDir; - s->legsAnim = ps->legsAnim; - s->torsoAnim = ps->torsoAnim; - s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number - // so corpses can also reference the proper config - s->eFlags = ps->eFlags; - - if( ps->stats[STAT_HEALTH] <= 0 ) - s->eFlags |= EF_DEAD; - else - s->eFlags &= ~EF_DEAD; - - if( ps->stats[ STAT_STATE ] & SS_BLOBLOCKED ) - s->eFlags |= EF_BLOBLOCKED; - else - s->eFlags &= ~EF_BLOBLOCKED; - - if( ps->externalEvent ) - { - s->event = ps->externalEvent; - s->eventParm = ps->externalEventParm; - } - else if( ps->entityEventSequence < ps->eventSequence ) - { - int seq; - - if( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS ) - ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; - - seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 ); - s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); - s->eventParm = ps->eventParms[ seq ]; - ps->entityEventSequence++; - } - - s->weapon = ps->weapon; - s->groundEntityNum = ps->groundEntityNum; - - //store items held and active items in modelindex and modelindex2 - s->modelindex = 0; - s->modelindex2 = 0; - - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - { - if( BG_InventoryContainsUpgrade( i, ps->stats ) ) - { - s->modelindex |= 1 << i; - - if( BG_UpgradeIsActive( i, ps->stats ) ) - s->modelindex2 |= 1 << i; - } - } - - // use misc field to store team/class info: - s->misc = ps->stats[ STAT_PTEAM ] | ( ps->stats[ STAT_PCLASS ] << 8 ); - - //TA: have to get the surfNormal thru somehow... - VectorCopy( ps->grapplePoint, s->angles2 ); - if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - s->eFlags |= EF_WALLCLIMBCEILING; - - s->loopSound = ps->loopSound; - s->generic1 = ps->generic1; - - if( s->generic1 <= WPM_NONE || s->generic1 >= WPM_NUM_WEAPONMODES ) - s->generic1 = WPM_PRIMARY; - - s->otherEntityNum = ps->otherEntityNum; -} - /* ======================== BG_WeaponIsFull diff --git a/src/game/bg_public.h b/src/game/bg_public.h index 790e206..98c0755 100644 --- a/src/game/bg_public.h +++ b/src/game/bg_public.h @@ -1258,7 +1258,6 @@ void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t res void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); -void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); diff --git a/src/game/g_active.c b/src/game/g_active.c index 5bee472..c1cc9f0 100644 --- a/src/game/g_active.c +++ b/src/game/g_active.c @@ -1566,6 +1566,98 @@ static void ClientGradualFunds( gentity_t *ent ) ent->client->pers.lastFreekillTime += FREEKILL_PERIOD; } +/* +============== +ClientSavePosition +============== +*/ +static void ClientSavePosition(gentity_t *ent, int client_time) +{ + savedPosition_t *pos; + + pos = ent->client->savedPositions + + (ent->client->savedPositionsCount + 1) % MAX_SAVED_POSITIONS; + ent->client->savedPositionsCount++; + + VectorCopy(ent->s.pos.trBase, pos->origin); + pos->time = level.time; + pos->client_time = client_time; +} + +/* +============== +ClientExtrapolate +============== +*/ +static void ClientExtrapolate(gentity_t *ent) +{ + savedPosition_t *pos1, *pos2; + vec3_t dxdt; + int flags, time_delta, time_since; + + flags = g_smoothClients.integer; + if (!flags) + return; + + // Don't bother if the player is lagging too hard. + if (ent->s.eFlags & EF_CONNECTION) + return; + + // Need at least 2 samples for linear extrapolation. + if (ent->client->savedPositionsCount < 2) + return; + + pos1 = ent->client->savedPositions + + ent->client->savedPositionsCount % MAX_SAVED_POSITIONS; + pos2 = ent->client->savedPositions + + (ent->client->savedPositionsCount + MAX_SAVED_POSITIONS - 1) + % MAX_SAVED_POSITIONS; + + switch ((flags & (2 | 4 | 8)) >> 1) { + default: + Com_Printf("^3warning: g_smoothClients has a bad value\n"); + case 0: + time_delta = pos2->time - pos1->time; + break; + case 1: + time_delta = pos2->client_time - pos1->client_time; + break; + case 2: + time_delta = 50; + break; + case 3: + time_delta = 25; + break; + case 4: + time_delta = 16; + break; + } + + VectorSubtract(pos2->origin, pos1->origin, dxdt); + VectorScale(dxdt, pos2->time - pos1->time, dxdt); + + if (flags & 16) + time_since = level.time - pos2->client_time; + else + time_since = level.time - pos2->time; + + VectorMA(pos1->origin, time_delta, dxdt, ent->s.pos.trBase); + + if (g_debugExtrapolation.integer) { + Com_Printf("Extrapolated player %i:\n", (int)(ent - g_entities)); + Com_Printf(" pos1=(%f, %f, %f) at t=%i, t'=%i\n", + pos1->origin[0], pos1->origin[1], pos1->origin[2], + pos1->time, pos1->client_time); + Com_Printf(" pos2=(%f, %f, %f) at t=%i, t'=%i\n", + pos2->origin[0], pos2->origin[1], pos2->origin[2], + pos2->time, pos2->client_time); + Com_Printf(" dxdt=(%f, %f, %f), td=%i, ts=%i\n", + dxdt[0], dxdt[1], dxdt[2], time_delta, time_since); + Com_Printf(" out=(%f, %f, %f)\n", ent->s.pos.trBase[0], + ent->s.pos.trBase[1], ent->s.pos.trBase[2]); + } +} + /* ============== ClientThink @@ -1882,10 +1974,7 @@ void ClientThink_real( gentity_t *ent ) if ( level.paused ) client->ps.pm_type = real_pm_type; - if( g_smoothClients.integer ) - BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); - else - BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); SendPendingPredictableEvents( &ent->client->ps ); @@ -2036,6 +2125,8 @@ void ClientThink_real( gentity_t *ent ) ent->suicideTime = 0; } + + ClientSavePosition( ent, ucmd->serverTime ); } /* @@ -2170,7 +2261,7 @@ void ClientEndFrame( gentity_t *ent ) P_DamageFeedback( ent ); // add the EF_CONNECTION flag if we haven't gotten commands recently - if( level.time - ent->client->lastCmdTime > 1000 ) + if( level.time - ent->client->lastCmdTime > 500 ) ent->s.eFlags |= EF_CONNECTION; else ent->s.eFlags &= ~EF_CONNECTION; @@ -2184,10 +2275,14 @@ void ClientEndFrame( gentity_t *ent ) G_SetClientSound( ent ); // set the latest infor - if( g_smoothClients.integer ) - BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); - else - BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + + if( ent->client->extrapolate ) + ClientExtrapolate( ent ); + + // This flag will go down as soon as a client frame is received. + // If a frame isn't received then it'll stay up and trigger extrapolation. + ent->client->extrapolate = qtrue; SendPendingPredictableEvents( &ent->client->ps ); } diff --git a/src/game/g_local.h b/src/game/g_local.h index a3c02c8..28bd059 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -450,6 +450,13 @@ typedef struct unlagged_s { qboolean used; } unlagged_t; +#define MAX_SAVED_POSITIONS 3 +typedef struct { + vec3_t origin; + int time; + int client_time; +} savedPosition_t; + // this structure is cleared on each ClientSpawn(), // except for 'client->pers' and 'client->sess' struct gclient_s @@ -550,6 +557,10 @@ struct gclient_s int tkcredits[ MAX_CLIENTS ]; int revertCookie; + + qboolean extrapolate; + savedPosition_t savedPositions[MAX_SAVED_POSITIONS]; + int savedPositionsCount; }; @@ -1391,6 +1402,7 @@ extern vmCvar_t g_popularMapsVotePercent; extern vmCvar_t g_banIPs; extern vmCvar_t g_filterBan; extern vmCvar_t g_smoothClients; +extern vmCvar_t g_debugExtrapolation; extern vmCvar_t g_outdatedClientMessage; extern vmCvar_t pmove_fixed; extern vmCvar_t pmove_msec; diff --git a/src/game/g_main.c b/src/game/g_main.c index 55508be..1c6d84e 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -122,6 +122,7 @@ vmCvar_t g_popularMapsVotePercent; vmCvar_t g_banIPs; vmCvar_t g_filterBan; vmCvar_t g_smoothClients; +vmCvar_t g_debugExtrapolation; vmCvar_t g_outdatedClientMessage; vmCvar_t pmove_fixed; vmCvar_t pmove_msec; @@ -416,6 +417,7 @@ static cvarTable_t gameCvarTable[ ] = "Set a name by pressing Escape and choosing Options", CVAR_ARCHIVE, 0, qfalse}, { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse}, + { &g_debugExtrapolation, "g_debugExtrapolation", "0", 0, 0, qfalse}, { &g_outdatedClientMessage, "g_outdatedClientMessage", "", CVAR_ARCHIVE, 0, qfalse}, { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse}, { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse}, -- cgit