Remove spectatorClientNum hack

spectatorClientNum was hardcoded to MAX_CLIENTS-1 but the spectator
actually connected at a different slot. All skip checks using it
were no-ops protecting the wrong slot. Removed entirely:

- Zombie slots handle player slot reservation
- G_RunFrame recreates the spectator entity via ClientThink_real
- PlayerState injection writes all slots (spectator's ps gets
  overwritten by ClientThink_real anyway)
- SVD_SpectatorClientNum accessor removed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
serge_shubin 2026-03-23 06:20:52 +08:00
parent 4d89d35a6d
commit b79eb77bb3
2 changed files with 5 additions and 24 deletions

View file

@ -357,7 +357,6 @@ void SVD_Stop_f( void );
qboolean SVD_PlaybackFrame( void ); qboolean SVD_PlaybackFrame( void );
qboolean SVD_IsRecording( void ); qboolean SVD_IsRecording( void );
qboolean SVD_IsPlaying( void ); qboolean SVD_IsPlaying( void );
int SVD_SpectatorClientNum( void );
//============================================================ //============================================================
// //

View file

@ -102,7 +102,8 @@ typedef struct {
int playFps; // original sv_fps int playFps; // original sv_fps
char *savedConfigstrings[MAX_CONFIGSTRINGS]; // from demo header, re-applied after map load char *savedConfigstrings[MAX_CONFIGSTRINGS]; // from demo header, re-applied after map load
char playMapName[SVDEMO_MAX_MAPNAME]; char playMapName[SVDEMO_MAX_MAPNAME];
int spectatorClientNum; // which client slot is the demo spectator // spectatorClientNum removed — zombie slots handle reservation,
// and G_RunFrame recreates the spectator entity each frame.
int nextFrameTime; // serverTime of next frame to read int nextFrameTime; // serverTime of next frame to read
qboolean endOfDemo; qboolean endOfDemo;
qboolean needConfigstrings; // apply saved configstrings on first frame qboolean needConfigstrings; // apply saved configstrings on first frame
@ -686,11 +687,8 @@ static qboolean SVD_ReadFrame( fileHandle_t f ) {
MSG_Init( &msg, msgBuf, sizeof(msgBuf) ); MSG_Init( &msg, msgBuf, sizeof(msgBuf) );
msg.cursize = blockLen; msg.cursize = blockLen;
// clear all non-spectator entities // clear all entities (spectator's entity is recreated by ClientThink_real)
for ( i = 0; i < sv.num_entities; i++ ) { for ( i = 0; i < sv.num_entities; i++ ) {
if ( i == demo.spectatorClientNum ) {
continue;
}
ent = SV_GentityNum( i ); ent = SV_GentityNum( i );
if ( ent->r.linked ) { if ( ent->r.linked ) {
SV_UnlinkEntity( ent ); SV_UnlinkEntity( ent );
@ -750,11 +748,6 @@ static qboolean SVD_ReadFrame( fileHandle_t f ) {
demo.playPrevEntities[entNum].es = newEs; demo.playPrevEntities[entNum].es = newEs;
demo.playPrevEntities[entNum].active = qtrue; demo.playPrevEntities[entNum].active = qtrue;
// skip the spectator's slot
if ( entNum == demo.spectatorClientNum ) {
continue;
}
// apply to server entity // apply to server entity
ent = SV_GentityNum( entNum ); ent = SV_GentityNum( entNum );
ent->s = newEs; ent->s = newEs;
@ -774,11 +767,6 @@ static qboolean SVD_ReadFrame( fileHandle_t f ) {
} }
} }
// ensure spectator slot is counted
if ( demo.spectatorClientNum + 1 > sv.num_entities ) {
sv.num_entities = demo.spectatorClientNum + 1;
}
// read player states (delta compressed) // read player states (delta compressed)
{ {
msg_t psmsg; msg_t psmsg;
@ -818,14 +806,14 @@ static qboolean SVD_ReadFrame( fileHandle_t f ) {
demo.playPrevPlayers[clientNum].active = qtrue; demo.playPrevPlayers[clientNum].active = qtrue;
// inject into game module's client state // inject into game module's client state
if ( clientNum != demo.spectatorClientNum ) { {
playerState_t *gamePs = SV_GameClientNum( clientNum ); playerState_t *gamePs = SV_GameClientNum( clientNum );
*gamePs = newPs; *gamePs = newPs;
} }
} else { } else {
demo.playPrevPlayers[clientNum].active = qfalse; demo.playPrevPlayers[clientNum].active = qfalse;
// clear game playerState so G_RunFrame sees commandTime=0 // clear game playerState so G_RunFrame sees commandTime=0
if ( clientNum != demo.spectatorClientNum ) { {
playerState_t *gamePs = SV_GameClientNum( clientNum ); playerState_t *gamePs = SV_GameClientNum( clientNum );
Com_Memset( gamePs, 0, sizeof(*gamePs) ); Com_Memset( gamePs, 0, sizeof(*gamePs) );
} }
@ -906,9 +894,6 @@ void SVD_Play_f( void ) {
demo.playing = qtrue; demo.playing = qtrue;
demo.endOfDemo = qfalse; demo.endOfDemo = qfalse;
// The spectator gets the highest slot: MAX_CLIENTS - 1
demo.spectatorClientNum = MAX_CLIENTS - 1;
Com_Printf( "Playing server demo: map=%s maxclients=%d fps=%d\n", Com_Printf( "Playing server demo: map=%s maxclients=%d fps=%d\n",
demo.playMapName, demo.playMaxClients, demo.playFps ); demo.playMapName, demo.playMaxClients, demo.playFps );
@ -1032,6 +1017,3 @@ qboolean SVD_IsPlaying( void ) {
return demo.playing; return demo.playing;
} }
int SVD_SpectatorClientNum( void ) {
return demo.spectatorClientNum;
}