From b769331dc06684ebe68a27f76a40e3b8717da725 Mon Sep 17 00:00:00 2001 From: nullsystem <15316579+nullsystem@users.noreply.github.com> Date: Fri, 28 Aug 2026 19:11:59 +0100 Subject: [PATCH 1/7] Overlay have its own CAvatarImage instead of pulling from scoreboard Just a minor thing so spectator doesn't have to press tab to get avatar working for overlay sometimes. --- .../neo/ui/neo_hud_spectator_overlay.cpp | 110 ++++++++++-------- .../client/neo/ui/neo_hud_spectator_overlay.h | 14 ++- 2 files changed, 75 insertions(+), 49 deletions(-) diff --git a/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp b/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp index 702a60a92..0a5108cba 100644 --- a/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp +++ b/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp @@ -20,9 +20,7 @@ #include #include #include -#include #include -#include // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -102,6 +100,7 @@ CNEOHud_SpectatorOverlay::CNEOHud_SpectatorOverlay(const char* pElementName, vgu g_pNeoHudSpecOverlay = this; SetAutoDelete(true); m_iHideHudElementNumber = NEO_HUD_ELEMENT_SPECTATOR_OVERLAY; + m_pImageList = new vgui::ImageList(false); if (parent) { @@ -117,11 +116,18 @@ CNEOHud_SpectatorOverlay::CNEOHud_SpectatorOverlay(const char* pElementName, vgu SetBounds(0, 0, iScrWide, iScrTall); SetVisible(true); + + m_mapAvatarsToImageList.SetLessFunc(DefLessFunc(CSteamID)); + m_mapAvatarsToImageList.RemoveAll(); } CNEOHud_SpectatorOverlay::~CNEOHud_SpectatorOverlay() { g_pNeoHudSpecOverlay = nullptr; + if (m_pImageList) + { + delete m_pImageList; + } } static bool ShouldDrawHUD() @@ -150,6 +156,7 @@ void CNEOHud_SpectatorOverlay::ClearAll() m_iEntIndexSelect = 0; m_iTexLogoJinrai = -1; m_iTexLogoNSF = -1; + m_mapAvatarsToImageList.RemoveAll(); } void CNEOHud_SpectatorOverlay::Init() @@ -217,12 +224,17 @@ void CNEOHud_SpectatorOverlay::ApplySchemeSettings(vgui::IScheme* pScheme) SetFgColor(COLOR_TRANSPARENT); SetBgColor(COLOR_TRANSPARENT); + + if (m_pImageList) + { + delete m_pImageList; + } + m_pImageList = new vgui::ImageList(false); + m_mapAvatarsToImageList.RemoveAll(); } void CNEOHud_SpectatorOverlay::UpdateStateForNeoHudElementDraw() { - bool bScoreboardNeedUpdate = false; - const int iOldCardsSize = m_iCardsSize; SpectatorPlayerCard oldCards[MAX_PLAYERS_ARRAY_SAFE] = {}; static_assert(sizeof(oldCards) == sizeof(m_cards)); @@ -256,35 +268,39 @@ void CNEOHud_SpectatorOverlay::UpdateStateForNeoHudElementDraw() SpectatorPlayerCard *pCard = &m_cards[m_iCardsSize++]; ++m_iTeamPlayersCount[iTeam]; - // Reuse the avatar image already generated by the scoreboard - pCard->pAvatar = nullptr; - if (g_pNeoScoreBoard && g_pNeoScoreBoard->m_pImageList && CNEOScoreBoard::ShowAvatars()) + const CSteamID steamID = GetSteamIDForPlayerIndex(i); + if (steamID.IsValid() && CNEOScoreBoard::ShowAvatars()) { - const CSteamID steamId = GetSteamIDForPlayerIndex(i); - if (steamId.IsValid()) + // See if we already have that avatar in our list + int iMapIndex = m_mapAvatarsToImageList.Find(steamID); + if (iMapIndex == m_mapAvatarsToImageList.InvalidIndex()) { - const CUtlMap &mapAvatars = g_pNeoScoreBoard->m_mapAvatarsToImageList; - const int iMapIdx = mapAvatars.Find(steamId); - bool bAvatarNeedUpdate = true; - if (iMapIdx != mapAvatars.InvalidIndex()) - { - const MapAvatarValue &avatar = mapAvatars[iMapIdx]; - if (avatar.i184Idx >= 0) - { - pCard->pAvatar = static_cast(g_pNeoScoreBoard->m_pImageList->GetImage(avatar.i184Idx)); - bAvatarNeedUpdate = (!pCard->pAvatar || !pCard->pAvatar->IsValid()); - } if ((!pCard->pAvatar || !pCard->pAvatar->IsValid()) && avatar.i64Idx >= 0) - { - pCard->pAvatar = static_cast(g_pNeoScoreBoard->m_pImageList->GetImage(avatar.i64Idx)); - } - if ((!pCard->pAvatar || !pCard->pAvatar->IsValid()) && avatar.i32Idx >= 0) - { - pCard->pAvatar = static_cast(g_pNeoScoreBoard->m_pImageList->GetImage(avatar.i32Idx)); - } - } - bScoreboardNeedUpdate = bScoreboardNeedUpdate || bAvatarNeedUpdate; + auto *pImage64 = new CAvatarImage; + auto *pImage184 = new CAvatarImage; + + pImage64->SetAvatarSize(64, 64); + pImage184->SetAvatarSize(184, 184); + + pImage64->SetAvatarSteamID(steamID, k_EAvatarSize64x64); + pImage184->SetAvatarSteamID(steamID, k_EAvatarSize184x184); + + pCard->avatar = { + .i64Idx = m_pImageList->AddImage(pImage64), + .i184Idx = m_pImageList->AddImage(pImage184), + }; + + m_mapAvatarsToImageList.Insert(steamID, pCard->avatar); + } + else + { + pCard->avatar = m_mapAvatarsToImageList[iMapIndex]; } } + else + { + pCard->avatar.i64Idx = -1; + pCard->avatar.i184Idx = -1; + } Q_UTF8ToUnicode(g_PR->GetPlayerName(i), pCard->wszPlayerName, sizeof(pCard->wszPlayerName)); @@ -392,17 +408,6 @@ void CNEOHud_SpectatorOverlay::UpdateStateForNeoHudElementDraw() } } } - - // NEO NOTE (nullsystem): This is so the spectator don't need to tab press - // on the scoreboard to update the avatar. Generally only happens in the - // beginning of match/demo - // - // NEO TODO (nullsystem): Move avatar handling off to something separate from - // the scoreboard but shared between scoreboard, overlay, and killer info - if (bScoreboardNeedUpdate) - { - g_pNeoScoreBoard->Update(); - } } void CNEOHud_SpectatorOverlay::DrawPlayerCard(const SpectatorPlayerCard &card, @@ -431,13 +436,26 @@ void CNEOHud_SpectatorOverlay::DrawPlayerCard(const SpectatorPlayerCard &card, const int iAvatarX = bIsLeftSide ? x : x + wide - iAvatarSize; const int iAvatarY = y; - // Draw the actual Steam avatar (borrowed from the scoreboard's avatar list) - if (card.pAvatar && card.pAvatar->IsValid()) + // Draw the actual Steam avatar + if (card.avatar.i184Idx >= 0 || card.avatar.i64Idx >= 0) { - card.pAvatar->m_bDeadAvatar = !card.bAlive; - card.pAvatar->SetPos(iAvatarX, iAvatarY); - card.pAvatar->SetSize(iAvatarSize, iAvatarSize); - card.pAvatar->Paint(); + CAvatarImage *pAvatarImg = nullptr; + if (card.avatar.i184Idx >= 0) + { + pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(card.avatar.i184Idx)); + } + if ((!pAvatarImg || !pAvatarImg->IsValid()) && card.avatar.i64Idx >= 0) + { + pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(card.avatar.i64Idx)); + } + + if (pAvatarImg) + { + pAvatarImg->m_bDeadAvatar = !card.bAlive; + pAvatarImg->SetPos(iAvatarX, iAvatarY); + pAvatarImg->SetSize(iAvatarSize, iAvatarSize); + pAvatarImg->Paint(); + } } else if (card.wszPlayerName[0]) { diff --git a/src/game/client/neo/ui/neo_hud_spectator_overlay.h b/src/game/client/neo/ui/neo_hud_spectator_overlay.h index 6fc9d7c89..c157eeb6e 100644 --- a/src/game/client/neo/ui/neo_hud_spectator_overlay.h +++ b/src/game/client/neo/ui/neo_hud_spectator_overlay.h @@ -3,8 +3,8 @@ #include "neo_hud_childelement.h" #include "hudelement.h" #include - -class CAvatarImage; +#include +#include extern ConVar cl_neo_hud_spectator_overlay_enabled; extern ConVar cl_neo_hud_spectator_overlay_jinrai_name; @@ -16,6 +16,12 @@ extern ConVar cl_neo_hud_spectator_overlay_nsf_matches_won; static constexpr const int COMP_MATCHES_WON_MAX = 2; +struct OverlayMapAvatarValue +{ + int i64Idx; + int i184Idx; +}; + struct SpectatorPlayerCard { int iUserID = 0; @@ -34,7 +40,7 @@ struct SpectatorPlayerCard int iHP = 0; int iRoundKills = 0; bool bAlive = false; - CAvatarImage *pAvatar = nullptr; + OverlayMapAvatarValue avatar; // Used for animations/fading float flLastAttackTime = 0.0f; float flLastAliveTime = 0.0f; @@ -61,6 +67,8 @@ class CNEOHud_SpectatorOverlay : public CNEOHud_ChildElement, public CHudElement void Paint() final; SpectatorPlayerCard m_cards[MAX_PLAYERS_ARRAY_SAFE] = {}; + vgui::ImageList *m_pImageList = nullptr; + CUtlMap m_mapAvatarsToImageList; int m_iCardsSize = 0; enum ESpecType From c2f96998b5f53700546dc06d510788f4238fdc18 Mon Sep 17 00:00:00 2001 From: nullsystem <15316579+nullsystem@users.noreply.github.com> Date: Sun, 30 Aug 2026 01:49:07 +0100 Subject: [PATCH 2/7] Code review fixes, avatar handling to just once --- .../neo/ui/neo_hud_spectator_overlay.cpp | 43 ++++++------------- .../client/neo/ui/neo_hud_spectator_overlay.h | 10 +---- 2 files changed, 14 insertions(+), 39 deletions(-) diff --git a/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp b/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp index 0a5108cba..98a88a13d 100644 --- a/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp +++ b/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp @@ -100,7 +100,7 @@ CNEOHud_SpectatorOverlay::CNEOHud_SpectatorOverlay(const char* pElementName, vgu g_pNeoHudSpecOverlay = this; SetAutoDelete(true); m_iHideHudElementNumber = NEO_HUD_ELEMENT_SPECTATOR_OVERLAY; - m_pImageList = new vgui::ImageList(false); + m_pImageList = new vgui::ImageList(true); if (parent) { @@ -229,8 +229,10 @@ void CNEOHud_SpectatorOverlay::ApplySchemeSettings(vgui::IScheme* pScheme) { delete m_pImageList; } - m_pImageList = new vgui::ImageList(false); + m_pImageList = new vgui::ImageList(true); m_mapAvatarsToImageList.RemoveAll(); + m_iCardsSize = 0; + V_memset(m_cards, 0, sizeof(m_cards)); } void CNEOHud_SpectatorOverlay::UpdateStateForNeoHudElementDraw() @@ -275,31 +277,19 @@ void CNEOHud_SpectatorOverlay::UpdateStateForNeoHudElementDraw() int iMapIndex = m_mapAvatarsToImageList.Find(steamID); if (iMapIndex == m_mapAvatarsToImageList.InvalidIndex()) { - auto *pImage64 = new CAvatarImage; - auto *pImage184 = new CAvatarImage; - - pImage64->SetAvatarSize(64, 64); - pImage184->SetAvatarSize(184, 184); - - pImage64->SetAvatarSteamID(steamID, k_EAvatarSize64x64); - pImage184->SetAvatarSteamID(steamID, k_EAvatarSize184x184); - - pCard->avatar = { - .i64Idx = m_pImageList->AddImage(pImage64), - .i184Idx = m_pImageList->AddImage(pImage184), - }; - - m_mapAvatarsToImageList.Insert(steamID, pCard->avatar); + auto *pImageAvatar = new CAvatarImage; + pImageAvatar->SetAvatarSize(184, 184); + pImageAvatar->SetAvatarSteamID(steamID, k_EAvatarSize184x184); + m_mapAvatarsToImageList.Insert(steamID, m_pImageList->AddImage(pImageAvatar)); } else { - pCard->avatar = m_mapAvatarsToImageList[iMapIndex]; + pCard->iAvatar = m_mapAvatarsToImageList[iMapIndex]; } } else { - pCard->avatar.i64Idx = -1; - pCard->avatar.i184Idx = -1; + pCard->iAvatar = -1; } Q_UTF8ToUnicode(g_PR->GetPlayerName(i), pCard->wszPlayerName, sizeof(pCard->wszPlayerName)); @@ -437,18 +427,9 @@ void CNEOHud_SpectatorOverlay::DrawPlayerCard(const SpectatorPlayerCard &card, const int iAvatarY = y; // Draw the actual Steam avatar - if (card.avatar.i184Idx >= 0 || card.avatar.i64Idx >= 0) + if (card.iAvatar >= 0) { - CAvatarImage *pAvatarImg = nullptr; - if (card.avatar.i184Idx >= 0) - { - pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(card.avatar.i184Idx)); - } - if ((!pAvatarImg || !pAvatarImg->IsValid()) && card.avatar.i64Idx >= 0) - { - pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(card.avatar.i64Idx)); - } - + CAvatarImage *pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(card.iAvatar)); if (pAvatarImg) { pAvatarImg->m_bDeadAvatar = !card.bAlive; diff --git a/src/game/client/neo/ui/neo_hud_spectator_overlay.h b/src/game/client/neo/ui/neo_hud_spectator_overlay.h index c157eeb6e..29d7c9c52 100644 --- a/src/game/client/neo/ui/neo_hud_spectator_overlay.h +++ b/src/game/client/neo/ui/neo_hud_spectator_overlay.h @@ -16,12 +16,6 @@ extern ConVar cl_neo_hud_spectator_overlay_nsf_matches_won; static constexpr const int COMP_MATCHES_WON_MAX = 2; -struct OverlayMapAvatarValue -{ - int i64Idx; - int i184Idx; -}; - struct SpectatorPlayerCard { int iUserID = 0; @@ -40,7 +34,7 @@ struct SpectatorPlayerCard int iHP = 0; int iRoundKills = 0; bool bAlive = false; - OverlayMapAvatarValue avatar; + int iAvatar = -1; // Used for animations/fading float flLastAttackTime = 0.0f; float flLastAliveTime = 0.0f; @@ -68,7 +62,7 @@ class CNEOHud_SpectatorOverlay : public CNEOHud_ChildElement, public CHudElement SpectatorPlayerCard m_cards[MAX_PLAYERS_ARRAY_SAFE] = {}; vgui::ImageList *m_pImageList = nullptr; - CUtlMap m_mapAvatarsToImageList; + CUtlMap m_mapAvatarsToImageList; int m_iCardsSize = 0; enum ESpecType From 5403871a3c2f16ee6ba071b212fe6ea5233a5b7a Mon Sep 17 00:00:00 2001 From: nullsystem <15316579+nullsystem@users.noreply.github.com> Date: Sun, 30 Aug 2026 03:32:18 +0100 Subject: [PATCH 3/7] Change to NeoAvatar Throw away using CAvatarImage and instead just have our own simpler version of it. Applied for overlay, scoreboard, and killer info. --- src/game/client/CMakeLists.txt | 2 + src/game/client/neo/ui/neo_avatar.cpp | 167 ++++++++++++++++++ src/game/client/neo/ui/neo_avatar.h | 18 ++ .../client/neo/ui/neo_hud_killer_info.cpp | 51 ++---- src/game/client/neo/ui/neo_hud_killer_info.h | 6 +- .../client/neo/ui/neo_hud_round_state.cpp | 1 - .../neo/ui/neo_hud_spectator_overlay.cpp | 71 +++----- .../client/neo/ui/neo_hud_spectator_overlay.h | 9 +- src/game/client/neo/ui/neo_scoreboard.cpp | 127 +++---------- src/game/client/neo/ui/neo_scoreboard.h | 19 +- 10 files changed, 259 insertions(+), 212 deletions(-) create mode 100644 src/game/client/neo/ui/neo_avatar.cpp create mode 100644 src/game/client/neo/ui/neo_avatar.h diff --git a/src/game/client/CMakeLists.txt b/src/game/client/CMakeLists.txt index 317cbb87a..7cf122623 100644 --- a/src/game/client/CMakeLists.txt +++ b/src/game/client/CMakeLists.txt @@ -1674,6 +1674,7 @@ set(UNITY_SOURCE_NEO_UI neo/ui/neo_scoreboard.cpp neo/ui/neo_hud_context_hint.cpp neo/ui/neo_spec_player_by_hud.cpp + neo/ui/neo_avatar.cpp ) set_source_files_properties( @@ -1715,6 +1716,7 @@ target_sources_grouped( neo/ui/neo_hud_worldpos_marker_generic.h neo/ui/neo_scoreboard.h neo/ui/neo_hud_context_hint.h + neo/ui/neo_avatar.h ${UNITY_SOURCE_NEO_UI} ) diff --git a/src/game/client/neo/ui/neo_avatar.cpp b/src/game/client/neo/ui/neo_avatar.cpp new file mode 100644 index 000000000..313a76c44 --- /dev/null +++ b/src/game/client/neo/ui/neo_avatar.cpp @@ -0,0 +1,167 @@ +#include "neo_avatar.h" + +#include "cbase.h" +#include +#include +#include "VGuiMatSurface/IMatSystemSurface.h" + +struct CacheAvatarValue +{ + int normal; + int dead; +}; + +struct CacheAvatarKey +{ + CSteamID m_SteamID; + int m_iAvatar; + + CacheAvatarKey() { m_iAvatar = 0; } + CacheAvatarKey(CSteamID steamID, int av) { m_SteamID = steamID; m_iAvatar = av; } + bool operator<(const CacheAvatarKey &rhs) const + { + return m_SteamID.ConvertToUint64() < rhs.m_SteamID.ConvertToUint64() + || (m_SteamID.ConvertToUint64() == rhs.m_SteamID.ConvertToUint64() + && m_iAvatar < rhs.m_iAvatar); + } +}; + +static inline CUtlMap gAvatarImageCache; +static inline bool gAvatarImageCacheInit = false; + +void NeoAvatar::SetSteamID(const CSteamID &steamID) +{ + if (steamID == m_SteamID) + { + return; + } + m_SteamID = steamID; + m_iTextureID = 0; + m_iTextureDeadID = 0; + m_flPrevLoadAttempt = 0.0f; +} + +void NeoAvatar::Fetch(const int iAvatarWH) +{ + if (!m_SteamID.IsValid() + || (m_iTextureID > 0 && m_iTexForAvatarWH == iAvatarWH) + || (m_flPrevLoadAttempt + 1.0f) > gpGlobals->realtime) + { + return; + } + + int iAvatar = 0; + if (iAvatarWH <= 32) + { + iAvatar = steamapicontext->SteamFriends()->GetSmallFriendAvatar(m_SteamID); + } + else if (iAvatarWH <= 64) + { + iAvatar = steamapicontext->SteamFriends()->GetMediumFriendAvatar(m_SteamID); + } + else + { + iAvatar = steamapicontext->SteamFriends()->GetLargeFriendAvatar(m_SteamID); + } + m_iTexForAvatarWH = iAvatarWH; + + if (iAvatar > 0) + { + if (!gAvatarImageCacheInit) + { + SetDefLessFunc(gAvatarImageCache); + gAvatarImageCacheInit = true; + } + + int iTexIndex = gAvatarImageCache.Find(CacheAvatarKey(m_SteamID, iAvatar)); + if (iTexIndex == gAvatarImageCache.InvalidIndex()) + { + uint32 u32wide = 0, u32tall = 0; + if (steamapicontext->SteamUtils()->GetImageSize(iAvatar, &u32wide, &u32tall) + && u32wide > 0 && u32tall > 0) + { + const int wide = u32wide; + const int tall = u32tall; + const int destBufferSize = wide * tall * 4; + byte *rgbaBuf = (byte *)stackalloc(destBufferSize); + if (steamapicontext->SteamUtils()->GetImageRGBA(iAvatar, rgbaBuf, destBufferSize)) + { + // Create normal avatar from RGBA without edits + m_iTextureID = vgui::surface()->CreateNewTextureID(true); + g_pMatSystemSurface->DrawSetTextureRGBAEx2(m_iTextureID, rgbaBuf, wide, tall, IMAGE_FORMAT_RGBA8888, true); + + // Create dead avatar from RGBA with redness edits + for (int offset = 0; offset < (wide * tall * 4); offset += 4) + { + constexpr float brightness = 0.5f; + constexpr float contrast = 1.5f; + + float r = (rgbaBuf + offset)[0] / 255.0f; + float g = (rgbaBuf + offset)[1] / 255.0f; + float b = (rgbaBuf + offset)[2] / 255.0f; + + // Contrast + r = Clamp((r - 0.5f) * contrast + 0.5f, 0.0f, 1.0f); + g = Clamp((g - 0.5f) * contrast + 0.5f, 0.0f, 1.0f); + b = Clamp((b - 0.5f) * contrast + 0.5f, 0.0f, 1.0f); + + // Convert to grayscale - Luminosity, then gradient from black -> red -> white + const float gray = 0.3f * r + 0.59f * g + 0.11f * b; + if (gray < 0.5) + { + r = gray * 2.0f; + g = 0; + b = 0; + } + else { + r = 1.0f; + g = (gray - 0.5f) * 2.0f; + b = (gray - 0.5f) * 2.0f; + } + + // Brightness + r = Clamp(r * brightness, 0.0f, 1.0f); + g = Clamp(g * brightness, 0.0f, 1.0f); + b = Clamp(b * brightness, 0.0f, 1.0f); + + (rgbaBuf + offset)[0] = r * 255; + (rgbaBuf + offset)[1] = g * 255; + (rgbaBuf + offset)[2] = b * 255; + } + + m_iTextureDeadID = vgui::surface()->CreateNewTextureID(true); + g_pMatSystemSurface->DrawSetTextureRGBAEx2(m_iTextureDeadID, rgbaBuf, wide, tall, IMAGE_FORMAT_RGBA8888, true); + + // Add textures to global cache + iTexIndex = gAvatarImageCache.Insert(CacheAvatarKey(m_SteamID, iAvatar)); + gAvatarImageCache[iTexIndex].normal = m_iTextureID; + gAvatarImageCache[iTexIndex].dead = m_iTextureDeadID; + } + stackfree(rgbaBuf); + } + } + else + { + m_iTextureID = gAvatarImageCache[iTexIndex].normal; + m_iTextureDeadID = gAvatarImageCache[iTexIndex].dead; + } + } + + if (m_iTextureID == 0) + { + m_flPrevLoadAttempt = gpGlobals->realtime; + } +} + +void NeoAvatar::Paint(const int x, const int y, const int widetall, + const bool bDead) const +{ + if (m_iTextureID == 0) + { + return; + } + vgui::surface()->DrawSetTexture(bDead ? m_iTextureDeadID : m_iTextureID); + vgui::surface()->DrawSetColor(COLOR_WHITE); + vgui::surface()->DrawTexturedRect(x, y, x + widetall, y + widetall); +} + diff --git a/src/game/client/neo/ui/neo_avatar.h b/src/game/client/neo/ui/neo_avatar.h new file mode 100644 index 000000000..b9fe6f94e --- /dev/null +++ b/src/game/client/neo/ui/neo_avatar.h @@ -0,0 +1,18 @@ +#pragma once + +#include "steam/steam_api.h" + +struct NeoAvatar +{ + CSteamID m_SteamID; + int m_iTextureID = 0; + int m_iTextureDeadID = 0; + int m_iTexForAvatarWH = 0; + float m_flPrevLoadAttempt = 0.0f; + + void SetSteamID(const CSteamID &steamID); + void Fetch(const int iAvatarWH); + void Paint(const int x, const int y, const int widetall, + const bool bDead = false) const; +}; + diff --git a/src/game/client/neo/ui/neo_hud_killer_info.cpp b/src/game/client/neo/ui/neo_hud_killer_info.cpp index 81d153385..e10d57c64 100644 --- a/src/game/client/neo/ui/neo_hud_killer_info.cpp +++ b/src/game/client/neo/ui/neo_hud_killer_info.cpp @@ -37,10 +37,6 @@ CNEOHud_KillerInfo::CNEOHud_KillerInfo(const char *pszName, vgui::Panel *parent) } SetVisible(false); CommonSetupHUD(); - - m_avatar32.SetAvatarSize(32, 32); - m_avatar64.SetAvatarSize(64, 64); - m_avatar184.SetAvatarSize(184, 184); } void CNEOHud_KillerInfo::ApplySchemeSettings(vgui::IScheme *pScheme) @@ -77,18 +73,10 @@ void CNEOHud_KillerInfo::UpdateStateForNeoHudElementDraw() && (gpGlobals->curtime < (const_cast(pLocalPlayer)->GetDeathTime() + DEATH_ANIMATION_TIME)); const CSteamID steamID = GetSteamIDForPlayerIndex(g_neoKillerInfos.iEntIndex); - if (steamID.IsValid()) - { - m_avatar32.SetAvatarSteamID(steamID, k_EAvatarSize32x32); - m_avatar64.SetAvatarSteamID(steamID, k_EAvatarSize64x64); - m_avatar184.SetAvatarSteamID(steamID, k_EAvatarSize184x184); - } - else - { - m_avatar32.ClearAvatarSteamID(); - m_avatar64.ClearAvatarSteamID(); - m_avatar184.ClearAvatarSteamID(); - } + m_avatar.SetSteamID( + (steamID.IsValid() && (CNEOScoreBoard::ShowAvatars())) + ? steamID : CSteamID{}); + m_avatar.Fetch(184); } void CNEOHud_KillerInfo::DrawNeoHudElement() @@ -116,28 +104,12 @@ void CNEOHud_KillerInfo::DrawNeoHudElement() const bool bSuicideKill = (iKillerEntIndex == pLocalPlayer->entindex()) || (iKillerEntIndex == NEO_ENVIRON_KILLED); - - CAvatarImage *pAvatarImg = nullptr; - if (CNEOScoreBoard::ShowAvatars()) - { - if (m_avatar184.IsValid()) - { - pAvatarImg = &m_avatar184; - } - else if (m_avatar64.IsValid()) - { - pAvatarImg = &m_avatar64; - } - else if (m_avatar32.IsValid()) - { - pAvatarImg = &m_avatar32; - } - } + const bool bValidAvatar = m_avatar.m_iTextureID > 0; // NEO NOTE (nullsystem): Similar sizing to how the scoreboard popup card is const int iMargin = wide / 384; - const int iAvatarWT = pAvatarImg ? (((tall / 35) * 3) - (iMargin * 2)) : 0; - const int iTextOffsetX = pAvatarImg ? (iAvatarWT + (2 * iMargin)) : iMargin; + const int iAvatarWT = bValidAvatar ? (((tall / 35) * 3) - (iMargin * 2)) : 0; + const int iTextOffsetX = bValidAvatar ? (iAvatarWT + (2 * iMargin)) : iMargin; wchar_t wszText[256] = {}; @@ -161,7 +133,7 @@ void CNEOHud_KillerInfo::DrawNeoHudElement() vgui::surface()->GetTextSize(m_hFontNormal, L"A", iNormalWide, iNormalTall); const int iBoxWide = Max(wide / 4, iTextOffsetX + iTitleWide + iMargin); - const int iBoxHeight = (pAvatarImg) + const int iBoxHeight = (bValidAvatar) ? (2 * iMargin) + iAvatarWT : iMargin + iTitleTall + iNormalTall + iMargin; @@ -173,12 +145,9 @@ void CNEOHud_KillerInfo::DrawNeoHudElement() }; DrawNeoHudRoundedBox(rect.x0, rect.y0, rect.x1, rect.y1, COLOR_BLACK_TRANSPARENT); - if (pAvatarImg) + if (bValidAvatar) { - pAvatarImg->m_bDeadAvatar = false; - pAvatarImg->SetPos(rect.x0 + iMargin, rect.y0 + iMargin); - pAvatarImg->SetSize(iAvatarWT, iAvatarWT); - pAvatarImg->Paint(); + m_avatar.Paint(rect.x0 + iMargin, rect.y0 + iMargin, iAvatarWT); } vgui::surface()->DrawSetTextFont(m_hFontTitle); diff --git a/src/game/client/neo/ui/neo_hud_killer_info.h b/src/game/client/neo/ui/neo_hud_killer_info.h index b213d430d..bd630fcf0 100644 --- a/src/game/client/neo/ui/neo_hud_killer_info.h +++ b/src/game/client/neo/ui/neo_hud_killer_info.h @@ -5,7 +5,7 @@ #include #include -#include +#include "neo_avatar.h" class CNEOHud_KillerInfo : public CNEOHud_ChildElement, public CHudElement, public vgui::Panel { @@ -28,8 +28,6 @@ class CNEOHud_KillerInfo : public CNEOHud_ChildElement, public CHudElement, publ vgui::HFont m_hFontNormal; vgui::HFont m_hFontTitle; - CAvatarImage m_avatar32; - CAvatarImage m_avatar64; - CAvatarImage m_avatar184; + NeoAvatar m_avatar = {}; bool m_bPlayerShownHud = false; }; diff --git a/src/game/client/neo/ui/neo_hud_round_state.cpp b/src/game/client/neo/ui/neo_hud_round_state.cpp index 6da8aafd7..173c9fea7 100644 --- a/src/game/client/neo/ui/neo_hud_round_state.cpp +++ b/src/game/client/neo/ui/neo_hud_round_state.cpp @@ -18,7 +18,6 @@ #include "c_neo_player.h" #include "c_team.h" #include "c_playerresource.h" -#include "vgui_avatarimage.h" #include "neo_scoreboard.h" #include "neo_hud_spectator_overlay.h" diff --git a/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp b/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp index 98a88a13d..471c89278 100644 --- a/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp +++ b/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp @@ -100,7 +100,6 @@ CNEOHud_SpectatorOverlay::CNEOHud_SpectatorOverlay(const char* pElementName, vgu g_pNeoHudSpecOverlay = this; SetAutoDelete(true); m_iHideHudElementNumber = NEO_HUD_ELEMENT_SPECTATOR_OVERLAY; - m_pImageList = new vgui::ImageList(true); if (parent) { @@ -116,18 +115,11 @@ CNEOHud_SpectatorOverlay::CNEOHud_SpectatorOverlay(const char* pElementName, vgu SetBounds(0, 0, iScrWide, iScrTall); SetVisible(true); - - m_mapAvatarsToImageList.SetLessFunc(DefLessFunc(CSteamID)); - m_mapAvatarsToImageList.RemoveAll(); } CNEOHud_SpectatorOverlay::~CNEOHud_SpectatorOverlay() { g_pNeoHudSpecOverlay = nullptr; - if (m_pImageList) - { - delete m_pImageList; - } } static bool ShouldDrawHUD() @@ -156,7 +148,6 @@ void CNEOHud_SpectatorOverlay::ClearAll() m_iEntIndexSelect = 0; m_iTexLogoJinrai = -1; m_iTexLogoNSF = -1; - m_mapAvatarsToImageList.RemoveAll(); } void CNEOHud_SpectatorOverlay::Init() @@ -170,7 +161,6 @@ void CNEOHud_SpectatorOverlay::LevelShutdown() { ClearAll(); } - void CNEOHud_SpectatorOverlay::FireGameEvent(IGameEvent *event) { const char *type = event->GetName(); @@ -225,12 +215,6 @@ void CNEOHud_SpectatorOverlay::ApplySchemeSettings(vgui::IScheme* pScheme) SetFgColor(COLOR_TRANSPARENT); SetBgColor(COLOR_TRANSPARENT); - if (m_pImageList) - { - delete m_pImageList; - } - m_pImageList = new vgui::ImageList(true); - m_mapAvatarsToImageList.RemoveAll(); m_iCardsSize = 0; V_memset(m_cards, 0, sizeof(m_cards)); } @@ -252,6 +236,14 @@ void CNEOHud_SpectatorOverlay::UpdateStateForNeoHudElementDraw() return; } + int iScrWide, iScrTall; + vgui::surface()->GetScreenSize(iScrWide, iScrTall); + + const float flWide = static_cast(iScrWide); + float flWideAs43 = static_cast(iScrTall) * (4.0f / 3.0f); + if (flWideAs43 > flWide) flWideAs43 = flWide; + const int iAvatarSize = flWideAs43 / 22.5f; + for (const auto iPutTeam : {TEAM_JINRAI, TEAM_NSF}) { for (int i = 1; i <= gpGlobals->maxClients; ++i) @@ -267,30 +259,16 @@ void CNEOHud_SpectatorOverlay::UpdateStateForNeoHudElementDraw() continue; } - SpectatorPlayerCard *pCard = &m_cards[m_iCardsSize++]; + NeoAvatar *pAvatar = &m_avatars[m_iCardsSize]; + SpectatorPlayerCard *pCard = &m_cards[m_iCardsSize]; + ++m_iCardsSize; ++m_iTeamPlayersCount[iTeam]; const CSteamID steamID = GetSteamIDForPlayerIndex(i); - if (steamID.IsValid() && CNEOScoreBoard::ShowAvatars()) - { - // See if we already have that avatar in our list - int iMapIndex = m_mapAvatarsToImageList.Find(steamID); - if (iMapIndex == m_mapAvatarsToImageList.InvalidIndex()) - { - auto *pImageAvatar = new CAvatarImage; - pImageAvatar->SetAvatarSize(184, 184); - pImageAvatar->SetAvatarSteamID(steamID, k_EAvatarSize184x184); - m_mapAvatarsToImageList.Insert(steamID, m_pImageList->AddImage(pImageAvatar)); - } - else - { - pCard->iAvatar = m_mapAvatarsToImageList[iMapIndex]; - } - } - else - { - pCard->iAvatar = -1; - } + pAvatar->SetSteamID( + (steamID.IsValid() && CNEOScoreBoard::ShowAvatars()) + ? steamID : CSteamID{}); + pAvatar->Fetch(iAvatarSize); Q_UTF8ToUnicode(g_PR->GetPlayerName(i), pCard->wszPlayerName, sizeof(pCard->wszPlayerName)); @@ -400,12 +378,15 @@ void CNEOHud_SpectatorOverlay::UpdateStateForNeoHudElementDraw() } } -void CNEOHud_SpectatorOverlay::DrawPlayerCard(const SpectatorPlayerCard &card, +void CNEOHud_SpectatorOverlay::DrawPlayerCard(const int iPlayerIdx, const bool bIsLeftSide, const int x, const int y, const int wide, const int tall, const Color accentColor, const float flWideAs43) const { + const SpectatorPlayerCard &card = m_cards[iPlayerIdx]; + const NeoAvatar &avatar = m_avatars[iPlayerIdx]; + // Card background static constexpr const float FL_LAST_ALIVE_DELTA_MAX = 1.5f; const float flLastAliveDelta = gpGlobals->curtime - card.flLastAliveTime; @@ -427,16 +408,10 @@ void CNEOHud_SpectatorOverlay::DrawPlayerCard(const SpectatorPlayerCard &card, const int iAvatarY = y; // Draw the actual Steam avatar - if (card.iAvatar >= 0) + if (avatar.m_iTextureID > 0) { - CAvatarImage *pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(card.iAvatar)); - if (pAvatarImg) - { - pAvatarImg->m_bDeadAvatar = !card.bAlive; - pAvatarImg->SetPos(iAvatarX, iAvatarY); - pAvatarImg->SetSize(iAvatarSize, iAvatarSize); - pAvatarImg->Paint(); - } + avatar.Paint(iAvatarX, iAvatarY, + iAvatarSize, !card.bAlive); } else if (card.wszPlayerName[0]) { @@ -1031,7 +1006,7 @@ void CNEOHud_SpectatorOverlay::DrawNeoHudElement() { const int iRowY = iColYStart + (iPlayerRow * iAvatarWH) + ((iPlayerRow > 0) ? (iPlayerRow * iGapBetween) : 0); - DrawPlayerCard(m_cards[i], bIsLeftSide, + DrawPlayerCard(i, bIsLeftSide, iColXStart, iRowY, iTotalCardWide, iAvatarWH, accentColor, flWideAs43); ++iPlayerRow; diff --git a/src/game/client/neo/ui/neo_hud_spectator_overlay.h b/src/game/client/neo/ui/neo_hud_spectator_overlay.h index 29d7c9c52..f53a88d1a 100644 --- a/src/game/client/neo/ui/neo_hud_spectator_overlay.h +++ b/src/game/client/neo/ui/neo_hud_spectator_overlay.h @@ -4,7 +4,7 @@ #include "hudelement.h" #include #include -#include +#include "neo_avatar.h" extern ConVar cl_neo_hud_spectator_overlay_enabled; extern ConVar cl_neo_hud_spectator_overlay_jinrai_name; @@ -34,7 +34,6 @@ struct SpectatorPlayerCard int iHP = 0; int iRoundKills = 0; bool bAlive = false; - int iAvatar = -1; // Used for animations/fading float flLastAttackTime = 0.0f; float flLastAliveTime = 0.0f; @@ -61,8 +60,8 @@ class CNEOHud_SpectatorOverlay : public CNEOHud_ChildElement, public CHudElement void Paint() final; SpectatorPlayerCard m_cards[MAX_PLAYERS_ARRAY_SAFE] = {}; - vgui::ImageList *m_pImageList = nullptr; - CUtlMap m_mapAvatarsToImageList; + // m_avatars separate as m_cards always cleared + NeoAvatar m_avatars[MAX_PLAYERS_ARRAY_SAFE] = {}; int m_iCardsSize = 0; enum ESpecType @@ -88,7 +87,7 @@ class CNEOHud_SpectatorOverlay : public CNEOHud_ChildElement, public CHudElement ConVar *GetUpdateFrequencyConVar() const final; private: - void DrawPlayerCard(const SpectatorPlayerCard &card, + void DrawPlayerCard(const int iPlayerIdx, const bool bIsLeftSide, const int x, const int y, const int wide, const int tall, diff --git a/src/game/client/neo/ui/neo_scoreboard.cpp b/src/game/client/neo/ui/neo_scoreboard.cpp index 9d7147d13..87fe71d38 100644 --- a/src/game/client/neo/ui/neo_scoreboard.cpp +++ b/src/game/client/neo/ui/neo_scoreboard.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -73,9 +72,6 @@ CNEOScoreBoard::CNEOScoreBoard(IViewPort *pViewPort) ListenForGameEvent("game_newmap"); ListenForGameEvent("hltv_status"); - m_mapAvatarsToImageList.SetLessFunc(DefLessFunc(CSteamID)); - m_mapAvatarsToImageList.RemoveAll(); - g_pNeoScoreBoard = this; for (int i = 0; i < CROSSHAIR_STYLE__TOTAL; ++i) @@ -96,10 +92,6 @@ CNEOScoreBoard::~CNEOScoreBoard() { g_pNeoScoreBoard = nullptr; } - if (m_pImageList) - { - delete m_pImageList; - } } bool CNEOScoreBoard::ShowAvatars() @@ -153,7 +145,7 @@ void CNEOScoreBoard::ShowPanel(bool bShow) } // Catch the case where we call ShowPanel before ApplySchemeSettings, eg when // going from windowed <-> fullscreen - if (m_pImageList == NULL) + if (!m_bAppliedApplyScheme) { InvalidateLayout(true, true); } @@ -229,12 +221,7 @@ void CNEOScoreBoard::ApplySchemeSettings(vgui::IScheme *pScheme) m_playerPopup = {}; m_iTotalPlayers = 0; - if (m_pImageList) - { - delete m_pImageList; - } - m_pImageList = new vgui::ImageList(false); - m_mapAvatarsToImageList.RemoveAll(); + m_bAppliedApplyScheme = true; m_flNextUpdateTime = gpGlobals->curtime + 0.1f; @@ -298,7 +285,6 @@ void CNEOScoreBoard::Reset() m_iTotalPlayers = 0; V_memset(m_playersInfo, 0, sizeof(m_playersInfo)); m_flNextUpdateTime = 0; - m_mapAvatarsToImageList.RemoveAll(); } void CNEOScoreBoard::ToggleMouseCapture(const bool bUseMouse) @@ -491,44 +477,12 @@ void CNEOScoreBoard::Update() } } - // pPlayerInfo->avatar - if (ShowAvatars() && pPlayerInfo->steamID.IsValid()) - { - // See if we already have that avatar in our list - int iMapIndex = m_mapAvatarsToImageList.Find(pPlayerInfo->steamID); - if (iMapIndex == m_mapAvatarsToImageList.InvalidIndex()) - { - auto *pImage32 = new CAvatarImage; - auto *pImage64 = new CAvatarImage; - auto *pImage184 = new CAvatarImage; - - pImage32->SetAvatarSize(32, 32); - pImage64->SetAvatarSize(64, 64); - pImage184->SetAvatarSize(184, 184); + pPlayerInfo->iAvatarIdx = m_iTotalPlayers; - pImage32->SetAvatarSteamID(pPlayerInfo->steamID, k_EAvatarSize32x32); - pImage64->SetAvatarSteamID(pPlayerInfo->steamID, k_EAvatarSize64x64); - pImage184->SetAvatarSteamID(pPlayerInfo->steamID, k_EAvatarSize184x184); - - pPlayerInfo->avatar = { - .i32Idx = m_pImageList->AddImage(pImage32), - .i64Idx = m_pImageList->AddImage(pImage64), - .i184Idx = m_pImageList->AddImage(pImage184), - }; - - m_mapAvatarsToImageList.Insert(pPlayerInfo->steamID, pPlayerInfo->avatar); - } - else - { - pPlayerInfo->avatar = m_mapAvatarsToImageList[iMapIndex]; - } - } - else - { - pPlayerInfo->avatar.i32Idx = -1; - pPlayerInfo->avatar.i64Idx = -1; - pPlayerInfo->avatar.i184Idx = -1; - } + NeoAvatar *pAvatar = &m_avatars[m_iTotalPlayers]; + pAvatar->SetSteamID((ShowAvatars() && pPlayerInfo->steamID.IsValid()) + ? pPlayerInfo->steamID : CSteamID{}); + pAvatar->Fetch(64); ++m_iTotalPlayers; } @@ -892,6 +846,7 @@ void CNEOScoreBoard::OnMainLoop(const NeoUI::Mode eMode) for (int i = 0; i < m_iTotalPlayers; ++i) { const CNEOScoreBoardPlayer *pPlayerInfo = &m_playersInfo[i]; + const NeoAvatar *pAvatar = &m_avatars[pPlayerInfo->iAvatarIdx]; const bool bIsPlaying = (pPlayerInfo->iTeam >= FIRST_GAME_TEAM); const bool bIsDMPlaying = false == bIsTeamplay && bIsPlaying; const bool bIsMuted = pPlayerInfo->bMuted && false == pPlayerInfo->bBot; @@ -909,6 +864,8 @@ void CNEOScoreBoard::OnMainLoop(const NeoUI::Mode eMode) && false == pPlayerInfo->bBot) { m_playerPopup = *pPlayerInfo; + m_avatarPopup = *pAvatar; + m_avatarPopup.Fetch(184); const bool bHaveFriendReq = (SteamFriends() && k_EFriendRelationshipRequestInitiator == SteamFriends()->GetFriendRelationship(m_playerPopup.steamID)); @@ -975,32 +932,15 @@ void CNEOScoreBoard::OnMainLoop(const NeoUI::Mode eMode) // Avatar/Dead-indicator NeoUI::Pad(); - CAvatarImage *pAvatarImg = nullptr; - if (ShowAvatars() && pPlayerInfo->avatar.i32Idx >= 0) + const bool bValidAvatar = (pAvatar->m_iTextureID > 0); + if (bValidAvatar) { - // Use higher px image if wanted, otherwise fallback to i32Idx - if (pPlayerInfo->avatar.i64Idx >= 0 && IN_BETWEEN_EQ(32, m_uiCtx.irWidgetTall, 64)) - { - pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(pPlayerInfo->avatar.i64Idx)); - } - else if (pPlayerInfo->avatar.i184Idx >= 0 && m_uiCtx.irWidgetTall > 64) - { - pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(pPlayerInfo->avatar.i184Idx)); - } - else - { - pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(pPlayerInfo->avatar.i32Idx)); - } - - if (pAvatarImg) - { - pAvatarImg->m_bDeadAvatar = pPlayerInfo->bDead && bIsPlaying; - pAvatarImg->SetPos(m_uiCtx.rWidgetArea.x0, m_uiCtx.rWidgetArea.y0); - pAvatarImg->SetSize(m_uiCtx.irWidgetTall, m_uiCtx.irWidgetTall); - pAvatarImg->Paint(); - } + pAvatar->Paint(m_uiCtx.rWidgetArea.x0, + m_uiCtx.rWidgetArea.y0, + m_uiCtx.irWidgetTall, + pPlayerInfo->bDead && bIsPlaying); } - if (bIsMuted && pAvatarImg) + if (bIsMuted && bValidAvatar) { // Slightly redden the avatar vgui::surface()->DrawSetColor(100, 0, 0, 75); @@ -1013,7 +953,7 @@ void CNEOScoreBoard::OnMainLoop(const NeoUI::Mode eMode) if (iCurTeam >= FIRST_GAME_TEAM) { // Darken the avatar if not ready - if (pAvatarImg + if (bValidAvatar && (bShowReadyUp && false == pPlayerInfo->bReady)) { vgui::surface()->DrawSetColor(0, 0, 0, 200); @@ -1133,29 +1073,12 @@ void CNEOScoreBoard::OnMainLoop(const NeoUI::Mode eMode) if (NeoUI::BeginPopup(NEOSCOREBOARDPOPUP_CARD)) { - CAvatarImage *pAvatarImg = nullptr; - if (ShowAvatars()) - { - if (m_playerPopup.avatar.i184Idx >= 0) - { - pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(m_playerPopup.avatar.i184Idx)); - } - if ((!pAvatarImg || !pAvatarImg->IsValid()) && m_playerPopup.avatar.i64Idx >= 0) - { - pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(m_playerPopup.avatar.i64Idx)); - } - if ((!pAvatarImg || !pAvatarImg->IsValid()) && m_playerPopup.avatar.i32Idx >= 0) - { - pAvatarImg = (CAvatarImage *)(m_pImageList->GetImage(m_playerPopup.avatar.i32Idx)); - } - } - if (pAvatarImg) + const bool bValidAvatar = (m_avatarPopup.m_iTextureID > 0); + if (bValidAvatar) { - pAvatarImg->m_bDeadAvatar = false; - pAvatarImg->SetPos(m_uiCtx.dPanel.x + iAvatarOffset, - m_uiCtx.dPanel.y + iAvatarOffset); - pAvatarImg->SetSize(iAvatarWT, iAvatarWT); - pAvatarImg->Paint(); + m_avatarPopup.Paint(m_uiCtx.dPanel.x + iAvatarOffset, + m_uiCtx.dPanel.y + iAvatarOffset, + iAvatarWT); } NeoUI::SetPerRowLayout(1, nullptr, iPopupCardPerRowTallAvatarName); @@ -1163,7 +1086,7 @@ void CNEOScoreBoard::OnMainLoop(const NeoUI::Mode eMode) NeoUI::Pad(); vgui::surface()->DrawSetTextPos( - m_uiCtx.dPanel.x + iAvatarOffset + (pAvatarImg ? (iAvatarWT + iAvatarOffset) : 0), + m_uiCtx.dPanel.x + iAvatarOffset + (bValidAvatar ? (iAvatarWT + iAvatarOffset) : 0), m_uiCtx.dPanel.y + iAvatarOffset); if (m_playerPopup.wszClantag[0]) { @@ -1174,7 +1097,7 @@ void CNEOScoreBoard::OnMainLoop(const NeoUI::Mode eMode) const auto *pFontI = &m_uiCtx.fonts[m_uiCtx.eFont]; const int iClantagTall = vgui::surface()->GetFontTall(pFontI->hdl); vgui::surface()->DrawSetTextPos( - m_uiCtx.dPanel.x + iAvatarOffset + (pAvatarImg ? (iAvatarWT + iAvatarOffset) : 0), + m_uiCtx.dPanel.x + iAvatarOffset + (bValidAvatar ? (iAvatarWT + iAvatarOffset) : 0), m_uiCtx.dPanel.y + iAvatarOffset + iClantagTall + iAvatarOffset); } NeoUI::SwapFont(NeoUI::FONT_NTLARGE); diff --git a/src/game/client/neo/ui/neo_scoreboard.h b/src/game/client/neo/ui/neo_scoreboard.h index 5d96ed318..61ef782ed 100644 --- a/src/game/client/neo/ui/neo_scoreboard.h +++ b/src/game/client/neo/ui/neo_scoreboard.h @@ -8,6 +8,7 @@ #include "neo_player_shared.h" #include "ui/neo_ui.h" #include "neo_crosshair.h" +#include "neo_avatar.h" enum ENeoScoreBoardPadding { @@ -18,13 +19,6 @@ enum ENeoScoreBoardPadding NEOSCOREBOARDPADDING__TOTAL, }; -struct MapAvatarValue -{ - int i32Idx; - int i64Idx; - int i184Idx; -}; - struct CNEOScoreBoardPlayer { // Common section @@ -35,7 +29,9 @@ struct CNEOScoreBoardPlayer bool bBot; bool bMuted; CSteamID steamID; - MapAvatarValue avatar; + // Becauses NeoAvatar is separate as this clears + // often, so have an index to it + int iAvatarIdx; wchar_t wszName[MAX_PLAYER_NAME_LENGTH]; wchar_t wszClantag[NEO_MAX_CLANTAG_LENGTH]; char szCrosshair[NEO_XHAIR_SEQMAX]; @@ -101,13 +97,12 @@ class CNEOScoreBoard : public vgui::Panel, public IViewPortPanel, public CGameEv int m_iTotalPlayers = 0; CNEOScoreBoardPlayer m_playersInfo[MAX_PLAYERS_ARRAY_SAFE] = {}; + NeoAvatar m_avatars[MAX_PLAYERS_ARRAY_SAFE] = {}; CNEOScoreBoardPlayer m_playerPopup = {}; + NeoAvatar m_avatarPopup = {}; wchar_t m_wszHostname[128] = {}; wchar_t m_wszMap[128] = {}; - - vgui::ImageList *m_pImageList = nullptr; - CUtlMap m_mapAvatarsToImageList; float m_flNextUpdateTime = 0.0f; ButtonCode_t m_nCloseKey = BUTTON_CODE_INVALID; @@ -118,6 +113,8 @@ class CNEOScoreBoard : public vgui::Panel, public IViewPortPanel, public CGameEv int iTall; }; Texture m_arTextures[CROSSHAIR_STYLE__TOTAL] = {}; + + bool m_bAppliedApplyScheme = false; }; extern CNEOScoreBoard *g_pNeoScoreBoard; From a331f638f4d272d6fbe88846b219b6e0d5a00162 Mon Sep 17 00:00:00 2001 From: nullsystem <15316579+nullsystem@users.noreply.github.com> Date: Sun, 30 Aug 2026 03:51:40 +0100 Subject: [PATCH 4/7] simplify CacheAvatarKey --- src/game/client/neo/ui/neo_avatar.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/game/client/neo/ui/neo_avatar.cpp b/src/game/client/neo/ui/neo_avatar.cpp index 313a76c44..9bf71f1a7 100644 --- a/src/game/client/neo/ui/neo_avatar.cpp +++ b/src/game/client/neo/ui/neo_avatar.cpp @@ -14,10 +14,8 @@ struct CacheAvatarValue struct CacheAvatarKey { CSteamID m_SteamID; - int m_iAvatar; + int m_iAvatar = 0; - CacheAvatarKey() { m_iAvatar = 0; } - CacheAvatarKey(CSteamID steamID, int av) { m_SteamID = steamID; m_iAvatar = av; } bool operator<(const CacheAvatarKey &rhs) const { return m_SteamID.ConvertToUint64() < rhs.m_SteamID.ConvertToUint64() @@ -73,7 +71,7 @@ void NeoAvatar::Fetch(const int iAvatarWH) gAvatarImageCacheInit = true; } - int iTexIndex = gAvatarImageCache.Find(CacheAvatarKey(m_SteamID, iAvatar)); + int iTexIndex = gAvatarImageCache.Find(CacheAvatarKey{m_SteamID, iAvatar}); if (iTexIndex == gAvatarImageCache.InvalidIndex()) { uint32 u32wide = 0, u32tall = 0; @@ -133,7 +131,7 @@ void NeoAvatar::Fetch(const int iAvatarWH) g_pMatSystemSurface->DrawSetTextureRGBAEx2(m_iTextureDeadID, rgbaBuf, wide, tall, IMAGE_FORMAT_RGBA8888, true); // Add textures to global cache - iTexIndex = gAvatarImageCache.Insert(CacheAvatarKey(m_SteamID, iAvatar)); + iTexIndex = gAvatarImageCache.Insert(CacheAvatarKey{m_SteamID, iAvatar}); gAvatarImageCache[iTexIndex].normal = m_iTextureID; gAvatarImageCache[iTexIndex].dead = m_iTextureDeadID; } From 8f8f78fc1497ecec3cbacc60b8cc605f6d172fc9 Mon Sep 17 00:00:00 2001 From: nullsystem <15316579+nullsystem@users.noreply.github.com> Date: Sun, 30 Aug 2026 11:46:27 +0100 Subject: [PATCH 5/7] fix indent --- src/game/client/neo/ui/neo_hud_spectator_overlay.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/client/neo/ui/neo_hud_spectator_overlay.h b/src/game/client/neo/ui/neo_hud_spectator_overlay.h index f53a88d1a..802e4cb58 100644 --- a/src/game/client/neo/ui/neo_hud_spectator_overlay.h +++ b/src/game/client/neo/ui/neo_hud_spectator_overlay.h @@ -60,8 +60,8 @@ class CNEOHud_SpectatorOverlay : public CNEOHud_ChildElement, public CHudElement void Paint() final; SpectatorPlayerCard m_cards[MAX_PLAYERS_ARRAY_SAFE] = {}; - // m_avatars separate as m_cards always cleared - NeoAvatar m_avatars[MAX_PLAYERS_ARRAY_SAFE] = {}; + // m_avatars separate as m_cards always cleared + NeoAvatar m_avatars[MAX_PLAYERS_ARRAY_SAFE] = {}; int m_iCardsSize = 0; enum ESpecType From 120a77c530c494675eb389d1ec69968377a6da55 Mon Sep 17 00:00:00 2001 From: nullsystem <15316579+nullsystem@users.noreply.github.com> Date: Sun, 30 Aug 2026 15:08:16 +0100 Subject: [PATCH 6/7] Add RequestUserInformation call Somehow Windows need it but Linux doesn't? But just one thing that was glossed over verses vgui avatar version. --- src/game/client/neo/ui/neo_avatar.cpp | 161 +++++++++++++------------- 1 file changed, 83 insertions(+), 78 deletions(-) diff --git a/src/game/client/neo/ui/neo_avatar.cpp b/src/game/client/neo/ui/neo_avatar.cpp index 9bf71f1a7..d7bb87896 100644 --- a/src/game/client/neo/ui/neo_avatar.cpp +++ b/src/game/client/neo/ui/neo_avatar.cpp @@ -42,106 +42,111 @@ void NeoAvatar::SetSteamID(const CSteamID &steamID) void NeoAvatar::Fetch(const int iAvatarWH) { if (!m_SteamID.IsValid() + || !steamapicontext->SteamFriends() + || !steamapicontext->SteamUtils() || (m_iTextureID > 0 && m_iTexForAvatarWH == iAvatarWH) || (m_flPrevLoadAttempt + 1.0f) > gpGlobals->realtime) { return; } - int iAvatar = 0; - if (iAvatarWH <= 32) + if (!steamapicontext->SteamFriends()->RequestUserInformation(m_SteamID, false)) { - iAvatar = steamapicontext->SteamFriends()->GetSmallFriendAvatar(m_SteamID); - } - else if (iAvatarWH <= 64) - { - iAvatar = steamapicontext->SteamFriends()->GetMediumFriendAvatar(m_SteamID); - } - else - { - iAvatar = steamapicontext->SteamFriends()->GetLargeFriendAvatar(m_SteamID); - } - m_iTexForAvatarWH = iAvatarWH; - - if (iAvatar > 0) - { - if (!gAvatarImageCacheInit) + int iAvatar = 0; + if (iAvatarWH <= 32) { - SetDefLessFunc(gAvatarImageCache); - gAvatarImageCacheInit = true; + iAvatar = steamapicontext->SteamFriends()->GetSmallFriendAvatar(m_SteamID); } + else if (iAvatarWH <= 64) + { + iAvatar = steamapicontext->SteamFriends()->GetMediumFriendAvatar(m_SteamID); + } + else + { + iAvatar = steamapicontext->SteamFriends()->GetLargeFriendAvatar(m_SteamID); + } + m_iTexForAvatarWH = iAvatarWH; - int iTexIndex = gAvatarImageCache.Find(CacheAvatarKey{m_SteamID, iAvatar}); - if (iTexIndex == gAvatarImageCache.InvalidIndex()) + if (iAvatar > 0) { - uint32 u32wide = 0, u32tall = 0; - if (steamapicontext->SteamUtils()->GetImageSize(iAvatar, &u32wide, &u32tall) - && u32wide > 0 && u32tall > 0) + if (!gAvatarImageCacheInit) { - const int wide = u32wide; - const int tall = u32tall; - const int destBufferSize = wide * tall * 4; - byte *rgbaBuf = (byte *)stackalloc(destBufferSize); - if (steamapicontext->SteamUtils()->GetImageRGBA(iAvatar, rgbaBuf, destBufferSize)) - { - // Create normal avatar from RGBA without edits - m_iTextureID = vgui::surface()->CreateNewTextureID(true); - g_pMatSystemSurface->DrawSetTextureRGBAEx2(m_iTextureID, rgbaBuf, wide, tall, IMAGE_FORMAT_RGBA8888, true); + SetDefLessFunc(gAvatarImageCache); + gAvatarImageCacheInit = true; + } - // Create dead avatar from RGBA with redness edits - for (int offset = 0; offset < (wide * tall * 4); offset += 4) + int iTexIndex = gAvatarImageCache.Find(CacheAvatarKey{m_SteamID, iAvatar}); + if (iTexIndex == gAvatarImageCache.InvalidIndex()) + { + uint32 u32wide = 0, u32tall = 0; + if (steamapicontext->SteamUtils()->GetImageSize(iAvatar, &u32wide, &u32tall) + && u32wide > 0 && u32tall > 0) + { + const int wide = u32wide; + const int tall = u32tall; + const int destBufferSize = wide * tall * 4; + byte *rgbaBuf = (byte *)stackalloc(destBufferSize); + if (steamapicontext->SteamUtils()->GetImageRGBA(iAvatar, rgbaBuf, destBufferSize)) { - constexpr float brightness = 0.5f; - constexpr float contrast = 1.5f; - - float r = (rgbaBuf + offset)[0] / 255.0f; - float g = (rgbaBuf + offset)[1] / 255.0f; - float b = (rgbaBuf + offset)[2] / 255.0f; + // Create normal avatar from RGBA without edits + m_iTextureID = vgui::surface()->CreateNewTextureID(true); + g_pMatSystemSurface->DrawSetTextureRGBAEx2(m_iTextureID, rgbaBuf, wide, tall, IMAGE_FORMAT_RGBA8888, true); - // Contrast - r = Clamp((r - 0.5f) * contrast + 0.5f, 0.0f, 1.0f); - g = Clamp((g - 0.5f) * contrast + 0.5f, 0.0f, 1.0f); - b = Clamp((b - 0.5f) * contrast + 0.5f, 0.0f, 1.0f); - - // Convert to grayscale - Luminosity, then gradient from black -> red -> white - const float gray = 0.3f * r + 0.59f * g + 0.11f * b; - if (gray < 0.5) + // Create dead avatar from RGBA with redness edits + for (int offset = 0; offset < (wide * tall * 4); offset += 4) { - r = gray * 2.0f; - g = 0; - b = 0; - } - else { - r = 1.0f; - g = (gray - 0.5f) * 2.0f; - b = (gray - 0.5f) * 2.0f; + constexpr float brightness = 0.5f; + constexpr float contrast = 1.5f; + + float r = (rgbaBuf + offset)[0] / 255.0f; + float g = (rgbaBuf + offset)[1] / 255.0f; + float b = (rgbaBuf + offset)[2] / 255.0f; + + // Contrast + r = Clamp((r - 0.5f) * contrast + 0.5f, 0.0f, 1.0f); + g = Clamp((g - 0.5f) * contrast + 0.5f, 0.0f, 1.0f); + b = Clamp((b - 0.5f) * contrast + 0.5f, 0.0f, 1.0f); + + // Convert to grayscale - Luminosity, then gradient from black -> red -> white + const float gray = 0.3f * r + 0.59f * g + 0.11f * b; + if (gray < 0.5) + { + r = gray * 2.0f; + g = 0; + b = 0; + } + else { + r = 1.0f; + g = (gray - 0.5f) * 2.0f; + b = (gray - 0.5f) * 2.0f; + } + + // Brightness + r = Clamp(r * brightness, 0.0f, 1.0f); + g = Clamp(g * brightness, 0.0f, 1.0f); + b = Clamp(b * brightness, 0.0f, 1.0f); + + (rgbaBuf + offset)[0] = r * 255; + (rgbaBuf + offset)[1] = g * 255; + (rgbaBuf + offset)[2] = b * 255; } - // Brightness - r = Clamp(r * brightness, 0.0f, 1.0f); - g = Clamp(g * brightness, 0.0f, 1.0f); - b = Clamp(b * brightness, 0.0f, 1.0f); + m_iTextureDeadID = vgui::surface()->CreateNewTextureID(true); + g_pMatSystemSurface->DrawSetTextureRGBAEx2(m_iTextureDeadID, rgbaBuf, wide, tall, IMAGE_FORMAT_RGBA8888, true); - (rgbaBuf + offset)[0] = r * 255; - (rgbaBuf + offset)[1] = g * 255; - (rgbaBuf + offset)[2] = b * 255; + // Add textures to global cache + iTexIndex = gAvatarImageCache.Insert(CacheAvatarKey{m_SteamID, iAvatar}); + gAvatarImageCache[iTexIndex].normal = m_iTextureID; + gAvatarImageCache[iTexIndex].dead = m_iTextureDeadID; } - - m_iTextureDeadID = vgui::surface()->CreateNewTextureID(true); - g_pMatSystemSurface->DrawSetTextureRGBAEx2(m_iTextureDeadID, rgbaBuf, wide, tall, IMAGE_FORMAT_RGBA8888, true); - - // Add textures to global cache - iTexIndex = gAvatarImageCache.Insert(CacheAvatarKey{m_SteamID, iAvatar}); - gAvatarImageCache[iTexIndex].normal = m_iTextureID; - gAvatarImageCache[iTexIndex].dead = m_iTextureDeadID; + stackfree(rgbaBuf); } - stackfree(rgbaBuf); } - } - else - { - m_iTextureID = gAvatarImageCache[iTexIndex].normal; - m_iTextureDeadID = gAvatarImageCache[iTexIndex].dead; + else + { + m_iTextureID = gAvatarImageCache[iTexIndex].normal; + m_iTextureDeadID = gAvatarImageCache[iTexIndex].dead; + } } } From a670556429649fc6befbe5a7736519ab126a1aca Mon Sep 17 00:00:00 2001 From: nullsystem <15316579+nullsystem@users.noreply.github.com> Date: Sun, 30 Aug 2026 15:30:21 +0100 Subject: [PATCH 7/7] Remove steamapicontext-> --- src/game/client/neo/ui/neo_avatar.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/game/client/neo/ui/neo_avatar.cpp b/src/game/client/neo/ui/neo_avatar.cpp index d7bb87896..2d0defc6b 100644 --- a/src/game/client/neo/ui/neo_avatar.cpp +++ b/src/game/client/neo/ui/neo_avatar.cpp @@ -42,28 +42,28 @@ void NeoAvatar::SetSteamID(const CSteamID &steamID) void NeoAvatar::Fetch(const int iAvatarWH) { if (!m_SteamID.IsValid() - || !steamapicontext->SteamFriends() - || !steamapicontext->SteamUtils() + || !SteamFriends() + || !SteamUtils() || (m_iTextureID > 0 && m_iTexForAvatarWH == iAvatarWH) || (m_flPrevLoadAttempt + 1.0f) > gpGlobals->realtime) { return; } - if (!steamapicontext->SteamFriends()->RequestUserInformation(m_SteamID, false)) + if (!SteamFriends()->RequestUserInformation(m_SteamID, false)) { int iAvatar = 0; if (iAvatarWH <= 32) { - iAvatar = steamapicontext->SteamFriends()->GetSmallFriendAvatar(m_SteamID); + iAvatar = SteamFriends()->GetSmallFriendAvatar(m_SteamID); } else if (iAvatarWH <= 64) { - iAvatar = steamapicontext->SteamFriends()->GetMediumFriendAvatar(m_SteamID); + iAvatar = SteamFriends()->GetMediumFriendAvatar(m_SteamID); } else { - iAvatar = steamapicontext->SteamFriends()->GetLargeFriendAvatar(m_SteamID); + iAvatar = SteamFriends()->GetLargeFriendAvatar(m_SteamID); } m_iTexForAvatarWH = iAvatarWH; @@ -79,14 +79,14 @@ void NeoAvatar::Fetch(const int iAvatarWH) if (iTexIndex == gAvatarImageCache.InvalidIndex()) { uint32 u32wide = 0, u32tall = 0; - if (steamapicontext->SteamUtils()->GetImageSize(iAvatar, &u32wide, &u32tall) + if (SteamUtils()->GetImageSize(iAvatar, &u32wide, &u32tall) && u32wide > 0 && u32tall > 0) { const int wide = u32wide; const int tall = u32tall; const int destBufferSize = wide * tall * 4; byte *rgbaBuf = (byte *)stackalloc(destBufferSize); - if (steamapicontext->SteamUtils()->GetImageRGBA(iAvatar, rgbaBuf, destBufferSize)) + if (SteamUtils()->GetImageRGBA(iAvatar, rgbaBuf, destBufferSize)) { // Create normal avatar from RGBA without edits m_iTextureID = vgui::surface()->CreateNewTextureID(true);