diff --git a/code/game/g_active.c b/code/game/g_active.c index 853f5fb..a1e7e82 100644 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -1034,6 +1034,12 @@ 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 ); } diff --git a/code/game/g_main.c b/code/game/g_main.c index 77b817a..1fee482 100644 --- a/code/game/g_main.c +++ b/code/game/g_main.c @@ -1761,6 +1761,17 @@ 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; } diff --git a/code/server/sv_client.c b/code/server/sv_client.c index c3fdc92..84c2e3b 100644 --- a/code/server/sv_client.c +++ b/code/server/sv_client.c @@ -1419,7 +1419,12 @@ 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 ) { - continue; + // 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 ]); }