2021-12-05 17:54:36 +00:00
# include "LeaderboardManager.h"
# include <utility>
# include "Database.h"
# include "EntityManager.h"
# include "Character.h"
2021-12-22 03:15:29 +00:00
# include "Game.h"
2021-12-05 17:54:36 +00:00
# include "GameMessages.h"
# include "dLogger.h"
2021-12-22 03:15:29 +00:00
# include "dConfig.h"
2021-12-05 17:54:36 +00:00
Leaderboard : : Leaderboard ( uint32_t gameID , uint32_t infoType , bool weekly , std : : vector < LeaderboardEntry > entries ,
LWOOBJID relatedPlayer , LeaderboardType leaderboardType ) {
this - > relatedPlayer = relatedPlayer ;
this - > gameID = gameID ;
this - > weekly = weekly ;
this - > infoType = infoType ;
this - > entries = std : : move ( entries ) ;
this - > leaderboardType = leaderboardType ;
}
std : : u16string Leaderboard : : ToString ( ) const {
std : : string 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 " ;
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 " ;
// 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 " ;
} else if ( leaderboardType = = SurvivalNS ) {
leaderboard + = " Result[0].Row[ " + std : : to_string ( index ) + " ].Wave=1: " + std : : to_string ( entry . score ) + " \n " ;
}
leaderboard + = " Result[0].Row[ " + std : : to_string ( index ) + " ].name=0: " + entry . playerName + " \n " ;
index + + ;
}
return GeneralUtils : : ASCIIToUTF16 ( leaderboard ) ;
}
std : : vector < LeaderboardEntry > Leaderboard : : GetEntries ( ) {
return entries ;
}
uint32_t Leaderboard : : GetGameID ( ) const {
return gameID ;
}
uint32_t Leaderboard : : GetInfoType ( ) const {
return infoType ;
}
void Leaderboard : : Send ( LWOOBJID targetID ) const {
auto * player = EntityManager : : Instance ( ) - > GetEntity ( relatedPlayer ) ;
if ( player ! = nullptr ) {
GameMessages : : SendActivitySummaryLeaderboardData ( targetID , this , player - > GetSystemAddress ( ) ) ;
}
}
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 ;
auto * character = player - > GetCharacter ( ) ;
if ( character = = nullptr )
return ;
auto * select = Database : : CreatePreppedStmt ( " SELECT time, score FROM leaderboard WHERE character_id = ? AND game_id = ?; " ) ;
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 ;
2021-12-22 03:15:29 +00:00
bool classicSurvivalScoring = Game : : config - > GetValue ( " classic_survival_scoring " ) = = " 1 " ;
2021-12-05 17:54:36 +00:00
switch ( leaderboardType ) {
case ShootingGallery :
if ( score < = storedScore )
highscore = false ;
break ;
case Racing :
2021-12-22 03:15:29 +00:00
if ( time > = storedTime )
highscore = false ;
break ;
2021-12-05 17:54:36 +00:00
case MonumentRace :
2021-12-22 03:15:29 +00:00
if ( time > = storedTime )
2021-12-05 17:54:36 +00:00
highscore = false ;
break ;
case FootRace :
if ( time < = storedTime )
highscore = false ;
break ;
case Survival :
2021-12-22 03:15:29 +00:00
if ( classicSurvivalScoring ) {
if ( time < = storedTime ) { // Based on time (LU live)
highscore = false ;
}
}
else {
if ( score < = storedScore ) // Based on score (DLU)
highscore = false ;
}
break ;
2021-12-05 17:54:36 +00:00
case SurvivalNS :
2021-12-22 03:30:37 +00:00
if ( score < storedScore | | time > = storedTime )
2021-12-05 17:54:36 +00:00
highscore = false ;
break ;
default :
highscore = false ;
}
if ( ! highscore ) {
delete select ;
delete result ;
return ;
}
}
delete select ;
delete result ;
if ( any ) {
2021-12-22 03:15:29 +00:00
auto * statement = Database : : CreatePreppedStmt ( " UPDATE leaderboard SET time = ?, score = ?, last_played=SYSDATE() WHERE character_id = ? AND game_id = ?; " ) ;
2021-12-05 17:54:36 +00:00
statement - > setInt ( 1 , time ) ;
statement - > setInt ( 2 , score ) ;
statement - > setUInt64 ( 3 , character - > GetID ( ) ) ;
statement - > setInt ( 4 , gameID ) ;
statement - > execute ( ) ;
delete statement ;
} else {
2021-12-22 03:30:37 +00:00
// Note: last_played will be set to SYSDATE() by default when inserting into leaderboard
2021-12-05 17:54:36 +00:00
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 ;
}
}
Leaderboard * LeaderboardManager : : GetLeaderboard ( uint32_t gameID , InfoType infoType , bool weekly , LWOOBJID playerID ) {
auto leaderboardType = GetLeaderboardType ( gameID ) ;
std : : string query ;
2021-12-22 03:15:29 +00:00
bool classicSurvivalScoring = Game : : config - > GetValue ( " classic_survival_scoring " ) = = " 1 " ;
2021-12-05 17:54:36 +00:00
switch ( infoType ) {
case InfoType : : Standings :
2021-12-22 03:15:29 +00:00
switch ( leaderboardType ) {
case ShootingGallery :
query = standingsScoreQuery ; // Shooting gallery is based on the highest score.
break ;
case FootRace :
2021-12-22 03:30:37 +00:00
query = standingsTimeQuery ; // The higher your time, the better for FootRace.
2021-12-22 03:15:29 +00:00
break ;
case Survival :
query = classicSurvivalScoring ? standingsTimeQuery : standingsScoreQuery ;
break ;
2021-12-22 03:30:37 +00:00
case SurvivalNS :
query = standingsScoreQueryAsc ; // BoNS is scored by highest wave (score) first, then time.
break ;
2021-12-22 03:15:29 +00:00
default :
2021-12-22 03:30:37 +00:00
query = standingsTimeQueryAsc ; // MonumentRace and Racing are based on the shortest time.
2021-12-22 03:15:29 +00:00
}
2021-12-05 17:54:36 +00:00
break ;
case InfoType : : Friends :
2021-12-22 03:15:29 +00:00
switch ( leaderboardType ) {
case ShootingGallery :
query = friendsScoreQuery ; // Shooting gallery is based on the highest score.
break ;
case FootRace :
2021-12-22 03:30:37 +00:00
query = friendsTimeQuery ; // The higher your time, the better for FootRace.
2021-12-22 03:15:29 +00:00
break ;
case Survival :
query = classicSurvivalScoring ? friendsTimeQuery : friendsScoreQuery ;
break ;
2021-12-22 03:30:37 +00:00
case SurvivalNS :
query = friendsScoreQueryAsc ; // BoNS is scored by highest wave (score) first, then time.
break ;
2021-12-22 03:15:29 +00:00
default :
2021-12-22 03:30:37 +00:00
query = friendsTimeQueryAsc ; // MonumentRace and Racing are based on the shortest time.
2021-12-22 03:15:29 +00:00
}
2021-12-05 17:54:36 +00:00
break ;
2021-12-22 03:15:29 +00:00
2021-12-05 17:54:36 +00:00
default :
2021-12-22 03:15:29 +00:00
switch ( leaderboardType ) {
case ShootingGallery :
query = topPlayersScoreQuery ; // Shooting gallery is based on the highest score.
break ;
case FootRace :
2021-12-22 03:30:37 +00:00
query = topPlayersTimeQuery ; // The higher your time, the better for FootRace.
2021-12-22 03:15:29 +00:00
break ;
case Survival :
query = classicSurvivalScoring ? topPlayersTimeQuery : topPlayersScoreQuery ;
break ;
2021-12-22 03:30:37 +00:00
case SurvivalNS :
query = topPlayersScoreQueryAsc ; // BoNS is scored by highest wave (score) first, then time.
break ;
2021-12-22 03:15:29 +00:00
default :
2021-12-22 03:30:37 +00:00
query = topPlayersTimeQueryAsc ; // MonumentRace and Racing are based on the shortest time.
2021-12-22 03:15:29 +00:00
}
2021-12-05 17:54:36 +00:00
}
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 < LeaderboardEntry > entries { } ;
uint32_t index = 0 ;
while ( res - > next ( ) ) {
entries . push_back ( {
res - > getUInt64 ( 4 ) ,
res - > getString ( 5 ) ,
res - > getUInt ( 1 ) ,
res - > getUInt ( 2 ) ,
res - > getUInt ( 3 ) ,
res - > getUInt ( 6 )
} ) ;
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 ,
LWOOBJID playerID ) {
const auto * leaderboard = LeaderboardManager : : GetLeaderboard ( gameID , infoType , weekly , playerID ) ;
leaderboard - > Send ( targetID ) ;
delete leaderboard ;
}
LeaderboardType LeaderboardManager : : GetLeaderboardType ( uint32_t gameID ) {
auto * activitiesTable = CDClientManager : : Instance ( ) - > GetTable < CDActivitiesTable > ( " Activities " ) ;
std : : vector < CDActivities > activities = activitiesTable - > Query ( [ = ] ( const CDActivities & entry ) {
return ( entry . ActivityID = = gameID ) ;
} ) ;
for ( const auto & activity : activities ) {
return static_cast < LeaderboardType > ( activity . leaderboardType ) ;
}
return LeaderboardType : : None ;
}
2021-12-22 03:15:29 +00:00
const std : : string LeaderboardManager : : topPlayersScoreQuery =
2021-12-05 17:54:36 +00:00
" 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; " ;
2021-12-22 03:15:29 +00:00
const std : : string LeaderboardManager : : friendsScoreQuery =
2021-12-05 17:54:36 +00:00
" 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); " ;
2021-12-22 03:15:29 +00:00
const std : : string LeaderboardManager : : standingsScoreQuery =
2021-12-05 17:54:36 +00:00
" 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; " ;
2021-12-22 03:15:29 +00:00
const std : : string LeaderboardManager : : topPlayersScoreQueryAsc =
2021-12-05 17:54:36 +00:00
" 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; " ;
2021-12-22 03:15:29 +00:00
const std : : string LeaderboardManager : : friendsScoreQueryAsc =
2021-12-05 17:54:36 +00:00
" 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); " ;
2021-12-22 03:15:29 +00:00
const std : : string LeaderboardManager : : standingsScoreQueryAsc =
2021-12-05 17:54:36 +00:00
" 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; " ;
2021-12-22 03:15:29 +00:00
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; " ;