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.
537 lines
13 KiB
537 lines
13 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// hltvclient.cpp: implementation of the CHLTVClient class. |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//===========================================================================// |
|
|
|
#include <tier0/vprof.h> |
|
#include "hltvclient.h" |
|
#include "netmessages.h" |
|
#include "hltvserver.h" |
|
#include "framesnapshot.h" |
|
#include "networkstringtable.h" |
|
#include "dt_send_eng.h" |
|
#include "GameEventManager.h" |
|
#include "cmd.h" |
|
#include "ihltvdirector.h" |
|
#include "host.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
static ConVar tv_maxrate( "tv_maxrate", "8000", 0, "Max SourceTV spectator bandwidth rate allowed, 0 == unlimited" ); |
|
static ConVar tv_relaypassword( "tv_relaypassword", "", FCVAR_NOTIFY | FCVAR_PROTECTED | FCVAR_DONTRECORD, "SourceTV password for relay proxies" ); |
|
static ConVar tv_chattimelimit( "tv_chattimelimit", "8", 0, "Limits spectators to chat only every n seconds" ); |
|
static ConVar tv_chatgroupsize( "tv_chatgroupsize", "0", 0, "Set the default chat group size" ); |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
// Construction/Destruction |
|
////////////////////////////////////////////////////////////////////// |
|
|
|
CHLTVClient::CHLTVClient(int slot, CBaseServer *pServer) |
|
{ |
|
Clear(); |
|
|
|
Assert( hltv == pServer ); |
|
|
|
m_nClientSlot = slot; |
|
m_Server = pServer; |
|
m_pHLTV = dynamic_cast<CHLTVServer*>(pServer); |
|
m_nEntityIndex = m_pHLTV->GetHLTVSlot() + 1; |
|
m_nLastSendTick = 0; |
|
m_fLastSendTime = 0.0f; |
|
m_flLastChatTime = 0.0f; |
|
m_bNoChat = false; |
|
|
|
if ( tv_chatgroupsize.GetInt() > 0 ) |
|
{ |
|
Q_snprintf( m_szChatGroup, sizeof(m_szChatGroup), "group%d", slot%tv_chatgroupsize.GetInt() ); |
|
} |
|
else |
|
{ |
|
Q_strncpy( m_szChatGroup, "all", sizeof(m_szChatGroup) ); |
|
} |
|
} |
|
|
|
CHLTVClient::~CHLTVClient() |
|
{ |
|
|
|
} |
|
|
|
bool CHLTVClient::SendSignonData( void ) |
|
{ |
|
// check class table CRCs |
|
if ( m_nSendtableCRC != SendTable_GetCRC() ) |
|
{ |
|
Disconnect( "Server uses different class tables" ); |
|
return false; |
|
} |
|
else |
|
{ |
|
// use your class infos, CRC is correct |
|
SVC_ClassInfo classmsg( true, m_Server->serverclasses ); |
|
m_NetChannel->SendNetMsg( classmsg ); |
|
} |
|
|
|
return CBaseClient::SendSignonData(); |
|
} |
|
|
|
bool CHLTVClient::ProcessClientInfo( CLC_ClientInfo *msg ) |
|
{ |
|
if ( !CBaseClient::ProcessClientInfo( msg ) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
bool CHLTVClient::ProcessMove(CLC_Move *msg) |
|
{ |
|
// HLTV clients can't move |
|
return true; |
|
} |
|
|
|
bool CHLTVClient::ProcessListenEvents( CLC_ListenEvents *msg ) |
|
{ |
|
// HLTV clients can't subscribe to events, we just send them |
|
return true; |
|
} |
|
|
|
bool CHLTVClient::ProcessRespondCvarValue( CLC_RespondCvarValue *msg ) |
|
{ |
|
return true; |
|
} |
|
|
|
bool CHLTVClient::ProcessFileCRCCheck( CLC_FileCRCCheck *msg ) |
|
{ |
|
return true; |
|
} |
|
|
|
bool CHLTVClient::ProcessSaveReplay( CLC_SaveReplay *msg ) |
|
{ |
|
return true; |
|
} |
|
|
|
bool CHLTVClient::ProcessVoiceData(CLC_VoiceData *msg) |
|
{ |
|
// HLTV clients can't speak |
|
return true; |
|
} |
|
|
|
void CHLTVClient::ConnectionClosing(const char *reason) |
|
{ |
|
Disconnect ( (reason!=NULL)?reason:"Connection closing" ); |
|
} |
|
|
|
void CHLTVClient::ConnectionCrashed(const char *reason) |
|
{ |
|
DebuggerBreakIfDebugging_StagingOnly(); |
|
|
|
Disconnect ( (reason!=NULL)?reason:"Connection lost" ); |
|
} |
|
|
|
void CHLTVClient::PacketStart(int incoming_sequence, int outgoing_acknowledged) |
|
{ |
|
// During connection, only respond if client sends a packet |
|
m_bReceivedPacket = true; |
|
} |
|
|
|
void CHLTVClient::PacketEnd() |
|
{ |
|
|
|
} |
|
|
|
void CHLTVClient::FileRequested(const char *fileName, unsigned int transferID ) |
|
{ |
|
DevMsg( "CHLTVClient::FileRequested: %s.\n", fileName ); |
|
m_NetChannel->DenyFile( fileName, transferID ); |
|
} |
|
|
|
void CHLTVClient::FileDenied(const char *fileName, unsigned int transferID ) |
|
{ |
|
DevMsg( "CHLTVClient::FileDenied: %s.\n", fileName ); |
|
} |
|
|
|
void CHLTVClient::FileReceived( const char *fileName, unsigned int transferID ) |
|
{ |
|
DevMsg( "CHLTVClient::FileReceived: %s.\n", fileName ); |
|
} |
|
|
|
void CHLTVClient::FileSent(const char *fileName, unsigned int transferID ) |
|
{ |
|
} |
|
|
|
CClientFrame *CHLTVClient::GetDeltaFrame( int nTick ) |
|
{ |
|
return m_pHLTV->GetDeltaFrame( nTick ); |
|
} |
|
|
|
|
|
bool CHLTVClient::ExecuteStringCommand( const char *pCommandString ) |
|
{ |
|
// first let the baseclass handle it |
|
if ( CBaseClient::ExecuteStringCommand( pCommandString ) ) |
|
return true; |
|
|
|
if ( !pCommandString || !pCommandString[0] ) |
|
return true; |
|
|
|
CCommand args; |
|
if ( !args.Tokenize( pCommandString ) ) |
|
return true; |
|
|
|
const char *cmd = args[ 0 ]; |
|
|
|
if ( !Q_stricmp( cmd, "spec_next" ) || |
|
!Q_stricmp( cmd, "spec_prev" ) || |
|
!Q_stricmp( cmd, "spec_mode" ) ) |
|
{ |
|
ClientPrintf("Camera settings can't be changed during a live broadcast.\n"); |
|
return true; |
|
} |
|
|
|
if ( !Q_stricmp( cmd, "say" ) && args.ArgC() > 1 ) |
|
{ |
|
// if tv_chattimelimit = 0, chat is turned off |
|
if ( tv_chattimelimit.GetFloat() <= 0 ) |
|
return true; |
|
|
|
if ( (m_flLastChatTime + tv_chattimelimit.GetFloat()) > net_time ) |
|
return true; |
|
|
|
m_flLastChatTime = net_time; |
|
|
|
char chattext[128]; |
|
|
|
Q_snprintf( chattext, sizeof(chattext), "%s : %s", GetClientName(), args[1] ); |
|
|
|
m_pHLTV->BroadcastLocalChat( chattext, m_szChatGroup ); |
|
|
|
return true; |
|
} |
|
else if ( !Q_strcmp( cmd, "tv_chatgroup" ) ) |
|
{ |
|
if ( args.ArgC() > 1 ) |
|
{ |
|
Q_strncpy( m_szChatGroup, args[1], sizeof(m_szChatGroup) ); |
|
} |
|
else |
|
{ |
|
ClientPrintf("Your current chat group is \"%s\"\n", m_szChatGroup ); |
|
} |
|
return true; |
|
} |
|
else if ( !Q_strcmp( cmd, "status" ) ) |
|
{ |
|
int slots, proxies, clients; |
|
char gd[MAX_OSPATH]; |
|
Q_FileBase( com_gamedir, gd, sizeof( gd ) ); |
|
|
|
if ( m_pHLTV->IsMasterProxy() ) |
|
{ |
|
ClientPrintf("SourceTV Master \"%s\", delay %.0f\n", |
|
m_pHLTV->GetName(), m_pHLTV->GetDirector()->GetDelay() ); |
|
} |
|
else // if ( m_Server->IsRelayProxy() ) |
|
{ |
|
if ( m_pHLTV->GetRelayAddress() ) |
|
{ |
|
ClientPrintf("SourceTV Relay \"%s\", connected.\n", |
|
m_pHLTV->GetName() ); |
|
} |
|
else |
|
{ |
|
ClientPrintf("SourceTV Relay \"%s\", not connect.\n", m_pHLTV->GetName() ); |
|
} |
|
} |
|
|
|
ClientPrintf("IP %s:%i, Online %s, Version %i (%s)\n", |
|
net_local_adr.ToString( true ), m_pHLTV->GetUDPPort(), |
|
COM_FormatSeconds( m_pHLTV->GetOnlineTime() ), build_number(), |
|
#ifdef _WIN32 |
|
"Win32" ); |
|
#else |
|
"Linux" ); |
|
#endif |
|
|
|
ClientPrintf("Game Time %s, Mod \"%s\", Map \"%s\", Players %i\n", COM_FormatSeconds( m_pHLTV->GetTime() ), |
|
gd, m_pHLTV->GetMapName(), m_pHLTV->GetNumPlayers() ); |
|
m_pHLTV->GetLocalStats( proxies, slots, clients ); |
|
|
|
ClientPrintf("Local Slots %i, Spectators %i, Proxies %i\n", |
|
slots, clients-proxies, proxies ); |
|
|
|
m_pHLTV->GetGlobalStats( proxies, slots, clients); |
|
|
|
ClientPrintf("Total Slots %i, Spectators %i, Proxies %i\n", |
|
slots, clients-proxies, proxies); |
|
} |
|
else |
|
{ |
|
DevMsg( "CHLTVClient::ExecuteStringCommand: Unknown command %s.\n", pCommandString ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CHLTVClient::ShouldSendMessages( void ) |
|
{ |
|
if ( !IsActive() ) |
|
{ |
|
// during signon behave like normal client |
|
return CBaseClient::ShouldSendMessages(); |
|
} |
|
|
|
// HLTV clients use snapshot rate used by HLTV server, not given by HLTV client |
|
|
|
// if the reliable message overflowed, drop the client |
|
if ( m_NetChannel->IsOverflowed() ) |
|
{ |
|
m_NetChannel->Reset(); |
|
Disconnect ("%s overflowed reliable buffer\n", m_Name ); |
|
return false; |
|
} |
|
|
|
// send a packet if server has a new tick we didn't already send |
|
bool bSendMessage = ( m_nLastSendTick != m_Server->m_nTickCount ); |
|
|
|
// send a packet at least every 2 seconds |
|
if ( !bSendMessage && (m_fLastSendTime + 2.0f) < net_time ) |
|
{ |
|
bSendMessage = true; // force sending a message even if server didn't update |
|
} |
|
|
|
if ( bSendMessage && !m_NetChannel->CanPacket() ) |
|
{ |
|
// we would like to send a message, but bandwidth isn't available yet |
|
// in HLTV we don't send choke information, doesn't matter |
|
bSendMessage = false; |
|
} |
|
|
|
return bSendMessage; |
|
} |
|
|
|
void CHLTVClient::SpawnPlayer( void ) |
|
{ |
|
// set view entity |
|
|
|
SVC_SetView setView( m_pHLTV->m_nViewEntity ); |
|
|
|
SendNetMsg( setView ); |
|
|
|
m_pHLTV->BroadcastLocalTitle( this ); |
|
|
|
m_flLastChatTime = net_time; |
|
|
|
CBaseClient::SpawnPlayer(); |
|
} |
|
|
|
|
|
void CHLTVClient::SetRate(int nRate, bool bForce ) |
|
{ |
|
if ( !bForce ) |
|
{ |
|
if ( m_bIsHLTV ) |
|
{ |
|
// allow higher bandwidth rates for HLTV proxies |
|
nRate = clamp( nRate, MIN_RATE, MAX_RATE ); |
|
} |
|
else if ( tv_maxrate.GetInt() > 0 ) |
|
{ |
|
// restrict rate for normal clients to hltv_maxrate |
|
nRate = clamp( nRate, MIN_RATE, tv_maxrate.GetInt() ); |
|
} |
|
} |
|
|
|
CBaseClient::SetRate( nRate, bForce ); |
|
} |
|
|
|
void CHLTVClient::SetUpdateRate(int udpaterate, bool bForce) |
|
{ |
|
// for HLTV clients ignore update rate settings, speed is tv_snapshotrate |
|
m_fSnapshotInterval = 1.0f / 100.0f; |
|
} |
|
|
|
bool CHLTVClient::ProcessSetConVar(NET_SetConVar *msg) |
|
{ |
|
if ( !CBaseClient::ProcessSetConVar( msg ) ) |
|
return false; |
|
|
|
// if this is the first time we get user settings, check password etc |
|
if ( m_nSignonState == SIGNONSTATE_CONNECTED ) |
|
{ |
|
const char *checkpwd = NULL; |
|
|
|
m_bIsHLTV = m_ConVars->GetInt( "tv_relay", 0 ) != 0; |
|
|
|
if ( m_bIsHLTV ) |
|
{ |
|
// if the connecting client is a TV relay, check the password |
|
checkpwd = tv_relaypassword.GetString(); |
|
|
|
if ( checkpwd && checkpwd[0] && Q_stricmp( checkpwd, "none") ) |
|
{ |
|
if ( Q_stricmp( m_szPassword, checkpwd ) ) |
|
{ |
|
Disconnect("Bad relay password"); |
|
return false; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// if client is a normal spectator, check if we can to forward him to other relays |
|
if ( m_pHLTV->DispatchToRelay( this ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
// if client stays here, check the normal password |
|
checkpwd = m_pHLTV->GetPassword(); |
|
|
|
if ( checkpwd ) |
|
{ |
|
|
|
if ( Q_stricmp( m_szPassword, checkpwd ) ) |
|
{ |
|
Disconnect("Bad spectator password"); |
|
return false; |
|
} |
|
} |
|
|
|
// check if server is LAN only |
|
if ( !m_pHLTV->CheckIPRestrictions( m_NetChannel->GetRemoteAddress(), PROTOCOL_HASHEDCDKEY ) ) |
|
{ |
|
Disconnect( "SourceTV server is restricted to local spectators (class C).\n" ); |
|
return false; |
|
} |
|
|
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void CHLTVClient::UpdateUserSettings() |
|
{ |
|
// set voice loopback |
|
m_bNoChat = m_ConVars->GetInt( "tv_nochat", 0 ) != 0; |
|
|
|
CBaseClient::UpdateUserSettings(); |
|
} |
|
|
|
void CHLTVClient::SendSnapshot( CClientFrame * pFrame ) |
|
{ |
|
VPROF_BUDGET( "CHLTVClient::SendSnapshot", "HLTV" ); |
|
|
|
ALIGN4 byte buf[NET_MAX_PAYLOAD] ALIGN4_POST; |
|
bf_write msg( "CHLTVClient::SendSnapshot", buf, sizeof(buf) ); |
|
|
|
// if we send a full snapshot (no delta-compression) before, wait until client |
|
// received and acknowledge that update. don't spam client with full updates |
|
|
|
if ( m_pLastSnapshot == pFrame->GetSnapshot() ) |
|
{ |
|
// never send the same snapshot twice |
|
m_NetChannel->Transmit(); |
|
return; |
|
} |
|
|
|
if ( m_nForceWaitForTick > 0 ) |
|
{ |
|
// just continue transmitting reliable data |
|
Assert( !m_bFakePlayer ); // Should never happen |
|
m_NetChannel->Transmit(); |
|
return; |
|
} |
|
|
|
CClientFrame *pDeltaFrame = GetDeltaFrame( m_nDeltaTick ); // NULL if delta_tick is not found |
|
CHLTVFrame *pLastFrame = (CHLTVFrame*) GetDeltaFrame( m_nLastSendTick ); |
|
|
|
if ( pLastFrame ) |
|
{ |
|
// start first frame after last send |
|
pLastFrame = (CHLTVFrame*) pLastFrame->m_pNext; |
|
} |
|
|
|
// add all reliable messages between ]lastframe,currentframe] |
|
// add all tempent & sound messages between ]lastframe,currentframe] |
|
while ( pLastFrame && pLastFrame->tick_count <= pFrame->tick_count ) |
|
{ |
|
m_NetChannel->SendData( pLastFrame->m_Messages[HLTV_BUFFER_RELIABLE], true ); |
|
|
|
if ( pDeltaFrame ) |
|
{ |
|
// if we send entities delta compressed, also send unreliable data |
|
m_NetChannel->SendData( pLastFrame->m_Messages[HLTV_BUFFER_UNRELIABLE], false ); |
|
} |
|
|
|
pLastFrame = (CHLTVFrame*) pLastFrame->m_pNext; |
|
} |
|
|
|
// now create client snapshot packet |
|
|
|
// send tick time |
|
NET_Tick tickmsg( pFrame->tick_count, host_frametime_unbounded, host_frametime_stddeviation ); |
|
tickmsg.WriteToBuffer( msg ); |
|
|
|
// Update shared client/server string tables. Must be done before sending entities |
|
m_Server->m_StringTables->WriteUpdateMessage( NULL, GetMaxAckTickCount(), msg ); |
|
|
|
// TODO delta cache whole snapshots, not just packet entities. then use net_Align |
|
// send entity update, delta compressed if deltaFrame != NULL |
|
m_Server->WriteDeltaEntities( this, pFrame, pDeltaFrame, msg ); |
|
|
|
// write message to packet and check for overflow |
|
if ( msg.IsOverflowed() ) |
|
{ |
|
if ( !pDeltaFrame ) |
|
{ |
|
// if this is a reliable snapshot, drop the client |
|
Disconnect( "ERROR! Reliable snapshot overflow." ); |
|
return; |
|
} |
|
else |
|
{ |
|
// unreliable snapshots may be dropped |
|
ConMsg ("WARNING: msg overflowed for %s\n", m_Name); |
|
msg.Reset(); |
|
} |
|
} |
|
|
|
// remember this snapshot |
|
m_pLastSnapshot = pFrame->GetSnapshot(); |
|
m_nLastSendTick = pFrame->tick_count; |
|
|
|
// Don't send the datagram to fakeplayers |
|
if ( m_bFakePlayer ) |
|
{ |
|
m_nDeltaTick = pFrame->tick_count; |
|
return; |
|
} |
|
|
|
bool bSendOK; |
|
|
|
// is this is a full entity update (no delta) ? |
|
if ( !pDeltaFrame ) |
|
{ |
|
// transmit snapshot as reliable data chunk |
|
bSendOK = m_NetChannel->SendData( msg ); |
|
bSendOK = bSendOK && m_NetChannel->Transmit(); |
|
|
|
// remember this tickcount we send the reliable snapshot |
|
// so we can continue sending other updates if this has been acknowledged |
|
m_nForceWaitForTick = pFrame->tick_count; |
|
} |
|
else |
|
{ |
|
// just send it as unreliable snapshot |
|
bSendOK = m_NetChannel->SendDatagram( &msg ) > 0; |
|
} |
|
|
|
if ( !bSendOK ) |
|
{ |
|
Disconnect( "ERROR! Couldn't send snapshot." ); |
|
} |
|
}
|
|
|