Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified game/neo/resource/neo_english.txt
Binary file not shown.
45 changes: 45 additions & 0 deletions src/game/client/c_playerresource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) );
Expand Down Expand Up @@ -141,6 +144,18 @@ void C_PlayerResource::OnDataChanged(DataUpdateType_t updateType)
}
}

#ifdef NEO
string_t C_PlayerResource::GetCachedName(int userid) const
{
const auto idx = m_cachedPlayerNames.Find(userid);
if (idx == m_cachedPlayerNames.InvalidIndex())
{
return "";
}
return m_cachedPlayerNames.Element(idx);
}
#endif

void C_PlayerResource::UpdatePlayerName( int slot )
{
if ( slot < 1 || slot > MAX_PLAYERS )
Expand All @@ -158,6 +173,20 @@ void C_PlayerResource::UpdatePlayerName( int slot )
if ( IsConnected( slot ) && engine->GetPlayerInfo( slot, &sPlayerInfo ) )
{
m_szName[slot] = AllocPooledString( UTIL_GetFilteredPlayerName( slot, sPlayerInfo.name ) );
#ifdef NEO
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() >= (unsigned int)(gpGlobals->maxClients * 2))
{
PurgeOldCachedNames();
}
#endif
}
else
{
Expand All @@ -168,6 +197,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();
Expand Down
23 changes: 23 additions & 0 deletions src/game/client/c_playerresource.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<useridCache_t>::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 );
Expand Down Expand Up @@ -71,6 +82,10 @@ public : // IGameResources interface
uint32 GetAccountID( int iIndex );
bool IsValid( int iIndex );

#ifdef NEO
string_t GetCachedName(int userid) const;
#endif

protected:
void UpdatePlayerName( int slot );

Expand Down Expand Up @@ -98,6 +113,14 @@ public : // IGameResources interface
bool m_bValid[MAX_PLAYERS_ARRAY_SAFE];
int m_iUserID[MAX_PLAYERS_ARRAY_SAFE];
string_t m_szUnconnectedName;

#ifdef NEO
private:
// 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<PlayerResource::useridCache_t, string_t> m_cachedPlayerNames;
void PurgeOldCachedNames();
#endif
};

extern C_PlayerResource *g_PR;
Expand Down
145 changes: 127 additions & 18 deletions src/game/client/clientmode_shared.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ extern ConVar replay_rendersetting_renderglow;
#include <GameUI/IGameUI.h>
#include "ui/neo_loading.h"
#include "neo_gamerules.h"
#include "jobthread.h"

#include <string>
#endif

#ifdef GLOWS_ENABLE
Expand Down Expand Up @@ -1086,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<C_NEO_Player*>(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 );
Expand All @@ -1102,38 +1201,25 @@ 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 );

char szLocalized[100];
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.
Expand All @@ -1143,7 +1229,19 @@ void ClientModeShared::FireGameEvent( IGameEvent *event )
if ( !hudChat )
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");
#else
const char* pszPlayerName = event->GetString( "name" );
#endif

if ( PlayerNameNotSetYet( pszPlayerName ) )
return;
Expand All @@ -1155,12 +1253,23 @@ void ClientModeShared::FireGameEvent( IGameEvent *event )

wchar_t wszReason[64];
const char *pszReason = event->GetString( "reason" );

if ( pszReason && ( pszReason[0] == '#' ) && g_pVGuiLocalize->Find( pszReason ) )
{
V_wcsncpy( wszReason, g_pVGuiLocalize->Find( pszReason ), sizeof( wszReason ) );
}
else
{
#ifdef NEO
// The default message is in the form:
// Player <name> left the game (<name> 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"))
{
pszReason = "connection timed out";
}
#endif
g_pVGuiLocalize->ConvertANSIToUnicode( pszReason, wszReason, sizeof(wszReason) );
}

Expand Down