/* (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 "camera.h" #include "controls.h" #include "nameplates.h" void CNamePlates::RenderNameplate( const CNetObj_Character *pPrevChar, const CNetObj_Character *pPlayerChar, const CNetObj_PlayerInfo *pPlayerInfo) { int ClientID = pPlayerInfo->m_ClientID; vec2 Position; if(ClientID >= 0 && ClientID < MAX_CLIENTS) Position = m_pClient->m_aClients[ClientID].m_RenderPos; else Position = mix(vec2(pPrevChar->m_X, pPrevChar->m_Y), vec2(pPlayerChar->m_X, pPlayerChar->m_Y), Client()->IntraGameTick(g_Config.m_ClDummy)); RenderNameplatePos(Position, pPlayerInfo, 1.0f); } void CNamePlates::RenderNameplatePos(vec2 Position, const CNetObj_PlayerInfo *pPlayerInfo, float Alpha, bool ForceAlpha) { int ClientID = pPlayerInfo->m_ClientID; bool OtherTeam = m_pClient->IsOtherTeam(ClientID); float FontSize = 18.0f + 20.0f * g_Config.m_ClNameplatesSize / 100.0f; float FontSizeClan = 18.0f + 20.0f * g_Config.m_ClNameplatesClanSize / 100.0f; TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_NO_FIRST_CHARACTER_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_LAST_CHARACTER_ADVANCE); float YOffset = Position.y - 38; ColorRGBA rgb = ColorRGBA(1.0f, 1.0f, 1.0f); // render players' key presses int ShowDirection = g_Config.m_ClShowDirection; #if defined(CONF_VIDEORECORDER) if(IVideo::Current()) ShowDirection = g_Config.m_ClVideoShowDirection; #endif if((ShowDirection && ShowDirection != 3 && !pPlayerInfo->m_Local) || (ShowDirection >= 2 && pPlayerInfo->m_Local) || (ShowDirection == 3 && Client()->DummyConnected() && Client()->State() != IClient::STATE_DEMOPLAYBACK && ClientID == m_pClient->m_aLocalIDs[!g_Config.m_ClDummy])) { Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); Graphics()->QuadsSetRotation(0); const float ShowDirectionImgSize = 22.0f; YOffset -= ShowDirectionImgSize; vec2 ShowDirectionPos = vec2(Position.x - 11.0f, YOffset); bool DirLeft = m_pClient->m_Snap.m_aCharacters[pPlayerInfo->m_ClientID].m_Cur.m_Direction == -1; bool DirRight = m_pClient->m_Snap.m_aCharacters[pPlayerInfo->m_ClientID].m_Cur.m_Direction == 1; bool Jump = m_pClient->m_Snap.m_aCharacters[pPlayerInfo->m_ClientID].m_Cur.m_Jumped & 1; if(pPlayerInfo->m_Local && Client()->State() != IClient::STATE_DEMOPLAYBACK) { DirLeft = m_pClient->m_Controls.m_aInputData[g_Config.m_ClDummy].m_Direction == -1; DirRight = m_pClient->m_Controls.m_aInputData[g_Config.m_ClDummy].m_Direction == 1; Jump = m_pClient->m_Controls.m_aInputData[g_Config.m_ClDummy].m_Jump == 1; } if(Client()->DummyConnected() && Client()->State() != IClient::STATE_DEMOPLAYBACK && pPlayerInfo->m_ClientID == m_pClient->m_aLocalIDs[!g_Config.m_ClDummy]) { DirLeft = m_pClient->m_Controls.m_aInputData[!g_Config.m_ClDummy].m_Direction == -1; DirRight = m_pClient->m_Controls.m_aInputData[!g_Config.m_ClDummy].m_Direction == 1; Jump = m_pClient->m_Controls.m_aInputData[!g_Config.m_ClDummy].m_Jump == 1; } if(DirLeft) { Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id); Graphics()->QuadsSetRotation(pi); Graphics()->RenderQuadContainerAsSprite(m_DirectionQuadContainerIndex, 0, ShowDirectionPos.x - 30.f, ShowDirectionPos.y); } else if(DirRight) { Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id); Graphics()->RenderQuadContainerAsSprite(m_DirectionQuadContainerIndex, 0, ShowDirectionPos.x + 30.f, ShowDirectionPos.y); } if(Jump) { Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id); Graphics()->QuadsSetRotation(pi * 3 / 2); Graphics()->RenderQuadContainerAsSprite(m_DirectionQuadContainerIndex, 0, ShowDirectionPos.x, ShowDirectionPos.y); } Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); Graphics()->QuadsSetRotation(0); } // render name plate if((!pPlayerInfo->m_Local || g_Config.m_ClNameplatesOwn) && g_Config.m_ClNameplates) { float a = 1; if(g_Config.m_ClNameplatesAlways == 0) a = clamp(1 - std::pow(distance(m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy], Position) / 200.0f, 16.0f), 0.0f, 1.0f); const char *pName = m_pClient->m_aClients[pPlayerInfo->m_ClientID].m_aName; if(str_comp(pName, m_aNamePlates[ClientID].m_aName) != 0 || FontSize != m_aNamePlates[ClientID].m_NameTextFontSize) { mem_copy(m_aNamePlates[ClientID].m_aName, pName, sizeof(m_aNamePlates[ClientID].m_aName)); m_aNamePlates[ClientID].m_NameTextFontSize = FontSize; CTextCursor Cursor; TextRender()->SetCursor(&Cursor, 0, 0, FontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = -1; // create nameplates at standard zoom float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y); m_aNamePlates[ClientID].m_NameTextWidth = TextRender()->TextWidth(FontSize, pName, -1, -1.0f); TextRender()->RecreateTextContainer(m_aNamePlates[ClientID].m_NameTextContainerIndex, &Cursor, pName); Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } if(g_Config.m_ClNameplatesClan) { const char *pClan = m_pClient->m_aClients[ClientID].m_aClan; if(str_comp(pClan, m_aNamePlates[ClientID].m_aClanName) != 0 || FontSizeClan != m_aNamePlates[ClientID].m_ClanNameTextFontSize) { mem_copy(m_aNamePlates[ClientID].m_aClanName, pClan, sizeof(m_aNamePlates[ClientID].m_aClanName)); m_aNamePlates[ClientID].m_ClanNameTextFontSize = FontSizeClan; CTextCursor Cursor; TextRender()->SetCursor(&Cursor, 0, 0, FontSizeClan, TEXTFLAG_RENDER); Cursor.m_LineWidth = -1; // create nameplates at standard zoom float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y); m_aNamePlates[ClientID].m_ClanNameTextWidth = TextRender()->TextWidth(FontSizeClan, pClan, -1, -1.0f); TextRender()->RecreateTextContainer(m_aNamePlates[ClientID].m_ClanNameTextContainerIndex, &Cursor, pClan); Graphics()->MapScreen(ScreenX0, ScreenY0, ScreenX1, ScreenY1); } } float tw = m_aNamePlates[ClientID].m_NameTextWidth; if(g_Config.m_ClNameplatesTeamcolors && m_pClient->m_Teams.Team(ClientID)) rgb = m_pClient->GetDDTeamColor(m_pClient->m_Teams.Team(ClientID), 0.75f); ColorRGBA TColor; ColorRGBA TOutlineColor; if(OtherTeam && !ForceAlpha) { TOutlineColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.2f * g_Config.m_ClShowOthersAlpha / 100.0f); TColor = ColorRGBA(rgb.r, rgb.g, rgb.b, g_Config.m_ClShowOthersAlpha / 100.0f); } else { TOutlineColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f * a); TColor = ColorRGBA(rgb.r, rgb.g, rgb.b, a); } if(g_Config.m_ClNameplatesTeamcolors && m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS) { if(m_pClient->m_aClients[ClientID].m_Team == TEAM_RED) TColor = ColorRGBA(1.0f, 0.5f, 0.5f, a); else if(m_pClient->m_aClients[ClientID].m_Team == TEAM_BLUE) TColor = ColorRGBA(0.7f, 0.7f, 1.0f, a); } TOutlineColor.a *= Alpha; TColor.a *= Alpha; if(m_aNamePlates[ClientID].m_NameTextContainerIndex.Valid()) { YOffset -= FontSize; TextRender()->RenderTextContainer(m_aNamePlates[ClientID].m_NameTextContainerIndex, TColor, TOutlineColor, Position.x - tw / 2.0f, YOffset); } if(g_Config.m_ClNameplatesClan) { YOffset -= FontSizeClan; if(m_aNamePlates[ClientID].m_ClanNameTextContainerIndex.Valid()) TextRender()->RenderTextContainer(m_aNamePlates[ClientID].m_ClanNameTextContainerIndex, TColor, TOutlineColor, Position.x - m_aNamePlates[ClientID].m_ClanNameTextWidth / 2.0f, YOffset); } if(g_Config.m_ClNameplatesFriendMark && m_pClient->m_aClients[ClientID].m_Friend) { YOffset -= FontSize; char aFriendMark[] = "♥"; ColorRGBA Color; if(OtherTeam && !ForceAlpha) Color = ColorRGBA(1.0f, 0.0f, 0.0f, g_Config.m_ClShowOthersAlpha / 100.0f); else Color = ColorRGBA(1.0f, 0.0f, 0.0f, a); Color.a *= Alpha; TextRender()->TextColor(Color); float XOffSet = TextRender()->TextWidth(FontSize, aFriendMark, -1, -1.0f) / 2.0f; TextRender()->Text(Position.x - XOffSet, YOffset, FontSize, aFriendMark, -1.0f); } if(g_Config.m_Debug || g_Config.m_ClNameplatesIDs) // render client id when in debug as well { YOffset -= FontSize; char aBuf[128]; str_from_int(pPlayerInfo->m_ClientID, aBuf); float XOffset = TextRender()->TextWidth(FontSize, aBuf, -1, -1.0f) / 2.0f; TextRender()->TextColor(rgb); TextRender()->Text(Position.x - XOffset, YOffset, FontSize, aBuf, -1.0f); } } if((g_Config.m_Debug || g_Config.m_ClNameplatesStrong) && g_Config.m_ClNameplates) { if(m_pClient->m_Snap.m_LocalClientID != -1 && m_pClient->m_Snap.m_aCharacters[pPlayerInfo->m_ClientID].m_HasExtendedData && m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientID].m_HasExtendedData) { CCharacter *pLocalChar = m_pClient->m_GameWorld.GetCharacterByID(m_pClient->m_Snap.m_LocalClientID); CCharacter *pCharacter = m_pClient->m_GameWorld.GetCharacterByID(pPlayerInfo->m_ClientID); if(pCharacter && pLocalChar) { if(pPlayerInfo->m_Local) TextRender()->TextColor(rgb); else { float ScaleX, ScaleY; const float StrongWeakImgSize = 40.0f; Graphics()->TextureClear(); Graphics()->TextureSet(g_pData->m_aImages[IMAGE_STRONGWEAK].m_Id); Graphics()->QuadsBegin(); ColorRGBA StrongWeakStatusColor; int StrongWeakSpriteID; if(pLocalChar->GetStrongWeakID() > pCharacter->GetStrongWeakID()) { StrongWeakStatusColor = color_cast(ColorHSLA(6401973)); StrongWeakSpriteID = SPRITE_HOOK_STRONG; } else { StrongWeakStatusColor = color_cast(ColorHSLA(41131)); StrongWeakSpriteID = SPRITE_HOOK_WEAK; } float ClampedAlpha = 1; if(g_Config.m_ClNameplatesAlways == 0) ClampedAlpha = clamp(1 - std::pow(distance(m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy], Position) / 200.0f, 16.0f), 0.0f, 1.0f); if(OtherTeam && !ForceAlpha) StrongWeakStatusColor.a = g_Config.m_ClShowOthersAlpha / 100.0f; else StrongWeakStatusColor.a = ClampedAlpha; StrongWeakStatusColor.a *= Alpha; Graphics()->SetColor(StrongWeakStatusColor); RenderTools()->SelectSprite(StrongWeakSpriteID); RenderTools()->GetSpriteScale(StrongWeakSpriteID, ScaleX, ScaleY); TextRender()->TextColor(StrongWeakStatusColor); YOffset -= StrongWeakImgSize * ScaleY; RenderTools()->DrawSprite(Position.x, YOffset + (StrongWeakImgSize / 2.0f) * ScaleY, StrongWeakImgSize); Graphics()->QuadsEnd(); } if(g_Config.m_Debug || g_Config.m_ClNameplatesStrong == 2) { YOffset -= FontSize; char aBuf[12]; str_from_int(pCharacter->GetStrongWeakID(), aBuf); float XOffset = TextRender()->TextWidth(FontSize, aBuf, -1, -1.0f) / 2.0f; TextRender()->Text(Position.x - XOffset, YOffset, FontSize, aBuf, -1.0f); } } } } TextRender()->TextColor(TextRender()->DefaultTextColor()); TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor()); TextRender()->SetRenderFlags(0); } void CNamePlates::OnRender() { int ShowDirection = g_Config.m_ClShowDirection; #if defined(CONF_VIDEORECORDER) if(IVideo::Current()) ShowDirection = g_Config.m_ClVideoShowDirection; #endif if(!g_Config.m_ClNameplates && ShowDirection == 0) return; // get screen edges to avoid rendering offscreen float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); // expand the edges to prevent popping in/out onscreen // // it is assumed that the nameplate and all its components fit into a 800x800 box placed directly above the tee // this may need to be changed or calculated differently in the future ScreenX0 -= 400; ScreenX1 += 400; //ScreenY0 -= 0; ScreenY1 += 800; for(int i = 0; i < MAX_CLIENTS; i++) { const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_apPlayerInfos[i]; if(!pInfo) { continue; } vec2 *pRenderPos; if(m_pClient->m_aClients[i].m_SpecCharPresent) { // Each player can also have a spec char whose nameplate is displayed independently pRenderPos = &m_pClient->m_aClients[i].m_SpecChar; // don't render offscreen if(!(pRenderPos->x < ScreenX0) && !(pRenderPos->x > ScreenX1) && !(pRenderPos->y < ScreenY0) && !(pRenderPos->y > ScreenY1)) { RenderNameplatePos(m_pClient->m_aClients[i].m_SpecChar, pInfo, 0.4f, true); } } if(m_pClient->m_Snap.m_aCharacters[i].m_Active) { // Only render nameplates for active characters pRenderPos = &m_pClient->m_aClients[i].m_RenderPos; // don't render offscreen if(!(pRenderPos->x < ScreenX0) && !(pRenderPos->x > ScreenX1) && !(pRenderPos->y < ScreenY0) && !(pRenderPos->y > ScreenY1)) { RenderNameplate( &m_pClient->m_Snap.m_aCharacters[i].m_Prev, &m_pClient->m_Snap.m_aCharacters[i].m_Cur, pInfo); } } } } void CNamePlates::SetPlayers(CPlayers *pPlayers) { m_pPlayers = pPlayers; } void CNamePlates::ResetNamePlates() { for(auto &NamePlate : m_aNamePlates) { TextRender()->DeleteTextContainer(NamePlate.m_NameTextContainerIndex); TextRender()->DeleteTextContainer(NamePlate.m_ClanNameTextContainerIndex); NamePlate.Reset(); } } void CNamePlates::OnWindowResize() { ResetNamePlates(); } void CNamePlates::OnInit() { ResetNamePlates(); // Quad for the direction arrows above the player m_DirectionQuadContainerIndex = Graphics()->CreateQuadContainer(false); RenderTools()->QuadContainerAddSprite(m_DirectionQuadContainerIndex, 0.f, 0.f, 22.f); Graphics()->QuadContainerUpload(m_DirectionQuadContainerIndex); }