Fix seeking: snapshot timing, backwards time, entity cleanup
Server: - Reset nextSnapshotTime for active clients after seek so SV_SendClientMessages doesn't skip sending (was comparing future nextSnapshotTime against past svs.time) - First frame always a keyframe (beginning seekable) - Keyframes during normal playback only reset delta state, no SNAPFLAG_RESET_ENTITIES (no visual glitch every 5 sec) Engine client: - Reset cl.oldFrameServerTime on SERVERCOUNT toggle to prevent "time went backwards" error in CL_SetCGameTime cgame: - Handle backwards time in CG_ProcessSnapshots for demo seek: accept the time jump, reset cg.time, clear all entity state (currentValid, interpolate, time-dependent fields), clear local entities (particles, gibs, smoke), wait for next snapshot - Prevents CG_InterpolateEntityPosition NULL nextSnap error Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
628007ec57
commit
a8bfa738b0
3 changed files with 46 additions and 6 deletions
|
|
@ -402,8 +402,34 @@ void CG_ProcessSnapshots( void ) {
|
||||||
CG_SetNextSnap( snap );
|
CG_SetNextSnap( snap );
|
||||||
|
|
||||||
|
|
||||||
// if time went backwards, we have a level restart
|
// if time went backwards, we have a level restart or demo seek
|
||||||
if ( cg.nextSnap->serverTime < cg.snap->serverTime ) {
|
if ( cg.nextSnap->serverTime < cg.snap->serverTime ) {
|
||||||
|
if ( cg.svDemoPlayback ) {
|
||||||
|
// demo seek — discard old snap, use nextSnap as current,
|
||||||
|
// and wait for another snapshot before rendering
|
||||||
|
cg.snap = cg.nextSnap;
|
||||||
|
cg.nextSnap = NULL;
|
||||||
|
cg.time = cg.snap->serverTime;
|
||||||
|
// reset all entity state and time-dependent fields
|
||||||
|
{
|
||||||
|
int e;
|
||||||
|
for ( e = 0; e < MAX_GENTITIES; e++ ) {
|
||||||
|
cg_entities[e].currentValid = qfalse;
|
||||||
|
cg_entities[e].interpolate = qfalse;
|
||||||
|
cg_entities[e].muzzleFlashTime = 0;
|
||||||
|
cg_entities[e].trailTime = 0;
|
||||||
|
cg_entities[e].dustTrailTime = 0;
|
||||||
|
cg_entities[e].miscTime = 0;
|
||||||
|
cg_entities[e].snapShotTime = 0;
|
||||||
|
cg_entities[e].previousEvent = 0;
|
||||||
|
cg_entities[e].teleportFlag = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// clear local entities (particles, gibs, etc.)
|
||||||
|
// they reference old times and would render incorrectly
|
||||||
|
CG_InitLocalEntities();
|
||||||
|
break; // exit loop, wait for next snapshot
|
||||||
|
}
|
||||||
CG_Error( "CG_ProcessSnapshots: Server time went backwards" );
|
CG_Error( "CG_ProcessSnapshots: Server time went backwards" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,7 @@ void CL_ParseSnapshot( msg_t *msg ) {
|
||||||
cl.serverTimeDelta = cl.snap.serverTime - cls.realtime;
|
cl.serverTimeDelta = cl.snap.serverTime - cls.realtime;
|
||||||
cl.oldServerTime = cl.snap.serverTime;
|
cl.oldServerTime = cl.snap.serverTime;
|
||||||
cl.serverTime = cl.snap.serverTime;
|
cl.serverTime = cl.snap.serverTime;
|
||||||
|
cl.oldFrameServerTime = cl.snap.serverTime; // prevent backwards time error on seek
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -396,7 +396,8 @@ static qboolean SVD_StartRecording( const char *demoname ) {
|
||||||
} else {
|
} else {
|
||||||
demo.keyframeInterval = 0;
|
demo.keyframeInterval = 0;
|
||||||
}
|
}
|
||||||
demo.framesSinceKeyframe = 0;
|
// first frame is always a keyframe (makes beginning seekable)
|
||||||
|
demo.framesSinceKeyframe = demo.keyframeInterval;
|
||||||
demo.numKeyframes = 0;
|
demo.numKeyframes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1083,13 +1084,24 @@ void SVD_Seek_f( void ) {
|
||||||
// toggle SERVERCOUNT to reset client time delta
|
// toggle SERVERCOUNT to reset client time delta
|
||||||
svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
|
svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
|
||||||
|
|
||||||
// mark that we seeked — next SVD_ReadFrame sets SNAPFLAG_RESET_ENTITIES
|
|
||||||
demo.seeked = qtrue;
|
demo.seeked = qtrue;
|
||||||
// on the next SV_Frame. SVD_ReadFrame will detect the keyframe flag
|
|
||||||
// and set SNAPFLAG_RESET_ENTITIES + reset delta state.
|
|
||||||
demo.endOfDemo = qfalse;
|
demo.endOfDemo = qfalse;
|
||||||
|
|
||||||
Com_Printf( "Seeked to keyframe at time %d.\n", demo.keyframeTimes[bestKf] );
|
// reset client snapshot timing so SV_SendClientMessages doesn't
|
||||||
|
// skip sending (nextSnapshotTime was in the future, now svs.time is past)
|
||||||
|
{
|
||||||
|
int j;
|
||||||
|
for ( j = 0; j < sv_maxclients->integer; j++ ) {
|
||||||
|
if ( svs.clients[j].state >= CS_ACTIVE ) {
|
||||||
|
svs.clients[j].nextSnapshotTime = svs.time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure one frame runs on next SV_Frame
|
||||||
|
sv.timeResidual = 1000 / sv_fps->integer;
|
||||||
|
|
||||||
|
Com_Printf( "Seeked to time %d.\n", svs.time );
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -1101,6 +1113,7 @@ qboolean SVD_PlaybackFrame( void ) {
|
||||||
return qfalse;
|
return qfalse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// manual pause — don't consume demo data
|
// manual pause — don't consume demo data
|
||||||
if ( demo.paused ) {
|
if ( demo.paused ) {
|
||||||
return qfalse;
|
return qfalse;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue