diff options
| author | daoge_cmd <3523206925@qq.com> | 2026-03-01 12:16:08 +0800 |
|---|---|---|
| committer | daoge_cmd <3523206925@qq.com> | 2026-03-01 12:16:08 +0800 |
| commit | b691c43c44ff180d10e7d4a9afc83b98551ff586 (patch) | |
| tree | 3e9849222cbc6ba49f2f1fc6e5fe7179632c7390 /Minecraft.Client/Durango/Leaderboards | |
| parent | def8cb415354ac390b7e89052a50605285f1aca9 (diff) | |
Initial commit
Diffstat (limited to 'Minecraft.Client/Durango/Leaderboards')
6 files changed, 1522 insertions, 0 deletions
diff --git a/Minecraft.Client/Durango/Leaderboards/DurangoLeaderboardManager.cpp b/Minecraft.Client/Durango/Leaderboards/DurangoLeaderboardManager.cpp new file mode 100644 index 00000000..cbedb033 --- /dev/null +++ b/Minecraft.Client/Durango/Leaderboards/DurangoLeaderboardManager.cpp @@ -0,0 +1,717 @@ +#include "stdafx.h" +#include "DurangoLeaderboardManager.h" +#include "..\..\..\Minecraft.World\StringHelpers.h" + +namespace WFC = Windows::Foundation::Collections; +namespace CC = concurrency; + +LeaderboardManager *LeaderboardManager::m_instance = new DurangoLeaderboardManager(); //Singleton instance of the LeaderboardManager + +DurangoLeaderboardManager::DurangoLeaderboardManager() +{ + m_eStatsState = eStatsState_Idle; + InitializeCriticalSection(&m_csStatsState); + + m_openSessions = 0; + m_xboxLiveContext = nullptr; + m_scores = NULL; + m_readCount = 0; + m_maxRank = 0; + m_waitingForProfiles = false; + + m_difficulty = 0; + m_type = eStatsType_UNDEFINED; + m_statNames = ref new PC::Vector<P::String^>(); + m_xboxUserIds = ref new PC::Vector<P::String^>(); + m_leaderboardAsyncOp = nullptr; + m_statsAsyncOp = nullptr; + + for(unsigned int difficulty = 0; difficulty < 4; ++difficulty) + { + m_leaderboardNames[difficulty][eStatsType_Travelling] = L"LeaderboardTravelling" + _toString(difficulty); + m_leaderboardNames[difficulty][eStatsType_Mining] = L"LeaderboardMining" + _toString(difficulty); + m_leaderboardNames[difficulty][eStatsType_Farming] = L"LeaderboardFarming" + _toString(difficulty); + m_leaderboardNames[difficulty][eStatsType_Kills] = L"LeaderboardKills" + _toString(difficulty); + + m_socialLeaderboardNames[difficulty][eStatsType_Travelling] = L"Leaderboard.LeaderboardId.0.DifficultyLevelId." + _toString(difficulty); + m_socialLeaderboardNames[difficulty][eStatsType_Mining] = L"Leaderboard.LeaderboardId.1.DifficultyLevelId." + _toString(difficulty); + m_socialLeaderboardNames[difficulty][eStatsType_Farming] = L"Leaderboard.LeaderboardId.2.DifficultyLevelId." + _toString(difficulty); + m_socialLeaderboardNames[difficulty][eStatsType_Kills] = L"Leaderboard.LeaderboardId.3.DifficultyLevelId." + _toString(difficulty); + + m_leaderboardStatNames[difficulty][eStatsType_Travelling].push_back( L"DistanceTravelled.DifficultyLevelId." + _toString(difficulty) + L".TravelMethodId.0"); // Walked + m_leaderboardStatNames[difficulty][eStatsType_Travelling].push_back( L"DistanceTravelled.DifficultyLevelId." + _toString(difficulty) + L".TravelMethodId.2"); // Fallen + m_leaderboardStatNames[difficulty][eStatsType_Travelling].push_back( L"DistanceTravelled.DifficultyLevelId." + _toString(difficulty) + L".TravelMethodId.4"); // Minecart + m_leaderboardStatNames[difficulty][eStatsType_Travelling].push_back( L"DistanceTravelled.DifficultyLevelId." + _toString(difficulty) + L".TravelMethodId.5"); // Boat + + m_leaderboardStatNames[difficulty][eStatsType_Mining].push_back( L"BlockBroken.DifficultyLevelId." + _toString(difficulty) + L".BlockId.3"); // Dirt + m_leaderboardStatNames[difficulty][eStatsType_Mining].push_back( L"BlockBroken.DifficultyLevelId." + _toString(difficulty) + L".BlockId.4"); // Cobblestone + m_leaderboardStatNames[difficulty][eStatsType_Mining].push_back( L"BlockBroken.DifficultyLevelId." + _toString(difficulty) + L".BlockId.12"); // Sand + m_leaderboardStatNames[difficulty][eStatsType_Mining].push_back( L"BlockBroken.DifficultyLevelId." + _toString(difficulty) + L".BlockId.1"); // Stone + m_leaderboardStatNames[difficulty][eStatsType_Mining].push_back( L"BlockBroken.DifficultyLevelId." + _toString(difficulty) + L".BlockId.13"); // Gravel + m_leaderboardStatNames[difficulty][eStatsType_Mining].push_back( L"BlockBroken.DifficultyLevelId." + _toString(difficulty) + L".BlockId.82"); // Clay + m_leaderboardStatNames[difficulty][eStatsType_Mining].push_back( L"BlockBroken.DifficultyLevelId." + _toString(difficulty) + L".BlockId.49"); // Obsidian + + m_leaderboardStatNames[difficulty][eStatsType_Farming].push_back( L"McItemAcquired.DifficultyLevelId." + _toString(difficulty) + L".AcquisitionMethodId.1.ItemId.344"); // Eggs + m_leaderboardStatNames[difficulty][eStatsType_Farming].push_back( L"BlockBroken.DifficultyLevelId." + _toString(difficulty) + L".BlockId.59"); // Wheat + m_leaderboardStatNames[difficulty][eStatsType_Farming].push_back( L"BlockBroken.DifficultyLevelId." + _toString(difficulty) + L".BlockId.39"); // Mushroom + m_leaderboardStatNames[difficulty][eStatsType_Farming].push_back( L"BlockBroken.DifficultyLevelId." + _toString(difficulty) + L".BlockId.83"); // Sugarcane + m_leaderboardStatNames[difficulty][eStatsType_Farming].push_back( L"McItemAcquired.DifficultyLevelId." + _toString(difficulty) + L".AcquisitionMethodId.2.ItemId.335"); // Milk + m_leaderboardStatNames[difficulty][eStatsType_Farming].push_back( L"McItemAcquired.DifficultyLevelId." + _toString(difficulty) + L".AcquisitionMethodId.1.ItemId.86"); // Pumpkin + + m_leaderboardStatNames[difficulty][eStatsType_Kills].push_back( L"MobKilledTotal.DifficultyLevelId." + _toString(difficulty) + L".EnemyRoleId.54"); // Zombie + m_leaderboardStatNames[difficulty][eStatsType_Kills].push_back( L"MobKilledTotal.DifficultyLevelId." + _toString(difficulty) + L".EnemyRoleId.51"); // Skeleton + m_leaderboardStatNames[difficulty][eStatsType_Kills].push_back( L"MobKilledTotal.DifficultyLevelId." + _toString(difficulty) + L".EnemyRoleId.50"); // Creeper + m_leaderboardStatNames[difficulty][eStatsType_Kills].push_back( L"MobKilledTotal.DifficultyLevelId." + _toString(difficulty) + L".EnemyRoleId.52"); // Spider + m_leaderboardStatNames[difficulty][eStatsType_Kills].push_back( L"MobKilledTotal.DifficultyLevelId." + _toString(difficulty) + L".EnemyRoleId.49"); // Spider Jockey + m_leaderboardStatNames[difficulty][eStatsType_Kills].push_back( L"MobKilledTotal.DifficultyLevelId." + _toString(difficulty) + L".EnemyRoleId.57"); // Zombie Pigman + m_leaderboardStatNames[difficulty][eStatsType_Kills].push_back( L"MobKilledTotal.DifficultyLevelId." + _toString(difficulty) + L".EnemyRoleId.55"); // Slime + } +} + +void DurangoLeaderboardManager::Tick() +{ + ReadView view; + + switch( getState() ) + { + case eStatsState_GettingLeaderboardInfo: + break; + case eStatsState_ReceivedLeaderboardInfo: + { + setState(eStatsState_GettingStatsInfo); + + // Get the actual display info for the stats + m_statsAsyncOp = m_xboxLiveContext->UserStatisticsService->GetMultipleUserStatisticsAsync( + m_xboxUserIds->GetView(), // the collection of Xbox user IDs whose stats we want to retrieve + SERVICE_CONFIG_ID, // the service config that contains the stats we want + m_statNames->GetView() // a list of stat names we want + ); + + auto task = concurrency::create_task(m_statsAsyncOp).then( [this] (CC::task<WFC::IVectorView<MXS::UserStatistics::UserStatisticsResult^>^> resultListTask) + { + try + { + app.DebugPrintf("[LeaderboardManager] Second continuation\n"); + m_statsAsyncOp = nullptr; + + WFC::IVectorView<MXS::UserStatistics::UserStatisticsResult^>^ resultList = resultListTask.get(); + + if (m_xboxLiveContext == nullptr) throw(ref new P::Exception(-1)); + + int userIndex = 0; + for( MXS::UserStatistics::UserStatisticsResult^ result : resultList ) + { + app.DebugPrintf("XboxUserId: %ls\n", result->XboxUserId->Data()); + + for( UINT index = 0; index<result->ServiceConfigurationStatistics->Size; index++ ) + { + MXS::UserStatistics::ServiceConfigurationStatistic^ configStat = result->ServiceConfigurationStatistics->GetAt(index); + //app.DebugPrintf("ServiceConfigurationId: %ls\n", configStat->ServiceConfigurationId->Data()); + + updateStatsInfo(userIndex, m_difficulty, m_type, configStat->Statistics); + } + ++userIndex; + } + + app.DebugPrintf("[LeaderboardManager] Setting to ready\n"); + setState(eStatsState_Ready); + + } + catch (Platform::Exception^ ex) + { + m_leaderboardAsyncOp = nullptr; + setState(eStatsState_Failed); + + if (ex->HResult == HTTP_E_STATUS_NOT_FOUND) app.DebugPrintf("[LeaderboardManager] ERROR calling GetLeaderboardAsync: 404 Not Found - 0x%0.8x\n", ex->HResult); + else app.DebugPrintf("[LeaderboardManager] ERROR calling GetLeaderboardAsync: 0x%0.8x\n", ex->HResult); + } + catch (...) + { + app.DebugPrintf("[LeaderboardManager] SecondContinuation: Unknown exception.\n"); + } + }); + } + break; + case eStatsState_GettingStatsInfo: + break; + case eStatsState_Ready: + { + // If we're waiting on profiles, don't return scores just yet + if (m_waitingForProfiles) + { + return; + } + else + { + if (m_displayNames.size() == m_readCount) + { + // Add display names to scores + for (int i = 0; i < m_displayNames.size(); i++) + { + m_scores[i].m_name = m_displayNames[i]; + } + } + else + { + // This seem to happen if something went wrong with Xbox Live + app.DebugPrintf("DurangoLeaderboardManager::Tick: Display names count (%i) didn't match read count (%i)", m_displayNames.size(), m_readCount); + } + + m_displayNames.clear(); + } + + //assert(m_scores != NULL || m_readCount == 0); + + view.m_numQueries = m_readCount; + view.m_queries = m_scores; + + // 4J-JEV: Debugging. + //LeaderboardManager::printStats(view); + + eStatsReturn ret = view.m_numQueries > 0 ? eStatsReturn_Success : eStatsReturn_NoResults; + + if (m_readListener != NULL) + { + app.DebugPrintf("[LeaderboardManager] OnStatsReadComplete(%i, %i, _)\n", ret, m_readCount); + m_readListener->OnStatsReadComplete(ret, m_maxRank, view); + } + + app.DebugPrintf("[LeaderboardManager] Deleting scores\n"); + delete [] m_scores; + m_scores = NULL; + + setState(eStatsState_Idle); + } + break; + + case eStatsState_Failed: + view.m_numQueries = 0; + view.m_queries = NULL; + + if ( m_readListener != NULL ) + { + m_readListener->OnStatsReadComplete(eStatsReturn_NetworkError, 0, view); + } + + setState(eStatsState_Idle); + + break; + + case eStatsState_Canceled: + app.DebugPrintf("[LeaderboardManager] Setting canceled\n"); + setState(eStatsState_Idle); + break; + + default: // Getting or Idle. + if (m_openSessions == 0 && m_xboxLiveContext != nullptr) + { + m_xboxLiveContext = nullptr; + + app.DebugPrintf("[LeaderboardManager] Tick(): Nulled XboxLiveContext\n"); + } + break; + } +} + +//Open a session +bool DurangoLeaderboardManager::OpenSession() +{ + if (m_openSessions == 0) + { + app.DebugPrintf("[LeaderboardManager] OpenSession()\n"); + + try + { + WXS::User^ user = ProfileManager.GetUser(ProfileManager.GetPrimaryPad()); + if(user != nullptr && user->IsSignedIn && !user->IsGuest) + { + m_xboxLiveContext = ref new MXS::XboxLiveContext(user); + } + else + { + app.DebugPrintf("[LeaderboardManager] OpenSession(): Failed to created new XboxLiveContext, No valid user\n"); + return false; + } + } + catch (Platform::Exception^ ex) + { + m_xboxLiveContext = nullptr; + app.DebugPrintf("[LeaderboardManager] OpenSession(): Failed to created new XboxLiveContext, ret == 0x%08X.\n", ex->HResult); + return false; + } + catch (...) + { + app.DebugPrintf("[LeaderboardManager] OpenSession(): Unknown exception.\n"); + return false; + } + } + else app.DebugPrintf("[LeaderboardManager] OpenSession(): Another session opened, total=%i\n", m_openSessions+1); + + m_openSessions++; + return true; +} + +//Close a session +void DurangoLeaderboardManager::CloseSession() +{ + m_openSessions--; + + if (m_openSessions == 0) + { + if(isIdle()) + { + m_xboxLiveContext = nullptr; + + app.DebugPrintf("[LeaderboardManager] CloseSession(): Nulled XboxLiveContext\n"); + } + } + else app.DebugPrintf("[LeaderboardManager] CloseSession(): %i sessions still open.\n", m_openSessions); +} + +//Delete a session +void DurangoLeaderboardManager::DeleteSession() +{ +} + +//Write the given stats +//This is called synchronously and will not free any memory allocated for views when it is done + +bool DurangoLeaderboardManager::WriteStats(unsigned int viewCount, ViewIn views) +{ + return false; +} + +bool DurangoLeaderboardManager::ReadStats_Friends(LeaderboardReadListener *listener, int difficulty, EStatsType type, PlayerUID myUID, unsigned int startIndex, unsigned int readCount) +{ + // Need to cancel read/write operation first. + if (!isIdle()) return false; + if (!LeaderboardManager::ReadStats_Friends(listener, difficulty, type, myUID, startIndex, readCount)) return false; + setState(eStatsState_GettingLeaderboardInfo); + + if( m_xboxLiveContext == nullptr ) + { + throw ref new Platform::InvalidArgumentException(L"A valid User is required"); + } + + // Request the leaderboard to get ranking information + auto asyncOp = m_xboxLiveContext->LeaderboardService->GetLeaderboardForSocialGroupWithSkipToRankAsync( + ref new P::String(myUID.toString().c_str()), + SERVICE_CONFIG_ID, + ref new P::String(m_socialLeaderboardNames[difficulty][type].c_str()), + MXS::Social::SocialGroupConstants::People, + startIndex, + ref new P::String(L"descending"), + readCount + ); + + runLeaderboardRequest(asyncOp, difficulty, type, readCount, EFilterMode::eFM_Friends); + + return true; +} + +bool DurangoLeaderboardManager::ReadStats_MyScore(LeaderboardReadListener *listener, int difficulty, EStatsType type, PlayerUID myUID, unsigned int readCount) +{ + // Need to cancel read/write operation first. + if (!isIdle()) return false; + if (!LeaderboardManager::ReadStats_MyScore(listener, difficulty, type, myUID, readCount)) return false; + setState(eStatsState_GettingLeaderboardInfo); + + if( m_xboxLiveContext == nullptr ) + { + throw ref new Platform::InvalidArgumentException(L"A valid User is required"); + } + + P::String^ leaderboardName = ref new P::String(m_leaderboardNames[difficulty][type].c_str()); + + // Request the leaderboard to get ranking information + auto asyncOp = m_xboxLiveContext->LeaderboardService->GetLeaderboardWithSkipToUserAsync( + SERVICE_CONFIG_ID, + leaderboardName, + ref new P::String(myUID.toString().c_str()), // skip to this user + readCount + ); + + runLeaderboardRequest(asyncOp, difficulty, type, readCount, EFilterMode::eFM_MyScore); + + return true; +} + +bool DurangoLeaderboardManager::ReadStats_TopRank(LeaderboardReadListener *listener, int difficulty, EStatsType type, unsigned int startIndex, unsigned int readCount) +{ + // Need to cancel read/write operation first. + if (!isIdle()) return false; + if (!LeaderboardManager::ReadStats_TopRank(listener, difficulty, type, startIndex, readCount)) return false; + setState(eStatsState_GettingLeaderboardInfo); + + if( m_xboxLiveContext == nullptr ) + { + throw ref new Platform::InvalidArgumentException(L"A valid User is required"); + } + + P::String^ leaderboardName = ref new P::String(m_leaderboardNames[difficulty][type].c_str()); + + // Request the leaderboard to get ranking information + auto asyncOp = m_xboxLiveContext->LeaderboardService->GetLeaderboardAsync( + SERVICE_CONFIG_ID, + leaderboardName, + startIndex, // skip this many ranks + readCount + ); + + runLeaderboardRequest(asyncOp, difficulty, type, readCount, EFilterMode::eFM_TopRank); + + return true; +} + +//Perform a flush of the stats +void DurangoLeaderboardManager::FlushStats() +{ +} + +//Cancel the current operation +void DurangoLeaderboardManager::CancelOperation() +{ + m_readListener = NULL; + setState(eStatsState_Canceled); + + if(m_leaderboardAsyncOp) m_leaderboardAsyncOp->Cancel(); + if(m_statsAsyncOp) m_statsAsyncOp->Cancel(); + + //if (m_transactionCtx != 0) + //{ + // int ret = sceNpScoreAbortTransaction(m_transactionCtx); + // + // if (ret < 0) + // { + // app.DebugPrintf("[LeaderboardManager] CancelOperation(): Problem encountered aborting current operation, 0x%X.\n", ret); + // } + // else + // { + // app.DebugPrintf("[LeaderboardManager] CancelOperation(): Operation aborted successfully.\n"); + // } + //} + //else app.DebugPrintf("[LeaderboardManager] CancelOperation(): No current operation.\n"); +} + +//Is the leaderboard manager idle. +bool DurangoLeaderboardManager::isIdle() +{ + return getState() == eStatsState_Idle; +} + +void DurangoLeaderboardManager::runLeaderboardRequest(WF::IAsyncOperation<MXSL::LeaderboardResult^>^ asyncOp, int difficulty, EStatsType type, unsigned int readCount, EFilterMode filter) +{ + m_leaderboardAsyncOp = asyncOp; + m_difficulty = difficulty; + m_type = type; + + // Build stat names + m_statNames = ref new PC::Vector<P::String^>(); + m_statNames->Clear(); + for(wstring name : m_leaderboardStatNames[difficulty][type]) + { + m_statNames->Append(ref new P::String(name.c_str())); + } + + app.DebugPrintf("[LeaderboardManager] Running request\n"); + CC::create_task(asyncOp) + .then( [this, readCount, difficulty, type, filter] (CC::task<MXSL::LeaderboardResult^> resultTask) + { + try + { + app.DebugPrintf("[LeaderboardManager] First continuation.\n"); + + m_leaderboardAsyncOp = nullptr; + + MXSL::LeaderboardResult^ lastResult = resultTask.get(); + + app.DebugPrintf( + "Name: %ls - Filter: %ls - Rows: Retrieved=%d Total=%d Requested=%d.\n", + lastResult->DisplayName->Data(), + LeaderboardManager::filterNames[ (int) filter ].c_str(), + lastResult->Rows->Size, lastResult->TotalRowCount, readCount + ); + + //app.DebugPrintf("Column count: %d, Column: %ls, %ls, %d\n", lastResult->Columns->Size, lastResult->Columns->GetAt(0)->DisplayName->Data(), lastResult->Columns->GetAt(0)->StatisticName->Data(), lastResult->Columns->GetAt(0)->StatisticType); + + // 4J-JEV: Fix for Xbox One #162541 - [CRASH] Code: Leaderboards: Title crashes after pressing [B] Back button while changing Leaderboards' filter to 'My Score' + // 4J-JEV: Fix for X1: #165487 - [CRASH] XR-074: Compliance: The title does not properly handle switching to offline session while browsing the Leaderboards. + if (m_xboxLiveContext == nullptr) throw(ref new P::Exception(-1)); + + // If this is My_Score, check that the user appears + if (filter == eFM_MyScore) + { + bool userIncluded = false; + for(int i = 0; i < lastResult->Rows->Size; i++) + { + if (lastResult->Rows->GetAt(i)->XboxUserId == m_xboxLiveContext->User->XboxUserId) userIncluded = true; + } + + // If the user isn't included, don't show the results + if (!userIncluded) + { + m_readCount = 0; + throw(ref new P::Exception(INET_E_DATA_NOT_AVAILABLE)); + } + } + + m_maxRank = lastResult->TotalRowCount; + m_readCount = lastResult->Rows->Size; + + if (m_scores != NULL) delete [] m_scores; + m_scores = new ReadScore[m_readCount]; + ZeroMemory(m_scores, sizeof(ReadScore) * m_readCount); + + m_xboxUserIds->Clear(); + + app.DebugPrintf("[LeaderboardManager] Retrieved Scores:\n"); + for( UINT index = 0; index < lastResult->Rows->Size; index++ ) + { + MXSL::LeaderboardRow^ row = lastResult->Rows->GetAt(index); + + app.DebugPrintf( + "\tIndex: %d\tRank: %d\tPercentile: %.1f%%\tXboxUserId: %ls\tValue: %ls.\n", + index, + row->Rank, + row->Percentile * 100, + row->XboxUserId->Data(), + row->Values->GetAt(0)->Data() + ); + + m_scores[index].m_name = row->Gamertag->Data(); + m_scores[index].m_rank = row->Rank; + m_scores[index].m_uid = PlayerUID(row->XboxUserId->Data()); + + // 4J-JEV: Added to help determine if this player's score is hidden due to their privacy settings. + m_scores[index].m_totalScore = (unsigned long) _fromString<long long>(row->Values->GetAt(0)->Data()); + + m_xboxUserIds->Append(row->XboxUserId); + } + + if(m_readCount > 0) + { + vector<PlayerUID> xuids = vector<PlayerUID>(); + for(int i = 0; i < lastResult->Rows->Size; i++) + { + xuids.push_back(PlayerUID(lastResult->Rows->GetAt(i)->XboxUserId->Data())); + } + m_waitingForProfiles = true; + ProfileManager.GetProfiles(xuids, &GetProfilesCallback, this); + setState(eStatsState_ReceivedLeaderboardInfo); + } + else + { + // we hit the end of the list + app.DebugPrintf("Reach the end\n"); + setState(eStatsState_Ready); + } + } + catch (Platform::Exception^ ex) + { + m_leaderboardAsyncOp = nullptr; + if (ex->HResult == INET_E_DATA_NOT_AVAILABLE) + { + // we hit the end of the list + app.DebugPrintf("ERROR: Reach the end\n"); + setState(eStatsState_Ready); + } + else if(ex->HResult == HTTP_E_STATUS_NOT_FOUND) + { + app.DebugPrintf("Error calling GetLeaderboardAsync function: 404 Not Found - 0x%0.8x\n", ex->HResult); + setState(eStatsState_Failed); + } + else + { + app.DebugPrintf("Error calling GetLeaderboardAsync function: 0x%0.8x\n", ex->HResult); + setState(eStatsState_Failed); + } + } + catch (...) + { + app.DebugPrintf("[LeaderboardManager] Unknown exception.\n"); + } + }); +} + +void DurangoLeaderboardManager::updateStatsInfo(int userIndex, int difficulty, EStatsType type, WFC::IVectorView<MXS::UserStatistics::Statistic^>^ statsResult) +{ + if (m_scores) + { + LeaderboardManager::ReadScore *userScore = &m_scores[userIndex]; + + switch (type) + { + case eStatsType_Farming: userScore->m_statsSize = 6; break; + case eStatsType_Mining: userScore->m_statsSize = 7; break; + case eStatsType_Kills: userScore->m_statsSize = 7; break; + case eStatsType_Travelling: userScore->m_statsSize = 4; break; + } + + int statIndex = 0, sumScores = 0; + for(wstring statName : m_leaderboardStatNames[difficulty][type]) + { + bool found = false; + for(auto result : statsResult) + { + if(statName.compare(result->StatisticName->Data()) == 0) + { + userScore->m_statsData[statIndex] = _fromString<unsigned long>(result->Value->Data()); + found = true; + break; + } + } + if(!found) + { + userScore->m_statsData[statIndex] = 0; + } + + sumScores += userScore->m_statsData[statIndex]; + ++statIndex; + } + + if ( (sumScores == 0) && (userScore->m_totalScore > 0) ) + { + app.DebugPrintf("[LeaderboardManager] Player '%s' (rank %d) likely has privacy settings enabled.\n", userScore->m_name.c_str(), userScore->m_rank); + userScore->m_idsErrorMessage = IDS_LEADERBOARD_SCORE_HIDDEN; + } + } +} + +void DurangoLeaderboardManager::GetProfilesCallback(LPVOID param, std::vector<Microsoft::Xbox::Services::Social::XboxUserProfile^> profiles) +{ + DurangoLeaderboardManager *dlm = (DurangoLeaderboardManager *)param; + + app.DebugPrintf("[LeaderboardManager] GetProfilesCallback, profiles.size() == %d.\n", profiles.size()); + + if (profiles.size() > 0) + { + dlm->m_displayNames = vector<wstring>(); + for (int i = 0; i < profiles.size(); i++) + { + dlm->m_displayNames.push_back(profiles[i]->GameDisplayName->Data()); + } + } + else + { + dlm->setState(eStatsState_Failed); + } + + dlm->m_waitingForProfiles = false; +} + +DurangoLeaderboardManager::EStatsState DurangoLeaderboardManager::getState() +{ + return m_eStatsState; +} + +void DurangoLeaderboardManager::setState(EStatsState newState) +{ + EnterCriticalSection(&m_csStatsState); + + bool validTransition = false; + + switch(m_eStatsState) + { + case eStatsState_Idle: + switch(newState) + { + case eStatsState_GettingLeaderboardInfo: + validTransition = true; + break; + }; + break; + case eStatsState_GettingLeaderboardInfo: + switch(newState) + { + case eStatsState_Ready: + case eStatsState_ReceivedLeaderboardInfo: + case eStatsState_Canceled: + case eStatsState_Failed: + validTransition = true; + break; + }; + break; + break; + case eStatsState_ReceivedLeaderboardInfo: + switch(newState) + { + case eStatsState_GettingStatsInfo: + case eStatsState_Canceled: + case eStatsState_Failed: + validTransition = true; + break; + }; + break; + case eStatsState_GettingStatsInfo: + switch(newState) + { + case eStatsState_Ready: + case eStatsState_Canceled: + case eStatsState_Failed: + validTransition = true; + break; + }; + break; + case eStatsState_Failed: + switch(newState) + { + case eStatsState_Idle: + validTransition = true; + break; + }; + break; + case eStatsState_Ready: + switch(newState) + { + case eStatsState_Canceled: + case eStatsState_Idle: + case eStatsState_Failed: + validTransition = true; + break; + }; + break; + case eStatsState_Canceled: + switch(newState) + { + case eStatsState_Ready: + newState = eStatsState_Idle; + case eStatsState_Idle: + validTransition = true; + break; + }; + break; + }; + +#ifndef _CONTENT_PACKAGE + app.DebugPrintf( + "[LeaderboardManager] %s state transition:\t%ls(%d) -> %ls(%d).\n", + (validTransition ? "Valid" : "INVALID"), + stateToString(m_eStatsState).c_str(), m_eStatsState, + stateToString(newState).c_str(), newState + ); +#endif + + if (validTransition) + { + m_eStatsState = newState; + } + + LeaveCriticalSection(&m_csStatsState); +} + +wstring DurangoLeaderboardManager::stateToString(EStatsState eState) +{ + switch (eState) + { + case eStatsState_Idle: return L"eStatsState_Idle"; + case eStatsState_GettingLeaderboardInfo: return L"eStatsState_GettingLeaderboardInfo"; + case eStatsState_ReceivedLeaderboardInfo: return L"eStatsState_ReceivedLeaderboardInfo"; + case eStatsState_GettingStatsInfo: return L"eStatsState_GettingStatsInfo"; + case eStatsState_ReceivedStatsInfo: return L"eStatsState_ReceivedStatsInfo"; + case eStatsState_Failed: return L"eStatsState_Failed"; + case eStatsState_Ready: return L"eStatsState_Ready"; + case eStatsState_Canceled: return L"eStatsState_Canceled"; + case eStatsState_Max: return L"eStatsState_MAX"; + default: return L"UNKNOWN"; + } +}
\ No newline at end of file diff --git a/Minecraft.Client/Durango/Leaderboards/DurangoLeaderboardManager.h b/Minecraft.Client/Durango/Leaderboards/DurangoLeaderboardManager.h new file mode 100644 index 00000000..233d8b71 --- /dev/null +++ b/Minecraft.Client/Durango/Leaderboards/DurangoLeaderboardManager.h @@ -0,0 +1,94 @@ +#pragma once + +#include "Common\Leaderboards\LeaderboardManager.h" + +namespace P = Platform; +namespace PC = Platform::Collections; +namespace WF = Windows::Foundation; +namespace WFC = Windows::Foundation::Collections; +namespace MXSL = Microsoft::Xbox::Services::Leaderboard; + +class DurangoLeaderboardManager : public LeaderboardManager +{ +protected: + enum EStatsState + { + eStatsState_Idle, + eStatsState_GettingLeaderboardInfo, + eStatsState_ReceivedLeaderboardInfo, + eStatsState_GettingStatsInfo, + eStatsState_ReceivedStatsInfo, + eStatsState_Failed, + eStatsState_Ready, + eStatsState_Canceled, + //eStatsState_Writing, + eStatsState_Max + }; + +private: + unsigned short m_openSessions; + + CRITICAL_SECTION m_csStatsState; + EStatsState m_eStatsState; //State of the stats read + + ReadScore *m_scores; + unsigned int m_maxRank; + + MXS::XboxLiveContext^ m_xboxLiveContext; + wstring m_leaderboardNames[4][eStatsType_MAX]; + wstring m_socialLeaderboardNames[4][eStatsType_MAX]; + std::vector<wstring> m_leaderboardStatNames[4][eStatsType_MAX]; + + // Display names for the current scores + std::vector<wstring> m_displayNames; + bool m_waitingForProfiles; + + int m_difficulty; + EStatsType m_type; + PC::Vector<P::String^>^ m_statNames; + PC::Vector<P::String^>^ m_xboxUserIds; + WF::IAsyncOperation<MXSL::LeaderboardResult^>^ m_leaderboardAsyncOp; + WF::IAsyncOperation<WFC::IVectorView<Microsoft::Xbox::Services::UserStatistics::UserStatisticsResult^ >^ >^ m_statsAsyncOp; + +public: + DurangoLeaderboardManager(); + + virtual void Tick(); + + //Open a session + virtual bool OpenSession(); + + //Close a session + virtual void CloseSession(); + + //Delete a session + virtual void DeleteSession(); + + //Write the given stats + //This is called synchronously and will not free any memory allocated for views when it is done + + virtual bool WriteStats(unsigned int viewCount, ViewIn views); + + virtual bool ReadStats_Friends(LeaderboardReadListener *listener, int difficulty, EStatsType type, PlayerUID myUID, unsigned int startIndex, unsigned int readCount); + virtual bool ReadStats_MyScore(LeaderboardReadListener *listener, int difficulty, EStatsType type, PlayerUID myUID, unsigned int readCount); + virtual bool ReadStats_TopRank(LeaderboardReadListener *listener, int difficulty, EStatsType type, unsigned int startIndex, unsigned int readCount); + + //Perform a flush of the stats + virtual void FlushStats(); + + //Cancel the current operation + virtual void CancelOperation(); + + //Is the leaderboard manager idle. + virtual bool isIdle(); + + static void GetProfilesCallback(LPVOID param, std::vector<Microsoft::Xbox::Services::Social::XboxUserProfile^> profiles); + +private: + void runLeaderboardRequest(WF::IAsyncOperation<MXSL::LeaderboardResult^>^ asyncOp, int difficulty, EStatsType type, unsigned int readCount, EFilterMode filter); + void updateStatsInfo(int userIndex, int difficulty, EStatsType type, WFC::IVectorView<MXS::UserStatistics::Statistic^>^ statsResult); + + EStatsState getState(); + void setState(EStatsState newState); + wstring stateToString(EStatsState eState); +}; diff --git a/Minecraft.Client/Durango/Leaderboards/DurangoStatsDebugger.cpp b/Minecraft.Client/Durango/Leaderboards/DurangoStatsDebugger.cpp new file mode 100644 index 00000000..caa5857e --- /dev/null +++ b/Minecraft.Client/Durango/Leaderboards/DurangoStatsDebugger.cpp @@ -0,0 +1,487 @@ +#include "stdafx.h" + +#include "..\Minecraft.World\Tile.h" +#include "..\Minecraft.World\Item.h" + +#include "..\Minecraft.World\DurangoStats.h" + +#include "..\Minecraft.World\EntityIO.h" + +#include "..\Minecraft.World\StringHelpers.h" + +#include "Common\Console_Awards_enum.h" + +#include "DurangoStatsDebugger.h" + +namespace WFC = Windows::Foundation::Collections; +namespace CC = concurrency; + +StatParam::StatParam(const wstring &base) +{ + m_base = base; + //m_numArgs = numArgs; + m_args = vector<int>(); + + unsigned int count=0; + wstring::size_type pos =base.find(L"*"); + while(pos!=string::npos) + { + count++; + pos=base.find(L"*",pos+1); + } + + m_numArgs = count; +} + +void StatParam::addArgs(int v1, ...) +{ + va_list argptr; + va_start(argptr, v1); + m_args.push_back(v1); + for (int i=0; i<(m_numArgs-1); i++) + { + int vi = va_arg(argptr, int); + m_args.push_back(vi); + } + va_end(argptr); +} + +vector<wstring> *StatParam::getStats() +{ + vector<wstring> *out = new vector<wstring>(); + + static const int MAXSIZE = 256; + static const wstring SUBSTR = L"*"; + + wstring wstr_itr, wstr_num; + + if (m_args.size() <= 0 || m_numArgs <= 0) + { + out->push_back(m_base); + } + else + { + for (int i = 0; i < m_args.size(); i += m_numArgs) + { + wstr_itr = m_base; + + if (m_numArgs > 0) + { + for (int j=0; j<m_numArgs; j++) + { + wstr_num = to_wstring(m_args.at(i+j)); + size_t sz = wstr_itr.find(SUBSTR); + + if (sz != wstring::npos) + wstr_itr.replace(sz, SUBSTR.length(), wstr_num); + } + } + + out->push_back( wstr_itr ); + } + } + + return out; +} + +DurangoStatsDebugger *DurangoStatsDebugger::instance = NULL; + +DurangoStatsDebugger::DurangoStatsDebugger() +{ + InitializeCriticalSection(&m_retrievedStatsLock); +} + +vector<wstring> *DurangoStatsDebugger::getStats() +{ + vector<wstring> *out = new vector<wstring>(); + + for (auto it = m_stats.begin(); it!=m_stats.end(); it++) + { + vector<wstring> *sublist = (*it)->getStats(); + out->insert(out->end(), sublist->begin(), sublist->end()); + } + + return out; +} + +DurangoStatsDebugger *DurangoStatsDebugger::Initialize() +{ + DurangoStatsDebugger *out = new DurangoStatsDebugger(); + + StatParam *sp = new StatParam(L"McItemAcquired.AcquisitionMethodId.*.ItemId.*.ItemAux.*"); + sp->addArgs(1, Tile::dirt_Id, 0); // works + sp->addArgs(2, Tile::dirt_Id, 0); // works + + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 0); // fixed (+ach 'Rainbow Collection') + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 1); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 2); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 3); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 4); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 5); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 6); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 7); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 8); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 9); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 10); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 11); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 12); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 13); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 14); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::cloth_Id, 15); + + out->m_stats.push_back(sp); + + sp = new StatParam(L"McItemAcquired.AcquisitionMethodId.*.ItemId.*"); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Tile::dirt_Id); // works + sp->addArgs(DsItemEvent::eAcquisitionMethod_Crafted, Item::milk_Id); // works. + sp->addArgs(DsItemEvent::eAcquisitionMethod_Crafted, Tile::dirt_Id); // works. + sp->addArgs(DsItemEvent::eAcquisitionMethod_Crafted, Item::porkChop_cooked_Id); // BROKEN! (ach 'Pork Chop' configured incorrectly) + sp->addArgs(DsItemEvent::eAcquisitionMethod_Crafted, Item::cake_Id); // works. (ach 'The Lie' configured incorrectly) + sp->addArgs(DsItemEvent::eAcquisitionMethod_Bought, Item::emerald_Id); // fixed (+ach) + sp->addArgs(DsItemEvent::eAcquisitionMethod_Crafted, Item::ironIngot_Id); // works. (+ach 'Acquired Hardware') + sp->addArgs(DsItemEvent::eAcquisitionMethod_Pickedup, Item::fish_raw_Id); // works. (+ach 'Delicious Fish') + sp->addArgs(DsItemEvent::eAcquisitionMethod_Crafted, Item::fish_cooked_Id); // works. (+ach 'Delicious Fish') + sp->addArgs(DsItemEvent::eAcquisitionMethod_Crafted, Item::sign_Id); + sp->addArgs(DsItemEvent::eAcquisitionMethod_Crafted, Item::flowerPot_Id); // FIXING! + out->m_stats.push_back(sp); + + sp = new StatParam(L"McItemAcquired.DifficultyLevelId.*.AcquisitionMethodId.*.ItemId.*"); + sp->addArgs(1, 1, Tile::dirt_Id); // works + sp->addArgs(2, 2, Tile::dirt_Id); // works + out->m_stats.push_back(sp); + + sp = new StatParam(L"McItemUsed.ItemId.*.ItemAux.*"); + //sp->addArgs(Item::apple_Id, 0); + //sp->addArgs(Item::cake_Id, 0); + sp->addArgs(Item::beef_raw_Id, 0); // works + sp->addArgs(Item::porkChop_cooked_Id, 0); // works + out->m_stats.push_back(sp); + + sp = new StatParam(L"MinHungerWhenEaten.ItemId.*"); + //sp->addArgs(Item::apple_Id); + //sp->addArgs(Item::cake_Id); + sp->addArgs(Item::beef_raw_Id); // works + sp->addArgs(Item::rotten_flesh_Id); // works (+ach IronBelly) + out->m_stats.push_back(sp); + + sp = new StatParam(L"BlockBroken.BlockId.*"); + sp->addArgs( Tile::dirt_Id ); + sp->addArgs( Tile::rock_Id ); + sp->addArgs( Tile::emeraldOre_Id ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"BlockBroken.BlockId.*.BlockAux.*"); + sp->addArgs( Tile::dirt_Id, 0 ); + sp->addArgs( Tile::rock_Id, 0 ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"BlockBroken.DifficultyLevelId.*.BlockId.*"); + sp->addArgs( 1, Tile::dirt_Id ); + sp->addArgs( 2, Tile::dirt_Id ); + sp->addArgs( 1, Tile::rock_Id ); + sp->addArgs( 2, Tile::rock_Id ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"BlockBroken"); + sp->addArgs( -1 ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"BlockPlaced.BlockId.*"); + sp->addArgs( Tile::dirt_Id ); + sp->addArgs( Tile::stoneBrick_Id ); + sp->addArgs( Tile::sand_Id ); // works + sp->addArgs( Tile::sign_Id ); // fixed + sp->addArgs( Tile::wallSign_Id ); // fixed + out->m_stats.push_back(sp); + + sp = new StatParam(L"MobKilled.KillTypeId.*.EnemyRoleId.*.PlayerWeaponId.*"); // BROKEN! + sp->addArgs( /*MELEE*/ 0, ioid_Cow, 0 ); + sp->addArgs( /*MELEE*/ 0, ioid_Cow, Item::sword_stone_Id ); + sp->addArgs( /*MELEE*/ 0, ioid_Pig, Item::sword_stone_Id ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"MaxKillDistance.KillTypeId.*.EnemyRoleId.*.PlayerWeaponId.*"); // BROKEN! + sp->addArgs( /*MELEE*/ 0, ioid_Cow, Item::sword_stone_Id ); + sp->addArgs( /*MELEE*/ 0, ioid_Pig, Item::sword_stone_Id ); + sp->addArgs( /*RANGE*/ 1, ioid_Creeper, ioid_Arrow ); // FIXING! + out->m_stats.push_back(sp); + + sp = new StatParam(L"MobKilled.KillTypeId.*.EnemyRoleId.*"); // BROKEN! + sp->addArgs( /*MELEE*/ 0, ioid_Cow ); + sp->addArgs( /*MELEE*/ 0, ioid_Pig ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"MobKilled.EnemyRoleId.*"); // BROKEN! + sp->addArgs( ioid_Cow ); + sp->addArgs( ioid_Pig ); + sp->addArgs( ioid_Zombie ); + sp->addArgs( ioid_Spiderjocky ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"MobKilledTotal.DifficultyLevelId.*.EnemyRoleId.*"); // BROKEN! + sp->addArgs( 1, ioid_Cow ); + sp->addArgs( 2, ioid_Cow ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"MobKilledTotal"); // BROKEN! + sp->addArgs(-1); + out->m_stats.push_back(sp); + + sp = new StatParam(L"MobInteract.MobId.*.Interaction.*"); + // sp->addArgs( ioid_Cow, /*MILKED*/ 6 ); no longer an interaction type. + sp->addArgs( ioid_Cow, /*BRED*/ 1 ); // fixed (+ach) + sp->addArgs( ioid_Sheep, /*SHEARED*/ 5 ); // works (+ach) + sp->addArgs( ioid_VillagerGolem, /*CRAFTED*/ 4 ); // works (+ach) + sp->addArgs( ioid_Ozelot, /*TAMED*/ 2 ); // works (+ach) + sp->addArgs( ioid_Zombie, /*CURED*/ 3 ); // fixed (+ach) + out->m_stats.push_back(sp); + + sp = new StatParam(L"EnteredNewBiome.BiomeId.*"); + for (int i=0; i<20; i++) + sp->addArgs( i ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"AchievementGet.AchievementId.*"); + sp->addArgs( eAward_TakingInventory ); // works (+ach) + sp->addArgs( eAward_WhenPigsFly ); // works (+ach) + sp->addArgs( eAward_InToTheNether ); // works (+ach) + sp->addArgs( eAward_theEnd ); // works (+ach) + sp->addArgs( eAward_winGame ); // fixed (+ach) + sp->addArgs( eAward_diamondsToYou ); + sp->addArgs( eAward_stayinFrosty ); // works (achievement configured incorrectly) + sp->addArgs( eAward_ironMan ); // fixed (+ach) + sp->addArgs( eAward_renewableEnergy ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"TimePlayed.DifficultyLevelId.*"); // BROKEN! + for (int i=0; i<4; i++) sp->addArgs(i); // Difficulty Levels. + out->m_stats.push_back(sp); + + sp = new StatParam(L"DistanceTravelled.DifficultyLevelId.*.TravelMethodId.*"); + for (int i = 0; i<4; i++) + { + sp->addArgs( i, DsTravel::eMethod_walk ); + sp->addArgs( i, DsTravel::eMethod_climb ); + sp->addArgs( i, DsTravel::eMethod_fall ); + sp->addArgs( i, DsTravel::eMethod_minecart ); + sp->addArgs( i, DsTravel::eMethod_swim ); + sp->addArgs( i, DsTravel::eMethod_pig ); + } + out->m_stats.push_back(sp); + + sp = new StatParam(L"DistanceTravelled"); + sp->addArgs( /*NO ARGUMENTS*/ -1 ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"PlayedMusicDisc.DiscId.*"); // works (+ach) + for (int i = 2256; i < 2268; i++) + sp->addArgs( i ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"ChestfulOfCobblestone"); // works. (+ach) + sp->addArgs( /*NO ARGUMENTS*/ -1 ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"Overkill"); // works. (+ach) + sp->addArgs( /*NO ARGUMENTS*/ -1 ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"OnARail"); // works. (+ach) + sp->addArgs( /*NO ARGUMENTS*/ -1 ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"Leaderboard.LeaderboardId.*.DifficultyLevelId.*"); + for (int i = 0; i < 4; i++) + { + sp->addArgs( eLeaderboardId_FARMING, i ); + sp->addArgs( eLeaderboardId_MINING, i ); + sp->addArgs( eLeaderboardId_TRAVELLING, i ); + sp->addArgs( eLeaderboardId_KILLING, i ); + } + out->m_stats.push_back(sp); + + + // Debugging 1 // + + sp = new StatParam(L"DistanceTravelled"); + sp->addArgs( /*NO ARGUMENTS*/ -1 ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"BlockBroken"); + sp->addArgs( /*NO ARGUMENTS*/ -1 ); + out->m_stats.push_back(sp); + + sp = new StatParam(L"MobKilledTotal"); + sp->addArgs( /*NO ARGUMENTS*/ -1 ); + out->m_stats.push_back(sp); + + return out; +} + +void DurangoStatsDebugger::PrintStats(int iPad) +{ + if (instance == NULL) instance = Initialize(); + + vector<wstring> *tmp = instance->getStats(); + instance->m_printQueue.insert(instance->m_printQueue.end(), tmp->begin(), tmp->end()); + + // app.DebugPrintf("[DEBUG] START\n"); + // for (wstring t : *tmp) app.DebugPrintf("[DEBUG] %s\n", wstringtofilename(t)); + // app.DebugPrintf("[DEBUG] END\n"); + + instance->retrieveStats(iPad); + + app.DebugPrintf("[DurangoStatsDebugger] (%i) Results returned, starting printing.\n", instance->m_retrievedStats.size()); + for (StatResult result : instance->m_retrievedStats) + { + app.DebugPrintf("[DSB] '%s' == ", wstringtofilename(result.m_statName)); + app.DebugPrintf("%s\n", wstringtofilename(result.m_score)); + } + + // Empty list. + EnterCriticalSection(&instance->m_retrievedStatsLock); + instance->m_retrievedStats.erase( + instance->m_retrievedStats.begin(), + instance->m_retrievedStats.end() + ); + LeaveCriticalSection(&instance->m_retrievedStatsLock); + +} + +void DurangoStatsDebugger::retrieveStats(int iPad) +{ + MXS::XboxLiveContext ^xboxLiveContext; + try + { + WFC::IVectorView<Windows::Xbox::System::User^>^ userList = Windows::Xbox::System::User::Users; + if( userList != nullptr ) + { + for (Windows::Xbox::System::User^ user : userList) + { + if( user->IsSignedIn && !user->IsGuest ) + { + xboxLiveContext = ref new MXS::XboxLiveContext(user); + break; + } + } + + if (xboxLiveContext == nullptr) + { + app.DebugPrintf("[DurangoStatsDebugger] Problem occured while creating 'XboxLiveContext'.\n"); return; + } + } + } + catch (Platform::Exception^ ex) + { + app.DebugPrintf("[DurangoStatsDebugger] Problem occured while creating 'XboxLiveContext'.\n"); return; + } + + if ( xboxLiveContext ) { app.DebugPrintf("[DurangoStatsDebugger] 'XboxLiveContext' created successfully.\n"); } + else { app.DebugPrintf("[DurangoStatsDebugger] 'XboxLiveContext' not created.\n"); return; } + + PlayerUID xuid; + ProfileManager.GetXUID(iPad, &xuid, true); + + const int readCount = 1, difficulty = 1, type = 0; + + // ----------------------------------------- // + + byte runningThreads = 0; + byte *r_runningThreads = &runningThreads; + + if (xuid.toString().compare(L"") == 0) + { + app.DebugPrintf("[DurangoStatsDebugger] NO LOGGED IN USER!\n"); + return; + } + + string plrname= wstringtofilename(xuid.toString()); + app.DebugPrintf( + "[DurangoStatsDebugger] Retrieving (%i) stats for '%s'.\n", + m_printQueue.size(), + plrname.c_str() + ); + + static const unsigned short R_SIZE = 20; + + // Create Stat retrieval threads until there is no long any stats to start retrieving. + while ( !instance->m_printQueue.empty() ) + { + vector<wstring> *printing = new vector<wstring>(); + + if (m_printQueue.size() > R_SIZE) + { + printing->insert( printing->end(), m_printQueue.begin(), m_printQueue.begin() + R_SIZE ); + m_printQueue.erase( m_printQueue.begin(), m_printQueue.begin() + R_SIZE ); + } + else + { + printing->insert( printing->end(), m_printQueue.begin(), m_printQueue.end() ); + m_printQueue.erase( m_printQueue.begin(), m_printQueue.end() ); + } + + // ------------------------------------------ // + + app.DebugPrintf("[DurangoStatsDebugger] Starting retrieval operation (%i/%i stats).\n", printing->size(), R_SIZE); + + runningThreads++; + + // Fill statNames string. + PC::Vector<P::String^>^ statNames = ref new PC::Vector<P::String^>(); + for ( auto it = printing->begin(); it != printing->end(); it++ ) + statNames->Append( ref new P::String(it->c_str()) ); + + // Create vector of the XboxId we want. + PC::Vector<P::String^>^ xboxUserIds = ref new PC::Vector<P::String^>(); + xboxUserIds->Append(ref new P::String(xuid.toString().c_str())); + + auto asyncOp = xboxLiveContext->UserStatisticsService->GetMultipleUserStatisticsAsync( + xboxUserIds->GetView(), // the collection of Xbox user IDs whose stats we want to retrieve + SERVICE_CONFIG_ID, // the service config that contains the stats we want + statNames->GetView() // a list of stat names we want + ); + + CC::create_task(asyncOp) + .then( [this,printing,iPad,r_runningThreads] (CC::task<WFC::IVectorView<MXS::UserStatistics::UserStatisticsResult^>^> resultListTask) + { + try + { + WFC::IVectorView<MXS::UserStatistics::UserStatisticsResult^>^ resultList = resultListTask.get(); + int userIndex = 0; + for( MXS::UserStatistics::UserStatisticsResult^ result : resultList ) + { + for( UINT index = 0; index<result->ServiceConfigurationStatistics->Size; index++ ) + { + MXS::UserStatistics::ServiceConfigurationStatistic^ configStat = result->ServiceConfigurationStatistics->GetAt(index); + + app.DebugPrintf("[DurangoStatsDebugger] Retrieve complete, %i results returned.\n", configStat->Statistics->Size); + + for (auto result : configStat->Statistics) + { + StatResult sr = { iPad, result->StatisticName->Data(), result->Value->Data() }; + this->addRetrievedStat(sr); + } + } + ++userIndex; + } + + + } + catch (Platform::Exception ^ex) + { + app.DebugPrintf("[DurangoStatsDebugger] resultListTask.get() encountered an exception:\n\t'%s'\n", ex->ToString()->Data() ); + } + + (*r_runningThreads)--; + }); + } + + while (runningThreads > 0) Sleep(5); +} + +void DurangoStatsDebugger::addRetrievedStat(StatResult result) +{ + EnterCriticalSection(&m_retrievedStatsLock); + m_retrievedStats.push_back(result); + LeaveCriticalSection(&m_retrievedStatsLock); +}
\ No newline at end of file diff --git a/Minecraft.Client/Durango/Leaderboards/DurangoStatsDebugger.h b/Minecraft.Client/Durango/Leaderboards/DurangoStatsDebugger.h new file mode 100644 index 00000000..f2ac0383 --- /dev/null +++ b/Minecraft.Client/Durango/Leaderboards/DurangoStatsDebugger.h @@ -0,0 +1,94 @@ +#pragma once + +#include "stdafx.h" + +namespace P = Platform; +namespace PC = Platform::Collections; +namespace WF = Windows::Foundation; +namespace WFC = Windows::Foundation::Collections; +namespace MXSL = Microsoft::Xbox::Services::Leaderboard; + +class StatParam +{ +private: + wstring m_base; + int m_numArgs; + + vector<int> m_args; + +public: + StatParam(const wstring &base); + + void addArgs(int v1, ...); + + vector<wstring> *getStats(); + +}; + + +class DurangoStatsDebugger +{ +protected: + static DurangoStatsDebugger *instance; + + enum + { + ioid_Arrow = 10, + + ioid_Spiderjocky = 49, + ioid_Creeper = 50, + ioid_Skeleton, + ioid_Spider, + ioid_Giant, + ioid_Zombie, + ioid_Slime, + ioid_Ghast, + ioid_PigZombie, + ioid_Enderman, + ioid_CaveSpider, + ioid_Silverfish, + ioid_Blaze, + ioid_LavaSlime, + ioid_EnderDragon, + + ioid_Pig = 90, + ioid_Sheep, + ioid_Cow, + ioid_Chicken, + ioid_Squid, + ioid_Wolf, + ioid_MushroomCow, + ioid_SnowMan, + ioid_Ozelot, + ioid_VillagerGolem, + }; + + DurangoStatsDebugger(); + + vector<StatParam *> m_stats; + + vector<wstring> *getStats(); + +public: + static DurangoStatsDebugger *Initialize(); + + static void PrintStats(int iPad); + +private: + vector<wstring> m_printQueue; + + void retrieveStats(int iPad); + + typedef struct + { + int m_iPad; + wstring m_statName; + wstring m_score; + } StatResult; + + CRITICAL_SECTION m_retrievedStatsLock; + + vector<StatResult> m_retrievedStats; + + void addRetrievedStat(StatResult result); +};
\ No newline at end of file diff --git a/Minecraft.Client/Durango/Leaderboards/GameProgress.cpp b/Minecraft.Client/Durango/Leaderboards/GameProgress.cpp new file mode 100644 index 00000000..dd07f3e9 --- /dev/null +++ b/Minecraft.Client/Durango/Leaderboards/GameProgress.cpp @@ -0,0 +1,107 @@ +#include "stdafx.h" + +#include "Durango\ServiceConfig\Events-XBLA.8-149E11AEEvents.h" + +#include "..\Minecraft.World\DurangoStats.h" + +#include "GameProgress.h" + +namespace WFC = Windows::Foundation::Collections; +namespace MXSA = Microsoft::Xbox::Services::Achievements; +namespace CC = concurrency; + +GameProgress *GameProgress::instance = NULL; + +void GameProgress::Flush(int iPad) +{ + if (instance == NULL) + instance = new GameProgress(); + + instance->updatePlayer(iPad); +} + +void GameProgress::Tick() +{ + if (instance == NULL) + instance = new GameProgress(); + + long long currentTime = System::currentTimeMillis(); + if ( (currentTime - instance->m_lastUpdate) > (UPDATE_FREQUENCY / 4) ) + { + instance->updatePlayer(instance->m_nextPad); + instance->m_nextPad = ++instance->m_nextPad % MAX_LOCAL_PLAYERS; + instance->m_lastUpdate = currentTime; + } +} + +GameProgress::GameProgress() +{ + m_nextPad = 0; + m_lastUpdate = 0; +} + +void GameProgress::updatePlayer(int iPad) +{ + if ( ProfileManager.IsGuest(iPad) || !ProfileManager.IsSignedInLive(iPad) ) return; + + PlayerUID uid; + ProfileManager.GetXUID(iPad, &uid, true); + + WXS::User^ user = ProfileManager.GetUser(iPad); + + if (user == nullptr) return; + + MXS::XboxLiveContext ^xlc = ref new MXS::XboxLiveContext(user); + + // Get these while they are still valid. + LPCGUID playerSession = DurangoStats::getPlayerSession(); + + CC::create_task( + xlc->AchievementService->GetAchievementsForTitleIdAsync( + ref new Platform::String(uid.toString().c_str()), // Xuid + 0x149E11AE, // TitleId + MXSA::AchievementType::Persistent, // Use regular achievements (not challenges) + false, // Unlocked only + MXSA::AchievementOrderBy::UnlockTime, // Order (we don't really care) + 0, // skipItems (start index) + 200 // MaxItems + ) + ).then( [this,iPad,uid,playerSession] (CC::task<MXSA::AchievementsResult^> resultTask) + { + try + { + int achievementsUnlocked = 0; + + MXSA::AchievementsResult^ result = resultTask.get(); + if(result) + { + for (unsigned int i = 0, iMax = result->Items->Size; i < iMax; i++) + { + MXSA::Achievement^ ach = result->Items->GetAt(i); + if (ach->ProgressState == MXSA::AchievementProgressState::Achieved) + achievementsUnlocked++; + } + + float gameprogress; + if (EventWriteGameProgress( + uid.toString().c_str(), + playerSession, + gameprogress = calcGameProgress(achievementsUnlocked) ) + == 0) + { + app.DebugPrintf("<%ls> GameProgress(%.1f)\n", uid.toString().c_str(), gameprogress); + } + } + } + catch (Platform::Exception ^ex) + { + app.DebugPrintf("GameProgress:: Error, couldn't contact the achievments service (?): %ls", ex->Message->Data()); + } + }); + +} + +float GameProgress::calcGameProgress(int achievementsUnlocked) +{ + return (float) achievementsUnlocked / 0.60f; +}
\ No newline at end of file diff --git a/Minecraft.Client/Durango/Leaderboards/GameProgress.h b/Minecraft.Client/Durango/Leaderboards/GameProgress.h new file mode 100644 index 00000000..8ebb2716 --- /dev/null +++ b/Minecraft.Client/Durango/Leaderboards/GameProgress.h @@ -0,0 +1,23 @@ +#pragma once + +class GameProgress +{ +private: + static GameProgress *instance; + +public: + static const long long UPDATE_FREQUENCY = 64 * 1000; + + static void Tick(); + static void Flush(int iPad); + +protected: + GameProgress(); + + int m_nextPad; + long long m_lastUpdate; + + void updatePlayer(int iPad); + + float calcGameProgress(int achievementsUnlocked); +};
\ No newline at end of file |
