diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index 0efe9c77..bb8697e6 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -3,8 +3,10 @@ #include "Database.h" #include "EntityManager.h" #include "Character.h" +#include "Game.h" #include "GameMessages.h" #include "dLogger.h" +#include "dConfig.h" Leaderboard::Leaderboard(uint32_t gameID, uint32_t infoType, bool weekly, std::vector entries, LWOOBJID relatedPlayer, LeaderboardType leaderboardType) { @@ -90,6 +92,7 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t 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 ShootingGallery: @@ -97,8 +100,11 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t highscore = false; break; case Racing: + if (time >= storedTime) + highscore = false; + break; case MonumentRace: - if (time > storedTime) + if (time >= storedTime) highscore = false; break; case FootRace: @@ -106,8 +112,18 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t highscore = false; break; case Survival: + if (classicSurvivalScoring) { + if (time <= storedTime) { // Based on time (LU live) + highscore = false; + } + } + else { + if (score <= storedScore) // Based on score (DLU) + highscore = false; + } + break; case SurvivalNS: - if (score < storedScore || time >= storedTime) + if (!(score > storedScore || (time < storedTime && score >= storedScore))) highscore = false; break; default: @@ -125,7 +141,7 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t delete result; if (any) { - auto* statement = Database::CreatePreppedStmt("UPDATE leaderboard SET time = ?, score = ? WHERE character_id = ? AND game_id = ?"); + 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()); @@ -134,6 +150,7 @@ void LeaderboardManager::SaveScore(LWOOBJID playerID, uint32_t gameID, uint32_t 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); @@ -149,15 +166,62 @@ Leaderboard *LeaderboardManager::GetLeaderboard(uint32_t gameID, InfoType infoTy auto leaderboardType = GetLeaderboardType(gameID); std::string query; + bool classicSurvivalScoring = Game::config->GetValue("classic_survival_scoring") == "1"; switch (infoType) { case InfoType::Standings: - query = leaderboardType == MonumentRace ? standingsQueryAsc : standingsQuery; + 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: - query = leaderboardType == MonumentRace ? friendsQueryAsc : friendsQuery; + 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: - query = leaderboardType == MonumentRace ? topPlayersQueryAsc : topPlayersQuery; + 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); @@ -220,7 +284,7 @@ LeaderboardType LeaderboardManager::GetLeaderboardType(uint32_t gameID) { return LeaderboardType::None; } -const std::string LeaderboardManager::topPlayersQuery = +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 " @@ -231,7 +295,7 @@ const std::string LeaderboardManager::topPlayersQuery = "SELECT time, score, leaderboard_rank, id, name, last_played " "FROM leaderboard_vales LIMIT 11;"; -const std::string LeaderboardManager::friendsQuery = +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 " @@ -249,7 +313,7 @@ const std::string LeaderboardManager::friendsQuery = "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::standingsQuery = +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 " @@ -265,7 +329,7 @@ const std::string LeaderboardManager::standingsQuery = "FROM leaderboard_vales, personal_values " "WHERE leaderboard_rank BETWEEN min_rank AND max_rank;"; -const std::string LeaderboardManager::topPlayersQueryAsc = +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 " @@ -276,7 +340,7 @@ const std::string LeaderboardManager::topPlayersQueryAsc = "SELECT time, score, leaderboard_rank, id, name, last_played " "FROM leaderboard_vales LIMIT 11;"; -const std::string LeaderboardManager::friendsQueryAsc = +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 " @@ -294,7 +358,7 @@ const std::string LeaderboardManager::friendsQueryAsc = "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::standingsQueryAsc = +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 " @@ -309,3 +373,93 @@ const std::string LeaderboardManager::standingsQueryAsc = "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 ce092462..c4479f1a 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -60,11 +60,21 @@ public: static LeaderboardType GetLeaderboardType(uint32_t gameID); private: static LeaderboardManager* address; - static const std::string topPlayersQuery; - static const std::string friendsQuery; - static const std::string standingsQuery; - static const std::string topPlayersQueryAsc; - static const std::string friendsQueryAsc; - static const std::string standingsQueryAsc; + + // 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; }; diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index d8d8ef87..b1fec1a6 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -52,4 +52,8 @@ solo_racing=0 disable_anti_speedhack=0 # 0 or 1, check server fdb (res/CDServer.fdb) against clients -check_fdb=0 \ No newline at end of file +check_fdb=0 + +# 0 or 1, DLU leaderboards will rate Avant Gardens Survival based on score by default. +# This option should be set to 1 if you would like it to reflect the game when it was live (scoring based on time). +classic_survival_scoring=0