text: ﻿//========= 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", V_STRINGIFY(DEFAULT_RATE), 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." );
  	}
  }
  