//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "tf_wardata.h" #include "gcsdk/enumutils.h" #include "schemainitutils.h" #ifdef CLIENT_DLL #include "c_tf_player.h" #endif #ifdef GAME_DLL #include "tf_player.h" #endif using namespace GCSDK; //----------------------------------------------------------------------------- // Purpose: Get user's War Data by steamID and war ID. Returns NULL if it // doesnt exist or if the war is inactive and bLoadEvenIfWarInactive // is false. On the GC bLoadSOCacheIfNeeded will load the user's // SOCache if needed in order to try and get their war data //----------------------------------------------------------------------------- CWarData* GetPlayerWarData( const CSteamID& steamID, war_definition_index_t warDefIndex, bool bLoadEvenIfWarInactive #ifdef GC_DLL , bool bLoadSOCacheIfNeeded #endif ) { const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( warDefIndex ); // If the war isn't active and we weren't told to ignore that, then return NULL if ( !pWarDef || ( !bLoadEvenIfWarInactive && !pWarDef->IsActive() ) ) return NULL; #ifdef GC_DLL CGCSharedObjectCache *pSOCache = GGCBase()->FindSOCache( steamID ); // Load their SO cache if needed if ( pSOCache == NULL && bLoadSOCacheIfNeeded ) { pSOCache = GGCBase()->YieldingFindOrLoadSOCache( steamID ); } #else GCSDK::CGCClientSharedObjectCache *pSOCache = GCClientSystem()->GetSOCache( steamID ); #endif if ( pSOCache ) { auto *pTypeCache = pSOCache->FindTypeCache( CWarData::k_nTypeID ); if ( pTypeCache ) { int nCount = pTypeCache->GetCount(); for( int i=0; i < nCount; ++i ) { CWarData* pWarData = static_cast< CWarData* >( pTypeCache->GetObject( i ) ); if ( pWarData->Obj().war_id() == warDefIndex ) return pWarData; } } } return NULL; } #ifdef CLIENT_DLL CWarData* GetLocalPlayerWarData( war_definition_index_t warDefIndex ) { if ( !steamapicontext || !steamapicontext->SteamUser() ) { return NULL; } return GetPlayerWarData( steamapicontext->SteamUser()->GetSteamID(), warDefIndex, true ); } #endif CWarDefinition::CWarDefinition() : m_nDefIndex( INVALID_WAR_DEF_INDEX ) , m_pszLocalizedWarname( NULL ) , m_pszDefName( NULL ) , m_mapSides( DefLessFunc( SidesMap_t::KeyType_t ) ) , m_rtTimeEnd( 0 ) , m_rtTimeStart( 0 ) {} bool CWarDefinition::BInitFromKV( KeyValues *pKV, CUtlVector *pVecErrors ) { m_nDefIndex = atoi( pKV->GetName() ); SCHEMA_INIT_CHECK( m_nDefIndex != INVALID_WAR_DEF_INDEX, "Missing 'war_id' for war def %s", pKV->GetName() ); m_pszDefName = pKV->GetString( "name" ); SCHEMA_INIT_CHECK( m_nDefIndex != INVALID_WAR_DEF_INDEX, "Missing 'name' for war def %s", pKV->GetName() ); const char *pszTime = pKV->GetString( "start_time", NULL ); SCHEMA_INIT_CHECK( pszTime != NULL, "war definition %s does not have 'start_time'", pKV->GetName() ); m_rtTimeStart = ( pszTime && pszTime[0] ) ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszTime ) : RTime32(0); pszTime = pKV->GetString( "end_time", NULL ); SCHEMA_INIT_CHECK( pszTime != NULL, "war definition %s does not have 'end_time'", pKV->GetName() ); m_rtTimeEnd = ( pszTime && pszTime[0] ) ? CRTime::RTime32FromFmtString( "YYYY-MM-DD hh:mm:ss" , pszTime ) : RTime32(0); KeyValues* pKVSidesBlock = pKV->FindKey( "sides" ); FOR_EACH_TRUE_SUBKEY( pKVSidesBlock, pKVSide ) { auto& side = m_mapSides[m_mapSides.Insert( atoi( pKVSide->GetName() ) ) ]; side.BInitFromKV( pKV->GetName(), pKVSide, pVecErrors ); } m_pszLocalizedWarname = pKV->GetString( "localized_name", NULL ); SCHEMA_INIT_CHECK( m_pszLocalizedWarname != NULL, "war definition %s does not have 'localized_name'", pKV->GetName() ); return SCHEMA_INIT_SUCCESS(); } const CWarDefinition::CWarSideDefinition_t* CWarDefinition::GetSide( war_side_t nSide ) const { if ( IsValidSide( nSide ) ) { SidesMap_t::IndexType_t idx = m_mapSides.Find( nSide ); if ( idx != m_mapSides.InvalidIndex() ) { return &m_mapSides[ idx ]; } } return NULL; } bool CWarDefinition::IsActive() const { Assert( m_rtTimeEnd != 0 ); Assert( m_rtTimeStart != 0 ); if ( m_rtTimeEnd == 0 || m_rtTimeStart == 0 ) return false; return CRTime::RTime32TimeCur() > m_rtTimeStart && CRTime::RTime32TimeCur() < m_rtTimeEnd; } bool CWarDefinition::IsValidSide( war_side_t nSide ) const { FOR_EACH_MAP_FAST( m_mapSides, i ) { if ( m_mapSides[i].m_nSideIndex == nSide ) return true; } return false; } bool CWarDefinition::CWarSideDefinition_t::BInitFromKV( const char* pszContainingWarName, KeyValues *pKVSide, CUtlVector *pVecErrors ) { m_nSideIndex = (war_side_t)atoi( pKVSide->GetName() ); SCHEMA_INIT_CHECK( m_nSideIndex != INVALID_WAR_SIDE, "war definition %s has invalid side index: %d", pszContainingWarName, m_nSideIndex ); m_pszLocalizedName = pKVSide->GetString( "localized_name", NULL ); SCHEMA_INIT_CHECK( m_pszLocalizedName != NULL, "war definition %s side %s missing side localization name", pszContainingWarName, pKVSide->GetName() ); m_pszLeaderboardName = pKVSide->GetString( "leaderboard_name", NULL ); SCHEMA_INIT_CHECK( m_pszLocalizedName != NULL, "war definition %s side %s missing side leaderboard name", pszContainingWarName, pKVSide->GetName() ); //TODO BRETT: Grab the leaderboard now? return SCHEMA_INIT_SUCCESS(); } CWarData::CWarData() { Obj().set_account_id( 0 ); Obj().set_war_id( INVALID_WAR_DEF_INDEX ); Obj().set_affiliation( INVALID_WAR_SIDE ); Obj().set_points_scored( 0 ); } #ifdef GC IMPLEMENT_CLASS_MEMPOOL( CWarData, 1000, UTLMEMORYPOOL_GROW_SLOW ); // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" CWarData::CWarData( uint32 unAccountID, war_definition_index_t eWarID, war_side_t eSide ) { Obj().set_account_id( unAccountID ); Obj().set_war_id( eWarID ); Obj().set_affiliation( eSide ); Obj().set_points_scored( 0 ); } bool CWarData::BYieldingAddInsertToTransaction( GCSDK::CSQLAccess & sqlAccess ) { CSchWarData schWarData; WriteToRecord( &schWarData ); return CSchemaSharedObjectHelper::BYieldingAddInsertToTransaction( sqlAccess, &schWarData ); } bool CWarData::BYieldingAddWriteToTransaction( GCSDK::CSQLAccess & sqlAccess, const CUtlVector< int > &fields ) { CSchWarData schWarData; WriteToRecord( &schWarData ); CColumnSet csDatabaseDirty( schWarData.GetPSchema()->GetRecordInfo() ); csDatabaseDirty.MakeEmpty(); FOR_EACH_VEC( fields, nField ) { switch ( fields[nField] ) { case CSOWarData::kAccountIdFieldNumber : csDatabaseDirty.BAddColumn( CSchWarData::k_iField_unAccountID ); break; case CSOWarData::kWarIdFieldNumber : csDatabaseDirty.BAddColumn( CSchWarData::k_iField_unWarID ); break; case CSOWarData::kAffiliationFieldNumber : csDatabaseDirty.BAddColumn( CSchWarData::k_iField_unAffiliation ); break; case CSOWarData::kPointsScoredFieldNumber : csDatabaseDirty.BAddColumn( CSchWarData::k_iField_unPointsScored ); break; default: Assert( false ); } } return CSchemaSharedObjectHelper::BYieldingAddWriteToTransaction( sqlAccess, &schWarData, csDatabaseDirty ); } bool CWarData::BYieldingAddRemoveToTransaction( GCSDK::CSQLAccess & sqlAccess ) { CSchWarData schDuelSummary; WriteToRecord( &schDuelSummary ); return CSchemaSharedObjectHelper::BYieldingAddRemoveToTransaction( sqlAccess, &schDuelSummary ); } void CWarData::WriteToRecord( CSchWarData *pWarData ) const { pWarData->m_unAccountID = Obj().account_id(); pWarData->m_unWarID = Obj().war_id(); pWarData->m_unAffiliation = Obj().affiliation(); pWarData->m_unPointsScored = Obj().points_scored(); } void CWarData::ReadFromRecord( const CSchWarData & warData ) { Obj().set_account_id( warData.m_unAccountID ); Obj().set_war_id( warData.m_unWarID ); Obj().set_affiliation( warData.m_unAffiliation ); Obj().set_points_scored( warData.m_unPointsScored ); } #endif #if defined( CLIENT_DLL ) || defined( GC ) CTFWarGlobalDataHelper::CTFWarGlobalDataHelper() : m_bInitialized( false ) , m_mapWarStats( DefLessFunc( WarStatsMap_t::KeyType_t ) ) #ifdef CLIENT_DLL , m_flLastUpdateRequest( 0.f ) , m_flLastUpdated( 0.f ) #endif { #ifdef CLIENT_DLL Init(); #endif } CGCMsgGC_War_GlobalStatsResponse* CTFWarGlobalDataHelper::FindOrCreateWarData( war_definition_index_t nWarDef, bool bCreateIfDoesntExist ) { const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( nWarDef ); if ( !pWarDef ) return NULL; if ( nWarDef == INVALID_WAR_DEF_INDEX ) return NULL; CGCMsgGC_War_GlobalStatsResponse* pGlobalData = NULL; auto waridx = m_mapWarStats.Find( (war_definition_index_t)nWarDef ); if ( waridx == m_mapWarStats.InvalidIndex() && bCreateIfDoesntExist ) { waridx = m_mapWarStats.Insert( nWarDef ); m_mapWarStats[ waridx ].set_war_id( nWarDef ); } if ( waridx != m_mapWarStats.InvalidIndex() ) { pGlobalData = &m_mapWarStats[ waridx ]; } return pGlobalData; } CGCMsgGC_War_GlobalStatsResponse_SideScore* CTFWarGlobalDataHelper::FindOrCreateWarDataSide( war_side_t nWarSide, war_definition_index_t nWarDef, bool bCreateIfDoesntExist ) { // Make sure it's a valid war const CWarDefinition* pWarDef = GetItemSchema()->GetWarDefinitionByIndex( nWarDef ); if ( !pWarDef ) return NULL; // Valid side on a valid awr if ( !pWarDef->IsValidSide( nWarSide ) ) return NULL; // Find or create the war stats CGCMsgGC_War_GlobalStatsResponse* pWarStats = FindOrCreateWarData( nWarDef, bCreateIfDoesntExist ); if ( !pWarStats ) return NULL; // Find or create the side for the war CGCMsgGC_War_GlobalStatsResponse_SideScore* pSide = NULL; for( int i=0; i < pWarStats->side_scores_size(); ++i ) { if ( pWarStats->side_scores( i ).side() == nWarSide ) { pSide = pWarStats->mutable_side_scores( i ); break; } } // Didn't already exist. Create and initialize if ( pSide == NULL && bCreateIfDoesntExist ) { pSide = pWarStats->add_side_scores(); pSide->set_score( 0 ); pSide->set_side( nWarSide ); } return pSide; } //----------------------------------------------------------------------------- // Purpose: On the GC, grab all records for the war and tally up the global stats // On the client, send a request for what the GC has //----------------------------------------------------------------------------- void CTFWarGlobalDataHelper::Init() { #ifdef GC TSQLCmdStr sStatement; const CColumnSet csCountsCount = CSET_FULL( CSchWarData ); BuildSelectStatementText( &sStatement, csCountsCount ); TSQLCmdStr sLoadQuery; CSQLAccess sqlAccess; if( !sqlAccess.BYieldingExecute( "CTFWarGlobalDataHelper::Init", sLoadQuery ) ) { EmitError( SPEW_GC, __FUNCTION__": Failed to run load query!\n" ); return; } CUtlVector< CSchWarData > vecRecords; if ( !sqlAccess.BYieldingReadRecordsWithQuery< CSchWarData >( &vecRecords, sStatement, csCountsCount ) ) { EmitError( SPEW_GC, __FUNCTION__": Failed to read spy vs engy points!\n" ); return; } // Tally up points for each side FOR_EACH_VEC( vecRecords, i ) { const CSchWarData& record = vecRecords[i]; AddToSideScore( record.m_unWarID, record.m_unAffiliation, record.m_unPointsScored ); } m_bInitialized = true; #else RequestUpdateGlobalStats(); RequestLeaderboard(); #endif } //----------------------------------------------------------------------------- // Purpose: Set the stats. We're initialized once we do this. //----------------------------------------------------------------------------- void CTFWarGlobalDataHelper::SetGlobalStats( const CGCMsgGC_War_GlobalStatsResponse& newData ) { #ifdef CLIENT_DLL m_flLastUpdated = Plat_FloatTime(); #endif m_bInitialized = true; CGCMsgGC_War_GlobalStatsResponse* pExistingData = FindOrCreateWarData( newData.war_id(), true ); if ( pExistingData ) { pExistingData->CopyFrom( newData ); } #ifdef CLIENT_DLL IGameEvent *event = gameeventmanager->CreateEvent( "global_war_data_updated" ); if ( event ) { gameeventmanager->FireEventClientSide( event ); } #endif } void CTFWarGlobalDataHelper::AddToSideScore( war_definition_index_t nWar, war_side_t nSide, uint32 nValue ) { Assert( nWar != INVALID_WAR_DEF_INDEX ); Assert( nSide != INVALID_WAR_SIDE ); CGCMsgGC_War_GlobalStatsResponse_SideScore* pSide = FindOrCreateWarDataSide( nSide, nWar, true ); if ( pSide ) { pSide->set_score( pSide->score() + nValue ); } } //----------------------------------------------------------------------------- // Purpose: Grab the Spy stats. Request an update if we're on the client and stale //----------------------------------------------------------------------------- uint64 CTFWarGlobalDataHelper::GetGlobalSideScore( war_definition_index_t nWar, war_side_t nSide ) { Assert( nWar != INVALID_WAR_DEF_INDEX ); Assert( nSide != INVALID_WAR_SIDE ); #ifdef CLIENT_DLL CheckGlobalStatsStaleness(); #endif CGCMsgGC_War_GlobalStatsResponse_SideScore* pSide = FindOrCreateWarDataSide( nSide, nWar, true ); if ( pSide ) { return pSide->score(); } Assert( false ); return 0; } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: Ask the GC for the global stats. //----------------------------------------------------------------------------- void CTFWarGlobalDataHelper::RequestUpdateGlobalStats() { // Ask for new war stats GCSDK::CProtoBufMsg msg( k_EMsgGC_War_RequestGlobalStats ); msg.Body().set_war_id( PYRO_VS_HEAVY_WAR_DEF_INDEX ); // TODO Brett: Get all the war data GCClientSystem()->BSendMessage( msg ); m_flLastUpdateRequest = Plat_FloatTime(); } //----------------------------------------------------------------------------- // Purpose: Checks if we're "stale". If so, request new stats from the GC. //----------------------------------------------------------------------------- void CTFWarGlobalDataHelper::CheckGlobalStatsStaleness() { float flTimeSinceLastRequest = Plat_FloatTime() - m_flLastUpdateRequest; if ( flTimeSinceLastRequest > 30.f ) { RequestUpdateGlobalStats(); } } //----------------------------------------------------------------------------- // Purpose: Requests the war leaderboard //----------------------------------------------------------------------------- void CTFWarGlobalDataHelper::RequestLeaderboard() { //TODO BRETT loop all the wars and grab each one /*if ( steamapicontext && steamapicontext->SteamUserStats() ) { CSteamID steamID = steamapicontext->SteamUser()->GetSteamID(); GCSDK::CGCClientSharedObjectCache *pSOCache = GCClientSystem()->GetSOCache( steamID ); if ( pSOCache ) { GCSDK::CGCClientSharedObjectTypeCache *pTypeCache = pSOCache->FindTypeCache( CWarData::k_nTypeID ); if ( pTypeCache ) { CWarData *pWarData = (CWarData*)pTypeCache->GetObject( 0 ); if ( pWarData ) { eSide = (EWarSides)pWarData->Obj().affiliation(); } } } if ( eSide != k_EInvalidSide ) { SteamAPICall_t apicall = steamapicontext->SteamUserStats()->FindLeaderboard( eSide == k_ESpy ? k_pszSpyVsEngyLeaderboard_Spy : k_pszSpyVsEngyLeaderboard_Engy ); m_findLeaderboardCallback.Set( apicall, this, &CTFWarGlobalDataHelper::OnFindLeaderboard ); } }*/ } //----------------------------------------------------------------------------- // Purpose: Requests the war leaderboard //----------------------------------------------------------------------------- void CTFWarGlobalDataHelper::OnFindLeaderboard( LeaderboardFindResult_t *pResult, bool bIOFailure ) { m_findLeaderboardResults = *pResult; DownloadLeaderboard(); } void CTFWarGlobalDataHelper::DownloadLeaderboard() { if ( m_findLeaderboardResults.m_bLeaderboardFound ) { // friends SteamAPICall_t apicall = steamapicontext->SteamUserStats()->DownloadLeaderboardEntries( m_findLeaderboardResults.m_hSteamLeaderboard, k_ELeaderboardDataRequestFriends, 1, 10 ); downloadLeaderboardCallbackFriends.Set( apicall, this, &CTFWarGlobalDataHelper::OnLeaderboardScoresDownloaded_Friends ); // global around user apicall = steamapicontext->SteamUserStats()->DownloadLeaderboardEntries( m_findLeaderboardResults.m_hSteamLeaderboard, k_ELeaderboardDataRequestGlobal, 1, 10 ); downloadLeaderboardCallbackGlobal.Set( apicall, this, &CTFWarGlobalDataHelper::OnLeaderboardScoresDownloaded_Global ); } } static void RetrieveLeaderboardEntries( LeaderboardScoresDownloaded_t &scores, CTFWarGlobalDataHelper::LeaderBoardEntries_t &entries ) { entries.m_bInitialized = true; entries.m_vecEntries.PurgeAndDeleteElements(); entries.m_vecEntries.EnsureCapacity( scores.m_cEntryCount ); for ( int i = 0; i < scores.m_cEntryCount; ++i ) { LeaderboardEntry_t *leaderboardEntry = new LeaderboardEntry_t; if ( steamapicontext->SteamUserStats()->GetDownloadedLeaderboardEntry( scores.m_hSteamLeaderboardEntries, i, leaderboardEntry, NULL, 0 ) ) { entries.m_vecEntries.AddToTail( leaderboardEntry ); } } } void CTFWarGlobalDataHelper::OnLeaderboardScoresDownloaded_Global( LeaderboardScoresDownloaded_t *pResult, bool bIOFailure ) { RetrieveLeaderboardEntries( *pResult, downloadedLeaderboardScoresGlobal ); } void CTFWarGlobalDataHelper::OnLeaderboardScoresDownloaded_Friends( LeaderboardScoresDownloaded_t *pResult, bool bIOFailure ) { RetrieveLeaderboardEntries( *pResult, downloadedLeaderboardScoresFriends ); } class CGC_War_GlobalStatsResponse : public GCSDK::CGCClientJob { public: CGC_War_GlobalStatsResponse( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) { } virtual bool BYieldingRunJobFromMsg( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CProtoBufMsg< CGCMsgGC_War_GlobalStatsResponse > msg( pNetPacket ); GetWarData().SetGlobalStats( msg.Body() ); return true; } }; GC_REG_JOB( GCSDK::CGCClient, CGC_War_GlobalStatsResponse, "CGC_War_GlobalStatsResponse", k_EMsgGC_War_GlobalStatsResponse, GCSDK::k_EServerTypeGCClient ); #endif CTFWarGlobalDataHelper& GetWarData() { #ifdef GC return GGCTF()->GetGlobalWarData(); #else static CTFWarGlobalDataHelper s_WarData; return s_WarData; #endif } #endif // defined( CLIENT_DLL ) || defined( GC )