ddnet/src/game/client/components/statboard.cpp

532 lines
17 KiB
C++
Raw Normal View History

2015-05-19 22:51:02 +00:00
#include <engine/graphics.h>
#include <engine/serverbrowser.h>
#include <engine/shared/config.h>
#include <engine/storage.h>
#include <engine/textrender.h>
2015-05-19 22:51:02 +00:00
#include <game/client/animstate.h>
2015-05-20 16:22:04 +00:00
#include <game/client/components/motd.h>
2015-05-21 09:41:59 +00:00
#include <game/client/components/statboard.h>
#include <game/client/gameclient.h>
#include <game/generated/client_data.h>
2022-05-29 16:33:38 +00:00
#include <game/localization.h>
2015-05-19 22:51:02 +00:00
2015-05-21 09:41:59 +00:00
CStatboard::CStatboard()
2015-05-19 22:51:02 +00:00
{
2015-05-20 16:22:04 +00:00
m_Active = false;
2015-05-19 22:51:02 +00:00
m_ScreenshotTaken = false;
m_ScreenshotTime = -1;
}
2015-05-21 09:41:59 +00:00
void CStatboard::OnReset()
2015-05-19 22:51:02 +00:00
{
2020-10-26 14:14:07 +00:00
for(auto &Stat : m_pClient->m_aStats)
Stat.Reset();
2015-05-20 16:22:04 +00:00
m_Active = false;
2015-05-19 22:51:02 +00:00
m_ScreenshotTaken = false;
m_ScreenshotTime = -1;
}
void CStatboard::OnRelease()
{
m_Active = false;
}
2015-05-21 09:41:59 +00:00
void CStatboard::ConKeyStats(IConsole::IResult *pResult, void *pUserData)
2015-05-19 22:51:02 +00:00
{
2015-05-21 09:41:59 +00:00
((CStatboard *)pUserData)->m_Active = pResult->GetInteger(0) != 0;
2015-05-19 22:51:02 +00:00
}
2015-05-21 09:41:59 +00:00
void CStatboard::OnConsoleInit()
2015-05-19 22:51:02 +00:00
{
Console()->Register("+statboard", "", CFGFLAG_CLIENT, ConKeyStats, this, "Show stats");
2015-05-19 22:51:02 +00:00
}
2015-05-21 09:41:59 +00:00
bool CStatboard::IsActive()
2015-05-19 22:51:02 +00:00
{
2015-05-20 16:22:04 +00:00
return m_Active;
2015-05-19 22:51:02 +00:00
}
2015-05-21 09:41:59 +00:00
void CStatboard::OnMessage(int MsgType, void *pRawMsg)
2015-05-19 22:51:02 +00:00
{
if(MsgType == NETMSGTYPE_SV_KILLMSG)
{
CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg;
CGameClient::CClientStats *pStats = m_pClient->m_aStats;
pStats[pMsg->m_Victim].m_Deaths++;
pStats[pMsg->m_Victim].m_CurrentSpree = 0;
if(pMsg->m_Weapon >= 0)
pStats[pMsg->m_Victim].m_aDeathsFrom[pMsg->m_Weapon]++;
if(pMsg->m_Victim != pMsg->m_Killer)
{
pStats[pMsg->m_Killer].m_Frags++;
pStats[pMsg->m_Killer].m_CurrentSpree++;
if(pStats[pMsg->m_Killer].m_CurrentSpree > pStats[pMsg->m_Killer].m_BestSpree)
pStats[pMsg->m_Killer].m_BestSpree = pStats[pMsg->m_Killer].m_CurrentSpree;
if(pMsg->m_Weapon >= 0)
pStats[pMsg->m_Killer].m_aFragsWith[pMsg->m_Weapon]++;
}
else
pStats[pMsg->m_Victim].m_Suicides++;
}
else if(MsgType == NETMSGTYPE_SV_CHAT)
{
CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg;
if(pMsg->m_ClientID < 0)
{
2019-04-16 00:24:24 +00:00
const char *p, *t;
2015-05-19 22:51:02 +00:00
const char *pLookFor = "flag was captured by '";
2019-04-15 23:47:12 +00:00
if((p = str_find(pMsg->m_pMessage, pLookFor)))
2015-05-19 22:51:02 +00:00
{
2019-04-15 23:47:12 +00:00
char aName[MAX_NAME_LENGTH];
2015-05-19 22:51:02 +00:00
p += str_length(pLookFor);
2019-04-16 00:24:24 +00:00
t = str_rchr(p, '\'');
2019-04-15 23:47:12 +00:00
2019-04-16 00:24:24 +00:00
if(t <= p)
2019-04-15 23:47:12 +00:00
return;
str_utf8_truncate(aName, sizeof(aName), p, t - p);
2015-05-19 22:51:02 +00:00
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(!m_pClient->m_aStats[i].IsActive())
2015-05-19 22:51:02 +00:00
continue;
if(str_comp(m_pClient->m_aClients[i].m_aName, aName) == 0)
{
m_pClient->m_aStats[i].m_FlagCaptures++;
break;
}
}
}
}
}
}
2015-05-21 09:41:59 +00:00
void CStatboard::OnRender()
2015-05-19 22:51:02 +00:00
{
if((g_Config.m_ClAutoStatboardScreenshot || g_Config.m_ClAutoCSV) && Client()->State() != IClient::STATE_DEMOPLAYBACK)
2015-05-19 22:51:02 +00:00
{
if(m_ScreenshotTime < 0 && m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)
m_ScreenshotTime = time_get() + time_freq() * 3;
2015-05-19 22:51:02 +00:00
if(m_ScreenshotTime > -1 && m_ScreenshotTime < time_get())
2015-05-20 16:22:04 +00:00
m_Active = true;
if(!m_ScreenshotTaken && m_ScreenshotTime > -1 && m_ScreenshotTime + time_freq() / 5 < time_get())
2015-05-19 22:51:02 +00:00
{
if(g_Config.m_ClAutoStatboardScreenshot)
AutoStatScreenshot();
if(g_Config.m_ClAutoCSV)
AutoStatCSV();
2015-05-19 22:51:02 +00:00
m_ScreenshotTaken = true;
}
}
if(IsActive())
2015-05-20 16:22:04 +00:00
RenderGlobalStats();
2015-05-19 22:51:02 +00:00
}
2015-05-21 09:41:59 +00:00
void CStatboard::RenderGlobalStats()
2015-05-19 22:51:02 +00:00
{
const float StatboardWidth = 400 * 3.0f * Graphics()->ScreenAspect();
const float StatboardHeight = 400 * 3.0f;
2015-07-10 20:12:20 +00:00
float StatboardContentWidth = 260.0f;
2015-06-14 11:41:03 +00:00
float StatboardContentHeight = 750.0f;
2015-05-19 22:51:02 +00:00
const CNetObj_PlayerInfo *apPlayers[MAX_CLIENTS] = {0};
int NumPlayers = 0;
// sort red or dm players by score
for(const auto *pInfo : m_pClient->m_Snap.m_paInfoByScore)
2015-05-19 22:51:02 +00:00
{
if(!pInfo || !m_pClient->m_aStats[pInfo->m_ClientID].IsActive() || m_pClient->m_aClients[pInfo->m_ClientID].m_Team != TEAM_RED)
2015-05-19 22:51:02 +00:00
continue;
apPlayers[NumPlayers] = pInfo;
NumPlayers++;
}
// sort blue players by score after
if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS)
2015-05-19 22:51:02 +00:00
{
for(const auto *pInfo : m_pClient->m_Snap.m_paInfoByScore)
2015-05-19 22:51:02 +00:00
{
if(!pInfo || !m_pClient->m_aStats[pInfo->m_ClientID].IsActive() || m_pClient->m_aClients[pInfo->m_ClientID].m_Team != TEAM_BLUE)
2015-05-19 22:51:02 +00:00
continue;
apPlayers[NumPlayers] = pInfo;
NumPlayers++;
}
}
// Dirty hack. Do not show scoreboard if there are more than 32 players
// remove as soon as support of more than 32 players is required
if(NumPlayers > 32)
return;
//clear motd if it is active
2021-07-12 09:43:56 +00:00
if(m_pClient->m_Motd.IsActive())
m_pClient->m_Motd.Clear();
2017-03-21 10:24:44 +00:00
bool GameWithFlags = m_pClient->m_Snap.m_pGameInfoObj &&
m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS;
StatboardContentWidth += 7 * 85 + 95; // Suicides 95; other labels 85
2015-05-19 22:51:02 +00:00
2017-03-21 10:24:44 +00:00
if(GameWithFlags)
2015-07-10 20:12:20 +00:00
StatboardContentWidth += 150; // Grabs & Flags
2015-05-19 22:51:02 +00:00
2015-05-21 09:41:59 +00:00
bool aDisplayWeapon[NUM_WEAPONS] = {false};
2015-06-14 11:41:03 +00:00
for(int i = 0; i < NumPlayers; i++)
2015-05-21 09:41:59 +00:00
{
const CGameClient::CClientStats *pStats = &m_pClient->m_aStats[apPlayers[i]->m_ClientID];
for(int j = 0; j < NUM_WEAPONS; j++)
aDisplayWeapon[j] = aDisplayWeapon[j] || pStats->m_aFragsWith[j] || pStats->m_aDeathsFrom[j];
2015-05-21 09:41:59 +00:00
}
2020-10-26 14:14:07 +00:00
for(bool DisplayWeapon : aDisplayWeapon)
if(DisplayWeapon)
2015-06-14 11:41:03 +00:00
StatboardContentWidth += 80;
2015-05-19 22:51:02 +00:00
float x = StatboardWidth / 2 - StatboardContentWidth / 2;
2015-05-19 22:51:02 +00:00
float y = 200.0f;
2015-06-14 11:41:03 +00:00
Graphics()->MapScreen(0, 0, StatboardWidth, StatboardHeight);
2015-05-19 22:51:02 +00:00
Graphics()->BlendNormal();
Graphics()->TextureClear();
2015-05-19 22:51:02 +00:00
Graphics()->QuadsBegin();
Graphics()->SetColor(0, 0, 0, 0.5f);
RenderTools()->DrawRoundRect(x - 10.f, y - 10.f, StatboardContentWidth, StatboardContentHeight, 17.0f);
2015-05-19 22:51:02 +00:00
Graphics()->QuadsEnd();
float tw;
int px = 325;
TextRender()->Text(0, x + 10, y - 5, 22.0f, Localize("Name"), -1.0f);
2015-06-14 11:41:03 +00:00
const char *apHeaders[] = {
Localize("Frags"), Localize("Deaths"), Localize("Suicides"),
Localize("Ratio"), Localize("Net"), Localize("FPM"),
Localize("Spree"), Localize("Best"), Localize("Grabs")};
2015-06-14 11:41:03 +00:00
for(int i = 0; i < 9; i++)
2015-05-19 22:51:02 +00:00
{
2015-05-21 17:21:13 +00:00
if(i == 2)
px += 10.0f; // Suicides
2017-03-21 10:24:44 +00:00
if(i == 8 && !GameWithFlags) // Don't draw "Grabs" in game with no flag
continue;
tw = TextRender()->TextWidth(0, 22.0f, apHeaders[i], -1, -1.0f);
TextRender()->Text(0, x + px - tw, y - 5, 22.0f, apHeaders[i], -1.0f);
px += 85;
}
2015-05-19 22:51:02 +00:00
2015-06-14 11:41:03 +00:00
px -= 40;
for(int i = 0; i < NUM_WEAPONS; i++)
{
2015-05-21 09:41:59 +00:00
if(!aDisplayWeapon[i])
continue;
float ScaleX, ScaleY;
RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[i].m_pSpriteBody, ScaleX, ScaleY);
Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteWeapons[i]);
Graphics()->QuadsBegin();
if(i == 0)
RenderTools()->DrawSprite(x + px, y + 10, g_pData->m_Weapons.m_aId[i].m_VisualSize * 0.8f * ScaleX, g_pData->m_Weapons.m_aId[i].m_VisualSize * 0.8f * ScaleY);
else
RenderTools()->DrawSprite(x + px, y + 10, g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleX, g_pData->m_Weapons.m_aId[i].m_VisualSize * ScaleY);
px += 80;
Graphics()->QuadsEnd();
2015-05-19 22:51:02 +00:00
}
2017-03-21 10:24:44 +00:00
if(GameWithFlags)
2015-05-19 22:51:02 +00:00
{
Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagRed);
float ScaleX, ScaleY;
RenderTools()->GetSpriteScale(SPRITE_FLAG_RED, ScaleX, ScaleY);
2015-05-19 22:51:02 +00:00
Graphics()->QuadsBegin();
Graphics()->QuadsSetRotation(0.78f);
RenderTools()->DrawSprite(x + px, y + 15, 48 * ScaleX, 48 * ScaleY);
2015-05-19 22:51:02 +00:00
Graphics()->QuadsEnd();
}
y += 29.0f;
float FontSize = 24.0f;
2015-05-19 22:51:02 +00:00
float LineHeight = 50.0f;
float TeeSizemod = 0.8f;
float ContentLineOffset = LineHeight * 0.05f;
2015-05-19 22:51:02 +00:00
if(NumPlayers > 16)
{
FontSize = 20.0f;
LineHeight = 22.0f;
TeeSizemod = 0.34f;
ContentLineOffset = 0;
}
else if(NumPlayers > 14)
2015-05-19 22:51:02 +00:00
{
FontSize = 24.0f;
2015-05-19 22:51:02 +00:00
LineHeight = 40.0f;
TeeSizemod = 0.7f;
2015-05-19 22:51:02 +00:00
}
for(int j = 0; j < NumPlayers; j++)
{
const CNetObj_PlayerInfo *pInfo = apPlayers[j];
const CGameClient::CClientStats *pStats = &m_pClient->m_aStats[pInfo->m_ClientID];
2015-05-19 22:51:02 +00:00
if(m_pClient->m_Snap.m_LocalClientID == pInfo->m_ClientID || (m_pClient->m_Snap.m_SpecInfo.m_Active && pInfo->m_ClientID == m_pClient->m_Snap.m_SpecInfo.m_SpectatorID))
2015-05-19 22:51:02 +00:00
{
// background so it's easy to find the local player
Graphics()->TextureClear();
2015-05-19 22:51:02 +00:00
Graphics()->QuadsBegin();
Graphics()->SetColor(1, 1, 1, 0.25f);
RenderTools()->DrawRoundRect(x - 10, y + ContentLineOffset / 2, StatboardContentWidth, LineHeight - ContentLineOffset, 0);
2015-05-19 22:51:02 +00:00
Graphics()->QuadsEnd();
}
CTeeRenderInfo Teeinfo = m_pClient->m_aClients[pInfo->m_ClientID].m_RenderInfo;
Teeinfo.m_Size *= TeeSizemod;
CAnimState *pIdleState = CAnimState::GetIdle();
vec2 OffsetToMid;
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &Teeinfo, OffsetToMid);
vec2 TeeRenderPos(x + Teeinfo.m_Size / 2, y + LineHeight / 2.0f + OffsetToMid.y);
RenderTools()->RenderTee(pIdleState, &Teeinfo, EMOTE_NORMAL, vec2(1, 0), TeeRenderPos);
2015-05-19 22:51:02 +00:00
char aBuf[128];
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, x + 64, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
2015-05-19 22:51:02 +00:00
Cursor.m_LineWidth = 220;
TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, -1);
px = 325;
2015-06-14 11:41:03 +00:00
// FRAGS
2015-05-19 22:51:02 +00:00
{
str_format(aBuf, sizeof(aBuf), "%d", pStats->m_Frags);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, x - tw + px, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, aBuf, -1.0f);
2020-11-15 08:06:03 +00:00
px += 85;
2015-05-19 22:51:02 +00:00
}
2015-06-14 11:41:03 +00:00
// DEATHS
2015-05-19 22:51:02 +00:00
{
str_format(aBuf, sizeof(aBuf), "%d", pStats->m_Deaths);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, x - tw + px, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, aBuf, -1.0f);
px += 85;
2015-05-19 22:51:02 +00:00
}
2015-06-14 11:41:03 +00:00
// SUICIDES
2015-05-19 22:51:02 +00:00
{
2015-05-21 17:21:13 +00:00
px += 10;
str_format(aBuf, sizeof(aBuf), "%d", pStats->m_Suicides);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, x - tw + px, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, aBuf, -1.0f);
px += 85;
2015-05-19 22:51:02 +00:00
}
2015-06-14 11:41:03 +00:00
// RATIO
2015-05-19 22:51:02 +00:00
{
if(pStats->m_Deaths == 0)
2015-05-19 22:51:02 +00:00
str_format(aBuf, sizeof(aBuf), "--");
else
str_format(aBuf, sizeof(aBuf), "%.2f", (float)(pStats->m_Frags) / pStats->m_Deaths);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, x - tw + px, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, aBuf, -1.0f);
px += 85;
2015-05-19 22:51:02 +00:00
}
2015-06-14 11:41:03 +00:00
// NET
2015-05-19 22:51:02 +00:00
{
str_format(aBuf, sizeof(aBuf), "%+d", pStats->m_Frags - pStats->m_Deaths);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, x - tw + px, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, aBuf, -1.0f);
px += 85;
2015-05-19 22:51:02 +00:00
}
2015-06-14 11:41:03 +00:00
// FPM
2015-05-19 22:51:02 +00:00
{
float Fpm = pStats->GetFPM(Client()->GameTick(g_Config.m_ClDummy), Client()->GameTickSpeed());
2015-05-19 22:51:02 +00:00
str_format(aBuf, sizeof(aBuf), "%.1f", Fpm);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, x - tw + px, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, aBuf, -1.0f);
px += 85;
2015-05-19 22:51:02 +00:00
}
2015-06-14 11:41:03 +00:00
// SPREE
2015-05-19 22:51:02 +00:00
{
str_format(aBuf, sizeof(aBuf), "%d", pStats->m_CurrentSpree);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, x - tw + px, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, aBuf, -1.0f);
px += 85;
2015-05-19 22:51:02 +00:00
}
2015-06-14 11:41:03 +00:00
// BEST SPREE
2015-05-19 22:51:02 +00:00
{
str_format(aBuf, sizeof(aBuf), "%d", pStats->m_BestSpree);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, x - tw + px, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, aBuf, -1.0f);
px += 85;
2015-05-19 22:51:02 +00:00
}
2015-06-14 11:41:03 +00:00
// GRABS
2017-03-21 10:24:44 +00:00
if(GameWithFlags)
2015-05-19 22:51:02 +00:00
{
str_format(aBuf, sizeof(aBuf), "%d", pStats->m_FlagGrabs);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, x - tw + px, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, aBuf, -1.0f);
px += 85;
2015-05-19 22:51:02 +00:00
}
2015-06-14 11:41:03 +00:00
// WEAPONS
px -= 40;
for(int i = 0; i < NUM_WEAPONS; i++)
2015-05-19 22:51:02 +00:00
{
2015-05-21 09:41:59 +00:00
if(!aDisplayWeapon[i])
continue;
str_format(aBuf, sizeof(aBuf), "%d/%d", pStats->m_aFragsWith[i], pStats->m_aDeathsFrom[i]);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, x + px - tw / 2, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, aBuf, -1.0f);
2015-05-19 22:51:02 +00:00
px += 80;
}
2015-06-14 11:41:03 +00:00
// FLAGS
2017-03-21 10:24:44 +00:00
if(GameWithFlags)
2015-05-19 22:51:02 +00:00
{
str_format(aBuf, sizeof(aBuf), "%d", pStats->m_FlagCaptures);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->Text(0, x - tw + px, y + (LineHeight * 0.95f - FontSize) / 2.f, FontSize, aBuf, -1.0f);
2015-05-19 22:51:02 +00:00
}
y += LineHeight;
}
}
2015-05-21 09:41:59 +00:00
void CStatboard::AutoStatScreenshot()
2015-05-19 22:51:02 +00:00
{
if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
Client()->AutoStatScreenshot_Start();
}
void CStatboard::AutoStatCSV()
{
if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
{
char aDate[20], aFilename[IO_MAX_PATH_LENGTH];
str_timestamp(aDate, sizeof(aDate));
str_format(aFilename, sizeof(aFilename), "screenshots/auto/stats_%s.csv", aDate);
IOHANDLE File = Storage()->OpenFile(aFilename, IOFLAG_WRITE, IStorage::TYPE_ALL);
if(File)
{
char aStats[1024 * (VANILLA_MAX_CLIENTS + 1)];
FormatStats(aStats, sizeof(aStats));
io_write(File, aStats, str_length(aStats));
io_close(File);
}
Client()->AutoCSV_Start();
}
}
std::string CStatboard::ReplaceCommata(char *pStr)
{
if(!str_find(pStr, ","))
2017-05-02 16:27:04 +00:00
return pStr;
char aOutbuf[256];
mem_zero(aOutbuf, sizeof(aOutbuf));
for(int i = 0, skip = 0; i < 64; i++)
{
if(pStr[i] == ',')
2017-05-02 16:27:04 +00:00
{
aOutbuf[i + skip++] = '%';
aOutbuf[i + skip++] = '2';
aOutbuf[i + skip] = 'C';
}
else
aOutbuf[i + skip] = pStr[i];
}
return aOutbuf;
}
void CStatboard::FormatStats(char *pDest, size_t DestSize)
{
// server stats
CServerInfo CurrentServerInfo;
Client()->GetServerInfo(&CurrentServerInfo);
char aServerStats[1024];
str_format(aServerStats, sizeof(aServerStats), "Servername,Game-type,Map\n%s,%s,%s", ReplaceCommata(CurrentServerInfo.m_aName).c_str(), ReplaceCommata(CurrentServerInfo.m_aGameType).c_str(), ReplaceCommata(CurrentServerInfo.m_aMap).c_str());
// player stats
// sort players
const CNetObj_PlayerInfo *apPlayers[MAX_CLIENTS] = {0};
int NumPlayers = 0;
// sort red or dm players by score
for(const auto *pInfo : m_pClient->m_Snap.m_paInfoByScore)
{
if(!pInfo || !m_pClient->m_aStats[pInfo->m_ClientID].IsActive() || m_pClient->m_aClients[pInfo->m_ClientID].m_Team != TEAM_RED)
continue;
apPlayers[NumPlayers] = pInfo;
NumPlayers++;
}
// sort blue players by score after
if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS)
{
for(const auto *pInfo : m_pClient->m_Snap.m_paInfoByScore)
{
if(!pInfo || !m_pClient->m_aStats[pInfo->m_ClientID].IsActive() || m_pClient->m_aClients[pInfo->m_ClientID].m_Team != TEAM_BLUE)
continue;
apPlayers[NumPlayers] = pInfo;
NumPlayers++;
}
}
char aPlayerStats[1024 * VANILLA_MAX_CLIENTS];
str_format(aPlayerStats, sizeof(aPlayerStats), "Local-player,Team,Name,Clan,Score,Frags,Deaths,Suicides,F/D-ratio,Net,FPM,Spree,Best,Hammer-F/D,Gun-F/D,Shotgun-F/D,Grenade-F/D,Laser-F/D,Ninja-F/D,GameWithFlags,Flag-grabs,Flag-captures\n");
for(int i = 0; i < NumPlayers; i++)
{
2017-04-28 09:28:18 +00:00
const CNetObj_PlayerInfo *pInfo = apPlayers[i];
const CGameClient::CClientStats *pStats = &m_pClient->m_aStats[pInfo->m_ClientID];
// Pre-formatting
// Weapons frags/deaths
char aWeaponFD[64 * NUM_WEAPONS];
for(int j = 0; j < NUM_WEAPONS; j++)
{
if(j == 0)
2017-04-28 09:28:18 +00:00
str_format(aWeaponFD, sizeof(aWeaponFD), "%d/%d", pStats->m_aFragsWith[j], pStats->m_aDeathsFrom[j]);
else
2017-04-28 09:28:18 +00:00
str_format(aWeaponFD, sizeof(aWeaponFD), "%s,%d/%d", aWeaponFD, pStats->m_aFragsWith[j], pStats->m_aDeathsFrom[j]);
}
// Frag/Death ratio
float fdratio = 0.0f;
if(pStats->m_Deaths != 0)
fdratio = (float)(pStats->m_Frags) / pStats->m_Deaths;
// Local player
bool localPlayer = (m_pClient->m_Snap.m_LocalClientID == pInfo->m_ClientID || (m_pClient->m_Snap.m_SpecInfo.m_Active && pInfo->m_ClientID == m_pClient->m_Snap.m_SpecInfo.m_SpectatorID));
// Game with flags
bool GameWithFlags = (m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS);
char aBuf[1024];
str_format(aBuf, sizeof(aBuf), "%d,%d,%s,%s,%d,%d,%d,%d,%.2f,%i,%.1f,%d,%d,%s,%d,%d,%d\n",
localPlayer ? 1 : 0, // Local player
m_pClient->m_aClients[pInfo->m_ClientID].m_Team, // Team
ReplaceCommata(m_pClient->m_aClients[pInfo->m_ClientID].m_aName).c_str(), // Name
ReplaceCommata(m_pClient->m_aClients[pInfo->m_ClientID].m_aClan).c_str(), // Clan
clamp(pInfo->m_Score, -999, 999), // Score
pStats->m_Frags, // Frags
pStats->m_Deaths, // Deaths
pStats->m_Suicides, // Suicides
fdratio, // fdratio
pStats->m_Frags - pStats->m_Deaths, // Net
pStats->GetFPM(Client()->GameTick(g_Config.m_ClDummy), Client()->GameTickSpeed()), // FPM
pStats->m_CurrentSpree, // CurSpree
pStats->m_BestSpree, // BestSpree
aWeaponFD, // WeaponFD
GameWithFlags ? 1 : 0, // GameWithFlags
pStats->m_FlagGrabs, // Flag grabs
pStats->m_FlagCaptures); // Flag captures
str_append(aPlayerStats, aBuf, sizeof(aPlayerStats));
}
str_format(pDest, DestSize, "%s\n\n%s", aServerStats, aPlayerStats);
}