You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2426 lines
56 KiB
2426 lines
56 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
// hltvserver.cpp: implementation of the CHLTVServer class. |
|
// |
|
////////////////////////////////////////////////////////////////////// |
|
|
|
#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 <ihltvdirector.h> |
|
#include <time.h> |
|
|
|
#include "hltvserver.h" |
|
#include "sv_client.h" |
|
#include "hltvclient.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 "proto_oob.h" |
|
#include "dt_common_eng.h" |
|
#include "baseautocompletefilelist.h" |
|
#include "sv_steamauth.h" |
|
#include "tier0/icommandline.h" |
|
#include "sys_dll.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#define S2A_EXTRA_DATA_HAS_GAME_PORT 0x80 // Next 2 bytes include the game port. |
|
#define S2A_EXTRA_DATA_HAS_SPECTATOR_DATA 0x40 // Next 2 bytes include the spectator port, then the spectator server name. |
|
#define S2A_EXTRA_DATA_HAS_GAMETAG_DATA 0x20 // Next bytes are the game tag string |
|
#define S2A_EXTRA_DATA_HAS_STEAMID 0x10 // Next 8 bytes are the steamID |
|
#define S2A_EXTRA_DATA_GAMEID 0x01 // Next 8 bytes are the gameID of the server |
|
|
|
#define A2S_KEY_STRING_STEAM "Source Engine Query" // required postfix to a A2S_INFO query |
|
|
|
extern CNetworkStringTableContainer *networkStringTableContainerClient; |
|
extern ConVar sv_tags; |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
// Construction/Destruction |
|
////////////////////////////////////////////////////////////////////// |
|
|
|
CHLTVServer *hltv = NULL; |
|
|
|
static void tv_title_changed_f( IConVar *var, const char *pOldString, float flOldValue ) |
|
{ |
|
if ( hltv && hltv->IsActive() ) |
|
{ |
|
hltv->BroadcastLocalTitle(); |
|
} |
|
} |
|
|
|
static void tv_name_changed_f( IConVar *var, const char *pOldValue, float flOldValue ) |
|
{ |
|
Steam3Server().NotifyOfServerNameChange(); |
|
} |
|
|
|
static ConVar tv_maxclients( "tv_maxclients", "128", 0, "Maximum client number on SourceTV server.", |
|
true, 0, true, 255 ); |
|
|
|
ConVar tv_autorecord( "tv_autorecord", "0", 0, "Automatically records all games as SourceTV demos." ); |
|
ConVar tv_name( "tv_name", "SourceTV", 0, "SourceTV host name", tv_name_changed_f ); |
|
static ConVar tv_password( "tv_password", "", FCVAR_NOTIFY | FCVAR_PROTECTED | FCVAR_DONTRECORD, "SourceTV password for all clients" ); |
|
|
|
static ConVar tv_overridemaster( "tv_overridemaster", "0", 0, "Overrides the SourceTV master root address." ); |
|
static ConVar tv_dispatchmode( "tv_dispatchmode", "1", 0, "Dispatch clients to relay proxies: 0=never, 1=if appropriate, 2=always" ); |
|
ConVar tv_transmitall( "tv_transmitall", "0", FCVAR_REPLICATED, "Transmit all entities (not only director view)" ); |
|
ConVar tv_debug( "tv_debug", "0", 0, "SourceTV debug info." ); |
|
ConVar tv_title( "tv_title", "SourceTV", 0, "Set title for SourceTV spectator UI", tv_title_changed_f ); |
|
static ConVar tv_deltacache( "tv_deltacache", "2", 0, "Enable delta entity bit stream cache" ); |
|
static ConVar tv_relayvoice( "tv_relayvoice", "1", 0, "Relay voice data: 0=off, 1=on" ); |
|
|
|
CDeltaEntityCache::CDeltaEntityCache() |
|
{ |
|
Q_memset( m_Cache, 0, sizeof(m_Cache) ); |
|
m_nTick = 0; |
|
m_nMaxEntities = 0; |
|
m_nCacheSize = 0; |
|
} |
|
|
|
CDeltaEntityCache::~CDeltaEntityCache() |
|
{ |
|
Flush(); |
|
} |
|
|
|
void CDeltaEntityCache::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 CDeltaEntityCache::SetTick( int nTick, int nMaxEntities ) |
|
{ |
|
if ( nTick == m_nTick ) |
|
return; |
|
|
|
Flush(); |
|
|
|
m_nCacheSize = tv_deltacache.GetInt() * 1024; |
|
|
|
if ( m_nCacheSize <= 0 ) |
|
return; |
|
|
|
m_nMaxEntities = min(nMaxEntities,MAX_EDICTS); |
|
m_nTick = nTick; |
|
} |
|
|
|
unsigned char* CDeltaEntityCache::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 CDeltaEntityCache::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 CHLTVServer::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 CHLTVServer::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("HLTV_InitRecvTableMgr: failed to allocate client class %s.\n", pCur->m_pNetworkName ); |
|
return; |
|
} |
|
} |
|
|
|
RecvTable_Init( m_pRecvTables, m_nRecvTables ); |
|
} |
|
|
|
|
|
|
|
CHLTVFrame::CHLTVFrame() |
|
{ |
|
|
|
} |
|
|
|
CHLTVFrame::~CHLTVFrame() |
|
{ |
|
FreeBuffers(); |
|
} |
|
|
|
void CHLTVFrame::Reset( void ) |
|
{ |
|
for ( int i=0; i<HLTV_BUFFER_MAX; i++ ) |
|
{ |
|
m_Messages[i].Reset(); |
|
} |
|
} |
|
|
|
bool CHLTVFrame::HasData( void ) |
|
{ |
|
for ( int i=0; i<HLTV_BUFFER_MAX; i++ ) |
|
{ |
|
if ( m_Messages[i].GetNumBitsWritten() > 0 ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void CHLTVFrame::CopyHLTVData( CHLTVFrame &frame ) |
|
{ |
|
// copy reliable messages |
|
int bits = frame.m_Messages[HLTV_BUFFER_RELIABLE].GetNumBitsWritten(); |
|
|
|
if ( bits > 0 ) |
|
{ |
|
int bytes = PAD_NUMBER( Bits2Bytes(bits), 4 ); |
|
m_Messages[HLTV_BUFFER_RELIABLE].StartWriting( new char[ bytes ], bytes, bits ); |
|
Q_memcpy( m_Messages[HLTV_BUFFER_RELIABLE].GetBasePointer(), frame.m_Messages[HLTV_BUFFER_RELIABLE].GetBasePointer(), bytes ); |
|
} |
|
|
|
// copy unreliable messages |
|
bits = frame.m_Messages[HLTV_BUFFER_UNRELIABLE].GetNumBitsWritten(); |
|
bits += frame.m_Messages[HLTV_BUFFER_TEMPENTS].GetNumBitsWritten(); |
|
bits += frame.m_Messages[HLTV_BUFFER_SOUNDS].GetNumBitsWritten(); |
|
|
|
if ( tv_relayvoice.GetBool() ) |
|
bits += frame.m_Messages[HLTV_BUFFER_VOICE].GetNumBitsWritten(); |
|
|
|
if ( bits > 0 ) |
|
{ |
|
// collapse all unreliable buffers in one |
|
int bytes = PAD_NUMBER( Bits2Bytes(bits), 4 ); |
|
m_Messages[HLTV_BUFFER_UNRELIABLE].StartWriting( new char[ bytes ], bytes ); |
|
m_Messages[HLTV_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[HLTV_BUFFER_UNRELIABLE].GetData(), frame.m_Messages[HLTV_BUFFER_UNRELIABLE].GetNumBitsWritten() ); |
|
m_Messages[HLTV_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[HLTV_BUFFER_TEMPENTS].GetData(), frame.m_Messages[HLTV_BUFFER_TEMPENTS].GetNumBitsWritten() ); |
|
m_Messages[HLTV_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[HLTV_BUFFER_SOUNDS].GetData(), frame.m_Messages[HLTV_BUFFER_SOUNDS].GetNumBitsWritten() ); |
|
|
|
if ( tv_relayvoice.GetBool() ) |
|
m_Messages[HLTV_BUFFER_UNRELIABLE].WriteBits( frame.m_Messages[HLTV_BUFFER_VOICE].GetData(), frame.m_Messages[HLTV_BUFFER_VOICE].GetNumBitsWritten() ); |
|
} |
|
} |
|
|
|
void CHLTVFrame::AllocBuffers( void ) |
|
{ |
|
// allocate buffers for input frame |
|
for ( int i=0; i < HLTV_BUFFER_MAX; i++ ) |
|
{ |
|
Assert( m_Messages[i].GetBasePointer() == NULL ); |
|
m_Messages[i].StartWriting( new char[NET_MAX_PAYLOAD], NET_MAX_PAYLOAD); |
|
} |
|
} |
|
|
|
void CHLTVFrame::FreeBuffers( void ) |
|
{ |
|
for ( int i=0; i<HLTV_BUFFER_MAX; i++ ) |
|
{ |
|
bf_write &msg = m_Messages[i]; |
|
|
|
if ( msg.GetBasePointer() ) |
|
{ |
|
delete[] msg.GetBasePointer(); |
|
msg.StartWriting( NULL, 0 ); |
|
} |
|
} |
|
} |
|
|
|
CHLTVServer::CHLTVServer() |
|
{ |
|
m_flTickInterval = 0.03; |
|
m_MasterClient = NULL; |
|
m_Server = NULL; |
|
m_Director = 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_nStartTick = 0; |
|
m_bPlayingBack = false; |
|
m_bPlaybackPaused = false; |
|
m_flPlaybackRateModifier = 0; |
|
m_nSkipToTick = 0; |
|
m_bMasterOnlyMode = false; |
|
m_ClientState.m_pHLTV = this; |
|
m_nGlobalSlots = 0; |
|
m_nGlobalClients = 0; |
|
m_nGlobalProxies = 0; |
|
} |
|
|
|
CHLTVServer::~CHLTVServer() |
|
{ |
|
if ( m_nRecvTables > 0 ) |
|
{ |
|
RecvTable_Term(); |
|
FreeClientRecvTables(); |
|
} |
|
|
|
// make sure everything was destroyed |
|
Assert( m_CurrentFrame == NULL ); |
|
Assert( CountClientFrames() == 0 ); |
|
} |
|
|
|
void CHLTVServer::SetMaxClients( int number ) |
|
{ |
|
// allow max clients 0 in HLTV |
|
m_nMaxclients = clamp( number, 0, ABSOLUTE_PLAYER_LIMIT ); |
|
} |
|
|
|
void CHLTVServer::StartMaster(CGameClient *client) |
|
{ |
|
Clear(); // clear old settings & buffers |
|
|
|
if ( !client ) |
|
{ |
|
ConMsg("SourceTV client not found.\n"); |
|
return; |
|
} |
|
|
|
m_Director = serverGameDirector; |
|
|
|
if ( !m_Director ) |
|
{ |
|
ConMsg("Mod doesn't support SourceTV. No director module found.\n"); |
|
return; |
|
} |
|
|
|
m_MasterClient = client; |
|
m_MasterClient->m_bIsHLTV = true; |
|
#if defined( REPLAY_ENABLED ) |
|
m_MasterClient->m_bIsReplay = false; |
|
#endif |
|
|
|
// let game.dll know that we are the HLTV client |
|
Assert( serverGameClients ); |
|
|
|
CPlayerState *player = serverGameClients->GetPlayerState( m_MasterClient->edict ); |
|
player->hltv = true; |
|
|
|
m_Server = (CGameServer*)m_MasterClient->GetServer(); |
|
|
|
// set default user settings |
|
m_MasterClient->m_ConVars->SetString( "name", tv_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; |
|
V_memcpy( worldmapMD5.bits, m_Server->worldmapMD5.bits, MD5_DIGEST_LENGTH ); |
|
m_flTickInterval= m_Server->GetTickInterval(); |
|
|
|
// allocate buffers for input frame |
|
m_HLTVFrame.AllocBuffers(); |
|
|
|
InstallStringTables(); |
|
|
|
// activate director in game.dll |
|
m_Director->SetHLTVServer( this ); |
|
|
|
// register as listener for mod specific events |
|
const char **modevents = m_Director->GetModEvents(); |
|
|
|
int j = 0; |
|
while ( modevents[j] != NULL ) |
|
{ |
|
const char *eventname = modevents[j]; |
|
|
|
CGameEventDescriptor *descriptor = g_GameEventManager.GetEventDescriptor( eventname ); |
|
|
|
if ( descriptor ) |
|
{ |
|
g_GameEventManager.AddListener( this, descriptor, CGameEventManager::CLIENTSTUB ); |
|
} |
|
else |
|
{ |
|
DevMsg("CHLTVServer::StartMaster: game event %s not found.\n", eventname ); |
|
} |
|
|
|
j++; |
|
} |
|
|
|
// 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) ); |
|
|
|
NET_ListenSocket( m_Socket, true ); // activated HLTV TCP socket |
|
|
|
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--; |
|
|
|
if ( m_bMasterOnlyMode ) |
|
{ |
|
// we allow only one client in master only mode |
|
tv_maxclients.SetValue( min(1,tv_maxclients.GetInt()) ); |
|
} |
|
|
|
SetMaxClients( tv_maxclients.GetInt() ); |
|
|
|
m_bSignonState = false; //master proxy is instantly connected |
|
|
|
m_nSpawnCount++; |
|
|
|
m_flStartTime = net_time; |
|
|
|
m_State = ss_active; |
|
|
|
// stop any previous recordings |
|
m_DemoRecorder.StopRecording(); |
|
|
|
// start new recording if autorecord is enabled |
|
if ( tv_autorecord.GetBool() ) |
|
{ |
|
m_DemoRecorder.StartAutoRecording(); |
|
} |
|
|
|
ReconnectClients(); |
|
} |
|
|
|
void CHLTVServer::StartDemo(const char *filename) |
|
{ |
|
|
|
} |
|
|
|
bool CHLTVServer::DispatchToRelay( CHLTVClient *pClient ) |
|
{ |
|
if ( tv_dispatchmode.GetInt() <= DISPATCH_MODE_OFF ) |
|
return false; // don't redirect |
|
|
|
CBaseClient *pBestProxy = NULL; |
|
float fBestRatio = 1.0f; |
|
|
|
// find best relay proxy |
|
for (int i=0; i < GetClientCount(); i++ ) |
|
{ |
|
CBaseClient *pProxy = m_Clients[ i ]; |
|
|
|
// check all known proxies |
|
if ( !pProxy->IsConnected() || !pProxy->IsHLTV() || (pClient == pProxy) ) |
|
continue; |
|
|
|
int slots = Q_atoi( pProxy->GetUserSetting( "hltv_slots" ) ); |
|
int clients = Q_atoi( pProxy->GetUserSetting( "hltv_clients" ) ); |
|
|
|
// skip overloaded proxies or proxies with no slots at all |
|
if ( (clients > slots) || slots <= 0 ) |
|
continue; |
|
|
|
// calc clients/slots ratio for this proxy |
|
float ratio = ((float)(clients))/((float)slots); |
|
|
|
if ( ratio < fBestRatio ) |
|
{ |
|
fBestRatio = ratio; |
|
pBestProxy = pProxy; |
|
} |
|
} |
|
|
|
if ( pBestProxy == NULL ) |
|
{ |
|
if ( tv_dispatchmode.GetInt() == DISPATCH_MODE_ALWAYS ) |
|
{ |
|
// we are in always forward mode, drop client if we can't forward it |
|
pClient->Disconnect("No SourceTV relay available"); |
|
return true; |
|
} |
|
else |
|
{ |
|
// just let client connect to this proxy |
|
return false; |
|
} |
|
} |
|
|
|
// check if client should stay on this relay server |
|
if ( (tv_dispatchmode.GetInt() == DISPATCH_MODE_AUTO) && (GetMaxClients() > 0) ) |
|
{ |
|
// ratio = clients/slots. give relay proxies 25% bonus |
|
float myRatio = ((float)GetNumClients()/(float)GetMaxClients()) * 1.25f; |
|
|
|
myRatio = min( myRatio, 1.0f ); // clamp to 1 |
|
|
|
// if we have a better local ratio then other proxies, keep this client here |
|
if ( myRatio < fBestRatio ) |
|
return false; // don't redirect |
|
} |
|
|
|
const char *pszRelayAddr = pBestProxy->GetUserSetting("hltv_addr"); |
|
|
|
if ( !pszRelayAddr ) |
|
return false; |
|
|
|
|
|
ConMsg( "Redirecting spectator %s to SourceTV relay %s\n", |
|
pClient->GetNetChannel()->GetRemoteAddress().ToString(), |
|
pszRelayAddr ); |
|
|
|
// first tell that client that we are a SourceTV server, |
|
// otherwise it's might ignore the command |
|
SVC_ServerInfo serverInfo; |
|
FillServerInfo( serverInfo ); |
|
pClient->SendNetMsg( serverInfo, true ); |
|
|
|
// tell the client to connect to this new address |
|
NET_StringCmd cmdMsg( va("redirect %s\n", pszRelayAddr ) ) ; |
|
pClient->SendNetMsg( cmdMsg, true ); |
|
|
|
// increase this proxies client number in advance so this proxy isn't used again next time |
|
int clients = Q_atoi( pBestProxy->GetUserSetting( "hltv_clients" ) ); |
|
pBestProxy->SetUserCVar( "hltv_clients", va("%d", clients+1 ) ); |
|
|
|
return true; |
|
} |
|
|
|
void CHLTVServer::ConnectRelay(const char *address) |
|
{ |
|
if ( m_ClientState.IsConnected() ) |
|
{ |
|
// do not try to reconnect to old connection |
|
m_ClientState.m_szRetryAddress[0] = 0; |
|
|
|
// disconnect first |
|
m_ClientState.Disconnect( "HLTV server connecting to relay", true ); |
|
|
|
Changelevel(); // inactivate clients |
|
} |
|
|
|
// connect to new server |
|
m_ClientState.Connect( address, "tvrelay" ); |
|
} |
|
|
|
void CHLTVServer::StartRelay() |
|
{ |
|
if ( !m_ClientState.IsConnected() && !IsPlayingBack() ) |
|
{ |
|
DevMsg("StartRelay: not connected.\n"); |
|
Shutdown(); |
|
return; |
|
} |
|
|
|
Clear(); // clear old settings & buffers |
|
|
|
if ( m_nRecvTables == 0 ) |
|
{ |
|
// must be done only once since Mod never changes |
|
InitClientRecvTables(); |
|
} |
|
|
|
m_HLTVFrame.AllocBuffers(); |
|
|
|
m_StringTables = &m_NetworkStringTables; |
|
|
|
SetMaxClients( tv_maxclients.GetInt() ); |
|
|
|
m_bSignonState = true; |
|
|
|
m_flStartTime = net_time; |
|
|
|
m_State = ss_loading; |
|
|
|
m_nSpawnCount++; |
|
} |
|
|
|
int CHLTVServer::GetHLTVSlot( void ) |
|
{ |
|
return m_nPlayerSlot; |
|
} |
|
|
|
float CHLTVServer::GetOnlineTime( void ) |
|
{ |
|
return max(0., net_time - m_flStartTime); |
|
} |
|
|
|
void CHLTVServer::GetLocalStats( int &proxies, int &slots, int &clients ) |
|
{ |
|
proxies = GetNumProxies(); |
|
clients = GetNumClients(); |
|
slots = GetMaxClients(); |
|
} |
|
|
|
void CHLTVServer::GetRelayStats( int &proxies, int &slots, int &clients ) |
|
{ |
|
proxies = slots = clients = 0; |
|
|
|
for (int i=0 ; i < GetClientCount() ; i++ ) |
|
{ |
|
CBaseClient *client = m_Clients[ i ]; |
|
|
|
if ( !client->IsConnected() || !client->IsHLTV() ) |
|
continue; |
|
|
|
proxies += Q_atoi( client->GetUserSetting( "hltv_proxies" ) ); |
|
slots += Q_atoi( client->GetUserSetting( "hltv_slots" ) ); |
|
clients += Q_atoi( client->GetUserSetting( "hltv_clients" ) ); |
|
} |
|
} |
|
|
|
void CHLTVServer::GetGlobalStats( int &proxies, int &slots, int &clients ) |
|
{ |
|
// the master proxy is the only one that really has all data to generate |
|
// global stats |
|
if ( IsMasterProxy() ) |
|
{ |
|
GetRelayStats( m_nGlobalProxies, m_nGlobalSlots, m_nGlobalClients ); |
|
|
|
m_nGlobalSlots += GetMaxClients(); |
|
m_nGlobalClients += GetNumClients(); |
|
} |
|
|
|
// if this is a relay proxies, global data comes via the |
|
// wire from the master proxy |
|
proxies = m_nGlobalProxies; |
|
slots = m_nGlobalSlots; |
|
clients = m_nGlobalClients; |
|
} |
|
|
|
const netadr_t *CHLTVServer::GetRelayAddress( void ) |
|
{ |
|
if ( IsMasterProxy() ) |
|
{ |
|
return &net_local_adr; // TODO wrong port |
|
} |
|
else if ( m_ClientState.m_NetChannel ) |
|
{ |
|
return &m_ClientState.m_NetChannel->GetRemoteAddress(); |
|
} |
|
else |
|
{ |
|
return NULL; |
|
} |
|
} |
|
|
|
bool CHLTVServer::IsMasterProxy( void ) |
|
{ |
|
return ( m_MasterClient != NULL ); |
|
} |
|
|
|
bool CHLTVServer::IsTVRelay() |
|
{ |
|
return !IsMasterProxy(); |
|
} |
|
|
|
bool CHLTVServer::IsDemoPlayback( void ) |
|
{ |
|
return false; |
|
} |
|
|
|
void CHLTVServer::BroadcastLocalTitle( CHLTVClient *client ) |
|
{ |
|
IGameEvent *event = g_GameEventManager.CreateEvent( "hltv_title", true ); |
|
|
|
if ( !event ) |
|
return; |
|
|
|
event->SetString( "text", tv_title.GetString() ); |
|
|
|
char buffer_data[MAX_EVENT_BYTES]; |
|
|
|
SVC_GameEvent eventMsg; |
|
|
|
eventMsg.SetReliable( true ); |
|
|
|
eventMsg.m_DataOut.StartWriting( buffer_data, sizeof(buffer_data) ); |
|
|
|
// create bit stream from KeyValues |
|
if ( !g_GameEventManager.SerializeEvent( event, &eventMsg.m_DataOut ) ) |
|
{ |
|
DevMsg("CHLTVServer: failed to serialize title '%s'.\n", event->GetName() ); |
|
g_GameEventManager.FreeEvent( event ); |
|
return; |
|
} |
|
|
|
if ( client ) |
|
{ |
|
client->SendNetMsg( eventMsg ); |
|
} |
|
else |
|
{ |
|
for ( int i = 0; i < m_Clients.Count(); i++ ) |
|
{ |
|
client = Client(i); |
|
|
|
if ( !client->IsActive() || client->IsHLTV() ) |
|
continue; |
|
|
|
client->SendNetMsg( eventMsg ); |
|
} |
|
} |
|
|
|
g_GameEventManager.FreeEvent( event ); |
|
} |
|
|
|
void CHLTVServer::BroadcastLocalChat( const char *pszChat, const char *pszGroup ) |
|
{ |
|
IGameEvent *event = g_GameEventManager.CreateEvent( "hltv_chat", true ); |
|
|
|
if ( !event ) |
|
return; |
|
|
|
event->SetString( "text", pszChat ); |
|
|
|
char buffer_data[MAX_EVENT_BYTES]; |
|
|
|
SVC_GameEvent eventMsg; |
|
|
|
eventMsg.SetReliable( false ); |
|
|
|
eventMsg.m_DataOut.StartWriting( buffer_data, sizeof(buffer_data) ); |
|
|
|
// create bit stream from KeyValues |
|
if ( !g_GameEventManager.SerializeEvent( event, &eventMsg.m_DataOut ) ) |
|
{ |
|
DevMsg("CHLTVServer: failed to serialize chat '%s'.\n", event->GetName() ); |
|
g_GameEventManager.FreeEvent( event ); |
|
return; |
|
} |
|
|
|
for ( int i = 0; i < m_Clients.Count(); i++ ) |
|
{ |
|
CHLTVClient *cl = Client(i); |
|
|
|
if ( !cl->IsActive() || !cl->IsSpawned() || cl->IsHLTV() ) |
|
continue; |
|
|
|
// if this is a spectator chat message and client disabled it, don't show it |
|
if ( Q_strcmp( cl->m_szChatGroup, pszGroup) || cl->m_bNoChat ) |
|
continue; |
|
|
|
cl->SendNetMsg( eventMsg ); |
|
} |
|
|
|
g_GameEventManager.FreeEvent( event ); |
|
} |
|
|
|
void CHLTVServer::BroadcastEventLocal( IGameEvent *event, bool bReliable ) |
|
{ |
|
char buffer_data[MAX_EVENT_BYTES]; |
|
|
|
SVC_GameEvent eventMsg; |
|
|
|
eventMsg.SetReliable( bReliable ); |
|
|
|
eventMsg.m_DataOut.StartWriting( buffer_data, sizeof(buffer_data) ); |
|
|
|
// create bit stream from KeyValues |
|
if ( !g_GameEventManager.SerializeEvent( event, &eventMsg.m_DataOut ) ) |
|
{ |
|
DevMsg("CHLTVServer: failed to serialize local event '%s'.\n", event->GetName() ); |
|
return; |
|
} |
|
|
|
for ( int i = 0; i < m_Clients.Count(); i++ ) |
|
{ |
|
CHLTVClient *cl = Client(i); |
|
|
|
if ( !cl->IsActive() || !cl->IsSpawned() || cl->IsHLTV() ) |
|
continue; |
|
|
|
if ( !cl->SendNetMsg( eventMsg ) ) |
|
{ |
|
if ( eventMsg.IsReliable() ) |
|
{ |
|
DevMsg( "BroadcastMessage: Reliable broadcast message overflow for client %s", cl->GetClientName() ); |
|
} |
|
} |
|
} |
|
|
|
if ( tv_debug.GetBool() ) |
|
Msg("SourceTV broadcast local event: %s\n", event->GetName() ); |
|
} |
|
|
|
void CHLTVServer::BroadcastEvent(IGameEvent *event) |
|
{ |
|
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 ) ) |
|
{ |
|
DevMsg("CHLTVServer: failed to serialize event '%s'.\n", event->GetName() ); |
|
return; |
|
} |
|
|
|
BroadcastMessage( eventMsg, true, true ); |
|
|
|
if ( tv_debug.GetBool() ) |
|
Msg("SourceTV broadcast event: %s\n", event->GetName() ); |
|
} |
|
|
|
void CHLTVServer::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("CHLTVServer::FireGameEvent: failed to serialize event '%s'.\n", event->GetName() ); |
|
} |
|
} |
|
|
|
bool CHLTVServer::ShouldUpdateMasterServer() |
|
{ |
|
|
|
// If the main game server is active, then we let it update Steam with the server info. |
|
return !sv.IsActive(); |
|
} |
|
|
|
CBaseClient *CHLTVServer::CreateNewClient(int slot ) |
|
{ |
|
return new CHLTVClient( slot, this ); |
|
} |
|
|
|
void CHLTVServer::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 hltv 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 *hltvTable = |
|
(CNetworkStringTable*)m_StringTables->CreateStringTableEx( |
|
serverTable->GetTableName(), |
|
serverTable->GetMaxStrings(), |
|
serverTable->GetUserDataSize(), |
|
serverTable->GetUserDataSizeBits(), |
|
serverTable->HasFileNameStrings() |
|
); |
|
|
|
if ( !hltvTable ) |
|
{ |
|
DevMsg("SV_InstallHLTVStringTableMirrors! Missing client table \"%s\".\n ", serverTable->GetTableName() ); |
|
continue; |
|
} |
|
|
|
// make hltv table an exact copy of server table |
|
hltvTable->CopyStringTable( serverTable ); |
|
|
|
// link hltv table to server table |
|
serverTable->SetMirrorTable( hltvTable ); |
|
} |
|
|
|
m_StringTables->AllowCreation( false ); |
|
|
|
#endif |
|
} |
|
|
|
void CHLTVServer::RestoreTick( int tick ) |
|
{ |
|
#ifndef SHARED_NET_STRING_TABLES |
|
|
|
// only master proxy delays time |
|
if ( !IsMasterProxy() ) |
|
return; |
|
|
|
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 CHLTVServer::UserInfoChanged( int nClientIndex ) |
|
{ |
|
// don't change UserInfo table, it keeps the infos of the original players |
|
} |
|
|
|
void CHLTVServer::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; |
|
} |
|
} |
|
} |
|
|
|
/* CHLTVServer::GetOriginFromPackedEntity is such a bad, bad hack. |
|
|
|
extern float DecodeFloat(SendProp const *pProp, bf_read *pIn); |
|
|
|
Vector CHLTVServer::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; |
|
} */ |
|
|
|
CHLTVEntityData *FindHLTVDataInSnapshot( 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_pHLTVEntityData[m]; |
|
|
|
if ( iEntIndex > index ) |
|
{ |
|
if ( pSnapshot->m_pValidEntities[z] == iEntIndex ) |
|
return &pSnapshot->m_pHLTVEntityData[z]; |
|
|
|
if ( a == m ) |
|
return NULL; |
|
|
|
a = m; |
|
} |
|
else |
|
{ |
|
if ( pSnapshot->m_pValidEntities[a] == iEntIndex ) |
|
return &pSnapshot->m_pHLTVEntityData[a]; |
|
|
|
if ( z == m ) |
|
return NULL; |
|
|
|
z = m; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
void CHLTVServer::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_pHLTVEntityData != NULL ); |
|
|
|
int nDirectorEntity = m_Director->GetPVSEntity(); |
|
|
|
if ( pSnapshot && nDirectorEntity > 0 ) |
|
{ |
|
CHLTVEntityData *pHLTVData = FindHLTVDataInSnapshot( pSnapshot, nDirectorEntity ); |
|
|
|
if ( pHLTVData ) |
|
{ |
|
m_vPVSOrigin.x = pHLTVData->origin[0]; |
|
m_vPVSOrigin.y = pHLTVData->origin[1]; |
|
m_vPVSOrigin.z = pHLTVData->origin[2]; |
|
} |
|
} |
|
else |
|
{ |
|
m_vPVSOrigin = m_Director->GetPVSOrigin(); |
|
} |
|
|
|
|
|
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; |
|
} |
|
|
|
CHLTVEntityData *pHLTVData = FindHLTVDataInSnapshot( pSnapshot, entindex ); |
|
|
|
if ( !pHLTVData ) |
|
continue; |
|
|
|
unsigned int nNodeCluster = pHLTVData->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 ); |
|
} |
|
} |
|
|
|
void CHLTVServer::SignonComplete() |
|
{ |
|
Assert ( !IsMasterProxy() ); |
|
|
|
m_bSignonState = false; |
|
|
|
LinkInstanceBaselines(); |
|
|
|
if ( tv_debug.GetBool() ) |
|
Msg("SourceTV signon complete.\n" ); |
|
} |
|
|
|
CClientFrame *CHLTVServer::AddNewFrame( CClientFrame *clientFrame ) |
|
{ |
|
VPROF_BUDGET( "CHLTVServer::AddNewFrame", "HLTV" ); |
|
|
|
Assert ( clientFrame ); |
|
Assert( clientFrame->tick_count > m_nLastTick ); |
|
|
|
m_nLastTick = clientFrame->tick_count; |
|
|
|
m_HLTVFrame.SetSnapshot( clientFrame->GetSnapshot() ); |
|
m_HLTVFrame.tick_count = clientFrame->tick_count; |
|
m_HLTVFrame.last_entity = clientFrame->last_entity; |
|
m_HLTVFrame.transmit_entity = clientFrame->transmit_entity; |
|
|
|
// remember tick of first valid frame |
|
if ( m_nFirstTick < 0 ) |
|
{ |
|
m_nFirstTick = clientFrame->tick_count; |
|
m_nTickCount = m_nFirstTick; |
|
|
|
if ( !IsMasterProxy() ) |
|
{ |
|
Assert ( m_State == ss_loading ); |
|
m_State = ss_active; // we are now ready to go |
|
|
|
ReconnectClients(); |
|
|
|
ConMsg("SourceTV relay active.\n" ); |
|
|
|
Steam3Server().Activate( CSteam3Server::eServerTypeTVRelay ); |
|
Steam3Server().SendUpdatedServerDetails(); |
|
} |
|
else |
|
{ |
|
ConMsg("SourceTV broadcast active.\n" ); |
|
} |
|
} |
|
|
|
CHLTVFrame *hltvFrame = new CHLTVFrame; |
|
|
|
// copy tickcount & entities from client frame |
|
hltvFrame->CopyFrame( *clientFrame ); |
|
|
|
//copy rest (messages, tempents) from current HLTV frame |
|
hltvFrame->CopyHLTVData( m_HLTVFrame ); |
|
|
|
// add frame to HLTV server |
|
AddClientFrame( hltvFrame ); |
|
|
|
if ( IsMasterProxy() && m_DemoRecorder.IsRecording() ) |
|
{ |
|
m_DemoRecorder.WriteFrame( &m_HLTVFrame ); |
|
} |
|
|
|
// reset HLTV frame for recording next messages etc. |
|
m_HLTVFrame.Reset(); |
|
m_HLTVFrame.SetSnapshot( NULL ); |
|
|
|
return hltvFrame; |
|
} |
|
|
|
void CHLTVServer::SendClientMessages ( bool bSendSnapshots ) |
|
{ |
|
// build individual updates |
|
for ( int i=0; i< m_Clients.Count(); i++ ) |
|
{ |
|
CHLTVClient* client = Client(i); |
|
|
|
// Update Host client send state... |
|
if ( !client->ShouldSendMessages() ) |
|
{ |
|
continue; |
|
} |
|
|
|
// Append the unreliable data (player updates and packet entities) |
|
if ( m_CurrentFrame && client->IsActive() ) |
|
{ |
|
// don't send same snapshot twice |
|
client->SendSnapshot( m_CurrentFrame ); |
|
} |
|
else |
|
{ |
|
// Connected, but inactive, just send reliable, sequenced info. |
|
client->m_NetChannel->Transmit(); |
|
} |
|
|
|
client->UpdateSendState(); |
|
client->m_fLastSendTime = net_time; |
|
} |
|
} |
|
|
|
void CHLTVServer::UpdateStats( void ) |
|
{ |
|
if ( m_fNextSendUpdateTime > net_time ) |
|
return; |
|
|
|
m_fNextSendUpdateTime = net_time + 8.0f; |
|
|
|
// fire game event for everyone |
|
IGameEvent *event = NULL; |
|
|
|
if ( !IsMasterProxy() && !m_ClientState.IsConnected() ) |
|
{ |
|
// we are disconnected from SourceTV server |
|
event = g_GameEventManager.CreateEvent( "hltv_message", true ); |
|
|
|
if ( !event ) |
|
return; |
|
|
|
event->SetString( "text", "SourceTV reconnecting ..." ); |
|
} |
|
else |
|
{ |
|
int proxies, slots, clients; |
|
GetGlobalStats( proxies, slots, clients ); |
|
|
|
event = g_GameEventManager.CreateEvent( "hltv_status", true ); |
|
|
|
if ( !event ) |
|
return; |
|
|
|
char address[32]; |
|
|
|
if ( IsMasterProxy() || tv_overridemaster.GetBool() ) |
|
{ |
|
// broadcast own address |
|
Q_snprintf( address, sizeof(address), "%s:%u", net_local_adr.ToString(true), GetUDPPort() ); |
|
} |
|
else |
|
{ |
|
// forward address |
|
Q_snprintf( address, sizeof(address), "%s", m_RootServer.ToString() ); |
|
} |
|
|
|
event->SetString( "master", address ); |
|
event->SetInt( "clients", clients ); |
|
event->SetInt( "slots", slots); |
|
event->SetInt( "proxies", proxies ); |
|
} |
|
|
|
if ( IsMasterProxy() ) |
|
{ |
|
// as a master fire event for every one |
|
g_GameEventManager.FireEvent( event ); |
|
} |
|
else |
|
{ |
|
// as a relay proxy just broadcast event |
|
BroadcastEvent( event ); |
|
} |
|
|
|
} |
|
|
|
bool CHLTVServer::SendNetMsg( INetMessage &msg, bool bForceReliable ) |
|
{ |
|
if ( m_bSignonState ) |
|
{ |
|
return msg.WriteToBuffer( m_Signon ); |
|
} |
|
|
|
int buffer = HLTV_BUFFER_UNRELIABLE; // default destination |
|
|
|
if ( msg.IsReliable() ) |
|
{ |
|
buffer = HLTV_BUFFER_RELIABLE; |
|
} |
|
else if ( msg.GetType() == svc_Sounds ) |
|
{ |
|
buffer = HLTV_BUFFER_SOUNDS; |
|
} |
|
else if ( msg.GetType() == svc_VoiceData ) |
|
{ |
|
buffer = HLTV_BUFFER_VOICE; |
|
} |
|
else if ( msg.GetType() == svc_TempEntities ) |
|
{ |
|
buffer = HLTV_BUFFER_TEMPENTS; |
|
} |
|
|
|
// anything else goes to the unreliable bin |
|
return msg.WriteToBuffer( m_HLTVFrame.m_Messages[buffer] ); |
|
} |
|
|
|
bf_write *CHLTVServer::GetBuffer( int nBuffer ) |
|
{ |
|
if ( nBuffer < 0 || nBuffer >= HLTV_BUFFER_MAX ) |
|
return NULL; |
|
|
|
return &m_HLTVFrame.m_Messages[nBuffer]; |
|
} |
|
|
|
IServer *CHLTVServer::GetBaseServer() |
|
{ |
|
return (IServer*)this; |
|
} |
|
|
|
IHLTVDirector *CHLTVServer::GetDirector() |
|
{ |
|
return m_Director; |
|
} |
|
|
|
CClientFrame *CHLTVServer::GetDeltaFrame( int nTick ) |
|
{ |
|
if ( !tv_deltacache.GetBool() ) |
|
return GetClientFrame( nTick ); //expensive |
|
|
|
// 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(); |
|
|
|
CFrameCacheEntry_s &entry = m_FrameCache[i]; |
|
|
|
entry.nTick = nTick; |
|
entry.pFrame = GetClientFrame( nTick ); //expensive |
|
|
|
return entry.pFrame; |
|
} |
|
|
|
void CHLTVServer::RunFrame() |
|
{ |
|
VPROF_BUDGET( "CHLTVServer::RunFrame", "HLTV" ); |
|
|
|
// update network time etc |
|
NET_RunFrame( Plat_FloatTime() ); |
|
|
|
if ( m_ClientState.m_nSignonState > SIGNONSTATE_NONE ) |
|
{ |
|
// process data from net socket |
|
NET_ProcessSocket( m_ClientState.m_Socket, &m_ClientState ); |
|
|
|
m_ClientState.RunFrame(); |
|
|
|
m_ClientState.SendPacket(); |
|
} |
|
|
|
// check if HLTV server if active |
|
if ( !IsActive() ) |
|
return; |
|
|
|
if ( host_frametime > 0 ) |
|
{ |
|
m_flFPS = m_flFPS * 0.99f + 0.01f/host_frametime; |
|
} |
|
|
|
if ( IsPlayingBack() ) |
|
return; |
|
|
|
// get current tick time for director module and restore |
|
// world (stringtables, framebuffers) as they were at this time |
|
UpdateTick(); |
|
|
|
// Run any commands from client and play client Think functions if it is time. |
|
CBaseServer::RunFrame(); |
|
|
|
UpdateStats(); |
|
|
|
SendClientMessages( true ); |
|
|
|
// Update the Steam server if we're running a relay. |
|
if ( !sv.IsActive() ) |
|
Steam3Server().RunFrame(); |
|
|
|
UpdateMasterServer(); |
|
} |
|
|
|
void CHLTVServer::UpdateTick( void ) |
|
{ |
|
VPROF_BUDGET( "CHLTVServer::UpdateTick", "HLTV" ); |
|
|
|
if ( m_nFirstTick < 0 ) |
|
{ |
|
m_nTickCount = 0; |
|
m_CurrentFrame = NULL; |
|
return; |
|
} |
|
|
|
// set tick time to last frame added |
|
int nNewTick = m_nLastTick; |
|
|
|
if ( IsMasterProxy() ) |
|
{ |
|
// get tick from director, he decides delay etc |
|
nNewTick = max( m_nFirstTick, m_Director->GetDirectorTick() ); |
|
} |
|
|
|
// the the closest available frame |
|
CHLTVFrame *newFrame = (CHLTVFrame*) 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; |
|
|
|
if ( IsMasterProxy() ) |
|
{ |
|
// now do master proxy stuff |
|
|
|
// restore string tables for this time |
|
RestoreTick( m_nTickCount ); |
|
|
|
// remove entities out of current PVS |
|
if ( tv_transmitall.GetBool() == false ) |
|
{ |
|
EntityPVSCheck( m_CurrentFrame ); |
|
} |
|
} |
|
else |
|
{ |
|
// delta entity cache works only for relay proxies |
|
m_DeltaCache.SetTick( m_CurrentFrame->tick_count, m_CurrentFrame->last_entity+1 ); |
|
} |
|
|
|
int removeTick = m_nTickCount - 16.0f/m_flTickInterval; // keep 16 seconds buffer |
|
|
|
if ( removeTick > 0 ) |
|
{ |
|
DeleteClientFrames( removeTick ); |
|
} |
|
|
|
m_FrameCache.RemoveAll(); |
|
} |
|
|
|
const char *CHLTVServer::GetName( void ) const |
|
{ |
|
return tv_name.GetString(); |
|
} |
|
|
|
void CHLTVServer::FillServerInfo(SVC_ServerInfo &serverinfo) |
|
{ |
|
CBaseServer::FillServerInfo( serverinfo ); |
|
|
|
serverinfo.m_nPlayerSlot = m_nPlayerSlot; // all spectators think they're the HLTV client |
|
serverinfo.m_nMaxClients = m_nGameServerMaxClients; |
|
} |
|
|
|
void CHLTVServer::Clear( void ) |
|
{ |
|
CBaseServer::Clear(); |
|
|
|
m_Director = NULL; |
|
m_MasterClient = NULL; |
|
m_ClientState.Clear(); |
|
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_HLTVFrame.FreeBuffers(); |
|
m_vPVSOrigin.Init(); |
|
|
|
DeleteClientFrames( -1 ); |
|
|
|
m_DeltaCache.Flush(); |
|
m_FrameCache.RemoveAll(); |
|
} |
|
|
|
bool CHLTVServer::ProcessConnectionlessPacket( netpacket_t * packet ) |
|
{ |
|
bf_read msg = packet->message; // We're copying the message, so we don't need to seek back when passing packet to the base class. |
|
// int bits = msg.GetNumBitsRead(); |
|
|
|
char c = msg.ReadChar(); |
|
|
|
if ( c == 0 ) |
|
{ |
|
return false; |
|
} |
|
|
|
switch ( c ) |
|
{ |
|
#ifndef NO_STEAM |
|
case A2S_INFO: |
|
char rgchInfoPostfix[64]; |
|
msg.ReadString( rgchInfoPostfix, sizeof( rgchInfoPostfix ) ); |
|
if ( !Q_stricmp( rgchInfoPostfix, A2S_KEY_STRING_STEAM ) ) |
|
{ |
|
ReplyInfo( packet->from ); |
|
return true; |
|
} |
|
|
|
break; |
|
//case A2S_PLAYER: |
|
// return true; |
|
#endif // #ifndef NO_STEAM |
|
} |
|
|
|
return CBaseServer::ProcessConnectionlessPacket( packet ); |
|
} |
|
|
|
void CHLTVServer::Init(bool bIsDedicated) |
|
{ |
|
CBaseServer::Init( bIsDedicated ); |
|
|
|
m_Socket = NS_HLTV; |
|
|
|
// check if only master proxy is allowed, no broadcasting |
|
if ( CommandLine()->FindParm("-tvmasteronly") ) |
|
{ |
|
m_bMasterOnlyMode = true; |
|
} |
|
} |
|
|
|
void CHLTVServer::Changelevel() |
|
{ |
|
m_DemoRecorder.StopRecording(); |
|
|
|
InactivateClients(); |
|
|
|
DeleteClientFrames(-1); |
|
|
|
m_CurrentFrame = NULL; |
|
} |
|
|
|
void CHLTVServer::GetNetStats( float &avgIn, float &avgOut ) |
|
{ |
|
CBaseServer::GetNetStats( avgIn, avgOut ); |
|
|
|
if ( m_ClientState.IsActive() ) |
|
{ |
|
avgIn += m_ClientState.m_NetChannel->GetAvgData(FLOW_INCOMING); |
|
avgOut += m_ClientState.m_NetChannel->GetAvgData(FLOW_OUTGOING); |
|
} |
|
} |
|
|
|
void CHLTVServer::Shutdown( void ) |
|
{ |
|
m_DemoRecorder.StopRecording(); // if recording, stop now |
|
|
|
if ( IsMasterProxy() ) |
|
{ |
|
if ( m_MasterClient ) |
|
m_MasterClient->Disconnect( "SourceTV stop." ); |
|
|
|
if ( m_Director ) |
|
m_Director->SetHLTVServer( NULL ); |
|
} |
|
else |
|
{ |
|
// do not try to reconnect to old connection |
|
m_ClientState.m_szRetryAddress[0] = 0; |
|
|
|
m_ClientState.Disconnect( "HLTV server shutting down", true ); |
|
} |
|
|
|
g_GameEventManager.RemoveListener( this ); |
|
|
|
CBaseServer::Shutdown(); |
|
} |
|
|
|
CDemoFile *CHLTVServer::GetDemoFile() |
|
{ |
|
return &m_DemoFile; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CHLTVServer::IsPlayingBack( void ) |
|
{ |
|
return m_bPlayingBack; |
|
} |
|
|
|
bool CHLTVServer::IsPlaybackPaused() |
|
{ |
|
return m_bPlaybackPaused; |
|
} |
|
|
|
float CHLTVServer::GetPlaybackTimeScale() |
|
{ |
|
return m_flPlaybackRateModifier; |
|
} |
|
|
|
void CHLTVServer::SetPlaybackTimeScale(float timescale) |
|
{ |
|
m_flPlaybackRateModifier = timescale; |
|
} |
|
|
|
void CHLTVServer::ResyncDemoClock() |
|
{ |
|
m_nStartTick = host_tickcount; |
|
} |
|
|
|
int CHLTVServer::GetPlaybackStartTick( void ) |
|
{ |
|
return m_nStartTick; |
|
} |
|
|
|
int CHLTVServer::GetPlaybackTick( void ) |
|
{ |
|
return host_tickcount - m_nStartTick; |
|
} |
|
|
|
int CHLTVServer::GetTotalTicks(void) |
|
{ |
|
return m_DemoFile.m_DemoHeader.playback_ticks; |
|
} |
|
|
|
bool CHLTVServer::StartPlayback( const char *filename, bool bAsTimeDemo ) |
|
{ |
|
Clear(); |
|
|
|
if ( !m_DemoFile.Open( filename, true ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
// Read in the m_DemoHeader |
|
demoheader_t *dh = m_DemoFile.ReadDemoHeader(); |
|
|
|
if ( !dh ) |
|
{ |
|
ConMsg( "Failed to read demo header.\n" ); |
|
m_DemoFile.Close(); |
|
return false; |
|
} |
|
|
|
// create a fake channel with a NULL address |
|
m_ClientState.m_NetChannel = NET_CreateNetChannel( NS_CLIENT, NULL, "DEMO", &m_ClientState ); |
|
|
|
if ( !m_ClientState.m_NetChannel ) |
|
{ |
|
ConMsg( "CDemo::Play: failed to create demo net channel\n" ); |
|
m_DemoFile.Close(); |
|
return false; |
|
} |
|
|
|
m_ClientState.m_NetChannel->SetTimeout( -1.0f ); // never timeout |
|
|
|
|
|
// Now read in the directory structure. |
|
|
|
m_bPlayingBack = true; |
|
|
|
ConMsg( "Reading complete demo file at once...\n"); |
|
|
|
double start = Plat_FloatTime(); |
|
|
|
ReadCompleteDemoFile(); |
|
|
|
double diff = Plat_FloatTime() - start; |
|
|
|
ConMsg( "Reading time :%.4f\n", diff ); |
|
|
|
NET_RemoveNetChannel( m_ClientState.m_NetChannel, true ); |
|
m_ClientState.m_NetChannel = NULL; |
|
|
|
return true; |
|
} |
|
|
|
void CHLTVServer::ReadCompleteDemoFile() |
|
{ |
|
int tick = 0; |
|
byte cmd = dem_signon; |
|
char buffer[NET_MAX_PAYLOAD]; |
|
netpacket_t demoPacket; |
|
|
|
// setup demo packet data buffer |
|
Q_memset( &demoPacket, 0, sizeof(demoPacket) ); |
|
demoPacket.from.SetType( NA_LOOPBACK); |
|
|
|
while ( true ) |
|
{ |
|
m_DemoFile.ReadCmdHeader( cmd, tick ); |
|
|
|
// COMMAND HANDLERS |
|
switch ( cmd ) |
|
{ |
|
case dem_synctick: |
|
ResyncDemoClock(); |
|
break; |
|
case dem_stop: |
|
// MOTODO we finished reading the file |
|
return ; |
|
case dem_consolecmd: |
|
{ |
|
NET_StringCmd cmdmsg( m_DemoFile.ReadConsoleCommand() ); |
|
m_ClientState.ProcessStringCmd( &cmdmsg ); |
|
} |
|
break; |
|
case dem_datatables: |
|
{ |
|
ALIGN4 char data[64*1024] ALIGN4_POST; |
|
bf_read buf( "dem_datatables", data, sizeof(data) ); |
|
|
|
m_DemoFile.ReadNetworkDataTables( &buf ); |
|
buf.Seek( 0 ); |
|
|
|
// support for older engine demos |
|
if ( !DataTable_LoadDataTablesFromBuffer( &buf, m_DemoFile.m_DemoHeader.demoprotocol ) ) |
|
{ |
|
Host_Error( "Error parsing network data tables during demo playback." ); |
|
} |
|
} |
|
break; |
|
case dem_stringtables: |
|
{ |
|
void *data = NULL; |
|
int dataLen = 512 * 1024; |
|
while ( dataLen <= DEMO_FILE_MAX_STRINGTABLE_SIZE ) |
|
{ |
|
data = realloc( data, dataLen ); |
|
bf_read buf( "dem_stringtables", data, dataLen ); |
|
// did we successfully read |
|
if ( m_DemoFile.ReadStringTables( &buf ) > 0 ) |
|
{ |
|
buf.Seek( 0 ); |
|
if ( !networkStringTableContainerClient->ReadStringTables( buf ) ) |
|
{ |
|
Host_Error( "Error parsing string tables during demo playback." ); |
|
} |
|
break; |
|
} |
|
|
|
// Didn't fit. Try doubling the size of the buffer |
|
dataLen *= 2; |
|
} |
|
|
|
if ( dataLen > DEMO_FILE_MAX_STRINGTABLE_SIZE ) |
|
{ |
|
Warning( "ReadCompleteDemoFile failed to read string tables. Trying to read string tables that's bigger than max string table size\n" ); |
|
} |
|
|
|
free( data ); |
|
} |
|
break; |
|
case dem_usercmd: |
|
{ |
|
char bufferIn[256]; |
|
int length = sizeof( bufferIn ); |
|
m_DemoFile.ReadUserCmd( bufferIn, length ); |
|
// MOTODO HLTV must store user commands too |
|
} |
|
break; |
|
case dem_signon: |
|
case dem_packet: |
|
{ |
|
int inseq, outseqack = 0; |
|
|
|
m_DemoFile.ReadCmdInfo( m_LastCmdInfo ); // MOTODO must be stored somewhere |
|
m_DemoFile.ReadSequenceInfo( inseq, outseqack ); |
|
|
|
int length = m_DemoFile.ReadRawData( buffer, sizeof(buffer) ); |
|
|
|
if ( length > 0 ) |
|
{ |
|
// succsessfully read new demopacket |
|
demoPacket.received = realtime; |
|
demoPacket.size = length; |
|
demoPacket.message.StartReading( buffer, length ); |
|
|
|
m_ClientState.m_NetChannel->ProcessPacket( &demoPacket, false ); |
|
} |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
|
|
int CHLTVServer::GetChallengeType ( netadr_t &adr ) |
|
{ |
|
return PROTOCOL_HASHEDCDKEY; // HLTV doesn't need Steam authentication |
|
} |
|
|
|
const char *CHLTVServer::GetPassword() const |
|
{ |
|
const char *password = tv_password.GetString(); |
|
|
|
// if password is empty or "none", return NULL |
|
if ( !password[0] || !Q_stricmp(password, "none" ) ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
return password; |
|
} |
|
|
|
IClient *CHLTVServer::ConnectClient ( netadr_t &adr, int protocol, int challenge, int clientChallenge, int authProtocol, |
|
const char *name, const char *password, const char *hashedCDkey, int cdKeyLen ) |
|
{ |
|
IClient *client = (CHLTVClient*)CBaseServer::ConnectClient( |
|
adr, protocol, challenge, clientChallenge, authProtocol, name, password, hashedCDkey, cdKeyLen ); |
|
|
|
if ( client ) |
|
{ |
|
// remember password |
|
CHLTVClient *pHltvClient = (CHLTVClient*)client; |
|
Q_strncpy( pHltvClient->m_szPassword, password, sizeof(pHltvClient->m_szPassword) ); |
|
} |
|
|
|
return client; |
|
} |
|
|
|
int CHLTVServer::GetProtocolVersion() |
|
{ |
|
if ( GetDemoFile() ) |
|
return GetDemoFile()->GetProtocolVersion(); |
|
return PROTOCOL_VERSION; |
|
} |
|
|
|
#ifndef NO_STEAM |
|
void CHLTVServer::ReplyInfo( const netadr_t &adr ) |
|
{ |
|
static char gamedir[MAX_OSPATH]; |
|
Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) ); |
|
|
|
CUtlBuffer buf; |
|
buf.EnsureCapacity( 2048 ); |
|
|
|
buf.PutUnsignedInt( LittleDWord( CONNECTIONLESS_HEADER ) ); |
|
buf.PutUnsignedChar( S2A_INFO_SRC ); |
|
|
|
buf.PutUnsignedChar( GetProtocolVersion() ); // Hardcoded protocol version number |
|
buf.PutString( GetName() ); |
|
buf.PutString( GetMapName() ); |
|
buf.PutString( gamedir ); |
|
buf.PutString( serverGameDLL->GetGameDescription() ); |
|
|
|
// The next field is a 16-bit version of the AppID. If our AppID < 65536, |
|
// then let's go ahead and put in in there, to maximize compatibility |
|
// with old clients who might be only using this field but not the new one. |
|
// However, if our AppID won't fit, there's no way we can be compatible, |
|
// anyway, so just put in a zero, which is better than a bogus AppID. |
|
uint16 usAppIdShort = (uint16)GetSteamAppID(); |
|
if ( (AppId_t)usAppIdShort != GetSteamAppID() ) |
|
{ |
|
usAppIdShort = 0; |
|
} |
|
buf.PutShort( LittleWord( usAppIdShort ) ); |
|
|
|
// player info |
|
buf.PutUnsignedChar( GetNumClients() ); |
|
buf.PutUnsignedChar( GetMaxClients() ); |
|
buf.PutUnsignedChar( 0 ); |
|
|
|
// NOTE: This key's meaning is changed in the new version. Since we send gameport and specport, |
|
// it knows whether we're running SourceTV or not. Then it only needs to know if we're a dedicated or listen server. |
|
if ( IsDedicated() ) |
|
buf.PutUnsignedChar( 'd' ); // d = dedicated server |
|
else |
|
buf.PutUnsignedChar( 'l' ); // l = listen server |
|
|
|
#if defined(_WIN32) |
|
buf.PutUnsignedChar( 'w' ); |
|
#elif defined(OSX) |
|
buf.PutUnsignedChar( 'm' ); |
|
#else // LINUX? |
|
buf.PutUnsignedChar( 'l' ); |
|
#endif |
|
|
|
// Password? |
|
buf.PutUnsignedChar( GetPassword() != NULL ? 1 : 0 ); |
|
buf.PutUnsignedChar( Steam3Server().BSecure() ? 1 : 0 ); |
|
buf.PutString( GetSteamInfIDVersionInfo().szVersionString ); |
|
|
|
// |
|
// NEW DATA. |
|
// |
|
|
|
// Write a byte with some flags that describe what is to follow. |
|
const char *pchTags = sv_tags.GetString(); |
|
byte nNewFlags = 0; |
|
//if ( GetGamePort() != 0 ) |
|
// nNewFlags |= S2A_EXTRA_DATA_HAS_GAME_PORT; |
|
|
|
if ( Steam3Server().GetGSSteamID().IsValid() ) |
|
nNewFlags |= S2A_EXTRA_DATA_HAS_STEAMID; |
|
|
|
if ( GetUDPPort() != 0 ) |
|
nNewFlags |= S2A_EXTRA_DATA_HAS_SPECTATOR_DATA; |
|
|
|
if ( pchTags && pchTags[0] != '\0' ) |
|
nNewFlags |= S2A_EXTRA_DATA_HAS_GAMETAG_DATA; |
|
|
|
nNewFlags |= S2A_EXTRA_DATA_GAMEID; |
|
|
|
buf.PutUnsignedChar( nNewFlags ); |
|
|
|
// Write the rest of the data. |
|
//if ( nNewFlags & S2A_EXTRA_DATA_HAS_GAME_PORT ) |
|
//{ |
|
// buf.PutShort( LittleWord( GetGamePort() ) ); |
|
//} |
|
|
|
if ( nNewFlags & S2A_EXTRA_DATA_HAS_STEAMID ) |
|
{ |
|
buf.PutUint64( LittleQWord( Steam3Server().GetGSSteamID().ConvertToUint64() ) ); |
|
} |
|
|
|
if ( nNewFlags & S2A_EXTRA_DATA_HAS_SPECTATOR_DATA ) |
|
{ |
|
buf.PutShort( LittleWord( GetUDPPort() ) ); |
|
buf.PutString( GetName() ); |
|
} |
|
|
|
if ( nNewFlags & S2A_EXTRA_DATA_HAS_GAMETAG_DATA ) |
|
{ |
|
buf.PutString( pchTags ); |
|
} |
|
|
|
if ( nNewFlags & S2A_EXTRA_DATA_GAMEID ) |
|
{ |
|
// !FIXME! Is there a reason we aren't using the other half |
|
// of this field? Shouldn't we put the game mod ID in there, too? |
|
// We have the game dir. |
|
buf.PutUint64( LittleQWord( CGameID( GetSteamAppID() ).ToUint64() ) ); |
|
} |
|
|
|
NET_SendPacket( NULL, m_Socket, adr, (unsigned char *)buf.Base(), buf.TellPut() ); |
|
} |
|
#endif // #ifndef NO_STEAM |
|
|
|
CON_COMMAND( tv_status, "Show SourceTV server status." ) |
|
{ |
|
int slots, proxies, clients; |
|
float in, out; |
|
char gd[MAX_OSPATH]; |
|
|
|
Q_FileBase( com_gamedir, gd, sizeof( gd ) ); |
|
|
|
if ( !hltv || !hltv->IsActive() ) |
|
{ |
|
ConMsg("SourceTV not active.\n" ); |
|
return; |
|
} |
|
|
|
hltv->GetNetStats( in, out ); |
|
|
|
in /= 1024; // as KB |
|
out /= 1024; |
|
|
|
ConMsg("--- SourceTV Status ---\n"); |
|
ConMsg("Online %s, FPS %.1f, Version %i (%s)\n", |
|
COM_FormatSeconds( hltv->GetOnlineTime() ), hltv->m_flFPS, build_number(), |
|
|
|
#ifdef _WIN32 |
|
"Win32" ); |
|
#else |
|
"Linux" ); |
|
#endif |
|
|
|
if ( hltv->IsDemoPlayback() ) |
|
{ |
|
ConMsg("Playing Demo File \"%s\"\n", "TODO demo file name" ); |
|
} |
|
else if ( hltv->IsMasterProxy() ) |
|
{ |
|
ConMsg("Master \"%s\", delay %.0f\n", hltv->GetName(), hltv->GetDirector()->GetDelay() ); |
|
} |
|
else // if ( m_Server->IsRelayProxy() ) |
|
{ |
|
if ( hltv->GetRelayAddress() ) |
|
{ |
|
ConMsg("Relay \"%s\", connect to %s\n", hltv->GetName(), hltv->GetRelayAddress()->ToString() ); |
|
} |
|
else |
|
{ |
|
ConMsg("Relay \"%s\", not connect.\n", hltv->GetName() ); |
|
} |
|
} |
|
|
|
ConMsg("Game Time %s, Mod \"%s\", Map \"%s\", Players %i\n", COM_FormatSeconds( hltv->GetTime() ), |
|
gd, hltv->GetMapName(), hltv->GetNumPlayers() ); |
|
|
|
ConMsg("Local IP %s:%i, KB/sec In %.1f, Out %.1f\n", |
|
net_local_adr.ToString( true ), hltv->GetUDPPort(), in ,out ); |
|
|
|
hltv->GetLocalStats( proxies, slots, clients ); |
|
|
|
ConMsg("Local Slots %i, Spectators %i, Proxies %i\n", |
|
slots, clients-proxies, proxies ); |
|
|
|
hltv->GetGlobalStats( proxies, slots, clients); |
|
|
|
ConMsg("Total Slots %i, Spectators %i, Proxies %i\n", |
|
slots, clients-proxies, proxies); |
|
|
|
if ( hltv->m_DemoRecorder.IsRecording() ) |
|
{ |
|
ConMsg("Recording to \"%s\", length %s.\n", hltv->m_DemoRecorder.GetDemoFile()->m_szFileName, |
|
COM_FormatSeconds( host_state.interval_per_tick * hltv->m_DemoRecorder.GetRecordingTick() ) ); |
|
} |
|
} |
|
|
|
CON_COMMAND( tv_relay, "Connect to SourceTV server and relay broadcast." ) |
|
{ |
|
if ( args.ArgC() < 2 ) |
|
{ |
|
ConMsg( "Usage: tv_relay <ip:port>\n" ); |
|
return; |
|
} |
|
|
|
const char *address = args.ArgS(); |
|
|
|
// If it's not a single player connection to "localhost", initialize networking & stop listenserver |
|
if ( !Q_strncmp( address, "localhost", 9 ) ) |
|
{ |
|
ConMsg( "SourceTV can't connect to localhost.\n" ); |
|
return; |
|
} |
|
|
|
if ( !hltv ) |
|
{ |
|
hltv = new CHLTVServer; |
|
hltv->Init( NET_IsDedicated() ); |
|
} |
|
|
|
if ( hltv->m_bMasterOnlyMode ) |
|
{ |
|
ConMsg("SourceTV in Master-Only mode.\n" ); |
|
return; |
|
} |
|
|
|
// shutdown anything else |
|
Host_Disconnect( false ); |
|
|
|
// start networking |
|
NET_SetMutiplayer( true ); |
|
|
|
hltv->ConnectRelay( address ); |
|
} |
|
|
|
CON_COMMAND( tv_stop, "Stops the SourceTV broadcast." ) |
|
{ |
|
if ( !hltv || !hltv->IsActive() ) |
|
{ |
|
ConMsg("SourceTV not active.\n" ); |
|
return; |
|
} |
|
|
|
int nClients = hltv->GetNumClients(); |
|
|
|
hltv->Shutdown(); |
|
|
|
ConMsg("SourceTV stopped, %i clients disconnected.\n", nClients ); |
|
} |
|
|
|
CON_COMMAND( tv_retry, "Reconnects the SourceTV relay proxy." ) |
|
{ |
|
if ( !hltv ) |
|
{ |
|
ConMsg("SourceTV not active.\n" ); |
|
return; |
|
} |
|
|
|
if ( hltv->m_bMasterOnlyMode ) |
|
{ |
|
ConMsg("SourceTV in Master-Only mode.\n" ); |
|
return; |
|
} |
|
|
|
if ( !hltv->m_ClientState.m_szRetryAddress[ 0 ] ) |
|
{ |
|
ConMsg( "Can't retry, no previous SourceTV connection\n" ); |
|
return; |
|
} |
|
|
|
ConMsg( "Commencing SourceTV connection retry to %s\n", hltv->m_ClientState.m_szRetryAddress ); |
|
Cbuf_AddText( va( "tv_relay %s\n", hltv->m_ClientState.m_szRetryAddress ) ); |
|
} |
|
|
|
CON_COMMAND( tv_record, "Starts SourceTV demo recording." ) |
|
{ |
|
if ( args.ArgC() < 2 ) |
|
{ |
|
ConMsg( "Usage: tv_record <filename>\n" ); |
|
return; |
|
} |
|
|
|
if ( !hltv || !hltv->IsActive() ) |
|
{ |
|
ConMsg("SourceTV not active.\n" ); |
|
return; |
|
} |
|
|
|
if ( !hltv->IsMasterProxy() ) |
|
{ |
|
ConMsg("Only SourceTV Master can record demos instantly.\n" ); |
|
return; |
|
} |
|
|
|
if ( hltv->m_DemoRecorder.IsRecording() ) |
|
{ |
|
ConMsg("SourceTV already recording to %s.\n", hltv->m_DemoRecorder.GetDemoFile()->m_szFileName ); |
|
return; |
|
} |
|
|
|
// check path first |
|
if ( !COM_IsValidPath( args[1] ) ) |
|
{ |
|
ConMsg( "record %s: invalid path.\n", args[1] ); |
|
return; |
|
} |
|
|
|
char name[ MAX_OSPATH ]; |
|
|
|
Q_strncpy( name, args[1], sizeof( name ) ); |
|
|
|
// add .dem if not already set by user |
|
Q_DefaultExtension( name, ".dem", sizeof( name ) ); |
|
|
|
hltv->m_DemoRecorder.StartRecording( name, false ); |
|
} |
|
|
|
CON_COMMAND( tv_stoprecord, "Stops SourceTV demo recording." ) |
|
{ |
|
if ( !hltv || !hltv->IsActive() ) |
|
{ |
|
ConMsg("SourceTV not active.\n" ); |
|
return; |
|
} |
|
|
|
hltv->m_DemoRecorder.StopRecording(); |
|
} |
|
|
|
CON_COMMAND( tv_clients, "Shows list of connected SourceTV clients." ) |
|
{ |
|
if ( !hltv || !hltv->IsActive() ) |
|
{ |
|
ConMsg("SourceTV not active.\n" ); |
|
return; |
|
} |
|
|
|
int nCount = 0; |
|
|
|
for ( int i=0; i<hltv->GetClientCount(); i++) |
|
{ |
|
CHLTVClient *client = hltv->Client( i ); |
|
INetChannel *netchan = client->GetNetChannel(); |
|
|
|
if ( !netchan ) |
|
continue; |
|
|
|
ConMsg("ID: %i, \"%s\" %s, Time %s, %s, In %.1f, Out %.1f.\n", |
|
client->GetUserID(), |
|
client->GetClientName(), |
|
client->IsHLTV() ? "(Relay)" : "", |
|
COM_FormatSeconds( netchan->GetTimeConnected() ), |
|
netchan->GetAddress(), |
|
netchan->GetAvgData( FLOW_INCOMING ) / 1024, |
|
netchan->GetAvgData( FLOW_OUTGOING ) / 1024 ); |
|
|
|
nCount++; |
|
} |
|
|
|
ConMsg("--- Total %i connected clients ---\n", nCount ); |
|
} |
|
|
|
CON_COMMAND( tv_msg, "Send a screen message to all clients." ) |
|
{ |
|
if ( !hltv || !hltv->IsActive() ) |
|
{ |
|
ConMsg("SourceTV not active.\n" ); |
|
return; |
|
} |
|
|
|
IGameEvent *msg = g_GameEventManager.CreateEvent( "hltv_message", true ); |
|
|
|
if ( msg ) |
|
{ |
|
msg->SetString( "text", args.ArgS() ); |
|
hltv->BroadcastEventLocal( msg, false ); |
|
g_GameEventManager.FreeEvent( msg ); |
|
} |
|
} |
|
|
|
#ifndef SWDS |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void EditDemo_f( const CCommand &args ) |
|
{ |
|
if ( cmd_source != src_command ) |
|
return; |
|
|
|
if ( args.ArgC() < 2 ) |
|
{ |
|
Msg ("editdemo <demoname> : edits a demo\n"); |
|
return; |
|
} |
|
|
|
// set current demo player to client demo player |
|
demoplayer = hltv; |
|
|
|
// |
|
// open the demo file |
|
// |
|
char name[ MAX_OSPATH ]; |
|
|
|
Q_strncpy( name, args[1], sizeof( name ) ); |
|
|
|
Q_DefaultExtension( name, ".dem", sizeof( name ) ); |
|
|
|
hltv->m_ClientState.m_bSaveMemory = true; |
|
|
|
demoplayer->StartPlayback( name, false ); |
|
} |
|
|
|
CON_COMMAND_AUTOCOMPLETEFILE( editdemo, EditDemo_f, "Edit a recorded demo file (.dem ).", NULL, dem ); |
|
#endif
|
|
|