From b2aebaebae0343213dd69d45f30eac81f852dcfc Mon Sep 17 00:00:00 2001 From: Rain Date: Tue, 15 Jul 2025 02:24:15 +0300 Subject: [PATCH 1/8] wip --- src/game/client/c_playerresource.cpp | 17 ++++++++++++++++- src/game/client/c_playerresource.h | 2 ++ src/game/client/clientmode_shared.cpp | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/game/client/c_playerresource.cpp b/src/game/client/c_playerresource.cpp index 85b2593fc1..a1465d0857 100644 --- a/src/game/client/c_playerresource.cpp +++ b/src/game/client/c_playerresource.cpp @@ -141,6 +141,19 @@ void C_PlayerResource::OnDataChanged(DataUpdateType_t updateType) } } +typedef unsigned char useridCache_t; +#pragma push_macro("max") +#undef max +constexpr auto useridNumericLimit{ std::numeric_limits::max() }; +#pragma pop_macro("max") +static_assert(MAX_PLAYERS < useridNumericLimit); +static CUtlMap cpn(DefLessFunc(useridCache_t)); + +const char* C_PlayerResource::GetCachedName(int slot) const +{ + return cpn[slot]; +} + void C_PlayerResource::UpdatePlayerName( int slot ) { if ( slot < 1 || slot > MAX_PLAYERS ) @@ -158,6 +171,8 @@ void C_PlayerResource::UpdatePlayerName( int slot ) if ( IsConnected( slot ) && engine->GetPlayerInfo( slot, &sPlayerInfo ) ) { m_szName[slot] = AllocPooledString( UTIL_GetFilteredPlayerName( slot, sPlayerInfo.name ) ); + + cpn.Insert(sPlayerInfo.userID, m_szName[slot]); } else { @@ -165,7 +180,7 @@ void C_PlayerResource::UpdatePlayerName( int slot ) { m_szName[slot] = m_szUnconnectedName; } - } + } } void C_PlayerResource::ClientThink() diff --git a/src/game/client/c_playerresource.h b/src/game/client/c_playerresource.h index 1aaff38d7c..83f8bb51bb 100644 --- a/src/game/client/c_playerresource.h +++ b/src/game/client/c_playerresource.h @@ -71,6 +71,8 @@ public : // IGameResources interface uint32 GetAccountID( int iIndex ); bool IsValid( int iIndex ); + const char* GetCachedName(int slot) const; + protected: void UpdatePlayerName( int slot ); diff --git a/src/game/client/clientmode_shared.cpp b/src/game/client/clientmode_shared.cpp index 14ff3eab37..a6dfeb41d7 100644 --- a/src/game/client/clientmode_shared.cpp +++ b/src/game/client/clientmode_shared.cpp @@ -1155,6 +1155,10 @@ void ClientModeShared::FireGameEvent( IGameEvent *event ) wchar_t wszReason[64]; const char *pszReason = event->GetString( "reason" ); + + auto foo = g_PR->GetCachedName(event->GetInt("userid")); + Msg("%s\n", foo); + if ( pszReason && ( pszReason[0] == '#' ) && g_pVGuiLocalize->Find( pszReason ) ) { V_wcsncpy( wszReason, g_pVGuiLocalize->Find( pszReason ), sizeof( wszReason ) ); From 4da1987c704d87008bc6d77d342082a374680ab5 Mon Sep 17 00:00:00 2001 From: Rain Date: Tue, 15 Jul 2025 13:47:51 +0300 Subject: [PATCH 2/8] wip --- src/game/client/c_playerresource.cpp | 43 ++++++++++++++++++++-------- src/game/client/c_playerresource.h | 21 +++++++++++++- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/game/client/c_playerresource.cpp b/src/game/client/c_playerresource.cpp index a1465d0857..774c5e2bb3 100644 --- a/src/game/client/c_playerresource.cpp +++ b/src/game/client/c_playerresource.cpp @@ -91,6 +91,9 @@ C_PlayerResource::C_PlayerResource() memset(m_szDispNameWDupeIdx, 0, sizeof(m_szDispNameWDupeIdx)); memset(m_iStar, 0, sizeof(m_iStar)); memset(m_szNeoClantag, 0, sizeof(m_szNeoClantag)); + + m_cachedPlayerNames.SetLessFunc(DefLessFunc(PlayerResource::useridCache_t)); + m_cachedPlayerNames.EnsureCapacity(gpGlobals->maxClients * 2); #endif memset( m_iScore, 0, sizeof( m_iScore ) ); memset( m_iDeaths, 0, sizeof( m_iDeaths ) ); @@ -141,18 +144,12 @@ void C_PlayerResource::OnDataChanged(DataUpdateType_t updateType) } } -typedef unsigned char useridCache_t; -#pragma push_macro("max") -#undef max -constexpr auto useridNumericLimit{ std::numeric_limits::max() }; -#pragma pop_macro("max") -static_assert(MAX_PLAYERS < useridNumericLimit); -static CUtlMap cpn(DefLessFunc(useridCache_t)); - -const char* C_PlayerResource::GetCachedName(int slot) const +#ifdef NEO +const char* C_PlayerResource::GetCachedName(int userid) const { - return cpn[slot]; + return m_cachedPlayerNames[userid]; } +#endif void C_PlayerResource::UpdatePlayerName( int slot ) { @@ -170,9 +167,15 @@ void C_PlayerResource::UpdatePlayerName( int slot ) player_info_t sPlayerInfo; if ( IsConnected( slot ) && engine->GetPlayerInfo( slot, &sPlayerInfo ) ) { + // Get the neo name here(?) m_szName[slot] = AllocPooledString( UTIL_GetFilteredPlayerName( slot, sPlayerInfo.name ) ); - - cpn.Insert(sPlayerInfo.userID, m_szName[slot]); +#ifdef NEO + m_cachedPlayerNames.InsertOrReplace(sPlayerInfo.userID, m_szName[slot]); + if (m_cachedPlayerNames.Count() >= gpGlobals->maxClients * 2) + { + PurgeOldCachedNames(); + } +#endif } else { @@ -183,6 +186,22 @@ void C_PlayerResource::UpdatePlayerName( int slot ) } } +#ifdef NEO +void C_PlayerResource::PurgeOldCachedNames() +{ + for (auto i = m_cachedPlayerNames.FirstInorder(); i != m_cachedPlayerNames.InvalidIndex(); ) + { + const auto idx = i; + i = m_cachedPlayerNames.NextInorder(i); + // TODO: optimize; don't need to run the clients loop for each iteration + if (!UTIL_PlayerByUserId(m_cachedPlayerNames.Key(idx))) + { + m_cachedPlayerNames.RemoveAt(idx); + } + } +} +#endif + void C_PlayerResource::ClientThink() { BaseClass::ClientThink(); diff --git a/src/game/client/c_playerresource.h b/src/game/client/c_playerresource.h index 83f8bb51bb..78a96a896a 100644 --- a/src/game/client/c_playerresource.h +++ b/src/game/client/c_playerresource.h @@ -23,6 +23,17 @@ #define PLAYER_UNCONNECTED_NAME "unconnected" #define PLAYER_ERROR_NAME "ERRORNAME" +#ifdef NEO +namespace PlayerResource { + typedef unsigned char useridCache_t; +#pragma push_macro("max") +#undef max + constexpr auto useridNumericLimit{ std::numeric_limits::max() }; +#pragma pop_macro("max") + static_assert(MAX_PLAYERS < useridNumericLimit); +} +#endif + class C_PlayerResource : public C_BaseEntity, public IGameResources { DECLARE_CLASS( C_PlayerResource, C_BaseEntity ); @@ -71,7 +82,9 @@ public : // IGameResources interface uint32 GetAccountID( int iIndex ); bool IsValid( int iIndex ); - const char* GetCachedName(int slot) const; +#ifdef NEO + const char* GetCachedName(int userid) const; +#endif protected: void UpdatePlayerName( int slot ); @@ -100,6 +113,12 @@ public : // IGameResources interface bool m_bValid[MAX_PLAYERS_ARRAY_SAFE]; int m_iUserID[MAX_PLAYERS_ARRAY_SAFE]; string_t m_szUnconnectedName; + +#ifdef NEO +private: + CUtlMap m_cachedPlayerNames; + void PurgeOldCachedNames(); +#endif }; extern C_PlayerResource *g_PR; From d1b66fa67afa63c3c77fc060cceded9bb182c5cd Mon Sep 17 00:00:00 2001 From: Rain Date: Tue, 15 Jul 2025 13:52:54 +0300 Subject: [PATCH 3/8] wip --- src/game/client/c_playerresource.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/game/client/c_playerresource.cpp b/src/game/client/c_playerresource.cpp index 774c5e2bb3..2f59b17d9a 100644 --- a/src/game/client/c_playerresource.cpp +++ b/src/game/client/c_playerresource.cpp @@ -147,7 +147,12 @@ void C_PlayerResource::OnDataChanged(DataUpdateType_t updateType) #ifdef NEO const char* C_PlayerResource::GetCachedName(int userid) const { - return m_cachedPlayerNames[userid]; + const auto idx = m_cachedPlayerNames.Find(userid); + if (idx == m_cachedPlayerNames.InvalidIndex()) + { + return ""; + } + return m_cachedPlayerNames.Element(idx); } #endif From 1b5bb98d88a820d03c301796e650d4dec517e370 Mon Sep 17 00:00:00 2001 From: Rain Date: Tue, 15 Jul 2025 13:55:18 +0300 Subject: [PATCH 4/8] refactor --- src/game/client/c_playerresource.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/client/c_playerresource.h b/src/game/client/c_playerresource.h index 78a96a896a..49fa98e7ae 100644 --- a/src/game/client/c_playerresource.h +++ b/src/game/client/c_playerresource.h @@ -116,7 +116,7 @@ public : // IGameResources interface #ifdef NEO private: - CUtlMap m_cachedPlayerNames; + CUtlMap m_cachedPlayerNames; void PurgeOldCachedNames(); #endif }; From 8867b3db2444015fc307d3ab5e2efa5b9ff0f9a8 Mon Sep 17 00:00:00 2001 From: Rain Date: Thu, 17 Jul 2025 14:56:17 +0300 Subject: [PATCH 5/8] wip --- src/game/client/c_playerresource.cpp | 4 ++-- src/game/client/c_playerresource.h | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/game/client/c_playerresource.cpp b/src/game/client/c_playerresource.cpp index 2f59b17d9a..5a16f137d7 100644 --- a/src/game/client/c_playerresource.cpp +++ b/src/game/client/c_playerresource.cpp @@ -145,7 +145,7 @@ void C_PlayerResource::OnDataChanged(DataUpdateType_t updateType) } #ifdef NEO -const char* C_PlayerResource::GetCachedName(int userid) const +string_t C_PlayerResource::GetCachedName(int userid) const { const auto idx = m_cachedPlayerNames.Find(userid); if (idx == m_cachedPlayerNames.InvalidIndex()) @@ -188,7 +188,7 @@ void C_PlayerResource::UpdatePlayerName( int slot ) { m_szName[slot] = m_szUnconnectedName; } - } + } } #ifdef NEO diff --git a/src/game/client/c_playerresource.h b/src/game/client/c_playerresource.h index 49fa98e7ae..be2af2ae11 100644 --- a/src/game/client/c_playerresource.h +++ b/src/game/client/c_playerresource.h @@ -83,7 +83,7 @@ public : // IGameResources interface bool IsValid( int iIndex ); #ifdef NEO - const char* GetCachedName(int userid) const; + string_t GetCachedName(int userid) const; #endif protected: @@ -116,7 +116,9 @@ public : // IGameResources interface #ifdef NEO private: - CUtlMap m_cachedPlayerNames; + // This name cache is used for fixing player post-disconnect messages where the disconnecting player is already gone, + // but we may want to display their neo "fake" name instead of their Steam name, which gets reported by the disconnect msg. + CUtlMap m_cachedPlayerNames; void PurgeOldCachedNames(); #endif }; From b3cde8b31c6cbe4ff7b4c4464968f4430393c99c Mon Sep 17 00:00:00 2001 From: Rain Date: Thu, 17 Jul 2025 15:41:11 +0300 Subject: [PATCH 6/8] wip --- src/game/client/c_playerresource.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/game/client/c_playerresource.cpp b/src/game/client/c_playerresource.cpp index 5a16f137d7..b9b34a6b8a 100644 --- a/src/game/client/c_playerresource.cpp +++ b/src/game/client/c_playerresource.cpp @@ -172,10 +172,15 @@ void C_PlayerResource::UpdatePlayerName( int slot ) player_info_t sPlayerInfo; if ( IsConnected( slot ) && engine->GetPlayerInfo( slot, &sPlayerInfo ) ) { - // Get the neo name here(?) m_szName[slot] = AllocPooledString( UTIL_GetFilteredPlayerName( slot, sPlayerInfo.name ) ); #ifdef NEO - m_cachedPlayerNames.InsertOrReplace(sPlayerInfo.userID, m_szName[slot]); + string_t name = m_szName[slot]; + const auto localPlayer = C_NEO_Player::GetLocalNEOPlayer(); + if (localPlayer && localPlayer->ClientWantNeoName()) + { + name = m_szNeoName[slot]; + } + m_cachedPlayerNames.InsertOrReplace(sPlayerInfo.userID, name); if (m_cachedPlayerNames.Count() >= gpGlobals->maxClients * 2) { PurgeOldCachedNames(); From 638b828db833d72ad3f105350bb1dcf082e40111 Mon Sep 17 00:00:00 2001 From: Rain Date: Thu, 17 Jul 2025 17:03:33 +0300 Subject: [PATCH 7/8] wip --- src/game/client/c_playerresource.cpp | 3 ++- src/game/client/clientmode_shared.cpp | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/game/client/c_playerresource.cpp b/src/game/client/c_playerresource.cpp index b9b34a6b8a..f051775cc5 100644 --- a/src/game/client/c_playerresource.cpp +++ b/src/game/client/c_playerresource.cpp @@ -180,8 +180,9 @@ void C_PlayerResource::UpdatePlayerName( int slot ) { name = m_szNeoName[slot]; } + m_cachedPlayerNames.InsertOrReplace(sPlayerInfo.userID, name); - if (m_cachedPlayerNames.Count() >= gpGlobals->maxClients * 2) + if (m_cachedPlayerNames.Count() >= (unsigned int)(gpGlobals->maxClients * 2)) { PurgeOldCachedNames(); } diff --git a/src/game/client/clientmode_shared.cpp b/src/game/client/clientmode_shared.cpp index a6dfeb41d7..92d808962e 100644 --- a/src/game/client/clientmode_shared.cpp +++ b/src/game/client/clientmode_shared.cpp @@ -72,6 +72,8 @@ extern ConVar replay_rendersetting_renderglow; #include #include "ui/neo_loading.h" #include "neo_gamerules.h" + +#include #endif #ifdef GLOWS_ENABLE @@ -1143,7 +1145,13 @@ void ClientModeShared::FireGameEvent( IGameEvent *event ) if ( !hudChat ) return; +#ifdef NEO + const char* pszPlayerName = g_PR->GetCachedName(event->GetInt("userid")); + if (!pszPlayerName || !pszPlayerName[0]) + pszPlayerName = event->GetString("name"); +#else const char* pszPlayerName = event->GetString( "name" ); +#endif if ( PlayerNameNotSetYet( pszPlayerName ) ) return; @@ -1156,15 +1164,23 @@ void ClientModeShared::FireGameEvent( IGameEvent *event ) wchar_t wszReason[64]; const char *pszReason = event->GetString( "reason" ); - auto foo = g_PR->GetCachedName(event->GetInt("userid")); - Msg("%s\n", foo); - if ( pszReason && ( pszReason[0] == '#' ) && g_pVGuiLocalize->Find( pszReason ) ) { V_wcsncpy( wszReason, g_pVGuiLocalize->Find( pszReason ), sizeof( wszReason ) ); } else { +#ifdef NEO + std::string sReason{ pszReason }; + constexpr std::string timedOutSuffix{ " timed out" }; + if (sReason.ends_with(timedOutSuffix)) + { + // overwrite steam name from the event msg, because this can be the neo name + sReason = pszPlayerName; + sReason.append(timedOutSuffix); + pszReason = sReason.c_str(); + } +#endif g_pVGuiLocalize->ConvertANSIToUnicode( pszReason, wszReason, sizeof(wszReason) ); } From 7e090b2d401617486371250bac8ef36016bc47ea Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 18 Jul 2025 15:02:55 +0300 Subject: [PATCH 8/8] wip --- game/neo/resource/neo_english.txt | Bin 14550 -> 14652 bytes src/game/client/clientmode_shared.cpp | 139 +++++++++++++++++++++----- 2 files changed, 114 insertions(+), 25 deletions(-) diff --git a/game/neo/resource/neo_english.txt b/game/neo/resource/neo_english.txt index ab7e26a248c10a119f4b8aa9b77f4229f4ccbb96..a0e9cc619a04507984bfb681cf03968ab81eece9 100644 GIT binary patch delta 179 zcmcasxTk1CjzwZRLn1>iLn=c&LlQ$iLkW=10@9fbc|frgAP*+5#J~xJN(@e5RSFEr z3^_nmsX%ol3SjL+nrns>%b(fW+biLn=c&LkdGCL-FK53sHGa1`t$YP-Q4)Fl10*$N;J-2GTh| Xb!iMGlh0a6^3?+QybN3nwO|qe)`t&v diff --git a/src/game/client/clientmode_shared.cpp b/src/game/client/clientmode_shared.cpp index 92d808962e..bfda2cb76e 100644 --- a/src/game/client/clientmode_shared.cpp +++ b/src/game/client/clientmode_shared.cpp @@ -72,6 +72,7 @@ extern ConVar replay_rendersetting_renderglow; #include #include "ui/neo_loading.h" #include "neo_gamerules.h" +#include "jobthread.h" #include #endif @@ -1088,6 +1089,102 @@ bool PlayerNameNotSetYet( const char *pszName ) return false; } +#ifdef NEO +// Delayed handling for a joining player, because we need to wait for them to fully connect first +// in order to pull some data required for printing the greeting. +class DeferredGreet : public CJob { +public: + DeferredGreet(int userid, CBaseHudChat* chat) + : m_userid(userid), m_chat(chat) + { + Assert(chat); + } + +private: + virtual JobStatus_t DoExecute() override final + { + JobStatus_t res = JOB_STATUS_ABORTED; + + constexpr auto sleepTimeMs = 500, maxTries = 20; + constexpr auto maxTimeSpentMs = sleepTimeMs * maxTries; + static_assert(maxTimeSpentMs <= 10000); + for (int i = 0; i < maxTries; ++i) + { + ThreadSleep(sleepTimeMs); + + if (!engine->IsInGame()) + { + DoRelease(); + return res; + } + + if (TryGreet(m_chat, m_userid)) + { + res = JOB_OK; + //Msg("Greet ok after %d retries\n", i); + break; + } + //Msg("Greet failed for userid %d\n", m_userid); + } + + if (res == JOB_STATUS_ABORTED) + { + PrintGenericGreeting(m_chat); + } + + DoRelease(); + return res; + } + + static void PrintGenericGreeting(CBaseHudChat* chat) + { + if (chat) + { + chat->Printf(CHAT_FILTER_JOINLEAVE, "Player joined the game"); + } + } + + static bool TryGreet(CBaseHudChat* chat, int userid) + { + Assert(engine->IsInGame()); + if (!chat) + { + return false; + } + + if (cl_neo_streamermode.GetBool()) + { + PrintGenericGreeting(chat); + return true; + } + + int iPlayerIndex = engine->GetPlayerForUserID(userid); + auto player = static_cast(UTIL_PlayerByIndex(iPlayerIndex)); + if (!player) + { + return false; + } + + wchar_t wszLocalized[100]; + wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH]; + UTIL_GetFilteredPlayerNameAsWChar(iPlayerIndex, player->GetPlayerName(), wszPlayerName); + + auto tlString = player->IsNextBot() ? "#game_bot_joined_game" : "#game_player_joined_game"; + g_pVGuiLocalize->ConstructString_safe(wszLocalized, g_pVGuiLocalize->Find(tlString), 1, wszPlayerName); + + char szLocalized[100]; + g_pVGuiLocalize->ConvertUnicodeToANSI(wszLocalized, szLocalized, sizeof(szLocalized)); + + chat->Printf(CHAT_FILTER_JOINLEAVE, "%s", szLocalized); + + return true; + } + + int m_userid; + CBaseHudChat* m_chat; +}; +#endif + void ClientModeShared::FireGameEvent( IGameEvent *event ) { CBaseHudChat *hudChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); @@ -1104,15 +1201,13 @@ void ClientModeShared::FireGameEvent( IGameEvent *event ) if ( !IsInCommentaryMode() ) { #ifdef NEO - if (cl_neo_streamermode.GetBool()) - { - hudChat->Printf(CHAT_FILTER_JOINLEAVE, "Player connected"); - return; - } -#endif + Assert(g_pThreadPool); + g_pThreadPool->AddJob(new DeferredGreet(event->GetInt("userid"), hudChat)); +#else wchar_t wszLocalized[100]; wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ]; int iPlayerIndex = engine->GetPlayerForUserID( event->GetInt( "userid" ) ); + UTIL_GetFilteredPlayerNameAsWChar( iPlayerIndex, event->GetString( "name" ), wszPlayerName ); g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#game_player_joined_game" ), 1, wszPlayerName ); @@ -1120,22 +1215,11 @@ void ClientModeShared::FireGameEvent( IGameEvent *event ) g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof(szLocalized) ); hudChat->Printf( CHAT_FILTER_JOINLEAVE, "%s", szLocalized ); +#endif } } else if ( Q_strcmp( "player_disconnect", eventname ) == 0 ) { -#ifdef NEO - if (cl_neo_streamermode.GetBool()) - { - hudChat->Printf(CHAT_FILTER_JOINLEAVE, "Player disconnected"); - return; - } -#endif - -#ifndef NEO // ??? - C_BasePlayer *pPlayer = USERID2PLAYER( event->GetInt("userid") ); - if ( !hudChat || !pPlayer ) -#endif // Josh: There used to be code here that would get the player entity here to get the name // Big problem with that. The player entity is probably already gone -- they disconnected after all! // So there used to be a bug where most of the time, disconnect messages just wouldn't show up in chat. @@ -1146,6 +1230,12 @@ void ClientModeShared::FireGameEvent( IGameEvent *event ) return; #ifdef NEO + if (cl_neo_streamermode.GetBool()) + { + hudChat->Printf(CHAT_FILTER_JOINLEAVE, "Player disconnected"); + return; + } + const char* pszPlayerName = g_PR->GetCachedName(event->GetInt("userid")); if (!pszPlayerName || !pszPlayerName[0]) pszPlayerName = event->GetString("name"); @@ -1171,14 +1261,13 @@ void ClientModeShared::FireGameEvent( IGameEvent *event ) else { #ifdef NEO - std::string sReason{ pszReason }; - constexpr std::string timedOutSuffix{ " timed out" }; - if (sReason.ends_with(timedOutSuffix)) + // The default message is in the form: + // Player left the game ( timed out) + // but for NT, we have to replace the engine-provided playername with the possible neo_name for consistency. + // Just simplify the "timed out" message here instead of manually fixing the duplicated player name. + if (std::string{ pszReason }.ends_with(" timed out")) { - // overwrite steam name from the event msg, because this can be the neo name - sReason = pszPlayerName; - sReason.append(timedOutSuffix); - pszReason = sReason.c_str(); + pszReason = "connection timed out"; } #endif g_pVGuiLocalize->ConvertANSIToUnicode( pszReason, wszReason, sizeof(wszReason) );