From e58414f564ed7c2650cb6684b8d87d25057c5da9 Mon Sep 17 00:00:00 2001 From: serge_shubin Date: Mon, 23 Mar 2026 05:11:27 +0800 Subject: [PATCH] Auto-record demos with svdemo_autorecord cvar Set svdemo_autorecord 1 to automatically record a demo on every map load. Demo files are named _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) --- code/server/server.h | 1 + code/server/sv_init.c | 6 ++++ code/server/sv_netdemo.c | 70 ++++++++++++++++++++++++++++++++-------- 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/code/server/server.h b/code/server/server.h index 00ce6bd..ac787c9 100644 --- a/code/server/server.h +++ b/code/server/server.h @@ -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 ); diff --git a/code/server/sv_init.c b/code/server/sv_init.c index 2c66a0d..2271f6f 100644 --- a/code/server/sv_init.c +++ b/code/server/sv_init.c @@ -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(); diff --git a/code/server/sv_netdemo.c b/code/server/sv_netdemo.c index 9b70979..6ddae76 100644 --- a/code/server/sv_netdemo.c +++ b/code/server/sv_netdemo.c @@ -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 \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 ) {