summaryrefslogtreecommitdiff
path: root/engine/sv_packedentities.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/sv_packedentities.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'engine/sv_packedentities.cpp')
-rw-r--r--engine/sv_packedentities.cpp693
1 files changed, 693 insertions, 0 deletions
diff --git a/engine/sv_packedentities.cpp b/engine/sv_packedentities.cpp
new file mode 100644
index 0000000..5bd3fd9
--- /dev/null
+++ b/engine/sv_packedentities.cpp
@@ -0,0 +1,693 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+
+#include "server_pch.h"
+#include "client.h"
+#include "sv_packedentities.h"
+#include "bspfile.h"
+#include "eiface.h"
+#include "dt_send_eng.h"
+#include "dt_common_eng.h"
+#include "changeframelist.h"
+#include "sv_main.h"
+#include "hltvserver.h"
+#if defined( REPLAY_ENABLED )
+#include "replayserver.h"
+#endif
+#include "dt_instrumentation_server.h"
+#include "LocalNetworkBackdoor.h"
+#include "tier0/vprof.h"
+#include "host.h"
+#include "networkstringtableserver.h"
+#include "networkstringtable.h"
+#include "utlbuffer.h"
+#include "dt.h"
+#include "con_nprint.h"
+#include "smooth_average.h"
+#include "vengineserver_impl.h"
+#include "tier0/vcrmode.h"
+#include "vstdlib/jobthread.h"
+#include "enginethreads.h"
+
+#ifdef SWDS
+IClientEntityList *entitylist = NULL;
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+ConVar sv_debugmanualmode( "sv_debugmanualmode", "0", 0, "Make sure entities correctly report whether or not their network data has changed." );
+
+// Returns false and calls Host_Error if the edict's pvPrivateData is NULL.
+static inline bool SV_EnsurePrivateData(edict_t *pEdict)
+{
+ if(pEdict->GetUnknown())
+ {
+ return true;
+ }
+ else
+ {
+ Host_Error("SV_EnsurePrivateData: pEdict->pvPrivateData==NULL (ent %d).\n", pEdict - sv.edicts);
+ return false;
+ }
+}
+
+// This function makes sure that this entity class has an instance baseline.
+// If it doesn't have one yet, it makes a new one.
+void SV_EnsureInstanceBaseline( ServerClass *pServerClass, int iEdict, const void *pData, int nBytes )
+{
+ edict_t *pEnt = &sv.edicts[iEdict];
+ ErrorIfNot( pEnt->GetNetworkable(),
+ ("SV_EnsureInstanceBaseline: edict %d missing ent", iEdict)
+ );
+
+ ServerClass *pClass = pEnt->GetNetworkable()->GetServerClass();
+
+ // See if we already have a baseline for this class.
+ if ( pClass->m_InstanceBaselineIndex == INVALID_STRING_INDEX )
+ {
+ AUTO_LOCK( g_svInstanceBaselineMutex );
+
+ // We need this second check in case multiple instances of the same class have grabbed the lock.
+ if ( pClass->m_InstanceBaselineIndex == INVALID_STRING_INDEX )
+ {
+ char idString[32];
+ Q_snprintf( idString, sizeof( idString ), "%d", pClass->m_ClassID );
+
+ // Ok, make a new instance baseline so they can reference it.
+ int temp = sv.GetInstanceBaselineTable()->AddString(
+ true,
+ idString, // Note we're sending a string with the ID number, not the class name.
+ nBytes,
+ pData );
+
+ // Insert a compiler and/or CPU memory barrier to ensure that all side-effects have
+ // been published before the index is published. Otherwise the string index may
+ // be visible before its initialization has finished. This potential problem is caused
+ // by the use of double-checked locking -- the problem is that the code outside of the
+ // lock is looking at the variable that is protected by the lock. See this article for details:
+ // http://en.wikipedia.org/wiki/Double-checked_locking
+ // Write-release barrier
+ ThreadMemoryBarrier();
+ pClass->m_InstanceBaselineIndex = temp;
+ Assert( pClass->m_InstanceBaselineIndex != INVALID_STRING_INDEX );
+ }
+ }
+ // Read-acquire barrier
+ ThreadMemoryBarrier();
+}
+
+//-----------------------------------------------------------------------------
+// Pack the entity....
+//-----------------------------------------------------------------------------
+
+static inline void SV_PackEntity(
+ int edictIdx,
+ edict_t* edict,
+ ServerClass* pServerClass,
+ CFrameSnapshot *pSnapshot )
+{
+ Assert( edictIdx < pSnapshot->m_nNumEntities );
+ tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "PackEntities_Normal%s", __FUNCTION__ );
+
+ int iSerialNum = pSnapshot->m_pEntities[ edictIdx ].m_nSerialNumber;
+
+ // Check to see if this entity specifies its changes.
+ // If so, then try to early out making the fullpack
+ bool bUsedPrev = false;
+
+ if ( !edict->HasStateChanged() )
+ {
+ // Now this may not work if we didn't previously send a packet;
+ // if not, then we gotta compute it
+ bUsedPrev = framesnapshotmanager->UsePreviouslySentPacket( pSnapshot, edictIdx, iSerialNum );
+ }
+
+ if ( bUsedPrev && !sv_debugmanualmode.GetInt() )
+ {
+ edict->ClearStateChanged();
+ return;
+ }
+
+ // First encode the entity's data.
+ ALIGN4 char packedData[MAX_PACKEDENTITY_DATA] ALIGN4_POST;
+ bf_write writeBuf( "SV_PackEntity->writeBuf", packedData, sizeof( packedData ) );
+
+ SendTable *pSendTable = pServerClass->m_pTable;
+
+ // (avoid constructor overhead).
+ unsigned char tempData[ sizeof( CSendProxyRecipients ) * MAX_DATATABLE_PROXIES ];
+ CUtlMemory< CSendProxyRecipients > recip( (CSendProxyRecipients*)tempData, pSendTable->m_pPrecalc->GetNumDataTableProxies() );
+
+ if( !SendTable_Encode( pSendTable, edict->GetUnknown(), &writeBuf, edictIdx, &recip, false ) )
+ {
+ Host_Error( "SV_PackEntity: SendTable_Encode returned false (ent %d).\n", edictIdx );
+ }
+
+#ifndef NO_VCR
+ // VCR mode stuff..
+ if ( vcr_verbose.GetInt() && writeBuf.GetNumBytesWritten() > 0 )
+ VCRGenericValueVerify( "writebuf", writeBuf.GetBasePointer(), writeBuf.GetNumBytesWritten()-1 );
+#endif
+
+ SV_EnsureInstanceBaseline( pServerClass, edictIdx, packedData, writeBuf.GetNumBytesWritten() );
+
+ int nFlatProps = SendTable_GetNumFlatProps( pSendTable );
+ IChangeFrameList *pChangeFrame = NULL;
+
+ // If this entity was previously in there, then it should have a valid IChangeFrameList
+ // which we can delta against to figure out which properties have changed.
+ //
+ // If not, then we want to setup a new IChangeFrameList.
+
+ PackedEntity *pPrevFrame = framesnapshotmanager->GetPreviouslySentPacket( edictIdx, pSnapshot->m_pEntities[ edictIdx ].m_nSerialNumber );
+ if ( pPrevFrame )
+ {
+ // Calculate a delta.
+ Assert( !pPrevFrame->IsCompressed() );
+
+ int deltaProps[MAX_DATATABLE_PROPS];
+
+ int nChanges = SendTable_CalcDelta(
+ pSendTable,
+ pPrevFrame->GetData(), pPrevFrame->GetNumBits(),
+ packedData, writeBuf.GetNumBitsWritten(),
+
+ deltaProps,
+ ARRAYSIZE( deltaProps ),
+
+ edictIdx
+ );
+
+#ifndef NO_VCR
+ if ( vcr_verbose.GetInt() )
+ VCRGenericValueVerify( "nChanges", &nChanges, sizeof( nChanges ) );
+#endif
+
+ // If it's non-manual-mode, but we detect that there are no changes here, then just
+ // use the previous pSnapshot if it's available (as though the entity were manual mode).
+ // It would be interesting to hook here and see how many non-manual-mode entities
+ // are winding up with no changes.
+ if ( nChanges == 0 )
+ {
+ if ( pPrevFrame->CompareRecipients( recip ) )
+ {
+ if ( framesnapshotmanager->UsePreviouslySentPacket( pSnapshot, edictIdx, iSerialNum ) )
+ {
+ edict->ClearStateChanged();
+ return;
+ }
+ }
+ }
+ else
+ {
+ if ( !edict->HasStateChanged() )
+ {
+ for ( int iDeltaProp=0; iDeltaProp < nChanges; iDeltaProp++ )
+ {
+ Assert( pSendTable->m_pPrecalc );
+ Assert( deltaProps[iDeltaProp] < pSendTable->m_pPrecalc->GetNumProps() );
+
+ const SendProp *pProp = pSendTable->m_pPrecalc->GetProp( deltaProps[iDeltaProp] );
+ // If a field changed, but it changed because it encoded against tickcount,
+ // then it's just like the entity changed the underlying field, not an error, that is.
+ if ( pProp->GetFlags() & SPROP_ENCODED_AGAINST_TICKCOUNT )
+ continue;
+
+ Msg( "Entity %d (class '%s') reported ENTITY_CHANGE_NONE but '%s' changed.\n",
+ edictIdx,
+ edict->GetClassName(),
+ pProp->GetName() );
+
+ }
+ }
+ }
+
+#ifndef _XBOX
+#if defined( REPLAY_ENABLED )
+ if ( (hltv && hltv->IsActive()) || (replay && replay->IsActive()) )
+#else
+ if ( hltv && hltv->IsActive() )
+#endif
+ {
+ // in HLTV or Replay mode every PackedEntity keeps it's own ChangeFrameList
+ // we just copy the ChangeFrameList from prev frame and update it
+ pChangeFrame = pPrevFrame->GetChangeFrameList();
+ pChangeFrame = pChangeFrame->Copy(); // allocs and copies ChangeFrameList
+ }
+ else
+#endif
+ {
+ // Ok, now snag the changeframe from the previous frame and update the 'last frame changed'
+ // for the properties in the delta.
+ pChangeFrame = pPrevFrame->SnagChangeFrameList();
+ }
+
+ ErrorIfNot( pChangeFrame,
+ ("SV_PackEntity: SnagChangeFrameList returned null") );
+ ErrorIfNot( pChangeFrame->GetNumProps() == nFlatProps,
+ ("SV_PackEntity: SnagChangeFrameList mismatched number of props[%d vs %d]", nFlatProps, pChangeFrame->GetNumProps() ) );
+
+ pChangeFrame->SetChangeTick( deltaProps, nChanges, pSnapshot->m_nTickCount );
+ }
+ else
+ {
+ // Ok, init the change frames for the first time.
+ pChangeFrame = AllocChangeFrameList( nFlatProps, pSnapshot->m_nTickCount );
+ }
+
+ // Now make a PackedEntity and store the new packed data in there.
+ {
+ PackedEntity *pPackedEntity = framesnapshotmanager->CreatePackedEntity( pSnapshot, edictIdx );
+ pPackedEntity->SetChangeFrameList( pChangeFrame );
+ pPackedEntity->SetServerAndClientClass( pServerClass, NULL );
+ pPackedEntity->AllocAndCopyPadded( packedData, writeBuf.GetNumBytesWritten() );
+ pPackedEntity->SetRecipients( recip );
+ }
+
+ edict->ClearStateChanged();
+}
+
+// in HLTV mode we ALWAYS have to store position and PVS info, even if entity didnt change
+void SV_FillHLTVData( CFrameSnapshot *pSnapshot, edict_t *edict, int iValidEdict )
+{
+#if !defined( _XBOX )
+ if ( pSnapshot->m_pHLTVEntityData && edict )
+ {
+ CHLTVEntityData *pHLTVData = &pSnapshot->m_pHLTVEntityData[iValidEdict];
+
+ PVSInfo_t *pvsInfo = edict->GetNetworkable()->GetPVSInfo();
+
+ if ( pvsInfo->m_nClusterCount == 1 )
+ {
+ // store cluster, if entity spawns only over one cluster
+ pHLTVData->m_nNodeCluster = pvsInfo->m_pClusters[0];
+ }
+ else
+ {
+ // otherwise save PVS head node for larger entities
+ pHLTVData->m_nNodeCluster = pvsInfo->m_nHeadNode | (1<<31);
+ }
+
+ // remember origin
+ pHLTVData->origin[0] = pvsInfo->m_vCenter[0];
+ pHLTVData->origin[1] = pvsInfo->m_vCenter[1];
+ pHLTVData->origin[2] = pvsInfo->m_vCenter[2];
+ }
+#endif
+}
+
+// in Replay mode we ALWAYS have to store position and PVS info, even if entity didnt change
+void SV_FillReplayData( CFrameSnapshot *pSnapshot, edict_t *edict, int iValidEdict )
+{
+#if !defined( _XBOX )
+ if ( pSnapshot->m_pReplayEntityData && edict )
+ {
+ CReplayEntityData *pReplayData = &pSnapshot->m_pReplayEntityData[iValidEdict];
+
+ PVSInfo_t *pvsInfo = edict->GetNetworkable()->GetPVSInfo();
+
+ if ( pvsInfo->m_nClusterCount == 1 )
+ {
+ // store cluster, if entity spawns only over one cluster
+ pReplayData->m_nNodeCluster = pvsInfo->m_pClusters[0];
+ }
+ else
+ {
+ // otherwise save PVS head node for larger entities
+ pReplayData->m_nNodeCluster = pvsInfo->m_nHeadNode | (1<<31);
+ }
+
+ // remember origin
+ pReplayData->origin[0] = pvsInfo->m_vCenter[0];
+ pReplayData->origin[1] = pvsInfo->m_vCenter[1];
+ pReplayData->origin[2] = pvsInfo->m_vCenter[2];
+ }
+#endif
+}
+
+// Returns the SendTable that should be used with the specified edict.
+SendTable* GetEntSendTable(edict_t *pEdict)
+{
+ if ( pEdict->GetNetworkable() )
+ {
+ ServerClass *pClass = pEdict->GetNetworkable()->GetServerClass();
+ if ( pClass )
+ {
+ return pClass->m_pTable;
+ }
+ }
+
+ return NULL;
+}
+
+
+void PackEntities_NetworkBackDoor(
+ int clientCount,
+ CGameClient **clients,
+ CFrameSnapshot *snapshot )
+{
+ Assert( clientCount == 1 );
+
+ VPROF_BUDGET( "PackEntities_NetworkBackDoor", VPROF_BUDGETGROUP_OTHER_NETWORKING );
+
+ CGameClient *client = clients[0]; // update variables cl, pInfo, frame for current client
+ CCheckTransmitInfo *pInfo = &client->m_PackInfo;
+
+ for ( int iValidEdict=0; iValidEdict < snapshot->m_nValidEntities; iValidEdict++ )
+ {
+ int index = snapshot->m_pValidEntities[iValidEdict];
+ edict_t* edict = &sv.edicts[ index ];
+
+ // this is a bit of a hack to ensure that we get a "preview" of the
+ // packet timstamp that the server will send so that things that
+ // are encoded relative to packet time will be correct
+ Assert( edict->m_NetworkSerialNumber != -1 );
+
+ bool bShouldTransmit = pInfo->m_pTransmitEdict->Get( index ) ? true : false;
+
+ //CServerDTITimer timer( pSendTable, SERVERDTI_ENCODE );
+ // If we're using the fast path for a single-player game, just pass the entity
+ // directly over to the client.
+ Assert( index < snapshot->m_nNumEntities );
+ ServerClass *pSVClass = snapshot->m_pEntities[ index ].m_pClass;
+ g_pLocalNetworkBackdoor->EntState( index, edict->m_NetworkSerialNumber,
+ pSVClass->m_ClassID, pSVClass->m_pTable, edict->GetUnknown(), edict->HasStateChanged(), bShouldTransmit );
+ edict->ClearStateChanged();
+ }
+
+ // Tell the client about any entities that are now dormant.
+ g_pLocalNetworkBackdoor->ProcessDormantEntities();
+ InvalidateSharedEdictChangeInfos();
+}
+
+static ConVar sv_parallel_packentities( "sv_parallel_packentities", "1" );
+
+struct PackWork_t
+{
+ int nIdx;
+ edict_t *pEdict;
+ CFrameSnapshot *pSnapshot;
+
+ static void Process( PackWork_t &item )
+ {
+ SV_PackEntity( item.nIdx, item.pEdict, item.pSnapshot->m_pEntities[ item.nIdx ].m_pClass, item.pSnapshot );
+ }
+};
+
+void PackEntities_Normal(
+ int clientCount,
+ CGameClient **clients,
+ CFrameSnapshot *snapshot )
+{
+ Assert( snapshot->m_nValidEntities >= 0 && snapshot->m_nValidEntities <= MAX_EDICTS );
+ tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s %d", __FUNCTION__, snapshot->m_nValidEntities );
+
+ CUtlVectorFixed< PackWork_t, MAX_EDICTS > workItems;
+
+ // check for all active entities, if they are seen by at least on client, if
+ // so, bit pack them
+ for ( int iValidEdict=0; iValidEdict < snapshot->m_nValidEntities; ++iValidEdict )
+ {
+ int index = snapshot->m_pValidEntities[ iValidEdict ];
+
+ Assert( index < snapshot->m_nNumEntities );
+
+ edict_t* edict = &sv.edicts[ index ];
+
+ // if HLTV is running save PVS info for each entity
+ SV_FillHLTVData( snapshot, edict, iValidEdict );
+
+ // if Replay is running save PVS info for each entity
+ SV_FillReplayData( snapshot, edict, iValidEdict );
+
+ // Check to see if the entity changed this frame...
+ //ServerDTI_RegisterNetworkStateChange( pSendTable, ent->m_bStateChanged );
+
+ for ( int iClient = 0; iClient < clientCount; ++iClient )
+ {
+ // entities is seen by at least this client, pack it and exit loop
+ CGameClient *client = clients[iClient]; // update variables cl, pInfo, frame for current client
+ CClientFrame *frame = client->m_pCurrentFrame;
+
+ if( frame->transmit_entity.Get( index ) )
+ {
+ PackWork_t w;
+ w.nIdx = index;
+ w.pEdict = edict;
+ w.pSnapshot = snapshot;
+
+ workItems.AddToTail( w );
+ break;
+ }
+ }
+ }
+
+ // Process work
+ if ( sv_parallel_packentities.GetBool() )
+ {
+ ParallelProcess( "PackWork_t::Process", workItems.Base(), workItems.Count(), &PackWork_t::Process );
+ }
+ else
+ {
+ int c = workItems.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ PackWork_t &w = workItems[ i ];
+ SV_PackEntity( w.nIdx, w.pEdict, w.pSnapshot->m_pEntities[ w.nIdx ].m_pClass, w.pSnapshot );
+ }
+ }
+
+ InvalidateSharedEdictChangeInfos();
+}
+
+
+//-----------------------------------------------------------------------------
+// Writes the compressed packet of entities to all clients
+//-----------------------------------------------------------------------------
+
+void SV_ComputeClientPacks(
+ int clientCount,
+ CGameClient **clients,
+ CFrameSnapshot *snapshot )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ MDLCACHE_CRITICAL_SECTION_(g_pMDLCache);
+ // Do some setup for each client
+ {
+ VPROF_BUDGET_FLAGS( "SV_ComputeClientPacks", "CheckTransmit", BUDGETFLAG_SERVER );
+
+ for (int iClient = 0; iClient < clientCount; ++iClient)
+ {
+ CCheckTransmitInfo *pInfo = &clients[iClient]->m_PackInfo;
+ clients[iClient]->SetupPackInfo( snapshot );
+ serverGameEnts->CheckTransmit( pInfo, snapshot->m_pValidEntities, snapshot->m_nValidEntities );
+ clients[iClient]->SetupPrevPackInfo();
+ }
+ }
+
+ VPROF_BUDGET_FLAGS( "SV_ComputeClientPacks", "ComputeClientPacks", BUDGETFLAG_SERVER );
+
+ if ( g_pLocalNetworkBackdoor )
+ {
+ // This will force network string table updates for local client to go through the backdoor if it's active
+#ifdef SHARED_NET_STRING_TABLES
+ sv.m_StringTables->TriggerCallbacks( clients[0]->m_nDeltaTick );
+#else
+ sv.m_StringTables->DirectUpdate( clients[0]->GetMaxAckTickCount() );
+#endif
+
+ g_pLocalNetworkBackdoor->StartEntityStateUpdate();
+
+#ifndef SWDS
+ int saveClientTicks = cl.GetClientTickCount();
+ int saveServerTicks = cl.GetServerTickCount();
+ bool bSaveSimulation = cl.insimulation;
+ float flSaveLastServerTickTime = cl.m_flLastServerTickTime;
+
+ cl.insimulation = true;
+ cl.SetClientTickCount( sv.m_nTickCount );
+ cl.SetServerTickCount( sv.m_nTickCount );
+
+ cl.m_flLastServerTickTime = sv.m_nTickCount * host_state.interval_per_tick;
+ g_ClientGlobalVariables.tickcount = cl.GetClientTickCount();
+ g_ClientGlobalVariables.curtime = cl.GetTime();
+#endif
+
+ PackEntities_NetworkBackDoor( clientCount, clients, snapshot );
+
+ g_pLocalNetworkBackdoor->EndEntityStateUpdate();
+
+#ifndef SWDS
+ cl.SetClientTickCount( saveClientTicks );
+ cl.SetServerTickCount( saveServerTicks );
+ cl.insimulation = bSaveSimulation;
+ cl.m_flLastServerTickTime = flSaveLastServerTickTime;
+
+ g_ClientGlobalVariables.tickcount = cl.GetClientTickCount();
+ g_ClientGlobalVariables.curtime = cl.GetTime();
+#endif
+
+ PrintPartialChangeEntsList();
+ }
+ else
+ {
+ PackEntities_Normal( clientCount, clients, snapshot );
+ }
+}
+
+
+
+// If the table's ID is -1, writes its info into the buffer and increments curID.
+void SV_MaybeWriteSendTable( SendTable *pTable, bf_write &pBuf, bool bNeedDecoder )
+{
+ // Already sent?
+ if ( pTable->GetWriteFlag() )
+ return;
+
+ pTable->SetWriteFlag( true );
+
+ SVC_SendTable sndTbl;
+
+ byte tmpbuf[4096];
+ sndTbl.m_DataOut.StartWriting( tmpbuf, sizeof(tmpbuf) );
+
+ // write send table properties into message buffer
+ SendTable_WriteInfos(pTable, &sndTbl.m_DataOut );
+
+ sndTbl.m_bNeedsDecoder = bNeedDecoder;
+
+ // write message to stream
+ sndTbl.WriteToBuffer( pBuf );
+}
+
+// Calls SV_MaybeWriteSendTable recursively.
+void SV_MaybeWriteSendTable_R( SendTable *pTable, bf_write &pBuf )
+{
+ SV_MaybeWriteSendTable( pTable, pBuf, false );
+
+ // Make sure we send child send tables..
+ for(int i=0; i < pTable->m_nProps; i++)
+ {
+ SendProp *pProp = &pTable->m_pProps[i];
+
+ if( pProp->m_Type == DPT_DataTable )
+ SV_MaybeWriteSendTable_R( pProp->GetDataTable(), pBuf );
+ }
+}
+
+
+// Sets up SendTable IDs and sends an svc_sendtable for each table.
+void SV_WriteSendTables( ServerClass *pClasses, bf_write &pBuf )
+{
+ ServerClass *pCur;
+
+ DataTable_ClearWriteFlags( pClasses );
+
+ // First, we send all the leaf classes. These are the ones that will need decoders
+ // on the client.
+ for ( pCur=pClasses; pCur; pCur=pCur->m_pNext )
+ {
+ SV_MaybeWriteSendTable( pCur->m_pTable, pBuf, true );
+ }
+
+ // Now, we send their base classes. These don't need decoders on the client
+ // because we will never send these SendTables by themselves.
+ for ( pCur=pClasses; pCur; pCur=pCur->m_pNext )
+ {
+ SV_MaybeWriteSendTable_R( pCur->m_pTable, pBuf );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : crc -
+//-----------------------------------------------------------------------------
+void SV_ComputeClassInfosCRC( CRC32_t* crc )
+{
+ ServerClass *pClasses = serverGameDLL->GetAllServerClasses();
+
+ for ( ServerClass *pClass=pClasses; pClass; pClass=pClass->m_pNext )
+ {
+ CRC32_ProcessBuffer( crc, (void *)pClass->m_pNetworkName, Q_strlen( pClass->m_pNetworkName ) );
+ CRC32_ProcessBuffer( crc, (void *)pClass->m_pTable->GetName(), Q_strlen(pClass->m_pTable->GetName() ) );
+ }
+}
+
+void CGameServer::AssignClassIds()
+{
+ ServerClass *pClasses = serverGameDLL->GetAllServerClasses();
+
+ // Count the number of classes.
+ int nClasses = 0;
+ for ( ServerClass *pCount=pClasses; pCount; pCount=pCount->m_pNext )
+ {
+ ++nClasses;
+ }
+
+ // These should be the same! If they're not, then it should detect an explicit create message.
+ ErrorIfNot( nClasses <= MAX_SERVER_CLASSES,
+ ("CGameServer::AssignClassIds: too many server classes (%i, MAX = %i).\n", nClasses, MAX_SERVER_CLASSES );
+ );
+
+ serverclasses = nClasses;
+ serverclassbits = Q_log2( serverclasses ) + 1;
+
+ bool bSpew = CommandLine()->FindParm( "-netspike" ) != 0;
+
+ int curID = 0;
+ for ( ServerClass *pClass=pClasses; pClass; pClass=pClass->m_pNext )
+ {
+ pClass->m_ClassID = curID++;
+
+ if ( bSpew )
+ {
+ Msg( "%d == '%s'\n", pClass->m_ClassID, pClass->GetName() );
+ }
+ }
+}
+
+// Assign each class and ID and write an svc_classinfo for each one.
+void SV_WriteClassInfos(ServerClass *pClasses, bf_write &pBuf)
+{
+ // Assert( sv.serverclasses < MAX_SERVER_CLASSES );
+
+ SVC_ClassInfo classinfomsg;
+
+ classinfomsg.m_bCreateOnClient = false;
+
+ for ( ServerClass *pClass=pClasses; pClass; pClass=pClass->m_pNext )
+ {
+ SVC_ClassInfo::class_t svclass;
+
+ svclass.classID = pClass->m_ClassID;
+ Q_strncpy( svclass.datatablename, pClass->m_pTable->GetName(), sizeof(svclass.datatablename) );
+ Q_strncpy( svclass.classname, pClass->m_pNetworkName, sizeof(svclass.classname) );
+
+ classinfomsg.m_Classes.AddToTail( svclass ); // add all known classes to message
+ }
+
+ classinfomsg.WriteToBuffer( pBuf );
+}
+
+// This is implemented for the datatable code so its warnings can include an object's classname.
+const char* GetObjectClassName( int objectID )
+{
+ if ( objectID >= 0 && objectID < sv.num_edicts )
+ {
+ return sv.edicts[objectID].GetClassName();
+ }
+ else
+ {
+ return "[unknown]";
+ }
+}
+
+
+