diff --git a/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c index 2f3dc10..bce36bb 100644 --- a/code/cgame/cg_snapshot.c +++ b/code/cgame/cg_snapshot.c @@ -402,8 +402,34 @@ void CG_ProcessSnapshots( void ) { 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.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" ); } } diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c index 6c55476..8626b3f 100644 --- a/code/client/cl_parse.c +++ b/code/client/cl_parse.c @@ -308,6 +308,7 @@ void CL_ParseSnapshot( msg_t *msg ) { cl.serverTimeDelta = cl.snap.serverTime - cls.realtime; cl.oldServerTime = cl.snap.serverTime; cl.serverTime = cl.snap.serverTime; + cl.oldFrameServerTime = cl.snap.serverTime; // prevent backwards time error on seek } } diff --git a/code/server/sv_netdemo.c b/code/server/sv_netdemo.c index 0c7f8fb..3409341 100644 --- a/code/server/sv_netdemo.c +++ b/code/server/sv_netdemo.c @@ -396,7 +396,8 @@ static qboolean SVD_StartRecording( const char *demoname ) { } else { demo.keyframeInterval = 0; } - demo.framesSinceKeyframe = 0; + // first frame is always a keyframe (makes beginning seekable) + demo.framesSinceKeyframe = demo.keyframeInterval; demo.numKeyframes = 0; } @@ -1083,13 +1084,24 @@ void SVD_Seek_f( void ) { // toggle SERVERCOUNT to reset client time delta svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; - // mark that we seeked — next SVD_ReadFrame sets SNAPFLAG_RESET_ENTITIES 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; - 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; } + // manual pause — don't consume demo data if ( demo.paused ) { return qfalse;