diff --git a/mp/src/game/client/CMakeLists.txt b/mp/src/game/client/CMakeLists.txt index 55bdffefe7..9fbd996e7c 100644 --- a/mp/src/game/client/CMakeLists.txt +++ b/mp/src/game/client/CMakeLists.txt @@ -113,7 +113,7 @@ if(OS_WINDOWS) AVI_VIDEO WMV_VIDEO fopen=dont_use_fopen - + PSAPI_VERSION=1 ) target_link_libraries(client @@ -121,6 +121,7 @@ if(OS_WINDOWS) legacy_stdio_definitions.lib #winmm.lib libz + Psapi.lib ) endif() diff --git a/mp/src/game/client/c_playerresource.cpp b/mp/src/game/client/c_playerresource.cpp index ae32ed81f2..7dae9a36db 100644 --- a/mp/src/game/client/c_playerresource.cpp +++ b/mp/src/game/client/c_playerresource.cpp @@ -60,6 +60,10 @@ BEGIN_PREDICTION_DATA( C_PlayerResource ) END_PREDICTION_DATA() +#ifdef NEO +extern ConVar neo_cl_streamermode; +#endif + C_PlayerResource *g_PR; IGameResources * GameResources( void ) { return g_PR; } @@ -187,6 +191,15 @@ const char *C_PlayerResource::GetPlayerName( int iIndex ) } #ifdef NEO + if (neo_cl_streamermode.GetBool() && !IsLocalPlayer(iIndex)) + { + [[maybe_unused]] uchar32 u32Out; + bool bError = false; + const int iSize = Q_UTF8ToUChar32(m_szName[iIndex], u32Out, bError); + if (!bError) V_memcpy(gStreamerModeNames[iIndex], m_szName[iIndex], iSize); + return gStreamerModeNames[iIndex]; + } + const bool clientWantNeoName = C_NEO_Player::GetLocalNEOPlayer()->ClientWantNeoName(); const int dupeIdx = m_iNeoNameDupeIdx[iIndex]; diff --git a/mp/src/game/client/cdll_client_int.cpp b/mp/src/game/client/cdll_client_int.cpp index 5957b50e8e..ea71f7d5d4 100644 --- a/mp/src/game/client/cdll_client_int.cpp +++ b/mp/src/game/client/cdll_client_int.cpp @@ -174,8 +174,19 @@ extern vgui::IInputInternal *g_InputInternal; #include "neo_version.h" #include "neo_mount_original.h" #include "ui/neo_loading.h" +#include "neo_player_shared.h" extern bool NeoRootCaptureESC(); extern CNeoLoading *g_pNeoLoading; +extern ConVar neo_cl_streamermode_autodetect_obs; +bool g_bOBSDetected = false; + +#ifdef WIN32 +#include +#include +#include +#include +#undef CreateEvent +#endif #ifdef LINUX #include "neo_fixup_glshaders.h" @@ -1273,7 +1284,69 @@ void CHLClient::PostInit() } NeoToggleConsoleEnforce(); + + // Detect OBS - Only on startup + if (neo_cl_streamermode_autodetect_obs.GetBool()) + { + g_bOBSDetected = false; +#ifdef LINUX + FileFindHandle_t findHdl; + for (const char *pszFilename = filesystem->FindFirst("/proc/*", &findHdl); + pszFilename && !g_bOBSDetected; + pszFilename = filesystem->FindNext(findHdl)) + { + if (!filesystem->FindIsDirectory(findHdl)) + { + continue; + } + + char szCmdlinePath[MAX_PATH]; + V_sprintf_safe(szCmdlinePath, "/proc/%s/cmdline", pszFilename); + if (!filesystem->FileExists(szCmdlinePath)) + { + continue; + } + + // NEO NOTE (nullsystem): At this point, cannot just do filesystem->ReadFile + // as it won't output anything. Instead just cat the file and fetch the line. + char szCmdCat[sizeof(szCmdlinePath) + 16]; + V_sprintf_safe(szCmdCat, "cat \"%s\"", szCmdlinePath); + FILE *fp = popen(szCmdCat, "r"); + if (fp) + { + char szCmdline[512] = {}; + if (fgets(szCmdline, sizeof(szCmdline), fp)) + { + // NEO TODO (nullsystem): Check on OBS flatpak, snaps, nix, and appimage. + // Could they give out different cmdline? + g_bOBSDetected = (V_strcmp(szCmdline, "obs") == 0); + } + pclose(fp); + } + } + filesystem->FindClose(findHdl); +#else // WIN32 + HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapShot != INVALID_HANDLE_VALUE) + { + PROCESSENTRY32 processInfo = {}; + processInfo.dwSize = sizeof(PROCESSENTRY32); + while (!g_bOBSDetected && Process32Next(hSnapShot, &processInfo) != FALSE) + { + g_bOBSDetected = (V_strcmp(processInfo.szExeFile, "obs64.exe") == 0); + } + CloseHandle(hSnapShot); + } #endif + ConVarRef("neo_cl_streamermode").SetValue(g_bOBSDetected); + } + + // Initialize streamer mode names + for (int i = 0; i < MAX_PLAYERS + 1; ++i) + { + V_memset(gStreamerModeNames[i], '.', 5); + } +#endif // NEO } //----------------------------------------------------------------------------- diff --git a/mp/src/game/client/clientmode_shared.cpp b/mp/src/game/client/clientmode_shared.cpp index da63a78d05..05c50609f0 100644 --- a/mp/src/game/client/clientmode_shared.cpp +++ b/mp/src/game/client/clientmode_shared.cpp @@ -92,6 +92,7 @@ class CHudVote; #ifdef NEO static CDllDemandLoader g_gameUI("GameUI"); +extern ConVar neo_cl_streamermode; #endif static vgui::HContext s_hVGuiContext = DEFAULT_VGUI_CONTEXT; @@ -1064,6 +1065,13 @@ void ClientModeShared::FireGameEvent( IGameEvent *event ) if ( !IsInCommentaryMode() ) { +#ifdef NEO + if (neo_cl_streamermode.GetBool()) + { + hudChat->Printf(CHAT_FILTER_JOINLEAVE, "Player connected"); + return; + } +#endif wchar_t wszLocalized[100]; wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH]; g_pVGuiLocalize->ConvertANSIToUnicode( event->GetString("name"), wszPlayerName, sizeof(wszPlayerName) ); @@ -1077,6 +1085,14 @@ void ClientModeShared::FireGameEvent( IGameEvent *event ) } else if ( Q_strcmp( "player_disconnect", eventname ) == 0 ) { +#ifdef NEO + if (neo_cl_streamermode.GetBool()) + { + hudChat->Printf(CHAT_FILTER_JOINLEAVE, "Player disconnected"); + return; + } +#endif + C_BasePlayer *pPlayer = USERID2PLAYER( event->GetInt("userid") ); if ( !hudChat || !pPlayer ) diff --git a/mp/src/game/client/hud_basechat.cpp b/mp/src/game/client/hud_basechat.cpp index 72a7d14eb6..70057d773a 100644 --- a/mp/src/game/client/hud_basechat.cpp +++ b/mp/src/game/client/hud_basechat.cpp @@ -53,6 +53,9 @@ Color g_ColorDarkGreen( 64, 255, 64, 255 ); Color g_ColorYellow( 255, 178, 0, 255 ); Color g_ColorGrey( 204, 204, 204, 255 ); +#ifdef NEO +extern ConVar neo_cl_streamermode; +#endif // removes all color markup characters, so Msg can deal with the string properly // returns a pointer to str @@ -833,6 +836,14 @@ void CBaseHudChat::MsgFunc_SayText2( bf_read &msg ) ReadLocalizedString( msg, szBuf[3], sizeof( szBuf[3] ), true ); ReadLocalizedString( msg, szBuf[4], sizeof( szBuf[4] ), true ); +#ifdef NEO + if (neo_cl_streamermode.GetBool()) + { + V_memset(szBuf[1], 0, sizeof(szBuf[1])); + g_pVGuiLocalize->ConvertANSIToUnicode(g_PR->GetPlayerName(client), szBuf[1], sizeof(szBuf[1])); + } +#endif + g_pVGuiLocalize->ConstructString( szBuf[5], sizeof( szBuf[5] ), msg_text, 4, szBuf[1], szBuf[2], szBuf[3], szBuf[4] ); char ansiString[512]; @@ -1841,6 +1852,18 @@ void CBaseHudChat::ChatPrintf( int iPlayerIndex, int iFilter, const char *fmt, . return; } +#ifdef NEO + if (neo_cl_streamermode.GetBool()) + { + auto neoPlayer = static_cast(UTIL_PlayerByIndex(iPlayerIndex)); + if (neoPlayer && !neoPlayer->IsLocalPlayer()) + { + // Only print local player's chat in streamer mode + return; + } + } +#endif + // If a player is muted for voice, also mute them for text because jerks gonna jerk. if ( cl_mute_all_comms.GetBool() && iPlayerIndex != 0 ) { @@ -1943,4 +1966,4 @@ void CBaseHudChat::FireGameEvent( IGameEvent *event ) ChatPrintf( player->entindex(), CHAT_FILTER_NONE, "(SourceTV) %s", event->GetString( "text" ) ); } #endif -} \ No newline at end of file +} diff --git a/mp/src/game/client/neo/c_neo_player.cpp b/mp/src/game/client/neo/c_neo_player.cpp index 2c442c4cc8..0c6781c5d6 100644 --- a/mp/src/game/client/neo/c_neo_player.cpp +++ b/mp/src/game/client/neo/c_neo_player.cpp @@ -200,6 +200,8 @@ USER_MESSAGE_REGISTER(IdleRespawnShowMenu); ConVar cl_drawhud_quickinfo("cl_drawhud_quickinfo", "0", 0, "Whether to display HL2 style ammo/health info near crosshair.", true, 0.0f, true, 1.0f); +ConVar neo_cl_streamermode("neo_cl_streamermode", "0", FCVAR_ARCHIVE | FCVAR_USERINFO, "Streamer mode turns player names into generic names and hide avatars.", true, 0.0f, true, 1.0f); +ConVar neo_cl_streamermode_autodetect_obs("neo_cl_streamermode_autodetect_obs", "0", FCVAR_ARCHIVE, "Automatically turn neo_cl_streamermode on if OBS was detected on startup.", true, 0.0f, true, 1.0f); class NeoLoadoutMenu_Cb : public ICommandCallback { @@ -553,7 +555,7 @@ int C_NEO_Player::GetAttackersScores(const int attackerIdx) const return min(m_rfAttackersScores.Get(attackerIdx), 100); } -const char *C_NEO_Player::GetNeoPlayerName() const +const char *C_NEO_Player::InternalGetNeoPlayerName() const { const int dupePos = m_szNameDupePos; const bool localWantNeoName = GetLocalNEOPlayer()->ClientWantNeoName(); @@ -585,6 +587,20 @@ const char *C_NEO_Player::GetNeoPlayerName() const return stndName; } +const char *C_NEO_Player::GetNeoPlayerName() const +{ + if (neo_cl_streamermode.GetBool() && !IsLocalPlayer()) + { + [[maybe_unused]] uchar32 u32Out; + bool bError = false; + const char *pSzName = InternalGetNeoPlayerName(); + const int iSize = Q_UTF8ToUChar32(pSzName, u32Out, bError); + if (!bError) V_memcpy(gStreamerModeNames[entindex()], pSzName, iSize); + return gStreamerModeNames[entindex()]; + } + return InternalGetNeoPlayerName(); +} + bool C_NEO_Player::ClientWantNeoName() const { return m_bClientWantNeoName; diff --git a/mp/src/game/client/neo/c_neo_player.h b/mp/src/game/client/neo/c_neo_player.h index dd7ab5aa60..2a9f9b14c7 100644 --- a/mp/src/game/client/neo/c_neo_player.h +++ b/mp/src/game/client/neo/c_neo_player.h @@ -155,6 +155,7 @@ class C_NEO_Player : public C_HL2MP_Player int GetAttackersScores(const int attackerIdx) const; int GetAttackerHits(const int attackerIdx) const; + const char *InternalGetNeoPlayerName() const; const char *GetNeoPlayerName() const; bool ClientWantNeoName() const; diff --git a/mp/src/game/client/neo/ui/neo_hud_round_state.cpp b/mp/src/game/client/neo/ui/neo_hud_round_state.cpp index 70ce4cdcf7..0112f04564 100644 --- a/mp/src/game/client/neo/ui/neo_hud_round_state.cpp +++ b/mp/src/game/client/neo/ui/neo_hud_round_state.cpp @@ -32,6 +32,7 @@ ConVar neo_cl_squad_hud_original("neo_cl_squad_hud_original", "0", FCVAR_ARCHIVE ConVar neo_cl_squad_hud_star_scale("neo_cl_squad_hud_star_scale", "0", FCVAR_ARCHIVE, "Scaling to apply from 1080p, 0 disables scaling"); extern ConVar neo_sv_dm_win_xp; +extern ConVar neo_cl_streamermode; namespace { constexpr int Y_POS = 2; @@ -775,13 +776,18 @@ void CNEOHud_RoundState::UpdatePlayerAvatar(int playerIndex) void CNEOHud_RoundState::SetTextureToAvatar(int playerIndex) { + if (neo_cl_streamermode.GetBool()) + { + return; + } + player_info_t pi; if (!engine->GetPlayerInfo(playerIndex, &pi)) return; if (!pi.friendsID) return; - + CSteamID steamIDForPlayer(pi.friendsID, 1, steamapicontext->SteamUtils()->GetConnectedUniverse(), k_EAccountTypeIndividual); const int mapIndex = m_mapAvatarsToImageList.Find(steamIDForPlayer); if ((mapIndex == m_mapAvatarsToImageList.InvalidIndex())) diff --git a/mp/src/game/client/neo/ui/neo_root.cpp b/mp/src/game/client/neo/ui/neo_root.cpp index 8e598beee9..e130ac6d73 100644 --- a/mp/src/game/client/neo/ui/neo_root.cpp +++ b/mp/src/game/client/neo/ui/neo_root.cpp @@ -42,6 +42,7 @@ static CDllDemandLoader g_GameUIDLL("GameUI"); extern ConVar neo_name; extern ConVar cl_onlysteamnick; +extern ConVar neo_cl_streamermode; CNeoRoot *g_pNeoRoot = nullptr; void NeoToggleconsole(); @@ -646,15 +647,18 @@ void CNeoRoot::MainLoopRoot(const MainLoopParam param) int iStatusTall = 0; if (eCurStatus != k_EPersonaStateMax) { - const wchar_t *wszState = WSZ_PERSONA_STATES[static_cast(eCurStatus)]; + wchar_t wszStatusTotal[48]; + const int iStatusTotalSize = V_swprintf_safe(wszStatusTotal, L"%ls%ls", + WSZ_PERSONA_STATES[static_cast(eCurStatus)], + neo_cl_streamermode.GetBool() ? L" [Streamer mode on]" : L""); const int iStatusTextStartPosY = g_uiCtx.iMarginY + iMainTextHeight + g_uiCtx.iMarginY; [[maybe_unused]] int iStatusWide; surface()->DrawSetTextFont(g_uiCtx.fonts[NeoUI::FONT_NTNORMAL].hdl); - surface()->GetTextSize(g_uiCtx.fonts[NeoUI::FONT_NTNORMAL].hdl, wszState, iStatusWide, iStatusTall); + surface()->GetTextSize(g_uiCtx.fonts[NeoUI::FONT_NTNORMAL].hdl, wszStatusTotal, iStatusWide, iStatusTall); surface()->DrawSetTextPos(iSteamPlaceXStart + iMainTextStartPosX, iRightSideYStart + iStatusTextStartPosY); - surface()->DrawPrintText(wszState, V_wcslen(wszState)); + surface()->DrawPrintText(wszStatusTotal, iStatusTotalSize); } // Put the news title in either from avatar or text end Y position @@ -1202,12 +1206,15 @@ void CNeoRoot::MainLoopServerDetails(const MainLoopParam param) wchar_t wszText[128]; if (bP) g_pVGuiLocalize->ConvertANSIToUnicode(gameServer->GetName(), wszText, sizeof(wszText)); NeoUI::Label(L"Name:", wszText); - if (bP) g_pVGuiLocalize->ConvertANSIToUnicode(gameServer->m_NetAdr.GetConnectionAddressString(), wszText, sizeof(wszText)); - NeoUI::Label(L"Address:", wszText); + if (!neo_cl_streamermode.GetBool()) + { + if (bP) g_pVGuiLocalize->ConvertANSIToUnicode(gameServer->m_NetAdr.GetConnectionAddressString(), wszText, sizeof(wszText)); + NeoUI::Label(L"Address:", wszText); + } if (bP) g_pVGuiLocalize->ConvertANSIToUnicode(gameServer->m_szMap, wszText, sizeof(wszText)); NeoUI::Label(L"Map:", wszText); if (bP) V_swprintf_safe(wszText, L"%d/%d", gameServer->m_nPlayers, gameServer->m_nMaxPlayers); - NeoUI::Label(L"Ping:", wszText); + NeoUI::Label(L"Players:", wszText); if (bP) V_swprintf_safe(wszText, L"%ls", gameServer->m_bSecure ? L"Enabled" : L"Disabled"); NeoUI::Label(L"VAC:", wszText); if (bP) V_swprintf_safe(wszText, L"%d", gameServer->m_nPing); @@ -1220,18 +1227,21 @@ void CNeoRoot::MainLoopServerDetails(const MainLoopParam param) g_uiCtx.dPanel.tall = (iTallTotal - g_uiCtx.iRowTall) - g_uiCtx.dPanel.tall; NeoUI::BeginSection(); { - if (m_serverPlayers.m_players.IsEmpty()) + if (!neo_cl_streamermode.GetBool()) { - g_uiCtx.eLabelTextStyle = NeoUI::TEXTSTYLE_CENTER; - NeoUI::Label(L"There are no players in the server."); - g_uiCtx.eLabelTextStyle = NeoUI::TEXTSTYLE_LEFT; - } - else - { - for (const auto &player : m_serverPlayers.m_players) + if (m_serverPlayers.m_players.IsEmpty()) + { + g_uiCtx.eLabelTextStyle = NeoUI::TEXTSTYLE_CENTER; + NeoUI::Label(L"There are no players in the server."); + g_uiCtx.eLabelTextStyle = NeoUI::TEXTSTYLE_LEFT; + } + else { - NeoUI::Label(player.wszName); - // TODO + for (const auto &player : m_serverPlayers.m_players) + { + NeoUI::Label(player.wszName); + // TODO + } } } } diff --git a/mp/src/game/client/neo/ui/neo_root_settings.cpp b/mp/src/game/client/neo/ui/neo_root_settings.cpp index c34ffb38ab..5b147703c7 100644 --- a/mp/src/game/client/neo/ui/neo_root_settings.cpp +++ b/mp/src/game/client/neo/ui/neo_root_settings.cpp @@ -22,6 +22,8 @@ extern CNeoRoot *g_pNeoRoot; extern NeoUI::Context g_uiCtx; extern int g_iRowsInScreen; +extern bool g_bOBSDetected; +extern ConVar neo_cl_streamermode; const wchar_t *QUALITY_LABELS[] = { L"Low", @@ -239,6 +241,8 @@ void NeoSettingsRestore(NeoSettings *ns, const NeoSettings::Keys::Flags flagsKey } } } + pGeneral->bStreamerMode = cvr->neo_cl_streamermode.GetBool(); + pGeneral->bAutoDetectOBS = cvr->neo_cl_streamermode_autodetect_obs.GetBool(); } { NeoSettings::Keys *pKeys = &ns->keys; @@ -438,6 +442,8 @@ void NeoSettingsSave(const NeoSettings *ns) cvr->cl_showpos.SetValue(pGeneral->bShowPos); cvr->cl_showfps.SetValue(pGeneral->iShowFps); cvr->cl_downloadfilter.SetValue(DLFILTER_STRMAP[pGeneral->iDlFilter]); + cvr->neo_cl_streamermode.SetValue(pGeneral->bStreamerMode); + cvr->neo_cl_streamermode_autodetect_obs.SetValue(pGeneral->bAutoDetectOBS); } { const NeoSettings::Keys *pKeys = &ns->keys; @@ -635,6 +641,9 @@ void NeoSettings_General(NeoSettings *ns) NeoUI::RingBoxBool(L"Show position", &pGeneral->bShowPos); NeoUI::RingBox(L"Show FPS", SHOWFPS_LABELS, ARRAYSIZE(SHOWFPS_LABELS), &pGeneral->iShowFps); NeoUI::RingBox(L"Download filter", DLFILTER_LABELS, ARRAYSIZE(DLFILTER_LABELS), &pGeneral->iDlFilter); + NeoUI::RingBoxBool(L"Streamer mode", &pGeneral->bStreamerMode); + NeoUI::RingBoxBool(L"Auto streamer mode (requires restart)", &pGeneral->bAutoDetectOBS); + NeoUI::Label(L"OBS detection", g_bOBSDetected ? L"OBS detected on startup" : L"Not detected on startup"); } void NeoSettings_Keys(NeoSettings *ns) diff --git a/mp/src/game/client/neo/ui/neo_root_settings.h b/mp/src/game/client/neo/ui/neo_root_settings.h index 8ce5413774..3ab7cae269 100644 --- a/mp/src/game/client/neo/ui/neo_root_settings.h +++ b/mp/src/game/client/neo/ui/neo_root_settings.h @@ -38,6 +38,8 @@ struct NeoSettings bool bShowPos; int iShowFps; int iDlFilter; + bool bStreamerMode; + bool bAutoDetectOBS; }; struct Keys @@ -160,6 +162,8 @@ struct NeoSettings CONVARREF_DEF(cl_showfps); CONVARREF_DEF(hud_fastswitch); CONVARREF_DEF(neo_cl_toggleconsole); + CONVARREF_DEF(neo_cl_streamermode); + CONVARREF_DEF(neo_cl_streamermode_autodetect_obs); // Multiplayer CONVARREF_DEF(cl_playerspraydisable); diff --git a/mp/src/game/client/neo/ui/neo_scoreboard.cpp b/mp/src/game/client/neo/ui/neo_scoreboard.cpp index feed3faea4..f4a14c06d4 100644 --- a/mp/src/game/client/neo/ui/neo_scoreboard.cpp +++ b/mp/src/game/client/neo/ui/neo_scoreboard.cpp @@ -48,6 +48,7 @@ using namespace vgui; #define SHOW_ENEMY_STATUS true ConVar neo_show_scoreboard_avatars("neo_show_scoreboard_avatars", "1", FCVAR_ARCHIVE, "Show avatars on scoreboard.", true, 0.0, true, 1.0 ); +extern ConVar neo_cl_streamermode; //----------------------------------------------------------------------------- // Purpose: Constructor @@ -265,7 +266,7 @@ void CNEOScoreBoard::ShowPanel(bool bShow) bool CNEOScoreBoard::ShowAvatars() { - return neo_show_scoreboard_avatars.GetBool(); + return neo_show_scoreboard_avatars.GetBool() && !neo_cl_streamermode.GetBool(); } void CNEOScoreBoard::FireGameEvent( IGameEvent *event ) diff --git a/mp/src/game/server/gameinterface.cpp b/mp/src/game/server/gameinterface.cpp index 9d5a76356b..5a5a469e45 100644 --- a/mp/src/game/server/gameinterface.cpp +++ b/mp/src/game/server/gameinterface.cpp @@ -133,6 +133,7 @@ extern ConVar tf_mm_servermode; #ifdef NEO #include "neo_mount_original.h" #include "neo_version.h" +#include "neo_player_shared.h" #endif extern IToolFrameworkServer *g_pToolFrameworkServer; @@ -767,6 +768,14 @@ bool CServerGameDLL::DLLInit( CreateInterfaceFn appSystemFactory, void CServerGameDLL::PostInit() { IGameSystem::PostInitAllSystems(); + +#ifdef NEO + // Initialize streamer mode names + for (int i = 0; i < MAX_PLAYERS + 1; ++i) + { + V_memset(gStreamerModeNames[i], '.', 5); + } +#endif } void CServerGameDLL::DLLShutdown( void ) diff --git a/mp/src/game/server/neo/neo_player.cpp b/mp/src/game/server/neo/neo_player.cpp index c4547d222f..545cb75183 100644 --- a/mp/src/game/server/neo/neo_player.cpp +++ b/mp/src/game/server/neo/neo_player.cpp @@ -1078,7 +1078,7 @@ int CNEO_Player::NameDupePos() const return m_szNameDupePos; } -const char *CNEO_Player::GetNeoPlayerName(const CNEO_Player *viewFrom) const +const char *CNEO_Player::InternalGetNeoPlayerName(const CNEO_Player *viewFrom) const { const bool nameFetchWantNeoName = (viewFrom) ? viewFrom->m_bClientWantNeoName : m_bClientWantNeoName; @@ -1111,6 +1111,20 @@ const char *CNEO_Player::GetNeoPlayerName(const CNEO_Player *viewFrom) const return stndName; } +const char *CNEO_Player::GetNeoPlayerName(const CNEO_Player *viewFrom) const +{ + if (viewFrom && viewFrom->m_bClientStreamermode && viewFrom->entindex() != entindex()) + { + [[maybe_unused]] uchar32 u32Out; + bool bError = false; + const char *pSzName = InternalGetNeoPlayerName(viewFrom); + const int iSize = Q_UTF8ToUChar32(pSzName, u32Out, bError); + if (!bError) V_memcpy(gStreamerModeNames[entindex()], pSzName, iSize); + return gStreamerModeNames[entindex()]; + } + return InternalGetNeoPlayerName(viewFrom); +} + const char *CNEO_Player::GetNeoPlayerNameDirect() const { return m_szNeoNameHasSet ? m_szNeoName.Get() : NULL; @@ -1173,8 +1187,28 @@ bool CNEO_Player::HandleCommand_JoinTeam( int team ) { SetPlayerTeamModel(); - UTIL_ClientPrintAll(HUD_PRINTTALK, "%s1 joined team %s2\n", - GetNeoPlayerName(), GetTeam()->GetName()); + CRecipientFilter filterNonStreamers; + CRecipientFilter filterStreamers; + filterNonStreamers.MakeReliable(); + filterStreamers.MakeReliable(); + for (int i = 1; i <= gpGlobals->maxClients; ++i) + { + auto neoPlayer = static_cast(UTIL_PlayerByIndex(i)); + if (neoPlayer) + { + if (neoPlayer->m_bClientStreamermode) + { + filterStreamers.AddRecipient(neoPlayer); + } + else + { + filterNonStreamers.AddRecipient(neoPlayer); + } + } + } + UTIL_ClientPrintFilter(filterNonStreamers, HUD_PRINTTALK, + "%s1 joined team %s2\n", GetNeoPlayerName(), GetTeam()->GetName()); + UTIL_ClientPrintFilter(filterStreamers, HUD_PRINTTALK, "Player joined team %s1\n", GetTeam()->GetName()); } return isAllowedToJoin; diff --git a/mp/src/game/server/neo/neo_player.h b/mp/src/game/server/neo/neo_player.h index 7cd3350274..a6b1ec3dc5 100644 --- a/mp/src/game/server/neo/neo_player.h +++ b/mp/src/game/server/neo/neo_player.h @@ -126,6 +126,7 @@ class CNEO_Player : public CHL2MP_Player void Weapon_AimToggle(CNEOBaseCombatWeapon *pWep, const NeoWeponAimToggleE toggleType); + const char *InternalGetNeoPlayerName(const CNEO_Player *viewFrom = nullptr) const; // "neo_name" if available otherwise "name" // Set "viewFrom" if fetching the name in the view of another player const char *GetNeoPlayerName(const CNEO_Player *viewFrom = nullptr) const; @@ -261,6 +262,7 @@ class CNEO_Player : public CHL2MP_Player bool m_bIsPendingTKKick = false; // To not spam the kickid ConCommand bool m_bDoNotShowDmgInfoMenu = false; EMenuSelectType m_eMenuSelectType = MENU_SELECT_TYPE_NONE; + bool m_bClientStreamermode = false; private: bool m_bFirstDeathTick; diff --git a/mp/src/game/shared/neo/neo_gamerules.cpp b/mp/src/game/shared/neo/neo_gamerules.cpp index 59963d58a5..1552b0d38b 100644 --- a/mp/src/game/shared/neo/neo_gamerules.cpp +++ b/mp/src/game/shared/neo/neo_gamerules.cpp @@ -1524,6 +1524,7 @@ void CNEORules::CheckChatCommand(CNEO_Player *pNeoCmdPlayer, const char *pSzChat { bool bHasPlayersInList = false; bool bHasUnreadyPlayers = false; + const bool bIsStreamer = pNeoCmdPlayer->m_bClientStreamermode; char szPrintText[((MAX_PLAYER_NAME_LENGTH + 1) * MAX_PLAYERS) + 32]; szPrintText[0] = '\0'; ReadyPlayers readyPlayers = {}; @@ -1539,31 +1540,34 @@ void CNEORules::CheckChatCommand(CNEO_Player *pNeoCmdPlayer, const char *pSzChat readyPlayers.array[pNeoOtherPlayer->GetTeamNumber()] += bPlayerReady; if (!bHasPlayersInList) { - V_strcat_safe(szPrintText, "Ready list:\n"); + if (!bIsStreamer) V_strcat_safe(szPrintText, "Ready list:\n"); bHasPlayersInList = true; } V_strcat_safe(szPrintText, pNeoOtherPlayer->GetNeoPlayerName(pNeoCmdPlayer)); if (bPlayerReady) { - V_strcat_safe(szPrintText, " [READY]"); + if (!bIsStreamer) V_strcat_safe(szPrintText, " [READY]"); } else { - V_strcat_safe(szPrintText, " [NOT READY]"); + if (!bIsStreamer) V_strcat_safe(szPrintText, " [NOT READY]"); bHasUnreadyPlayers = true; } - V_strcat_safe(szPrintText, "\n"); + if (!bIsStreamer) V_strcat_safe(szPrintText, "\n"); } } - if (bHasPlayersInList) + if (!bIsStreamer && bHasPlayersInList) { ClientPrint(pNeoCmdPlayer, HUD_PRINTTALK, szPrintText); } - if (!bHasUnreadyPlayers) + if (bIsStreamer || !bHasUnreadyPlayers) { - ClientPrint(pNeoCmdPlayer, HUD_PRINTTALK, "All players are ready."); + if (!bHasUnreadyPlayers) + { + ClientPrint(pNeoCmdPlayer, HUD_PRINTTALK, "All players are ready."); + } if (readyPlayers.array[TEAM_JINRAI] < iThres || readyPlayers.array[TEAM_NSF] < iThres) { const int iNeedJin = max(0, iThres - readyPlayers.array[TEAM_JINRAI]); @@ -2466,6 +2470,8 @@ void CNEORules::ClientSettingsChanged(CBasePlayer *pPlayer) updateDupeCheck = true; } pNEOPlayer->SetClientWantNeoName(clientAllowsNeoName); + const auto optClStreamerMode = StrToInt(engine->GetClientConVarValue(engine->IndexOfEdict(pNEOPlayer->edict()), "neo_cl_streamermode")); + pNEOPlayer->m_bClientStreamermode = (optClStreamerMode && *optClStreamerMode); const char *pszName = pszSteamName; const char *pszOldName = pPlayer->GetPlayerName(); @@ -2481,7 +2487,17 @@ void CNEORules::ClientSettingsChanged(CBasePlayer *pPlayer) char text[256]; Q_snprintf(text, sizeof(text), "%s changed name to %s\n", pszOldName, pszName); - UTIL_ClientPrintAll(HUD_PRINTTALK, text); + CRecipientFilter filterNonStreamers; + filterNonStreamers.MakeReliable(); + for (int i = 1; i <= gpGlobals->maxClients; ++i) + { + auto neoPlayer = static_cast(UTIL_PlayerByIndex(i)); + if (neoPlayer && !neoPlayer->m_bClientStreamermode) + { + filterNonStreamers.AddRecipient(neoPlayer); + } + } + UTIL_ClientPrintFilter(filterNonStreamers, HUD_PRINTTALK, text); IGameEvent *event = gameeventmanager->CreateEvent("player_changename"); if (event) diff --git a/mp/src/game/shared/neo/neo_player_shared.h b/mp/src/game/shared/neo/neo_player_shared.h index 1b2bffd19c..f4f508a9e9 100644 --- a/mp/src/game/shared/neo/neo_player_shared.h +++ b/mp/src/game/shared/neo/neo_player_shared.h @@ -311,4 +311,6 @@ struct PlayerXPInfo void DMClSortedPlayers(PlayerXPInfo (*pPlayersOrder)[MAX_PLAYERS + 1], int *piTotalPlayers); #endif +inline char gStreamerModeNames[MAX_PLAYERS + 1][MAX_PLAYER_NAME_LENGTH + 1]; + #endif // NEO_PLAYER_SHARED_H