//========= Copyright Valve Corporation, All rights reserved. ============// // //---------------------------------------------------------------------------------------- #if defined( REPLAY_ENABLED ) #include #include #include #include #include #include #include #include #include #include "replayserver.h" #include "sv_client.h" #include "server.h" #include "sv_main.h" #include "framesnapshot.h" #include "networkstringtable.h" #include "cmodel_engine.h" #include "dt_recv_eng.h" #include "cdll_engine_int.h" #include "GameEventManager.h" #include "host.h" #include "proto_version.h" #include "dt_common_eng.h" #include "baseautocompletefilelist.h" #include "sv_steamauth.h" #include "con_nprint.h" #include "tier0/icommandline.h" #include "client_class.h" #include "replay_internal.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern CNetworkStringTableContainer *networkStringTableContainerClient; ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CReplayServer *replay = NULL; CReplayDeltaEntityCache::CReplayDeltaEntityCache() { Q_memset( m_Cache, 0, sizeof(m_Cache) ); m_nTick = 0; m_nMaxEntities = 0; m_nCacheSize = 0; } CReplayDeltaEntityCache::~CReplayDeltaEntityCache() { Flush(); } void CReplayDeltaEntityCache::Flush() { if ( m_nMaxEntities != 0 ) { // at least one entity was set for ( int i=0; i= m_nMaxEntities ) return NULL; DeltaEntityEntry_s *pEntry = m_Cache[nEntityIndex]; while ( pEntry ) { if ( pEntry->nDeltaTick == nDeltaTick ) { nBits = pEntry->nBits; return (unsigned char*)(pEntry) + sizeof(DeltaEntityEntry_s); } else { // keep searching entry list pEntry = pEntry->pNext; } } return NULL; } void CReplayDeltaEntityCache::AddDeltaBits( int nEntityIndex, int nDeltaTick, int nBits, bf_write *pBuffer ) { if ( nEntityIndex < 0 || nEntityIndex >= m_nMaxEntities || m_nCacheSize <= 0 ) return; int nBufferSize = PAD_NUMBER( Bits2Bytes(nBits), 4); DeltaEntityEntry_s *pEntry = m_Cache[nEntityIndex]; if ( pEntry == NULL ) { if ( (int)(nBufferSize+sizeof(DeltaEntityEntry_s)) > m_nCacheSize ) return; // way too big, don't even create an entry pEntry = m_Cache[nEntityIndex] = (DeltaEntityEntry_s *) malloc( m_nCacheSize ); } else { char *pEnd = (char*)(pEntry) + m_nCacheSize; // end marker while( pEntry->pNext ) { pEntry = pEntry->pNext; } int entrySize = sizeof(DeltaEntityEntry_s) + PAD_NUMBER( Bits2Bytes(pEntry->nBits), 4); DeltaEntityEntry_s *pNew = (DeltaEntityEntry_s*)((char*)(pEntry) + entrySize); if ( ((char*)(pNew) + sizeof(DeltaEntityEntry_s) + nBufferSize) > pEnd ) return; // data wouldn't fit into cache anymore, don't add new entries pEntry = pNew; pEntry->pNext = pEntry; } pEntry->pNext = NULL; // link to next pEntry->nDeltaTick = nDeltaTick; pEntry->nBits = nBits; if ( nBits > 0 ) { bf_read inBuffer; inBuffer.StartReading( pBuffer->GetData(), pBuffer->m_nDataBytes, pBuffer->GetNumBitsWritten() ); bf_write outBuffer( (char*)(pEntry) + sizeof(DeltaEntityEntry_s), nBufferSize ); outBuffer.WriteBitsFromBuffer( &inBuffer, nBits ); } } static RecvTable* FindRecvTable( const char *pName, RecvTable **pRecvTables, int nRecvTables ) { for ( int i=0; i< nRecvTables; i++ ) { if ( !Q_strcmp( pName, pRecvTables[i]->GetName() ) ) return pRecvTables[i]; } return NULL; } static RecvTable* AddRecvTableR( SendTable *sendt, RecvTable **pRecvTables, int &nRecvTables ) { RecvTable *recvt = FindRecvTable( sendt->m_pNetTableName, pRecvTables, nRecvTables ); if ( recvt ) return recvt; // already in list if ( sendt->m_nProps > 0 ) { RecvProp *receiveProps = new RecvProp[sendt->m_nProps]; for ( int i=0; i < sendt->m_nProps; i++ ) { // copy property data SendProp * sp = sendt->GetProp( i ); RecvProp * rp = &receiveProps[i]; rp->m_pVarName = sp->m_pVarName; rp->m_RecvType = sp->m_Type; if ( sp->IsExcludeProp() ) { // if prop is excluded, give different name rp->m_pVarName = "IsExcludedProp"; } if ( sp->IsInsideArray() ) { rp->SetInsideArray(); rp->m_pVarName = "InsideArrayProp"; // give different name } if ( sp->GetType() == DPT_Array ) { Assert ( sp->GetArrayProp() == sendt->GetProp( i-1 ) ); Assert( receiveProps[i-1].IsInsideArray() ); rp->SetArrayProp( &receiveProps[i-1] ); rp->InitArray( sp->m_nElements, sp->m_ElementStride ); } if ( sp->GetType() == DPT_DataTable ) { // recursive create Assert ( sp->GetDataTable() ); RecvTable *subTable = AddRecvTableR( sp->GetDataTable(), pRecvTables, nRecvTables ); rp->SetDataTable( subTable ); } } recvt = new RecvTable( receiveProps, sendt->m_nProps, sendt->m_pNetTableName ); } else { // table with no properties recvt = new RecvTable( NULL, 0, sendt->m_pNetTableName ); } pRecvTables[nRecvTables] = recvt; nRecvTables++; return recvt; } void CReplayServer::FreeClientRecvTables() { for ( int i=0; i< m_nRecvTables; i++ ) { RecvTable *rt = m_pRecvTables[i]; // delete recv table props if ( rt->m_pProps ) { Assert( rt->m_nProps > 0 ); delete [] rt->m_pProps; } // delete the table itself delete rt; } Q_memset( m_pRecvTables, 0, sizeof( m_pRecvTables ) ); m_nRecvTables = 0; } // creates client receive tables from server send tables void CReplayServer::InitClientRecvTables() { ServerClass* pCur = NULL; if ( ClientDLL_GetAllClasses() != NULL ) return; //already initialized // first create all SendTables for ( pCur = serverGameDLL->GetAllServerClasses(); pCur; pCur=pCur->m_pNext ) { // create receive table from send table. AddRecvTableR( pCur->m_pTable, m_pRecvTables, m_nRecvTables ); ErrorIfNot( m_nRecvTables < ARRAYSIZE( m_pRecvTables ), ("AddRecvTableR: overflowed MAX_DATATABLES") ); } // now register client classes for ( pCur = serverGameDLL->GetAllServerClasses(); pCur; pCur=pCur->m_pNext ) { ErrorIfNot( m_nRecvTables < ARRAYSIZE( m_pRecvTables ), ("ClientDLL_InitRecvTableMgr: overflowed MAX_DATATABLES") ); // find top receive table for class RecvTable * recvt = FindRecvTable( pCur->m_pTable->GetName(), m_pRecvTables, m_nRecvTables ); Assert ( recvt ); // register class, constructor addes clientClass to g_pClientClassHead list ClientClass * clientclass = new ClientClass( pCur->m_pNetworkName, NULL, NULL, recvt ); if ( !clientclass ) { Msg("REPLAY_InitRecvTableMgr: failed to allocate client class %s.\n", pCur->m_pNetworkName ); return; } } RecvTable_Init( m_pRecvTables, m_nRecvTables ); } CReplayFrame::CReplayFrame() { } CReplayFrame::~CReplayFrame() { FreeBuffers(); } void CReplayFrame::Reset( void ) { for ( int i=0; i 0 ) return true; } return false; } void CReplayFrame::CopyReplayData( CReplayFrame &frame ) { // copy reliable messages int bits = frame.m_Messages[REPLAY_BUFFER_RELIABLE].GetNumBitsWritten(); if ( bits > 0 ) { int bytes = PAD_NUMBER( Bits2Bytes(bits), 4 ); m_Messages[REPLAY_BUFFER_RELIABLE].StartWriting( new char[ bytes ], bytes, bits ); Q_memcpy( m_Messages[REPLAY_BUFFER_RELIABLE].GetBasePointer(), frame.m_Messages[REPLAY_BUFFER_RELIABLE].GetBasePointer(), bytes ); } // copy unreliable messages bits = frame.m_Messages[REPLAY_BUFFER_UNRELIABLE].GetNumBitsWritten(); bits += frame.m_Messages[REPLAY_BUFFER_TEMPENTS].GetNumBitsWritten(); bits += frame.m_Messages[REPLAY_BUFFER_SOUNDS].GetNumBitsWritten(); bits += frame.m_Messages[REPLAY_BUFFER_VOICE].GetNumBitsWritten(); if ( bits > 0 ) { // collapse all unreliable buffers in one int bytes = PAD_NUMBER( Bits2Bytes(bits), 4 ); m_Messages[REPLAY_BUFFER_UNRELIABLE].StartWriting( new char[ bytes ], bytes ); m_Messages[REPLAY_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[REPLAY_BUFFER_UNRELIABLE].GetData(), frame.m_Messages[REPLAY_BUFFER_UNRELIABLE].GetNumBitsWritten() ); m_Messages[REPLAY_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[REPLAY_BUFFER_TEMPENTS].GetData(), frame.m_Messages[REPLAY_BUFFER_TEMPENTS].GetNumBitsWritten() ); m_Messages[REPLAY_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[REPLAY_BUFFER_SOUNDS].GetData(), frame.m_Messages[REPLAY_BUFFER_SOUNDS].GetNumBitsWritten() ); m_Messages[REPLAY_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[REPLAY_BUFFER_VOICE].GetData(), frame.m_Messages[REPLAY_BUFFER_VOICE].GetNumBitsWritten() ); } } void CReplayFrame::AllocBuffers( void ) { // allocate buffers for input frame for ( int i=0; i < REPLAY_BUFFER_MAX; i++ ) { Assert( m_Messages[i].GetBasePointer() == NULL ); m_Messages[i].StartWriting( new char[NET_MAX_PAYLOAD], NET_MAX_PAYLOAD); } } void CReplayFrame::FreeBuffers( void ) { for ( int i=0; i 0 ) { RecvTable_Term(); FreeClientRecvTables(); } // make sure everything was destroyed Assert( m_CurrentFrame == NULL ); Assert( CountClientFrames() == 0 ); } void CReplayServer::SetMaxClients( int number ) { // allow max clients 0 in Replay m_nMaxclients = clamp( number, 0, ABSOLUTE_PLAYER_LIMIT ); } void CReplayServer::StartMaster(CGameClient *client) { Clear(); // clear old settings & buffers if ( !client ) { ConMsg("Replay client not found.\n"); return; } m_MasterClient = client; m_MasterClient->m_bIsHLTV = false; m_MasterClient->m_bIsReplay = true; // let game.dll know that we are the Replay client Assert( serverGameClients ); CPlayerState *player = serverGameClients->GetPlayerState( m_MasterClient->edict ); player->replay = true; m_Server = (CGameServer*)m_MasterClient->GetServer(); // set default user settings ConVarRef replay_name( "replay_name" ); m_MasterClient->m_ConVars->SetString( "name", replay_name.GetString() ); m_MasterClient->m_ConVars->SetString( "cl_team", "1" ); m_MasterClient->m_ConVars->SetString( "rate", "30000" ); m_MasterClient->m_ConVars->SetString( "cl_updaterate", "22" ); m_MasterClient->m_ConVars->SetString( "cl_interp_ratio", "1.0" ); m_MasterClient->m_ConVars->SetString( "cl_predict", "0" ); m_nViewEntity = m_MasterClient->GetPlayerSlot() + 1; m_nPlayerSlot = m_MasterClient->GetPlayerSlot(); // copy server settings from m_Server m_nGameServerMaxClients = m_Server->GetMaxClients(); // maxclients is different on proxy (128) serverclasses = m_Server->serverclasses; serverclassbits = m_Server->serverclassbits; worldmapMD5 = m_Server->worldmapMD5; m_flTickInterval= m_Server->GetTickInterval(); // allocate buffers for input frame m_ReplayFrame.AllocBuffers(); InstallStringTables(); // copy signon buffers m_Signon.StartWriting( m_Server->m_Signon.GetBasePointer(), m_Server->m_Signon.m_nDataBytes, m_Server->m_Signon.GetNumBitsWritten() ); Q_strncpy( m_szMapname, m_Server->m_szMapname, sizeof(m_szMapname) ); Q_strncpy( m_szSkyname, m_Server->m_szSkyname, sizeof(m_szSkyname) ); m_MasterClient->ExecuteStringCommand( "spectate" ); // become a spectator m_MasterClient->UpdateUserSettings(); // make sure UserInfo is correct // hack reduce signontick by one to catch changes made in the current tick m_MasterClient->m_nSignonTick--; SetMaxClients( 0 ); m_bSignonState = false; //master proxy is instantly connected m_nSpawnCount++; m_flStartTime = net_time; m_State = ss_active; // stop any previous recordings StopRecording(); ReconnectClients(); } int CReplayServer::GetReplaySlot( void ) { return m_nPlayerSlot; } float CReplayServer::GetOnlineTime( void ) { return MAX(0, net_time - m_flStartTime); } void CReplayServer::FireGameEvent(IGameEvent *event) { if ( !IsActive() ) return; char buffer_data[MAX_EVENT_BYTES]; SVC_GameEvent eventMsg; eventMsg.m_DataOut.StartWriting( buffer_data, sizeof(buffer_data) ); // create bit stream from KeyValues if ( g_GameEventManager.SerializeEvent( event, &eventMsg.m_DataOut ) ) { SendNetMsg( eventMsg ); } else { DevMsg("CReplayServer::FireGameEvent: failed to serialize event '%s'.\n", event->GetName() ); } } int CReplayServer::GetEventDebugID() { return m_nDebugID; } bool CReplayServer::ShouldUpdateMasterServer() { // The replay server should never do this work return false; } void CReplayServer::InstallStringTables( void ) { #ifndef SHARED_NET_STRING_TABLES int numTables = m_Server->m_StringTables->GetNumTables(); m_StringTables = &m_NetworkStringTables; Assert( m_StringTables->GetNumTables() == 0); // must be empty m_StringTables->AllowCreation( true ); // master replay needs to keep a list of changes for all table items m_StringTables->EnableRollback( true ); for ( int i =0; im_StringTables->GetTable( i ); if ( !serverTable ) continue; // get matching client table CNetworkStringTable *replayTable = (CNetworkStringTable*)m_StringTables->CreateStringTableEx( serverTable->GetTableName(), serverTable->GetMaxStrings(), serverTable->GetUserDataSize(), serverTable->GetUserDataSizeBits(), serverTable->HasFileNameStrings() ); if ( !replayTable ) { DevMsg("SV_InstallReplayStringTableMirrors! Missing client table \"%s\".\n ", serverTable->GetTableName() ); continue; } // make replay table an exact copy of server table replayTable->CopyStringTable( serverTable ); // link replay table to server table serverTable->SetMirrorTable( replayTable ); } m_StringTables->AllowCreation( false ); #endif } void CReplayServer::RestoreTick( int tick ) { #ifndef SHARED_NET_STRING_TABLES int numTables = m_StringTables->GetNumTables(); for ( int i =0; iGetTable( i ); pTable->RestoreTick( tick ); } #endif } void CReplayServer::UserInfoChanged( int nClientIndex ) { // don't change UserInfo table, it keeps the infos of the original players } void CReplayServer::LinkInstanceBaselines( void ) { // Forces to update m_pInstanceBaselineTable. AUTO_LOCK( g_svInstanceBaselineMutex ); GetInstanceBaselineTable(); Assert( m_pInstanceBaselineTable ); // update all found server classes for ( ServerClass *pClass = serverGameDLL->GetAllServerClasses(); pClass; pClass=pClass->m_pNext ) { char idString[32]; Q_snprintf( idString, sizeof( idString ), "%d", pClass->m_ClassID ); // Ok, make a new instance baseline so they can reference it. int index = m_pInstanceBaselineTable->FindStringIndex( idString ); if ( index != -1 ) { pClass->m_InstanceBaselineIndex = index; } else { pClass->m_InstanceBaselineIndex = INVALID_STRING_INDEX; } } } /* CReplayServer::GetOriginFromPackedEntity is such a bad, bad hack. extern float DecodeFloat(SendProp const *pProp, bf_read *pIn); Vector CReplayServer::GetOriginFromPackedEntity(PackedEntity* pe) { Vector origin; origin.Init(); SendTable *pSendTable = pe->m_pSendTable; // recursively go down until BaseEntity sendtable while ( Q_strcmp( pSendTable->GetName(), "DT_BaseEntity") ) { SendProp *pProp = pSendTable->GetProp( 0 ); // 0 = baseclass pSendTable = pProp->GetDataTable(); } for ( int i=0; i < pSendTable->GetNumProps(); i++ ) { SendProp *pProp = pSendTable->GetProp( i ); if ( Q_strcmp( pProp->GetName(), "m_vecOrigin" ) == 0 ) { Assert( pProp->GetType() == DPT_Vector ); bf_read buf( pe->LockData(), Bits2Bytes(pe->GetNumBits()), pProp->GetOffset() ); origin[0] = DecodeFloat(pProp, &buf); origin[1] = DecodeFloat(pProp, &buf); origin[2] = DecodeFloat(pProp, &buf); break; } } return origin; } */ CReplayEntityData *FindReplayDataInSnapshot( CFrameSnapshot * pSnapshot, int iEntIndex ) { int a = 0; int z = pSnapshot->m_nValidEntities-1; if ( iEntIndex < pSnapshot->m_pValidEntities[a] || iEntIndex > pSnapshot->m_pValidEntities[z] ) return NULL; while ( a < z ) { int m = (a+z)/2; int index = pSnapshot->m_pValidEntities[m]; if ( index == iEntIndex ) return &pSnapshot->m_pReplayEntityData[m]; if ( iEntIndex > index ) { if ( pSnapshot->m_pValidEntities[z] == iEntIndex ) return &pSnapshot->m_pReplayEntityData[z]; if ( a == m ) return NULL; a = m; } else { if ( pSnapshot->m_pValidEntities[a] == iEntIndex ) return &pSnapshot->m_pReplayEntityData[a]; if ( z == m ) return NULL; z = m; } } return NULL; } void CReplayServer::EntityPVSCheck( CClientFrame *pFrame ) { byte PVS[PAD_NUMBER( MAX_MAP_CLUSTERS,8 ) / 8]; int nPVSSize = (GetCollisionBSPData()->numclusters + 7) / 8; // setup engine PVS SV_ResetPVS( PVS, nPVSSize ); CFrameSnapshot * pSnapshot = pFrame->GetSnapshot(); Assert ( pSnapshot->m_pReplayEntityData != NULL ); m_vPVSOrigin.Init(); SV_AddOriginToPVS( m_vPVSOrigin ); // know remove all entities that aren't in PVS int entindex = -1; while ( true ) { entindex = pFrame->transmit_entity.FindNextSetBit( entindex+1 ); if ( entindex < 0 ) break; // is transmit_always is set -> no PVS check if ( pFrame->transmit_always->Get(entindex) ) { pFrame->last_entity = entindex; continue; } CReplayEntityData *pReplayData = FindReplayDataInSnapshot( pSnapshot, entindex ); if ( !pReplayData ) continue; unsigned int nNodeCluster = pReplayData->m_nNodeCluster; // check if node or cluster is in PVS if ( nNodeCluster & (1<<31) ) { // it's a node SLOW nNodeCluster &= ~(1<<31); if ( CM_HeadnodeVisible( nNodeCluster, PVS, nPVSSize ) ) { pFrame->last_entity = entindex; continue; } } else { // it's a cluster QUICK if ( PVS[nNodeCluster >> 3] & (1 << (nNodeCluster & 7)) ) { pFrame->last_entity = entindex; continue; } } // entity is not in PVS, remove from transmit_entity list pFrame->transmit_entity.Clear( entindex ); } } CClientFrame *CReplayServer::AddNewFrame( CClientFrame *clientFrame ) { VPROF_BUDGET( "CReplayServer::AddNewFrame", "Replay" ); Assert ( clientFrame ); Assert( clientFrame->tick_count > m_nLastTick ); m_nLastTick = clientFrame->tick_count; m_ReplayFrame.SetSnapshot( clientFrame->GetSnapshot() ); m_ReplayFrame.tick_count = clientFrame->tick_count; m_ReplayFrame.last_entity = clientFrame->last_entity; m_ReplayFrame.transmit_entity = clientFrame->transmit_entity; // remember tick of first valid frame if ( m_nFirstTick < 0 ) { m_nFirstTick = clientFrame->tick_count; m_nTickCount = m_nFirstTick; } CReplayFrame *replayFrame = new CReplayFrame; // copy tickcount & entities from client frame replayFrame->CopyFrame( *clientFrame ); //copy rest (messages, tempents) from current Replay frame replayFrame->CopyReplayData( m_ReplayFrame ); // add frame to Replay server AddClientFrame( replayFrame ); if ( m_DemoRecorder.IsRecording() ) { m_DemoRecorder.WriteFrame( &m_ReplayFrame ); } // reset Replay frame for recording next messages etc. m_ReplayFrame.Reset(); m_ReplayFrame.SetSnapshot( NULL ); return replayFrame; } bool CReplayServer::SendNetMsg( INetMessage &msg, bool bForceReliable ) { if ( m_bSignonState ) { return msg.WriteToBuffer( m_Signon ); } int buffer = REPLAY_BUFFER_UNRELIABLE; // default destination if ( msg.IsReliable() ) { buffer = REPLAY_BUFFER_RELIABLE; } else if ( msg.GetType() == svc_Sounds ) { buffer = REPLAY_BUFFER_SOUNDS; } else if ( msg.GetType() == svc_VoiceData ) { buffer = REPLAY_BUFFER_VOICE; } else if ( msg.GetType() == svc_TempEntities ) { buffer = REPLAY_BUFFER_TEMPENTS; } // anything else goes to the unreliable bin return msg.WriteToBuffer( m_ReplayFrame.m_Messages[buffer] ); } bf_write *CReplayServer::GetBuffer( int nBuffer ) { if ( nBuffer < 0 || nBuffer >= REPLAY_BUFFER_MAX ) return NULL; return &m_ReplayFrame.m_Messages[nBuffer]; } IServer *CReplayServer::GetBaseServer() { return (IServer*)this; } CClientFrame *CReplayServer::GetDeltaFrame( int nTick ) { // TODO make that a utlmap FOR_EACH_VEC( m_FrameCache, iFrame ) { if ( m_FrameCache[iFrame].nTick == nTick ) return m_FrameCache[iFrame].pFrame; } int i = m_FrameCache.AddToTail(); CReplayFrameCacheEntry_s &entry = m_FrameCache[i]; entry.nTick = nTick; entry.pFrame = GetClientFrame( nTick ); //expensive return entry.pFrame; } void CReplayServer::RunFrame() { VPROF_BUDGET( "CReplayServer::RunFrame", "Replay" ); // update network time etc NET_RunFrame( Plat_FloatTime() ); // check if Replay server if active if ( !IsActive() ) return; if ( host_frametime > 0 ) { m_flFPS = m_flFPS * 0.99f + 0.01f/host_frametime; } // get current tick time for director module and restore // world (stringtables, framebuffers) as they were at this time UpdateTick(); // Update the Steam server if we're running a relay. if ( !sv.IsActive() ) Steam3Server().RunFrame(); UpdateMasterServer(); SendPendingEvents(); } void CReplayServer::UpdateTick( void ) { VPROF_BUDGET( "CReplayServer::UpdateTick", "Replay" ); if ( m_nFirstTick < 0 ) { m_nTickCount = 0; m_CurrentFrame = NULL; return; } // set tick time to last frame added int nNewTick = MAX( m_nLastTick, 0 ); // the the closest available frame CReplayFrame *newFrame = (CReplayFrame*) GetClientFrame( nNewTick, false ); if ( newFrame == NULL ) return; // we dont have a new frame if ( m_CurrentFrame == newFrame ) return; // current frame didn't change m_CurrentFrame = newFrame; m_nTickCount = m_CurrentFrame->tick_count; // restore string tables for this time RestoreTick( m_nTickCount ); int removeTick = m_nTickCount - 16.0f/m_flTickInterval; // keep 16 second buffer if ( removeTick > 0 ) { DeleteClientFrames( removeTick ); } m_FrameCache.RemoveAll(); } const char *CReplayServer::GetName( void ) const { ConVarRef replay_name( "replay_name" ); return replay_name.GetString(); } void CReplayServer::FillServerInfo(SVC_ServerInfo &serverinfo) { CBaseServer::FillServerInfo( serverinfo ); serverinfo.m_nPlayerSlot = m_nPlayerSlot; // all spectators think they're the Replay client serverinfo.m_nMaxClients = m_nGameServerMaxClients; } void CReplayServer::Clear( void ) { CBaseServer::Clear(); m_MasterClient = NULL; m_Server = NULL; m_nFirstTick = -1; m_nLastTick = 0; m_nTickCount = 0; m_CurrentFrame = NULL; m_nPlayerSlot = 0; m_flStartTime = 0.0f; m_nViewEntity = 1; m_nGameServerMaxClients = 0; m_fNextSendUpdateTime = 0.0f; m_ReplayFrame.FreeBuffers(); m_vPVSOrigin.Init(); DeleteClientFrames( -1 ); m_DeltaCache.Flush(); m_FrameCache.RemoveAll(); } void CReplayServer::Init(bool bIsDedicated) { CBaseServer::Init( bIsDedicated ); // No broadcasting m_bMasterOnlyMode = true; } void CReplayServer::Changelevel() { if ( g_pReplay->IsRecording() ) { g_pReplay->SV_EndRecordingSession(); } InactivateClients(); DeleteClientFrames(-1); m_CurrentFrame = NULL; } void CReplayServer::GetNetStats( float &avgIn, float &avgOut ) { CBaseServer::GetNetStats( avgIn, avgOut ); } void CReplayServer::Shutdown() { StopRecording(); // if recording, stop now if ( m_MasterClient ) m_MasterClient->Disconnect( "Replay stop." ); g_GameEventManager.RemoveListener( this ); // Delete the temp replay if it exists if ( g_pFullFileSystem->FileExists( TMP_REPLAY_FILENAME ) ) { g_pFullFileSystem->RemoveFile( TMP_REPLAY_FILENAME ); } CBaseServer::Shutdown(); } int CReplayServer::GetChallengeType ( netadr_t &adr ) { return PROTOCOL_HASHEDCDKEY; // Replay doesn't need Steam authentication } const char *CReplayServer::GetPassword() const { return NULL; } IClient *CReplayServer::ConnectClient ( netadr_t &adr, int protocol, int challenge, int clientChallenge, int authProtocol, const char *name, const char *password, const char *hashedCDkey, int cdKeyLen ) { // Don't let anyone connect to the replay server return NULL; } void CReplayServer::ReplyChallenge(netadr_t &adr, int clientChallenge ) { // No reply for replay. return; } void CReplayServer::ReplyServerChallenge(netadr_t &adr) { return; } void CReplayServer::RejectConnection( const netadr_t &adr, int clientChallenge, const char *s ) { return; } CBaseClient *CReplayServer::CreateFakeClient(const char *name) { return NULL; } void CReplayServer::StartRecording() { if ( m_DemoRecorder.IsRecording() ) return; extern ConVar replay_debug; if ( replay_debug.GetBool() ) Msg( "CReplayServer::StartRecording() now, %f\n", host_time ); m_DemoRecorder.StartRecording(); m_flStartRecordTime = host_time; } void CReplayServer::StopRecording() { if ( !m_DemoRecorder.IsRecording() ) return; m_DemoRecorder.StopRecording(); m_flStopRecordTime = host_time; } void CReplayServer::SendPendingEvents() { // Did we recently stop recording? if ( m_flStopRecordTime != 0.0f ) { // Let clients know the server has stopped recording replays g_pReplay->SV_SendReplayEvent( "replay_endrecord", -1 ); // Reset stop record time m_flStopRecordTime = 0.0f; } // Did we recently begin recording? if ( m_flStartRecordTime != 0.0f ) { // Send start record event to everyone g_pReplay->SV_SendReplayEvent( "replay_startrecord", -1 ); // Send recording session info to everyone IGameEvent *pSessionInfoEvent = g_pServerReplayContext->CreateReplaySessionInfoEvent(); if ( pSessionInfoEvent ) { // Let clients know the server is ready to capture replays g_pReplay->SV_SendReplayEvent( pSessionInfoEvent, -1 ); } else { AssertMsg( 0, "Failed to create replay_sessioninfo event!" ); } // Reset the start record timer m_flStartRecordTime = 0.0f; } } #endif