diff --git a/Minecraft.Client/Common/Consoles_App.cpp b/Minecraft.Client/Common/Consoles_App.cpp index 0ca50199..02f56258 100644 --- a/Minecraft.Client/Common/Consoles_App.cpp +++ b/Minecraft.Client/Common/Consoles_App.cpp @@ -954,6 +954,13 @@ void CMinecraftApp::InitGameSettings() SetDefaultOptions(pProfileSettings,i,false); #endif + + Minecraft* minecraft = Minecraft::GetInstance(); + if (minecraft != nullptr && minecraft->stats[i] != nullptr) + { + minecraft->stats[i]->clear(); + minecraft->stats[i]->parse(GameSettingsA[i]); + } } } diff --git a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp index 1c08827b..2b54b2eb 100644 --- a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp +++ b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp @@ -15,7 +15,7 @@ #include "../../Minecraft.h" #include "../../Options.h" #include "../../Minecraft.World/Level.h" -#include "../../MultiplayerLevel.h" +#include "../../MultiPlayerLevel.h" #include "../../../Minecraft.World/Enchantment.h" #ifdef __ORBIS__ #include diff --git a/Minecraft.Client/Common/UI/UIScene_LeaderboardsMenu.cpp b/Minecraft.Client/Common/UI/UIScene_LeaderboardsMenu.cpp index ad27fa9a..57f11f45 100644 --- a/Minecraft.Client/Common/UI/UIScene_LeaderboardsMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_LeaderboardsMenu.cpp @@ -424,6 +424,10 @@ void UIScene_LeaderboardsMenu::handleInput(int iPad, int key, bool repeat, bool void UIScene_LeaderboardsMenu::ReadStats(int startIndex) { + // load before so real stats cant get overwritten afterwards + m_labelInfo.setLabel(app.GetString(IDS_LEADERBOARD_LOADING)); + m_labelInfo.setVisible(true); + //If startIndex == -1, then use default values if( startIndex == -1 ) { @@ -490,10 +494,6 @@ void UIScene_LeaderboardsMenu::ReadStats(int startIndex) } break; } - - //Show the loading message - m_labelInfo.setLabel(app.GetString(IDS_LEADERBOARD_LOADING)); - m_labelInfo.setVisible(true); } bool UIScene_LeaderboardsMenu::OnStatsReadComplete(LeaderboardManager::eStatsReturn retIn, int numResults, LeaderboardManager::ViewOut results) diff --git a/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp b/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp index 3a5fb128..a98a5dbf 100644 --- a/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp @@ -517,6 +517,17 @@ void UIScene_LoadMenu::tick() m_MoreOptionsParams.bNaturalRegeneration = app.GetGameHostOption(uiHostOptions, eGameHostOption_NaturalRegeneration); m_MoreOptionsParams.bDoDaylightCycle = app.GetGameHostOption(uiHostOptions, eGameHostOption_DoDaylightCycle); + unsigned int saveDifficulty = app.GetGameHostOption(uiHostOptions, eGameHostOption_Difficulty); + if (saveDifficulty <= 3) + { + m_CurrentDifficulty = static_cast(saveDifficulty); + app.SetGameSettings(m_iPad, eGameSetting_Difficulty, m_CurrentDifficulty); + + WCHAR difficultyLabel[256]; + swprintf((WCHAR*)difficultyLabel, 256, L"%ls: %ls", app.GetString(IDS_SLIDER_DIFFICULTY), app.GetString(m_iDifficultyTitleSettingA[m_CurrentDifficulty])); + m_sliderDifficulty.init(difficultyLabel, eControl_Difficulty, 0, 3, m_CurrentDifficulty); + } + bool cheatsOn = m_MoreOptionsParams.bHostPrivileges; if (!cheatsOn) { diff --git a/Minecraft.Client/Extrax64Stubs.cpp b/Minecraft.Client/Extrax64Stubs.cpp index d606f810..5bdb155c 100644 --- a/Minecraft.Client/Extrax64Stubs.cpp +++ b/Minecraft.Client/Extrax64Stubs.cpp @@ -576,6 +576,126 @@ DWORD XEnableGuestSignin(BOOL fEnable) { return 0; } #ifdef _WINDOWS64 static void* profileData[4]; static bool s_bProfileIsFullVersion; +static unsigned int s_profileDataBytesPerPad = 0; + +static void Win64_GetProfileDataPath(int iQuadrant, char* outPath, size_t outPathSize) +{ + if (outPath == nullptr || outPathSize == 0) + return; + + outPath[0] = '\0'; + GetModuleFileNameA(nullptr, outPath, static_cast(outPathSize)); + char* lastSlash = strrchr(outPath, '\\'); + char* lastForwardSlash = strrchr(outPath, '/'); + if (lastForwardSlash != nullptr && (lastSlash == nullptr || lastForwardSlash > lastSlash)) + lastSlash = lastForwardSlash; + if (lastSlash != nullptr) + *(lastSlash + 1) = '\0'; + + char profileFileName[32] = {}; + sprintf_s(profileFileName, "profile%d.dat", iQuadrant); + strncat_s(outPath, outPathSize, profileFileName, _TRUNCATE); +} + +static bool Win64_LoadProfileDataBlob(int iQuadrant, void* dstData, unsigned int dataSize) +{ + if (dstData == nullptr || dataSize == 0) + return false; + + char filePath[MAX_PATH] = {}; + Win64_GetProfileDataPath(iQuadrant, filePath, MAX_PATH); + + FILE* profileFile = nullptr; + if (fopen_s(&profileFile, filePath, "rb") != 0 || profileFile == nullptr) + return false; + + fseek(profileFile, 0, SEEK_END); + long fileSize = ftell(profileFile); + rewind(profileFile); + + if (fileSize != static_cast(dataSize)) + { + fclose(profileFile); + return false; + } + + const size_t bytesRead = fread(dstData, 1, dataSize, profileFile); + fclose(profileFile); + + return bytesRead == dataSize; +} + +static void Win64_SaveProfileDataBlob(int iQuadrant, const void* srcData, unsigned int dataSize) +{ + if (srcData == nullptr || dataSize == 0) + return; + + char filePath[MAX_PATH] = {}; + Win64_GetProfileDataPath(iQuadrant, filePath, MAX_PATH); + + FILE* profileFile = nullptr; + if (fopen_s(&profileFile, filePath, "wb") != 0 || profileFile == nullptr) + return; + + fwrite(srcData, 1, dataSize, profileFile); + fclose(profileFile); +} + +static void Win64_ApplyDefaultProfileGameSettings(void* profileBytes) +{ + if (profileBytes == nullptr) + return; + + // Set some sane initial values! + GAME_SETTINGS* pGameSettings = static_cast(profileBytes); + pGameSettings->ucMenuSensitivity = 100; //eGameSetting_Sensitivity_InMenu + pGameSettings->ucInterfaceOpacity = 80; //eGameSetting_Sensitivity_InMenu + pGameSettings->usBitmaskValues |= 0x0200; //eGameSetting_DisplaySplitscreenGamertags - on + pGameSettings->usBitmaskValues |= 0x0400; //eGameSetting_Hints - on + pGameSettings->usBitmaskValues |= 0x1000; //eGameSetting_Autosave - 2 + pGameSettings->usBitmaskValues |= 0x8000; //eGameSetting_Tooltips - on + pGameSettings->uiBitmaskValues = 0L; // reset + pGameSettings->uiBitmaskValues |= GAMESETTING_CLOUDS; //eGameSetting_Clouds - on + pGameSettings->uiBitmaskValues |= GAMESETTING_ONLINE; //eGameSetting_GameSetting_Online - on + pGameSettings->uiBitmaskValues |= GAMESETTING_FRIENDSOFFRIENDS; //eGameSetting_GameSetting_FriendsOfFriends - on + pGameSettings->uiBitmaskValues |= GAMESETTING_DISPLAYUPDATEMSG; //eGameSetting_DisplayUpdateMessage (counter) + pGameSettings->uiBitmaskValues &= ~GAMESETTING_BEDROCKFOG; //eGameSetting_BedrockFog - off + pGameSettings->uiBitmaskValues |= GAMESETTING_DISPLAYHUD; //eGameSetting_DisplayHUD - on + pGameSettings->uiBitmaskValues |= GAMESETTING_DISPLAYHAND; //eGameSetting_DisplayHand - on + pGameSettings->uiBitmaskValues |= GAMESETTING_CUSTOMSKINANIM; //eGameSetting_CustomSkinAnim - on + pGameSettings->uiBitmaskValues |= GAMESETTING_DEATHMESSAGES; //eGameSetting_DeathMessages - on + pGameSettings->uiBitmaskValues |= (GAMESETTING_UISIZE & 0x00000800); // uisize 2 + pGameSettings->uiBitmaskValues |= (GAMESETTING_UISIZE_SPLITSCREEN & 0x00004000); // splitscreen ui size 3 + pGameSettings->uiBitmaskValues |= GAMESETTING_ANIMATEDCHARACTER; //eGameSetting_AnimatedCharacter - on + + // TU12 + // favorite skins added, but only set in TU12 - set to FFs + for (int skinIndex = 0; skinIndex < MAX_FAVORITE_SKINS; ++skinIndex) + { + pGameSettings->uiFavoriteSkinA[skinIndex] = 0xFFFFFFFF; + } + pGameSettings->ucCurrentFavoriteSkinPos = 0; + // Added a bitmask in TU13 to enable/disable display of the Mash-up pack worlds in the saves list + pGameSettings->uiMashUpPackWorldsDisplay = 0xFFFFFFFF; + + // PS3DEC13 + pGameSettings->uiBitmaskValues &= ~GAMESETTING_PS3EULAREAD; //eGameSetting_PS3_EULA_Read - off + + // PS3 1.05 - added Greek + pGameSettings->ucLanguage = MINECRAFT_LANGUAGE_DEFAULT; // use the system language + + // PS Vita - network mode added + pGameSettings->uiBitmaskValues &= ~GAMESETTING_PSVITANETWORKMODEADHOC; //eGameSetting_PSVita_NetworkModeAdhoc - off + + // Tutorials for most menus, and a few other things + pGameSettings->ucTutorialCompletion[0] = 0xFF; + pGameSettings->ucTutorialCompletion[1] = 0xFF; + pGameSettings->ucTutorialCompletion[2] = 0xF; + + // Has gone halfway through the tutorial + pGameSettings->ucTutorialCompletion[28] |= 1 << 0; +} + void C_4JProfile::Initialise(DWORD dwTitleID, DWORD dwOfferID, unsigned short usProfileVersion, @@ -585,60 +705,24 @@ void C_4JProfile::Initialise(DWORD dwTitleID, int iGameDefinedDataSizeX4, unsigned int* puiGameDefinedDataChangedBitmask) { + s_profileDataBytesPerPad = static_cast(iGameDefinedDataSizeX4 / XUSER_MAX_COUNT); + if (s_profileDataBytesPerPad == 0) + s_profileDataBytesPerPad = static_cast(iGameDefinedDataSizeX4 / 4); + for (int i = 0; i < 4; i++) { - profileData[i] = new byte[iGameDefinedDataSizeX4 / 4]; - ZeroMemory(profileData[i], sizeof(byte) * iGameDefinedDataSizeX4 / 4); + profileData[i] = new byte[s_profileDataBytesPerPad]; + ZeroMemory(profileData[i], sizeof(byte) * s_profileDataBytesPerPad); - // Set some sane initial values! - GAME_SETTINGS* pGameSettings = static_cast(profileData[i]); - pGameSettings->ucMenuSensitivity = 100; //eGameSetting_Sensitivity_InMenu - pGameSettings->ucInterfaceOpacity = 80; //eGameSetting_Sensitivity_InMenu - pGameSettings->usBitmaskValues |= 0x0200; //eGameSetting_DisplaySplitscreenGamertags - on - pGameSettings->usBitmaskValues |= 0x0400; //eGameSetting_Hints - on - pGameSettings->usBitmaskValues |= 0x1000; //eGameSetting_Autosave - 2 - pGameSettings->usBitmaskValues |= 0x8000; //eGameSetting_Tooltips - on - pGameSettings->uiBitmaskValues = 0L; // reset - pGameSettings->uiBitmaskValues |= GAMESETTING_CLOUDS; //eGameSetting_Clouds - on - pGameSettings->uiBitmaskValues |= GAMESETTING_ONLINE; //eGameSetting_GameSetting_Online - on - pGameSettings->uiBitmaskValues |= GAMESETTING_FRIENDSOFFRIENDS; //eGameSetting_GameSetting_FriendsOfFriends - on - pGameSettings->uiBitmaskValues |= GAMESETTING_DISPLAYUPDATEMSG; //eGameSetting_DisplayUpdateMessage (counter) - pGameSettings->uiBitmaskValues &= ~GAMESETTING_BEDROCKFOG; //eGameSetting_BedrockFog - off - pGameSettings->uiBitmaskValues |= GAMESETTING_DISPLAYHUD; //eGameSetting_DisplayHUD - on - pGameSettings->uiBitmaskValues |= GAMESETTING_DISPLAYHAND; //eGameSetting_DisplayHand - on - pGameSettings->uiBitmaskValues |= GAMESETTING_CUSTOMSKINANIM; //eGameSetting_CustomSkinAnim - on - pGameSettings->uiBitmaskValues |= GAMESETTING_DEATHMESSAGES; //eGameSetting_DeathMessages - on - pGameSettings->uiBitmaskValues |= (GAMESETTING_UISIZE & 0x00000800); // uisize 2 - pGameSettings->uiBitmaskValues |= (GAMESETTING_UISIZE_SPLITSCREEN & 0x00004000); // splitscreen ui size 3 - pGameSettings->uiBitmaskValues |= GAMESETTING_ANIMATEDCHARACTER; //eGameSetting_AnimatedCharacter - on - - // TU12 - // favorite skins added, but only set in TU12 - set to FFs - for (int i = 0; i < MAX_FAVORITE_SKINS; i++) + if (!Win64_LoadProfileDataBlob(i, profileData[i], s_profileDataBytesPerPad)) { - pGameSettings->uiFavoriteSkinA[i] = 0xFFFFFFFF; + Win64_ApplyDefaultProfileGameSettings(profileData[i]); } - pGameSettings->ucCurrentFavoriteSkinPos = 0; - // Added a bitmask in TU13 to enable/disable display of the Mash-up pack worlds in the saves list - pGameSettings->uiMashUpPackWorldsDisplay = 0xFFFFFFFF; + } - // PS3DEC13 - pGameSettings->uiBitmaskValues &= ~GAMESETTING_PS3EULAREAD; //eGameSetting_PS3_EULA_Read - off - - // PS3 1.05 - added Greek - pGameSettings->ucLanguage = MINECRAFT_LANGUAGE_DEFAULT; // use the system language - - // PS Vita - network mode added - pGameSettings->uiBitmaskValues &= ~GAMESETTING_PSVITANETWORKMODEADHOC; //eGameSetting_PSVita_NetworkModeAdhoc - off - - - // Tutorials for most menus, and a few other things - pGameSettings->ucTutorialCompletion[0] = 0xFF; - pGameSettings->ucTutorialCompletion[1] = 0xFF; - pGameSettings->ucTutorialCompletion[2] = 0xF; - - // Has gone halfway through the tutorial - pGameSettings->ucTutorialCompletion[28] |= 1 << 0; + if (puiGameDefinedDataChangedBitmask != nullptr) + { + *puiGameDefinedDataChangedBitmask = (1u << XUSER_MAX_COUNT) - 1u; } } void C_4JProfile::SetTrialTextStringTable(CXuiStringTable * pStringTable, int iAccept, int iReject) {} @@ -730,8 +814,31 @@ int C_4JProfile::SetOldProfileVersionCallback(int(*Func)(LPVOID, unsigned ch C_4JProfile::PROFILESETTINGS ProfileSettingsA[XUSER_MAX_COUNT]; C_4JProfile::PROFILESETTINGS* C_4JProfile::GetDashboardProfileSettings(int iPad) { return &ProfileSettingsA[iPad]; } -void C_4JProfile::WriteToProfile(int iQuadrant, bool bGameDefinedDataChanged, bool bOverride5MinuteLimitOnProfileWrites) {} -void C_4JProfile::ForceQueuedProfileWrites(int iPad) {} +void C_4JProfile::WriteToProfile(int iQuadrant, bool bGameDefinedDataChanged, bool bOverride5MinuteLimitOnProfileWrites) +{ + if (s_profileDataBytesPerPad == 0) + return; + + if (iQuadrant == XUSER_INDEX_ANY) + { + for (int i = 0; i < XUSER_MAX_COUNT; ++i) + { + if (profileData[i] != nullptr) + Win64_SaveProfileDataBlob(i, profileData[i], s_profileDataBytesPerPad); + } + return; + } + + if (iQuadrant < 0 || iQuadrant >= XUSER_MAX_COUNT) + return; + + if (profileData[iQuadrant] != nullptr) + Win64_SaveProfileDataBlob(iQuadrant, profileData[iQuadrant], s_profileDataBytesPerPad); +} +void C_4JProfile::ForceQueuedProfileWrites(int iPad) +{ + WriteToProfile(iPad, true, true); +} void* C_4JProfile::GetGameDefinedProfileData(int iQuadrant) { // 4J Stu - Don't reset the options when we call this!! diff --git a/Minecraft.Client/Windows64/Leaderboards/WindowsLeaderboardManager.cpp b/Minecraft.Client/Windows64/Leaderboards/WindowsLeaderboardManager.cpp index cc197ce5..ef77144d 100644 --- a/Minecraft.Client/Windows64/Leaderboards/WindowsLeaderboardManager.cpp +++ b/Minecraft.Client/Windows64/Leaderboards/WindowsLeaderboardManager.cpp @@ -2,4 +2,493 @@ #include "WindowsLeaderboardManager.h" -LeaderboardManager *LeaderboardManager::m_instance = new WindowsLeaderboardManager(); //Singleton instance of the LeaderboardManager \ No newline at end of file +#include + +#include "../../Minecraft.h" +#include "../../StatsCounter.h" + +#include "../../../Minecraft.World/StringHelpers.h" +#include "../../../Minecraft.World/Stats.h" +#include "../../../Minecraft.World/net.minecraft.world.item.h" +#include "../../../Minecraft.World/net.minecraft.world.level.tile.h" + +namespace +{ + static const DWORD kLeaderboardCacheMagic = 0x31424C57; // file identifier: WLB1 + static const DWORD kLeaderboardCacheVersion = 1; + static const unsigned int kDifficultyCount = 4; + static const unsigned int kPersistedBoardCount = + static_cast(LeaderboardManager::eStatsType_MAX) * kDifficultyCount; + + struct PersistedLeaderboardRow + { + BYTE valid; + BYTE statsSize; + WORD reserved; + DWORD totalScore; + ULONGLONG uid; + DWORD statsData[LeaderboardManager::ReadScore::STATSDATA_MAX]; + wchar_t name[XUSER_NAME_SIZE + 1]; + }; + + struct PersistedLeaderboardCache + { + DWORD magic; + DWORD version; + PersistedLeaderboardRow rows[kPersistedBoardCount]; + }; + + static int ClampDifficulty(int difficulty) + { + if (difficulty < 0) + return 0; + if (difficulty > 3) + return 3; + return difficulty; + } + + static unsigned long ClampToULong(unsigned long long value) + { + const unsigned long long maxValue = static_cast(ULONG_MAX); + if (value > maxValue) + return ULONG_MAX; + return static_cast(value); + } + + static unsigned int CombinePigmanKills(StatsCounter* stats, unsigned int difficulty) + { + if (stats == nullptr) + return 0; + + const unsigned int zombiePigman = stats->getValue(Stats::killsZombiePigman, difficulty); + const unsigned int netherPigman = stats->getValue(Stats::killsNetherZombiePigman, difficulty); + return zombiePigman + netherPigman; + } + + static void BuildLeaderboardCachePath(char* outPath, size_t outPathSize) + { + if (outPath == nullptr || outPathSize == 0) + return; + + outPath[0] = '\0'; + GetModuleFileNameA(nullptr, outPath, static_cast(outPathSize)); + + char* lastSlash = strrchr(outPath, '\\'); + char* lastForwardSlash = strrchr(outPath, '/'); + if (lastForwardSlash != nullptr && (lastSlash == nullptr || lastForwardSlash > lastSlash)) + lastSlash = lastForwardSlash; + + if (lastSlash != nullptr) + *(lastSlash + 1) = '\0'; + + strncat_s(outPath, outPathSize, "leaderboards-cache.dat", _TRUNCATE); + } + + static void InitLeaderboardCache(PersistedLeaderboardCache& cache) + { + ZeroMemory(&cache, sizeof(cache)); + cache.magic = kLeaderboardCacheMagic; + cache.version = kLeaderboardCacheVersion; + } + + static bool LoadLeaderboardCache(PersistedLeaderboardCache& outCache) + { + InitLeaderboardCache(outCache); + + char filePath[MAX_PATH] = {}; + BuildLeaderboardCachePath(filePath, MAX_PATH); + + FILE* f = nullptr; + if (fopen_s(&f, filePath, "rb") != 0 || f == nullptr) + return false; + + PersistedLeaderboardCache loaded = {}; + const size_t read = fread(&loaded, 1, sizeof(loaded), f); + fclose(f); + + if (read != sizeof(loaded)) + return false; + + if (loaded.magic != kLeaderboardCacheMagic || loaded.version != kLeaderboardCacheVersion) + return false; + + outCache = loaded; + return true; + } + + static void SaveLeaderboardCache(const PersistedLeaderboardCache& cache) + { + char filePath[MAX_PATH] = {}; + BuildLeaderboardCachePath(filePath, MAX_PATH); + + FILE* f = nullptr; + if (fopen_s(&f, filePath, "wb") != 0 || f == nullptr) + return; + + fwrite(&cache, 1, sizeof(cache), f); + fclose(f); + } + + static int GetPersistedRowIndex(LeaderboardManager::EStatsType type, unsigned int difficulty) + { + if (type < 0 || type >= LeaderboardManager::eStatsType_MAX) + return -1; + + unsigned int clampedDifficulty = (difficulty > 3) ? 3 : difficulty; + if (type == LeaderboardManager::eStatsType_Kills && clampedDifficulty == 0) + clampedDifficulty = 1; + + return static_cast(type) * static_cast(kDifficultyCount) + static_cast(clampedDifficulty); + } + + static void RecomputeTotalScore(LeaderboardManager::ReadScore& score) + { + unsigned long long total = 0; + const unsigned int columnCount = std::min( + static_cast(score.m_statsSize), + LeaderboardManager::ReadScore::STATSDATA_MAX); + + for (unsigned int i = 0; i < columnCount; ++i) + total += score.m_statsData[i]; + + score.m_totalScore = ClampToULong(total); + } + + static bool ScoreHasAnyData(const LeaderboardManager::ReadScore& score) + { + if (score.m_totalScore > 0) + return true; + + const unsigned int columnCount = std::min( + static_cast(score.m_statsSize), + LeaderboardManager::ReadScore::STATSDATA_MAX); + + for (unsigned int i = 0; i < columnCount; ++i) + { + if (score.m_statsData[i] > 0) + return true; + } + + return false; + } + + static void ApplyPersistedRow(const PersistedLeaderboardRow& row, LeaderboardManager::ReadScore& outScore) + { + if (row.valid == 0) + return; + + ZeroMemory(&outScore.m_statsData, sizeof(outScore.m_statsData)); + + outScore.m_uid = static_cast(row.uid); + outScore.m_rank = 1; + outScore.m_idsErrorMessage = 0; + outScore.m_statsSize = static_cast(std::min( + static_cast(row.statsSize), + LeaderboardManager::ReadScore::STATSDATA_MAX)); + outScore.m_totalScore = row.totalScore; + outScore.m_name = row.name; + + for (unsigned int i = 0; i < outScore.m_statsSize; ++i) + outScore.m_statsData[i] = row.statsData[i]; + } + + static void PersistRow(PersistedLeaderboardRow& row, const LeaderboardManager::ReadScore& score) + { + row.valid = 1; + row.statsSize = static_cast(std::min( + static_cast(score.m_statsSize), + LeaderboardManager::ReadScore::STATSDATA_MAX)); + row.reserved = 0; + row.totalScore = score.m_totalScore; + row.uid = static_cast(score.m_uid); + + ZeroMemory(row.statsData, sizeof(row.statsData)); + for (unsigned int i = 0; i < row.statsSize; ++i) + row.statsData[i] = score.m_statsData[i]; + + ZeroMemory(row.name, sizeof(row.name)); + if (!score.m_name.empty()) + wcsncpy_s(row.name, score.m_name.c_str(), _TRUNCATE); + } + + static bool BuildReadScoreFromRegisterScore(const LeaderboardManager::RegisterScore& source, + LeaderboardManager::ReadScore& outScore) + { + ZeroMemory(&outScore, sizeof(outScore)); + + PlayerUID uid = INVALID_XUID; + ProfileManager.GetXUID(source.m_iPad, &uid, true); + outScore.m_uid = uid; + outScore.m_rank = 1; + outScore.m_idsErrorMessage = 0; + outScore.m_name = convStringToWstring(ProfileManager.GetGamertag(source.m_iPad)); + + switch (source.m_commentData.m_statsType) + { + case LeaderboardManager::eStatsType_Travelling: + outScore.m_statsSize = 4; + outScore.m_statsData[0] = source.m_commentData.m_travelling.m_walked; + outScore.m_statsData[1] = source.m_commentData.m_travelling.m_fallen; + outScore.m_statsData[2] = source.m_commentData.m_travelling.m_minecart; + outScore.m_statsData[3] = source.m_commentData.m_travelling.m_boat; + break; + + case LeaderboardManager::eStatsType_Mining: + outScore.m_statsSize = 7; + outScore.m_statsData[0] = source.m_commentData.m_mining.m_dirt; + outScore.m_statsData[1] = source.m_commentData.m_mining.m_cobblestone; + outScore.m_statsData[2] = source.m_commentData.m_mining.m_sand; + outScore.m_statsData[3] = source.m_commentData.m_mining.m_stone; + outScore.m_statsData[4] = source.m_commentData.m_mining.m_gravel; + outScore.m_statsData[5] = source.m_commentData.m_mining.m_clay; + outScore.m_statsData[6] = source.m_commentData.m_mining.m_obsidian; + break; + + case LeaderboardManager::eStatsType_Farming: + outScore.m_statsSize = 6; + outScore.m_statsData[0] = source.m_commentData.m_farming.m_eggs; + outScore.m_statsData[1] = source.m_commentData.m_farming.m_wheat; + outScore.m_statsData[2] = source.m_commentData.m_farming.m_mushroom; + outScore.m_statsData[3] = source.m_commentData.m_farming.m_sugarcane; + outScore.m_statsData[4] = source.m_commentData.m_farming.m_milk; + outScore.m_statsData[5] = source.m_commentData.m_farming.m_pumpkin; + break; + + case LeaderboardManager::eStatsType_Kills: + outScore.m_statsSize = 7; + outScore.m_statsData[0] = source.m_commentData.m_kills.m_zombie; + outScore.m_statsData[1] = source.m_commentData.m_kills.m_skeleton; + outScore.m_statsData[2] = source.m_commentData.m_kills.m_creeper; + outScore.m_statsData[3] = source.m_commentData.m_kills.m_spider; + outScore.m_statsData[4] = source.m_commentData.m_kills.m_spiderJockey; + outScore.m_statsData[5] = source.m_commentData.m_kills.m_zombiePigman; + outScore.m_statsData[6] = source.m_commentData.m_kills.m_slime; + break; + + default: + return false; + } + + RecomputeTotalScore(outScore); + if (source.m_score > 0) + outScore.m_totalScore = std::max(outScore.m_totalScore, static_cast(source.m_score)); + + return true; + } +} + +LeaderboardManager *LeaderboardManager::m_instance = new WindowsLeaderboardManager(); // Singleton instance of the LeaderboardManager + +bool WindowsLeaderboardManager::WriteStats(unsigned int viewCount, ViewIn views) +{ + PersistedLeaderboardCache cache = {}; + LoadLeaderboardCache(cache); + + if (views != nullptr) + { + for (unsigned int i = 0; i < viewCount; ++i) + { + ReadScore score = {}; + if (!BuildReadScoreFromRegisterScore(views[i], score)) + continue; + + unsigned int difficulty = static_cast(ClampDifficulty(views[i].m_difficulty)); + if (views[i].m_commentData.m_statsType == eStatsType_Kills && difficulty == 0) + difficulty = 1; + + const int index = GetPersistedRowIndex(views[i].m_commentData.m_statsType, difficulty); + if (index < 0 || index >= static_cast(kPersistedBoardCount)) + continue; + + PersistRow(cache.rows[index], score); + } + + delete[] views; + } + + SaveLeaderboardCache(cache); + return true; +} + +bool WindowsLeaderboardManager::ReadStats_Friends(LeaderboardReadListener* callback, int difficulty, + EStatsType type, PlayerUID myUID, unsigned int startIndex, unsigned int readCount) +{ + if (!LeaderboardManager::ReadStats_Friends(callback, difficulty, type, myUID, startIndex, readCount)) + return false; + + return ReadLocalStats(callback, difficulty, type, myUID); +} + +bool WindowsLeaderboardManager::ReadStats_MyScore(LeaderboardReadListener* callback, int difficulty, + EStatsType type, PlayerUID myUID, unsigned int readCount) +{ + if (!LeaderboardManager::ReadStats_MyScore(callback, difficulty, type, myUID, readCount)) + return false; + + return ReadLocalStats(callback, difficulty, type, myUID); +} + +bool WindowsLeaderboardManager::ReadStats_TopRank(LeaderboardReadListener* callback, int difficulty, + EStatsType type, unsigned int startIndex, unsigned int readCount) +{ + if (!LeaderboardManager::ReadStats_TopRank(callback, difficulty, type, startIndex, readCount)) + return false; + + PlayerUID uid = INVALID_XUID; + ProfileManager.GetXUID(ProfileManager.GetPrimaryPad(), &uid, true); + return ReadLocalStats(callback, difficulty, type, uid); +} + +bool WindowsLeaderboardManager::ReadLocalStats(LeaderboardReadListener* callback, int difficulty, + EStatsType type, PlayerUID uid) +{ + if (callback == nullptr) + return false; + + ReadView view = {}; + ReadScore row = {}; + + if (BuildLocalReadScore(row, difficulty, type, uid)) + { + view.m_numQueries = 1; + view.m_queries = &row; + + callback->OnStatsReadComplete(eStatsReturn_Success, 1, view); + } + else + { + view.m_numQueries = 0; + view.m_queries = nullptr; + + callback->OnStatsReadComplete(eStatsReturn_NoResults, 0, view); + } + + return true; +} + +bool WindowsLeaderboardManager::BuildLocalReadScore(ReadScore& outScore, int difficulty, + EStatsType type, PlayerUID uid) +{ + const int primaryPad = ProfileManager.GetPrimaryPad(); + if (primaryPad < 0 || primaryPad >= XUSER_MAX_COUNT) + return false; + + Minecraft* minecraft = Minecraft::GetInstance(); + if (minecraft == nullptr) + return false; + + StatsCounter* stats = minecraft->stats[primaryPad]; + if (stats == nullptr) + return false; + + ZeroMemory(&outScore, sizeof(outScore)); + + outScore.m_uid = uid; + outScore.m_rank = 1; + outScore.m_idsErrorMessage = 0; + + char* gamertag = ProfileManager.GetGamertag(primaryPad); + if (gamertag != nullptr && gamertag[0] != 0) + outScore.m_name = convStringToWstring(gamertag); + else + outScore.m_name = L"Player"; + + unsigned int diff = static_cast(ClampDifficulty(difficulty)); + if (type == eStatsType_Kills && diff == 0) + diff = 1; + unsigned long long totalScore = 0; + + auto setColumn = [&](unsigned int index, unsigned int value) + { + if (index >= ReadScore::STATSDATA_MAX) + return; + + outScore.m_statsData[index] = value; + totalScore += value; + }; + + switch (type) + { + case eStatsType_Travelling: + outScore.m_statsSize = 4; + setColumn(0, stats->getValue(Stats::walkOneM, diff)); + setColumn(1, stats->getValue(Stats::fallOneM, diff)); + setColumn(2, stats->getValue(Stats::minecartOneM, diff)); + setColumn(3, stats->getValue(Stats::boatOneM, diff)); + break; + + case eStatsType_Mining: + outScore.m_statsSize = 7; + setColumn(0, stats->getValue(Stats::blocksMined[Tile::dirt_Id], diff)); + setColumn(1, stats->getValue(Stats::blocksMined[Tile::cobblestone_Id], diff)); + setColumn(2, stats->getValue(Stats::blocksMined[Tile::sand_Id], diff)); + setColumn(3, stats->getValue(Stats::blocksMined[Tile::stone_Id], diff)); + setColumn(4, stats->getValue(Stats::blocksMined[Tile::gravel_Id], diff)); + setColumn(5, stats->getValue(Stats::blocksMined[Tile::clay_Id], diff)); + setColumn(6, stats->getValue(Stats::blocksMined[Tile::obsidian_Id], diff)); + break; + + case eStatsType_Farming: + outScore.m_statsSize = 6; + setColumn(0, stats->getValue(Stats::itemsCollected[Item::egg_Id], diff)); + setColumn(1, stats->getValue(Stats::blocksMined[Tile::wheat_Id], diff)); + setColumn(2, stats->getValue(Stats::blocksMined[Tile::mushroom_brown_Id], diff)); + setColumn(3, stats->getValue(Stats::blocksMined[Tile::reeds_Id], diff)); + setColumn(4, stats->getValue(Stats::cowsMilked, diff)); + setColumn(5, stats->getValue(Stats::itemsCollected[Tile::pumpkin_Id], diff)); + break; + + case eStatsType_Kills: + outScore.m_statsSize = 7; + setColumn(0, stats->getValue(Stats::killsZombie, diff)); + setColumn(1, stats->getValue(Stats::killsSkeleton, diff)); + setColumn(2, stats->getValue(Stats::killsCreeper, diff)); + setColumn(3, stats->getValue(Stats::killsSpider, diff)); + setColumn(4, stats->getValue(Stats::killsSpiderJockey, diff)); + setColumn(5, CombinePigmanKills(stats, diff)); + setColumn(6, stats->getValue(Stats::killsSlime, diff)); + break; + + default: + return false; + } + + outScore.m_totalScore = ClampToULong(totalScore); + + PersistedLeaderboardCache cache = {}; + LoadLeaderboardCache(cache); + + const int persistedIndex = GetPersistedRowIndex(type, diff); + if (persistedIndex >= 0 && persistedIndex < static_cast(kPersistedBoardCount)) + { + PersistedLeaderboardRow& persistedRow = cache.rows[persistedIndex]; + const bool currentHasData = ScoreHasAnyData(outScore); + + if (persistedRow.valid != 0) + { + if (!currentHasData) + { + ApplyPersistedRow(persistedRow, outScore); + } + else if (persistedRow.statsSize == outScore.m_statsSize) + { + const unsigned int columnCount = std::min( + static_cast(outScore.m_statsSize), + ReadScore::STATSDATA_MAX); + + for (unsigned int i = 0; i < columnCount; ++i) + outScore.m_statsData[i] = (std::max)(outScore.m_statsData[i], persistedRow.statsData[i]); + + RecomputeTotalScore(outScore); + + if (outScore.m_name.empty() && persistedRow.name[0] != 0) + outScore.m_name = persistedRow.name; + } + } + + PersistRow(persistedRow, outScore); + SaveLeaderboardCache(cache); + } + + return true; +} diff --git a/Minecraft.Client/Windows64/Leaderboards/WindowsLeaderboardManager.h b/Minecraft.Client/Windows64/Leaderboards/WindowsLeaderboardManager.h index a66bc9cc..ec59653b 100644 --- a/Minecraft.Client/Windows64/Leaderboards/WindowsLeaderboardManager.h +++ b/Minecraft.Client/Windows64/Leaderboards/WindowsLeaderboardManager.h @@ -5,32 +5,38 @@ class WindowsLeaderboardManager : public LeaderboardManager { public: - virtual void Tick() {} + virtual void Tick() override {} //Open a session - virtual bool OpenSession() { return true; } + virtual bool OpenSession() override { return true; } //Close a session - virtual void CloseSession() {} + virtual void CloseSession() override {} //Delete a session - virtual void DeleteSession() {} + virtual void DeleteSession() override {} //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) override; - virtual bool WriteStats(unsigned int viewCount, ViewIn views) { return false; } - - virtual bool ReadStats_Friends(LeaderboardReadListener *callback, int difficulty, EStatsType type, PlayerUID myUID) { return false; } - virtual bool ReadStats_MyScore(LeaderboardReadListener *callback, int difficulty, EStatsType type, PlayerUID myUID, unsigned int readCount) { return false; } - virtual bool ReadStats_TopRank(LeaderboardReadListener *callback, int difficulty, EStatsType type, unsigned int startIndex, unsigned int readCount) { return false; } + virtual bool ReadStats_Friends(LeaderboardReadListener* callback, int difficulty, EStatsType type, + PlayerUID myUID, unsigned int startIndex, unsigned int readCount) override; + virtual bool ReadStats_MyScore(LeaderboardReadListener* callback, int difficulty, EStatsType type, + PlayerUID myUID, unsigned int readCount) override; + virtual bool ReadStats_TopRank(LeaderboardReadListener* callback, int difficulty, EStatsType type, + unsigned int startIndex, unsigned int readCount) override; //Perform a flush of the stats - virtual void FlushStats() {} + virtual void FlushStats() override {} //Cancel the current operation - virtual void CancelOperation() {} + virtual void CancelOperation() override {} //Is the leaderboard manager idle. - virtual bool isIdle() { return true; } + virtual bool isIdle() override { return true; } + +private: + bool ReadLocalStats(LeaderboardReadListener* callback, int difficulty, EStatsType type, PlayerUID uid); + bool BuildLocalReadScore(ReadScore& outScore, int difficulty, EStatsType type, PlayerUID uid); };