2020-06-24 21:14:09 +00:00
# include "score.h"
2020-07-04 17:53:27 +00:00
# include "entities/character.h"
# include "gamemodes/DDRace.h"
2021-01-09 14:37:02 +00:00
# include "player.h"
2020-07-04 17:53:27 +00:00
# include "save.h"
# include <base/system.h>
# include <engine/server/databases/connection.h>
# include <engine/server/databases/connection_pool.h>
# include <engine/server/sql_string_helpers.h>
# include <engine/shared/config.h>
# include <engine/shared/console.h>
# include <engine/shared/linereader.h>
# include <engine/storage.h>
2020-11-03 13:48:24 +00:00
# include <game/generated/wordlist.h>
2020-07-04 17:53:27 +00:00
2020-09-26 19:41:58 +00:00
# include <algorithm>
2020-07-04 17:53:27 +00:00
# include <cstring>
2020-09-26 19:41:58 +00:00
# include <fstream>
2020-07-04 17:53:27 +00:00
# include <random>
2020-06-24 21:14:09 +00:00
2021-01-23 00:12:19 +00:00
// "6b407e81-8b77-3e04-a207-8da17f37d000"
// "save-no-save-id@ddnet.tw"
static const CUuid UUID_NO_SAVE_ID =
{ { 0x6b , 0x40 , 0x7e , 0x81 , 0x8b , 0x77 , 0x3e , 0x04 ,
0xa2 , 0x07 , 0x8d , 0xa1 , 0x7f , 0x37 , 0xd0 , 0x00 } } ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult : : CScorePlayerResult ( )
2020-06-24 21:14:09 +00:00
{
SetVariant ( Variant : : DIRECT ) ;
}
void CScorePlayerResult : : SetVariant ( Variant v )
{
m_MessageKind = v ;
switch ( v )
{
case DIRECT :
case ALL :
2020-10-26 14:14:07 +00:00
for ( auto & aMessage : m_Data . m_aaMessages )
aMessage [ 0 ] = 0 ;
2020-06-24 21:14:09 +00:00
break ;
case BROADCAST :
m_Data . m_Broadcast [ 0 ] = 0 ;
break ;
case MAP_VOTE :
m_Data . m_MapVote . m_Map [ 0 ] = ' \0 ' ;
m_Data . m_MapVote . m_Reason [ 0 ] = ' \0 ' ;
m_Data . m_MapVote . m_Server [ 0 ] = ' \0 ' ;
break ;
case PLAYER_INFO :
m_Data . m_Info . m_Score = - 9999 ;
m_Data . m_Info . m_Birthday = 0 ;
m_Data . m_Info . m_HasFinishScore = false ;
m_Data . m_Info . m_Time = 0 ;
2020-10-26 14:14:07 +00:00
for ( float & CpTime : m_Data . m_Info . m_CpTime )
CpTime = 0 ;
2020-06-24 21:14:09 +00:00
}
}
2020-07-04 17:53:27 +00:00
2020-07-18 13:19:26 +00:00
CTeamrank : : CTeamrank ( ) :
m_NumNames ( 0 )
{
2020-10-26 14:14:07 +00:00
for ( auto & aName : m_aaNames )
aName [ 0 ] = ' \0 ' ;
2020-07-18 13:19:26 +00:00
mem_zero ( & m_TeamID . m_aData , sizeof ( m_TeamID ) ) ;
}
2021-01-31 16:58:30 +00:00
bool CTeamrank : : NextSqlResult ( IDbConnection * pSqlServer , bool * pEnd , char * pError , int ErrorSize )
2020-07-18 13:19:26 +00:00
{
pSqlServer - > GetBlob ( 1 , m_TeamID . m_aData , sizeof ( m_TeamID . m_aData ) ) ;
pSqlServer - > GetString ( 2 , m_aaNames [ 0 ] , sizeof ( m_aaNames [ 0 ] ) ) ;
m_NumNames = 1 ;
2021-01-31 16:58:30 +00:00
bool End = false ;
while ( ! pSqlServer - > Step ( & End , pError , ErrorSize ) & & ! End )
2020-07-18 13:19:26 +00:00
{
CUuid TeamID ;
pSqlServer - > GetBlob ( 1 , TeamID . m_aData , sizeof ( TeamID . m_aData ) ) ;
if ( m_TeamID ! = TeamID )
2021-01-31 16:58:30 +00:00
{
* pEnd = false ;
return false ;
}
2020-07-18 13:19:26 +00:00
pSqlServer - > GetString ( 2 , m_aaNames [ m_NumNames ] , sizeof ( m_aaNames [ m_NumNames ] ) ) ;
m_NumNames + + ;
}
2021-01-31 16:58:30 +00:00
if ( ! End )
{
return true ;
}
* pEnd = true ;
2020-07-18 13:19:26 +00:00
return false ;
}
bool CTeamrank : : SamePlayers ( const std : : vector < std : : string > * aSortedNames )
{
if ( aSortedNames - > size ( ) ! = m_NumNames )
return false ;
for ( unsigned int i = 0 ; i < m_NumNames ; i + + )
{
if ( str_comp ( aSortedNames - > at ( i ) . c_str ( ) , m_aaNames [ i ] ) ! = 0 )
return false ;
}
return true ;
}
2020-07-04 17:53:27 +00:00
std : : shared_ptr < CScorePlayerResult > CScore : : NewSqlPlayerResult ( int ClientID )
{
CPlayer * pCurPlayer = GameServer ( ) - > m_apPlayers [ ClientID ] ;
if ( pCurPlayer - > m_ScoreQueryResult ! = nullptr ) // TODO: send player a message: "too many requests"
return nullptr ;
pCurPlayer - > m_ScoreQueryResult = std : : make_shared < CScorePlayerResult > ( ) ;
return pCurPlayer - > m_ScoreQueryResult ;
}
void CScore : : ExecPlayerThread (
2021-01-31 16:58:30 +00:00
bool ( * pFuncPtr ) ( IDbConnection * , const ISqlData * , char * pError , int ErrorSize ) ,
2020-09-26 19:41:58 +00:00
const char * pThreadName ,
int ClientID ,
const char * pName ,
int Offset )
2020-07-04 17:53:27 +00:00
{
auto pResult = NewSqlPlayerResult ( ClientID ) ;
if ( pResult = = nullptr )
return ;
auto Tmp = std : : unique_ptr < CSqlPlayerRequest > ( new CSqlPlayerRequest ( pResult ) ) ;
str_copy ( Tmp - > m_Name , pName , sizeof ( Tmp - > m_Name ) ) ;
str_copy ( Tmp - > m_Map , g_Config . m_SvMap , sizeof ( Tmp - > m_Map ) ) ;
2021-03-03 13:23:18 +00:00
str_copy ( Tmp - > m_Server , g_Config . m_SvSqlServerName , sizeof ( Tmp - > m_Server ) ) ;
2020-07-04 17:53:27 +00:00
str_copy ( Tmp - > m_RequestingPlayer , Server ( ) - > ClientName ( ClientID ) , sizeof ( Tmp - > m_RequestingPlayer ) ) ;
Tmp - > m_Offset = Offset ;
m_pPool - > Execute ( pFuncPtr , std : : move ( Tmp ) , pThreadName ) ;
}
bool CScore : : RateLimitPlayer ( int ClientID )
{
CPlayer * pPlayer = GameServer ( ) - > m_apPlayers [ ClientID ] ;
if ( pPlayer = = 0 )
return true ;
2021-02-06 14:42:34 +00:00
if ( pPlayer - > m_LastSQLQuery + ( int64 ) g_Config . m_SvSqlQueriesDelay * Server ( ) - > TickSpeed ( ) > = Server ( ) - > Tick ( ) )
2020-07-04 17:53:27 +00:00
return true ;
pPlayer - > m_LastSQLQuery = Server ( ) - > Tick ( ) ;
return false ;
}
void CScore : : GeneratePassphrase ( char * pBuf , int BufSize )
{
for ( int i = 0 ; i < 3 ; i + + )
{
if ( i ! = 0 )
str_append ( pBuf , " " , BufSize ) ;
// TODO: decide if the slight bias towards lower numbers is ok
int Rand = m_Prng . RandomBits ( ) % m_aWordlist . size ( ) ;
str_append ( pBuf , m_aWordlist [ Rand ] . c_str ( ) , BufSize ) ;
}
}
CScore : : CScore ( CGameContext * pGameServer , CDbConnectionPool * pPool ) :
2020-09-26 19:41:58 +00:00
m_pPool ( pPool ) ,
m_pGameServer ( pGameServer ) ,
m_pServer ( pGameServer - > Server ( ) )
2020-07-04 17:53:27 +00:00
{
auto InitResult = std : : make_shared < CScoreInitResult > ( ) ;
auto Tmp = std : : unique_ptr < CSqlInitData > ( new CSqlInitData ( InitResult ) ) ;
2020-09-26 19:41:58 +00:00
( ( CGameControllerDDRace * ) ( pGameServer - > m_pController ) ) - > m_pInitResult = InitResult ;
2020-07-04 17:53:27 +00:00
str_copy ( Tmp - > m_Map , g_Config . m_SvMap , sizeof ( Tmp - > m_Map ) ) ;
uint64 aSeed [ 2 ] ;
secure_random_fill ( aSeed , sizeof ( aSeed ) ) ;
m_Prng . Seed ( aSeed ) ;
2020-11-03 13:48:24 +00:00
IOHANDLE File = GameServer ( ) - > Storage ( ) - > OpenFile ( " wordlist.txt " , IOFLAG_READ , IStorage : : TYPE_ALL ) ;
if ( File )
{
CLineReader LineReader ;
LineReader . Init ( File ) ;
char * pLine ;
while ( ( pLine = LineReader . Get ( ) ) )
{
char Word [ 32 ] = { 0 } ;
sscanf ( pLine , " %*s %31s " , Word ) ;
Word [ 31 ] = 0 ;
m_aWordlist . push_back ( Word ) ;
}
}
else
2020-07-04 17:53:27 +00:00
{
2020-11-03 13:48:24 +00:00
dbg_msg ( " sql " , " failed to open wordlist, using fallback " ) ;
m_aWordlist . assign ( std : : begin ( g_aFallbackWordlist ) , std : : end ( g_aFallbackWordlist ) ) ;
2020-07-04 17:53:27 +00:00
}
2020-11-03 13:48:24 +00:00
2020-07-04 17:53:27 +00:00
if ( m_aWordlist . size ( ) < 1000 )
{
dbg_msg ( " sql " , " too few words in wordlist " ) ;
Server ( ) - > SetErrorShutdown ( " sql too few words in wordlist " ) ;
return ;
}
2020-11-03 13:48:24 +00:00
2020-07-04 17:53:27 +00:00
m_pPool - > Execute ( Init , std : : move ( Tmp ) , " load best time " ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : Init ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlInitData * pData = dynamic_cast < const CSqlInitData * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScoreInitResult * pResult = dynamic_cast < CScoreInitResult * > ( pGameData - > m_pResult . get ( ) ) ;
2020-07-04 17:53:27 +00:00
char aBuf [ 512 ] ;
// get the best time
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT Time FROM %s_race WHERE Map=? ORDER BY `Time` ASC LIMIT 1; " ,
pSqlServer - > GetPrefix ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
{
2020-12-08 11:03:43 +00:00
pResult - > m_CurrentRecord = pSqlServer - > GetFloat ( 1 ) ;
2021-01-31 16:58:30 +00:00
}
2020-07-04 17:53:27 +00:00
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
void CScore : : LoadPlayerData ( int ClientID )
{
ExecPlayerThread ( LoadPlayerDataThread , " load player data " , ClientID , " " , 0 ) ;
}
// update stuff
2021-01-31 16:58:30 +00:00
bool CScore : : LoadPlayerDataThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
pResult - > SetVariant ( CScorePlayerResult : : PLAYER_INFO ) ;
2020-07-04 17:53:27 +00:00
char aBuf [ 512 ] ;
// get best race time
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT Time, cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, "
" cp11, cp12, cp13, cp14, cp15, cp16, cp17, cp18, cp19, cp20, "
" cp21, cp22, cp23, cp24, cp25 "
" FROM %s_race "
" WHERE Map = ? AND Name = ? "
" ORDER BY Time ASC "
" LIMIT 1; " ,
pSqlServer - > GetPrefix ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindString ( 2 , pData - > m_RequestingPlayer ) ;
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
2020-07-04 17:53:27 +00:00
{
// get the best time
float Time = pSqlServer - > GetFloat ( 1 ) ;
2020-12-08 11:03:43 +00:00
pResult - > m_Data . m_Info . m_Time = Time ;
pResult - > m_Data . m_Info . m_Score = - Time ;
pResult - > m_Data . m_Info . m_HasFinishScore = true ;
2020-07-04 17:53:27 +00:00
if ( g_Config . m_SvCheckpointSave )
{
for ( int i = 0 ; i < NUM_CHECKPOINTS ; i + + )
{
2020-12-08 11:03:43 +00:00
pResult - > m_Data . m_Info . m_CpTime [ i ] = pSqlServer - > GetFloat ( i + 2 ) ;
2020-07-04 17:53:27 +00:00
}
}
}
// birthday check
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT CURRENT_TIMESTAMP AS Current, MIN(Timestamp) AS Stamp "
" FROM %s_race "
" WHERE Name = ? " ,
pSqlServer - > GetPrefix ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_RequestingPlayer ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End & & ! pSqlServer - > IsNull ( 2 ) )
2020-07-04 17:53:27 +00:00
{
2020-07-13 19:19:40 +00:00
char aCurrent [ TIMESTAMP_STR_LENGTH ] ;
pSqlServer - > GetString ( 1 , aCurrent , sizeof ( aCurrent ) ) ;
char aStamp [ TIMESTAMP_STR_LENGTH ] ;
pSqlServer - > GetString ( 2 , aStamp , sizeof ( aStamp ) ) ;
int CurrentYear , CurrentMonth , CurrentDay ;
int StampYear , StampMonth , StampDay ;
2020-09-26 19:41:58 +00:00
if ( sscanf ( aCurrent , " %d-%d-%d " , & CurrentYear , & CurrentMonth , & CurrentDay ) = = 3 & & sscanf ( aStamp , " %d-%d-%d " , & StampYear , & StampMonth , & StampDay ) = = 3 & & CurrentMonth = = StampMonth & & CurrentDay = = StampDay )
2020-12-08 11:03:43 +00:00
pResult - > m_Data . m_Info . m_Birthday = CurrentYear - StampYear ;
2020-07-04 17:53:27 +00:00
}
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
2020-09-26 19:41:58 +00:00
void CScore : : MapVote ( int ClientID , const char * MapName )
2020-07-04 17:53:27 +00:00
{
if ( RateLimitPlayer ( ClientID ) )
return ;
ExecPlayerThread ( MapVoteThread , " map vote " , ClientID , MapName , 0 ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : MapVoteThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
auto * paMessages = pResult - > m_Data . m_aaMessages ;
2020-07-04 17:53:27 +00:00
char aFuzzyMap [ 128 ] ;
str_copy ( aFuzzyMap , pData - > m_Name , sizeof ( aFuzzyMap ) ) ;
sqlstr : : FuzzyString ( aFuzzyMap , sizeof ( aFuzzyMap ) ) ;
char aMapPrefix [ 128 ] ;
str_copy ( aMapPrefix , pData - > m_Name , sizeof ( aMapPrefix ) ) ;
str_append ( aMapPrefix , " % " , sizeof ( aMapPrefix ) ) ;
char aBuf [ 768 ] ;
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT Map, Server "
" FROM %s_maps "
" WHERE Map LIKE %s "
" ORDER BY "
" CASE WHEN Map = ? THEN 0 ELSE 1 END, "
" CASE WHEN Map LIKE ? THEN 0 ELSE 1 END, "
" LENGTH(Map), Map "
" LIMIT 1; " ,
pSqlServer - > GetPrefix ( ) , pSqlServer - > CollateNocase ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , aFuzzyMap ) ;
pSqlServer - > BindString ( 2 , pData - > m_Name ) ;
pSqlServer - > BindString ( 3 , aMapPrefix ) ;
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
2020-07-04 17:53:27 +00:00
{
2020-12-08 11:03:43 +00:00
pResult - > SetVariant ( CScorePlayerResult : : MAP_VOTE ) ;
auto * MapVote = & pResult - > m_Data . m_MapVote ;
2020-07-04 17:53:27 +00:00
pSqlServer - > GetString ( 1 , MapVote - > m_Map , sizeof ( MapVote - > m_Map ) ) ;
pSqlServer - > GetString ( 2 , MapVote - > m_Server , sizeof ( MapVote - > m_Server ) ) ;
2020-10-17 11:14:38 +00:00
str_copy ( MapVote - > m_Reason , " /map " , sizeof ( MapVote - > m_Reason ) ) ;
2020-07-04 17:53:27 +00:00
for ( char * p = MapVote - > m_Server ; * p ; p + + ) // lower case server
* p = tolower ( * p ) ;
}
else
{
2020-12-08 11:03:43 +00:00
pResult - > SetVariant ( CScorePlayerResult : : DIRECT ) ;
2020-07-04 17:53:27 +00:00
str_format ( paMessages [ 0 ] , sizeof ( paMessages [ 0 ] ) ,
2020-09-26 19:41:58 +00:00
" No map like \" %s \" found. "
" Try adding a '%%' at the start if you don't know the first character. "
" Example: /map %%castle for \" Out of Castle \" " ,
pData - > m_Name ) ;
2020-07-04 17:53:27 +00:00
}
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
2020-09-26 19:41:58 +00:00
void CScore : : MapInfo ( int ClientID , const char * MapName )
2020-07-04 17:53:27 +00:00
{
if ( RateLimitPlayer ( ClientID ) )
return ;
ExecPlayerThread ( MapInfoThread , " map info " , ClientID , MapName , 0 ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : MapInfoThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
2020-07-04 17:53:27 +00:00
char aFuzzyMap [ 128 ] ;
str_copy ( aFuzzyMap , pData - > m_Name , sizeof ( aFuzzyMap ) ) ;
sqlstr : : FuzzyString ( aFuzzyMap , sizeof ( aFuzzyMap ) ) ;
char aMapPrefix [ 128 ] ;
str_copy ( aMapPrefix , pData - > m_Name , sizeof ( aMapPrefix ) ) ;
str_append ( aMapPrefix , " % " , sizeof ( aMapPrefix ) ) ;
2020-07-18 18:49:34 +00:00
char aCurrentTimestamp [ 512 ] ;
pSqlServer - > ToUnixTimestamp ( " CURRENT_TIMESTAMP " , aCurrentTimestamp , sizeof ( aCurrentTimestamp ) ) ;
char aTimestamp [ 512 ] ;
pSqlServer - > ToUnixTimestamp ( " l.Timestamp " , aTimestamp , sizeof ( aTimestamp ) ) ;
2021-01-12 13:24:38 +00:00
char aMedianMapTime [ 2048 ] ;
char aBuf [ 4096 ] ;
2020-07-04 17:53:27 +00:00
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT l.Map, l.Server, Mapper, Points, Stars, "
" (SELECT COUNT(Name) FROM %s_race WHERE Map = l.Map) AS Finishes, "
" (SELECT COUNT(DISTINCT Name) FROM %s_race WHERE Map = l.Map) AS Finishers, "
2021-01-12 13:24:38 +00:00
" (%s) AS Median, "
2020-09-26 19:41:58 +00:00
" %s AS Stamp, "
" %s-%s AS Ago, "
" (SELECT MIN(Time) FROM %s_race WHERE Map = l.Map AND Name = ?) AS OwnTime "
" FROM ( "
" SELECT * FROM %s_maps "
" WHERE Map LIKE %s "
" ORDER BY "
" CASE WHEN Map = ? THEN 0 ELSE 1 END, "
" CASE WHEN Map LIKE ? THEN 0 ELSE 1 END, "
" LENGTH(Map), "
" Map "
" LIMIT 1 "
" ) as l; " ,
2021-01-12 13:24:38 +00:00
pSqlServer - > GetPrefix ( ) , pSqlServer - > GetPrefix ( ) ,
pSqlServer - > MedianMapTime ( aMedianMapTime , sizeof ( aMedianMapTime ) ) ,
2020-09-26 19:41:58 +00:00
aTimestamp , aCurrentTimestamp , aTimestamp ,
pSqlServer - > GetPrefix ( ) , pSqlServer - > GetPrefix ( ) ,
pSqlServer - > CollateNocase ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_RequestingPlayer ) ;
pSqlServer - > BindString ( 2 , aFuzzyMap ) ;
pSqlServer - > BindString ( 3 , pData - > m_Name ) ;
pSqlServer - > BindString ( 4 , aMapPrefix ) ;
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
2020-07-04 17:53:27 +00:00
{
char aMap [ MAX_MAP_LENGTH ] ;
pSqlServer - > GetString ( 1 , aMap , sizeof ( aMap ) ) ;
char aServer [ 32 ] ;
pSqlServer - > GetString ( 2 , aServer , sizeof ( aServer ) ) ;
char aMapper [ 128 ] ;
pSqlServer - > GetString ( 3 , aMapper , sizeof ( aMapper ) ) ;
int Points = pSqlServer - > GetInt ( 4 ) ;
int Stars = pSqlServer - > GetInt ( 5 ) ;
int Finishes = pSqlServer - > GetInt ( 6 ) ;
int Finishers = pSqlServer - > GetInt ( 7 ) ;
2021-02-28 23:37:38 +00:00
float Median = ! pSqlServer - > IsNull ( 8 ) ? pSqlServer - > GetInt ( 8 ) : - 1.0f ;
2020-07-04 17:53:27 +00:00
int Stamp = pSqlServer - > GetInt ( 9 ) ;
int Ago = pSqlServer - > GetInt ( 10 ) ;
2021-02-28 23:37:38 +00:00
float OwnTime = ! pSqlServer - > IsNull ( 11 ) ? pSqlServer - > GetFloat ( 11 ) : - 1.0f ;
2020-07-04 17:53:27 +00:00
char aAgoString [ 40 ] = " \0 " ;
char aReleasedString [ 60 ] = " \0 " ;
if ( Stamp ! = 0 )
{
2020-10-12 16:31:55 +00:00
sqlstr : : AgoTimeToString ( Ago , aAgoString , sizeof ( aAgoString ) ) ;
2020-07-04 17:53:27 +00:00
str_format ( aReleasedString , sizeof ( aReleasedString ) , " , released %s ago " , aAgoString ) ;
}
2021-01-12 13:24:38 +00:00
char aMedianString [ 60 ] = " \0 " ;
if ( Median > 0 )
2020-07-04 17:53:27 +00:00
{
2021-01-12 13:24:38 +00:00
str_time ( ( int64 ) Median * 100 , TIME_HOURS , aBuf , sizeof ( aBuf ) ) ;
str_format ( aMedianString , sizeof ( aMedianString ) , " in %s median " , aBuf ) ;
2020-07-04 17:53:27 +00:00
}
char aStars [ 20 ] ;
switch ( Stars )
{
2020-10-17 11:14:38 +00:00
case 0 : str_copy ( aStars , " ✰✰✰✰✰ " , sizeof ( aStars ) ) ; break ;
case 1 : str_copy ( aStars , " ★✰✰✰✰ " , sizeof ( aStars ) ) ; break ;
case 2 : str_copy ( aStars , " ★★✰✰✰ " , sizeof ( aStars ) ) ; break ;
case 3 : str_copy ( aStars , " ★★★✰✰ " , sizeof ( aStars ) ) ; break ;
case 4 : str_copy ( aStars , " ★★★★✰ " , sizeof ( aStars ) ) ; break ;
case 5 : str_copy ( aStars , " ★★★★★ " , sizeof ( aStars ) ) ; break ;
2020-09-26 19:41:58 +00:00
default : aStars [ 0 ] = ' \0 ' ;
2020-07-04 17:53:27 +00:00
}
char aOwnFinishesString [ 40 ] = " \0 " ;
if ( OwnTime > 0 )
{
2020-10-18 20:55:11 +00:00
str_time_float ( OwnTime , TIME_HOURS_CENTISECS , aBuf , sizeof ( aBuf ) ) ;
2020-07-04 17:53:27 +00:00
str_format ( aOwnFinishesString , sizeof ( aOwnFinishesString ) ,
2020-10-18 20:55:11 +00:00
" , your time: %s " , aBuf ) ;
2020-07-04 17:53:27 +00:00
}
2020-12-08 11:03:43 +00:00
str_format ( pResult - > m_Data . m_aaMessages [ 0 ] , sizeof ( pResult - > m_Data . m_aaMessages [ 0 ] ) ,
2020-09-26 19:41:58 +00:00
" \" %s \" by %s on %s, %s, %d %s%s, %d %s by %d %s%s%s " ,
aMap , aMapper , aServer , aStars ,
Points , Points = = 1 ? " point " : " points " ,
aReleasedString ,
Finishes , Finishes = = 1 ? " finish " : " finishes " ,
Finishers , Finishers = = 1 ? " tee " : " tees " ,
2021-01-12 13:24:38 +00:00
aMedianString , aOwnFinishesString ) ;
2020-07-04 17:53:27 +00:00
}
else
{
2020-12-08 11:03:43 +00:00
str_format ( pResult - > m_Data . m_aaMessages [ 0 ] , sizeof ( pResult - > m_Data . m_aaMessages [ 0 ] ) ,
2020-09-26 19:41:58 +00:00
" No map like \" %s \" found. " , pData - > m_Name ) ;
2020-07-04 17:53:27 +00:00
}
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
void CScore : : SaveScore ( int ClientID , float Time , const char * pTimestamp , float CpTime [ NUM_CHECKPOINTS ] , bool NotEligible )
{
2020-09-26 19:41:58 +00:00
CConsole * pCon = ( CConsole * ) GameServer ( ) - > Console ( ) ;
2020-07-04 17:53:27 +00:00
if ( pCon - > m_Cheated | | NotEligible )
return ;
CPlayer * pCurPlayer = GameServer ( ) - > m_apPlayers [ ClientID ] ;
if ( pCurPlayer - > m_ScoreFinishResult ! = nullptr )
dbg_msg ( " sql " , " WARNING: previous save score result didn't complete, overwriting it now " ) ;
pCurPlayer - > m_ScoreFinishResult = std : : make_shared < CScorePlayerResult > ( ) ;
auto Tmp = std : : unique_ptr < CSqlScoreData > ( new CSqlScoreData ( pCurPlayer - > m_ScoreFinishResult ) ) ;
str_copy ( Tmp - > m_Map , g_Config . m_SvMap , sizeof ( Tmp - > m_Map ) ) ;
FormatUuid ( GameServer ( ) - > GameUuid ( ) , Tmp - > m_GameUuid , sizeof ( Tmp - > m_GameUuid ) ) ;
Tmp - > m_ClientID = ClientID ;
str_copy ( Tmp - > m_Name , Server ( ) - > ClientName ( ClientID ) , sizeof ( Tmp - > m_Name ) ) ;
Tmp - > m_Time = Time ;
str_copy ( Tmp - > m_aTimestamp , pTimestamp , sizeof ( Tmp - > m_aTimestamp ) ) ;
for ( int i = 0 ; i < NUM_CHECKPOINTS ; i + + )
Tmp - > m_aCpCurrent [ i ] = CpTime [ i ] ;
m_pPool - > ExecuteWrite ( SaveScoreThread , std : : move ( Tmp ) , " save score " ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : SaveScoreThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , bool Failure , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlScoreData * pData = dynamic_cast < const CSqlScoreData * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
auto * paMessages = pResult - > m_Data . m_aaMessages ;
2020-07-04 17:53:27 +00:00
char aBuf [ 1024 ] ;
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT COUNT(*) AS NumFinished FROM %s_race WHERE Map=? AND Name=? ORDER BY time ASC LIMIT 1; " ,
pSqlServer - > GetPrefix ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindString ( 2 , pData - > m_Name ) ;
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
int NumFinished = pSqlServer - > GetInt ( 1 ) ;
if ( NumFinished = = 0 )
{
str_format ( aBuf , sizeof ( aBuf ) , " SELECT Points FROM %s_maps WHERE Map=? " , pSqlServer - > GetPrefix ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
2020-07-04 17:53:27 +00:00
{
int Points = pSqlServer - > GetInt ( 1 ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > AddPoints ( pData - > m_Name , Points , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
str_format ( paMessages [ 0 ] , sizeof ( paMessages [ 0 ] ) ,
2020-09-26 19:41:58 +00:00
" You earned %d point%s for finishing this map! " ,
Points , Points = = 1 ? " " : " s " ) ;
2020-07-04 17:53:27 +00:00
}
}
2020-07-12 12:07:12 +00:00
// save score. Can't fail, because no UNIQUE/PRIMARY KEY constrain is defined.
2020-07-04 17:53:27 +00:00
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-10 18:24:34 +00:00
" %s INTO %s_race( "
" Map, Name, Timestamp, Time, Server, "
" cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, "
" cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, "
" GameID, DDNet7) "
" VALUES (?, ?, %s, %.2f, ?, "
" %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, "
" %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, "
" %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, "
" ?, false); " ,
pSqlServer - > InsertIgnore ( ) , pSqlServer - > GetPrefix ( ) ,
pSqlServer - > InsertTimestampAsUtc ( ) , pData - > m_Time ,
pData - > m_aCpCurrent [ 0 ] , pData - > m_aCpCurrent [ 1 ] , pData - > m_aCpCurrent [ 2 ] ,
pData - > m_aCpCurrent [ 3 ] , pData - > m_aCpCurrent [ 4 ] , pData - > m_aCpCurrent [ 5 ] ,
pData - > m_aCpCurrent [ 6 ] , pData - > m_aCpCurrent [ 7 ] , pData - > m_aCpCurrent [ 8 ] ,
pData - > m_aCpCurrent [ 9 ] , pData - > m_aCpCurrent [ 10 ] , pData - > m_aCpCurrent [ 11 ] ,
pData - > m_aCpCurrent [ 12 ] , pData - > m_aCpCurrent [ 13 ] , pData - > m_aCpCurrent [ 14 ] ,
pData - > m_aCpCurrent [ 15 ] , pData - > m_aCpCurrent [ 16 ] , pData - > m_aCpCurrent [ 17 ] ,
pData - > m_aCpCurrent [ 18 ] , pData - > m_aCpCurrent [ 19 ] , pData - > m_aCpCurrent [ 20 ] ,
pData - > m_aCpCurrent [ 21 ] , pData - > m_aCpCurrent [ 22 ] , pData - > m_aCpCurrent [ 23 ] ,
pData - > m_aCpCurrent [ 24 ] ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindString ( 2 , pData - > m_Name ) ;
pSqlServer - > BindString ( 3 , pData - > m_aTimestamp ) ;
pSqlServer - > BindString ( 4 , g_Config . m_SvSqlServerName ) ;
2020-07-12 12:07:12 +00:00
pSqlServer - > BindString ( 5 , pData - > m_GameUuid ) ;
2020-09-06 21:34:18 +00:00
pSqlServer - > Print ( ) ;
2021-02-24 19:42:47 +00:00
int NumInserted ;
if ( pSqlServer - > ExecuteUpdate ( & NumInserted , pError , ErrorSize ) )
2021-01-31 16:58:30 +00:00
{
return true ;
}
return false ;
2020-07-04 17:53:27 +00:00
}
2020-09-26 19:41:58 +00:00
void CScore : : SaveTeamScore ( int * aClientIDs , unsigned int Size , float Time , const char * pTimestamp )
2020-07-04 17:53:27 +00:00
{
2020-09-26 19:41:58 +00:00
CConsole * pCon = ( CConsole * ) GameServer ( ) - > Console ( ) ;
2020-07-04 17:53:27 +00:00
if ( pCon - > m_Cheated )
return ;
for ( unsigned int i = 0 ; i < Size ; i + + )
{
if ( GameServer ( ) - > m_apPlayers [ aClientIDs [ i ] ] - > m_NotEligibleForFinish )
return ;
}
2020-09-26 19:41:58 +00:00
auto Tmp = std : : unique_ptr < CSqlTeamScoreData > ( new CSqlTeamScoreData ( ) ) ;
2020-07-04 17:53:27 +00:00
for ( unsigned int i = 0 ; i < Size ; i + + )
str_copy ( Tmp - > m_aNames [ i ] , Server ( ) - > ClientName ( aClientIDs [ i ] ) , sizeof ( Tmp - > m_aNames [ i ] ) ) ;
Tmp - > m_Size = Size ;
Tmp - > m_Time = Time ;
str_copy ( Tmp - > m_aTimestamp , pTimestamp , sizeof ( Tmp - > m_aTimestamp ) ) ;
FormatUuid ( GameServer ( ) - > GameUuid ( ) , Tmp - > m_GameUuid , sizeof ( Tmp - > m_GameUuid ) ) ;
str_copy ( Tmp - > m_Map , g_Config . m_SvMap , sizeof ( Tmp - > m_Map ) ) ;
m_pPool - > ExecuteWrite ( SaveTeamScoreThread , std : : move ( Tmp ) , " save team score " ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : SaveTeamScoreThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , bool Failure , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlTeamScoreData * pData = dynamic_cast < const CSqlTeamScoreData * > ( pGameData ) ;
2020-07-14 22:13:54 +00:00
char aBuf [ 512 ] ;
2020-07-04 17:53:27 +00:00
// get the names sorted in a tab separated string
std : : vector < std : : string > aNames ;
for ( unsigned int i = 0 ; i < pData - > m_Size ; i + + )
aNames . push_back ( pData - > m_aNames [ i ] ) ;
std : : sort ( aNames . begin ( ) , aNames . end ( ) ) ;
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT l.ID, Name, Time "
" FROM ( " // preselect teams with first name in team
" SELECT ID "
" FROM %s_teamrace "
" WHERE Map = ? AND Name = ? AND DDNet7 = false "
" ) as l INNER JOIN %s_teamrace AS r ON l.ID = r.ID "
" ORDER BY l.ID, Name COLLATE %s; " ,
pSqlServer - > GetPrefix ( ) , pSqlServer - > GetPrefix ( ) , pSqlServer - > BinaryCollate ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindString ( 2 , pData - > m_aNames [ 0 ] ) ;
2020-07-14 22:13:54 +00:00
bool FoundTeam = false ;
float Time ;
2020-07-18 13:19:26 +00:00
CTeamrank Teamrank ;
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
2020-07-14 22:13:54 +00:00
{
2021-01-31 16:58:30 +00:00
return true ;
}
if ( ! End )
{
2021-02-27 21:35:44 +00:00
bool SearchTeamEnd = false ;
2021-01-31 16:58:30 +00:00
while ( ! SearchTeamEnd )
2020-07-14 22:13:54 +00:00
{
2020-07-18 13:19:26 +00:00
Time = pSqlServer - > GetFloat ( 3 ) ;
2021-01-31 16:58:30 +00:00
if ( Teamrank . NextSqlResult ( pSqlServer , & SearchTeamEnd , pError , ErrorSize ) )
{
return true ;
}
2020-07-18 13:19:26 +00:00
if ( Teamrank . SamePlayers ( & aNames ) )
2020-07-14 22:13:54 +00:00
{
2020-07-18 13:19:26 +00:00
FoundTeam = true ;
2020-07-14 22:13:54 +00:00
break ;
}
}
}
if ( FoundTeam )
2020-07-04 17:53:27 +00:00
{
dbg_msg ( " sql " , " found team rank from same team (old time: %f, new time: %f) " , Time , pData - > m_Time ) ;
if ( pData - > m_Time < Time )
{
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" UPDATE %s_teamrace SET Time=%.2f, Timestamp=?, DDNet7=false, GameID=? WHERE ID = ?; " ,
pSqlServer - > GetPrefix ( ) , pData - > m_Time ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_aTimestamp ) ;
pSqlServer - > BindString ( 2 , pData - > m_GameUuid ) ;
2020-08-12 08:57:43 +00:00
pSqlServer - > BindBlob ( 3 , Teamrank . m_TeamID . m_aData , sizeof ( Teamrank . m_TeamID . m_aData ) ) ;
2020-09-06 21:34:18 +00:00
pSqlServer - > Print ( ) ;
2021-04-07 15:19:53 +00:00
int NumUpdated ;
if ( pSqlServer - > ExecuteUpdate ( & NumUpdated , pError , ErrorSize ) )
2021-01-31 16:58:30 +00:00
{
return true ;
}
2020-07-04 17:53:27 +00:00
}
}
else
{
CUuid GameID = RandomUuid ( ) ;
for ( unsigned int i = 0 ; i < pData - > m_Size ; i + + )
{
// if no entry found... create a new one
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-10 18:24:34 +00:00
" %s INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID, DDNet7) "
" VALUES (?, ?, %s, %.2f, ?, ?, false); " ,
pSqlServer - > InsertIgnore ( ) , pSqlServer - > GetPrefix ( ) ,
pSqlServer - > InsertTimestampAsUtc ( ) , pData - > m_Time ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindString ( 2 , pData - > m_aNames [ i ] ) ;
pSqlServer - > BindString ( 3 , pData - > m_aTimestamp ) ;
2020-07-14 22:13:54 +00:00
pSqlServer - > BindBlob ( 4 , GameID . m_aData , sizeof ( GameID . m_aData ) ) ;
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 5 , pData - > m_GameUuid ) ;
2020-09-06 21:34:18 +00:00
pSqlServer - > Print ( ) ;
2021-02-24 19:42:47 +00:00
int NumInserted ;
if ( pSqlServer - > ExecuteUpdate ( & NumInserted , pError , ErrorSize ) )
2021-01-31 16:58:30 +00:00
{
return true ;
}
2020-07-04 17:53:27 +00:00
}
}
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
2020-09-26 19:41:58 +00:00
void CScore : : ShowRank ( int ClientID , const char * pName )
2020-07-04 17:53:27 +00:00
{
if ( RateLimitPlayer ( ClientID ) )
return ;
ExecPlayerThread ( ShowRankThread , " show rank " , ClientID , pName , 0 ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : ShowRankThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
2020-07-04 17:53:27 +00:00
2021-03-03 13:23:18 +00:00
char aServerLike [ 16 ] ;
str_format ( aServerLike , sizeof ( aServerLike ) , " %%%s%% " , pData - > m_Server ) ;
2021-03-07 22:41:48 +00:00
// check sort method
char aBuf [ 600 ] ;
2020-07-04 17:53:27 +00:00
str_format ( aBuf , sizeof ( aBuf ) ,
2021-03-08 12:53:35 +00:00
" SELECT Rank, Time, PercentRank "
" FROM ( "
" SELECT RANK() OVER w AS Rank, PERCENT_RANK() OVER w as PercentRank, Name, MIN(Time) AS Time "
" FROM %s_race "
" WHERE Map = ? "
" AND Server LIKE ? "
" GROUP BY Name "
" WINDOW w AS (ORDER BY Time) "
" ) as a "
" WHERE Name = ?; " ,
pSqlServer - > GetPrefix ( ) ) ;
2021-03-03 13:23:18 +00:00
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
2021-03-03 13:23:18 +00:00
pSqlServer - > BindString ( 2 , aServerLike ) ;
pSqlServer - > BindString ( 3 , pData - > m_Name ) ;
2020-07-04 17:53:27 +00:00
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
2021-03-03 13:23:18 +00:00
2021-03-07 22:41:48 +00:00
char aRegionalRank [ 16 ] ;
2021-03-03 13:23:18 +00:00
if ( End )
{
2021-03-07 22:41:48 +00:00
str_copy ( aRegionalRank , " unranked " , sizeof ( aRegionalRank ) ) ;
}
else
{
str_format ( aRegionalRank , sizeof ( aRegionalRank ) , " rank %d " , pSqlServer - > GetInt ( 1 ) ) ;
2021-03-03 13:23:18 +00:00
}
const char * pAny = " % " ;
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindString ( 2 , pAny ) ;
pSqlServer - > BindString ( 3 , pData - > m_Name ) ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
2021-01-31 16:58:30 +00:00
if ( ! End )
2020-07-04 17:53:27 +00:00
{
int Rank = pSqlServer - > GetInt ( 1 ) ;
2021-01-23 22:19:44 +00:00
float Time = pSqlServer - > GetFloat ( 2 ) ;
// CEIL and FLOOR are not supported in SQLite
2021-01-24 15:41:43 +00:00
int BetterThanPercent = std : : floor ( 100.0 - 100.0 * pSqlServer - > GetFloat ( 3 ) ) ;
2020-10-18 20:55:11 +00:00
str_time_float ( Time , TIME_HOURS_CENTISECS , aBuf , sizeof ( aBuf ) ) ;
2020-07-04 17:53:27 +00:00
if ( g_Config . m_SvHideScore )
{
2020-12-08 11:03:43 +00:00
str_format ( pResult - > m_Data . m_aaMessages [ 0 ] , sizeof ( pResult - > m_Data . m_aaMessages [ 0 ] ) ,
2021-01-23 22:19:44 +00:00
" Your time: %s, better than %d%% " , aBuf , BetterThanPercent ) ;
2020-07-04 17:53:27 +00:00
}
else
{
2020-12-08 11:03:43 +00:00
pResult - > m_MessageKind = CScorePlayerResult : : ALL ;
2021-03-03 14:49:11 +00:00
2021-03-08 12:53:35 +00:00
if ( str_comp_nocase ( pData - > m_RequestingPlayer , pData - > m_Name ) = = 0 )
2021-03-03 14:49:11 +00:00
{
str_format ( pResult - > m_Data . m_aaMessages [ 0 ] , sizeof ( pResult - > m_Data . m_aaMessages [ 0 ] ) ,
2021-03-15 13:38:53 +00:00
" %s - %s - better than %d%% " ,
2021-03-03 14:49:11 +00:00
pData - > m_Name , aBuf , BetterThanPercent ) ;
}
else
{
str_format ( pResult - > m_Data . m_aaMessages [ 0 ] , sizeof ( pResult - > m_Data . m_aaMessages [ 0 ] ) ,
2021-03-15 13:38:53 +00:00
" %s - %s - better than %d%% - requested by %s " ,
2021-03-08 12:53:35 +00:00
pData - > m_Name , aBuf , BetterThanPercent , pData - > m_RequestingPlayer ) ;
2021-03-03 14:49:11 +00:00
}
2021-03-03 13:23:18 +00:00
str_format ( pResult - > m_Data . m_aaMessages [ 1 ] , sizeof ( pResult - > m_Data . m_aaMessages [ 1 ] ) ,
2021-03-15 13:38:53 +00:00
" Global rank %d - %s %s " ,
2021-03-07 22:41:48 +00:00
Rank , pData - > m_Server , aRegionalRank ) ;
2020-07-04 17:53:27 +00:00
}
}
else
{
2020-12-08 11:03:43 +00:00
str_format ( pResult - > m_Data . m_aaMessages [ 0 ] , sizeof ( pResult - > m_Data . m_aaMessages [ 0 ] ) ,
2020-09-26 19:41:58 +00:00
" %s is not ranked " , pData - > m_Name ) ;
2020-07-04 17:53:27 +00:00
}
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
2020-09-26 19:41:58 +00:00
void CScore : : ShowTeamRank ( int ClientID , const char * pName )
2020-07-04 17:53:27 +00:00
{
if ( RateLimitPlayer ( ClientID ) )
return ;
ExecPlayerThread ( ShowTeamRankThread , " show team rank " , ClientID , pName , 0 ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : ShowTeamRankThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
2020-07-04 17:53:27 +00:00
// check sort method
char aBuf [ 2400 ] ;
str_format ( aBuf , sizeof ( aBuf ) ,
2021-01-24 15:41:43 +00:00
" SELECT l.ID, Name, Time, Rank, PercentRank "
2020-09-26 19:41:58 +00:00
" FROM ( " // teamrank score board
2021-01-24 15:41:43 +00:00
" SELECT RANK() OVER w AS Rank, PERCENT_RANK() OVER w AS PercentRank, ID "
2020-09-26 19:41:58 +00:00
" FROM %s_teamrace "
" WHERE Map = ? "
" GROUP BY ID "
" WINDOW w AS (ORDER BY Time) "
" ) AS TeamRank INNER JOIN ( " // select rank with Name in team
" SELECT ID "
" FROM %s_teamrace "
" WHERE Map = ? AND Name = ? "
" ORDER BY Time "
" LIMIT 1 "
" ) AS l ON TeamRank.ID = l.ID "
" INNER JOIN %s_teamrace AS r ON l.ID = r.ID " ,
pSqlServer - > GetPrefix ( ) , pSqlServer - > GetPrefix ( ) , pSqlServer - > GetPrefix ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindString ( 2 , pData - > m_Map ) ;
pSqlServer - > BindString ( 3 , pData - > m_Name ) ;
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
2020-07-04 17:53:27 +00:00
{
float Time = pSqlServer - > GetFloat ( 3 ) ;
2020-10-18 20:55:11 +00:00
str_time_float ( Time , TIME_HOURS_CENTISECS , aBuf , sizeof ( aBuf ) ) ;
2020-07-04 17:53:27 +00:00
int Rank = pSqlServer - > GetInt ( 4 ) ;
2021-01-23 22:19:44 +00:00
// CEIL and FLOOR are not supported in SQLite
2021-01-24 15:41:43 +00:00
int BetterThanPercent = std : : floor ( 100.0 - 100.0 * pSqlServer - > GetFloat ( 5 ) ) ;
2020-07-18 13:36:16 +00:00
CTeamrank Teamrank ;
2021-01-31 16:58:30 +00:00
if ( Teamrank . NextSqlResult ( pSqlServer , & End , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
char aFormattedNames [ 512 ] = " " ;
2020-07-18 13:36:16 +00:00
for ( unsigned int Name = 0 ; Name < Teamrank . m_NumNames ; Name + + )
2020-07-04 17:53:27 +00:00
{
2020-07-18 13:36:16 +00:00
str_append ( aFormattedNames , Teamrank . m_aaNames [ Name ] , sizeof ( aFormattedNames ) ) ;
2020-07-04 17:53:27 +00:00
2020-09-26 19:41:58 +00:00
if ( Name < Teamrank . m_NumNames - 2 )
2020-07-04 17:53:27 +00:00
str_append ( aFormattedNames , " , " , sizeof ( aFormattedNames ) ) ;
2020-09-26 19:41:58 +00:00
else if ( Name < Teamrank . m_NumNames - 1 )
2020-07-04 17:53:27 +00:00
str_append ( aFormattedNames , " & " , sizeof ( aFormattedNames ) ) ;
}
if ( g_Config . m_SvHideScore )
{
2020-12-08 11:03:43 +00:00
str_format ( pResult - > m_Data . m_aaMessages [ 0 ] , sizeof ( pResult - > m_Data . m_aaMessages [ 0 ] ) ,
2021-01-23 22:19:44 +00:00
" Your team time: %s, better than %d%% " , aBuf , BetterThanPercent ) ;
2020-07-04 17:53:27 +00:00
}
else
{
2020-12-08 11:03:43 +00:00
pResult - > m_MessageKind = CScorePlayerResult : : ALL ;
str_format ( pResult - > m_Data . m_aaMessages [ 0 ] , sizeof ( pResult - > m_Data . m_aaMessages [ 0 ] ) ,
2021-01-23 22:19:44 +00:00
" %d. %s Team time: %s, better than %d%%, requested by %s " ,
Rank , aFormattedNames , aBuf , BetterThanPercent , pData - > m_RequestingPlayer ) ;
2020-07-04 17:53:27 +00:00
}
}
else
{
2020-12-08 11:03:43 +00:00
str_format ( pResult - > m_Data . m_aaMessages [ 0 ] , sizeof ( pResult - > m_Data . m_aaMessages [ 0 ] ) ,
2020-09-26 19:41:58 +00:00
" %s has no team ranks " , pData - > m_Name ) ;
2020-07-04 17:53:27 +00:00
}
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
2021-03-07 22:41:48 +00:00
void CScore : : ShowTop ( int ClientID , int Offset )
2020-07-04 17:53:27 +00:00
{
2021-03-03 14:50:43 +00:00
if ( RateLimitPlayer ( ClientID ) )
2020-07-04 17:53:27 +00:00
return ;
2021-03-07 22:41:48 +00:00
ExecPlayerThread ( ShowTopThread , " show top5 " , ClientID , " " , Offset ) ;
2020-07-04 17:53:27 +00:00
}
2021-03-07 22:41:48 +00:00
bool CScore : : ShowTopThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
2020-07-04 17:53:27 +00:00
2020-09-26 19:41:58 +00:00
int LimitStart = maximum ( abs ( pData - > m_Offset ) - 1 , 0 ) ;
2020-07-04 17:53:27 +00:00
const char * pOrder = pData - > m_Offset > = 0 ? " ASC " : " DESC " ;
2021-03-03 13:23:18 +00:00
const char * pAny = " % " ;
2020-07-04 17:53:27 +00:00
2021-03-08 12:53:35 +00:00
// check sort method
2020-07-04 17:53:27 +00:00
char aBuf [ 512 ] ;
str_format ( aBuf , sizeof ( aBuf ) ,
2021-03-09 16:27:25 +00:00
" SELECT Name, Time, Rank, Server "
2020-09-26 19:41:58 +00:00
" FROM ( "
2021-03-09 16:27:25 +00:00
" SELECT RANK() OVER w AS Rank, Name, MIN(Time) AS Time, Server "
2020-09-26 19:41:58 +00:00
" FROM %s_race "
" WHERE Map = ? "
2021-03-03 13:23:18 +00:00
" AND Server LIKE ? "
2020-09-26 19:41:58 +00:00
" GROUP BY Name "
" WINDOW w AS (ORDER BY Time) "
" ) as a "
" ORDER BY Rank %s "
2021-03-09 16:27:25 +00:00
" LIMIT %d, ?; " ,
2020-09-26 19:41:58 +00:00
pSqlServer - > GetPrefix ( ) ,
pOrder ,
LimitStart ) ;
2021-03-03 13:23:18 +00:00
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
2021-03-03 13:23:18 +00:00
pSqlServer - > BindString ( 2 , pAny ) ;
2021-03-15 13:38:53 +00:00
pSqlServer - > BindInt ( 3 , 5 ) ;
2020-07-04 17:53:27 +00:00
2021-03-07 22:41:48 +00:00
// show top
2021-03-09 16:27:25 +00:00
int Line = 0 ;
2021-03-09 16:41:48 +00:00
str_copy ( pResult - > m_Data . m_aaMessages [ Line ] , " ------------ Global Top ------------ " , sizeof ( pResult - > m_Data . m_aaMessages [ Line ] ) ) ;
2021-03-09 16:27:25 +00:00
Line + + ;
2020-07-04 17:53:27 +00:00
2021-03-07 22:41:48 +00:00
char aTime [ 32 ] ;
2021-01-31 16:58:30 +00:00
bool End = false ;
2021-03-09 16:27:25 +00:00
bool HasLocal = false ;
2021-01-31 16:58:30 +00:00
while ( ! pSqlServer - > Step ( & End , pError , ErrorSize ) & & ! End )
2020-07-04 17:53:27 +00:00
{
char aName [ MAX_NAME_LENGTH ] ;
pSqlServer - > GetString ( 1 , aName , sizeof ( aName ) ) ;
float Time = pSqlServer - > GetFloat ( 2 ) ;
2021-03-03 13:23:18 +00:00
str_time_float ( Time , TIME_HOURS_CENTISECS , aTime , sizeof ( aTime ) ) ;
int Rank = pSqlServer - > GetInt ( 3 ) ;
str_format ( pResult - > m_Data . m_aaMessages [ Line ] , sizeof ( pResult - > m_Data . m_aaMessages [ Line ] ) ,
" %d. %s Time: %s " , Rank , aName , aTime ) ;
2021-03-09 16:27:25 +00:00
char aRecordServer [ 6 ] ;
pSqlServer - > GetString ( 4 , aRecordServer , sizeof ( aRecordServer ) ) ;
HasLocal = HasLocal | | str_comp ( aRecordServer , pData - > m_Server ) = = 0 ;
2021-03-03 13:23:18 +00:00
Line + + ;
2021-03-09 16:27:25 +00:00
}
2021-03-03 13:23:18 +00:00
2021-03-09 16:27:25 +00:00
if ( ! HasLocal )
2021-03-03 13:23:18 +00:00
{
2021-03-09 16:27:25 +00:00
char aServerLike [ 16 ] ;
str_format ( aServerLike , sizeof ( aServerLike ) , " %%%s%% " , pData - > m_Server ) ;
2021-03-08 13:33:26 +00:00
2021-03-09 16:27:25 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindString ( 2 , aServerLike ) ;
pSqlServer - > BindInt ( 3 , 3 ) ;
2021-03-03 13:23:18 +00:00
2020-12-08 11:03:43 +00:00
str_format ( pResult - > m_Data . m_aaMessages [ Line ] , sizeof ( pResult - > m_Data . m_aaMessages [ Line ] ) ,
2021-03-09 16:41:48 +00:00
" ------------ %s Top ------------ " , pData - > m_Server ) ;
2020-07-04 17:53:27 +00:00
Line + + ;
2021-03-09 16:27:25 +00:00
// show top
while ( ! pSqlServer - > Step ( & End , pError , ErrorSize ) & & ! End )
{
char aName [ MAX_NAME_LENGTH ] ;
pSqlServer - > GetString ( 1 , aName , sizeof ( aName ) ) ;
float Time = pSqlServer - > GetFloat ( 2 ) ;
str_time_float ( Time , TIME_HOURS_CENTISECS , aTime , sizeof ( aTime ) ) ;
int Rank = pSqlServer - > GetInt ( 3 ) ;
str_format ( pResult - > m_Data . m_aaMessages [ Line ] , sizeof ( pResult - > m_Data . m_aaMessages [ Line ] ) ,
" %d. %s Time: %s " , Rank , aName , aTime ) ;
Line + + ;
}
2020-07-04 17:53:27 +00:00
}
2021-03-09 16:41:48 +00:00
else
{
str_copy ( pResult - > m_Data . m_aaMessages [ Line ] , " --------------------------------------- " ,
sizeof ( pResult - > m_Data . m_aaMessages [ Line ] ) ) ;
}
2021-03-09 16:27:25 +00:00
2021-01-31 16:58:30 +00:00
if ( ! End )
{
return true ;
}
2020-07-04 17:53:27 +00:00
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
void CScore : : ShowTeamTop5 ( int ClientID , int Offset )
{
if ( RateLimitPlayer ( ClientID ) )
return ;
ExecPlayerThread ( ShowTeamTop5Thread , " show team top5 " , ClientID , " " , Offset ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : ShowTeamTop5Thread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
auto * paMessages = pResult - > m_Data . m_aaMessages ;
2020-07-04 17:53:27 +00:00
2020-09-26 19:41:58 +00:00
int LimitStart = maximum ( abs ( pData - > m_Offset ) - 1 , 0 ) ;
2020-07-04 17:53:27 +00:00
const char * pOrder = pData - > m_Offset > = 0 ? " ASC " : " DESC " ;
// check sort method
char aBuf [ 512 ] ;
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT Name, Time, Rank, TeamSize "
" FROM ( " // limit to 5
" SELECT TeamSize, Rank, ID "
" FROM ( " // teamrank score board
" SELECT RANK() OVER w AS Rank, ID, COUNT(*) AS Teamsize "
" FROM %s_teamrace "
" WHERE Map = ? "
" GROUP BY Id "
" WINDOW w AS (ORDER BY Time) "
" ) as l1 "
" ORDER BY Rank %s "
" LIMIT %d, 5 "
" ) as l2 "
" INNER JOIN %s_teamrace as r ON l2.ID = r.ID "
" ORDER BY Rank %s, r.ID, Name ASC; " ,
pSqlServer - > GetPrefix ( ) , pOrder , LimitStart , pSqlServer - > GetPrefix ( ) , pOrder ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
// show teamtop5
int Line = 0 ;
2020-10-17 11:14:38 +00:00
str_copy ( paMessages [ Line + + ] , " ------- Team Top 5 ------- " , sizeof ( paMessages [ Line ] ) ) ;
2020-07-04 17:53:27 +00:00
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
2020-07-04 17:53:27 +00:00
{
for ( Line = 1 ; Line < 6 ; Line + + ) // print
{
2020-07-18 13:18:55 +00:00
bool Last = false ;
2020-07-04 17:53:27 +00:00
float Time = pSqlServer - > GetFloat ( 2 ) ;
2020-10-18 20:55:11 +00:00
str_time_float ( Time , TIME_HOURS_CENTISECS , aBuf , sizeof ( aBuf ) ) ;
2020-07-04 17:53:27 +00:00
int Rank = pSqlServer - > GetInt ( 3 ) ;
int TeamSize = pSqlServer - > GetInt ( 4 ) ;
2020-09-26 19:41:58 +00:00
char aNames [ 2300 ] = { 0 } ;
2020-07-04 17:53:27 +00:00
for ( int i = 0 ; i < TeamSize ; i + + )
{
char aName [ MAX_NAME_LENGTH ] ;
pSqlServer - > GetString ( 1 , aName , sizeof ( aName ) ) ;
str_append ( aNames , aName , sizeof ( aNames ) ) ;
2020-09-26 19:41:58 +00:00
if ( i < TeamSize - 2 )
2020-07-04 17:53:27 +00:00
str_append ( aNames , " , " , sizeof ( aNames ) ) ;
2020-09-26 19:41:58 +00:00
else if ( i = = TeamSize - 2 )
2020-07-04 17:53:27 +00:00
str_append ( aNames , " & " , sizeof ( aNames ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > Step ( & Last , pError , ErrorSize ) )
{
return true ;
}
if ( Last )
2020-07-04 17:53:27 +00:00
{
break ;
}
}
2020-10-18 20:55:11 +00:00
str_format ( paMessages [ Line ] , sizeof ( paMessages [ Line ] ) , " %d. %s Team Time: %s " ,
Rank , aNames , aBuf ) ;
2020-07-18 13:18:55 +00:00
if ( Last )
{
Line + + ;
2020-07-04 17:53:27 +00:00
break ;
2020-07-18 13:18:55 +00:00
}
2020-07-04 17:53:27 +00:00
}
}
2020-10-17 11:14:38 +00:00
str_copy ( paMessages [ Line ] , " ------------------------------- " , sizeof ( paMessages [ Line ] ) ) ;
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
2020-12-09 19:23:26 +00:00
void CScore : : ShowTeamTop5 ( int ClientID , const char * pName , int Offset )
{
if ( RateLimitPlayer ( ClientID ) )
return ;
ExecPlayerThread ( ShowPlayerTeamTop5Thread , " show team top5 player " , ClientID , pName , Offset ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : ShowPlayerTeamTop5Thread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-12-09 19:23:26 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
auto * paMessages = pResult - > m_Data . m_aaMessages ;
int LimitStart = maximum ( abs ( pData - > m_Offset ) - 1 , 0 ) ;
const char * pOrder = pData - > m_Offset > = 0 ? " ASC " : " DESC " ;
// check sort method
char aBuf [ 2400 ] ;
str_format ( aBuf , sizeof ( aBuf ) ,
" SELECT l.ID, Name, Time, Rank "
" FROM ( " // teamrank score board
" SELECT RANK() OVER w AS Rank, ID "
" FROM %s_teamrace "
" WHERE Map = ? "
" GROUP BY ID "
" WINDOW w AS (ORDER BY Time) "
" ) AS TeamRank INNER JOIN ( " // select rank with Name in team
" SELECT ID "
" FROM %s_teamrace "
" WHERE Map = ? AND Name = ? "
" ORDER BY Time %s "
" LIMIT %d, 5 "
" ) AS l ON TeamRank.ID = l.ID "
" INNER JOIN %s_teamrace AS r ON l.ID = r.ID "
" ORDER BY Time %s, l.ID " ,
pSqlServer - > GetPrefix ( ) , pSqlServer - > GetPrefix ( ) , pOrder , LimitStart , pSqlServer - > GetPrefix ( ) , pOrder ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-12-09 19:23:26 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindString ( 2 , pData - > m_Map ) ;
pSqlServer - > BindString ( 3 , pData - > m_Name ) ;
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
2020-12-09 19:23:26 +00:00
{
// show teamtop5
int Line = 0 ;
str_copy ( paMessages [ Line + + ] , " ------- Team Top 5 ------- " , sizeof ( paMessages [ Line ] ) ) ;
for ( Line = 1 ; Line < 6 ; Line + + ) // print
{
float Time = pSqlServer - > GetFloat ( 3 ) ;
str_time_float ( Time , TIME_HOURS_CENTISECS , aBuf , sizeof ( aBuf ) ) ;
int Rank = pSqlServer - > GetInt ( 4 ) ;
CTeamrank Teamrank ;
2021-01-31 16:58:30 +00:00
bool Last ;
if ( Teamrank . NextSqlResult ( pSqlServer , & Last , pError , ErrorSize ) )
{
return true ;
}
2020-12-09 19:23:26 +00:00
char aFormattedNames [ 512 ] = " " ;
for ( unsigned int Name = 0 ; Name < Teamrank . m_NumNames ; Name + + )
{
str_append ( aFormattedNames , Teamrank . m_aaNames [ Name ] , sizeof ( aFormattedNames ) ) ;
if ( Name < Teamrank . m_NumNames - 2 )
str_append ( aFormattedNames , " , " , sizeof ( aFormattedNames ) ) ;
else if ( Name < Teamrank . m_NumNames - 1 )
str_append ( aFormattedNames , " & " , sizeof ( aFormattedNames ) ) ;
}
str_format ( paMessages [ Line ] , sizeof ( paMessages [ Line ] ) , " %d. %s Team Time: %s " ,
Rank , aFormattedNames , aBuf ) ;
if ( Last )
{
Line + + ;
break ;
}
}
str_copy ( paMessages [ Line ] , " ------------------------------- " , sizeof ( paMessages [ Line ] ) ) ;
}
else
{
if ( pData - > m_Offset = = 0 )
str_format ( paMessages [ 0 ] , sizeof ( paMessages [ 0 ] ) , " %s has no team ranks " , pData - > m_Name ) ;
else
str_format ( paMessages [ 0 ] , sizeof ( paMessages [ 0 ] ) , " %s has no team ranks in the specified range " , pData - > m_Name ) ;
}
2021-01-31 16:58:30 +00:00
return false ;
2020-12-09 19:23:26 +00:00
}
2020-07-04 17:53:27 +00:00
void CScore : : ShowTimes ( int ClientID , int Offset )
{
if ( RateLimitPlayer ( ClientID ) )
return ;
ExecPlayerThread ( ShowTimesThread , " show times " , ClientID , " " , Offset ) ;
}
2020-09-26 19:41:58 +00:00
void CScore : : ShowTimes ( int ClientID , const char * pName , int Offset )
2020-07-04 17:53:27 +00:00
{
if ( RateLimitPlayer ( ClientID ) )
return ;
ExecPlayerThread ( ShowTimesThread , " show times " , ClientID , pName , Offset ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : ShowTimesThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
auto * paMessages = pResult - > m_Data . m_aaMessages ;
2020-07-04 17:53:27 +00:00
2020-09-26 19:41:58 +00:00
int LimitStart = maximum ( abs ( pData - > m_Offset ) - 1 , 0 ) ;
2020-07-04 17:53:27 +00:00
const char * pOrder = pData - > m_Offset > = 0 ? " DESC " : " ASC " ;
2020-07-18 18:49:34 +00:00
char aCurrentTimestamp [ 512 ] ;
pSqlServer - > ToUnixTimestamp ( " CURRENT_TIMESTAMP " , aCurrentTimestamp , sizeof ( aCurrentTimestamp ) ) ;
char aTimestamp [ 512 ] ;
pSqlServer - > ToUnixTimestamp ( " Timestamp " , aTimestamp , sizeof ( aTimestamp ) ) ;
2020-07-04 17:53:27 +00:00
char aBuf [ 512 ] ;
if ( pData - > m_Name [ 0 ] ! = ' \0 ' ) // last 5 times of a player
{
str_format ( aBuf , sizeof ( aBuf ) ,
2021-02-26 22:44:26 +00:00
" SELECT Time, (%s-%s) as Ago, %s as Stamp, Server "
2020-09-26 19:41:58 +00:00
" FROM %s_race "
" WHERE Map = ? AND Name = ? "
" ORDER BY Timestamp %s "
" LIMIT ?, 5; " ,
aCurrentTimestamp , aTimestamp , aTimestamp ,
pSqlServer - > GetPrefix ( ) , pOrder ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindString ( 2 , pData - > m_Name ) ;
pSqlServer - > BindInt ( 3 , LimitStart ) ;
}
else // last 5 times of server
{
str_format ( aBuf , sizeof ( aBuf ) ,
2021-02-26 22:44:26 +00:00
" SELECT Time, (%s-%s) as Ago, %s as Stamp, Server, Name "
2020-09-26 19:41:58 +00:00
" FROM %s_race "
" WHERE Map = ? "
" ORDER BY Timestamp %s "
" LIMIT ?, 5; " ,
aCurrentTimestamp , aTimestamp , aTimestamp ,
pSqlServer - > GetPrefix ( ) , pOrder ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindInt ( 2 , LimitStart ) ;
}
// show top5
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
2020-07-04 17:53:27 +00:00
{
return true ;
}
2021-01-31 16:58:30 +00:00
if ( End )
{
str_copy ( paMessages [ 0 ] , " There are no times in the specified range " , sizeof ( paMessages [ 0 ] ) ) ;
return false ;
}
2020-07-04 17:53:27 +00:00
2020-10-17 11:14:38 +00:00
str_copy ( paMessages [ 0 ] , " ------------- Last Times ------------- " , sizeof ( paMessages [ 0 ] ) ) ;
2020-07-04 17:53:27 +00:00
int Line = 1 ;
do
{
float Time = pSqlServer - > GetFloat ( 1 ) ;
2020-10-18 20:55:11 +00:00
str_time_float ( Time , TIME_HOURS_CENTISECS , aBuf , sizeof ( aBuf ) ) ;
2020-07-04 17:53:27 +00:00
int Ago = pSqlServer - > GetInt ( 2 ) ;
int Stamp = pSqlServer - > GetInt ( 3 ) ;
2021-02-18 23:13:46 +00:00
char aServer [ 5 ] ;
2021-02-26 22:44:26 +00:00
pSqlServer - > GetString ( 4 , aServer , sizeof ( aServer ) ) ;
2021-02-18 23:13:46 +00:00
char aServerFormatted [ 8 ] = " \0 " ;
if ( str_comp ( aServer , " UNK " ) ! = 0 )
str_format ( aServerFormatted , sizeof ( aServerFormatted ) , " [%s] " , aServer ) ;
2020-07-04 17:53:27 +00:00
char aAgoString [ 40 ] = " \0 " ;
2020-10-12 16:31:55 +00:00
sqlstr : : AgoTimeToString ( Ago , aAgoString , sizeof ( aAgoString ) ) ;
2020-07-04 17:53:27 +00:00
if ( pData - > m_Name [ 0 ] ! = ' \0 ' ) // last 5 times of a player
{
if ( Stamp = = 0 ) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet
str_format ( paMessages [ Line ] , sizeof ( paMessages [ Line ] ) ,
2021-02-18 23:13:46 +00:00
" %s%s, don't know how long ago " , aServerFormatted , aBuf ) ;
2020-07-04 17:53:27 +00:00
else
str_format ( paMessages [ Line ] , sizeof ( paMessages [ Line ] ) ,
2021-02-18 23:13:46 +00:00
" %s%s ago, %s " , aServerFormatted , aAgoString , aBuf ) ;
2020-07-04 17:53:27 +00:00
}
else // last 5 times of the server
{
char aName [ MAX_NAME_LENGTH ] ;
2021-02-26 22:44:26 +00:00
pSqlServer - > GetString ( 5 , aName , sizeof ( aName ) ) ;
2020-07-04 17:53:27 +00:00
if ( Stamp = = 0 ) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet
{
str_format ( paMessages [ Line ] , sizeof ( paMessages [ Line ] ) ,
2021-02-18 23:13:46 +00:00
" %s%s, %s, don't know when " , aServerFormatted , aName , aBuf ) ;
2020-07-04 17:53:27 +00:00
}
else
{
str_format ( paMessages [ Line ] , sizeof ( paMessages [ Line ] ) ,
2021-02-18 23:13:46 +00:00
" %s%s, %s ago, %s " , aServerFormatted , aName , aAgoString , aBuf ) ;
2020-07-04 17:53:27 +00:00
}
}
Line + + ;
2021-01-31 16:58:30 +00:00
} while ( ! pSqlServer - > Step ( & End , pError , ErrorSize ) & & ! End ) ;
if ( ! End )
{
return true ;
}
2020-10-17 11:14:38 +00:00
str_copy ( paMessages [ Line ] , " ---------------------------------------------------- " , sizeof ( paMessages [ Line ] ) ) ;
2020-07-04 17:53:27 +00:00
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
2020-09-26 19:41:58 +00:00
void CScore : : ShowPoints ( int ClientID , const char * pName )
2020-07-04 17:53:27 +00:00
{
if ( RateLimitPlayer ( ClientID ) )
return ;
ExecPlayerThread ( ShowPointsThread , " show points " , ClientID , pName , 0 ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : ShowPointsThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
auto * paMessages = pResult - > m_Data . m_aaMessages ;
2020-07-04 17:53:27 +00:00
char aBuf [ 512 ] ;
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT ( "
" SELECT COUNT(Name) + 1 FROM %s_points WHERE Points > ( "
" SELECT points FROM %s_points WHERE Name = ? "
" )) as Rank, Points, Name "
" FROM %s_points WHERE Name = ?; " ,
pSqlServer - > GetPrefix ( ) , pSqlServer - > GetPrefix ( ) , pSqlServer - > GetPrefix ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Name ) ;
2020-09-07 20:47:42 +00:00
pSqlServer - > BindString ( 2 , pData - > m_Name ) ;
2020-07-04 17:53:27 +00:00
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
2020-07-04 17:53:27 +00:00
{
int Rank = pSqlServer - > GetInt ( 1 ) ;
int Count = pSqlServer - > GetInt ( 2 ) ;
char aName [ MAX_NAME_LENGTH ] ;
pSqlServer - > GetString ( 3 , aName , sizeof ( aName ) ) ;
2020-12-08 11:03:43 +00:00
pResult - > m_MessageKind = CScorePlayerResult : : ALL ;
2020-07-04 17:53:27 +00:00
str_format ( paMessages [ 0 ] , sizeof ( paMessages [ 0 ] ) ,
2020-09-26 19:41:58 +00:00
" %d. %s Points: %d, requested by %s " ,
Rank , aName , Count , pData - > m_RequestingPlayer ) ;
2020-07-04 17:53:27 +00:00
}
else
{
str_format ( paMessages [ 0 ] , sizeof ( paMessages [ 0 ] ) ,
2020-09-26 19:41:58 +00:00
" %s has not collected any points so far " , pData - > m_Name ) ;
2020-07-04 17:53:27 +00:00
}
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
void CScore : : ShowTopPoints ( int ClientID , int Offset )
{
if ( RateLimitPlayer ( ClientID ) )
return ;
ExecPlayerThread ( ShowTopPointsThread , " show top points " , ClientID , " " , Offset ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : ShowTopPointsThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
auto * paMessages = pResult - > m_Data . m_aaMessages ;
2020-07-04 17:53:27 +00:00
2021-01-31 23:31:53 +00:00
int LimitStart = maximum ( pData - > m_Offset - 1 , 0 ) ;
2020-07-04 17:53:27 +00:00
char aBuf [ 512 ] ;
str_format ( aBuf , sizeof ( aBuf ) ,
2021-01-31 23:31:53 +00:00
" SELECT RANK() OVER (ORDER BY a.Points DESC) as Rank, Points, Name "
2020-09-26 19:41:58 +00:00
" FROM ( "
2021-01-31 23:31:53 +00:00
" SELECT Points, Name "
2020-09-26 19:41:58 +00:00
" FROM %s_points "
2021-01-31 23:31:53 +00:00
" ORDER BY Points DESC LIMIT ? "
2020-09-26 19:41:58 +00:00
" ) as a "
" LIMIT ?, 5; " ,
2021-01-31 23:31:53 +00:00
pSqlServer - > GetPrefix ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2021-01-31 23:31:53 +00:00
pSqlServer - > BindInt ( 1 , LimitStart + 5 ) ;
pSqlServer - > BindInt ( 2 , LimitStart ) ;
2020-07-04 17:53:27 +00:00
// show top points
2020-10-17 11:14:38 +00:00
str_copy ( paMessages [ 0 ] , " -------- Top Points -------- " , sizeof ( paMessages [ 0 ] ) ) ;
2020-07-04 17:53:27 +00:00
2021-01-31 16:58:30 +00:00
bool End = false ;
2020-07-04 17:53:27 +00:00
int Line = 1 ;
2021-01-31 16:58:30 +00:00
while ( ! pSqlServer - > Step ( & End , pError , ErrorSize ) & & ! End )
2020-07-04 17:53:27 +00:00
{
int Rank = pSqlServer - > GetInt ( 1 ) ;
int Points = pSqlServer - > GetInt ( 2 ) ;
char aName [ MAX_NAME_LENGTH ] ;
pSqlServer - > GetString ( 3 , aName , sizeof ( aName ) ) ;
str_format ( paMessages [ Line ] , sizeof ( paMessages [ Line ] ) ,
2020-09-26 19:41:58 +00:00
" %d. %s Points: %d " , Rank , aName , Points ) ;
2020-07-04 17:53:27 +00:00
Line + + ;
}
2021-01-31 16:58:30 +00:00
if ( ! End )
{
return true ;
}
2020-10-17 11:14:38 +00:00
str_copy ( paMessages [ Line ] , " ------------------------------- " , sizeof ( paMessages [ Line ] ) ) ;
2020-07-04 17:53:27 +00:00
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
void CScore : : RandomMap ( int ClientID , int Stars )
{
auto pResult = std : : make_shared < CScoreRandomMapResult > ( ClientID ) ;
GameServer ( ) - > m_SqlRandomMapResult = pResult ;
auto Tmp = std : : unique_ptr < CSqlRandomMapRequest > ( new CSqlRandomMapRequest ( pResult ) ) ;
Tmp - > m_Stars = Stars ;
str_copy ( Tmp - > m_CurrentMap , g_Config . m_SvMap , sizeof ( Tmp - > m_CurrentMap ) ) ;
str_copy ( Tmp - > m_ServerType , g_Config . m_SvServerType , sizeof ( Tmp - > m_ServerType ) ) ;
str_copy ( Tmp - > m_RequestingPlayer , GameServer ( ) - > Server ( ) - > ClientName ( ClientID ) , sizeof ( Tmp - > m_RequestingPlayer ) ) ;
m_pPool - > Execute ( RandomMapThread , std : : move ( Tmp ) , " random map " ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : RandomMapThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlRandomMapRequest * pData = dynamic_cast < const CSqlRandomMapRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScoreRandomMapResult * pResult = dynamic_cast < CScoreRandomMapResult * > ( pGameData - > m_pResult . get ( ) ) ;
2020-07-04 17:53:27 +00:00
char aBuf [ 512 ] ;
if ( 0 < = pData - > m_Stars & & pData - > m_Stars < = 5 )
{
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT Map FROM %s_maps "
" WHERE Server = ? AND Map != ? AND Stars = ? "
2020-10-28 21:34:48 +00:00
" ORDER BY %s LIMIT 1; " ,
pSqlServer - > GetPrefix ( ) , pSqlServer - > Random ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindInt ( 3 , pData - > m_Stars ) ;
}
else
{
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT Map FROM %s_maps "
" WHERE Server = ? AND Map != ? "
2020-10-28 21:34:48 +00:00
" ORDER BY %s LIMIT 1; " ,
pSqlServer - > GetPrefix ( ) , pSqlServer - > Random ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
}
pSqlServer - > BindString ( 1 , pData - > m_ServerType ) ;
pSqlServer - > BindString ( 2 , pData - > m_CurrentMap ) ;
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
2020-07-04 17:53:27 +00:00
{
2020-12-08 11:03:43 +00:00
pSqlServer - > GetString ( 1 , pResult - > m_Map , sizeof ( pResult - > m_Map ) ) ;
2020-07-04 17:53:27 +00:00
}
else
{
2020-12-08 11:03:43 +00:00
str_copy ( pResult - > m_aMessage , " No maps found on this server! " , sizeof ( pResult - > m_aMessage ) ) ;
2020-07-04 17:53:27 +00:00
}
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
void CScore : : RandomUnfinishedMap ( int ClientID , int Stars )
{
auto pResult = std : : make_shared < CScoreRandomMapResult > ( ClientID ) ;
GameServer ( ) - > m_SqlRandomMapResult = pResult ;
auto Tmp = std : : unique_ptr < CSqlRandomMapRequest > ( new CSqlRandomMapRequest ( pResult ) ) ;
Tmp - > m_Stars = Stars ;
str_copy ( Tmp - > m_CurrentMap , g_Config . m_SvMap , sizeof ( Tmp - > m_CurrentMap ) ) ;
str_copy ( Tmp - > m_ServerType , g_Config . m_SvServerType , sizeof ( Tmp - > m_ServerType ) ) ;
str_copy ( Tmp - > m_RequestingPlayer , GameServer ( ) - > Server ( ) - > ClientName ( ClientID ) , sizeof ( Tmp - > m_RequestingPlayer ) ) ;
m_pPool - > Execute ( RandomUnfinishedMapThread , std : : move ( Tmp ) , " random unfinished map " ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : RandomUnfinishedMapThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlRandomMapRequest * pData = dynamic_cast < const CSqlRandomMapRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScoreRandomMapResult * pResult = dynamic_cast < CScoreRandomMapResult * > ( pGameData - > m_pResult . get ( ) ) ;
2020-07-04 17:53:27 +00:00
char aBuf [ 512 ] ;
if ( pData - > m_Stars > = 0 )
{
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT Map "
" FROM %s_maps "
" WHERE Server = ? AND Map != ? AND Stars = ? AND Map NOT IN ( "
" SELECT Map "
" FROM %s_race "
" WHERE Name = ? "
2020-10-28 21:34:48 +00:00
" ) ORDER BY %s "
2020-09-26 19:41:58 +00:00
" LIMIT 1; " ,
2020-10-28 21:34:48 +00:00
pSqlServer - > GetPrefix ( ) , pSqlServer - > GetPrefix ( ) , pSqlServer - > Random ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_ServerType ) ;
pSqlServer - > BindString ( 2 , pData - > m_CurrentMap ) ;
pSqlServer - > BindInt ( 3 , pData - > m_Stars ) ;
pSqlServer - > BindString ( 4 , pData - > m_RequestingPlayer ) ;
}
else
{
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT Map "
" FROM %s_maps AS maps "
" WHERE Server = ? AND Map != ? AND Map NOT IN ( "
" SELECT Map "
" FROM %s_race as race "
" WHERE Name = ? "
2020-10-28 21:34:48 +00:00
" ) ORDER BY %s "
2020-09-26 19:41:58 +00:00
" LIMIT 1; " ,
2020-10-28 21:34:48 +00:00
pSqlServer - > GetPrefix ( ) , pSqlServer - > GetPrefix ( ) , pSqlServer - > Random ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_ServerType ) ;
pSqlServer - > BindString ( 2 , pData - > m_CurrentMap ) ;
pSqlServer - > BindString ( 3 , pData - > m_RequestingPlayer ) ;
}
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
2020-07-04 17:53:27 +00:00
{
2020-12-08 11:03:43 +00:00
pSqlServer - > GetString ( 1 , pResult - > m_Map , sizeof ( pResult - > m_Map ) ) ;
2020-07-04 17:53:27 +00:00
}
else
{
2020-12-08 11:03:43 +00:00
str_copy ( pResult - > m_aMessage , " You have no more unfinished maps on this server! " , sizeof ( pResult - > m_aMessage ) ) ;
2020-07-04 17:53:27 +00:00
}
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
2020-09-26 19:41:58 +00:00
void CScore : : SaveTeam ( int ClientID , const char * Code , const char * Server )
2020-07-04 17:53:27 +00:00
{
if ( RateLimitPlayer ( ClientID ) )
return ;
2020-10-26 10:28:18 +00:00
auto * pController = ( ( CGameControllerDDRace * ) ( GameServer ( ) - > m_pController ) ) ;
2020-07-04 17:53:27 +00:00
int Team = pController - > m_Teams . m_Core . Team ( ClientID ) ;
if ( pController - > m_Teams . GetSaving ( Team ) )
return ;
auto SaveResult = std : : make_shared < CScoreSaveResult > ( ClientID , pController ) ;
2020-12-08 11:03:43 +00:00
SaveResult - > m_SaveID = RandomUuid ( ) ;
2021-02-16 17:15:50 +00:00
int Result = SaveResult - > m_SavedTeam . Save ( Team ) ;
2020-07-04 17:53:27 +00:00
if ( CSaveTeam : : HandleSaveError ( Result , ClientID , GameServer ( ) ) )
return ;
pController - > m_Teams . SetSaving ( Team , SaveResult ) ;
auto Tmp = std : : unique_ptr < CSqlTeamSave > ( new CSqlTeamSave ( SaveResult ) ) ;
str_copy ( Tmp - > m_Code , Code , sizeof ( Tmp - > m_Code ) ) ;
str_copy ( Tmp - > m_Map , g_Config . m_SvMap , sizeof ( Tmp - > m_Map ) ) ;
str_copy ( Tmp - > m_Server , Server , sizeof ( Tmp - > m_Server ) ) ;
str_copy ( Tmp - > m_ClientName , this - > Server ( ) - > ClientName ( ClientID ) , sizeof ( Tmp - > m_ClientName ) ) ;
Tmp - > m_aGeneratedCode [ 0 ] = ' \0 ' ;
GeneratePassphrase ( Tmp - > m_aGeneratedCode , sizeof ( Tmp - > m_aGeneratedCode ) ) ;
pController - > m_Teams . KillSavedTeam ( ClientID , Team ) ;
m_pPool - > ExecuteWrite ( SaveTeamThread , std : : move ( Tmp ) , " save team " ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : SaveTeamThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , bool Failure , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlTeamSave * pData = dynamic_cast < const CSqlTeamSave * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScoreSaveResult * pResult = dynamic_cast < CScoreSaveResult * > ( pGameData - > m_pResult . get ( ) ) ;
2020-07-04 17:53:27 +00:00
char aSaveID [ UUID_MAXSTRSIZE ] ;
2020-12-08 11:03:43 +00:00
FormatUuid ( pResult - > m_SaveID , aSaveID , UUID_MAXSTRSIZE ) ;
2020-07-04 17:53:27 +00:00
2020-12-08 11:03:43 +00:00
char * pSaveState = pResult - > m_SavedTeam . GetString ( ) ;
2020-07-04 17:53:27 +00:00
char aBuf [ 65536 ] ;
2021-01-31 16:58:30 +00:00
dbg_msg ( " score/dbg " , " code=%s failure=%d " , pData - > m_Code , ( int ) Failure ) ;
2021-01-20 13:25:57 +00:00
bool UseGeneratedCode = pData - > m_Code [ 0 ] = = ' \0 ' | | Failure ;
2020-07-04 17:53:27 +00:00
2021-01-20 13:25:57 +00:00
bool Retry = false ;
// two tries, first use the user provided code, then the autogenerated
do
2020-07-04 17:53:27 +00:00
{
2021-01-20 13:25:57 +00:00
Retry = false ;
char Code [ 128 ] = { 0 } ;
if ( UseGeneratedCode )
2020-07-04 17:53:27 +00:00
str_copy ( Code , pData - > m_aGeneratedCode , sizeof ( Code ) ) ;
2021-01-20 13:25:57 +00:00
else
str_copy ( Code , pData - > m_Code , sizeof ( Code ) ) ;
2020-07-04 17:53:27 +00:00
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-10 18:24:34 +00:00
" %s INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) "
" VALUES (?, ?, ?, CURRENT_TIMESTAMP, ?, ?, false) " ,
pSqlServer - > InsertIgnore ( ) , pSqlServer - > GetPrefix ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pSaveState ) ;
pSqlServer - > BindString ( 2 , pData - > m_Map ) ;
pSqlServer - > BindString ( 3 , Code ) ;
pSqlServer - > BindString ( 4 , pData - > m_Server ) ;
pSqlServer - > BindString ( 5 , aSaveID ) ;
2020-09-06 21:34:18 +00:00
pSqlServer - > Print ( ) ;
2021-01-31 16:58:30 +00:00
int NumInserted ;
if ( pSqlServer - > ExecuteUpdate ( & NumInserted , pError , ErrorSize ) )
{
return true ;
}
2021-01-20 13:25:57 +00:00
if ( NumInserted = = 1 )
2020-07-04 17:53:27 +00:00
{
2021-01-20 13:25:57 +00:00
if ( ! Failure )
2020-07-04 17:53:27 +00:00
{
2021-01-20 13:25:57 +00:00
if ( str_comp ( pData - > m_Server , g_Config . m_SvSqlServerName ) = = 0 )
{
str_format ( pResult - > m_aMessage , sizeof ( pResult - > m_aMessage ) ,
" Team successfully saved by %s. Use '/load %s' to continue " ,
pData - > m_ClientName , Code ) ;
}
else
{
str_format ( pResult - > m_aMessage , sizeof ( pResult - > m_aMessage ) ,
" Team successfully saved by %s. Use '/load %s' on %s to continue " ,
pData - > m_ClientName , Code , pData - > m_Server ) ;
}
2020-07-04 17:53:27 +00:00
}
else
{
2021-01-20 13:25:57 +00:00
str_copy ( pResult - > m_aBroadcast ,
" Database connection failed, teamsave written to a file instead. Admins will add it manually in a few days. " ,
sizeof ( pResult - > m_aBroadcast ) ) ;
if ( str_comp ( pData - > m_Server , g_Config . m_SvSqlServerName ) = = 0 )
{
str_format ( pResult - > m_aMessage , sizeof ( pResult - > m_aMessage ) ,
" Team successfully saved by %s. The database connection failed, using generated save code instead to avoid collisions. Use '/load %s' to continue " ,
pData - > m_ClientName , Code ) ;
}
else
{
str_format ( pResult - > m_aMessage , sizeof ( pResult - > m_aMessage ) ,
" Team successfully saved by %s. The database connection failed, using generated save code instead to avoid collisions. Use '/load %s' on %s to continue " ,
pData - > m_ClientName , Code , pData - > m_Server ) ;
}
2020-07-04 17:53:27 +00:00
}
2021-01-20 13:25:57 +00:00
pResult - > m_Status = CScoreSaveResult : : SAVE_SUCCESS ;
2020-07-04 17:53:27 +00:00
}
2021-01-20 13:25:57 +00:00
else if ( ! UseGeneratedCode )
2020-07-04 17:53:27 +00:00
{
2021-01-20 13:25:57 +00:00
UseGeneratedCode = true ;
Retry = true ;
2020-07-04 17:53:27 +00:00
}
2021-01-20 13:25:57 +00:00
} while ( Retry ) ;
2020-07-04 17:53:27 +00:00
2021-01-20 13:25:57 +00:00
if ( pResult - > m_Status ! = CScoreSaveResult : : SAVE_SUCCESS )
2020-07-04 17:53:27 +00:00
{
dbg_msg ( " sql " , " ERROR: This save-code already exists " ) ;
2020-12-08 11:03:43 +00:00
pResult - > m_Status = CScoreSaveResult : : SAVE_FAILED ;
str_copy ( pResult - > m_aMessage , " This save-code already exists " , sizeof ( pResult - > m_aMessage ) ) ;
2020-07-04 17:53:27 +00:00
}
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
2020-09-26 19:41:58 +00:00
void CScore : : LoadTeam ( const char * Code , int ClientID )
2020-07-04 17:53:27 +00:00
{
if ( RateLimitPlayer ( ClientID ) )
return ;
2020-10-26 10:28:18 +00:00
auto * pController = ( ( CGameControllerDDRace * ) ( GameServer ( ) - > m_pController ) ) ;
2020-07-04 17:53:27 +00:00
int Team = pController - > m_Teams . m_Core . Team ( ClientID ) ;
if ( pController - > m_Teams . GetSaving ( Team ) )
return ;
if ( Team < TEAM_FLOCK | | Team > = MAX_CLIENTS | | ( g_Config . m_SvTeam ! = 3 & & Team = = TEAM_FLOCK ) )
{
GameServer ( ) - > SendChatTarget ( ClientID , " You have to be in a team (from 1-63) " ) ;
return ;
}
if ( pController - > m_Teams . GetTeamState ( Team ) ! = CGameTeams : : TEAMSTATE_OPEN )
{
GameServer ( ) - > SendChatTarget ( ClientID , " Team can't be loaded while racing " ) ;
return ;
}
auto SaveResult = std : : make_shared < CScoreSaveResult > ( ClientID , pController ) ;
SaveResult - > m_Status = CScoreSaveResult : : LOAD_FAILED ;
pController - > m_Teams . SetSaving ( Team , SaveResult ) ;
auto Tmp = std : : unique_ptr < CSqlTeamLoad > ( new CSqlTeamLoad ( SaveResult ) ) ;
str_copy ( Tmp - > m_Code , Code , sizeof ( Tmp - > m_Code ) ) ;
str_copy ( Tmp - > m_Map , g_Config . m_SvMap , sizeof ( Tmp - > m_Map ) ) ;
Tmp - > m_ClientID = ClientID ;
str_copy ( Tmp - > m_RequestingPlayer , Server ( ) - > ClientName ( ClientID ) , sizeof ( Tmp - > m_RequestingPlayer ) ) ;
Tmp - > m_NumPlayer = 0 ;
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
if ( pController - > m_Teams . m_Core . Team ( i ) = = Team )
{
// put all names at the beginning of the array
str_copy ( Tmp - > m_aClientNames [ Tmp - > m_NumPlayer ] , Server ( ) - > ClientName ( i ) , sizeof ( Tmp - > m_aClientNames [ Tmp - > m_NumPlayer ] ) ) ;
Tmp - > m_aClientID [ Tmp - > m_NumPlayer ] = i ;
Tmp - > m_NumPlayer + + ;
}
}
2020-07-19 10:22:40 +00:00
m_pPool - > ExecuteWrite ( LoadTeamThread , std : : move ( Tmp ) , " load team " ) ;
2020-07-04 17:53:27 +00:00
}
2021-01-31 16:58:30 +00:00
bool CScore : : LoadTeamThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , bool Failure , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlTeamLoad * pData = dynamic_cast < const CSqlTeamLoad * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScoreSaveResult * pResult = dynamic_cast < CScoreSaveResult * > ( pGameData - > m_pResult . get ( ) ) ;
pResult - > m_Status = CScoreSaveResult : : LOAD_FAILED ;
2020-07-04 17:53:27 +00:00
2021-01-20 13:25:57 +00:00
char aCurrentTimestamp [ 512 ] ;
pSqlServer - > ToUnixTimestamp ( " CURRENT_TIMESTAMP " , aCurrentTimestamp , sizeof ( aCurrentTimestamp ) ) ;
char aTimestamp [ 512 ] ;
pSqlServer - > ToUnixTimestamp ( " Timestamp " , aTimestamp , sizeof ( aTimestamp ) ) ;
2020-07-04 17:53:27 +00:00
2021-01-20 13:25:57 +00:00
char aBuf [ 512 ] ;
str_format ( aBuf , sizeof ( aBuf ) ,
" SELECT Savegame, %s-%s AS Ago, SaveID "
" FROM %s_saves "
2021-01-28 22:45:16 +00:00
" where Code = ? AND Map = ? AND DDNet7 = false; " ,
2021-01-20 13:25:57 +00:00
aCurrentTimestamp , aTimestamp ,
pSqlServer - > GetPrefix ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2021-01-20 13:25:57 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Code ) ;
pSqlServer - > BindString ( 2 , pData - > m_Map ) ;
2020-07-04 17:53:27 +00:00
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
2021-01-20 13:25:57 +00:00
{
return true ;
}
2021-01-31 16:58:30 +00:00
if ( End )
{
str_copy ( pResult - > m_aMessage , " No such savegame for this map " , sizeof ( pResult - > m_aMessage ) ) ;
return false ;
}
2020-07-18 19:35:59 +00:00
2021-01-23 00:12:19 +00:00
pResult - > m_SaveID = UUID_NO_SAVE_ID ;
2021-01-20 13:25:57 +00:00
if ( ! pSqlServer - > IsNull ( 3 ) )
{
2021-01-22 20:04:18 +00:00
char aSaveID [ UUID_MAXSTRSIZE ] ;
2021-01-20 13:25:57 +00:00
pSqlServer - > GetString ( 3 , aSaveID , sizeof ( aSaveID ) ) ;
2021-01-23 00:12:19 +00:00
if ( ParseUuid ( & pResult - > m_SaveID , aSaveID ) | | pResult - > m_SaveID = = UUID_NO_SAVE_ID )
2020-07-04 17:53:27 +00:00
{
2021-01-20 13:25:57 +00:00
str_copy ( pResult - > m_aMessage , " Unable to load savegame: SaveID corrupted " , sizeof ( pResult - > m_aMessage ) ) ;
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
2021-01-20 13:25:57 +00:00
}
2020-07-04 17:53:27 +00:00
2021-01-20 13:25:57 +00:00
char aSaveString [ 65536 ] ;
pSqlServer - > GetString ( 1 , aSaveString , sizeof ( aSaveString ) ) ;
int Num = pResult - > m_SavedTeam . FromString ( aSaveString ) ;
2020-07-04 17:53:27 +00:00
2021-01-20 13:25:57 +00:00
if ( Num ! = 0 )
{
str_copy ( pResult - > m_aMessage , " Unable to load savegame: data corrupted " , sizeof ( pResult - > m_aMessage ) ) ;
2021-01-31 16:58:30 +00:00
return false ;
2021-01-20 13:25:57 +00:00
}
2020-07-04 17:53:27 +00:00
2021-01-28 22:45:16 +00:00
bool Found = false ;
for ( int i = 0 ; i < pResult - > m_SavedTeam . GetMembersCount ( ) ; i + + )
{
2021-03-20 14:45:11 +00:00
if ( str_comp ( pResult - > m_SavedTeam . m_pSavedTees [ i ] . GetName ( ) , pData - > m_RequestingPlayer ) = = 0 )
2021-01-28 22:45:16 +00:00
{
Found = true ;
break ;
}
}
if ( ! Found )
{
str_copy ( pResult - > m_aMessage , " You don't belong to this team " , sizeof ( pResult - > m_aMessage ) ) ;
2021-02-24 19:43:07 +00:00
return false ;
2021-01-28 22:45:16 +00:00
}
int Since = pSqlServer - > GetInt ( 2 ) ;
2021-03-20 16:08:54 +00:00
if ( Since < g_Config . m_SvSaveSwapGamesDelay )
2021-01-28 22:45:16 +00:00
{
str_format ( pResult - > m_aMessage , sizeof ( pResult - > m_aMessage ) ,
" You have to wait %d seconds until you can load this savegame " ,
2021-03-20 16:08:54 +00:00
g_Config . m_SvSaveSwapGamesDelay - Since ) ;
2021-02-24 19:43:07 +00:00
return false ;
2021-01-28 22:45:16 +00:00
}
2021-01-20 13:25:57 +00:00
bool CanLoad = pResult - > m_SavedTeam . MatchPlayers (
pData - > m_aClientNames , pData - > m_aClientID , pData - > m_NumPlayer ,
pResult - > m_aMessage , sizeof ( pResult - > m_aMessage ) ) ;
2020-07-04 17:53:27 +00:00
2021-01-20 13:25:57 +00:00
if ( ! CanLoad )
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
2021-01-20 13:25:57 +00:00
str_format ( aBuf , sizeof ( aBuf ) ,
2021-01-23 00:12:19 +00:00
" DELETE FROM %s_saves "
" WHERE Code = ? AND Map = ? AND SaveID %s; " ,
pSqlServer - > GetPrefix ( ) ,
pResult - > m_SaveID ! = UUID_NO_SAVE_ID ? " = ? " : " IS NULL " ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2021-01-20 13:25:57 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Code ) ;
pSqlServer - > BindString ( 2 , pData - > m_Map ) ;
2021-01-23 00:12:19 +00:00
char aUuid [ UUID_MAXSTRSIZE ] ;
if ( pResult - > m_SaveID ! = UUID_NO_SAVE_ID )
{
FormatUuid ( pResult - > m_SaveID , aUuid , sizeof ( aUuid ) ) ;
pSqlServer - > BindString ( 3 , aUuid ) ;
}
2021-01-20 13:25:57 +00:00
pSqlServer - > Print ( ) ;
2021-01-31 16:58:30 +00:00
int NumDeleted ;
if ( pSqlServer - > ExecuteUpdate ( & NumDeleted , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
2021-01-20 13:25:57 +00:00
if ( NumDeleted ! = 1 )
{
str_copy ( pResult - > m_aMessage , " Unable to load savegame: loaded on a different server " , sizeof ( pResult - > m_aMessage ) ) ;
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
2021-01-20 13:25:57 +00:00
pResult - > m_Status = CScoreSaveResult : : LOAD_SUCCESS ;
str_copy ( pResult - > m_aMessage , " Loading successfully done " , sizeof ( pResult - > m_aMessage ) ) ;
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}
void CScore : : GetSaves ( int ClientID )
{
if ( RateLimitPlayer ( ClientID ) )
return ;
ExecPlayerThread ( GetSavesThread , " get saves " , ClientID , " " , 0 ) ;
}
2021-01-31 16:58:30 +00:00
bool CScore : : GetSavesThread ( IDbConnection * pSqlServer , const ISqlData * pGameData , char * pError , int ErrorSize )
2020-07-04 17:53:27 +00:00
{
const CSqlPlayerRequest * pData = dynamic_cast < const CSqlPlayerRequest * > ( pGameData ) ;
2020-12-08 11:03:43 +00:00
CScorePlayerResult * pResult = dynamic_cast < CScorePlayerResult * > ( pGameData - > m_pResult . get ( ) ) ;
auto * paMessages = pResult - > m_Data . m_aaMessages ;
2020-07-04 17:53:27 +00:00
char aSaveLike [ 128 ] = " " ;
str_append ( aSaveLike , " % \n " , sizeof ( aSaveLike ) ) ;
sqlstr : : EscapeLike ( aSaveLike + str_length ( aSaveLike ) ,
2020-09-26 19:41:58 +00:00
pData - > m_RequestingPlayer ,
sizeof ( aSaveLike ) - str_length ( aSaveLike ) ) ;
2020-07-04 17:53:27 +00:00
str_append ( aSaveLike , " \t % " , sizeof ( aSaveLike ) ) ;
2020-07-18 18:49:34 +00:00
char aCurrentTimestamp [ 512 ] ;
pSqlServer - > ToUnixTimestamp ( " CURRENT_TIMESTAMP " , aCurrentTimestamp , sizeof ( aCurrentTimestamp ) ) ;
char aMaxTimestamp [ 512 ] ;
pSqlServer - > ToUnixTimestamp ( " MAX(Timestamp) " , aMaxTimestamp , sizeof ( aMaxTimestamp ) ) ;
2020-07-04 17:53:27 +00:00
char aBuf [ 512 ] ;
str_format ( aBuf , sizeof ( aBuf ) ,
2020-09-26 19:41:58 +00:00
" SELECT COUNT(*) AS NumSaves, %s-%s AS Ago "
" FROM %s_saves "
" WHERE Map = ? AND Savegame LIKE ?; " ,
aCurrentTimestamp , aMaxTimestamp ,
pSqlServer - > GetPrefix ( ) ) ;
2021-01-31 16:58:30 +00:00
if ( pSqlServer - > PrepareStatement ( aBuf , pError , ErrorSize ) )
{
return true ;
}
2020-07-04 17:53:27 +00:00
pSqlServer - > BindString ( 1 , pData - > m_Map ) ;
pSqlServer - > BindString ( 2 , aSaveLike ) ;
2021-01-31 16:58:30 +00:00
bool End ;
if ( pSqlServer - > Step ( & End , pError , ErrorSize ) )
{
return true ;
}
if ( ! End )
2020-07-04 17:53:27 +00:00
{
int NumSaves = pSqlServer - > GetInt ( 1 ) ;
int Ago = pSqlServer - > GetInt ( 2 ) ;
char aAgoString [ 40 ] = " \0 " ;
char aLastSavedString [ 60 ] = " \0 " ;
if ( Ago )
{
2020-10-12 16:31:55 +00:00
sqlstr : : AgoTimeToString ( Ago , aAgoString , sizeof ( aAgoString ) ) ;
2020-07-04 17:53:27 +00:00
str_format ( aLastSavedString , sizeof ( aLastSavedString ) , " , last saved %s ago " , aAgoString ) ;
}
str_format ( paMessages [ 0 ] , sizeof ( paMessages [ 0 ] ) ,
2020-09-26 19:41:58 +00:00
" %s has %d save%s on %s%s " ,
pData - > m_RequestingPlayer ,
NumSaves , NumSaves = = 1 ? " " : " s " ,
pData - > m_Map , aLastSavedString ) ;
2020-07-04 17:53:27 +00:00
}
2021-01-31 16:58:30 +00:00
return false ;
2020-07-04 17:53:27 +00:00
}