Skip to content

Commit

Permalink
Load up news from remote server on startup with caching/rate limit
Browse files Browse the repository at this point in the history
  • Loading branch information
nullsystem committed Sep 14, 2024
1 parent abe3cd9 commit 7afe8ca
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions mp/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ if(OS_LINUX)
add_compile_definitions(
LINUX
_LINUX
_FILE_OFFSET_BITS=64
_TIME_BITS=64
)
endif()

Expand Down
157 changes: 150 additions & 7 deletions mp/src/game/client/neo/ui/neo_root.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include <voice_status.h>
#include <c_playerresource.h>
#include <ivoicetweak.h>
#include "tier1/interface.h"
#include <ctime>

#include <vgui/IInput.h>
#include <vgui_controls/Controls.h>
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -178,6 +205,40 @@ 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 can use GCC 64-bit compiled time_t or Win32 API direct to side-step IFileSystem "long" 32-bit limitation for now.
// 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();
Expand Down Expand Up @@ -219,6 +280,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();
Expand Down Expand Up @@ -460,7 +522,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);
Expand Down Expand Up @@ -572,14 +634,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();
Expand Down Expand Up @@ -1276,6 +1350,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
Expand Down
17 changes: 17 additions & 0 deletions mp/src/game/client/neo/ui/neo_root.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <vgui_controls/EditablePanel.h>
#include "GameUI/IGameUI.h"
#include <steam/isteamhttp.h>
#include <steam/steam_api.h>

#include "neo_ui.h"
#include "neo_root_serverbrowser.h"
Expand Down Expand Up @@ -167,6 +169,21 @@ class CNeoRoot : public vgui::EditablePanel, public CGameEventListener
wchar_t m_wszMap[128];

wchar_t m_wszServerPassword[128] = {};

CCallResult<CNeoRoot, HTTPRequestCompleted_t> 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;
34 changes: 31 additions & 3 deletions mp/src/game/client/neo/ui/neo_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
{
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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
4 changes: 4 additions & 0 deletions mp/src/game/client/neo/ui/neo_ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ struct Context
wchar_t unichar;
Color bgColor;
Color selectBgColor;
Color normalBgColor;

// Mouse handling
int iMouseAbsX;
Expand Down Expand Up @@ -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
{
Expand All @@ -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);
}

0 comments on commit 7afe8ca

Please sign in to comment.