2021-12-05 17:54:36 +00:00
# include "LeaderboardManager.h"
2023-06-01 06:05:19 +00:00
# include <sstream>
2021-12-05 17:54:36 +00:00
# include <utility>
2023-06-01 06:05:19 +00:00
2021-12-05 17:54:36 +00:00
# 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"
2022-12-18 15:46:04 +00:00
# include "CDClientManager.h"
2023-03-17 14:36:21 +00:00
# include "GeneralUtils.h"
# include "Entity.h"
2023-04-14 08:32:52 +00:00
# include "LDFFormat.h"
2023-05-30 11:11:37 +00:00
# include "DluAssert.h"
2023-03-17 14:36:21 +00:00
# include "CDActivitiesTable.h"
2023-04-13 07:45:03 +00:00
# include "Metrics.hpp"
2023-04-26 09:10:57 +00:00
2023-05-30 11:11:37 +00:00
namespace LeaderboardManager {
2023-06-05 11:10:59 +00:00
std : : map < GameID , Leaderboard : : Type > leaderboardCache ;
2023-05-30 11:11:37 +00:00
}
2023-05-09 08:42:11 +00:00
2023-05-03 07:38:38 +00:00
Leaderboard : : Leaderboard ( const GameID gameID , const Leaderboard : : InfoType infoType , const bool weekly , LWOOBJID relatedPlayer , const Leaderboard : : Type leaderboardType ) {
2021-12-05 17:54:36 +00:00
this - > gameID = gameID ;
this - > weekly = weekly ;
this - > infoType = infoType ;
this - > leaderboardType = leaderboardType ;
2023-05-03 07:38:38 +00:00
this - > relatedPlayer = relatedPlayer ;
2021-12-05 17:54:36 +00:00
}
2023-05-03 07:38:38 +00:00
Leaderboard : : ~ Leaderboard ( ) {
2023-06-01 06:05:19 +00:00
Clear ( ) ;
}
void Leaderboard : : Clear ( ) {
2023-06-05 11:10:59 +00:00
for ( auto & entry : entries ) for ( auto ldfData : entry ) delete ldfData ;
2023-04-14 08:32:52 +00:00
}
2023-05-30 11:38:19 +00:00
inline void WriteLeaderboardRow ( std : : ostringstream & leaderboard , const uint32_t & index , LDFBaseData * data ) {
2023-05-10 05:00:13 +00:00
leaderboard < < " \n Result[0].Row[ " < < index < < " ]. " < < data - > GetString ( ) ;
2023-05-03 07:38:38 +00:00
}
2023-05-30 11:38:19 +00:00
void Leaderboard : : Serialize ( RakNet : : BitStream * bitStream ) const {
2023-05-09 02:35:19 +00:00
bitStream - > Write ( gameID ) ;
2023-05-09 08:42:11 +00:00
bitStream - > Write ( infoType ) ;
2023-05-09 02:35:19 +00:00
2023-04-13 07:45:03 +00:00
std : : ostringstream leaderboard ;
2023-06-01 06:05:19 +00:00
2023-05-10 05:00:13 +00:00
leaderboard < < " ADO.Result=7:1 " ; // Unused in 1.10.64, but is in captures
2023-05-28 11:30:20 +00:00
leaderboard < < " \n Result.Count=1:1 " ; // number of results, always 1
if ( ! this - > entries . empty ( ) ) leaderboard < < " \n Result[0].Index=0:RowNumber " ; // "Primary key". Live doesn't include this if there are no entries.
leaderboard < < " \n Result[0].RowCount=1: " < < entries . size ( ) ;
2022-07-28 13:39:57 +00:00
2023-05-09 01:36:28 +00:00
int32_t rowNumber = 0 ;
2023-05-03 07:38:38 +00:00
for ( auto & entry : entries ) {
2023-05-09 01:36:28 +00:00
for ( auto * data : entry ) {
WriteLeaderboardRow ( leaderboard , rowNumber , data ) ;
2021-12-05 17:54:36 +00:00
}
2023-05-09 01:36:28 +00:00
rowNumber + + ;
2021-12-05 17:54:36 +00:00
}
2023-05-28 11:30:20 +00:00
2023-04-13 04:57:58 +00:00
// Serialize the thing to a BitStream
2023-05-09 08:42:11 +00:00
uint32_t leaderboardSize = leaderboard . tellp ( ) ;
bitStream - > Write < uint32_t > ( leaderboardSize ) ;
// Doing this all in 1 call so there is no possbility of a dangling pointer.
2023-05-28 11:30:20 +00:00
bitStream - > WriteAlignedBytes ( reinterpret_cast < const unsigned char * > ( GeneralUtils : : ASCIIToUTF16 ( leaderboard . str ( ) ) . c_str ( ) ) , leaderboardSize * sizeof ( char16_t ) ) ;
2023-06-05 11:10:59 +00:00
if ( leaderboardSize > 0 ) bitStream - > Write < uint16_t > ( 0 ) ;
2023-05-09 02:35:19 +00:00
bitStream - > Write0 ( ) ;
bitStream - > Write0 ( ) ;
2021-12-05 17:54:36 +00:00
}
2023-05-09 01:36:28 +00:00
void Leaderboard : : QueryToLdf ( std : : unique_ptr < sql : : ResultSet > & rows ) {
2023-06-01 06:05:19 +00:00
Clear ( ) ;
2023-05-09 01:36:28 +00:00
if ( rows - > rowsCount ( ) = = 0 ) return ;
2023-05-03 07:38:38 +00:00
2023-05-09 01:36:28 +00:00
this - > entries . reserve ( rows - > rowsCount ( ) ) ;
while ( rows - > next ( ) ) {
constexpr int32_t MAX_NUM_DATA_PER_ROW = 9 ;
this - > entries . push_back ( std : : vector < LDFBaseData * > ( ) ) ;
auto & entry = this - > entries . back ( ) ;
entry . reserve ( MAX_NUM_DATA_PER_ROW ) ;
entry . push_back ( new LDFData < uint64_t > ( u " CharacterID " , rows - > getInt ( " character_id " ) ) ) ;
entry . push_back ( new LDFData < uint64_t > ( u " LastPlayed " , rows - > getUInt64 ( " lastPlayed " ) ) ) ;
2023-06-05 09:31:49 +00:00
entry . push_back ( new LDFData < int32_t > ( u " NumPlayed " , rows - > getInt ( " timesPlayed " ) ) ) ;
2023-05-09 01:36:28 +00:00
entry . push_back ( new LDFData < std : : u16string > ( u " name " , GeneralUtils : : ASCIIToUTF16 ( rows - > getString ( " name " ) . c_str ( ) ) ) ) ;
2023-05-09 08:42:11 +00:00
entry . push_back ( new LDFData < uint64_t > ( u " RowNumber " , rows - > getInt ( " ranking " ) ) ) ;
2023-05-09 01:36:28 +00:00
switch ( leaderboardType ) {
case Type : : ShootingGallery :
2023-06-22 04:46:11 +00:00
entry . push_back ( new LDFData < int32_t > ( u " Score " , rows - > getInt ( " primaryScore " ) ) ) ;
2023-05-09 01:36:28 +00:00
// Score:1
2023-06-22 04:46:11 +00:00
entry . push_back ( new LDFData < int32_t > ( u " Streak " , rows - > getInt ( " secondaryScore " ) ) ) ;
2023-05-09 01:36:28 +00:00
// Streak:1
2023-06-22 04:46:11 +00:00
entry . push_back ( new LDFData < float > ( u " HitPercentage " , ( rows - > getInt ( " tertiaryScore " ) / 100.0f ) ) ) ;
// HitPercentage:3 between 0 and 1
2023-05-09 01:36:28 +00:00
break ;
case Type : : Racing :
2023-06-01 06:05:19 +00:00
entry . push_back ( new LDFData < float > ( u " BestTime " , rows - > getDouble ( " primaryScore " ) ) ) ;
2023-05-09 01:36:28 +00:00
// BestLapTime:3
2023-06-01 06:05:19 +00:00
entry . push_back ( new LDFData < float > ( u " BestLapTime " , rows - > getDouble ( " secondaryScore " ) ) ) ;
2023-05-09 01:36:28 +00:00
// BestTime:3
entry . push_back ( new LDFData < int32_t > ( u " License " , 1 ) ) ;
// License:1 - 1 if player has completed mission 637 and 0 otherwise
2023-07-22 06:18:51 +00:00
entry . push_back ( new LDFData < int32_t > ( u " NumWins " , rows - > getInt ( " numWins " ) ) ) ;
2023-05-09 01:36:28 +00:00
// NumWins:1
break ;
case Type : : UnusedLeaderboard4 :
2023-06-01 06:05:19 +00:00
entry . push_back ( new LDFData < int32_t > ( u " Points " , rows - > getInt ( " primaryScore " ) ) ) ;
2023-05-09 01:36:28 +00:00
// Points:1
break ;
case Type : : MonumentRace :
2023-06-01 06:05:19 +00:00
entry . push_back ( new LDFData < int32_t > ( u " Time " , rows - > getInt ( " primaryScore " ) ) ) ;
2023-05-09 01:36:28 +00:00
// Time:1(?)
break ;
case Type : : FootRace :
2023-06-01 06:05:19 +00:00
entry . push_back ( new LDFData < int32_t > ( u " Time " , rows - > getInt ( " primaryScore " ) ) ) ;
2023-05-09 01:36:28 +00:00
// Time:1
break ;
case Type : : Survival :
2023-06-01 06:05:19 +00:00
entry . push_back ( new LDFData < int32_t > ( u " Points " , rows - > getInt ( " primaryScore " ) ) ) ;
2023-05-09 01:36:28 +00:00
// Points:1
2023-06-01 06:05:19 +00:00
entry . push_back ( new LDFData < int32_t > ( u " Time " , rows - > getInt ( " secondaryScore " ) ) ) ;
2023-05-09 01:36:28 +00:00
// Time:1
break ;
case Type : : SurvivalNS :
2023-06-01 06:05:19 +00:00
entry . push_back ( new LDFData < int32_t > ( u " Wave " , rows - > getInt ( " primaryScore " ) ) ) ;
2023-05-09 01:36:28 +00:00
// Wave:1
2023-06-01 06:05:19 +00:00
entry . push_back ( new LDFData < int32_t > ( u " Time " , rows - > getInt ( " secondaryScore " ) ) ) ;
2023-05-09 01:36:28 +00:00
// Time:1
break ;
case Type : : Donations :
2023-08-04 02:44:03 +00:00
entry . push_back ( new LDFData < int32_t > ( u " Score " , rows - > getInt ( " primaryScore " ) ) ) ;
// Score:1
2023-05-09 01:36:28 +00:00
break ;
case Type : : None :
// This type is included here simply to resolve a compiler warning on mac about unused enum types
break ;
default :
break ;
}
}
}
2023-05-03 07:38:38 +00:00
2023-05-30 11:11:37 +00:00
const std : : string_view Leaderboard : : GetOrdering ( Leaderboard : : Type leaderboardType ) {
2023-06-01 06:05:19 +00:00
// Use a switch case and return desc for all 3 columns if higher is better and asc if lower is better
2023-05-01 04:30:41 +00:00
switch ( leaderboardType ) {
2023-06-01 06:05:19 +00:00
case Type : : Racing :
case Type : : MonumentRace :
return " primaryScore ASC, secondaryScore ASC, tertiaryScore ASC " ;
case Type : : Survival :
return Game : : config - > GetValue ( " classic_survival_scoring " ) = = " 1 " ?
2023-06-22 02:46:01 +00:00
" secondaryScore DESC, primaryScore DESC, tertiaryScore DESC " :
" primaryScore DESC, secondaryScore DESC, tertiaryScore DESC " ;
case Type : : SurvivalNS :
return " primaryScore DESC, secondaryScore ASC, tertiaryScore DESC " ;
2023-06-05 09:43:02 +00:00
case Type : : ShootingGallery :
case Type : : FootRace :
case Type : : UnusedLeaderboard4 :
case Type : : Donations :
case Type : : None :
2023-06-01 06:05:19 +00:00
default :
2023-06-05 09:43:02 +00:00
return " primaryScore DESC, secondaryScore DESC, tertiaryScore DESC " ;
2023-05-01 04:30:41 +00:00
}
2023-05-09 01:36:28 +00:00
}
2023-06-05 23:04:56 +00:00
void Leaderboard : : SetupLeaderboard ( bool weekly , uint32_t resultStart , uint32_t resultEnd ) {
2023-05-09 02:59:10 +00:00
resultStart + + ;
resultEnd + + ;
2023-06-05 09:31:49 +00:00
// We need everything except 1 column so i'm selecting * from leaderboard
2023-05-31 10:10:28 +00:00
const std : : string queryBase =
2023-08-04 02:44:03 +00:00
R " QUERY(
WITH leaderboardsRanked AS (
SELECT leaderboard . * , charinfo . name ,
RANK ( ) OVER
(
2023-05-09 01:36:28 +00:00
ORDER BY % s , UNIX_TIMESTAMP ( last_played ) ASC , id DESC
2023-08-04 02:44:03 +00:00
) 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 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 - 9 )
AND
LEAST ( GREATEST ( myRank + 5 , % i ) , lowestRanking . lowestRank )
2023-05-09 01:36:28 +00:00
ORDER BY ranking ASC ;
) QUERY " ;
2023-06-05 23:04:56 +00:00
std : : string friendsFilter =
2023-06-05 09:50:40 +00:00
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
)
2023-05-09 01:36:28 +00:00
OR character_id = ?
)
) QUERY " ;
2023-06-22 02:46:01 +00:00
std : : string weeklyFilter = " AND UNIX_TIMESTAMP(last_played) BETWEEN UNIX_TIMESTAMP(date_sub(now(),INTERVAL 1 WEEK)) AND UNIX_TIMESTAMP(now()) " ;
2023-06-05 23:04:56 +00:00
std : : string filter ;
// Setup our filter based on the query type
if ( this - > infoType = = InfoType : : Friends ) filter + = friendsFilter ;
if ( this - > weekly ) filter + = weeklyFilter ;
2023-05-30 11:11:37 +00:00
const auto orderBase = GetOrdering ( this - > leaderboardType ) ;
2023-05-04 21:48:26 +00:00
2023-06-05 09:43:02 +00:00
// For top query, we want to just rank all scores, but for all others we need the scores around a specific player
2023-06-01 06:05:19 +00:00
std : : string baseLookup ;
if ( this - > infoType = = InfoType : : Top ) {
2023-06-22 04:46:11 +00:00
baseLookup = " SELECT id, last_played FROM leaderboard WHERE game_id = ? " + ( this - > weekly ? weeklyFilter : std : : string ( " " ) ) + " ORDER BY " ;
2023-06-01 06:05:19 +00:00
baseLookup + = orderBase . data ( ) ;
2023-05-31 01:21:10 +00:00
} else {
2023-06-22 04:46:11 +00:00
baseLookup = " SELECT id, last_played FROM leaderboard WHERE game_id = ? " + ( this - > weekly ? weeklyFilter : std : : string ( " " ) ) + " AND character_id = " ;
2023-06-05 11:10:59 +00:00
baseLookup + = std : : to_string ( static_cast < uint32_t > ( this - > relatedPlayer ) ) ;
2023-05-31 01:21:10 +00:00
}
2023-06-01 06:05:19 +00:00
baseLookup + = " LIMIT 1 " ;
2023-06-22 04:46:11 +00:00
Game : : logger - > LogDebug ( " LeaderboardManager " , " query is %s " , baseLookup . c_str ( ) ) ;
2023-06-01 06:05:19 +00:00
std : : unique_ptr < sql : : PreparedStatement > baseQuery ( Database : : CreatePreppedStmt ( baseLookup ) ) ;
2023-05-03 07:38:38 +00:00
baseQuery - > setInt ( 1 , this - > gameID ) ;
std : : unique_ptr < sql : : ResultSet > baseResult ( baseQuery - > executeQuery ( ) ) ;
2023-05-28 11:30:20 +00:00
2023-05-09 01:36:28 +00:00
if ( ! baseResult - > next ( ) ) return ; // In this case, there are no entries in the leaderboard for this game.
2023-05-03 07:38:38 +00:00
uint32_t relatedPlayerLeaderboardId = baseResult - > getInt ( " id " ) ;
2023-06-05 09:43:02 +00:00
// 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 < char [ ] > lookupBuffer = std : : make_unique < char [ ] > ( STRING_LENGTH ) ;
2023-06-22 02:46:01 +00:00
int32_t res = snprintf ( lookupBuffer . get ( ) , STRING_LENGTH , queryBase . c_str ( ) , orderBase . data ( ) , filter . c_str ( ) , resultStart , resultEnd ) ;
2023-06-01 06:05:19 +00:00
DluAssert ( res ! = - 1 ) ;
2023-06-05 09:43:02 +00:00
std : : unique_ptr < sql : : PreparedStatement > query ( Database : : CreatePreppedStmt ( lookupBuffer . get ( ) ) ) ;
2023-06-22 04:46:11 +00:00
Game : : logger - > LogDebug ( " LeaderboardManager " , " Query is %s vars are %i %i %i " , lookupBuffer . get ( ) , this - > gameID , this - > relatedPlayer , relatedPlayerLeaderboardId ) ;
2023-05-01 04:30:41 +00:00
query - > setInt ( 1 , this - > gameID ) ;
2023-05-04 23:53:36 +00:00
if ( this - > infoType = = InfoType : : Friends ) {
2023-05-03 07:38:38 +00:00
query - > setInt ( 2 , this - > relatedPlayer ) ;
query - > setInt ( 3 , this - > relatedPlayer ) ;
query - > setInt ( 4 , this - > relatedPlayer ) ;
query - > setInt ( 5 , relatedPlayerLeaderboardId ) ;
} else {
query - > setInt ( 2 , relatedPlayerLeaderboardId ) ;
}
2023-05-09 01:36:28 +00:00
std : : unique_ptr < sql : : ResultSet > result ( query - > executeQuery ( ) ) ;
QueryToLdf ( result ) ;
2021-12-05 17:54:36 +00:00
}
2023-05-30 11:23:48 +00:00
void Leaderboard : : Send ( const LWOOBJID targetID ) const {
2023-07-22 02:38:59 +00:00
auto * player = Game : : entityManager - > GetEntity ( relatedPlayer ) ;
2021-12-05 17:54:36 +00:00
if ( player ! = nullptr ) {
GameMessages : : SendActivitySummaryLeaderboardData ( targetID , this , player - > GetSystemAddress ( ) ) ;
}
}
2023-05-30 11:38:19 +00:00
std : : string FormatInsert ( const Leaderboard : : Type & type , const Score & score , const bool useUpdate ) {
2023-06-01 06:17:13 +00:00
std : : string insertStatement ;
if ( useUpdate ) {
insertStatement =
R " QUERY(
2023-08-04 02:44:03 +00:00
UPDATE leaderboard
SET primaryScore = % f , secondaryScore = % f , tertiaryScore = % f ,
2023-06-01 06:17:13 +00:00
timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ? ;
) QUERY " ;
} else {
insertStatement =
R " QUERY(
2023-08-04 02:44:03 +00:00
INSERT leaderboard SET
primaryScore = % f , secondaryScore = % f , tertiaryScore = % f ,
2023-06-01 06:17:13 +00:00
character_id = ? , game_id = ? ;
) QUERY " ;
}
2023-05-07 11:09:10 +00:00
2023-04-18 08:26:35 +00:00
constexpr uint16_t STRING_LENGTH = 400 ;
2023-05-30 11:11:37 +00:00
// Then fill in our score
2023-04-18 08:26:35 +00:00
char finishedQuery [ STRING_LENGTH ] ;
2023-06-22 02:46:01 +00:00
int32_t res = snprintf ( finishedQuery , STRING_LENGTH , insertStatement . c_str ( ) , score . GetPrimaryScore ( ) , score . GetSecondaryScore ( ) , score . GetTertiaryScore ( ) ) ;
2023-05-30 11:18:32 +00:00
DluAssert ( res ! = - 1 ) ;
2023-04-18 08:26:35 +00:00
return finishedQuery ;
}
2022-07-28 13:39:57 +00:00
2023-06-05 11:10:59 +00:00
void LeaderboardManager : : SaveScore ( const LWOOBJID & playerID , const GameID activityId , const float primaryScore , const float secondaryScore , const float tertiaryScore ) {
const Leaderboard : : Type leaderboardType = GetLeaderboardType ( activityId ) ;
2023-05-07 11:09:10 +00:00
2023-08-04 02:44:03 +00:00
std : : unique_ptr < sql : : PreparedStatement > query ( Database : : CreatePreppedStmt ( " SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?; " ) ) ;
2023-05-07 07:31:38 +00:00
query - > setInt ( 1 , playerID ) ;
2023-06-05 11:10:59 +00:00
query - > setInt ( 2 , activityId ) ;
2023-05-07 07:31:38 +00:00
std : : unique_ptr < sql : : ResultSet > myScoreResult ( query - > executeQuery ( ) ) ;
2023-05-30 11:18:32 +00:00
std : : string saveQuery ( " UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?; " ) ;
2023-05-30 11:11:37 +00:00
Score newScore ( primaryScore , secondaryScore , tertiaryScore ) ;
2023-05-07 11:09:10 +00:00
if ( myScoreResult - > next ( ) ) {
2023-05-30 11:11:37 +00:00
Score oldScore ;
bool lowerScoreBetter = false ;
2023-05-07 11:09:10 +00:00
switch ( leaderboardType ) {
2023-05-30 11:11:37 +00:00
// Higher score better
2023-05-07 11:09:10 +00:00
case Leaderboard : : Type : : ShootingGallery : {
2023-06-01 06:05:19 +00:00
oldScore . SetPrimaryScore ( myScoreResult - > getInt ( " primaryScore " ) ) ;
oldScore . SetSecondaryScore ( myScoreResult - > getInt ( " secondaryScore " ) ) ;
oldScore . SetTertiaryScore ( myScoreResult - > getInt ( " tertiaryScore " ) ) ;
2023-05-07 11:09:10 +00:00
break ;
}
case Leaderboard : : Type : : FootRace : {
2023-06-01 06:05:19 +00:00
oldScore . SetPrimaryScore ( myScoreResult - > getInt ( " primaryScore " ) ) ;
2023-05-07 11:09:10 +00:00
break ;
}
case Leaderboard : : Type : : Survival : {
2023-06-01 06:05:19 +00:00
oldScore . SetPrimaryScore ( myScoreResult - > getInt ( " primaryScore " ) ) ;
oldScore . SetSecondaryScore ( myScoreResult - > getInt ( " secondaryScore " ) ) ;
2023-05-07 11:09:10 +00:00
break ;
}
case Leaderboard : : Type : : SurvivalNS : {
2023-06-01 06:05:19 +00:00
oldScore . SetPrimaryScore ( myScoreResult - > getInt ( " primaryScore " ) ) ;
oldScore . SetSecondaryScore ( myScoreResult - > getInt ( " secondaryScore " ) ) ;
2023-05-07 11:09:10 +00:00
break ;
}
2023-05-30 11:11:37 +00:00
case Leaderboard : : Type : : UnusedLeaderboard4 :
2023-05-07 11:09:10 +00:00
case Leaderboard : : Type : : Donations : {
2023-06-01 06:05:19 +00:00
oldScore . SetPrimaryScore ( myScoreResult - > getInt ( " primaryScore " ) ) ;
2023-08-04 02:44:03 +00:00
newScore . SetPrimaryScore ( oldScore . GetPrimaryScore ( ) + newScore . GetPrimaryScore ( ) ) ;
2023-05-07 11:09:10 +00:00
break ;
}
2023-05-31 01:21:10 +00:00
case Leaderboard : : Type : : Racing : {
2023-06-01 06:05:19 +00:00
oldScore . SetPrimaryScore ( myScoreResult - > getInt ( " primaryScore " ) ) ;
oldScore . SetSecondaryScore ( myScoreResult - > getInt ( " secondaryScore " ) ) ;
2023-07-22 06:18:51 +00:00
// 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 ) ;
2023-05-30 11:11:37 +00:00
lowerScoreBetter = true ;
break ;
2023-05-31 01:21:10 +00:00
}
case Leaderboard : : Type : : MonumentRace : {
2023-06-01 06:05:19 +00:00
oldScore . SetPrimaryScore ( myScoreResult - > getInt ( " primaryScore " ) ) ;
2023-05-30 11:11:37 +00:00
lowerScoreBetter = true ;
// Do score checking here
break ;
2023-05-31 01:21:10 +00:00
}
2023-05-09 01:36:28 +00:00
case Leaderboard : : Type : : None :
2023-05-07 11:09:10 +00:00
default :
2023-06-05 11:10:59 +00:00
Game : : logger - > Log ( " LeaderboardManager " , " Unknown leaderboard type %i for game %i. Cannot save score! " , leaderboardType , activityId ) ;
2023-05-09 08:42:11 +00:00
return ;
2023-05-07 11:09:10 +00:00
}
2023-05-30 11:11:37 +00:00
bool newHighScore = lowerScoreBetter ? newScore < oldScore : newScore > oldScore ;
2023-06-22 02:46:01 +00:00
// 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 ;
}
2023-05-30 11:11:37 +00:00
if ( newHighScore ) {
saveQuery = FormatInsert ( leaderboardType , newScore , true ) ;
}
2023-05-07 11:09:10 +00:00
} else {
2023-05-30 11:11:37 +00:00
saveQuery = FormatInsert ( leaderboardType , newScore , false ) ;
2023-05-07 07:31:38 +00:00
}
2023-07-22 06:18:51 +00:00
Game : : logger - > Log ( " LeaderboardManager " , " save query %s %i %i " , saveQuery . c_str ( ) , playerID , activityId ) ;
2023-05-30 11:11:37 +00:00
std : : unique_ptr < sql : : PreparedStatement > saveStatement ( Database : : CreatePreppedStmt ( saveQuery ) ) ;
2023-05-09 01:36:28 +00:00
saveStatement - > setInt ( 1 , playerID ) ;
2023-06-05 11:10:59 +00:00
saveStatement - > setInt ( 2 , activityId ) ;
2023-05-09 01:36:28 +00:00
saveStatement - > execute ( ) ;
2023-08-04 02:44:03 +00:00
2023-07-22 06:18:51 +00:00
// track wins separately
if ( leaderboardType = = Leaderboard : : Type : : Racing & & tertiaryScore ! = 0.0f ) {
std : : unique_ptr < sql : : PreparedStatement > 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 ( ) ;
}
2021-12-05 17:54:36 +00:00
}
2023-06-05 11:10:59 +00:00
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 ) {
2023-05-03 07:38:38 +00:00
Leaderboard leaderboard ( gameID , infoType , weekly , playerID , GetLeaderboardType ( gameID ) ) ;
2023-06-05 23:04:56 +00:00
leaderboard . SetupLeaderboard ( weekly , resultStart , resultEnd ) ;
2023-05-10 05:21:41 +00:00
leaderboard . Send ( targetID ) ;
2021-12-05 17:54:36 +00:00
}
2023-04-13 04:57:58 +00:00
Leaderboard : : Type LeaderboardManager : : GetLeaderboardType ( const GameID gameID ) {
auto lookup = leaderboardCache . find ( gameID ) ;
if ( lookup ! = leaderboardCache . end ( ) ) return lookup - > second ;
2023-03-17 14:36:21 +00:00
auto * activitiesTable = CDClientManager : : Instance ( ) . GetTable < CDActivitiesTable > ( ) ;
2023-06-05 09:50:40 +00:00
std : : vector < CDActivities > activities = activitiesTable - > Query ( [ gameID ] ( const CDActivities & entry ) {
2023-05-30 11:23:48 +00:00
return entry . ActivityID = = gameID ;
2021-12-05 17:54:36 +00:00
} ) ;
2023-05-09 08:42:11 +00:00
auto type = ! activities . empty ( ) ? static_cast < Leaderboard : : Type > ( activities . at ( 0 ) . leaderboardType ) : Leaderboard : : Type : : None ;
2023-04-13 04:57:58 +00:00
leaderboardCache . insert_or_assign ( gameID , type ) ;
return type ;
2021-12-05 17:54:36 +00:00
}