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.
4707 lines
148 KiB
4707 lines
148 KiB
![]()
4 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
#include "cbase.h"
|
||
|
#include "tf_gc_client.h"
|
||
|
#include "gcsdk/gcsdk_auto.h"
|
||
|
#include "tf_gcmessages.h"
|
||
|
#include "kvpacker.h"
|
||
|
#include "tf_party.h"
|
||
|
// XXX(JohnS): Eventually, we want to send a smaller lobby object to clients. For now, they use the CTFGSLobby, which is
|
||
|
// in shared code for that reason.
|
||
|
#include "tf_lobby_server.h"
|
||
|
#include "base_gcmessages.pb.h"
|
||
|
#include "igameevents.h"
|
||
|
#include "netadr.h"
|
||
|
#include "protocol.h"
|
||
|
#include "econ_item_inventory.h"
|
||
|
#include "tf_item_inventory.h"
|
||
|
#include "tf_hud_mann_vs_machine_status.h"
|
||
|
#include "econ/confirm_dialog.h"
|
||
|
#include "rtime.h"
|
||
|
#include "ienginevgui.h"
|
||
|
#include "clientmode_tf.h"
|
||
|
#include "tf_match_description.h"
|
||
|
#include "tf_xp_source.h"
|
||
|
#include "tf_notification.h"
|
||
|
#include "c_tf_notification.h"
|
||
|
#include "tf_gc_shared.h"
|
||
|
#include <google/protobuf/text_format.h>
|
||
|
#include "tf_match_join_handlers.h"
|
||
|
#include "tf_matchmaking_dashboard.h"
|
||
|
#include "tf_ladder_data.h"
|
||
|
#include "tf_rating_data.h"
|
||
|
|
||
|
#include "econ_item_description.h"
|
||
|
|
||
|
#include "tf_hud_disconnect_prompt.h"
|
||
|
|
||
|
#include "util_shared.h"
|
||
|
#include <steamnetworkingsockets/isteamnetworkingutils.h>
|
||
|
#include <steamnetworkingsockets/isteamnetworkingsockets.h>
|
||
|
#include "filesystem.h"
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include "tier0/memdbgon.h"
|
||
|
|
||
|
void SelectGroup( EMatchmakingGroupType eGroup, bool bSelected );
|
||
|
|
||
|
#ifdef _DEBUG
|
||
|
#define GCMATCHMAKING_DEBUG_LEVEL 4
|
||
|
#else
|
||
|
#define GCMATCHMAKING_DEBUG_LEVEL 1
|
||
|
#endif
|
||
|
#define GCMatchmakingDebugSpew( lvl, ...) do { if ( lvl <= GCMATCHMAKING_DEBUG_LEVEL ) { Msg( __VA_ARGS__); } } while(false)
|
||
|
|
||
|
#define MM_REJOIN_WAIT_TIME 1.0f
|
||
|
|
||
|
static const char* s_pszCasualCriteriaSaveFileName = "casual_criteria.vdf";
|
||
|
|
||
|
|
||
|
// Ping stuff
|
||
|
static ConVar tf_datacenter_ping_interval( "tf_datacenter_ping_interval", "180", FCVAR_DEVELOPMENTONLY );
|
||
|
|
||
|
#ifdef TF_GC_PING_DEBUG
|
||
|
#define CV_TF_DATACENTER_PING_DEBUG_DEFAULT "1"
|
||
|
#else
|
||
|
#define CV_TF_DATACENTER_PING_DEBUG_DEFAULT "0"
|
||
|
#endif
|
||
|
static ConVar tf_datacenter_ping_debug( "tf_datacenter_ping_debug", CV_TF_DATACENTER_PING_DEBUG_DEFAULT, FCVAR_INTERNAL_USE );
|
||
|
|
||
|
static bool BPingDebug() { return tf_datacenter_ping_debug.GetBool(); }
|
||
|
#define TFPingMsg(...) Msg("[SDR Ping] " __VA_ARGS__)
|
||
|
#define TFPingDbg(...) if ( BPingDebug() ) { TFPingMsg( __VA_ARGS__ ); }
|
||
|
|
||
|
// Allow disabling for staging. Will only send dummy values set by the overrides above
|
||
|
#ifdef TF_GC_PING_DEBUG
|
||
|
#include "tier0/icommandline.h"
|
||
|
static bool BUseSteamDatagram() { return !CommandLine()->CheckParm("-nosteamdatagram" ); }
|
||
|
#else
|
||
|
static bool BUseSteamDatagram() { return true; }
|
||
|
#endif
|
||
|
|
||
|
using namespace GCSDK;
|
||
|
|
||
|
// @FD We need this for TF?
|
||
|
//DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_CONSOLE, "Console" );
|
||
|
|
||
|
static CTFGCClientSystem s_TFGCClientSystem;
|
||
|
CTFGCClientSystem *GTFGCClientSystem() { return &s_TFGCClientSystem; }
|
||
|
|
||
|
// Dialog Prompt Asking users if they want to rejoin a MvM Game
|
||
|
static CTFRejoinConfirmDialog *s_pRejoinLobbyDialog;
|
||
|
|
||
|
//bool g_bClientReceivedGCWelcome = false;
|
||
|
//bool CTFGCClientSystem::HasGCUserSessionBeenCreated() { return g_bClientReceivedGCWelcome; }
|
||
|
|
||
|
//static ConVar tf_spectator_auto_spectate_games( "tf_spectator_auto_spectate_games", "0", 0, "Automatically spectate available games" );
|
||
|
//static ConVar tf_auto_connect( "tf_auto_connect", "", 0, "Automatically connect to the specified server forever" );
|
||
|
static ConVar tf_matchgroups( "tf_matchgroups", "0", FCVAR_ARCHIVE, "Bit masks of match groups to search in for matchmaking" );
|
||
|
//static ConVar tf_auto_create_proxy( "tf_auto_create_proxy", "0", 0, "Automatically create a proxy" );
|
||
|
//ConVar tf_debug_today_message_sorting( "tf_debug_today_message_sorting", "0", 0, "Print out unsorted and sorted today messages to the console" );
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
CON_COMMAND( cl_check_process_count, "cl_check_process_count" )
|
||
|
{
|
||
|
int iProcessCount = engine->GetInstancesRunningCount();
|
||
|
Msg( "cl_check_process_count - %d \n", iProcessCount );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// This triggers a GC packet so isn't great to let clients misuse
|
||
|
#if defined( STAGING_ONLY ) || defined( _DEBUG )
|
||
|
CON_COMMAND( tf_datacenter_ping_refresh, "Force an immediate refresh of datacenter ping" )
|
||
|
{
|
||
|
GTFGCClientSystem()->InvalidatePingData();
|
||
|
}
|
||
|
#endif // defined( STAGING_ONLY ) || defined( _DEBUG )
|
||
|
|
||
|
CON_COMMAND( tf_datacenter_ping_dump, "Dump current datacenter ping values to console" )
|
||
|
{
|
||
|
GTFGCClientSystem()->DumpPing();
|
||
|
}
|
||
|
|
||
|
static void OnRejoinMvMLobbyDialogCallBack( bool bConfirmed, void *pContext )
|
||
|
{
|
||
|
GTFGCClientSystem()->RejoinLobby( bConfirmed );
|
||
|
s_pRejoinLobbyDialog = NULL;
|
||
|
}
|
||
|
|
||
|
void SubscribeToLocalPlayerSOCache( ISharedObjectListener* pListener )
|
||
|
{
|
||
|
if ( steamapicontext && steamapicontext->SteamUser() )
|
||
|
{
|
||
|
CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
|
||
|
GCClientSystem()->GetGCClient()->AddSOCacheListener( steamID, pListener );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Assert( !"Failed to subscribe to local user's SOCache!" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Helper to add or replace a ping entry in an update
|
||
|
static void ApplyPingToMsg( CMsgGCDataCenterPing_Update &msg, const CMsgGCDataCenterPing_Update_PingEntry &entry )
|
||
|
{
|
||
|
// Existing?
|
||
|
const char *pszName = entry.name().c_str();
|
||
|
CMsgGCDataCenterPing_Update_PingEntry *pEntry = NULL;
|
||
|
for ( int j = 0; j < msg.pingdata_size(); ++j )
|
||
|
{
|
||
|
CMsgGCDataCenterPing_Update_PingEntry& existingEntry = *msg.mutable_pingdata(j);
|
||
|
|
||
|
if ( V_stricmp( existingEntry.name().c_str(), pszName ) == 0 )
|
||
|
{
|
||
|
pEntry = &existingEntry;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// New?
|
||
|
if ( !pEntry )
|
||
|
{
|
||
|
pEntry = msg.add_pingdata();
|
||
|
}
|
||
|
|
||
|
pEntry->CopyFrom( entry );
|
||
|
}
|
||
|
|
||
|
const char *CTFGCClientSystem::k_pszSteamLobbyKey_PartyID = "PartyID";
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Reliable messages
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class ReliableMsgNotificationAcknowledge : public CJobReliableMessageBase < ReliableMsgNotificationAcknowledge,
|
||
|
CMsgNotificationAcknowledge,
|
||
|
k_EMsgGC_NotificationAcknowledge,
|
||
|
CMsgNotificationAcknowledgeReply,
|
||
|
k_EMsgGC_NotificationAcknowledgeReply >
|
||
|
{
|
||
|
public:
|
||
|
const char *MsgName() { return "NotificationAcknowledge"; }
|
||
|
void InitDebugString( CUtlString &dbgStr )
|
||
|
{
|
||
|
dbgStr.Format( "Account %u / Notification %016llx",
|
||
|
Msg().Body().account_id(), Msg().Body().notification_id() );
|
||
|
}
|
||
|
};
|
||
|
|
||
|
CTFGCClientSystem::CTFGCClientSystem()
|
||
|
: m_pPendingCreateOrUpdatePartyMsg( NULL )
|
||
|
, m_flSendPartyUpdateMessageTime( FLT_MAX )
|
||
|
, m_nMostSearchedMapCount( 0 )
|
||
|
, m_WorldStatus()
|
||
|
, m_bRegisteredSharedObjects( false )
|
||
|
, m_bInittedGC( false )
|
||
|
, m_eAcceptInviteStep( eAcceptInviteStep_None )
|
||
|
, m_eCreateLobbyStatus( k_EResultOK )
|
||
|
, m_bWantToActivateInviteUI( false )
|
||
|
, m_steamIDGCAssignedMatch()
|
||
|
, m_bAssignedMatchEnded( false )
|
||
|
, m_eAssignedMatchGroup( k_nMatchGroup_Invalid )
|
||
|
, m_uAssignedMatchID( 0 )
|
||
|
, m_bServerAssignmentChanged( false )
|
||
|
, m_rtLastPingFix( 0 )
|
||
|
, m_bPendingPingRefresh( false )
|
||
|
, m_bSentInitialPingFix( false )
|
||
|
, m_flCheckForRejoinTime( 0 )
|
||
|
, m_pSOCache( NULL )
|
||
|
, m_eConnectState( eConnectState_Disconnected )
|
||
|
, m_bGCUserSessionCreated( false )
|
||
|
, m_bUserWantsToBeInMatchmaking( false )
|
||
|
, m_nPendingAutoJoinPartyID( 0 )
|
||
|
, m_eLocalWizardStep( TF_Matchmaking_WizardStep_INVALID )
|
||
|
, m_callbackSteamLobbyCreated( this, &CTFGCClientSystem::OnSteamLobbyCreated )
|
||
|
, m_callbackSteamLobbyEnter( this, &CTFGCClientSystem::OnSteamLobbyEnter )
|
||
|
, m_callbackSteamLobbyChatMsg( this, &CTFGCClientSystem::OnSteamLobbyChatMsg )
|
||
|
, m_callbackSteamGameLobbyJoinRequested( this, &CTFGCClientSystem::OnSteamGameLobbyJoinRequested )
|
||
|
, m_callbackSteamLobbyDataUpdate( this, &CTFGCClientSystem::OnSteamLobbyDataUpdate )
|
||
|
, m_callbackSteamLobbyChatUpdate( this, &CTFGCClientSystem::OnSteamLobbyChatUpdate )
|
||
|
{
|
||
|
// replace base GCClientSystem
|
||
|
SetGCClientSystem( this );
|
||
|
|
||
|
s_pRejoinLobbyDialog = NULL;
|
||
|
|
||
|
// if ( g_bClientReceivedGCWelcome )
|
||
|
// {
|
||
|
// Msg( "CTFGCClientSystem::CTFGCClientSystem firing event\n" );
|
||
|
//
|
||
|
// IGameEvent *pEvent = gameeventmanager->CreateEvent( "gc_user_session_created" );
|
||
|
// if ( pEvent )
|
||
|
// {
|
||
|
// gameeventmanager->FireEventClientSide( pEvent );
|
||
|
// }
|
||
|
// }
|
||
|
// else
|
||
|
// {
|
||
|
// Msg( "CTFGCClientSystem::CTFGCClientSystem user session not yet created\n" );
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
|
||
|
CTFGCClientSystem::~CTFGCClientSystem( void )
|
||
|
{
|
||
|
// Prevent other system from using this pointer after it's destroyed
|
||
|
SetGCClientSystem( NULL );
|
||
|
}
|
||
|
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//// Purpose: Asynchronous job for getting news
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//class CGCClientJobGetNews : public GCSDK::CGCClientJob
|
||
|
//{
|
||
|
//public:
|
||
|
// CGCClientJobGetNews( GCSDK::CGCClient *pGCClient, int nAppID ) : GCSDK::CGCClientJob( pGCClient )
|
||
|
// {
|
||
|
// m_nAppID = nAppID;
|
||
|
// }
|
||
|
//
|
||
|
// virtual bool BYieldingRunJob( void *pvStartParam )
|
||
|
// {
|
||
|
// CGCMsg<MsgGCGetNews_t> msgGetNews( k_EMsgGCGetNews );
|
||
|
// msgGetNews.Body().m_unAppID = m_nAppID;
|
||
|
//
|
||
|
// GCSDK::CGCMsg<MsgGCNewsReponse_t> msgResponse( k_EMsgGCNewsResponse );
|
||
|
// bool bRet = BYldSendMessageAndGetReply( msgGetNews, 150, &msgResponse, k_EMsgGCNewsResponse );
|
||
|
// //Assert( bRet );
|
||
|
//
|
||
|
// if ( !bRet )
|
||
|
// {
|
||
|
// Warning( "CGCClientJobGetNews failed to get reply\n" );
|
||
|
// GTFGCClientSystem()->SetGetNewsTime( Plat_FloatTime() + 5.0f );
|
||
|
// return false;
|
||
|
// }
|
||
|
//
|
||
|
// //deserialize KV
|
||
|
// CUtlBuffer bufNews;
|
||
|
// bufNews.Put( msgResponse.PubReadCur(), msgResponse.Body().m_cMsgLen );
|
||
|
//
|
||
|
// KVPacker packer;
|
||
|
// KeyValues *pNewsKeys = GTFGCClientSystem()->GetNewsKeys();
|
||
|
// pNewsKeys->Clear();
|
||
|
// if ( !packer.ReadAsBinary( pNewsKeys, bufNews ) )
|
||
|
// {
|
||
|
// Warning( "Failed to deserialize key values from news request\n" );
|
||
|
// return false;
|
||
|
// }
|
||
|
//
|
||
|
// //KeyValuesDumpAsDevMsg( pNewsKeys );
|
||
|
//
|
||
|
// IGameEvent *pEvent = gameeventmanager->CreateEvent( "news_updated" );
|
||
|
// if ( pEvent )
|
||
|
// {
|
||
|
// gameeventmanager->FireEventClientSide( pEvent );
|
||
|
// }
|
||
|
//
|
||
|
// return true;
|
||
|
// }
|
||
|
//
|
||
|
//private:
|
||
|
// int m_nAppID;
|
||
|
//};
|
||
|
|
||
|
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//// Purpose: Asynchronous job for pinging the GC with a hello until we get
|
||
|
//// a welcome
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//class CGCClientJobHello : public GCSDK::CGCClientJob
|
||
|
//{
|
||
|
//public:
|
||
|
// CGCClientJobHello( GCSDK::CGCClient *pGCClient )
|
||
|
// : GCSDK::CGCClientJob( pGCClient )
|
||
|
// {
|
||
|
// }
|
||
|
//
|
||
|
// virtual bool BYieldingRunJob( void *pvStartParam )
|
||
|
// {
|
||
|
// CProtoBufMsg<CMsgClientHello> msg( k_EMsgGCClientHello );
|
||
|
// msg.Body().set_version( engine->GetClientVersion() );
|
||
|
//
|
||
|
// while ( !g_bClientReceivedGCWelcome )
|
||
|
// {
|
||
|
// // Wait two seconds between messages
|
||
|
// BYieldingWaitTime( 2 * k_nMillion );
|
||
|
//
|
||
|
// if ( !m_pGCClient->BSendMessage( msg ) )
|
||
|
// return false;
|
||
|
// }
|
||
|
// return true;
|
||
|
// }
|
||
|
//};
|
||
|
|
||
|
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//class CGCClientJobFindSourceTVGamesAutoSpectate : public GCSDK::CGCClientJob
|
||
|
//{
|
||
|
//public:
|
||
|
// CGCClientJobFindSourceTVGamesAutoSpectate( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient )
|
||
|
// {
|
||
|
// }
|
||
|
//
|
||
|
// virtual bool BYieldingRunJob( void *pvStartParam )
|
||
|
// {
|
||
|
// CProtoBufMsg<CMsgFindSourceTVGames> msg( k_EMsgGCFindSourceTVGames );
|
||
|
// CProtoBufMsg<CMsgSourceTVGamesResponse> msgResponse( k_EMsgGCSourceTVGamesResponse );
|
||
|
//
|
||
|
// static ConVarRef sv_search_key("sv_search_key");
|
||
|
// if ( sv_search_key.IsValid() && *sv_search_key.GetString() )
|
||
|
// {
|
||
|
// msg.Body().set_search_key( sv_search_key.GetString() );
|
||
|
// }
|
||
|
//
|
||
|
// msg.Body().set_start( 0 );
|
||
|
// msg.Body().set_num_games( 10 );
|
||
|
//
|
||
|
// bool bRet = BYldSendMessageAndGetReply( msg, 15, &msgResponse, k_EMsgGCSourceTVGamesResponse );
|
||
|
//
|
||
|
// GTFGCClientSystem()->SetAutoSpectateCheckTime( Plat_FloatTime() + 30.0f ); // try again in 30 seconds
|
||
|
//
|
||
|
// if ( !bRet )
|
||
|
// {
|
||
|
// Warning( "CGCClientJobFindSourceTVGamesDebug failed to get reply\n" );
|
||
|
// return false;
|
||
|
// }
|
||
|
//
|
||
|
// if ( GTFGCClientSystem()->GetSignonState() != SIGNONSTATE_NONE || !msgResponse.Body().games_size() )
|
||
|
// {
|
||
|
// return true; // already connected somewhere else
|
||
|
// }
|
||
|
//
|
||
|
// const CSourceTVGame &game = msgResponse.Body().games( RandomInt( 0, msgResponse.Body().games_size() - 1 ));
|
||
|
//
|
||
|
// GTFGCClientSystem()->StartWatchingGame( game.server_steamid() );
|
||
|
// return true;
|
||
|
// }
|
||
|
//
|
||
|
//private:
|
||
|
//};
|
||
|
|
||
|
void CTFGCClientSystem::LoadCasualSearchCriteria()
|
||
|
{
|
||
|
// Read casual criteria if the file exists
|
||
|
CUtlBuffer buffer;
|
||
|
buffer.SetBufferType( true, true );
|
||
|
if ( g_pFullFileSystem->ReadFile( s_pszCasualCriteriaSaveFileName, NULL, buffer ) &&
|
||
|
buffer.TellPut() > buffer.TellGet() )
|
||
|
{
|
||
|
// Null terminate. Why is buffer this pseudo-text class but has AddNullTerminator private?
|
||
|
const char zero = '\0';
|
||
|
buffer.Put( &zero, sizeof( zero ) );
|
||
|
|
||
|
std::string strIn( (const char *)buffer.PeekGet() );
|
||
|
|
||
|
google::protobuf::TextFormat::ParseFromString( strIn, m_msgLocalSearchCriteria.mutable_casual_criteria() );
|
||
|
|
||
|
// let the CCasualCriteriaHelper validate/cleanup the bits that we've just loaded
|
||
|
CCasualCriteriaHelper casualHelper( m_msgLocalSearchCriteria.casual_criteria() );
|
||
|
if ( GetParty() != NULL )
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->mutable_search_criteria()->mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
|
||
|
}
|
||
|
|
||
|
m_msgLocalSearchCriteria.mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
|
||
|
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// default to the Core maps
|
||
|
SelectGroup( kMatchmakingType_Core, true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Initialize steam client datagram lib if we haven't already
|
||
|
static bool CheckInitSteamDatagramClientLib()
|
||
|
{
|
||
|
if ( !BUseSteamDatagram() )
|
||
|
return false;
|
||
|
|
||
|
if ( !steamapicontext || !steamapicontext->SteamHTTP() || !steamapicontext->SteamUtils() )
|
||
|
{
|
||
|
Assert( false );
|
||
|
Warning( "Steam datagram not initialized - no Steam context\n" );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static bool bInittedNetwork = false;
|
||
|
if ( bInittedNetwork )
|
||
|
return true;
|
||
|
|
||
|
// Locate the first PLATFORM path
|
||
|
char szAbsPlatform[MAX_PATH] = "";
|
||
|
const char *pszConfigDir = "config";
|
||
|
g_pFullFileSystem->GetSearchPath( "PLATFORM", false, szAbsPlatform, sizeof(szAbsPlatform) );
|
||
|
|
||
|
char *semi = strchr( szAbsPlatform, ';' );
|
||
|
if ( semi )
|
||
|
*semi = '\0';
|
||
|
|
||
|
char szAbsConfigDir[MAX_PATH];
|
||
|
V_ComposeFileName( szAbsPlatform, pszConfigDir, szAbsConfigDir, sizeof(szAbsConfigDir) );
|
||
|
SteamDatagramErrMsg errMsg;
|
||
|
if ( !SteamDatagramClient_Init( szAbsConfigDir, k_ESteamDatagramPartner_Steam, (1<<k_ESteamDatagramPartner_Steam), errMsg ) )
|
||
|
{
|
||
|
Warning( "Failed to initialize steam datagram client. %s\n", errMsg );
|
||
|
return false;
|
||
|
}
|
||
|
bInittedNetwork = true;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
bool CTFGCClientSystem::Init()
|
||
|
{
|
||
|
// Get this guy created
|
||
|
GetMMDashboard();
|
||
|
|
||
|
// Convars may have initialized before us
|
||
|
UpdateCustomPingTolerance();
|
||
|
|
||
|
ListenForGameEvent( "client_disconnect" );
|
||
|
ListenForGameEvent( "client_beginconnect" );
|
||
|
ListenForGameEvent( "server_spawn" );
|
||
|
|
||
|
// init steamdatagram system ASAP so we're more likely to have initial ping data to the clusters ready by the time
|
||
|
// we ask for it
|
||
|
CheckInitSteamDatagramClientLib();
|
||
|
if ( SteamNetworkingUtils() )
|
||
|
SteamNetworkingUtils()->CheckPingDataUpToDate( 0.0f );
|
||
|
|
||
|
// Just loading the library starts initial pinging
|
||
|
m_bPendingPingRefresh = true;
|
||
|
|
||
|
// m_GameVersion = GAME_VERSION_CURRENT;
|
||
|
|
||
|
// Default search criteria
|
||
|
//m_msgLocalSearchCriteria.set_matchgroups( 1 );
|
||
|
m_msgLocalSearchCriteria.set_matchmaking_mode( TF_Matchmaking_LADDER );
|
||
|
|
||
|
m_bLocalSquadSurplus = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CTFGCClientSystem::PostInit()
|
||
|
{
|
||
|
BaseClass::PostInit();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::PreInitGC()
|
||
|
{
|
||
|
if ( !m_bRegisteredSharedObjects )
|
||
|
{
|
||
|
// REG_SHARED_OBJECT_SUBCLASS( CTFHeroStandings );
|
||
|
// REG_SHARED_OBJECT_SUBCLASS( CTFGameAccountClient );
|
||
|
REG_SHARED_OBJECT_SUBCLASS( CTFParty );
|
||
|
REG_SHARED_OBJECT_SUBCLASS( CTFGSLobby );
|
||
|
REG_SHARED_OBJECT_SUBCLASS( CPartyInvite );
|
||
|
// REG_SHARED_OBJECT_SUBCLASS( CTFBetaParticipation );
|
||
|
REG_SHARED_OBJECT_SUBCLASS( CTFPartyInvite );
|
||
|
REG_SHARED_OBJECT_SUBCLASS( CTFRatingData );
|
||
|
|
||
|
m_bRegisteredSharedObjects = true;
|
||
|
}
|
||
|
|
||
|
// if ( m_flGetNewsTime == 0.0f )
|
||
|
// {
|
||
|
// m_flGetNewsTime = Plat_FloatTime() + 2.0f;
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::PostInitGC()
|
||
|
{
|
||
|
GCMatchmakingDebugSpew( 1, "CTFGCClientSystem::PostInitGC\n" );
|
||
|
|
||
|
if ( steamapicontext && steamapicontext->SteamUser() )
|
||
|
{
|
||
|
GCMatchmakingDebugSpew( 1, "CTFGCClientSystem - adding listener\n" );
|
||
|
|
||
|
CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
|
||
|
GCClientSystem()->FindOrAddSOCache( steamID )->AddListener( this );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Warning( "CTFGCClientSystem - couldn't add listener because Steam wasn't ready\n" );
|
||
|
}
|
||
|
|
||
|
// @FD We need this?
|
||
|
// // Force a resend of our SO cache.
|
||
|
// // This is only necessary because the Steam client doesn't detect a quick relaunch of the game, so the GC doesn't get a SessionStartPlaying call.
|
||
|
// CProtoBufMsg<CMsgForceSOCacheResend> msg( k_EMsgForceSOCacheResend );
|
||
|
// GCClientSystem()->BSendMessage( msg );
|
||
|
|
||
|
// // Start hello job to ping the GC until we get a welcome
|
||
|
// if ( !g_bClientReceivedGCWelcome )
|
||
|
// {
|
||
|
// CGCClientJobHello *pJob = new CGCClientJobHello( GCClientSystem()->GetGCClient() );
|
||
|
// pJob->StartJob( NULL );
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::ReceivedClientWelcome( const CMsgClientWelcome &msg )
|
||
|
{
|
||
|
BaseClass::ReceivedClientWelcome( msg );
|
||
|
|
||
|
// Send a client init message in response to welcome
|
||
|
GCSDK::CProtoBufMsg<CMsgTFClientInit> initMsg( k_EMsgGC_TFClientInit );
|
||
|
initMsg.Body().set_client_version( engine->GetClientVersion() );
|
||
|
char uilanguage[ 64 ];
|
||
|
engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
|
||
|
initMsg.Body().set_language( PchLanguageToELanguage( uilanguage ) );
|
||
|
this->BSendMessage( initMsg );
|
||
|
|
||
|
// Send a ping fix if we know it (e.g. we re-connected to the GC, or got a fix before the GC was ready)
|
||
|
if ( BHavePingData() )
|
||
|
{
|
||
|
GCSDK::CProtoBufMsg<CMsgGCDataCenterPing_Update> pingmsg( k_EMsgGCDataCenterPing_Update );
|
||
|
pingmsg.Body().CopyFrom( GetPingData() );
|
||
|
|
||
|
m_bSentInitialPingFix = this->BSendMessage( pingmsg );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// PingThink will send it next fix
|
||
|
m_bSentInitialPingFix = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class CSendCreateOrUpdatePartyMsgJob;
|
||
|
|
||
|
static int s_nNumWizardStepChangesWaitingForReply = 0;
|
||
|
|
||
|
class CSendCreateOrUpdatePartyMsgJob : public GCSDK::CGCClientJob
|
||
|
{
|
||
|
public:
|
||
|
|
||
|
CSendCreateOrUpdatePartyMsgJob()
|
||
|
: GCSDK::CGCClientJob( GCClientSystem()->GetGCClient() )
|
||
|
, msg( k_EMsgGCCreateOrUpdateParty )
|
||
|
{
|
||
|
msg.Body().set_client_version( engine->GetClientVersion() );
|
||
|
}
|
||
|
|
||
|
CProtoBufMsg<CMsgCreateOrUpdateParty> msg;
|
||
|
CProtoBufMsg<CMsgCreateOrUpdatePartyReply> msgReply;
|
||
|
|
||
|
static void UpdateWizardStepFromParty()
|
||
|
{
|
||
|
// Make sure we have a party and the data changed
|
||
|
CTFParty *pParty = GTFGCClientSystem()->GetParty();
|
||
|
if ( pParty != NULL && GTFGCClientSystem()->m_eLocalWizardStep != pParty->Obj().wizard_step() )
|
||
|
{
|
||
|
GTFGCClientSystem()->m_eLocalWizardStep = pParty->Obj().wizard_step();
|
||
|
GTFGCClientSystem()->FireGameEventPartyUpdated();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
virtual bool BYieldingRunJob( void *pvStartParam )
|
||
|
{
|
||
|
Assert( s_nNumWizardStepChangesWaitingForReply >= 0 );
|
||
|
if ( msg.Body().has_wizard_step() )
|
||
|
++s_nNumWizardStepChangesWaitingForReply;
|
||
|
|
||
|
bool bGotReply = BYldSendMessageAndGetReply( msg, 10, &msgReply, k_EMsgGCCreateOrUpdatePartyReply );
|
||
|
|
||
|
if ( msg.Body().has_wizard_step() )
|
||
|
--s_nNumWizardStepChangesWaitingForReply;
|
||
|
Assert( s_nNumWizardStepChangesWaitingForReply >= 0 );
|
||
|
|
||
|
if ( !bGotReply )
|
||
|
{
|
||
|
CTFParty *pParty = GTFGCClientSystem()->GetParty();
|
||
|
if ( pParty )
|
||
|
{
|
||
|
pParty->SetOffline( true );
|
||
|
}
|
||
|
|
||
|
GTFGCClientSystem()->FireGameEventPartyUpdated();
|
||
|
// !FIXME! Here we really should mark the GC Client as not
|
||
|
// being connected to the GC
|
||
|
|
||
|
Warning( "Timed out getting reply from GC to change party.\n" );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Any error message?
|
||
|
EResult result = (EResult)msgReply.Body().result();
|
||
|
const char *pszMsg = msgReply.Body().message().c_str();
|
||
|
if ( *pszMsg != '\0' )
|
||
|
{
|
||
|
if ( result != k_EResultOK )
|
||
|
{
|
||
|
Warning( "%s\n", pszMsg );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Msg( "%s\n", pszMsg );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check for error.
|
||
|
switch ( result )
|
||
|
{
|
||
|
case k_EResultOK:
|
||
|
break;
|
||
|
case k_EResultInvalidProtocolVer:
|
||
|
//if ( GTFGCClientSystem()->BIsPartyLeader() )
|
||
|
//{
|
||
|
//}
|
||
|
GTFGCClientSystem()->EndMatchmaking();
|
||
|
ShowMessageBox( "#TF_MM_NotCurrentVersionTitle", "#TF_MM_NotCurrentVersionMessage", "#GameUI_OK" );
|
||
|
return true;
|
||
|
default:
|
||
|
Warning( "CreateOrUpdate returned error code %d\n", result );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// If no more messages pending that will change the wizard step, then
|
||
|
// get in sync with the GC
|
||
|
if ( s_nNumWizardStepChangesWaitingForReply <= 0 )
|
||
|
{
|
||
|
UpdateWizardStepFromParty();
|
||
|
GTFGCClientSystem()->FireGameEventPartyUpdated();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Did we request a particular wizard step?
|
||
|
if ( !msg.Body().has_wizard_step() )
|
||
|
return true;
|
||
|
|
||
|
// Do we have a party?
|
||
|
CTFParty *pParty = GTFGCClientSystem()->GetParty();
|
||
|
if ( pParty == NULL )
|
||
|
return true;
|
||
|
|
||
|
// We got a response. Definitely not offline anymore
|
||
|
pParty->SetOffline( false );
|
||
|
|
||
|
// We should be the party leader
|
||
|
Assert( GTFGCClientSystem()->BIsPartyLeader() );
|
||
|
|
||
|
// Party should have a known wizard step
|
||
|
Assert( pParty->Obj().has_wizard_step() );
|
||
|
if ( !pParty->Obj().has_wizard_step() )
|
||
|
return true;
|
||
|
|
||
|
// If GC did not like our request, or we are starting or stopping searching,
|
||
|
// the force us to get on the same page as the GC
|
||
|
if (
|
||
|
msg.Body().wizard_step() != pParty->Obj().wizard_step() // after processing message, we are not in requested step
|
||
|
|| msg.Body().wizard_step() == TF_Matchmaking_WizardStep_SEARCHING // we requested to search
|
||
|
|| pParty->Obj().wizard_step() == TF_Matchmaking_WizardStep_SEARCHING // we are now searching
|
||
|
|| GTFGCClientSystem()->m_eLocalWizardStep == TF_Matchmaking_WizardStep_SEARCHING // we think we were searching previously
|
||
|
)
|
||
|
{
|
||
|
UpdateWizardStepFromParty();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
CMsgCreateOrUpdateParty *CTFGCClientSystem::GetCreateOrUpdatePartyMsg()
|
||
|
{
|
||
|
// TODO We should only send updates if something changes, some callers might just be copying same-values back in :-/
|
||
|
|
||
|
if ( m_pPendingCreateOrUpdatePartyMsg == NULL )
|
||
|
{
|
||
|
m_pPendingCreateOrUpdatePartyMsg = new CSendCreateOrUpdatePartyMsgJob;
|
||
|
}
|
||
|
|
||
|
if ( m_flSendPartyUpdateMessageTime == FLT_MAX )
|
||
|
{
|
||
|
if ( GetParty() && GetParty()->GetNumMembers() > 1 )
|
||
|
{
|
||
|
// If we're in a party, delay the sending of the message to queue up any rapid changes
|
||
|
// that might occur from users clicking on criteria UI controls
|
||
|
m_flSendPartyUpdateMessageTime = Plat_FloatTime() + 2.f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_flSendPartyUpdateMessageTime = 0.f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return &m_pPendingCreateOrUpdatePartyMsg->msg.Body();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::LevelShutdownPostEntity()
|
||
|
{
|
||
|
BaseClass::LevelShutdownPostEntity();
|
||
|
// // clear caches, so the player will see his stats update after a game
|
||
|
// if ( Dashboard() )
|
||
|
// {
|
||
|
// Dashboard()->ClearDashboardCaches();
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::LevelInitPreEntity()
|
||
|
{
|
||
|
BaseClass::LevelInitPreEntity();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::Shutdown()
|
||
|
{
|
||
|
GCMatchmakingDebugSpew( 1, "CTFGCClientSystem::ShutdownGC\n" );
|
||
|
|
||
|
if ( steamapicontext && steamapicontext->SteamUser() )
|
||
|
{
|
||
|
GCMatchmakingDebugSpew( 1, "CTFGCClientSystem - adding listener\n" );
|
||
|
|
||
|
CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
|
||
|
GCSDK::CGCClientSharedObjectCache *pSOCache = GCClientSystem()->GetSOCache( steamID );
|
||
|
Assert( pSOCache ); // we installed ourselves as a listener, right, so it shouldn't have deleted the cache
|
||
|
if ( pSOCache )
|
||
|
{
|
||
|
pSOCache->RemoveListener( this );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Warning( "CTFGCClientSystem - couldn't add listener because Steam wasn't ready\n" );
|
||
|
}
|
||
|
|
||
|
BaseClass::Shutdown();
|
||
|
|
||
|
SteamDatagramClient_Kill();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::FireGameEvent( IGameEvent *event )
|
||
|
{
|
||
|
const char *pEventName = event->GetName();
|
||
|
// Disconnected from gameserver
|
||
|
if ( !Q_stricmp( pEventName, "client_disconnect" ) )
|
||
|
{
|
||
|
m_steamIDCurrentServer.Clear();
|
||
|
|
||
|
// Do not send end match making if we see the mvm end message
|
||
|
if ( !Q_stricmp( event->GetString( "message", "" ), "#TF_PVE_Disconnect" ) )
|
||
|
return;
|
||
|
|
||
|
// Don't bail if GC has told us to expect to be put into a new party
|
||
|
if ( m_nPendingAutoJoinPartyID != 0 )
|
||
|
return;
|
||
|
|
||
|
m_eConnectState = eConnectState_Disconnected; // clear variable first to avoid recursion
|
||
|
|
||
|
// Ladder games
|
||
|
//if ( !Q_stricmp( event->GetString( "message", "" ), "#TF_Competitive_Disconnect" ) ) // FIXME only disconnect if we were previously connected(ing), this fires spuriously from the main menu
|
||
|
//{
|
||
|
// engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby ladder" );
|
||
|
// return;
|
||
|
//}
|
||
|
|
||
|
CTFParty *pParty = GetParty();
|
||
|
|
||
|
// Return to party screen upon disconnect
|
||
|
if ( m_bUserWantsToBeInMatchmaking && ( ( pParty && pParty->GetNumMembers() > 1 ) || m_eLocalWizardStep == TF_Matchmaking_WizardStep_SEARCHING ) )
|
||
|
{
|
||
|
switch( GTFGCClientSystem()->GetSearchMode() )
|
||
|
{
|
||
|
case TF_Matchmaking_LADDER:
|
||
|
engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby ladder" );
|
||
|
break;
|
||
|
|
||
|
case TF_Matchmaking_CASUAL:
|
||
|
engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby casual" );
|
||
|
break;
|
||
|
case TF_Matchmaking_MVM:
|
||
|
engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby mvm" );
|
||
|
break;
|
||
|
default:
|
||
|
engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby" );
|
||
|
Assert( !"Unhandled enum value" );
|
||
|
break;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Started attempting connection to gameserver
|
||
|
if ( !Q_stricmp( pEventName, "client_beginconnect" ) )
|
||
|
{
|
||
|
Assert( IsConnectStateDisconnected() );
|
||
|
|
||
|
// TODO does the retry command set this source? It should go through ::ConnectToServer
|
||
|
const char *pszSource = event->GetString( "source", "" );
|
||
|
if ( FStrEq( pszSource, "matchmaking" ) )
|
||
|
{
|
||
|
// Assume we're doing the right thing until we hit server_spawn and figure otherwise.
|
||
|
m_steamIDCurrentServer = m_steamIDGCAssignedMatch;
|
||
|
m_eConnectState = eConnectState_ConnectingToMatchmade;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( !BAllowMatchMakingInGame() )
|
||
|
{
|
||
|
EndMatchmaking();
|
||
|
}
|
||
|
|
||
|
m_eConnectState = eConnectState_NonmatchmadeServer;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Successfully connected to a gameserver. For MM purposes, we stay in state connecting until server spawn as that
|
||
|
// ensures there's no intermediate "loading into some server but we're not sure of its steamid yet" state.
|
||
|
if ( !Q_strcmp( pEventName, "server_spawn" ) )
|
||
|
{
|
||
|
GCMatchmakingDebugSpew( 4, "Client reached server_spawn.\n" );
|
||
|
switch ( m_eConnectState )
|
||
|
{
|
||
|
default:
|
||
|
AssertMsg1( false, "Unknown connect state %d", m_eConnectState );
|
||
|
// These two can happen when doing weird things with timedemo or listen servers
|
||
|
case eConnectState_Disconnected:
|
||
|
m_eConnectState = eConnectState_NonmatchmadeServer;
|
||
|
GCMatchmakingDebugSpew( 4, "Client connected to non-matchmade.\n" );
|
||
|
break;
|
||
|
|
||
|
case eConnectState_ConnectingToMatchmade:
|
||
|
m_eConnectState = eConnectState_ConnectedToMatchmade;
|
||
|
GCMatchmakingDebugSpew( 4, "Client connected to matchmade.\n" );
|
||
|
break;
|
||
|
|
||
|
case eConnectState_ConnectedToMatchmade:
|
||
|
break;
|
||
|
|
||
|
case eConnectState_NonmatchmadeServer:
|
||
|
break;
|
||
|
}
|
||
|
m_steamIDCurrentServer.Clear();
|
||
|
if ( steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUtils() )
|
||
|
{
|
||
|
m_steamIDCurrentServer.SetFromString( event->GetString( "steamid", "" ), GetUniverse() );
|
||
|
GCMatchmakingDebugSpew( 4, "Recognizing MM server id %s\n", m_steamIDCurrentServer.Render() );
|
||
|
}
|
||
|
|
||
|
if ( m_eConnectState == eConnectState_ConnectedToMatchmade && !m_steamIDCurrentServer.IsValid() )
|
||
|
{
|
||
|
Warning( "Connected to MM server but no GS steamid is set.\n" );
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::InvalidatePingData()
|
||
|
{
|
||
|
// Invalidate current refresh
|
||
|
TFPingMsg("Forcing ping refresh\n" );
|
||
|
m_bPendingPingRefresh = true;
|
||
|
|
||
|
// Wipe all cached data.
|
||
|
m_rtLastPingFix = 0; // 0 means never. Or time traveler. 50/50.
|
||
|
m_msgCachedPingUpdate = CMsgGCDataCenterPing_Update();
|
||
|
|
||
|
if ( BUseSteamDatagram() && SteamNetworkingUtils() )
|
||
|
{
|
||
|
SteamNetworkingUtils()->CheckPingDataUpToDate( 0.0f );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::PingThink()
|
||
|
{
|
||
|
ISteamNetworkingUtils *pUtils = SteamNetworkingUtils();
|
||
|
if ( !pUtils && BUseSteamDatagram() )
|
||
|
{
|
||
|
Assert( pUtils );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( !m_bPendingPingRefresh )
|
||
|
{
|
||
|
// No refresh in progress, start one if necessary
|
||
|
RTime32 rtRefreshInterval = (RTime32)Clamp( tf_datacenter_ping_interval.GetInt(), 0, INT32_MAX );
|
||
|
RTime32 rtLastRefreshAge = CRTime::RTime32TimeCur() - m_rtLastPingFix;
|
||
|
|
||
|
if ( rtLastRefreshAge <= rtRefreshInterval )
|
||
|
{ return; }
|
||
|
|
||
|
// Start a refresh
|
||
|
m_bPendingPingRefresh = true;
|
||
|
|
||
|
// Non-steam datagram will just succeed next heartbeat
|
||
|
if ( BUseSteamDatagram() )
|
||
|
{ pUtils->CheckPingDataUpToDate(0.0f); }
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Refresh pending, speculatively start building an update and bail out if we find missing data
|
||
|
//
|
||
|
|
||
|
CMsgGCDataCenterPing_Update newPingUpdate;
|
||
|
|
||
|
// Check if our connection status is good enough for ping measurement
|
||
|
// Without steamdatagram, we'll just always succeed immediately with empty data (plus overrides below)
|
||
|
if ( BUseSteamDatagram() )
|
||
|
{
|
||
|
// Not ready yet?
|
||
|
if ( pUtils->IsPingMeasurementInProgress() )
|
||
|
return;
|
||
|
|
||
|
// Get complete list of points of presence
|
||
|
CUtlVector<SteamNetworkingPOPID> vecPoPs;
|
||
|
vecPoPs.SetCount( pUtils->GetPOPCount() );
|
||
|
vecPoPs.SetCountNonDestructively( pUtils->GetPOPList( vecPoPs.Base(), vecPoPs.Count() ) );
|
||
|
|
||
|
// Waiting on a ping refresh to complete. Check if we have every datacenter and cache off if so.
|
||
|
//
|
||
|
// NOTE that we don't use SDR for actual-routing yet, so we are purposefully using the *router* ping values as
|
||
|
// estimates for that DC -- since we will talk to the DC directly and not via the relay.
|
||
|
for ( SteamNetworkingPOPID id: vecPoPs )
|
||
|
{
|
||
|
char szCode[ 8 ];
|
||
|
GetSteamNetworkingLocationPOPStringFromID( id, szCode );
|
||
|
|
||
|
CMsgGCDataCenterPing_Update_PingEntry *pMsgPingEntry = newPingUpdate.add_pingdata();
|
||
|
pMsgPingEntry->set_name( szCode );
|
||
|
int nPing = pUtils->GetDirectPingToPOP( id );
|
||
|
if ( nPing >= 0 )
|
||
|
{
|
||
|
pMsgPingEntry->set_ping( nPing );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nPing = pUtils->GetPingToDataCenter( id, nullptr );
|
||
|
if ( nPing >= 0 )
|
||
|
{
|
||
|
pMsgPingEntry->set_ping( nPing );
|
||
|
pMsgPingEntry->set_ping_status( CMsgGCDataCenterPing_Update_Status_FallbackToDCPing );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !pMsgPingEntry->has_ping() )
|
||
|
{
|
||
|
pMsgPingEntry->set_ping_status( CMsgGCDataCenterPing_Update_Status_Unreachable );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Otherwise we're fine
|
||
|
TFPingMsg( "Not using steam datagram, proceeding with empty cluster ping data\n" );
|
||
|
}
|
||
|
|
||
|
// If we're in beta/dev, add the magic "beta" cluster. See tf_datacenter_info on GC.
|
||
|
EUniverse eUniverse = GetUniverse();
|
||
|
if ( eUniverse == k_EUniverseBeta || eUniverse == k_EUniverseDev )
|
||
|
{
|
||
|
CMsgGCDataCenterPing_Update_PingEntry newEntry;
|
||
|
newEntry.set_name( "beta" );
|
||
|
newEntry.set_ping( 5 );
|
||
|
newEntry.set_ping_status( CMsgGCDataCenterPing_Update_Status_Normal );
|
||
|
ApplyPingToMsg( newPingUpdate, newEntry );
|
||
|
}
|
||
|
|
||
|
#ifdef TF_GC_PING_DEBUG
|
||
|
// Apply overrides
|
||
|
for ( int j = 0; j < m_msgPingOverrides.pingdata_size(); ++j )
|
||
|
{
|
||
|
ApplyPingToMsg( newPingUpdate, m_msgPingOverrides.pingdata(j) );
|
||
|
}
|
||
|
#endif // def TF_GC_PING_DEBUG
|
||
|
|
||
|
// We made it through all routers without bailing, can claim to have ping data now
|
||
|
if ( BConnectedtoGC() )
|
||
|
{
|
||
|
GCSDK::CProtoBufMsg<CMsgGCDataCenterPing_Update> msg( k_EMsgGCDataCenterPing_Update );
|
||
|
msg.Body().CopyFrom( newPingUpdate );
|
||
|
|
||
|
if ( this->BSendMessage( msg ) )
|
||
|
{
|
||
|
TFPingDbg( "Initial ping fix sent\n" );
|
||
|
m_bSentInitialPingFix = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_bPendingPingRefresh = false;
|
||
|
m_rtLastPingFix = CRTime::RTime32TimeCur();
|
||
|
m_msgCachedPingUpdate = newPingUpdate;
|
||
|
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "ping_updated" );
|
||
|
if ( event )
|
||
|
{
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
|
||
|
if ( BPingDebug() )
|
||
|
{
|
||
|
DumpPing();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef TF_GC_PING_DEBUG
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::SetPingOverride( const char *pszDataCenter, uint32 nPing, CMsgGCDataCenterPing_Update_Status eStatus )
|
||
|
{
|
||
|
CMsgGCDataCenterPing_Update_PingEntry newEntry;
|
||
|
newEntry.set_name( pszDataCenter );
|
||
|
newEntry.set_ping( nPing );
|
||
|
newEntry.set_ping_status( eStatus );
|
||
|
ApplyPingToMsg( m_msgPingOverrides, newEntry );
|
||
|
|
||
|
InvalidatePingData();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::ClearPingOverrides()
|
||
|
{
|
||
|
m_msgPingOverrides.clear_pingdata();
|
||
|
|
||
|
InvalidatePingData();
|
||
|
}
|
||
|
#endif // def TF_GC_PING_DEBUG
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::Update( float frametime )
|
||
|
{
|
||
|
BaseClass::Update( frametime );
|
||
|
|
||
|
// if ( m_flGetNewsTime != 0.0f && Plat_FloatTime() > m_flGetNewsTime && steamapicontext && steamapicontext->SteamUtils() )
|
||
|
// {
|
||
|
// m_flGetNewsTime = 0.0f;
|
||
|
//
|
||
|
// // get the latest news
|
||
|
// CGCClientJobGetNews *pJob = new CGCClientJobGetNews( GCClientSystem()->GetGCClient(), (int) engine->GetAppID() );
|
||
|
// pJob->StartJob( NULL );
|
||
|
// }
|
||
|
|
||
|
PingThink();
|
||
|
|
||
|
// Check if it's time to send a party update message
|
||
|
if ( Plat_FloatTime() > m_flSendPartyUpdateMessageTime )
|
||
|
{
|
||
|
m_flSendPartyUpdateMessageTime = FLT_MAX;
|
||
|
|
||
|
Assert( m_pPendingCreateOrUpdatePartyMsg );
|
||
|
if ( m_pPendingCreateOrUpdatePartyMsg )
|
||
|
{
|
||
|
// Send the message
|
||
|
m_pPendingCreateOrUpdatePartyMsg->StartJob( NULL );
|
||
|
m_pPendingCreateOrUpdatePartyMsg = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
CTFParty *pParty = GetParty();
|
||
|
CTFGSLobby *pLobby = GetLobby();
|
||
|
|
||
|
// Are we in a active lobby?
|
||
|
bool bInLiveMatch = BConnectedToMatchServer( true );
|
||
|
// If we do, are we actively connected to said match?
|
||
|
bool bHaveLiveMatch = BHaveLiveMatch();
|
||
|
bool bNewServerAssignment = m_bServerAssignmentChanged;
|
||
|
m_bServerAssignmentChanged = false;
|
||
|
|
||
|
Assert( !bInLiveMatch || m_steamIDCurrentServer.IsValid() );
|
||
|
|
||
|
|
||
|
if ( bInLiveMatch )
|
||
|
{
|
||
|
// We cannot assume cannot assume the gc will tell of us the match ending -- the GC connection is fallible, and
|
||
|
// the gameserver is authoritative once we're assigned (as long as the GC doesn't revoke said assignment, see
|
||
|
// SOChanged)
|
||
|
CTFGameRules *pTFGameRules = TFGameRules();
|
||
|
// If we're not loaded enough to look at gamerules, assume the match is live until we reach that state.
|
||
|
//
|
||
|
// - Because source engine, we can get a stale TFGameRules from our *last* game *after* starting a new
|
||
|
// connection. Only even think about asking once our connect state hits connected (keyed to server_spawn)
|
||
|
if ( m_eConnectState == eConnectState_ConnectedToMatchmade &&
|
||
|
engine->IsInGame() &&
|
||
|
pTFGameRules && pTFGameRules->RecievedBaseline() && pTFGameRules->IsManagedMatchEnded() )
|
||
|
{
|
||
|
// We no longer consider this our assigned match. Only the GC can change the GCAssignedMatch, this bool is
|
||
|
// our "but we reject this". SOChanged will clear it if a new assignment overrides things.
|
||
|
GCMatchmakingDebugSpew( 1, "GS marked assigned match as ended\n" );
|
||
|
m_bAssignedMatchEnded = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !bHaveLiveMatch )
|
||
|
{
|
||
|
// Are we waiting to activate the lobby UI until a certain party appears?
|
||
|
if ( m_nPendingAutoJoinPartyID != 0 && pParty != NULL && pParty->GetGroupID() == m_nPendingAutoJoinPartyID )
|
||
|
{
|
||
|
Msg( "New party was instanced that GC told us to expect. Entering matchmaking lobby UI\n" );
|
||
|
engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby ladder" );
|
||
|
BeginMatchmaking( pParty->GetMatchmakingMode() );
|
||
|
}
|
||
|
|
||
|
/// XXX(JohnS): Right now invites just wont work if you're in a match, needs better flow.
|
||
|
|
||
|
// Do we have a pending invite we need to process?
|
||
|
if ( m_eAcceptInviteStep == eAcceptInviteStep_ReadyToJoinSteamLobby )
|
||
|
{
|
||
|
|
||
|
// Wait for everything else to go away, if we have anything
|
||
|
if ( !IsConnectStateDisconnected() )
|
||
|
{
|
||
|
//Msg( "Disconnecting from current server to accept invite\n" );
|
||
|
engine->ClientCmd_Unrestricted( "disconnect" );
|
||
|
}
|
||
|
else if ( m_bUserWantsToBeInMatchmaking )
|
||
|
{
|
||
|
EndMatchmaking();
|
||
|
}
|
||
|
else if ( pLobby == NULL && GetParty() == NULL )
|
||
|
{
|
||
|
Assert( m_steamIDLobbyInviteAccepted.IsValid() );
|
||
|
m_eAcceptInviteStep = eAcceptInviteStep_JoinSteamLobby;
|
||
|
|
||
|
// OK, start joining the lobby.
|
||
|
Msg( "Joining lobby %s\n", m_steamIDLobbyInviteAccepted.Render() );
|
||
|
steamapicontext->SteamMatchmaking()->JoinLobby( m_steamIDLobbyInviteAccepted );
|
||
|
m_steamIDLobbyInviteAccepted = CSteamID();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( bHaveLiveMatch )
|
||
|
{
|
||
|
if ( m_eConnectState != eConnectState_Disconnected && engine->IsInGame() )
|
||
|
{
|
||
|
// The dashboard will handle this automatically
|
||
|
}
|
||
|
else if ( m_bUserWantsToBeInMatchmaking && bNewServerAssignment )
|
||
|
{
|
||
|
// Use the autojoin if in the MM flow and this is a fresh match
|
||
|
m_AutoJoinHandler.MatchFound();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Use the prompt
|
||
|
m_PromptJoinHandler.MatchFound();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FOR_EACH_VEC_BACK( m_vecDelayedLocalPlayerSOListenersToAdd, i )
|
||
|
{
|
||
|
SubscribeToLocalPlayerSOCache( m_vecDelayedLocalPlayerSOListenersToAdd[ i ] );
|
||
|
m_vecDelayedLocalPlayerSOListenersToAdd.Remove( i );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent )
|
||
|
{
|
||
|
if ( steamIDOwner == ClientSteamContext().GetLocalPlayerSteamID() )
|
||
|
{
|
||
|
// Assert( m_pSOCache == NULL ); // we *can* get two SOCacheSubscribed calls in a row.
|
||
|
m_pSOCache = GCClientSystem()->GetSOCache( steamIDOwner );
|
||
|
Assert( m_pSOCache != NULL );
|
||
|
|
||
|
if ( gameeventmanager )
|
||
|
{
|
||
|
|
||
|
// force a party/lobby update whenever our SO cache arrives
|
||
|
FireGameEventPartyUpdated();
|
||
|
FireGameEventLobbyUpdated();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::FireGameEventPartyUpdated()
|
||
|
{
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "party_updated" );
|
||
|
if ( event )
|
||
|
{
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::FireGameEventLobbyUpdated()
|
||
|
{
|
||
|
IGameEvent *event2 = gameeventmanager->CreateEvent( "lobby_updated" );
|
||
|
if ( event2 )
|
||
|
{
|
||
|
gameeventmanager->FireEventClientSide( event2 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) { SOChanged( pObject, SOChanged_Create, eEvent ); }
|
||
|
void CTFGCClientSystem::SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) { SOChanged( pObject, SOChanged_Update, eEvent ); }
|
||
|
void CTFGCClientSystem::SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) { SOChanged( pObject, SOChanged_Destroy, eEvent ); }
|
||
|
|
||
|
void CTFGCClientSystem::SOChanged( const GCSDK::CSharedObject *pObject, SOChangeType_t changeType, GCSDK::ESOCacheEvent eEvent )
|
||
|
{
|
||
|
// Broadcasts
|
||
|
if ( pObject->GetTypeID() == CTFParty::k_nTypeID )
|
||
|
{
|
||
|
#if GCMATCHMAKING_DEBUG_LEVEL > 0
|
||
|
switch ( changeType )
|
||
|
{
|
||
|
case SOChanged_Create: GCMatchmakingDebugSpew( 1, "Party created\n"); break;
|
||
|
case SOChanged_Update: GCMatchmakingDebugSpew( 2, "Party updated\n"); break;
|
||
|
case SOChanged_Destroy: GCMatchmakingDebugSpew( 1, "Party destroyed\n"); break;
|
||
|
default: AssertMsg1( false, "Bogus change type %d", changeType );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
CTFParty *pParty = GetParty();
|
||
|
if ( changeType == SOChanged_Destroy )
|
||
|
{
|
||
|
Assert( pParty == NULL );
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
else if ( pParty != NULL ) // FIXME we're restarting the game after a crash, rejoining a match, and have no wizard step (or other BeginMatchmaking()) setup, so when we return to UI it's state is running but fucked
|
||
|
{
|
||
|
if ( pParty->BOffline() )
|
||
|
{
|
||
|
pParty->SetOffline( false );
|
||
|
// The user says they dont want to be in matchmaking, but the party coming in says that its
|
||
|
// searching or hanging out in the UI, which is not what we're doing. This can happen in
|
||
|
// the following circumstances:
|
||
|
// 1) With the GC up, start searching for a match
|
||
|
// 2) Crash the GC
|
||
|
// 3) Go back to the main menu
|
||
|
// 4) Reboot the GC
|
||
|
if ( pParty->GetState() == CSOTFParty_State_UI || pParty->GetState() == CSOTFParty_State_FINDING_MATCH )
|
||
|
{
|
||
|
if ( !m_bUserWantsToBeInMatchmaking )
|
||
|
{
|
||
|
Msg( "Party was updated/created, but our party is marked offline, we don't want to be matchmaking, and the party is not in a match. Ending matchmaking\n" );
|
||
|
EndMatchmaking();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Msg( "Party was updated/created, and our party is marked offline, and the party is not in a match. Sending update to GC with our predicted changes\n" );
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->set_wizard_step( m_eLocalWizardStep );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bool bShouldGoToMMUI = false;
|
||
|
|
||
|
// We'll hit this when start the game back up after a crash
|
||
|
if ( !m_bUserWantsToBeInMatchmaking && ( eEvent == eSOCacheEvent_Subscribed || eEvent == eSOCacheEvent_ListenerAdded ) && m_eAcceptInviteStep != eAcceptInviteStep_JoinParty )
|
||
|
{
|
||
|
switch( pParty->GetState() )
|
||
|
{
|
||
|
case CSOTFParty_State_UI:
|
||
|
case CSOTFParty_State_FINDING_MATCH:
|
||
|
// They backed out of the MM UI somehow, and are getting party updates. We want out.
|
||
|
if ( pParty->GetNumMembers() <= 1 )
|
||
|
{
|
||
|
|
||
|
Msg( "Creating a party when we don't want to be in matchmaking, and we're the only ones in it. Possibly and old party from an old session. Ending matchmaking.\n" );
|
||
|
EndMatchmaking();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Msg( "Creating a party when we don't want to be in matchmaking, and it has other players in it. Possibly and old party from an old session. Going to MM UI.\n" );
|
||
|
bShouldGoToMMUI = true;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CSOTFParty_State_IN_MATCH:
|
||
|
case CSOTFParty_State_AWAITING_RESERVATION_CONFIRMATION:
|
||
|
// We don't have a match, but we're still in a party. Leave matchmaking.
|
||
|
// TODO: Once the lobby panels are no longer a nightmare, let this happen.
|
||
|
// We dont really want to destroy their party, but it's too much of
|
||
|
// a hassle to support now.
|
||
|
if ( !BHaveLiveMatch() )
|
||
|
{
|
||
|
Msg( "Creating a party when we don't want to be in matchmaking, and it has other players in it, and it's live. Leaving matchmaking\n" );
|
||
|
EndMatchmaking();
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
AssertMsg1( false, "Unhandled party state %d", pParty->GetState() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( bShouldGoToMMUI )
|
||
|
{
|
||
|
if ( IsLadderGroup( pParty->GetMatchGroup() ) )
|
||
|
{
|
||
|
engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby ladder" );
|
||
|
}
|
||
|
else if ( IsCasualGroup( pParty->GetMatchGroup() ) )
|
||
|
{
|
||
|
engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby casual" );
|
||
|
}
|
||
|
else if ( IsMvMMatchGroup( pParty->GetMatchGroup() ) )
|
||
|
{
|
||
|
engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby mvm" );
|
||
|
}
|
||
|
|
||
|
BeginMatchmaking( pParty->GetMatchmakingMode() );
|
||
|
}
|
||
|
|
||
|
// Check if a party was instanced on us as a process of accepting an invite
|
||
|
if ( m_eAcceptInviteStep == eAcceptInviteStep_JoinParty )
|
||
|
{
|
||
|
m_eAcceptInviteStep = eAcceptInviteStep_None;
|
||
|
Msg( "Party was instanced as a result of accepting invite. Entering matchmaking lobby UI\n" );
|
||
|
engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby invited" );
|
||
|
BeginMatchmaking( pParty->GetMatchmakingMode() );
|
||
|
}
|
||
|
|
||
|
//m_msgLocalSearchCriteria.set_key( pParty->Obj().search_key() );
|
||
|
m_msgLocalSearchCriteria.set_late_join_ok( pParty->Obj().search_late_join_ok() );
|
||
|
//m_msgLocalSearchCriteria.set_matchgroups( pParty->Obj().matchgroups() );
|
||
|
m_msgLocalSearchCriteria.set_matchmaking_mode( pParty->GetMatchmakingMode() );
|
||
|
m_msgLocalSearchCriteria.set_quickplay_game_type( pParty->GetSearchQuickplayGameType() );
|
||
|
m_msgLocalSearchCriteria.clear_mvm_missions();
|
||
|
#ifdef USE_MVM_TOUR
|
||
|
m_msgLocalSearchCriteria.clear_mvm_mannup_tour();
|
||
|
#endif // USE_MVM_TOUR
|
||
|
if ( pParty->GetMatchmakingMode() == TF_Matchmaking_MVM )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.mutable_mvm_missions()->MergeFrom( pParty->Obj().search_mvm_missions() );
|
||
|
#ifdef USE_MVM_TOUR
|
||
|
if ( pParty->GetSearchPlayForBraggingRights() )
|
||
|
m_msgLocalSearchCriteria.set_mvm_mannup_tour( pParty->GetSearchMannUpTourName() );
|
||
|
#endif // USE_MVM_TOUR
|
||
|
}
|
||
|
else if ( pParty->GetMatchmakingMode() == TF_Matchmaking_LADDER )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.set_ladder_game_type( pParty->Obj().search_ladder_game_type() );
|
||
|
}
|
||
|
else if ( pParty->GetMatchmakingMode() == TF_Matchmaking_CASUAL )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.mutable_casual_criteria()->CopyFrom( pParty->Obj().search_casual() );
|
||
|
}
|
||
|
m_bLocalSquadSurplus = false;
|
||
|
int iLocalMemberIdx = pParty->GetMemberIndexBySteamID( steamapicontext->SteamUser()->GetSteamID() );
|
||
|
if ( iLocalMemberIdx >= 0 )
|
||
|
{
|
||
|
m_bLocalSquadSurplus = pParty->Obj().members( iLocalMemberIdx ).squad_surplus();
|
||
|
}
|
||
|
Assert( pParty->Obj().has_wizard_step() );
|
||
|
if ( pParty->Obj().has_wizard_step() )
|
||
|
{
|
||
|
// If entering or leaving the searching state, clear searching stats
|
||
|
if ( m_eLocalWizardStep != TF_Matchmaking_WizardStep_SEARCHING || pParty->Obj().wizard_step() != TF_Matchmaking_WizardStep_SEARCHING )
|
||
|
{
|
||
|
m_msgMatchmakingProgress.Clear();
|
||
|
}
|
||
|
|
||
|
// Get on the same page as the GC. But if we have a pending request to change the current step,
|
||
|
// then wait for that to finish. Otherwise the current step could flicker back and forth.
|
||
|
if ( s_nNumWizardStepChangesWaitingForReply == 0 )
|
||
|
{
|
||
|
m_eLocalWizardStep = pParty->Obj().wizard_step();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Assert( pParty != NULL );
|
||
|
}
|
||
|
|
||
|
FireGameEventPartyUpdated();
|
||
|
|
||
|
CheckAssociatePartyAndSteamLobby();
|
||
|
|
||
|
// Check if we're ready to active the Steam overlay to invite a user
|
||
|
CheckReadyToActivateInvite();
|
||
|
}
|
||
|
else if ( pObject->GetTypeID() == CTFGSLobby::k_nTypeID )
|
||
|
{
|
||
|
#if GCMATCHMAKING_DEBUG_LEVEL > 0
|
||
|
switch ( changeType )
|
||
|
{
|
||
|
case SOChanged_Create: GCMatchmakingDebugSpew( 1, "Lobby created\n"); break;
|
||
|
case SOChanged_Update: GCMatchmakingDebugSpew( 2, "Lobby updated\n"); break;
|
||
|
case SOChanged_Destroy: GCMatchmakingDebugSpew( 1, "Lobby destroyed\n"); break;
|
||
|
default: AssertMsg1( false, "Bogus change type %d", changeType );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
CTFGSLobby *pLobby = GetLobby();
|
||
|
|
||
|
CSteamID currentServer;
|
||
|
if ( pLobby && pLobby->GetState() == CSOTFGameServerLobby_State_RUN )
|
||
|
{
|
||
|
currentServer = pLobby->GetServerID();
|
||
|
}
|
||
|
|
||
|
// We cannot take the Lobby being deleted as a server assignment change, since the GC could crash and fail to
|
||
|
// recover lobbies. Or, since lobbies are lazy-loaded from memcache, it may come back up and lazily put us back
|
||
|
// into our lobby.
|
||
|
//
|
||
|
// However, since we have no way to ask the gameserver without being connected to it, we'll treat
|
||
|
// lobby-destroyed as canon only if we're not connected to the match. Otherwise, we'll assume the gameserver's
|
||
|
// m_bAssignedMatchEnded flag is the authority.
|
||
|
//
|
||
|
// Thus, this line reads:
|
||
|
// - If we got a *new and differing* lobby, propagate it to the m_*Assigned* convars.
|
||
|
// - If our lobby *went away*, clear these convars IF:
|
||
|
// - We're not connected to a match server
|
||
|
// - OR the gameserver concurs that the match is over
|
||
|
bool bLobbyChanged = ( currentServer != m_steamIDGCAssignedMatch ) ||
|
||
|
( pLobby && pLobby->GetMatchID() != m_uAssignedMatchID ) ;
|
||
|
if ( bLobbyChanged && ( !BConnectedToMatchServer( true ) || pLobby || m_bAssignedMatchEnded ) )
|
||
|
{
|
||
|
Msg( "Lobby received with a differing steamID. Lobby's: %s CurrentlyAssigned: %s ConnectedToMatchServer: %d HasLobby: %d AssignedMatchEnded: %d\n"
|
||
|
, currentServer.Render()
|
||
|
, m_steamIDGCAssignedMatch.Render()
|
||
|
, BConnectedToMatchServer( true )
|
||
|
, pLobby != NULL
|
||
|
, m_bAssignedMatchEnded );
|
||
|
|
||
|
m_bServerAssignmentChanged = true;
|
||
|
m_steamIDGCAssignedMatch = currentServer;
|
||
|
m_bAssignedMatchEnded = pLobby ? false : m_bAssignedMatchEnded; // If the lobby is still here, we know the match isn't over.
|
||
|
m_uAssignedMatchID = pLobby ? pLobby->GetMatchID() : 0;
|
||
|
m_eAssignedMatchGroup = pLobby ? pLobby->GetMatchGroup() : k_nMatchGroup_Invalid;
|
||
|
// Store match connection history for generic server browser/connection code to reason about which of our
|
||
|
// connections was match related.
|
||
|
netadr_t connectAdr; // but y is string
|
||
|
if ( pLobby && connectAdr.SetFromString( pLobby->GetConnect() ) )
|
||
|
{
|
||
|
m_vecMatchServerHistory.AddToTail( connectAdr );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//CTFParty *pParty = GetParty();
|
||
|
|
||
|
|
||
|
// Lobby is gone, but we're connected to our match server still.
|
||
|
/*if ( pParty && !pLobby && BConnectedToMatchServer( false ) )
|
||
|
{
|
||
|
const IMatchGroupDescription* pDesc = GetMatchGroupDescription( pParty->GetMatchGroup() );
|
||
|
|
||
|
if ( pDesc && pDesc->BShouldAutomaticallyRequeueOnMatchEnd() )
|
||
|
{
|
||
|
SendCreateOrUpdatePartyMsg( TF_Matchmaking_WizardStep_SEARCHING );
|
||
|
}
|
||
|
}*/
|
||
|
|
||
|
FireGameEventLobbyUpdated();
|
||
|
}
|
||
|
// Notifications. Sync/add/delete with what's in our notification drawer
|
||
|
else if ( pObject->GetTypeID() == CTFNotification::k_nTypeID )
|
||
|
{
|
||
|
const CTFNotification* pSONotification = ( const CTFNotification* )( pObject );
|
||
|
Msg( "Notification %llu %s: \"%s\"\n",
|
||
|
pSONotification->Obj().notification_id(),
|
||
|
changeType == SOChanged_Create ? "created" : changeType == SOChanged_Destroy ? "destroyed" : "updated",
|
||
|
pSONotification->Obj().notification_string().c_str() );
|
||
|
|
||
|
// Update existing notification if found
|
||
|
bool bFound = false;
|
||
|
for ( int i = NotificationQueue_GetNumNotifications() - 1; i >= 0; --i )
|
||
|
{
|
||
|
CClientNotification *pNotif = dynamic_cast<CClientNotification *>(NotificationQueue_GetByIndex( i ));
|
||
|
if ( pNotif && pNotif->NotificationID() == pSONotification->Obj().notification_id() )
|
||
|
{
|
||
|
Msg( "Notification %llu already displayed, updating\n",
|
||
|
pSONotification->Obj().notification_id() );
|
||
|
bFound = true;
|
||
|
if ( changeType == SOChanged_Destroy )
|
||
|
{
|
||
|
NotificationQueue_Remove( pNotif );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pNotif->Update( pSONotification );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add them to our notifications drawer if not
|
||
|
if ( !bFound && changeType != SOChanged_Destroy )
|
||
|
{
|
||
|
Msg( "New notification %llu arrived: \"%s\"\n",
|
||
|
pSONotification->Obj().notification_id(),
|
||
|
pSONotification->Obj().notification_string().c_str() );
|
||
|
CClientNotification *pClientNotification = new CClientNotification();
|
||
|
pClientNotification->Update( pSONotification );
|
||
|
NotificationQueue_Add( pClientNotification );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// // After here we only care about create or change events
|
||
|
// if ( changeType == SOChanged_Destroy )
|
||
|
// {
|
||
|
// return;
|
||
|
// }
|
||
|
//
|
||
|
// if( pObject->GetTypeID() == CTFGameAccountClient::k_nTypeID )
|
||
|
// {
|
||
|
// CTFGameAccountClient *pAccount = (CTFGameAccountClient *)pObject;
|
||
|
// m_unWinCount = pAccount->GetWins();
|
||
|
// m_unLossCount = pAccount->GetLosses();
|
||
|
// }
|
||
|
// else if ( pObject->GetTypeID() == CTFHeroStandings::k_nTypeID )
|
||
|
// {
|
||
|
// CTFHeroStandings *pHeroStandings = (CTFHeroStandings *)pObject;
|
||
|
// // see if we have an entry for this already
|
||
|
// int nFoundIndex = -1;
|
||
|
// for ( int i = 0; i < m_aHeroRecords.Count(); i++ )
|
||
|
// {
|
||
|
// if ( m_aHeroRecords[i].m_unHeroID == pHeroStandings->GetHeroID() )
|
||
|
// {
|
||
|
// nFoundIndex = i;
|
||
|
// break;
|
||
|
// }
|
||
|
// }
|
||
|
// if ( nFoundIndex == -1 )
|
||
|
// {
|
||
|
// GCHeroRecord_t newHeroStanding;
|
||
|
// nFoundIndex = m_aHeroRecords.InsertNoSort( newHeroStanding );
|
||
|
// }
|
||
|
//
|
||
|
// m_aHeroRecords[ nFoundIndex ].m_unHeroID = pHeroStandings->GetHeroID();
|
||
|
// m_aHeroRecords[ nFoundIndex ].m_unWinCount = pHeroStandings->GetWins();
|
||
|
// m_aHeroRecords[ nFoundIndex ].m_unLossCount = pHeroStandings->GetLosses();
|
||
|
//
|
||
|
// m_aHeroRecords.RedoSort();
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
//KeyValues *CTFGCClientSystem::GetNewsStory( uint64 unNewsID )
|
||
|
//{
|
||
|
// if ( !m_pNewsKeys )
|
||
|
// return NULL;
|
||
|
//
|
||
|
// for ( KeyValues *pItems = m_pNewsKeys->GetFirstSubKey(); pItems; pItems = pItems->GetNextKey() )
|
||
|
// {
|
||
|
// if ( !Q_stricmp( pItems->GetName(), "newsitems" ) )
|
||
|
// {
|
||
|
// for ( KeyValues *pItem = pItems->GetFirstSubKey(); pItem; pItem = pItem->GetNextKey() )
|
||
|
// {
|
||
|
// if ( !Q_stricmp( pItem->GetName(), "newsitem" ) )
|
||
|
// {
|
||
|
// for ( KeyValues *pStory = pItem->GetFirstSubKey(); pStory; pStory = pStory->GetNextKey() )
|
||
|
// {
|
||
|
// if ( pStory->GetUint64( "gid" ) == unNewsID )
|
||
|
// {
|
||
|
// return pStory;
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
// return NULL;
|
||
|
//}
|
||
|
//
|
||
|
//KeyValues *CTFGCClientSystem::GetNewsStoryByIndex( int nNewsIndex )
|
||
|
//{
|
||
|
// if ( !m_pNewsKeys )
|
||
|
// return NULL;
|
||
|
//
|
||
|
// int nCount = 0;
|
||
|
// for ( KeyValues *pItems = m_pNewsKeys->GetFirstSubKey(); pItems; pItems = pItems->GetNextKey() )
|
||
|
// {
|
||
|
// if ( !Q_stricmp( pItems->GetName(), "newsitems" ) )
|
||
|
// {
|
||
|
// for ( KeyValues *pItem = pItems->GetFirstSubKey(); pItem; pItem = pItem->GetNextKey() )
|
||
|
// {
|
||
|
// if ( !Q_stricmp( pItem->GetName(), "newsitem" ) )
|
||
|
// {
|
||
|
// for ( KeyValues *pStory = pItem->GetFirstSubKey(); pStory; pStory = pStory->GetNextKey() )
|
||
|
// {
|
||
|
// if ( nCount >= nNewsIndex )
|
||
|
// {
|
||
|
// return pStory;
|
||
|
// }
|
||
|
// nCount++;
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
// return NULL;
|
||
|
//}
|
||
|
|
||
|
void CTFGCClientSystem::DumpInvites()
|
||
|
{
|
||
|
if ( !m_pSOCache )
|
||
|
{
|
||
|
Msg( "No SO cache.\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindBaseTypeCache( CTFPartyInvite::k_nTypeID );
|
||
|
if ( !pTypeCache )
|
||
|
{
|
||
|
Msg( "No invites typecache.\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Msg( "Listing invites in typecache:\n" );
|
||
|
for ( uint32 i = 0; i < pTypeCache->GetCount(); i++ )
|
||
|
{
|
||
|
CTFPartyInvite *pInvite = static_cast<CTFPartyInvite*>( pTypeCache->GetObject( i ) );
|
||
|
Msg( "[%u] PartyID = %llu Sender = %s %s\n", i, pInvite->GetGroupID(), pInvite->GetSenderID().Render(), pInvite->GetSenderName() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::DumpPing()
|
||
|
{
|
||
|
// RTime32 m_rtLastPingFix;
|
||
|
// bool m_bPendingPingRefresh;
|
||
|
// bool m_bSentInitialPingFix;
|
||
|
if ( !m_rtLastPingFix )
|
||
|
{
|
||
|
TFPingMsg( "No current ping data. Pending refresh: %i, Sent initial fix: %i\n",
|
||
|
m_bPendingPingRefresh, m_bSentInitialPingFix );
|
||
|
return;
|
||
|
}
|
||
|
char szLastFix[ k_RTimeRenderBufferSize ] = { 0 };
|
||
|
CRTime::Render( m_rtLastPingFix, szLastFix );
|
||
|
|
||
|
TFPingMsg( "Ping data is current as of %s. Pending refresh: %i, Sent initial fix: %i\n",
|
||
|
szLastFix, m_bPendingPingRefresh, m_bSentInitialPingFix );
|
||
|
for ( int i = 0; i < m_msgCachedPingUpdate.pingdata_size(); i++ )
|
||
|
{
|
||
|
Msg( " %5s: %dms, status %i\n",
|
||
|
m_msgCachedPingUpdate.pingdata( i ).name().c_str(),
|
||
|
m_msgCachedPingUpdate.pingdata( i ).ping(),
|
||
|
m_msgCachedPingUpdate.pingdata( i ).ping_status() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//CTFGameAccountClient* CTFGCClientSystem::GetGameAccountClient()
|
||
|
//{
|
||
|
// if ( !m_pSOCache )
|
||
|
// return NULL;
|
||
|
//
|
||
|
// CSharedObjectTypeCache *pTypeCache = m_pSOCache->GetBaseTypeCache( CTFGameAccountClient::k_nTypeID );
|
||
|
// if ( pTypeCache && pTypeCache->GetCount() > 0 )
|
||
|
// {
|
||
|
// AssertMsg1( pTypeCache->GetCount() == 1, "Client has %d CTFGameAccountClient objects in his cache! He should only have 1.", pTypeCache->GetCount() );
|
||
|
// CTFGameAccountClient *pObject = dynamic_cast<CTFGameAccountClient*>( pTypeCache->GetObject( pTypeCache->GetCount() - 1 ) );
|
||
|
// return pObject;
|
||
|
// }
|
||
|
// return NULL;
|
||
|
//}
|
||
|
//
|
||
|
//void CTFGCClientSystem::DumpGameAccountClient()
|
||
|
//{
|
||
|
// CTFGameAccountClient *pObj = GetGameAccountClient();
|
||
|
// if ( !pObj )
|
||
|
// {
|
||
|
// Msg( "Failed to find CTFGameAccountClient shared object\n" );
|
||
|
// return;
|
||
|
// }
|
||
|
//
|
||
|
// Msg( "CTFGameAccountClient:\n" );
|
||
|
// pObj->Dump();
|
||
|
//}
|
||
|
|
||
|
//CTFBetaParticipation* CTFGCClientSystem::GetBetaParticipation()
|
||
|
//{
|
||
|
// if ( !m_pSOCache )
|
||
|
// return NULL;
|
||
|
//
|
||
|
// CSharedObjectTypeCache *pTypeCache = m_pSOCache->GetBaseTypeCache( CTFBetaParticipation::k_nTypeID );
|
||
|
// if ( pTypeCache && pTypeCache->GetCount() > 0 )
|
||
|
// {
|
||
|
// AssertMsg1( pTypeCache->GetCount() == 1, "Client has %d CTFBetaParticipation objects in his cache! He should only have 1.", pTypeCache->GetCount() );
|
||
|
// CTFBetaParticipation *pObject = dynamic_cast<CTFBetaParticipation*>( pTypeCache->GetObject( pTypeCache->GetCount() - 1 ) );
|
||
|
// return pObject;
|
||
|
// }
|
||
|
// return NULL;
|
||
|
//}
|
||
|
//
|
||
|
//void CTFGCClientSystem::DumpBetaParticipation()
|
||
|
//{
|
||
|
// CTFBetaParticipation *pObj = GetBetaParticipation();
|
||
|
// if ( !pObj )
|
||
|
// {
|
||
|
// Msg( "Failed to find beta participation shared object\n" );
|
||
|
// return;
|
||
|
// }
|
||
|
//
|
||
|
// Msg( "Beta participation:\n" );
|
||
|
// pObj->Dump();
|
||
|
//}
|
||
|
|
||
|
void CTFGCClientSystem::DumpParty()
|
||
|
{
|
||
|
CTFParty *pParty = GetParty();
|
||
|
if ( !pParty )
|
||
|
{
|
||
|
Msg( "Failed to find party shared object\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
pParty->SpewDebug();
|
||
|
}
|
||
|
|
||
|
CTFParty* CTFGCClientSystem::GetParty()
|
||
|
{
|
||
|
if ( !m_pSOCache )
|
||
|
return NULL;
|
||
|
|
||
|
CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindBaseTypeCache( CTFParty::k_nTypeID );
|
||
|
if ( pTypeCache && pTypeCache->GetCount() > 0 )
|
||
|
{
|
||
|
AssertMsg1( pTypeCache->GetCount() == 1, "Client has %d party objects in his cache! He should only have 1.", pTypeCache->GetCount() );
|
||
|
return static_cast<CTFParty*>( pTypeCache->GetObject( pTypeCache->GetCount() - 1 ) );
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CTFGCClientSystem::CreateNewParty()
|
||
|
{
|
||
|
Assert( GetParty() == NULL );
|
||
|
if ( GetParty() )
|
||
|
return;
|
||
|
|
||
|
switch( GetSearchMode() )
|
||
|
{
|
||
|
case TF_Matchmaking_LADDER:
|
||
|
RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER );
|
||
|
break;
|
||
|
|
||
|
case TF_Matchmaking_CASUAL:
|
||
|
RequestSelectWizardStep( TF_Matchmaking_WizardStep_CASUAL );
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// Unhandled for now.
|
||
|
// TODO: When GetSearchMode() goes away and we just deal with match groups
|
||
|
// fixup all these damn switches everywhere
|
||
|
Assert( false );
|
||
|
break;
|
||
|
};
|
||
|
|
||
|
// Get the party created. This will get our search criteria set. It will
|
||
|
// be the criteria of whatever our previous party was. I *think* this is the
|
||
|
// most intuitive thing to do, but we can instead use whatever the local guy's
|
||
|
// preferred criteria if this feels weird.
|
||
|
SendCreateOrUpdatePartyMsg( m_eLocalWizardStep );
|
||
|
}
|
||
|
|
||
|
CTFGSLobby* CTFGCClientSystem::GetLobby()
|
||
|
{
|
||
|
if ( !m_pSOCache )
|
||
|
return NULL;
|
||
|
|
||
|
CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindBaseTypeCache( CTFGSLobby::k_nTypeID );
|
||
|
if ( pTypeCache && pTypeCache->GetCount() > 0 )
|
||
|
{
|
||
|
AssertMsg1( pTypeCache->GetCount() == 1, "Client has %d lobby objects in his cache! He should only have 1.", pTypeCache->GetCount() );
|
||
|
CTFGSLobby *pLobby = dynamic_cast<CTFGSLobby*>( pTypeCache->GetObject( pTypeCache->GetCount() - 1 ) );
|
||
|
return pLobby;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BIsPartyLeader()
|
||
|
{
|
||
|
CTFParty *pParty = GetParty();
|
||
|
if ( pParty == NULL )
|
||
|
return true;
|
||
|
Assert( steamapicontext );
|
||
|
Assert( steamapicontext->SteamUser() );
|
||
|
if ( pParty->GetLeader() == steamapicontext->SteamUser()->GetSteamID() )
|
||
|
return true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BHasOutstandingMatchmakingPartyMessage() const
|
||
|
{
|
||
|
return m_pPendingCreateOrUpdatePartyMsg != NULL || s_nNumWizardStepChangesWaitingForReply > 0;
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::DumpLobby()
|
||
|
{
|
||
|
CTFGSLobby *pLobby = GetLobby();
|
||
|
if ( !pLobby )
|
||
|
{
|
||
|
Msg( "Failed to find lobby shared object\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
pLobby->SpewDebug();
|
||
|
}
|
||
|
|
||
|
#ifdef _DEBUG
|
||
|
static ConVar mm_debug_ignore_connect( "mm_debug_ignore_connect", "0", FCVAR_ARCHIVE, "Debug command to discard matchmaking commands to connect to server" );
|
||
|
#endif
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
#ifdef STAGING_ONLY
|
||
|
static ConVar tf_competitive_convar_restrictions_disabled( "tf_competitive_convar_restrictions_disabled", "0", FCVAR_NONE, "If set, this will disable competitive convar restrictions." );
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
bool ForceCompetitiveConvars()
|
||
|
{
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_competitive_convar_restrictions_disabled.GetBool() )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
#endif // STAGING_ONLY
|
||
|
|
||
|
bool anyFailures = false;
|
||
|
|
||
|
Assert( ThreadInMainThread() );
|
||
|
for ( ConCommandBase *ccb = g_pCVar->GetCommands(); ccb; ccb = ccb->GetNext() )
|
||
|
{
|
||
|
if ( ccb->IsCommand() )
|
||
|
continue;
|
||
|
|
||
|
ConVar *pVar = ( ConVar * ) ccb;
|
||
|
|
||
|
if ( !pVar->IsCompetitiveRestricted() )
|
||
|
continue;
|
||
|
|
||
|
// Hack: This var is created by the dxconfig system, but it doesn't actually exist.
|
||
|
// Skip it so we have no vars change when running a clean config.
|
||
|
if ( V_stricmp( pVar->GetName(), "r_decal_cullsize" ) == 0 )
|
||
|
continue;
|
||
|
|
||
|
if ( !pVar->SetCompetitiveMode( true ) )
|
||
|
anyFailures = true;
|
||
|
}
|
||
|
|
||
|
return !anyFailures;
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::ConnectToServer( const char *connect )
|
||
|
{
|
||
|
CTFGSLobby *pLobby = GetLobby();
|
||
|
Assert( pLobby );
|
||
|
if ( !pLobby )
|
||
|
return;
|
||
|
|
||
|
// !TEST! Check convar to stub connection
|
||
|
#ifdef _DEBUG
|
||
|
if ( mm_debug_ignore_connect.GetBool() )
|
||
|
{
|
||
|
Warning(" IGNORING request to connect to %s as per mm_debug_ignore_connect\n", connect );
|
||
|
return;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
Msg("Connecting to %s\n", connect );
|
||
|
CUtlString connectCmd;
|
||
|
connectCmd.Format( "connect %s matchmaking", connect );
|
||
|
if ( engine )
|
||
|
{
|
||
|
const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( m_eAssignedMatchGroup );
|
||
|
bool bAllowed = !( pMatchDesc && pMatchDesc->m_params.m_bForceClientSettings ) || ForceCompetitiveConvars();
|
||
|
if ( !bAllowed )
|
||
|
{
|
||
|
// ForceCompetitiveConvars() shouldn't fail
|
||
|
Assert( 0 );
|
||
|
}
|
||
|
|
||
|
engine->ClientCmd_Unrestricted( connectCmd.String() );
|
||
|
//vgui::surface()->PlaySound( "ui/ui_findmatch_join_01.wav" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Warning( "Failed to reconnect to game server as engine wasn't ready\n" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//void CTFGCClientSystem::StartWatchingGame( const CSteamID &gameServerSteamID )
|
||
|
//{
|
||
|
// CSteamID steamIDEmpty;
|
||
|
// StartWatchingGame( gameServerSteamID, steamIDEmpty );
|
||
|
//}
|
||
|
//
|
||
|
//void CTFGCClientSystem::StartWatchingGame( const CSteamID &gameServerSteamID, const CSteamID &watchServerSteamID )
|
||
|
//{
|
||
|
// CProtoBufMsg<CMsgWatchGame> msg( k_EMsgGCWatchGame );
|
||
|
// msg.Body().set_server_steamid( gameServerSteamID.ConvertToUint64() );
|
||
|
// msg.Body().set_watch_server_steamid( watchServerSteamID.ConvertToUint64() );
|
||
|
// msg.Body().set_client_version( engine->GetClientVersion() );
|
||
|
// GCClientSystem()->BSendMessage( msg );
|
||
|
// Msg( "StartWatchingGame request SteamID: %s, watching SteamID: %s\n", gameServerSteamID.Render(), watchServerSteamID.Render() );
|
||
|
//}
|
||
|
//
|
||
|
//void CTFGCClientSystem::CancelWatchGameRequest()
|
||
|
//{
|
||
|
// CProtoBufMsg< CMsgCancelWatchGame > msg( k_EMsgGCCancelWatchGame );
|
||
|
// GCClientSystem()->BSendMessage( msg );
|
||
|
//}
|
||
|
//
|
||
|
//void CTFGCClientSystem::StartWatchingGameResponse( const CMsgWatchGameResponse &response )
|
||
|
//{
|
||
|
// Msg( "Received CMsgWatchGameResponse result %d.\n", response.watch_game_result() );
|
||
|
//
|
||
|
// // Tell UI what is going on
|
||
|
// if ( g_pWatchGameStatus != NULL )
|
||
|
// {
|
||
|
// g_pWatchGameStatus->OnWatchGameResult( response.watch_game_result() );
|
||
|
// }
|
||
|
//
|
||
|
// if ( response.watch_game_result() != CMsgWatchGameResponse_WatchGameResult_READY )
|
||
|
// {
|
||
|
// return;
|
||
|
// }
|
||
|
//
|
||
|
// if ( tf_auto_create_proxy.GetBool() )
|
||
|
// {
|
||
|
// CreateSourceTVProxy( response.source_tv_public_addr(), response.source_tv_private_addr(), response.source_tv_port() );
|
||
|
// return;
|
||
|
// }
|
||
|
//
|
||
|
// RichPresence()->OnStartedWatchingGame( response.game_server_steamid(), response.watch_server_steamid() );
|
||
|
//
|
||
|
// netadr_t serverPublicIPAddr( response.source_tv_public_addr(), response.source_tv_port() );
|
||
|
// netadr_t serverPrivateIPAddr( response.source_tv_private_addr(), response.source_tv_port() );
|
||
|
//
|
||
|
// CUtlString connect;
|
||
|
//
|
||
|
// if ( serverPublicIPAddr.GetIP() != serverPrivateIPAddr.GetIP() )
|
||
|
// {
|
||
|
// connect.Format( "connect %s %s", serverPublicIPAddr.ToString(), serverPrivateIPAddr.ToString() );
|
||
|
// }
|
||
|
// else
|
||
|
// {
|
||
|
// connect.Format( "connect %s", serverPublicIPAddr.ToString() );
|
||
|
// }
|
||
|
//
|
||
|
// Msg( "StartWatchingGame: Sending console command: %s\n", connect.String() );
|
||
|
// engine->ClientCmd_Unrestricted( connect );
|
||
|
//}
|
||
|
//
|
||
|
|
||
|
void CTFGCClientSystem::RequestSelectWizardStep( TF_Matchmaking_WizardStep eWizardStep )
|
||
|
{
|
||
|
// We should only be calling this if we're the party leader
|
||
|
Assert( BIsPartyLeader() );
|
||
|
|
||
|
if ( BAllowMatchmakingSearch() )
|
||
|
{
|
||
|
// Make sure the wizard step makes sense for the search mode we are using
|
||
|
switch ( GetSearchMode() )
|
||
|
{
|
||
|
case TF_Matchmaking_MVM:
|
||
|
#ifdef USE_MVM_TOUR
|
||
|
Assert(
|
||
|
eWizardStep == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS
|
||
|
|| eWizardStep == TF_Matchmaking_WizardStep_MVM_TOUR_OF_DUTY
|
||
|
|| eWizardStep == TF_Matchmaking_WizardStep_MVM_CHALLENGE
|
||
|
|| eWizardStep == TF_Matchmaking_WizardStep_SEARCHING
|
||
|
);
|
||
|
#else // new mm
|
||
|
Assert(
|
||
|
eWizardStep == TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS
|
||
|
|| eWizardStep == TF_Matchmaking_WizardStep_MVM_CHALLENGE
|
||
|
|| eWizardStep == TF_Matchmaking_WizardStep_SEARCHING
|
||
|
);
|
||
|
#endif // USE_MVM_TOUR
|
||
|
break;
|
||
|
case TF_Matchmaking_LADDER:
|
||
|
Assert(
|
||
|
eWizardStep == TF_Matchmaking_WizardStep_LADDER
|
||
|
|| eWizardStep == TF_Matchmaking_WizardStep_SEARCHING );
|
||
|
break;
|
||
|
case TF_Matchmaking_CASUAL:
|
||
|
Assert( eWizardStep == TF_Matchmaking_WizardStep_CASUAL
|
||
|
|| eWizardStep == TF_Matchmaking_WizardStep_SEARCHING );
|
||
|
break;
|
||
|
default:
|
||
|
AssertMsg1( false, "Invalid matchmaking mode %d", (int)GetSearchMode() );
|
||
|
}
|
||
|
|
||
|
// If we already have a party, or we're asking to start searching, then
|
||
|
// ask the GC to set our state.
|
||
|
bool bApplyLocally = false;
|
||
|
CTFParty* pParty = GetParty();
|
||
|
if ( ( pParty != NULL ) || ( eWizardStep == TF_Matchmaking_WizardStep_SEARCHING ) )
|
||
|
{
|
||
|
SendCreateOrUpdatePartyMsg( eWizardStep );
|
||
|
bApplyLocally = ( eWizardStep != TF_Matchmaking_WizardStep_SEARCHING ) || ( pParty && pParty->BOffline() );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We're just setting local options by ourself right now,
|
||
|
// nothing exists on the GC. We can apply this change immediately locally.
|
||
|
bApplyLocally = true;
|
||
|
}
|
||
|
|
||
|
// Can we apply this change immediately?
|
||
|
if ( bApplyLocally )
|
||
|
{
|
||
|
m_eLocalWizardStep = eWizardStep;
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EMatchmakingUIState CTFGCClientSystem::GetMatchmakingUIState()
|
||
|
{
|
||
|
// User shutdown?
|
||
|
if ( !m_bUserWantsToBeInMatchmaking )
|
||
|
{
|
||
|
return eMatchmakingUIState_Inactive;
|
||
|
}
|
||
|
|
||
|
// Check if we're connected / connecting to any game server
|
||
|
switch ( m_eConnectState )
|
||
|
{
|
||
|
default:
|
||
|
AssertMsg1( false, "Unknown connect state %d", m_eConnectState );
|
||
|
case eConnectState_Disconnected: // we should have gotten a beginconnect message first, right?
|
||
|
break;
|
||
|
|
||
|
case eConnectState_ConnectingToMatchmade:
|
||
|
return eMatchmakingUIState_Connecting;
|
||
|
|
||
|
case eConnectState_ConnectedToMatchmade:
|
||
|
return eMatchmakingUIState_InGame;
|
||
|
|
||
|
case eConnectState_NonmatchmadeServer:
|
||
|
{
|
||
|
if ( BAllowMatchMakingInGame() )
|
||
|
break;
|
||
|
|
||
|
// Eh??? How did we connect to this other server without
|
||
|
// exiting matchmaking alrady?
|
||
|
Assert( !"In eConnectState_NonmatchmadeServer state, but m_bUserWantsToBeInMatchmaking=true" );
|
||
|
EndMatchmaking();
|
||
|
return eMatchmakingUIState_Inactive;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We're not connected to a server.
|
||
|
// So we should not be in a game right now, unless it's for ranked games
|
||
|
if ( !BAllowMatchMakingInGame() )
|
||
|
{
|
||
|
Assert( !engine->IsInGame() );
|
||
|
}
|
||
|
|
||
|
CTFParty *pParty = GetParty();
|
||
|
CTFGSLobby *pLobby = GetLobby();
|
||
|
|
||
|
if ( pLobby )
|
||
|
{
|
||
|
switch ( pLobby->GetState() )
|
||
|
{
|
||
|
case CSOTFGameServerLobby_State_UNKNOWN:
|
||
|
default:
|
||
|
AssertMsg1( false, "Unexpected lobby state %d", pLobby->GetState() );
|
||
|
case CSOTFGameServerLobby_State_RUN:
|
||
|
case CSOTFGameServerLobby_State_SERVERSETUP:
|
||
|
return eMatchmakingUIState_Connecting;
|
||
|
// case CSOTFGameServerLobby_State_NOTREADY:
|
||
|
// case CSOTFGameServerLobby_State_SERVERASSIGN:
|
||
|
// return eMatchmakingUIState_InQueue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Are we in a search party?
|
||
|
if ( pParty )
|
||
|
{
|
||
|
if ( pParty->GetState() == CSOTFParty_State_FINDING_MATCH )
|
||
|
{
|
||
|
return eMatchmakingUIState_InQueue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return eMatchmakingUIState_Chat;
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::AssertMakesSenseToReadSearchCriteria()
|
||
|
{
|
||
|
// EMatchmakingUIState eState = GetMatchmakingUIState();
|
||
|
// switch ( eState )
|
||
|
// {
|
||
|
// case eMatchmakingUIState_Chat:
|
||
|
// case eMatchmakingUIState_InQueue:
|
||
|
// case eMatchmakingUIState_Connecting:
|
||
|
// // They might need to update the UI during this state
|
||
|
// break;
|
||
|
//
|
||
|
// case eMatchmakingUIState_Inactive:
|
||
|
// case eMatchmakingUIState_InGame:
|
||
|
// default:
|
||
|
// // Why do you want to know?
|
||
|
// AssertMsg1( false, "Invalid matchmaking UI state %d", eState );
|
||
|
// break;
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BAllowMatchmakingSearch()
|
||
|
{
|
||
|
bool bLeavingIncursPenalty = ( GTFGCClientSystem()->GetAssignedMatchAbandonStatus() == k_EAbandonGameStatus_AbandonWithPenalty );
|
||
|
bool bAllowInGame = ( BAllowMatchMakingInGame() && !bLeavingIncursPenalty );
|
||
|
|
||
|
EMatchmakingUIState eState = GetMatchmakingUIState();
|
||
|
switch ( eState )
|
||
|
{
|
||
|
case eMatchmakingUIState_Chat:
|
||
|
case eMatchmakingUIState_InQueue:
|
||
|
// They might need to update the UI during this state
|
||
|
return true;
|
||
|
|
||
|
case eMatchmakingUIState_Inactive:
|
||
|
case eMatchmakingUIState_Connecting:
|
||
|
case eMatchmakingUIState_InGame:
|
||
|
if ( bAllowInGame )
|
||
|
return true;
|
||
|
return false;
|
||
|
default:
|
||
|
AssertMsg1( false, "Invalid matchmaking UI state %d", eState );
|
||
|
// Why do you want to know?
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TF_MatchmakingMode CTFGCClientSystem::GetSearchMode()
|
||
|
{
|
||
|
return m_msgLocalSearchCriteria.matchmaking_mode();
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::GetSearchChallenges( CMvMMissionSet &challenges )
|
||
|
{
|
||
|
challenges.Clear();
|
||
|
|
||
|
// O(n^2) goodness...
|
||
|
for ( int i = 0 ; i < m_msgLocalSearchCriteria.mvm_missions_size() ; ++i )
|
||
|
{
|
||
|
int iChallengeIndex = GetItemSchema()->FindMvmMissionByName( m_msgLocalSearchCriteria.mvm_missions( i ).c_str() );
|
||
|
if ( iChallengeIndex >= 0 )
|
||
|
challenges.SetMissionBySchemaIndex( iChallengeIndex, true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SetSearchChallenges( const CMvMMissionSet &challenges )
|
||
|
{
|
||
|
if ( BInternalSetSearchChallenges( challenges ) )
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BInternalSetSearchChallenges( const CMvMMissionSet &challenges )
|
||
|
{
|
||
|
if ( !BAllowMatchmakingSearch() )
|
||
|
return false;
|
||
|
|
||
|
if ( !BIsPartyLeader() )
|
||
|
{
|
||
|
AssertMsg( false, "Not party leader" );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// No change?
|
||
|
CMvMMissionSet currentChallenges;
|
||
|
GetSearchChallenges( currentChallenges );
|
||
|
if ( currentChallenges == challenges )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Apply the change locally
|
||
|
m_msgLocalSearchCriteria.clear_mvm_missions();
|
||
|
for ( int i = 0 ; i < GetItemSchema()->GetMvmMissions().Count() ; ++i )
|
||
|
{
|
||
|
if ( challenges.GetMissionBySchemaIndex( i ) )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.add_mvm_missions( GetItemSchema()->GetMvmMissionName( i ) );
|
||
|
}
|
||
|
}
|
||
|
if ( m_msgLocalSearchCriteria.mvm_missions_size() == 0 )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.add_mvm_missions( "invalid" );
|
||
|
}
|
||
|
|
||
|
// Check if we need to send a message
|
||
|
if ( GetParty() != NULL )
|
||
|
{
|
||
|
CMsgMatchSearchCriteria *pSearchCriteria = GetCreateOrUpdatePartyMsg()->mutable_search_criteria();
|
||
|
pSearchCriteria->clear_mvm_missions();
|
||
|
pSearchCriteria->mutable_mvm_missions()->MergeFrom( m_msgLocalSearchCriteria.mvm_missions() );
|
||
|
}
|
||
|
|
||
|
// Fire event
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::GetSearchJoinLate()
|
||
|
{
|
||
|
CTFParty *pParty = GetParty();
|
||
|
// if ( pParty == NULL || m_msgLocalSearchCriteria.has_late_join_ok() )
|
||
|
if ( pParty == NULL )
|
||
|
{
|
||
|
return m_msgLocalSearchCriteria.late_join_ok();
|
||
|
}
|
||
|
return pParty->Obj().search_late_join_ok();
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SetSearchJoinLate( bool bJoinLate )
|
||
|
{
|
||
|
if ( !BAllowMatchmakingSearch() )
|
||
|
return;
|
||
|
|
||
|
if ( !BIsPartyLeader() )
|
||
|
{
|
||
|
AssertMsg( false, "Not party leader" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( m_msgLocalSearchCriteria.late_join_ok() != bJoinLate )
|
||
|
{
|
||
|
if ( GetParty() == NULL )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.set_late_join_ok( bJoinLate );
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->mutable_search_criteria()->set_late_join_ok( bJoinLate );
|
||
|
}
|
||
|
}
|
||
|
//CheckSendAdjustSearchCriteria();
|
||
|
}
|
||
|
|
||
|
EGameCategory CTFGCClientSystem::GetQuickplayGameType()
|
||
|
{
|
||
|
return (EGameCategory)m_msgLocalSearchCriteria.quickplay_game_type();
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SetQuickplayGameType( EGameCategory type )
|
||
|
{
|
||
|
if ( !BAllowMatchmakingSearch() )
|
||
|
return;
|
||
|
|
||
|
if ( !BIsPartyLeader() )
|
||
|
{
|
||
|
AssertMsg( false, "Not party leader" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( (EGameCategory)m_msgLocalSearchCriteria.quickplay_game_type() != type )
|
||
|
{
|
||
|
if ( GetParty() == NULL )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.set_quickplay_game_type( type );
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->mutable_search_criteria()->set_quickplay_game_type( type );
|
||
|
}
|
||
|
}
|
||
|
//CheckSendAdjustSearchCriteria();
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::UpdateCustomPingTolerance()
|
||
|
{
|
||
|
bool bEnabled = ConVarRef( "tf_custom_ping_enabled" ).GetBool();
|
||
|
uint32 unValue = bEnabled ? (uint32)Max( 0, ConVarRef( "tf_custom_ping" ).GetInt() ) : 0u;
|
||
|
|
||
|
// Don't queue unnecessary messages
|
||
|
if ( m_msgLocalSearchCriteria.custom_ping_tolerance() == unValue )
|
||
|
{ return; }
|
||
|
|
||
|
if ( GetParty() && BIsPartyLeader() )
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
auto *pCriteria = pMsg->mutable_search_criteria();
|
||
|
pCriteria->set_custom_ping_tolerance( unValue );
|
||
|
}
|
||
|
|
||
|
m_msgLocalSearchCriteria.set_custom_ping_tolerance( unValue );
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SelectCasualMap( uint32 nMapDefIndex, bool bSelected )
|
||
|
{
|
||
|
CCasualCriteriaHelper casualHelper( m_msgLocalSearchCriteria.casual_criteria() );
|
||
|
casualHelper.SetMapSelected( nMapDefIndex, bSelected );
|
||
|
|
||
|
if ( casualHelper.IsValid() || !casualHelper.AnySelected() )
|
||
|
{
|
||
|
if ( GetParty() != NULL )
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->mutable_search_criteria()->mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
|
||
|
}
|
||
|
|
||
|
m_msgLocalSearchCriteria.mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
|
||
|
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::ClearCasualSearchCriteria()
|
||
|
{
|
||
|
CCasualCriteriaHelper casualHelper( m_msgLocalSearchCriteria.casual_criteria() );
|
||
|
if ( casualHelper.AnySelected() )
|
||
|
{
|
||
|
casualHelper.Clear();
|
||
|
|
||
|
if ( GetParty() != NULL )
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->mutable_search_criteria()->mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
|
||
|
}
|
||
|
|
||
|
m_msgLocalSearchCriteria.mutable_casual_criteria()->CopyFrom( casualHelper.GetCasualCriteria() );
|
||
|
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::IsCasualMapSelected( uint32 nMapDefIndex ) const
|
||
|
{
|
||
|
CCasualCriteriaHelper casualHelper( m_msgLocalSearchCriteria.casual_criteria() );
|
||
|
return casualHelper.IsMapSelected( nMapDefIndex );
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::GetLocalPlayerSquadSurplus()
|
||
|
{
|
||
|
return m_bLocalSquadSurplus;
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SetLocalPlayerSquadSurplus( bool bSquadSurplus )
|
||
|
{
|
||
|
if ( m_bLocalSquadSurplus != bSquadSurplus )
|
||
|
{
|
||
|
if ( GetParty() == NULL )
|
||
|
{
|
||
|
m_bLocalSquadSurplus = bSquadSurplus;
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->set_squad_surplus( bSquadSurplus );
|
||
|
}
|
||
|
}
|
||
|
//CheckSendAdjustSearchCriteria();
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BLocalPlayerInventoryHasMvmTicket( void )
|
||
|
{
|
||
|
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
|
||
|
if ( pLocalInv == NULL )
|
||
|
return false;
|
||
|
|
||
|
static CSchemaItemDefHandle pItemDef_MvmTicket( CTFItemSchema::k_rchMvMTicketItemDefName );
|
||
|
if ( !pItemDef_MvmTicket )
|
||
|
return false;
|
||
|
|
||
|
for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
|
||
|
{
|
||
|
CEconItemView *pItem = pLocalInv->GetItem( i );
|
||
|
Assert( pItem );
|
||
|
if ( pItem->GetItemDefinition() == pItemDef_MvmTicket )
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int CTFGCClientSystem::GetLocalPlayerInventoryMvmTicketCount( void )
|
||
|
{
|
||
|
int nCount = 0;
|
||
|
|
||
|
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
|
||
|
if ( pLocalInv )
|
||
|
{
|
||
|
static CSchemaItemDefHandle pItemDef_MvmTicket( CTFItemSchema::k_rchMvMTicketItemDefName );
|
||
|
if ( pItemDef_MvmTicket )
|
||
|
{
|
||
|
for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
|
||
|
{
|
||
|
CEconItemView *pItem = pLocalInv->GetItem( i );
|
||
|
Assert( pItem );
|
||
|
if ( pItem->GetItemDefinition() == pItemDef_MvmTicket )
|
||
|
{
|
||
|
nCount++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nCount;
|
||
|
}
|
||
|
|
||
|
uint32 CTFGCClientSystem::GetLadderType()
|
||
|
{
|
||
|
return m_msgLocalSearchCriteria.has_ladder_game_type() ? m_msgLocalSearchCriteria.ladder_game_type() : k_nMatchGroup_Invalid;
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SetLadderType( uint32 nType )
|
||
|
{
|
||
|
if ( !BIsPartyLeader() )
|
||
|
{
|
||
|
AssertMsg( false, "Not party leader" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( m_msgLocalSearchCriteria.ladder_game_type() != nType )
|
||
|
{
|
||
|
if ( !GetParty() )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.set_ladder_game_type( nType );
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->mutable_search_criteria()->set_ladder_game_type( nType );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BLocalPlayerInventoryHasSquadSurplusVoucher( void )
|
||
|
{
|
||
|
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
|
||
|
if ( pLocalInv == NULL )
|
||
|
return false;
|
||
|
|
||
|
static CSchemaItemDefHandle k_rchMvMSquadSurplusVoucherItemDefName( CTFItemSchema::k_rchMvMSquadSurplusVoucherItemDefName );
|
||
|
if ( !k_rchMvMSquadSurplusVoucherItemDefName )
|
||
|
return false;
|
||
|
|
||
|
for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
|
||
|
{
|
||
|
CEconItemView *pItem = pLocalInv->GetItem( i );
|
||
|
Assert( pItem );
|
||
|
if ( pItem->GetItemDefinition() == k_rchMvMSquadSurplusVoucherItemDefName )
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int CTFGCClientSystem::GetLocalPlayerInventorySquadSurplusVoucherCount( void )
|
||
|
{
|
||
|
int nCount = 0;
|
||
|
|
||
|
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
|
||
|
if ( pLocalInv )
|
||
|
{
|
||
|
static CSchemaItemDefHandle k_rchMvMSquadSurplusVoucherItemDefName( CTFItemSchema::k_rchMvMSquadSurplusVoucherItemDefName );
|
||
|
if ( k_rchMvMSquadSurplusVoucherItemDefName )
|
||
|
{
|
||
|
for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
|
||
|
{
|
||
|
CEconItemView *pItem = pLocalInv->GetItem( i );
|
||
|
Assert( pItem );
|
||
|
if ( pItem->GetItemDefinition() == k_rchMvMSquadSurplusVoucherItemDefName )
|
||
|
{
|
||
|
nCount++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nCount;
|
||
|
}
|
||
|
|
||
|
#ifdef USE_MVM_TOUR
|
||
|
bool CTFGCClientSystem::BGetLocalPlayerBadgeInfoForTour( int iTourIndex, uint32 *pnBadgeLevel, uint32 *pnCompletedChallenges )
|
||
|
{
|
||
|
Assert( iTourIndex >= 0 );
|
||
|
Assert( iTourIndex < GetItemSchema()->GetMvmTours().Count() );
|
||
|
Assert( pnBadgeLevel );
|
||
|
Assert( pnCompletedChallenges );
|
||
|
|
||
|
*pnBadgeLevel = 0;
|
||
|
*pnCompletedChallenges = 0;
|
||
|
|
||
|
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
|
||
|
if ( pLocalInv == NULL )
|
||
|
return false;
|
||
|
|
||
|
// We can't search for a badge without knowing which attribute to look for.
|
||
|
static CSchemaAttributeDefHandle pAttribDef_MvmChallengeCompleted( CTFItemSchema::k_rchMvMChallengeCompletedMaskAttribName );
|
||
|
Assert( pAttribDef_MvmChallengeCompleted );
|
||
|
if ( !pAttribDef_MvmChallengeCompleted )
|
||
|
return false;
|
||
|
|
||
|
if ( iTourIndex < 0 || iTourIndex >= GetItemSchema()->GetMvmTours().Count() )
|
||
|
{
|
||
|
AssertMsg1( false, "Invalid tour index %d", iTourIndex );
|
||
|
return false;
|
||
|
}
|
||
|
const CEconItemDefinition *pBadgeDef = GetItemSchema()->GetMvmTours()[iTourIndex].m_pBadgeItemDef;
|
||
|
if ( pBadgeDef == NULL )
|
||
|
{
|
||
|
Assert( pBadgeDef );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
|
||
|
{
|
||
|
CEconItemView *pBadge = pLocalInv->GetItem( i );
|
||
|
Assert( pBadge );
|
||
|
if ( pBadge->GetItemDefinition() != pBadgeDef )
|
||
|
continue;
|
||
|
|
||
|
if ( !pBadge->FindAttribute( pAttribDef_MvmChallengeCompleted, pnCompletedChallenges ) )
|
||
|
{
|
||
|
AssertMsg( false, "Badge missing challenges completed attribute?" );
|
||
|
*pnCompletedChallenges = 0;
|
||
|
}
|
||
|
|
||
|
extern uint32 GetItemDescriptionDisplayLevel( const IEconItemInterface *pEconItem );
|
||
|
*pnBadgeLevel = GetItemDescriptionDisplayLevel( pBadge );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int CTFGCClientSystem::GetSearchMannUpTourIndex()
|
||
|
{
|
||
|
CTFParty *pParty = GetParty();
|
||
|
// if ( pParty == NULL || m_msgLocalSearchCriteria.has_late_join_ok() )
|
||
|
if ( pParty == NULL )
|
||
|
{
|
||
|
if ( !m_msgLocalSearchCriteria.play_for_bragging_rights() )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.clear_mvm_mannup_tour();
|
||
|
return k_iMvmTourIndex_NotMannedUp;
|
||
|
}
|
||
|
return GetItemSchema()->FindMvmTourByName( m_msgLocalSearchCriteria.mvm_mannup_tour().c_str() );
|
||
|
}
|
||
|
return pParty->GetSearchMannUpTourIndex();
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SetSearchMannUpTourIndex( int idxTour )
|
||
|
{
|
||
|
Assert( GetSearchPlayForBraggingRights() );
|
||
|
if ( BInternalSetSearchMannUpTourIndex( idxTour ) )
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BInternalSetSearchMannUpTourIndex( int idxTour )
|
||
|
{
|
||
|
if ( !BAllowMatchmakingSearch() )
|
||
|
return false;
|
||
|
|
||
|
if ( !BIsPartyLeader() )
|
||
|
{
|
||
|
AssertMsg( false, "Not party leader" );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// No change?
|
||
|
if ( GetSearchMannUpTourIndex() == idxTour )
|
||
|
return false;
|
||
|
|
||
|
const char *pszTourName = "";
|
||
|
if ( idxTour >= 0 )
|
||
|
{
|
||
|
pszTourName = GetItemSchema()->GetMvmTours()[ idxTour ].m_sTourInternalName.Get();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Assert( idxTour == k_iMvmTourIndex_Empty );
|
||
|
}
|
||
|
|
||
|
bool bResult = false;
|
||
|
if ( GetParty() == NULL )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.set_mvm_mannup_tour( pszTourName );
|
||
|
bResult = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->mutable_search_criteria()->set_mvm_mannup_tour( pszTourName );
|
||
|
}
|
||
|
|
||
|
// Check if we need to deselect inappropriate challenges
|
||
|
if ( idxTour >= 0 )
|
||
|
{
|
||
|
CMvMMissionSet challenges;
|
||
|
GetSearchChallenges( challenges );
|
||
|
bool bChanged = false;
|
||
|
for ( int i = 0 ; i < GetItemSchema()->GetMvmMissions().Count() ; ++i )
|
||
|
{
|
||
|
if ( GetItemSchema()->FindMvmMissionInTour( idxTour, i ) < 0 )
|
||
|
{
|
||
|
if ( challenges.GetMissionBySchemaIndex( i ) )
|
||
|
{
|
||
|
challenges.SetMissionBySchemaIndex( i, false );
|
||
|
bChanged = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if ( bChanged )
|
||
|
{
|
||
|
if ( BInternalSetSearchChallenges( challenges ) )
|
||
|
bResult = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bResult;
|
||
|
}
|
||
|
#endif // USE_MVM_TOUR
|
||
|
|
||
|
bool CTFGCClientSystem::GetSearchPlayForBraggingRights()
|
||
|
{
|
||
|
CTFParty *pParty = GetParty();
|
||
|
// if ( pParty == NULL || m_msgLocalSearchCriteria.has_late_join_ok() )
|
||
|
if ( pParty == NULL )
|
||
|
{
|
||
|
return m_msgLocalSearchCriteria.play_for_bragging_rights();
|
||
|
}
|
||
|
return pParty->GetSearchPlayForBraggingRights();
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SetSearchPlayForBraggingRights( bool bPlayForBraggingRights )
|
||
|
{
|
||
|
if ( !BAllowMatchmakingSearch() )
|
||
|
return;
|
||
|
|
||
|
if ( !BIsPartyLeader() )
|
||
|
{
|
||
|
AssertMsg( false, "Not party leader" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Do we need to fire local event?
|
||
|
bool bFirePartyUpdated = false;
|
||
|
|
||
|
// Any change?
|
||
|
#ifdef USE_MVM_TOUR
|
||
|
if ( GetSearchPlayForBraggingRights() != bPlayForBraggingRights )
|
||
|
{
|
||
|
if ( GetParty() == NULL )
|
||
|
{
|
||
|
if ( m_msgLocalSearchCriteria.play_for_bragging_rights() != bPlayForBraggingRights )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.set_play_for_bragging_rights( bPlayForBraggingRights );
|
||
|
bFirePartyUpdated = true;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->mutable_search_criteria()->set_play_for_bragging_rights( bPlayForBraggingRights );
|
||
|
}
|
||
|
|
||
|
// Clear tour selection when first entering mann up
|
||
|
if ( bPlayForBraggingRights )
|
||
|
{
|
||
|
if ( BInternalSetSearchMannUpTourIndex( k_iMvmTourIndex_Empty ) )
|
||
|
bFirePartyUpdated = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check if we must deselect the non-Mann-UP challenges
|
||
|
if ( !bPlayForBraggingRights )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.clear_mvm_mannup_tour();
|
||
|
}
|
||
|
#else // new mm
|
||
|
if ( GetSearchPlayForBraggingRights() != bPlayForBraggingRights )
|
||
|
{
|
||
|
if ( GetParty() == NULL )
|
||
|
{
|
||
|
if ( m_msgLocalSearchCriteria.play_for_bragging_rights() != bPlayForBraggingRights )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.set_play_for_bragging_rights( bPlayForBraggingRights );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->mutable_search_criteria()->set_play_for_bragging_rights( bPlayForBraggingRights );
|
||
|
}
|
||
|
|
||
|
bFirePartyUpdated = true;
|
||
|
}
|
||
|
#endif // USE_MVM_TOUR
|
||
|
|
||
|
if ( bFirePartyUpdated )
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
|
||
|
//void CTFGCClientSystem::CheckSendAdjustSearchCriteria()
|
||
|
//{
|
||
|
// if ( !BMakesSenseToWriteSearchCriteria() )
|
||
|
// {
|
||
|
// AssertMsg1( false, "Invalid matchmaking UI state %d", GetMatchmakingUIState() );
|
||
|
// return;
|
||
|
// }
|
||
|
//
|
||
|
// // We only need to do this if we have a party!
|
||
|
// if ( GetParty() == NULL )
|
||
|
// {
|
||
|
// return;
|
||
|
// }
|
||
|
//
|
||
|
// if ( m_msgLocalSearchCriteria.has_map() ||
|
||
|
// m_msgLocalSearchCriteria.has_challenge() ||
|
||
|
// m_msgLocalSearchCriteria.has_late_join_ok() ||
|
||
|
// m_msgLocalSearchCriteria.has_matchgroups() )
|
||
|
// {
|
||
|
// }
|
||
|
//}
|
||
|
|
||
|
void CTFGCClientSystem::SendCreateOrUpdatePartyMsg( TF_Matchmaking_WizardStep eWizardStep )
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->set_wizard_step( eWizardStep );
|
||
|
|
||
|
// If we don't have a party yet, populate message with our search criteria
|
||
|
CTFParty *pParty = GetParty();
|
||
|
if ( pParty == NULL )
|
||
|
{
|
||
|
*pMsg->mutable_search_criteria() = m_msgLocalSearchCriteria;
|
||
|
pMsg->set_squad_surplus( m_bLocalSquadSurplus );
|
||
|
}
|
||
|
|
||
|
// Send the steam lobby, if we have one
|
||
|
if ( m_steamIDLobby.IsValid() )
|
||
|
{
|
||
|
if ( pParty == NULL || pParty->GetSteamLobbyID() != m_steamIDLobby )
|
||
|
{
|
||
|
pMsg->set_steam_lobby_id( m_steamIDLobby.ConvertToUint64() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pMsg->set_wizard_step( eWizardStep );
|
||
|
|
||
|
// This is important! Send it now, even if we have a party.
|
||
|
m_flSendPartyUpdateMessageTime = 0.f;
|
||
|
|
||
|
// static ConVarRef sv_search_key("sv_search_key");
|
||
|
// if ( sv_search_key.IsValid() && *sv_search_key.GetString() )
|
||
|
// {
|
||
|
// msg.Body().set_key( sv_search_key.GetString() );
|
||
|
// }
|
||
|
|
||
|
// static ConVarRef dota_matchgroups("dota_matchgroups");
|
||
|
// if ( dota_matchgroups.IsValid() )
|
||
|
// {
|
||
|
// // abort if no matchgroups set
|
||
|
// if ( dota_matchgroups.GetInt() == 0 )
|
||
|
// {
|
||
|
// DOTA_SF_AddErrorMessage( "#DOTA_Matchmaking_NoRegion_Error" );
|
||
|
// return;
|
||
|
// }
|
||
|
//
|
||
|
// msg.Body().set_matchgroups( dota_matchgroups.GetInt() );
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SendExitMatchmaking( bool bExplicitAbandon )
|
||
|
{
|
||
|
Msg( "Sending request to exit matchmaking system [ abandon = %d ]\n", bExplicitAbandon );
|
||
|
CProtoBufMsg<CMsgExitMatchmaking> msg( k_EMsgGCExitMatchmaking );
|
||
|
msg.Body().set_explicit_abandon( bExplicitAbandon );
|
||
|
msg.Body().set_party_id( GetParty() ? GetParty()->GetGroupID() : 0 );
|
||
|
msg.Body().set_lobby_id( GetLobby() ? GetLobby()->GetGroupID() : 0 );
|
||
|
GCClientSystem()->BSendMessage( msg );
|
||
|
|
||
|
// We're done! No more messages!
|
||
|
if ( m_pPendingCreateOrUpdatePartyMsg )
|
||
|
{
|
||
|
delete m_pPendingCreateOrUpdatePartyMsg;
|
||
|
m_pPendingCreateOrUpdatePartyMsg = NULL;
|
||
|
m_flSendPartyUpdateMessageTime = FLT_MAX;
|
||
|
s_nNumWizardStepChangesWaitingForReply = 0;
|
||
|
}
|
||
|
|
||
|
if ( bExplicitAbandon && m_steamIDGCAssignedMatch.IsValid() && !m_bAssignedMatchEnded )
|
||
|
{
|
||
|
// Consider this match over on our end, since we're not waiting for the lobby to update (the GC may even be gone)
|
||
|
GCMatchmakingDebugSpew( 1, "Sending request to exit matchmaking, marking assigned match as ended\n" );
|
||
|
m_bAssignedMatchEnded = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SaveCasualSearchCriteriaToDisk()
|
||
|
{
|
||
|
std::string strOut;
|
||
|
google::protobuf::TextFormat::PrintToString( m_msgLocalSearchCriteria.casual_criteria(), &strOut );
|
||
|
CUtlBuffer bufOut;
|
||
|
bufOut.SetBufferType( true, true );
|
||
|
bufOut.PutString( strOut.c_str() );
|
||
|
g_pFullFileSystem->WriteFile( s_pszCasualCriteriaSaveFileName, NULL, bufOut );
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::RejoinActiveMatch( void )
|
||
|
{
|
||
|
// Dialog already exists, just quit
|
||
|
if ( s_pRejoinLobbyDialog )
|
||
|
return;
|
||
|
|
||
|
if ( enginevgui == NULL || GetClientModeTFNormal()->GameUI() == NULL )
|
||
|
return;
|
||
|
|
||
|
// Check if this player is in Abandon territory, if so warn them
|
||
|
EAbandonGameStatus eAbandonStatus = GTFGCClientSystem()->GetAssignedMatchAbandonStatus();
|
||
|
const char* pszTitle = "#TF_MM_Rejoin_Title";
|
||
|
const char* pszBody = NULL;
|
||
|
const char* pszConfirm = "#TF_MM_Rejoin_Confirm";
|
||
|
const char* pszCancel = NULL;
|
||
|
|
||
|
switch ( eAbandonStatus )
|
||
|
{
|
||
|
case k_EAbandonGameStatus_Safe:
|
||
|
pszBody = "#TF_MM_Rejoin_BaseText";
|
||
|
pszCancel = "#TF_MM_Rejoin_Leave";
|
||
|
break;
|
||
|
case k_EAbandonGameStatus_AbandonWithoutPenalty:
|
||
|
pszBody = "#TF_MM_Rejoin_AbandonText_NoPenalty";
|
||
|
pszCancel = "#TF_MM_Rejoin_Abandon";
|
||
|
break;
|
||
|
case k_EAbandonGameStatus_AbandonWithPenalty:
|
||
|
pszBody = "#TF_MM_Rejoin_AbandonText";
|
||
|
pszCancel = "#TF_MM_Rejoin_Abandon";
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
s_pRejoinLobbyDialog = vgui::SETUP_PANEL( new CTFRejoinConfirmDialog(
|
||
|
pszTitle,
|
||
|
pszBody,
|
||
|
pszConfirm,
|
||
|
pszCancel,
|
||
|
&OnRejoinMvMLobbyDialogCallBack,
|
||
|
NULL
|
||
|
));
|
||
|
|
||
|
if ( s_pRejoinLobbyDialog )
|
||
|
{
|
||
|
s_pRejoinLobbyDialog->Show();
|
||
|
// VGUI is being dumb so I need to manually calculate this windows position
|
||
|
int sW, sT, dW, dT;
|
||
|
vgui::surface()->GetScreenSize( sW, sT );
|
||
|
s_pRejoinLobbyDialog->GetSize( dW, dT );
|
||
|
s_pRejoinLobbyDialog->SetPos( (sW - dW) / 2, (sT - dT) / 2 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::BeginMatchmaking( TF_MatchmakingMode mode )
|
||
|
{
|
||
|
Assert( !m_bUserWantsToBeInMatchmaking );
|
||
|
m_bUserWantsToBeInMatchmaking = true;
|
||
|
m_msgMatchmakingProgress.Clear();
|
||
|
m_nPendingAutoJoinPartyID = 0;
|
||
|
|
||
|
// Disconnect from any server we're already in
|
||
|
if ( ( mode != TF_Matchmaking_LADDER ) && ( mode != TF_Matchmaking_CASUAL ) )
|
||
|
{
|
||
|
engine->ClientCmd_Unrestricted( "disconnect" );
|
||
|
}
|
||
|
|
||
|
TF_Matchmaking_WizardStep eWizardStep = TF_Matchmaking_WizardStep_INVALID;
|
||
|
switch ( mode )
|
||
|
{
|
||
|
case TF_Matchmaking_MVM:
|
||
|
eWizardStep = TF_Matchmaking_WizardStep_MVM_PLAY_FOR_BRAGGING_RIGHTS;
|
||
|
break;
|
||
|
|
||
|
case TF_Matchmaking_LADDER:
|
||
|
eWizardStep = TF_Matchmaking_WizardStep_LADDER;
|
||
|
break;
|
||
|
|
||
|
case TF_Matchmaking_CASUAL:
|
||
|
eWizardStep = TF_Matchmaking_WizardStep_CASUAL;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
AssertMsg1( false, "Unknown wizard step %d\n", (int)mode );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Check if we don't already have a party, then set some default search options
|
||
|
CTFParty *pParty = GetParty();
|
||
|
if ( pParty == NULL )
|
||
|
{
|
||
|
m_msgLocalSearchCriteria.set_matchmaking_mode( mode );
|
||
|
m_eLocalWizardStep = eWizardStep;
|
||
|
|
||
|
// Default late join option
|
||
|
m_msgLocalSearchCriteria.set_late_join_ok( false );
|
||
|
|
||
|
// Default Mann Up state based on whether they have a ticket
|
||
|
SetSearchPlayForBraggingRights( mode == TF_Matchmaking_MVM && BLocalPlayerInventoryHasMvmTicket() );
|
||
|
|
||
|
FireGameEventPartyUpdated();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
|
||
|
// Hmmm. we already have a party. We really should already be in the correct mode.
|
||
|
if ( pParty->GetMatchmakingMode() != mode && BIsPartyLeader() )
|
||
|
{
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->mutable_search_criteria()->set_matchmaking_mode( mode );
|
||
|
pMsg->set_wizard_step( eWizardStep );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Post an event so we'll know that we joined the lobby OK
|
||
|
IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_lobby_member_join" );
|
||
|
if ( !pEvent )
|
||
|
return;
|
||
|
pEvent->SetString( "steamid", CFmtStr("%llu", steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() ) );
|
||
|
pEvent->SetInt( "solo", 1 ); // is this always true?
|
||
|
gameeventmanager->FireEventClientSide( pEvent );
|
||
|
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BAllowMatchMakingInGame( void ) const
|
||
|
{
|
||
|
return !BHaveLiveMatch();
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::EndMatchmaking( bool bSendAbandonLobby /* = false */)
|
||
|
{
|
||
|
// Set flag, so if GC sends us any further messages, we'll know to ignore them
|
||
|
m_bUserWantsToBeInMatchmaking = false;
|
||
|
m_bWantToActivateInviteUI = false;
|
||
|
m_msgMatchmakingProgress.Clear();
|
||
|
|
||
|
// If bSendAbandonLobby is false, this will only ask the GC to drop us from our party. If this message races with
|
||
|
// us finding a match, the GC will decline.
|
||
|
// ( If that happens, the rejoin game in progress dialog will pop up and resolve the race, so you can't accidentally
|
||
|
// abandon by canceling queue at the right millisecond )
|
||
|
SendExitMatchmaking( bSendAbandonLobby );
|
||
|
|
||
|
if ( BConnectedToMatchServer( true ) )
|
||
|
{
|
||
|
// If we were connected to a server we matchmade into, then disconnect
|
||
|
switch ( m_eConnectState )
|
||
|
{
|
||
|
default:
|
||
|
AssertMsg1( false, "Unknown connect state %d", m_eConnectState );
|
||
|
case eConnectState_NonmatchmadeServer:
|
||
|
case eConnectState_Disconnected:
|
||
|
break;
|
||
|
|
||
|
case eConnectState_ConnectingToMatchmade:
|
||
|
case eConnectState_ConnectedToMatchmade:
|
||
|
Msg( "Disconnecting from matchmade server\n" );
|
||
|
engine->ClientCmd_Unrestricted( "disconnect" );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BExitMatchmakingAfterDisconnect( void )
|
||
|
{
|
||
|
return BConnectedToMatchServer( true );
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::LeaveSteamLobby()
|
||
|
{
|
||
|
if ( m_steamIDLobby.IsValid() )
|
||
|
{
|
||
|
Assert( steamapicontext );
|
||
|
Assert( m_steamIDLobby.IsLobby() );
|
||
|
if ( steamapicontext )
|
||
|
{
|
||
|
Msg( "Leaving steam lobby %s\n", m_steamIDLobby.Render() );
|
||
|
steamapicontext->SteamMatchmaking()->LeaveLobby( m_steamIDLobby );
|
||
|
}
|
||
|
m_steamIDLobby = CSteamID();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int CTFGCClientSystem::CheckSteamLobbyCreated()
|
||
|
{
|
||
|
if ( !m_bUserWantsToBeInMatchmaking )
|
||
|
{
|
||
|
Assert( m_bUserWantsToBeInMatchmaking ); // why are you calling this?
|
||
|
LeaveSteamLobby();
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// Already in a lobby?
|
||
|
if ( m_steamIDLobby.IsValid() )
|
||
|
return 1;
|
||
|
|
||
|
// Do we have the interfaces we need?
|
||
|
if ( steamapicontext == NULL || steamapicontext->SteamMatchmaking() == NULL )
|
||
|
return -1;
|
||
|
|
||
|
// Is a creation request already in progress?
|
||
|
if ( m_eCreateLobbyStatus == -1 )
|
||
|
return 0;
|
||
|
|
||
|
Msg( "Creating Steam lobby\n" );
|
||
|
|
||
|
m_eCreateLobbyStatus = -1;
|
||
|
steamapicontext->SteamMatchmaking()->CreateLobby( k_ELobbyTypePrivate, MAX_PLAYERS );
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::RequestActivateInvite()
|
||
|
{
|
||
|
|
||
|
// What state are we in?
|
||
|
switch ( GetMatchmakingUIState() )
|
||
|
{
|
||
|
case eMatchmakingUIState_Chat:
|
||
|
break;
|
||
|
|
||
|
case eMatchmakingUIState_InQueue:
|
||
|
Warning( "Leaving matchmaking queue due to request to active friend invite UI\n" );
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
Warning( "Can only invite friends to party when in the chat state, or the searching state\n" );
|
||
|
m_bWantToActivateInviteUI = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Set flag. We'll try to activate the UI at he earliest opportunity
|
||
|
m_bWantToActivateInviteUI = true;
|
||
|
|
||
|
// Create our party I we don't have one, and also
|
||
|
// get us out of the queue, if we're in it.
|
||
|
SendCreateOrUpdatePartyMsg( GetWizardStep() );
|
||
|
|
||
|
// Check if we're ready to activate the UI now
|
||
|
CheckReadyToActivateInvite();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Ask the GC for the latest global casual criteria stats
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::RequestMatchMakerStats() const
|
||
|
{
|
||
|
CProtoBufMsg<CMsgGCRequestMatchMakerStats> msg( k_EMsgGCRequestMatchMakerStats );
|
||
|
GCClientSystem()->BSendMessage( msg );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Set our cached global casual criteria stats and figure out the most
|
||
|
// popular map so we can do some health computations later.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::SetMatchMakerStats( const CMsgGCMatchMakerStatsResponse newStats )
|
||
|
{
|
||
|
m_MatchMakerStats = newStats;
|
||
|
|
||
|
// Update m_nMostSearchedMapCount to be the largest in m_CasualCriteriaStats
|
||
|
m_nMostSearchedMapCount = 0;
|
||
|
for( int iMap=0; iMap < m_MatchMakerStats.map_count_size(); ++iMap )
|
||
|
{
|
||
|
m_nMostSearchedMapCount = Max( m_nMostSearchedMapCount, m_MatchMakerStats.map_count( iMap ) );
|
||
|
}
|
||
|
|
||
|
// put data_center_population in dict so we don't have to loop over and strcmp everytime we ask for it
|
||
|
COMPILE_TIME_ASSERT( ARRAYSIZE( m_dictDataCenterPopulationRatio ) == k_nMatchGroup_Count );
|
||
|
Assert( m_MatchMakerStats.matchgroup_data_center_population_size() == k_nMatchGroup_Count );
|
||
|
for ( int iMatchGroup=0; iMatchGroup<k_nMatchGroup_Count; ++iMatchGroup )
|
||
|
{
|
||
|
m_dictDataCenterPopulationRatio[ iMatchGroup ].Purge();
|
||
|
const auto& matchgroup_datacenter_population = m_MatchMakerStats.matchgroup_data_center_population( iMatchGroup );
|
||
|
for ( int iDataCenter=0; iDataCenter<matchgroup_datacenter_population.data_center_population_size(); ++iDataCenter )
|
||
|
{
|
||
|
auto dcp = matchgroup_datacenter_population.data_center_population( iDataCenter );
|
||
|
m_dictDataCenterPopulationRatio[ iMatchGroup ].Insert( dcp.name().c_str(), dcp.health_ratio() );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Given a health ratio, get the health data
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CTFGCClientSystem::MatchMakerHealthData_t CTFGCClientSystem::GetHealthBracketForRatio( float flRatio ) const
|
||
|
{
|
||
|
CTFGCClientSystem::MatchMakerHealthData_t data;
|
||
|
data.m_flRatio = flRatio;
|
||
|
|
||
|
static const Color colorBad( 128, 128, 128, 60 );
|
||
|
static const Color colorOK( 188, 112, 0, 128 );
|
||
|
static const Color colorGood( 94, 150, 49, 255 );
|
||
|
|
||
|
// Walk through our brackets and fine where we fall and setup data accordingly
|
||
|
if ( flRatio < 0.3f )
|
||
|
{
|
||
|
data.m_colorBar = LerpColor( colorBad, colorOK, RemapValClamped( flRatio, 0.2f, 0.3f, 0.f, 1.f ) );
|
||
|
data.m_strLocToken = "TF_Casual_QueueEstimation_Bad";
|
||
|
}
|
||
|
else if ( flRatio < 0.7f )
|
||
|
{
|
||
|
data.m_colorBar = LerpColor( colorOK, colorGood, RemapValClamped( flRatio, 0.3f, 0.7f, 0.f, 1.f ) );
|
||
|
data.m_strLocToken = "TF_Casual_QueueEstimation_OK";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
data.m_colorBar = colorGood;
|
||
|
data.m_strLocToken = "TF_Casual_QueueEstimation_Good";
|
||
|
}
|
||
|
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
ConVar tf_fake_casual_map_stats( "tf_fake_casual_map_stats", "0" );
|
||
|
#endif
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Really here just so we can shortcircuit some staging_only debug
|
||
|
//-----------------------------------------------------------------------------
|
||
|
inline uint32 GetCountForMap( const CMsgGCMatchMakerStatsResponse msg, int nIndex )
|
||
|
{
|
||
|
#ifdef STAGING_ONLY
|
||
|
// If we're faking, then fake some stats
|
||
|
if ( tf_fake_casual_map_stats.GetBool() )
|
||
|
{
|
||
|
CUniformRandomStream randomStream;
|
||
|
randomStream.SetSeed( nIndex + tf_fake_casual_map_stats.GetInt() );
|
||
|
return randomStream.RandomInt( 0, 100000 );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return msg.map_count( nIndex );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Get the overall health of the current local casual criteria.
|
||
|
// Currently just takes the best individual map health.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CTFGCClientSystem::MatchMakerHealthData_t CTFGCClientSystem::GetOverallHealthDataForLocalCriteria() const
|
||
|
{
|
||
|
uint32 nMostSearchedCount = m_nMostSearchedMapCount;
|
||
|
uint32 nLargestOfSelected = 0;
|
||
|
CCasualCriteriaHelper helper( m_msgLocalSearchCriteria.casual_criteria() );
|
||
|
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_fake_casual_map_stats.GetBool() )
|
||
|
{
|
||
|
nMostSearchedCount = 100000;
|
||
|
|
||
|
// Force some fake stats if we dont have the baseline message yet
|
||
|
if ( m_MatchMakerStats.map_count_size() == 0 )
|
||
|
{
|
||
|
for( int i=0; i < GetItemSchema()->GetMasterMapsList().Count(); ++i )
|
||
|
{
|
||
|
if ( helper.IsMapSelected( i ) )
|
||
|
{
|
||
|
nLargestOfSelected = Max( nLargestOfSelected, GetCountForMap( m_MatchMakerStats, i ) );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// No data -- we assume bad
|
||
|
if ( nMostSearchedCount == 0 )
|
||
|
return GetHealthBracketForRatio( 0.f );
|
||
|
|
||
|
// Go through all the locallty selected maps and find the one with the best health.
|
||
|
// Use that to get our estimated overall criteria health.
|
||
|
for( int i=0; i < m_MatchMakerStats.map_count_size(); ++i )
|
||
|
{
|
||
|
if ( helper.IsMapSelected( i ) )
|
||
|
{
|
||
|
nLargestOfSelected = Max( nLargestOfSelected, GetCountForMap( m_MatchMakerStats, i ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return GetHealthBracketForRatio( (float)nLargestOfSelected / (float)nMostSearchedCount );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Gets the health of a given map
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CTFGCClientSystem::MatchMakerHealthData_t CTFGCClientSystem::GetHealthDataForMap( uint32 nMapIndex ) const
|
||
|
{
|
||
|
uint32 nMostSearchedCount = m_nMostSearchedMapCount;
|
||
|
uint32 nLargestOfSelected = 0;
|
||
|
#ifdef STAGING_ONLY
|
||
|
if ( tf_fake_casual_map_stats.GetBool() )
|
||
|
{
|
||
|
nMostSearchedCount = 100000;
|
||
|
nLargestOfSelected = GetCountForMap( m_MatchMakerStats, nMapIndex );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// No data -- we assume bad
|
||
|
if ( nMostSearchedCount == 0 )
|
||
|
return GetHealthBracketForRatio( 0.f );
|
||
|
|
||
|
if ( (int)nMapIndex < m_MatchMakerStats.map_count_size() )
|
||
|
{
|
||
|
nLargestOfSelected = GetCountForMap( m_MatchMakerStats, nMapIndex );
|
||
|
}
|
||
|
|
||
|
return GetHealthBracketForRatio( (float)nLargestOfSelected / (float)nMostSearchedCount );
|
||
|
}
|
||
|
|
||
|
|
||
|
//CON_COMMAND( tf_resend_so_cache, "Resend SO cache" )
|
||
|
//{
|
||
|
// // Force a resend of our SO cache.
|
||
|
// CProtoBufMsg<CMsgForceSOCacheResend> msg( k_EMsgForceSOCacheResend );
|
||
|
// GCClientSystem()->BSendMessage( msg );
|
||
|
//}
|
||
|
//
|
||
|
//CON_COMMAND( tf_get_news, "Request game news from the GC" )
|
||
|
//{
|
||
|
// if ( args.ArgC() < 2 )
|
||
|
// return;
|
||
|
//
|
||
|
// CGCClientJobGetNews *pJob = new CGCClientJobGetNews( GCClientSystem()->GetGCClient(), atoi( args[1] ) );
|
||
|
// pJob->StartJob( NULL );
|
||
|
//}
|
||
|
|
||
|
//CON_COMMAND( tf_party_test, "Tests sending a party invite" )
|
||
|
//{
|
||
|
// CProtoBufMsg<CMsgInviteToParty> msg( k_EMsgGCInviteToParty );
|
||
|
// msg.Body().set_steam_id( steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() );
|
||
|
//// msg.Body().set_client_version( engine->GetClientVersion() );
|
||
|
// GCClientSystem()->BSendMessage( msg );
|
||
|
//}
|
||
|
|
||
|
CON_COMMAND( tf_party_debug, "Prints local party objects" )
|
||
|
{
|
||
|
GTFGCClientSystem()->DumpParty();
|
||
|
}
|
||
|
|
||
|
CON_COMMAND( tf_invite_debug, "Prints local invite objects" )
|
||
|
{
|
||
|
GTFGCClientSystem()->DumpInvites();
|
||
|
}
|
||
|
|
||
|
CON_COMMAND( tf_lobby_debug, "Prints local lobby objects" )
|
||
|
{
|
||
|
GTFGCClientSystem()->DumpLobby();
|
||
|
}
|
||
|
|
||
|
//CON_COMMAND( tf_beta_debug, "Prints local dota beta participation object" )
|
||
|
//{
|
||
|
// GTFGCClientSystem()->DumpBetaParticipation();
|
||
|
//}
|
||
|
|
||
|
//CON_COMMAND( tf_game_account_debug, "Prints game account info" )
|
||
|
//{
|
||
|
// GTFGCClientSystem()->DumpGameAccountClient();
|
||
|
//}
|
||
|
|
||
|
//class CGCClientJobNestedTest : public GCSDK::CGCClientJob
|
||
|
//{
|
||
|
//public:
|
||
|
// CGCClientJobNestedTest( GCSDK::CGCClient *pGCClient, int nIndex ) : GCSDK::CGCClientJob( pGCClient ), m_nIndex( nIndex ) { }
|
||
|
//
|
||
|
// virtual bool BYieldingRunJob( void *pvStartParam )
|
||
|
// {
|
||
|
// Msg( "Nested job %d running!\n", m_nIndex );
|
||
|
// BYieldingWaitOneFrame();
|
||
|
// Msg( "Nested job %d done running!\n", m_nIndex );
|
||
|
// return true;
|
||
|
// }
|
||
|
// int m_nIndex;
|
||
|
//};
|
||
|
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//// Purpose: Job for being told when the user GC connection is established
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//class CGCClientJobClientWelcome : public GCSDK::CGCClientJob
|
||
|
//{
|
||
|
//public:
|
||
|
// CGCClientJobClientWelcome( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
//
|
||
|
// virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
// {
|
||
|
// GCSDK::CProtoBufMsg<CMsgClientWelcome> msg( pNetPacket );
|
||
|
//
|
||
|
// // Validate version
|
||
|
// int engineClientVersion = engine->GetClientVersion();
|
||
|
// int gcClientVersion = (int)msg.Body().version();
|
||
|
//
|
||
|
// // Version checking is enforced if both sides do not report zero as their version
|
||
|
// if ( engineClientVersion && gcClientVersion && engineClientVersion != gcClientVersion )
|
||
|
// {
|
||
|
// IGameEvent *pEvent = gameeventmanager->CreateEvent( "gc_mismatched_version" );
|
||
|
// if ( pEvent )
|
||
|
// {
|
||
|
// gameeventmanager->FireEventClientSide( pEvent );
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// g_bClientReceivedGCWelcome = true;
|
||
|
//
|
||
|
// if ( GTFGCClientSystem() && gameeventmanager )
|
||
|
// {
|
||
|
// // when client has reconnected to Steam, wipe dashboard caches.
|
||
|
// if ( Dashboard() )
|
||
|
// {
|
||
|
// Dashboard()->ClearDashboardCaches();
|
||
|
// }
|
||
|
//
|
||
|
// Msg( "CGCClientJobUserSessionCreated::BYieldingRunJobFromMsg firing event\n" );
|
||
|
// IGameEvent *pEvent = gameeventmanager->CreateEvent( "gc_user_session_created" );
|
||
|
// if ( pEvent )
|
||
|
// {
|
||
|
// gameeventmanager->FireEventClientSide( pEvent );
|
||
|
// }
|
||
|
// }
|
||
|
// else
|
||
|
// {
|
||
|
// Msg( "CGCClientJobUserSessionCreated::BYieldingRunJobFromMsg not firing event\n" );
|
||
|
// }
|
||
|
//
|
||
|
// if ( DOTAChat() )
|
||
|
// {
|
||
|
// if ( !DOTAChat()->HasJoinedStartupChannels() )
|
||
|
// {
|
||
|
// DOTAChat()->JoinStartupChannels();
|
||
|
// }
|
||
|
// else
|
||
|
// {
|
||
|
// DOTAChat()->SetRejoinChannels();
|
||
|
// }
|
||
|
// }
|
||
|
// return true;
|
||
|
// }
|
||
|
//};
|
||
|
//GC_REG_JOB( GCSDK::CGCClient, CGCClientJobClientWelcome, "CGCClientJobClientWelcome", k_EMsgGCClientWelcome, k_EServerTypeGCClient );
|
||
|
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//// Purpose: Job for being told when the user's GC session is created
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//class CGCClientInvitationCreated : public GCSDK::CGCClientJob
|
||
|
//{
|
||
|
//public:
|
||
|
// CGCClientInvitationCreated( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
//
|
||
|
// virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
// {
|
||
|
// GCSDK::CProtoBufMsg<CMsgInvitationCreated> msg( pNetPacket );
|
||
|
//
|
||
|
// CUtlString commandline;
|
||
|
// commandline.Format( "+invite %llu", msg.Body().group_id() );
|
||
|
// steamapicontext->SteamFriends()->InviteUserToGame( msg.Body().steam_id(), commandline.String() );
|
||
|
//
|
||
|
// return true;
|
||
|
// }
|
||
|
//};
|
||
|
//GC_REG_JOB( GCSDK::CGCClient, CGCClientInvitationCreated, "CGCClientInvitationCreated", k_EMsgGCInvitationCreated, k_EServerTypeGCClient );
|
||
|
|
||
|
class CGCClientMatchmakingProgress : public GCSDK::CGCClientJob
|
||
|
{
|
||
|
public:
|
||
|
CGCClientMatchmakingProgress( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
|
||
|
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
{
|
||
|
GCSDK::CProtoBufMsg<CMsgMatchmakingProgress> msg( pNetPacket );
|
||
|
GTFGCClientSystem()->m_msgMatchmakingProgress = msg.Body();
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
GC_REG_JOB( GCSDK::CGCClient, CGCClientMatchmakingProgress, "CGCClientMatchmakingProgress", k_EMsgGCMatchmakingProgress, k_EServerTypeGCClient );
|
||
|
|
||
|
|
||
|
class CGCClientMatchMakerStats : public GCSDK::CGCClientJob
|
||
|
{
|
||
|
public:
|
||
|
CGCClientMatchMakerStats( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
|
||
|
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
{
|
||
|
GCSDK::CProtoBufMsg<CMsgGCMatchMakerStatsResponse> msg( pNetPacket );
|
||
|
GTFGCClientSystem()->SetMatchMakerStats( msg.Body() );
|
||
|
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "matchmaker_stats_updated" );
|
||
|
if ( event )
|
||
|
{
|
||
|
gameeventmanager->FireEventClientSide( event );
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
GC_REG_JOB( GCSDK::CGCClient, CGCClientMatchMakerStats, "CGCClientMatchMakerStats", k_EMsgGCMatchMakerStatsResponse, k_EServerTypeGCClient );
|
||
|
|
||
|
class CGCClientSurveyRequest : public GCSDK::CGCClientJob
|
||
|
{
|
||
|
public:
|
||
|
CGCClientSurveyRequest( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
|
||
|
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
{
|
||
|
GCSDK::CProtoBufMsg<CMsgGCSurveyRequest> msg( pNetPacket );
|
||
|
GTFGCClientSystem()->SetSurveyRequest( msg.Body() );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
GC_REG_JOB( GCSDK::CGCClient, CGCClientSurveyRequest, "CGCClientSurveyRequest", k_EMsgGC_SurveyQuestionRequest, k_EServerTypeGCClient );
|
||
|
|
||
|
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//class CGCClientJobFindSourceTVGamesDebug : public GCSDK::CGCClientJob
|
||
|
//{
|
||
|
//public:
|
||
|
// CGCClientJobFindSourceTVGamesDebug( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient )
|
||
|
// {
|
||
|
//
|
||
|
// }
|
||
|
//
|
||
|
// virtual bool BYieldingRunJob( void *pvStartParam )
|
||
|
// {
|
||
|
// CProtoBufMsg<CMsgFindSourceTVGames> msg( k_EMsgGCFindSourceTVGames );
|
||
|
// CProtoBufMsg<CMsgSourceTVGamesResponse> msgResponse( k_EMsgGCSourceTVGamesResponse );
|
||
|
//
|
||
|
// static ConVarRef sv_search_key("sv_search_key");
|
||
|
// if ( sv_search_key.IsValid() && *sv_search_key.GetString() )
|
||
|
// {
|
||
|
// msg.Body().set_search_key( sv_search_key.GetString() );
|
||
|
// }
|
||
|
//
|
||
|
// bool bRet = BYldSendMessageAndGetReply( msg, 15, &msgResponse, k_EMsgGCSourceTVGamesResponse );
|
||
|
// if ( !bRet )
|
||
|
// {
|
||
|
// Warning( "CGCClientJobFindSourceTVGamesDebug failed to get reply\n" );
|
||
|
// return false;
|
||
|
// }
|
||
|
//
|
||
|
// Msg( "CGCClientJobFindSourceTVGamesDebug: %d\n", msgResponse.Body().games_size() );
|
||
|
// for ( int i = 0; i < msgResponse.Body().games_size(); i++ )
|
||
|
// {
|
||
|
// const CSourceTVGame &game = msgResponse.Body().games(i);
|
||
|
// Msg( " Game %d:\n", i );
|
||
|
//
|
||
|
// CUtlString sGoodPlayers;
|
||
|
// for ( int p = 0; p < game.good_players_size(); p++ )
|
||
|
// {
|
||
|
// if ( sGoodPlayers.Length() > 0 )
|
||
|
// {
|
||
|
// sGoodPlayers += ", ";
|
||
|
// }
|
||
|
// sGoodPlayers += game.good_players(p).name().c_str();
|
||
|
// }
|
||
|
//
|
||
|
// CUtlString sBadPlayers;
|
||
|
// for ( int p = 0; p < game.bad_players_size(); p++ )
|
||
|
// {
|
||
|
// if ( sBadPlayers.Length() > 0 )
|
||
|
// {
|
||
|
// sBadPlayers += ", ";
|
||
|
// }
|
||
|
// sBadPlayers += game.bad_players(p).name().c_str();
|
||
|
// }
|
||
|
//
|
||
|
// CUtlString sOtherPlayers;
|
||
|
// for ( int p = 0; p < game.other_players_size(); p++ )
|
||
|
// {
|
||
|
// if ( sOtherPlayers.Length() > 0 )
|
||
|
// {
|
||
|
// sOtherPlayers += ", ";
|
||
|
// }
|
||
|
// sOtherPlayers += game.other_players(p).name().c_str();
|
||
|
// }
|
||
|
//
|
||
|
// CSteamID steamIDServer( game.server_steamid() );
|
||
|
// Msg( " SteamID: %s\n Good: %s\n Bad: %s\n Other: %s\n", steamIDServer.Render(), sGoodPlayers.Get(), sBadPlayers.Get(), sOtherPlayers.Get() );
|
||
|
// }
|
||
|
//
|
||
|
// return true;
|
||
|
// }
|
||
|
//
|
||
|
//private:
|
||
|
//};
|
||
|
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//// Purpose: Receive a broadcast message for notification
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//class CGCClientJobDOTABroadcastNotificationClient : public GCSDK::CGCClientJob
|
||
|
//{
|
||
|
//public:
|
||
|
// CGCClientJobDOTABroadcastNotificationClient( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
//
|
||
|
// virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
// {
|
||
|
// CProtoBufMsg<CMsgDOTABroadcastNotification> msg( pNetPacket );
|
||
|
//
|
||
|
// wchar_t wszText[256];
|
||
|
// if ( g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().message().c_str(), wszText, sizeof( wszText ) ) <= 0 )
|
||
|
// return false;
|
||
|
//
|
||
|
// // create a console chat message
|
||
|
// KeyValues *pChatMsg = new KeyValues( "Command::Game::Chat" );
|
||
|
// KeyValues::AutoDelete autodelete( pChatMsg );
|
||
|
// pChatMsg->SetString( "run", "all" );
|
||
|
// pChatMsg->SetUint64( "xuid", 0 );
|
||
|
// pChatMsg->SetString( "name", "Console" );
|
||
|
// pChatMsg->SetWString( "chat", wszText );
|
||
|
//
|
||
|
// // make each channel receive the message
|
||
|
// for ( int i = 0; i < DOTAChat()->GetNumChannels(); ++i )
|
||
|
// {
|
||
|
// // receive message immediately
|
||
|
// DOTAChat()->GetChannel(i)->ReceiveMessage( pChatMsg );
|
||
|
// }
|
||
|
//
|
||
|
// return true;
|
||
|
// }
|
||
|
//};
|
||
|
//GC_REG_JOB( GCSDK::CGCClient, CGCClientJobDOTABroadcastNotificationClient, "CGCClientJobDOTABroadcastNotificationClient", k_EMsgGCBroadcastNotification, k_EServerTypeGCClient );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
//CON_COMMAND( tf_find_source_tv_games, "Request game news from the GC" )
|
||
|
//{
|
||
|
// CGCClientJobFindSourceTVGamesDebug *pJob = new CGCClientJobFindSourceTVGamesDebug( GCClientSystem()->GetGCClient() );
|
||
|
// pJob->StartJob( NULL );
|
||
|
//}
|
||
|
|
||
|
//CON_COMMAND( tf_set_lobby_details, "Set game/team names" )
|
||
|
//{
|
||
|
// if ( args.ArgC() != 6 )
|
||
|
// {
|
||
|
// Msg( "Usage: tf_set_lobby_details <game name> <radiant team name> <radiant team logo> <dire team name> <dire team logo>\n" );
|
||
|
// return;
|
||
|
// }
|
||
|
//
|
||
|
// CTFGSLobby *pLobby = GTFGCClientSystem() ? GTFGCClientSystem()->GetLobby() : NULL;
|
||
|
// if ( !pLobby )
|
||
|
// {
|
||
|
// Msg( "No lobby found.\n" );
|
||
|
// return;
|
||
|
// }
|
||
|
//
|
||
|
// CProtoBufMsg<CMsgPracticeLobbySetDetails> msg( k_EMsgGCPracticeLobbySetDetails );
|
||
|
// msg.Body().set_lobby_id( pLobby->GetGroupID() );
|
||
|
// msg.Body().set_game_name( args[1] );
|
||
|
// msg.Body().add_team_details(); // radiant
|
||
|
// msg.Body().add_team_details(); // dire
|
||
|
// msg.Body().mutable_team_details( DOTA_GC_TEAM_GOOD_GUYS )->set_team_name( args[2] );
|
||
|
// msg.Body().mutable_team_details( DOTA_GC_TEAM_GOOD_GUYS )->set_team_logo( args[3] );
|
||
|
// msg.Body().mutable_team_details( DOTA_GC_TEAM_BAD_GUYS )->set_team_name( args[4] );
|
||
|
// msg.Body().mutable_team_details( DOTA_GC_TEAM_BAD_GUYS )->set_team_logo( args[5] );
|
||
|
// GCClientSystem()->BSendMessage( msg );
|
||
|
//}
|
||
|
//
|
||
|
//CON_COMMAND( request_today_messages, "Ask the GC for a list of today messages" )
|
||
|
//{
|
||
|
// Msg( "Requesting today messages...\n" );
|
||
|
// CProtoBufMsg<CMsgDOTARequestTodayMessages> msg( k_EMsgGCRequestTodayMessages );
|
||
|
// GCClientSystem()->BSendMessage( msg );
|
||
|
//}
|
||
|
|
||
|
|
||
|
//class CUtlSortVectorTodayMessageGreater
|
||
|
//{
|
||
|
//public:
|
||
|
// bool Less( const CMsgDOTATodayMessages_TodayMessage& lhs, const CMsgDOTATodayMessages_TodayMessage& rhs, void * )
|
||
|
// {
|
||
|
// return lhs.date() > rhs.date();
|
||
|
// }
|
||
|
//};
|
||
|
//
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//// Purpose: Receive a list of today messages
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//class CGCClientJobDOTATodayMessages : public GCSDK::CGCClientJob
|
||
|
//{
|
||
|
//public:
|
||
|
// CGCClientJobDOTATodayMessages( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
//
|
||
|
// virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
// {
|
||
|
// CProtoBufMsg<CMsgDOTATodayMessages> msg( pNetPacket );
|
||
|
//
|
||
|
// g_pFullFileSystem->CreateDirHierarchy( "resource/flash3/images/today/", "GAME" );
|
||
|
//
|
||
|
// CUtlSortVector< CMsgDOTATodayMessages_TodayMessage, CUtlSortVectorTodayMessageGreater > sortedMessageList;
|
||
|
//
|
||
|
// int nMessages = msg.Body().messages_size();
|
||
|
// for ( int i = 0; i < nMessages; i++ )
|
||
|
// {
|
||
|
// sortedMessageList.Insert( msg.Body().messages( i ) );
|
||
|
// }
|
||
|
//
|
||
|
// CMsgDOTATodayMessages sortedMessages;
|
||
|
// for ( int i = 0; i < sortedMessageList.Count(); i++ )
|
||
|
// {
|
||
|
// CMsgDOTATodayMessages_TodayMessage *pNewMessage = sortedMessages.add_messages();
|
||
|
// *pNewMessage = sortedMessageList[i];
|
||
|
// }
|
||
|
// if ( tf_debug_today_message_sorting.GetBool() )
|
||
|
// {
|
||
|
// Msg ( "\nUNSORTED ***\n = %s\n", sortedMessages.DebugString().c_str() );
|
||
|
// Msg ( "\nSORTED ***\n = %s\n", msg.Body().DebugString().c_str() );
|
||
|
// }
|
||
|
// GTFGCClientSystem()->SetTodayMessages( &sortedMessages );
|
||
|
//
|
||
|
// IGameEvent *pEvent = gameeventmanager->CreateEvent( "today_messages_updated" );
|
||
|
// if ( pEvent )
|
||
|
// {
|
||
|
// pEvent->SetInt( "num_messages", nMessages );
|
||
|
// gameeventmanager->FireEventClientSide( pEvent );
|
||
|
// }
|
||
|
//
|
||
|
// // make sure we have all the today images downloaded
|
||
|
// for ( int i = 0; i < nMessages; i++ )
|
||
|
// {
|
||
|
// const char *pszImageURL = msg.Body().messages( i ).image_url().c_str();
|
||
|
// if ( pszImageURL && pszImageURL[0] )
|
||
|
// {
|
||
|
// const char *pszFilename = V_UnqualifiedFileName( pszImageURL );
|
||
|
// if ( pszFilename && pszFilename[0] )
|
||
|
// {
|
||
|
// char szBuffer[MAX_PATH];
|
||
|
// szBuffer[0] = '\0';
|
||
|
// V_strcat( szBuffer, "resource/flash3/images/today/", sizeof( szBuffer ) );
|
||
|
// V_strcat( szBuffer, pszFilename, sizeof( szBuffer ) );
|
||
|
// GTFGCClientSystem()->DownloadFile( pszImageURL, szBuffer );
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// return true;
|
||
|
// }
|
||
|
//};
|
||
|
//GC_REG_JOB( GCSDK::CGCClient, CGCClientJobDOTATodayMessages, "CGCClientJobDOTATodayMessages", k_EMsgGCTodayMessages, k_EServerTypeGCClient );
|
||
|
//
|
||
|
///*
|
||
|
//CON_COMMAND( reports_remaining, "Request number of reports remaining this week" )
|
||
|
//{
|
||
|
// CProtoBufMsg<CMsgDOTAReportsRemainingRequest> msg( k_EMsgGCReportsRemainingRequest );
|
||
|
// GCClientSystem()->BSendMessage( msg );
|
||
|
//
|
||
|
// Msg( "Report submitted\n" );
|
||
|
//}
|
||
|
//*/
|
||
|
//
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//// Purpose: Receive number of reports remaining
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//class CGCClientJobDOTAReportsRemaining : public GCSDK::CGCClientJob
|
||
|
//{
|
||
|
//public:
|
||
|
// CGCClientJobDOTAReportsRemaining( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
//
|
||
|
// virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
// {
|
||
|
// CProtoBufMsg<CMsgDOTAReportsRemainingResponse> msg( pNetPacket );
|
||
|
// //Msg( "You have %u positive and %u negative reports remaining\n", msg.Body().num_positive_reports_remaining(), msg.Body().num_negative_reports_remaining() );
|
||
|
// //Msg( "You have %u positive and %u negative reports total\n", msg.Body().num_positive_reports_total(), msg.Body().num_negative_reports_total() );
|
||
|
//
|
||
|
// IGameEvent *pEvent = gameeventmanager->CreateEvent( "player_report_counts_updated" );
|
||
|
// if ( pEvent )
|
||
|
// {
|
||
|
// pEvent->SetInt( "positive_remaining", msg.Body().num_positive_reports_remaining() );
|
||
|
// pEvent->SetInt( "negative_remaining", msg.Body().num_negative_reports_remaining() );
|
||
|
// pEvent->SetInt( "positive_total", msg.Body().num_positive_reports_total() );
|
||
|
// pEvent->SetInt( "negative_total", msg.Body().num_negative_reports_total() );
|
||
|
// gameeventmanager->FireEventClientSide( pEvent );
|
||
|
// }
|
||
|
//
|
||
|
// return true;
|
||
|
// }
|
||
|
//};
|
||
|
//GC_REG_JOB( GCSDK::CGCClient, CGCClientJobDOTAReportsRemaining, "CGCClientJobDOTAReportsRemaining", k_EMsgGCReportsRemainingResponse, k_EServerTypeGCClient );
|
||
|
//
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//// Purpose: Handle response to k_EMsgGCWatchGame (k_EMsgGCWatchGameResponse)
|
||
|
////-----------------------------------------------------------------------------
|
||
|
//class CGCClientJobWatchGameResponse : public GCSDK::CGCClientJob
|
||
|
//{
|
||
|
//public:
|
||
|
// CGCClientJobWatchGameResponse( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
//
|
||
|
// virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
// {
|
||
|
// GCSDK::CProtoBufMsg<CMsgWatchGameResponse> msg( pNetPacket );
|
||
|
// GTFGCClientSystem()->StartWatchingGameResponse( msg.Body() );
|
||
|
// return true;
|
||
|
// }
|
||
|
//};
|
||
|
//GC_REG_JOB( GCSDK::CGCClient, CGCClientJobWatchGameResponse, "CGCClientJobWatchGameResponse", k_EMsgGCWatchGameResponse, k_EServerTypeGCClient );
|
||
|
//
|
||
|
//
|
||
|
//#ifdef _WIN32
|
||
|
//#undef DECLARE_HANDLE
|
||
|
//#undef INVALID_HANDLE_VALUE
|
||
|
//#include <windows.h>
|
||
|
//#include <shellapi.h>
|
||
|
//#undef GetCurrentDirectory
|
||
|
//#elif POSIX
|
||
|
//#include <sys/syscall.h>
|
||
|
//#include <sys/wait.h>
|
||
|
//#include <signal.h>
|
||
|
//#endif
|
||
|
//
|
||
|
//void CTFGCClientSystem::CreateSourceTVProxy( uint32 source_tv_public_addr, uint32 source_tv_private_addr, uint32 source_tv_port )
|
||
|
//{
|
||
|
// char szExecutablePath[MAX_PATH];
|
||
|
// if ( g_pFullFileSystem->RelativePathToFullPath( "..", "EXECUTABLE_PATH", szExecutablePath, sizeof( szExecutablePath ) ) )
|
||
|
// {
|
||
|
// Q_FixSlashes( szExecutablePath );
|
||
|
//
|
||
|
// char szSrcdsProxyBinary[MAX_PATH];
|
||
|
// Q_snprintf( szSrcdsProxyBinary, sizeof( szSrcdsProxyBinary ), "%s/srcds.exe", szExecutablePath );
|
||
|
// Q_FixSlashes( szSrcdsProxyBinary );
|
||
|
//
|
||
|
// netadr_t serverPublicIPAddr( source_tv_public_addr, source_tv_port );
|
||
|
// netadr_t serverPrivateIPAddr( source_tv_private_addr, source_tv_port );
|
||
|
//
|
||
|
// netadr_t *addr = NULL;
|
||
|
//
|
||
|
// if ( serverPublicIPAddr.GetIP() && !serverPublicIPAddr.IsLocalhost() )
|
||
|
// {
|
||
|
// addr = &serverPublicIPAddr;
|
||
|
// }
|
||
|
// else if ( serverPublicIPAddr.GetIP() != serverPrivateIPAddr.GetIP() && serverPrivateIPAddr.GetIP() && !serverPrivateIPAddr.IsLocalhost() )
|
||
|
// {
|
||
|
// addr = &serverPrivateIPAddr;
|
||
|
// }
|
||
|
//
|
||
|
// if (!addr)
|
||
|
// {
|
||
|
// Msg("unable to determine address for proxy at public:%s private:%s\n", serverPublicIPAddr.ToString(), serverPrivateIPAddr.ToString() );
|
||
|
// return;
|
||
|
// }
|
||
|
//
|
||
|
// CUtlString srcds_args;
|
||
|
//
|
||
|
// srcds_args.Format( "-console -game dota +tv_relay %s", addr->ToString() );
|
||
|
//
|
||
|
//#ifdef _WIN32
|
||
|
// int nRet = (int) ShellExecute( NULL, "open", szSrcdsProxyBinary, srcds_args.String(), szExecutablePath, 1 /*SW_SHOWNORMAL*/ );
|
||
|
//
|
||
|
// if ( nRet > 0 && nRet < 32 )
|
||
|
// {
|
||
|
// Warning( "Failed to execute srcds proxy for public:%s private:%s\n", serverPublicIPAddr.ToString(), serverPrivateIPAddr.ToString() );
|
||
|
// Warning( " szSrcdsProxyBinary = %s\n", szSrcdsProxyBinary );
|
||
|
// Warning( " szExecutablePath = %s\n", szExecutablePath );
|
||
|
// Warning( " ShellExecute result = %d\n", nRet );
|
||
|
// }
|
||
|
// else
|
||
|
// {
|
||
|
// Msg( "Executed srcds proxy: %s args: %s dir:%s\n", szSrcdsProxyBinary, srcds_args.String(), szExecutablePath );
|
||
|
// }
|
||
|
//#else
|
||
|
// /* */
|
||
|
//#endif
|
||
|
// }
|
||
|
//
|
||
|
//}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Sends an xp acknowledge via k_EMsgGC_AcknowledgeXP to the GC if
|
||
|
// we have any outstanding xp sources or a mismatch in our last
|
||
|
// acknowledged xp and our current one.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::AcknowledgePendingXPSources( EMatchGroup eMatchGroup ) const
|
||
|
{
|
||
|
if ( !m_pSOCache )
|
||
|
return;
|
||
|
|
||
|
const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( eMatchGroup );
|
||
|
if ( !pMatchDesc )
|
||
|
return;
|
||
|
|
||
|
bool bHasProgressToAcknowledge = false;
|
||
|
|
||
|
// Check if we have any xp notifications to acknowledge
|
||
|
CSharedObjectTypeCache *pXPTypeCache = m_pSOCache->FindBaseTypeCache( CXPSource::k_nTypeID );
|
||
|
if ( pXPTypeCache && pXPTypeCache->GetCount() > 0 )
|
||
|
{
|
||
|
bHasProgressToAcknowledge = true;
|
||
|
}
|
||
|
|
||
|
if ( !steamapicontext || !steamapicontext->SteamUser() )
|
||
|
return;
|
||
|
|
||
|
// Check if the last acknowledged and the current xp are different.
|
||
|
auto nLastAckd = pMatchDesc->m_pProgressionDesc->GetLocalPlayerLastAckdExperience();
|
||
|
auto nCurrent = pMatchDesc->m_pProgressionDesc->GetPlayerExperienceBySteamID( steamapicontext->SteamUser()->GetSteamID() );
|
||
|
|
||
|
if ( nLastAckd != nCurrent )
|
||
|
{
|
||
|
bHasProgressToAcknowledge = true;
|
||
|
}
|
||
|
|
||
|
if ( bHasProgressToAcknowledge )
|
||
|
{
|
||
|
// Send a message acknowledging the XP
|
||
|
CProtoBufMsg<CMsgAcknowledgeXP> msg( k_EMsgGC_AcknowledgeXP );
|
||
|
msg.Body().set_match_group( eMatchGroup );
|
||
|
GCClientSystem()->BSendMessage( msg );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::AcknowledgeNotification( uint32 nAccountID, uint64 ulNotificationID ) const
|
||
|
{
|
||
|
if ( !m_pSOCache )
|
||
|
return;
|
||
|
|
||
|
CSharedObjectTypeCache *pTypeCache = m_pSOCache->FindBaseTypeCache( CTFNotification::k_nTypeID );
|
||
|
if ( pTypeCache && pTypeCache->GetCount() > 0 )
|
||
|
{
|
||
|
// Send a message acknowledging our notifications
|
||
|
ReliableMsgNotificationAcknowledge *pReliable = new ReliableMsgNotificationAcknowledge;
|
||
|
|
||
|
auto &msg = pReliable->Msg().Body();
|
||
|
msg.set_account_id( nAccountID );
|
||
|
msg.set_notification_id( ulNotificationID );
|
||
|
|
||
|
pReliable->Enqueue();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Hang onto the survey request
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::SetSurveyRequest( const CMsgGCSurveyRequest& msgSurveyRequest )
|
||
|
{
|
||
|
m_msgSurveyRequest = msgSurveyRequest;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Send survey response and clear the stored survey request
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFGCClientSystem::SendSurveyResponse( int32 nResponse )
|
||
|
{
|
||
|
Assert( m_msgSurveyRequest.has_question_type() );
|
||
|
|
||
|
GCSDK::CProtoBufMsg< CMsgGCSurveyResponse > msgSurveyResponse( k_EMsgGC_SurveyQuestionResponse );
|
||
|
msgSurveyResponse.Body().set_match_id( m_msgSurveyRequest.match_id() );
|
||
|
msgSurveyResponse.Body().set_question_type( m_msgSurveyRequest.question_type() );
|
||
|
msgSurveyResponse.Body().set_response( nResponse );
|
||
|
|
||
|
if ( this->BSendMessage( msgSurveyResponse ) )
|
||
|
{
|
||
|
ClearSurveyRequest();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::ClearSurveyRequest()
|
||
|
{
|
||
|
m_msgSurveyRequest.Clear();
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::CheckAssociatePartyAndSteamLobby()
|
||
|
{
|
||
|
if ( !m_bUserWantsToBeInMatchmaking )
|
||
|
{
|
||
|
LeaveSteamLobby();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CTFParty *pParty = GetParty();
|
||
|
if ( pParty == NULL && m_eAcceptInviteStep == eAcceptInviteStep_None )
|
||
|
{
|
||
|
// Left party, leave lobby now. We don't do this when we request to leave the party, because the GC may deny to
|
||
|
// drop us if we raced with a match starting (See SendExitMatchmaking)
|
||
|
if ( m_steamIDLobby.IsValid() )
|
||
|
{
|
||
|
LeaveSteamLobby();
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL || steamapicontext->SteamMatchmaking() == NULL )
|
||
|
return; // WAT. How did we create our lobby?
|
||
|
|
||
|
// Let the leader of the party take charge
|
||
|
if ( BIsPartyLeader() )
|
||
|
{
|
||
|
|
||
|
// Make sure we have a Steam lobby. If we don't, initiate creation of one.
|
||
|
int r = CheckSteamLobbyCreated();
|
||
|
if ( r <= 0 )
|
||
|
return; // in progress, or failed
|
||
|
Assert( m_steamIDLobby.IsValid() );
|
||
|
|
||
|
// Do we need to update the party, linking it to the steam lobby?
|
||
|
if ( pParty->GetSteamLobbyID() != m_steamIDLobby )
|
||
|
{
|
||
|
Msg( "Sending GCUpdateParty to associate party %016llX with steam lobby %s\n", pParty->GetGroupID(), m_steamIDLobby.Render() );
|
||
|
CMsgCreateOrUpdateParty *pMsg = GetCreateOrUpdatePartyMsg();
|
||
|
pMsg->set_steam_lobby_id( m_steamIDLobby.ConvertToUint64() );
|
||
|
}
|
||
|
|
||
|
// Do we need to update the steam lobby, linking it to the party?
|
||
|
uint64 nCurrentPartyID = 0;
|
||
|
const char *pszData = steamapicontext->SteamMatchmaking()->GetLobbyData( m_steamIDLobby, k_pszSteamLobbyKey_PartyID );
|
||
|
sscanf( pszData, "%llX", &nCurrentPartyID );
|
||
|
if ( nCurrentPartyID != pParty->GetGroupID() )
|
||
|
{
|
||
|
Msg( "Setting lobby %s data to associate it with party %016llX\n", m_steamIDLobby.Render(), pParty->GetGroupID() );
|
||
|
|
||
|
char rchValue[64];
|
||
|
V_sprintf_safe( rchValue, "%016llX", pParty->GetGroupID() );
|
||
|
steamapicontext->SteamMatchmaking()->SetLobbyData( m_steamIDLobby, k_pszSteamLobbyKey_PartyID, rchValue );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Check if we're not in the lobby associated with this party.
|
||
|
if ( pParty->GetSteamLobbyID() != m_steamIDLobby )
|
||
|
{
|
||
|
|
||
|
// If we're already in a party, get out of it
|
||
|
if ( m_steamIDLobby.IsValid() )
|
||
|
{
|
||
|
Warning( "Leaving steam lobby %s, the party is associated with lobby %s\n", m_steamIDLobby.Render(), pParty->GetSteamLobbyID().Render() );
|
||
|
LeaveSteamLobby();
|
||
|
}
|
||
|
|
||
|
// If the party has a lobby, then join it
|
||
|
if ( pParty->GetSteamLobbyID().IsValid() )
|
||
|
{
|
||
|
|
||
|
// OK, start joining the lobby.
|
||
|
m_steamIDLobby = pParty->GetSteamLobbyID();
|
||
|
Msg( "Joining lobby %s\n", m_steamIDLobby.Render() );
|
||
|
steamapicontext->SteamMatchmaking()->JoinLobby( m_steamIDLobby );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::OnSteamLobbyCreated( LobbyCreated_t *pInfo )
|
||
|
{
|
||
|
Assert( !m_steamIDLobby.IsValid() );
|
||
|
m_eCreateLobbyStatus = pInfo->m_eResult;
|
||
|
if ( m_eCreateLobbyStatus == k_EResultOK )
|
||
|
{
|
||
|
m_steamIDLobby.SetFromUint64( pInfo->m_ulSteamIDLobby );
|
||
|
Msg( "Steam lobby %s created OK\n", m_steamIDLobby.Render() );
|
||
|
|
||
|
// If they already bailed, then destroy the newly created lobby
|
||
|
if ( !m_bUserWantsToBeInMatchmaking )
|
||
|
{
|
||
|
LeaveSteamLobby();
|
||
|
m_eCreateLobbyStatus = k_EResultFail;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Check if we should let the GC know what lobby to associate
|
||
|
// with our party
|
||
|
CheckAssociatePartyAndSteamLobby();
|
||
|
|
||
|
// Check if now we have everything we need to active the UI
|
||
|
CheckReadyToActivateInvite();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Warning( "FAILED to create steam lobby, error code %d\n", m_eCreateLobbyStatus );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::OnSteamGameLobbyJoinRequested( GameLobbyJoinRequested_t *pInfo )
|
||
|
{
|
||
|
Msg( "OnSteamGameLobbyJoinRequested(%s)\n", pInfo->m_steamIDLobby.Render() );
|
||
|
|
||
|
// Transform it into a console command and do it!
|
||
|
char rchCommand[256];
|
||
|
V_sprintf_safe( rchCommand, "connect_lobby %lld", pInfo->m_steamIDLobby.ConvertToUint64() );
|
||
|
engine->ClientCmd_Unrestricted( rchCommand );
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::AcceptFriendInviteToJoinLobby( const CSteamID &steamIDLobby )
|
||
|
{
|
||
|
Msg( "Disconnecting from current server to accept invite\n" );
|
||
|
|
||
|
// Whatever matchmaking we're doing right right now, get out of it.
|
||
|
EndMatchmaking();
|
||
|
|
||
|
// Just queue it to be joined at the earliest opportunity
|
||
|
Msg( "Ready to join steam lobby at next opportunity\n" );
|
||
|
m_steamIDLobbyInviteAccepted = steamIDLobby;
|
||
|
m_eAcceptInviteStep = eAcceptInviteStep_ReadyToJoinSteamLobby;
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::OnSteamLobbyEnter( LobbyEnter_t *pInfo )
|
||
|
{
|
||
|
CSteamID steamIDLobby( pInfo->m_ulSteamIDLobby );
|
||
|
|
||
|
// Are we expecting this?
|
||
|
if ( m_steamIDLobby == steamIDLobby )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if ( m_eAcceptInviteStep != eAcceptInviteStep_JoinSteamLobby )
|
||
|
{
|
||
|
Assert( m_eAcceptInviteStep == eAcceptInviteStep_None );
|
||
|
Assert( BIsPartyLeader() );
|
||
|
return;
|
||
|
}
|
||
|
m_eAcceptInviteStep = eAcceptInviteStep_GetLobbyMetadata;
|
||
|
Assert( !m_steamIDLobby.IsValid() );
|
||
|
|
||
|
// Make sure it succeeded
|
||
|
if ( pInfo->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess )
|
||
|
{
|
||
|
Warning(" Failed to join Steam lobby with error code %d. Cannot join party\n", pInfo->m_EChatRoomEnterResponse );
|
||
|
OnFailedToAcceptInvite();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// We're in a lobby. Remember that, in case we need to bail for some other
|
||
|
// reason.
|
||
|
m_steamIDLobby.SetFromUint64( pInfo->m_ulSteamIDLobby );
|
||
|
Msg( "OnSteamLobbyEnter(%s)\n", m_steamIDLobby.Render() );
|
||
|
Assert( m_steamIDLobby.IsLobby() );
|
||
|
|
||
|
// Request the lobby data
|
||
|
steamapicontext->SteamMatchmaking()->RequestLobbyData( m_steamIDLobby );
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::OnFailedToAcceptInvite()
|
||
|
{
|
||
|
m_eAcceptInviteStep = eAcceptInviteStep_None;
|
||
|
EndMatchmaking();
|
||
|
|
||
|
// !KLUDGE! Well, this is a bit crappy us showing a dialog box in this part of the code...
|
||
|
|
||
|
// IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_accept_invite_fail" );
|
||
|
// if ( pEvent )
|
||
|
// {
|
||
|
// gameeventmanager->FireEventClientSide( pEvent );
|
||
|
// }
|
||
|
|
||
|
ShowMessageBox( "#TF_Matchmaking_AcceptInviteFailTitle", "#TF_Matchmaking_AcceptInviteFailMessage", "#TF_OK" );
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::AddLocalPlayerSOListener( ISharedObjectListener* pListener, bool bImmediately )
|
||
|
{
|
||
|
if ( bImmediately )
|
||
|
{
|
||
|
SubscribeToLocalPlayerSOCache( pListener );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_vecDelayedLocalPlayerSOListenersToAdd.AddToTail( pListener );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::RemoveLocalPlayerSOListener( ISharedObjectListener* pListener )
|
||
|
{
|
||
|
// Remove if it was a delayed add
|
||
|
auto idx = m_vecDelayedLocalPlayerSOListenersToAdd.Find( pListener );
|
||
|
if ( idx != m_vecDelayedLocalPlayerSOListenersToAdd.InvalidIndex() )
|
||
|
{
|
||
|
m_vecDelayedLocalPlayerSOListenersToAdd.Remove( idx );
|
||
|
}
|
||
|
|
||
|
if ( steamapicontext && steamapicontext->SteamUser() )
|
||
|
{
|
||
|
CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
|
||
|
GetGCClient()->RemoveSOCacheListener( steamID, pListener );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CTFGCClientSystem::OnSteamLobbyDataUpdate( LobbyDataUpdate_t *pInfo )
|
||
|
{
|
||
|
CSteamID steamIDLobby( pInfo->m_ulSteamIDLobby );
|
||
|
Msg( "OnSteamLobbyDataUpdate(%s)\n", steamIDLobby.Render() );
|
||
|
|
||
|
// Check if we're in the process of accepting an invite
|
||
|
if ( steamIDLobby != m_steamIDLobby )
|
||
|
{
|
||
|
Assert( steamIDLobby == m_steamIDLobby );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( m_eAcceptInviteStep != eAcceptInviteStep_GetLobbyMetadata )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_eAcceptInviteStep = eAcceptInviteStep_JoinParty;
|
||
|
|
||
|
// Fetch the party ID
|
||
|
uint64 nPartyToJoin = 0;
|
||
|
const char *pszData = steamapicontext->SteamMatchmaking()->GetLobbyData( m_steamIDLobby, k_pszSteamLobbyKey_PartyID );
|
||
|
sscanf( pszData, "%llX", &nPartyToJoin );
|
||
|
if ( nPartyToJoin == 0 )
|
||
|
{
|
||
|
Warning(" No %s metadata set in steam lobby, cannot join party\n", k_pszSteamLobbyKey_PartyID );
|
||
|
OnFailedToAcceptInvite();
|
||
|
return;
|
||
|
}
|
||
|
Msg( "Requesting GC add us to party %016llX\n", nPartyToJoin );
|
||
|
|
||
|
// Join the party.
|
||
|
CProtoBufMsg<CMsgAcceptInvite> msgAcceptInvite( k_EMsgGCAcceptInvite );
|
||
|
msgAcceptInvite.Body().set_party_id( nPartyToJoin );
|
||
|
msgAcceptInvite.Body().set_steamid_lobby( pInfo->m_ulSteamIDLobby );
|
||
|
msgAcceptInvite.Body().set_client_version( engine->GetClientVersion() );
|
||
|
GCClientSystem()->BSendMessage( msgAcceptInvite );
|
||
|
}
|
||
|
|
||
|
class CGCClientAcceptInviteResponse : public GCSDK::CGCClientJob
|
||
|
{
|
||
|
public:
|
||
|
CGCClientAcceptInviteResponse( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
|
||
|
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
{
|
||
|
GCSDK::CProtoBufMsg<CMsgAcceptInviteResponse> msg( pNetPacket );
|
||
|
|
||
|
switch ( msg.Body().result_code() )
|
||
|
{
|
||
|
case k_EResultOK:
|
||
|
case k_EResultDuplicateRequest:
|
||
|
break;
|
||
|
|
||
|
case k_EResultInvalidProtocolVer:
|
||
|
GTFGCClientSystem()->EndMatchmaking();
|
||
|
ShowMessageBox( "#TF_MM_NotCurrentVersionTitle", "#TF_MM_NotCurrentVersionMessage", "#GameUI_OK" );
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
Warning( "Failed to accept invite, result code %d\n", msg.Body().result_code() );
|
||
|
GTFGCClientSystem()->OnFailedToAcceptInvite();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
GC_REG_JOB( GCSDK::CGCClient, CGCClientAcceptInviteResponse, "CGCClientAcceptInviteResponse", k_EMsgGCAcceptInviteResponse, k_EServerTypeGCClient );
|
||
|
|
||
|
void CTFGCClientSystem::OnSteamLobbyChatUpdate( LobbyChatUpdate_t *pInfo )
|
||
|
{
|
||
|
if ( m_steamIDLobby.ConvertToUint64() != pInfo->m_ulSteamIDLobby || !m_steamIDLobby.IsValid() )
|
||
|
return;
|
||
|
CSteamID steamIDWhoChanged( pInfo->m_ulSteamIDUserChanged );
|
||
|
if ( !steamIDWhoChanged.IsValid() || steamIDWhoChanged == steamapicontext->SteamUser()->GetSteamID() )
|
||
|
return;
|
||
|
|
||
|
if ( BChatMemberStateChangeRemoved( pInfo->m_rgfChatMemberStateChange ) )
|
||
|
{
|
||
|
IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_lobby_member_leave" );
|
||
|
if ( !pEvent )
|
||
|
return;
|
||
|
pEvent->SetString( "steamid", CFmtStr("%llu", steamIDWhoChanged.ConvertToUint64() ) );
|
||
|
pEvent->SetInt( "flags", pInfo->m_rgfChatMemberStateChange );
|
||
|
gameeventmanager->FireEventClientSide( pEvent );
|
||
|
}
|
||
|
else if ( pInfo->m_rgfChatMemberStateChange & k_EChatMemberStateChangeEntered )
|
||
|
{
|
||
|
IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_lobby_member_join" );
|
||
|
if ( !pEvent )
|
||
|
return;
|
||
|
pEvent->SetString( "steamid", CFmtStr("%llu", steamIDWhoChanged.ConvertToUint64() ) );
|
||
|
gameeventmanager->FireEventClientSide( pEvent );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void PostChatGameEvent( const CSteamID &steamIDPoster, CTFGCClientSystem::ELobbyMsgType eType, const char *pszText )
|
||
|
{
|
||
|
IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_lobby_chat" );
|
||
|
if ( !pEvent )
|
||
|
return;
|
||
|
pEvent->SetString( "steamid", CFmtStr("%llu", steamIDPoster.ConvertToUint64() ) );
|
||
|
pEvent->SetString( "text", pszText );
|
||
|
pEvent->SetInt( "type", eType );
|
||
|
gameeventmanager->FireEventClientSide( pEvent );
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::OnSteamLobbyChatMsg( LobbyChatMsg_t *pInfo )
|
||
|
{
|
||
|
if ( pInfo->m_eChatEntryType != k_EChatEntryTypeChatMsg )
|
||
|
return;
|
||
|
|
||
|
CSteamID steamIDPoster;
|
||
|
EChatEntryType eEntryType;
|
||
|
int nBufferSize = 2048;
|
||
|
char *pszText = (char *)calloc( nBufferSize + 4, 1 );
|
||
|
int nDataSize = steamapicontext->SteamMatchmaking()->GetLobbyChatEntry( pInfo->m_ulSteamIDLobby, pInfo->m_iChatID, &steamIDPoster, pszText, nBufferSize, &eEntryType );
|
||
|
if ( nDataSize > 0 )
|
||
|
{
|
||
|
PostChatGameEvent( steamIDPoster, ELobbyMsgType(pszText[0]), pszText+1 );
|
||
|
}
|
||
|
|
||
|
free( pszText );
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::SendSteamLobbyChat( ELobbyMsgType eType, const char *pszText )
|
||
|
{
|
||
|
if ( steamapicontext == NULL )
|
||
|
return;
|
||
|
|
||
|
// If we are going to send it to Steam, let's post the message as a result of the callback
|
||
|
// on our own message. That gives them some feedback if their message is not being sent.
|
||
|
if ( m_steamIDLobby.IsValid() )
|
||
|
{
|
||
|
int sz = V_strlen(pszText)+2;
|
||
|
char *temp = new char[sz];
|
||
|
temp[0] = eType;
|
||
|
memcpy( temp+1, pszText, sz-1 );
|
||
|
steamapicontext->SteamMatchmaking()->SendLobbyChatMsg( m_steamIDLobby, temp, sz );
|
||
|
delete[] temp;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
|
||
|
// Not sending to Steam, just echo locally
|
||
|
PostChatGameEvent( steamapicontext->SteamUser()->GetSteamID(), eType, pszText );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::CheckReadyToActivateInvite()
|
||
|
{
|
||
|
|
||
|
// Don't bother, if we don't have a pending action to popup the UI
|
||
|
if ( !m_bWantToActivateInviteUI )
|
||
|
return;
|
||
|
|
||
|
// What high-level state are we in?
|
||
|
switch ( GetMatchmakingUIState() )
|
||
|
{
|
||
|
case eMatchmakingUIState_Chat:
|
||
|
case eMatchmakingUIState_InQueue:
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
Warning( "We missed our opportunity to active the invite UI\n" );
|
||
|
m_bWantToActivateInviteUI = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Make sure we have a Steam lobby. If we don't, initiate creation of one.
|
||
|
int r = CheckSteamLobbyCreated();
|
||
|
if ( r == 0 )
|
||
|
return; // in progress
|
||
|
|
||
|
// Steam lobby creation finished. No matter what, let's make this the last time we
|
||
|
// do this polling work.
|
||
|
m_bWantToActivateInviteUI = false;
|
||
|
|
||
|
// Do we have a steam lobby?
|
||
|
if ( r < 0 )
|
||
|
{
|
||
|
Warning( "Cannot active invite UI. Failed to create Steam Lobby\n" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Assert( m_steamIDLobby.IsValid() );
|
||
|
|
||
|
// Let's invite some of these muthas
|
||
|
Msg( "Activating Steam overlay to process invite to lobby %s\n", m_steamIDLobby.Render() );
|
||
|
steamapicontext->SteamFriends()->ActivateGameOverlayInviteDialog( m_steamIDLobby );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BConnectedToMatchServer( bool bLiveMatch )
|
||
|
{
|
||
|
// A false bLiveMatch means we don't require the lobby and the match to be currently running.
|
||
|
// Meaning, you're connected to the match server, but not necessarily in the match (ie. post-match win podium)
|
||
|
if ( bLiveMatch && !BHaveLiveMatch() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Are we in the process of connecting, or connected to, our assigned server?
|
||
|
if ( m_eConnectState != eConnectState_ConnectedToMatchmade && m_eConnectState != eConnectState_ConnectingToMatchmade )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If steamIDCurrentServer isn't set yet (it only happens late in the connect) default to assuming it's the right
|
||
|
// server if it came from matchmaking. We'll find out otherwise when we finish loading.
|
||
|
if ( !bLiveMatch || !m_steamIDCurrentServer.IsValid() || m_steamIDCurrentServer == m_steamIDGCAssignedMatch )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BHaveLiveMatch() const
|
||
|
{
|
||
|
// NOTE That we don't check the lobby -- SOChanged() and Update() together decide to set or clear our assigned
|
||
|
// match, in a way that considers GC connections being fallible but the gameserver remaining authoritative.
|
||
|
// See comments there.
|
||
|
return m_steamIDGCAssignedMatch.IsValid() && !m_bAssignedMatchEnded;
|
||
|
}
|
||
|
|
||
|
EAbandonGameStatus CTFGCClientSystem::GetAssignedMatchAbandonStatus()
|
||
|
{
|
||
|
// Bootcamp is a magical cannot-trust-the-game-server land that never has abandons.
|
||
|
//
|
||
|
// TODO move this to match description as bNonTrustedMM or something.
|
||
|
if ( m_eAssignedMatchGroup == k_nMatchGroup_MvM_Practice )
|
||
|
{
|
||
|
// Can never abandon from bootcamp
|
||
|
return k_EAbandonGameStatus_Safe;
|
||
|
}
|
||
|
|
||
|
if ( BConnectedToMatchServer( true ) )
|
||
|
{
|
||
|
// Gameserver is authoritative if we're connected to this point.
|
||
|
C_TFPlayer *pTFPlayer = C_TFPlayer::GetLocalTFPlayer();
|
||
|
CTFGameRules *pTFGameRules = TFGameRules();
|
||
|
if ( pTFPlayer && pTFGameRules )
|
||
|
{
|
||
|
bool bLiveMatch = !pTFGameRules->IsManagedMatchEnded();
|
||
|
bool bPenalty = !pTFPlayer->GetMatchSafeToLeave();
|
||
|
if ( !bLiveMatch )
|
||
|
{ return k_EAbandonGameStatus_Safe; }
|
||
|
else if ( bPenalty )
|
||
|
{ return k_EAbandonGameStatus_AbandonWithPenalty; }
|
||
|
else
|
||
|
{ return k_EAbandonGameStatus_AbandonWithoutPenalty; }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Only fall back to looking at the lobby if we're not in the match, or not loaded enough to look at gamerules. The
|
||
|
// gameserver is the authority on MM matches, so this is just getting that data secondhand through the GC.
|
||
|
|
||
|
// TODO(JohnS): Right now, if we have a match via the GC, assume we're required to return. Ideally we'd pipe the
|
||
|
// MatchSafeToLeave flag through the lobby, but right now you'll be told you must return and can find
|
||
|
// out otherwise once you connect.
|
||
|
if ( BHaveLiveMatch() )
|
||
|
{ return k_EAbandonGameStatus_AbandonWithPenalty; }
|
||
|
|
||
|
return k_EAbandonGameStatus_Safe;
|
||
|
}
|
||
|
|
||
|
EMatchGroup CTFGCClientSystem::GetLiveMatchGroup() const
|
||
|
{
|
||
|
if ( !m_bAssignedMatchEnded )
|
||
|
return m_eAssignedMatchGroup;
|
||
|
|
||
|
return k_nMatchGroup_Invalid;
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::RejoinLobby( bool bConfirmed )
|
||
|
{
|
||
|
// Ask to GC to Rejoin the game
|
||
|
// For now try to immediately join
|
||
|
|
||
|
// XXX(JohnS): Ideally we need to craft a BeginMatchmaking() call to put them back into the matchmaking system --
|
||
|
// right now, after a crash, you will lose your MM state and end up at the main menu after a
|
||
|
// crash-rejoin. We cannot just set m_bUserWantsToBeInMatchmaking because this skips most of the other
|
||
|
// BeginMatchmaking() setup like wizard step and results in broken UI
|
||
|
if ( bConfirmed )
|
||
|
{
|
||
|
CTFGSLobby *pLobby = GetLobby();
|
||
|
if ( pLobby )
|
||
|
{
|
||
|
ConnectToServer( pLobby->GetConnect() );
|
||
|
return;
|
||
|
}
|
||
|
// Lobby disappeared
|
||
|
ShowMessageBox( "#TF_MM_Rejoin_FailedTitle", "#TF_MM_Rejoin_FailedBody", "#GameUI_OK" );
|
||
|
Log( "Unable to Rejoin existing Lobby game since the Lobby no longer exists." );
|
||
|
}
|
||
|
|
||
|
// Canceled or no lobby for rejoin
|
||
|
EndMatchmaking( BHaveLiveMatch() ? true : false );
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::JoinMMMatch()
|
||
|
{
|
||
|
CTFGSLobby *pLobby = GetLobby();
|
||
|
if ( pLobby )
|
||
|
{
|
||
|
ConnectToServer( pLobby->GetConnect() );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void CTFGCClientSystem::LeaveGameAndPrepareToJoinParty( GCSDK::PlayerGroupID_t nPartyID )
|
||
|
{
|
||
|
// Remember that we are expecting to join a party
|
||
|
m_nPendingAutoJoinPartyID = nPartyID;
|
||
|
m_bUserWantsToBeInMatchmaking = false;
|
||
|
|
||
|
// Clear any other action that might be in progress
|
||
|
m_bWantToActivateInviteUI = false;
|
||
|
m_msgMatchmakingProgress.Clear();
|
||
|
|
||
|
// Leave current server
|
||
|
engine->ClientCmd_Unrestricted( "disconnect" );
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BIsPhoneVerified( void )
|
||
|
{
|
||
|
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
|
||
|
if ( !pLocalInv )
|
||
|
return false;
|
||
|
|
||
|
if ( !pLocalInv->GetSOC() )
|
||
|
return false;
|
||
|
|
||
|
CEconGameAccountClient *pGameAccountClient = pLocalInv->GetSOC()->GetSingleton< CEconGameAccountClient >();
|
||
|
return ( pGameAccountClient && pGameAccountClient->Obj().has_phone_verified() && pGameAccountClient->Obj().phone_verified() );
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BIsPhoneIdentifying( void )
|
||
|
{
|
||
|
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
|
||
|
if ( !pLocalInv )
|
||
|
return false;
|
||
|
|
||
|
if ( !pLocalInv->GetSOC() )
|
||
|
return false;
|
||
|
|
||
|
CEconGameAccountClient *pGameAccountClient = pLocalInv->GetSOC()->GetSingleton< CEconGameAccountClient >();
|
||
|
return ( pGameAccountClient && pGameAccountClient->Obj().has_phone_identifying() && pGameAccountClient->Obj().phone_identifying() );
|
||
|
}
|
||
|
|
||
|
bool CTFGCClientSystem::BHasCompetitiveAccess( void )
|
||
|
{
|
||
|
CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
|
||
|
if ( !pLocalInv )
|
||
|
return false;
|
||
|
|
||
|
if ( !pLocalInv->GetSOC() )
|
||
|
return false;
|
||
|
|
||
|
CEconGameAccountClient *pGameAccountClient = pLocalInv->GetSOC()->GetSingleton< CEconGameAccountClient >();
|
||
|
return ( pGameAccountClient && pGameAccountClient->Obj().has_competitive_access() && pGameAccountClient->Obj().competitive_access() );
|
||
|
}
|
||
|
|
||
|
class CGCClientMvMVictoryInfo : public GCSDK::CGCClientJob
|
||
|
{
|
||
|
public:
|
||
|
CGCClientMvMVictoryInfo( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
|
||
|
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
{
|
||
|
GCSDK::CProtoBufMsg<CMsgMvMVictoryInfo> msg( pNetPacket );
|
||
|
|
||
|
CTFHudMannVsMachineStatus *pMannVsMachineStatus = GET_HUDELEMENT( CTFHudMannVsMachineStatus );
|
||
|
if ( pMannVsMachineStatus )
|
||
|
{
|
||
|
pMannVsMachineStatus->MVMVictoryGCResponse( msg.Body() );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Warning( "Received CMsgMvMVictoryInfo but CTFHudMannVsMachineStatus does not exist \n" );
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
GC_REG_JOB( GCSDK::CGCClient, CGCClientMvMVictoryInfo, "CGCClientMvMVictoryInfo", k_EMsgGCMvMVictoryInfo, k_EServerTypeGCClient );
|
||
|
|
||
|
|
||
|
class CGCLeaveGameAndPrepareToJoinPartyJob : public GCSDK::CGCClientJob
|
||
|
{
|
||
|
public:
|
||
|
CGCLeaveGameAndPrepareToJoinPartyJob( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
|
||
|
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
{
|
||
|
GCSDK::CProtoBufMsg<CMsgLeaveGameAndPrepareToJoinParty> msg( pNetPacket );
|
||
|
GTFGCClientSystem()->LeaveGameAndPrepareToJoinParty( msg.Body().party_id() );
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
GC_REG_JOB( GCSDK::CGCClient, CGCLeaveGameAndPrepareToJoinPartyJob, "CGCClientMvMVictoryInfo", k_EMsgGCLeaveGameAndPrepareToJoinParty, k_EServerTypeGCClient );
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: GC Msg handler to receive the periodic world status message
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CGCWorldStatusBroadcast : public GCSDK::CGCClientJob
|
||
|
{
|
||
|
public:
|
||
|
CGCWorldStatusBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
|
||
|
|
||
|
virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
|
||
|
{
|
||
|
GCSDK::CProtoBufMsg<CMsgTFWorldStatus> msg( pNetPacket );
|
||
|
|
||
|
GTFGCClientSystem()->SetWorldStatus( msg.Body() );
|
||
|
|
||
|
DevMsg( "TF world status heartbeat.\n Event: %s\n",
|
||
|
msg.Body().beta_stress_test_event_active() ? "Y" : "N" );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
GC_REG_JOB( GCSDK::CGCClient, CGCWorldStatusBroadcast, "CGCWorldStatusBroadcast", k_EMsgGC_WorldStatusBroadcast, GCSDK::k_EServerTypeGCClient );
|
||
|
|
||
|
|
||
|
#if TF_ANTI_IDLEBOT_VERIFICATION
|
||
|
|
||
|
static void GenerateClientVerificationMD5ForItemList( MD5Context_t& out_md5Context, const CUtlVector<const CEconItemView *>& vecItems, bool bIsVerbose = false )
|
||
|
{
|
||
|
FOR_EACH_VEC( vecItems, i )
|
||
|
{
|
||
|
const CEconItemView *pEconItemView = vecItems[i];
|
||
|
Assert( pEconItemView );
|
||
|
|
||
|
CEconItemDescription desc;
|
||
|
desc.SetHashContext( &out_md5Context );
|
||
|
desc.SetVerbose( bIsVerbose );
|
||
|
IEconItemDescription::YieldingFillOutEconItemDescription( &desc, GLocalizationProvider(), pEconItemView );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
CON_COMMAND_F( tf_generate_client_verification, "<itemID0> ... <itemIDn>", FCVAR_CLIENTDLL )
|
||
|
{
|
||
|
CUtlVector<const CEconItemView *> vecItems;
|
||
|
for ( int i = 1; i < args.ArgC(); i++ )
|
||
|
{
|
||
|
#ifdef POSIX
|
||
|
const itemid_t unItemId = static_cast<itemid_t >( atoll( args[i] ) );
|
||
|
#else
|
||
|
const itemid_t unItemId = static_cast<itemid_t >( _atoi64( args[i] ) );
|
||
|
#endif // LINUX
|
||
|
const CEconItemView *pEconItemView = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( unItemId );
|
||
|
if ( !pEconItemView )
|
||
|
{
|
||
|
Msg( "Unable to find item id %llu.\n", unItemId );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
vecItems.AddToTail( pEconItemView );
|
||
|
}
|
||
|
|
||
|
MD5Context_t md5Context;
|
||
|
MD5Init( &md5Context );
|
||
|
GenerateClientVerificationMD5ForItemList( md5Context, vecItems );
|
||
|
}
|
||
|
#endif // DEBUG
|
||
|
|
||
|
class CGCClientHelloResponse : public GCSDK::CGCClientJob
|
||
|
{
|
||
|
public:
|
||
|
CGCClientHelloResponse( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { }
|
||
|
|
||
|
virtual bool BYieldingRunJobFromMsg( IMsgNetPacket *pNetPacket )
|
||
|
{
|
||
|
GCSDK::CProtoBufMsg<CGCMsgTFHelloResponse> msg( pNetPacket );
|
||
|
|
||
|
// Don't send a response if we don't have our inventory from Steam yet. The GC will send down another challenge in
|
||
|
// a few seconds.
|
||
|
if ( !TFInventoryManager()->GetLocalTFInventory()->RetrievedInventoryFromSteam() )
|
||
|
return true;
|
||
|
|
||
|
#ifdef DBEUG
|
||
|
{
|
||
|
Msg( "Beginning response to client verification challenge:\n" );
|
||
|
}
|
||
|
#endif // DEBUG
|
||
|
|
||
|
CUtlVector<const CEconItemView *> vecItems;
|
||
|
for ( int i = 0; i < msg.Body().version_checksum_size(); i++ )
|
||
|
{
|
||
|
const itemid_t unItemId = msg.Body().version_checksum(i);
|
||
|
const CEconItemView *pEconItemView = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( unItemId );
|
||
|
if ( !pEconItemView )
|
||
|
{
|
||
|
// The GC thought we had this item but we don't agree. It should be impossible to mess with the session on the
|
||
|
// GC to add/remove items from the SO cache while we've got the lock we use to build the challenge, but we might
|
||
|
// have traded away an item *after* that. For this, we ignore the challenge and hope the GC sends us down another
|
||
|
// one with our new current inventory.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vecItems.AddToTail( pEconItemView );
|
||
|
}
|
||
|
|
||
|
bool bIsVerbose = false;
|
||
|
if ( msg.Body().has_version_verbose() )
|
||
|
{
|
||
|
bIsVerbose = msg.Body().version_verbose();
|
||
|
}
|
||
|
|
||
|
MD5Context_t md5Context;
|
||
|
MD5Init( &md5Context );
|
||
|
GenerateClientVerificationMD5ForItemList( md5Context, vecItems, bIsVerbose );
|
||
|
GCSDK::CProtoBufMsg<CGCMsgTFSync> msgResponse( k_EMsgGC_ClientVerificationChallengeResponse );
|
||
|
|
||
|
MD5Context_t md5ContextEx = md5Context;
|
||
|
MD5Value_t md5ResultEx;
|
||
|
MD5Final( &md5ResultEx.bits[0], &md5ContextEx );
|
||
|
msgResponse.Body().set_version_checksum_ex( &md5ResultEx.bits[0], MD5_DIGEST_LENGTH );
|
||
|
|
||
|
const unsigned int unRandomSeed = msg.Body().version_check();
|
||
|
|
||
|
int key;
|
||
|
if ( (*((bool *)g_pClientPurchaseInterface - 156) ) )
|
||
|
{
|
||
|
key = kTFDescriptionHash_TextmodeArbitraryKey;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int iInstanceCount = engine->GetInstancesRunningCount();
|
||
|
key = iInstanceCount > 1 ? kTFDescriptionHash_MultiRunArbitraryKey : kTFDescriptionHash_ValidArbitraryKey;
|
||
|
}
|
||
|
|
||
|
const unsigned int unMungedRandomSeed = unRandomSeed + key;
|
||
|
TFDescription_HashDataMunge( &md5Context, unMungedRandomSeed, bIsVerbose, VarArgs( "%d", unMungedRandomSeed ) );
|
||
|
|
||
|
MD5Value_t md5Result;
|
||
|
MD5Final( &md5Result.bits[0], &md5Context );
|
||
|
|
||
|
msgResponse.Body().set_version_checksum( &md5Result.bits[0], MD5_DIGEST_LENGTH );
|
||
|
|
||
|
// What language are we running in? We need to send this up so the GC will localize with the same strings we're using.
|
||
|
char uilanguage[ 64 ];
|
||
|
uilanguage[0] = 0;
|
||
|
engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
|
||
|
msgResponse.Body().set_version_check( PchLanguageToELanguage( uilanguage ) );
|
||
|
|
||
|
// Send back up the challenge identifier to maintain sync.
|
||
|
msgResponse.Body().set_version_check_ex( unRandomSeed ^ kTFDescriptionHash_ChallengeXorShenanigans );
|
||
|
|
||
|
GCClientSystem()->BSendMessage( msgResponse );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
GC_REG_JOB( GCSDK::CGCClient, CGCClientHelloResponse, "CGCClientHelloResponse", k_EMsgGC_ClientVerificationChallenge, k_EServerTypeGCClient );
|
||
|
|
||
|
#endif // TF_ANTI_IDLEBOT_VERIFICATION
|
||
|
|
||
|
#ifdef TF_GC_PING_DEBUG
|
||
|
// Ping debug commands for spoofin'
|
||
|
CON_COMMAND( tf_datacenter_ping_override, "Override the ping data we'll report for a specific datacenter." )
|
||
|
{
|
||
|
if ( args.ArgC() != 4 )
|
||
|
{
|
||
|
ConMsg( "Usage: tf_datacenter_ping_override <datacenter> <ping> <status>\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const char *pszDC = args[1];
|
||
|
uint32 nPing = (uint32)Clamp( V_atoi( args[2] ), 0, INT_MAX );
|
||
|
CMsgGCDataCenterPing_Update_Status eStatus = (CMsgGCDataCenterPing_Update_Status)V_atoi( args[3] );
|
||
|
GTFGCClientSystem()->SetPingOverride( pszDC, nPing, eStatus );
|
||
|
|
||
|
ConMsg( "Started overriding datacenter \"%s\" to %ums ping with status %i (enum)\n"
|
||
|
"Forcing a ping refresh to submit new data with this override\n",
|
||
|
pszDC, nPing, eStatus );
|
||
|
}
|
||
|
|
||
|
CON_COMMAND( tf_datacenter_clear_ping_override, "Stop overriding ping data." )
|
||
|
{
|
||
|
if ( args.ArgC() != 1 )
|
||
|
{
|
||
|
ConMsg( "Usage: tf_datacenter_clear_ping_override\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GTFGCClientSystem()->ClearPingOverrides();
|
||
|
|
||
|
ConMsg( "Stopped overriding any datacenter ping data\n" );
|
||
|
}
|
||
|
#endif
|