From bb4e8fb8a0ed7b76271c92ddced2b6c8501fc3ce Mon Sep 17 00:00:00 2001 From: nullsystem <15316579+nullsystem@users.noreply.github.com> Date: Fri, 13 Sep 2024 20:18:45 +0100 Subject: [PATCH] git v tag + news on startup with caching/rate limit * Build now utilizes latest git tags starting with v * Loads up news on startup, rate limited to one day --- .github/workflows/cibuild.yml | 3 + .gitignore | 3 + mp/src/CMakeLists.txt | 3 + mp/src/cmake/build_info.cmake | 8 + mp/src/game/client/neo/ui/neo_root.cpp | 158 +++++++++++++++++- mp/src/game/client/neo/ui/neo_root.h | 17 ++ mp/src/game/client/neo/ui/neo_ui.cpp | 34 +++- mp/src/game/client/neo/ui/neo_ui.h | 4 + .../game/shared/neo/neo_version_info.cpp.in | 3 +- mp/src/game/shared/neo/neo_version_info.h | 1 + 10 files changed, 223 insertions(+), 11 deletions(-) diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index 13cb2956b..3299c49b7 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -62,6 +62,9 @@ jobs: run: | echo "PATH: $PATH" + - name: Git fetch tags + run: git fetch origin --tags + # Libraries - name: CMake configure libraries build diff --git a/.gitignore b/.gitignore index 3976c4c30..c5f21c37a 100644 --- a/.gitignore +++ b/.gitignore @@ -206,3 +206,6 @@ mp/game/neo/cfg/sourcemod # Auto-generated by CMake mp/game/neo/resource/GameMenu.res mp/src/game/shared/neo/neo_version_info.cpp + +# News cache +mp/game/neo/news.txt diff --git a/mp/src/CMakeLists.txt b/mp/src/CMakeLists.txt index bd8a9bc82..320c272b9 100644 --- a/mp/src/CMakeLists.txt +++ b/mp/src/CMakeLists.txt @@ -338,6 +338,9 @@ if(OS_LINUX) add_compile_definitions( LINUX _LINUX + # TODO 64-bit time to sidestep 32-bit unix timestamp 2038 issue, although could affect Valve filesystem API + # _FILE_OFFSET_BITS=64 + # _TIME_BITS=64 ) endif() diff --git a/mp/src/cmake/build_info.cmake b/mp/src/cmake/build_info.cmake index b6611b43a..218bb0a6e 100644 --- a/mp/src/cmake/build_info.cmake +++ b/mp/src/cmake/build_info.cmake @@ -9,6 +9,14 @@ execute_process( ) string(SUBSTRING "${GIT_LONGHASH}" 0 7 GIT_HASH) +execute_process( + COMMAND git describe --tags --abbrev=0 --match v* + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_LATESTTAG + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET +) + string(TIMESTAMP BUILD_DATE_SHORT "%Y%m%d") string(TIMESTAMP BUILD_DATE_LONG "%Y-%m-%d") diff --git a/mp/src/game/client/neo/ui/neo_root.cpp b/mp/src/game/client/neo/ui/neo_root.cpp index 97b80b725..39a25a66a 100644 --- a/mp/src/game/client/neo/ui/neo_root.cpp +++ b/mp/src/game/client/neo/ui/neo_root.cpp @@ -17,6 +17,8 @@ #include #include #include +#include "tier1/interface.h" +#include #include #include @@ -50,8 +52,33 @@ int g_iRowsInScreen; int g_iAvatar = 64; int g_iRootSubPanelWide = 600; constexpr wchar_t WSZ_GAME_TITLE[] = L"neatbkyoc ue"; +#define SZ_WEBSITE "https://neotokyorebuild.github.io" ConCommand neo_toggleconsole("neo_toggleconsole", NeoToggleconsole); + +struct YMD +{ + YMD(const struct tm tm) + : year(tm.tm_year + 1900) + , month(tm.tm_mon + 1) + , day(tm.tm_mday) + { + } + + bool operator==(const YMD &other) const + { + return year == other.year && month == other.month && day == other.day; + } + bool operator!=(const YMD &other) const + { + return !(*this == other); + } + + int year; + int month; + int day; +}; + } void OverrideGameUI() @@ -178,6 +205,41 @@ CNeoRoot::CNeoRoot(VPANEL parent) m_serverBrowser[i].m_pSortCtx = &m_sortCtx; } + // NEO TODO (nullsystem): What will happen in 2038? 64-bit Source 1 SDK when? Source 2 SDK when? + // We could use GCC 64-bit compiled time_t or Win32 API direct to side-step IFileSystem "long" 32-bit + // limitation for now. Although that could mess with the internal IFileSystem related API usages of time_t. + // If _FILE_OFFSET_BITS=64 and _TIME_BITS=64 is set on Linux, time_t will be 64-bit even on 32-bit executable + // + // If news.txt doesn't exists, it'll just give 1970-01-01 which will always be different to ymdNow anyway + const long lFileTime = filesystem->GetFileTime("news.txt"); + const time_t ttFileTime = lFileTime; + struct tm tmFileStruct; + const struct tm tmFile = *(Plat_localtime(&ttFileTime, &tmFileStruct)); + const YMD ymdFile{tmFile}; + + struct tm tmNowStruct; + const time_t tNow = time(nullptr); + const struct tm tmNow = *(Plat_localtime(&tNow, &tmNowStruct)); + const YMD ymdNow{tmNow}; + + // Read the cached file regardless of needing update or not + if (filesystem->FileExists("news.txt")) + { + CUtlBuffer buf(0, 0, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY); + if (filesystem->ReadFile("news.txt", nullptr, buf)) + { + ReadNewsFile(buf); + } + } + if (ymdFile != ymdNow) + { + ISteamHTTP *http = steamapicontext->SteamHTTP(); + HTTPRequestHandle httpReqHdl = http->CreateHTTPRequest(k_EHTTPMethodGET, SZ_WEBSITE "/news.txt"); + SteamAPICall_t httpReqCallback; + http->SendHTTPRequest(httpReqHdl, &httpReqCallback); + m_ccallbackHttp.Set(httpReqCallback, this, &CNeoRoot::HTTPCallbackRequest); + } + SetKeyBoardInputEnabled(true); SetMouseInputEnabled(true); UpdateControls(); @@ -219,6 +281,7 @@ void CNeoRoot::UpdateControls() g_uiCtx.iActiveSection = -1; V_memset(g_uiCtx.iYOffset, 0, sizeof(g_uiCtx.iYOffset)); m_ns.bBack = false; + m_bShowBrowserLabel = false; RequestFocus(); m_panelCaptureInput->RequestFocus(); InvalidateLayout(); @@ -460,7 +523,7 @@ void CNeoRoot::MainLoopRoot(const MainLoopParam param) const int iRightXPos = iBtnPlaceXMid + (m_iBtnWide / 2) + g_uiCtx.iMarginX; int iRightSideYStart = yTopPos; - if (param.eMode == NeoUI::MODE_PAINT) + // Draw top steam section portion { // Draw title m_iBtnWide = m_iTitleWidth + (2 * g_uiCtx.iMarginX); @@ -572,14 +635,26 @@ void CNeoRoot::MainLoopRoot(const MainLoopParam param) NeoUI::Label(L"News"); NeoUI::SwapFont(NeoUI::FONT_NTSMALL); - // Write some headlines - static constexpr const wchar_t *WSZ_NEWS_HEADLINES[] = { - L"2024-08-03: NT;RE v7.1 Released", - }; - for (const wchar_t *wszHeadline : WSZ_NEWS_HEADLINES) + g_uiCtx.eButtonTextStyle = NeoUI::TEXTSTYLE_LEFT; + NeoUI::SwapColorNormal(COLOR_TRANSPARENT); + for (int i = 0; i < m_iNewsSize; ++i) { - NeoUI::Label(wszHeadline); + if (NeoUI::Button(m_news[i].wszTitle).bPressed) + { + NeoUI::OpenURL(SZ_WEBSITE, m_news[i].szSitePath); + m_bShowBrowserLabel = true; + } } + + if (m_bShowBrowserLabel) + { + surface()->DrawSetTextColor(Color(178, 178, 178, 178)); + NeoUI::Label(L"Link opened in your web browser"); + surface()->DrawSetTextColor(COLOR_NEOPANELTEXTNORMAL); + } + + g_uiCtx.eButtonTextStyle = NeoUI::TEXTSTYLE_CENTER; + NeoUI::SwapColorNormal(COLOR_NEOPANELACCENTBG); } } NeoUI::EndSection(); @@ -1276,6 +1351,75 @@ void CNeoRoot::MainLoopPopup(const MainLoopParam param) NeoUI::EndContext(); } +void CNeoRoot::HTTPCallbackRequest(HTTPRequestCompleted_t *request, bool bIOFailure) +{ + ISteamHTTP *http = steamapicontext->SteamHTTP(); + if (request->m_bRequestSuccessful && !bIOFailure) + { + uint32 unBodySize = 0; + http->GetHTTPResponseBodySize(request->m_hRequest, &unBodySize); + + if (unBodySize > 0) + { + uint8 *pData = new uint8[unBodySize + 1]; + http->GetHTTPResponseBodyData(request->m_hRequest, pData, unBodySize); + + CUtlBuffer buf(0, 0, CUtlBuffer::TEXT_BUFFER); + buf.CopyBuffer(pData, unBodySize); + filesystem->WriteFile("news.txt", nullptr, buf); + ReadNewsFile(buf); + + delete[] pData; + } + } + http->ReleaseHTTPRequest(request->m_hRequest); +} + +void CNeoRoot::ReadNewsFile(CUtlBuffer &buf) +{ + buf.SeekGet(CUtlBuffer::SEEK_HEAD, 0); + m_iNewsSize = 0; + while (buf.IsValid() && m_iNewsSize < MAX_NEWS) + { + // TSV row: Path\tDate\tTitle + char szLine[512] = {}; + buf.GetLine(szLine, ARRAYSIZE(szLine) - 1); + char *pszDate = strchr(szLine, '\t'); + if (!pszDate) + { + continue; + } + + *pszDate = '\0'; + ++pszDate; + if (!*pszDate) + { + continue; + } + + char *pszTitle = strchr(pszDate, '\t'); + if (!pszTitle) + { + continue; + } + + *pszTitle = '\0'; + ++pszTitle; + if (!*pszTitle) + { + continue; + } + + V_strcpy_safe(m_news[m_iNewsSize].szSitePath, szLine); + wchar_t wszDate[12]; + wchar_t wszTitle[235]; + g_pVGuiLocalize->ConvertANSIToUnicode(pszDate, wszDate, sizeof(wszDate)); + g_pVGuiLocalize->ConvertANSIToUnicode(pszTitle, wszTitle, sizeof(wszTitle)); + V_swprintf_safe(m_news[m_iNewsSize].wszTitle, L"%ls: %ls", wszDate, wszTitle); + ++m_iNewsSize; + } +} + // NEO NOTE (nullsystem): NeoRootCaptureESC is so that ESC keybinds can be recognized by non-root states, but root // state still want to have ESC handled by the game as IsVisible/HasFocus isn't reliable indicator to depend on. // This goes along with NeoToggleconsole which if the toggleconsole is activated on non-root state that can end up diff --git a/mp/src/game/client/neo/ui/neo_root.h b/mp/src/game/client/neo/ui/neo_root.h index 79b1f0bf4..b495a7fc4 100644 --- a/mp/src/game/client/neo/ui/neo_root.h +++ b/mp/src/game/client/neo/ui/neo_root.h @@ -2,6 +2,8 @@ #include #include "GameUI/IGameUI.h" +#include +#include #include "neo_ui.h" #include "neo_root_serverbrowser.h" @@ -167,6 +169,21 @@ class CNeoRoot : public vgui::EditablePanel, public CGameEventListener wchar_t m_wszMap[128]; wchar_t m_wszServerPassword[128] = {}; + + CCallResult m_ccallbackHttp; + void HTTPCallbackRequest(HTTPRequestCompleted_t *request, bool bIOFailure); + + // Display maximum of 5 items on home screen + struct NewsEntry + { + char szSitePath[64]; + wchar_t wszTitle[256]; + }; + static constexpr int MAX_NEWS = 5; + NewsEntry m_news[MAX_NEWS] = {}; + int m_iNewsSize = 0; + void ReadNewsFile(CUtlBuffer &buf); + bool m_bShowBrowserLabel = false; }; extern CNeoRoot *g_pNeoRoot; diff --git a/mp/src/game/client/neo/ui/neo_ui.cpp b/mp/src/game/client/neo/ui/neo_ui.cpp index 7869c38fb..22d9739e3 100644 --- a/mp/src/game/client/neo/ui/neo_ui.cpp +++ b/mp/src/game/client/neo/ui/neo_ui.cpp @@ -65,6 +65,12 @@ void SwapFont(const EFont eFont) surface()->DrawSetTextFont(g_pCtx->fonts[g_pCtx->eFont].hdl); } +void SwapColorNormal(const Color &color) +{ + g_pCtx->normalBgColor = color; + surface()->DrawSetColor(g_pCtx->normalBgColor); +} + void BeginContext(NeoUI::Context *ctx, const NeoUI::Mode eMode, const wchar_t *wszTitle, const char *pSzCtxName) { g_pCtx = ctx; @@ -80,6 +86,7 @@ void BeginContext(NeoUI::Context *ctx, const NeoUI::Mode eMode, const wchar_t *w g_pCtx->eLabelTextStyle = TEXTSTYLE_LEFT; g_pCtx->bTextEditIsPassword = false; g_pCtx->selectBgColor = COLOR_NEOPANELSELECTBG; + g_pCtx->normalBgColor = COLOR_NEOPANELACCENTBG; // Different pointer, change context if (g_pCtx->pSzCurCtxName != pSzCtxName) { @@ -242,7 +249,7 @@ void BeginSection(const bool bDefaultFocus) g_pCtx->dPanel.x + g_pCtx->dPanel.wide, g_pCtx->dPanel.y + g_pCtx->dPanel.tall); - surface()->DrawSetColor(COLOR_NEOPANELACCENTBG); + surface()->DrawSetColor(g_pCtx->normalBgColor); surface()->DrawSetTextColor(COLOR_NEOPANELTEXTNORMAL); break; case MODE_KEYPRESSED: @@ -373,7 +380,7 @@ static void InternalUpdatePartitionState(const GetMouseinFocusedRet wdgState) ++g_pCtx->iWidget; if (wdgState.bActive || wdgState.bHot) { - surface()->DrawSetColor(COLOR_NEOPANELACCENTBG); + surface()->DrawSetColor(g_pCtx->normalBgColor); surface()->DrawSetTextColor(COLOR_NEOPANELTEXTNORMAL); } } @@ -589,7 +596,7 @@ void Tabs(const wchar_t **wszLabelsList, const int iLabelsSize, int *iIndex) { // NEO NOTE (nullsystem): On the final tab, just expand it to the end width as iTabWide isn't always going // to give a properly aligned width - surface()->DrawSetColor(bHoverTab ? COLOR_NEOPANELSELECTBG : COLOR_NEOPANELACCENTBG); + surface()->DrawSetColor(bHoverTab ? COLOR_NEOPANELSELECTBG : g_pCtx->normalBgColor); GCtxDrawFilledRectXtoX(iXPosTab, (i == (iLabelsSize - 1)) ? (g_pCtx->dPanel.wide) : (iXPosTab + iTabWide)); } const wchar_t *wszText = wszLabelsList[i]; @@ -976,4 +983,25 @@ bool Bind(const ButtonCode_t eCode) return g_pCtx->eMode == MODE_KEYPRESSED && g_pCtx->eCode == eCode; } +void OpenURL(const char *szBaseUrl, const char *szPath) +{ + Assert(szPath && szPath[0] == '/'); + if (!szPath || szPath[0] != '/') + { + // Must start with / otherwise don't open URL + return; + } + + static constexpr char CMD[] = +#ifdef _WIN32 + "start" +#else + "xdg-open" +#endif + ; + char syscmd[512] = {}; + V_sprintf_safe(syscmd, "%s %s%s", CMD, szBaseUrl, szPath); + system(syscmd); +} + } // namespace NeoUI diff --git a/mp/src/game/client/neo/ui/neo_ui.h b/mp/src/game/client/neo/ui/neo_ui.h index 017d882db..42b3fad3a 100644 --- a/mp/src/game/client/neo/ui/neo_ui.h +++ b/mp/src/game/client/neo/ui/neo_ui.h @@ -118,6 +118,7 @@ struct Context wchar_t unichar; Color bgColor; Color selectBgColor; + Color normalBgColor; // Mouse handling int iMouseAbsX; @@ -192,7 +193,9 @@ void BeginSection(const bool bDefaultFocus = false); void EndSection(); void BeginHorizontal(const int iHorizontalWidth); void EndHorizontal(); + void SwapFont(const EFont eFont); +void SwapColorNormal(const Color &color); struct RetButton { @@ -216,4 +219,5 @@ void SliderInt(const wchar_t *wszLeftLabel, int *iValue, const int iMin, const i const wchar_t *wszSpecialText = nullptr); void TextEdit(const wchar_t *wszLeftLabel, wchar_t *wszText, const int iMaxSize); bool Bind(const ButtonCode_t eCode); +void OpenURL(const char *szBaseUrl, const char *szPath); } diff --git a/mp/src/game/shared/neo/neo_version_info.cpp.in b/mp/src/game/shared/neo/neo_version_info.cpp.in index 0a9d76b5e..65bebd93b 100644 --- a/mp/src/game/shared/neo/neo_version_info.cpp.in +++ b/mp/src/game/shared/neo/neo_version_info.cpp.in @@ -10,8 +10,9 @@ const char* GIT_LONGHASH = "@GIT_LONGHASH@"; const char* OS_BUILD = "@OS_BUILD@"; const char* COMPILER_ID = "@COMPILER_ID@"; const char* COMPILER_VERSION = "@COMPILER_VERSION@"; +const char* GIT_LATESTTAG = "@GIT_LATESTTAG@"; -static constexpr wchar_t INTERN_BUILD_DISPLAY[] = L"Build: @BUILD_DATE_SHORT@_@GIT_HASH@ (@OS_BUILD@)"; +inline const wchar_t INTERN_BUILD_DISPLAY[] = L"Build: @GIT_LATESTTAG@ (@BUILD_DATE_SHORT@_@GIT_HASH@, @OS_BUILD@)"; const wchar_t* BUILD_DISPLAY = INTERN_BUILD_DISPLAY; static const int INTERN_BUILD_DISPLAY_SIZE = sizeof(INTERN_BUILD_DISPLAY) / sizeof(wchar_t); const int* BUILD_DISPLAY_SIZE = &INTERN_BUILD_DISPLAY_SIZE; diff --git a/mp/src/game/shared/neo/neo_version_info.h b/mp/src/game/shared/neo/neo_version_info.h index bb7abe0f5..602c3c5e9 100644 --- a/mp/src/game/shared/neo/neo_version_info.h +++ b/mp/src/game/shared/neo/neo_version_info.h @@ -8,5 +8,6 @@ static constexpr int GIT_LONGHASH_SIZE = 41; extern const char* OS_BUILD; extern const char* COMPILER_ID; extern const char* COMPILER_VERSION; +extern const char* GIT_LATESTTAG; extern const wchar_t* BUILD_DISPLAY; extern const int* BUILD_DISPLAY_SIZE;