diff options
Diffstat (limited to 'engine/hltvclient.cpp')
| -rw-r--r-- | engine/hltvclient.cpp | 537 |
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." ); + } +} |