From 5c086909ed3776087d9764c4b915aac4eb2651b3 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 May 2023 18:36:28 -0700 Subject: [PATCH] Ready for implementation --- dGame/LeaderboardManager.cpp | 524 ++++++++++++++------------ dGame/LeaderboardManager.h | 86 ++++- tests/dGameTests/LeaderboardTests.cpp | 26 +- 3 files changed, 352 insertions(+), 284 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 44ef17cc..d6713518 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -40,12 +40,12 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) { leaderboard << "Result[0].Index=0:RowNumber\n"; // "Primary key" leaderboard << "Result[0].RowCount=1:" << entries.size() << '\n'; // number of rows - int32_t index = 0; + int32_t rowNumber = 0; for (auto& entry : entries) { - for (auto data : entry) { - WriteLeaderboardRow(leaderboard, index, data); + for (auto* data : entry) { + WriteLeaderboardRow(leaderboard, rowNumber, data); } - index++; + rowNumber++; } // Serialize the thing to a BitStream @@ -62,187 +62,65 @@ bool Leaderboard::GetRankingQuery(std::string& lookupReturn) const { } } -void Leaderboard::SetupLeaderboard() { - std::string queryBase = - R"QUERY( - WITH leaderboardsRanked AS ( - SELECT leaderboard.*, charinfo.name, - RANK() OVER - ( - ORDER BY %s, UNIX_TIMESTAMP(last_played) ASC, id DESC - ) AS ranking - FROM leaderboard JOIN charinfo on charinfo.id = leaderboard.character_id - WHERE game_id = ? %s - ), - myStanding AS ( - SELECT - ranking as myRank - FROM leaderboardsRanked - WHERE id = ? - ), - lowestRanking AS ( - SELECT MAX(ranking) AS lowestRank - FROM leaderboardsRanked - ) - SELECT %s, character_id, UNIX_TIMESTAMP(last_played) as lastPlayed, leaderboardsRanked.name, leaderboardsRanked.ranking FROM leaderboardsRanked, myStanding, lowestRanking - WHERE leaderboardsRanked.ranking - BETWEEN - LEAST(GREATEST(CAST(myRank AS SIGNED) - 5, 1), lowestRanking.lowestRank - 10) - AND - LEAST(GREATEST(myRank + 5, 11), lowestRanking.lowestRank) - ORDER BY ranking ASC;)QUERY"; - // Setup query based on activity. - // Where clause will vary based on what query we are doing - // Get base based on InfoType - // Fill in base with arguments based on leaderboard type - // If this is a friends query we need to join another table and add even more to the where clause. +void Leaderboard::QueryToLdf(std::unique_ptr& rows) { + if (rows->rowsCount() == 0) return; - const char* friendsQuery = - " AND (character_id IN (SELECT fr.requested_player FROM (SELECT CASE " - "WHEN player_id = ? THEN friend_id " - "WHEN friend_id = ? THEN player_id " - "END AS requested_player FROM friends) AS fr " - "JOIN charinfo AS ci ON ci.id = fr.requested_player " - "WHERE fr.requested_player IS NOT NULL) OR character_id = ?) "; - - std::string orderBase; - std::string selectBase; - switch (leaderboardType) { - case Type::ShootingGallery: { - orderBase = "score DESC, streak DESC, hitPercentage DESC"; - selectBase = "hitPercentage, score, streak"; - break; - } - case Type::Racing: - orderBase = "bestTime ASC, bestLapTime ASC, numWins DESC"; - selectBase = "bestLapTime, bestTime, numWins"; - break; - case Type::UnusedLeaderboard4: - orderBase = "score DESC"; - selectBase = "score"; - break; - case Type::MonumentRace: - orderBase = "bestTime ASC"; - selectBase = "bestTime"; - break; - case Type::FootRace: - orderBase = "bestTime DESC"; - selectBase = "bestTime"; - break; - case Type::Survival: - orderBase = "score DESC, bestTime DESC"; - selectBase = "score, bestTime"; - // If the config option default_survival_scoring is 1, reverse the order of the points and time columns - break; - case Type::SurvivalNS: - orderBase = "bestTime DESC, score DESC"; - selectBase = "bestTime, score"; - break; - case Type::Donations: - orderBase = "score DESC"; - selectBase = "score"; - break; - case Type::None: - Game::logger->Log("LeaderboardManager", "Attempting to get leaderboard for type none. Is this intended?"); - // This type is included here simply to resolve a compiler warning on mac about unused enum types - break; - } - - constexpr uint16_t STRING_LENGTH = 1526; - char lookupBuffer[STRING_LENGTH]; - if (this->infoType == InfoType::Friends) snprintf(lookupBuffer, STRING_LENGTH, queryBase.c_str(), orderBase.c_str(), friendsQuery, selectBase.c_str()); - else snprintf(lookupBuffer, STRING_LENGTH, queryBase.c_str(), orderBase.c_str(), "", selectBase.c_str()); - - std::string baseLookupStr; - char baseRankingBuffer[STRING_LENGTH]; - bool neededFormatting = GetRankingQuery(baseLookupStr); - if (neededFormatting) snprintf(baseRankingBuffer, STRING_LENGTH, baseLookupStr.c_str(), orderBase.c_str()); - else std::copy(baseLookupStr.begin(), baseLookupStr.end() + 1, baseRankingBuffer); - - Game::logger->Log("LeaderboardManager", "lookup query is %s", baseRankingBuffer); - std::unique_ptr baseQuery(Database::CreatePreppedStmt(baseRankingBuffer)); - baseQuery->setInt(1, this->gameID); - if (!neededFormatting) { - baseQuery->setInt(2, this->relatedPlayer); - } - - std::unique_ptr baseResult(baseQuery->executeQuery()); - if (!baseResult->next()) return; - // Get the ID of the row fetched. - uint32_t relatedPlayerLeaderboardId = baseResult->getInt("id"); - - // create and execute query here - Game::logger->Log("LeaderboardManager", "filled in query is %s %i %i %i", lookupBuffer, this->gameID, this->relatedPlayer, relatedPlayerLeaderboardId); - std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer)); - query->setInt(1, this->gameID); - if (this->infoType == InfoType::Friends) { - query->setInt(2, this->relatedPlayer); - query->setInt(3, this->relatedPlayer); - query->setInt(4, this->relatedPlayer); - query->setInt(5, relatedPlayerLeaderboardId); - } else { - query->setInt(2, relatedPlayerLeaderboardId); - } - std::unique_ptr result(query->executeQuery()); - - if (result->rowsCount() == 0) return; - - this->entries.reserve(result->rowsCount()); - while (result->next()) { + this->entries.reserve(rows->rowsCount()); + while (rows->next()) { constexpr int32_t MAX_NUM_DATA_PER_ROW = 9; this->entries.push_back(std::vector()); auto& entry = this->entries.back(); entry.reserve(MAX_NUM_DATA_PER_ROW); - entry.push_back(new LDFData(u"CharacterID", result->getInt("character_id"))); - entry.push_back(new LDFData(u"LastPlayed", result->getUInt64("lastPlayed"))); + entry.push_back(new LDFData(u"CharacterID", rows->getInt("character_id"))); + entry.push_back(new LDFData(u"LastPlayed", rows->getUInt64("lastPlayed"))); entry.push_back(new LDFData(u"NumPlayed", 1)); - entry.push_back(new LDFData(u"name", GeneralUtils::ASCIIToUTF16(result->getString("name").c_str()))); - entry.push_back(new LDFData(u"RowNumber", result->getInt("ranking"))); + entry.push_back(new LDFData(u"name", GeneralUtils::ASCIIToUTF16(rows->getString("name").c_str()))); + entry.push_back(new LDFData(u"RowNumber", rows->getInt("ranking"))); switch (leaderboardType) { case Type::ShootingGallery: - entry.push_back(new LDFData(u"HitPercentage", result->getDouble("hitPercentage"))); + entry.push_back(new LDFData(u"HitPercentage", rows->getDouble("hitPercentage"))); // HitPercentage:3 between 0 and 1 - entry.push_back(new LDFData(u"Score", result->getInt("score"))); + entry.push_back(new LDFData(u"Score", rows->getInt("score"))); // Score:1 - entry.push_back(new LDFData(u"Streak", result->getInt("streak"))); + entry.push_back(new LDFData(u"Streak", rows->getInt("streak"))); // Streak:1 break; case Type::Racing: - entry.push_back(new LDFData(u"BestLapTime", result->getDouble("bestLapTime"))); + entry.push_back(new LDFData(u"BestLapTime", rows->getDouble("bestLapTime"))); // BestLapTime:3 - entry.push_back(new LDFData(u"BestTime", result->getDouble("bestTime"))); + entry.push_back(new LDFData(u"BestTime", rows->getDouble("bestTime"))); // BestTime:3 entry.push_back(new LDFData(u"License", 1)); // License:1 - 1 if player has completed mission 637 and 0 otherwise - entry.push_back(new LDFData(u"NumWins", result->getInt("numWins"))); + entry.push_back(new LDFData(u"NumWins", rows->getInt("numWins"))); // NumWins:1 break; case Type::UnusedLeaderboard4: - entry.push_back(new LDFData(u"Score", result->getInt("score"))); + entry.push_back(new LDFData(u"Score", rows->getInt("score"))); // Points:1 break; case Type::MonumentRace: - entry.push_back(new LDFData(u"Time", result->getInt("bestTime"))); + entry.push_back(new LDFData(u"Time", rows->getInt("bestTime"))); // Time:1(?) break; case Type::FootRace: - entry.push_back(new LDFData(u"Time", result->getInt("bestTime"))); + entry.push_back(new LDFData(u"Time", rows->getInt("bestTime"))); // Time:1 break; case Type::Survival: - entry.push_back(new LDFData(u"Score", result->getInt("score"))); + entry.push_back(new LDFData(u"Score", rows->getInt("score"))); // Points:1 - entry.push_back(new LDFData(u"Time", result->getInt("bestTime"))); + entry.push_back(new LDFData(u"Time", rows->getInt("bestTime"))); // Time:1 break; case Type::SurvivalNS: - entry.push_back(new LDFData(u"Score", result->getInt("score"))); + entry.push_back(new LDFData(u"Score", rows->getInt("score"))); // Wave:1 - entry.push_back(new LDFData(u"Time", result->getInt("bestTime"))); + entry.push_back(new LDFData(u"Time", rows->getInt("bestTime"))); // Time:1 break; case Type::Donations: - entry.push_back(new LDFData(u"Score", result->getInt("score"))); + entry.push_back(new LDFData(u"Score", rows->getInt("score"))); // Score:1 break; case Type::None: @@ -252,11 +130,202 @@ void Leaderboard::SetupLeaderboard() { break; } } - for (auto& entry : entries) { - for (auto data : entry) { - Game::logger->Log("LeaderboardManager", "entry is %s", data->GetString().c_str()); - } +} + +std::string Leaderboard::GetColumns(Leaderboard::Type leaderboardType) { + std::string columns; + switch (leaderboardType) { + case Type::ShootingGallery: + columns = "hitPercentage, score, streak"; + break; + case Type::Racing: + columns = "bestLapTime, bestTime, numWins"; + break; + case Type::UnusedLeaderboard4: + columns = "score"; + break; + case Type::MonumentRace: + columns = "bestTime"; + break; + case Type::FootRace: + columns = "bestTime"; + break; + case Type::Survival: + columns = "bestTime, score"; + break; + case Type::SurvivalNS: + columns = "bestTime, score"; + break; + case Type::Donations: + columns = "score"; + break; + case Type::None: + // This type is included here simply to resolve a compiler warning on mac about unused enum types + break; } + return columns; +} + +std::string Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { + std::string columns; + switch (leaderboardType) { + case Type::ShootingGallery: + columns = "hitPercentage=%f, score=%i, streak=%i"; + break; + case Type::Racing: + columns = "bestLapTime=%i, bestTime=%i, numWins=numWins + %i"; + break; + case Type::UnusedLeaderboard4: + columns = "score=%i"; + break; + case Type::MonumentRace: + columns = "bestTime=%i"; + break; + 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::Donations: + columns = "score=%i"; + break; + case Type::None: + // This type is included here simply to resolve a compiler warning on mac about unused enum types + break; + } + return columns; +} + +std::string Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { + std::string orderBase; + switch (leaderboardType) { + case Type::ShootingGallery: + orderBase = "score DESC, streak DESC, hitPercentage DESC"; + break; + case Type::Racing: + orderBase = "bestTime ASC, bestLapTime ASC, numWins DESC"; + break; + case Type::UnusedLeaderboard4: + orderBase = "score DESC"; + break; + case Type::MonumentRace: + orderBase = "bestTime ASC"; + break; + case Type::FootRace: + orderBase = "bestTime DESC"; + break; + case Type::Survival: + orderBase = "score DESC, bestTime DESC"; + break; + case Type::SurvivalNS: + orderBase = "bestTime DESC, score DESC"; + break; + case Type::Donations: + orderBase = "score DESC"; + break; + case Type::None: + // This type is included here simply to resolve a compiler warning on mac about unused enum types + break; + } + return orderBase; +} + +void Leaderboard::SetupLeaderboard() { + std::string queryBase = + R"QUERY( + WITH leaderboardsRanked AS ( + SELECT leaderboard.*, charinfo.name, + RANK() OVER + ( + ORDER BY %s, UNIX_TIMESTAMP(last_played) ASC, id DESC + ) AS ranking + FROM leaderboard JOIN charinfo on charinfo.id = leaderboard.character_id + WHERE game_id = ? %s + ), + myStanding AS ( + SELECT + ranking as myRank + FROM leaderboardsRanked + WHERE id = ? + ), + lowestRanking AS ( + SELECT MAX(ranking) AS lowestRank + FROM leaderboardsRanked + ) + SELECT %s, character_id, UNIX_TIMESTAMP(last_played) as lastPlayed, leaderboardsRanked.name, leaderboardsRanked.ranking FROM leaderboardsRanked, myStanding, lowestRanking + WHERE leaderboardsRanked.ranking + BETWEEN + LEAST(GREATEST(CAST(myRank AS SIGNED) - 5, 1), lowestRanking.lowestRank - 10) + AND + LEAST(GREATEST(myRank + 5, 11), lowestRanking.lowestRank) + ORDER BY ranking ASC; + )QUERY"; + + const char* friendsQuery = + R"QUERY( AND ( + character_id IN ( + SELECT fr.requested_player FROM ( + SELECT CASE + WHEN player_id = ? THEN friend_id + WHEN friend_id = ? THEN player_id + END AS requested_player + FROM friends + ) AS fr + JOIN charinfo AS ci + ON ci.id = fr.requested_player + WHERE fr.requested_player IS NOT NULL + ) + OR character_id = ? + ) + )QUERY"; + + std::string orderBase = GetOrdering(this->leaderboardType); + std::string selectBase = GetColumns(this->leaderboardType); + + constexpr uint16_t STRING_LENGTH = 1526; + char lookupBuffer[STRING_LENGTH]; + // If we are getting the friends leaderboard, add the friends query, otherwise fill it in with nothing. + if (this->infoType == InfoType::Friends) snprintf(lookupBuffer, STRING_LENGTH, queryBase.c_str(), orderBase.c_str(), friendsQuery, selectBase.c_str()); + else snprintf(lookupBuffer, STRING_LENGTH, queryBase.c_str(), orderBase.c_str(), "", selectBase.c_str()); + + std::string baseLookupStr; + char baseRankingBuffer[STRING_LENGTH]; + bool neededFormatting = GetRankingQuery(baseLookupStr); + + // If we need to format the base ranking query, do so, otherwise just copy the query since it's already formatted. + if (neededFormatting) snprintf(baseRankingBuffer, STRING_LENGTH, baseLookupStr.c_str(), orderBase.c_str()); + else std::copy(baseLookupStr.begin(), baseLookupStr.end() + 1, baseRankingBuffer); + + std::unique_ptr baseQuery(Database::CreatePreppedStmt(baseRankingBuffer)); + baseQuery->setInt(1, this->gameID); + if (!neededFormatting) { + baseQuery->setInt(2, this->relatedPlayer); + } + + std::unique_ptr baseResult(baseQuery->executeQuery()); + if (!baseResult->next()) return; // In this case, there are no entries in the leaderboard for this game. + + uint32_t relatedPlayerLeaderboardId = baseResult->getInt("id"); + + // Create and execute the actual save here + std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer)); + + query->setInt(1, this->gameID); + if (this->infoType == InfoType::Friends) { + query->setInt(2, this->relatedPlayer); + query->setInt(3, this->relatedPlayer); + query->setInt(4, this->relatedPlayer); + query->setInt(5, relatedPlayerLeaderboardId); + } else { + query->setInt(2, relatedPlayerLeaderboardId); + } + + std::unique_ptr result(query->executeQuery()); + QueryToLdf(result); } void Leaderboard::Send(LWOOBJID targetID) const { @@ -292,56 +361,9 @@ std::string FormatInsert(const std::string& columns, const std::string& format, return finishedQuery; } -void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list args) { - std::string insertStatement; - std::string selectedColumns; - std::string insertFormat; - std::va_list argsCopy; - va_copy(argsCopy, args); - - switch (leaderboardType) { - case Leaderboard::Type::ShootingGallery: { - selectedColumns = "score, hitPercentage, streak"; - insertFormat = "score=%i, hitPercentage=%f, streak=%i"; - break; - } - case Leaderboard::Type::Racing: { - selectedColumns = "bestLapTime, bestTime, numWins"; - insertFormat = "bestLapTime=%f, bestTime=%f, numWins=%i"; - break; - } - case Leaderboard::Type::UnusedLeaderboard4: { - selectedColumns = "score"; - insertFormat = "score=%i"; - break; - } - case Leaderboard::Type::MonumentRace: - case Leaderboard::Type::FootRace: { - selectedColumns = "bestTime"; - insertFormat = "bestTime=%i"; - break; - } - case Leaderboard::Type::Survival: { - selectedColumns = "score, bestTime"; - insertFormat = "score=%i, bestTime=%i"; - break; - } - case Leaderboard::Type::SurvivalNS: { - selectedColumns = "bestTime, score"; - insertFormat = "bestTime=%i, score=%i"; - break; - } - case Leaderboard::Type::Donations: { - selectedColumns = "score"; - insertFormat = "score=%i"; - break; - } - case Leaderboard::Type::None: - default: { - Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); - return; - } - } +void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list activityScore) { + std::string selectedColumns = Leaderboard::GetColumns(leaderboardType); + std::string insertFormat = Leaderboard::GetInsertFormat(leaderboardType); const char* lookup = "SELECT %s FROM leaderboard WHERE character_id = ? AND game_id = ?;"; constexpr uint16_t STRING_LENGTH = 400; @@ -353,6 +375,8 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead query->setInt(2, gameID); std::unique_ptr myScoreResult(query->executeQuery()); + std::va_list argsCopy; + va_copy(argsCopy, activityScore); std::string saveQuery; if (myScoreResult->next()) { switch (leaderboardType) { @@ -369,74 +393,78 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead int32_t streak; streak = va_arg(argsCopy, int32_t); - if ( - score > oldScore || // If score is better + if (score > oldScore || // If score is better (score == oldScore && hitPercentage > oldHitPercentage) || // or if the score is tied and the hitPercentage is better (score == oldScore && hitPercentage == oldHitPercentage && streak > oldStreak)) { // or if the score and hitPercentage are tied and the streak is better - saveQuery = FormatInsert(selectedColumns, insertFormat, args, true); + saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true); } break; } case Leaderboard::Type::Racing: { - float oldLapTime = myScoreResult->getFloat("bestLapTime"); - float lapTime; - lapTime = va_arg(argsCopy, double); + uint32_t oldLapTime = myScoreResult->getFloat("bestLapTime"); + uint32_t lapTime; + lapTime = va_arg(argsCopy, uint32_t); - float oldTime = myScoreResult->getFloat("bestTime"); - float newTime; - newTime = va_arg(argsCopy, double); + uint32_t oldTime = myScoreResult->getFloat("bestTime"); + uint32_t newTime; + newTime = va_arg(argsCopy, uint32_t); - int32_t oldNumWins = myScoreResult->getInt("numWins"); bool won; won = va_arg(argsCopy, int32_t); - // Compare bestTime, if LOWER save - // Compare bestLapTime, if LOWER save - // Increment numWins if player won + + if (newTime < oldTime || + (newTime == oldTime && lapTime < oldLapTime)) { + saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true); + } else if (won) { + std::unique_ptr incrementStatement(Database::CreatePreppedStmt("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;")); + incrementStatement->setInt(1, playerID); + incrementStatement->setInt(2, gameID); + incrementStatement->executeUpdate(); + } break; } case Leaderboard::Type::UnusedLeaderboard4: { int32_t oldScore = myScoreResult->getInt("score"); int32_t points; points = va_arg(argsCopy, int32_t); + if (points > oldScore) { - saveQuery = FormatInsert(selectedColumns, insertFormat, args, true); + saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true); } - // Compare score, if HIGHER save break; } case Leaderboard::Type::MonumentRace: { int32_t oldTime = myScoreResult->getInt("bestTime"); int32_t time; time = va_arg(argsCopy, int32_t); + if (time < oldTime) { - saveQuery = FormatInsert(selectedColumns, insertFormat, args, true); + saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true); } - // Compare time, if LOWER save break; } case Leaderboard::Type::FootRace: { int32_t oldTime = myScoreResult->getInt("bestTime"); int32_t time; time = va_arg(argsCopy, int32_t); + if (time < oldTime) { - saveQuery = FormatInsert(selectedColumns, insertFormat, args, true); + saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true); } - // Compare time, if HIGHER save break; } case Leaderboard::Type::Survival: { + int32_t oldTime = myScoreResult->getInt("bestTime"); + int32_t time; + time = va_arg(argsCopy, int32_t); + int32_t oldPoints = myScoreResult->getInt("score"); int32_t points; points = va_arg(argsCopy, int32_t); - int32_t oldTime = myScoreResult->getInt("bestTime"); - int32_t time; - time = va_arg(argsCopy, int32_t); if (points > oldPoints || (points == oldPoints && time < oldTime)) { - saveQuery = FormatInsert(selectedColumns, insertFormat, args, true); + saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true); } - // Compare points, if HIGHER save, if TIED compare time, if LOWER save - // If classic_survival_scoring is 1, reverse the order of the points and time columns break; } case Leaderboard::Type::SurvivalNS: { @@ -447,52 +475,48 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead int32_t oldWave = myScoreResult->getInt("score"); int32_t wave; wave = va_arg(argsCopy, int32_t); + if (time < oldTime || (time == oldTime && wave > oldWave)) { - saveQuery = FormatInsert(selectedColumns, insertFormat, args, true); + saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true); } - // Compare wave, if HIGHER save, if TIED compare time, if LOWER save break; } case Leaderboard::Type::Donations: { int32_t oldScore = myScoreResult->getInt("score"); int32_t score; score = va_arg(argsCopy, int32_t); - if (score > oldScore) { - saveQuery = FormatInsert(selectedColumns, insertFormat, args, true); - } - // Compare score, if HIGHER save - break; - } - case Leaderboard::Type::None: { - // This type is included here simply to resolve a compiler warning on mac about unused enum types - Game::logger->Log("LeaderboardManager", "Warning: Saving score for leaderboard of type None. Are you sure this is intended?"); - break; - } + if (score > oldScore) { + saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true); + } + break; + } + case Leaderboard::Type::None: default: Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); break; } } else { - saveQuery = FormatInsert(selectedColumns, insertFormat, argsCopy, false); + saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, false); } + std::unique_ptr saveStatement; if (!saveQuery.empty()) { - Game::logger->Log("LeaderboardManager", "%s", saveQuery.c_str()); - std::unique_ptr insertQuery(Database::CreatePreppedStmt(saveQuery)); - insertQuery->setInt(1, playerID); - insertQuery->setInt(2, gameID); - insertQuery->execute(); + Game::logger->Log("LeaderboardManager", "Executing update with query %s", saveQuery.c_str()); + std::unique_ptr updateStatement(Database::CreatePreppedStmt(saveQuery)); + saveStatement = std::move(updateStatement); } else { Game::logger->Log("LeaderboardManager", "No new score to save, incrementing numTimesPlayed"); // Increment the numTimes this player has played this game. - std::unique_ptr incrementStatement(Database::CreatePreppedStmt("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;")); - incrementStatement->setInt(1, playerID); - incrementStatement->setInt(2, gameID); - incrementStatement->executeUpdate(); + std::unique_ptr updateStatement(Database::CreatePreppedStmt("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;")); + saveStatement = std::move(updateStatement); } + saveStatement->setInt(1, playerID); + saveStatement->setInt(2, gameID); + saveStatement->execute(); va_end(argsCopy); } +// Done void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, LWOOBJID playerID) { Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID)); leaderboard.SetupLeaderboard(); diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 898dfbbb..c6c9b0e1 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -7,7 +7,11 @@ #include "dCommonVars.h" #include "LDFFormat.h" -namespace RakNet{ +namespace sql { + class ResultSet; +}; + +namespace RakNet { class BitStream; }; @@ -43,7 +47,7 @@ public: /** * Serialize the Leaderboard to a BitStream - * + * * Expensive! Leaderboards are very string intensive so be wary of performatnce calling this method. */ void Serialize(RakNet::BitStream* bitStream); @@ -51,9 +55,9 @@ public: /** * Based on the associated gameID, return true if the score provided * is better than the current entries' score - * @param score - * @return true - * @return false + * @param score + * @return true + * @return false */ bool IsScoreBetter(const uint32_t score) const { return false; }; @@ -66,11 +70,21 @@ public: * Sends the leaderboard to the client specified by targetID. */ void Send(LWOOBJID targetID) const; + + // Helper functions to get the columns, ordering and insert format for a leaderboard + static std::string GetColumns(Type leaderboardType); + static std::string GetInsertFormat(Type leaderboardType); + static std::string GetOrdering(Type leaderboardType); private: inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data); // Returns true if the string needs formatting bool GetRankingQuery(std::string& lookupReturn) const; + + // Takes the resulting query from a leaderboard lookup and converts it to the LDF we need + // to send it to a client. + void QueryToLdf(std::unique_ptr& rows); + LeaderboardEntries entries; LWOOBJID relatedPlayer; GameID gameID; @@ -79,23 +93,61 @@ private: bool weekly; }; -class LeaderboardManager: public Singleton { +class LeaderboardManager : public Singleton { typedef std::map LeaderboardCache; public: - void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, - LWOOBJID playerID = LWOOBJID_EMPTY); - /** - * @brief Public facing Score saving method. This method is simply a wrapper to ensure va_end is called properly. - * - * @param playerID The player whos score to save - * @param gameID The ID of the game which was played - * @param argumentCount The number of arguments in the va_list - * @param ... - */ - void SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t argumentCount, ...); + void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, LWOOBJID playerID = LWOOBJID_EMPTY); + + // Saves a score to the database for the Racing minigame + inline void SaveRacingScore(const LWOOBJID& playerID, GameID gameID, uint32_t bestLapTime, uint32_t bestTime, bool won) { + SaveScore(playerID, gameID, Leaderboard::Racing, 3, bestLapTime, bestTime, won); + }; + + // Saves a score to the database for the Shooting Gallery minigame + inline void SaveShootingGalleryScore(const LWOOBJID& playerID, GameID gameID, uint32_t score, float hitPercentage, uint32_t streak) { + SaveScore(playerID, gameID, Leaderboard::ShootingGallery, 3, score, hitPercentage, streak); + }; + + // Saves a score to the database for the footrace minigame + inline void SaveFootRaceScore(const LWOOBJID& playerID, GameID gameID, uint32_t bestTime) { + SaveScore(playerID, gameID, Leaderboard::FootRace, 1, bestTime); + }; + + // Saves a score to the database for the Monument footrace minigame + inline void SaveMonumentRaceScore(const LWOOBJID& playerID, GameID gameID, uint32_t bestTime) { + SaveScore(playerID, gameID, Leaderboard::MonumentRace, 1, bestTime); + }; + + // Saves a score to the database for the Survival minigame + inline void SaveSurvivalScore(const LWOOBJID& playerID, GameID gameID, uint32_t score, uint32_t waves) { + SaveScore(playerID, gameID, Leaderboard::Survival, 2, score, waves); + }; + + // Saves a score to the database for the SurvivalNS minigame + // Same as the Survival minigame, but good for explicitness + inline void SaveSurvivalNSScore(const LWOOBJID& playerID, GameID gameID, uint32_t score, uint32_t waves) { + SaveScore(playerID, gameID, Leaderboard::SurvivalNS, 2, score, waves); + }; + + // Saves a score to the database for the Donations minigame + inline void SaveDonationsScore(const LWOOBJID& playerID, GameID gameID, uint32_t score) { + SaveScore(playerID, gameID, Leaderboard::Donations, 1, score); + }; + private: void SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list args); void GetLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID = LWOOBJID_EMPTY); + + /** + * @brief Public facing Score saving method. This method is simply a wrapper to ensure va_end is called properly. + * + * @param playerID The player whos score to save + * @param gameID The ID of the game which was played + * @param argumentCount The number of arguments in the va_list + * @param ... The score to save + */ + void SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t argumentCount, ...); + Leaderboard::Type GetLeaderboardType(const GameID gameID); LeaderboardCache leaderboardCache; }; diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index a4b7b9c0..fbe76df4 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -73,21 +73,13 @@ protected: TEST_F(LeaderboardTests, LeaderboardSpeedTest) { RunTests(1864, Leaderboard::Type::ShootingGallery , Leaderboard::InfoType::Top); - // RunTests(1864, Leaderboard::Type::ShootingGallery, Leaderboard::InfoType::MyStanding); - // RunTests(1864, Leaderboard::Type::ShootingGallery, Leaderboard::InfoType::Friends); - LeaderboardManager::Instance().SaveScore(14231, 1864, Leaderboard::Type::ShootingGallery, 3, 53002, 15.0f, 100); - // RunTests(0, Leaderboard::Type::Racing); - LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::Racing, 3, 259.0f, 250.0f, true); - // RunTests(0, Leaderboard::Type::MonumentRace); - LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::MonumentRace, 1, 149); - // RunTests(0, Leaderboard::Type::FootRace); - LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::FootRace, 1, 151); - // RunTests(0, Leaderboard::Type::UnusedLeaderboard4); - LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::UnusedLeaderboard4, 1, 101); - // RunTests(0, Leaderboard::Type::Survival); - LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::Survival, 2, 3001, 15); - // RunTests(0, Leaderboard::Type::SurvivalNS); - LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::SurvivalNS, 2, 301, 15); - // RunTests(0, Leaderboard::Type::Donations); - LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::Donations, 1, 300001); + RunTests(1864, Leaderboard::Type::ShootingGallery, Leaderboard::InfoType::MyStanding); + RunTests(1864, Leaderboard::Type::ShootingGallery, Leaderboard::InfoType::Friends); + LeaderboardManager::Instance().SaveShootingGalleryScore(14231, 1864, 53003, 15.0f, 100); + LeaderboardManager::Instance().SaveRacingScore(14231, 0, 40, 250, true); + LeaderboardManager::Instance().SaveMonumentRaceScore(14231, 0, 148); + LeaderboardManager::Instance().SaveFootRaceScore(14231, 0, 15); + LeaderboardManager::Instance().SaveSurvivalScore(14231, 0, 3002, 15); + LeaderboardManager::Instance().SaveSurvivalNSScore(14231, 0, 302, 15); + LeaderboardManager::Instance().SaveDonationsScore(14231, 0, 300003); }