Convert to using only floats

This will cover all of our bases for any type of score.  No need to do any conversions.
This commit is contained in:
EmosewaMC 2023-05-31 23:05:19 -07:00
parent 47deca6f4f
commit b8878da61b
2 changed files with 79 additions and 155 deletions

View File

@ -1,7 +1,8 @@
#define _DEBUG
#include "LeaderboardManager.h" #include "LeaderboardManager.h"
#include <sstream>
#include <utility> #include <utility>
#include "Database.h" #include "Database.h"
#include "EntityManager.h" #include "EntityManager.h"
#include "Character.h" #include "Character.h"
@ -14,7 +15,6 @@
#include "Entity.h" #include "Entity.h"
#include "LDFFormat.h" #include "LDFFormat.h"
#include "DluAssert.h" #include "DluAssert.h"
#include <sstream>
#include "CDActivitiesTable.h" #include "CDActivitiesTable.h"
#include "Metrics.hpp" #include "Metrics.hpp"
@ -32,7 +32,12 @@ Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoTy
} }
Leaderboard::~Leaderboard() { Leaderboard::~Leaderboard() {
Clear();
}
void Leaderboard::Clear() {
for (auto& entry : entries) for (auto data : entry) delete data; for (auto& entry : entries) for (auto data : entry) delete data;
} }
inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data) { inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data) {
@ -44,7 +49,7 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const {
bitStream->Write(infoType); bitStream->Write(infoType);
std::ostringstream leaderboard; std::ostringstream leaderboard;
Game::logger->Log("LeaderboardManager", "game is %i info type %i ", gameID, infoType);
leaderboard << "ADO.Result=7:1"; // Unused in 1.10.64, but is in captures leaderboard << "ADO.Result=7:1"; // Unused in 1.10.64, but is in captures
leaderboard << "\nResult.Count=1:1"; // number of results, always 1 leaderboard << "\nResult.Count=1:1"; // number of results, always 1
if (!this->entries.empty()) leaderboard << "\nResult[0].Index=0:RowNumber"; // "Primary key". Live doesn't include this if there are no entries. if (!this->entries.empty()) leaderboard << "\nResult[0].Index=0:RowNumber"; // "Primary key". Live doesn't include this if there are no entries.
@ -63,12 +68,12 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const {
bitStream->Write<uint32_t>(leaderboardSize); bitStream->Write<uint32_t>(leaderboardSize);
// Doing this all in 1 call so there is no possbility of a dangling pointer. // Doing this all in 1 call so there is no possbility of a dangling pointer.
bitStream->WriteAlignedBytes(reinterpret_cast<const unsigned char*>(GeneralUtils::ASCIIToUTF16(leaderboard.str()).c_str()), leaderboardSize * sizeof(char16_t)); bitStream->WriteAlignedBytes(reinterpret_cast<const unsigned char*>(GeneralUtils::ASCIIToUTF16(leaderboard.str()).c_str()), leaderboardSize * sizeof(char16_t));
if (leaderboardSize > 0) bitStream->Write<uint16_t>(0);
bitStream->Write0(); bitStream->Write0();
bitStream->Write0(); bitStream->Write0();
} }
void Leaderboard::QueryToLdf(std::unique_ptr<sql::ResultSet>& rows) { void Leaderboard::QueryToLdf(std::unique_ptr<sql::ResultSet>& rows) {
Clear();
if (rows->rowsCount() == 0) return; if (rows->rowsCount() == 0) return;
this->entries.reserve(rows->rowsCount()); this->entries.reserve(rows->rowsCount());
@ -84,49 +89,49 @@ void Leaderboard::QueryToLdf(std::unique_ptr<sql::ResultSet>& rows) {
entry.push_back(new LDFData<uint64_t>(u"RowNumber", rows->getInt("ranking"))); entry.push_back(new LDFData<uint64_t>(u"RowNumber", rows->getInt("ranking")));
switch (leaderboardType) { switch (leaderboardType) {
case Type::ShootingGallery: case Type::ShootingGallery:
entry.push_back(new LDFData<float>(u"HitPercentage", (rows->getInt("hitPercentage") / 100.0f))); entry.push_back(new LDFData<float>(u"HitPercentage", (rows->getInt("primaryScore") / 100.0f)));
// HitPercentage:3 between 0 and 1 // HitPercentage:3 between 0 and 1
entry.push_back(new LDFData<int32_t>(u"Score", rows->getInt("score"))); entry.push_back(new LDFData<int32_t>(u"Score", rows->getInt("secondaryScore")));
// Score:1 // Score:1
entry.push_back(new LDFData<int32_t>(u"Streak", rows->getInt("streak"))); entry.push_back(new LDFData<int32_t>(u"Streak", rows->getInt("tertiaryScore")));
// Streak:1 // Streak:1
break; break;
case Type::Racing: case Type::Racing:
entry.push_back(new LDFData<float>(u"BestLapTime", rows->getDouble("bestLapTime"))); entry.push_back(new LDFData<float>(u"BestTime", rows->getDouble("primaryScore")));
// BestLapTime:3 // BestLapTime:3
entry.push_back(new LDFData<float>(u"BestTime", rows->getDouble("bestTime"))); entry.push_back(new LDFData<float>(u"BestLapTime", rows->getDouble("secondaryScore")));
// BestTime:3 // BestTime:3
entry.push_back(new LDFData<int32_t>(u"License", 1)); entry.push_back(new LDFData<int32_t>(u"License", 1));
// License:1 - 1 if player has completed mission 637 and 0 otherwise // License:1 - 1 if player has completed mission 637 and 0 otherwise
entry.push_back(new LDFData<int32_t>(u"NumWins", rows->getInt("numWins"))); entry.push_back(new LDFData<int32_t>(u"NumWins", rows->getInt("tertiaryScore")));
// NumWins:1 // NumWins:1
break; break;
case Type::UnusedLeaderboard4: case Type::UnusedLeaderboard4:
entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("score"))); entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("primaryScore")));
// Points:1 // Points:1
break; break;
case Type::MonumentRace: case Type::MonumentRace:
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("bestTime"))); entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("primaryScore")));
// Time:1(?) // Time:1(?)
break; break;
case Type::FootRace: case Type::FootRace:
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("bestTime"))); entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("primaryScore")));
// Time:1 // Time:1
break; break;
case Type::Survival: case Type::Survival:
entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("score"))); entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("primaryScore")));
// Points:1 // Points:1
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("bestTime"))); entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("secondaryScore")));
// Time:1 // Time:1
break; break;
case Type::SurvivalNS: case Type::SurvivalNS:
entry.push_back(new LDFData<int32_t>(u"Wave", rows->getInt("score"))); entry.push_back(new LDFData<int32_t>(u"Wave", rows->getInt("primaryScore")));
// Wave:1 // Wave:1
entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("bestTime"))); entry.push_back(new LDFData<int32_t>(u"Time", rows->getInt("secondaryScore")));
// Time:1 // Time:1
break; break;
case Type::Donations: case Type::Donations:
entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("score"))); entry.push_back(new LDFData<int32_t>(u"Points", rows->getInt("primaryScore")));
// Score:1 // Score:1
break; break;
case Type::None: case Type::None:
@ -139,98 +144,33 @@ void Leaderboard::QueryToLdf(std::unique_ptr<sql::ResultSet>& rows) {
} }
const std::string_view Leaderboard::GetColumns(Leaderboard::Type leaderboardType) { const std::string_view Leaderboard::GetColumns(Leaderboard::Type leaderboardType) {
const char* columns; return "primaryScore, secondaryScore, tertiaryScore";
switch (leaderboardType) {
case Type::ShootingGallery:
columns = "hitPercentage, score, streak";
break;
case Type::Racing:
columns = "bestLapTime, bestTime, numWins";
break;
case Type::Donations:
case Type::UnusedLeaderboard4:
columns = "score";
break;
case Type::MonumentRace:
case Type::FootRace:
columns = "bestTime";
break;
case Type::Survival:
columns = "bestTime, score";
break;
case Type::SurvivalNS:
columns = "bestTime, score";
break;
case Type::None:
columns = "";
// This type is included here simply to resolve a compiler warning on mac about unused enum types
break;
}
return columns;
} }
const std::string_view Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { const std::string_view Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) {
const char* columns; return "primaryScore %f, secondaryScore %f, tertiaryScore %f";
switch (leaderboardType) {
case Type::ShootingGallery:
columns = "score=%i, hitPercentage=%i, streak=%i";
break;
case Type::Racing:
columns = "bestLapTime=%i, bestTime=%i, numWins=numWins + %i";
break;
case Type::Donations:
case Type::UnusedLeaderboard4:
columns = "score=%i";
break;
case Type::MonumentRace:
case Type::FootRace:
columns = "bestTime=%i";
break;
case Type::Survival:
columns = "bestTime=%i, score=%i";
break;
case Type::SurvivalNS:
columns = "bestTime=%i, score=%i";
break;
case Type::None:
columns = "";
// This type is included here simply to resolve a compiler warning on mac about unused enum types
break;
}
return columns;
} }
const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) {
const char* orderBase; // Use a switch case and return desc for all 3 columns if higher is better and asc if lower is better
switch (leaderboardType) { switch (leaderboardType) {
case Type::ShootingGallery: case Type::ShootingGallery:
orderBase = "score DESC, streak DESC, hitPercentage DESC";
break;
case Type::Racing:
orderBase = "bestTime ASC, bestLapTime ASC, numWins DESC";
break;
case Type::Donations:
case Type::UnusedLeaderboard4:
orderBase = "score DESC";
break;
case Type::MonumentRace:
orderBase = "bestTime ASC";
break;
case Type::FootRace: case Type::FootRace:
orderBase = "bestTime DESC"; case Type::UnusedLeaderboard4:
break;
case Type::Survival:
orderBase = "score DESC, bestTime DESC";
break;
case Type::SurvivalNS: case Type::SurvivalNS:
orderBase = "bestTime DESC, score DESC"; case Type::Donations:
break; return "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC";
case Type::Racing:
case Type::MonumentRace:
return "primaryScore ASC, secondaryScore ASC, tertiaryScore ASC";
case Type::None: case Type::None:
orderBase = ""; case Type::Survival:
// This type is included here simply to resolve a compiler warning on mac about unused enum types return Game::config->GetValue("classic_survival_scoring") == "1" ?
break; "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC" :
"secondaryScore DESC, primaryScore DESC, tertiaryScore DESC";
default:
return "";
} }
return orderBase;
} }
void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) {
@ -285,34 +225,22 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) {
) )
)QUERY"; )QUERY";
[[likely]] if (this->infoType != InfoType::Friends) friendsQuery.clear(); if (this->infoType != InfoType::Friends) friendsQuery.clear();
const auto orderBase = GetOrdering(this->leaderboardType); const auto orderBase = GetOrdering(this->leaderboardType);
const auto selectBase = GetColumns(this->leaderboardType); const auto selectBase = GetColumns(this->leaderboardType);
constexpr uint16_t STRING_LENGTH = 2048; std::string baseLookup;
char lookupBuffer[STRING_LENGTH]; if (this->infoType == InfoType::Top) {
int32_t res = snprintf(lookupBuffer, STRING_LENGTH, queryBase.data(), orderBase.data(), friendsQuery.data(), selectBase.data(), resultStart, resultEnd); baseLookup = "SELECT id FROM leaderboard WHERE game_id = ? ORDER BY ";
DluAssert(res != -1); baseLookup += orderBase.data();
std::string baseLookupStr;
char baseRankingBuffer[STRING_LENGTH];
bool neededFormatting;
[[unlikely]] if (this->infoType == InfoType::Top) {
baseLookupStr = "SELECT id FROM leaderboard WHERE game_id = ? ORDER BY %s LIMIT 1";
neededFormatting = true;
} else { } else {
baseLookupStr = "SELECT id FROM leaderboard WHERE game_id = ? AND character_id = ? LIMIT 1"; baseLookup = "SELECT id FROM leaderboard WHERE game_id = ? AND character_id = ";
neededFormatting = false; baseLookup += std::to_string(this->relatedPlayer);
} }
baseLookup += " LIMIT 1";
// If we need to format the base ranking query, do so, otherwise just copy the query since it's already formatted. std::unique_ptr<sql::PreparedStatement> baseQuery(Database::CreatePreppedStmt(baseLookup));
if (neededFormatting) snprintf(baseRankingBuffer, STRING_LENGTH, baseLookupStr.c_str(), orderBase.data());
else std::copy(baseLookupStr.begin(), baseLookupStr.end() + 1, baseRankingBuffer);
std::unique_ptr<sql::PreparedStatement> baseQuery(Database::CreatePreppedStmt(baseRankingBuffer));
baseQuery->setInt(1, this->gameID); baseQuery->setInt(1, this->gameID);
if (!neededFormatting) baseQuery->setInt(2, this->relatedPlayer);
std::unique_ptr<sql::ResultSet> baseResult(baseQuery->executeQuery()); std::unique_ptr<sql::ResultSet> baseResult(baseQuery->executeQuery());
if (!baseResult->next()) return; // In this case, there are no entries in the leaderboard for this game. if (!baseResult->next()) return; // In this case, there are no entries in the leaderboard for this game.
@ -320,6 +248,10 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) {
uint32_t relatedPlayerLeaderboardId = baseResult->getInt("id"); uint32_t relatedPlayerLeaderboardId = baseResult->getInt("id");
// Create and execute the actual save here // Create and execute the actual save here
constexpr uint16_t STRING_LENGTH = 2048;
char lookupBuffer[STRING_LENGTH];
[[maybe_unused]] int32_t res = snprintf(lookupBuffer, STRING_LENGTH, queryBase.data(), orderBase.data(), friendsQuery.data(), selectBase.data(), resultStart, resultEnd);
DluAssert(res != -1);
std::unique_ptr<sql::PreparedStatement> query(Database::CreatePreppedStmt(lookupBuffer)); std::unique_ptr<sql::PreparedStatement> query(Database::CreatePreppedStmt(lookupBuffer));
query->setInt(1, this->gameID); query->setInt(1, this->gameID);
@ -365,7 +297,7 @@ std::string FormatInsert(const Leaderboard::Type& type, const Score& score, cons
return finishedQuery; return finishedQuery;
} }
void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID gameID, const Leaderboard::Type leaderboardType, const uint32_t primaryScore, const uint32_t secondaryScore, const uint32_t tertiaryScore) { void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID gameID, const Leaderboard::Type leaderboardType, const float primaryScore, const float secondaryScore, const float tertiaryScore) {
auto* lookup = "SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;"; auto* lookup = "SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;";
std::unique_ptr<sql::PreparedStatement> query(Database::CreatePreppedStmt(lookup)); std::unique_ptr<sql::PreparedStatement> query(Database::CreatePreppedStmt(lookup));
@ -381,46 +313,46 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID gameID
switch (leaderboardType) { switch (leaderboardType) {
// Higher score better // Higher score better
case Leaderboard::Type::ShootingGallery: { case Leaderboard::Type::ShootingGallery: {
oldScore.SetPrimaryScore(myScoreResult->getInt("score")); oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
oldScore.SetSecondaryScore(myScoreResult->getInt("hitPercentage")); oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
oldScore.SetTertiaryScore(myScoreResult->getInt("streak")); oldScore.SetTertiaryScore(myScoreResult->getInt("tertiaryScore"));
break; break;
} }
case Leaderboard::Type::FootRace: { case Leaderboard::Type::FootRace: {
oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
break; break;
} }
case Leaderboard::Type::Survival: { case Leaderboard::Type::Survival: {
// Config option may reverse these // Config option may reverse these
oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
oldScore.SetSecondaryScore(myScoreResult->getInt("score")); oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
break; break;
} }
case Leaderboard::Type::SurvivalNS: { case Leaderboard::Type::SurvivalNS: {
oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
oldScore.SetSecondaryScore(myScoreResult->getInt("score")); oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
break; break;
} }
case Leaderboard::Type::UnusedLeaderboard4: case Leaderboard::Type::UnusedLeaderboard4:
case Leaderboard::Type::Donations: { case Leaderboard::Type::Donations: {
oldScore.SetPrimaryScore(myScoreResult->getInt("score")); oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
break; break;
} }
case Leaderboard::Type::Racing: { case Leaderboard::Type::Racing: {
oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
oldScore.SetSecondaryScore(myScoreResult->getInt("bestLapTime")); oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore"));
lowerScoreBetter = true; lowerScoreBetter = true;
break; break;
} }
case Leaderboard::Type::MonumentRace: { case Leaderboard::Type::MonumentRace: {
oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore"));
lowerScoreBetter = true; lowerScoreBetter = true;
// Do score checking here // Do score checking here
break; break;
} }
case Leaderboard::Type::None: case Leaderboard::Type::None:
default: default:
Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i for game %i. Cannot save score!", leaderboardType, gameID);
return; return;
} }
bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore; bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore;

View File

@ -25,7 +25,7 @@ public:
secondaryScore = 0; secondaryScore = 0;
tertiaryScore = 0; tertiaryScore = 0;
} }
Score(const uint32_t primaryScore, const uint32_t secondaryScore = 0, const uint32_t tertiaryScore = 0) { Score(const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0) {
this->primaryScore = primaryScore; this->primaryScore = primaryScore;
this->secondaryScore = secondaryScore; this->secondaryScore = secondaryScore;
this->tertiaryScore = tertiaryScore; this->tertiaryScore = tertiaryScore;
@ -36,18 +36,18 @@ public:
bool operator>(const Score& rhs) const { bool operator>(const Score& rhs) const {
return primaryScore > rhs.primaryScore || (primaryScore == rhs.primaryScore && secondaryScore > rhs.secondaryScore) || (primaryScore == rhs.primaryScore && secondaryScore == rhs.secondaryScore && tertiaryScore > rhs.tertiaryScore); return primaryScore > rhs.primaryScore || (primaryScore == rhs.primaryScore && secondaryScore > rhs.secondaryScore) || (primaryScore == rhs.primaryScore && secondaryScore == rhs.secondaryScore && tertiaryScore > rhs.tertiaryScore);
} }
void SetPrimaryScore(const uint32_t score) { primaryScore = score; } void SetPrimaryScore(const float score) { primaryScore = score; }
uint32_t GetPrimaryScore() const { return primaryScore; } float GetPrimaryScore() const { return primaryScore; }
void SetSecondaryScore(const uint32_t score) { secondaryScore = score; } void SetSecondaryScore(const float score) { secondaryScore = score; }
uint32_t GetSecondaryScore() const { return secondaryScore; } float GetSecondaryScore() const { return secondaryScore; }
void SetTertiaryScore(const uint32_t score) { tertiaryScore = score; } void SetTertiaryScore(const float score) { tertiaryScore = score; }
uint32_t GetTertiaryScore() const { return tertiaryScore; } float GetTertiaryScore() const { return tertiaryScore; }
private: private:
uint32_t primaryScore; float primaryScore;
uint32_t secondaryScore; float secondaryScore;
uint32_t tertiaryScore; float tertiaryScore;
}; };
using GameID = uint32_t; using GameID = uint32_t;
@ -73,10 +73,11 @@ public:
Donations, Donations,
None None
}; };
Leaderboard() = delete;
Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type = None); Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type = None);
~Leaderboard(); ~Leaderboard();
void Clear();
/** /**
* Serialize the Leaderboard to a BitStream * Serialize the Leaderboard to a BitStream
@ -85,15 +86,6 @@ public:
*/ */
void Serialize(RakNet::BitStream* bitStream) const; void Serialize(RakNet::BitStream* bitStream) const;
/**
* Based on the associated gameID, return true if the score provided
* is better than the current entries' score
* @param score
* @return true
* @return false
*/
bool IsScoreBetter(const uint32_t score) const { return false; };
/** /**
* Builds the leaderboard from the database based on the associated gameID * Builds the leaderboard from the database based on the associated gameID
* *
@ -135,7 +127,7 @@ namespace LeaderboardManager {
using LeaderboardCache = std::map<GameID, Leaderboard::Type>; using LeaderboardCache = std::map<GameID, Leaderboard::Type>;
void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, LWOOBJID targetID, uint32_t resultStart = 0, uint32_t resultEnd = 10); void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, LWOOBJID targetID, uint32_t resultStart = 0, uint32_t resultEnd = 10);
void SaveScore(const LWOOBJID& playerID, const GameID gameID, const Leaderboard::Type leaderboardType, const uint32_t primaryScore, const uint32_t secondaryScore = 0, const uint32_t tertiaryScore = 0); void SaveScore(const LWOOBJID& playerID, const GameID gameID, const Leaderboard::Type leaderboardType, const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0);
void GetLeaderboard(const uint32_t gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID = LWOOBJID_EMPTY); void GetLeaderboard(const uint32_t gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID = LWOOBJID_EMPTY);