From c02963013bd0dfd7d04753e79a53ed2d1efb3a2d Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 12 Apr 2023 21:57:58 -0700 Subject: [PATCH 01/59] first draft --- dGame/LeaderboardManager.cpp | 353 ++++----------------------------- dGame/LeaderboardManager.h | 128 ++++++------ dMasterServer/MasterServer.cpp | 6 + 3 files changed, 109 insertions(+), 378 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index d85a95d4..72101e64 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -13,17 +13,20 @@ #include "CDActivitiesTable.h" -Leaderboard::Leaderboard(uint32_t gameID, uint32_t infoType, bool weekly, std::vector entries, - LWOOBJID relatedPlayer, LeaderboardType leaderboardType) { +Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const Leaderboard::Type leaderboardType) { this->relatedPlayer = relatedPlayer; this->gameID = gameID; this->weekly = weekly; this->infoType = infoType; - this->entries = std::move(entries); + this->entries = entries; this->leaderboardType = leaderboardType; } -std::u16string Leaderboard::ToString() const { +bool Leaderboard::IsScoreBetter(const uint32_t score) const { + +} + +void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { std::string leaderboard; leaderboard += "ADO.Result=7:1\n"; @@ -50,19 +53,14 @@ std::u16string Leaderboard::ToString() const { index++; } - return GeneralUtils::UTF8ToUTF16(leaderboard); + // Serialize the thing to a BitStream + bitStream->WriteAlignedBytes((const unsigned char*)leaderboard.c_str(), leaderboard.size()); } -std::vector Leaderboard::GetEntries() { - return entries; -} - -uint32_t Leaderboard::GetGameID() const { - return gameID; -} - -uint32_t Leaderboard::GetInfoType() const { - return infoType; +void Leaderboard::SetupLeaderboard() { + // Setup query based on activity. + // Where clause will vary based on what query we are doing + } void Leaderboard::Send(LWOOBJID targetID) const { @@ -78,10 +76,10 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t return; auto* character = player->GetCharacter(); - if (character == nullptr) + if (!character) return; - auto* select = Database::CreatePreppedStmt("SELECT time, score FROM leaderboard WHERE character_id = ? AND game_id = ?;"); + std::unique_ptr select(Database::CreatePreppedStmt("SELECT time, score FROM leaderboard WHERE character_id = ? AND game_id = ?;")); select->setUInt64(1, character->GetID()); select->setInt(2, gameID); @@ -100,23 +98,23 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t bool classicSurvivalScoring = Game::config->GetValue("classic_survival_scoring") == "1"; switch (leaderboardType) { - case ShootingGallery: + case Leaderboard::Type::ShootingGallery: if (score <= storedScore) highscore = false; break; - case Racing: + case Leaderboard::Type::Racing: if (time >= storedTime) highscore = false; break; - case MonumentRace: + case Leaderboard::Type::MonumentRace: if (time >= storedTime) highscore = false; break; - case FootRace: + case Leaderboard::Type::FootRace: if (time <= storedTime) highscore = false; break; - case Survival: + case Leaderboard::Type::Survival: if (classicSurvivalScoring) { if (time <= storedTime) { // Based on time (LU live) highscore = false; @@ -126,7 +124,7 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t highscore = false; } break; - case SurvivalNS: + case Leaderboard::Type::SurvivalNS: if (!(score > storedScore || (time < storedTime && score >= storedScore))) highscore = false; break; @@ -135,13 +133,11 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t } if (!highscore) { - delete select; delete result; return; } } - delete select; delete result; if (any) { @@ -166,305 +162,24 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t } } -Leaderboard* LeaderboardManager::GetLeaderboard(uint32_t gameID, InfoType infoType, bool weekly, LWOOBJID playerID) { - auto leaderboardType = GetLeaderboardType(gameID); - - std::string query; - bool classicSurvivalScoring = Game::config->GetValue("classic_survival_scoring") == "1"; - switch (infoType) { - case InfoType::Standings: - switch (leaderboardType) { - case ShootingGallery: - query = standingsScoreQuery; // Shooting gallery is based on the highest score. - break; - case FootRace: - query = standingsTimeQuery; // The higher your time, the better for FootRace. - break; - case Survival: - query = classicSurvivalScoring ? standingsTimeQuery : standingsScoreQuery; - break; - case SurvivalNS: - query = standingsScoreQueryAsc; // BoNS is scored by highest wave (score) first, then time. - break; - default: - query = standingsTimeQueryAsc; // MonumentRace and Racing are based on the shortest time. - } - break; - case InfoType::Friends: - switch (leaderboardType) { - case ShootingGallery: - query = friendsScoreQuery; // Shooting gallery is based on the highest score. - break; - case FootRace: - query = friendsTimeQuery; // The higher your time, the better for FootRace. - break; - case Survival: - query = classicSurvivalScoring ? friendsTimeQuery : friendsScoreQuery; - break; - case SurvivalNS: - query = friendsScoreQueryAsc; // BoNS is scored by highest wave (score) first, then time. - break; - default: - query = friendsTimeQueryAsc; // MonumentRace and Racing are based on the shortest time. - } - break; - - default: - switch (leaderboardType) { - case ShootingGallery: - query = topPlayersScoreQuery; // Shooting gallery is based on the highest score. - break; - case FootRace: - query = topPlayersTimeQuery; // The higher your time, the better for FootRace. - break; - case Survival: - query = classicSurvivalScoring ? topPlayersTimeQuery : topPlayersScoreQuery; - break; - case SurvivalNS: - query = topPlayersScoreQueryAsc; // BoNS is scored by highest wave (score) first, then time. - break; - default: - query = topPlayersTimeQueryAsc; // MonumentRace and Racing are based on the shortest time. - } - } - - auto* statement = Database::CreatePreppedStmt(query); - statement->setUInt(1, gameID); - - // Only the standings and friends leaderboards require the character ID to be set - if (infoType == Standings || infoType == Friends) { - auto characterID = 0; - - const auto* player = EntityManager::Instance()->GetEntity(playerID); - if (player != nullptr) { - auto* character = player->GetCharacter(); - if (character != nullptr) - characterID = character->GetID(); - } - - statement->setUInt64(2, characterID); - } - - auto* res = statement->executeQuery(); - - std::vector entries{}; - - uint32_t index = 0; - while (res->next()) { - LeaderboardEntry entry; - entry.playerID = res->getUInt64(4); - entry.playerName = res->getString(5); - entry.time = res->getUInt(1); - entry.score = res->getUInt(2); - entry.placement = res->getUInt(3); - entry.lastPlayed = res->getUInt(6); - - entries.push_back(entry); - index++; - } - - delete res; - delete statement; - - return new Leaderboard(gameID, infoType, weekly, entries, playerID, leaderboardType); -} - -void LeaderboardManager::SendLeaderboard(uint32_t gameID, InfoType infoType, bool weekly, LWOOBJID targetID, +void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, LWOOBJID playerID) { - const auto* leaderboard = LeaderboardManager::GetLeaderboard(gameID, infoType, weekly, playerID); - leaderboard->Send(targetID); - delete leaderboard; + // Create the leaderboard here and then send it right after. On the stack. + Leaderboard leaderboard(gameID, infoType, weekly, GetLeaderboardType(gameID)); + leaderboard.SetupLeaderboard(); + leaderboard.Send(targetID); } -LeaderboardType LeaderboardManager::GetLeaderboardType(uint32_t gameID) { +// Done +Leaderboard::Type LeaderboardManager::GetLeaderboardType(const GameID gameID) { + auto lookup = leaderboardCache.find(gameID); + if (lookup != leaderboardCache.end()) return lookup->second; + auto* activitiesTable = CDClientManager::Instance().GetTable(); std::vector activities = activitiesTable->Query([=](const CDActivities& entry) { return (entry.ActivityID == gameID); }); - - for (const auto& activity : activities) { - return static_cast(activity.leaderboardType); - } - - return LeaderboardType::None; + auto type = activities.empty() ? static_cast(activities.at(0).leaderboardType) : Leaderboard::Type::None; + leaderboardCache.insert_or_assign(gameID, type); + return type; } - -const std::string LeaderboardManager::topPlayersScoreQuery = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " -"RANK() OVER ( ORDER BY l.score DESC, l.time DESC, last_played ) leaderboard_rank " -" FROM leaderboard l " -"INNER JOIN charinfo c ON l.character_id = c.id " -"WHERE l.game_id = ? " -"ORDER BY leaderboard_rank) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales LIMIT 11;"; - -const std::string LeaderboardManager::friendsScoreQuery = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, f.friend_id, f.player_id, " -" RANK() OVER ( ORDER BY l.score DESC, l.time DESC, last_played ) leaderboard_rank " -" FROM leaderboard l " -" INNER JOIN charinfo c ON l.character_id = c.id " -" INNER JOIN friends f ON f.player_id = c.id " -" WHERE l.game_id = ? " -" ORDER BY leaderboard_rank), " -" personal_values AS ( " -" SELECT id as related_player_id, " -" GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, " -" GREATEST(leaderboard_rank + 5, 11) AS max_rank " -" FROM leaderboard_vales WHERE leaderboard_vales.id = ? LIMIT 1) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales, personal_values " -"WHERE leaderboard_rank BETWEEN min_rank AND max_rank AND (player_id = related_player_id OR friend_id = related_player_id);"; - -const std::string LeaderboardManager::standingsScoreQuery = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " -" RANK() OVER ( ORDER BY l.score DESC, l.time DESC, last_played ) leaderboard_rank " -" FROM leaderboard l " -" INNER JOIN charinfo c ON l.character_id = c.id " -" WHERE l.game_id = ? " -" ORDER BY leaderboard_rank), " -"personal_values AS ( " -" SELECT GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, " -" GREATEST(leaderboard_rank + 5, 11) AS max_rank " -" FROM leaderboard_vales WHERE id = ? LIMIT 1) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales, personal_values " -"WHERE leaderboard_rank BETWEEN min_rank AND max_rank;"; - -const std::string LeaderboardManager::topPlayersScoreQueryAsc = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " -"RANK() OVER ( ORDER BY l.score DESC, l.time ASC, last_played ) leaderboard_rank " -" FROM leaderboard l " -"INNER JOIN charinfo c ON l.character_id = c.id " -"WHERE l.game_id = ? " -"ORDER BY leaderboard_rank) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales LIMIT 11;"; - -const std::string LeaderboardManager::friendsScoreQueryAsc = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, f.friend_id, f.player_id, " -" RANK() OVER ( ORDER BY l.score DESC, l.time ASC, last_played ) leaderboard_rank " -" FROM leaderboard l " -" INNER JOIN charinfo c ON l.character_id = c.id " -" INNER JOIN friends f ON f.player_id = c.id " -" WHERE l.game_id = ? " -" ORDER BY leaderboard_rank), " -" personal_values AS ( " -" SELECT id as related_player_id, " -" GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, " -" GREATEST(leaderboard_rank + 5, 11) AS max_rank " -" FROM leaderboard_vales WHERE leaderboard_vales.id = ? LIMIT 1) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales, personal_values " -"WHERE leaderboard_rank BETWEEN min_rank AND max_rank AND (player_id = related_player_id OR friend_id = related_player_id);"; - -const std::string LeaderboardManager::standingsScoreQueryAsc = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " -" RANK() OVER ( ORDER BY l.score DESC, l.time ASC, last_played ) leaderboard_rank " -" FROM leaderboard l " -" INNER JOIN charinfo c ON l.character_id = c.id " -" WHERE l.game_id = ? " -" ORDER BY leaderboard_rank), " -"personal_values AS ( " -" SELECT GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, " -" GREATEST(leaderboard_rank + 5, 11) AS max_rank " -" FROM leaderboard_vales WHERE id = ? LIMIT 1) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales, personal_values " -"WHERE leaderboard_rank BETWEEN min_rank AND max_rank;"; - -const std::string LeaderboardManager::topPlayersTimeQuery = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " -"RANK() OVER ( ORDER BY l.time DESC, l.score DESC, last_played ) leaderboard_rank " -" FROM leaderboard l " -"INNER JOIN charinfo c ON l.character_id = c.id " -"WHERE l.game_id = ? " -"ORDER BY leaderboard_rank) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales LIMIT 11;"; - -const std::string LeaderboardManager::friendsTimeQuery = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, f.friend_id, f.player_id, " -" RANK() OVER ( ORDER BY l.time DESC, l.score DESC, last_played ) leaderboard_rank " -" FROM leaderboard l " -" INNER JOIN charinfo c ON l.character_id = c.id " -" INNER JOIN friends f ON f.player_id = c.id " -" WHERE l.game_id = ? " -" ORDER BY leaderboard_rank), " -" personal_values AS ( " -" SELECT id as related_player_id, " -" GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, " -" GREATEST(leaderboard_rank + 5, 11) AS max_rank " -" FROM leaderboard_vales WHERE leaderboard_vales.id = ? LIMIT 1) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales, personal_values " -"WHERE leaderboard_rank BETWEEN min_rank AND max_rank AND (player_id = related_player_id OR friend_id = related_player_id);"; - -const std::string LeaderboardManager::standingsTimeQuery = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " -" RANK() OVER ( ORDER BY l.time DESC, l.score DESC, last_played ) leaderboard_rank " -" FROM leaderboard l " -" INNER JOIN charinfo c ON l.character_id = c.id " -" WHERE l.game_id = ? " -" ORDER BY leaderboard_rank), " -"personal_values AS ( " -" SELECT GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, " -" GREATEST(leaderboard_rank + 5, 11) AS max_rank " -" FROM leaderboard_vales WHERE id = ? LIMIT 1) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales, personal_values " -"WHERE leaderboard_rank BETWEEN min_rank AND max_rank;"; - -const std::string LeaderboardManager::topPlayersTimeQueryAsc = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " -"RANK() OVER ( ORDER BY l.time ASC, l.score DESC, last_played ) leaderboard_rank " -" FROM leaderboard l " -"INNER JOIN charinfo c ON l.character_id = c.id " -"WHERE l.game_id = ? " -"ORDER BY leaderboard_rank) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales LIMIT 11;"; - -const std::string LeaderboardManager::friendsTimeQueryAsc = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, f.friend_id, f.player_id, " -" RANK() OVER ( ORDER BY l.time ASC, l.score DESC, last_played ) leaderboard_rank " -" FROM leaderboard l " -" INNER JOIN charinfo c ON l.character_id = c.id " -" INNER JOIN friends f ON f.player_id = c.id " -" WHERE l.game_id = ? " -" ORDER BY leaderboard_rank), " -" personal_values AS ( " -" SELECT id as related_player_id, " -" GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, " -" GREATEST(leaderboard_rank + 5, 11) AS max_rank " -" FROM leaderboard_vales WHERE leaderboard_vales.id = ? LIMIT 1) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales, personal_values " -"WHERE leaderboard_rank BETWEEN min_rank AND max_rank AND (player_id = related_player_id OR friend_id = related_player_id);"; - -const std::string LeaderboardManager::standingsTimeQueryAsc = -"WITH leaderboard_vales AS ( " -" SELECT l.time, l.score, UNIX_TIMESTAMP(l.last_played) last_played, c.name, c.id, " -" RANK() OVER ( ORDER BY l.time ASC, l.score DESC, last_played ) leaderboard_rank " -" FROM leaderboard l " -" INNER JOIN charinfo c ON l.character_id = c.id " -" WHERE l.game_id = ? " -" ORDER BY leaderboard_rank), " -"personal_values AS ( " -" SELECT GREATEST(CAST(leaderboard_rank AS SIGNED) - 5, 1) AS min_rank, " -" GREATEST(leaderboard_rank + 5, 11) AS max_rank " -" FROM leaderboard_vales WHERE id = ? LIMIT 1) " -"SELECT time, score, leaderboard_rank, id, name, last_played " -"FROM leaderboard_vales, personal_values " -"WHERE leaderboard_rank BETWEEN min_rank AND max_rank;"; diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index cabdf2d6..5f3fa0b2 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -1,80 +1,90 @@ #pragma once #include #include + +#include "Singleton.h" #include "dCommonVars.h" -struct LeaderboardEntry { - uint64_t playerID; - std::string playerName; - uint32_t time; - uint32_t score; - uint32_t placement; - time_t lastPlayed; +namespace RakNet{ + class BitStream; }; -enum InfoType : uint32_t { - Top, // Top 11 all time players - Standings, // Ranking of the current player - Friends // Ranking between friends -}; - -enum LeaderboardType : uint32_t { - ShootingGallery, - Racing, - MonumentRace, - FootRace, - Survival = 5, - SurvivalNS = 6, - None = UINT_MAX -}; +typedef uint32_t GameID; class Leaderboard { public: - Leaderboard(uint32_t gameID, uint32_t infoType, bool weekly, std::vector entries, - LWOOBJID relatedPlayer = LWOOBJID_EMPTY, LeaderboardType = None); - std::vector GetEntries(); - [[nodiscard]] std::u16string ToString() const; - [[nodiscard]] uint32_t GetGameID() const; - [[nodiscard]] uint32_t GetInfoType() const; + struct Entry { + uint64_t playerID; + uint32_t time; + uint32_t score; + uint32_t placement; + time_t lastPlayed; + std::string playerName; + }; + typedef std::vector LeaderboardEntries; + + // Enums for leaderboards + enum InfoType : uint32_t { + Top, // Top 11 all time players + MyStanding, // Ranking of the current player + Friends // Ranking between friends + }; + + enum Type : uint32_t { + ShootingGallery, + Racing, + MonumentRace, + FootRace, + Survival = 5, + SurvivalNS = 6, + None = UINT_MAX + }; + + Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const Leaderboard::Type = None); + + /** + * 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) 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; + + /** + * Builds the leaderboard from the database based on the associated gameID + */ + void SetupLeaderboard(); + + /** + * Sends the leaderboard to the client specified by targetID. + */ void Send(LWOOBJID targetID) const; private: - std::vector entries{}; + LeaderboardEntries entries; LWOOBJID relatedPlayer; - uint32_t gameID; - uint32_t infoType; - LeaderboardType leaderboardType; + GameID gameID; + InfoType infoType; + Leaderboard::Type leaderboardType; bool weekly; }; -class LeaderboardManager { +class LeaderboardManager: public Singleton { + typedef std::map LeaderboardCache; public: - static LeaderboardManager* Instance() { - if (address == nullptr) - address = new LeaderboardManager; - return address; - } - static void SendLeaderboard(uint32_t gameID, InfoType infoType, bool weekly, LWOOBJID targetID, + void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, LWOOBJID playerID = LWOOBJID_EMPTY); - static Leaderboard* GetLeaderboard(uint32_t gameID, InfoType infoType, bool weekly, LWOOBJID playerID = LWOOBJID_EMPTY); - static void SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t score, uint32_t time); - static LeaderboardType GetLeaderboardType(uint32_t gameID); + void SaveScore(LWOOBJID playerID, GameID gameID, uint32_t score, uint32_t time); private: - static LeaderboardManager* address; - - // Modified 12/12/2021: Existing queries were renamed to be more descriptive. - static const std::string topPlayersScoreQuery; - static const std::string friendsScoreQuery; - static const std::string standingsScoreQuery; - static const std::string topPlayersScoreQueryAsc; - static const std::string friendsScoreQueryAsc; - static const std::string standingsScoreQueryAsc; - - // Added 12/12/2021: Queries dictated by time are needed for certain minigames. - static const std::string topPlayersTimeQuery; - static const std::string friendsTimeQuery; - static const std::string standingsTimeQuery; - static const std::string topPlayersTimeQueryAsc; - static const std::string friendsTimeQueryAsc; - static const std::string standingsTimeQueryAsc; + Leaderboard::Type GetLeaderboardType(const GameID gameID); + void GetLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID = LWOOBJID_EMPTY); + LeaderboardCache leaderboardCache; }; diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index cc2bdd90..f10b33bf 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -150,6 +150,12 @@ int main(int argc, char** argv) { const bool oldCDServerExists = std::filesystem::exists(Game::assetManager->GetResPath() / "CDServer.sqlite"); const bool fdbExists = std::filesystem::exists(Game::assetManager->GetResPath() / "cdclient.fdb"); + auto query = Database::CreatePreppedStmt("select name, score, time, UNIX_TIMESTAMP(last_played) as lastPlayed from leaderboard as l join charinfo as ci on ci.id = l.character_id where game_id = 1864 order by score desc, time desc limit 11;"); + auto myResult = query->executeQuery(); + while (myResult->next()) { + Game::logger->Log("MasterServer", "%s %i %i %i", myResult->getString("name").c_str(), myResult->getInt("score"), myResult->getInt("time"), myResult->getInt("lastPlayed")); + } + if (!cdServerExists) { if (oldCDServerExists) { // If the file doesn't exist in the new CDServer location, copy it there. We copy because we may not have write permissions from the previous directory. From c91f0d16b3f78700be26a501c61e671c170aba4e Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Thu, 13 Apr 2023 00:45:03 -0700 Subject: [PATCH 02/59] Get it compiling, add a test and optimize heavily --- dCommon/Metrics.cpp | 1 + dCommon/Metrics.hpp | 1 + dGame/LeaderboardManager.cpp | 35 ++++----- dGame/LeaderboardManager.h | 13 +++- dGame/dGameMessages/GameMessages.cpp | 24 +++--- .../02_server/Map/AG/NpcAgCourseStarter.cpp | 4 +- dScripts/ActivityManager.cpp | 12 +-- tests/dGameTests/CMakeLists.txt | 1 + tests/dGameTests/LeaderboardTests.cpp | 76 +++++++++++++++++++ 9 files changed, 125 insertions(+), 42 deletions(-) create mode 100644 tests/dGameTests/LeaderboardTests.cpp diff --git a/dCommon/Metrics.cpp b/dCommon/Metrics.cpp index b97b5435..522bcf61 100644 --- a/dCommon/Metrics.cpp +++ b/dCommon/Metrics.cpp @@ -14,6 +14,7 @@ std::vector Metrics::m_Variables = { MetricVariable::CPUTime, MetricVariable::Sleep, MetricVariable::Frame, + MetricVariable::Leaderboard, }; void Metrics::AddMeasurement(MetricVariable variable, int64_t value) { diff --git a/dCommon/Metrics.hpp b/dCommon/Metrics.hpp index c03c914f..7009701b 100644 --- a/dCommon/Metrics.hpp +++ b/dCommon/Metrics.hpp @@ -20,6 +20,7 @@ enum class MetricVariable : int32_t CPUTime, Sleep, Frame, + Leaderboard, }; struct Metric diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 72101e64..9388b183 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -10,9 +10,10 @@ #include "CDClientManager.h" #include "GeneralUtils.h" #include "Entity.h" +#include #include "CDActivitiesTable.h" - +#include "Metrics.hpp" Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const Leaderboard::Type leaderboardType) { this->relatedPlayer = relatedPlayer; this->gameID = gameID; @@ -22,39 +23,35 @@ Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoTy this->leaderboardType = leaderboardType; } -bool Leaderboard::IsScoreBetter(const uint32_t score) const { - -} - void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { - std::string leaderboard; + std::ostringstream leaderboard; - leaderboard += "ADO.Result=7:1\n"; - leaderboard += "Result.Count=1:1\n"; - leaderboard += "Result[0].Index=0:RowNumber\n"; - leaderboard += "Result[0].RowCount=1:" + std::to_string(entries.size()) + "\n"; + leaderboard << "ADO.Result=7:1\n"; // Unused in 1.10.64, but is in captures + leaderboard << "Result.Count=1:1\n"; // number of results, always 1? + leaderboard << "Result[0].Index=0:RowNumber\n"; // "Primary key" + leaderboard << "Result[0].RowCount=1:" << entries.size() << '\n'; // number of rows auto index = 0; for (const auto& entry : entries) { - leaderboard += "Result[0].Row[" + std::to_string(index) + "].LastPlayed=8:" + std::to_string(entry.lastPlayed) + "\n"; - leaderboard += "Result[0].Row[" + std::to_string(index) + "].CharacterID=8:" + std::to_string(entry.playerID) + "\n"; - leaderboard += "Result[0].Row[" + std::to_string(index) + "].NumPlayed=1:1\n"; - leaderboard += "Result[0].Row[" + std::to_string(index) + "].RowNumber=8:" + std::to_string(entry.placement) + "\n"; - leaderboard += "Result[0].Row[" + std::to_string(index) + "].Time=1:" + std::to_string(entry.time) + "\n"; + leaderboard << "Result[0].Row[" << index << "].LastPlayed=8:" << entry.lastPlayed << '\n'; + leaderboard << "Result[0].Row[" << index << "].CharacterID=8:" << entry.playerID << '\n'; + leaderboard << "Result[0].Row[" << index << "].NumPlayed=1:1\n"; // number of times the activity was played + leaderboard << "Result[0].Row[" << index << "].RowNumber=8:" << entry.placement << '\n'; + leaderboard << "Result[0].Row[" << index << "].Time=1:" << entry.time << '\n'; // Only these minigames have a points system if (leaderboardType == Survival || leaderboardType == ShootingGallery) { - leaderboard += "Result[0].Row[" + std::to_string(index) + "].Points=1:" + std::to_string(entry.score) + "\n"; + leaderboard << "Result[0].Row[" << index << "].Points=1:"<< entry.score << '\n'; } else if (leaderboardType == SurvivalNS) { - leaderboard += "Result[0].Row[" + std::to_string(index) + "].Wave=1:" + std::to_string(entry.score) + "\n"; + leaderboard << "Result[0].Row[" << index << "].Wave=1:"<< entry.score << '\n'; } - leaderboard += "Result[0].Row[" + std::to_string(index) + "].name=0:" + entry.playerName + "\n"; + leaderboard << "Result[0].Row[" << index << "].name=0:" << entry.playerName << '\n'; index++; } // Serialize the thing to a BitStream - bitStream->WriteAlignedBytes((const unsigned char*)leaderboard.c_str(), leaderboard.size()); + bitStream->Write(leaderboard.str().c_str(), leaderboard.tellp()); } void Leaderboard::SetupLeaderboard() { diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 5f3fa0b2..bbdf76e5 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -1,6 +1,7 @@ #pragma once -#include #include +#include +#include #include "Singleton.h" #include "dCommonVars.h" @@ -14,7 +15,7 @@ typedef uint32_t GameID; class Leaderboard { public: struct Entry { - uint64_t playerID; + LWOOBJID playerID; uint32_t time; uint32_t score; uint32_t placement; @@ -56,7 +57,7 @@ public: * @return true * @return false */ - bool IsScoreBetter(const uint32_t score) const; + bool IsScoreBetter(const uint32_t score) const { return false; }; /** * Builds the leaderboard from the database based on the associated gameID @@ -67,6 +68,12 @@ public: * Sends the leaderboard to the client specified by targetID. */ void Send(LWOOBJID targetID) const; + + /** + * Adds a new entry to the leaderboard + * Used for debug only! + */ + void AddEntry(Entry entry) { entries.push_back(entry); } private: LeaderboardEntries entries; LWOOBJID relatedPlayer; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 0fc32e2e..df58e8f1 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1630,17 +1630,17 @@ void GameMessages::SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, bitStream.Write(objectID); bitStream.Write(GAME_MSG::GAME_MSG_SEND_ACTIVITY_SUMMARY_LEADERBOARD_DATA); - - bitStream.Write(leaderboard->GetGameID()); - bitStream.Write(leaderboard->GetInfoType()); + throw ""; + //bitStream.Write(leaderboard->GetGameID()); + //bitStream.Write(leaderboard->GetInfoType()); // Leaderboard is written back as LDF string - const auto leaderboardString = leaderboard->ToString(); - bitStream.Write(leaderboardString.size()); - for (const auto c : leaderboardString) { - bitStream.Write(c); - } - if (!leaderboardString.empty()) bitStream.Write(uint16_t(0)); + //const auto leaderboardString = leaderboard->ToString(); + //bitStream.Write(leaderboardString.size()); + //for (const auto c : leaderboardString) { + // bitStream.Write(c); + //} + //if (!leaderboardString.empty()) bitStream.Write(uint16_t(0)); bitStream.Write0(); bitStream.Write0(); @@ -1666,9 +1666,9 @@ void GameMessages::HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream bool weekly = inStream->ReadBit(); - const auto* leaderboard = LeaderboardManager::GetLeaderboard(gameID, (InfoType)queryType, weekly, entity->GetObjectID()); - SendActivitySummaryLeaderboardData(entity->GetObjectID(), leaderboard, sysAddr); - delete leaderboard; + //const auto* leaderboard = LeaderboardManager::GetLeaderboard(gameID, (InfoType)queryType, weekly, entity->GetObjectID()); + //SendActivitySummaryLeaderboardData(entity->GetObjectID(), leaderboard, sysAddr); + //delete leaderboard; } void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream* inStream, Entity* entity) { diff --git a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp index d2cc647e..7da4ccef 100644 --- a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp +++ b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp @@ -96,8 +96,8 @@ void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std } EntityManager::Instance()->SerializeEntity(self); - LeaderboardManager::SaveScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), - 0, (uint32_t)finish); + //LeaderboardManager::SaveScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), + // 0, (uint32_t)finish); GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard", scriptedActivityComponent->GetActivityID(), 0, sender->GetObjectID(), diff --git a/dScripts/ActivityManager.cpp b/dScripts/ActivityManager.cpp index 078a7a02..acfa5a68 100644 --- a/dScripts/ActivityManager.cpp +++ b/dScripts/ActivityManager.cpp @@ -74,11 +74,11 @@ void ActivityManager::StopActivity(Entity* self, const LWOOBJID playerID, const LootGenerator::Instance().GiveActivityLoot(player, self, gameID, CalculateActivityRating(self, playerID)); // Save the new score to the leaderboard and show the leaderboard to the player - LeaderboardManager::SaveScore(playerID, gameID, score, value1); - const auto* leaderboard = LeaderboardManager::GetLeaderboard(gameID, InfoType::Standings, - false, player->GetObjectID()); - GameMessages::SendActivitySummaryLeaderboardData(self->GetObjectID(), leaderboard, player->GetSystemAddress()); - delete leaderboard; + //LeaderboardManager::SaveScore(playerID, gameID, score, value1); + //const auto* leaderboard = LeaderboardManager::GetLeaderboard(gameID, InfoType::Standings, + // false, player->GetObjectID()); + //GameMessages::SendActivitySummaryLeaderboardData(self->GetObjectID(), leaderboard, player->GetSystemAddress()); + //delete leaderboard; // Makes the leaderboard show up for the player GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard", @@ -117,7 +117,7 @@ uint32_t ActivityManager::GetActivityID(const Entity* self) { } void ActivityManager::GetLeaderboardData(Entity* self, const LWOOBJID playerID, const uint32_t activityID, uint32_t numResults) { - LeaderboardManager::SendLeaderboard(activityID, Standings, false, self->GetObjectID(), playerID); + //LeaderboardManager::SendLeaderboard(activityID, Standings, false, self->GetObjectID(), playerID); } void ActivityManager::ActivityTimerStart(Entity* self, const std::string& timerName, const float_t updateInterval, diff --git a/tests/dGameTests/CMakeLists.txt b/tests/dGameTests/CMakeLists.txt index b1fdaa07..30b5e20b 100644 --- a/tests/dGameTests/CMakeLists.txt +++ b/tests/dGameTests/CMakeLists.txt @@ -1,5 +1,6 @@ set(DGAMETEST_SOURCES "GameDependencies.cpp" + "LeaderboardTests.cpp" ) add_subdirectory(dComponentsTests) diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp new file mode 100644 index 00000000..6a45298b --- /dev/null +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -0,0 +1,76 @@ +#include "LeaderboardManager.h" + +#include "BitStream.h" +#include "GameDependencies.h" +#include "Metrics.hpp" +#include + +class LeaderboardTests : public GameDependenciesTest { +protected: + void SetUp() override { + SetUpDependencies(); + } + void TearDown() override { + TearDownDependencies(); + } + + void TestLeaderboard(Leaderboard& leaderboard, int32_t entries) { + Leaderboard::Entry entry; + entry.playerID = UINT64_MAX; + entry.time = 100; + entry.score = 100; + entry.placement = 1; + entry.lastPlayed = 0; + entry.playerName = "TestThreeWords"; + for (int32_t i = 0; i < entries; i++) leaderboard.AddEntry(entry); + Metrics::StartMeasurement(MetricVariable::Leaderboard); + for (int32_t i = 0; i < MAX_MEASURMENT_POINTS; i++) leaderboard.Serialize(&bitStream); + Metrics::EndMeasurement(MetricVariable::Leaderboard); + + auto timePassed = Metrics::GetMetric(MetricVariable::Leaderboard)->average; + Game::logger->Log("LeaderboardManager", "average time passed for %i leaderboard entries is %lluns", entries, timePassed); + bitStream.Reset(); + } + + CBITSTREAM; +}; + +/** + * Initial metrics + * 19: [12-04-23 23:56:31] [LeaderboardManager] average time passed for 1 leaderboard entries is 1671700ns + * 19: [12-04-23 23:56:31] [LeaderboardManager] average time passed for 10 leaderboard entries is 8388900ns + * 19: [12-04-23 23:56:31] [LeaderboardManager] average time passed for 100 leaderboard entries is 54680133ns + * 19: [12-04-23 23:56:33] [LeaderboardManager] average time passed for 1000 leaderboard entries is 506289325ns + + * Only do each std::to_string once + * 19: [12-04-23 23:57:31] [LeaderboardManager] average time passed for 1 leaderboard entries is 1472700ns + * 19: [12-04-23 23:57:31] [LeaderboardManager] average time passed for 10 leaderboard entries is 7035650ns + * 19: [12-04-23 23:57:31] [LeaderboardManager] average time passed for 100 leaderboard entries is 45147466ns + * 19: [12-04-23 23:57:33] [LeaderboardManager] average time passed for 1000 leaderboard entries is 435724550ns + * + * Only do Result[0].Row[index] once + * 19: [12-04-23 23:59:43] [LeaderboardManager] average time passed for 1 leaderboard entries is 1357700ns + * 19: [12-04-23 23:59:43] [LeaderboardManager] average time passed for 10 leaderboard entries is 6635350ns + * 19: [12-04-23 23:59:43] [LeaderboardManager] average time passed for 100 leaderboard entries is 40247800ns + * 19: [12-04-23 23:59:45] [LeaderboardManager] average time passed for 1000 leaderboard entries is 400965900ns + * + * Switch to ostringstream + * 19: [13-04-23 00:24:44] [LeaderboardManager] average time passed for 1 leaderboard entries is 1334300ns + * 19: [13-04-23 00:24:44] [LeaderboardManager] average time passed for 10 leaderboard entries is 5566250ns + * 19: [13-04-23 00:24:44] [LeaderboardManager] average time passed for 100 leaderboard entries is 34640066ns + * 19: [13-04-23 00:24:46] [LeaderboardManager] average time passed for 1000 leaderboard entries is 357226950ns + * + * No more std::to_string and revert "Only do Result[0].Row[index] once" + * 19: [13-04-23 00:39:18] [LeaderboardManager] average time passed for 1 leaderboard entries is 979200ns + * 19: [13-04-23 00:39:18] [LeaderboardManager] average time passed for 10 leaderboard entries is 4053350ns + * 19: [13-04-23 00:39:18] [LeaderboardManager] average time passed for 100 leaderboard entries is 24785233ns + * 19: [13-04-23 00:39:19] [LeaderboardManager] average time passed for 1000 leaderboard entries is 279457375ns + */ + +TEST_F(LeaderboardTests, LeaderboardSpeedTest) { + Leaderboard leaderboard(10, Leaderboard::InfoType::MyStanding, false, Leaderboard::Type::Survival); + TestLeaderboard(leaderboard, 1); + TestLeaderboard(leaderboard, 10); + TestLeaderboard(leaderboard, 100); + TestLeaderboard(leaderboard, 1000); +} From 41355cea58d875b5f5cc18a4d134d1b387b6db57 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Thu, 13 Apr 2023 21:55:09 -0700 Subject: [PATCH 03/59] Add remaining enum types --- dGame/LeaderboardManager.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index bbdf76e5..cb3b5455 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -36,8 +36,10 @@ public: Racing, MonumentRace, FootRace, + // There is no 4 Survival = 5, - SurvivalNS = 6, + SurvivalNS, + Donations, None = UINT_MAX }; From ed2639ce4e185b4b95ecc2a129a93f9f7f6f48bd Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 14 Apr 2023 01:32:52 -0700 Subject: [PATCH 04/59] Use inline functions --- dGame/LeaderboardManager.cpp | 73 +++++++++++++++++++++++++++++------- dGame/LeaderboardManager.h | 5 ++- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 9388b183..d3037fae 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -10,19 +10,23 @@ #include "CDClientManager.h" #include "GeneralUtils.h" #include "Entity.h" +#include "LDFFormat.h" #include #include "CDActivitiesTable.h" #include "Metrics.hpp" Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const Leaderboard::Type leaderboardType) { - this->relatedPlayer = relatedPlayer; this->gameID = gameID; this->weekly = weekly; this->infoType = infoType; - this->entries = entries; this->leaderboardType = leaderboardType; } +template +void Leaderboard::WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, const std::string& key, const eLDFType& ldfType, const TypeToWrite& value) const { + leaderboard << "Result[0].Row[" << index << "]." << key << '=' << ldfType << ':' << value << '\n'; +} + void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { std::ostringstream leaderboard; @@ -33,20 +37,61 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { auto index = 0; for (const auto& entry : entries) { - leaderboard << "Result[0].Row[" << index << "].LastPlayed=8:" << entry.lastPlayed << '\n'; - leaderboard << "Result[0].Row[" << index << "].CharacterID=8:" << entry.playerID << '\n'; - leaderboard << "Result[0].Row[" << index << "].NumPlayed=1:1\n"; // number of times the activity was played - leaderboard << "Result[0].Row[" << index << "].RowNumber=8:" << entry.placement << '\n'; - leaderboard << "Result[0].Row[" << index << "].Time=1:" << entry.time << '\n'; + WriteLeaderboardRow(leaderboard, index, "CharacterID", eLDFType::LDF_TYPE_U64, entry.playerID); + WriteLeaderboardRow(leaderboard, index, "LastPlayed", eLDFType::LDF_TYPE_U64, entry.lastPlayed); + WriteLeaderboardRow(leaderboard, index, "NumPlayed", eLDFType::LDF_TYPE_S32, 1); + WriteLeaderboardRow(leaderboard, index, "name", eLDFType::LDF_TYPE_UTF_16, entry.playerName); - // Only these minigames have a points system - if (leaderboardType == Survival || leaderboardType == ShootingGallery) { - leaderboard << "Result[0].Row[" << index << "].Points=1:"<< entry.score << '\n'; - } else if (leaderboardType == SurvivalNS) { - leaderboard << "Result[0].Row[" << index << "].Wave=1:"<< entry.score << '\n'; + // Each minigame has its own "points" system + switch (leaderboardType) { + case Type::ShootingGallery: + WriteLeaderboardRow(leaderboard, index, "HitPercentage", eLDFType::LDF_TYPE_FLOAT, 0.0f); + // HitPercentage:3 between 0 and 1 + WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_S32, entry.placement); + // RowNumber:1 + WriteLeaderboardRow(leaderboard, index, "Score", eLDFType::LDF_TYPE_S32, entry.score); + // Score:1 + case Type::Racing: + WriteLeaderboardRow(leaderboard, index, "BestLapTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); + // BestLapTime:3 + WriteLeaderboardRow(leaderboard, index, "BestTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); + // BestTime:3 + WriteLeaderboardRow(leaderboard, index, "License", eLDFType::LDF_TYPE_S32, 0); + // License:1 + WriteLeaderboardRow(leaderboard, index, "NumWins", eLDFType::LDF_TYPE_S32, 0); + // NumWins:1 + WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_U64, entry.placement); + // RowNumber:8 + case Type::MonumentRace: + WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_S32, entry.placement); + // RowNumber:1 + // Time:1(?) + case Type::FootRace: + WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_S32, entry.placement); + // RowNumber:1 + WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, 0); + // Time:1 + case Type::Survival: + WriteLeaderboardRow(leaderboard, index, "Points", eLDFType::LDF_TYPE_S32, entry.score); + // Points:1 + WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_S32, entry.placement); + // RowNumber:1 + WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, 0); + // Time:1 + case Type::SurvivalNS: + WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_U64, entry.placement); + // RowNumber:8 + WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + // Time:1 + WriteLeaderboardRow(leaderboard, index, "Wave", eLDFType::LDF_TYPE_S32, entry.score); + // Wave:1 + case Type::Donations: + // Something? idk yet. + case Type::None: + break; + default: + break; } - - leaderboard << "Result[0].Row[" << index << "].name=0:" << entry.playerName << '\n'; index++; } diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index cb3b5455..6432aa8d 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -5,6 +5,7 @@ #include "Singleton.h" #include "dCommonVars.h" +#include "LDFFormat.h" namespace RakNet{ class BitStream; @@ -36,7 +37,7 @@ public: Racing, MonumentRace, FootRace, - // There is no 4 + // There is no 4 defined anywhere in the client or cdclient Survival = 5, SurvivalNS, Donations, @@ -77,6 +78,8 @@ public: */ void AddEntry(Entry entry) { entries.push_back(entry); } private: +template + inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, const std::string& key, const eLDFType& ldfType, const TypeToWrite& value) const; LeaderboardEntries entries; LWOOBJID relatedPlayer; GameID gameID; From fcb088d263c4081ebc5a4436cc83cf9e8068a383 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Mon, 17 Apr 2023 01:16:23 -0700 Subject: [PATCH 05/59] Further work on leaderboards - Modularize tests - Add migrations - Fix switch case so it actually breaks - Add in missing writes - Beginning work on custom migration to move the leaderboard to the final state --- dDatabase/MigrationRunner.cpp | 30 ++++++- dDatabase/MigrationRunner.h | 1 + dGame/LeaderboardManager.cpp | 78 ++++++++++--------- dGame/LeaderboardManager.h | 2 +- dMasterServer/MasterServer.cpp | 9 +-- .../dlu/10_Update_Leaderboard_Columns.sql | 1 + .../dlu/9_Update_Leaderboard_Storage.sql | 8 ++ tests/dGameTests/LeaderboardTests.cpp | 15 ++-- 8 files changed, 93 insertions(+), 51 deletions(-) create mode 100644 migrations/dlu/10_Update_Leaderboard_Columns.sql create mode 100644 migrations/dlu/9_Update_Leaderboard_Storage.sql diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index 5e70c401..d9c5aa38 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -7,6 +7,8 @@ #include "GeneralUtils.h" #include "dLogger.h" #include "BinaryPathFinder.h" +#include "CDActivitiesTable.h" +#include "CDClientManager.h" #include @@ -56,6 +58,8 @@ void MigrationRunner::RunMigrations() { Game::logger->Log("MigrationRunner", "Running migration: %s", migration.name.c_str()); if (migration.name == "dlu/5_brick_model_sd0.sql") { runSd0Migrations = true; + } else if (migration.name == "dlu/10_Update_Leaderboard_Columns.sql") { + continue; } else { finalSQL.append(migration.data.c_str()); } @@ -109,7 +113,7 @@ void MigrationRunner::RunSQLiteMigrations() { // Check if there is an entry in the migration history table on the cdclient database. cdstmt = CDClientDatabase::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); - cdstmt.bind((int32_t) 1, migration.name.c_str()); + cdstmt.bind((int32_t)1, migration.name.c_str()); auto cdres = cdstmt.execQuery(); bool doExit = !cdres.eof(); cdres.finalize(); @@ -127,7 +131,7 @@ void MigrationRunner::RunSQLiteMigrations() { if (doExit) { // Insert into cdclient database if there is an entry in the main database but not the cdclient database. cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); - cdstmt.bind((int32_t) 1, migration.name.c_str()); + cdstmt.bind((int32_t)1, migration.name.c_str()); cdstmt.execQuery().finalize(); cdstmt.finalize(); continue; @@ -148,7 +152,7 @@ void MigrationRunner::RunSQLiteMigrations() { // Insert into cdclient database. cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); - cdstmt.bind((int32_t) 1, migration.name.c_str()); + cdstmt.bind((int32_t)1, migration.name.c_str()); cdstmt.execQuery().finalize(); cdstmt.finalize(); CDClientDatabase::ExecuteQuery("COMMIT;"); @@ -156,3 +160,23 @@ void MigrationRunner::RunSQLiteMigrations() { Game::logger->Log("MigrationRunner", "CDServer database is up to date."); } + +void MigrationRunner::MigrateLeaderboard() { + Game::logger->Log("MigrationRunner", "Checking if leaderboard migration needs to be run..."); + { + std::unique_ptr stmt(Database::CreatePreppedStmt("SELECT * FROM migration_history WHERE name = ?;")); + stmt->setString(1, "dlu/10_Update_Leaderboard_Columns.sql"); + std::unique_ptr res(stmt->executeQuery()); + if (res->next()) { + Game::logger->Log("MigrationRunner", "Leaderboard migration has already been run."); + return; + } + } + auto activitiesTable = CDClientManager::Instance().GetTable(); + auto leaderboards = activitiesTable->Query([=](const CDActivities& entry) { + return entry.leaderboardType != -1; + }); + for (auto entry : leaderboards) { + Game::logger->Log("MigrationRunner", "Got leaderboard type %i for activity %i", entry.leaderboardType, entry.ActivityID); + } +} diff --git a/dDatabase/MigrationRunner.h b/dDatabase/MigrationRunner.h index 0cb36d53..00a1d720 100644 --- a/dDatabase/MigrationRunner.h +++ b/dDatabase/MigrationRunner.h @@ -10,4 +10,5 @@ struct Migration { namespace MigrationRunner { void RunMigrations(); void RunSQLiteMigrations(); + void MigrateLeaderboard(); }; diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index d3037fae..cc47c109 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -41,53 +41,59 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { WriteLeaderboardRow(leaderboard, index, "LastPlayed", eLDFType::LDF_TYPE_U64, entry.lastPlayed); WriteLeaderboardRow(leaderboard, index, "NumPlayed", eLDFType::LDF_TYPE_S32, 1); WriteLeaderboardRow(leaderboard, index, "name", eLDFType::LDF_TYPE_UTF_16, entry.playerName); + WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_S32, entry.placement); // Each minigame has its own "points" system switch (leaderboardType) { case Type::ShootingGallery: - WriteLeaderboardRow(leaderboard, index, "HitPercentage", eLDFType::LDF_TYPE_FLOAT, 0.0f); - // HitPercentage:3 between 0 and 1 - WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_S32, entry.placement); - // RowNumber:1 - WriteLeaderboardRow(leaderboard, index, "Score", eLDFType::LDF_TYPE_S32, entry.score); - // Score:1 + WriteLeaderboardRow(leaderboard, index, "HitPercentage", eLDFType::LDF_TYPE_FLOAT, 0.0f); + // HitPercentage:3 between 0 and 1 + WriteLeaderboardRow(leaderboard, index, "Score", eLDFType::LDF_TYPE_S32, entry.score); + // Score:1 + WriteLeaderboardRow(leaderboard, index, "Streak", eLDFType::LDF_TYPE_S32, 0); + // Streak:1 + break; case Type::Racing: - WriteLeaderboardRow(leaderboard, index, "BestLapTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); - // BestLapTime:3 - WriteLeaderboardRow(leaderboard, index, "BestTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); - // BestTime:3 - WriteLeaderboardRow(leaderboard, index, "License", eLDFType::LDF_TYPE_S32, 0); - // License:1 - WriteLeaderboardRow(leaderboard, index, "NumWins", eLDFType::LDF_TYPE_S32, 0); - // NumWins:1 - WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_U64, entry.placement); - // RowNumber:8 + WriteLeaderboardRow(leaderboard, index, "BestLapTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); + // BestLapTime:3 + WriteLeaderboardRow(leaderboard, index, "BestTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); + // BestTime:3 + WriteLeaderboardRow(leaderboard, index, "License", eLDFType::LDF_TYPE_S32, 0); + // License:1 + WriteLeaderboardRow(leaderboard, index, "NumWins", eLDFType::LDF_TYPE_S32, 0); + // NumWins:1 + break; + case Type::UnusedLeaderboard4: + WriteLeaderboardRow(leaderboard, index, "Points", eLDFType::LDF_TYPE_S32, entry.score); + // Points:1 + break; case Type::MonumentRace: - WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_S32, entry.placement); - // RowNumber:1 - // Time:1(?) + WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + // Time:1(?) + break; case Type::FootRace: - WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_S32, entry.placement); - // RowNumber:1 - WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, 0); - // Time:1 + WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + // Time:1 + break; case Type::Survival: - WriteLeaderboardRow(leaderboard, index, "Points", eLDFType::LDF_TYPE_S32, entry.score); - // Points:1 - WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_S32, entry.placement); - // RowNumber:1 - WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, 0); - // Time:1 + WriteLeaderboardRow(leaderboard, index, "Points", eLDFType::LDF_TYPE_S32, entry.score); + // Points:1 + WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + // Time:1 + break; case Type::SurvivalNS: - WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_U64, entry.placement); - // RowNumber:8 - WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); - // Time:1 - WriteLeaderboardRow(leaderboard, index, "Wave", eLDFType::LDF_TYPE_S32, entry.score); - // Wave:1 + WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + // Time:1 + WriteLeaderboardRow(leaderboard, index, "Wave", eLDFType::LDF_TYPE_S32, entry.score); + // Wave:1 + break; case Type::Donations: - // Something? idk yet. + WriteLeaderboardRow(leaderboard, index, "Score", eLDFType::LDF_TYPE_S32, entry.score); + // Score:1 + // Something? idk yet. + break; case Type::None: + // This type is included here simply to resolve a compiler warning on mac about unused enum types break; default: break; diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 6432aa8d..4a42a268 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -37,7 +37,7 @@ public: Racing, MonumentRace, FootRace, - // There is no 4 defined anywhere in the client or cdclient + UnusedLeaderboard4,// There is no 4 defined anywhere in the client or cdclient Survival = 5, SurvivalNS, Donations, diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index f10b33bf..61dbb121 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -150,12 +150,6 @@ int main(int argc, char** argv) { const bool oldCDServerExists = std::filesystem::exists(Game::assetManager->GetResPath() / "CDServer.sqlite"); const bool fdbExists = std::filesystem::exists(Game::assetManager->GetResPath() / "cdclient.fdb"); - auto query = Database::CreatePreppedStmt("select name, score, time, UNIX_TIMESTAMP(last_played) as lastPlayed from leaderboard as l join charinfo as ci on ci.id = l.character_id where game_id = 1864 order by score desc, time desc limit 11;"); - auto myResult = query->executeQuery(); - while (myResult->next()) { - Game::logger->Log("MasterServer", "%s %i %i %i", myResult->getString("name").c_str(), myResult->getInt("score"), myResult->getInt("time"), myResult->getInt("lastPlayed")); - } - if (!cdServerExists) { if (oldCDServerExists) { // If the file doesn't exist in the new CDServer location, copy it there. We copy because we may not have write permissions from the previous directory. @@ -208,6 +202,9 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } + // This migration relies on cdclient data so run it last + MigrationRunner::MigrateLeaderboard(); + //If the first command line argument is -a or --account then make the user //input a username and password, with the password being hidden. if (argc > 1 && diff --git a/migrations/dlu/10_Update_Leaderboard_Columns.sql b/migrations/dlu/10_Update_Leaderboard_Columns.sql new file mode 100644 index 00000000..c9afb81e --- /dev/null +++ b/migrations/dlu/10_Update_Leaderboard_Columns.sql @@ -0,0 +1 @@ +# This migration is a mock. See the real migration in MigrationRunner::MigrateLeaderboard. diff --git a/migrations/dlu/9_Update_Leaderboard_Storage.sql b/migrations/dlu/9_Update_Leaderboard_Storage.sql new file mode 100644 index 00000000..1b3b1f13 --- /dev/null +++ b/migrations/dlu/9_Update_Leaderboard_Storage.sql @@ -0,0 +1,8 @@ +ALTER TABLE leaderboard +ADD COLUMN hitPercentage FLOAT NOT NULL DEFAULT 0, +ADD COLUMN streak INT NOT NULL DEFAULT 0, +ADD COLUMN bestLapTime FLOAT NOT NULL DEFAULT 0, +ADD COLUMN numWins INT NOT NULL DEFAULT 0, +MODIFY time FLOAT NOT NULL DEFAULT 0; + +ALTER TABLE leaderboard CHANGE time bestTime float; diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index 6a45298b..86fb6333 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -32,6 +32,15 @@ protected: bitStream.Reset(); } + void RunTests(Leaderboard::Type type) { + Game::logger->Log("LeaderboardTests", "Testing leaderboard %i for Serialize speed", type); + Leaderboard leaderboard(0, Leaderboard::InfoType::Top, false, type); + TestLeaderboard(leaderboard, 1); + TestLeaderboard(leaderboard, 10); + TestLeaderboard(leaderboard, 100); + TestLeaderboard(leaderboard, 1000); + } + CBITSTREAM; }; @@ -68,9 +77,5 @@ protected: */ TEST_F(LeaderboardTests, LeaderboardSpeedTest) { - Leaderboard leaderboard(10, Leaderboard::InfoType::MyStanding, false, Leaderboard::Type::Survival); - TestLeaderboard(leaderboard, 1); - TestLeaderboard(leaderboard, 10); - TestLeaderboard(leaderboard, 100); - TestLeaderboard(leaderboard, 1000); + RunTests(Leaderboard::Type::Racing); } From 42870028e473a3e46ec30a77ca9c79547fea6e1b Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 17 Apr 2023 15:19:13 -0700 Subject: [PATCH 06/59] Remove complex migration Only shooting gallery had a value put in the wrong column. All others were either already correct or unimplemented. --- dDatabase/MigrationRunner.cpp | 23 +------------------ dDatabase/MigrationRunner.h | 1 - dGame/LeaderboardManager.h | 4 ++-- dMasterServer/MasterServer.cpp | 3 --- .../dlu/10_Update_Leaderboard_Columns.sql | 1 - .../dlu/9_Update_Leaderboard_Storage.sql | 14 ++++++----- 6 files changed, 11 insertions(+), 35 deletions(-) delete mode 100644 migrations/dlu/10_Update_Leaderboard_Columns.sql diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index d9c5aa38..40259a74 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -9,6 +9,7 @@ #include "BinaryPathFinder.h" #include "CDActivitiesTable.h" #include "CDClientManager.h" +#include "LeaderboardManager.h" #include @@ -58,8 +59,6 @@ void MigrationRunner::RunMigrations() { Game::logger->Log("MigrationRunner", "Running migration: %s", migration.name.c_str()); if (migration.name == "dlu/5_brick_model_sd0.sql") { runSd0Migrations = true; - } else if (migration.name == "dlu/10_Update_Leaderboard_Columns.sql") { - continue; } else { finalSQL.append(migration.data.c_str()); } @@ -160,23 +159,3 @@ void MigrationRunner::RunSQLiteMigrations() { Game::logger->Log("MigrationRunner", "CDServer database is up to date."); } - -void MigrationRunner::MigrateLeaderboard() { - Game::logger->Log("MigrationRunner", "Checking if leaderboard migration needs to be run..."); - { - std::unique_ptr stmt(Database::CreatePreppedStmt("SELECT * FROM migration_history WHERE name = ?;")); - stmt->setString(1, "dlu/10_Update_Leaderboard_Columns.sql"); - std::unique_ptr res(stmt->executeQuery()); - if (res->next()) { - Game::logger->Log("MigrationRunner", "Leaderboard migration has already been run."); - return; - } - } - auto activitiesTable = CDClientManager::Instance().GetTable(); - auto leaderboards = activitiesTable->Query([=](const CDActivities& entry) { - return entry.leaderboardType != -1; - }); - for (auto entry : leaderboards) { - Game::logger->Log("MigrationRunner", "Got leaderboard type %i for activity %i", entry.leaderboardType, entry.ActivityID); - } -} diff --git a/dDatabase/MigrationRunner.h b/dDatabase/MigrationRunner.h index 00a1d720..0cb36d53 100644 --- a/dDatabase/MigrationRunner.h +++ b/dDatabase/MigrationRunner.h @@ -10,5 +10,4 @@ struct Migration { namespace MigrationRunner { void RunMigrations(); void RunSQLiteMigrations(); - void MigrateLeaderboard(); }; diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 4a42a268..ad05a1ca 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -37,8 +37,8 @@ public: Racing, MonumentRace, FootRace, - UnusedLeaderboard4,// There is no 4 defined anywhere in the client or cdclient - Survival = 5, + UnusedLeaderboard4, // There is no 4 defined anywhere in the cdclient, but it takes a Score. + Survival, SurvivalNS, Donations, None = UINT_MAX diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 61dbb121..cc2bdd90 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -202,9 +202,6 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - // This migration relies on cdclient data so run it last - MigrationRunner::MigrateLeaderboard(); - //If the first command line argument is -a or --account then make the user //input a username and password, with the password being hidden. if (argc > 1 && diff --git a/migrations/dlu/10_Update_Leaderboard_Columns.sql b/migrations/dlu/10_Update_Leaderboard_Columns.sql deleted file mode 100644 index c9afb81e..00000000 --- a/migrations/dlu/10_Update_Leaderboard_Columns.sql +++ /dev/null @@ -1 +0,0 @@ -# This migration is a mock. See the real migration in MigrationRunner::MigrateLeaderboard. diff --git a/migrations/dlu/9_Update_Leaderboard_Storage.sql b/migrations/dlu/9_Update_Leaderboard_Storage.sql index 1b3b1f13..5e9c5fa2 100644 --- a/migrations/dlu/9_Update_Leaderboard_Storage.sql +++ b/migrations/dlu/9_Update_Leaderboard_Storage.sql @@ -1,8 +1,10 @@ -ALTER TABLE leaderboard -ADD COLUMN hitPercentage FLOAT NOT NULL DEFAULT 0, -ADD COLUMN streak INT NOT NULL DEFAULT 0, -ADD COLUMN bestLapTime FLOAT NOT NULL DEFAULT 0, -ADD COLUMN numWins INT NOT NULL DEFAULT 0, -MODIFY time FLOAT NOT NULL DEFAULT 0; +ALTER TABLE leaderboard + ADD COLUMN hitPercentage FLOAT NOT NULL DEFAULT 0, + ADD COLUMN streak INT NOT NULL DEFAULT 0, + ADD COLUMN bestLapTime FLOAT NOT NULL DEFAULT 0, + ADD COLUMN numWins INT NOT NULL DEFAULT 0, + MODIFY time FLOAT NOT NULL DEFAULT 0; ALTER TABLE leaderboard CHANGE time bestTime float; + +UPDATE leaderboard SET streak = bestTime where game_id = 1864; From 08220d68525082d0dcfdba19246363b6f9e1a72d Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 17 Apr 2023 15:22:44 -0700 Subject: [PATCH 07/59] Remove extra includes --- dDatabase/MigrationRunner.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index 40259a74..3cc4266e 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -7,9 +7,6 @@ #include "GeneralUtils.h" #include "dLogger.h" #include "BinaryPathFinder.h" -#include "CDActivitiesTable.h" -#include "CDClientManager.h" -#include "LeaderboardManager.h" #include From 89f427ace0d29874c8ca6105684cfc55ba2447e5 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 18 Apr 2023 01:26:35 -0700 Subject: [PATCH 08/59] Further work on leaderboards --- dGame/LeaderboardManager.cpp | 156 ++++++++++++-------------- dGame/LeaderboardManager.h | 13 ++- tests/dGameTests/LeaderboardTests.cpp | 17 ++- 3 files changed, 101 insertions(+), 85 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index cc47c109..7473ebad 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -59,7 +59,7 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { WriteLeaderboardRow(leaderboard, index, "BestTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); // BestTime:3 WriteLeaderboardRow(leaderboard, index, "License", eLDFType::LDF_TYPE_S32, 0); - // License:1 + // License:1 - 1 if player has completed mission 637 and 0 otherwise WriteLeaderboardRow(leaderboard, index, "NumWins", eLDFType::LDF_TYPE_S32, 0); // NumWins:1 break; @@ -118,100 +118,92 @@ void Leaderboard::Send(LWOOBJID targetID) const { } } -void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t score, uint32_t time) { - const auto* player = EntityManager::Instance()->GetEntity(playerID); - if (player == nullptr) - return; +void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t argumentCount, ...) { + va_list args; + va_start(args, argumentCount); + SaveScore(playerID, gameID, leaderboardType, args); + va_end(args); +} - auto* character = player->GetCharacter(); - if (!character) - return; +std::string FormatInsert(const char* columns, const char* format, va_list args) { + auto queryBase = "INSERT INTO leaderboard (%s) VALUES (%s)"; + constexpr uint16_t STRING_LENGTH = 400; + char formattedInsert[STRING_LENGTH]; + char finishedQuery[STRING_LENGTH]; + snprintf(formattedInsert, 400, queryBase, columns, format); + vsnprintf(finishedQuery, 400, formattedInsert, args); + return finishedQuery; +} - std::unique_ptr select(Database::CreatePreppedStmt("SELECT time, score FROM leaderboard WHERE character_id = ? AND game_id = ?;")); +void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list args){ + std::string insertStatement; + // use replace into to update the score if it already exists instead of needing an update and an insert + switch (leaderboardType) { + case Leaderboard::Type::ShootingGallery: { + // Check that the score exists and is better. If the score is better update it. + // If the score is the same but the streak is better, update it. + // If the score is the same and the streak is the same but the hit percentage is better, update it. + // If the score doesn't exist, insert it. + auto lookup = Database::CreatePreppedStmt("SELECT score, streak, hitPercentage FROM leaderboard WHERE playerID = ? AND gameID = ?"); + lookup->setInt64(1, playerID); + lookup->setInt(2, gameID); + auto lookupResult = lookup->executeQuery(); + if (lookupResult->next()) { - select->setUInt64(1, character->GetID()); - select->setInt(2, gameID); - - auto any = false; - auto* result = select->executeQuery(); - auto leaderboardType = GetLeaderboardType(gameID); - - // Check if the new score is a high score - while (result->next()) { - any = true; - - const auto storedTime = result->getInt(1); - const auto storedScore = result->getInt(2); - auto highscore = true; - bool classicSurvivalScoring = Game::config->GetValue("classic_survival_scoring") == "1"; - - switch (leaderboardType) { - case Leaderboard::Type::ShootingGallery: - if (score <= storedScore) - highscore = false; - break; - case Leaderboard::Type::Racing: - if (time >= storedTime) - highscore = false; - break; - case Leaderboard::Type::MonumentRace: - if (time >= storedTime) - highscore = false; - break; - case Leaderboard::Type::FootRace: - if (time <= storedTime) - highscore = false; - break; - case Leaderboard::Type::Survival: - if (classicSurvivalScoring) { - if (time <= storedTime) { // Based on time (LU live) - highscore = false; - } } else { - if (score <= storedScore) // Based on score (DLU) - highscore = false; + auto result = FormatInsert("hitPercentage, score, streak", "%f, %i, %i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); } break; - case Leaderboard::Type::SurvivalNS: - if (!(score > storedScore || (time < storedTime && score >= storedScore))) - highscore = false; - break; - default: - highscore = false; } - - if (!highscore) { - delete result; + case Leaderboard::Type::Racing: { + auto result = FormatInsert("bestLapTime, bestTime", "%f, %f", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::UnusedLeaderboard4: { + auto result = FormatInsert("points", "%i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::MonumentRace: { + auto result = FormatInsert("time", "%i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::FootRace: { + auto result = FormatInsert("time", "%i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::Survival: { + auto result = FormatInsert("points, time", "%i, %i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::SurvivalNS: { + auto result = FormatInsert("time, wave", "%i, %i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::Donations: { + auto result = FormatInsert("score", "%i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::None: { + Game::logger->Log("LeaderboardManager", "Warning: Saving leaderboard of type None. Are you sure this is intended?"); + break; + } + default: { + Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); return; } } - delete result; - - if (any) { - auto* statement = Database::CreatePreppedStmt("UPDATE leaderboard SET time = ?, score = ?, last_played=SYSDATE() WHERE character_id = ? AND game_id = ?;"); - statement->setInt(1, time); - statement->setInt(2, score); - statement->setUInt64(3, character->GetID()); - statement->setInt(4, gameID); - statement->execute(); - - delete statement; - } else { - // Note: last_played will be set to SYSDATE() by default when inserting into leaderboard - auto* statement = Database::CreatePreppedStmt("INSERT INTO leaderboard (character_id, game_id, time, score) VALUES (?, ?, ?, ?);"); - statement->setUInt64(1, character->GetID()); - statement->setInt(2, gameID); - statement->setInt(3, time); - statement->setInt(4, score); - statement->execute(); - - delete statement; - } } -void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, - LWOOBJID playerID) { +void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, LWOOBJID playerID) { // Create the leaderboard here and then send it right after. On the stack. Leaderboard leaderboard(gameID, infoType, weekly, GetLeaderboardType(gameID)); leaderboard.SetupLeaderboard(); diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index ad05a1ca..abe679e9 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -93,10 +93,19 @@ class LeaderboardManager: public Singleton { public: void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, LWOOBJID playerID = LWOOBJID_EMPTY); - void SaveScore(LWOOBJID playerID, GameID gameID, uint32_t score, uint32_t time); + /** + * @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, ...); private: - Leaderboard::Type GetLeaderboardType(const GameID gameID); + 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); + Leaderboard::Type GetLeaderboardType(const GameID gameID); LeaderboardCache leaderboardCache; }; diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index 86fb6333..e3fa4264 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -77,5 +77,20 @@ protected: */ TEST_F(LeaderboardTests, LeaderboardSpeedTest) { - RunTests(Leaderboard::Type::Racing); + // RunTests(Leaderboard::Type::ShootingGallery); + LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::ShootingGallery, 3, 3000, 15.0f, 100); + // RunTests(Leaderboard::Type::Racing); + LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Racing, 2, 260.0f, 250.0f); + // RunTests(Leaderboard::Type::MonumentRace); + LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::MonumentRace, 1, 150); + // RunTests(Leaderboard::Type::FootRace); + LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::FootRace, 1, 150); + // RunTests(Leaderboard::Type::UnusedLeaderboard4); + LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::UnusedLeaderboard4, 1, 100); + // RunTests(Leaderboard::Type::Survival); + LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Survival, 2, 3000, 15); + // RunTests(Leaderboard::Type::SurvivalNS); + LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::SurvivalNS, 2, 300, 15); + // RunTests(Leaderboard::Type::Donations); + LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Donations, 1, 300000); } From 10a2c24d5e0249669d7532781e2d7bda4593d5d0 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Wed, 26 Apr 2023 02:10:57 -0700 Subject: [PATCH 09/59] Add base MyStandings query --- dGame/LeaderboardManager.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 7473ebad..e6aef981 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -15,6 +15,37 @@ #include "CDActivitiesTable.h" #include "Metrics.hpp" + +// DON'T YOU DARE MERGE THIS WITH A GLOBAL I WANT TO TEST FAST + +// The below query creates 2 derived tables +// The first is just a straight ranking of the leaderboard based on a provided ranking parameter. +// The second is a query that finds the ranking of the requested score +// The third and final query takes the score gotten above and gets the rankings as follows:. +// If the requested score is in the top 5, it will return the top 11 scores. +// If the requested score is in the bottom 5, it will return the bottom 11 scores. +// In all other cases, the second query will return the 5 scores above and below the requested score. +std::string myStandingsQueryBase = +"WITH leaderboardsRanked AS (" +" SELECT *," +" RANK() OVER" +" (" +" ORDER BY score desc, streak, hitPercentage DESC" +" ) AS ranking" +" FROM leaderboard WHERE game_id = ?" +")," +"myStanding AS (" +" SELECT ranking as myRank" +" FROM leaderboardsRanked" +" WHERE id = ? LIMIT 1" +")" +"SELECT * FROM leaderboardsRanked, myStanding, (SELECT MAX(leaderboardsRanked.ranking) AS lowestRank FROM leaderboardsRanked) AS lowestRanking" +"WHERE leaderboardsRanked.ranking BETWEEN" +"LEAST(GREATEST(myRank - 5, 1), lowestRanking.lowestRank - 10)" +"AND" +"LEAST(GREATEST(myRank + 5, 11), lowestRanking.lowestRank)" +"ORDER BY ranking ASC;"; + Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const Leaderboard::Type leaderboardType) { this->gameID = gameID; this->weekly = weekly; From 48264e2cf46b15b4bf5852f81d14ac63ff1ede49 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 30 Apr 2023 21:30:41 -0700 Subject: [PATCH 10/59] push --- dGame/LeaderboardManager.cpp | 336 +++++++++++++++----------- dGame/LeaderboardManager.h | 20 +- tests/dGameTests/LeaderboardTests.cpp | 41 ++-- 3 files changed, 221 insertions(+), 176 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index e6aef981..8c32d383 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -16,36 +16,6 @@ #include "CDActivitiesTable.h" #include "Metrics.hpp" -// DON'T YOU DARE MERGE THIS WITH A GLOBAL I WANT TO TEST FAST - -// The below query creates 2 derived tables -// The first is just a straight ranking of the leaderboard based on a provided ranking parameter. -// The second is a query that finds the ranking of the requested score -// The third and final query takes the score gotten above and gets the rankings as follows:. -// If the requested score is in the top 5, it will return the top 11 scores. -// If the requested score is in the bottom 5, it will return the bottom 11 scores. -// In all other cases, the second query will return the 5 scores above and below the requested score. -std::string myStandingsQueryBase = -"WITH leaderboardsRanked AS (" -" SELECT *," -" RANK() OVER" -" (" -" ORDER BY score desc, streak, hitPercentage DESC" -" ) AS ranking" -" FROM leaderboard WHERE game_id = ?" -")," -"myStanding AS (" -" SELECT ranking as myRank" -" FROM leaderboardsRanked" -" WHERE id = ? LIMIT 1" -")" -"SELECT * FROM leaderboardsRanked, myStanding, (SELECT MAX(leaderboardsRanked.ranking) AS lowestRank FROM leaderboardsRanked) AS lowestRanking" -"WHERE leaderboardsRanked.ranking BETWEEN" -"LEAST(GREATEST(myRank - 5, 1), lowestRanking.lowestRank - 10)" -"AND" -"LEAST(GREATEST(myRank + 5, 11), lowestRanking.lowestRank)" -"ORDER BY ranking ASC;"; - Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const Leaderboard::Type leaderboardType) { this->gameID = gameID; this->weekly = weekly; @@ -68,66 +38,60 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { auto index = 0; for (const auto& entry : entries) { - WriteLeaderboardRow(leaderboard, index, "CharacterID", eLDFType::LDF_TYPE_U64, entry.playerID); - WriteLeaderboardRow(leaderboard, index, "LastPlayed", eLDFType::LDF_TYPE_U64, entry.lastPlayed); - WriteLeaderboardRow(leaderboard, index, "NumPlayed", eLDFType::LDF_TYPE_S32, 1); - WriteLeaderboardRow(leaderboard, index, "name", eLDFType::LDF_TYPE_UTF_16, entry.playerName); - WriteLeaderboardRow(leaderboard, index, "RowNumber", eLDFType::LDF_TYPE_S32, entry.placement); - // Each minigame has its own "points" system switch (leaderboardType) { - case Type::ShootingGallery: - WriteLeaderboardRow(leaderboard, index, "HitPercentage", eLDFType::LDF_TYPE_FLOAT, 0.0f); - // HitPercentage:3 between 0 and 1 - WriteLeaderboardRow(leaderboard, index, "Score", eLDFType::LDF_TYPE_S32, entry.score); - // Score:1 - WriteLeaderboardRow(leaderboard, index, "Streak", eLDFType::LDF_TYPE_S32, 0); - // Streak:1 - break; - case Type::Racing: - WriteLeaderboardRow(leaderboard, index, "BestLapTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); - // BestLapTime:3 - WriteLeaderboardRow(leaderboard, index, "BestTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); - // BestTime:3 - WriteLeaderboardRow(leaderboard, index, "License", eLDFType::LDF_TYPE_S32, 0); - // License:1 - 1 if player has completed mission 637 and 0 otherwise - WriteLeaderboardRow(leaderboard, index, "NumWins", eLDFType::LDF_TYPE_S32, 0); - // NumWins:1 - break; - case Type::UnusedLeaderboard4: - WriteLeaderboardRow(leaderboard, index, "Points", eLDFType::LDF_TYPE_S32, entry.score); - // Points:1 - break; - case Type::MonumentRace: - WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); - // Time:1(?) - break; - case Type::FootRace: - WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); - // Time:1 - break; - case Type::Survival: - WriteLeaderboardRow(leaderboard, index, "Points", eLDFType::LDF_TYPE_S32, entry.score); - // Points:1 - WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); - // Time:1 - break; - case Type::SurvivalNS: - WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); - // Time:1 - WriteLeaderboardRow(leaderboard, index, "Wave", eLDFType::LDF_TYPE_S32, entry.score); - // Wave:1 - break; - case Type::Donations: - WriteLeaderboardRow(leaderboard, index, "Score", eLDFType::LDF_TYPE_S32, entry.score); - // Score:1 - // Something? idk yet. - break; - case Type::None: - // This type is included here simply to resolve a compiler warning on mac about unused enum types - break; - default: - break; + case Type::ShootingGallery: + WriteLeaderboardRow(leaderboard, index, "HitPercentage", eLDFType::LDF_TYPE_FLOAT, 0.0f); + // HitPercentage:3 between 0 and 1 + WriteLeaderboardRow(leaderboard, index, "Score", eLDFType::LDF_TYPE_S32, entry.score); + // Score:1 + WriteLeaderboardRow(leaderboard, index, "Streak", eLDFType::LDF_TYPE_S32, 0); + // Streak:1 + break; + case Type::Racing: + WriteLeaderboardRow(leaderboard, index, "BestLapTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); + // BestLapTime:3 + WriteLeaderboardRow(leaderboard, index, "BestTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); + // BestTime:3 + WriteLeaderboardRow(leaderboard, index, "License", eLDFType::LDF_TYPE_S32, 0); + // License:1 - 1 if player has completed mission 637 and 0 otherwise + WriteLeaderboardRow(leaderboard, index, "NumWins", eLDFType::LDF_TYPE_S32, 0); + // NumWins:1 + break; + case Type::UnusedLeaderboard4: + WriteLeaderboardRow(leaderboard, index, "Points", eLDFType::LDF_TYPE_S32, entry.score); + // Points:1 + break; + case Type::MonumentRace: + WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + // Time:1(?) + break; + case Type::FootRace: + WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + // Time:1 + break; + case Type::Survival: + WriteLeaderboardRow(leaderboard, index, "Points", eLDFType::LDF_TYPE_S32, entry.score); + // Points:1 + WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + // Time:1 + break; + case Type::SurvivalNS: + WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + // Time:1 + WriteLeaderboardRow(leaderboard, index, "Wave", eLDFType::LDF_TYPE_S32, entry.score); + // Wave:1 + break; + case Type::Donations: + WriteLeaderboardRow(leaderboard, index, "Score", eLDFType::LDF_TYPE_S32, entry.score); + // Score:1 + // Something? idk yet. + break; + case Type::None: + // This type is included here simply to resolve a compiler warning on mac about unused enum types + break; + default: + break; } index++; } @@ -137,9 +101,89 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { } void Leaderboard::SetupLeaderboard() { + std::string queryBase = + "SELECT %s, character_id, UNIX_TIMESTAMP(last_played), charinfo.name as lastPlayed" + "FROM leaderboard JOIN charinfo" + "ON charinfo.id = leaderboard.character_id" + "WHERE game_id = ?" + "ORDER BY %s"; // 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 + char queryBuffer[1024]; + switch (leaderboardType) { + case Type::ShootingGallery: + snprintf(queryBuffer, 1024, queryBase.c_str(), "hitPercentage, score, streak", "score DESC, streak DESC, hitPercentage DESC"); + break; + case Type::Racing: + snprintf(queryBuffer, 1024, queryBase.c_str(), "bestLapTime, bestTime, numWins", "bestTime ASC, bestLapTime ASC, numWins DESC"); + break; + case Type::UnusedLeaderboard4: + snprintf(queryBuffer, 1024, queryBase.c_str(), "points", "points DESC"); + break; + case Type::MonumentRace: + snprintf(queryBuffer, 1024, queryBase.c_str(), "time", "time ASC"); + break; + case Type::FootRace: + snprintf(queryBuffer, 1024, queryBase.c_str(), "time", "time DESC"); + break; + case Type::Survival: + snprintf(queryBuffer, 1024, queryBase.c_str(), "points, time", "points DESC, time DESC"); + // If the config option default_survival_scoring is 1, reverse the order of the points and time columns + break; + case Type::SurvivalNS: + snprintf(queryBuffer, 1024, queryBase.c_str(), "time, wave", "time DESC, wave DESC"); + break; + case Type::Donations: + snprintf(queryBuffer, 1024, queryBase.c_str(), "score", "score DESC"); + 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; + } + Game::logger->Log("LeaderboardManager", "filled in query is %s", queryBuffer); + // create and execute query here + std::unique_ptr query(Database::CreatePreppedStmt(queryBuffer)); + query->setInt(1, this->gameID); + std::unique_ptr result(query->executeQuery()); + if (result->rowsCount() == 0) return; + + uint32_t myRanking = 1; + uint32_t myCharacterId = 0; + // Find my ranking in the leaderboard + while (result->next()) { + if (result->getInt("character_id") != myCharacterId) myRanking++; + else break; + } + // Once you've found my ranking, figure out if we need to adjust the + // row pointer to get the top 11 or the bottom 11. + + int32_t lowestRanking = result->rowsCount() - 5; + if (lowestRanking > 0 && myRanking >= lowestRanking) { // I am in the bottom 10, so set row pointer back to the top of the bottom 6 + for (uint32_t i = myRanking - lowestRanking; i > lowestRanking; i--) { + result->previous(); + } + } + + uint32_t startRanking = 1; // Default to top 11 + if (myRanking >= 6) startRanking = myRanking - 5; // If i am not in the top 5, set row pointer to 5 above my ranking + else if (myRanking > result->rowsCount()) { // If i am in the bottom 10, set the row pointer to the top of the bottom 11 + startRanking = result->rowsCount() - 10; + } + + for (uint32_t i = myRanking - 5; i > 0; i--) { // Adjust first row gotten to be 5 above my ranking. + result->previous(); + } + for (uint32_t i = 11; i > 0; i--) { + this->entries.push_back(LDFData(u"CharacterID", result->getInt("character_id"))); + this->entries.push_back(LDFData(u"LastPlayed", result->getUInt64("lastPlayed"))); + this->entries.push_back(LDFData(u"NumPlayed", 1)); + this->entries.push_back(LDFData(u"name", GeneralUtils::ASCIIToUTF16(result->getString("name").c_str()))); + this->entries.push_back(LDFData(u"RowNumber", startRanking + i)); + } } void Leaderboard::Send(LWOOBJID targetID) const { @@ -166,70 +210,70 @@ std::string FormatInsert(const char* columns, const char* format, va_list args) return finishedQuery; } -void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list args){ +void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list args) { std::string insertStatement; // use replace into to update the score if it already exists instead of needing an update and an insert switch (leaderboardType) { - case Leaderboard::Type::ShootingGallery: { - // Check that the score exists and is better. If the score is better update it. - // If the score is the same but the streak is better, update it. - // If the score is the same and the streak is the same but the hit percentage is better, update it. - // If the score doesn't exist, insert it. - auto lookup = Database::CreatePreppedStmt("SELECT score, streak, hitPercentage FROM leaderboard WHERE playerID = ? AND gameID = ?"); - lookup->setInt64(1, playerID); - lookup->setInt(2, gameID); - auto lookupResult = lookup->executeQuery(); - if (lookupResult->next()) { + case Leaderboard::Type::ShootingGallery: { + // Check that the score exists and is better. If the score is better update it. + // If the score is the same but the streak is better, update it. + // If the score is the same and the streak is the same but the hit percentage is better, update it. + // If the score doesn't exist, insert it. + auto lookup = Database::CreatePreppedStmt("SELECT score, streak, hitPercentage FROM leaderboard WHERE playerID = ? AND gameID = ?"); + lookup->setInt64(1, playerID); + lookup->setInt(2, gameID); + auto lookupResult = lookup->executeQuery(); + if (lookupResult->next()) { - } else { - auto result = FormatInsert("hitPercentage, score, streak", "%f, %i, %i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); - } - break; - } - case Leaderboard::Type::Racing: { - auto result = FormatInsert("bestLapTime, bestTime", "%f, %f", args); + } else { + auto result = FormatInsert("hitPercentage, score, streak", "%f, %i, %i", args); Game::logger->Log("LeaderboardManager", "%s", result.c_str()); - break; - } - case Leaderboard::Type::UnusedLeaderboard4: { - auto result = FormatInsert("points", "%i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); - break; - } - case Leaderboard::Type::MonumentRace: { - auto result = FormatInsert("time", "%i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); - break; - } - case Leaderboard::Type::FootRace: { - auto result = FormatInsert("time", "%i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); - break; - } - case Leaderboard::Type::Survival: { - auto result = FormatInsert("points, time", "%i, %i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); - break; - } - case Leaderboard::Type::SurvivalNS: { - auto result = FormatInsert("time, wave", "%i, %i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); - break; - } - case Leaderboard::Type::Donations: { - auto result = FormatInsert("score", "%i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); - break; - } - case Leaderboard::Type::None: { - Game::logger->Log("LeaderboardManager", "Warning: Saving leaderboard of type None. Are you sure this is intended?"); - break; - } - default: { - Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); - return; } + break; + } + case Leaderboard::Type::Racing: { + auto result = FormatInsert("bestLapTime, bestTime", "%f, %f", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::UnusedLeaderboard4: { + auto result = FormatInsert("points", "%i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::MonumentRace: { + auto result = FormatInsert("time", "%i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::FootRace: { + auto result = FormatInsert("time", "%i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::Survival: { + auto result = FormatInsert("points, time", "%i, %i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::SurvivalNS: { + auto result = FormatInsert("time, wave", "%i, %i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::Donations: { + auto result = FormatInsert("score", "%i", args); + Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + break; + } + case Leaderboard::Type::None: { + Game::logger->Log("LeaderboardManager", "Warning: Saving leaderboard of type None. Are you sure this is intended?"); + break; + } + default: { + Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); + return; + } } } diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index abe679e9..1a44fde4 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -15,15 +15,15 @@ typedef uint32_t GameID; class Leaderboard { public: - struct Entry { - LWOOBJID playerID; - uint32_t time; - uint32_t score; - uint32_t placement; - time_t lastPlayed; - std::string playerName; - }; - typedef std::vector LeaderboardEntries; + // struct Entry { + // LWOOBJID playerID; + // uint32_t time; + // uint32_t score; + // uint32_t placement; + // time_t lastPlayed; + // std::string playerName; + // }; + typedef std::vector LeaderboardEntries; // Enums for leaderboards enum InfoType : uint32_t { @@ -76,7 +76,7 @@ public: * Adds a new entry to the leaderboard * Used for debug only! */ - void AddEntry(Entry entry) { entries.push_back(entry); } + void AddEntry(LDFBaseData& entry) { entries.push_back(entry); } private: template inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, const std::string& key, const eLDFType& ldfType, const TypeToWrite& value) const; diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index e3fa4264..8594b186 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -35,10 +35,11 @@ protected: void RunTests(Leaderboard::Type type) { Game::logger->Log("LeaderboardTests", "Testing leaderboard %i for Serialize speed", type); Leaderboard leaderboard(0, Leaderboard::InfoType::Top, false, type); - TestLeaderboard(leaderboard, 1); - TestLeaderboard(leaderboard, 10); - TestLeaderboard(leaderboard, 100); - TestLeaderboard(leaderboard, 1000); + leaderboard.SetupLeaderboard(); + // TestLeaderboard(leaderboard, 1); + // TestLeaderboard(leaderboard, 10); + // TestLeaderboard(leaderboard, 100); + // TestLeaderboard(leaderboard, 1000); } CBITSTREAM; @@ -77,20 +78,20 @@ protected: */ TEST_F(LeaderboardTests, LeaderboardSpeedTest) { - // RunTests(Leaderboard::Type::ShootingGallery); - LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::ShootingGallery, 3, 3000, 15.0f, 100); - // RunTests(Leaderboard::Type::Racing); - LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Racing, 2, 260.0f, 250.0f); - // RunTests(Leaderboard::Type::MonumentRace); - LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::MonumentRace, 1, 150); - // RunTests(Leaderboard::Type::FootRace); - LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::FootRace, 1, 150); - // RunTests(Leaderboard::Type::UnusedLeaderboard4); - LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::UnusedLeaderboard4, 1, 100); - // RunTests(Leaderboard::Type::Survival); - LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Survival, 2, 3000, 15); - // RunTests(Leaderboard::Type::SurvivalNS); - LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::SurvivalNS, 2, 300, 15); - // RunTests(Leaderboard::Type::Donations); - LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Donations, 1, 300000); + RunTests(Leaderboard::Type::ShootingGallery); + // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::ShootingGallery, 3, 3000, 15.0f, 100); + RunTests(Leaderboard::Type::Racing); + // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Racing, 2, 260.0f, 250.0f); + RunTests(Leaderboard::Type::MonumentRace); + // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::MonumentRace, 1, 150); + RunTests(Leaderboard::Type::FootRace); + // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::FootRace, 1, 150); + RunTests(Leaderboard::Type::UnusedLeaderboard4); + // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::UnusedLeaderboard4, 1, 100); + RunTests(Leaderboard::Type::Survival); + // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Survival, 2, 3000, 15); + RunTests(Leaderboard::Type::SurvivalNS); + // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::SurvivalNS, 2, 300, 15); + RunTests(Leaderboard::Type::Donations); + // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Donations, 1, 300000); } From a3626a3b53dcfefc184954e57c7f9ef35c96db50 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Wed, 3 May 2023 00:38:38 -0700 Subject: [PATCH 11/59] The query is supposed to get SMALLER Still better than 9 different queries all with 1 minor change i guess. --- dGame/LeaderboardManager.cpp | 328 +++++++++++++++++--------- dGame/LeaderboardManager.h | 26 +- tests/dGameTests/GameDependencies.h | 2 + tests/dGameTests/LeaderboardTests.cpp | 42 ++-- 4 files changed, 242 insertions(+), 156 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 8c32d383..940e4932 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -16,19 +16,23 @@ #include "CDActivitiesTable.h" #include "Metrics.hpp" -Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const Leaderboard::Type leaderboardType) { +Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type leaderboardType) { this->gameID = gameID; this->weekly = weekly; this->infoType = infoType; this->leaderboardType = leaderboardType; + this->relatedPlayer = relatedPlayer; } -template -void Leaderboard::WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, const std::string& key, const eLDFType& ldfType, const TypeToWrite& value) const { - leaderboard << "Result[0].Row[" << index << "]." << key << '=' << ldfType << ':' << value << '\n'; +Leaderboard::~Leaderboard() { + for (auto& entry : entries) for (auto data : entry) delete data; } -void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { +void Leaderboard::WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data) { + leaderboard << "Result[0].Row[" << index << "]." << data->GetString() << '\n'; +} + +void Leaderboard::Serialize(RakNet::BitStream* bitStream) { std::ostringstream leaderboard; leaderboard << "ADO.Result=7:1\n"; // Unused in 1.10.64, but is in captures @@ -36,54 +40,235 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { leaderboard << "Result[0].Index=0:RowNumber\n"; // "Primary key" leaderboard << "Result[0].RowCount=1:" << entries.size() << '\n'; // number of rows - auto index = 0; - for (const auto& entry : entries) { - // Each minigame has its own "points" system + int32_t index = 0; + for (auto& entry : entries) { + for (auto data : entry) { + WriteLeaderboardRow(leaderboard, index, data); + } + index++; + } + + // Serialize the thing to a BitStream + bitStream->Write(leaderboard.str().c_str(), leaderboard.tellp()); +} + +#define MAX_QUERY_LENGTH 1526 + +void Leaderboard::SetupLeaderboard() { + bool isTopQuery = this->infoType == InfoType::Top; + bool isMyStandingQuery = this->infoType == InfoType::MyStanding; + bool isFriendsQuery = this->infoType == InfoType::Friends; + std::string baseLookupStr; + + if (!isTopQuery) { + baseLookupStr = "SELECT id FROM leaderboard WHERE game_id = ? AND character_id = ? LIMIT 1"; + } else { + baseLookupStr = "SELECT id FROM leaderboard WHERE game_id = ? ORDER BY %s LIMIT 1"; + } + + std::string queryBase = + " \ + WITH leaderboardsRanked AS ( \ + SELECT leaderboard.*, charinfo.name, \ + RANK() OVER \ + ( \ + ORDER BY %s \ + ) 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 FROM leaderboardsRanked, myStanding, lowestRanking \ + WHERE leaderboardsRanked.ranking \ + BETWEEN \ + LEAST(GREATEST(myRank - 5, 1), lowestRanking.lowestRank - 10) \ + AND \ + LEAST(GREATEST(myRank + 5, 11), lowestRanking.lowestRank) \ + ORDER BY ranking ASC;"; + // 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. + + 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 = ?) "; + + char baseStandingBuffer[1024]; + char lookupBuffer[MAX_QUERY_LENGTH]; + + switch (leaderboardType) { + case Type::ShootingGallery: { + const char* orderBase = "score DESC, streak DESC, hitPercentage DESC"; + const char* selectBase = "hitPercentage, score, streak"; + snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase, selectBase); + if (isFriendsQuery) snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase, friendsQuery, selectBase); + else snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase, "", selectBase); + if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), orderBase); + break; + } + case Type::Racing: + snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "bestTime ASC, bestLapTime ASC, numWins DESC", "bestLapTime, bestTime, numWins"); + if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "bestTime ASC, bestLapTime ASC, numWins DESC"); + break; + case Type::UnusedLeaderboard4: + snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "score DESC", "score"); + if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "score DESC"); + break; + case Type::MonumentRace: + snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "bestTime ASC", "bestTime"); + if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "bestTime ASC"); + break; + case Type::FootRace: + snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "bestTime DESC", "bestTime"); + if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "bestTime DESC"); + break; + case Type::Survival: + snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "score DESC, bestTime DESC", "score, bestTime"); + if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "score DESC, bestTime DESC"); + // If the config option default_survival_scoring is 1, reverse the order of the points and time columns + break; + case Type::SurvivalNS: + snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "bestTime DESC, score DESC", "bestTime, score"); + if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "bestTime DESC, score DESC"); + break; + case Type::Donations: + snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "score DESC", "score"); + if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "score DESC"); + 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; + } + Game::logger->Log("LeaderboardManager", "lookup query is %s", (!isTopQuery) ? baseLookupStr.c_str() : baseStandingBuffer); + std::unique_ptr baseQuery(Database::CreatePreppedStmt((!isTopQuery) ? baseLookupStr : baseStandingBuffer)); + baseQuery->setInt(1, this->gameID); + if (!isTopQuery) { + baseQuery->setInt(2, this->relatedPlayer); + } + + std::unique_ptr baseResult(baseQuery->executeQuery()); + if (baseResult->rowsCount() == 0) return; + baseResult->next(); + // 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 (isFriendsQuery) { + 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; + + uint32_t myRanking = 1; + uint32_t myCharacterId = 0; + int32_t lowestRanking = result->rowsCount() - 5; + uint32_t startRanking = 1; // Default to top 11 + if (this->infoType == InfoType::MyStanding) { + // Find my ranking in the leaderboard + while (result->next()) { + if (result->getInt("character_id") != myCharacterId) myRanking++; + else break; + } + // Once you've found my ranking, figure out if we need to adjust the + // row pointer to get the top 11 or the bottom 11. + + if (lowestRanking > 0 && myRanking >= lowestRanking) { // I am in the bottom 10, so set row pointer back to the top of the bottom 6 + for (uint32_t i = myRanking - lowestRanking; i > lowestRanking; i--) { + result->previous(); + } + } + + if (myRanking >= 6) startRanking = myRanking - 5; // If i am not in the top 5, set row pointer to 5 above my ranking + else if (myRanking > result->rowsCount()) { // If i am in the bottom 10, set the row pointer to the top of the bottom 11 + startRanking = result->rowsCount() - 10; + } + + for (uint32_t i = myRanking - 5; i > 0; i--) { // Adjust first row gotten to be 5 above my ranking. + result->previous(); + } + } + + this->entries.reserve(11); + for (uint32_t i = 0; i < 11 && result->next(); i++) { + 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"NumPlayed", 1)); + entry.push_back(new LDFData(u"name", GeneralUtils::ASCIIToUTF16(result->getString("name").c_str()))); + entry.push_back(new LDFData(u"RowNumber", startRanking + i)); switch (leaderboardType) { case Type::ShootingGallery: - WriteLeaderboardRow(leaderboard, index, "HitPercentage", eLDFType::LDF_TYPE_FLOAT, 0.0f); + entry.push_back(new LDFData(u"HitPercentage", result->getDouble("hitPercentage"))); // HitPercentage:3 between 0 and 1 - WriteLeaderboardRow(leaderboard, index, "Score", eLDFType::LDF_TYPE_S32, entry.score); + entry.push_back(new LDFData(u"Score", result->getInt("score"))); // Score:1 - WriteLeaderboardRow(leaderboard, index, "Streak", eLDFType::LDF_TYPE_S32, 0); + entry.push_back(new LDFData(u"Streak", result->getInt("streak"))); // Streak:1 break; case Type::Racing: - WriteLeaderboardRow(leaderboard, index, "BestLapTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); + entry.push_back(new LDFData(u"BestLapTime", result->getDouble("bestLapTime"))); // BestLapTime:3 - WriteLeaderboardRow(leaderboard, index, "BestTime", eLDFType::LDF_TYPE_FLOAT, 0.0f); + entry.push_back(new LDFData(u"BestTime", result->getDouble("bestTime"))); // BestTime:3 - WriteLeaderboardRow(leaderboard, index, "License", eLDFType::LDF_TYPE_S32, 0); + entry.push_back(new LDFData(u"License", 1)); // License:1 - 1 if player has completed mission 637 and 0 otherwise - WriteLeaderboardRow(leaderboard, index, "NumWins", eLDFType::LDF_TYPE_S32, 0); + entry.push_back(new LDFData(u"NumWins", result->getInt("numWins"))); // NumWins:1 break; case Type::UnusedLeaderboard4: - WriteLeaderboardRow(leaderboard, index, "Points", eLDFType::LDF_TYPE_S32, entry.score); + entry.push_back(new LDFData(u"Score", result->getInt("score"))); // Points:1 break; case Type::MonumentRace: - WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + entry.push_back(new LDFData(u"Time", result->getInt("bestTime"))); // Time:1(?) break; case Type::FootRace: - WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + entry.push_back(new LDFData(u"Time", result->getInt("bestTime"))); // Time:1 break; case Type::Survival: - WriteLeaderboardRow(leaderboard, index, "Points", eLDFType::LDF_TYPE_S32, entry.score); + entry.push_back(new LDFData(u"Score", result->getInt("score"))); // Points:1 - WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + entry.push_back(new LDFData(u"Time", result->getInt("bestTime"))); // Time:1 break; case Type::SurvivalNS: - WriteLeaderboardRow(leaderboard, index, "Time", eLDFType::LDF_TYPE_S32, entry.time); + entry.push_back(new LDFData(u"Time", result->getInt("bestTime"))); // Time:1 - WriteLeaderboardRow(leaderboard, index, "Wave", eLDFType::LDF_TYPE_S32, entry.score); + entry.push_back(new LDFData(u"Score", result->getInt("score"))); // Wave:1 break; case Type::Donations: - WriteLeaderboardRow(leaderboard, index, "Score", eLDFType::LDF_TYPE_S32, entry.score); + entry.push_back(new LDFData(u"Score", result->getInt("score"))); // Score:1 // Something? idk yet. break; @@ -93,97 +278,12 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { default: break; } - index++; } - - // Serialize the thing to a BitStream - bitStream->Write(leaderboard.str().c_str(), leaderboard.tellp()); -} - -void Leaderboard::SetupLeaderboard() { - std::string queryBase = - "SELECT %s, character_id, UNIX_TIMESTAMP(last_played), charinfo.name as lastPlayed" - "FROM leaderboard JOIN charinfo" - "ON charinfo.id = leaderboard.character_id" - "WHERE game_id = ?" - "ORDER BY %s"; - // 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 - char queryBuffer[1024]; - switch (leaderboardType) { - case Type::ShootingGallery: - snprintf(queryBuffer, 1024, queryBase.c_str(), "hitPercentage, score, streak", "score DESC, streak DESC, hitPercentage DESC"); - break; - case Type::Racing: - snprintf(queryBuffer, 1024, queryBase.c_str(), "bestLapTime, bestTime, numWins", "bestTime ASC, bestLapTime ASC, numWins DESC"); - break; - case Type::UnusedLeaderboard4: - snprintf(queryBuffer, 1024, queryBase.c_str(), "points", "points DESC"); - break; - case Type::MonumentRace: - snprintf(queryBuffer, 1024, queryBase.c_str(), "time", "time ASC"); - break; - case Type::FootRace: - snprintf(queryBuffer, 1024, queryBase.c_str(), "time", "time DESC"); - break; - case Type::Survival: - snprintf(queryBuffer, 1024, queryBase.c_str(), "points, time", "points DESC, time DESC"); - // If the config option default_survival_scoring is 1, reverse the order of the points and time columns - break; - case Type::SurvivalNS: - snprintf(queryBuffer, 1024, queryBase.c_str(), "time, wave", "time DESC, wave DESC"); - break; - case Type::Donations: - snprintf(queryBuffer, 1024, queryBase.c_str(), "score", "score DESC"); - 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; - } - Game::logger->Log("LeaderboardManager", "filled in query is %s", queryBuffer); - // create and execute query here - std::unique_ptr query(Database::CreatePreppedStmt(queryBuffer)); - query->setInt(1, this->gameID); - std::unique_ptr result(query->executeQuery()); - if (result->rowsCount() == 0) return; - - uint32_t myRanking = 1; - uint32_t myCharacterId = 0; - // Find my ranking in the leaderboard - while (result->next()) { - if (result->getInt("character_id") != myCharacterId) myRanking++; - else break; - } - // Once you've found my ranking, figure out if we need to adjust the - // row pointer to get the top 11 or the bottom 11. - - int32_t lowestRanking = result->rowsCount() - 5; - if (lowestRanking > 0 && myRanking >= lowestRanking) { // I am in the bottom 10, so set row pointer back to the top of the bottom 6 - for (uint32_t i = myRanking - lowestRanking; i > lowestRanking; i--) { - result->previous(); + for (auto& entry : entries) { + for (auto data : entry) { + Game::logger->Log("LeaderboardManager", "entry is %s", data->GetString().c_str()); } } - - uint32_t startRanking = 1; // Default to top 11 - if (myRanking >= 6) startRanking = myRanking - 5; // If i am not in the top 5, set row pointer to 5 above my ranking - else if (myRanking > result->rowsCount()) { // If i am in the bottom 10, set the row pointer to the top of the bottom 11 - startRanking = result->rowsCount() - 10; - } - - for (uint32_t i = myRanking - 5; i > 0; i--) { // Adjust first row gotten to be 5 above my ranking. - result->previous(); - } - - for (uint32_t i = 11; i > 0; i--) { - this->entries.push_back(LDFData(u"CharacterID", result->getInt("character_id"))); - this->entries.push_back(LDFData(u"LastPlayed", result->getUInt64("lastPlayed"))); - this->entries.push_back(LDFData(u"NumPlayed", 1)); - this->entries.push_back(LDFData(u"name", GeneralUtils::ASCIIToUTF16(result->getString("name").c_str()))); - this->entries.push_back(LDFData(u"RowNumber", startRanking + i)); - } } void Leaderboard::Send(LWOOBJID targetID) const { @@ -271,7 +371,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead break; } default: { - Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); + Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); return; } } @@ -279,8 +379,8 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead } void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, LWOOBJID playerID) { - // Create the leaderboard here and then send it right after. On the stack. - Leaderboard leaderboard(gameID, infoType, weekly, GetLeaderboardType(gameID)); + // Create the leaderboard here and then send it right after. On the stack. + Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID)); leaderboard.SetupLeaderboard(); leaderboard.Send(targetID); } diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 1a44fde4..b0d6bf26 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -15,15 +15,8 @@ typedef uint32_t GameID; class Leaderboard { public: - // struct Entry { - // LWOOBJID playerID; - // uint32_t time; - // uint32_t score; - // uint32_t placement; - // time_t lastPlayed; - // std::string playerName; - // }; - typedef std::vector LeaderboardEntries; + using LeaderboardEntry = std::vector; + using LeaderboardEntries = std::vector; // Enums for leaderboards enum InfoType : uint32_t { @@ -44,14 +37,16 @@ public: None = UINT_MAX }; - Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const Leaderboard::Type = None); + Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type = None); + + ~Leaderboard(); /** * 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) const; + void Serialize(RakNet::BitStream* bitStream); /** * Based on the associated gameID, return true if the score provided @@ -71,15 +66,8 @@ public: * Sends the leaderboard to the client specified by targetID. */ void Send(LWOOBJID targetID) const; - - /** - * Adds a new entry to the leaderboard - * Used for debug only! - */ - void AddEntry(LDFBaseData& entry) { entries.push_back(entry); } private: -template - inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, const std::string& key, const eLDFType& ldfType, const TypeToWrite& value) const; + inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data); LeaderboardEntries entries; LWOOBJID relatedPlayer; GameID gameID; diff --git a/tests/dGameTests/GameDependencies.h b/tests/dGameTests/GameDependencies.h index 353b53b8..c0fb8bce 100644 --- a/tests/dGameTests/GameDependencies.h +++ b/tests/dGameTests/GameDependencies.h @@ -6,6 +6,7 @@ #include "dServer.h" #include "EntityInfo.h" #include "EntityManager.h" +#include "Database.h" #include "dConfig.h" #include @@ -32,6 +33,7 @@ protected: Game::logger = new dLogger("./testing.log", true, true); Game::server = new dServerMock(); Game::config = new dConfig("worldconfig.ini"); + Database::Connect(Game::config->GetValue("mysql_host"), Game::config->GetValue("mysql_database"), Game::config->GetValue("mysql_username"), Game::config->GetValue("mysql_password")); } void TearDownDependencies() { diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index 8594b186..7cec021b 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -15,14 +15,7 @@ protected: } void TestLeaderboard(Leaderboard& leaderboard, int32_t entries) { - Leaderboard::Entry entry; - entry.playerID = UINT64_MAX; - entry.time = 100; - entry.score = 100; - entry.placement = 1; - entry.lastPlayed = 0; - entry.playerName = "TestThreeWords"; - for (int32_t i = 0; i < entries; i++) leaderboard.AddEntry(entry); + bitStream.Reset(); Metrics::StartMeasurement(MetricVariable::Leaderboard); for (int32_t i = 0; i < MAX_MEASURMENT_POINTS; i++) leaderboard.Serialize(&bitStream); Metrics::EndMeasurement(MetricVariable::Leaderboard); @@ -32,14 +25,15 @@ protected: bitStream.Reset(); } - void RunTests(Leaderboard::Type type) { - Game::logger->Log("LeaderboardTests", "Testing leaderboard %i for Serialize speed", type); - Leaderboard leaderboard(0, Leaderboard::InfoType::Top, false, type); + void RunTests(uint32_t gameID, Leaderboard::Type type, Leaderboard::InfoType infoType) { + Game::logger->Log("LeaderboardTests", "Testing leaderboard %i for Serialize speed", infoType); + Leaderboard leaderboard(gameID, infoType, false, 14231, type); leaderboard.SetupLeaderboard(); - // TestLeaderboard(leaderboard, 1); - // TestLeaderboard(leaderboard, 10); - // TestLeaderboard(leaderboard, 100); - // TestLeaderboard(leaderboard, 1000); + leaderboard.Serialize(&bitStream); + TestLeaderboard(leaderboard, 1); + TestLeaderboard(leaderboard, 10); + TestLeaderboard(leaderboard, 100); + TestLeaderboard(leaderboard, 1000); } CBITSTREAM; @@ -78,20 +72,22 @@ protected: */ TEST_F(LeaderboardTests, LeaderboardSpeedTest) { - RunTests(Leaderboard::Type::ShootingGallery); + 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(0, 0, Leaderboard::Type::ShootingGallery, 3, 3000, 15.0f, 100); - RunTests(Leaderboard::Type::Racing); + // RunTests(0, Leaderboard::Type::Racing); // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Racing, 2, 260.0f, 250.0f); - RunTests(Leaderboard::Type::MonumentRace); + // RunTests(0, Leaderboard::Type::MonumentRace); // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::MonumentRace, 1, 150); - RunTests(Leaderboard::Type::FootRace); + // RunTests(0, Leaderboard::Type::FootRace); // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::FootRace, 1, 150); - RunTests(Leaderboard::Type::UnusedLeaderboard4); + // RunTests(0, Leaderboard::Type::UnusedLeaderboard4); // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::UnusedLeaderboard4, 1, 100); - RunTests(Leaderboard::Type::Survival); + // RunTests(0, Leaderboard::Type::Survival); // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Survival, 2, 3000, 15); - RunTests(Leaderboard::Type::SurvivalNS); + // RunTests(0, Leaderboard::Type::SurvivalNS); // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::SurvivalNS, 2, 300, 15); - RunTests(Leaderboard::Type::Donations); + // RunTests(0, Leaderboard::Type::Donations); // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Donations, 1, 300000); } From 8de528e77caa49ffe72ab4479c6e0ae67b5acb51 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 4 May 2023 13:58:48 -0700 Subject: [PATCH 12/59] Remove old code --- dGame/LeaderboardManager.cpp | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 940e4932..7f294285 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -184,37 +184,8 @@ void Leaderboard::SetupLeaderboard() { if (result->rowsCount() == 0) return; - uint32_t myRanking = 1; - uint32_t myCharacterId = 0; - int32_t lowestRanking = result->rowsCount() - 5; - uint32_t startRanking = 1; // Default to top 11 - if (this->infoType == InfoType::MyStanding) { - // Find my ranking in the leaderboard - while (result->next()) { - if (result->getInt("character_id") != myCharacterId) myRanking++; - else break; - } - // Once you've found my ranking, figure out if we need to adjust the - // row pointer to get the top 11 or the bottom 11. - - if (lowestRanking > 0 && myRanking >= lowestRanking) { // I am in the bottom 10, so set row pointer back to the top of the bottom 6 - for (uint32_t i = myRanking - lowestRanking; i > lowestRanking; i--) { - result->previous(); - } - } - - if (myRanking >= 6) startRanking = myRanking - 5; // If i am not in the top 5, set row pointer to 5 above my ranking - else if (myRanking > result->rowsCount()) { // If i am in the bottom 10, set the row pointer to the top of the bottom 11 - startRanking = result->rowsCount() - 10; - } - - for (uint32_t i = myRanking - 5; i > 0; i--) { // Adjust first row gotten to be 5 above my ranking. - result->previous(); - } - } - this->entries.reserve(11); - for (uint32_t i = 0; i < 11 && result->next(); i++) { + while (result->next()) { constexpr int32_t MAX_NUM_DATA_PER_ROW = 9; this->entries.push_back(std::vector()); auto& entry = this->entries.back(); @@ -223,7 +194,7 @@ void Leaderboard::SetupLeaderboard() { entry.push_back(new LDFData(u"LastPlayed", result->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", startRanking + i)); + entry.push_back(new LDFData(u"RowNumber", result->getInt("ranking"))); switch (leaderboardType) { case Type::ShootingGallery: entry.push_back(new LDFData(u"HitPercentage", result->getDouble("hitPercentage"))); From 2ab995b9c3287d88fd52bf9d479c6f8d81913712 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 4 May 2023 14:28:53 -0700 Subject: [PATCH 13/59] Simplify snprintfs so much better to read --- dGame/LeaderboardManager.cpp | 43 +++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 7f294285..88276978 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -111,50 +111,53 @@ void Leaderboard::SetupLeaderboard() { char baseStandingBuffer[1024]; char lookupBuffer[MAX_QUERY_LENGTH]; + std::string orderBase; + std::string selectBase; + switch (leaderboardType) { case Type::ShootingGallery: { - const char* orderBase = "score DESC, streak DESC, hitPercentage DESC"; - const char* selectBase = "hitPercentage, score, streak"; - snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase, selectBase); - if (isFriendsQuery) snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase, friendsQuery, selectBase); - else snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase, "", selectBase); - if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), orderBase); + orderBase = "score DESC, streak DESC, hitPercentage DESC"; + selectBase = "hitPercentage, score, streak"; break; } case Type::Racing: - snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "bestTime ASC, bestLapTime ASC, numWins DESC", "bestLapTime, bestTime, numWins"); - if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "bestTime ASC, bestLapTime ASC, numWins DESC"); + orderBase = "bestTime ASC, bestLapTime ASC, numWins DESC"; + selectBase = "bestLapTime, bestTime, numWins"; break; case Type::UnusedLeaderboard4: - snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "score DESC", "score"); - if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "score DESC"); + orderBase = "score DESC"; + selectBase = "score"; break; case Type::MonumentRace: - snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "bestTime ASC", "bestTime"); - if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "bestTime ASC"); + orderBase = "bestTime ASC"; + selectBase = "bestTime"; break; case Type::FootRace: - snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "bestTime DESC", "bestTime"); - if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "bestTime DESC"); + orderBase = "bestTime DESC"; + selectBase = "bestTime"; break; case Type::Survival: - snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "score DESC, bestTime DESC", "score, bestTime"); - if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "score DESC, bestTime DESC"); + 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: - snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "bestTime DESC, score DESC", "bestTime, score"); - if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "bestTime DESC, score DESC"); + orderBase = "bestTime DESC, score DESC"; + selectBase = "bestTime, score"; break; case Type::Donations: - snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), "score DESC", "score"); - if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), "score DESC"); + 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; } + if (isFriendsQuery) snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase.c_str(), friendsQuery, selectBase.c_str()); + else snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase.c_str(), "", selectBase.c_str()); + if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), orderBase.c_str()); + Game::logger->Log("LeaderboardManager", "lookup query is %s", (!isTopQuery) ? baseLookupStr.c_str() : baseStandingBuffer); std::unique_ptr baseQuery(Database::CreatePreppedStmt((!isTopQuery) ? baseLookupStr : baseStandingBuffer)); baseQuery->setInt(1, this->gameID); From 28a0492201bdd313cb8138dcbeed43e4fc8af717 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 4 May 2023 14:48:26 -0700 Subject: [PATCH 14/59] Fix bugs --- dGame/LeaderboardManager.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 88276978..09f65475 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -87,10 +87,10 @@ void Leaderboard::SetupLeaderboard() { SELECT MAX(ranking) AS lowestRank \ FROM leaderboardsRanked \ ) \ - SELECT %s, character_id, UNIX_TIMESTAMP(last_played) as lastPlayed, leaderboardsRanked.name FROM leaderboardsRanked, myStanding, lowestRanking \ + SELECT %s, character_id, UNIX_TIMESTAMP(last_played) as lastPlayed, leaderboardsRanked.name, leaderboardsRanked.ranking FROM leaderboardsRanked, myStanding, lowestRanking \ WHERE leaderboardsRanked.ranking \ BETWEEN \ - LEAST(GREATEST(myRank - 5, 1), lowestRanking.lowestRank - 10) \ + LEAST(GREATEST(CAST(myRank AS SIGNED) - 5, 1), lowestRanking.lowestRank - 10) \ AND \ LEAST(GREATEST(myRank + 5, 11), lowestRanking.lowestRank) \ ORDER BY ranking ASC;"; @@ -108,12 +108,8 @@ void Leaderboard::SetupLeaderboard() { "JOIN charinfo AS ci ON ci.id = fr.requested_player " "WHERE fr.requested_player IS NOT NULL) OR character_id = ?) "; - char baseStandingBuffer[1024]; - char lookupBuffer[MAX_QUERY_LENGTH]; - std::string orderBase; std::string selectBase; - switch (leaderboardType) { case Type::ShootingGallery: { orderBase = "score DESC, streak DESC, hitPercentage DESC"; @@ -154,6 +150,9 @@ void Leaderboard::SetupLeaderboard() { // This type is included here simply to resolve a compiler warning on mac about unused enum types break; } + + char baseStandingBuffer[1024]; + char lookupBuffer[MAX_QUERY_LENGTH]; if (isFriendsQuery) snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase.c_str(), friendsQuery, selectBase.c_str()); else snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase.c_str(), "", selectBase.c_str()); if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), orderBase.c_str()); From 0faef7d7918f452a0deb0a8b93f9eff8e02e5034 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 4 May 2023 16:53:36 -0700 Subject: [PATCH 15/59] Finished saving --- dGame/LeaderboardManager.cpp | 71 +++++++++++++++++------------------- dGame/LeaderboardManager.h | 3 ++ 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 09f65475..a814d953 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -52,20 +52,17 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) { bitStream->Write(leaderboard.str().c_str(), leaderboard.tellp()); } -#define MAX_QUERY_LENGTH 1526 +bool Leaderboard::GetRankingQuery(std::string& lookupReturn) const { + if (this->infoType == InfoType::Top) { + lookupReturn = "SELECT id FROM leaderboard WHERE game_id = ? ORDER BY %s LIMIT 1"; + return true; + } else { + lookupReturn = "SELECT id FROM leaderboard WHERE game_id = ? AND character_id = ? LIMIT 1"; + return false; + } +} void Leaderboard::SetupLeaderboard() { - bool isTopQuery = this->infoType == InfoType::Top; - bool isMyStandingQuery = this->infoType == InfoType::MyStanding; - bool isFriendsQuery = this->infoType == InfoType::Friends; - std::string baseLookupStr; - - if (!isTopQuery) { - baseLookupStr = "SELECT id FROM leaderboard WHERE game_id = ? AND character_id = ? LIMIT 1"; - } else { - baseLookupStr = "SELECT id FROM leaderboard WHERE game_id = ? ORDER BY %s LIMIT 1"; - } - std::string queryBase = " \ WITH leaderboardsRanked AS ( \ @@ -151,22 +148,26 @@ void Leaderboard::SetupLeaderboard() { break; } - char baseStandingBuffer[1024]; - char lookupBuffer[MAX_QUERY_LENGTH]; - if (isFriendsQuery) snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase.c_str(), friendsQuery, selectBase.c_str()); - else snprintf(lookupBuffer, MAX_QUERY_LENGTH, queryBase.c_str(), orderBase.c_str(), "", selectBase.c_str()); - if (isTopQuery) snprintf(baseStandingBuffer, 1024, baseLookupStr.c_str(), orderBase.c_str()); + 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()); - Game::logger->Log("LeaderboardManager", "lookup query is %s", (!isTopQuery) ? baseLookupStr.c_str() : baseStandingBuffer); - std::unique_ptr baseQuery(Database::CreatePreppedStmt((!isTopQuery) ? baseLookupStr : baseStandingBuffer)); + 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 (!isTopQuery) { + if (!neededFormatting) { baseQuery->setInt(2, this->relatedPlayer); } std::unique_ptr baseResult(baseQuery->executeQuery()); - if (baseResult->rowsCount() == 0) return; - baseResult->next(); + if (!baseResult->next()) return; // Get the ID of the row fetched. uint32_t relatedPlayerLeaderboardId = baseResult->getInt("id"); @@ -174,7 +175,7 @@ void Leaderboard::SetupLeaderboard() { 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 (isFriendsQuery) { + if (this->infoType == InfoType::Friends) { query->setInt(2, this->relatedPlayer); query->setInt(3, this->relatedPlayer); query->setInt(4, this->relatedPlayer); @@ -186,7 +187,7 @@ void Leaderboard::SetupLeaderboard() { if (result->rowsCount() == 0) return; - this->entries.reserve(11); + this->entries.reserve(result->rowsCount()); while (result->next()) { constexpr int32_t MAX_NUM_DATA_PER_ROW = 9; this->entries.push_back(std::vector()); @@ -235,15 +236,14 @@ void Leaderboard::SetupLeaderboard() { // Time:1 break; case Type::SurvivalNS: - entry.push_back(new LDFData(u"Time", result->getInt("bestTime"))); - // Time:1 entry.push_back(new LDFData(u"Score", result->getInt("score"))); // Wave:1 + entry.push_back(new LDFData(u"Time", result->getInt("bestTime"))); + // Time:1 break; case Type::Donations: entry.push_back(new LDFData(u"Score", result->getInt("score"))); // Score:1 - // Something? idk yet. break; case Type::None: // This type is included here simply to resolve a compiler warning on mac about unused enum types @@ -278,23 +278,21 @@ std::string FormatInsert(const char* columns, const char* format, va_list args) constexpr uint16_t STRING_LENGTH = 400; char formattedInsert[STRING_LENGTH]; char finishedQuery[STRING_LENGTH]; - snprintf(formattedInsert, 400, queryBase, columns, format); - vsnprintf(finishedQuery, 400, formattedInsert, args); + snprintf(formattedInsert, STRING_LENGTH, queryBase, columns, format); + vsnprintf(finishedQuery, STRING_LENGTH, formattedInsert, args); return finishedQuery; } void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list args) { std::string insertStatement; // use replace into to update the score if it already exists instead of needing an update and an insert + std::unique_ptr lookup(Database::CreatePreppedStmt("SELECT * FROM leaderboard WHERE playerID = ? AND gameID = ?")); + lookup->setInt64(1, playerID); + lookup->setInt(2, gameID); + std::unique_ptr lookupResult(lookup->executeQuery()); + switch (leaderboardType) { case Leaderboard::Type::ShootingGallery: { - // Check that the score exists and is better. If the score is better update it. - // If the score is the same but the streak is better, update it. - // If the score is the same and the streak is the same but the hit percentage is better, update it. - // If the score doesn't exist, insert it. - auto lookup = Database::CreatePreppedStmt("SELECT score, streak, hitPercentage FROM leaderboard WHERE playerID = ? AND gameID = ?"); - lookup->setInt64(1, playerID); - lookup->setInt(2, gameID); auto lookupResult = lookup->executeQuery(); if (lookupResult->next()) { @@ -352,7 +350,6 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead } void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, LWOOBJID playerID) { - // Create the leaderboard here and then send it right after. On the stack. Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID)); leaderboard.SetupLeaderboard(); leaderboard.Send(targetID); diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index b0d6bf26..898dfbbb 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -68,6 +68,9 @@ public: void Send(LWOOBJID targetID) const; 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; LeaderboardEntries entries; LWOOBJID relatedPlayer; GameID gameID; From 3b8f18d2be9acb1e54b1f7ef8d20caafd1b3c3d4 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Fri, 5 May 2023 21:33:30 -0700 Subject: [PATCH 16/59] Fix new lines --- dGame/LeaderboardManager.cpp | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index a814d953..9dc32061 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -64,33 +64,33 @@ bool Leaderboard::GetRankingQuery(std::string& lookupReturn) const { void Leaderboard::SetupLeaderboard() { std::string queryBase = - " \ - WITH leaderboardsRanked AS ( \ - SELECT leaderboard.*, charinfo.name, \ - RANK() OVER \ - ( \ - ORDER BY %s \ - ) 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;"; + R"QUERY( + WITH leaderboardsRanked AS ( + SELECT leaderboard.*, charinfo.name, + RANK() OVER + ( + ORDER BY %s + ) 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 From 820b375c509b2af234fbfbbf1315c486748b0a7f Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 7 May 2023 00:31:38 -0700 Subject: [PATCH 17/59] push --- dGame/LeaderboardManager.cpp | 165 ++++++++++++++---- .../dlu/9_Update_Leaderboard_Storage.sql | 1 + tests/dGameTests/LeaderboardTests.cpp | 16 +- 3 files changed, 142 insertions(+), 40 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 9dc32061..1f436d30 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -273,68 +273,61 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead va_end(args); } -std::string FormatInsert(const char* columns, const char* format, va_list args) { - auto queryBase = "INSERT INTO leaderboard (%s) VALUES (%s)"; +std::string FormatInsert(const std::string& columns, const std::string& format, va_list args, bool update) { + const char* insert = "INSERT"; + const char* update = "UPDATE"; + auto queryBase = "%s leaderboard SET %s WHERE id = ?;"; constexpr uint16_t STRING_LENGTH = 400; char formattedInsert[STRING_LENGTH]; char finishedQuery[STRING_LENGTH]; - snprintf(formattedInsert, STRING_LENGTH, queryBase, columns, format); + snprintf(formattedInsert, STRING_LENGTH, queryBase, columns.c_str(), format.c_str()); vsnprintf(finishedQuery, STRING_LENGTH, formattedInsert, args); return finishedQuery; } void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list args) { std::string insertStatement; - // use replace into to update the score if it already exists instead of needing an update and an insert - std::unique_ptr lookup(Database::CreatePreppedStmt("SELECT * FROM leaderboard WHERE playerID = ? AND gameID = ?")); - lookup->setInt64(1, playerID); - lookup->setInt(2, gameID); - std::unique_ptr lookupResult(lookup->executeQuery()); + std::string selectedColumns; + std::string insertFormat; + // If ResultSet is empty, just insert our score. + std::va_list argsCopy; + va_copy(argsCopy, args); switch (leaderboardType) { case Leaderboard::Type::ShootingGallery: { - auto lookupResult = lookup->executeQuery(); - if (lookupResult->next()) { - - } else { - auto result = FormatInsert("hitPercentage, score, streak", "%f, %i, %i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); - } + selectedColumns = "score, hitPercentage, streak"; + insertFormat = "score=%i, hitPercentage=%f, streak=%i"; break; } case Leaderboard::Type::Racing: { - auto result = FormatInsert("bestLapTime, bestTime", "%f, %f", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + selectedColumns = "bestLapTime, bestTime"; + insertFormat = "bestLapTime=%f, bestTime=%f"; break; } case Leaderboard::Type::UnusedLeaderboard4: { - auto result = FormatInsert("points", "%i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); - break; - } - case Leaderboard::Type::MonumentRace: { - auto result = FormatInsert("time", "%i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + selectedColumns = "score"; + insertFormat = "score=%i"; break; } + case Leaderboard::Type::MonumentRace: case Leaderboard::Type::FootRace: { - auto result = FormatInsert("time", "%i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + selectedColumns = "time"; + insertFormat = "time=%i"; break; } case Leaderboard::Type::Survival: { - auto result = FormatInsert("points, time", "%i, %i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + selectedColumns = "points, time"; + insertFormat = "points=%i, time=%i"; break; } case Leaderboard::Type::SurvivalNS: { - auto result = FormatInsert("time, wave", "%i, %i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + selectedColumns = "time, wave"; + insertFormat = "time=%i, wave=%i"; break; } case Leaderboard::Type::Donations: { - auto result = FormatInsert("score", "%i", args); - Game::logger->Log("LeaderboardManager", "%s", result.c_str()); + selectedColumns = "score"; + insertFormat = "score=%i"; break; } case Leaderboard::Type::None: { @@ -346,7 +339,115 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead return; } } + const char* lookup = "SELECT %s FROM leaderboard WHERE character_id = ? AND game_id = ?;"; + constexpr uint16_t STRING_LENGTH = 400; + char lookupBuffer[STRING_LENGTH]; + snprintf(lookupBuffer, STRING_LENGTH, lookup, selectedColumns.c_str()); + std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer)); + query->setInt(1, playerID); + query->setInt(2, gameID); + std::unique_ptr myScoreResult(query->executeQuery()); + switch (leaderboardType) { + case Leaderboard::Type::ShootingGallery: { + int32_t oldScore = myScoreResult->getInt("score"); + int32_t score; + score = va_arg(argsCopy, int32_t); + + float oldHitPercentage = myScoreResult->getFloat("hitPercentage"); + float hitPercentage; + hitPercentage = va_arg(argsCopy, double); + + int32_t oldStreak = myScoreResult->getInt("streak"); + int32_t streak; + streak = va_arg(argsCopy, int32_t); + + 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 + // Save + } + break; + } + case Leaderboard::Type::Racing: { + float oldLapTime = myScoreResult->getFloat("bestLapTime"); + float lapTime; + lapTime = va_arg(argsCopy, double); + + float oldTime = myScoreResult->getFloat("bestTime"); + float oldTime; + oldTime = va_arg(argsCopy, double); + + 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 + break; + } + case Leaderboard::Type::UnusedLeaderboard4: { + int32_t oldScore = myScoreResult->getInt("score"); + int32_t points; + points = va_arg(argsCopy, int32_t); + // Compare score, if HIGHER save + break; + } + case Leaderboard::Type::MonumentRace: { + int32_t oldTime = myScoreResult->getInt("time"); + int32_t time; + time = va_arg(argsCopy, int32_t); + // Compare time, if LOWER save + break; + } + case Leaderboard::Type::FootRace: { + int32_t oldTime = myScoreResult->getInt("time"); + int32_t time; + time = va_arg(argsCopy, int32_t); + // Compare time, if HIGHER save + break; + } + case Leaderboard::Type::Survival: { + int32_t oldPoints = myScoreResult->getInt("points"); + int32_t points; + points = va_arg(argsCopy, int32_t); + + int32_t oldTime = myScoreResult->getInt("time"); + int32_t time; + time = va_arg(argsCopy, int32_t); + // 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: { + int32_t oldTime = myScoreResult->getInt("time"); + int32_t time; + time = va_arg(argsCopy, int32_t); + + int32_t oldWave = myScoreResult->getInt("wave"); + int32_t wave; + wave = va_arg(argsCopy, int32_t); + // 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); + // 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; + } + default: + Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); + break; + } + va_end(argsCopy); } void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, LWOOBJID playerID) { diff --git a/migrations/dlu/9_Update_Leaderboard_Storage.sql b/migrations/dlu/9_Update_Leaderboard_Storage.sql index 5e9c5fa2..d6f185dc 100644 --- a/migrations/dlu/9_Update_Leaderboard_Storage.sql +++ b/migrations/dlu/9_Update_Leaderboard_Storage.sql @@ -3,6 +3,7 @@ ALTER TABLE leaderboard ADD COLUMN streak INT NOT NULL DEFAULT 0, ADD COLUMN bestLapTime FLOAT NOT NULL DEFAULT 0, ADD COLUMN numWins INT NOT NULL DEFAULT 0, + ADD COLUMN timesPlayed INT NOT NULL DEFAULT 0, MODIFY time FLOAT NOT NULL DEFAULT 0; ALTER TABLE leaderboard CHANGE time bestTime float; diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index 7cec021b..38e92658 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -75,19 +75,19 @@ 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(0, 0, Leaderboard::Type::ShootingGallery, 3, 3000, 15.0f, 100); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::ShootingGallery, 3, 3000, 15.0f, 100); // RunTests(0, Leaderboard::Type::Racing); - // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Racing, 2, 260.0f, 250.0f); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::Racing, 3, 260.0f, 250.0f, true); // RunTests(0, Leaderboard::Type::MonumentRace); - // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::MonumentRace, 1, 150); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::MonumentRace, 1, 150); // RunTests(0, Leaderboard::Type::FootRace); - // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::FootRace, 1, 150); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::FootRace, 1, 150); // RunTests(0, Leaderboard::Type::UnusedLeaderboard4); - // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::UnusedLeaderboard4, 1, 100); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::UnusedLeaderboard4, 1, 100); // RunTests(0, Leaderboard::Type::Survival); - // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Survival, 2, 3000, 15); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::Survival, 2, 3000, 15); // RunTests(0, Leaderboard::Type::SurvivalNS); - // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::SurvivalNS, 2, 300, 15); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::SurvivalNS, 2, 300, 15); // RunTests(0, Leaderboard::Type::Donations); - // LeaderboardManager::Instance().SaveScore(0, 0, Leaderboard::Type::Donations, 1, 300000); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::Donations, 1, 300000); } From bc518be65459c48dbce4816b14726476c6b3be22 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Sun, 7 May 2023 04:09:10 -0700 Subject: [PATCH 18/59] Bug fixes and cleanup Fix co-pilot induced column bugs Fix insert/update statements Added saving functionality Added update clause for column --- dGame/LeaderboardManager.cpp | 227 ++++++++++-------- .../dlu/9_Update_Leaderboard_Storage.sql | 1 + tests/dGameTests/LeaderboardTests.cpp | 14 +- 3 files changed, 134 insertions(+), 108 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 1f436d30..826fcef3 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -273,23 +273,34 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead va_end(args); } -std::string FormatInsert(const std::string& columns, const std::string& format, va_list args, bool update) { +std::string FormatInsert(const std::string& columns, const std::string& format, va_list args, bool useUpdate) { const char* insert = "INSERT"; const char* update = "UPDATE"; - auto queryBase = "%s leaderboard SET %s WHERE id = ?;"; + const char* queryType = useUpdate ? update : insert; + + const char* scoreFilter = "character_id = ? AND game_id = ?"; + const char* usedFilter = useUpdate ? scoreFilter : ""; + constexpr uint16_t STRING_LENGTH = 400; char formattedInsert[STRING_LENGTH]; + auto queryBase = "%s leaderboard SET %s, character_id = ?, game_id = ? %s;"; + snprintf(formattedInsert, STRING_LENGTH, queryBase, queryType, format.c_str(), usedFilter); + char finishedQuery[STRING_LENGTH]; - snprintf(formattedInsert, STRING_LENGTH, queryBase, columns.c_str(), format.c_str()); vsnprintf(finishedQuery, STRING_LENGTH, formattedInsert, args); return finishedQuery; } void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list args) { + // 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::string insertStatement; std::string selectedColumns; std::string insertFormat; - // If ResultSet is empty, just insert our score. std::va_list argsCopy; va_copy(argsCopy, args); @@ -300,8 +311,8 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead break; } case Leaderboard::Type::Racing: { - selectedColumns = "bestLapTime, bestTime"; - insertFormat = "bestLapTime=%f, bestTime=%f"; + selectedColumns = "bestLapTime, bestTime, numWins"; + insertFormat = "bestLapTime=%f, bestTime=%f, numWins=%i"; break; } case Leaderboard::Type::UnusedLeaderboard4: { @@ -311,18 +322,18 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead } case Leaderboard::Type::MonumentRace: case Leaderboard::Type::FootRace: { - selectedColumns = "time"; - insertFormat = "time=%i"; + selectedColumns = "bestTime"; + insertFormat = "bestTime=%i"; break; } case Leaderboard::Type::Survival: { - selectedColumns = "points, time"; - insertFormat = "points=%i, time=%i"; + selectedColumns = "score, bestTime"; + insertFormat = "score=%i, bestTime=%i"; break; } case Leaderboard::Type::SurvivalNS: { - selectedColumns = "time, wave"; - insertFormat = "time=%i, wave=%i"; + selectedColumns = "bestTime, score"; + insertFormat = "bestTime=%i, score=%i"; break; } case Leaderboard::Type::Donations: { @@ -340,112 +351,126 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead } } const char* lookup = "SELECT %s FROM leaderboard WHERE character_id = ? AND game_id = ?;"; + constexpr uint16_t STRING_LENGTH = 400; char lookupBuffer[STRING_LENGTH]; snprintf(lookupBuffer, STRING_LENGTH, lookup, selectedColumns.c_str()); + std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer)); query->setInt(1, playerID); query->setInt(2, gameID); std::unique_ptr myScoreResult(query->executeQuery()); - switch (leaderboardType) { - case Leaderboard::Type::ShootingGallery: { - int32_t oldScore = myScoreResult->getInt("score"); - int32_t score; - score = va_arg(argsCopy, int32_t); + std::string saveQuery; + if (myScoreResult->next()) { + switch (leaderboardType) { + case Leaderboard::Type::ShootingGallery: { + int32_t oldScore = myScoreResult->getInt("score"); + int32_t score; + score = va_arg(argsCopy, int32_t); - float oldHitPercentage = myScoreResult->getFloat("hitPercentage"); - float hitPercentage; - hitPercentage = va_arg(argsCopy, double); + float oldHitPercentage = myScoreResult->getFloat("hitPercentage"); + float hitPercentage; + hitPercentage = va_arg(argsCopy, double); - int32_t oldStreak = myScoreResult->getInt("streak"); - int32_t streak; - streak = va_arg(argsCopy, int32_t); + int32_t oldStreak = myScoreResult->getInt("streak"); + int32_t streak; + streak = va_arg(argsCopy, int32_t); - 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 - // Save + 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); + } + break; } - break; - } - case Leaderboard::Type::Racing: { - float oldLapTime = myScoreResult->getFloat("bestLapTime"); - float lapTime; - lapTime = va_arg(argsCopy, double); + case Leaderboard::Type::Racing: { + float oldLapTime = myScoreResult->getFloat("bestLapTime"); + float lapTime; + lapTime = va_arg(argsCopy, double); - float oldTime = myScoreResult->getFloat("bestTime"); - float oldTime; - oldTime = va_arg(argsCopy, double); + float oldTime = myScoreResult->getFloat("bestTime"); + float newTime; + newTime = va_arg(argsCopy, double); - 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 - break; - } - case Leaderboard::Type::UnusedLeaderboard4: { - int32_t oldScore = myScoreResult->getInt("score"); - int32_t points; - points = va_arg(argsCopy, int32_t); - // Compare score, if HIGHER save - break; - } - case Leaderboard::Type::MonumentRace: { - int32_t oldTime = myScoreResult->getInt("time"); - int32_t time; - time = va_arg(argsCopy, int32_t); - // Compare time, if LOWER save - break; - } - case Leaderboard::Type::FootRace: { - int32_t oldTime = myScoreResult->getInt("time"); - int32_t time; - time = va_arg(argsCopy, int32_t); - // Compare time, if HIGHER save - break; - } - case Leaderboard::Type::Survival: { - int32_t oldPoints = myScoreResult->getInt("points"); - int32_t points; - points = va_arg(argsCopy, int32_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 + break; + } + case Leaderboard::Type::UnusedLeaderboard4: { + int32_t oldScore = myScoreResult->getInt("score"); + int32_t points; + points = va_arg(argsCopy, int32_t); + // 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); + // 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); + // Compare time, if HIGHER save + break; + } + case Leaderboard::Type::Survival: { + int32_t oldPoints = myScoreResult->getInt("score"); + int32_t points; + points = va_arg(argsCopy, int32_t); - int32_t oldTime = myScoreResult->getInt("time"); - int32_t time; - time = va_arg(argsCopy, int32_t); - // 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: { - int32_t oldTime = myScoreResult->getInt("time"); - int32_t time; - time = va_arg(argsCopy, int32_t); + int32_t oldTime = myScoreResult->getInt("bestTime"); + int32_t time; + time = va_arg(argsCopy, int32_t); + // 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: { + int32_t oldTime = myScoreResult->getInt("bestTime"); + int32_t time; + time = va_arg(argsCopy, int32_t); - int32_t oldWave = myScoreResult->getInt("wave"); - int32_t wave; - wave = va_arg(argsCopy, int32_t); - // Compare wave, if HIGHER save, if TIED compare time, if LOWER save - break; + int32_t oldWave = myScoreResult->getInt("score"); + int32_t wave; + wave = va_arg(argsCopy, int32_t); + // 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); + // 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; + } + default: + Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); + break; + } + } else { + saveQuery = FormatInsert(selectedColumns, insertFormat, argsCopy, false); } - case Leaderboard::Type::Donations: { - int32_t oldScore = myScoreResult->getInt("score"); - int32_t score; - score = va_arg(argsCopy, int32_t); - // 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; - } - default: - Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); - break; + Game::logger->Log("LeaderboardManager", "%s", saveQuery.c_str()); + if (!saveQuery.empty()) { + std::unique_ptr insertQuery(Database::CreatePreppedStmt(saveQuery)); + insertQuery->setInt(1, playerID); + insertQuery->setInt(2, gameID); + insertQuery->execute(); } va_end(argsCopy); } diff --git a/migrations/dlu/9_Update_Leaderboard_Storage.sql b/migrations/dlu/9_Update_Leaderboard_Storage.sql index d6f185dc..498e833c 100644 --- a/migrations/dlu/9_Update_Leaderboard_Storage.sql +++ b/migrations/dlu/9_Update_Leaderboard_Storage.sql @@ -7,5 +7,6 @@ ALTER TABLE leaderboard MODIFY time FLOAT NOT NULL DEFAULT 0; ALTER TABLE leaderboard CHANGE time bestTime float; +ALTER TABLE leaderboard CHANGE last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(); UPDATE leaderboard SET streak = bestTime where game_id = 1864; diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index 38e92658..8e6cb4e6 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -30,10 +30,10 @@ protected: Leaderboard leaderboard(gameID, infoType, false, 14231, type); leaderboard.SetupLeaderboard(); leaderboard.Serialize(&bitStream); - TestLeaderboard(leaderboard, 1); - TestLeaderboard(leaderboard, 10); - TestLeaderboard(leaderboard, 100); - TestLeaderboard(leaderboard, 1000); + // TestLeaderboard(leaderboard, 1); + // TestLeaderboard(leaderboard, 10); + // TestLeaderboard(leaderboard, 100); + // TestLeaderboard(leaderboard, 1000); } CBITSTREAM; @@ -73,9 +73,9 @@ 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, 0, Leaderboard::Type::ShootingGallery, 3, 3000, 15.0f, 100); + // 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, 53001, 15.0f, 100); // RunTests(0, Leaderboard::Type::Racing); LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::Racing, 3, 260.0f, 250.0f, true); // RunTests(0, Leaderboard::Type::MonumentRace); From 0f307ac4be582950f306a275c0dd35ad0ef9187c Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Mon, 8 May 2023 02:46:55 -0700 Subject: [PATCH 19/59] Fix bugs - Reinforce Query formatting - Guarantee 11 rows are selected at a time by ranking by id, should there be more than an 11 way tie. --- dGame/LeaderboardManager.cpp | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 826fcef3..83703724 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -69,7 +69,7 @@ void Leaderboard::SetupLeaderboard() { SELECT leaderboard.*, charinfo.name, RANK() OVER ( - ORDER BY %s + 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 @@ -274,16 +274,17 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead } std::string FormatInsert(const std::string& columns, const std::string& format, va_list args, bool useUpdate) { - const char* insert = "INSERT"; - const char* update = "UPDATE"; - const char* queryType = useUpdate ? update : insert; + const char* insertClause = "INSERT"; + const char* updateClause = "UPDATE"; + const char* queryType = useUpdate ? updateClause : insertClause; - const char* scoreFilter = "character_id = ? AND game_id = ?"; - const char* usedFilter = useUpdate ? scoreFilter : ""; + const char* insertFilter = ", character_id = ?, game_id = ?"; + const char* updateFilter = "WHERE character_id = ? AND game_id = ?"; + const char* usedFilter = useUpdate ? updateFilter : insertFilter; constexpr uint16_t STRING_LENGTH = 400; char formattedInsert[STRING_LENGTH]; - auto queryBase = "%s leaderboard SET %s, character_id = ?, game_id = ? %s;"; + auto queryBase = "%s leaderboard SET %s %s;"; snprintf(formattedInsert, STRING_LENGTH, queryBase, queryType, format.c_str(), usedFilter); char finishedQuery[STRING_LENGTH]; @@ -406,6 +407,9 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead int32_t oldScore = myScoreResult->getInt("score"); int32_t points; points = va_arg(argsCopy, int32_t); + if (points > oldScore) { + saveQuery = FormatInsert(selectedColumns, insertFormat, args, true); + } // Compare score, if HIGHER save break; } @@ -413,6 +417,9 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead int32_t oldTime = myScoreResult->getInt("bestTime"); int32_t time; time = va_arg(argsCopy, int32_t); + if (time < oldTime) { + saveQuery = FormatInsert(selectedColumns, insertFormat, args, true); + } // Compare time, if LOWER save break; } @@ -420,6 +427,9 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead int32_t oldTime = myScoreResult->getInt("bestTime"); int32_t time; time = va_arg(argsCopy, int32_t); + if (time < oldTime) { + saveQuery = FormatInsert(selectedColumns, insertFormat, args, true); + } // Compare time, if HIGHER save break; } @@ -431,6 +441,9 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead 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); + } // 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; @@ -443,6 +456,9 @@ 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); + } // Compare wave, if HIGHER save, if TIED compare time, if LOWER save break; } @@ -450,6 +466,9 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead 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; } @@ -458,6 +477,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead Game::logger->Log("LeaderboardManager", "Warning: Saving score for leaderboard of type None. Are you sure this is intended?"); break; } + default: Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); break; From 1c7ac93d4b75a55c4f58360311568ae51bfeb9af Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Mon, 8 May 2023 03:55:10 -0700 Subject: [PATCH 20/59] Fix various bugs and make code cleaner. Still have work to go. --- dGame/LeaderboardManager.cpp | 26 +++++++++---------- .../dlu/9_Update_Leaderboard_Storage.sql | 2 +- tests/dGameTests/LeaderboardTests.cpp | 16 ++++++------ 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 83703724..44ef17cc 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -64,7 +64,7 @@ bool Leaderboard::GetRankingQuery(std::string& lookupReturn) const { void Leaderboard::SetupLeaderboard() { std::string queryBase = - R"QUERY( + R"QUERY( WITH leaderboardsRanked AS ( SELECT leaderboard.*, charinfo.name, RANK() OVER @@ -278,8 +278,8 @@ std::string FormatInsert(const std::string& columns, const std::string& format, const char* updateClause = "UPDATE"; const char* queryType = useUpdate ? updateClause : insertClause; - const char* insertFilter = ", character_id = ?, game_id = ?"; - const char* updateFilter = "WHERE character_id = ? AND game_id = ?"; + const char* insertFilter = ", character_id = ?, game_id = ?, timesPlayed = 1"; + const char* updateFilter = ", timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?"; const char* usedFilter = useUpdate ? updateFilter : insertFilter; constexpr uint16_t STRING_LENGTH = 400; @@ -293,12 +293,6 @@ std::string FormatInsert(const std::string& columns, const std::string& format, } void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, va_list args) { - // 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::string insertStatement; std::string selectedColumns; std::string insertFormat; @@ -342,10 +336,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead insertFormat = "score=%i"; break; } - case Leaderboard::Type::None: { - Game::logger->Log("LeaderboardManager", "Warning: Saving leaderboard of type None. Are you sure this is intended?"); - break; - } + case Leaderboard::Type::None: default: { Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); return; @@ -485,12 +476,19 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead } else { saveQuery = FormatInsert(selectedColumns, insertFormat, argsCopy, false); } - Game::logger->Log("LeaderboardManager", "%s", saveQuery.c_str()); 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(); + } 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(); } va_end(argsCopy); } diff --git a/migrations/dlu/9_Update_Leaderboard_Storage.sql b/migrations/dlu/9_Update_Leaderboard_Storage.sql index 498e833c..c90ba4a2 100644 --- a/migrations/dlu/9_Update_Leaderboard_Storage.sql +++ b/migrations/dlu/9_Update_Leaderboard_Storage.sql @@ -7,6 +7,6 @@ ALTER TABLE leaderboard MODIFY time FLOAT NOT NULL DEFAULT 0; ALTER TABLE leaderboard CHANGE time bestTime float; -ALTER TABLE leaderboard CHANGE last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(); +ALTER TABLE leaderboard CHANGE last_played last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(); UPDATE leaderboard SET streak = bestTime where game_id = 1864; diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index 8e6cb4e6..a4b7b9c0 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -75,19 +75,19 @@ 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, 53001, 15.0f, 100); + 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, 260.0f, 250.0f, true); + 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, 150); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::MonumentRace, 1, 149); // RunTests(0, Leaderboard::Type::FootRace); - LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::FootRace, 1, 150); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::FootRace, 1, 151); // RunTests(0, Leaderboard::Type::UnusedLeaderboard4); - LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::UnusedLeaderboard4, 1, 100); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::UnusedLeaderboard4, 1, 101); // RunTests(0, Leaderboard::Type::Survival); - LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::Survival, 2, 3000, 15); + 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, 300, 15); + 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, 300000); + LeaderboardManager::Instance().SaveScore(14231, 0, Leaderboard::Type::Donations, 1, 300001); } 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 21/59] 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); } From d98ad4b94f9e2cc23641666c162d5ddca9eae111 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 May 2023 19:35:19 -0700 Subject: [PATCH 22/59] Implement leaderboard page offsets --- dCommon/Metrics.cpp | 1 - dGame/LeaderboardManager.cpp | 27 ++++++++++++++++++--------- dGame/LeaderboardManager.h | 4 ++-- dGame/dGameMessages/GameMessages.cpp | 16 ++-------------- dGame/dGameMessages/GameMessages.h | 2 +- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/dCommon/Metrics.cpp b/dCommon/Metrics.cpp index 522bcf61..b97b5435 100644 --- a/dCommon/Metrics.cpp +++ b/dCommon/Metrics.cpp @@ -14,7 +14,6 @@ std::vector Metrics::m_Variables = { MetricVariable::CPUTime, MetricVariable::Sleep, MetricVariable::Frame, - MetricVariable::Leaderboard, }; void Metrics::AddMeasurement(MetricVariable variable, int64_t value) { diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index d6713518..4b56fb60 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -33,6 +33,9 @@ void Leaderboard::WriteLeaderboardRow(std::ostringstream& leaderboard, const uin } void Leaderboard::Serialize(RakNet::BitStream* bitStream) { + bitStream->Write(gameID); + bitStream->Write(leaderboardType); + std::ostringstream leaderboard; leaderboard << "ADO.Result=7:1\n"; // Unused in 1.10.64, but is in captures @@ -49,7 +52,9 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) { } // Serialize the thing to a BitStream - bitStream->Write(leaderboard.str().c_str(), leaderboard.tellp()); + bitStream->WriteAlignedBytes((const unsigned char*)leaderboard.str().c_str(), leaderboard.tellp()); + bitStream->Write0(); + bitStream->Write0(); } bool Leaderboard::GetRankingQuery(std::string& lookupReturn) const { @@ -234,7 +239,7 @@ std::string Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { return orderBase; } -void Leaderboard::SetupLeaderboard() { +void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { std::string queryBase = R"QUERY( WITH leaderboardsRanked AS ( @@ -259,9 +264,9 @@ void Leaderboard::SetupLeaderboard() { 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) + LEAST(GREATEST(CAST(myRank AS SIGNED) - 5, %i), lowestRanking.lowestRank - 10) AND - LEAST(GREATEST(myRank + 5, 11), lowestRanking.lowestRank) + LEAST(GREATEST(myRank + 5, %i), lowestRanking.lowestRank) ORDER BY ranking ASC; )QUERY"; @@ -289,8 +294,14 @@ void Leaderboard::SetupLeaderboard() { 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()); + if (this->infoType == InfoType::Friends) { + snprintf(lookupBuffer, STRING_LENGTH, queryBase.c_str(), + orderBase.c_str(), friendsQuery, selectBase.c_str(), resultStart + 1, resultEnd + 1); + } + else { + snprintf(lookupBuffer, STRING_LENGTH, queryBase.c_str(), + orderBase.c_str(), "", selectBase.c_str(), resultStart + 1, resultEnd + 1); + } std::string baseLookupStr; char baseRankingBuffer[STRING_LENGTH]; @@ -328,7 +339,7 @@ void Leaderboard::SetupLeaderboard() { QueryToLdf(result); } -void Leaderboard::Send(LWOOBJID targetID) const { +void Leaderboard::Send(LWOOBJID targetID) { auto* player = EntityManager::Instance()->GetEntity(relatedPlayer); if (player != nullptr) { GameMessages::SendActivitySummaryLeaderboardData(targetID, this, player->GetSystemAddress()); @@ -516,14 +527,12 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead 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(); leaderboard.Send(targetID); } -// Done Leaderboard::Type LeaderboardManager::GetLeaderboardType(const GameID gameID) { auto lookup = leaderboardCache.find(gameID); if (lookup != leaderboardCache.end()) return lookup->second; diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index c6c9b0e1..3d3e4b70 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -64,12 +64,12 @@ public: /** * Builds the leaderboard from the database based on the associated gameID */ - void SetupLeaderboard(); + void SetupLeaderboard(uint32_t resultStart = 0, uint32_t resultEnd = 10); /** * Sends the leaderboard to the client specified by targetID. */ - void Send(LWOOBJID targetID) const; + void Send(LWOOBJID targetID); // Helper functions to get the columns, ordering and insert format for a leaderboard static std::string GetColumns(Type leaderboardType); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index b999eb61..ec24248b 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1631,26 +1631,14 @@ void GameMessages::HandleActivitySummaryLeaderboardData(RakNet::BitStream* instr Game::logger->Log("AGS", "We got mail!"); } -void GameMessages::SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, const Leaderboard* leaderboard, const SystemAddress& sysAddr) { +void GameMessages::SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, Leaderboard* leaderboard, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; bitStream.Write(objectID); bitStream.Write(GAME_MSG::GAME_MSG_SEND_ACTIVITY_SUMMARY_LEADERBOARD_DATA); - throw ""; - //bitStream.Write(leaderboard->GetGameID()); - //bitStream.Write(leaderboard->GetInfoType()); - // Leaderboard is written back as LDF string - //const auto leaderboardString = leaderboard->ToString(); - //bitStream.Write(leaderboardString.size()); - //for (const auto c : leaderboardString) { - // bitStream.Write(c); - //} - //if (!leaderboardString.empty()) bitStream.Write(uint16_t(0)); - - bitStream.Write0(); - bitStream.Write0(); + leaderboard->Serialize(&bitStream); SEND_PACKET; } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index ce24a105..7f358f59 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -561,7 +561,7 @@ namespace GameMessages { void SendUpdateReputation(const LWOOBJID objectId, const int64_t reputation, const SystemAddress& sysAddr); // Leaderboards - void SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, const Leaderboard* leaderboard, + void SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, Leaderboard* leaderboard, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); void HandleActivitySummaryLeaderboardData(RakNet::BitStream* instream, Entity* entity, const SystemAddress& sysAddr); void SendRequestActivitySummaryLeaderboardData(const LWOOBJID& objectID, const LWOOBJID& targetID, From 6c2312fe87c9a514b3b42baccd92be16ea206a08 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 May 2023 19:59:10 -0700 Subject: [PATCH 23/59] Implement page fetching --- dGame/LeaderboardManager.cpp | 12 +++++++----- dGame/LeaderboardManager.h | 5 ++++- dGame/dGameMessages/GameMessages.cpp | 8 +++----- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 4b56fb60..c662bd6d 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -240,6 +240,8 @@ std::string Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { } void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { + resultStart++; + resultEnd++; std::string queryBase = R"QUERY( WITH leaderboardsRanked AS ( @@ -296,11 +298,11 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { // 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(), resultStart + 1, resultEnd + 1); + orderBase.c_str(), friendsQuery, selectBase.c_str(), resultStart, resultEnd); } else { snprintf(lookupBuffer, STRING_LENGTH, queryBase.c_str(), - orderBase.c_str(), "", selectBase.c_str(), resultStart + 1, resultEnd + 1); + orderBase.c_str(), "", selectBase.c_str(), resultStart, resultEnd); } std::string baseLookupStr; @@ -527,10 +529,10 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead va_end(argsCopy); } -void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, LWOOBJID playerID) { +void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, uint32_t resultStart, uint32_t resultEnd) { Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID)); - leaderboard.SetupLeaderboard(); - leaderboard.Send(targetID); + leaderboard.SetupLeaderboard(resultStart, resultEnd); + leaderboard.Send(playerID); } Leaderboard::Type LeaderboardManager::GetLeaderboardType(const GameID gameID) { diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 3d3e4b70..ab70ae88 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -63,6 +63,9 @@ public: /** * Builds the leaderboard from the database based on the associated gameID + * + * @param resultStart The index to start the leaderboard at. Zero indexed. + * @param resultEnd The index to end the leaderboard at. Zero indexed. */ void SetupLeaderboard(uint32_t resultStart = 0, uint32_t resultEnd = 10); @@ -96,7 +99,7 @@ private: class LeaderboardManager : public Singleton { typedef std::map LeaderboardCache; public: - void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID targetID, LWOOBJID playerID = LWOOBJID_EMPTY); + void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, uint32_t resultStart = 0, uint32_t resultEnd = 10); // 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) { diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index ec24248b..55f40037 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1647,8 +1647,8 @@ void GameMessages::HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream int32_t gameID = 0; if (inStream->ReadBit()) inStream->Read(gameID); - int32_t queryType = 1; - if (inStream->ReadBit()) inStream->Read(queryType); + Leaderboard::InfoType queryType = Leaderboard::InfoType::MyStanding; + if (inStream->ReadBit()) inStream->Read(queryType); int32_t resultsEnd = 10; if (inStream->ReadBit()) inStream->Read(resultsEnd); @@ -1661,9 +1661,7 @@ void GameMessages::HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream bool weekly = inStream->ReadBit(); - //const auto* leaderboard = LeaderboardManager::GetLeaderboard(gameID, (InfoType)queryType, weekly, entity->GetObjectID()); - //SendActivitySummaryLeaderboardData(entity->GetObjectID(), leaderboard, sysAddr); - //delete leaderboard; + LeaderboardManager::Instance().SendLeaderboard(gameID, queryType, weekly, target, resultsStart, resultsEnd); } void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream* inStream, Entity* entity) { From fcf5c5ea8a722b63256bed616ab2ac86516ee412 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Tue, 9 May 2023 00:06:26 -0700 Subject: [PATCH 24/59] Resolve compiler errors --- dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp | 1 + tests/dGameTests/LeaderboardTests.cpp | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp b/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp index 4d1ae5f5..1c3fb830 100644 --- a/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp +++ b/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp @@ -1,6 +1,7 @@ #include "BaseFootRaceManager.h" #include "EntityManager.h" #include "Character.h" +#include "Entity.h" void BaseFootRaceManager::OnStartup(Entity* self) { // TODO: Add to FootRaceStarter group diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index fbe76df4..b3480f51 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -75,11 +75,4 @@ 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().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); } From 411c9eaf1f4ddbbc3a8c7fde7c629780db439d6d Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Tue, 9 May 2023 00:06:43 -0700 Subject: [PATCH 25/59] Remove dead code --- dGame/LeaderboardManager.h | 50 ++++++-------------------------------- 1 file changed, 7 insertions(+), 43 deletions(-) diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index ab70ae88..ce614555 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include "Singleton.h" @@ -38,7 +39,7 @@ public: Survival, SurvivalNS, Donations, - None = UINT_MAX + None }; Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type = None); @@ -101,46 +102,6 @@ class LeaderboardManager : public Singleton { public: void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, uint32_t resultStart = 0, uint32_t resultEnd = 10); - // 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. * @@ -151,7 +112,10 @@ private: */ void SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t argumentCount, ...); - Leaderboard::Type GetLeaderboardType(const GameID gameID); - LeaderboardCache leaderboardCache; + static Leaderboard::Type GetLeaderboardType(const GameID gameID); + static LeaderboardCache leaderboardCache; +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); }; From 7a067e7b48c63798fd929703996d89239465c470 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Tue, 9 May 2023 01:42:11 -0700 Subject: [PATCH 26/59] Working in game! - Add score saving for races - Fix some bugs - Still work to do. --- dGame/LeaderboardManager.cpp | 34 ++++++++++++------- dGame/dComponents/RacingControlComponent.cpp | 7 ++-- dGame/dGameMessages/GameMessages.cpp | 4 +-- .../02_server/Map/AG/NpcAgCourseStarter.cpp | 3 +- dScripts/ActivityManager.cpp | 27 ++++++++------- dScripts/ActivityManager.h | 1 + .../ai/ACT/FootRace/BaseFootRaceManager.cpp | 1 + 7 files changed, 46 insertions(+), 31 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index c662bd6d..c350dda8 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -16,6 +16,8 @@ #include "CDActivitiesTable.h" #include "Metrics.hpp" +LeaderboardManager::LeaderboardCache LeaderboardManager::leaderboardCache = {}; + Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type leaderboardType) { this->gameID = gameID; this->weekly = weekly; @@ -34,10 +36,10 @@ void Leaderboard::WriteLeaderboardRow(std::ostringstream& leaderboard, const uin void Leaderboard::Serialize(RakNet::BitStream* bitStream) { bitStream->Write(gameID); - bitStream->Write(leaderboardType); + bitStream->Write(infoType); std::ostringstream leaderboard; - + Game::logger->Log("LeaderboardManager", "game is %i info type %i ", gameID, infoType); leaderboard << "ADO.Result=7:1\n"; // Unused in 1.10.64, but is in captures leaderboard << "Result.Count=1:1\n"; // number of results, always 1? leaderboard << "Result[0].Index=0:RowNumber\n"; // "Primary key" @@ -46,13 +48,18 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) { int32_t rowNumber = 0; for (auto& entry : entries) { for (auto* data : entry) { + Game::logger->Log("LeaderboardManager", "writing data %s", data->GetString().c_str()); WriteLeaderboardRow(leaderboard, rowNumber, data); } rowNumber++; } - + Game::logger->Log("LeaderboardManager", "leaderboard is %s", leaderboard.str().c_str()); // Serialize the thing to a BitStream - bitStream->WriteAlignedBytes((const unsigned char*)leaderboard.str().c_str(), leaderboard.tellp()); + uint32_t leaderboardSize = leaderboard.tellp(); + bitStream->Write(leaderboardSize); + // Doing this all in 1 call so there is no possbility of a dangling pointer. + bitStream->WriteAlignedBytes(reinterpret_cast(GeneralUtils::ASCIIToUTF16(leaderboard.str()).c_str()), leaderboard.tellp() * 2); + if (leaderboardSize > 0) bitStream->Write(0); bitStream->Write0(); bitStream->Write0(); } @@ -80,7 +87,7 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { 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(rows->getString("name").c_str()))); - entry.push_back(new LDFData(u"RowNumber", rows->getInt("ranking"))); + entry.push_back(new LDFData(u"RowNumber", rows->getInt("ranking"))); switch (leaderboardType) { case Type::ShootingGallery: entry.push_back(new LDFData(u"HitPercentage", rows->getDouble("hitPercentage"))); @@ -101,7 +108,7 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { // NumWins:1 break; case Type::UnusedLeaderboard4: - entry.push_back(new LDFData(u"Score", rows->getInt("score"))); + entry.push_back(new LDFData(u"Points", rows->getInt("score"))); // Points:1 break; case Type::MonumentRace: @@ -113,19 +120,19 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { // Time:1 break; case Type::Survival: - entry.push_back(new LDFData(u"Score", rows->getInt("score"))); + entry.push_back(new LDFData(u"Points", rows->getInt("score"))); // Points:1 entry.push_back(new LDFData(u"Time", rows->getInt("bestTime"))); // Time:1 break; case Type::SurvivalNS: - entry.push_back(new LDFData(u"Score", rows->getInt("score"))); + entry.push_back(new LDFData(u"Wave", rows->getInt("score"))); // Wave:1 entry.push_back(new LDFData(u"Time", rows->getInt("bestTime"))); // Time:1 break; case Type::Donations: - entry.push_back(new LDFData(u"Score", rows->getInt("score"))); + entry.push_back(new LDFData(u"Points", rows->getInt("score"))); // Score:1 break; case Type::None: @@ -320,11 +327,13 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { } std::unique_ptr baseResult(baseQuery->executeQuery()); + Game::logger->Log("LeaderboardManager", "%s", baseRankingBuffer); 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 + Game::logger->Log("LeaderboardManager", "query is %s", lookupBuffer); std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer)); query->setInt(1, this->gameID); @@ -461,7 +470,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead int32_t time; time = va_arg(argsCopy, int32_t); - if (time < oldTime) { + if (time > oldTime) { saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true); } break; @@ -507,7 +516,8 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead case Leaderboard::Type::None: default: Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); - break; + va_end(argsCopy); + return; } } else { saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, false); @@ -543,7 +553,7 @@ Leaderboard::Type LeaderboardManager::GetLeaderboardType(const GameID gameID) { std::vector activities = activitiesTable->Query([=](const CDActivities& entry) { return (entry.ActivityID == gameID); }); - auto type = activities.empty() ? static_cast(activities.at(0).leaderboardType) : Leaderboard::Type::None; + auto type = !activities.empty() ? static_cast(activities.at(0).leaderboardType) : Leaderboard::Type::None; leaderboardCache.insert_or_assign(gameID, type); return type; } diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 703ec7f3..f74c92fa 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -23,6 +23,7 @@ #include "dConfig.h" #include "Loot.h" #include "eMissionTaskType.h" +#include "LeaderboardManager.h" #ifndef M_PI #define M_PI 3.14159265358979323846264338327950288 @@ -391,9 +392,7 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player, } if (id == "rewardButton") { - if (data->collectedRewards) { - return; - } + if (data->collectedRewards) return; data->collectedRewards = true; @@ -401,6 +400,8 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player, const auto score = m_LoadedPlayers * 10 + data->finished; LootGenerator::Instance().GiveActivityLoot(player, m_Parent, m_ActivityID, score); + auto leaderboardType = LeaderboardManager::Instance().GetLeaderboardType(m_ActivityID); + LeaderboardManager::Instance().SaveScore(player->GetObjectID(), m_ActivityID, leaderboardType, 3, data->bestLapTime, data->raceTime, data->finished == 1); // Giving rewards GameMessages::SendNotifyRacingClient( diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 55f40037..992311cb 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1639,7 +1639,7 @@ void GameMessages::SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, bitStream.Write(GAME_MSG::GAME_MSG_SEND_ACTIVITY_SUMMARY_LEADERBOARD_DATA); leaderboard->Serialize(&bitStream); - + PacketUtils::SavePacket("leaderboardData.bin", (const char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed()); SEND_PACKET; } @@ -1661,7 +1661,7 @@ void GameMessages::HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream bool weekly = inStream->ReadBit(); - LeaderboardManager::Instance().SendLeaderboard(gameID, queryType, weekly, target, resultsStart, resultsEnd); + LeaderboardManager::Instance().SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), resultsStart, resultsEnd); } void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream* inStream, Entity* entity) { diff --git a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp index 7da4ccef..03ed9328 100644 --- a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp +++ b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp @@ -96,8 +96,7 @@ void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std } EntityManager::Instance()->SerializeEntity(self); - //LeaderboardManager::SaveScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), - // 0, (uint32_t)finish); + // LeaderboardManager::Instance().SaveFootRaceScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), finish); GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard", scriptedActivityComponent->GetActivityID(), 0, sender->GetObjectID(), diff --git a/dScripts/ActivityManager.cpp b/dScripts/ActivityManager.cpp index acfa5a68..3e039024 100644 --- a/dScripts/ActivityManager.cpp +++ b/dScripts/ActivityManager.cpp @@ -73,24 +73,27 @@ void ActivityManager::StopActivity(Entity* self, const LWOOBJID playerID, const LootGenerator::Instance().GiveActivityLoot(player, self, gameID, CalculateActivityRating(self, playerID)); - // Save the new score to the leaderboard and show the leaderboard to the player - //LeaderboardManager::SaveScore(playerID, gameID, score, value1); - //const auto* leaderboard = LeaderboardManager::GetLeaderboard(gameID, InfoType::Standings, - // false, player->GetObjectID()); - //GameMessages::SendActivitySummaryLeaderboardData(self->GetObjectID(), leaderboard, player->GetSystemAddress()); - //delete leaderboard; - - // Makes the leaderboard show up for the player - GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard", - gameID, 0, playerID, "", - player->GetSystemAddress()); - if (sac != nullptr) { sac->PlayerRemove(player->GetObjectID()); } } } +void ActivityManager::SaveScore(Entity* self, LWOOBJID playerID, uint32_t val1, uint32_t val2, uint32_t val3) { + auto* player = EntityManager::Instance()->GetEntity(playerID); + if (!player) return; + + auto* sac = self->GetComponent(); + uint32_t gameID = sac != nullptr ? sac->GetActivityID() : self->GetLOT(); + // Save the new score to the leaderboard and show the leaderboard to the player + auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID); + Game::logger->Log("ActivityManager", "leaderboard type %i %i", leaderboardType, gameID); + LeaderboardManager::Instance().SaveScore(playerID, gameID, leaderboardType, 3, val1, val2, val3); + + // Makes the leaderboard show up for the player + GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard", gameID, 0, playerID, "", player->GetSystemAddress()); +} + bool ActivityManager::TakeActivityCost(const Entity* self, const LWOOBJID playerID) { auto* sac = self->GetComponent(); if (sac == nullptr) diff --git a/dScripts/ActivityManager.h b/dScripts/ActivityManager.h index 640cf4bf..633fd5d8 100644 --- a/dScripts/ActivityManager.h +++ b/dScripts/ActivityManager.h @@ -18,6 +18,7 @@ public: static bool TakeActivityCost(const Entity* self, LWOOBJID playerID); static uint32_t GetActivityID(const Entity* self); void StopActivity(Entity* self, LWOOBJID playerID, uint32_t score, uint32_t value1 = 0, uint32_t value2 = 0, bool quit = false); + void SaveScore(Entity* self, LWOOBJID playerID, uint32_t val1 = 0, uint32_t val2 = 0, uint32_t val3 = 0); virtual uint32_t CalculateActivityRating(Entity* self, LWOOBJID playerID); static void GetLeaderboardData(Entity* self, LWOOBJID playerID, uint32_t activityID, uint32_t numResults = 0); // void FreezePlayer(Entity *self, const LWOOBJID playerID, const bool state) const; diff --git a/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp b/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp index 1c3fb830..3efb5ff4 100644 --- a/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp +++ b/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp @@ -42,6 +42,7 @@ void BaseFootRaceManager::OnFireEventServerSide(Entity* self, Entity* sender, st } StopActivity(self, player->GetObjectID(), 0, param1); + SaveScore(self, player->GetObjectID(), param1, param2, param3); } } } From 3448426cafe75d3d23a7207c901faa9177fcc9be Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Tue, 9 May 2023 22:00:13 -0700 Subject: [PATCH 27/59] commit --- dGame/LeaderboardManager.cpp | 10 +++++----- dGame/dGameMessages/GameMessageHandler.cpp | 2 +- dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp | 12 +++++------- dScripts/ActivityManager.cpp | 7 ++++++- dScripts/BaseSurvivalServer.cpp | 1 + dScripts/BaseWavesServer.cpp | 1 + dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 2 +- 7 files changed, 20 insertions(+), 15 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index c350dda8..22e107d0 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -31,7 +31,7 @@ Leaderboard::~Leaderboard() { } void Leaderboard::WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data) { - leaderboard << "Result[0].Row[" << index << "]." << data->GetString() << '\n'; + leaderboard << "\nResult[0].Row[" << index << "]." << data->GetString(); } void Leaderboard::Serialize(RakNet::BitStream* bitStream) { @@ -40,10 +40,10 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) { std::ostringstream leaderboard; Game::logger->Log("LeaderboardManager", "game is %i info type %i ", gameID, infoType); - leaderboard << "ADO.Result=7:1\n"; // Unused in 1.10.64, but is in captures - leaderboard << "Result.Count=1:1\n"; // number of results, always 1? - leaderboard << "Result[0].Index=0:RowNumber\n"; // "Primary key" - leaderboard << "Result[0].RowCount=1:" << entries.size() << '\n'; // number of rows + leaderboard << "ADO.Result=7:1"; // Unused in 1.10.64, but is in captures + leaderboard << "\nResult.Count=1:1"; // number of results, always 1? + if (!this->entries.empty()) leaderboard << "\nResult[0].Index=0:RowNumber"; // "Primary key" + leaderboard << "\nResult[0].RowCount=1:" << entries.size(); // number of rows int32_t rowNumber = 0; for (auto& entry : entries) { diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index c0893a09..8a2238c9 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -44,7 +44,7 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System Entity* entity = EntityManager::Instance()->GetEntity(objectID); User* usr = UserManager::Instance()->GetUser(sysAddr); - + if (messageID != 888) Game::logger->Log("GameMessageHandler", "message %i", messageID); if (!entity) { Game::logger->Log("GameMessageHandler", "Failed to find associated entity (%llu), aborting GM (%X)!", objectID, messageID); diff --git a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp index 03ed9328..670fd7ee 100644 --- a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp +++ b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp @@ -68,15 +68,12 @@ void NpcAgCourseStarter::OnMessageBoxResponse(Entity* self, Entity* sender, int3 } } -void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, - int32_t param3) { +void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) { auto* scriptedActivityComponent = self->GetComponent(); - if (scriptedActivityComponent == nullptr) - return; + if (scriptedActivityComponent == nullptr) return; auto* data = scriptedActivityComponent->GetActivityPlayerData(sender->GetObjectID()); - if (data == nullptr) - return; + if (data == nullptr) return; if (args == "course_cancel") { GameMessages::SendNotifyClientObject(self->GetObjectID(), u"cancel_timer", 0, 0, @@ -96,7 +93,8 @@ void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std } EntityManager::Instance()->SerializeEntity(self); - // LeaderboardManager::Instance().SaveFootRaceScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), finish); + auto leaderboardType = LeaderboardManager::GetLeaderboardType(scriptedActivityComponent->GetActivityID()); + LeaderboardManager::Instance().SaveScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), leaderboardType, 1, finish); GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard", scriptedActivityComponent->GetActivityID(), 0, sender->GetObjectID(), diff --git a/dScripts/ActivityManager.cpp b/dScripts/ActivityManager.cpp index 3e039024..d9bf56cd 100644 --- a/dScripts/ActivityManager.cpp +++ b/dScripts/ActivityManager.cpp @@ -120,7 +120,12 @@ uint32_t ActivityManager::GetActivityID(const Entity* self) { } void ActivityManager::GetLeaderboardData(Entity* self, const LWOOBJID playerID, const uint32_t activityID, uint32_t numResults) { - //LeaderboardManager::SendLeaderboard(activityID, Standings, false, self->GetObjectID(), playerID); + auto* sac = self->GetComponent(); + uint32_t gameID = sac != nullptr ? sac->GetActivityID() : self->GetLOT(); + // Save the new score to the leaderboard and show the leaderboard to the player + auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID); + Game::logger->Log("ActivityManager", "gameID %i", gameID, activityID); + LeaderboardManager::Instance().SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, 0, numResults); } void ActivityManager::ActivityTimerStart(Entity* self, const std::string& timerName, const float_t updateInterval, diff --git a/dScripts/BaseSurvivalServer.cpp b/dScripts/BaseSurvivalServer.cpp index 3d72628d..ac775787 100644 --- a/dScripts/BaseSurvivalServer.cpp +++ b/dScripts/BaseSurvivalServer.cpp @@ -377,6 +377,7 @@ void BaseSurvivalServer::GameOver(Entity* self) { } StopActivity(self, playerID, score, time); + SaveScore(self, playerID, time, score); } state.waveNumber = 1; diff --git a/dScripts/BaseWavesServer.cpp b/dScripts/BaseWavesServer.cpp index 00340bd0..48144960 100644 --- a/dScripts/BaseWavesServer.cpp +++ b/dScripts/BaseWavesServer.cpp @@ -378,6 +378,7 @@ void BaseWavesServer::GameOver(Entity* self, bool won) { } StopActivity(self, playerID, wave, time, score); + SaveScore(self, playerID, time, wave); } } diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index bdd7a506..ac9d13ee 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -349,7 +349,7 @@ void SGCannon::StartGame(Entity* self) { auto* player = EntityManager::Instance()->GetEntity(self->GetVar(PlayerIDVariable)); if (player != nullptr) { - GetLeaderboardData(self, player->GetObjectID(), GetActivityID(self)); + GetLeaderboardData(self, player->GetObjectID(), GetActivityID(self), 1); Game::logger->Log("SGCannon", "Sending ActivityStart"); GameMessages::SendActivityStart(self->GetObjectID(), player->GetSystemAddress()); From 4dba8d9225c537c901c0edd5e521d90eadf3fb15 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Tue, 9 May 2023 22:21:41 -0700 Subject: [PATCH 28/59] Correct column order --- dGame/LeaderboardManager.cpp | 6 +++--- dGame/LeaderboardManager.h | 2 +- dGame/dGameMessages/GameMessages.cpp | 2 +- dScripts/ActivityManager.cpp | 2 +- dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 22e107d0..0c89030e 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -182,7 +182,7 @@ std::string Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { std::string columns; switch (leaderboardType) { case Type::ShootingGallery: - columns = "hitPercentage=%f, score=%i, streak=%i"; + columns = "score=%i, hitPercentage=%f, streak=%i"; break; case Type::Racing: columns = "bestLapTime=%i, bestTime=%i, numWins=numWins + %i"; @@ -539,10 +539,10 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead va_end(argsCopy); } -void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, uint32_t resultStart, uint32_t resultEnd) { +void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, LWOOBJID targetID, uint32_t resultStart, uint32_t resultEnd) { Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID)); leaderboard.SetupLeaderboard(resultStart, resultEnd); - leaderboard.Send(playerID); + leaderboard.Send(targetID); } Leaderboard::Type LeaderboardManager::GetLeaderboardType(const GameID gameID) { diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index ce614555..f84e4228 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -100,7 +100,7 @@ private: class LeaderboardManager : public Singleton { typedef std::map LeaderboardCache; public: - void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, 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); /** * @brief Public facing Score saving method. This method is simply a wrapper to ensure va_end is called properly. diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 992311cb..dae0636a 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1661,7 +1661,7 @@ void GameMessages::HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream bool weekly = inStream->ReadBit(); - LeaderboardManager::Instance().SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), resultsStart, resultsEnd); + LeaderboardManager::Instance().SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), entity->GetObjectID(), resultsStart, resultsEnd); } void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream* inStream, Entity* entity) { diff --git a/dScripts/ActivityManager.cpp b/dScripts/ActivityManager.cpp index d9bf56cd..5b361510 100644 --- a/dScripts/ActivityManager.cpp +++ b/dScripts/ActivityManager.cpp @@ -125,7 +125,7 @@ void ActivityManager::GetLeaderboardData(Entity* self, const LWOOBJID playerID, // Save the new score to the leaderboard and show the leaderboard to the player auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID); Game::logger->Log("ActivityManager", "gameID %i", gameID, activityID); - LeaderboardManager::Instance().SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, 0, numResults); + LeaderboardManager::Instance().SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, self->GetObjectID(), 0, numResults); } void ActivityManager::ActivityTimerStart(Entity* self, const std::string& timerName, const float_t updateInterval, diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index ac9d13ee..098d9e55 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -565,6 +565,7 @@ void SGCannon::StopGame(Entity* self, bool cancel) { LootGenerator::Instance().GiveActivityLoot(player, self, GetGameID(self), self->GetVar(TotalScoreVariable)); StopActivity(self, player->GetObjectID(), self->GetVar(TotalScoreVariable), self->GetVar(MaxStreakVariable), percentage); + SaveScore(self, player->GetObjectID(), self->GetVar(TotalScoreVariable), percentage, self->GetVar(MaxStreakVariable)); self->SetNetworkVar(AudioFinalWaveDoneVariable, true); // Give the player the model rewards they earned From af1abe9e7412d452d1dd3aabb317a6fd9323f2a1 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Wed, 10 May 2023 01:32:55 -0700 Subject: [PATCH 29/59] Use only ints --- dGame/LeaderboardManager.cpp | 8 ++++---- dScripts/ActivityManager.cpp | 2 +- dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 0c89030e..4b236384 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -182,7 +182,7 @@ std::string Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { std::string columns; switch (leaderboardType) { case Type::ShootingGallery: - columns = "score=%i, hitPercentage=%f, streak=%i"; + columns = "score=%i, hitPercentage=%i, streak=%i"; break; case Type::Racing: columns = "bestLapTime=%i, bestTime=%i, numWins=numWins + %i"; @@ -407,9 +407,9 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead int32_t score; score = va_arg(argsCopy, int32_t); - float oldHitPercentage = myScoreResult->getFloat("hitPercentage"); - float hitPercentage; - hitPercentage = va_arg(argsCopy, double); + int32_t oldHitPercentage = myScoreResult->getFloat("hitPercentage"); + int32_t hitPercentage; + hitPercentage = va_arg(argsCopy, int32_t); int32_t oldStreak = myScoreResult->getInt("streak"); int32_t streak; diff --git a/dScripts/ActivityManager.cpp b/dScripts/ActivityManager.cpp index 5b361510..85365e01 100644 --- a/dScripts/ActivityManager.cpp +++ b/dScripts/ActivityManager.cpp @@ -87,7 +87,7 @@ void ActivityManager::SaveScore(Entity* self, LWOOBJID playerID, uint32_t val1, uint32_t gameID = sac != nullptr ? sac->GetActivityID() : self->GetLOT(); // Save the new score to the leaderboard and show the leaderboard to the player auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID); - Game::logger->Log("ActivityManager", "leaderboard type %i %i", leaderboardType, gameID); + Game::logger->Log("ActivityManager", "leaderboard type %i %i args %i %i %i", leaderboardType, gameID, val1, val2, val3); LeaderboardManager::Instance().SaveScore(playerID, gameID, leaderboardType, 3, val1, val2, val3); // Makes the leaderboard show up for the player diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index 098d9e55..3d386775 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -546,13 +546,13 @@ void SGCannon::StopGame(Entity* self, bool cancel) { // The player won, store all the score and send rewards if (!cancel) { - auto percentage = 0; + int32_t percentage = 50; auto misses = self->GetVar(MissesVariable); auto fired = self->GetVar(ShotsFiredVariable); - if (fired > 0) { - percentage = misses / fired; - } + // if (fired > 0) { + // percentage = misses / fired; + // } auto* missionComponent = player->GetComponent(); From f26e66da4d1fe8a2575ba81d961f21921e7b6d9c Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Sun, 28 May 2023 04:30:20 -0700 Subject: [PATCH 30/59] small updates to typs --- dGame/LeaderboardManager.cpp | 26 ++++++++++++-------------- dGame/LeaderboardManager.h | 13 +++++++------ 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 4b236384..81cdbb8b 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -41,24 +41,23 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) { 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 << "\nResult.Count=1:1"; // number of results, always 1? - if (!this->entries.empty()) leaderboard << "\nResult[0].Index=0:RowNumber"; // "Primary key" - leaderboard << "\nResult[0].RowCount=1:" << entries.size(); // number of rows + 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. + leaderboard << "\nResult[0].RowCount=1:" << entries.size(); int32_t rowNumber = 0; for (auto& entry : entries) { for (auto* data : entry) { - Game::logger->Log("LeaderboardManager", "writing data %s", data->GetString().c_str()); WriteLeaderboardRow(leaderboard, rowNumber, data); } rowNumber++; } - Game::logger->Log("LeaderboardManager", "leaderboard is %s", leaderboard.str().c_str()); + // Serialize the thing to a BitStream uint32_t leaderboardSize = leaderboard.tellp(); bitStream->Write(leaderboardSize); // Doing this all in 1 call so there is no possbility of a dangling pointer. - bitStream->WriteAlignedBytes(reinterpret_cast(GeneralUtils::ASCIIToUTF16(leaderboard.str()).c_str()), leaderboard.tellp() * 2); + bitStream->WriteAlignedBytes(reinterpret_cast(GeneralUtils::ASCIIToUTF16(leaderboard.str()).c_str()), leaderboardSize * sizeof(char16_t)); if (leaderboardSize > 0) bitStream->Write(0); bitStream->Write0(); bitStream->Write0(); @@ -90,7 +89,7 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { entry.push_back(new LDFData(u"RowNumber", rows->getInt("ranking"))); switch (leaderboardType) { case Type::ShootingGallery: - entry.push_back(new LDFData(u"HitPercentage", rows->getDouble("hitPercentage"))); + entry.push_back(new LDFData(u"HitPercentage", (rows->getInt("hitPercentage") / 100.0f))); // HitPercentage:3 between 0 and 1 entry.push_back(new LDFData(u"Score", rows->getInt("score"))); // Score:1 @@ -144,7 +143,7 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { } } -std::string Leaderboard::GetColumns(Leaderboard::Type leaderboardType) { +const std::string Leaderboard::GetColumns(Leaderboard::Type leaderboardType) { std::string columns; switch (leaderboardType) { case Type::ShootingGallery: @@ -178,7 +177,7 @@ std::string Leaderboard::GetColumns(Leaderboard::Type leaderboardType) { return columns; } -std::string Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { +const std::string Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { std::string columns; switch (leaderboardType) { case Type::ShootingGallery: @@ -212,7 +211,7 @@ std::string Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { return columns; } -std::string Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { +const std::string Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { std::string orderBase; switch (leaderboardType) { case Type::ShootingGallery: @@ -297,8 +296,8 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { ) )QUERY"; - std::string orderBase = GetOrdering(this->leaderboardType); - std::string selectBase = GetColumns(this->leaderboardType); + const std::string orderBase = GetOrdering(this->leaderboardType); + const std::string selectBase = GetColumns(this->leaderboardType); constexpr uint16_t STRING_LENGTH = 1526; char lookupBuffer[STRING_LENGTH]; @@ -327,13 +326,12 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { } std::unique_ptr baseResult(baseQuery->executeQuery()); - Game::logger->Log("LeaderboardManager", "%s", baseRankingBuffer); + 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 - Game::logger->Log("LeaderboardManager", "query is %s", lookupBuffer); std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer)); query->setInt(1, this->gameID); diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index f84e4228..6a7ba665 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -20,8 +20,6 @@ typedef uint32_t GameID; class Leaderboard { public: - using LeaderboardEntry = std::vector; - using LeaderboardEntries = std::vector; // Enums for leaderboards enum InfoType : uint32_t { @@ -76,9 +74,9 @@ public: void Send(LWOOBJID targetID); // 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); + static const std::string GetColumns(Type leaderboardType); + static const std::string GetInsertFormat(Type leaderboardType); + static const std::string GetOrdering(Type leaderboardType); private: inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data); @@ -89,6 +87,9 @@ private: // to send it to a client. void QueryToLdf(std::unique_ptr& rows); + using LeaderboardEntry = std::vector; + using LeaderboardEntries = std::vector; + LeaderboardEntries entries; LWOOBJID relatedPlayer; GameID gameID; @@ -98,7 +99,7 @@ private: }; class LeaderboardManager : public Singleton { - typedef std::map LeaderboardCache; + using LeaderboardCache = std::map; public: void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, LWOOBJID targetID, uint32_t resultStart = 0, uint32_t resultEnd = 10); From 0107d05d55364fd01a9e5241275585577abbbcf3 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 30 May 2023 04:11:37 -0700 Subject: [PATCH 31/59] draft --- dGame/LeaderboardManager.cpp | 215 ++++++------------ dGame/LeaderboardManager.h | 61 +++-- dGame/dComponents/RacingControlComponent.cpp | 4 +- dGame/dGameMessages/GameMessages.cpp | 2 +- .../02_server/Map/AG/NpcAgCourseStarter.cpp | 2 +- dScripts/ActivityManager.cpp | 4 +- .../dlu/9_Update_Leaderboard_Storage.sql | 10 +- 7 files changed, 123 insertions(+), 175 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 81cdbb8b..d153a1fd 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -11,12 +11,15 @@ #include "GeneralUtils.h" #include "Entity.h" #include "LDFFormat.h" +#include "DluAssert.h" #include #include "CDActivitiesTable.h" #include "Metrics.hpp" -LeaderboardManager::LeaderboardCache LeaderboardManager::leaderboardCache = {}; +namespace LeaderboardManager { + LeaderboardCache leaderboardCache; +} Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type leaderboardType) { this->gameID = gameID; @@ -143,7 +146,7 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { } } -const std::string Leaderboard::GetColumns(Leaderboard::Type leaderboardType) { +const std::string_view Leaderboard::GetColumns(Leaderboard::Type leaderboardType) { std::string columns; switch (leaderboardType) { case Type::ShootingGallery: @@ -177,7 +180,7 @@ const std::string Leaderboard::GetColumns(Leaderboard::Type leaderboardType) { return columns; } -const std::string Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { +const std::string_view Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { std::string columns; switch (leaderboardType) { case Type::ShootingGallery: @@ -211,7 +214,7 @@ const std::string Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType return columns; } -const std::string Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { +const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { std::string orderBase; switch (leaderboardType) { case Type::ShootingGallery: @@ -296,19 +299,18 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { ) )QUERY"; - const std::string orderBase = GetOrdering(this->leaderboardType); - const std::string selectBase = GetColumns(this->leaderboardType); + const auto orderBase = GetOrdering(this->leaderboardType); + const auto 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(), resultStart, resultEnd); - } - else { - snprintf(lookupBuffer, STRING_LENGTH, queryBase.c_str(), - orderBase.c_str(), "", selectBase.c_str(), resultStart, resultEnd); + snprintf(lookupBuffer, STRING_LENGTH, queryBase.data(), + orderBase.data(), friendsQuery, selectBase.data(), resultStart, resultEnd); + } else { + snprintf(lookupBuffer, STRING_LENGTH, queryBase.data(), + orderBase.data(), "", selectBase.data(), resultStart, resultEnd); } std::string baseLookupStr; @@ -355,186 +357,99 @@ void Leaderboard::Send(LWOOBJID targetID) { } } -void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t argumentCount, ...) { - va_list args; - va_start(args, argumentCount); - SaveScore(playerID, gameID, leaderboardType, args); - va_end(args); -} +std::string LeaderboardManager::FormatInsert(const Leaderboard::Type& type, const Score& score, const bool useUpdate) { + auto insertFormat = Leaderboard::GetInsertFormat(type); -std::string FormatInsert(const std::string& columns, const std::string& format, va_list args, bool useUpdate) { - const char* insertClause = "INSERT"; - const char* updateClause = "UPDATE"; - const char* queryType = useUpdate ? updateClause : insertClause; + auto* queryType = useUpdate ? "UPDATE" : "INSERT"; - const char* insertFilter = ", character_id = ?, game_id = ?, timesPlayed = 1"; - const char* updateFilter = ", timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?"; - const char* usedFilter = useUpdate ? updateFilter : insertFilter; + auto* usedFilter = useUpdate ? + ", timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?" : + ", character_id = ?, game_id = ?"; + // First fill in the format constexpr uint16_t STRING_LENGTH = 400; char formattedInsert[STRING_LENGTH]; - auto queryBase = "%s leaderboard SET %s %s;"; - snprintf(formattedInsert, STRING_LENGTH, queryBase, queryType, format.c_str(), usedFilter); + snprintf(formattedInsert, STRING_LENGTH, "%s leaderboard SET %s %s;", queryType, insertFormat.data(), usedFilter); + // Then fill in our score char finishedQuery[STRING_LENGTH]; - vsnprintf(finishedQuery, STRING_LENGTH, formattedInsert, args); + snprintf(finishedQuery, STRING_LENGTH, formattedInsert, score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore()); + DluAssert() return finishedQuery; } -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 = ?;"; +void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t primaryScore, uint32_t secondaryScore, uint32_t tertiaryScore) { + auto* lookup = "SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;"; - constexpr uint16_t STRING_LENGTH = 400; - char lookupBuffer[STRING_LENGTH]; - snprintf(lookupBuffer, STRING_LENGTH, lookup, selectedColumns.c_str()); - - std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer)); + std::unique_ptr query(Database::CreatePreppedStmt(lookup)); query->setInt(1, playerID); query->setInt(2, gameID); std::unique_ptr myScoreResult(query->executeQuery()); - std::va_list argsCopy; - va_copy(argsCopy, activityScore); std::string saveQuery; + Score newScore(primaryScore, secondaryScore, tertiaryScore); if (myScoreResult->next()) { + Score oldScore; + bool lowerScoreBetter = false; switch (leaderboardType) { + // Higher score better case Leaderboard::Type::ShootingGallery: { - int32_t oldScore = myScoreResult->getInt("score"); - int32_t score; - score = va_arg(argsCopy, int32_t); - - int32_t oldHitPercentage = myScoreResult->getFloat("hitPercentage"); - int32_t hitPercentage; - hitPercentage = va_arg(argsCopy, int32_t); - - int32_t oldStreak = myScoreResult->getInt("streak"); - int32_t streak; - streak = va_arg(argsCopy, int32_t); - - 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, activityScore, true); - } - break; - } - case Leaderboard::Type::Racing: { - uint32_t oldLapTime = myScoreResult->getFloat("bestLapTime"); - uint32_t lapTime; - lapTime = va_arg(argsCopy, uint32_t); - - uint32_t oldTime = myScoreResult->getFloat("bestTime"); - uint32_t newTime; - newTime = va_arg(argsCopy, uint32_t); - - bool won; - won = va_arg(argsCopy, int32_t); - - 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, activityScore, true); - } - 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, activityScore, true); - } + oldScore.SetPrimaryScore(myScoreResult->getInt("score")); + oldScore.SetSecondaryScore(myScoreResult->getInt("hitPercentage")); + oldScore.SetTertiaryScore(myScoreResult->getInt("streak")); 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, activityScore, true); - } + oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); 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); - - if (points > oldPoints || (points == oldPoints && time < oldTime)) { - saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, true); - } + // Config option may reverse these + oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); + oldScore.SetSecondaryScore(myScoreResult->getInt("score")); break; } case Leaderboard::Type::SurvivalNS: { - int32_t oldTime = myScoreResult->getInt("bestTime"); - int32_t time; - time = va_arg(argsCopy, int32_t); - - 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, activityScore, true); - } + oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); + oldScore.SetSecondaryScore(myScoreResult->getInt("score")); break; } + case Leaderboard::Type::UnusedLeaderboard4: 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, activityScore, true); - } + oldScore.SetPrimaryScore(myScoreResult->getInt("score")); break; } + case Leaderboard::Type::Racing: + oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); + oldScore.SetSecondaryScore(myScoreResult->getInt("bestLapTime")); + lowerScoreBetter = true; + break; + case Leaderboard::Type::MonumentRace: + oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); + lowerScoreBetter = true; + // Do score checking here + break; case Leaderboard::Type::None: default: - Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); - va_end(argsCopy); + Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); return; } + bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore; + if (newHighScore) { + saveQuery = FormatInsert(leaderboardType, newScore, true); + } else if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore) { + saveQuery = "UPDATE leaderboard SET numWins = numWins + 1, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;"; + } else { + saveQuery = "UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;"; + } } else { - saveQuery = FormatInsert(selectedColumns, insertFormat, activityScore, false); - } - std::unique_ptr saveStatement; - if (!saveQuery.empty()) { - 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 updateStatement(Database::CreatePreppedStmt("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;")); - saveStatement = std::move(updateStatement); + saveQuery = FormatInsert(leaderboardType, newScore, false); } + + std::unique_ptr saveStatement(Database::CreatePreppedStmt(saveQuery)); saveStatement->setInt(1, playerID); saveStatement->setInt(2, gameID); saveStatement->execute(); - va_end(argsCopy); } void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, LWOOBJID targetID, uint32_t resultStart, uint32_t resultEnd) { diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 6a7ba665..0d810a67 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -1,7 +1,8 @@ -#pragma once -#include +#ifndef __LEADERBOARDMANAGER__H__ +#define __LEADERBOARDMANAGER__H__ + #include -#include +#include #include #include "Singleton.h" @@ -62,7 +63,7 @@ public: /** * Builds the leaderboard from the database based on the associated gameID - * + * * @param resultStart The index to start the leaderboard at. Zero indexed. * @param resultEnd The index to end the leaderboard at. Zero indexed. */ @@ -74,9 +75,9 @@ public: void Send(LWOOBJID targetID); // Helper functions to get the columns, ordering and insert format for a leaderboard - static const std::string GetColumns(Type leaderboardType); - static const std::string GetInsertFormat(Type leaderboardType); - static const std::string GetOrdering(Type leaderboardType); + static const std::string_view GetColumns(Type leaderboardType); + static const std::string_view GetInsertFormat(Type leaderboardType); + static const std::string_view GetOrdering(Type leaderboardType); private: inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data); @@ -98,9 +99,40 @@ private: bool weekly; }; -class LeaderboardManager : public Singleton { +namespace LeaderboardManager { + class Score { + public: + Score() { + primaryScore = 0; + secondaryScore = 0; + tertiaryScore = 0; + } + Score(uint32_t primaryScore, uint32_t secondaryScore = 0, uint32_t tertiaryScore = 0) { + this->primaryScore = primaryScore; + this->secondaryScore = secondaryScore; + this->tertiaryScore = tertiaryScore; + } + 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); + } + 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); + } + void SetPrimaryScore(const uint32_t score) { primaryScore = score; } + uint32_t GetPrimaryScore() const { return primaryScore; } + + void SetSecondaryScore(const uint32_t score) { secondaryScore = score; } + uint32_t GetSecondaryScore() const { return secondaryScore; } + + void SetTertiaryScore(const uint32_t score) { tertiaryScore = score; } + uint32_t GetTertiaryScore() const { return tertiaryScore; } + private: + uint32_t primaryScore; + uint32_t secondaryScore; + uint32_t tertiaryScore; + }; + using LeaderboardCache = std::map; -public: void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, LWOOBJID targetID, uint32_t resultStart = 0, uint32_t resultEnd = 10); /** @@ -111,12 +143,13 @@ public: * @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, ...); + void SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t primaryScore, uint32_t secondaryScore = 0, uint32_t tertiaryScore = 0); - static Leaderboard::Type GetLeaderboardType(const GameID gameID); - static LeaderboardCache leaderboardCache; -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); + std::string FormatInsert(const Leaderboard::Type& type, const Score& score, const bool useUpdate); + + Leaderboard::Type GetLeaderboardType(const GameID gameID); + extern LeaderboardCache leaderboardCache; }; +#endif //!__LEADERBOARDMANAGER__H__ diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index f74c92fa..efd6b44e 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -400,8 +400,8 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player, const auto score = m_LoadedPlayers * 10 + data->finished; LootGenerator::Instance().GiveActivityLoot(player, m_Parent, m_ActivityID, score); - auto leaderboardType = LeaderboardManager::Instance().GetLeaderboardType(m_ActivityID); - LeaderboardManager::Instance().SaveScore(player->GetObjectID(), m_ActivityID, leaderboardType, 3, data->bestLapTime, data->raceTime, data->finished == 1); + // auto leaderboardType = LeaderboardManager::Instance().GetLeaderboardType(m_ActivityID); + // LeaderboardManager::Instance().SaveScore(player->GetObjectID(), m_ActivityID, leaderboardType, 3, data->bestLapTime, data->raceTime, data->finished == 1); // Giving rewards GameMessages::SendNotifyRacingClient( diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index dae0636a..5bdec2ac 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1661,7 +1661,7 @@ void GameMessages::HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream bool weekly = inStream->ReadBit(); - LeaderboardManager::Instance().SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), entity->GetObjectID(), resultsStart, resultsEnd); + // LeaderboardManager::Instance().SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), entity->GetObjectID(), resultsStart, resultsEnd); } void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream* inStream, Entity* entity) { diff --git a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp index 670fd7ee..20e749ee 100644 --- a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp +++ b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp @@ -94,7 +94,7 @@ void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std EntityManager::Instance()->SerializeEntity(self); auto leaderboardType = LeaderboardManager::GetLeaderboardType(scriptedActivityComponent->GetActivityID()); - LeaderboardManager::Instance().SaveScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), leaderboardType, 1, finish); + // LeaderboardManager::Instance().SaveScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), leaderboardType, 1, finish); GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard", scriptedActivityComponent->GetActivityID(), 0, sender->GetObjectID(), diff --git a/dScripts/ActivityManager.cpp b/dScripts/ActivityManager.cpp index 85365e01..96186688 100644 --- a/dScripts/ActivityManager.cpp +++ b/dScripts/ActivityManager.cpp @@ -88,7 +88,7 @@ void ActivityManager::SaveScore(Entity* self, LWOOBJID playerID, uint32_t val1, // Save the new score to the leaderboard and show the leaderboard to the player auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID); Game::logger->Log("ActivityManager", "leaderboard type %i %i args %i %i %i", leaderboardType, gameID, val1, val2, val3); - LeaderboardManager::Instance().SaveScore(playerID, gameID, leaderboardType, 3, val1, val2, val3); + // LeaderboardManager::Instance().SaveScore(playerID, gameID, leaderboardType, 3, val1, val2, val3); // Makes the leaderboard show up for the player GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard", gameID, 0, playerID, "", player->GetSystemAddress()); @@ -125,7 +125,7 @@ void ActivityManager::GetLeaderboardData(Entity* self, const LWOOBJID playerID, // Save the new score to the leaderboard and show the leaderboard to the player auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID); Game::logger->Log("ActivityManager", "gameID %i", gameID, activityID); - LeaderboardManager::Instance().SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, self->GetObjectID(), 0, numResults); + // LeaderboardManager::Instance().SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, self->GetObjectID(), 0, numResults); } void ActivityManager::ActivityTimerStart(Entity* self, const std::string& timerName, const float_t updateInterval, diff --git a/migrations/dlu/9_Update_Leaderboard_Storage.sql b/migrations/dlu/9_Update_Leaderboard_Storage.sql index c90ba4a2..6721e74e 100644 --- a/migrations/dlu/9_Update_Leaderboard_Storage.sql +++ b/migrations/dlu/9_Update_Leaderboard_Storage.sql @@ -1,12 +1,12 @@ ALTER TABLE leaderboard - ADD COLUMN hitPercentage FLOAT NOT NULL DEFAULT 0, + ADD COLUMN hitPercentage INT NOT NULL DEFAULT 0, ADD COLUMN streak INT NOT NULL DEFAULT 0, - ADD COLUMN bestLapTime FLOAT NOT NULL DEFAULT 0, + ADD COLUMN bestLapTime INT NOT NULL DEFAULT 0, ADD COLUMN numWins INT NOT NULL DEFAULT 0, - ADD COLUMN timesPlayed INT NOT NULL DEFAULT 0, - MODIFY time FLOAT NOT NULL DEFAULT 0; + ADD COLUMN timesPlayed INT NOT NULL DEFAULT 1, + MODIFY time INT NOT NULL DEFAULT 0; -ALTER TABLE leaderboard CHANGE time bestTime float; +ALTER TABLE leaderboard CHANGE time bestTime INT; ALTER TABLE leaderboard CHANGE last_played last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(); UPDATE leaderboard SET streak = bestTime where game_id = 1864; From 7b1626901a13a19bd85b26e20cf03bc489365d4a Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 30 May 2023 04:18:32 -0700 Subject: [PATCH 32/59] Add some debug asserts --- dGame/LeaderboardManager.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index d153a1fd..3af5abce 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -1,3 +1,5 @@ +#define _DEBUG + #include "LeaderboardManager.h" #include #include "Database.h" @@ -318,7 +320,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { 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()); + if (neededFormatting) snprintf(baseRankingBuffer, STRING_LENGTH, baseLookupStr.c_str(), orderBase.data()); else std::copy(baseLookupStr.begin(), baseLookupStr.end() + 1, baseRankingBuffer); std::unique_ptr baseQuery(Database::CreatePreppedStmt(baseRankingBuffer)); @@ -369,12 +371,13 @@ std::string LeaderboardManager::FormatInsert(const Leaderboard::Type& type, cons // First fill in the format constexpr uint16_t STRING_LENGTH = 400; char formattedInsert[STRING_LENGTH]; - snprintf(formattedInsert, STRING_LENGTH, "%s leaderboard SET %s %s;", queryType, insertFormat.data(), usedFilter); + int32_t res = snprintf(formattedInsert, STRING_LENGTH, "%s leaderboard SET %s %s;", queryType, insertFormat.data(), usedFilter); + DluAssert(res != -1); // Then fill in our score char finishedQuery[STRING_LENGTH]; - snprintf(finishedQuery, STRING_LENGTH, formattedInsert, score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore()); - DluAssert() + res = snprintf(finishedQuery, STRING_LENGTH, formattedInsert, score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore()); + DluAssert(res != -1); return finishedQuery; } @@ -386,7 +389,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead query->setInt(2, gameID); std::unique_ptr myScoreResult(query->executeQuery()); - std::string saveQuery; + std::string saveQuery("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;"); Score newScore(primaryScore, secondaryScore, tertiaryScore); if (myScoreResult->next()) { Score oldScore; @@ -439,8 +442,6 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead saveQuery = FormatInsert(leaderboardType, newScore, true); } else if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore) { saveQuery = "UPDATE leaderboard SET numWins = numWins + 1, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;"; - } else { - saveQuery = "UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;"; } } else { saveQuery = FormatInsert(leaderboardType, newScore, false); From f5f599764d8bdcb4a439ad8a39581f78e5de264c Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 30 May 2023 04:23:48 -0700 Subject: [PATCH 33/59] more const and views --- dGame/LeaderboardManager.cpp | 26 ++++++++------------------ dGame/LeaderboardManager.h | 2 +- dGame/dGameMessages/GameMessages.cpp | 2 +- dGame/dGameMessages/GameMessages.h | 2 +- 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 3af5abce..695978fb 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -149,7 +149,7 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { } const std::string_view Leaderboard::GetColumns(Leaderboard::Type leaderboardType) { - std::string columns; + const char* columns; switch (leaderboardType) { case Type::ShootingGallery: columns = "hitPercentage, score, streak"; @@ -157,12 +157,11 @@ const std::string_view Leaderboard::GetColumns(Leaderboard::Type leaderboardType case Type::Racing: columns = "bestLapTime, bestTime, numWins"; break; + case Type::Donations: case Type::UnusedLeaderboard4: columns = "score"; break; case Type::MonumentRace: - columns = "bestTime"; - break; case Type::FootRace: columns = "bestTime"; break; @@ -172,9 +171,6 @@ const std::string_view Leaderboard::GetColumns(Leaderboard::Type leaderboardType 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; @@ -183,7 +179,7 @@ const std::string_view Leaderboard::GetColumns(Leaderboard::Type leaderboardType } const std::string_view Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { - std::string columns; + const char* columns; switch (leaderboardType) { case Type::ShootingGallery: columns = "score=%i, hitPercentage=%i, streak=%i"; @@ -191,12 +187,11 @@ const std::string_view Leaderboard::GetInsertFormat(Leaderboard::Type leaderboar 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: - columns = "bestTime=%i"; - break; case Type::FootRace: columns = "bestTime=%i"; break; @@ -206,9 +201,6 @@ const std::string_view Leaderboard::GetInsertFormat(Leaderboard::Type leaderboar 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; @@ -217,7 +209,7 @@ const std::string_view Leaderboard::GetInsertFormat(Leaderboard::Type leaderboar } const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { - std::string orderBase; + const char* orderBase; switch (leaderboardType) { case Type::ShootingGallery: orderBase = "score DESC, streak DESC, hitPercentage DESC"; @@ -225,6 +217,7 @@ const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardTyp case Type::Racing: orderBase = "bestTime ASC, bestLapTime ASC, numWins DESC"; break; + case Type::Donations: case Type::UnusedLeaderboard4: orderBase = "score DESC"; break; @@ -240,9 +233,6 @@ const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardTyp 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; @@ -352,7 +342,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { QueryToLdf(result); } -void Leaderboard::Send(LWOOBJID targetID) { +void Leaderboard::Send(const LWOOBJID targetID) const { auto* player = EntityManager::Instance()->GetEntity(relatedPlayer); if (player != nullptr) { GameMessages::SendActivitySummaryLeaderboardData(targetID, this, player->GetSystemAddress()); @@ -465,7 +455,7 @@ Leaderboard::Type LeaderboardManager::GetLeaderboardType(const GameID gameID) { auto* activitiesTable = CDClientManager::Instance().GetTable(); std::vector activities = activitiesTable->Query([=](const CDActivities& entry) { - return (entry.ActivityID == gameID); + return entry.ActivityID == gameID; }); auto type = !activities.empty() ? static_cast(activities.at(0).leaderboardType) : Leaderboard::Type::None; leaderboardCache.insert_or_assign(gameID, type); diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 0d810a67..8f39aac4 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -72,7 +72,7 @@ public: /** * Sends the leaderboard to the client specified by targetID. */ - void Send(LWOOBJID targetID); + void Send(const LWOOBJID targetID) const; // Helper functions to get the columns, ordering and insert format for a leaderboard static const std::string_view GetColumns(Type leaderboardType); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 5deeb393..b78cacda 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1639,7 +1639,7 @@ void GameMessages::HandleActivitySummaryLeaderboardData(RakNet::BitStream* instr Game::logger->Log("AGS", "We got mail!"); } -void GameMessages::SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, Leaderboard* leaderboard, const SystemAddress& sysAddr) { +void GameMessages::SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, const Leaderboard* leaderboard, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index d64248e4..7e7a8f70 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -561,7 +561,7 @@ namespace GameMessages { void SendUpdateReputation(const LWOOBJID objectId, const int64_t reputation, const SystemAddress& sysAddr); // Leaderboards - void SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, Leaderboard* leaderboard, + void SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, const Leaderboard* leaderboard, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); void HandleActivitySummaryLeaderboardData(RakNet::BitStream* instream, Entity* entity, const SystemAddress& sysAddr); void SendRequestActivitySummaryLeaderboardData(const LWOOBJID& objectID, const LWOOBJID& targetID, From 83da45575e745f1df92c90fbb8a124e9a35fa2ad Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 30 May 2023 04:28:50 -0700 Subject: [PATCH 34/59] CONST --- dGame/LeaderboardManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 8f39aac4..fe5bc16d 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -107,7 +107,7 @@ namespace LeaderboardManager { secondaryScore = 0; tertiaryScore = 0; } - Score(uint32_t primaryScore, uint32_t secondaryScore = 0, uint32_t tertiaryScore = 0) { + Score(const uint32_t primaryScore, const uint32_t secondaryScore = 0, const uint32_t tertiaryScore = 0) { this->primaryScore = primaryScore; this->secondaryScore = secondaryScore; this->tertiaryScore = tertiaryScore; From a43e03255cfca2572c0d840ab676fcad96e59704 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 30 May 2023 04:38:19 -0700 Subject: [PATCH 35/59] It compiles at least now --- dGame/LeaderboardManager.cpp | 13 +-- dGame/LeaderboardManager.h | 82 ++++++++----------- .../dComponents/ScriptedActivityComponent.cpp | 2 +- dGame/dGameMessages/GameMessageHandler.cpp | 2 +- 4 files changed, 46 insertions(+), 53 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 695978fb..b61b1f49 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -35,11 +35,11 @@ Leaderboard::~Leaderboard() { for (auto& entry : entries) for (auto data : entry) delete data; } -void Leaderboard::WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data) { +inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data) { leaderboard << "\nResult[0].Row[" << index << "]." << data->GetString(); } -void Leaderboard::Serialize(RakNet::BitStream* bitStream) { +void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { bitStream->Write(gameID); bitStream->Write(infoType); @@ -172,6 +172,7 @@ const std::string_view Leaderboard::GetColumns(Leaderboard::Type leaderboardType 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; } @@ -202,6 +203,7 @@ const std::string_view Leaderboard::GetInsertFormat(Leaderboard::Type leaderboar 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; } @@ -234,6 +236,7 @@ const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardTyp orderBase = "bestTime DESC, score DESC"; break; case Type::None: + orderBase = ""; // This type is included here simply to resolve a compiler warning on mac about unused enum types break; } @@ -349,7 +352,7 @@ void Leaderboard::Send(const LWOOBJID targetID) const { } } -std::string LeaderboardManager::FormatInsert(const Leaderboard::Type& type, const Score& score, const bool useUpdate) { +std::string FormatInsert(const Leaderboard::Type& type, const Score& score, const bool useUpdate) { auto insertFormat = Leaderboard::GetInsertFormat(type); auto* queryType = useUpdate ? "UPDATE" : "INSERT"; @@ -371,7 +374,7 @@ std::string LeaderboardManager::FormatInsert(const Leaderboard::Type& type, cons return finishedQuery; } -void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Leaderboard::Type leaderboardType, uint32_t primaryScore, uint32_t secondaryScore, uint32_t tertiaryScore) { +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) { auto* lookup = "SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;"; std::unique_ptr query(Database::CreatePreppedStmt(lookup)); @@ -443,7 +446,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, GameID gameID, Lead saveStatement->execute(); } -void LeaderboardManager::SendLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, LWOOBJID targetID, uint32_t resultStart, uint32_t resultEnd) { +void LeaderboardManager::SendLeaderboard(const uint32_t gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t resultStart, const uint32_t resultEnd) { Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID)); leaderboard.SetupLeaderboard(resultStart, resultEnd); leaderboard.Send(targetID); diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index fe5bc16d..7aac2b12 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -17,7 +17,39 @@ namespace RakNet { class BitStream; }; -typedef uint32_t GameID; +class Score { +public: + Score() { + primaryScore = 0; + secondaryScore = 0; + tertiaryScore = 0; + } + Score(const uint32_t primaryScore, const uint32_t secondaryScore = 0, const uint32_t tertiaryScore = 0) { + this->primaryScore = primaryScore; + this->secondaryScore = secondaryScore; + this->tertiaryScore = tertiaryScore; + } + 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); + } + 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); + } + void SetPrimaryScore(const uint32_t score) { primaryScore = score; } + uint32_t GetPrimaryScore() const { return primaryScore; } + + void SetSecondaryScore(const uint32_t score) { secondaryScore = score; } + uint32_t GetSecondaryScore() const { return secondaryScore; } + + void SetTertiaryScore(const uint32_t score) { tertiaryScore = score; } + uint32_t GetTertiaryScore() const { return tertiaryScore; } +private: + uint32_t primaryScore; + uint32_t secondaryScore; + uint32_t tertiaryScore; +}; + +using GameID = uint32_t; class Leaderboard { public: @@ -50,7 +82,7 @@ public: * * Expensive! Leaderboards are very string intensive so be wary of performatnce calling this method. */ - void Serialize(RakNet::BitStream* bitStream); + void Serialize(RakNet::BitStream* bitStream) const; /** * Based on the associated gameID, return true if the score provided @@ -79,8 +111,6 @@ public: static const std::string_view GetInsertFormat(Type leaderboardType); static const std::string_view 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; @@ -100,53 +130,13 @@ private: }; namespace LeaderboardManager { - class Score { - public: - Score() { - primaryScore = 0; - secondaryScore = 0; - tertiaryScore = 0; - } - Score(const uint32_t primaryScore, const uint32_t secondaryScore = 0, const uint32_t tertiaryScore = 0) { - this->primaryScore = primaryScore; - this->secondaryScore = secondaryScore; - this->tertiaryScore = tertiaryScore; - } - 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); - } - 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); - } - void SetPrimaryScore(const uint32_t score) { primaryScore = score; } - uint32_t GetPrimaryScore() const { return primaryScore; } - - void SetSecondaryScore(const uint32_t score) { secondaryScore = score; } - uint32_t GetSecondaryScore() const { return secondaryScore; } - - void SetTertiaryScore(const uint32_t score) { tertiaryScore = score; } - uint32_t GetTertiaryScore() const { return tertiaryScore; } - private: - uint32_t primaryScore; - uint32_t secondaryScore; - uint32_t tertiaryScore; - }; using LeaderboardCache = std::map; void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, LWOOBJID targetID, uint32_t resultStart = 0, uint32_t resultEnd = 10); - /** - * @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 primaryScore, uint32_t secondaryScore = 0, uint32_t tertiaryScore = 0); + 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 GetLeaderboard(uint32_t gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID = LWOOBJID_EMPTY); - std::string FormatInsert(const Leaderboard::Type& type, const Score& score, const bool useUpdate); + void GetLeaderboard(const uint32_t gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID = LWOOBJID_EMPTY); Leaderboard::Type GetLeaderboardType(const GameID gameID); extern LeaderboardCache leaderboardCache; diff --git a/dGame/dComponents/ScriptedActivityComponent.cpp b/dGame/dComponents/ScriptedActivityComponent.cpp index 555332f4..b7d473cf 100644 --- a/dGame/dComponents/ScriptedActivityComponent.cpp +++ b/dGame/dComponents/ScriptedActivityComponent.cpp @@ -36,7 +36,7 @@ ScriptedActivityComponent::ScriptedActivityComponent(Entity* parent, int activit for (CDActivities activity : activities) { m_ActivityInfo = activity; - if (static_cast(activity.leaderboardType) == LeaderboardType::Racing && Game::config->GetValue("solo_racing") == "1") { + if (static_cast(activity.leaderboardType) == Leaderboard::Type::Racing && Game::config->GetValue("solo_racing") == "1") { m_ActivityInfo.minTeamSize = 1; m_ActivityInfo.minTeams = 1; } diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index b76f391e..ca7a98cb 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -45,7 +45,7 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System Entity* entity = EntityManager::Instance()->GetEntity(objectID); User* usr = UserManager::Instance()->GetUser(sysAddr); - if (messageID != 888) Game::logger->Log("GameMessageHandler", "message %i", messageID); + if (messageID != eGameMessageType::READY_FOR_UPDATES) Game::logger->Log("GameMessageHandler", "message %i", messageID); if (!entity) { Game::logger->Log("GameMessageHandler", "Failed to find associated entity (%llu), aborting GM (%X)!", objectID, messageID); From 0ecc5d83c37ab26f1583b759b444fe862c77ab46 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Tue, 30 May 2023 17:30:50 -0700 Subject: [PATCH 36/59] Fix out of bounds access Fixes an issue where we would try to access an array out of the physics bounds --- dPhysics/dpGrid.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dPhysics/dpGrid.cpp b/dPhysics/dpGrid.cpp index 1631c91a..c3259b51 100644 --- a/dPhysics/dpGrid.cpp +++ b/dPhysics/dpGrid.cpp @@ -43,8 +43,8 @@ void dpGrid::Add(dpEntity* entity) { if (cellX < 0) cellX = 0; if (cellZ < 0) cellZ = 0; - if (cellX > NUM_CELLS) cellX = NUM_CELLS; - if (cellZ > NUM_CELLS) cellZ = NUM_CELLS; + if (cellX >= NUM_CELLS) cellX = NUM_CELLS - 1; + if (cellZ >= NUM_CELLS) cellZ = NUM_CELLS - 1; //Add to cell: m_Cells[cellX][cellZ].push_front(entity); @@ -87,8 +87,8 @@ void dpGrid::Delete(dpEntity* entity) { if (oldCellX < 0) oldCellX = 0; if (oldCellZ < 0) oldCellZ = 0; - if (oldCellX > NUM_CELLS) oldCellX = NUM_CELLS; - if (oldCellZ > NUM_CELLS) oldCellZ = NUM_CELLS; + if (oldCellX >= NUM_CELLS) oldCellX = NUM_CELLS - 1; + if (oldCellZ >= NUM_CELLS) oldCellZ = NUM_CELLS - 1; m_Cells[oldCellX][oldCellZ].remove(entity); From e1a7b4993eb3ad333bb3c7d2131f588a65dba478 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Tue, 30 May 2023 18:21:10 -0700 Subject: [PATCH 37/59] look ma, more work --- dGame/LeaderboardManager.cpp | 45 +++++++++++++++--------------------- dGame/LeaderboardManager.h | 1 + 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index b61b1f49..5830b980 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -68,16 +68,6 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { bitStream->Write0(); } -bool Leaderboard::GetRankingQuery(std::string& lookupReturn) const { - if (this->infoType == InfoType::Top) { - lookupReturn = "SELECT id FROM leaderboard WHERE game_id = ? ORDER BY %s LIMIT 1"; - return true; - } else { - lookupReturn = "SELECT id FROM leaderboard WHERE game_id = ? AND character_id = ? LIMIT 1"; - return false; - } -} - void Leaderboard::QueryToLdf(std::unique_ptr& rows) { if (rows->rowsCount() == 0) return; @@ -276,7 +266,8 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { ORDER BY ranking ASC; )QUERY"; - const char* friendsQuery = + // If we are getting the friends leaderboard, add the friends query, otherwise fill it in with nothing. + std::string friendsQuery = R"QUERY( AND ( character_id IN ( SELECT fr.requested_player FROM ( @@ -294,23 +285,25 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { ) )QUERY"; + [[likely]] if (this->infoType != InfoType::Friends) friendsQuery.clear(); const auto orderBase = GetOrdering(this->leaderboardType); const auto selectBase = GetColumns(this->leaderboardType); - constexpr uint16_t STRING_LENGTH = 1526; + constexpr uint16_t STRING_LENGTH = 2048; 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.data(), - orderBase.data(), friendsQuery, selectBase.data(), resultStart, resultEnd); - } else { - snprintf(lookupBuffer, STRING_LENGTH, queryBase.data(), - orderBase.data(), "", selectBase.data(), resultStart, resultEnd); - } + int32_t res = snprintf(lookupBuffer, STRING_LENGTH, queryBase.data(), orderBase.data(), friendsQuery.data(), selectBase.data(), resultStart, resultEnd); + DluAssert(res != -1); std::string baseLookupStr; char baseRankingBuffer[STRING_LENGTH]; - bool neededFormatting = GetRankingQuery(baseLookupStr); + bool neededFormatting; + [[unlikely]] if (this->infoType == InfoType::Top) { + baseLookupStr = "SELECT id FROM leaderboard WHERE game_id = ? ORDER BY %s LIMIT 1"; + neededFormatting = true; + } else { + baseLookupStr = "SELECT id FROM leaderboard WHERE game_id = ? AND character_id = ? LIMIT 1"; + neededFormatting = false; + } // 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.data()); @@ -318,9 +311,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { std::unique_ptr baseQuery(Database::CreatePreppedStmt(baseRankingBuffer)); baseQuery->setInt(1, this->gameID); - if (!neededFormatting) { - baseQuery->setInt(2, this->relatedPlayer); - } + if (!neededFormatting) baseQuery->setInt(2, this->relatedPlayer); std::unique_ptr baseResult(baseQuery->executeQuery()); @@ -415,16 +406,18 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID gameID oldScore.SetPrimaryScore(myScoreResult->getInt("score")); break; } - case Leaderboard::Type::Racing: + case Leaderboard::Type::Racing: { oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); oldScore.SetSecondaryScore(myScoreResult->getInt("bestLapTime")); lowerScoreBetter = true; break; - case Leaderboard::Type::MonumentRace: + } + case Leaderboard::Type::MonumentRace: { oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); lowerScoreBetter = true; // Do score checking here break; + } case Leaderboard::Type::None: default: Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i. Cannot save score!", leaderboardType); diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 7aac2b12..1dff91bf 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -2,6 +2,7 @@ #define __LEADERBOARDMANAGER__H__ #include +#include #include #include From 8a1f27517652eee16bf1f9b86b954f803e079878 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Wed, 31 May 2023 03:10:28 -0700 Subject: [PATCH 38/59] add a const --- dGame/LeaderboardManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 5830b980..c41c3765 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -236,7 +236,7 @@ const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardTyp void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { resultStart++; resultEnd++; - std::string queryBase = + const std::string queryBase = R"QUERY( WITH leaderboardsRanked AS ( SELECT leaderboard.*, charinfo.name, From 47deca6f4f2f79bdcb23378377d8174168ada229 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 31 May 2023 23:04:33 -0700 Subject: [PATCH 39/59] Update migration Better column names and maintainability Better updating Convert all data to floats --- .../dlu/9_Update_Leaderboard_Storage.sql | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/migrations/dlu/9_Update_Leaderboard_Storage.sql b/migrations/dlu/9_Update_Leaderboard_Storage.sql index 6721e74e..89712147 100644 --- a/migrations/dlu/9_Update_Leaderboard_Storage.sql +++ b/migrations/dlu/9_Update_Leaderboard_Storage.sql @@ -1,12 +1,18 @@ ALTER TABLE leaderboard - ADD COLUMN hitPercentage INT NOT NULL DEFAULT 0, - ADD COLUMN streak INT NOT NULL DEFAULT 0, - ADD COLUMN bestLapTime INT NOT NULL DEFAULT 0, + ADD COLUMN tertiaryScore FLOAT NOT NULL DEFAULT 0, ADD COLUMN numWins INT NOT NULL DEFAULT 0, ADD COLUMN timesPlayed INT NOT NULL DEFAULT 1, MODIFY time INT NOT NULL DEFAULT 0; -ALTER TABLE leaderboard CHANGE time bestTime INT; -ALTER TABLE leaderboard CHANGE last_played last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(); +/* Can only ALTER one column at a time... */ +ALTER TABLE leaderboard + CHANGE last_played last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(); +ALTER TABLE leaderboard RENAME COLUMN score TO primaryScore; +ALTER TABLE leaderboard RENAME COLUMN time TO secondaryScore; +ALTER TABLE leaderboard MODIFY COLUMN secondaryScore FLOAT NOT NULL DEFAULT 0 AFTER primaryScore; +ALTER TABLE leaderboard MODIFY COLUMN primaryScore FLOAT NOT NULL DEFAULT 0; -UPDATE leaderboard SET streak = bestTime where game_id = 1864; +/* A bit messy, but better than going through a bunch of code fixes all to be run once. */ +UPDATE leaderboard SET + primaryScore = secondaryScore, + secondaryScore = 0 WHERE game_id IN (1, 44, 46, 47, 48, 49, 53, 103, 104, 108, 1901); From b8878da61b431c77f761495841a057f480475c22 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 31 May 2023 23:05:19 -0700 Subject: [PATCH 40/59] Convert to using only floats This will cover all of our bases for any type of score. No need to do any conversions. --- dGame/LeaderboardManager.cpp | 200 ++++++++++++----------------------- dGame/LeaderboardManager.h | 34 +++--- 2 files changed, 79 insertions(+), 155 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index c41c3765..7246f434 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -1,7 +1,8 @@ -#define _DEBUG - #include "LeaderboardManager.h" + +#include #include + #include "Database.h" #include "EntityManager.h" #include "Character.h" @@ -14,7 +15,6 @@ #include "Entity.h" #include "LDFFormat.h" #include "DluAssert.h" -#include #include "CDActivitiesTable.h" #include "Metrics.hpp" @@ -32,7 +32,12 @@ Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoTy } Leaderboard::~Leaderboard() { + Clear(); +} + +void Leaderboard::Clear() { for (auto& entry : entries) for (auto data : entry) delete 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); 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 << "\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. @@ -63,12 +68,12 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { bitStream->Write(leaderboardSize); // Doing this all in 1 call so there is no possbility of a dangling pointer. bitStream->WriteAlignedBytes(reinterpret_cast(GeneralUtils::ASCIIToUTF16(leaderboard.str()).c_str()), leaderboardSize * sizeof(char16_t)); - if (leaderboardSize > 0) bitStream->Write(0); bitStream->Write0(); bitStream->Write0(); } void Leaderboard::QueryToLdf(std::unique_ptr& rows) { + Clear(); if (rows->rowsCount() == 0) return; this->entries.reserve(rows->rowsCount()); @@ -84,49 +89,49 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { entry.push_back(new LDFData(u"RowNumber", rows->getInt("ranking"))); switch (leaderboardType) { case Type::ShootingGallery: - entry.push_back(new LDFData(u"HitPercentage", (rows->getInt("hitPercentage") / 100.0f))); + entry.push_back(new LDFData(u"HitPercentage", (rows->getInt("primaryScore") / 100.0f))); // HitPercentage:3 between 0 and 1 - entry.push_back(new LDFData(u"Score", rows->getInt("score"))); + entry.push_back(new LDFData(u"Score", rows->getInt("secondaryScore"))); // Score:1 - entry.push_back(new LDFData(u"Streak", rows->getInt("streak"))); + entry.push_back(new LDFData(u"Streak", rows->getInt("tertiaryScore"))); // Streak:1 break; case Type::Racing: - entry.push_back(new LDFData(u"BestLapTime", rows->getDouble("bestLapTime"))); + entry.push_back(new LDFData(u"BestTime", rows->getDouble("primaryScore"))); // BestLapTime:3 - entry.push_back(new LDFData(u"BestTime", rows->getDouble("bestTime"))); + entry.push_back(new LDFData(u"BestLapTime", rows->getDouble("secondaryScore"))); // 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", rows->getInt("numWins"))); + entry.push_back(new LDFData(u"NumWins", rows->getInt("tertiaryScore"))); // NumWins:1 break; case Type::UnusedLeaderboard4: - entry.push_back(new LDFData(u"Points", rows->getInt("score"))); + entry.push_back(new LDFData(u"Points", rows->getInt("primaryScore"))); // Points:1 break; case Type::MonumentRace: - entry.push_back(new LDFData(u"Time", rows->getInt("bestTime"))); + entry.push_back(new LDFData(u"Time", rows->getInt("primaryScore"))); // Time:1(?) break; case Type::FootRace: - entry.push_back(new LDFData(u"Time", rows->getInt("bestTime"))); + entry.push_back(new LDFData(u"Time", rows->getInt("primaryScore"))); // Time:1 break; case Type::Survival: - entry.push_back(new LDFData(u"Points", rows->getInt("score"))); + entry.push_back(new LDFData(u"Points", rows->getInt("primaryScore"))); // Points:1 - entry.push_back(new LDFData(u"Time", rows->getInt("bestTime"))); + entry.push_back(new LDFData(u"Time", rows->getInt("secondaryScore"))); // Time:1 break; case Type::SurvivalNS: - entry.push_back(new LDFData(u"Wave", rows->getInt("score"))); + entry.push_back(new LDFData(u"Wave", rows->getInt("primaryScore"))); // Wave:1 - entry.push_back(new LDFData(u"Time", rows->getInt("bestTime"))); + entry.push_back(new LDFData(u"Time", rows->getInt("secondaryScore"))); // Time:1 break; case Type::Donations: - entry.push_back(new LDFData(u"Points", rows->getInt("score"))); + entry.push_back(new LDFData(u"Points", rows->getInt("primaryScore"))); // Score:1 break; case Type::None: @@ -139,98 +144,33 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { } const std::string_view Leaderboard::GetColumns(Leaderboard::Type leaderboardType) { - const char* columns; - 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; + return "primaryScore, secondaryScore, tertiaryScore"; } const std::string_view Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { - const char* columns; - 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; + return "primaryScore %f, secondaryScore %f, tertiaryScore %f"; } 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) { 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: - orderBase = "bestTime DESC"; - break; - case Type::Survival: - orderBase = "score DESC, bestTime DESC"; - break; + case Type::UnusedLeaderboard4: case Type::SurvivalNS: - orderBase = "bestTime DESC, score DESC"; - break; + case Type::Donations: + return "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC"; + case Type::Racing: + case Type::MonumentRace: + return "primaryScore ASC, secondaryScore ASC, tertiaryScore ASC"; case Type::None: - orderBase = ""; - // This type is included here simply to resolve a compiler warning on mac about unused enum types - break; + case Type::Survival: + return Game::config->GetValue("classic_survival_scoring") == "1" ? + "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) { @@ -285,34 +225,22 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { ) )QUERY"; - [[likely]] if (this->infoType != InfoType::Friends) friendsQuery.clear(); + if (this->infoType != InfoType::Friends) friendsQuery.clear(); const auto orderBase = GetOrdering(this->leaderboardType); const auto selectBase = GetColumns(this->leaderboardType); - constexpr uint16_t STRING_LENGTH = 2048; - char lookupBuffer[STRING_LENGTH]; - int32_t res = snprintf(lookupBuffer, STRING_LENGTH, queryBase.data(), orderBase.data(), friendsQuery.data(), selectBase.data(), resultStart, resultEnd); - DluAssert(res != -1); - - 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; + std::string baseLookup; + if (this->infoType == InfoType::Top) { + baseLookup = "SELECT id FROM leaderboard WHERE game_id = ? ORDER BY "; + baseLookup += orderBase.data(); } else { - baseLookupStr = "SELECT id FROM leaderboard WHERE game_id = ? AND character_id = ? LIMIT 1"; - neededFormatting = false; + baseLookup = "SELECT id FROM leaderboard WHERE game_id = ? AND character_id = "; + 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. - if (neededFormatting) snprintf(baseRankingBuffer, STRING_LENGTH, baseLookupStr.c_str(), orderBase.data()); - else std::copy(baseLookupStr.begin(), baseLookupStr.end() + 1, baseRankingBuffer); - - std::unique_ptr baseQuery(Database::CreatePreppedStmt(baseRankingBuffer)); + std::unique_ptr baseQuery(Database::CreatePreppedStmt(baseLookup)); 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. @@ -320,6 +248,10 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { uint32_t relatedPlayerLeaderboardId = baseResult->getInt("id"); // 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 query(Database::CreatePreppedStmt(lookupBuffer)); query->setInt(1, this->gameID); @@ -365,7 +297,7 @@ std::string FormatInsert(const Leaderboard::Type& type, const Score& score, cons 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 = ?;"; std::unique_ptr query(Database::CreatePreppedStmt(lookup)); @@ -381,46 +313,46 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID gameID switch (leaderboardType) { // Higher score better case Leaderboard::Type::ShootingGallery: { - oldScore.SetPrimaryScore(myScoreResult->getInt("score")); - oldScore.SetSecondaryScore(myScoreResult->getInt("hitPercentage")); - oldScore.SetTertiaryScore(myScoreResult->getInt("streak")); + oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore")); + oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore")); + oldScore.SetTertiaryScore(myScoreResult->getInt("tertiaryScore")); break; } case Leaderboard::Type::FootRace: { - oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); + oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore")); break; } case Leaderboard::Type::Survival: { // Config option may reverse these - oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); - oldScore.SetSecondaryScore(myScoreResult->getInt("score")); + oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore")); + oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore")); break; } case Leaderboard::Type::SurvivalNS: { - oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); - oldScore.SetSecondaryScore(myScoreResult->getInt("score")); + oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore")); + oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore")); break; } case Leaderboard::Type::UnusedLeaderboard4: case Leaderboard::Type::Donations: { - oldScore.SetPrimaryScore(myScoreResult->getInt("score")); + oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore")); break; } case Leaderboard::Type::Racing: { - oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); - oldScore.SetSecondaryScore(myScoreResult->getInt("bestLapTime")); + oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore")); + oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore")); lowerScoreBetter = true; break; } case Leaderboard::Type::MonumentRace: { - oldScore.SetPrimaryScore(myScoreResult->getInt("bestTime")); + oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore")); lowerScoreBetter = true; // Do score checking here break; } case Leaderboard::Type::None: 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; } bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore; diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 1dff91bf..92d90615 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -25,7 +25,7 @@ public: secondaryScore = 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->secondaryScore = secondaryScore; this->tertiaryScore = tertiaryScore; @@ -36,18 +36,18 @@ public: 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); } - void SetPrimaryScore(const uint32_t score) { primaryScore = score; } - uint32_t GetPrimaryScore() const { return primaryScore; } + void SetPrimaryScore(const float score) { primaryScore = score; } + float GetPrimaryScore() const { return primaryScore; } - void SetSecondaryScore(const uint32_t score) { secondaryScore = score; } - uint32_t GetSecondaryScore() const { return secondaryScore; } + void SetSecondaryScore(const float score) { secondaryScore = score; } + float GetSecondaryScore() const { return secondaryScore; } - void SetTertiaryScore(const uint32_t score) { tertiaryScore = score; } - uint32_t GetTertiaryScore() const { return tertiaryScore; } + void SetTertiaryScore(const float score) { tertiaryScore = score; } + float GetTertiaryScore() const { return tertiaryScore; } private: - uint32_t primaryScore; - uint32_t secondaryScore; - uint32_t tertiaryScore; + float primaryScore; + float secondaryScore; + float tertiaryScore; }; using GameID = uint32_t; @@ -73,10 +73,11 @@ public: Donations, None }; - + Leaderboard() = delete; Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type = None); ~Leaderboard(); + void Clear(); /** * Serialize the Leaderboard to a BitStream @@ -85,15 +86,6 @@ public: */ 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 * @@ -135,7 +127,7 @@ namespace LeaderboardManager { using LeaderboardCache = std::map; 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); From 8267823ca4a33dddbe8ff73a0ec15d8aba9e649a Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 31 May 2023 23:17:13 -0700 Subject: [PATCH 41/59] More simplification --- dGame/LeaderboardManager.cpp | 45 ++++++++++++++++-------------------- dGame/LeaderboardManager.h | 4 +--- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 7246f434..89f438f4 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -143,14 +143,6 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { } } -const std::string_view Leaderboard::GetColumns(Leaderboard::Type leaderboardType) { - return "primaryScore, secondaryScore, tertiaryScore"; -} - -const std::string_view Leaderboard::GetInsertFormat(Leaderboard::Type leaderboardType) { - return "primaryScore %f, secondaryScore %f, tertiaryScore %f"; -} - const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { // Use a switch case and return desc for all 3 columns if higher is better and asc if lower is better switch (leaderboardType) { @@ -179,7 +171,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { const std::string queryBase = R"QUERY( WITH leaderboardsRanked AS ( - SELECT leaderboard.*, charinfo.name, + SELECT leaderboard.primaryScore, leaderboard.secondaryScore, leaderboard.tertiaryScore, charinfo.name, RANK() OVER ( ORDER BY %s, UNIX_TIMESTAMP(last_played) ASC, id DESC @@ -197,7 +189,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { 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 + SELECT leaderboardsRanked.*, 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, %i), lowestRanking.lowestRank - 10) @@ -227,7 +219,6 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { if (this->infoType != InfoType::Friends) friendsQuery.clear(); const auto orderBase = GetOrdering(this->leaderboardType); - const auto selectBase = GetColumns(this->leaderboardType); std::string baseLookup; if (this->infoType == InfoType::Top) { @@ -250,7 +241,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { // 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); + [[maybe_unused]] int32_t res = snprintf(lookupBuffer, STRING_LENGTH, queryBase.data(), orderBase.data(), friendsQuery.data(), resultStart, resultEnd); DluAssert(res != -1); std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer)); @@ -276,23 +267,27 @@ void Leaderboard::Send(const LWOOBJID targetID) const { } std::string FormatInsert(const Leaderboard::Type& type, const Score& score, const bool useUpdate) { - auto insertFormat = Leaderboard::GetInsertFormat(type); + std::string insertStatement; + if (useUpdate) { + insertStatement = + R"QUERY( + UPDATE leaderboard + SET primaryScore %f, secondaryScore %f, tertiaryScore %f, + timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?; + )QUERY"; + } else { + insertStatement = + R"QUERY( + INSERT leaderboard SET + primaryScore %f, secondaryScore %f, tertiaryScore %f, + character_id = ?, game_id = ?; + )QUERY"; + } - auto* queryType = useUpdate ? "UPDATE" : "INSERT"; - - auto* usedFilter = useUpdate ? - ", timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?" : - ", character_id = ?, game_id = ?"; - - // First fill in the format constexpr uint16_t STRING_LENGTH = 400; - char formattedInsert[STRING_LENGTH]; - int32_t res = snprintf(formattedInsert, STRING_LENGTH, "%s leaderboard SET %s %s;", queryType, insertFormat.data(), usedFilter); - DluAssert(res != -1); - // Then fill in our score char finishedQuery[STRING_LENGTH]; - res = snprintf(finishedQuery, STRING_LENGTH, formattedInsert, score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore()); + int32_t res = snprintf(finishedQuery, STRING_LENGTH, insertStatement.c_str(), score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore()); DluAssert(res != -1); return finishedQuery; } diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 92d90615..b4927ced 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -99,9 +99,7 @@ public: */ void Send(const LWOOBJID targetID) const; - // Helper functions to get the columns, ordering and insert format for a leaderboard - static const std::string_view GetColumns(Type leaderboardType); - static const std::string_view GetInsertFormat(Type leaderboardType); + // Helper function to get the columns, ordering and insert format for a leaderboard static const std::string_view GetOrdering(Type leaderboardType); private: // Returns true if the string needs formatting From 96fc6e81d88298a88d4c9ec615a542891eb5d190 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Mon, 5 Jun 2023 02:24:00 -0700 Subject: [PATCH 42/59] Update sql to work The old way was supposed to work but doesn't. Oh well! --- dGame/LeaderboardManager.cpp | 2 +- migrations/dlu/9_Update_Leaderboard_Storage.sql | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 89f438f4..dd890bd6 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -287,7 +287,7 @@ std::string FormatInsert(const Leaderboard::Type& type, const Score& score, cons constexpr uint16_t STRING_LENGTH = 400; // Then fill in our score char finishedQuery[STRING_LENGTH]; - int32_t res = snprintf(finishedQuery, STRING_LENGTH, insertStatement.c_str(), score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore()); + [[maybe_unused]] int32_t res = snprintf(finishedQuery, STRING_LENGTH, insertStatement.c_str(), score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore()); DluAssert(res != -1); return finishedQuery; } diff --git a/migrations/dlu/9_Update_Leaderboard_Storage.sql b/migrations/dlu/9_Update_Leaderboard_Storage.sql index 89712147..a68cc2d8 100644 --- a/migrations/dlu/9_Update_Leaderboard_Storage.sql +++ b/migrations/dlu/9_Update_Leaderboard_Storage.sql @@ -7,10 +7,8 @@ ALTER TABLE leaderboard /* Can only ALTER one column at a time... */ ALTER TABLE leaderboard CHANGE last_played last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(); -ALTER TABLE leaderboard RENAME COLUMN score TO primaryScore; -ALTER TABLE leaderboard RENAME COLUMN time TO secondaryScore; -ALTER TABLE leaderboard MODIFY COLUMN secondaryScore FLOAT NOT NULL DEFAULT 0 AFTER primaryScore; -ALTER TABLE leaderboard MODIFY COLUMN primaryScore FLOAT NOT NULL DEFAULT 0; +ALTER TABLE leaderboard CHANGE score primaryScore FLOAT NOT NULL DEFAULT 0; +ALTER TABLE leaderboard CHANGE time secondaryScore FLOAT NOT NULL DEFAULT 0 AFTER primaryScore; /* A bit messy, but better than going through a bunch of code fixes all to be run once. */ UPDATE leaderboard SET From 5bff441c01cecac91936c08b002f67d392f0e50d Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Mon, 5 Jun 2023 02:31:49 -0700 Subject: [PATCH 43/59] Fix query crashing Just select all columns since we need most of them anyways --- dGame/LeaderboardManager.cpp | 5 +++-- tests/dGameTests/LeaderboardTests.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index dd890bd6..d9ba6a87 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -84,7 +84,7 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { entry.reserve(MAX_NUM_DATA_PER_ROW); 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"NumPlayed", rows->getInt("timesPlayed"))); 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) { @@ -168,10 +168,11 @@ const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardTyp void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { resultStart++; resultEnd++; + // We need everything except 1 column so i'm selecting * from leaderboard const std::string queryBase = R"QUERY( WITH leaderboardsRanked AS ( - SELECT leaderboard.primaryScore, leaderboard.secondaryScore, leaderboard.tertiaryScore, charinfo.name, + SELECT leaderboard.*, charinfo.name, RANK() OVER ( ORDER BY %s, UNIX_TIMESTAMP(last_played) ASC, id DESC diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index b3480f51..b7946368 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -30,10 +30,10 @@ protected: Leaderboard leaderboard(gameID, infoType, false, 14231, type); leaderboard.SetupLeaderboard(); leaderboard.Serialize(&bitStream); - // TestLeaderboard(leaderboard, 1); - // TestLeaderboard(leaderboard, 10); - // TestLeaderboard(leaderboard, 100); - // TestLeaderboard(leaderboard, 1000); + TestLeaderboard(leaderboard, 1); + TestLeaderboard(leaderboard, 10); + TestLeaderboard(leaderboard, 100); + TestLeaderboard(leaderboard, 1000); } CBITSTREAM; From c572f2a58dfd71ab18339b0e3f3142fcab30a609 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Mon, 5 Jun 2023 02:43:02 -0700 Subject: [PATCH 44/59] better tabs and organization --- dGame/LeaderboardManager.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index d9ba6a87..a1e6d31e 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -146,22 +146,21 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardType) { // Use a switch case and return desc for all 3 columns if higher is better and asc if lower is better switch (leaderboardType) { + case Type::Racing: + case Type::MonumentRace: + return "primaryScore ASC, secondaryScore ASC, tertiaryScore ASC"; + case Type::Survival: + return Game::config->GetValue("classic_survival_scoring") == "1" ? + "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC" : + "secondaryScore DESC, primaryScore DESC, tertiaryScore DESC"; case Type::ShootingGallery: case Type::FootRace: case Type::UnusedLeaderboard4: case Type::SurvivalNS: case Type::Donations: - 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::Survival: - return Game::config->GetValue("classic_survival_scoring") == "1" ? - "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC" : - "secondaryScore DESC, primaryScore DESC, tertiaryScore DESC"; default: - return ""; + return "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC"; } } @@ -221,6 +220,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { if (this->infoType != InfoType::Friends) friendsQuery.clear(); const auto orderBase = GetOrdering(this->leaderboardType); + // For top query, we want to just rank all scores, but for all others we need the scores around a specific player std::string baseLookup; if (this->infoType == InfoType::Top) { baseLookup = "SELECT id FROM leaderboard WHERE game_id = ? ORDER BY "; @@ -239,12 +239,12 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { uint32_t relatedPlayerLeaderboardId = baseResult->getInt("id"); - // 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(), resultStart, resultEnd); + // Create and execute the actual save here. Using a heap allocated buffer to avoid stack overflow + constexpr uint16_t STRING_LENGTH = 4096; + std::unique_ptr lookupBuffer = std::make_unique(STRING_LENGTH); + [[maybe_unused]] int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.data(), orderBase.data(), friendsQuery.data(), resultStart, resultEnd); DluAssert(res != -1); - std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer)); + std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer.get())); query->setInt(1, this->gameID); if (this->infoType == InfoType::Friends) { From a5e63529dc085569c748c78887309369710ec0ac Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Mon, 5 Jun 2023 02:50:40 -0700 Subject: [PATCH 45/59] const and compile save --- dGame/LeaderboardManager.cpp | 29 +++++++++++++++-------------- dGame/LeaderboardManager.h | 7 +------ 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index a1e6d31e..cf399601 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -200,19 +200,20 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { // If we are getting the friends leaderboard, add the friends query, otherwise fill it in with nothing. std::string 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 - ) + 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"; @@ -378,7 +379,7 @@ Leaderboard::Type LeaderboardManager::GetLeaderboardType(const GameID gameID) { if (lookup != leaderboardCache.end()) return lookup->second; auto* activitiesTable = CDClientManager::Instance().GetTable(); - std::vector activities = activitiesTable->Query([=](const CDActivities& entry) { + std::vector activities = activitiesTable->Query([gameID](const CDActivities& entry) { return entry.ActivityID == gameID; }); auto type = !activities.empty() ? static_cast(activities.at(0).leaderboardType) : Leaderboard::Type::None; diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index b4927ced..2512eb87 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -102,9 +102,6 @@ public: // Helper function to get the columns, ordering and insert format for a leaderboard static const std::string_view GetOrdering(Type leaderboardType); private: - // 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); @@ -123,12 +120,10 @@ private: namespace LeaderboardManager { using LeaderboardCache = std::map; - void SendLeaderboard(GameID gameID, Leaderboard::InfoType infoType, bool weekly, LWOOBJID playerID, LWOOBJID targetID, uint32_t resultStart = 0, uint32_t resultEnd = 10); + void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t resultStart = 0, const uint32_t resultEnd = 10); 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); - Leaderboard::Type GetLeaderboardType(const GameID gameID); extern LeaderboardCache leaderboardCache; }; From 259f0c8371b2689fa7641d5fd45dfe508ec9297a Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Mon, 5 Jun 2023 04:10:59 -0700 Subject: [PATCH 46/59] Working in game again hooray --- dGame/LeaderboardManager.cpp | 28 +++++++++---------- dGame/LeaderboardManager.h | 11 +++++--- dGame/dComponents/RacingControlComponent.cpp | 3 +- dGame/dGameMessages/GameMessages.cpp | 2 +- .../02_server/Map/AG/NpcAgCourseStarter.cpp | 3 +- dScripts/ActivityManager.cpp | 10 ++----- dScripts/ActivityManager.h | 2 +- .../ai/ACT/FootRace/BaseFootRaceManager.cpp | 2 +- .../ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 9 +++--- 9 files changed, 34 insertions(+), 36 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index cf399601..7ae1e0f9 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -20,7 +20,7 @@ #include "Metrics.hpp" namespace LeaderboardManager { - LeaderboardCache leaderboardCache; + std::map leaderboardCache; } Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type leaderboardType) { @@ -36,8 +36,7 @@ Leaderboard::~Leaderboard() { } void Leaderboard::Clear() { - for (auto& entry : entries) for (auto data : entry) delete data; - + for (auto& entry : entries) for (auto ldfData : entry) delete ldfData; } inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data) { @@ -68,6 +67,7 @@ void Leaderboard::Serialize(RakNet::BitStream* bitStream) const { bitStream->Write(leaderboardSize); // Doing this all in 1 call so there is no possbility of a dangling pointer. bitStream->WriteAlignedBytes(reinterpret_cast(GeneralUtils::ASCIIToUTF16(leaderboard.str()).c_str()), leaderboardSize * sizeof(char16_t)); + if (leaderboardSize > 0) bitStream->Write(0); bitStream->Write0(); bitStream->Write0(); } @@ -192,7 +192,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { SELECT leaderboardsRanked.*, 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, %i), lowestRanking.lowestRank - 10) + LEAST(GREATEST(CAST(myRank AS SIGNED) - 5, %i), lowestRanking.lowestRank - 9) AND LEAST(GREATEST(myRank + 5, %i), lowestRanking.lowestRank) ORDER BY ranking ASC; @@ -228,7 +228,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { baseLookup += orderBase.data(); } else { baseLookup = "SELECT id FROM leaderboard WHERE game_id = ? AND character_id = "; - baseLookup += std::to_string(this->relatedPlayer); + baseLookup += std::to_string(static_cast(this->relatedPlayer)); } baseLookup += " LIMIT 1"; @@ -243,7 +243,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { // Create and execute the actual save here. Using a heap allocated buffer to avoid stack overflow constexpr uint16_t STRING_LENGTH = 4096; std::unique_ptr lookupBuffer = std::make_unique(STRING_LENGTH); - [[maybe_unused]] int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.data(), orderBase.data(), friendsQuery.data(), resultStart, resultEnd); + [[maybe_unused]] int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), friendsQuery.c_str(), resultStart, resultEnd); DluAssert(res != -1); std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer.get())); @@ -256,7 +256,6 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { } else { query->setInt(2, relatedPlayerLeaderboardId); } - std::unique_ptr result(query->executeQuery()); QueryToLdf(result); } @@ -274,14 +273,14 @@ std::string FormatInsert(const Leaderboard::Type& type, const Score& score, cons insertStatement = R"QUERY( UPDATE leaderboard - SET primaryScore %f, secondaryScore %f, tertiaryScore %f, + SET primaryScore = %f, secondaryScore = %f, tertiaryScore = %f, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?; )QUERY"; } else { insertStatement = R"QUERY( INSERT leaderboard SET - primaryScore %f, secondaryScore %f, tertiaryScore %f, + primaryScore = %f, secondaryScore = %f, tertiaryScore = %f, character_id = ?, game_id = ?; )QUERY"; } @@ -294,12 +293,13 @@ std::string FormatInsert(const Leaderboard::Type& type, const Score& score, cons return finishedQuery; } -void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID gameID, const Leaderboard::Type leaderboardType, const float primaryScore, const float secondaryScore, const float tertiaryScore) { +void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore, const float tertiaryScore) { + const Leaderboard::Type leaderboardType = GetLeaderboardType(activityId); auto* lookup = "SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;"; std::unique_ptr query(Database::CreatePreppedStmt(lookup)); query->setInt(1, playerID); - query->setInt(2, gameID); + query->setInt(2, activityId); std::unique_ptr myScoreResult(query->executeQuery()); std::string saveQuery("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;"); @@ -349,7 +349,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID gameID } case Leaderboard::Type::None: default: - Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i for game %i. Cannot save score!", leaderboardType, gameID); + Game::logger->Log("LeaderboardManager", "Unknown leaderboard type %i for game %i. Cannot save score!", leaderboardType, activityId); return; } bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore; @@ -364,11 +364,11 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID gameID std::unique_ptr saveStatement(Database::CreatePreppedStmt(saveQuery)); saveStatement->setInt(1, playerID); - saveStatement->setInt(2, gameID); + saveStatement->setInt(2, activityId); saveStatement->execute(); } -void LeaderboardManager::SendLeaderboard(const uint32_t gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t resultStart, const uint32_t resultEnd) { +void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t resultStart, const uint32_t resultEnd) { Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID)); leaderboard.SetupLeaderboard(resultStart, resultEnd); leaderboard.Send(targetID); diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 2512eb87..165e870f 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -77,6 +77,11 @@ public: Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type = None); ~Leaderboard(); + + /** + * @brief Resets the leaderboard state and frees its allocated memory + * + */ void Clear(); /** @@ -118,14 +123,12 @@ private: }; namespace LeaderboardManager { - - using LeaderboardCache = std::map; void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t resultStart = 0, const uint32_t resultEnd = 10); - void SaveScore(const LWOOBJID& playerID, const GameID gameID, const Leaderboard::Type leaderboardType, const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0); + void SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0); Leaderboard::Type GetLeaderboardType(const GameID gameID); - extern LeaderboardCache leaderboardCache; + extern std::map leaderboardCache; }; #endif //!__LEADERBOARDMANAGER__H__ diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 4a8668ca..9ccfe12a 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -379,8 +379,7 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t bu const auto score = m_LoadedPlayers * 10 + data->finished; LootGenerator::Instance().GiveActivityLoot(player, m_Parent, m_ActivityID, score); - // auto leaderboardType = LeaderboardManager::Instance().GetLeaderboardType(m_ActivityID); - // LeaderboardManager::Instance().SaveScore(player->GetObjectID(), m_ActivityID, leaderboardType, 3, data->bestLapTime, data->raceTime, data->finished == 1); + LeaderboardManager::SaveScore(player->GetObjectID(), m_ActivityID, static_cast(data->bestLapTime), static_cast(data->raceTime), static_cast(data->finished == 1)); // Giving rewards GameMessages::SendNotifyRacingClient( diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 46f5cb93..198ea151 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1669,7 +1669,7 @@ void GameMessages::HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream bool weekly = inStream->ReadBit(); - // LeaderboardManager::Instance().SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), entity->GetObjectID(), resultsStart, resultsEnd); + LeaderboardManager::SendLeaderboard(gameID, queryType, weekly, entity->GetObjectID(), entity->GetObjectID(), resultsStart, resultsEnd); } void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream* inStream, Entity* entity) { diff --git a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp index 20e749ee..2a619b6b 100644 --- a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp +++ b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp @@ -93,8 +93,7 @@ void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std } EntityManager::Instance()->SerializeEntity(self); - auto leaderboardType = LeaderboardManager::GetLeaderboardType(scriptedActivityComponent->GetActivityID()); - // LeaderboardManager::Instance().SaveScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), leaderboardType, 1, finish); + LeaderboardManager::SaveScore(sender->GetObjectID(), scriptedActivityComponent->GetActivityID(), static_cast(finish)); GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard", scriptedActivityComponent->GetActivityID(), 0, sender->GetObjectID(), diff --git a/dScripts/ActivityManager.cpp b/dScripts/ActivityManager.cpp index 96186688..57557ae6 100644 --- a/dScripts/ActivityManager.cpp +++ b/dScripts/ActivityManager.cpp @@ -79,16 +79,14 @@ void ActivityManager::StopActivity(Entity* self, const LWOOBJID playerID, const } } -void ActivityManager::SaveScore(Entity* self, LWOOBJID playerID, uint32_t val1, uint32_t val2, uint32_t val3) { +void ActivityManager::SaveScore(Entity* self, const LWOOBJID playerID, const float primaryScore, const float secondaryScore, const float tertiaryScore) const { auto* player = EntityManager::Instance()->GetEntity(playerID); if (!player) return; auto* sac = self->GetComponent(); uint32_t gameID = sac != nullptr ? sac->GetActivityID() : self->GetLOT(); // Save the new score to the leaderboard and show the leaderboard to the player - auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID); - Game::logger->Log("ActivityManager", "leaderboard type %i %i args %i %i %i", leaderboardType, gameID, val1, val2, val3); - // LeaderboardManager::Instance().SaveScore(playerID, gameID, leaderboardType, 3, val1, val2, val3); + LeaderboardManager::SaveScore(playerID, gameID, primaryScore, secondaryScore, tertiaryScore); // Makes the leaderboard show up for the player GameMessages::SendNotifyClientObject(self->GetObjectID(), u"ToggleLeaderBoard", gameID, 0, playerID, "", player->GetSystemAddress()); @@ -123,9 +121,7 @@ void ActivityManager::GetLeaderboardData(Entity* self, const LWOOBJID playerID, auto* sac = self->GetComponent(); uint32_t gameID = sac != nullptr ? sac->GetActivityID() : self->GetLOT(); // Save the new score to the leaderboard and show the leaderboard to the player - auto leaderboardType = LeaderboardManager::GetLeaderboardType(gameID); - Game::logger->Log("ActivityManager", "gameID %i", gameID, activityID); - // LeaderboardManager::Instance().SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, self->GetObjectID(), 0, numResults); + LeaderboardManager::SendLeaderboard(activityID, Leaderboard::InfoType::MyStanding, false, playerID, self->GetObjectID(), 0, numResults); } void ActivityManager::ActivityTimerStart(Entity* self, const std::string& timerName, const float_t updateInterval, diff --git a/dScripts/ActivityManager.h b/dScripts/ActivityManager.h index 633fd5d8..a2202bfb 100644 --- a/dScripts/ActivityManager.h +++ b/dScripts/ActivityManager.h @@ -18,7 +18,7 @@ public: static bool TakeActivityCost(const Entity* self, LWOOBJID playerID); static uint32_t GetActivityID(const Entity* self); void StopActivity(Entity* self, LWOOBJID playerID, uint32_t score, uint32_t value1 = 0, uint32_t value2 = 0, bool quit = false); - void SaveScore(Entity* self, LWOOBJID playerID, uint32_t val1 = 0, uint32_t val2 = 0, uint32_t val3 = 0); + void SaveScore(Entity* self, const LWOOBJID playerID, const float primaryScore, const float secondaryScore = 0.0f, const float tertiaryScore = 0.0f) const; virtual uint32_t CalculateActivityRating(Entity* self, LWOOBJID playerID); static void GetLeaderboardData(Entity* self, LWOOBJID playerID, uint32_t activityID, uint32_t numResults = 0); // void FreezePlayer(Entity *self, const LWOOBJID playerID, const bool state) const; diff --git a/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp b/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp index 3efb5ff4..412d9d88 100644 --- a/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp +++ b/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp @@ -42,7 +42,7 @@ void BaseFootRaceManager::OnFireEventServerSide(Entity* self, Entity* sender, st } StopActivity(self, player->GetObjectID(), 0, param1); - SaveScore(self, player->GetObjectID(), param1, param2, param3); + SaveScore(self, player->GetObjectID(), static_cast(param1), static_cast(param2), static_cast(param3)); } } } diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index a37551ed..558c24c4 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -150,7 +150,7 @@ void SGCannon::OnMessageBoxResponse(Entity* self, Entity* sender, int32_t button if (IsPlayerInActivity(self, player->GetObjectID())) return; self->SetNetworkVar(ClearVariable, true); StartGame(self); - } else if (button == 0 && ((identifier == u"Shooting_Gallery_Retry" || identifier == u"RePlay"))){ + } else if (button == 0 && ((identifier == u"Shooting_Gallery_Retry" || identifier == u"RePlay"))) { RemovePlayer(player->GetObjectID()); UpdatePlayer(self, player->GetObjectID(), true); } else if (button == 1 && identifier == u"Shooting_Gallery_Exit") { @@ -436,8 +436,8 @@ void SGCannon::RemovePlayer(LWOOBJID playerID) { } } -void SGCannon::OnRequestActivityExit(Entity* self, LWOOBJID player, bool canceled){ - if (canceled){ +void SGCannon::OnRequestActivityExit(Entity* self, LWOOBJID player, bool canceled) { + if (canceled) { StopGame(self, canceled); RemovePlayer(player); } @@ -565,7 +565,8 @@ void SGCannon::StopGame(Entity* self, bool cancel) { LootGenerator::Instance().GiveActivityLoot(player, self, GetGameID(self), self->GetVar(TotalScoreVariable)); StopActivity(self, player->GetObjectID(), self->GetVar(TotalScoreVariable), self->GetVar(MaxStreakVariable), percentage); - SaveScore(self, player->GetObjectID(), self->GetVar(TotalScoreVariable), percentage, self->GetVar(MaxStreakVariable)); + SaveScore(self, player->GetObjectID(), + static_cast(self->GetVar(TotalScoreVariable)), percentage, static_cast(self->GetVar(MaxStreakVariable))); self->SetNetworkVar(AudioFinalWaveDoneVariable, true); // Give the player the model rewards they earned From b4aa5db305f4cfac832ba159985b8f04b1bc0d9c Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 5 Jun 2023 15:10:08 -0700 Subject: [PATCH 47/59] Comment out tests rest in pepperoni tests --- tests/dGameTests/LeaderboardTests.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index b7946368..5157e5d6 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -71,8 +71,9 @@ protected: * 19: [13-04-23 00:39:19] [LeaderboardManager] average time passed for 1000 leaderboard entries is 279457375ns */ -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); -} +// Commented tests out because we dont have a sql server running for tests +// 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); +// } From dab075fc39c8ebfa1471b24e0f147d32d832bb3a Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 5 Jun 2023 15:19:52 -0700 Subject: [PATCH 48/59] forgor about this one --- tests/dGameTests/GameDependencies.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dGameTests/GameDependencies.h b/tests/dGameTests/GameDependencies.h index c0fb8bce..87f56ad0 100644 --- a/tests/dGameTests/GameDependencies.h +++ b/tests/dGameTests/GameDependencies.h @@ -33,7 +33,7 @@ protected: Game::logger = new dLogger("./testing.log", true, true); Game::server = new dServerMock(); Game::config = new dConfig("worldconfig.ini"); - Database::Connect(Game::config->GetValue("mysql_host"), Game::config->GetValue("mysql_database"), Game::config->GetValue("mysql_username"), Game::config->GetValue("mysql_password")); + // Database::Connect(Game::config->GetValue("mysql_host"), Game::config->GetValue("mysql_database"), Game::config->GetValue("mysql_username"), Game::config->GetValue("mysql_password")); } void TearDownDependencies() { From c99e2a372bf6ab1f5343bbd442c5417c782fd3e6 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 5 Jun 2023 16:04:56 -0700 Subject: [PATCH 49/59] Add weekly filter --- dGame/LeaderboardManager.cpp | 16 ++++++++++------ dGame/LeaderboardManager.h | 2 +- tests/dGameTests/LeaderboardTests.cpp | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 7ae1e0f9..bd362bf2 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -164,7 +164,7 @@ const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardTyp } } -void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { +void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t resultEnd) { resultStart++; resultEnd++; // We need everything except 1 column so i'm selecting * from leaderboard @@ -198,8 +198,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { ORDER BY ranking ASC; )QUERY"; - // If we are getting the friends leaderboard, add the friends query, otherwise fill it in with nothing. - std::string friendsQuery = + std::string friendsFilter = R"QUERY( AND ( character_id IN ( @@ -218,7 +217,12 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { ) )QUERY"; - if (this->infoType != InfoType::Friends) friendsQuery.clear(); + std::string weeklyFilter = " AND date >= curdate() - INTERVAL DAYOFWEEK(curdate()) - 7 DAY"; + + std::string filter; + // Setup our filter based on the query type + if (this->infoType == InfoType::Friends) filter += friendsFilter; + if (this->weekly) filter += weeklyFilter; const auto orderBase = GetOrdering(this->leaderboardType); // For top query, we want to just rank all scores, but for all others we need the scores around a specific player @@ -243,7 +247,7 @@ void Leaderboard::SetupLeaderboard(uint32_t resultStart, uint32_t resultEnd) { // Create and execute the actual save here. Using a heap allocated buffer to avoid stack overflow constexpr uint16_t STRING_LENGTH = 4096; std::unique_ptr lookupBuffer = std::make_unique(STRING_LENGTH); - [[maybe_unused]] int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), friendsQuery.c_str(), resultStart, resultEnd); + [[maybe_unused]] int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), filter.c_str(), resultStart, resultEnd); DluAssert(res != -1); std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer.get())); @@ -370,7 +374,7 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t resultStart, const uint32_t resultEnd) { Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID)); - leaderboard.SetupLeaderboard(resultStart, resultEnd); + leaderboard.SetupLeaderboard(weekly, resultStart, resultEnd); leaderboard.Send(targetID); } diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 165e870f..e2ce3f97 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -97,7 +97,7 @@ public: * @param resultStart The index to start the leaderboard at. Zero indexed. * @param resultEnd The index to end the leaderboard at. Zero indexed. */ - void SetupLeaderboard(uint32_t resultStart = 0, uint32_t resultEnd = 10); + void SetupLeaderboard(bool weekly, uint32_t resultStart = 0, uint32_t resultEnd = 10); /** * Sends the leaderboard to the client specified by targetID. diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp index 5157e5d6..8cb833a3 100644 --- a/tests/dGameTests/LeaderboardTests.cpp +++ b/tests/dGameTests/LeaderboardTests.cpp @@ -28,7 +28,7 @@ protected: void RunTests(uint32_t gameID, Leaderboard::Type type, Leaderboard::InfoType infoType) { Game::logger->Log("LeaderboardTests", "Testing leaderboard %i for Serialize speed", infoType); Leaderboard leaderboard(gameID, infoType, false, 14231, type); - leaderboard.SetupLeaderboard(); + leaderboard.SetupLeaderboard(true, 0, 10); leaderboard.Serialize(&bitStream); TestLeaderboard(leaderboard, 1); TestLeaderboard(leaderboard, 10); From d340874284c8d1043dae92ff50d4d799234e9711 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Wed, 21 Jun 2023 19:46:01 -0700 Subject: [PATCH 50/59] more bug fixing - fix weekly leaderboards - fix ag classic vs dlu scoring - fix sorting for survival ns - fix sorting for racing --- dGame/LeaderboardManager.cpp | 23 ++++++++++++++------ dGame/dComponents/RacingControlComponent.cpp | 6 ++--- dScripts/BaseSurvivalServer.cpp | 4 +++- dScripts/BaseWavesServer.cpp | 2 +- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index bd362bf2..2fd48dc3 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -151,12 +151,13 @@ const std::string_view Leaderboard::GetOrdering(Leaderboard::Type leaderboardTyp return "primaryScore ASC, secondaryScore ASC, tertiaryScore ASC"; case Type::Survival: return Game::config->GetValue("classic_survival_scoring") == "1" ? - "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC" : - "secondaryScore DESC, primaryScore DESC, tertiaryScore DESC"; + "secondaryScore DESC, primaryScore DESC, tertiaryScore DESC" : + "primaryScore DESC, secondaryScore DESC, tertiaryScore DESC"; + case Type::SurvivalNS: + return "primaryScore DESC, secondaryScore ASC, tertiaryScore DESC"; case Type::ShootingGallery: case Type::FootRace: case Type::UnusedLeaderboard4: - case Type::SurvivalNS: case Type::Donations: case Type::None: default: @@ -217,7 +218,7 @@ void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t r ) )QUERY"; - std::string weeklyFilter = " AND date >= curdate() - INTERVAL DAYOFWEEK(curdate()) - 7 DAY"; + std::string weeklyFilter = " AND UNIX_TIMESTAMP(last_played) BETWEEN UNIX_TIMESTAMP(date_sub(now(),INTERVAL 1 WEEK)) AND UNIX_TIMESTAMP(now()) "; std::string filter; // Setup our filter based on the query type @@ -247,7 +248,7 @@ void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t r // Create and execute the actual save here. Using a heap allocated buffer to avoid stack overflow constexpr uint16_t STRING_LENGTH = 4096; std::unique_ptr lookupBuffer = std::make_unique(STRING_LENGTH); - [[maybe_unused]] int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), filter.c_str(), resultStart, resultEnd); + int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), filter.c_str(), resultStart, resultEnd); DluAssert(res != -1); std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer.get())); @@ -292,7 +293,7 @@ std::string FormatInsert(const Leaderboard::Type& type, const Score& score, cons constexpr uint16_t STRING_LENGTH = 400; // Then fill in our score char finishedQuery[STRING_LENGTH]; - [[maybe_unused]] int32_t res = snprintf(finishedQuery, STRING_LENGTH, insertStatement.c_str(), score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore()); + int32_t res = snprintf(finishedQuery, STRING_LENGTH, insertStatement.c_str(), score.GetPrimaryScore(), score.GetSecondaryScore(), score.GetTertiaryScore()); DluAssert(res != -1); return finishedQuery; } @@ -324,7 +325,6 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi break; } case Leaderboard::Type::Survival: { - // Config option may reverse these oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore")); oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore")); break; @@ -357,6 +357,15 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi return; } bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore; + // Nimbus station has a weird leaderboard where we need a custom scoring system + if (leaderboardType == Leaderboard::Type::SurvivalNS) { + newHighScore = newScore.GetPrimaryScore() > oldScore.GetPrimaryScore() || + (newScore.GetPrimaryScore() == oldScore.GetPrimaryScore() && newScore.GetSecondaryScore() < oldScore.GetSecondaryScore()); + } else if (leaderboardType == Leaderboard::Type::Survival && Game::config->GetValue("classic_survival_scoring") == "1") { + Score oldScoreFlipped(oldScore.GetSecondaryScore(), oldScore.GetPrimaryScore()); + Score newScoreFlipped(newScore.GetSecondaryScore(), newScore.GetPrimaryScore()); + newHighScore = newScoreFlipped > oldScoreFlipped; + } if (newHighScore) { saveQuery = FormatInsert(leaderboardType, newScore, true); } else if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore) { diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 9ccfe12a..dd7921db 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -50,7 +50,7 @@ RacingControlComponent::RacingControlComponent(Entity* parent) m_MainWorld = 1200; const auto worldID = Game::server->GetZoneID(); - if (dZoneManager::Instance()->CheckIfAccessibleZone((worldID/10)*10)) m_MainWorld = (worldID/10)*10; + if (dZoneManager::Instance()->CheckIfAccessibleZone((worldID / 10) * 10)) m_MainWorld = (worldID / 10) * 10; m_ActivityID = 42; CDActivitiesTable* activitiesTable = CDClientManager::Instance().GetTable(); @@ -323,7 +323,7 @@ void RacingControlComponent::OnRequestDie(Entity* player) { // Reset imagination to half its current value, rounded up to the nearest value divisible by 10, as it was done in live. if (destroyableComponent) destroyableComponent->SetImagination(respawnImagination); EntityManager::Instance()->SerializeEntity(vehicle); - }); + }); auto* characterComponent = player->GetComponent(); if (characterComponent != nullptr) { @@ -379,7 +379,6 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t bu const auto score = m_LoadedPlayers * 10 + data->finished; LootGenerator::Instance().GiveActivityLoot(player, m_Parent, m_ActivityID, score); - LeaderboardManager::SaveScore(player->GetObjectID(), m_ActivityID, static_cast(data->bestLapTime), static_cast(data->raceTime), static_cast(data->finished == 1)); // Giving rewards GameMessages::SendNotifyRacingClient( @@ -838,6 +837,7 @@ void RacingControlComponent::Update(float deltaTime) { "Completed time %llu, %llu", raceTime, raceTime * 1000); + LeaderboardManager::SaveScore(playerEntity->GetObjectID(), m_ActivityID, static_cast(player.bestLapTime), static_cast(player.raceTime), static_cast(player.finished == 1)); // Entire race time missionComponent->Progress(eMissionTaskType::RACING, (raceTime) * 1000, (LWOOBJID)eRacingTaskParam::TOTAL_TRACK_TIME); diff --git a/dScripts/BaseSurvivalServer.cpp b/dScripts/BaseSurvivalServer.cpp index ac775787..7f522eb5 100644 --- a/dScripts/BaseSurvivalServer.cpp +++ b/dScripts/BaseSurvivalServer.cpp @@ -8,6 +8,8 @@ #include "eMissionState.h" #include "MissionComponent.h" #include "Character.h" +#include "Game.h" +#include "dConfig.h" void BaseSurvivalServer::SetGameVariables(Entity* self) { this->constants = std::move(GetConstants()); @@ -354,6 +356,7 @@ void BaseSurvivalServer::GameOver(Entity* self) { const auto score = GetActivityValue(self, playerID, 0); const auto time = GetActivityValue(self, playerID, 1); + SaveScore(self, playerID, score, time); GameMessages::SendNotifyClientZoneObject(self->GetObjectID(), u"Update_ScoreBoard", time, 0, playerID, std::to_string(score), UNASSIGNED_SYSTEM_ADDRESS); @@ -377,7 +380,6 @@ void BaseSurvivalServer::GameOver(Entity* self) { } StopActivity(self, playerID, score, time); - SaveScore(self, playerID, time, score); } state.waveNumber = 1; diff --git a/dScripts/BaseWavesServer.cpp b/dScripts/BaseWavesServer.cpp index 48144960..e240c7ca 100644 --- a/dScripts/BaseWavesServer.cpp +++ b/dScripts/BaseWavesServer.cpp @@ -378,7 +378,7 @@ void BaseWavesServer::GameOver(Entity* self, bool won) { } StopActivity(self, playerID, wave, time, score); - SaveScore(self, playerID, time, wave); + SaveScore(self, playerID, wave, time); } } From 238fc98ea541af108ee07a82b2e377c07b99aa14 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Wed, 21 Jun 2023 21:46:11 -0700 Subject: [PATCH 51/59] Fix shooting gallery leaderboard bugs - add weekly functionality for top scores - Fix shooting gallery score saving - remove extra leaderboard fetch --- dGame/LeaderboardManager.cpp | 16 +++++++------- .../ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 21 +++++-------------- .../dlu/9_Update_Leaderboard_Storage.sql | 6 ++++-- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 2fd48dc3..f9e44b2a 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -89,12 +89,12 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { entry.push_back(new LDFData(u"RowNumber", rows->getInt("ranking"))); switch (leaderboardType) { case Type::ShootingGallery: - entry.push_back(new LDFData(u"HitPercentage", (rows->getInt("primaryScore") / 100.0f))); - // HitPercentage:3 between 0 and 1 - entry.push_back(new LDFData(u"Score", rows->getInt("secondaryScore"))); + entry.push_back(new LDFData(u"Score", rows->getInt("primaryScore"))); // Score:1 - entry.push_back(new LDFData(u"Streak", rows->getInt("tertiaryScore"))); + entry.push_back(new LDFData(u"Streak", rows->getInt("secondaryScore"))); // Streak:1 + entry.push_back(new LDFData(u"HitPercentage", (rows->getInt("tertiaryScore") / 100.0f))); + // HitPercentage:3 between 0 and 1 break; case Type::Racing: entry.push_back(new LDFData(u"BestTime", rows->getDouble("primaryScore"))); @@ -229,14 +229,14 @@ void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t r // For top query, we want to just rank all scores, but for all others we need the scores around a specific player std::string baseLookup; if (this->infoType == InfoType::Top) { - baseLookup = "SELECT id FROM leaderboard WHERE game_id = ? ORDER BY "; + baseLookup = "SELECT id, last_played FROM leaderboard WHERE game_id = ? " + (this->weekly ? weeklyFilter : std::string("")) + " ORDER BY "; baseLookup += orderBase.data(); } else { - baseLookup = "SELECT id FROM leaderboard WHERE game_id = ? AND character_id = "; + baseLookup = "SELECT id, last_played FROM leaderboard WHERE game_id = ? " + (this->weekly ? weeklyFilter : std::string("")) + " AND character_id = "; baseLookup += std::to_string(static_cast(this->relatedPlayer)); } baseLookup += " LIMIT 1"; - + Game::logger->LogDebug("LeaderboardManager", "query is %s", baseLookup.c_str()); std::unique_ptr baseQuery(Database::CreatePreppedStmt(baseLookup)); baseQuery->setInt(1, this->gameID); std::unique_ptr baseResult(baseQuery->executeQuery()); @@ -251,7 +251,7 @@ void Leaderboard::SetupLeaderboard(bool weekly, uint32_t resultStart, uint32_t r int32_t res = snprintf(lookupBuffer.get(), STRING_LENGTH, queryBase.c_str(), orderBase.data(), filter.c_str(), resultStart, resultEnd); DluAssert(res != -1); std::unique_ptr query(Database::CreatePreppedStmt(lookupBuffer.get())); - + Game::logger->LogDebug("LeaderboardManager", "Query is %s vars are %i %i %i", lookupBuffer.get(), this->gameID, this->relatedPlayer, relatedPlayerLeaderboardId); query->setInt(1, this->gameID); if (this->infoType == InfoType::Friends) { query->setInt(2, this->relatedPlayer); diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index 558c24c4..5b0dfc36 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -546,13 +546,13 @@ void SGCannon::StopGame(Entity* self, bool cancel) { // The player won, store all the score and send rewards if (!cancel) { - int32_t percentage = 50; + int32_t percentage = 0.0f; auto misses = self->GetVar(MissesVariable); auto fired = self->GetVar(ShotsFiredVariable); - // if (fired > 0) { - // percentage = misses / fired; - // } + if (fired > 0) { + percentage = misses / fired; + } auto* missionComponent = player->GetComponent(); @@ -566,7 +566,7 @@ void SGCannon::StopGame(Entity* self, bool cancel) { StopActivity(self, player->GetObjectID(), self->GetVar(TotalScoreVariable), self->GetVar(MaxStreakVariable), percentage); SaveScore(self, player->GetObjectID(), - static_cast(self->GetVar(TotalScoreVariable)), percentage, static_cast(self->GetVar(MaxStreakVariable))); + static_cast(self->GetVar(TotalScoreVariable)), static_cast(self->GetVar(MaxStreakVariable)), percentage); self->SetNetworkVar(AudioFinalWaveDoneVariable, true); // Give the player the model rewards they earned @@ -580,17 +580,6 @@ void SGCannon::StopGame(Entity* self, bool cancel) { self->SetNetworkVar(u"UI_Rewards", GeneralUtils::to_u16string(self->GetVar(TotalScoreVariable)) + u"_0_0_0_0_0_0" ); - - GameMessages::SendRequestActivitySummaryLeaderboardData( - player->GetObjectID(), - self->GetObjectID(), - player->GetSystemAddress(), - GetGameID(self), - 1, - 10, - 0, - false - ); } GameMessages::SendActivityStop(self->GetObjectID(), false, cancel, player->GetSystemAddress()); diff --git a/migrations/dlu/9_Update_Leaderboard_Storage.sql b/migrations/dlu/9_Update_Leaderboard_Storage.sql index a68cc2d8..c87e3501 100644 --- a/migrations/dlu/9_Update_Leaderboard_Storage.sql +++ b/migrations/dlu/9_Update_Leaderboard_Storage.sql @@ -5,8 +5,6 @@ ALTER TABLE leaderboard MODIFY time INT NOT NULL DEFAULT 0; /* Can only ALTER one column at a time... */ -ALTER TABLE leaderboard - CHANGE last_played last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(); ALTER TABLE leaderboard CHANGE score primaryScore FLOAT NOT NULL DEFAULT 0; ALTER TABLE leaderboard CHANGE time secondaryScore FLOAT NOT NULL DEFAULT 0 AFTER primaryScore; @@ -14,3 +12,7 @@ ALTER TABLE leaderboard CHANGE time secondaryScore FLOAT NOT NULL DEFAULT 0 AFTE UPDATE leaderboard SET primaryScore = secondaryScore, secondaryScore = 0 WHERE game_id IN (1, 44, 46, 47, 48, 49, 53, 103, 104, 108, 1901); + +/* Do this last so we dont update entry times erroneously */ +ALTER TABLE leaderboard + CHANGE last_played last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(); From d0e79d19fc2c4167dd5cd4c561b8cff12ac9ca94 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 21 Jun 2023 21:48:58 -0700 Subject: [PATCH 52/59] Update Metrics.hpp --- dCommon/Metrics.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/dCommon/Metrics.hpp b/dCommon/Metrics.hpp index 7009701b..c03c914f 100644 --- a/dCommon/Metrics.hpp +++ b/dCommon/Metrics.hpp @@ -20,7 +20,6 @@ enum class MetricVariable : int32_t CPUTime, Sleep, Frame, - Leaderboard, }; struct Metric From 8156e5cc91f2907287a94e4e5fed50aed9429f4c Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 21 Jun 2023 21:50:16 -0700 Subject: [PATCH 53/59] Update MigrationRunner.cpp --- dDatabase/MigrationRunner.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index 3cc4266e..5e70c401 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -109,7 +109,7 @@ void MigrationRunner::RunSQLiteMigrations() { // Check if there is an entry in the migration history table on the cdclient database. cdstmt = CDClientDatabase::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); - cdstmt.bind((int32_t)1, migration.name.c_str()); + cdstmt.bind((int32_t) 1, migration.name.c_str()); auto cdres = cdstmt.execQuery(); bool doExit = !cdres.eof(); cdres.finalize(); @@ -127,7 +127,7 @@ void MigrationRunner::RunSQLiteMigrations() { if (doExit) { // Insert into cdclient database if there is an entry in the main database but not the cdclient database. cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); - cdstmt.bind((int32_t)1, migration.name.c_str()); + cdstmt.bind((int32_t) 1, migration.name.c_str()); cdstmt.execQuery().finalize(); cdstmt.finalize(); continue; @@ -148,7 +148,7 @@ void MigrationRunner::RunSQLiteMigrations() { // Insert into cdclient database. cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); - cdstmt.bind((int32_t)1, migration.name.c_str()); + cdstmt.bind((int32_t) 1, migration.name.c_str()); cdstmt.execQuery().finalize(); cdstmt.finalize(); CDClientDatabase::ExecuteQuery("COMMIT;"); From 393733cc67a8e73090809ed7620c10d883d8056f Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 21 Jun 2023 21:50:54 -0700 Subject: [PATCH 54/59] Update GameMessageHandler.cpp --- dGame/dGameMessages/GameMessageHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index ca7a98cb..be751598 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -45,7 +45,7 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System Entity* entity = EntityManager::Instance()->GetEntity(objectID); User* usr = UserManager::Instance()->GetUser(sysAddr); - if (messageID != eGameMessageType::READY_FOR_UPDATES) Game::logger->Log("GameMessageHandler", "message %i", messageID); + if (!entity) { Game::logger->Log("GameMessageHandler", "Failed to find associated entity (%llu), aborting GM (%X)!", objectID, messageID); From 85aa82b4b902b71c7f612f95c33ebf09c4196ece Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Wed, 21 Jun 2023 21:53:10 -0700 Subject: [PATCH 55/59] cleanup --- dGame/dGameMessages/GameMessages.cpp | 1 - tests/dGameTests/CMakeLists.txt | 1 - tests/dGameTests/GameDependencies.h | 2 - tests/dGameTests/LeaderboardTests.cpp | 79 --------------------------- 4 files changed, 83 deletions(-) delete mode 100644 tests/dGameTests/LeaderboardTests.cpp diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index f1f345ea..deae57d3 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1646,7 +1646,6 @@ void GameMessages::SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, bitStream.Write(eGameMessageType::SEND_ACTIVITY_SUMMARY_LEADERBOARD_DATA); leaderboard->Serialize(&bitStream); - PacketUtils::SavePacket("leaderboardData.bin", (const char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed()); SEND_PACKET; } diff --git a/tests/dGameTests/CMakeLists.txt b/tests/dGameTests/CMakeLists.txt index 30b5e20b..b1fdaa07 100644 --- a/tests/dGameTests/CMakeLists.txt +++ b/tests/dGameTests/CMakeLists.txt @@ -1,6 +1,5 @@ set(DGAMETEST_SOURCES "GameDependencies.cpp" - "LeaderboardTests.cpp" ) add_subdirectory(dComponentsTests) diff --git a/tests/dGameTests/GameDependencies.h b/tests/dGameTests/GameDependencies.h index 87f56ad0..353b53b8 100644 --- a/tests/dGameTests/GameDependencies.h +++ b/tests/dGameTests/GameDependencies.h @@ -6,7 +6,6 @@ #include "dServer.h" #include "EntityInfo.h" #include "EntityManager.h" -#include "Database.h" #include "dConfig.h" #include @@ -33,7 +32,6 @@ protected: Game::logger = new dLogger("./testing.log", true, true); Game::server = new dServerMock(); Game::config = new dConfig("worldconfig.ini"); - // Database::Connect(Game::config->GetValue("mysql_host"), Game::config->GetValue("mysql_database"), Game::config->GetValue("mysql_username"), Game::config->GetValue("mysql_password")); } void TearDownDependencies() { diff --git a/tests/dGameTests/LeaderboardTests.cpp b/tests/dGameTests/LeaderboardTests.cpp deleted file mode 100644 index 8cb833a3..00000000 --- a/tests/dGameTests/LeaderboardTests.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "LeaderboardManager.h" - -#include "BitStream.h" -#include "GameDependencies.h" -#include "Metrics.hpp" -#include - -class LeaderboardTests : public GameDependenciesTest { -protected: - void SetUp() override { - SetUpDependencies(); - } - void TearDown() override { - TearDownDependencies(); - } - - void TestLeaderboard(Leaderboard& leaderboard, int32_t entries) { - bitStream.Reset(); - Metrics::StartMeasurement(MetricVariable::Leaderboard); - for (int32_t i = 0; i < MAX_MEASURMENT_POINTS; i++) leaderboard.Serialize(&bitStream); - Metrics::EndMeasurement(MetricVariable::Leaderboard); - - auto timePassed = Metrics::GetMetric(MetricVariable::Leaderboard)->average; - Game::logger->Log("LeaderboardManager", "average time passed for %i leaderboard entries is %lluns", entries, timePassed); - bitStream.Reset(); - } - - void RunTests(uint32_t gameID, Leaderboard::Type type, Leaderboard::InfoType infoType) { - Game::logger->Log("LeaderboardTests", "Testing leaderboard %i for Serialize speed", infoType); - Leaderboard leaderboard(gameID, infoType, false, 14231, type); - leaderboard.SetupLeaderboard(true, 0, 10); - leaderboard.Serialize(&bitStream); - TestLeaderboard(leaderboard, 1); - TestLeaderboard(leaderboard, 10); - TestLeaderboard(leaderboard, 100); - TestLeaderboard(leaderboard, 1000); - } - - CBITSTREAM; -}; - -/** - * Initial metrics - * 19: [12-04-23 23:56:31] [LeaderboardManager] average time passed for 1 leaderboard entries is 1671700ns - * 19: [12-04-23 23:56:31] [LeaderboardManager] average time passed for 10 leaderboard entries is 8388900ns - * 19: [12-04-23 23:56:31] [LeaderboardManager] average time passed for 100 leaderboard entries is 54680133ns - * 19: [12-04-23 23:56:33] [LeaderboardManager] average time passed for 1000 leaderboard entries is 506289325ns - - * Only do each std::to_string once - * 19: [12-04-23 23:57:31] [LeaderboardManager] average time passed for 1 leaderboard entries is 1472700ns - * 19: [12-04-23 23:57:31] [LeaderboardManager] average time passed for 10 leaderboard entries is 7035650ns - * 19: [12-04-23 23:57:31] [LeaderboardManager] average time passed for 100 leaderboard entries is 45147466ns - * 19: [12-04-23 23:57:33] [LeaderboardManager] average time passed for 1000 leaderboard entries is 435724550ns - * - * Only do Result[0].Row[index] once - * 19: [12-04-23 23:59:43] [LeaderboardManager] average time passed for 1 leaderboard entries is 1357700ns - * 19: [12-04-23 23:59:43] [LeaderboardManager] average time passed for 10 leaderboard entries is 6635350ns - * 19: [12-04-23 23:59:43] [LeaderboardManager] average time passed for 100 leaderboard entries is 40247800ns - * 19: [12-04-23 23:59:45] [LeaderboardManager] average time passed for 1000 leaderboard entries is 400965900ns - * - * Switch to ostringstream - * 19: [13-04-23 00:24:44] [LeaderboardManager] average time passed for 1 leaderboard entries is 1334300ns - * 19: [13-04-23 00:24:44] [LeaderboardManager] average time passed for 10 leaderboard entries is 5566250ns - * 19: [13-04-23 00:24:44] [LeaderboardManager] average time passed for 100 leaderboard entries is 34640066ns - * 19: [13-04-23 00:24:46] [LeaderboardManager] average time passed for 1000 leaderboard entries is 357226950ns - * - * No more std::to_string and revert "Only do Result[0].Row[index] once" - * 19: [13-04-23 00:39:18] [LeaderboardManager] average time passed for 1 leaderboard entries is 979200ns - * 19: [13-04-23 00:39:18] [LeaderboardManager] average time passed for 10 leaderboard entries is 4053350ns - * 19: [13-04-23 00:39:18] [LeaderboardManager] average time passed for 100 leaderboard entries is 24785233ns - * 19: [13-04-23 00:39:19] [LeaderboardManager] average time passed for 1000 leaderboard entries is 279457375ns - */ - -// Commented tests out because we dont have a sql server running for tests -// 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); -// } From cc251d0986d5e9f3ab5106fbc089087dcc530e95 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Fri, 21 Jul 2023 19:39:44 -0700 Subject: [PATCH 56/59] Update ActivityManager.cpp --- dScripts/ActivityManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dScripts/ActivityManager.cpp b/dScripts/ActivityManager.cpp index fbe22da8..24e661e3 100644 --- a/dScripts/ActivityManager.cpp +++ b/dScripts/ActivityManager.cpp @@ -80,7 +80,7 @@ void ActivityManager::StopActivity(Entity* self, const LWOOBJID playerID, const } void ActivityManager::SaveScore(Entity* self, const LWOOBJID playerID, const float primaryScore, const float secondaryScore, const float tertiaryScore) const { - auto* player = EntityManager::Instance()->GetEntity(playerID); + auto* player = Game::entityManager->GetEntity(playerID); if (!player) return; auto* sac = self->GetComponent(); From 887e2a25f08a9572a7f2ef768ba3ebef39c29b73 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Fri, 21 Jul 2023 23:18:51 -0700 Subject: [PATCH 57/59] Fix race score bugs num wins serialized properly scores are saved and num wins and times played are incremented properly --- dGame/LeaderboardManager.cpp | 19 +++++++++++++++---- dGame/dComponents/RacingControlComponent.cpp | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 71efe141..a4c6ec1a 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -103,7 +103,7 @@ void Leaderboard::QueryToLdf(std::unique_ptr& rows) { // 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", rows->getInt("tertiaryScore"))); + entry.push_back(new LDFData(u"NumWins", rows->getInt("numWins"))); // NumWins:1 break; case Type::UnusedLeaderboard4: @@ -342,6 +342,11 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi case Leaderboard::Type::Racing: { oldScore.SetPrimaryScore(myScoreResult->getInt("primaryScore")); oldScore.SetSecondaryScore(myScoreResult->getInt("secondaryScore")); + + // For wins we dont care about the score, just the time, so zero out the tertiary. + // Wins are updated later. + oldScore.SetTertiaryScore(0); + newScore.SetTertiaryScore(0); lowerScoreBetter = true; break; } @@ -368,17 +373,23 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi } if (newHighScore) { saveQuery = FormatInsert(leaderboardType, newScore, true); - } else if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore) { - saveQuery = "UPDATE leaderboard SET numWins = numWins + 1, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;"; } } else { saveQuery = FormatInsert(leaderboardType, newScore, false); } - + Game::logger->Log("LeaderboardManager", "save query %s %i %i", saveQuery.c_str(), playerID, activityId); std::unique_ptr saveStatement(Database::CreatePreppedStmt(saveQuery)); saveStatement->setInt(1, playerID); saveStatement->setInt(2, activityId); saveStatement->execute(); + + // track wins separately + if (leaderboardType == Leaderboard::Type::Racing && tertiaryScore != 0.0f) { + std::unique_ptr winUpdate(Database::CreatePreppedStmt("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;")); + winUpdate->setInt(1, playerID); + winUpdate->setInt(2, activityId); + winUpdate->execute(); + } } void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t resultStart, const uint32_t resultEnd) { diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 1f3e144a..5de24445 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -838,7 +838,7 @@ void RacingControlComponent::Update(float deltaTime) { "Completed time %llu, %llu", raceTime, raceTime * 1000); - LeaderboardManager::SaveScore(playerEntity->GetObjectID(), m_ActivityID, static_cast(player.bestLapTime), static_cast(player.raceTime), static_cast(player.finished == 1)); + LeaderboardManager::SaveScore(playerEntity->GetObjectID(), m_ActivityID, static_cast(player.raceTime), static_cast(player.bestLapTime), static_cast(player.finished == 1)); // Entire race time missionComponent->Progress(eMissionTaskType::RACING, (raceTime) * 1000, (LWOOBJID)eRacingTaskParam::TOTAL_TRACK_TIME); From a625d2eae5d80ea56e19568c7d1fb69d6301032e Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Fri, 21 Jul 2023 23:42:50 -0700 Subject: [PATCH 58/59] Update SGCannon.cpp --- dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index d825d8cf..4aa8d0c9 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -564,9 +564,10 @@ void SGCannon::StopGame(Entity* self, bool cancel) { LootGenerator::Instance().GiveActivityLoot(player, self, GetGameID(self), self->GetVar(TotalScoreVariable)); - StopActivity(self, player->GetObjectID(), self->GetVar(TotalScoreVariable), self->GetVar(MaxStreakVariable), percentage); SaveScore(self, player->GetObjectID(), static_cast(self->GetVar(TotalScoreVariable)), static_cast(self->GetVar(MaxStreakVariable)), percentage); + + StopActivity(self, player->GetObjectID(), self->GetVar(TotalScoreVariable), self->GetVar(MaxStreakVariable), percentage); self->SetNetworkVar(AudioFinalWaveDoneVariable, true); // Give the player the model rewards they earned From b87537c637a40736a68545a99c2c833c39b76745 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Sat, 22 Jul 2023 02:16:53 -0700 Subject: [PATCH 59/59] bump minor version --- CMakeVariables.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeVariables.txt b/CMakeVariables.txt index d3c8b36f..02173ba7 100644 --- a/CMakeVariables.txt +++ b/CMakeVariables.txt @@ -1,6 +1,6 @@ PROJECT_VERSION_MAJOR=1 -PROJECT_VERSION_MINOR=0 -PROJECT_VERSION_PATCH=4 +PROJECT_VERSION_MINOR=1 +PROJECT_VERSION_PATCH=0 # LICENSE LICENSE=AGPL-3.0 # The network version.