feat: leaderboards! (technically a fix but whatever)

feat. leaderboard
This commit is contained in:
piebot 2026-04-22 14:05:24 +03:00 committed by GitHub
commit 5d0c126898
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 958 additions and 73 deletions

View file

@ -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]);
}
}
}

View file

@ -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)

View file

@ -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<unsigned char>(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)
{

View file

@ -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<DWORD>(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<long>(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<GAME_SETTINGS*>(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<unsigned int>(iGameDefinedDataSizeX4 / XUSER_MAX_COUNT);
if (s_profileDataBytesPerPad == 0)
s_profileDataBytesPerPad = static_cast<unsigned int>(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<GAME_SETTINGS *>(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!!

View file

@ -2,4 +2,758 @@
#include "WindowsLeaderboardManager.h"
LeaderboardManager *LeaderboardManager::m_instance = new WindowsLeaderboardManager(); //Singleton instance of the LeaderboardManager
#include <algorithm>
#include <vector>
#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 = 2;
static const unsigned int kDifficultyCount = 4;
static const unsigned int kPersistedBoardCount =
static_cast<unsigned int>(LeaderboardManager::eStatsType_MAX) * kDifficultyCount;
static const unsigned int kMaxRowsPerBoard = 64;
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 PersistedLeaderboardBoard
{
DWORD rowCount;
PersistedLeaderboardRow rows[kMaxRowsPerBoard];
};
struct PersistedLeaderboardCache
{
DWORD magic;
DWORD version;
PersistedLeaderboardBoard boards[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<unsigned long long>(ULONG_MAX);
if (value > maxValue)
return ULONG_MAX;
return static_cast<unsigned long>(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<DWORD>(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;
if (fseek(f, 0, SEEK_END) != 0)
{
fclose(f);
return false;
}
const long fileSize = ftell(f);
if (fileSize <= 0 || fseek(f, 0, SEEK_SET) != 0)
{
fclose(f);
return false;
}
if (static_cast<size_t>(fileSize) == sizeof(PersistedLeaderboardCache))
{
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;
for (unsigned int boardIndex = 0; boardIndex < kPersistedBoardCount; ++boardIndex)
{
if (outCache.boards[boardIndex].rowCount > kMaxRowsPerBoard)
outCache.boards[boardIndex].rowCount = kMaxRowsPerBoard;
}
return true;
}
fclose(f);
return false;
}
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 GetPersistedBoardIndex(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<int>(type) * static_cast<int>(kDifficultyCount) + static_cast<int>(clampedDifficulty);
}
static unsigned int GetBoardRowCount(const PersistedLeaderboardBoard& board)
{
return (board.rowCount > kMaxRowsPerBoard) ? kMaxRowsPerBoard : board.rowCount;
}
static void RecomputeTotalScore(LeaderboardManager::ReadScore& score)
{
unsigned long long total = 0;
const unsigned int columnCount = std::min<unsigned int>(
static_cast<unsigned int>(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<unsigned int>(
static_cast<unsigned int>(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<PlayerUID>(row.uid);
outScore.m_rank = 1;
outScore.m_idsErrorMessage = 0;
outScore.m_statsSize = static_cast<unsigned short>(std::min<unsigned int>(
static_cast<unsigned int>(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<BYTE>(std::min<unsigned int>(
static_cast<unsigned int>(score.m_statsSize),
LeaderboardManager::ReadScore::STATSDATA_MAX));
row.reserved = 0;
row.totalScore = score.m_totalScore;
row.uid = static_cast<ULONGLONG>(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 int FindMatchingRowIndex(const PersistedLeaderboardBoard& board, const LeaderboardManager::ReadScore& score)
{
const unsigned int rowCount = GetBoardRowCount(board);
if (!score.m_name.empty())
{
for (unsigned int rowIndex = 0; rowIndex < rowCount; ++rowIndex)
{
const PersistedLeaderboardRow& row = board.rows[rowIndex];
if (row.valid == 0 || row.name[0] == 0)
continue;
if (_wcsicmp(row.name, score.m_name.c_str()) == 0)
return static_cast<int>(rowIndex);
}
return -1;
}
if (score.m_uid != INVALID_XUID)
{
for (unsigned int rowIndex = 0; rowIndex < rowCount; ++rowIndex)
{
const PersistedLeaderboardRow& row = board.rows[rowIndex];
if (row.valid == 0)
continue;
if (static_cast<PlayerUID>(row.uid) == score.m_uid)
return static_cast<int>(rowIndex);
}
}
return -1;
}
static int FindWritableRowIndex(PersistedLeaderboardBoard& board, const LeaderboardManager::ReadScore& score)
{
if (board.rowCount > kMaxRowsPerBoard)
board.rowCount = kMaxRowsPerBoard;
const int matchingRowIndex = FindMatchingRowIndex(board, score);
if (matchingRowIndex >= 0)
return matchingRowIndex;
const unsigned int rowCount = GetBoardRowCount(board);
for (unsigned int rowIndex = 0; rowIndex < rowCount; ++rowIndex)
{
if (board.rows[rowIndex].valid == 0)
return static_cast<int>(rowIndex);
}
if (rowCount < kMaxRowsPerBoard)
{
board.rowCount = rowCount + 1;
return static_cast<int>(rowCount);
}
unsigned int replaceIndex = 0;
DWORD lowestScore = ULONG_MAX;
for (unsigned int rowIndex = 0; rowIndex < kMaxRowsPerBoard; ++rowIndex)
{
if (board.rows[rowIndex].valid == 0)
return static_cast<int>(rowIndex);
if (board.rows[rowIndex].totalScore <= lowestScore)
{
lowestScore = board.rows[rowIndex].totalScore;
replaceIndex = rowIndex;
}
}
return static_cast<int>(replaceIndex);
}
static void MergeScoreWithPersistedRow(const PersistedLeaderboardRow& persistedRow, LeaderboardManager::ReadScore& inOutScore)
{
if (persistedRow.valid == 0)
return;
const bool currentHasData = ScoreHasAnyData(inOutScore);
if (!currentHasData)
{
ApplyPersistedRow(persistedRow, inOutScore);
return;
}
if (persistedRow.statsSize == inOutScore.m_statsSize)
{
const unsigned int columnCount = std::min<unsigned int>(
static_cast<unsigned int>(inOutScore.m_statsSize),
LeaderboardManager::ReadScore::STATSDATA_MAX);
for (unsigned int i = 0; i < columnCount; ++i)
inOutScore.m_statsData[i] = (std::max)(inOutScore.m_statsData[i], persistedRow.statsData[i]);
RecomputeTotalScore(inOutScore);
}
if (inOutScore.m_name.empty() && persistedRow.name[0] != 0)
inOutScore.m_name = persistedRow.name;
}
static bool RowMatchesPlayer(const LeaderboardManager::ReadScore& row, const LeaderboardManager::ReadScore& player)
{
if (!player.m_name.empty() && !row.m_name.empty())
return _wcsicmp(row.m_name.c_str(), player.m_name.c_str()) == 0;
if (player.m_uid != INVALID_XUID)
return row.m_uid == player.m_uid;
return false;
}
static bool CompareRowsForRank(const LeaderboardManager::ReadScore& left, const LeaderboardManager::ReadScore& right)
{
if (left.m_totalScore != right.m_totalScore)
return left.m_totalScore > right.m_totalScore;
const wchar_t* leftName = left.m_name.empty() ? L"" : left.m_name.c_str();
const wchar_t* rightName = right.m_name.empty() ? L"" : right.m_name.c_str();
const int nameCompare = _wcsicmp(leftName, rightName);
if (nameCompare != 0)
return nameCompare < 0;
return left.m_uid < right.m_uid;
}
static void CollectScoresFromBoard(const PersistedLeaderboardBoard& board,
std::vector<LeaderboardManager::ReadScore>& outScores)
{
const unsigned int rowCount = GetBoardRowCount(board);
outScores.reserve(outScores.size() + rowCount);
for (unsigned int rowIndex = 0; rowIndex < rowCount; ++rowIndex)
{
const PersistedLeaderboardRow& row = board.rows[rowIndex];
if (row.valid == 0)
continue;
LeaderboardManager::ReadScore score = {};
ApplyPersistedRow(row, score);
if (score.m_statsSize == 0)
continue;
outScores.push_back(score);
}
}
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;
char* gamertag = ProfileManager.GetGamertag(source.m_iPad);
if (gamertag != nullptr && gamertag[0] != 0)
outScore.m_name = convStringToWstring(gamertag);
else
outScore.m_name = L"Player";
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<unsigned long>(outScore.m_totalScore, static_cast<unsigned long>(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<unsigned int>(ClampDifficulty(views[i].m_difficulty));
if (views[i].m_commentData.m_statsType == eStatsType_Kills && difficulty == 0)
difficulty = 1;
const int boardIndex = GetPersistedBoardIndex(views[i].m_commentData.m_statsType, difficulty);
if (boardIndex < 0 || boardIndex >= static_cast<int>(kPersistedBoardCount))
continue;
PersistedLeaderboardBoard& board = cache.boards[boardIndex];
const int rowIndex = FindWritableRowIndex(board, score);
if (rowIndex < 0 || rowIndex >= static_cast<int>(kMaxRowsPerBoard))
continue;
PersistRow(board.rows[rowIndex], 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 localScore = {};
if (!BuildLocalReadScore(localScore, difficulty, type, uid))
{
view.m_numQueries = 0;
view.m_queries = nullptr;
callback->OnStatsReadComplete(eStatsReturn_NoResults, 0, view);
return true;
}
unsigned int diff = static_cast<unsigned int>(ClampDifficulty(difficulty));
if (type == eStatsType_Kills && diff == 0)
diff = 1;
std::vector<ReadScore> allRows;
PersistedLeaderboardCache cache = {};
LoadLeaderboardCache(cache);
const int boardIndex = GetPersistedBoardIndex(type, diff);
if (boardIndex >= 0 && boardIndex < static_cast<int>(kPersistedBoardCount))
CollectScoresFromBoard(cache.boards[boardIndex], allRows);
bool hasLocalRow = false;
for (const ReadScore& row : allRows)
{
if (RowMatchesPlayer(row, localScore))
{
hasLocalRow = true;
break;
}
}
if (!hasLocalRow)
allRows.push_back(localScore);
if (allRows.empty())
{
view.m_numQueries = 0;
view.m_queries = nullptr;
callback->OnStatsReadComplete(eStatsReturn_NoResults, 0, view);
return true;
}
std::sort(allRows.begin(), allRows.end(), CompareRowsForRank);
for (size_t i = 0; i < allRows.size(); ++i)
allRows[i].m_rank = static_cast<unsigned long>(i + 1);
size_t playerIndex = 0;
for (size_t i = 0; i < allRows.size(); ++i)
{
if (RowMatchesPlayer(allRows[i], localScore))
{
playerIndex = i;
break;
}
}
size_t pageStart = 0;
size_t pageCount = allRows.size();
if (m_eFilterMode == eFM_MyScore)
{
pageStart = playerIndex;
pageCount = 1;
}
else if (m_eFilterMode == eFM_TopRank)
{
if (m_startIndex > 0)
pageStart = std::min<size_t>(static_cast<size_t>(m_startIndex - 1), allRows.size());
pageCount = allRows.size() - pageStart;
if (m_readCount > 0)
pageCount = std::min<size_t>(pageCount, static_cast<size_t>(m_readCount));
}
std::vector<ReadScore> pageRows;
if (pageStart < allRows.size() && pageCount > 0)
{
pageRows.insert(
pageRows.end(),
allRows.begin() + static_cast<std::ptrdiff_t>(pageStart),
allRows.begin() + static_cast<std::ptrdiff_t>(pageStart + pageCount));
}
if (pageRows.empty())
{
view.m_numQueries = 0;
view.m_queries = nullptr;
callback->OnStatsReadComplete(eStatsReturn_NoResults, 0, view);
return true;
}
view.m_numQueries = static_cast<unsigned int>(pageRows.size());
view.m_queries = pageRows.data();
const int totalResults = (m_eFilterMode == eFM_MyScore)
? static_cast<int>(pageRows.size())
: static_cast<int>(allRows.size());
callback->OnStatsReadComplete(
eStatsReturn_Success,
totalResults,
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<unsigned int>(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 boardIndex = GetPersistedBoardIndex(type, diff);
if (boardIndex >= 0 && boardIndex < static_cast<int>(kPersistedBoardCount))
{
PersistedLeaderboardBoard& board = cache.boards[boardIndex];
const int matchingRowIndex = FindMatchingRowIndex(board, outScore);
if (matchingRowIndex >= 0 && matchingRowIndex < static_cast<int>(kMaxRowsPerBoard))
MergeScoreWithPersistedRow(board.rows[matchingRowIndex], outScore);
const int writeRowIndex = FindWritableRowIndex(board, outScore);
if (writeRowIndex >= 0 && writeRowIndex < static_cast<int>(kMaxRowsPerBoard))
PersistRow(board.rows[writeRowIndex], outScore);
SaveLeaderboardCache(cache);
}
return true;
}

View file

@ -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);
};

View file

@ -75,7 +75,7 @@ When in flying mode, you can hold down{*CONTROLLER_ACTION_JUMP*} to move up and{
</data>
<data name="IDS_TOOLTIPS_INVITE_PARTY">
<value>Invite Xbox LIVE Party</value>
<value>Invite Party</value>
</data>
<data name="IDS_CONFIRM_START_CREATIVE">
@ -95,11 +95,11 @@ When in flying mode, you can hold down{*CONTROLLER_ACTION_JUMP*} to move up and{
</data>
<data name="IDS_CONNECTION_LOST_LIVE">
<value>Connection to Xbox LIVE was lost. Exiting to the main menu.</value>
<value>Connection to the server was lost. Exiting to the main menu.</value>
</data>
<data name="IDS_CONNECTION_LOST_LIVE_NO_EXIT">
<value>Connection to Xbox LIVE was lost.</value>
<value>Connection to the server was lost.</value>
</data>
<data name="IDS_AWARD_AVATAR1">
@ -152,7 +152,7 @@ Would you like to unlock the full game?</value>
</data>
<data name="IDS_UNLOCK_GUEST_TEXT">
<value>Guest players cannot unlock the full game. Please sign in with an Xbox LIVE user ID.</value>
<value>Guest players cannot unlock the full game. Please sign in with a correct user ID.</value>
</data>
<data name="IDS_LEADERBOARD_GAMERTAG">
@ -176,11 +176,11 @@ Would you like to unlock the full game?</value>
</data>
<data name="IDS_NO_MULTIPLAYER_PRIVILEGE_JOIN_TEXT">
<value>Failed to join the game as one or more players are not allowed to play multiplayer games on Xbox LIVE.</value>
<value>Failed to join the game as one or more players are not allowed to play multiplayer games online.</value>
</data>
<data name="IDS_NO_MULTIPLAYER_PRIVILEGE_HOST_TEXT">
<value>Failed to create an online game as one or more players are not allowed to play multiplayer games on Xbox LIVE. Uncheck the "Online Game" box to start an offline game.</value>
<value>Failed to create a game as one or more players are not allowed to play multiplayer games online. Uncheck the "Online Game" box to start an offline game.</value>
</data>
<data name="IDS_NO_USER_CREATED_CONTENT_PRIVILEGE_SINGLE_LOCAL">