Compare commits

..

No commits in common. "3d8291658f539285d511a5db45f64ff67312dc8f" and "6e13c747ba1a55477653d1f82fbcb834347cd806" have entirely different histories.

12 changed files with 168 additions and 165 deletions

View file

@ -1650,16 +1650,6 @@ static void CG_DrawDisconnect( void ) {
const char *s;
int w; // bk010215 - FIXME char message[1024];
// server demo playback: detect pause from frozen snapshot time
if ( cg.svDemoPlayback ) {
if ( cg.nextSnap && cg.nextSnap->serverTime == cg.snap->serverTime ) {
s = "Playback Paused";
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
CG_DrawBigString( 320 - w/2, 100, s, 1.0F );
}
return;
}
// draw the phone jack if we are completely past our buffers
cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1;
trap_GetUserCmd( cmdNum, &cmd );

View file

@ -288,9 +288,6 @@ void CL_ParseSnapshot( msg_t *msg ) {
cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse;
}
{
int oldSnapFlags = cl.snap.snapFlags;
// copy to the current good spot
cl.snap = newSnap;
cl.snap.ping = 999;
@ -302,15 +299,6 @@ void CL_ParseSnapshot( msg_t *msg ) {
break;
}
}
// if server count changed (map_restart or demo unpause), force-reset
// the time delta so the client doesn't slowly drift back to sync
if ( ( oldSnapFlags ^ newSnap.snapFlags ) & SNAPFLAG_SERVERCOUNT ) {
cl.serverTimeDelta = cl.snap.serverTime - cls.realtime;
cl.oldServerTime = cl.snap.serverTime;
cl.serverTime = cl.snap.serverTime;
}
}
// save the frame off in the backup array for later delta comparisons
cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap;

View file

@ -1034,12 +1034,6 @@ void ClientThink( int clientNum ) {
// phone jack if they don't get any for a while
ent->client->lastCmdTime = level.time;
// demo playback: don't run ClientThink_real — cgame owns
// the camera movement, G_RunFrame handles buttons and PVS origin
if ( g_svDemoPlaying.integer ) {
return;
}
if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) {
ClientThink_real( ent );
}

View file

@ -1013,6 +1013,7 @@ void ClientBegin( int clientNum ) {
client->pers.teamState.state = TEAM_BEGIN;
// demo playback: force all clients to spectator
// demo playback: force to spectator and update configstring
if ( g_svDemoPlaying.integer ) {
client->sess.sessionTeam = TEAM_SPECTATOR;
ClientUserinfoChanged( ent->client - level.clients );

View file

@ -662,7 +662,7 @@ void Cmd_Team_f( gentity_t *ent ) {
char s[MAX_TOKEN_CHARS];
// demo playback: only allow "team spectator" (to exit follow mode)
if ( g_svDemoPlaying.integer ) {
if ( trap_Cvar_VariableIntegerValue( "sv_demoplaying" ) ) {
char s2[MAX_TOKEN_CHARS];
if ( trap_Argc() == 2 ) {
trap_Argv( 1, s2, sizeof(s2) );

View file

@ -419,7 +419,7 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) {
G_RegisterCvars();
// signal server-side demo mode to cgame via configstring
if ( g_svDemoPlaying.integer ) {
if ( trap_Cvar_VariableIntegerValue( "sv_demoplaying" ) ) {
trap_SetConfigstring( CS_SVDEMO, "1" );
}
@ -1761,17 +1761,6 @@ int start, end;
VectorCopy( cl->pers.cmd.origin, e->s.pos.trBase );
VectorCopy( cl->pers.cmd.origin, e->r.currentOrigin );
}
// process spectator buttons for follow mode switching
// (pers.cmd is updated by ClientThink which still runs)
{
int oldButtons = cl->buttons;
cl->oldbuttons = cl->buttons;
cl->buttons = cl->pers.cmd.buttons;
// attack cycles follow targets
if ( ( cl->buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) {
Cmd_FollowCycle_f( e, 1 );
}
}
specEnt = e;
continue;
}
@ -1792,27 +1781,6 @@ int start, end;
}
}
// evaluate trajectories and link all recorded entities for PVS.
// SVD_ReadFrame injected entityState_t but didn't link because
// the server doesn't have BG_EvaluateTrajectory.
// scan up to MAX_GENTITIES because recorded entities may exceed
// level.num_entities (which only counts game-module-spawned entities).
for ( i = 0; i < MAX_GENTITIES; i++ ) {
ent = &g_entities[i];
if ( ent->s.eType == 0 && !ent->inuse ) {
continue;
}
// skip spectator slot
if ( ent->client && ent->client->pers.connected == CON_CONNECTED
&& ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
continue;
}
// compute currentOrigin from trajectory
BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );
ent->r.linked = qtrue;
trap_LinkEntity( ent );
}
// update rankings so follow mode can cycle through players
CalculateRanks();

View file

@ -183,12 +183,6 @@ G_WriteSessionData
void G_WriteSessionData( void ) {
int i;
// don't persist demo spectator sessions — the forced TEAM_SPECTATOR
// would carry over to the next normal game
if ( g_svDemoPlaying.integer ) {
return;
}
trap_Cvar_Set( "session", va("%i", g_gametype.integer) );
for ( i = 0 ; i < level.maxclients ; i++ ) {

View file

@ -997,6 +997,8 @@
</ClCompile>
<ClCompile Include="server\sv_netdemo.c">
</ClCompile>
<ClCompile Include="server\lz4.c">
</ClCompile>
<ClCompile Include="server\sv_world.c">
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug TA DEMO|Win32'">Disabled</Optimization>
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug TA DEMO|Win32'">true</BrowseInformation>

View file

@ -352,14 +352,12 @@ void SVD_AutoRecord( void );
void SVD_CaptureServerCommand( const char *cmd );
void SVD_Play_f( void );
void SVD_StopPlay_f( void );
void SVD_CleanupPlayback( void );
void SVD_Stop_f( void );
void SVD_Pause_f( void );
qboolean SVD_PlaybackFrame( void );
qboolean SVD_IsRecording( void );
qboolean SVD_IsPlaying( void );
qboolean SVD_IsPaused( void );
qboolean SVD_IsStarting( void );
qboolean SVD_ShouldPause( void );
//============================================================

View file

@ -1419,13 +1419,8 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) {
// don't execute if this is an old cmd which is already executed
// these old cmds are included when cl_packetdup > 0
if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) {
// demo playback: still process the LAST cmd even with duplicate
// serverTime (paused = frozen time, all cmds have same time).
// Need buttons and origin from fresh usercmds.
if ( !SVD_IsPlaying() || i != cmdCount - 1 ) {
continue;
}
}
SV_ClientThink (cl, &cmds[ i ]);
}
}

View file

@ -351,17 +351,11 @@ void SV_SpawnServer( char *server, qboolean killBots ) {
char systemInfo[16384];
const char *p;
// stop any active demo recording/playback (one demo = one map).
// SVD_IsStarting() returns true when SVD_Play_f is calling devmap
// internally — don't stop our own playback.
// stop any active demo recording (one demo = one map)
if ( SVD_IsRecording() ) {
Com_Printf( "Map change — stopping demo recording.\n" );
SVD_StopRecord_f();
}
if ( SVD_IsPlaying() && !SVD_IsStarting() ) {
Com_Printf( "Map change — stopping demo playback.\n" );
SVD_CleanupPlayback();
}
// shut down the existing game if it is running
SV_ShutdownGameProgs();
@ -629,6 +623,7 @@ void SV_Init (void) {
// server-side demo settings
Cvar_Get ("svdemo_autorecord", "0", CVAR_ARCHIVE);
Cvar_Get ("svdemo_compress", "1", CVAR_ARCHIVE);
Cvar_Get ("svdemo_pauseEmpty", "1", CVAR_ARCHIVE);
// initialize bot cvars so they are listed and can be set before loading the botlib
@ -686,13 +681,12 @@ void SV_Shutdown( char *finalmsg ) {
Com_Printf( "----- Server Shutdown -----\n" );
// clean up any active demo recording/playback.
// skip if SVD_Play_f is calling SV_Shutdown internally.
// clean up any active demo recording/playback
if ( SVD_IsRecording() ) {
SVD_StopRecord_f();
}
if ( SVD_IsPlaying() && !SVD_IsStarting() ) {
SVD_CleanupPlayback();
if ( SVD_IsPlaying() ) {
SVD_StopPlay_f();
}
if ( svs.clients && !com_errorEntered ) {

View file

@ -10,9 +10,7 @@ snapshot pipeline delivers them to a spectator client.
*/
#include "server.h"
// Cvar_Set2 not in public header but needed to force-set CVAR_ROM cvars
extern cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force );
#include "lz4.h"
// ---------------------------------------------------------------
// File format
@ -53,19 +51,24 @@ extern cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean forc
//
#define SVDEMO_MAGIC (('S') | ('V' << 8) | ('D' << 16) | ('M' << 24))
#define SVDEMO_VERSION 3 // v3: removed PVS data, svFlags only
#define SVDEMO_VERSION 2 // v2: optional LZ4 compression
#define SVDEMO_MAX_MAPNAME 64
// header flags
#define SVDEMO_FLAG_COMPRESSED 1 // per-frame data is LZ4 compressed
// ---------------------------------------------------------------
// State
// ---------------------------------------------------------------
// per-entity data stored for delta compression
// per-entity data stored for delta compression (entityState + PVS fields)
typedef struct {
entityState_t es;
int svFlags;
qboolean linked;
vec3_t currentOrigin;
vec3_t absmin;
vec3_t absmax;
qboolean active; // was this entity present last frame?
} svdEntityState_t;
@ -82,6 +85,7 @@ typedef struct {
// recording
fileHandle_t recordFile;
qboolean recording;
qboolean compressed; // LZ4 compression enabled
char *lastConfigstrings[MAX_CONFIGSTRINGS]; // for delta detection
svdEntityState_t prevEntities[MAX_GENTITIES]; // previous frame for delta
svdPlayerState_t prevPlayers[MAX_CLIENTS]; // previous frame player states
@ -100,13 +104,13 @@ typedef struct {
char playMapName[SVDEMO_MAX_MAPNAME];
qboolean endOfDemo;
qboolean needConfigstrings; // apply saved configstrings on first frame
qboolean starting; // SVD_Play_f is running devmap internally
qboolean paused;
svdEntityState_t playPrevEntities[MAX_GENTITIES]; // previous frame for delta read
svdPlayerState_t playPrevPlayers[MAX_CLIENTS]; // previous frame player states
} svDemo_t;
static svDemo_t demo;
// ---------------------------------------------------------------
// Recording helpers
// ---------------------------------------------------------------
@ -131,6 +135,58 @@ static short SVD_ReadShort( fileHandle_t f ) {
return v;
}
// ---------------------------------------------------------------
// LZ4 block I/O: writes [uncompressed_size][compressed_size][data]
// ---------------------------------------------------------------
static void SVD_WriteBlock( fileHandle_t f, const byte *data, int len ) {
if ( demo.compressed && len > 0 ) {
int bound = LZ4_compressBound( len );
static byte compBuf[MAX_GENTITIES * 300];
int compLen;
compLen = LZ4_compress_default( (const char *)data, (char *)compBuf, len, bound );
if ( compLen > 0 ) {
SVD_WriteInt( f, len ); // original size
SVD_WriteInt( f, compLen ); // compressed size
FS_Write( compBuf, compLen, f );
return;
}
// fall through to uncompressed on failure
}
SVD_WriteInt( f, len ); // original size
SVD_WriteInt( f, 0 ); // 0 = not compressed
FS_Write( data, len, f );
}
static int SVD_ReadBlock( fileHandle_t f, byte *buf, int bufSize ) {
int origLen, compLen;
origLen = SVD_ReadInt( f );
compLen = SVD_ReadInt( f );
if ( origLen <= 0 || origLen > bufSize ) {
return -1;
}
if ( compLen > 0 ) {
// compressed
static byte compBuf[MAX_GENTITIES * 300];
if ( compLen > (int)sizeof(compBuf) ) {
return -1;
}
FS_Read( compBuf, compLen, f );
if ( LZ4_decompress_safe( (const char *)compBuf, (char *)buf, compLen, bufSize ) != origLen ) {
return -1;
}
} else {
// uncompressed
FS_Read( buf, origLen, f );
}
return origLen;
}
// ---------------------------------------------------------------
// Write header
// ---------------------------------------------------------------
@ -141,7 +197,7 @@ static void SVD_WriteHeader( fileHandle_t f ) {
SVD_WriteInt( f, SVDEMO_MAGIC );
SVD_WriteInt( f, SVDEMO_VERSION );
SVD_WriteInt( f, 0 ); // flags (reserved)
SVD_WriteInt( f, demo.compressed ? SVDEMO_FLAG_COMPRESSED : 0 );
SVD_WriteInt( f, sv_maxclients->integer );
SVD_WriteInt( f, sv_fps->integer );
@ -219,17 +275,38 @@ static void SVD_WriteFrame( fileHandle_t f ) {
}
MSG_WriteDeltaEntity( &msg, from, &ent->s, qtrue );
// write svFlags only if changed (rarely changes)
if ( ent->r.svFlags != demo.prevEntities[i].svFlags ) {
MSG_WriteBits( &msg, 1, 1 );
// write PVS fields only if changed from previous frame
{
qboolean pvsChanged = !demo.prevEntities[i].active
|| ent->r.svFlags != demo.prevEntities[i].svFlags
|| ent->r.linked != demo.prevEntities[i].linked
|| !VectorCompare( ent->r.currentOrigin, demo.prevEntities[i].currentOrigin )
|| !VectorCompare( ent->r.absmin, demo.prevEntities[i].absmin )
|| !VectorCompare( ent->r.absmax, demo.prevEntities[i].absmax );
MSG_WriteBits( &msg, pvsChanged, 1 );
if ( pvsChanged ) {
MSG_WriteLong( &msg, ent->r.svFlags );
} else {
MSG_WriteBits( &msg, 0, 1 );
MSG_WriteLong( &msg, ent->r.linked );
MSG_WriteFloat( &msg, ent->r.currentOrigin[0] );
MSG_WriteFloat( &msg, ent->r.currentOrigin[1] );
MSG_WriteFloat( &msg, ent->r.currentOrigin[2] );
MSG_WriteFloat( &msg, ent->r.absmin[0] );
MSG_WriteFloat( &msg, ent->r.absmin[1] );
MSG_WriteFloat( &msg, ent->r.absmin[2] );
MSG_WriteFloat( &msg, ent->r.absmax[0] );
MSG_WriteFloat( &msg, ent->r.absmax[1] );
MSG_WriteFloat( &msg, ent->r.absmax[2] );
}
}
// update prev state
demo.prevEntities[i].es = ent->s;
demo.prevEntities[i].svFlags = ent->r.svFlags;
demo.prevEntities[i].linked = ent->r.linked;
VectorCopy( ent->r.currentOrigin, demo.prevEntities[i].currentOrigin );
VectorCopy( ent->r.absmin, demo.prevEntities[i].absmin );
VectorCopy( ent->r.absmax, demo.prevEntities[i].absmax );
demo.prevEntities[i].active = qtrue;
} else if ( demo.prevEntities[i].active ) {
// entity was removed — write a remove marker
@ -242,9 +319,8 @@ static void SVD_WriteFrame( fileHandle_t f ) {
// end of entities marker
MSG_WriteBits( &msg, (MAX_GENTITIES - 1), GENTITYNUM_BITS );
// write entity message to file
SVD_WriteInt( f, msg.cursize );
FS_Write( msg.data, msg.cursize, f );
// write entity message to file (optionally LZ4 compressed)
SVD_WriteBlock( f, msg.data, msg.cursize );
// write player states (delta compressed)
{
@ -295,8 +371,7 @@ static void SVD_WriteFrame( fileHandle_t f ) {
MSG_WriteBits( &psmsg, MAX_CLIENTS - 1, 6 );
MSG_WriteBits( &psmsg, 0, 1 );
SVD_WriteInt( f, psmsg.cursize );
FS_Write( psmsg.data, psmsg.cursize, f );
SVD_WriteBlock( f, psmsg.data, psmsg.cursize );
}
// configstring changes
@ -367,8 +442,10 @@ static qboolean SVD_StartRecording( const char *demoname ) {
return qfalse;
}
Com_Printf( "Recording server demo to %s\n", path );
Com_Printf( "Recording server demo to %s%s\n", path,
Cvar_VariableIntegerValue("svdemo_compress") ? " (LZ4)" : "" );
demo.recording = qtrue;
demo.compressed = Cvar_VariableIntegerValue("svdemo_compress") ? qtrue : qfalse;
// clear delta state for fresh recording
Com_Memset( demo.prevEntities, 0, sizeof(demo.prevEntities) );
@ -515,7 +592,10 @@ static qboolean SVD_ReadHeader( fileHandle_t f ) {
return qfalse;
}
SVD_ReadInt( f ); // flags (reserved)
{
int flags = SVD_ReadInt( f );
demo.compressed = ( flags & SVDEMO_FLAG_COMPRESSED ) ? qtrue : qfalse;
}
demo.playMaxClients = SVD_ReadInt( f );
demo.playFps = SVD_ReadInt( f );
@ -606,12 +686,11 @@ static qboolean SVD_ReadFrame( fileHandle_t f ) {
}
}
// read entity message
blockLen = SVD_ReadInt( f );
if ( blockLen <= 0 || blockLen > (int)sizeof(msgBuf) ) {
// read entity message (optionally LZ4 compressed)
blockLen = SVD_ReadBlock( f, msgBuf, sizeof(msgBuf) );
if ( blockLen <= 0 ) {
return qfalse;
}
FS_Read( msgBuf, blockLen, f );
MSG_Init( &msg, msgBuf, sizeof(msgBuf) );
msg.cursize = blockLen;
@ -655,20 +734,40 @@ static qboolean SVD_ReadFrame( fileHandle_t f ) {
continue;
}
// read svFlags
if ( MSG_ReadBits( &msg, 1 ) ) {
// read PVS fields
{
qboolean pvsChanged = MSG_ReadBits( &msg, 1 );
if ( pvsChanged ) {
demo.playPrevEntities[entNum].svFlags = MSG_ReadLong( &msg );
demo.playPrevEntities[entNum].linked = MSG_ReadLong( &msg );
demo.playPrevEntities[entNum].currentOrigin[0] = MSG_ReadFloat( &msg );
demo.playPrevEntities[entNum].currentOrigin[1] = MSG_ReadFloat( &msg );
demo.playPrevEntities[entNum].currentOrigin[2] = MSG_ReadFloat( &msg );
demo.playPrevEntities[entNum].absmin[0] = MSG_ReadFloat( &msg );
demo.playPrevEntities[entNum].absmin[1] = MSG_ReadFloat( &msg );
demo.playPrevEntities[entNum].absmin[2] = MSG_ReadFloat( &msg );
demo.playPrevEntities[entNum].absmax[0] = MSG_ReadFloat( &msg );
demo.playPrevEntities[entNum].absmax[1] = MSG_ReadFloat( &msg );
demo.playPrevEntities[entNum].absmax[2] = MSG_ReadFloat( &msg );
}
}
demo.playPrevEntities[entNum].es = newEs;
demo.playPrevEntities[entNum].active = qtrue;
// apply to server entity (entity linking done in G_RunFrame
// which has BG_EvaluateTrajectory for computing currentOrigin)
// apply to server entity
ent = SV_GentityNum( entNum );
ent->s = newEs;
ent->s.number = entNum;
ent->r.svFlags = demo.playPrevEntities[entNum].svFlags;
ent->r.linked = demo.playPrevEntities[entNum].linked;
VectorCopy( demo.playPrevEntities[entNum].currentOrigin, ent->r.currentOrigin );
VectorCopy( demo.playPrevEntities[entNum].absmin, ent->r.absmin );
VectorCopy( demo.playPrevEntities[entNum].absmax, ent->r.absmax );
if ( ent->r.linked ) {
SV_LinkEntity( ent );
}
if ( entNum + 1 > sv.num_entities ) {
sv.num_entities = entNum + 1;
@ -681,9 +780,8 @@ static qboolean SVD_ReadFrame( fileHandle_t f ) {
static byte psBuf[MAX_CLIENTS * 600]; // worst case: full playerState from baseline
int psMsgLen;
psMsgLen = SVD_ReadInt( f );
if ( psMsgLen > 0 && psMsgLen <= (int)sizeof(psBuf) ) {
FS_Read( psBuf, psMsgLen, f );
psMsgLen = SVD_ReadBlock( f, psBuf, sizeof(psBuf) );
if ( psMsgLen > 0 ) {
MSG_Init( &psmsg, psBuf, sizeof(psBuf) );
psmsg.cursize = psMsgLen;
@ -773,8 +871,8 @@ void SVD_Play_f( void ) {
char *s;
int len;
if ( demo.recording ) {
Com_Printf( "Stop recording first (svdemo_stop).\n" );
if ( demo.playing ) {
Com_Printf( "Already playing a server demo.\n" );
return;
}
@ -784,11 +882,6 @@ void SVD_Play_f( void ) {
return;
}
// stop current playback if switching demos
if ( demo.playing ) {
SVD_CleanupPlayback();
}
Com_sprintf( name, sizeof(name), "svdemos/%s.svdm", s );
memset( &demo, 0, sizeof(demo) );
@ -807,46 +900,42 @@ void SVD_Play_f( void ) {
demo.playing = qtrue;
demo.endOfDemo = qfalse;
demo.needConfigstrings = qtrue;
Com_Printf( "Playing server demo: map=%s maxclients=%d fps=%d\n",
demo.playMapName, demo.playMaxClients, demo.playFps );
// Shut down current server first so no clients carry over to
// reserved slots. SV_Shutdown triggers our cleanup hook, but
// demo.starting prevents it from clearing our state.
demo.starting = qtrue;
if ( com_sv_running->integer ) {
SV_Shutdown( "Demo playback\n" );
}
// Set demo cvar BEFORE devmap so G_InitGame can read it.
// The previous server is gone so no old game module to conflict with.
Cvar_Set2( "sv_demoplaying", "1", qtrue );
// Signal demo mode BEFORE map load so the game module knows
// during ClientConnect/ClientBegin to force spectator team.
Cvar_Set( "sv_demoplaying", "1" );
// Load the map with maxclients = MAX_CLIENTS to avoid entity slot collisions.
Cbuf_ExecuteText( EXEC_NOW, va("set sv_maxclients %d\n", MAX_CLIENTS) );
Cbuf_ExecuteText( EXEC_NOW, va("set sv_fps %d\n", demo.playFps) );
Cbuf_ExecuteText( EXEC_NOW, va("devmap %s\n", demo.playMapName) );
demo.starting = qfalse;
// CS_SVDEMO configstring is set by G_InitGame from the cvar
// Reserve recorded player slots. Server is fresh (SV_Shutdown cleared
// old clients), local client hasn't connected yet.
// Reserve recorded player slots so the connecting spectator
// doesn't land in slot 0 (which collides with recorded player 0).
// Mark as CS_ZOMBIE (non-free, won't be reused by SV_DirectConnect).
// Set rate and nextSnapshotTime so SV_SendClientMessages doesn't
// crash on division by zero or try to send them snapshots.
{
int i;
for ( i = 0; i < demo.playMaxClients; i++ ) {
if ( svs.clients[i].state == CS_FREE ) {
svs.clients[i].state = CS_ZOMBIE;
svs.clients[i].rate = 10000;
svs.clients[i].nextSnapshotTime = 0x7FFFFFFF;
svs.clients[i].nextSnapshotTime = 0x7FFFFFFF; // never send
svs.clients[i].lastPacketTime = svs.time;
}
}
}
// Configstrings will be applied on the first playback frame,
// after the map has fully loaded.
demo.needConfigstrings = qtrue;
}
void SVD_CleanupPlayback( void ) {
static void SVD_CleanupPlayback( void ) {
int i;
if ( !demo.playing ) {
@ -870,7 +959,7 @@ void SVD_CleanupPlayback( void ) {
}
memset( &demo, 0, sizeof(demo) );
Cvar_Set2( "sv_demoplaying", "0", qtrue );
Cvar_Set( "sv_demoplaying", "0" );
}
void SVD_StopPlay_f( void ) {
@ -905,11 +994,6 @@ void SVD_Pause_f( void ) {
return;
}
demo.paused = !demo.paused;
if ( !demo.paused ) {
// resuming — toggle SERVERCOUNT to reset client snapshot timing
// (drifted during pause from identical serverTimes).
svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT;
}
Com_Printf( "Demo playback %s.\n", demo.paused ? "paused" : "resumed" );
}
@ -969,11 +1053,6 @@ qboolean SVD_IsPaused( void ) {
return demo.playing && demo.paused;
}
qboolean SVD_IsStarting( void ) {
return demo.starting;
}
/*
Returns qtrue if demo playback should pause (no active spectators).
Controlled by svdemo_pauseEmpty cvar.