diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/sv_packedentities.cpp | |
| download | archived-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.cpp | 693 |
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]"; + } +} + + + |