summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrey Nazarov <skuller@skuller.net>2011-04-25 12:18:33 +0400
committerAndrey Nazarov <skuller@skuller.net>2011-04-26 16:00:37 +0400
commitc8a0069ea58669e7adc828d1c2fc1a179f21e247 (patch)
tree3852c7f461b48aefc45baecda0b47d17f0100075 /src
parent396285b78b11f7bd2e6f6cf0535c386053d84f43 (diff)
Add client demo seeking support.
Add ‘cl_demosnaps’ cvar. Add ‘seek’ command. Move demo clenup code into cl_demo.c. Client demo recorder now always starts at frame 1.
Diffstat (limited to 'src')
-rw-r--r--src/cl_demo.c458
-rw-r--r--src/cl_ents.c8
-rw-r--r--src/cl_local.h10
-rw-r--r--src/cl_main.c21
-rw-r--r--src/cl_parse.c109
5 files changed, 492 insertions, 114 deletions
diff --git a/src/cl_demo.c b/src/cl_demo.c
index 5cf8c61..4a1c95f 100644
--- a/src/cl_demo.c
+++ b/src/cl_demo.c
@@ -27,6 +27,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
static byte demo_buffer[MAX_PACKETLEN_WRITABLE];
static int demo_extra;
+static cvar_t *cl_demosnaps;
+
// =========================================================================
/*
@@ -68,14 +70,8 @@ fail:
return qfalse;
}
-/*
-=============
-CL_EmitPacketEntities
-
-Writes a delta update of an entity_state_t list to the message.
-=============
-*/
-static void CL_EmitPacketEntities( server_frame_t *from, server_frame_t *to ) {
+// writes a delta update of an entity_state_t list to the message.
+static void emit_packet_entities( server_frame_t *from, server_frame_t *to ) {
entity_state_t *oldent, *newent;
int oldindex, newindex;
int oldnum, newnum;
@@ -135,6 +131,27 @@ static void CL_EmitPacketEntities( server_frame_t *from, server_frame_t *to ) {
MSG_WriteShort( 0 ); // end of packetentities
}
+static void emit_delta_frame( server_frame_t *from, server_frame_t *to,
+ int fromnum, int tonum )
+{
+ MSG_WriteByte( svc_frame );
+ MSG_WriteLong( tonum );
+ MSG_WriteLong( fromnum ); // what we are delta'ing from
+ MSG_WriteByte( 0 ); // rate dropped packets
+
+ // send over the areabits
+ MSG_WriteByte( to->areabytes );
+ MSG_WriteData( to->areabits, to->areabytes );
+
+ // delta encode the playerstate
+ MSG_WriteByte( svc_playerinfo );
+ MSG_WriteDeltaPlayerstate_Default( from ? &from->ps : NULL, &to->ps );
+
+ // delta encode the entities
+ MSG_WriteByte( svc_packetentities );
+ emit_packet_entities( from, to );
+}
+
/*
====================
CL_EmitDemoFrame
@@ -147,19 +164,19 @@ void CL_EmitDemoFrame( void ) {
player_state_t *oldstate;
int lastframe;
- if( cls.demo.paused ) {
+ if( cls.demo.paused )
return;
- }
- if( cl.demoframe < 0 ) {
+ // the first frame is delta uncompressed
+ if( !cls.demo.frames_written ) {
oldframe = NULL;
oldstate = NULL;
lastframe = -1;
} else {
- oldframe = &cl.frames[cl.demoframe & UPDATE_MASK];
+ oldframe = &cl.frames[cls.demo.last_frame & UPDATE_MASK];
oldstate = &oldframe->ps;
- lastframe = cl.demoframe + cl.demodelta;
- if( oldframe->number != cl.demoframe || !oldframe->valid ||
+ lastframe = cls.demo.frames_written;
+ if( oldframe->number != cls.demo.last_frame || !oldframe->valid ||
cl.numEntityStates - oldframe->firstEntity > MAX_PARSE_ENTITIES )
{
oldframe = NULL;
@@ -168,42 +185,34 @@ void CL_EmitDemoFrame( void ) {
}
}
- MSG_WriteByte( svc_frame );
- MSG_WriteLong( cl.frame.number + cl.demodelta );
- MSG_WriteLong( lastframe ); // what we are delta'ing from
- MSG_WriteByte( 0 ); // rate dropped packets
-
- // send over the areabits
- MSG_WriteByte( cl.frame.areabytes );
- MSG_WriteData( cl.frame.areabits, cl.frame.areabytes );
-
- // delta encode the playerstate
- MSG_WriteByte( svc_playerinfo );
- MSG_WriteDeltaPlayerstate_Default( oldstate, &cl.frame.ps );
-
- // delta encode the entities
- MSG_WriteByte( svc_packetentities );
- CL_EmitPacketEntities( oldframe, &cl.frame );
+ // we add 1 to frame number because frame 0 can't be used
+ emit_delta_frame( oldframe, &cl.frame, lastframe, cls.demo.frames_written + 1 );
if( cls.demo.buffer.cursize + msg_write.cursize > cls.demo.buffer.maxsize ) {
Com_DPrintf( "Demo frame overflowed\n" );
cls.demo.frames_dropped++;
} else {
SZ_Write( &cls.demo.buffer, msg_write.data, msg_write.cursize );
- cl.demoframe = cl.frame.number;
+ cls.demo.last_frame = cl.frame.number;
cls.demo.frames_written++;
}
SZ_Clear( &msg_write );
}
-static void CL_EmitZeroFrame( void ) {
- cl.demodelta++; // insert new zero frame
+static void emit_zero_frame( void ) {
+ int lastframe;
+
+ // the first frame is delta uncompressed
+ if( !cls.demo.frames_written )
+ lastframe = -1;
+ else
+ lastframe = cls.demo.frames_written;
MSG_WriteByte( svc_frame );
- MSG_WriteLong( cl.frame.number + cl.demodelta );
- MSG_WriteLong( cl.frame.number + cl.demodelta - 1 ); // what we are delta'ing from
- MSG_WriteByte( 0 ); // rate dropped packets
+ MSG_WriteLong( cls.demo.frames_written + 1 );
+ MSG_WriteLong( lastframe ); // what we are delta'ing from
+ MSG_WriteByte( 0 ); // rate dropped packets
// send over the areabits
MSG_WriteByte( cl.frame.areabytes );
@@ -363,10 +372,6 @@ static void CL_Record_f( void ) {
demo_extra = 0;
- // the first frame will be delta uncompressed
- cl.demoframe = -1;
- cl.demodelta = 0;
-
// clear dirty configstrings
memset( cl.dcs, 0, sizeof( cl.dcs ) );
@@ -439,53 +444,61 @@ static void CL_Record_f( void ) {
// the rest of the demo file will be individual frames
}
-static void CL_Suspend_f( void ) {
+static void flush_dirty_configstrings( void ) {
int i, j, index;
- size_t length, total = 0;
-
- if( !cls.demo.recording ) {
- Com_Printf( "Not recording a demo.\n" );
- return;
- }
- if( !cls.demo.paused ) {
- Com_Printf( "Suspended demo.\n" );
- cls.demo.paused = qtrue;
- return;
- }
+ size_t len, total = 0;
- // XXX: embed these in frame instead?
for( i = 0; i < CS_BITMAP_LONGS; i++ ) {
- if( (( uint32_t * )cl.dcs)[i] == 0 ) {
+ if( ((uint32_t *)cl.dcs)[i] == 0 )
continue;
- }
+
index = i << 5;
for( j = 0; j < 32; j++, index++ ) {
- if( !Q_IsBitSet( cl.dcs, index ) ) {
+ if( !Q_IsBitSet( cl.dcs, index ) )
continue;
- }
- length = strlen( cl.configstrings[index] );
- if( length > MAX_QPATH ) {
- length = MAX_QPATH;
- }
- if( msg_write.cursize + length + 4 > MAX_PACKETLEN_WRITABLE_DEFAULT ) {
+
+ len = strlen( cl.configstrings[index] );
+ if( len > MAX_QPATH )
+ len = MAX_QPATH;
+
+ if( msg_write.cursize + len + 4 > MAX_PACKETLEN_WRITABLE_DEFAULT ) {
if( !CL_WriteDemoMessage( &msg_write ) )
return;
}
+
MSG_WriteByte( svc_configstring );
MSG_WriteShort( index );
- MSG_WriteData( cl.configstrings[index], length );
+ MSG_WriteData( cl.configstrings[index], len );
MSG_WriteByte( 0 );
- total += length + 4;
+ total += len + 4;
}
}
- // write it to the demo file
- if( !CL_WriteDemoMessage( &msg_write ) )
+ CL_WriteDemoMessage( &msg_write );
+
+ Com_DPrintf( "Flushed %"PRIz" bytes\n", total );
+}
+
+static void CL_Suspend_f( void ) {
+ if( !cls.demo.recording ) {
+ Com_Printf( "Not recording a demo.\n" );
+ return;
+ }
+
+ if( !cls.demo.paused ) {
+ Com_Printf( "Suspended demo recording.\n" );
+ cls.demo.paused = qtrue;
+ return;
+ }
+
+ // XXX: embed these in frame instead?
+ flush_dirty_configstrings();
+
+ if( !cls.demo.recording )
return;
- Com_Printf( "Resumed demo (%"PRIz" bytes flushed).\n", total );
+ Com_Printf( "Resumed demo recording.\n" );
- cl.demodelta += cl.demoframe - cl.frame.number; // do not create holes
cls.demo.paused = qfalse;
// clear dirty configstrings
@@ -585,33 +598,27 @@ static int read_next_message( qhandle_t f ) {
return 1;
}
-static void parse_next_message( void ) {
- int ret;
-
- ret = read_next_message( cls.demo.playback );
- if( ret <= 0 ) {
- char *s = Cvar_VariableString( "nextserver" );
+static void finish_demo( int ret ) {
+ char *s = Cvar_VariableString( "nextserver" );
- if( !s[0] ) {
- if( ret == 0 ) {
- Com_Error( ERR_DISCONNECT, "Demo finished" );
- } else {
- Com_Error( ERR_DROP, "Couldn't read demo: %s", Q_ErrorString( ret ) );
- }
+ if( !s[0] ) {
+ if( ret == 0 ) {
+ Com_Error( ERR_DISCONNECT, "Demo finished" );
+ } else {
+ Com_Error( ERR_DROP, "Couldn't read demo: %s", Q_ErrorString( ret ) );
}
+ }
- CL_Disconnect( ERR_RECONNECT );
-
- Cvar_Set( "nextserver", "" );
+ CL_Disconnect( ERR_RECONNECT );
- Cbuf_AddText( &cmd_buffer, s );
- Cbuf_AddText( &cmd_buffer, "\n" );
- Cbuf_Execute( &cmd_buffer );
- return;
- }
+ Cvar_Set( "nextserver", "" );
- CL_ParseServerMessage();
+ Cbuf_AddText( &cmd_buffer, s );
+ Cbuf_AddText( &cmd_buffer, "\n" );
+ Cbuf_Execute( &cmd_buffer );
+}
+static void update_status( void ) {
if( cls.demo.file_size ) {
off_t pos = FS_Tell( cls.demo.playback );
@@ -621,6 +628,20 @@ static void parse_next_message( void ) {
}
}
+static void parse_next_message( void ) {
+ int ret;
+
+ ret = read_next_message( cls.demo.playback );
+ if( ret <= 0 ) {
+ finish_demo( ret );
+ return;
+ }
+
+ CL_ParseServerMessage();
+
+ update_status();
+}
+
/*
====================
CL_PlayDemo_f
@@ -675,6 +696,8 @@ static void CL_PlayDemo_f( void ) {
parse_next_message();
}
+ memcpy( cl.baseconfigstrings, cl.configstrings, sizeof( cl.baseconfigstrings ) );
+
len = FS_Length( f );
ofs = FS_Tell( f );
if( len > 0 && ofs > 0 ) {
@@ -694,6 +717,223 @@ static void CL_Demo_c( genctx_t *ctx, int argnum ) {
}
}
+#define DEMO_FPS 10
+
+typedef struct {
+ list_t entry;
+ int framenum;
+ off_t filepos;
+ size_t msglen;
+ byte data[1];
+} demosnap_t;
+
+/*
+====================
+CL_EmitDemoSnapshot
+
+Periodically builds a fake demo packet used to reconstruct delta compression
+state, configstrings and layouts at the given server frame.
+====================
+*/
+void CL_EmitDemoSnapshot( void ) {
+ demosnap_t *snap;
+ off_t pos;
+ char *from, *to;
+ size_t len;
+ server_frame_t *lastframe, *frame;
+ int i, j, lastnum;
+
+ if( cl_demosnaps->integer <= 0 )
+ return;
+
+ if( cl.frame.number < cls.demo.last_snapshot + cl_demosnaps->integer * DEMO_FPS )
+ return;
+
+ if( !cls.demo.file_size )
+ return;
+
+ pos = FS_Tell( cls.demo.playback );
+ if( pos < cls.demo.file_offset )
+ return;
+
+ // write all the backups, since we can't predict what frame the next
+ // delta will come from
+ lastframe = NULL;
+ lastnum = -1;
+ for( i = 0; i < UPDATE_BACKUP; i++ ) {
+ j = cl.frame.number - ( UPDATE_BACKUP - 1 ) + i;
+ frame = &cl.frames[j & UPDATE_MASK];
+ if( frame->number != j || !frame->valid ||
+ cl.numEntityStates - frame->firstEntity > MAX_PARSE_ENTITIES )
+ {
+ continue;
+ }
+
+ emit_delta_frame( lastframe, frame, lastnum, j );
+ lastframe = frame;
+ lastnum = frame->number;
+ }
+
+ // write configstrings
+ for( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
+ from = cl.baseconfigstrings[i];
+ to = cl.configstrings[i];
+
+ if( !strcmp( from, to ) )
+ continue;
+
+ len = strlen( to );
+ if( len > MAX_QPATH )
+ len = MAX_QPATH;
+
+ MSG_WriteByte( svc_configstring );
+ MSG_WriteShort( i );
+ MSG_WriteData( to, len );
+ MSG_WriteByte( 0 );
+ }
+
+ // write layout
+ MSG_WriteByte( svc_layout );
+ MSG_WriteString( cl.layout );
+
+ snap = Z_Malloc( sizeof( *snap ) + msg_write.cursize - 1 );
+ snap->framenum = cl.frame.number;
+ snap->filepos = pos;
+ snap->msglen = msg_write.cursize;
+ memcpy( snap->data, msg_write.data, msg_write.cursize );
+ List_Append( &cls.demo.snapshots, &snap->entry );
+
+ Com_DPrintf( "[%d] snaplen %"PRIz"\n", cl.frame.number, msg_write.cursize );
+
+ SZ_Clear( &msg_write );
+
+ cls.demo.last_snapshot = cl.frame.number;
+}
+
+static demosnap_t *find_snapshot( int framenum ) {
+ demosnap_t *snap, *prev;
+
+ if( LIST_EMPTY( &cls.demo.snapshots ) )
+ return NULL;
+
+ prev = LIST_FIRST( demosnap_t, &cls.demo.snapshots, entry );
+
+ LIST_FOR_EACH( demosnap_t, snap, &cls.demo.snapshots, entry ) {
+ if( snap->framenum > framenum )
+ break;
+ prev = snap;
+ }
+
+ return prev;
+}
+
+static void CL_Seek_f( void ) {
+ demosnap_t *snap;
+ int i, j, ret, index, frames, dest, prev;
+ char *from, *to;
+
+ if( Cmd_Argc() < 2 ) {
+ Com_Printf( "Usage: %s [+-]<seconds>\n", Cmd_Argv( 0 ) );
+ return;
+ }
+
+ if( !cls.demo.playback ) {
+ Com_Printf( "Not playing a demo.\n" );
+ return;
+ }
+
+ frames = atoi( Cmd_Argv( 1 ) ) * DEMO_FPS;
+ if( !frames )
+ return;
+
+ // disable effects processing
+ cls.demo.seeking = qtrue;
+
+ // clear dirty configstrings
+ memset( cl.dcs, 0, sizeof( cl.dcs ) );
+
+ dest = cl.frame.number + frames;
+ prev = cl.frame.number;
+
+ Com_DPrintf( "[%d] seeking to %d\n", cl.frame.number, dest );
+
+ // seek to the previous most recent snapshot
+ if( frames < 0 || cls.demo.last_snapshot > cl.frame.number ) {
+ snap = find_snapshot( dest );
+
+ if( snap ) {
+ Com_DPrintf( "found snap at %d\n", snap->framenum );
+ ret = FS_Seek( cls.demo.playback, snap->filepos );
+ if( ret < 0 ) {
+ Com_EPrintf( "Couldn't seek demo: %s\n", Q_ErrorString( ret ) );
+ goto done;
+ }
+
+ // reset configstrings
+ for( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
+ from = cl.baseconfigstrings[i];
+ to = cl.configstrings[i];
+
+ if( !strcmp( from, to ) )
+ continue;
+
+ Q_SetBit( cl.dcs, i );
+ strcpy( to, from );
+ }
+
+ SZ_Init( &msg_read, snap->data, snap->msglen );
+ msg_read.cursize = snap->msglen;
+
+ CL_SeekDemoMessage();
+ Com_DPrintf( "[%d] after snap parse\n", cl.frame.number );
+ } else if( frames < 0 ) {
+ Com_Printf( "Couldn't seek backwards without snapshots!\n" );
+ goto done;
+ }
+ }
+
+ // skip forward to destination frame
+ while( cl.frame.number < dest ) {
+ ret = read_next_message( cls.demo.playback );
+ if( ret <= 0 ) {
+ finish_demo( ret );
+ return;
+ }
+
+ CL_SeekDemoMessage();
+ }
+
+ Com_DPrintf( "[%d] after skip\n", cl.frame.number );
+
+ // don't lerp to old
+ memset( &cl.oldframe, 0, sizeof( cl.oldframe ) );
+
+ // fix time delta
+ cl.serverdelta += cl.frame.number - prev;
+
+ CL_DeltaFrame();
+
+ // update dirty configstrings
+ for( i = 0; i < CS_BITMAP_LONGS; i++ ) {
+ if( ((uint32_t *)cl.dcs)[i] == 0 )
+ continue;
+
+ index = i << 5;
+ for( j = 0; j < 32; j++, index++ ) {
+ if( Q_IsBitSet( cl.dcs, index ) )
+ CL_UpdateConfigstring( index );
+ }
+ }
+
+ if( cls.demo.recording && !cls.demo.paused )
+ flush_dirty_configstrings();
+
+ update_status();
+
+done:
+ cls.demo.seeking = qfalse;
+}
+
static void parse_info_string( demoInfo_t *info, int clientNum, int index, const char *string ) {
size_t len;
char *p;
@@ -807,6 +1047,40 @@ fail:
// =========================================================================
+void CL_CleanupDemos( void ) {
+ demosnap_t *snap, *next;
+ size_t total;
+
+ if( cls.demo.recording ) {
+ CL_Stop_f();
+ }
+
+ if( cls.demo.playback ) {
+ FS_FCloseFile( cls.demo.playback );
+
+ if( com_timedemo->integer ) {
+ unsigned msec = Sys_Milliseconds();
+ float sec = ( msec - cls.demo.time_start ) * 0.001f;
+ float fps = cls.demo.time_frames / sec;
+
+ Com_Printf( "%u frames, %3.1f seconds: %3.1f fps\n",
+ cls.demo.time_frames, sec, fps );
+ }
+ }
+
+ total = 0;
+ LIST_FOR_EACH_SAFE( demosnap_t, snap, next, &cls.demo.snapshots, entry ) {
+ total += snap->msglen;
+ Z_Free( snap );
+ }
+
+ if( total )
+ Com_DPrintf( "Freed %"PRIz" bytes of snaps\n", total );
+
+ memset( &cls.demo, 0, sizeof( cls.demo ) );
+
+ List_Init( &cls.demo.snapshots );
+}
/*
====================
@@ -828,7 +1102,7 @@ void CL_DemoFrame( int msec ) {
// for syncing with audio comments, etc
demo_extra += msec;
if( demo_extra > 100 ) {
- CL_EmitZeroFrame();
+ emit_zero_frame();
demo_extra = 0;
}
}
@@ -855,6 +1129,7 @@ static const cmdreg_t c_demo[] = {
{ "record", CL_Record_f, CL_Demo_c },
{ "stop", CL_Stop_f },
{ "suspend", CL_Suspend_f },
+ { "seek", CL_Seek_f },
{ NULL }
};
@@ -865,7 +1140,10 @@ CL_InitDemos
====================
*/
void CL_InitDemos( void ) {
+ cl_demosnaps = Cvar_Get( "cl_demosnaps", "10", 0 );
+
Cmd_Register( c_demo );
+ List_Init( &cls.demo.snapshots );
}
diff --git a/src/cl_ents.c b/src/cl_ents.c
index 057f0f9..2b22547 100644
--- a/src/cl_ents.c
+++ b/src/cl_ents.c
@@ -174,8 +174,12 @@ static void CL_SetActiveState( void ) {
cl.initialSeq = cls.netchan->outgoing_sequence;
}
- // set initial cl.predicted_origin and cl.predicted_angles
- if( !cls.demo.playback ) {
+ if( cls.demo.playback ) {
+ // force initial snapshot
+ cls.demo.last_snapshot = INT_MIN;
+ CL_EmitDemoSnapshot();
+ } else {
+ // set initial cl.predicted_origin and cl.predicted_angles
VectorScale( cl.frame.ps.pmove.origin, 0.125f, cl.predicted_origin );
VectorScale( cl.frame.ps.pmove.velocity, 0.125f, cl.predicted_velocity );
if( cl.frame.ps.pmove.pm_type < PM_DEAD &&
diff --git a/src/cl_local.h b/src/cl_local.h
index 8fc810e..5c4db2e 100644
--- a/src/cl_local.h
+++ b/src/cl_local.h
@@ -149,8 +149,6 @@ typedef struct client_state_s {
int lastframe;
- int demoframe;
- int demodelta;
byte dcs[CS_BITMAP_BYTES];
// the client maintains its own idea of view angles, which are
@@ -206,6 +204,7 @@ typedef struct client_state_s {
int maxclients;
pmoveParams_t pmp;
+ char baseconfigstrings[MAX_CONFIGSTRINGS][MAX_QPATH];
char configstrings[MAX_CONFIGSTRINGS][MAX_QPATH];
char mapname[MAX_QPATH]; // short format - q2dm1, etc
@@ -371,11 +370,15 @@ typedef struct client_static_s {
unsigned frames_written;
unsigned frames_dropped;
unsigned messages_dropped;
+ int last_snapshot;
+ int last_frame;
int file_size;
int file_offset;
int file_percent;
sizebuf_t buffer;
+ list_t snapshots;
qboolean paused;
+ qboolean seeking;
} demo;
} client_static_t;
@@ -568,6 +571,7 @@ extern mz_params_t mz;
extern snd_params_t snd;
void CL_ParseServerMessage (void);
+void CL_SeekDemoMessage( void );
//
// cl_ents.c
@@ -762,9 +766,11 @@ void CL_ParticleSteamEffect2(cl_sustain_t *self);
// cl_demo.c
//
void CL_InitDemos( void );
+void CL_CleanupDemos( void );
void CL_DemoFrame( int msec );
qboolean CL_WriteDemoMessage( sizebuf_t *buf );
void CL_EmitDemoFrame( void );
+void CL_EmitDemoSnapshot( void );
void CL_Stop_f( void );
demoInfo_t *CL_GetDemoInfo( const char *path, demoInfo_t *info );
diff --git a/src/cl_main.c b/src/cl_main.c
index 7a334b8..ca01da7 100644
--- a/src/cl_main.c
+++ b/src/cl_main.c
@@ -661,26 +661,9 @@ void CL_Disconnect( error_type_t type ) {
cls.netchan = NULL;
}
- // stop demo
- if( cls.demo.recording ) {
- CL_Stop_f();
- }
-
- if( cls.demo.playback ) {
- FS_FCloseFile( cls.demo.playback );
-
- if( com_timedemo->integer ) {
- unsigned msec = Sys_Milliseconds();
- float sec = ( msec - cls.demo.time_start ) * 0.001f;
- float fps = cls.demo.time_frames / sec;
+ // stop playback and/or recording
+ CL_CleanupDemos();
- Com_Printf( "%u frames, %3.1f seconds: %3.1f fps\n",
- cls.demo.time_frames, sec, fps );
- }
- }
-
- memset( &cls.demo, 0, sizeof( cls.demo ) );
-
// stop download
CL_CleanupDownloads();
diff --git a/src/cl_parse.c b/src/cl_parse.c
index 22373eb..d88d842 100644
--- a/src/cl_parse.c
+++ b/src/cl_parse.c
@@ -375,7 +375,11 @@ static void CL_ParseFrame( int extrabits ) {
cl.oldframe = cl.frame;
cl.frame = frame;
- CL_DeltaFrame();
+ if( cls.demo.playback )
+ CL_EmitDemoSnapshot();
+
+ if( !cls.demo.seeking )
+ CL_DeltaFrame();
}
/*
@@ -407,6 +411,11 @@ static void CL_ParseConfigstring( int index ) {
len = maxlen - 1;
}
+ if( cls.demo.seeking ) {
+ Q_SetBit( cl.dcs, index );
+ return;
+ }
+
if( cls.demo.recording && cls.demo.paused ) {
Q_SetBit( cl.dcs, index );
}
@@ -1234,3 +1243,101 @@ void CL_ParseServerMessage( void ) {
}
}
+/*
+=====================
+CL_SeekDemoMessage
+
+A variant of ParseServerMessage that skips over non-important action messages,
+used for seeking in demos.
+=====================
+*/
+void CL_SeekDemoMessage( void ) {
+ int cmd, extrabits;
+ int index;
+
+#ifdef _DEBUG
+ if( cl_shownet->integer == 1 ) {
+ Com_LPrintf( PRINT_DEVELOPER, "%"PRIz" ", msg_read.cursize );
+ } else if( cl_shownet->integer > 1 ) {
+ Com_LPrintf( PRINT_DEVELOPER, "------------------\n" );
+ }
+#endif
+
+//
+// parse the message
+//
+ while( 1 ) {
+ if( msg_read.readcount > msg_read.cursize ) {
+ Com_Error( ERR_DROP, "%s: read past end of server message", __func__ );
+ }
+
+ if( ( cmd = MSG_ReadByte() ) == -1 ) {
+ SHOWNET( 1, "%3"PRIz":END OF MESSAGE\n", msg_read.readcount - 1 );
+ break;
+ }
+
+ extrabits = cmd >> SVCMD_BITS;
+ cmd &= SVCMD_MASK;
+
+#ifdef _DEBUG
+ if( cl_shownet->integer > 1 ) {
+ MSG_ShowSVC( cmd );
+ }
+#endif
+
+ // other commands
+ switch( cmd ) {
+ default:
+ Com_Error( ERR_DROP, "%s: illegible server message: %d", __func__, cmd );
+ break;
+
+ case svc_nop:
+ break;
+
+ case svc_disconnect:
+ case svc_reconnect:
+ Com_Error( ERR_DISCONNECT, "Server disconnected" );
+ break;
+
+ case svc_print:
+ MSG_ReadByte();
+ // fall thorugh
+
+ case svc_centerprint:
+ case svc_stufftext:
+ MSG_ReadString( NULL, 0 );
+ break;
+
+ case svc_configstring:
+ index = MSG_ReadShort();
+ CL_ParseConfigstring( index );
+ break;
+
+ case svc_sound:
+ CL_ParseStartSoundPacket();
+ break;
+
+ case svc_temp_entity:
+ CL_ParseTEntPacket();
+ break;
+
+ case svc_muzzleflash:
+ case svc_muzzleflash2:
+ CL_ParseMuzzleFlashPacket( 0 );
+ break;
+
+ case svc_frame:
+ CL_ParseFrame( extrabits );
+ continue;
+
+ case svc_inventory:
+ CL_ParseInventory();
+ break;
+
+ case svc_layout:
+ CL_ParseLayout();
+ break;
+
+ }
+ }
+}