//==== Copyright © 1996-2005, Valve Corporation, All rights reserved. =========// // // Purpose: // //=============================================================================// #include "cbase.h" #include "fmtstr.h" #if defined( INCLUDE_SCALEFORM ) #include "basepanel.h" #include "leaderboardsdialog_scaleform.h" #include #ifdef _X360 #include "xbox/xbox_launch.h" #include "ixboxsystem.h" #include "../../common/xlast_csgo/csgo.spa.h" #endif #include "keyvalues.h" #include "engineinterface.h" #include "modinfo.h" #include "gameui_interface.h" #include "tier1/utlbuffer.h" #include "filesystem.h" using namespace vgui; // for SRC #include // memdbgon must be the last include file in a .cpp file!!! #include #define LEADERBOARD_SHOW_RANK_UNDER 1000 CCreateLeaderboardsDialogScaleform* CCreateLeaderboardsDialogScaleform::m_pInstance = NULL; SFUI_BEGIN_GAME_API_DEF SFUI_DECL_METHOD( OnOk ), SFUI_DECL_METHOD( SetQuery ), SFUI_DECL_METHOD( Query_NumResults ), SFUI_DECL_METHOD( Query_GetCurrentPlayerRow ), SFUI_DECL_METHOD( QueryRow_GamerTag ), SFUI_DECL_METHOD( QueryRow_ColumnValue ), SFUI_DECL_METHOD( QueryRow_ColumnRatio ), SFUI_DECL_METHOD( DisplayUserInfo ), SFUI_END_GAME_API_DEF( CCreateLeaderboardsDialogScaleform, LeaderBoards ) ; // For tracking the last panel the user viewed on this screen ConVar player_last_leaderboards_panel( "player_last_leaderboards_panel", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_GAMECONSOLE | FCVAR_SS, "Last opened panel in the Leaderboards screen" ); ConVar player_last_leaderboards_mode( "player_last_leaderboards_mode", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_GAMECONSOLE | FCVAR_SS, "Last mode setting in the Leaderboards screen" ); ConVar player_last_leaderboards_filter( "player_last_leaderboards_filter", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_GAMECONSOLE | FCVAR_SS, "Last mode setting in the Leaderboards screen" ); const float QUERY_DELAY_TIME = 0.5f; const float QUERY_SHORT_DELAY_TIME = 0.35f; // Enable this define for asynch debug output // #define DIAGNOSE_ASYNCH_PROBLEMS 1 void CCreateLeaderboardsDialogScaleform::LoadDialog( void ) { if ( !m_pInstance ) { STEAMWORKS_TESTSECRETALWAYS(); m_pInstance = new CCreateLeaderboardsDialogScaleform( ); SFUI_REQUEST_ELEMENT( SF_FULL_SCREEN_SLOT, g_pScaleformUI, CCreateLeaderboardsDialogScaleform, m_pInstance, LeaderBoards ); } } void CCreateLeaderboardsDialogScaleform::UnloadDialog( void ) { if ( m_pInstance ) { m_pInstance->RemoveFlashElement(); } } void CCreateLeaderboardsDialogScaleform::UpdateDialog( void ) { if ( m_pInstance ) { m_pInstance->Tick(); } } CCreateLeaderboardsDialogScaleform::CCreateLeaderboardsDialogScaleform( void ) : m_hAsyncQuery(0), m_currentFilterType(eLBFilter_Overall), m_startingRowIndex(-1), m_rowsPerPage(0), m_bCheckForQueryResults(false), m_iNumFriends(0), m_iNextFriend(-1), m_bEnumeratingFriends(false), m_bResultsValid(false), m_iTotalViewRows(0) { ACTIVE_SPLITSCREEN_PLAYER_GUARD( GET_ACTIVE_SPLITSCREEN_SLOT() ); m_iPlayerSlot = XBX_GetActiveUserId(); IPlayerLocal *pProfile = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( m_iPlayerSlot ); if ( pProfile ) m_PlayerXUID = pProfile->GetXUID(); #if defined (_X360) m_pResultsBuffer = NULL; m_pFriends = NULL; ZeroMemory( m_pFriendsResult, sizeof(m_pFriendsResult[0]) * (MAX_FRIENDS+1) ); #endif // _X360 #if !defined( NO_STEAM ) m_currentLeaderboardHandle = (SteamLeaderboard_t)0; m_currentLeaderboardName = NULL; V_memset( &m_cachedLeaderboardScores, 0, sizeof(LeaderboardScoresDownloaded_t) ); m_pLeaderboardDescription = NULL; V_memset( m_payloadSizes, 0, sizeof(int)*kMaxPayloadEntries ); SetDefLessFunc( m_LeaderboardHandles ); #endif // !NO_STEAM } void CCreateLeaderboardsDialogScaleform::OnOk( SCALEFORM_CALLBACK_ARGS_DECL ) { CCreateLeaderboardsDialogScaleform::UnloadDialog(); } void CCreateLeaderboardsDialogScaleform::FlashLoaded( void ) { if ( FlashAPIIsValid() ) { m_pScaleformUI->Value_InvokeWithoutReturn( m_FlashAPI, "InitDialogData", NULL, 0 ); } } void CCreateLeaderboardsDialogScaleform::FlashReady( void ) { SFVALUE PanelHandle = m_pScaleformUI->Value_GetMember( m_FlashAPI, "ScoreBoard" ); if ( PanelHandle ) { SFVALUE AnimatedPanelHandle = m_pScaleformUI->Value_GetMember( PanelHandle, "Panel" ); if ( AnimatedPanelHandle ) { // Grab any references you need now... m_pScaleformUI->ReleaseValue( AnimatedPanelHandle ); } m_pScaleformUI->ReleaseValue( PanelHandle ); } m_bCheckForQueryResults = false; m_bResultsValid = false; m_iNumFriends = 0; m_iNextFriend = -1; #if defined (_X360) m_pResultsBuffer = NULL; m_pFriends = NULL; ZeroMemory( m_pFriendsResult, sizeof(m_pFriendsResult[0]) * (MAX_FRIENDS+1) ); #endif // _X360 Show(); } bool CCreateLeaderboardsDialogScaleform::PreUnloadFlash( void ) { return true; } void CCreateLeaderboardsDialogScaleform::PostUnloadFlash( void ) { #ifdef _X360 // release the async handle if ( m_hAsyncQuery != 0 ) { xboxsystem->CancelOverlappedOperation( m_hAsyncQuery ); xboxsystem->ReleaseAsyncHandle( m_hAsyncQuery ); } // Wipe out any previous results if ( m_pResultsBuffer != NULL ) { delete [] m_pResultsBuffer; m_pResultsBuffer = NULL; } if ( m_pFriends ) { delete [] m_pFriends; m_pFriends = NULL; // Free the structures that track our friends' statistics for ( int idx = 0; idx < MAX_FRIENDS+1; ++idx ) { if ( m_pFriendsResult[idx] != NULL ) { delete [] m_pFriendsResult[idx]; m_pFriendsResult[idx] = NULL; } } } #endif // _X360 #if !defined( NO_STEAM ) if ( !m_pLeaderboardDescription ) m_pLeaderboardDescription->deleteThis(); #endif // !NO_STEAM if ( GameUI().IsInLevel() ) { BasePanel()->RestorePauseMenu(); } else { BasePanel()->RestoreMainMenuScreen(); } m_pInstance = NULL; delete this; } void CCreateLeaderboardsDialogScaleform::Show( void ) { if ( FlashAPIIsValid() ) { WITH_SLOT_LOCKED { ScaleformUI()->Value_InvokeWithoutReturn( m_FlashAPI, "showPanel", 0, NULL ); } } } void CCreateLeaderboardsDialogScaleform::Hide( void ) { if ( FlashAPIIsValid() ) { WITH_SLOT_LOCKED { ScaleformUI()->Value_InvokeWithoutReturn( m_FlashAPI, "hidePanel", 0, NULL ); } } } void CCreateLeaderboardsDialogScaleform::Tick( void ) { if ( FlashAPIIsValid() ) { QueryUpdate(); CheckForQueryResults(); } } //----------------------------------------------------------------------------- // Purpose: Called from scaleform whenever any of our search parameters change: // game mode & type, filter (me/friends/overall), position in board //----------------------------------------------------------------------------- void CCreateLeaderboardsDialogScaleform::SetQuery( SCALEFORM_CALLBACK_ARGS_DECL ) { #if defined (_X360) // As soon as we change any parameters, cancel any previous queries if ( m_hAsyncQuery != 0 ) { #ifdef DIAGNOSE_ASYNCH_PROBLEMS printf( "Leaderboard: SetQuery: Aborting a pending query.\n" ); #endif xboxsystem->CancelOverlappedOperation( m_hAsyncQuery ); xboxsystem->ReleaseAsyncHandle( m_hAsyncQuery ); m_hAsyncQuery = 0; } m_bCheckForQueryResults = false; int nIdx = 0; int filterType = (int)( pui->Params_GetArgAsNumber( obj, nIdx++ ) ); m_currentFilterType = (eLeaderboardFiltersType) clamp( filterType, (int)eLBFilter_Overall, (int)eLBFilter_Friends ); // if this is a new friends query, free all of the tracking structures from previous friends stat queries if ( m_currentFilterType == eLBFilter_Friends ) { for ( int idx = 0; idx < MAX_FRIENDS+1; ++idx ) { if ( m_pFriendsResult[idx] != NULL ) { delete [] m_pFriendsResult[idx]; m_pFriendsResult[idx] = NULL; } } // Do not reset m_pFriends list here! It's slow to read out, so once we've gotten it keep // it around until the dialog goes away (not when we're flicking through filters) m_bEnumeratingFriends = false; m_iNextFriend = -1; } m_startingRowIndex = (int)( pui->Params_GetArgAsNumber( obj, nIdx++ ) ); m_rowsPerPage = (int)( pui->Params_GetArgAsNumber( obj, nIdx++ ) ); DWORD dwViewId = (DWORD)( pui->Params_GetArgAsNumber( obj, nIdx++ ) ); DWORD dwNumColumns = (DWORD)( pui->Params_GetArgAsNumber( obj, nIdx++ ) ); // Sanity check the column list passed in: SFVALUE columnIdArray = pui->Params_GetArg( obj, nIdx++ ); if ( pui->Value_GetType( columnIdArray ) != IScaleformUI::VT_Array ) { Warning( "CCreateLeaderboardsDialogScaleform::SetQuery: Invalid final argument - expected an array of columnIds!\n" ); dwNumColumns = 0; } else { int arrayLen = pui->Value_GetArraySize( columnIdArray ); if ( arrayLen != (int)dwNumColumns ) { Warning( "CCreateLeaderboardsDialogScaleform::SetQuery: columnIds length differs from param count - using %d columns instead!\n", arrayLen ); dwNumColumns = arrayLen; } } // Wait a second before we handle this query, in case user is flicking through options m_fQueryDelayTime = Plat_FloatTime() + QUERY_DELAY_TIME; // Sanity check the board we are reading from Assert( dwViewId >= STATS_VIEW_WINS_ONLINE_CASUAL && dwViewId <= STATS_VIEW_GP_ONLINE_GG_BOMB ); // Clear the query, and copy over our search settings ZeroMemory( &m_statsSpec, sizeof(m_statsSpec) ); m_statsSpec.dwViewId = dwViewId; m_statsSpec.dwNumColumnIds = clamp( dwNumColumns, (DWORD)0, (DWORD)XUSER_STATS_ATTRS_IN_SPEC - 1 ); for (DWORD columnIdx = 0; columnIdx < m_statsSpec.dwNumColumnIds; ++columnIdx ) { SFVALUE element = pui->Value_GetArrayElement( columnIdArray, columnIdx ); m_statsSpec.rgwColumnIds[columnIdx] = (WORD)(pui->Value_GetNumber( element )); } #endif #if !defined( NO_STEAM ) // As soon as we change any parameters, cancel any previous queries m_bCheckForQueryResults = false; int nIdx = 0; int filterType = (int)( pui->Params_GetArgAsNumber( obj, nIdx++ ) ); m_currentFilterType = (eLeaderboardFiltersType) clamp( filterType, (int)eLBFilter_Overall, (int)eLBFilter_Friends ); m_startingRowIndex = (int)( pui->Params_GetArgAsNumber( obj, nIdx++ ) ); m_rowsPerPage = (int)( pui->Params_GetArgAsNumber( obj, nIdx++ ) ); m_currentLeaderboardName = ( pui->Params_GetArgAsString( obj, nIdx++ ) ); // DevMsg(" Querying Steam board \"%s\" starting at row %d, %d total rows..\n\n", m_currentLeaderboardName, m_startingRowIndex, m_rowsPerPage ); // Wait a second before we handle this query, in case user is flicking through options m_fQueryDelayTime = Plat_FloatTime() + QUERY_DELAY_TIME; #endif } //----------------------------------------------------------------------------- // Purpose: Determine how many results we have obtained from our previous query // Returns -1 while the query is still pending. //----------------------------------------------------------------------------- void CCreateLeaderboardsDialogScaleform::Query_NumResults( SCALEFORM_CALLBACK_ARGS_DECL ) { int numResults = 0; #if defined (_X360) if ( m_fQueryDelayTime > 0.f ) numResults = -1; // Query is still in progress; display the "Searching..." msg else numResults = m_pResults.Count(); #endif // _X360 #if !defined( NO_STEAM ) if ( m_fQueryDelayTime > 0.f ) numResults = -1; // Query is still in progress; display the "Searching..." msg else if ( m_bCheckForQueryResults ) numResults = ( m_cachedLeaderboardScores.m_cEntryCount ); #endif m_pScaleformUI->Params_SetResult( obj, numResults ); } //----------------------------------------------------------------------------- // Purpose: Retrieve the row that contains information about the current player //----------------------------------------------------------------------------- void CCreateLeaderboardsDialogScaleform::Query_GetCurrentPlayerRow( SCALEFORM_CALLBACK_ARGS_DECL ) { int playerRow = 0; #if defined (_X360) for ( int Idx = 0; Idx < m_pResults.Count(); Idx ++ ) { if ( m_pResults[Idx]->xuid == m_PlayerXUID ) { playerRow = Idx; break; } } #endif // _X360 #if !defined (NO_STEAM) if ( m_currentLeaderboardHandle != 0 && m_cachedLeaderboardScores.m_hSteamLeaderboardEntries != 0 ) { for ( int Idx = 0; Idx < m_cachedLeaderboardScores.m_cEntryCount; Idx++ ) { LeaderboardEntry_t leaderboardEntry; if ( steamapicontext->SteamUserStats()->GetDownloadedLeaderboardEntry( m_cachedLeaderboardScores.m_hSteamLeaderboardEntries, Idx, &leaderboardEntry, NULL, 0 ) ) { if ( leaderboardEntry.m_steamIDUser.ConvertToUint64() == m_PlayerXUID ) { playerRow = Idx; break; } } } } #endif // !NO_STEAM m_pScaleformUI->Params_SetResult( obj, playerRow ); } //----------------------------------------------------------------------------- // Purpose: Get the name of the user in our results list at the row specified //----------------------------------------------------------------------------- void CCreateLeaderboardsDialogScaleform::QueryRow_GamerTag( SCALEFORM_CALLBACK_ARGS_DECL ) { int rowNumber = (int)( pui->Params_GetArgAsNumber( obj, 0 ) ); // We retrieve all friend info as one big list, so offset into that list by the current row we're filling in Flash if ( m_currentFilterType == eLBFilter_Friends ) rowNumber += m_startingRowIndex; const char *gamerTag = NULL; #if defined( _X360 ) if ( rowNumber >= 0 && rowNumber < m_pResults.Count() ) { gamerTag = m_pResults[rowNumber]->szGamertag; } #endif // _X360 #if !defined( NO_STEAM ) LeaderboardEntry_t leaderboardEntry; if ( steamapicontext->SteamUserStats()->GetDownloadedLeaderboardEntry( m_cachedLeaderboardScores.m_hSteamLeaderboardEntries, rowNumber, &leaderboardEntry, NULL, 0 ) ) { gamerTag = steamapicontext->SteamFriends()->GetFriendPersonaName( leaderboardEntry.m_steamIDUser ); } #endif // !NO_STEAM if ( !gamerTag ) gamerTag = "ERROR"; wchar_t unicodeName[MAX_NETWORKID_LENGTH]; g_pVGuiLocalize->ConvertANSIToUnicode( gamerTag, unicodeName, sizeof(unicodeName) ); wchar_t safe_wide_name[MAX_NETWORKID_LENGTH * 10 ]; // add enough room to safely escape all 64 name characters safe_wide_name[0] = L'\0'; g_pScaleformUI->MakeStringSafe( unicodeName, safe_wide_name, sizeof( safe_wide_name ) ); SFVALUE sfGamertag = CreateFlashString( safe_wide_name ); m_pScaleformUI->Params_SetResult( obj, sfGamertag ); SafeReleaseSFVALUE( sfGamertag ); } //----------------------------------------------------------------------------- // Purpose: Get the column value in our results list at the row specified //----------------------------------------------------------------------------- void CCreateLeaderboardsDialogScaleform::QueryRow_ColumnValue( SCALEFORM_CALLBACK_ARGS_DECL ) { int numArgs = pui->Params_GetNumArgs( obj ); if ( numArgs < 2 ) { Warning( "QueryRow_ColumnValue: Invalid number of arguments in call to function: %d\n", numArgs ); return; } int rowNumber = (int)( pui->Params_GetArgAsNumber( obj, 0 ) ); // We retrieve all friend info as one big list, so offset into that list by the current row we're filling in Flash if ( m_currentFilterType == eLBFilter_Friends ) rowNumber += m_startingRowIndex; int columnId = (int)( pui->Params_GetArgAsNumber( obj, 1 ) ); int columnValue = -1; char largeNumberString[64] = {0}; // if the field is a 64-bit value, we have to retrieve it as a string #if defined( _X360 ) if ( rowNumber >= 0 && rowNumber < m_pResults.Count() ) { XUSER_STATS_ROW *pRow = m_pResults[rowNumber]; if ( columnId == 0 ) { // 0 retrieves just the player's Ranking (1st place player is 1, 2nd place is 2, etc) columnValue = pRow->dwRank; } else if ( columnId == -1 ) { // -1 gets the Rating column, used to Rank the player V_snprintf( largeNumberString, sizeof(largeNumberString), "%lld", pRow->i64Rating ); } else if ( columnId == -2 ) { // -2 gets the row's overall board "Ranking / TotalRankedUsers" DWORD totalViewRows = 0; // Try to find the total view rows for this board if ( m_pResultsBuffer && m_pResultsBuffer->dwNumViews > 0 ) { totalViewRows = m_pResultsBuffer->pViews[0].dwTotalViewRows; } else if ( m_pFriendsResult[0] && m_pFriendsResult[0]->dwNumViews > 0 ) { totalViewRows = m_pFriendsResult[0]->pViews[0].dwTotalViewRows; } if ( totalViewRows > 0 ) { V_snprintf( largeNumberString, sizeof(largeNumberString), "%d / %d", pRow->dwRank, totalViewRows ); } else { // Couldn't locate valid totalViewRows, so just return this row's rank columnValue = pRow->dwRank; } } else if ( columnId == -3 ) { // -3 retrieves the total number of users ranked on this view if ( m_pResultsBuffer && m_pResultsBuffer->dwNumViews > 0 ) { columnValue = m_pResultsBuffer->pViews[0].dwTotalViewRows; } } else { for ( DWORD colIdx = 0; colIdx < pRow->dwNumColumns; ++colIdx ) { if ( pRow->pColumns[colIdx].wColumnId == columnId ) { switch ( pRow->pColumns[colIdx].Value.type ) { case XUSER_DATA_TYPE_INT64: V_snprintf( largeNumberString, sizeof(largeNumberString), "%lld", pRow->pColumns[colIdx].Value.i64Data ); break; case XUSER_DATA_TYPE_INT32: columnValue = pRow->pColumns[colIdx].Value.nData; break; default: Warning( "CCreateLeaderboardsDialogScaleform::QueryRow_ColumnValue - the type for columnId %d is not currently support: %d\n", columnId, pRow->pColumns[colIdx].Value.type ); break; } } } } } #endif // _X360 #if !defined( NO_STEAM ) LeaderboardEntry_t leaderboardEntry; int lbDetails[kMaxPayloadEntries] = { 0 }; int numDetails = kMaxPayloadEntries; if ( columnId == -3 ) { // -3 retrieves the total number of users ranked on this view columnValue = steamapicontext->SteamUserStats()->GetLeaderboardEntryCount( m_currentLeaderboardHandle ); } else if ( columnId == -4 ) { columnValue = ( steamapicontext->SteamUser()->BLoggedOn() ? 1 : 0 ); } else if ( m_currentLeaderboardHandle != 0 && m_cachedLeaderboardScores.m_hSteamLeaderboardEntries != 0 && steamapicontext->SteamUserStats()->GetDownloadedLeaderboardEntry( m_cachedLeaderboardScores.m_hSteamLeaderboardEntries, rowNumber, &leaderboardEntry, lbDetails, numDetails ) ) { // DevMsg( "Reading from row %d columnId: %d, extra details count = %d.\n", rowNumber, columnId, numDetails ); if ( columnId == 0 ) { int totalViewRows = steamapicontext->SteamUserStats()->GetLeaderboardEntryCount( m_currentLeaderboardHandle ); if ( totalViewRows > 0 ) { if ( leaderboardEntry.m_nGlobalRank <= LEADERBOARD_SHOW_RANK_UNDER ) V_snprintf( largeNumberString, sizeof(largeNumberString), "%d", leaderboardEntry.m_nGlobalRank ); else V_snprintf( largeNumberString, sizeof(largeNumberString), "%.1f%%", MAX( 0.1f, ((float)leaderboardEntry.m_nGlobalRank / (float)totalViewRows) * 100 )); } else { // Couldn't locate valid totalViewRows, so just return this row's rank if ( leaderboardEntry.m_nGlobalRank <= LEADERBOARD_SHOW_RANK_UNDER ) columnValue = leaderboardEntry.m_nGlobalRank; else V_snprintf( largeNumberString, sizeof(largeNumberString), "%s", "--" ); } } else if ( columnId == -1 ) { columnValue = leaderboardEntry.m_nScore; } else if ( columnId == -2 ) { // -2 gets the row's overall board "Ranking / TotalRankedUsers" int totalViewRows = steamapicontext->SteamUserStats()->GetLeaderboardEntryCount( m_currentLeaderboardHandle ); if ( totalViewRows > 0 ) { if ( leaderboardEntry.m_nGlobalRank <= LEADERBOARD_SHOW_RANK_UNDER ) V_snprintf( largeNumberString, sizeof(largeNumberString), "%d", leaderboardEntry.m_nGlobalRank ); else V_snprintf( largeNumberString, sizeof(largeNumberString), "%.1f%%", MAX( 0.1f, ((float)leaderboardEntry.m_nGlobalRank / (float)totalViewRows) * 100 )); } else { // Couldn't locate valid totalViewRows, so just return this row's rank if ( leaderboardEntry.m_nGlobalRank <= LEADERBOARD_SHOW_RANK_UNDER ) columnValue = leaderboardEntry.m_nGlobalRank; else V_snprintf( largeNumberString, sizeof(largeNumberString), "%s", "--" ); } } else if ( columnId == -3 ) { // -3 retrieves the total number of users ranked on this view columnValue = steamapicontext->SteamUserStats()->GetLeaderboardEntryCount( m_currentLeaderboardHandle ); } else { V_snprintf( largeNumberString, sizeof(largeNumberString), "%lld", ExtractPayloadDataByColumnID( lbDetails, columnId-1 ) ); } } #endif // !NO_STEAM if ( largeNumberString[0] != 0 ) { SFVALUE sfNumberString = CreateFlashString( largeNumberString ); m_pScaleformUI->Params_SetResult( obj, sfNumberString ); SafeReleaseSFVALUE( sfNumberString ); } else { m_pScaleformUI->Params_SetResult( obj, columnValue ); } } //----------------------------------------------------------------------------- // Purpose: Get the ratio of 2 column values in our results list at the row specified // (for example, to retrieve the ratio of Kills to Deaths by querying // those two columns in unison and returning their ratio). //----------------------------------------------------------------------------- void CCreateLeaderboardsDialogScaleform::QueryRow_ColumnRatio( SCALEFORM_CALLBACK_ARGS_DECL ) { int numArgs = pui->Params_GetNumArgs( obj ); if ( numArgs < 3 ) { Warning( "QueryRow_ColumnRatio: Invalid number of arguments in call to function: %d\n", numArgs ); return; } int rowNumber = (int)( pui->Params_GetArgAsNumber( obj, 0 ) ); // We retrieve all friend info as one big list, so offset into that list by the current row we're filling in Flash if ( m_currentFilterType == eLBFilter_Friends ) rowNumber += m_startingRowIndex; int columnIdA = (int)( pui->Params_GetArgAsNumber( obj, 1 ) ); int columnIdB = (int)( pui->Params_GetArgAsNumber( obj, 2 ) ); // Optional param: specify formatting string for result const char *formatStr = "%3.3f"; if ( numArgs > 3 ) { formatStr = pui->Params_GetArgAsString( obj, 3 ); } // Optional param: If true, return the value scaled up by 100 to be a percentage bool bPercentage = false; if ( numArgs > 4 ) { bPercentage = pui->Params_GetArgAsBool( obj, 4 ); } // Optional param: If true, we want the ratio of columnA / (columnA+columnB) - useful for calculating win percentage bool bRatioSumAB = false; if ( numArgs > 5 ) { bRatioSumAB = pui->Params_GetArgAsBool( obj, 5 ); } #if defined( _X360 ) // Our resulting ratio will be "columnA / columnB" or "column A / (columnA + columnB)" LONGLONG columnValueA = 0; LONGLONG columnValueB = 0; if ( rowNumber >= 0 && rowNumber < m_pResults.Count() ) { XUSER_STATS_ROW *pRow = m_pResults[rowNumber]; if ( columnIdA == -1 ) { columnValueA = pRow->i64Rating; } if ( columnIdB == -1 ) { columnValueB = pRow->i64Rating; } if ( columnIdA == -2 ) { columnValueA = pRow->dwRank; } if ( columnIdB == -2 ) { columnValueB = pRow->dwRank; } for ( DWORD colIdx = 0; colIdx < pRow->dwNumColumns; ++colIdx ) { bool bIsColumnA = pRow->pColumns[colIdx].wColumnId == columnIdA; bool bIsColumnB = pRow->pColumns[colIdx].wColumnId == columnIdB; if ( bIsColumnA || bIsColumnB ) { switch ( pRow->pColumns[colIdx].Value.type ) { case XUSER_DATA_TYPE_INT64: if ( bIsColumnA ) columnValueA = pRow->pColumns[colIdx].Value.i64Data; if ( bIsColumnB ) columnValueB = pRow->pColumns[colIdx].Value.i64Data; break; case XUSER_DATA_TYPE_INT32: if ( bIsColumnA ) columnValueA = pRow->pColumns[colIdx].Value.nData; if ( bIsColumnB ) columnValueB = pRow->pColumns[colIdx].Value.nData; break; default: Warning( "CCreateLeaderboardsDialogScaleform::QueryRow_ColumnValue - the type for columnId %d is not currently support: %d\n", pRow->pColumns[colIdx].wColumnId, pRow->pColumns[colIdx].Value.type ); break; } } } } #endif // _X360 #if !defined( NO_STEAM ) // Our resulting ratio will be "columnA / columnB" or "column A / (columnA + columnB)" uint64 columnValueA = 0; uint64 columnValueB = 0; LeaderboardEntry_t leaderboardEntry; int lbDetails[kMaxPayloadEntries] = { 0 }; int numDetails = kMaxPayloadEntries; if ( m_currentLeaderboardHandle != 0 && m_cachedLeaderboardScores.m_hSteamLeaderboardEntries != 0 && steamapicontext->SteamUserStats()->GetDownloadedLeaderboardEntry( m_cachedLeaderboardScores.m_hSteamLeaderboardEntries, rowNumber, &leaderboardEntry, lbDetails, numDetails ) ) { if ( columnIdA == -1 ) { columnValueA = leaderboardEntry.m_nScore; } if ( columnIdB == -1 ) { columnValueB = leaderboardEntry.m_nScore; } if ( columnIdA == -2 ) { columnValueA = leaderboardEntry.m_nGlobalRank; } if ( columnIdB == -2 ) { columnValueB = leaderboardEntry.m_nGlobalRank; } if ( columnIdA > 0 ) { // 1+ are the details of the entry, subtract 1 to lookup in the array columnValueA = ExtractPayloadDataByColumnID( lbDetails, columnIdA-1 ); } if ( columnIdB > 0 ) { // 1+ are the details of the entry, subtract 1 to lookup in the array columnValueB = ExtractPayloadDataByColumnID( lbDetails, columnIdB-1 ); } } #endif // !NO_STEAM #if !defined( NO_STEAM ) || defined( _X360 ) float fResult = (float)columnValueA; float fDivisor = (float)columnValueB; if ( bRatioSumAB ) { // When this flag is set, we want to divide by the sum of the two columns, so we return the ratio of A to the sum of A and B fDivisor = (float)columnValueA + (float)columnValueB; } if ( fDivisor != 0 ) { fResult = (float)columnValueA / fDivisor; } if ( bPercentage ) { fResult *= 100.f; } char ratioString[64]; V_snprintf( ratioString, sizeof(ratioString), formatStr, fResult ); SFVALUE sfRatioString = CreateFlashString( ratioString ); m_pScaleformUI->Params_SetResult( obj, sfRatioString ); SafeReleaseSFVALUE( sfRatioString ); #endif // !defined( NO_STEAM ) || defined( _X360 ) } //----------------------------------------------------------------------------- // Purpose: Trigger the display of additional info about a user in the specified // row of our query results. i.e. On XBox this brings up the Gamer Card //----------------------------------------------------------------------------- void CCreateLeaderboardsDialogScaleform::DisplayUserInfo( SCALEFORM_CALLBACK_ARGS_DECL ) { int rowNumber = (int)( pui->Params_GetArgAsNumber( obj, 0 ) ); // We retrieve all friend info as one big list, so offset into that list by the current row we're filling in Flash if ( m_currentFilterType == eLBFilter_Friends ) rowNumber += m_startingRowIndex; #if defined( _X360 ) if ( rowNumber >= 0 && rowNumber < m_pResults.Count() ) { ACTIVE_SPLITSCREEN_PLAYER_GUARD( GET_ACTIVE_SPLITSCREEN_SLOT() ); XShowGamerCardUI( XBX_GetActiveUserId(), m_pResults[rowNumber]->xuid ); } #endif // _X360 // Steam overlay not available on console (PS3) #if !defined( _GAMECONSOLE ) && !defined( NO_STEAM ) // Show the steam user id in the overlay LeaderboardEntry_t leaderboardEntry; if ( m_currentLeaderboardHandle != 0 && m_cachedLeaderboardScores.m_hSteamLeaderboardEntries != 0 && steamapicontext->SteamUserStats()->GetDownloadedLeaderboardEntry( m_cachedLeaderboardScores.m_hSteamLeaderboardEntries, rowNumber, &leaderboardEntry, NULL, 0 ) ) { steamapicontext->SteamFriends()->ActivateGameOverlayToUser( "steamid", leaderboardEntry.m_steamIDUser ); } #endif // !_GAMECONSOLE && !NO_STEAM } //----------------------------------------------------------------------------- // Purpose: Cancels any existing queries and issues a new query. //----------------------------------------------------------------------------- void CCreateLeaderboardsDialogScaleform::QueryUpdate( void ) { // If we have no new query, early out if ( m_fQueryDelayTime <= 0.f ) return; // Don't actually update the query until our delay has passed if ( Plat_FloatTime() < m_fQueryDelayTime ) return; #if defined (_X360) // Cancel any existing queries if ( m_hAsyncQuery != 0 ) { Warning( "Leaderboard: QueryUpdate: Triggering a new update with an AsynchHandle still in flight!\n" ); xboxsystem->CancelOverlappedOperation( m_hAsyncQuery ); xboxsystem->ReleaseAsyncHandle( m_hAsyncQuery ); m_hAsyncQuery = 0; } #ifdef DIAGNOSE_ASYNCH_PROBLEMS printf( "Leaderboard: QueryUpdate: Starting a new network query; allocating an AsyncHandle.\n" ); #endif // Create a new query handle m_hAsyncQuery = xboxsystem->CreateAsyncHandle(); m_bCheckForQueryResults = false; // Wipe out any previous results if ( m_pResultsBuffer != NULL ) { delete [] m_pResultsBuffer; m_pResultsBuffer = NULL; } if ( m_currentFilterType != eLBFilter_Friends ) { m_pResults.RemoveAll(); } // Invalidate any results m_bResultsValid = false; m_fQueryDelayTime = 0.f; // Write any XBox API results into this value int asynchResult = ERROR_SUCCESS; switch ( m_currentFilterType ) { case eLBFilter_Me: { asynchResult = xboxsystem->EnumerateStatsByXuid( m_PlayerXUID, m_rowsPerPage, 1, &m_statsSpec, (void**)&m_pResultsBuffer, true, &m_hAsyncQuery ); } break; case eLBFilter_Overall: { asynchResult = xboxsystem->EnumerateStatsByRank( m_startingRowIndex, m_rowsPerPage, 1, &m_statsSpec, (void**)&m_pResultsBuffer, true, &m_hAsyncQuery ); } break; case eLBFilter_Friends: { // If we haven't retrieved our friends list yet, do that first if ( m_pFriends == NULL ) { // Reset our friend tracking members m_iNextFriend = -1; m_bEnumeratingFriends = true; asynchResult = xboxsystem->EnumerateFriends( m_iPlayerSlot, (void**)&m_pFriends, true, &m_hAsyncQuery ); if ( asynchResult != ERROR_SUCCESS && asynchResult != ERROR_IO_PENDING ) { // Free the buffer, and error out now! delete [] m_pFriends; m_pFriends = NULL; Warning( "LEADERBOARDS: QueryUpdate - Failed to enumerate our friends!" ); } } else { // Once we have a friends list, start querying each user individually if ( m_iNextFriend < 0 ) { // Reset the results list now - from here on out, we're writing to this incrementally for each friend in our list m_pResults.RemoveAll(); m_iNextFriend = 0; // Local user comes first, first entry in friends result list asynchResult = xboxsystem->EnumerateStatsByXuid( m_PlayerXUID, 1, 1, &m_statsSpec, (void**)&(m_pFriendsResult[m_iNextFriend]), true, &m_hAsyncQuery ); } else { // Now, once for each friend in the list // NOTE! m_pFriends has exactly NumFriends entries, but m_pFriendsResult has an extra leading entry for the local user! asynchResult = xboxsystem->EnumerateStatsByXuid( m_pFriends[m_iNextFriend - 1].xuid, 1, 1, &m_statsSpec, (void**)&(m_pFriendsResult[m_iNextFriend]), true, &m_hAsyncQuery ); } } } break; } // Check return value if ( asynchResult != ERROR_SUCCESS && asynchResult != ERROR_IO_PENDING ) { // Free the buffer, and error out now! delete [] m_pResultsBuffer; m_pResultsBuffer = NULL; Warning( "LEADERBOARDS: QueryUpdate - Failed to enumerate leaderboard results!" ); return; } // Begin waiting for results to come back m_bCheckForQueryResults = true; #endif // _X360 #if !defined( NO_STEAM ) if ( !steamapicontext || !steamapicontext->SteamUserStats() ) return; // Invalidate any results m_bResultsValid = false; V_memset( &m_cachedLeaderboardScores, 0, sizeof(LeaderboardScoresDownloaded_t) ); m_fQueryDelayTime = 0.f; // Read out the leaderboard format description from matchmaking if ( !m_pLeaderboardDescription ) m_pLeaderboardDescription->deleteThis(); m_pLeaderboardDescription = g_pMatchFramework->GetMatchTitle()->DescribeTitleLeaderboard( m_currentLeaderboardName ); // Extract payload data size info V_memset( m_payloadSizes, 0, sizeof(int)*kMaxPayloadEntries ); KeyValues *pPayloadFormat = m_pLeaderboardDescription ? m_pLeaderboardDescription->FindKey( ":payloadformat" ) : NULL; if ( pPayloadFormat ) { for ( int payloadIndex=0; payloadIndex < kMaxPayloadEntries; ++payloadIndex ) { KeyValues *pPayload = pPayloadFormat->FindKey( CFmtStr( "payload%d", payloadIndex ) ); if ( !pPayload ) { // No more payload entries specified. break; } const char* pszFormat = pPayload->GetString( ":format", NULL ); if ( V_stricmp( pszFormat, "int" ) == 0 ) { m_payloadSizes[payloadIndex] += sizeof( uint32 ); } else if ( V_stricmp( pszFormat, "uint64" ) == 0 ) { m_payloadSizes[payloadIndex] += sizeof( uint64 ); } else { Warning( " LEADERBOARDS: WARNING! Unsupported data type in leaderboard payload data - assuming 32 bit\n\n" ); m_payloadSizes[payloadIndex] += sizeof( uint32 ); } } } m_currentLeaderboardHandle = GetLeaderboardHandle( m_currentLeaderboardName ); // If we haven't loaded this leaderboard yet, look up its handle first if ( m_currentLeaderboardHandle == 0 ) { SteamAPICall_t hSteamAPICall = steamapicontext->SteamUserStats()->FindLeaderboard( m_currentLeaderboardName ); if ( hSteamAPICall != 0 ) { m_SteamCallResultFindLeaderboard.Set( hSteamAPICall, this, &CCreateLeaderboardsDialogScaleform::Steam_OnFindLeaderboard ); } } else { // We can perform our query for this leaderboard immediately QueryLeaderboard( ); } #endif // !NO_STEAM } #if !defined ( NO_STEAM ) uint64 CCreateLeaderboardsDialogScaleform::ExtractPayloadDataByColumnID( int *pData, int columnId ) { char* pPtr = (char*)pData; int curColumnId = 0; if ( !pPtr ) return 0ull; while ( curColumnId < columnId && curColumnId < kMaxPayloadEntries ) { pPtr += m_payloadSizes[curColumnId++]; } if ( curColumnId >= kMaxPayloadEntries ) return 0ull; #if defined( PLAT_BIG_ENDIAN ) // On big-endian platforms, we need to byteswap the values written to Steam since we always write them little-endian // and Steam doesn't swap them for us return ( m_payloadSizes[curColumnId] == sizeof(uint32) ? DWordSwap(*(uint32*)pPtr) : QWordSwap(*(uint64*)pPtr) ); #else return ( m_payloadSizes[curColumnId] == sizeof(uint32) ? (*(uint32*)pPtr) : (*(uint64*)pPtr) ); #endif } SteamLeaderboard_t CCreateLeaderboardsDialogScaleform::GetLeaderboardHandle( const char* szLeaderboardName ) { // make a mapping of map contexts to leaderboard handles unsigned short index = m_LeaderboardHandles.Find( szLeaderboardName ); if ( index != m_LeaderboardHandles.InvalidIndex() ) { return m_LeaderboardHandles.Element(index); } return (SteamLeaderboard_t)0; } void CCreateLeaderboardsDialogScaleform::SetLeaderboardHandle( const char* szLeaderboardName, SteamLeaderboard_t hLeaderboard ) { m_LeaderboardHandles.InsertOrReplace( szLeaderboardName, hLeaderboard ); } //============================================================================= // Callback for FindLeaderboard //============================================================================= void CCreateLeaderboardsDialogScaleform::Steam_OnFindLeaderboard( LeaderboardFindResult_t *pFindLeaderboardResult, bool bIOFailure ) { // see if we encountered an error during the call if ( !pFindLeaderboardResult->m_bLeaderboardFound || bIOFailure ) { Warning( " Leaderboards: Steam error trying to retrieve leaderboard \"%s\"\n\n", m_currentLeaderboardName ); // Report the error now m_bCheckForQueryResults = true; return; } // check to see which leaderboard handle we just retrieved const char *pszReturnedName = steamapicontext->SteamUserStats()->GetLeaderboardName( pFindLeaderboardResult->m_hSteamLeaderboard ); Assert( StringHasPrefix( pszReturnedName, m_currentLeaderboardName ) ); SetLeaderboardHandle( pszReturnedName, pFindLeaderboardResult->m_hSteamLeaderboard ); m_currentLeaderboardHandle = pFindLeaderboardResult->m_hSteamLeaderboard; // Now we can perform our query for this leaderboard QueryLeaderboard( ); } //============================================================================= // Callback for DownloadLeaderboardEntries //============================================================================= void CCreateLeaderboardsDialogScaleform::Steam_OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *p, bool bError ) { if ( bError ) Warning( " Leaderboard \"%s\": Steam_OnLeaderboardScoresDownloaded received an error.\n", m_currentLeaderboardName ); // Fetch the data if found and no error if ( !bError ) { // DevMsg( " Leaderboard \"%s\": %d Entries found.\n", m_currentLeaderboardName, p->m_cEntryCount ); V_memcpy( &m_cachedLeaderboardScores, p, sizeof( LeaderboardScoresDownloaded_t ) ); } m_bCheckForQueryResults = true; } void CCreateLeaderboardsDialogScaleform::QueryLeaderboard( void ) { SteamAPICall_t hSteamAPICall = (SteamAPICall_t)0; if ( m_currentLeaderboardHandle == 0 ) { Warning( " CCreateLeaderboardsDialogScaleform::QueryLeaderboard - leaderboard not found: \"%s\"\n", m_currentLeaderboardName ); // Report the error now m_bCheckForQueryResults = true; return; } switch ( m_currentFilterType ) { case eLBFilter_Me: { hSteamAPICall = steamapicontext->SteamUserStats()->DownloadLeaderboardEntries( m_currentLeaderboardHandle, k_ELeaderboardDataRequestGlobalAroundUser, -(m_rowsPerPage/2), (m_rowsPerPage/2) ); } break; case eLBFilter_Overall: { hSteamAPICall = steamapicontext->SteamUserStats()->DownloadLeaderboardEntries( m_currentLeaderboardHandle, k_ELeaderboardDataRequestGlobal, m_startingRowIndex, m_startingRowIndex + m_rowsPerPage - 1 ); } break; case eLBFilter_Friends: { hSteamAPICall = steamapicontext->SteamUserStats()->DownloadLeaderboardEntries( m_currentLeaderboardHandle, k_ELeaderboardDataRequestFriends, 0, 0 ); } break; } if ( hSteamAPICall ) { // Register for the async callback m_SteamCallbackOnLeaderboardScoresDownloaded.Set( hSteamAPICall, this, &CCreateLeaderboardsDialogScaleform::Steam_OnLeaderboardScoresDownloaded ); } else { // Report the error now m_bCheckForQueryResults = true; } } #endif // !NO_STEAM #if defined (_X360) //============================================================================= // Purpose: Sorts a vector of rank/indexes in to the results buffer so we can // sort friend lists //============================================================================= int FriendsSortFunc( XUSER_STATS_ROW *const *a, XUSER_STATS_ROW *const *b ) { if ( (*a)->dwRank < (*b)->dwRank ) return -1; if ( (*a)->dwRank > (*b)->dwRank ) return 1; return 0; } #endif // _X360 //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCreateLeaderboardsDialogScaleform::CheckForQueryResults( void ) { #if defined (_X360) // No new query to check on if ( !m_bCheckForQueryResults ) return; // Check our async handle to see if it's done uint iResultCode = 0; int result = xboxsystem->GetOverlappedResult( m_hAsyncQuery, &iResultCode, false ); // Abort on non-recoverable error codes if ( result == ERROR_INVALID_HANDLE || result == ERROR_FUNCTION_FAILED ) { Warning( "LEADERBOARDS: CheckForQueryResults got error code: %s. Aborting query.", result == ERROR_INVALID_HANDLE ? "ERROR_INVALID_HANDLE" : "ERROR_FUNCTION_FAILED "); if ( m_hAsyncQuery != 0 ) { xboxsystem->ReleaseAsyncHandle( m_hAsyncQuery ); m_hAsyncQuery = 0; } // Signal flash to terminate WITH_SLOT_LOCKED { ScaleformUI()->Value_InvokeWithoutReturn( m_FlashAPI, "NotifyResults", 0, NULL ); } m_bCheckForQueryResults = false; return; } #ifdef DIAGNOSE_ASYNCH_PROBLEMS printf( "Leaderboard: CheckForQueryResults - GetOverlappedResult returns %d, (result code %d)\n", result, iResultCode ); #endif // If we are still retrieving friends info, check on that operation first if ( m_bEnumeratingFriends ) { if ( result == ERROR_SUCCESS ) { m_iNumFriends = iResultCode; m_bEnumeratingFriends = false; m_bCheckForQueryResults = false; // Queue up the next query m_fQueryDelayTime = Plat_FloatTime() + QUERY_SHORT_DELAY_TIME; } return; } // Our query is complete, now we need to see if the query ran into any errors if ( result == ERROR_SUCCESS ) { // Filter: FRIENDS // Handle friends differently, by pushing multiple queries until we've read out everyone if ( m_currentFilterType == eLBFilter_Friends ) { // Check that the friend result is valid, and add it to our vector if ( m_pFriendsResult[m_iNextFriend] && m_pFriendsResult[m_iNextFriend]->dwNumViews > 0 && m_pFriendsResult[m_iNextFriend]->pViews[0].dwNumRows > 0 ) { #ifdef DIAGNOSE_ASYNCH_PROBLEMS printf( "Found FRIEND result user = %s: xuid: %d columns: %d (rank: %d with rating: %lld) \n", m_pFriendsResult[m_iNextFriend]->pViews[0].pRows[0].szGamertag, m_pFriendsResult[m_iNextFriend]->pViews[0].pRows[0].xuid, m_pFriendsResult[m_iNextFriend]->pViews[0].pRows[0].dwNumColumns, m_pFriendsResult[m_iNextFriend]->pViews[0].pRows[0].dwRank, m_pFriendsResult[m_iNextFriend]->pViews[0].pRows[0].i64Rating ); #endif if ( m_pFriendsResult[m_iNextFriend]->pViews[0].pRows[0].dwRank > 0 ) { m_pResults.AddToTail( &m_pFriendsResult[m_iNextFriend]->pViews[0].pRows[0] ); } } m_iNextFriend++; if ( m_iNextFriend < m_iNumFriends + 1 ) { m_bCheckForQueryResults = false; // Queue up the next query m_fQueryDelayTime = Plat_FloatTime() + QUERY_SHORT_DELAY_TIME; return; } else { // Now sort the results list by the ranking column m_pResults.Sort( FriendsSortFunc ); // we have all results, return now! m_bResultsValid = true; } } // Filter: Overall/Me // Non-friends query - all results come back in one batch else { // We assume the results are valid now, and push only ranked values into our m_pResults list m_bResultsValid = true; // Reset the list, fill it with current data m_pResults.RemoveAll(); // Make sure to update the starting index based on the actual starting index we get back from the results if ( m_pResultsBuffer ) { if ( m_pResultsBuffer->dwNumViews > 0 && m_pResultsBuffer->pViews[0].dwNumRows > 0 ) { // Copy pointers to our results buffer into a vector so we can sort and handle friends properly for ( uint i=0; ipViews[0].dwNumRows; i++ ) { // Only add players that are ranked if ( m_pResultsBuffer->pViews[0].pRows[i].dwRank > 0 ) { m_pResults.AddToTail( &m_pResultsBuffer->pViews[0].pRows[i] ); #ifdef DIAGNOSE_ASYNCH_PROBLEMS printf( "Found result user = %s: xuid: %d columns: %d (rank: %d with rating: %lld) \n", m_pResultsBuffer->pViews[0].pRows[i].szGamertag, m_pResultsBuffer->pViews[0].pRows[i].xuid, m_pResultsBuffer->pViews[0].pRows[i].dwNumColumns, m_pResultsBuffer->pViews[0].pRows[i].dwRank, m_pResultsBuffer->pViews[0].pRows[i].i64Rating ); for ( DWORD col = 0; col < m_pResultsBuffer->pViews[0].pRows[i].dwNumColumns; col++ ) { printf( " column[%d] (type %d) = %d \n", col, m_pResultsBuffer->pViews[0].pRows[i].pColumns[col].wColumnId, m_pResultsBuffer->pViews[0].pRows[i].pColumns[col].Value.nData ); } #endif } } } } } // Signal Flash that we are ready to show results WITH_SLOT_LOCKED { ScaleformUI()->Value_InvokeWithoutReturn( m_FlashAPI, "NotifyResults", 0, NULL ); } m_bCheckForQueryResults = false; } #endif // _X360 #if !defined( NO_STEAM ) // No new query to check on if ( !m_bCheckForQueryResults ) return; // Signal Flash that we are ready to show results WITH_SLOT_LOCKED { ScaleformUI()->Value_InvokeWithoutReturn( m_FlashAPI, "NotifyResults", 0, NULL ); } m_bCheckForQueryResults = false; #endif // !NO_STEAM } #endif // INCLUDE_SCALEFORM