@@ -31,6 +31,9 @@ ConVar cl_neo_hud_rangefinder_pos_frac_y("cl_neo_hud_rangefinder_pos_frac_y", "0
3131 " In fractional to the total screen height, the y-axis position of the rangefinder." ,
3232 true , 0 .0f , true , 1 .0f );
3333
34+ ConVar cl_neo_ghost_callout_compass_time (" cl_neo_ghost_callout_compass_time" , " 10.0" , FCVAR_CHEAT ,
35+ " Time in seconds that ghost callouts remain on the compass." , true , 0 .0f , false , 0 .0f );
36+
3437DECLARE_NAMED_HUDELEMENT (CNEOHud_Compass, NHudCompass);
3538
3639NEO_HUD_ELEMENT_DECLARE_FREQ_CVAR (Compass, 0.00695 )
@@ -58,6 +61,61 @@ CNEOHud_Compass::CNEOHud_Compass(const char *pElementName, vgui::Panel *parent)
5861 SetVisible (true );
5962}
6063
64+ void CNEOHud_Compass::Init ()
65+ {
66+ ListenForGameEvent (" ghost_enemy_callout" );
67+ ListenForGameEvent (" round_start" );
68+ ListenForGameEvent (" player_team" );
69+ }
70+
71+ void CNEOHud_Compass::LevelShutdown ()
72+ {
73+ HideAllGhostCallouts ();
74+ }
75+
76+ void CNEOHud_Compass::HideAllGhostCallouts ()
77+ {
78+ for (int i = 0 ; i < V_ARRAYSIZE (m_GhostCallouts); ++i)
79+ {
80+ m_GhostCallouts[i].timer .Invalidate ();
81+ }
82+ }
83+
84+ void CNEOHud_Compass::FireGameEvent (IGameEvent* event)
85+ {
86+ auto eventName = event->GetName ();
87+ if (!Q_stricmp (eventName, " ghost_enemy_callout" ))
88+ {
89+ const int localTeam = GetLocalPlayerTeam ();
90+ const int playerTeam = event->GetInt (" team" );
91+
92+ if (localTeam == TEAM_SPECTATOR || playerTeam != localTeam)
93+ {
94+ return ;
95+ }
96+
97+ int targetId = event->GetInt (" targetid" );
98+ if (targetId > 0 && targetId < V_ARRAYSIZE (m_GhostCallouts))
99+ {
100+ GhostCallout &callout = m_GhostCallouts[targetId];
101+ callout.worldPos = Vector (event->GetInt (" targetx" ), event->GetInt (" targety" ), event->GetInt (" targetz" ));
102+ callout.timer .Start (cl_neo_ghost_callout_compass_time.GetFloat ());
103+ }
104+ }
105+ else if (!Q_stricmp (eventName, " round_start" ))
106+ {
107+ HideAllGhostCallouts ();
108+ }
109+ else if (!Q_stricmp (eventName, " player_team" ))
110+ {
111+ auto player = UTIL_PlayerByUserId (event->GetInt (" userid" ));
112+ if (player && player->IsLocalPlayer ())
113+ {
114+ HideAllGhostCallouts ();
115+ }
116+ }
117+ }
118+
61119void CNEOHud_Compass::Paint ()
62120{
63121 PaintNeoElement ();
@@ -159,6 +217,7 @@ void CNEOHud_Compass::ApplySchemeSettings(vgui::IScheme *pScheme)
159217 LoadControlSettings (" scripts/HudLayout.res" );
160218
161219 m_hFont = pScheme->GetFont (" NHudOCRSmall" );
220+ m_hFontSmall = pScheme->GetFont (" NHudOCRSmallerNoAdditive" );
162221
163222 surface ()->GetScreenSize (m_resX, m_resY);
164223 SetBounds (0 , 0 , m_resX, m_resY);
@@ -232,4 +291,111 @@ void CNEOHud_Compass::DrawCompass() const
232291 surface ()->DrawPrintText (arrowUnicode, Q_UnicodeLength (arrowUnicode));
233292 }
234293 }
294+
295+ DrawCallouts ();
296+ }
297+
298+ void CNEOHud_Compass::DrawCallouts () const
299+ {
300+ struct CalloutDrawInfo
301+ {
302+ int calloutIdx; // Index into m_GhostCallouts
303+ float age;
304+ float renderX;
305+ float renderY;
306+ };
307+
308+ CUtlVectorFixed<CalloutDrawInfo, MAX_PLAYERS_ARRAY_SAFE > visibleCallouts;
309+
310+ int latestIdx = -1 ;
311+ float newestAge = 9999 .0f ;
312+
313+ const wchar_t arrowUnicode[] = L" ▼" ;
314+ int labelWidth, labelHeight;
315+ surface ()->GetTextSize (m_hFont, arrowUnicode, labelWidth, labelHeight);
316+ const float padding = (float )labelHeight;
317+
318+ // Cache ConVars and View parameters
319+ const float maxAge = cl_neo_ghost_callout_compass_time.GetFloat ();
320+ const Vector viewOrigin = MainViewOrigin ();
321+ const float viewYaw = MainViewAngles ()[YAW ];
322+
323+ // Collect visible callouts
324+ for (int i = 0 ; i < V_ARRAYSIZE (m_GhostCallouts); ++i)
325+ {
326+ const GhostCallout& callout = m_GhostCallouts[i];
327+
328+ if (!callout.timer .HasStarted () || callout.timer .IsElapsed ())
329+ continue ;
330+
331+ float age = callout.timer .GetElapsedTime ();
332+
333+ const Vector objVec = callout.worldPos - viewOrigin;
334+ const float objYaw = RAD2DEG (atan2f (objVec.y , objVec.x ));
335+ float drawObjAngle = AngleNormalize (-objYaw + viewYaw);
336+ float clampedAngle = Clamp (drawObjAngle, -(float )m_fov / 2 , (float )m_fov / 2 );
337+
338+ const float proportion = clampedAngle / m_fov + 0.5 ;
339+
340+ float renderX = m_xPos + padding + (m_width - padding * 2 ) * proportion - (float )labelWidth / 2 ;
341+ float renderY = m_yPos - labelHeight;
342+
343+ CalloutDrawInfo info;
344+ info.calloutIdx = i;
345+ info.age = age;
346+ info.renderX = renderX;
347+ info.renderY = renderY;
348+
349+ int currentIdx = visibleCallouts.AddToTail (info);
350+
351+ // Determine latest
352+ if (age < newestAge)
353+ {
354+ newestAge = age;
355+ latestIdx = currentIdx;
356+ }
357+ }
358+
359+ auto DrawSingleCallout = [&](int idx, bool bDrawText)
360+ {
361+ CalloutDrawInfo& info = visibleCallouts[idx];
362+
363+ float alphaScale = (maxAge > 0 .f ) ? (1 .0f - Clamp (info.age / maxAge, 0 .0f , 1 .0f )) : 0 .0f ;
364+ int alpha = Clamp ((int )(255 .0f * alphaScale), 0 , 255 );
365+ Color calloutColor = Color (255 , 0 , 0 , alpha);
366+
367+ surface ()->DrawSetTextColor (calloutColor);
368+ surface ()->DrawSetTextPos (info.renderX , info.renderY );
369+ surface ()->DrawPrintText (arrowUnicode, 1 );
370+
371+ if (bDrawText)
372+ {
373+ float dist = METERS_PER_INCH * viewOrigin.DistTo (m_GhostCallouts[info.calloutIdx ].worldPos );
374+
375+ wchar_t wszDist[16 ];
376+ V_swprintf_safe (wszDist, L" %im" , (int )dist);
377+
378+ int distWidth, distHeight;
379+ surface ()->GetTextSize (m_hFontSmall, wszDist, distWidth, distHeight);
380+
381+ surface ()->DrawSetTextFont (m_hFontSmall);
382+ surface ()->DrawSetTextPos (info.renderX + (labelWidth / 2 ) - (distWidth / 2 ), info.renderY - distHeight);
383+ surface ()->DrawPrintText (wszDist, Q_UnicodeLength (wszDist));
384+ }
385+ };
386+
387+ // Draw older ones first
388+ for (int i = 0 ; i < visibleCallouts.Count (); ++i)
389+ {
390+ if (i == latestIdx)
391+ continue ;
392+
393+ DrawSingleCallout (i, false );
394+ }
395+
396+ // Draw the latest one on top with distance text
397+ if (latestIdx != -1 )
398+ {
399+ DrawSingleCallout (latestIdx, true );
400+ }
235401}
0 commit comments