/* (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 #include #include #include #include #include #include #include #include #include #include #include "scoreboard.h" CScoreboard::CScoreboard() { OnReset(); } void CScoreboard::ConKeyScoreboard(IConsole::IResult *pResult, void *pUserData) { ((CScoreboard *)pUserData)->m_Active = pResult->GetInteger(0) != 0; } void CScoreboard::OnReset() { m_Active = false; } void CScoreboard::OnRelease() { m_Active = false; } 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()->TextureSet(-1); Graphics()->QuadsBegin(); Graphics()->SetColor(0,0,0,0.5f); RenderTools()->DrawRoundRect(x-10.f, y-10.f, 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, y, 20.0f, aBuf, -1); } 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+220.0f, y, 20.0f, aBuf, -1); } 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); TextRender()->Text(0, x+w-tw-20.0f, y, 20.0f, aBuf, -1); } } } void CScoreboard::RenderSpectators(float x, float y, float w) { char aBuffer[1024*4]; int Count = 0; float h = 120.0f; str_format(aBuffer, sizeof(aBuffer), "%s: ", Localize("Spectators")); Graphics()->BlendNormal(); Graphics()->TextureSet(-1); Graphics()->QuadsBegin(); Graphics()->SetColor(0,0,0,0.5f); RenderTools()->DrawRoundRect(x-10.f, y-10.f, w, h, 10.0f); Graphics()->QuadsEnd(); for(int i = 0; i < Client()->SnapNumItems(IClient::SNAP_CURRENT); i++) { IClient::CSnapItem Item; const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, i, &Item); if(Item.m_Type == NETOBJTYPE_PLAYERINFO) { const CNetObj_PlayerInfo *pInfo = (const CNetObj_PlayerInfo *)pData; if(pInfo->m_Team == TEAM_SPECTATORS) { if(Count) str_append(aBuffer, ", ", sizeof(aBuffer)); str_append(aBuffer, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, sizeof(aBuffer)); Count++; } } } TextRender()->Text(0, x+10, y, 32, aBuffer, (int)w-20); } void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const char *pTitle) { if(Team == TEAM_SPECTATORS) return; //float ystart = y; float h = 740.0f; Graphics()->BlendNormal(); Graphics()->TextureSet(-1); Graphics()->QuadsBegin(); Graphics()->SetColor(0,0,0,0.5f); RenderTools()->DrawRoundRect(x-10.f, y, w, h, 17.0f); Graphics()->QuadsEnd(); // render title if(!pTitle) { if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER) pTitle = Localize("Game over"); else pTitle = Localize("Score board"); } float tw = TextRender()->TextWidth(0, 48, pTitle, -1); TextRender()->Text(0, x+10, y, 48, pTitle, -1); if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) { if(m_pClient->m_Snap.m_pGameDataObj) { char aBuf[128]; 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); tw = TextRender()->TextWidth(0, 48, aBuf, -1); TextRender()->Text(0, x+w-tw-30, y, 48, aBuf, -1); } } else { if(m_pClient->m_Snap.m_pLocalInfo) { char aBuf[128]; int Score = m_pClient->m_Snap.m_pLocalInfo->m_Score; str_format(aBuf, sizeof(aBuf), "%d", Score); tw = TextRender()->TextWidth(0, 48, aBuf, -1); TextRender()->Text(0, x+w-tw-30, y, 48, aBuf, -1); } } y += 54.0f; // render headlines TextRender()->Text(0, x+10, y, 24.0f, Localize("Score"), -1); TextRender()->Text(0, x+125, y, 24.0f, Localize("Name"), -1); TextRender()->Text(0, x+w-70, y, 24.0f, Localize("Ping"), -1); y += 29.0f; float FontSize = 35.0f; float LineHeight = 50.0f; float TeeSizeMod = 1.0f; float TeeOffset = 0.0f; if(m_pClient->m_Snap.m_aTeamSize[Team] > 13) { FontSize = 30.0f; LineHeight = 40.0f; TeeSizeMod = 0.8f; TeeOffset = -5.0f; } // render player scores for(int i = 0; i < MAX_CLIENTS; i++) { const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_paInfoByScore[i]; if(!pInfo || pInfo->m_Team != Team) continue; // make sure that we render the correct team char aBuf[128]; if(pInfo->m_Local) { // background so it's easy to find the local player Graphics()->TextureSet(-1); Graphics()->QuadsBegin(); Graphics()->SetColor(1,1,1,0.25f); RenderTools()->DrawRoundRect(x, y, w-20, LineHeight*0.95f, 17.0f); Graphics()->QuadsEnd(); } float FontSizeResize = FontSize; float Width; const float ScoreWidth = 60.0f; const float PingWidth = 60.0f; str_format(aBuf, sizeof(aBuf), "%d", clamp(pInfo->m_Score, -9999, 9999)); while((Width = TextRender()->TextWidth(0, FontSizeResize, aBuf, -1)) > ScoreWidth) --FontSizeResize; TextRender()->Text(0, x+ScoreWidth-Width, y+(FontSize-FontSizeResize)/2, FontSizeResize, aBuf, -1); FontSizeResize = FontSize; while(TextRender()->TextWidth(0, FontSizeResize, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, -1) > w-163.0f-PingWidth) --FontSizeResize; TextRender()->Text(0, x+128.0f, y+(FontSize-FontSizeResize)/2, FontSizeResize, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, -1); FontSizeResize = FontSize; str_format(aBuf, sizeof(aBuf), "%d", clamp(pInfo->m_Latency, -9999, 9999)); while((Width = TextRender()->TextWidth(0, FontSizeResize, aBuf, -1)) > PingWidth) --FontSizeResize; TextRender()->Text(0, x+w-35.0f-Width, y+(FontSize-FontSizeResize)/2, FontSizeResize, aBuf, -1); // render flag if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS && 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(); Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); Graphics()->QuadsBegin(); RenderTools()->SelectSprite(pInfo->m_Team==TEAM_RED ? SPRITE_FLAG_BLUE : SPRITE_FLAG_RED, SPRITE_FLAG_FLIP_X); float size = 64.0f; IGraphics::CQuadItem QuadItem(x+55, y-15, size/2, size); Graphics()->QuadsDrawTL(&QuadItem, 1); Graphics()->QuadsEnd(); } // render avatar CTeeRenderInfo TeeInfo = m_pClient->m_aClients[pInfo->m_ClientID].m_RenderInfo; TeeInfo.m_Size *= TeeSizeMod; RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, EMOTE_NORMAL, vec2(1,0), vec2(x+90, y+28+TeeOffset)); y += LineHeight; } } void CScoreboard::RenderRecordingNotification(float x) { if(!m_pClient->DemoRecorder()->IsRecording()) return; //draw the box Graphics()->BlendNormal(); Graphics()->TextureSet(-1); Graphics()->QuadsBegin(); Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.4f); RenderTools()->DrawRoundRectExt(x, 0.0f, 180.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(); //draw the text char aBuf[64]; int Seconds = m_pClient->DemoRecorder()->Length()/SERVER_TICK_SPEED; str_format(aBuf, sizeof(aBuf), Localize("REC %3d:%02d"), Seconds/60, Seconds%60); TextRender()->Text(0, x+50.0f, 10.0f, 20.0f, aBuf, -1); } void CScoreboard::OnRender() { if(!Active()) return; // if the score board is active, then we should clear the motd message aswell if(m_pClient->m_pMotd->IsActive()) m_pClient->m_pMotd->Clear(); float Width = 400*3.0f*Graphics()->ScreenAspect(); float Height = 400*3.0f; Graphics()->MapScreen(0, 0, Width, Height); float w = 650.0f; if(m_pClient->m_Snap.m_pGameInfoObj) { if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS)) RenderScoreboard(Width/2-w/2, 150.0f, w, 0, 0); else { if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER && m_pClient->m_Snap.m_pGameDataObj) { const char *pText = Localize("Draw!"); if(m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreRed > m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreBlue) pText = Localize("Red team wins!"); else if(m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreBlue > m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreRed) pText = Localize("Blue team wins!"); float w = TextRender()->TextWidth(0, 86.0f, pText, -1); TextRender()->Text(0, Width/2-w/2, 39, 86.0f, pText, -1); } RenderScoreboard(Width/2-w-20, 150.0f, w, TEAM_RED, Localize("Red team")); RenderScoreboard(Width/2 + 20, 150.0f, w, TEAM_BLUE, Localize("Blue team")); } } RenderGoals(Width/2-w/2, 150+750+25, w); RenderSpectators(Width/2-w/2, 150+750+25+50+25, w); RenderRecordingNotification((Width/7)*4); } bool CScoreboard::Active() { // if we activly wanna look on the scoreboard if(m_Active) return true; if(m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS) { // we are not a spectator, check if we are dead if(!m_pClient->m_Snap.m_pLocalCharacter) 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; }