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
2 changes: 2 additions & 0 deletions game/neo/resource/NeoModEvents.res
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@
"pingy" "short" // ping y position
"pingz" "short" // ping z position
"ghosterping" "bool" // the player is carrying the ghost
"shotuserid" "short" // the player shot an enemy
"deathping" "bool" // the player was killed
}

// inherited from NT
Expand Down
227 changes: 147 additions & 80 deletions src/game/client/neo/ui/neo_hud_player_ping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ void CNEOHud_PlayerPing::ApplySchemeSettings(vgui::IScheme *pScheme)
vgui::surface()->DrawSetTextureFile(m_hTexture, "vgui/hud/ping/ping", 1, false);
vgui::surface()->DrawGetTextureSize(m_hTexture, m_iTexWidth, m_iTexHeight);

m_hDeathPingTexture = vgui::surface()->CreateNewTextureID();
vgui::surface()->DrawSetTextureFile(m_hDeathPingTexture, "vgui/hud/kill_headshot", 1, false);

vgui::surface()->GetScreenSize(m_iPosX, m_iPosY);
SetBounds(0, 0, m_iPosX, m_iPosY);

Expand All @@ -85,12 +88,20 @@ void CNEOHud_PlayerPing::UpdateStateForNeoHudElementDraw()
const int playerTeam = localPlayer->GetTeamNumber();
for (int playerSlot = 0; playerSlot < gpGlobals->maxClients; playerSlot++)
{
if (gpGlobals->curtime >= m_iPlayerPings[playerSlot].deathTime || (playerTeam != TEAM_SPECTATOR && m_iPlayerPings[playerSlot].team != playerTeam))
if (gpGlobals->curtime < m_iPlayerPings[playerSlot].deathTime && (playerTeam == TEAM_SPECTATOR || m_iPlayerPings[playerSlot].team == playerTeam))
{
continue;
UpdateDistanceToPlayer(localPlayer, playerSlot, false, false);
}

if (gpGlobals->curtime < m_iEnemyHitPings[playerSlot].deathTime && (playerTeam == TEAM_SPECTATOR || m_iEnemyHitPings[playerSlot].team == playerTeam))
{
UpdateDistanceToPlayer(localPlayer, playerSlot, true, false);
}

UpdateDistanceToPlayer(localPlayer, playerSlot);
if (gpGlobals->curtime < m_iDeathPings[playerSlot].deathTime)
{
UpdateDistanceToPlayer(localPlayer, playerSlot, false, true);
}
}
}

Expand All @@ -102,6 +113,7 @@ void CNEOHud_PlayerPing::Init(void)
ListenForGameEvent("player_ping");
ListenForGameEvent("round_start");
ListenForGameEvent("player_team");
ListenForGameEvent("player_death");
}

//-----------------------------------------------------------------------------
Expand All @@ -119,21 +131,26 @@ void CNEOHud_PlayerPing::FireGameEvent(IGameEvent* event)

const int localTeam = GetLocalPlayerTeam();
const int playerTeam = event->GetInt("playerteam");
if (localTeam != TEAM_SPECTATOR && playerTeam != localTeam)
bool isDeathPing = event->GetBool("deathping");
if (!isDeathPing && localTeam != TEAM_SPECTATOR && playerTeam != localTeam)
{
return;
}

const int userID = event->GetInt("userid");
const int playerIndex = engine->GetPlayerForUserID(userID);
if (GetClientVoiceMgr()->IsPlayerBlocked(playerIndex))

int shotUserID = event->GetInt("shotuserid", 0);
bool isShotPing = (shotUserID != 0);

if (!isShotPing && !isDeathPing && GetClientVoiceMgr()->IsPlayerBlocked(playerIndex))
{
return;
}

const Vector worldpos = Vector(event->GetInt("pingx"), event->GetInt("pingy"), event->GetInt("pingz"));
bool ghosterPing = event->GetBool("ghosterping");
SetPos(playerIndex - 1, playerTeam, worldpos, ghosterPing);
SetPos(playerIndex - 1, playerTeam, worldpos, ghosterPing, isShotPing, isDeathPing, shotUserID);
}
else if (!Q_stricmp(eventName, "round_start"))
{
Expand All @@ -147,6 +164,17 @@ void CNEOHud_PlayerPing::FireGameEvent(IGameEvent* event)
HideAllPings();
}
}
else if (!Q_stricmp(eventName, "player_death"))
{
const int dead_player_userid = event->GetInt("userid");
for (int i = 0; i < gpGlobals->maxClients; i++)
{
if (m_iEnemyHitPings[i].victimUserID == dead_player_userid)
{
m_iEnemyHitPings[i].deathTime = 0;
}
}
}

}

Expand All @@ -165,53 +193,30 @@ enum NeoPlayerPingsInSpectate
NEO_SPECTATE_PINGS_LAST_VALUE = NEO_SPECTATE_PINGS_ALL
};
ConVar cl_neo_player_pings_in_spectate("cl_neo_player_pings_in_spectate", "1", FCVAR_ARCHIVE, "See pings of players playing the game. 0 = disabled, 1 = spectate target team, 2 = all", true, NEO_SPECTATE_PINGS_DISABLED, true, NEO_SPECTATE_PINGS_LAST_VALUE);
void CNEOHud_PlayerPing::DrawNeoHudElement()
void CNEOHud_PlayerPing::DrawPings(playerPing* pings, int localPlayerTeam, int spectateTargetTeam, int playerPingsInSpectate, bool isEnemyPings, bool isDeathPing)
{
if (!ShouldDraw() || !NEORules()->IsTeamplay())
{
return;
}

C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
if (!pLocalPlayer)
{
return;
}

const int localPlayerTeam = pLocalPlayer->GetTeamNumber();
const int playerPingsInSpectate = cl_neo_player_pings_in_spectate.GetInt();
if (localPlayerTeam == TEAM_SPECTATOR && playerPingsInSpectate == NEO_SPECTATE_PINGS_DISABLED)
{
return;
}

int spectateTargetTeam = TEAM_UNASSIGNED;
if (auto observerTarget = pLocalPlayer->GetObserverTarget())
{
spectateTargetTeam = observerTarget->GetTeamNumber();
};

int x, y, x2, y2;
vgui::surface()->DrawSetTexture(m_hTexture);
vgui::surface()->DrawSetTexture(isDeathPing ? m_hDeathPingTexture : m_hTexture);

for (int i = 0; i < gpGlobals->maxClients; i++)
{
Vector offset = Vector(0, 0, 32);
if (gpGlobals->curtime >= m_iPlayerPings[i].deathTime || !GetVectorInScreenSpace(m_iPlayerPings[i].worldPos, x, y) || !GetVectorInScreenSpace(m_iPlayerPings[i].worldPos, x2, y2, &offset))
if (gpGlobals->curtime >= pings[i].deathTime || !GetVectorInScreenSpace(pings[i].worldPos, x, y) || !GetVectorInScreenSpace(pings[i].worldPos, x2, y2, &offset))
{
continue;
}

if (localPlayerTeam == TEAM_SPECTATOR && playerPingsInSpectate == NEO_SPECTATE_PINGS_TARGET && m_iPlayerPings[i].team != spectateTargetTeam)
if (localPlayerTeam == TEAM_SPECTATOR && playerPingsInSpectate == 1 && pings[i].team != spectateTargetTeam)
{
continue;
}

constexpr float PING_NORMAL_OPACITY = 200;
constexpr float PING_OBSTRUCTED_OPACITY = 80;
float opacity = m_iPlayerPings[i].noLineOfSight ? PING_OBSTRUCTED_OPACITY : PING_NORMAL_OPACITY;
float opacity = pings[i].noLineOfSight ? PING_OBSTRUCTED_OPACITY : PING_NORMAL_OPACITY;

constexpr float PING_FADE_START = 1.f;
const float timeTillDeath = m_iPlayerPings[i].deathTime - gpGlobals->curtime;
const float timeTillDeath = pings[i].deathTime - gpGlobals->curtime;
if (timeTillDeath < PING_FADE_START)
{
opacity *= timeTillDeath / PING_FADE_START;
Expand All @@ -226,57 +231,111 @@ void CNEOHud_PlayerPing::DrawNeoHudElement()
y2 += ((y - y2) * PING_PLACE_ANIMATION_MAX_OFFSET) * sin(M_PI * (timeTillDeath - (PLAYER_PING_LIFETIME - PING_PLACE_ANIMATION_DURATION)) * (1 / PING_PLACE_ANIMATION_DURATION));
}

// Idle animation
/*constexpr float PLAYER_PING_BOUNCE_INTERVAL = 2;
constexpr float PING_PLACE_ANIMATION_MAX_OFFSET = 0.2;
y2 += ((y - y2) * PING_PLACE_ANIMATION_MAX_OFFSET) * sin(M_PI * timeTillDeath * (2 / PLAYER_PING_BOUNCE_INTERVAL));*/

// Draw Ping Shape
const int halfTexture = m_iPlayerPings[i].ghosterPing ? m_iTexTall * 0.8 : m_iTexTall * 0.5;
Color color = m_iPlayerPings[i].ghosterPing ? COLOR_GREY : (m_iPlayerPings[i].team == TEAM_JINRAI) ? COLOR_JINRAI : COLOR_NSF;
const int halfTexture = isDeathPing ? (int)((float)m_iTexTall * 0.5f) : (pings[i].ghosterPing ? m_iTexTall * 0.8 : m_iTexTall * 0.5);
Color color = COLOR_RED;
if (isDeathPing)
{
if (pings[i].team == localPlayerTeam)
{
color = (pings[i].team == TEAM_JINRAI) ? COLOR_JINRAI : COLOR_NSF;
}
}
else if (!isEnemyPings) // Regular pings
{
color = pings[i].ghosterPing ? COLOR_GREY : (pings[i].team == TEAM_JINRAI) ? COLOR_JINRAI : COLOR_NSF;
}
color.SetColor(color.r(), color.g(), color.b(), opacity);
vgui::surface()->DrawSetColor(color);

int drawX = x2;
int drawY = y2;

if (isEnemyPings || isDeathPing)
{
drawX = x;
drawY = y;
}

vgui::surface()->DrawTexturedRect(
x2 - halfTexture,
y2 - halfTexture,
x2 + halfTexture,
y2 + halfTexture);

// Draw Line to root of Ping
const Vector2D directionToOffset = Vector2D(x2 - x, y2 - y);
const float offsetLengthOnScreen = Vector2DLength(directionToOffset);
if (offsetLengthOnScreen > m_iTexTall)
drawX - halfTexture,
drawY - halfTexture,
drawX + halfTexture,
drawY + halfTexture);

if (!isEnemyPings && !isDeathPing)
{
// Offset the end position of the line so it doesn't draw over the ping icon
const int lineX2 = x + directionToOffset.x * ((offsetLengthOnScreen - m_iTexTall) / offsetLengthOnScreen);
const int lineY2 = y + directionToOffset.y * ((offsetLengthOnScreen - m_iTexTall) / offsetLengthOnScreen);

vgui::surface()->DrawLine(x-1,y,lineX2-1,lineY2);
color.SetColor(COLOR_BLACK.r(), COLOR_BLACK.g(), COLOR_BLACK.b(), opacity);
vgui::surface()->DrawSetColor(color);
vgui::surface()->DrawLine(x, y+1, lineX2, lineY2+1);
// Draw Line to root of Ping
const Vector2D directionToOffset = Vector2D(x2 - x, y2 - y);
const float offsetLengthOnScreen = Vector2DLength(directionToOffset);
if (offsetLengthOnScreen > m_iTexTall)
{
// Offset the end position of the line so it doesn't draw over the ping icon
const int lineX2 = x + directionToOffset.x * ((offsetLengthOnScreen - m_iTexTall) / offsetLengthOnScreen);
const int lineY2 = y + directionToOffset.y * ((offsetLengthOnScreen - m_iTexTall) / offsetLengthOnScreen);

vgui::surface()->DrawLine(x - 1, y, lineX2 - 1, lineY2);
color.SetColor(COLOR_BLACK.r(), COLOR_BLACK.g(), COLOR_BLACK.b(), opacity);
vgui::surface()->DrawSetColor(color);
vgui::surface()->DrawLine(x, y + 1, lineX2, lineY2 + 1);
}
}

// Draw Distance to Ping in meters
vgui::surface()->DrawSetTextFont(m_iPlayerPings[i].ghosterPing ? m_hFont : m_hFontSmall);
vgui::surface()->DrawSetTextFont(pings[i].ghosterPing ? m_hFont : m_hFontSmall);
char m_szMarkerText[4 + 1] = {};
wchar_t m_wszMarkerTextUnicode[4 + 1] = {};
V_snprintf(m_szMarkerText, sizeof(m_szMarkerText), "%im", (int)m_iPlayerPings[i].distance);
V_snprintf(m_szMarkerText, sizeof(m_szMarkerText), "%im", (int)pings[i].distance);
g_pVGuiLocalize->ConvertANSIToUnicode(m_szMarkerText, m_wszMarkerTextUnicode, sizeof(m_wszMarkerTextUnicode));
const int halfTextWidth = 0.5 * GetStringPixelWidth(m_wszMarkerTextUnicode, m_iPlayerPings[i].ghosterPing ? m_hFont : m_hFontSmall);
const int halfTextWidth = 0.5 * GetStringPixelWidth(m_wszMarkerTextUnicode, pings[i].ghosterPing ? m_hFont : m_hFontSmall);

// Adjust text position based on whether it's an enemy ping
int textDrawX = drawX;
int textDrawY = drawY;

color.SetColor(COLOR_BLACK.r(), COLOR_BLACK.g(), COLOR_BLACK.b(), opacity);
vgui::surface()->DrawSetTextColor(color);
vgui::surface()->DrawSetTextPos(x2 - halfTextWidth, y2 + 1 - m_iTexTall - m_iFontTall);
vgui::surface()->DrawSetTextPos(textDrawX - halfTextWidth, textDrawY + 1 - m_iTexTall - m_iFontTall);
vgui::surface()->DrawPrintText(m_wszMarkerTextUnicode, 5);

color.SetColor(COLOR_WHITE.r(), COLOR_WHITE.g(), COLOR_WHITE.b(), opacity);
vgui::surface()->DrawSetTextColor(color);
vgui::surface()->DrawSetTextPos(x2 - 1 - halfTextWidth, y2 - m_iTexTall - m_iFontTall);
vgui::surface()->DrawSetTextPos(textDrawX - 1 - halfTextWidth, textDrawY - m_iTexTall - m_iFontTall);
vgui::surface()->DrawPrintText(m_wszMarkerTextUnicode, 5);
}
}

void CNEOHud_PlayerPing::DrawNeoHudElement()
{
if (!ShouldDraw() || !NEORules()->IsTeamplay())
{
return;
}

C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
if (!pLocalPlayer)
{
return;
}

const int localPlayerTeam = pLocalPlayer->GetTeamNumber();
const int playerPingsInSpectate = cl_neo_player_pings_in_spectate.GetInt();
if (localPlayerTeam == TEAM_SPECTATOR && playerPingsInSpectate == NEO_SPECTATE_PINGS_DISABLED)
{
return;
}

int spectateTargetTeam = TEAM_UNASSIGNED;
if (auto observerTarget = pLocalPlayer->GetObserverTarget())
{
spectateTargetTeam = observerTarget->GetTeamNumber();
};

DrawPings(m_iPlayerPings, localPlayerTeam, spectateTargetTeam, playerPingsInSpectate, false, false);
DrawPings(m_iEnemyHitPings, localPlayerTeam, spectateTargetTeam, playerPingsInSpectate, true, false);
DrawPings(m_iDeathPings, localPlayerTeam, spectateTargetTeam, playerPingsInSpectate, false, true);
}

void CNEOHud_PlayerPing::Paint()
{
BaseClass::Paint();
Expand All @@ -300,32 +359,39 @@ int CNEOHud_PlayerPing::GetStringPixelWidth(wchar_t* pString, vgui::HFont hFont)

void CNEOHud_PlayerPing::HideAllPings()
{
for (int i = 0; i < ARRAYSIZE(m_iPlayerPings); i++)
for (int i = 0; i < gpGlobals->maxClients; i++)
{
m_iPlayerPings[i].deathTime = 0;
m_iEnemyHitPings[i].deathTime = 0;
m_iDeathPings[i].deathTime = 0;
}
}

void CNEOHud_PlayerPing::UpdateDistanceToPlayer(C_BasePlayer* player, const int playerSlot)
void CNEOHud_PlayerPing::UpdateDistanceToPlayer(C_BasePlayer* player, const int playerSlot, bool isShotPing, bool isDeathPing)
{
m_iPlayerPings[playerSlot].distance = METERS_PER_INCH * player->GetAbsOrigin().DistTo(m_iPlayerPings[playerSlot].worldPos);
playerPing* pings = isDeathPing ? m_iDeathPings : (isShotPing ? m_iEnemyHitPings : m_iPlayerPings);

pings[playerSlot].distance = METERS_PER_INCH * player->GetAbsOrigin().DistTo(pings[playerSlot].worldPos);

trace_t tr;
UTIL_TraceLine(player->EyePosition(), m_iPlayerPings[playerSlot].worldPos, MASK_VISIBLE_AND_NPCS, player, COLLISION_GROUP_NONE, &tr);
m_iPlayerPings[playerSlot].noLineOfSight = tr.fraction < 0.999;
UTIL_TraceLine(player->EyePosition(), pings[playerSlot].worldPos, MASK_VISIBLE_AND_NPCS, player, COLLISION_GROUP_NONE, &tr);
pings[playerSlot].noLineOfSight = tr.fraction < 0.999;
}

void CNEOHud_PlayerPing::SetPos(const int playerSlot, const int playerTeam, const Vector& pos, bool ghosterPing) {
void CNEOHud_PlayerPing::SetPos(const int playerSlot, const int playerTeam, const Vector& pos, bool ghosterPing, bool isShotPing, bool isDeathPing, int shotUserID) {
constexpr float PLAYER_PING_LIFETIME = 8;
auto localPlayer = C_NEO_Player::GetLocalNEOPlayer();
if (!localPlayer) { return; }

m_iPlayerPings[playerSlot].worldPos = pos;
m_iPlayerPings[playerSlot].deathTime = gpGlobals->curtime + PLAYER_PING_LIFETIME;
m_iPlayerPings[playerSlot].team = playerTeam;
m_iPlayerPings[playerSlot].ghosterPing = ghosterPing;
playerPing* pings = isDeathPing ? m_iDeathPings : (isShotPing ? m_iEnemyHitPings : m_iPlayerPings);

pings[playerSlot].worldPos = pos;
pings[playerSlot].deathTime = gpGlobals->curtime + PLAYER_PING_LIFETIME;
pings[playerSlot].team = playerTeam;
pings[playerSlot].ghosterPing = ghosterPing;
pings[playerSlot].victimUserID = isShotPing ? shotUserID : 0;

UpdateDistanceToPlayer(localPlayer, playerSlot);
UpdateDistanceToPlayer(localPlayer, playerSlot, isShotPing, isDeathPing);
const int playerPingsInSpectate = cl_neo_player_pings_in_spectate.GetInt();
if (GetLocalPlayerTeam() == TEAM_SPECTATOR && playerPingsInSpectate != NEO_SPECTATE_PINGS_ALL)
{
Expand All @@ -348,21 +414,21 @@ void CNEOHud_PlayerPing::SetPos(const int playerSlot, const int playerTeam, cons
return;
}
}
NotifyPing(playerSlot);
NotifyPing(playerSlot, isShotPing, isDeathPing);
}

ConVar snd_ping_volume("snd_ping_volume", "0.33", FCVAR_ARCHIVE, "Player ping volume", true, 0.f, true, 1.f);
ConVar cl_neo_player_pings_chat_message("cl_neo_player_pings_chat_message", "1", FCVAR_ARCHIVE, "Show message in chat for player pings.", true, 0, true, 1); // NEO TODO (Adam) custom chat filter instead?
ConVar cl_neo_player_pings_time_between_sounds("cl_neo_player_pings_time_between_sounds", "0", FCVAR_ARCHIVE, "Minimum time between two ping sounds", true, 0, false, 0);
void CNEOHud_PlayerPing::NotifyPing(const int playerSlot)
void CNEOHud_PlayerPing::NotifyPing(const int playerSlot, bool isShotPing, bool isDeathPing)
{
C_NEO_Player* pPlayer = static_cast<C_NEO_Player*>(UTIL_PlayerByIndex(playerSlot + 1));
if (!pPlayer || pPlayer->IsLocalPlayer())
{
return;
}

if (cl_neo_player_pings_chat_message.GetBool())
if (!isShotPing && !isDeathPing && cl_neo_player_pings_chat_message.GetBool())
{
CBaseHudChat* hudChat = (CBaseHudChat*)GET_HUDELEMENT(CHudChat);
if (hudChat)
Expand Down Expand Up @@ -400,7 +466,8 @@ void CNEOHud_PlayerPing::NotifyPing(const int playerSlot)
et.m_flVolume = snd_ping_volume.GetFloat();
et.m_pOrigin = const_cast<Vector *>(&m_iPlayerPings[playerSlot].worldPos);*/

const Vector* origin = const_cast<Vector *>(&m_iPlayerPings[playerSlot].worldPos);
playerPing* pings = isDeathPing ? m_iDeathPings : (isShotPing ? m_iEnemyHitPings : m_iPlayerPings);
const Vector* origin = const_cast<Vector *>(&pings[playerSlot].worldPos);

CLocalPlayerFilter filter;
//CBaseEntity::EmitSound(filter, SOUND_FROM_WORLD, et);
Expand Down
Loading