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"
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-04-13 07:45:03 +00:00
# include <sstream>
2023-03-17 14:36:21 +00:00
# include "CDActivitiesTable.h"
2023-04-13 07:45:03 +00:00
# include "Metrics.hpp"
2023-04-13 04:57:58 +00:00
Leaderboard : : Leaderboard ( const GameID gameID , const Leaderboard : : InfoType infoType , const bool weekly , 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-04-14 08:32:52 +00:00
template < class TypeToWrite >
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 ' ;
}
2023-04-13 04:57:58 +00:00
void Leaderboard : : Serialize ( RakNet : : BitStream * bitStream ) const {
2023-04-13 07:45:03 +00:00
std : : ostringstream leaderboard ;
2022-07-28 13:39:57 +00:00
2023-04-13 07:45:03 +00:00
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
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
auto index = 0 ;
for ( const auto & entry : entries ) {
2023-04-14 08:32:52 +00:00
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 ) ;
2023-04-17 08:16:23 +00:00
WriteLeaderboardRow ( leaderboard , index , " RowNumber " , eLDFType : : LDF_TYPE_S32 , entry . placement ) ;
2022-07-28 13:39:57 +00:00
2023-04-14 08:32:52 +00:00
// Each minigame has its own "points" system
switch ( leaderboardType ) {
case Type : : ShootingGallery :
2023-04-17 08:16:23 +00:00
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 ;
2023-04-14 08:32:52 +00:00
case Type : : Racing :
2023-04-17 08:16:23 +00:00
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 ;
2023-04-14 08:32:52 +00:00
case Type : : MonumentRace :
2023-04-17 08:16:23 +00:00
WriteLeaderboardRow ( leaderboard , index , " Time " , eLDFType : : LDF_TYPE_S32 , entry . time ) ;
// Time:1(?)
break ;
2023-04-14 08:32:52 +00:00
case Type : : FootRace :
2023-04-17 08:16:23 +00:00
WriteLeaderboardRow ( leaderboard , index , " Time " , eLDFType : : LDF_TYPE_S32 , entry . time ) ;
// Time:1
break ;
2023-04-14 08:32:52 +00:00
case Type : : Survival :
2023-04-17 08:16:23 +00:00
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 ;
2023-04-14 08:32:52 +00:00
case Type : : SurvivalNS :
2023-04-17 08:16:23 +00:00
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 ;
2023-04-14 08:32:52 +00:00
case Type : : Donations :
2023-04-17 08:16:23 +00:00
WriteLeaderboardRow ( leaderboard , index , " Score " , eLDFType : : LDF_TYPE_S32 , entry . score ) ;
// Score:1
// Something? idk yet.
break ;
2023-04-14 08:32:52 +00:00
case Type : : None :
2023-04-17 08:16:23 +00:00
// This type is included here simply to resolve a compiler warning on mac about unused enum types
2023-04-14 08:32:52 +00:00
break ;
default :
break ;
2021-12-05 17:54:36 +00:00
}
index + + ;
}
2022-07-28 13:39:57 +00:00
2023-04-13 04:57:58 +00:00
// Serialize the thing to a BitStream
2023-04-13 07:45:03 +00:00
bitStream - > Write ( leaderboard . str ( ) . c_str ( ) , leaderboard . tellp ( ) ) ;
2021-12-05 17:54:36 +00:00
}
2023-04-13 04:57:58 +00:00
void Leaderboard : : SetupLeaderboard ( ) {
// Setup query based on activity.
// Where clause will vary based on what query we are doing
2021-12-05 17:54:36 +00:00
}
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 ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
auto * character = player - > GetCharacter ( ) ;
2023-04-13 04:57:58 +00:00
if ( ! character )
2021-12-05 17:54:36 +00:00
return ;
2022-07-28 13:39:57 +00:00
2023-04-13 04:57:58 +00:00
std : : unique_ptr < sql : : PreparedStatement > select ( Database : : CreatePreppedStmt ( " SELECT time, score FROM leaderboard WHERE character_id = ? AND game_id = ?; " ) ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
select - > setUInt64 ( 1 , character - > GetID ( ) ) ;
select - > setInt ( 2 , gameID ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
auto any = false ;
auto * result = select - > executeQuery ( ) ;
auto leaderboardType = GetLeaderboardType ( gameID ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
// Check if the new score is a high score
while ( result - > next ( ) ) {
any = true ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
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 " ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
switch ( leaderboardType ) {
2023-04-13 04:57:58 +00:00
case Leaderboard : : Type : : ShootingGallery :
2021-12-05 17:54:36 +00:00
if ( score < = storedScore )
highscore = false ;
break ;
2023-04-13 04:57:58 +00:00
case Leaderboard : : Type : : Racing :
2021-12-22 03:15:29 +00:00
if ( time > = storedTime )
highscore = false ;
break ;
2023-04-13 04:57:58 +00:00
case Leaderboard : : Type : : MonumentRace :
2021-12-22 03:15:29 +00:00
if ( time > = storedTime )
2021-12-05 17:54:36 +00:00
highscore = false ;
break ;
2023-04-13 04:57:58 +00:00
case Leaderboard : : Type : : FootRace :
2021-12-05 17:54:36 +00:00
if ( time < = storedTime )
highscore = false ;
break ;
2023-04-13 04:57:58 +00:00
case Leaderboard : : Type : : 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 ;
2023-04-13 04:57:58 +00:00
case Leaderboard : : Type : : SurvivalNS :
2022-05-25 02:03:40 +00:00
if ( ! ( score > storedScore | | ( time < storedTime & & score > = storedScore ) ) )
2021-12-05 17:54:36 +00:00
highscore = false ;
break ;
default :
highscore = false ;
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
if ( ! highscore ) {
delete result ;
return ;
}
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
delete result ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
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 ( ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
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 ( ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
delete statement ;
}
}
2023-04-13 04:57:58 +00:00
void LeaderboardManager : : SendLeaderboard ( uint32_t gameID , Leaderboard : : InfoType infoType , bool weekly , LWOOBJID targetID ,
2021-12-05 17:54:36 +00:00
LWOOBJID playerID ) {
2023-04-13 04:57:58 +00:00
// 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 ) ;
2021-12-05 17:54:36 +00:00
}
2023-04-13 04:57:58 +00:00
// Done
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 > ( ) ;
2021-12-05 17:54:36 +00:00
std : : vector < CDActivities > activities = activitiesTable - > Query ( [ = ] ( const CDActivities & entry ) {
return ( entry . ActivityID = = gameID ) ;
} ) ;
2023-04-13 04:57:58 +00:00
auto type = activities . empty ( ) ? static_cast < Leaderboard : : Type > ( activities . at ( 0 ) . leaderboardType ) : Leaderboard : : Type : : None ;
leaderboardCache . insert_or_assign ( gameID , type ) ;
return type ;
2021-12-05 17:54:36 +00:00
}