ddnet/src/game/client/components/scoreboard.cpp
c0d3d3v fa2ac1bb64
Merge branch 'master' into New_HUD
* master: (87 commits)
  Remove base/tl/string.h
  Replace remaining usage of base/tl/string with std::string
  Remove unused includes of base/tl/string.h
  Store localized strings in a CHeap instead of using tl/string.h
  Mark methods as const
  Add CHeap::StoreString method
  Rules are chat responses too
  Add margins to demo slice popup, decrease error font size, UI scaling
  Remove redundant parameters which are overridden later
  Use Margin instead of both VMargin and HMargin
  Move variable declaration
  Only output messages intended for chat to the user of a chat command
  Remove unused chat response variables
  Don't print the first "Waiting for score threads to complete"
  fix usage of undefined behavior for default eyes
  remove duplicate HOOK_RETRACTED assignment
  do not send swap request notification to complete team 0
  make swap messages more personal
  Move ninja shield to other position (fixes #5047)
  do not release the hooks if you swap
  ...
2022-05-10 19:03:09 +02:00

750 lines
23 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <engine/demo.h>
#include <engine/graphics.h>
#include <engine/shared/config.h>
#include <engine/textrender.h>
#include <game/generated/client_data.h>
#include <game/generated/protocol.h>
#include <game/client/animstate.h>
#include <game/client/components/countryflags.h>
#include <game/client/components/motd.h>
#include <game/client/components/skins.h>
#include <game/client/components/statboard.h>
#include <game/client/gameclient.h>
#include <game/client/render.h>
#include <game/localization.h>
#include "scoreboard.h"
#include <engine/serverbrowser.h>
CScoreboard::CScoreboard()
{
OnReset();
}
void CScoreboard::ConKeyScoreboard(IConsole::IResult *pResult, void *pUserData)
{
CScoreboard *pSelf = (CScoreboard *)pUserData;
pSelf->m_Active = pResult->GetInteger(0) != 0;
}
void CScoreboard::OnReset()
{
m_Active = false;
m_ServerRecord = -1.0f;
}
void CScoreboard::OnRelease()
{
m_Active = false;
}
void CScoreboard::OnMessage(int MsgType, void *pRawMsg)
{
if(MsgType == NETMSGTYPE_SV_RECORD || MsgType == NETMSGTYPE_SV_RECORDLEGACY)
{
CNetMsg_Sv_Record *pMsg = (CNetMsg_Sv_Record *)pRawMsg;
m_ServerRecord = (float)pMsg->m_ServerTimeBest / 100;
//m_PlayerRecord = (float)pMsg->m_PlayerTimeBest/100;
}
}
void CScoreboard::OnConsoleInit()
{
Console()->Register("+scoreboard", "", CFGFLAG_CLIENT, ConKeyScoreboard, this, "Show scoreboard");
}
void CScoreboard::RenderGoals(float x, float y, float w)
{
float h = 50.0f;
Graphics()->BlendNormal();
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(0, 0, 0, 0.5f);
RenderTools()->DrawRoundRect(x, y, w, h, 10.0f);
Graphics()->QuadsEnd();
// render goals
if(m_pClient->m_Snap.m_pGameInfoObj)
{
if(m_pClient->m_Snap.m_pGameInfoObj->m_ScoreLimit)
{
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "%s: %d", Localize("Score limit"), m_pClient->m_Snap.m_pGameInfoObj->m_ScoreLimit);
TextRender()->Text(0, x + 10.0f, y + (h - 20.f) / 2.f, 20.0f, aBuf, -1.0f);
}
if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit)
{
char aBuf[64];
str_format(aBuf, sizeof(aBuf), Localize("Time limit: %d min"), m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit);
TextRender()->Text(0, x + 230.0f, y + (h - 20.f) / 2.f, 20.0f, aBuf, -1.0f);
}
if(m_pClient->m_Snap.m_pGameInfoObj->m_RoundNum && m_pClient->m_Snap.m_pGameInfoObj->m_RoundCurrent)
{
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "%s %d/%d", Localize("Round"), m_pClient->m_Snap.m_pGameInfoObj->m_RoundCurrent, m_pClient->m_Snap.m_pGameInfoObj->m_RoundNum);
float tw = TextRender()->TextWidth(0, 20.0f, aBuf, -1, -1.0f);
TextRender()->Text(0, x + w - tw - 10.0f, y + (h - 20.f) / 2.f, 20.0f, aBuf, -1.0f);
}
}
}
void CScoreboard::RenderSpectators(float x, float y, float w)
{
float h = 140.0f;
// background
Graphics()->BlendNormal();
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(0, 0, 0, 0.5f);
RenderTools()->DrawRoundRect(x, y, w, h, 10.0f);
Graphics()->QuadsEnd();
// Headline
y += 10.0f;
TextRender()->Text(0, x + 10.0f, y + (30.f - 28.f) / 2.f, 28.0f, Localize("Spectators"), w - 20.0f);
// spectator names
y += 30.0f;
bool Multiple = false;
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, x + 10.0f, y, 22.0f, TEXTFLAG_RENDER);
Cursor.m_LineWidth = w - 20.0f;
Cursor.m_MaxLines = 4;
for(const auto *pInfo : m_pClient->m_Snap.m_paInfoByName)
{
if(!pInfo || pInfo->m_Team != TEAM_SPECTATORS)
continue;
if(Multiple)
TextRender()->TextEx(&Cursor, ", ", 2);
if(m_pClient->m_aClients[pInfo->m_ClientID].m_AuthLevel)
{
ColorRGBA Color = color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClAuthedPlayerColor));
TextRender()->TextColor(Color);
}
if(g_Config.m_ClShowIDs)
{
char aBuffer[5];
int size = str_format(aBuffer, sizeof(aBuffer), "%d: ", pInfo->m_ClientID);
TextRender()->TextEx(&Cursor, aBuffer, size);
}
TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, -1);
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
Multiple = true;
}
}
void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const char *pTitle, int NumPlayers)
{
if(Team == TEAM_SPECTATORS)
return;
bool lower16 = false;
bool upper16 = false;
bool lower24 = false;
bool upper24 = false;
bool lower32 = false;
bool upper32 = false;
if(Team == -3)
upper16 = true;
else if(Team == -4)
lower32 = true;
else if(Team == -5)
upper32 = true;
else if(Team == -6)
lower16 = true;
else if(Team == -7)
lower24 = true;
else if(Team == -8)
upper24 = true;
bool IsTeamplayTeam = Team > TEAM_SPECTATORS;
if(Team < -1)
Team = 0;
if(NumPlayers < 0)
NumPlayers = m_pClient->m_Snap.m_aTeamSize[Team];
float h = 760.0f;
// background
Graphics()->BlendNormal();
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.5f);
if(upper16 || upper32 || upper24)
RenderTools()->DrawRoundRectExt(x, y, w, h, 17.0f, 10);
else if(lower16 || lower32 || lower24)
RenderTools()->DrawRoundRectExt(x, y, w, h, 17.0f, 5);
else
RenderTools()->DrawRoundRect(x, y, w, h, 17.0f);
Graphics()->QuadsEnd();
char aBuf[128] = {0};
// render title
float TitleFontsize = 40.0f;
int TitleWidth = (lower32 || lower24 || lower16) ? 1140 : 440;
if(!pTitle)
{
if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)
pTitle = Localize("Game over");
else
{
str_copy(aBuf, Client()->GetCurrentMap(), sizeof(aBuf));
while(TextRender()->TextWidth(0, TitleFontsize, aBuf, -1, -1.0f) > TitleWidth)
aBuf[str_length(aBuf) - 1] = '\0';
if(str_comp(aBuf, Client()->GetCurrentMap()))
str_append(aBuf, "", sizeof(aBuf));
pTitle = aBuf;
}
}
TextRender()->Text(0, x + 20.0f, y + (50.f - TitleFontsize) / 2.f, TitleFontsize, pTitle, -1.0f);
if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS)
{
if(m_pClient->m_Snap.m_pGameDataObj)
{
int Score = Team == TEAM_RED ? m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreRed : m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreBlue;
str_format(aBuf, sizeof(aBuf), "%d", Score);
}
}
else
{
if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW &&
m_pClient->m_Snap.m_paPlayerInfos[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID])
{
int Score = m_pClient->m_Snap.m_paPlayerInfos[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID]->m_Score;
str_format(aBuf, sizeof(aBuf), "%d", Score);
}
else if(m_pClient->m_Snap.m_pLocalInfo)
{
int Score = m_pClient->m_Snap.m_pLocalInfo->m_Score;
str_format(aBuf, sizeof(aBuf), "%d", Score);
}
}
if(m_pClient->m_GameInfo.m_TimeScore && g_Config.m_ClDDRaceScoreBoard)
{
if(m_ServerRecord > 0)
str_time_float(m_ServerRecord, TIME_HOURS, aBuf, sizeof(aBuf));
else
aBuf[0] = 0;
}
float tw;
if(!lower16 && !lower32 && !lower24)
{
tw = TextRender()->TextWidth(0, TitleFontsize, aBuf, -1, -1.0f);
TextRender()->Text(0, x + w - tw - 20.0f, y + (50.f - TitleFontsize) / 2.f, TitleFontsize, aBuf, -1.0f);
}
// calculate measurements
float LineHeight = 60.0f;
float TeeSizeMod = 1.0f;
float Spacing = 16.0f;
float RoundRadius = 15.0f;
float FontSize = 24.0f;
if(NumPlayers > 48)
{
LineHeight = 20.0f;
TeeSizeMod = 0.4f;
Spacing = 0.0f;
RoundRadius = 5.0f;
FontSize = 16.0f;
}
else if(NumPlayers > 32)
{
LineHeight = 27.0f;
TeeSizeMod = 0.6f;
Spacing = 0.0f;
RoundRadius = 5.0f;
FontSize = 20.0f;
}
else if(NumPlayers > 12)
{
LineHeight = 40.0f;
TeeSizeMod = 0.8f;
Spacing = 0.0f;
RoundRadius = 15.0f;
}
else if(NumPlayers > 8)
{
LineHeight = 50.0f;
TeeSizeMod = 0.9f;
Spacing = 5.0f;
RoundRadius = 15.0f;
}
float ScoreOffset = x + 10.0f + 10.0f, ScoreLength = TextRender()->TextWidth(0, FontSize, "00:00:00", -1, -1.0f);
if(IsTeamplayTeam)
ScoreLength = TextRender()->TextWidth(0, FontSize, "99999", -1, -1.0f);
float TeeOffset = ScoreOffset + ScoreLength + 15.0f, TeeLength = 60 * TeeSizeMod;
float NameOffset = TeeOffset + TeeLength, NameLength = 300.0f - TeeLength;
float CountryLength = (LineHeight - Spacing - TeeSizeMod * 5.0f) * 2.0f;
float PingLength = 65.0f;
float PingOffset = x + w - PingLength - 10.0f - 10.0f;
float CountryOffset = PingOffset - CountryLength;
float ClanLength = w - ((NameOffset - x) + NameLength) - (w - (CountryOffset - x));
float ClanOffset = CountryOffset - ClanLength;
// render headlines
x += 10.0f;
y += 50.0f;
float HeadlineFontsize = 22.0f;
const char *pScore = (m_pClient->m_GameInfo.m_TimeScore && g_Config.m_ClDDRaceScoreBoard) ? Localize("Time") : Localize("Score");
tw = TextRender()->TextWidth(0, HeadlineFontsize, pScore, -1, -1.0f);
TextRender()->Text(0, ScoreOffset + ScoreLength - tw, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, pScore, -1.0f);
TextRender()->Text(0, NameOffset, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, Localize("Name"), -1.0f);
tw = TextRender()->TextWidth(0, HeadlineFontsize, Localize("Clan"), -1, -1.0f);
TextRender()->Text(0, ClanOffset + (ClanLength - tw) / 2, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, Localize("Clan"), -1.0f);
tw = TextRender()->TextWidth(0, HeadlineFontsize, Localize("Ping"), -1, -1.0f);
TextRender()->Text(0, PingOffset + PingLength - tw, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, Localize("Ping"), -1.0f);
// render player entries
y += HeadlineFontsize * 2.0f;
CTextCursor Cursor;
int rendered = 0;
if(upper16)
rendered = -16;
if(upper32)
rendered = -32;
if(upper24)
rendered = -24;
int OldDDTeam = -1;
for(int i = 0; i < MAX_CLIENTS; i++)
{
// make sure that we render the correct team
const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_paInfoByDDTeamScore[i];
if(!pInfo || pInfo->m_Team != Team)
continue;
if(rendered++ < 0)
continue;
int DDTeam = m_pClient->m_Teams.Team(pInfo->m_ClientID);
int NextDDTeam = 0;
for(int j = i + 1; j < MAX_CLIENTS; j++)
{
const CNetObj_PlayerInfo *pInfo2 = m_pClient->m_Snap.m_paInfoByDDTeamScore[j];
if(!pInfo2 || pInfo2->m_Team != Team)
continue;
NextDDTeam = m_pClient->m_Teams.Team(pInfo2->m_ClientID);
break;
}
if(OldDDTeam == -1)
{
for(int j = i - 1; j >= 0; j--)
{
const CNetObj_PlayerInfo *pInfo2 = m_pClient->m_Snap.m_paInfoByDDTeamScore[j];
if(!pInfo2 || pInfo2->m_Team != Team)
continue;
OldDDTeam = m_pClient->m_Teams.Team(pInfo2->m_ClientID);
break;
}
}
if(DDTeam != TEAM_FLOCK)
{
Graphics()->TextureClear();
Graphics()->QuadsBegin();
ColorRGBA rgb = color_cast<ColorRGBA>(ColorHSLA(DDTeam / 64.0f, 1.0f, 0.5f, 0.5f));
Graphics()->SetColor(rgb);
int Corners = 0;
if(OldDDTeam != DDTeam)
Corners |= CUI::CORNER_TL | CUI::CORNER_TR;
if(NextDDTeam != DDTeam)
Corners |= CUI::CORNER_BL | CUI::CORNER_BR;
RenderTools()->DrawRoundRectExt(x - 10.0f, y, w, LineHeight + Spacing, RoundRadius, Corners);
Graphics()->QuadsEnd();
if(NextDDTeam != DDTeam)
{
if(m_pClient->m_Snap.m_aTeamSize[0] > 8)
{
if(DDTeam == TEAM_SUPER)
str_copy(aBuf, Localize("Super"), sizeof(aBuf));
else
str_format(aBuf, sizeof(aBuf), "%d", DDTeam);
TextRender()->SetCursor(&Cursor, x - 10.0f, y + Spacing + FontSize - (FontSize / 1.5f), FontSize / 1.5f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = NameLength + 3;
}
else
{
if(DDTeam == TEAM_SUPER)
str_copy(aBuf, Localize("Super"), sizeof(aBuf));
else
str_format(aBuf, sizeof(aBuf), Localize("Team %d"), DDTeam);
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->SetCursor(&Cursor, ScoreOffset + w / 2.0f - tw / 2.0f, y + LineHeight, FontSize / 1.5f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = NameLength + 3;
}
TextRender()->TextEx(&Cursor, aBuf, -1);
}
}
OldDDTeam = DDTeam;
// background so it's easy to find the local player or the followed one in spectator mode
if((!m_pClient->m_Snap.m_SpecInfo.m_Active && pInfo->m_Local) || (m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW && pInfo->m_Local) || (m_pClient->m_Snap.m_SpecInfo.m_Active && pInfo->m_ClientID == m_pClient->m_Snap.m_SpecInfo.m_SpectatorID))
{
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f);
RenderTools()->DrawRoundRect(x, y, w - 20.0f, LineHeight, RoundRadius);
Graphics()->QuadsEnd();
}
// score
if(m_pClient->m_GameInfo.m_TimeScore && g_Config.m_ClDDRaceScoreBoard)
{
if(pInfo->m_Score == -9999)
aBuf[0] = 0;
else
str_time((int64_t)abs(pInfo->m_Score) * 100, TIME_HOURS, aBuf, sizeof(aBuf));
}
else
str_format(aBuf, sizeof(aBuf), "%d", clamp(pInfo->m_Score, -999, 99999));
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
TextRender()->SetCursor(&Cursor, ScoreOffset + ScoreLength - tw, y + (LineHeight - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER);
TextRender()->TextEx(&Cursor, aBuf, -1);
// flag
if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS &&
m_pClient->m_Snap.m_pGameDataObj && (m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed == pInfo->m_ClientID || m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue == pInfo->m_ClientID))
{
Graphics()->BlendNormal();
if(m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue == pInfo->m_ClientID)
Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagBlue);
else
Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagRed);
Graphics()->QuadsBegin();
Graphics()->QuadsSetSubset(1, 0, 0, 1);
float Size = LineHeight;
IGraphics::CQuadItem QuadItem(TeeOffset + 0.0f, y - 5.0f - Spacing / 2.0f, Size / 2.0f, Size);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
}
// avatar
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(TeeOffset + TeeLength / 2, y + LineHeight / 2.0f + OffsetToMid.y);
RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos);
// name
TextRender()->SetCursor(&Cursor, NameOffset, y + (LineHeight - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
if(m_pClient->m_aClients[pInfo->m_ClientID].m_AuthLevel)
{
ColorRGBA Color = color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClAuthedPlayerColor));
TextRender()->TextColor(Color);
}
if(g_Config.m_ClShowIDs)
{
char aId[64] = "";
if(pInfo->m_ClientID < 10)
{
str_format(aId, sizeof(aId), "%d: %s", pInfo->m_ClientID, m_pClient->m_aClients[pInfo->m_ClientID].m_aName);
}
else
{
str_format(aId, sizeof(aId), "%d: %s", pInfo->m_ClientID, m_pClient->m_aClients[pInfo->m_ClientID].m_aName);
}
Cursor.m_LineWidth = NameLength;
TextRender()->TextEx(&Cursor, aId, -1);
}
else
{
Cursor.m_LineWidth = NameLength;
TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, -1);
}
// clan
if(str_comp(m_pClient->m_aClients[pInfo->m_ClientID].m_aClan,
m_pClient->m_aClients[GameClient()->m_LocalIDs[g_Config.m_ClDummy]].m_aClan) == 0)
{
ColorRGBA Color = color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClSameClanColor));
TextRender()->TextColor(Color);
}
else
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
tw = minimum(TextRender()->TextWidth(nullptr, FontSize, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1, -1.0f), ClanLength);
TextRender()->SetCursor(&Cursor, ClanOffset + (ClanLength - tw) / 2, y + (LineHeight - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = ClanLength;
TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1);
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
// country flag
ColorRGBA Color(1.0f, 1.0f, 1.0f, 0.5f);
m_pClient->m_CountryFlags.Render(m_pClient->m_aClients[pInfo->m_ClientID].m_Country, &Color,
CountryOffset, y + (Spacing + TeeSizeMod * 5.0f) / 2.0f, CountryLength, LineHeight - Spacing - TeeSizeMod * 5.0f);
// ping
if(g_Config.m_ClEnablePingColor)
{
ColorRGBA rgb = color_cast<ColorRGBA>(ColorHSLA((300.0f - clamp(pInfo->m_Latency, 0, 300)) / 1000.0f, 1.0f, 0.5f));
TextRender()->TextColor(rgb);
}
str_format(aBuf, sizeof(aBuf), "%d", clamp(pInfo->m_Latency, 0, 999));
tw = TextRender()->TextWidth(nullptr, FontSize, aBuf, -1, -1.0f);
TextRender()->SetCursor(&Cursor, PingOffset + PingLength - tw, y + (LineHeight - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = PingLength;
TextRender()->TextEx(&Cursor, aBuf, -1);
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
y += LineHeight + Spacing;
if(lower32 || upper32)
{
if(rendered == 32)
break;
}
else if(lower24 || upper24)
{
if(rendered == 24)
break;
}
else
{
if(rendered == 16)
break;
}
}
}
void CScoreboard::RenderRecordingNotification(float x)
{
if(!m_pClient->DemoRecorder(RECORDER_MANUAL)->IsRecording() &&
!m_pClient->DemoRecorder(RECORDER_AUTO)->IsRecording() &&
!m_pClient->DemoRecorder(RECORDER_RACE)->IsRecording() &&
!m_pClient->DemoRecorder(RECORDER_REPLAYS)->IsRecording())
{
return;
}
//draw the text
char aBuf[64] = "\0";
char aBuf2[64];
char aTime[32];
if(m_pClient->DemoRecorder(RECORDER_MANUAL)->IsRecording())
{
str_time((int64_t)m_pClient->DemoRecorder(RECORDER_MANUAL)->Length() * 100, TIME_HOURS, aTime, sizeof(aTime));
str_format(aBuf2, sizeof(aBuf2), "%s %s ", Localize("Manual"), aTime);
str_append(aBuf, aBuf2, sizeof(aBuf));
}
if(m_pClient->DemoRecorder(RECORDER_RACE)->IsRecording())
{
str_time((int64_t)m_pClient->DemoRecorder(RECORDER_RACE)->Length() * 100, TIME_HOURS, aTime, sizeof(aTime));
str_format(aBuf2, sizeof(aBuf2), "%s %s ", Localize("Race"), aTime);
str_append(aBuf, aBuf2, sizeof(aBuf));
}
if(m_pClient->DemoRecorder(RECORDER_AUTO)->IsRecording())
{
str_time((int64_t)m_pClient->DemoRecorder(RECORDER_AUTO)->Length() * 100, TIME_HOURS, aTime, sizeof(aTime));
str_format(aBuf2, sizeof(aBuf2), "%s %s ", Localize("Auto"), aTime);
str_append(aBuf, aBuf2, sizeof(aBuf));
}
if(m_pClient->DemoRecorder(RECORDER_REPLAYS)->IsRecording())
{
str_time((int64_t)m_pClient->DemoRecorder(RECORDER_REPLAYS)->Length() * 100, TIME_HOURS, aTime, sizeof(aTime));
str_format(aBuf2, sizeof(aBuf2), "%s %s ", Localize("Replay"), aTime);
str_append(aBuf, aBuf2, sizeof(aBuf));
}
float w = TextRender()->TextWidth(0, 20.0f, aBuf, -1, -1.0f);
//draw the box
Graphics()->BlendNormal();
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.4f);
RenderTools()->DrawRoundRectExt(x, 0.0f, w + 60.0f, 50.0f, 15.0f, CUI::CORNER_B);
Graphics()->QuadsEnd();
//draw the red dot
Graphics()->QuadsBegin();
Graphics()->SetColor(1.0f, 0.0f, 0.0f, 1.0f);
RenderTools()->DrawRoundRect(x + 20, 15.0f, 20.0f, 20.0f, 10.0f);
Graphics()->QuadsEnd();
TextRender()->Text(0, x + 50.0f, (50.f - 20.f) / 2.f, 20.0f, aBuf, -1.0f);
}
void CScoreboard::OnRender()
{
if(!Active())
return;
// if the score board is active, then we should clear the motd message as well
if(m_pClient->m_Motd.IsActive())
m_pClient->m_Motd.Clear();
float Width = 400 * 3.0f * Graphics()->ScreenAspect();
float Height = 400 * 3.0f;
Graphics()->MapScreen(0, 0, Width, Height);
float w = 750.0f;
float ExtraWidthSingle = 20.0f;
if(m_pClient->m_Snap.m_pGameInfoObj)
{
if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS))
{
if(m_pClient->m_Snap.m_aTeamSize[0] > 48)
{
RenderScoreboard(Width / 2, 150.0f, w, -5, "");
RenderScoreboard(Width / 2 - w, 150.0f, w, -4, 0);
}
else if(m_pClient->m_Snap.m_aTeamSize[0] > 32)
{
RenderScoreboard(Width / 2, 150.0f, w, -8, "");
RenderScoreboard(Width / 2 - w, 150.0f, w, -7, 0);
}
else if(m_pClient->m_Snap.m_aTeamSize[0] > 16)
{
RenderScoreboard(Width / 2, 150.0f, w, -3, "");
RenderScoreboard(Width / 2 - w, 150.0f, w, -6, 0);
}
else
{
w += ExtraWidthSingle;
RenderScoreboard(Width / 2 - w / 2, 150.0f, w, -2, 0);
}
}
else
{
const char *pRedClanName = GetClanName(TEAM_RED);
const char *pBlueClanName = GetClanName(TEAM_BLUE);
if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER && m_pClient->m_Snap.m_pGameDataObj)
{
char aText[256];
str_copy(aText, Localize("Draw!"), sizeof(aText));
if(m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreRed > m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreBlue)
{
if(pRedClanName)
str_format(aText, sizeof(aText), Localize("%s wins!"), pRedClanName);
else
str_copy(aText, Localize("Red team wins!"), sizeof(aText));
}
else if(m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreBlue > m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreRed)
{
if(pBlueClanName)
str_format(aText, sizeof(aText), Localize("%s wins!"), pBlueClanName);
else
str_copy(aText, Localize("Blue team wins!"), sizeof(aText));
}
float TextWidth = TextRender()->TextWidth(0, 86.0f, aText, -1, -1.0f);
TextRender()->Text(0, Width / 2 - TextWidth / 2, 39, 86.0f, aText, -1.0f);
}
//decrease width, because team games use additional offsets
w -= 10.0f;
int NumPlayers = maximum(m_pClient->m_Snap.m_aTeamSize[TEAM_RED], m_pClient->m_Snap.m_aTeamSize[TEAM_BLUE]);
RenderScoreboard(Width / 2 - w - 5.0f, 150.0f, w, TEAM_RED, pRedClanName ? pRedClanName : Localize("Red team"), NumPlayers);
RenderScoreboard(Width / 2 + 5.0f, 150.0f, w, TEAM_BLUE, pBlueClanName ? pBlueClanName : Localize("Blue team"), NumPlayers);
}
}
RenderGoals(Width / 2 - w / 2, 150 + 760 + 10, w);
RenderSpectators(Width / 2 - w / 2, 150 + 760 + 10 + 50 + 10, w);
RenderRecordingNotification((Width / 7) * 4 + 20);
}
bool CScoreboard::Active()
{
// if statboard is active don't show scoreboard
if(m_pClient->m_Statboard.IsActive())
return false;
if(m_Active)
return true;
if(m_pClient->m_Snap.m_pLocalInfo && !m_pClient->m_Snap.m_SpecInfo.m_Active)
{
// we are not a spectator, check if we are dead
if(!m_pClient->m_Snap.m_pLocalCharacter && g_Config.m_ClScoreboardOnDeath)
return true;
}
// if the game is over
if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)
return true;
return false;
}
const char *CScoreboard::GetClanName(int Team)
{
int ClanPlayers = 0;
const char *pClanName = 0;
for(const auto *pInfo : m_pClient->m_Snap.m_paInfoByScore)
{
if(!pInfo || pInfo->m_Team != Team)
continue;
if(!pClanName)
{
pClanName = m_pClient->m_aClients[pInfo->m_ClientID].m_aClan;
ClanPlayers++;
}
else
{
if(str_comp(m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, pClanName) == 0)
ClanPlayers++;
else
return 0;
}
}
if(ClanPlayers > 1 && pClanName[0])
return pClanName;
else
return 0;
}