diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp index ad2c0752b..fd952c7e8 100644 --- a/src/game/client/components/scoreboard.cpp +++ b/src/game/client/components/scoreboard.cpp @@ -1,5 +1,7 @@ /* (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 "scoreboard.h" + #include #include #include @@ -13,10 +15,9 @@ #include #include #include +#include #include -#include "scoreboard.h" - CScoreboard::CScoreboard() { OnReset(); @@ -24,10 +25,15 @@ CScoreboard::CScoreboard() void CScoreboard::ConKeyScoreboard(IConsole::IResult *pResult, void *pUserData) { - CScoreboard *pSelf = (CScoreboard *)pUserData; + CScoreboard *pSelf = static_cast(pUserData); pSelf->m_Active = pResult->GetInteger(0) != 0; } +void CScoreboard::OnConsoleInit() +{ + Console()->Register("+scoreboard", "", CFGFLAG_CLIENT, ConKeyScoreboard, this, "Show scoreboard"); +} + void CScoreboard::OnReset() { m_Active = false; @@ -41,216 +47,216 @@ void CScoreboard::OnRelease() void CScoreboard::OnMessage(int MsgType, void *pRawMsg) { - if(MsgType == NETMSGTYPE_SV_RECORD || MsgType == NETMSGTYPE_SV_RECORDLEGACY) + if(MsgType == NETMSGTYPE_SV_RECORD) { - CNetMsg_Sv_Record *pMsg = (CNetMsg_Sv_Record *)pRawMsg; - m_ServerRecord = (float)pMsg->m_ServerTimeBest / 100; + CNetMsg_Sv_Record *pMsg = static_cast(pRawMsg); + m_ServerRecord = pMsg->m_ServerTimeBest / 100.0f; + } + else if(MsgType == NETMSGTYPE_SV_RECORDLEGACY) + { + CNetMsg_Sv_RecordLegacy *pMsg = static_cast(pRawMsg); + m_ServerRecord = pMsg->m_ServerTimeBest / 100.0f; } } -void CScoreboard::OnConsoleInit() +void CScoreboard::RenderTitle(CUIRect TitleBar, int Team, const char *pTitle) { - Console()->Register("+scoreboard", "", CFGFLAG_CLIENT, ConKeyScoreboard, this, "Show scoreboard"); -} + dbg_assert(Team == TEAM_RED || Team == TEAM_BLUE, "Team invalid"); -void CScoreboard::RenderGoals(float x, float y, float w) -{ - float h = 50.0f; + const CNetObj_GameInfo *pGameInfoObj = GameClient()->m_Snap.m_pGameInfoObj; - Graphics()->DrawRect(x, y, w, h, ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f), IGraphics::CORNER_ALL, 10.0f); - - // render goals - if(m_pClient->m_Snap.m_pGameInfoObj) + char aScore[128] = ""; + if(GameClient()->m_GameInfo.m_TimeScore) { - if(m_pClient->m_Snap.m_pGameInfoObj->m_ScoreLimit) + if(m_ServerRecord > 0) { - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%s: %d", Localize("Score limit"), m_pClient->m_Snap.m_pGameInfoObj->m_ScoreLimit); - TextRender()->Text(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(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(20.0f, aBuf, -1, -1.0f); - TextRender()->Text(x + w - tw - 10.0f, y + (h - 20.f) / 2.f, 20.0f, aBuf, -1.0f); + str_time_float(m_ServerRecord, TIME_HOURS, aScore, sizeof(aScore)); } } -} - -void CScoreboard::RenderSpectators(float x, float y, float w, float h) -{ - // background - Graphics()->DrawRect(x, y, w, h, ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f), IGraphics::CORNER_ALL, 10.0f); - - // Headline - y += 10.0f; - TextRender()->Text(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_apInfoByName) + else if(pGameInfoObj && (pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS)) { - if(!pInfo || pInfo->m_Team != TEAM_SPECTATORS) - continue; - - if(Multiple) - TextRender()->TextEx(&Cursor, ", ", 2); - - if(m_pClient->m_aClients[pInfo->m_ClientId].m_AuthLevel) + const CNetObj_GameData *pGameDataObj = GameClient()->m_Snap.m_pGameDataObj; + if(pGameDataObj) { - ColorRGBA Color = color_cast(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 - { - int Corners; - if(upper16 || upper32 || upper24) - Corners = IGraphics::CORNER_R; - else if(lower16 || lower32 || lower24) - Corners = IGraphics::CORNER_L; - else - Corners = IGraphics::CORNER_ALL; - Graphics()->DrawRect(x, y, w, h, ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f), Corners, 17.0f); - } - - 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()); - while(TextRender()->TextWidth(TitleFontsize, aBuf, -1, -1.0f) > TitleWidth) - aBuf[str_length(aBuf) - 1] = '\0'; - if(str_comp(aBuf, Client()->GetCurrentMap())) - str_append(aBuf, "…"); - pTitle = aBuf; - } - } - TextRender()->Text(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); + str_format(aScore, sizeof(aScore), "%d", Team == TEAM_RED ? pGameDataObj->m_TeamscoreRed : pGameDataObj->m_TeamscoreBlue); } } 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_apPlayerInfos[m_pClient->m_Snap.m_SpecInfo.m_SpectatorId]) + if(GameClient()->m_Snap.m_SpecInfo.m_Active && + GameClient()->m_Snap.m_SpecInfo.m_SpectatorId != SPEC_FREEVIEW && + GameClient()->m_Snap.m_apPlayerInfos[GameClient()->m_Snap.m_SpecInfo.m_SpectatorId]) { - int Score = m_pClient->m_Snap.m_apPlayerInfos[m_pClient->m_Snap.m_SpecInfo.m_SpectatorId]->m_Score; - str_format(aBuf, sizeof(aBuf), "%d", Score); + str_format(aScore, sizeof(aScore), "%d", GameClient()->m_Snap.m_apPlayerInfos[GameClient()->m_Snap.m_SpecInfo.m_SpectatorId]->m_Score); } - else if(m_pClient->m_Snap.m_pLocalInfo) + else if(GameClient()->m_Snap.m_pLocalInfo) { - int Score = m_pClient->m_Snap.m_pLocalInfo->m_Score; - str_format(aBuf, sizeof(aBuf), "%d", Score); + str_format(aScore, sizeof(aScore), "%d", GameClient()->m_Snap.m_pLocalInfo->m_Score); } } - if(m_pClient->m_GameInfo.m_TimeScore) + const float TitleFontSize = 40.0f; + const float ScoreTextWidth = TextRender()->TextWidth(TitleFontSize, aScore); + + TitleBar.VMargin(20.0f, &TitleBar); + CUIRect TitleLabel, ScoreLabel; + if(Team == TEAM_RED) { - if(m_ServerRecord > 0) - str_time_float(m_ServerRecord, TIME_HOURS, aBuf, sizeof(aBuf)); - else - aBuf[0] = 0; + TitleBar.VSplitRight(ScoreTextWidth, &TitleLabel, &ScoreLabel); + TitleLabel.VSplitRight(10.0f, &TitleLabel, nullptr); + } + else + { + TitleBar.VSplitLeft(ScoreTextWidth, &ScoreLabel, &TitleLabel); + TitleLabel.VSplitLeft(10.0f, nullptr, &TitleLabel); } - float tw; - - if(!lower16 && !lower32 && !lower24) { - tw = TextRender()->TextWidth(TitleFontsize, aBuf, -1, -1.0f); - TextRender()->Text(x + w - tw - 20.0f, y + (50.f - TitleFontsize) / 2.f, TitleFontsize, aBuf, -1.0f); + SLabelProperties Props; + Props.m_MaxWidth = TitleLabel.w; + Props.m_EllipsisAtEnd = true; + Ui()->DoLabel(&TitleLabel, pTitle, TitleFontSize, Team == TEAM_RED ? TEXTALIGN_ML : TEXTALIGN_MR, Props); } + if(aScore[0] != '\0') + { + Ui()->DoLabel(&ScoreLabel, aScore, TitleFontSize, Team == TEAM_RED ? TEXTALIGN_MR : TEXTALIGN_ML); + } +} + +void CScoreboard::RenderGoals(CUIRect Goals) +{ + Goals.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f), IGraphics::CORNER_ALL, 15.0f); + Goals.VMargin(10.0f, &Goals); + + const float FontSize = 20.0f; + const CNetObj_GameInfo *pGameInfoObj = GameClient()->m_Snap.m_pGameInfoObj; + char aBuf[64]; + + if(pGameInfoObj->m_ScoreLimit) + { + str_format(aBuf, sizeof(aBuf), "%s: %d", Localize("Score limit"), pGameInfoObj->m_ScoreLimit); + Ui()->DoLabel(&Goals, aBuf, FontSize, TEXTALIGN_ML); + } + + if(pGameInfoObj->m_TimeLimit) + { + str_format(aBuf, sizeof(aBuf), Localize("Time limit: %d min"), pGameInfoObj->m_TimeLimit); + Ui()->DoLabel(&Goals, aBuf, FontSize, TEXTALIGN_MC); + } + + if(pGameInfoObj->m_RoundNum && pGameInfoObj->m_RoundCurrent) + { + str_format(aBuf, sizeof(aBuf), Localize("Round %d/%d"), pGameInfoObj->m_RoundCurrent, pGameInfoObj->m_RoundNum); + Ui()->DoLabel(&Goals, aBuf, FontSize, TEXTALIGN_MR); + } +} + +void CScoreboard::RenderSpectators(CUIRect Spectators) +{ + Spectators.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f), IGraphics::CORNER_ALL, 15.0f); + Spectators.Margin(10.0f, &Spectators); + + CTextCursor Cursor; + TextRender()->SetCursor(&Cursor, Spectators.x, Spectators.y, 22.0f, TEXTFLAG_RENDER); + Cursor.m_LineWidth = Spectators.w; + Cursor.m_MaxLines = round_truncate(Spectators.h / Cursor.m_FontSize); + + int RemainingSpectators = 0; + for(const CNetObj_PlayerInfo *pInfo : GameClient()->m_Snap.m_apInfoByName) + { + if(!pInfo || pInfo->m_Team != TEAM_SPECTATORS) + continue; + ++RemainingSpectators; + } + + TextRender()->TextEx(&Cursor, Localize("Spectators")); + + if(RemainingSpectators > 0) + { + TextRender()->TextEx(&Cursor, ": "); + } + + bool CommaNeeded = false; + for(const CNetObj_PlayerInfo *pInfo : GameClient()->m_Snap.m_apInfoByName) + { + if(!pInfo || pInfo->m_Team != TEAM_SPECTATORS) + continue; + + if(CommaNeeded) + { + TextRender()->TextEx(&Cursor, ", "); + } + + if(Cursor.m_LineCount == Cursor.m_MaxLines && RemainingSpectators >= 2) + { + // This is less expensive than checking with a separate invisible + // text cursor though we waste some space at the end of the line. + char aRemaining[64]; + str_format(aRemaining, sizeof(aRemaining), Localize("%d others…", "Spectators"), RemainingSpectators); + TextRender()->TextEx(&Cursor, aRemaining); + break; + } + + if(GameClient()->m_aClients[pInfo->m_ClientId].m_AuthLevel) + { + TextRender()->TextColor(color_cast(ColorHSLA(g_Config.m_ClAuthedPlayerColor))); + } + + if(g_Config.m_ClShowIds) + { + char aClientId[5]; + str_format(aClientId, sizeof(aClientId), "%d: ", pInfo->m_ClientId); + TextRender()->TextEx(&Cursor, aClientId); + } + TextRender()->TextEx(&Cursor, GameClient()->m_aClients[pInfo->m_ClientId].m_aName); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + + CommaNeeded = true; + --RemainingSpectators; + } +} + +void CScoreboard::RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart, int CountEnd) +{ + dbg_assert(Team == TEAM_RED || Team == TEAM_BLUE, "Team invalid"); + + const CNetObj_GameInfo *pGameInfoObj = GameClient()->m_Snap.m_pGameInfoObj; + const CNetObj_GameData *pGameDataObj = GameClient()->m_Snap.m_pGameDataObj; + const bool TimeScore = GameClient()->m_GameInfo.m_TimeScore; + const int NumPlayers = CountEnd - CountStart; + // 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) + float LineHeight; + float TeeSizeMod; + float Spacing; + float RoundRadius; + float FontSize; + if(NumPlayers <= 8) { - LineHeight = 20.0f; - TeeSizeMod = 0.4f; + LineHeight = 60.0f; + TeeSizeMod = 1.0f; + Spacing = 16.0f; + RoundRadius = 10.0f; + FontSize = 24.0f; + } + else if(NumPlayers <= 12) + { + LineHeight = 50.0f; + TeeSizeMod = 0.9f; + Spacing = 5.0f; + RoundRadius = 10.0f; + FontSize = 24.0f; + } + else if(NumPlayers <= 16) + { + LineHeight = 40.0f; + TeeSizeMod = 0.8f; Spacing = 0.0f; RoundRadius = 5.0f; - FontSize = 16.0f; + FontSize = 24.0f; } - else if(NumPlayers > 32) + else if(NumPlayers <= 24) { LineHeight = 27.0f; TeeSizeMod = 0.6f; @@ -258,121 +264,107 @@ void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const ch RoundRadius = 5.0f; FontSize = 20.0f; } - else if(NumPlayers > 12) + else { - LineHeight = 40.0f; - TeeSizeMod = 0.8f; + LineHeight = 20.0f; + TeeSizeMod = 0.4f; Spacing = 0.0f; - RoundRadius = 15.0f; - } - else if(NumPlayers > 8) - { - LineHeight = 50.0f; - TeeSizeMod = 0.9f; - Spacing = 5.0f; - RoundRadius = 15.0f; + RoundRadius = 5.0f; + FontSize = 16.0f; } - float ScoreOffset = x + 10.0f + 10.0f, ScoreLength = TextRender()->TextWidth(FontSize, "00:00:00", -1, -1.0f); - if(IsTeamplayTeam) - ScoreLength = TextRender()->TextWidth(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; + const float ScoreOffset = Scoreboard.x + 10.0f + 10.0f; + const float ScoreLength = TextRender()->TextWidth(FontSize, TimeScore ? "00:00:00" : "99999"); + const float TeeOffset = ScoreOffset + ScoreLength + 15.0f; + const float TeeLength = 60.0f * TeeSizeMod; + const float NameOffset = TeeOffset + TeeLength; + const float NameLength = 300.0f - TeeLength; + const float CountryLength = (LineHeight - Spacing - TeeSizeMod * 5.0f) * 2.0f; + const float PingLength = 65.0f; + const float PingOffset = Scoreboard.x + Scoreboard.w - PingLength - 10.0f - 10.0f; + const float CountryOffset = PingOffset - CountryLength; + const float ClanLength = Scoreboard.w - ((NameOffset - Scoreboard.x) + NameLength) - (Scoreboard.w - (CountryOffset - Scoreboard.x)); + const 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 ? Localize("Time") : Localize("Score"); - tw = TextRender()->TextWidth(HeadlineFontsize, pScore, -1, -1.0f); - TextRender()->Text(ScoreOffset + ScoreLength - tw, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, pScore, -1.0f); - - TextRender()->Text(NameOffset, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, Localize("Name"), -1.0f); - - tw = TextRender()->TextWidth(HeadlineFontsize, Localize("Clan"), -1, -1.0f); - TextRender()->Text(ClanOffset + (ClanLength - tw) / 2, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, Localize("Clan"), -1.0f); - - tw = TextRender()->TextWidth(HeadlineFontsize, Localize("Ping"), -1, -1.0f); - TextRender()->Text(PingOffset + PingLength - tw, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, Localize("Ping"), -1.0f); + const float HeadlineFontsize = 22.0f; + CUIRect Headline; + Scoreboard.HSplitTop(HeadlineFontsize * 2.0f, &Headline, &Scoreboard); + const float HeadlineY = Headline.y + Headline.h / 2.0f - HeadlineFontsize / 2.0f; + const char *pScore = TimeScore ? Localize("Time") : Localize("Score"); + TextRender()->Text(ScoreOffset + ScoreLength - TextRender()->TextWidth(HeadlineFontsize, pScore), HeadlineY, HeadlineFontsize, pScore); + TextRender()->Text(NameOffset, HeadlineY, HeadlineFontsize, Localize("Name")); + const char *pClanLabel = Localize("Clan"); + TextRender()->Text(ClanOffset + (ClanLength - TextRender()->TextWidth(HeadlineFontsize, pClanLabel)) / 2.0f, HeadlineY, HeadlineFontsize, pClanLabel); + const char *pPingLabel = Localize("Ping"); + TextRender()->Text(PingOffset + PingLength - TextRender()->TextWidth(HeadlineFontsize, pPingLabel), HeadlineY, HeadlineFontsize, pPingLabel); // 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; + int CountRendered = 0; + int PrevDDTeam = -1; + char aBuf[64]; 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_apInfoByDDTeamScore[i]; + const CNetObj_PlayerInfo *pInfo = GameClient()->m_Snap.m_apInfoByDDTeamScore[i]; if(!pInfo || pInfo->m_Team != Team) continue; - if(rendered++ < 0) + if(CountRendered++ < CountStart) continue; - int DDTeam = m_pClient->m_Teams.Team(pInfo->m_ClientId); + int DDTeam = GameClient()->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_apInfoByDDTeamScore[j]; - - if(!pInfo2 || pInfo2->m_Team != Team) + const CNetObj_PlayerInfo *pInfoNext = GameClient()->m_Snap.m_apInfoByDDTeamScore[j]; + if(!pInfoNext || pInfoNext->m_Team != Team) continue; - NextDDTeam = m_pClient->m_Teams.Team(pInfo2->m_ClientId); + NextDDTeam = GameClient()->m_Teams.Team(pInfoNext->m_ClientId); break; } - if(OldDDTeam == -1) + if(PrevDDTeam == -1) { for(int j = i - 1; j >= 0; j--) { - const CNetObj_PlayerInfo *pInfo2 = m_pClient->m_Snap.m_apInfoByDDTeamScore[j]; - - if(!pInfo2 || pInfo2->m_Team != Team) + const CNetObj_PlayerInfo *pInfoPrev = GameClient()->m_Snap.m_apInfoByDDTeamScore[j]; + if(!pInfoPrev || pInfoPrev->m_Team != Team) continue; - OldDDTeam = m_pClient->m_Teams.Team(pInfo2->m_ClientId); + PrevDDTeam = GameClient()->m_Teams.Team(pInfoPrev->m_ClientId); break; } } + CUIRect RowAndSpacing, Row; + Scoreboard.HSplitTop(LineHeight + Spacing, &RowAndSpacing, &Scoreboard); + RowAndSpacing.HSplitTop(LineHeight, &Row, nullptr); + + // team background if(DDTeam != TEAM_FLOCK) { - const ColorRGBA Color = m_pClient->GetDDTeamColor(DDTeam).WithAlpha(0.5f); - int Corners = 0; - if(OldDDTeam != DDTeam) - Corners |= IGraphics::CORNER_TL | IGraphics::CORNER_TR; + const ColorRGBA Color = GameClient()->GetDDTeamColor(DDTeam).WithAlpha(0.5f); + int TeamRectCorners = 0; + if(PrevDDTeam != DDTeam) + TeamRectCorners |= IGraphics::CORNER_T; if(NextDDTeam != DDTeam) - Corners |= IGraphics::CORNER_BL | IGraphics::CORNER_BR; - Graphics()->DrawRect(x - 10.0f, y, w, LineHeight + Spacing, Color, Corners, RoundRadius); + TeamRectCorners |= IGraphics::CORNER_B; + RowAndSpacing.Draw(Color, TeamRectCorners, RoundRadius); if(NextDDTeam != DDTeam) { - if(m_pClient->m_Snap.m_aTeamSize[0] > 8) + const float TeamFontSize = FontSize / 1.5f; + if(NumPlayers > 8) { if(DDTeam == TEAM_SUPER) str_copy(aBuf, Localize("Super")); 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; + TextRender()->Text(Row.x, Row.y + Row.h / 2.0f - TeamFontSize / 2.0f, TeamFontSize, aBuf); } else { @@ -380,144 +372,120 @@ void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const ch str_copy(aBuf, Localize("Super")); else str_format(aBuf, sizeof(aBuf), Localize("Team %d"), DDTeam); - tw = TextRender()->TextWidth(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()->Text(Row.x + Row.w / 2.0f - TextRender()->TextWidth(TeamFontSize, aBuf) / 2.0f + 10.0f, Row.y + Row.h, TeamFontSize, aBuf); } - TextRender()->TextEx(&Cursor, aBuf, -1); } } - - OldDDTeam = DDTeam; + PrevDDTeam = 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)) + if((!GameClient()->m_Snap.m_SpecInfo.m_Active && pInfo->m_Local) || + (GameClient()->m_Snap.m_SpecInfo.m_SpectatorId == SPEC_FREEVIEW && pInfo->m_Local) || + (GameClient()->m_Snap.m_SpecInfo.m_Active && pInfo->m_ClientId == GameClient()->m_Snap.m_SpecInfo.m_SpectatorId)) { - Graphics()->DrawRect(x, y, w - 20.0f, LineHeight, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, RoundRadius); + CUIRect Highlight; + Row.VMargin(10.0f, &Highlight); + Highlight.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, RoundRadius); } // score - if(m_pClient->m_GameInfo.m_TimeScore) + if(TimeScore) { if(pInfo->m_Score == -9999) - aBuf[0] = 0; + { + aBuf[0] = '\0'; + } else + { str_time((int64_t)absolute(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(FontSize, aBuf, -1, -1.0f); - TextRender()->SetCursor(&Cursor, ScoreOffset + ScoreLength - tw, y + (LineHeight - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER); - TextRender()->TextEx(&Cursor, aBuf, -1); + } + TextRender()->Text(ScoreOffset + ScoreLength - TextRender()->TextWidth(FontSize, aBuf), Row.y + (Row.h - FontSize) / 2.0f, FontSize, aBuf); - // 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)) + // CTF flag + if(pGameInfoObj && (pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS) && + pGameDataObj && (pGameDataObj->m_FlagCarrierRed == pInfo->m_ClientId || 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()->TextureSet(pGameDataObj->m_FlagCarrierBlue == pInfo->m_ClientId ? GameClient()->m_GameSkin.m_SpriteFlagBlue : 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()->QuadsSetSubset(1.0f, 0.0f, 0.0f, 1.0f); + IGraphics::CQuadItem QuadItem(TeeOffset, Row.y - 5.0f - Spacing / 2.0f, Row.h / 2.0f, Row.h); Graphics()->QuadsDrawTL(&QuadItem, 1); Graphics()->QuadsEnd(); } - // avatar - CTeeRenderInfo TeeInfo = m_pClient->m_aClients[pInfo->m_ClientId].m_RenderInfo; - TeeInfo.m_Size *= TeeSizeMod; - const CAnimState *pIdleState = CAnimState::GetIdle(); - vec2 OffsetToMid; - CRenderTools::GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); - vec2 TeeRenderPos(TeeOffset + TeeLength / 2, y + LineHeight / 2.0f + OffsetToMid.y); + const CGameClient::CClientData &ClientData = GameClient()->m_aClients[pInfo->m_ClientId]; - 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_ELLIPSIS_AT_END); - if(m_pClient->m_aClients[pInfo->m_ClientId].m_AuthLevel) + // skin { - ColorRGBA Color = color_cast(ColorHSLA(g_Config.m_ClAuthedPlayerColor)); - TextRender()->TextColor(Color); + CTeeRenderInfo TeeInfo = ClientData.m_RenderInfo; + TeeInfo.m_Size *= TeeSizeMod; + vec2 OffsetToMid; + CRenderTools::GetRenderTeeOffsetToRenderedTee(CAnimState::GetIdle(), &TeeInfo, OffsetToMid); + const vec2 TeeRenderPos = vec2(TeeOffset + TeeLength / 2, Row.y + Row.h / 2.0f + OffsetToMid.y); + RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); } - if(g_Config.m_ClShowIds) + // name { - char aId[64] = ""; - if(pInfo->m_ClientId < 10) + CTextCursor Cursor; + TextRender()->SetCursor(&Cursor, NameOffset, Row.y + (Row.h - FontSize) / 2.0f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_ELLIPSIS_AT_END); + Cursor.m_LineWidth = NameLength; + if(ClientData.m_AuthLevel) { - str_format(aId, sizeof(aId), " %d: %s", pInfo->m_ClientId, m_pClient->m_aClients[pInfo->m_ClientId].m_aName); + TextRender()->TextColor(color_cast(ColorHSLA(g_Config.m_ClAuthedPlayerColor))); + } + if(g_Config.m_ClShowIds) + { + str_format(aBuf, sizeof(aBuf), "%s%d: %s", pInfo->m_ClientId < 10 ? " " : "", pInfo->m_ClientId, ClientData.m_aName); + TextRender()->TextEx(&Cursor, aBuf); } else { - str_format(aId, sizeof(aId), "%d: %s", pInfo->m_ClientId, m_pClient->m_aClients[pInfo->m_ClientId].m_aName); + TextRender()->TextEx(&Cursor, ClientData.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_aLocalIds[g_Config.m_ClDummy]].m_aClan) == 0) { - ColorRGBA Color = color_cast(ColorHSLA(g_Config.m_ClSameClanColor)); - TextRender()->TextColor(Color); + if(str_comp(ClientData.m_aClan, GameClient()->m_aClients[GameClient()->m_aLocalIds[g_Config.m_ClDummy]].m_aClan) == 0) + { + TextRender()->TextColor(color_cast(ColorHSLA(g_Config.m_ClSameClanColor))); + } + else + { + TextRender()->TextColor(TextRender()->DefaultTextColor()); + } + CTextCursor Cursor; + TextRender()->SetCursor(&Cursor, ClanOffset + (ClanLength - minimum(TextRender()->TextWidth(FontSize, ClientData.m_aClan), ClanLength)) / 2.0f, Row.y + (Row.h - FontSize) / 2.0f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_ELLIPSIS_AT_END); + Cursor.m_LineWidth = ClanLength; + TextRender()->TextEx(&Cursor, ClientData.m_aClan); } - else - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - - tw = minimum(TextRender()->TextWidth(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_ELLIPSIS_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 - m_pClient->m_CountryFlags.Render(m_pClient->m_aClients[pInfo->m_ClientId].m_Country, ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f), - CountryOffset, y + (Spacing + TeeSizeMod * 5.0f) / 2.0f, CountryLength, LineHeight - Spacing - TeeSizeMod * 5.0f); + GameClient()->m_CountryFlags.Render(ClientData.m_Country, ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f), + CountryOffset, Row.y + (Spacing + TeeSizeMod * 5.0f) / 2.0f, CountryLength, Row.h - Spacing - TeeSizeMod * 5.0f); // ping if(g_Config.m_ClEnablePingColor) { - ColorRGBA rgb = color_cast(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(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; + TextRender()->TextColor(color_cast(ColorHSLA((300.0f - clamp(pInfo->m_Latency, 0, 300)) / 1000.0f, 1.0f, 0.5f))); } else { - if(rendered == 16) - break; + TextRender()->TextColor(TextRender()->DefaultTextColor()); } + str_format(aBuf, sizeof(aBuf), "%d", clamp(pInfo->m_Latency, 0, 999)); + TextRender()->Text(PingOffset + PingLength - TextRender()->TextWidth(FontSize, aBuf), Row.y + (Row.h - FontSize) / 2.0f, FontSize, aBuf); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + + if(CountRendered == CountEnd) + break; } } @@ -570,140 +538,176 @@ void CScoreboard::OnRender() 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; + if(GameClient()->m_Motd.IsActive()) + GameClient()->m_Motd.Clear(); + const float Height = 400.0f * 3.0f; + const float Width = Height * Graphics()->ScreenAspect(); Graphics()->MapScreen(0, 0, Width, Height); - float w = 750.0f; - float ExtraWidthSingle = 20.0f; + const CNetObj_GameInfo *pGameInfoObj = GameClient()->m_Snap.m_pGameInfoObj; + const bool Teams = pGameInfoObj && (pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS); + const int NumPlayers = maximum(GameClient()->m_Snap.m_aTeamSize[TEAM_RED], GameClient()->m_Snap.m_aTeamSize[TEAM_BLUE]); - if(m_pClient->m_Snap.m_pGameInfoObj) + const float ScoreboardSmallWidth = 750.0f + 20.0f; + const float ScoreboardWidth = !Teams && NumPlayers <= 16 ? ScoreboardSmallWidth : 1500.0f; + const float TitleHeight = 60.0f; + + CUIRect Scoreboard = {(Width - ScoreboardWidth) / 2.0f, 150.0f, ScoreboardWidth, 710.0f + TitleHeight}; + + if(Teams) { - if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS)) + const char *pRedTeamName = GetTeamName(TEAM_RED); + const char *pBlueTeamName = GetTeamName(TEAM_BLUE); + + // Game over title + const CNetObj_GameData *pGameDataObj = GameClient()->m_Snap.m_pGameDataObj; + if((pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER) && pGameDataObj) { - if(m_pClient->m_Snap.m_aTeamSize[0] > 48) + char aTitle[256]; + if(pGameDataObj->m_TeamscoreRed > pGameDataObj->m_TeamscoreBlue) { - RenderScoreboard(Width / 2, 150.0f, w, -5, ""); - RenderScoreboard(Width / 2 - w, 150.0f, w, -4, 0); + TextRender()->TextColor(ColorRGBA(0.975f, 0.17f, 0.17f, 1.0f)); + str_format(aTitle, sizeof(aTitle), Localize("%s wins!"), pRedTeamName); } - else if(m_pClient->m_Snap.m_aTeamSize[0] > 32) + else if(pGameDataObj->m_TeamscoreBlue > pGameDataObj->m_TeamscoreRed) { - 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); + TextRender()->TextColor(ColorRGBA(0.17f, 0.46f, 0.975f, 1.0f)); + str_format(aTitle, sizeof(aTitle), Localize("%s wins!"), pBlueTeamName); } 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!")); - - 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!")); - } - 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!")); - } - - float TextWidth = TextRender()->TextWidth(86.0f, aText, -1, -1.0f); - TextRender()->Text(Width / 2 - TextWidth / 2, 39, 86.0f, aText, -1.0f); + TextRender()->TextColor(ColorRGBA(0.91f, 0.78f, 0.33f, 1.0f)); + str_copy(aTitle, Localize("Draw!")); } - //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); + const float TitleFontSize = 72.0f; + CUIRect GameOverTitle = {Scoreboard.x, Scoreboard.y - TitleFontSize - 12.0f, Scoreboard.w, TitleFontSize}; + Ui()->DoLabel(&GameOverTitle, aTitle, TitleFontSize, TEXTALIGN_MC); + TextRender()->TextColor(TextRender()->DefaultTextColor()); } - } - if(m_pClient->m_Snap.m_pGameInfoObj && (m_pClient->m_Snap.m_pGameInfoObj->m_ScoreLimit || m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit || (m_pClient->m_Snap.m_pGameInfoObj->m_RoundNum && m_pClient->m_Snap.m_pGameInfoObj->m_RoundCurrent))) - { - RenderGoals(Width / 2 - w / 2, 150 + 760 + 10, w); - RenderSpectators(Width / 2 - w / 2, 150 + 760 + 10 + 50 + 10, w, 160.0f); + + CUIRect RedScoreboard, BlueScoreboard, RedTitle, BlueTitle; + Scoreboard.VSplitMid(&RedScoreboard, &BlueScoreboard, 15.0f); + RedScoreboard.HSplitTop(TitleHeight, &RedTitle, &RedScoreboard); + BlueScoreboard.HSplitTop(TitleHeight, &BlueTitle, &BlueScoreboard); + + RedTitle.Draw(ColorRGBA(0.975f, 0.17f, 0.17f, 0.5f), IGraphics::CORNER_T, 15.0f); + BlueTitle.Draw(ColorRGBA(0.17f, 0.46f, 0.975f, 0.5f), IGraphics::CORNER_T, 15.0f); + RedScoreboard.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f), IGraphics::CORNER_B, 15.0f); + BlueScoreboard.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f), IGraphics::CORNER_B, 15.0f); + + RenderTitle(RedTitle, TEAM_RED, pRedTeamName); + RenderTitle(BlueTitle, TEAM_BLUE, pBlueTeamName); + RenderScoreboard(RedScoreboard, TEAM_RED, 0, NumPlayers); + RenderScoreboard(BlueScoreboard, TEAM_BLUE, 0, NumPlayers); } else { - RenderSpectators(Width / 2 - w / 2, 150 + 760 + 10, w, 200.0f); + Scoreboard.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f), IGraphics::CORNER_ALL, 15.0f); + + const char *pTitle; + if(pGameInfoObj && (pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)) + { + pTitle = Localize("Game over"); + } + else + { + pTitle = Client()->GetCurrentMap(); + } + + CUIRect Title; + Scoreboard.HSplitTop(TitleHeight, &Title, &Scoreboard); + RenderTitle(Title, TEAM_RED, pTitle); + + if(NumPlayers <= 16) + { + RenderScoreboard(Scoreboard, TEAM_RED, 0, NumPlayers); + } + else + { + int PlayersPerSide; + if(NumPlayers <= 24) + PlayersPerSide = 12; + else if(NumPlayers <= 32) + PlayersPerSide = 16; + else if(NumPlayers <= 48) + PlayersPerSide = 24; + else + PlayersPerSide = 32; + + CUIRect LeftScoreboard, RightScoreboard; + Scoreboard.VSplitMid(&LeftScoreboard, &RightScoreboard); + RenderScoreboard(LeftScoreboard, TEAM_RED, 0, PlayersPerSide); + RenderScoreboard(RightScoreboard, TEAM_RED, PlayersPerSide, 2 * PlayersPerSide); + } } + CUIRect Spectators = {(Width - ScoreboardSmallWidth) / 2.0f, Scoreboard.y + Scoreboard.h + 10.0f, ScoreboardSmallWidth, 200.0f}; + if(pGameInfoObj && (pGameInfoObj->m_ScoreLimit || pGameInfoObj->m_TimeLimit || (pGameInfoObj->m_RoundNum && pGameInfoObj->m_RoundCurrent))) + { + CUIRect Goals; + Spectators.HSplitTop(50.0f, &Goals, &Spectators); + Spectators.HSplitTop(10.0f, nullptr, &Spectators); + RenderGoals(Goals); + } + RenderSpectators(Spectators); + RenderRecordingNotification((Width / 7) * 4 + 20); } -bool CScoreboard::Active() +bool CScoreboard::Active() const { // if statboard is active don't show scoreboard - if(m_pClient->m_Statboard.IsActive()) + if(GameClient()->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) + if(GameClient()->m_Snap.m_pLocalInfo && !GameClient()->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) + if(!GameClient()->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) + const CNetObj_GameInfo *pGameInfoObj = GameClient()->m_Snap.m_pGameInfoObj; + if(pGameInfoObj && pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER) return true; return false; } -const char *CScoreboard::GetClanName(int Team) +const char *CScoreboard::GetTeamName(int Team) const { + dbg_assert(Team == TEAM_RED || Team == TEAM_BLUE, "Team invalid"); + int ClanPlayers = 0; - const char *pClanName = 0; - for(const auto *pInfo : m_pClient->m_Snap.m_apInfoByScore) + const char *pClanName = nullptr; + const char *pDefaultTeamName = Team == TEAM_RED ? Localize("Red team") : Localize("Blue team"); + for(const CNetObj_PlayerInfo *pInfo : GameClient()->m_Snap.m_apInfoByScore) { if(!pInfo || pInfo->m_Team != Team) continue; if(!pClanName) { - pClanName = m_pClient->m_aClients[pInfo->m_ClientId].m_aClan; + pClanName = GameClient()->m_aClients[pInfo->m_ClientId].m_aClan; ClanPlayers++; } else { - if(str_comp(m_pClient->m_aClients[pInfo->m_ClientId].m_aClan, pClanName) == 0) + if(str_comp(GameClient()->m_aClients[pInfo->m_ClientId].m_aClan, pClanName) == 0) ClanPlayers++; else - return 0; + return pDefaultTeamName; } } - if(ClanPlayers > 1 && pClanName[0]) + if(ClanPlayers > 1 && pClanName[0] != '\0') return pClanName; else - return 0; + return pDefaultTeamName; } diff --git a/src/game/client/components/scoreboard.h b/src/game/client/components/scoreboard.h index e45b781cf..5532cf988 100644 --- a/src/game/client/components/scoreboard.h +++ b/src/game/client/components/scoreboard.h @@ -6,36 +6,32 @@ #include #include +#include class CScoreboard : public CComponent { - void RenderGoals(float x, float y, float w); - void RenderSpectators(float x, float y, float w, float h); - void RenderScoreboard(float x, float y, float w, int Team, const char *pTitle, int NumPlayers = -1); + void RenderTitle(CUIRect TitleBar, int Team, const char *pTitle); + void RenderGoals(CUIRect Goals); + void RenderSpectators(CUIRect Spectators); + void RenderScoreboard(CUIRect Scoreboard, int Team, int CountStart, int CountEnd); void RenderRecordingNotification(float x); static void ConKeyScoreboard(IConsole::IResult *pResult, void *pUserData); - - const char *GetClanName(int Team); + const char *GetTeamName(int Team) const; bool m_Active; + float m_ServerRecord; public: CScoreboard(); virtual int Sizeof() const override { return sizeof(*this); } - virtual void OnReset() override; virtual void OnConsoleInit() override; + virtual void OnReset() override; virtual void OnRender() override; virtual void OnRelease() override; - - bool Active(); - - // DDRace - virtual void OnMessage(int MsgType, void *pRawMsg) override; -private: - float m_ServerRecord; + bool Active() const; }; #endif