Auto-record demos with svdemo_autorecord cvar

Set svdemo_autorecord 1 to automatically record a demo on every
map load. Demo files are named <mapname>_YYYYMMDD_HHMMSS.svdm
in the svdemos/ directory.

Refactored SVD_Record_f to use SVD_StartRecording helper so both
manual and auto recording share the same code path. Also fixed
prevPlayers delta state not being cleared on recording start.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
serge_shubin 2026-03-23 05:11:27 +08:00
parent 6fc96e7070
commit e58414f564
3 changed files with 63 additions and 14 deletions

View file

@ -348,6 +348,7 @@ void SVD_Record_f( void );
void SVD_StopRecord_f( void );
void SVD_RecordFrame( void );
void SVD_ResetDeltaState( void );
void SVD_AutoRecord( void );
void SVD_Play_f( void );
void SVD_StopPlay_f( void );
void SVD_Stop_f( void );

View file

@ -552,6 +552,9 @@ void SV_SpawnServer( char *server, qboolean killBots ) {
Hunk_SetMark();
Com_Printf ("-----------------------------------\n");
// auto-record demo if enabled
SVD_AutoRecord();
}
/*
@ -618,6 +621,9 @@ void SV_Init (void) {
sv_lanForceRate = Cvar_Get ("sv_lanForceRate", "1", CVAR_ARCHIVE );
sv_strictAuth = Cvar_Get ("sv_strictAuth", "1", CVAR_ARCHIVE );
// server-side demo auto-recording
Cvar_Get ("svdemo_autorecord", "0", CVAR_ARCHIVE);
// initialize bot cvars so they are listed and can be set before loading the botlib
SV_BotInitCvars();

View file

@ -321,41 +321,83 @@ static void SVD_WriteFrame( fileHandle_t f ) {
// Recording commands
// ---------------------------------------------------------------
void SVD_Record_f( void ) {
char name[MAX_OSPATH];
char *s;
/*
Start recording a demo with the given name.
Returns qtrue on success.
*/
static qboolean SVD_StartRecording( const char *demoname ) {
char path[MAX_OSPATH];
if ( demo.recording ) {
Com_Printf( "Already recording a server demo.\n" );
return;
return qfalse;
}
if ( sv.state != SS_GAME ) {
Com_Printf( "Not running a server.\n" );
return;
return qfalse;
}
Com_sprintf( path, sizeof(path), "svdemos/%s.svdm", demoname );
demo.recordFile = FS_FOpenFileWrite( path );
if ( !demo.recordFile ) {
Com_Printf( "ERROR: couldn't open %s for writing.\n", path );
return qfalse;
}
Com_Printf( "Recording server demo to %s\n", path );
demo.recording = qtrue;
// clear delta state for fresh recording
Com_Memset( demo.prevEntities, 0, sizeof(demo.prevEntities) );
Com_Memset( demo.prevPlayers, 0, sizeof(demo.prevPlayers) );
SVD_WriteHeader( demo.recordFile );
return qtrue;
}
void SVD_Record_f( void ) {
char *s;
s = Cmd_Argv(1);
if ( !s[0] ) {
Com_Printf( "Usage: svdemo_record <demoname>\n" );
return;
}
Com_sprintf( name, sizeof(name), "svdemos/%s.svdm", s );
SVD_StartRecording( s );
}
demo.recordFile = FS_FOpenFileWrite( name );
if ( !demo.recordFile ) {
Com_Printf( "ERROR: couldn't open %s for writing.\n", name );
/*
Auto-record: called from SV_SpawnServer after the map is fully loaded.
Generates a name from map name + timestamp.
*/
void SVD_AutoRecord( void ) {
char demoname[MAX_OSPATH];
const char *mapname;
qtime_t now;
if ( demo.recording || demo.playing ) {
return;
}
Com_Printf( "Recording server demo to %s\n", name );
demo.recording = qtrue;
if ( !Cvar_VariableIntegerValue( "svdemo_autorecord" ) ) {
return;
}
// clear delta state for fresh recording
Com_Memset( demo.prevEntities, 0, sizeof(demo.prevEntities) );
if ( sv.state != SS_GAME ) {
return;
}
SVD_WriteHeader( demo.recordFile );
mapname = Cvar_VariableString( "mapname" );
Com_RealTime( &now );
Com_sprintf( demoname, sizeof(demoname), "%s_%04d%02d%02d_%02d%02d%02d",
mapname,
1900 + now.tm_year, 1 + now.tm_mon, now.tm_mday,
now.tm_hour, now.tm_min, now.tm_sec );
SVD_StartRecording( demoname );
}
void SVD_StopRecord_f( void ) {