diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c0cf916ab0..f85fb08c8d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1829,6 +1829,12 @@ set(EXPECTED_DATA skins7/x_ninja.json skins7/xmas_hat.png strong_weak.png + tclient/fonts/Cabin-Regular.ttf + tclient/fonts/GoogleSans-Regular.ttf + tclient/fonts/minecraft_font.ttf + tclient/fonts/Montserrat-Regular.ttf + tclient/fonts/Nunito-Black.ttf + tclient/fonts/Rubik-Regular.ttf themes/auto.png themes/autumn.png themes/autumn_day.map diff --git a/data/tclient/fonts/Cabin-Regular.ttf b/data/tclient/fonts/Cabin-Regular.ttf new file mode 100644 index 00000000000..cea1ddde278 Binary files /dev/null and b/data/tclient/fonts/Cabin-Regular.ttf differ diff --git a/data/tclient/fonts/GoogleSans-Regular.ttf b/data/tclient/fonts/GoogleSans-Regular.ttf new file mode 100644 index 00000000000..ab605f9e2e3 Binary files /dev/null and b/data/tclient/fonts/GoogleSans-Regular.ttf differ diff --git a/data/tclient/fonts/Montserrat-Regular.ttf b/data/tclient/fonts/Montserrat-Regular.ttf new file mode 100644 index 00000000000..f4a266dd37d Binary files /dev/null and b/data/tclient/fonts/Montserrat-Regular.ttf differ diff --git a/data/tclient/fonts/Nunito-Black.ttf b/data/tclient/fonts/Nunito-Black.ttf new file mode 100644 index 00000000000..81d557c5b26 Binary files /dev/null and b/data/tclient/fonts/Nunito-Black.ttf differ diff --git a/data/tclient/fonts/Rubik-Regular.ttf b/data/tclient/fonts/Rubik-Regular.ttf new file mode 100644 index 00000000000..8b7b632f9c0 Binary files /dev/null and b/data/tclient/fonts/Rubik-Regular.ttf differ diff --git a/data/tclient/fonts/minecraft_font.ttf b/data/tclient/fonts/minecraft_font.ttf new file mode 100644 index 00000000000..61b4610bd20 Binary files /dev/null and b/data/tclient/fonts/minecraft_font.ttf differ diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp index 7bea8c5f890..7c23ab83266 100644 --- a/src/engine/client/text.cpp +++ b/src/engine/client/text.cpp @@ -603,6 +603,8 @@ class CGlyphMap delete[] pTextureData; } } + // TClient + std::vector *GetFaces() { return &m_vFtFaces; } FT_Face DefaultFace() const { @@ -959,6 +961,10 @@ class CTextRender : public IEngineTextRender std::chrono::nanoseconds m_CursorRenderTime; + // TClient + std::vector m_CustomFontFaces; + std::vector m_DefaultFontFaces; + int GetFreeTextContainerIndex() { if(m_FirstFreeTextContainerIndex == -1) @@ -1153,6 +1159,76 @@ class CTextRender : public IEngineTextRender m_pGraphics = nullptr; m_pStorage = nullptr; } + // TClient + static int LaziestFileCallback(const char *pFilename, int IsDir, int StorageType, void *pUser) + { + std::vector *pVector = static_cast*>(pUser); + if(IsDir) + return 0; + pVector->push_back(std::string(pFilename)); + return 0; + } + + // TClient + void CheckDefaultFaces() + { + for(const auto &CurrentFace : *m_pGlyphMap->GetFaces()) + m_DefaultFontFaces.push_back(std::string(CurrentFace->family_name)); + } + // TClient + void UpdateCustomFontList() + { + std::vector m_AllFaces; + for(const auto &CurrentFace : *m_pGlyphMap->GetFaces()) + m_AllFaces.push_back(std::string(CurrentFace->family_name)); + + m_CustomFontFaces.clear(); + m_CustomFontFaces.push_back(std::string("DejaVu Sans")); + for(const auto &face : m_AllFaces) + if(std::find(m_DefaultFontFaces.begin(), m_DefaultFontFaces.end(), face) == m_DefaultFontFaces.end()) + m_CustomFontFaces.push_back(face); + } + // TClient + void LoadCustomFonts() + { + CheckDefaultFaces(); + std::vector vCustomFonts; + Storage()->ListDirectory(IStorage::TYPE_ALL, "tclient/fonts", LaziestFileCallback, &vCustomFonts); + std::sort(vCustomFonts.begin(), vCustomFonts.end()); + for(std::string sFile : vCustomFonts) + { + char aFontName[IO_MAX_PATH_LENGTH]; + str_format(aFontName, sizeof(aFontName), "tclient/fonts/%s", sFile.c_str()); + void *pFontData; + unsigned FontDataSize; + if(Storage()->ReadFile(aFontName, IStorage::TYPE_ALL, &pFontData, &FontDataSize)) + { + if(LoadFontCollection(aFontName, static_cast(pFontData), (FT_Long)FontDataSize)) + { + m_vpFontData.push_back(pFontData); + } + else + { + free(pFontData); + } + } + else + { + log_error("textrender", "Failed to open/read font file '%s'", aFontName); + } + } + UpdateCustomFontList(); + } + // TClient + std::vector *GetCustomFaces() override + { + return &m_CustomFontFaces; + } + // TClient + void SetCustomFace(const char *pFace) override + { + m_pGlyphMap->SetDefaultFaceByName(pFace); + } bool LoadFonts() override { @@ -1239,6 +1315,8 @@ class CTextRender : public IEngineTextRender log_error("textrender", "Font index malformed: 'default' must be a string"); Success = false; } + // TClient + LoadCustomFonts(); // extract language variant family names const json_value &Variants = (*pJsonData)["language variants"]; diff --git a/src/engine/shared/tater_variables.h b/src/engine/shared/tater_variables.h index 8c9393de414..d83fb8c7697 100644 --- a/src/engine/shared/tater_variables.h +++ b/src/engine/shared/tater_variables.h @@ -157,7 +157,8 @@ MACRO_CONFIG_COL(ClStatusBarTextColor, tc_statusbar_text_color, 4278190335, CFGF MACRO_CONFIG_COL(ClStatusBarAlpha, tc_statusbar_alpha, 75, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Status bar background alpha") MACRO_CONFIG_COL(ClStatusBarTextAlpha, tc_statusbar_text_alpha, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Status bar text alpha") - +// Font +MACRO_CONFIG_STR(ClCustomFont, tc_custom_font, 255, "Dejavu Sans", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Custom font face") diff --git a/src/engine/textrender.h b/src/engine/textrender.h index 99de9e29747..414a27f6aa0 100644 --- a/src/engine/textrender.h +++ b/src/engine/textrender.h @@ -321,6 +321,9 @@ class ITextRender : public IInterface virtual void MoveCursor(CTextCursor *pCursor, float x, float y) const = 0; virtual void SetCursorPosition(CTextCursor *pCursor, float x, float y) const = 0; + virtual std::vector *GetCustomFaces() = 0; // TClient + virtual void SetCustomFace(const char *pFace) = 0; // TClient + virtual bool LoadFonts() = 0; virtual void SetFontPreset(EFontPreset FontPreset) = 0; virtual void SetFontLanguageVariant(const char *pLanguageFile) = 0; diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp index e516f9696e3..083dfabfb1d 100644 --- a/src/game/client/components/controls.cpp +++ b/src/game/client/components/controls.cpp @@ -188,7 +188,7 @@ int CControls::SnapInput(int *pData) else m_aInputData[g_Config.m_ClDummy].m_PlayerFlags = PLAYERFLAG_PLAYING; - if(m_pClient->m_Scoreboard.Active() || g_Config.m_ClPingNameCircle) + if(m_pClient->m_Scoreboard.Active() || g_Config.m_ClPingNameCircle || GameClient()->m_StatusBar.m_PingActive) m_aInputData[g_Config.m_ClDummy].m_PlayerFlags |= PLAYERFLAG_SCOREBOARD; if(Client()->ServerCapAnyPlayerFlag() && m_pClient->m_Controls.m_aShowHookColl[g_Config.m_ClDummy]) diff --git a/src/game/client/components/tclient/menus_tclient.cpp b/src/game/client/components/tclient/menus_tclient.cpp index 8947074157a..5e794a2e692 100644 --- a/src/game/client/components/tclient/menus_tclient.cpp +++ b/src/game/client/components/tclient/menus_tclient.cpp @@ -165,7 +165,6 @@ int CMenus::DoButtonLineSize_Menu(CButtonContainer *pButtonContainer, const char return 0; return Ui()->DoButtonLogic(pButtonContainer, Checked, pRect); - } void CMenus::RenderSettingsTClient(CUIRect MainView) @@ -194,7 +193,8 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) for(int Tab = 0; Tab < NUMBER_OF_TCLIENT_TABS; ++Tab) { TabBar.VSplitLeft(TabWidth, &Button, &TabBar); - const int Corners = Tab == 0 ? IGraphics::CORNER_L : Tab == NUMBER_OF_TCLIENT_TABS - 1 ? IGraphics::CORNER_R : IGraphics::CORNER_NONE; + const int Corners = Tab == 0 ? IGraphics::CORNER_L : Tab == NUMBER_OF_TCLIENT_TABS - 1 ? IGraphics::CORNER_R : + IGraphics::CORNER_NONE; if(DoButton_MenuTab(&s_aPageTabs[Tab], apTabNames[Tab], s_CurCustomTab == Tab, &Button, Corners, nullptr, nullptr, nullptr, nullptr, 4.0f)) s_CurCustomTab = Tab; } @@ -266,6 +266,35 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) s_WhiteFeet.SetEmptyText("x_ninja"); Ui()->DoEditBox(&s_WhiteFeet, &FeetBox, EditBoxFontSize); } + Column.HSplitTop(MarginSmall, nullptr, &Column); + + static std::vector s_FontDropDownNames = {}; + static CUi::SDropDownState s_FontDropDownState; + static CScrollRegion s_FontDropDownScrollRegion; + s_FontDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_FontDropDownScrollRegion; + s_FontDropDownState.m_SelectionPopupContext.m_SpecialFontRenderMode = true; + int FontSelectedOld = -1; + for(size_t i = 0; i < TextRender()->GetCustomFaces()->size(); ++i) + { + if(s_FontDropDownNames.size() != TextRender()->GetCustomFaces()->size()) + s_FontDropDownNames.push_back(TextRender()->GetCustomFaces()->at(i).c_str()); + + if(str_find_nocase(g_Config.m_ClCustomFont, TextRender()->GetCustomFaces()->at(i).c_str())) + FontSelectedOld = i; + } + + CUIRect FontDropDownRect; + Column.HSplitTop(LineSize, &FontDropDownRect, &Column); + FontDropDownRect.VSplitLeft(100.0f, &Label, &FontDropDownRect); + Ui()->DoLabel(&Label, Localize("Custom Font: "), FontSize, TEXTALIGN_ML); + const int FontSelectedNew = Ui()->DoDropDown(&FontDropDownRect, FontSelectedOld, s_FontDropDownNames.data(), s_FontDropDownNames.size(), s_FontDropDownState); + if(FontSelectedOld != FontSelectedNew) + { + str_copy(g_Config.m_ClCustomFont, s_FontDropDownNames[FontSelectedNew]); + FontSelectedOld = FontSelectedNew; + TextRender()->SetCustomFace(g_Config.m_ClCustomFont); + } + Column.HSplitTop(MarginExtraSmall, nullptr, &Column); s_SectionBoxes.back().h = Column.y - s_SectionBoxes.back().y; @@ -573,8 +602,8 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) Ui()->DoScrollbarOption(&g_Config.m_ClRainbowSpeed, &g_Config.m_ClRainbowSpeed, &Button, Localize("Rainbow speed"), 0, 5000, &CUi::ms_LogarithmicScrollbarScale, 0, "%"); Column.HSplitTop(MarginExtraSmall, nullptr, &Column); s_SectionBoxes.back().h = Column.y - s_SectionBoxes.back().y; - Column.HSplitTop(MarginSmall, nullptr, &Column); + // ***** END OF PAGE 1 SETTINGS ***** // RightView = Column; @@ -599,8 +628,7 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) Ui()->DoLabel(&Label, Localize("Kaomoji"), HeadlineFontSize, TEXTALIGN_ML); Column.HSplitTop(MarginSmall, nullptr, &Column); - auto DoBindchat = [&](CLineInput &LineInput, const char *pLabel, const char *pName, const char *pCommand) - { + auto DoBindchat = [&](CLineInput &LineInput, const char *pLabel, const char *pName, const char *pCommand) { Column.HSplitTop(LineSize, &Button, &Column); char *BindCommand; int BindIndex = GameClient()->m_Bindchat.GetBindNoDefault(pCommand); @@ -645,7 +673,6 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) Ui()->DoLabel(&Label, Localize("Warlist"), HeadlineFontSize, TEXTALIGN_ML); Column.HSplitTop(MarginSmall, nullptr, &Column); - static CLineInput s_Warlist1, s_Warlist2, s_Warlist3, s_Warlist4, s_Warlist5, s_Warlist6, s_Warlist7, s_Warlist8; char aBuf[128]; char aGroup1Name[MAX_WARLIST_TYPE_LENGTH]; // enemy by default @@ -676,7 +703,6 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) Column.HSplitTop(MarginSmall, nullptr, &Column); str_format(aBuf, sizeof(aBuf), "Remove %s clan:", aGroup2Name); DoBindchat(s_Warlist8, aBuf, ".delteamclan", "remove_war_clan_index 2"); - } if(s_CurCustomTab == TCLIENT_TAB_BINDWHEEL) @@ -917,7 +943,6 @@ void CMenus::RenderSettingsWarList(CUIRect MainView) static CWarEntry *pSelectedEntry = nullptr; static CWarType *pSelectedType = GameClient()->m_WarList.m_WarTypes[0]; - // Filter the list static CLineInputBuffered<128> s_EntriesFilterInput; std::vector vpFilteredEntries; @@ -1135,7 +1160,6 @@ void CMenus::RenderSettingsWarList(CUIRect MainView) TextRender()->TextColor(TextRender()->DefaultTextColor()); } - Column2.HSplitBottom(150.0f, nullptr, &Column2); Column2.HSplitTop(HeadlineHeight, &Label, &Column2); @@ -1148,7 +1172,6 @@ void CMenus::RenderSettingsWarList(CUIRect MainView) DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClWarListScoreboard, Localize("Colors in scoreboard"), &g_Config.m_ClWarListScoreboard, &Column2, LineSize); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClWarListShowClan, Localize("Show clan if war"), &g_Config.m_ClWarListShowClan, &Column2, LineSize); - // ======WAR TYPE EDITING====== Column3.HSplitTop(HeadlineHeight, &Label, &Column3); @@ -1398,7 +1421,6 @@ void CMenus::RenderSettingsInfo(CUIRect MainView) Client()->ViewFile(aBuf); } - // =======RIGHT VIEW======== RightView.HSplitTop(HeadlineHeight, &Label, &RightView); diff --git a/src/game/client/components/tclient/statusbar.cpp b/src/game/client/components/tclient/statusbar.cpp index f2e9ad7aaba..81e4c46490e 100644 --- a/src/game/client/components/tclient/statusbar.cpp +++ b/src/game/client/components/tclient/statusbar.cpp @@ -51,11 +51,11 @@ float CStatusBar::AngleWidth() } void CStatusBar::AngleRender() { - CNetObj_Character *pCharacter = &m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientId].m_Cur; + CNetObj_Character *pCharacter = &m_pClient->m_Snap.m_aCharacters[m_PlayerId].m_Cur; float Angle = 0.0f; - if(m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientId].m_HasExtendedDisplayInfo) + if(m_pClient->m_Snap.m_aCharacters[m_PlayerId].m_HasExtendedDisplayInfo) { - CNetObj_DDNetCharacter *pExtendedData = &m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientId].m_ExtendedData; + CNetObj_DDNetCharacter *pExtendedData = &m_pClient->m_Snap.m_aCharacters[m_PlayerId].m_ExtendedData; Angle = atan2f(pExtendedData->m_TargetY, pExtendedData->m_TargetX); } else @@ -69,17 +69,18 @@ void CStatusBar::AngleRender() float CStatusBar::PingWidth() { - if(!m_pClient->m_Snap.m_apPlayerInfos[m_pClient->m_Snap.m_LocalClientId]) + if(!m_pClient->m_Snap.m_apPlayerInfos[m_PlayerId]) return 0.0f; return TextRender()->TextWidth(m_FontSize, "0000"); } void CStatusBar::PingRender() { - const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_apPlayerInfos[m_pClient->m_Snap.m_LocalClientId]; + const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_apPlayerInfos[m_PlayerId]; char aBuf[32]; - str_format(aBuf, sizeof(aBuf), "%d", GameClient()->Client()->NetClient()->GetLatency()); + str_format(aBuf, sizeof(aBuf), "%d", pInfo->m_Latency); TextRender()->Text(m_CursorX, m_CursorY, m_FontSize, aBuf); + m_PingActive = true; } float CStatusBar::PredictionWidth() @@ -150,11 +151,19 @@ float CStatusBar::LabelWidth(const char *pLabel) { return 0.0f; } void CStatusBar::OnRender() { + m_PingActive = false; + if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) return; if(!g_Config.m_ClStatusBar || !m_pClient->m_Snap.m_pGameInfoObj) return; + + m_PlayerId = m_pClient->m_Snap.m_LocalClientId; + if(m_pClient->m_Snap.m_SpecInfo.m_Active) + m_PlayerId = m_pClient->m_Snap.m_SpecInfo.m_SpectatorId; + + UpdateStatusBarSize(); Graphics()->MapScreen(0.0f, 0.0f, m_Width, m_Height); diff --git a/src/game/client/components/tclient/statusbar.h b/src/game/client/components/tclient/statusbar.h index e6991033e8c..5d3dba425cc 100644 --- a/src/game/client/components/tclient/statusbar.h +++ b/src/game/client/components/tclient/statusbar.h @@ -96,8 +96,9 @@ class CStatusBar : public CComponent void UpdateStatusBarSize(); - + bool m_PingActive = false; float m_FrameTimeAverage = 0.0f; + int m_PlayerId = 0; // float Width(); // void Render(); // float Width(); diff --git a/src/game/client/components/tclient/tater.cpp b/src/game/client/components/tclient/tater.cpp index cca696b0d72..c9d1ea0af83 100644 --- a/src/game/client/components/tclient/tater.cpp +++ b/src/game/client/components/tclient/tater.cpp @@ -88,6 +88,11 @@ void CTater::ConchainRandomColor(IConsole::IResult *pResult, void *pUserData, IC pThis->m_pClient->SendInfo(false); } +void CTater::OnInit() +{ + TextRender()->SetCustomFace(g_Config.m_ClCustomFont); +} + void CTater::OnConsoleInit() { Console()->Register("tc_random_player", "s[type]", CFGFLAG_CLIENT, ConRandomTee, this, "Randomize player color (0 = all, 1 = body, 2 = feet, 3 = skin, 4 = flag) example: 0011 = randomize skin and flag [number is position] "); diff --git a/src/game/client/components/tclient/tater.h b/src/game/client/components/tclient/tater.h index e7162d1b70e..5a1730e9c49 100644 --- a/src/game/client/components/tclient/tater.h +++ b/src/game/client/components/tclient/tater.h @@ -14,6 +14,7 @@ class CTater : public CComponent public: CTater(); virtual int Sizeof() const override { return sizeof(*this); } + virtual void OnInit() override; virtual void OnConsoleInit() override; }; diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 2dc58986591..66545a49516 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -2548,17 +2548,17 @@ void CGameClient::OnPredict() } CNetObj_PlayerInput *pInputData = m_PredictedWorld.GetCharacterById(m_Snap.m_LocalClientId)->LatestInput(); CNetObj_PlayerInput *pDummyInputData = !pPredDummyChar ? 0 : m_PredictedWorld.GetCharacterById(m_PredictedDummyId)->LatestInput(); - bool DummyFirst = pInputData && pDummyInputData && pSmoothDummyChar->GetCid() < pSmoothLocalChar->GetCid(); - if(DummyFirst) + bool DummyFirst = pSmoothLocalChar && pSmoothDummyChar && pSmoothDummyChar->GetCid() < pSmoothLocalChar->GetCid(); + if(DummyFirst && pSmoothDummyChar) pSmoothDummyChar->OnDirectInput(pDummyInputData); - if(pInputData) + if(pInputData && pSmoothLocalChar) pSmoothLocalChar->OnDirectInput(pInputData); - if(pDummyInputData && !DummyFirst) + if(pDummyInputData && !DummyFirst && pSmoothDummyChar) pSmoothDummyChar->OnDirectInput(pDummyInputData); m_PredSmoothingWorld.m_GameTick++; - if(pInputData) + if(pInputData && pSmoothLocalChar) pSmoothLocalChar->OnPredictedInput(pInputData); - if(pDummyInputData) + if(pDummyInputData && pSmoothDummyChar) pSmoothDummyChar->OnPredictedInput(pDummyInputData); m_PredSmoothingWorld.Tick(); diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index e33ed88291a..94872776553 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -1822,6 +1822,10 @@ CUi::EPopupMenuFunctionResult CUi::PopupSelection(void *pContext, CUIRect View, size_t Index = 0; for(const auto &Entry : pSelectionPopup->m_vEntries) { + // TClient + if(pSelectionPopup->m_SpecialFontRenderMode) + pUI->TextRender()->SetCustomFace(Entry.c_str()); + if(pSelectionPopup->m_aMessage[0] != '\0' || Index != 0) View.HSplitTop(pSelectionPopup->m_EntrySpacing, nullptr, &View); View.HSplitTop(pSelectionPopup->m_EntryHeight, &Slot, &View); @@ -1835,6 +1839,9 @@ CUi::EPopupMenuFunctionResult CUi::PopupSelection(void *pContext, CUIRect View, } ++Index; } + // TClient + if(pSelectionPopup->m_SpecialFontRenderMode) + pUI->TextRender()->SetCustomFace(g_Config.m_ClCustomFont); pScrollRegion->End(); diff --git a/src/game/client/ui.h b/src/game/client/ui.h index 94bba962876..5c6ab315ddf 100644 --- a/src/game/client/ui.h +++ b/src/game/client/ui.h @@ -719,6 +719,8 @@ class CUi float m_AlignmentHeight; bool m_TransparentButtons; + bool m_SpecialFontRenderMode = false; // TClient + SSelectionPopupContext(); void Reset(); };