summaryrefslogtreecommitdiff
path: root/engine/hltvclient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/hltvclient.cpp')
-rw-r--r--engine/hltvclient.cpp537
1 files changed, 537 insertions, 0 deletions
diff --git a/engine/hltvclient.cpp b/engine/hltvclient.cpp
new file mode 100644
index 0000000..8cbb4ee
--- /dev/null
+++ b/engine/hltvclient.cpp
@@ -0,0 +1,537 @@
+//========= 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." );
+ }
+}