Fix spectator display and recorded spectator handling

- Set sv_demoplaying before devmap so game module knows during
  ClientConnect/ClientBegin
- Call ClientUserinfoChanged after forcing spectator team so
  CS_PLAYERS configstring has correct team value for cgame
- Record sanitized playerState for spectators (pm_type=PM_SPECTATOR,
  PERS_TEAM=TEAM_SPECTATOR) so they show correctly on scoreboard
  instead of appearing as regular players from follow-mode corruption

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
serge_shubin 2026-03-23 23:44:22 +08:00
parent d78f5c7aaf
commit fcd5fc6ce8
2 changed files with 23 additions and 5 deletions

View file

@ -1013,10 +1013,10 @@ 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;
client->ps.pm_type = PM_SPECTATOR;
client->ps.persistant[PERS_TEAM] = TEAM_SPECTATOR;
ClientUserinfoChanged( ent->client - level.clients );
}
// save eflags around this, because changing teams will

View file

@ -336,6 +336,23 @@ static void SVD_WriteFrame( fileHandle_t f ) {
playerState_t *ps = SV_GameClientNum( i );
client_t *cl = &svs.clients[i];
qboolean active = ( cl->state >= CS_ACTIVE );
qboolean isSpectator;
playerState_t specPs;
// detect spectators: free cam or follow mode
isSpectator = active && ( ps->pm_type == PM_SPECTATOR || (ps->pm_flags & PMF_FOLLOW) );
// for spectators, record a sanitized ps so they appear on
// the scoreboard as spectators (follow mode corrupts their ps
// with the followed player's data)
if ( isSpectator ) {
Com_Memset( &specPs, 0, sizeof(specPs) );
specPs.commandTime = ps->commandTime;
specPs.pm_type = PM_SPECTATOR;
specPs.persistant[PERS_TEAM] = TEAM_SPECTATOR;
specPs.clientNum = i;
ps = &specPs;
}
if ( active ) {
playerState_t *from = demo.prevPlayers[i].active ? &demo.prevPlayers[i].ps : NULL;
@ -897,14 +914,15 @@ void SVD_Play_f( void ) {
Com_Printf( "Playing server demo: map=%s maxclients=%d fps=%d\n",
demo.playMapName, demo.playMaxClients, demo.playFps );
// 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) );
// Signal demo mode to the game module AFTER map load
Cvar_Set( "sv_demoplaying", "1" );
// 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).