Index: src/game/g_local.h =================================================================== --- src/game/g_local.h (revision 838) +++ src/game/g_local.h (working copy) @@ -351,6 +351,14 @@ int adminLevel; } clientPersistant_t; +#define MAX_UNLAGGED_MARKERS 10 +typedef struct unlagged_s { + vec3_t origin; + vec3_t mins; + vec3_t maxs; + qboolean used; +} unlagged_t; + // this structure is cleared on each ClientSpawn(), // except for 'client->pers' and 'client->sess' struct gclient_s @@ -442,6 +450,10 @@ #define RAM_FRAMES 1 // number of frames to wait before retriggering int retriggerArmouryMenu; // frame number to retrigger the armoury menu + unlagged_t unlaggedHist[ MAX_UNLAGGED_MARKERS ]; + unlagged_t unlaggedBackup; + unlagged_t unlaggedCalc; + int attackTime; }; @@ -628,6 +640,9 @@ qboolean uncondHumanWin; qboolean alienTeamLocked; qboolean humanTeamLocked; + + int unlaggedIndex; + int unlaggedTimes[ MAX_UNLAGGED_MARKERS ]; } level_locals_t; // @@ -913,6 +928,13 @@ // // g_active.c // +void G_UnlaggedOn( void ); +void G_UnlaggedOff( void ); +void G_UnlaggedStore( void ); +#define G_UnlaggedTrace( a, b, c, d, e, f, g ) \ + G_UnlaggedOn( ); \ + trap_Trace( a, b, c, d, e, f, g ); \ + G_UnlaggedOff( ); void ClientThink( int clientNum ); void ClientEndFrame( gentity_t *ent ); void G_RunClient( gentity_t *ent ); @@ -1099,6 +1121,8 @@ extern vmCvar_t g_alienStage2Threshold; extern vmCvar_t g_alienStage3Threshold; +extern vmCvar_t g_unlagged; + extern vmCvar_t g_disabledEquipment; extern vmCvar_t g_disabledClasses; extern vmCvar_t g_disabledBuildables; Index: src/game/g_active.c =================================================================== --- src/game/g_active.c (revision 838) +++ src/game/g_active.c (working copy) @@ -954,6 +954,195 @@ /* ============== + G_UnlaggedStore + + Called on every ClientEndFrame() for an active client. Stores position + data for the client at that into client->unlaggedHist[] and the time + into level.unlaggedTimes[]. This data is used by G_UnlaggedCalc() +============== +*/ +void G_UnlaggedStore( void ) +{ + int i = 0; + gentity_t *ent; + unlagged_t *save; + + if( !g_unlagged.integer ) + return; + + if( ++level.unlaggedIndex >= MAX_UNLAGGED_MARKERS ) + level.unlaggedIndex = 0; + + level.unlaggedTimes[ level.unlaggedIndex ] = level.time; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + memset( &ent->client->unlaggedHist[ level.unlaggedIndex ], 0, + sizeof( unlagged_t ) ); + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + save = &ent->client->unlaggedHist[ level.unlaggedIndex ]; + VectorCopy( ent->r.mins, save->mins ); + VectorCopy( ent->r.maxs, save->maxs ); + VectorCopy( ent->s.pos.trBase, save->origin ); + save->used = qtrue; + } +} + +// FIXME: this should be defined in q_shared.h +#define VectorLerp( f, s, e, r ) (\ + (r)[0]=(s)[0]+(f)*((e)[0]-(s)[0]),\ + (r)[1]=(s)[1]+(f)*((e)[1]-(s)[1]),\ + (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2])) + +/* +============== + G_UnlaggedCalc + + Loops through all active clients and estimates their position data + for rewindEnt's command time then stores it in client->unlaggedCalc + for each of the active clients +============== +*/ +static void G_UnlaggedCalc( gentity_t *rewindEnt ) +{ + int i = 0; + gentity_t *ent; + int startIndex = level.unlaggedIndex; + int stopIndex = -1; + int frameMsec = 0; + float lerp = 0.5f; + + if( !g_unlagged.integer ) + return; + + for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ ) + { + if( level.unlaggedTimes[ startIndex ] <= rewindEnt->client->attackTime ) + break; + stopIndex = startIndex; + if( --startIndex <= -1 ) + startIndex = MAX_UNLAGGED_MARKERS - 1; + } + if( i == MAX_UNLAGGED_MARKERS ) + { + // if we searched all markers and the oldest one still isn't old enough + // just use the oldest marker with no lerping + lerp = 0.0f; + } + + // client is on the current frame, no need for unlagged + if( stopIndex == -1 ) + return; + + // lerp between two markers + frameMsec = level.unlaggedTimes[ stopIndex ] - + level.unlaggedTimes[ startIndex ]; + if( frameMsec > 0 ) + { + lerp = ( ( float ) + ( rewindEnt->client->attackTime - level.unlaggedTimes[ startIndex ] ) ) + / ( float ) frameMsec; + } + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + ent->client->unlaggedCalc.used = qfalse; + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + if( !ent->client->unlaggedHist[ startIndex ].used ) + continue; + + // between two unlagged markers + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].mins, + ent->client->unlaggedHist[ stopIndex ].mins, + ent->client->unlaggedCalc.mins ); + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].maxs, + ent->client->unlaggedHist[ stopIndex ].maxs, + ent->client->unlaggedCalc.maxs ); + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].origin, + ent->client->unlaggedHist[ stopIndex ].origin, + ent->client->unlaggedCalc.origin ); + + ent->client->unlaggedCalc.used = qtrue; + } +} + +/* +============== + G_UnlaggedOff + + Reverses the changes made to all active clients by G_UnlaggedOn() +============== +*/ +void G_UnlaggedOff( void ) +{ + int i = 0; + gentity_t *ent; + + if( !g_unlagged.integer ) + return; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + if( !ent->client->unlaggedBackup.used ) + continue; + VectorCopy( ent->client->unlaggedBackup.mins, ent->r.mins ); + VectorCopy( ent->client->unlaggedBackup.maxs, ent->r.maxs ); + VectorCopy( ent->client->unlaggedBackup.origin, ent->r.currentOrigin ); + ent->client->unlaggedBackup.used = qfalse; + trap_LinkEntity( ent ); + } +} + +/* +============== + G_UnlaggedOn + + Called after G_UnlaggedCalc() to apply the calculated values to all active + clients. Once finished tracing, G_UnlaggedOff() must be called to restore + the clients' position data +============== +*/ +void G_UnlaggedOn( void ) +{ + int i = 0; + gentity_t *ent; + + if( !g_unlagged.integer ) + return; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + if( !ent->client->unlaggedCalc.used ) + continue; + if( ent->client->unlaggedBackup.used ) + continue; + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + + VectorCopy( ent->r.mins, ent->client->unlaggedBackup.mins ); + VectorCopy( ent->r.maxs, ent->client->unlaggedBackup.maxs ); + VectorCopy( ent->r.currentOrigin, ent->client->unlaggedBackup.origin ); + ent->client->unlaggedBackup.used = qtrue; + + VectorCopy( ent->client->unlaggedCalc.mins, ent->r.mins ); + VectorCopy( ent->client->unlaggedCalc.maxs, ent->r.maxs ); + VectorCopy( ent->client->unlaggedCalc.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } +} + +/* +============== ClientThink This will be called once for each client frame, which will @@ -1002,6 +1191,8 @@ if( msec > 200 ) msec = 200; + client->attackTime = ucmd->serverTime; + if( pmove_msec.integer < 8 ) trap_Cvar_Set( "pmove_msec", "8" ); else if( pmove_msec.integer > 33 ) @@ -1039,6 +1230,9 @@ if( !ClientInactivityTimer( client ) ) return; + // calculate where ent is currently seeing all the other active clients + G_UnlaggedCalc( ent ); + if( client->noclip ) client->ps.pm_type = PM_NOCLIP; else if( client->ps.stats[ STAT_HEALTH ] <= 0 ) Index: src/game/g_main.c =================================================================== --- src/game/g_main.c (revision 838) +++ src/game/g_main.c (working copy) @@ -103,6 +103,8 @@ vmCvar_t g_alienStage2Threshold; vmCvar_t g_alienStage3Threshold; +vmCvar_t g_unlagged; + vmCvar_t g_disabledEquipment; vmCvar_t g_disabledClasses; vmCvar_t g_disabledBuildables; @@ -210,6 +212,8 @@ { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse }, { &g_alienStage2Threshold, "g_alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, 0, 0, qfalse }, { &g_alienStage3Threshold, "g_alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, 0, 0, qfalse }, + + { &g_unlagged, "g_unlagged", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM, 0, qfalse }, { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM, 0, qfalse }, @@ -2173,6 +2177,8 @@ end = trap_Milliseconds(); + G_UnlaggedStore( ); + //TA: G_CountSpawns( ); G_CalculateBuildPoints( ); Index: src/game/g_weapon.c =================================================================== --- src/game/g_weapon.c (revision 838) +++ src/game/g_weapon.c (working copy) @@ -179,7 +179,8 @@ VectorMA( muzzle, range, forward, end ); - trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); + G_UnlaggedTrace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); + if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -223,7 +224,7 @@ VectorMA( end, r, right, end ); VectorMA( end, u, up, end ); - trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedTrace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -308,8 +309,9 @@ SnapVector( tent->s.origin2 ); tent->s.eventParm = rand() & 255; // seed for spread pattern tent->s.otherEntityNum = ent->s.number; - + G_UnlaggedOn(); ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent ); + G_UnlaggedOff(); } /* @@ -329,7 +331,7 @@ VectorMA( muzzle, 8192 * 16, forward, end ); - trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedTrace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -482,7 +484,7 @@ VectorMA( muzzle, 8192 * 16, forward, end ); - trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedTrace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -534,7 +536,7 @@ VectorMA( muzzle, PAINSAW_RANGE, forward, end ); - trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedTrace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -805,7 +807,7 @@ VectorMA( muzzle, LEVEL0_BITE_RANGE, forward, end ); - trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); + G_UnlaggedTrace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); if( tr.surfaceFlags & SURF_NOIMPACT ) return qfalse; @@ -878,7 +880,8 @@ VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end ); - trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedTrace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -939,6 +942,7 @@ VectorAdd( ent->client->ps.origin, range, maxs ); VectorSubtract( ent->client->ps.origin, range, mins ); + G_UnlaggedOn( ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { @@ -967,6 +971,7 @@ } } } + G_UnlaggedOff( ); } @@ -1000,6 +1005,7 @@ VectorAdd( ent->s.origin, range, maxs ); VectorSubtract( ent->s.origin, range, mins ); + G_UnlaggedOn( ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) @@ -1038,11 +1044,11 @@ // enemy is already targetted if( foundOldTarget ) continue; - + G_UnlaggedOff( ); return enemy; } } - + G_UnlaggedOff( ); return NULL; } @@ -1231,7 +1237,7 @@ VectorMA( muzzle, LEVEL2_AREAZAP_RANGE, forward, end ); - trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); + G_UnlaggedTrace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -1291,7 +1297,8 @@ VectorMA( muzzle, LEVEL3_POUNCE_RANGE, forward, end ); - trap_Trace( &tr, ent->s.origin, mins, maxs, end, ent->s.number, MASK_SHOT ); + G_UnlaggedTrace( &tr, ent->s.origin, mins, maxs, end, ent->s.number, + MASK_SHOT ); //miss if( tr.fraction >= 1.0 )