3669: Local ranking info viewable in-game r=def- a=houseme-kyle

The feature is based on the issue below:
https://github.com/ddnet/ddnet/issues/3655

The main concern was the impact it would have if we had to double our ranking/point commands to support it. I took the feedback into account and combined the info into one command. The PR is in draft as I don't want to implement it for the remaining commands before getting thoughts on the approach taken.

**Feature**
Display local stats alongside global while using the same commands

**Implementation**
- /rank, /top5 etc to display their local position alongside global. 
- Top 5 changed to top 3 due to space limitations
- Message limit increased by 1
- Don't show "requested by" when the request was made by the same individual

**Reason for feature**
- Foster growth in the smaller communities as they compete amongst each other in their own leaderboard
- The names become more relevant to all communities as they can better relate to the names displayed 

**TODO**
~- Tested with production sqlite data but not MySQL storage~
~- Expand concept to other relevant ranking commands~

(Example of NovaShock who is the top player in South Africa)
![image](https://user-images.githubusercontent.com/25198124/109824890-f03a4080-7c41-11eb-84dc-ad319cb98f1e.png)
![image](https://user-images.githubusercontent.com/25198124/109824910-f6302180-7c41-11eb-9013-f3254c08f068.png)

## Checklist

- [x] Tested the change ingame
- [x] Provided screenshots if it is a visual change
- [x] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: houseme-kyle <kyle@houseme.co.za>
Co-authored-by: Kyle Bradley <kyle@house.me>
Co-authored-by: = <=>
This commit is contained in:
bors[bot] 2021-03-08 22:51:05 +00:00 committed by GitHub
commit e2cb24b7d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 109 additions and 25 deletions

View file

@ -35,7 +35,8 @@ CHAT_COMMAND("teamrank", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTe
CHAT_COMMAND("rank", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConRank, this, "Shows the rank of player with name r (your rank by default)")
CHAT_COMMAND("top5team", "?s[player name] ?i[rank to start with]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTeamTop5, this, "Shows five team ranks of the ladder or of a player beginning with rank i (1 by default, -1 for worst)")
CHAT_COMMAND("teamtop5", "?s[player name] ?i[rank to start with]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTeamTop5, this, "Shows five team ranks of the ladder or of a player beginning with rank i (1 by default, -1 for worst)")
CHAT_COMMAND("top5", "?i[rank to start with]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTop5, this, "Shows five ranks of the ladder beginning with rank i (1 by default, -1 for worst)")
CHAT_COMMAND("top", "?i[rank to start with]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTop, this, "Shows five ranks of the global and regional ladder beginning with rank i (1 by default, -1 for worst)")
CHAT_COMMAND("top5", "?i[rank to start with]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTop, this, "Shows five ranks of the global and regional ladder beginning with rank i (1 by default, -1 for worst)")
CHAT_COMMAND("times", "?s[player name] ?i[number of times to skip]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTimes, this, "/times ?s?i shows last 5 times of the server or of a player beginning with name s starting with time i (i = 1 by default, -1 for first)")
CHAT_COMMAND("points", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConPoints, this, "Shows the global points of a player beginning with name r (your rank by default)")
CHAT_COMMAND("top5points", "?i[number]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTopPoints, this, "Shows five points of the global point ladder beginning with rank i (1 by default)")

View file

@ -423,7 +423,7 @@ void CGameContext::ConTeamTop5(IConsole::IResult *pResult, void *pUserData)
}
}
void CGameContext::ConTop5(IConsole::IResult *pResult, void *pUserData)
void CGameContext::ConTop(IConsole::IResult *pResult, void *pUserData)
{
CGameContext *pSelf = (CGameContext *)pUserData;
if(!CheckClientID(pResult->m_ClientID))
@ -431,15 +431,15 @@ void CGameContext::ConTop5(IConsole::IResult *pResult, void *pUserData)
if(g_Config.m_SvHideScore)
{
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "top5",
"Showing the top 5 is not allowed on this server.");
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "top",
"Showing the top is not allowed on this server.");
return;
}
if(pResult->NumArguments() > 0)
pSelf->Score()->ShowTop5(pResult->m_ClientID, pResult->GetInteger(0));
pSelf->Score()->ShowTop(pResult->m_ClientID, pResult->GetInteger(0));
else
pSelf->Score()->ShowTop5(pResult->m_ClientID);
pSelf->Score()->ShowTop(pResult->m_ClientID);
}
void CGameContext::ConTimes(IConsole::IResult *pResult, void *pUserData)

View file

@ -346,7 +346,7 @@ private:
static void ConToggleSpecVoted(IConsole::IResult *pResult, void *pUserData);
static void ConForcePause(IConsole::IResult *pResult, void *pUserData);
static void ConTeamTop5(IConsole::IResult *pResult, void *pUserData);
static void ConTop5(IConsole::IResult *pResult, void *pUserData);
static void ConTop(IConsole::IResult *pResult, void *pUserData);
static void ConTimes(IConsole::IResult *pResult, void *pUserData);
static void ConPoints(IConsole::IResult *pResult, void *pUserData);
static void ConTopPoints(IConsole::IResult *pResult, void *pUserData);

View file

@ -933,13 +933,18 @@ void CPlayer::ProcessScoreResult(CScorePlayerResult &Result)
}
break;
case CScorePlayerResult::ALL:
{
int MessageClientId = m_ClientID;
for(auto &aMessage : Result.m_Data.m_aaMessages)
{
if(aMessage[0] == 0)
break;
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aMessage, m_ClientID);
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aMessage, MessageClientId);
MessageClientId = -1; // Prevent multi-messages being flagged as spam.
}
break;
}
case CScorePlayerResult::BROADCAST:
if(Result.m_Data.m_Broadcast[0] != 0)
GameServer()->SendBroadcast(Result.m_Data.m_Broadcast, -1);

View file

@ -126,6 +126,7 @@ void CScore::ExecPlayerThread(
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));
str_copy(Tmp->m_Server, g_Config.m_SvSqlServerName, sizeof(Tmp->m_Server));
str_copy(Tmp->m_RequestingPlayer, Server()->ClientName(ClientID), sizeof(Tmp->m_RequestingPlayer));
Tmp->m_Offset = Offset;
@ -766,32 +767,63 @@ bool CScore::ShowRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData
const CSqlPlayerRequest *pData = dynamic_cast<const CSqlPlayerRequest *>(pGameData);
CScorePlayerResult *pResult = dynamic_cast<CScorePlayerResult *>(pGameData->m_pResult.get());
char aServerLike[16];
str_format(aServerLike, sizeof(aServerLike), "%%%s%%", pData->m_Server);
// check sort method
char aBuf[600];
str_format(aBuf, sizeof(aBuf),
"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());
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
{
return true;
}
pSqlServer->BindString(1, pData->m_Map);
pSqlServer->BindString(2, pData->m_Name);
pSqlServer->BindString(2, aServerLike);
pSqlServer->BindString(3, pData->m_Name);
bool End;
if(pSqlServer->Step(&End, pError, ErrorSize))
{
return true;
}
char aRegionalRank[16];
if(End)
{
str_copy(aRegionalRank, "unranked", sizeof(aRegionalRank));
}
else
{
str_format(aRegionalRank, sizeof(aRegionalRank), "rank %d", pSqlServer->GetInt(1));
}
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;
}
if(!End)
{
int Rank = pSqlServer->GetInt(1);
@ -807,9 +839,23 @@ bool CScore::ShowRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData
else
{
pResult->m_MessageKind = CScorePlayerResult::ALL;
str_format(pResult->m_Data.m_aaMessages[0], sizeof(pResult->m_Data.m_aaMessages[0]),
"%d. %s Time: %s, better than %d%%, requested by %s",
Rank, pData->m_Name, aBuf, BetterThanPercent, pData->m_RequestingPlayer);
if(str_comp_nocase(pData->m_RequestingPlayer, pData->m_Name) == 0)
{
str_format(pResult->m_Data.m_aaMessages[0], sizeof(pResult->m_Data.m_aaMessages[0]),
"%s Time: %s, better than %d%%",
pData->m_Name, aBuf, BetterThanPercent);
}
else
{
str_format(pResult->m_Data.m_aaMessages[0], sizeof(pResult->m_Data.m_aaMessages[0]),
"%s Time: %s, better than %d%%, requested by %s",
pData->m_Name, aBuf, BetterThanPercent, pData->m_RequestingPlayer);
}
str_format(pResult->m_Data.m_aaMessages[1], sizeof(pResult->m_Data.m_aaMessages[1]),
"Global rank %d || %s %s",
Rank, pData->m_Server, aRegionalRank);
}
}
else
@ -910,20 +956,21 @@ bool CScore::ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGame
return false;
}
void CScore::ShowTop5(int ClientID, int Offset)
void CScore::ShowTop(int ClientID, int Offset)
{
if(RateLimitPlayer(ClientID))
return;
ExecPlayerThread(ShowTop5Thread, "show top5", ClientID, "", Offset);
ExecPlayerThread(ShowTopThread, "show top5", ClientID, "", Offset);
}
bool CScore::ShowTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize)
bool CScore::ShowTopThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize)
{
const CSqlPlayerRequest *pData = dynamic_cast<const CSqlPlayerRequest *>(pGameData);
CScorePlayerResult *pResult = dynamic_cast<CScorePlayerResult *>(pGameData->m_pResult.get());
int LimitStart = maximum(abs(pData->m_Offset) - 1, 0);
const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC";
const char *pAny = "%";
// check sort method
char aBuf[512];
@ -933,23 +980,27 @@ bool CScore::ShowTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData
" SELECT RANK() OVER w AS Rank, Name, MIN(Time) AS Time "
" FROM %s_race "
" WHERE Map = ? "
" AND Server LIKE ? "
" GROUP BY Name "
" WINDOW w AS (ORDER BY Time)"
") as a "
"ORDER BY Rank %s "
"LIMIT %d, 5;",
"LIMIT %d, 3;",
pSqlServer->GetPrefix(),
pOrder,
LimitStart);
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
{
return true;
}
pSqlServer->BindString(1, pData->m_Map);
pSqlServer->BindString(2, pAny);
// show top5
str_copy(pResult->m_Data.m_aaMessages[0], "----------- Top 5 -----------", sizeof(pResult->m_Data.m_aaMessages[0]));
// show top
str_copy(pResult->m_Data.m_aaMessages[0], "-----------< Global Top 3 >-----------", sizeof(pResult->m_Data.m_aaMessages[0]));
char aTime[32];
int Line = 1;
bool End = false;
while(!pSqlServer->Step(&End, pError, ErrorSize) && !End)
@ -957,17 +1008,43 @@ bool CScore::ShowTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData
char aName[MAX_NAME_LENGTH];
pSqlServer->GetString(1, aName, sizeof(aName));
float Time = pSqlServer->GetFloat(2);
str_time_float(Time, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf));
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, aBuf);
"%d. %s Time: %s", Rank, aName, aTime);
Line++;
}
char aServerLike[16];
str_format(aServerLike, sizeof(aServerLike), "%%%s%%", pData->m_Server);
if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize))
{
return true;
}
pSqlServer->BindString(1, pData->m_Map);
pSqlServer->BindString(2, aServerLike);
str_format(pResult->m_Data.m_aaMessages[Line], sizeof(pResult->m_Data.m_aaMessages[Line]),
"-----------< %s Top 3 >-----------", pData->m_Server);
Line++;
// 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++;
}
if(!End)
{
return true;
}
str_copy(pResult->m_Data.m_aaMessages[Line], "-------------------------------", sizeof(pResult->m_Data.m_aaMessages[Line]));
return false;
}

View file

@ -31,7 +31,7 @@ struct CScorePlayerResult : ISqlResult
enum
{
MAX_MESSAGES = 7,
MAX_MESSAGES = 8,
};
enum Variant
@ -167,6 +167,7 @@ struct CSqlPlayerRequest : ISqlData
char m_RequestingPlayer[MAX_NAME_LENGTH];
// relevant for /top5 kind of requests
int m_Offset;
char m_Server[5];
};
struct CSqlRandomMapRequest : ISqlData
@ -291,7 +292,7 @@ class CScore
static bool MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
static bool ShowRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
static bool ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
static bool ShowTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
static bool ShowTopThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
static bool ShowTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
static bool ShowPlayerTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
static bool ShowTimesThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
@ -340,7 +341,7 @@ public:
void SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const char *pTimestamp);
void ShowTop5(int ClientID, int Offset = 1);
void ShowTop(int ClientID, int Offset = 1);
void ShowRank(int ClientID, const char *pName);
void ShowTeamTop5(int ClientID, int Offset = 1);