summaryrefslogtreecommitdiff
path: root/engine/replayserver.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/replayserver.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'engine/replayserver.cpp')
-rw-r--r--engine/replayserver.cpp1166
1 files changed, 1166 insertions, 0 deletions
diff --git a/engine/replayserver.cpp b/engine/replayserver.cpp
new file mode 100644
index 0000000..1d4664f
--- /dev/null
+++ b/engine/replayserver.cpp
@@ -0,0 +1,1166 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//----------------------------------------------------------------------------------------
+
+#if defined( REPLAY_ENABLED )
+
+#include <server_class.h>
+#include <inetmessage.h>
+#include <tier0/vprof.h>
+#include <tier0/vcrmode.h>
+#include <KeyValues.h>
+#include <edict.h>
+#include <eiface.h>
+#include <PlayerState.h>
+#include <time.h>
+
+#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; i++ )
+ {
+ if ( m_Cache[i] != NULL )
+ {
+ free( m_Cache[i] );
+ m_Cache[i] = NULL;
+ }
+ }
+
+ m_nMaxEntities = 0;
+ }
+
+ m_nCacheSize = 0;
+}
+
+void CReplayDeltaEntityCache::SetTick( int nTick, int nMaxEntities )
+{
+ if ( nTick == m_nTick )
+ return;
+
+ Flush();
+
+ m_nCacheSize = 2 * 1024;
+
+ if ( m_nCacheSize <= 0 )
+ return;
+
+ m_nMaxEntities = MIN(nMaxEntities,MAX_EDICTS);
+ m_nTick = nTick;
+}
+
+unsigned char* CReplayDeltaEntityCache::FindDeltaBits( int nEntityIndex, int nDeltaTick, int &nBits )
+{
+ nBits = -1;
+
+ if ( nEntityIndex < 0 || nEntityIndex >= 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<REPLAY_BUFFER_MAX; i++ )
+ {
+ m_Messages[i].Reset();
+ }
+}
+
+bool CReplayFrame::HasData( void )
+{
+ for ( int i=0; i<REPLAY_BUFFER_MAX; i++ )
+ {
+ if ( m_Messages[i].GetNumBitsWritten() > 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<REPLAY_BUFFER_MAX; i++ )
+ {
+ bf_write &msg = m_Messages[i];
+
+ if ( msg.GetBasePointer() )
+ {
+ delete[] msg.GetBasePointer();
+ msg.StartWriting( NULL, 0 );
+ }
+ }
+}
+
+CReplayServer::CReplayServer()
+: m_DemoRecorder( this )
+{
+ m_flTickInterval = 0.03;
+ m_MasterClient = NULL;
+ m_Server = NULL;
+ m_nFirstTick = -1;
+ m_nLastTick = 0;
+ m_CurrentFrame = NULL;
+ m_nViewEntity = 0;
+ m_nPlayerSlot = 0;
+ m_bSignonState = false;
+ m_flStartTime = 0;
+ m_flFPS = 0;
+ m_nGameServerMaxClients = 0;
+ m_fNextSendUpdateTime = 0;
+ Q_memset( m_pRecvTables, 0, sizeof( m_pRecvTables ) );
+ m_nRecvTables = 0;
+ m_vPVSOrigin.Init();
+ m_bMasterOnlyMode = false;
+ m_nGlobalSlots = 0;
+ m_nGlobalClients = 0;
+ m_nGlobalProxies = 0;
+ m_flStartRecordTime = 0.0f;
+ m_flStopRecordTime = 0.0f;
+}
+
+CReplayServer::~CReplayServer()
+{
+ if ( m_nRecvTables > 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; i<numTables; i++)
+ {
+ // iterate through server tables
+ CNetworkStringTable *serverTable =
+ (CNetworkStringTable*)m_Server->m_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; i<numTables; i++)
+ {
+ // iterate through server tables
+ CNetworkStringTable *pTable = (CNetworkStringTable*) m_StringTables->GetTable( 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 \ No newline at end of file