summaryrefslogtreecommitdiff
path: root/game/client/tf/tf_hud_passtime.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/client/tf/tf_hud_passtime.cpp')
-rw-r--r--game/client/tf/tf_hud_passtime.cpp1670
1 files changed, 1670 insertions, 0 deletions
diff --git a/game/client/tf/tf_hud_passtime.cpp b/game/client/tf/tf_hud_passtime.cpp
new file mode 100644
index 0000000..6181fed
--- /dev/null
+++ b/game/client/tf/tf_hud_passtime.cpp
@@ -0,0 +1,1670 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "c_func_passtime_goal.h"
+#include "c_tf_passtime_ball.h"
+#include "c_tf_passtime_logic.h"
+#include "tf_hud_passtime.h"
+#include "tf_hud_passtime_ball_offscreen_arrow.h"
+#include "tf_weapon_passtime_gun.h"
+#include "passtime_convars.h"
+#include "passtime_game_events.h"
+#include "tf_hud_freezepanel.h"
+#include "tf_gamerules.h"
+#include "c_tf_team.h"
+#include "c_tf_player.h"
+#include "c_tf_playerresource.h"
+#include "iclientmode.h"
+#include "vgui_controls/AnimationController.h"
+#include "vgui_controls/CircularProgressBar.h"
+#include "vgui_controls/ProgressBar.h"
+#include "vgui/ISurface.h"
+#include <algorithm>
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// The team colors from g_PR are wrong for probably good reasons that I don't understand.
+static Color GetTeamColor( int iTeam, byte alpha = 255 )
+{
+ switch ( iTeam )
+ {
+ case TF_TEAM_RED: return Color( 159, 55, 34, alpha );
+ case TF_TEAM_BLUE: return Color( 76, 109, 128, alpha );
+ default: return Color( 245, 231, 222, alpha );
+ }
+}
+
+//-----------------------------------------------------------------------------
+static const char *GetProgressBallImageForTeam( int iTeam )
+{
+ switch( iTeam )
+ {
+ case TF_TEAM_RED: return "../passtime/hud/passtime_ballcontrol_red";
+ case TF_TEAM_BLUE: return "../passtime/hud/passtime_ballcontrol_blue";
+ default: return "../passtime/hud/passtime_ballcontrol_none";
+ };
+}
+static const char *GetProgressBallImageForTeam( C_BaseEntity *pEnt )
+{
+ if ( !pEnt )
+ {
+ return "../passtime/hud/passtime_ball";
+ }
+ return GetProgressBallImageForTeam( pEnt->GetTeamNumber() );
+}
+
+//-----------------------------------------------------------------------------
+static const char *GetPlayerProgressPortrait( C_TFPlayer *pPlayer )
+{
+ if ( !pPlayer )
+ {
+ return "../passtime/hud/portrait_scout_red";
+ }
+
+ int iTeam = pPlayer->GetTeamNumber();
+ int iClass = pPlayer->GetPlayerClass()->GetClassIndex();
+
+ switch(iClass)
+ {
+ case TF_CLASS_SOLDIER:
+ return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_soldier_red" : "../passtime/hud/portrait_soldier_blu";
+ case TF_CLASS_SCOUT:
+ return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_scout_red" : "../passtime/hud/portrait_scout_blu";
+ case TF_CLASS_SNIPER:
+ return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_sniper_red" : "../passtime/hud/portrait_sniper_blu";
+ case TF_CLASS_DEMOMAN:
+ return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_demo_red" : "../passtime/hud/portrait_demo_blu";
+ case TF_CLASS_MEDIC:
+ return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_medic_red" : "../passtime/hud/portrait_medic_blu";
+ case TF_CLASS_HEAVYWEAPONS:
+ return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_heavy_red" : "../passtime/hud/portrait_heavy_blu";
+ case TF_CLASS_PYRO:
+ return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_pyro_red" : "../passtime/hud/portrait_pyro_blu";
+ case TF_CLASS_SPY:
+ return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_spy_red" : "../passtime/hud/portrait_spy_blu";
+ case TF_CLASS_ENGINEER:
+ return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_eng_red" : "../passtime/hud/portrait_eng_blu";
+ default:
+ return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_scout_red" : "../passtime/hud/portrait_scout_blu";
+ }
+}
+
+//-----------------------------------------------------------------------------
+static const char *GetProgressGoalImage( const C_FuncPasstimeGoal *pGoal )
+{
+ if ( !pGoal )
+ {
+ return "";
+ }
+
+ // NOTE: keep in mind that a goal that's on team blue is the goal where blue scores
+ // and should look red on the hud since it's in the red part of the map. oops.
+ bool bRedIcon = pGoal->GetTeamNumber() == TF_TEAM_BLUE;
+
+ if ( pGoal->GetGoalType() == C_FuncPasstimeGoal::TYPE_TOWER )
+ {
+ if ( pGoal->BGoalTriggerDisabled() )
+ {
+ return bRedIcon
+ ? "../passtime/hud/passtime_goal_red_locked"
+ : "../passtime/hud/passtime_goal_blue_locked";
+ }
+ else
+ {
+ return bRedIcon
+ ? "../passtime/hud/passtime_goal_red_unlocked"
+ : "../passtime/hud/passtime_goal_blue_unlocked";
+ }
+ }
+ else if ( pGoal->GetGoalType() == C_FuncPasstimeGoal::TYPE_ENDZONE )
+ {
+ return bRedIcon
+ ? "../passtime/hud/passtime_endzone_red_icon"
+ : "../passtime/hud/passtime_endzone_blue_icon";
+ }
+ else
+ {
+ return bRedIcon
+ ? "../passtime/hud/passtime_goal_red_icon"
+ : "../passtime/hud/passtime_goal_blue_icon";
+ }
+}
+
+//-----------------------------------------------------------------------------
+// CTFHudPasstimePanel
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+CTFHudPasstimePanel::CTFHudPasstimePanel( vgui::Panel *pParent, const char* name )
+ : EditablePanel( pParent, name )
+{}
+
+//-----------------------------------------------------------------------------
+bool CTFHudPasstimePanel::IsVisible()
+{
+ if ( IsTakingAFreezecamScreenshot() )
+ {
+ return false;
+ }
+ return BaseClass::IsVisible();
+}
+
+
+//-----------------------------------------------------------------------------
+// CTFHudTeamScore
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+CTFHudTeamScore::CTFHudTeamScore( vgui::Panel *pParent )
+ : CTFHudPasstimePanel( pParent, "HudTeamScore" )
+ , m_pPlayingToCluster( 0 )
+{
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudTeamScore::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+ LoadControlSettings( "resource/UI/HudPasstimeTeamScore.res", NULL, NULL, NULL );
+ m_pPlayingToCluster = FindControl<EditablePanel>( "PlayingToCluster" );
+
+ if ( m_pPlayingToCluster )
+ {
+ m_pPlayingToCluster->SetVisible( true );
+ }
+
+ OnTick();
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudTeamScore::OnTick()
+{
+ // I would rather not do this every tick, but i couldn't find a reliable way
+ // to do it from events.
+ if( !g_pPasstimeLogic )
+ {
+ return;
+ }
+
+ int iBlueScore = 0;
+ int iRedScore = 0;
+
+ C_TFTeam *pTeam = GetGlobalTFTeam( TF_TEAM_BLUE );
+ if ( pTeam )
+ {
+ iBlueScore = pTeam->GetFlagCaptures();
+ }
+
+ pTeam = GetGlobalTFTeam( TF_TEAM_RED );
+ if ( pTeam )
+ {
+ iRedScore = pTeam->GetFlagCaptures();
+ }
+
+ if ( m_pPlayingToCluster )
+ {
+ m_pPlayingToCluster->SetDialogVariable( "rounds",
+ tf_passtime_scores_per_round.GetInt() );
+ }
+
+ SetDialogVariable( "bluescore", iBlueScore );
+ SetDialogVariable( "redscore", iRedScore );
+}
+
+//-----------------------------------------------------------------------------
+int CTFHudTeamScore::GetTeamScore( int iTeam )
+{
+ C_TFTeam *pTeam = GetGlobalTFTeam( iTeam );
+ return pTeam
+ ? pTeam->Get_Score()
+ : 0;
+}
+
+//-----------------------------------------------------------------------------
+// CTFHudPasstimePassNotify
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+CTFHudPasstimePassNotify::CTFHudPasstimePassNotify( vgui::Panel *pParent )
+ : CTFHudPasstimePanel( pParent, "HudPasstimePassNotify" )
+{
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimePassNotify::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+ LoadControlSettings( "resource/UI/HudPasstimePassNotify.res", NULL, NULL, NULL );
+
+ m_pTextBox = FindControl<EditablePanel>( "TextBox" );
+ m_pTextInPassRange = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextInPassRange" ) : NULL;
+ m_pTextLockedOn = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextLockedOn" ) : NULL;
+ m_pTextPassIncoming = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextPassIncoming" ) : NULL;
+ m_pTextPlayerName = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextPlayerName" ) : NULL;
+ m_pSpeechIndicator = FindControl<ImagePanel>( "SpeechIndicator" );
+ m_pPassLockIndicator = FindControl<ImagePanel>( "PassLockIndicator" );
+ m_pTextBoxBorderNormal = pScheme->GetBorder( "TFFatLineBorder" );
+ m_pTextBoxBorderIncomingRed = pScheme->GetBorder( "TFFatLineBorderRedBG" );
+ m_pTextBoxBorderIncomingBlu = pScheme->GetBorder( "TFFatLineBorderBlueBG" );
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+
+ Assert( m_pTextInPassRange && m_pTextLockedOn && m_pTextPassIncoming && m_pTextPlayerName
+ && m_pSpeechIndicator && m_pPassLockIndicator );
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimePassNotify::OnTick()
+{
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pLocalPlayer || !g_pPasstimeLogic || !g_pPasstimeLogic->GetBall() )
+ {
+ // nothing can work
+ if ( m_pTextBox )
+ {
+ m_pTextBox->SetVisible( false );
+ }
+
+ if ( m_pSpeechIndicator )
+ {
+ m_pSpeechIndicator->SetVisible( false );
+ }
+
+ if ( m_pPassLockIndicator )
+ {
+ m_pPassLockIndicator->SetVisible( false );
+ }
+ return;
+ }
+
+ C_PasstimeBall *pBall = g_pPasstimeLogic->GetBall();
+ CTFPlayer *pBallCarrier = pBall ? pBall->GetCarrier() : NULL;
+ if ( !pBallCarrier )
+ {
+ if ( pBall && pBall->GetHomingTarget() == pLocalPlayer )
+ {
+ //
+ // Incoming pass
+ //
+ if ( m_pSpeechIndicator )
+ {
+ m_pSpeechIndicator->SetVisible( false );
+ }
+
+ if ( m_pPassLockIndicator )
+ {
+ m_pPassLockIndicator->SetVisible( false );
+ }
+
+ if ( m_pTextBox )
+ {
+ m_pTextBox->SetVisible( true );
+ }
+
+ // this should really be GetThrower instead of PrevCarrier,
+ // but it doesn't exist on the client and this will have the
+ // desired value anyway.
+ C_TFPlayer *pThrower = pBall->GetPrevCarrier();
+ if ( pThrower )
+ {
+ if ( m_pTextPlayerName )
+ {
+ m_pTextPlayerName->SetText( pThrower->GetPlayerName() );
+ }
+ }
+ else
+ {
+ if ( m_pTextPlayerName )
+ {
+ m_pTextPlayerName->SetText( "..." );
+ }
+ }
+
+ if ( m_pTextLockedOn )
+ {
+ m_pTextLockedOn->SetVisible( false );
+ }
+
+ if ( m_pTextPassIncoming )
+ {
+ m_pTextPassIncoming->SetVisible( true );
+ }
+
+ if ( m_pTextInPassRange )
+ {
+ m_pTextInPassRange->SetVisible( false );
+ }
+
+ if ( m_pTextBox )
+ {
+ m_pTextBox->SetBorder( (pLocalPlayer->GetTeamNumber() == TF_TEAM_RED)
+ ? m_pTextBoxBorderIncomingRed
+ : m_pTextBoxBorderIncomingBlu );
+ }
+
+ return;
+ }
+ }
+
+ //
+ // Can't be an incoming pass at this point
+ //
+
+ if( !pBallCarrier
+ || pLocalPlayer->IsObserver()
+ || (pBallCarrier == pLocalPlayer)
+ || (pBallCarrier->GetTeamNumber() != pLocalPlayer->GetTeamNumber()) )
+ {
+ //
+ // No carrier, or carrier is on enemy team, or carrier is local player
+ //
+
+ if ( m_pTextBox )
+ {
+ m_pTextBox->SetVisible( false );
+ }
+
+ if ( m_pSpeechIndicator )
+ {
+ m_pSpeechIndicator->SetVisible( false );
+ }
+
+ if ( m_pPassLockIndicator )
+ {
+ m_pPassLockIndicator->SetVisible( false );
+ }
+ return;
+ }
+
+ //
+ // Carrier has the ball and is on our team
+ //
+
+ float flMaxPassRangeSqr = g_pPasstimeLogic->GetMaxPassRange();
+ flMaxPassRangeSqr *= flMaxPassRangeSqr;
+ bool bTargetable = pBallCarrier->EyePosition().DistToSqr( pLocalPlayer->EyePosition() ) < flMaxPassRangeSqr;
+ if ( bTargetable )
+ {
+ trace_t tr;
+ CTraceFilterIgnorePlayers tracefilter( pLocalPlayer, COLLISION_GROUP_PROJECTILE );
+ UTIL_TraceLine( pBallCarrier->EyePosition(), pLocalPlayer->EyePosition(), MASK_PLAYERSOLID, &tracefilter, &tr );
+ bTargetable = tr.fraction == 1;
+ }
+
+ if ( !bTargetable )
+ {
+ if ( m_pTextBox )
+ {
+ m_pTextBox->SetVisible( false );
+ }
+
+ if ( m_pSpeechIndicator )
+ {
+ m_pSpeechIndicator->SetVisible( false );
+ }
+
+ if ( m_pPassLockIndicator )
+ {
+ m_pPassLockIndicator->SetVisible( false );
+ }
+ return;
+ }
+
+ bool bTargeted = pLocalPlayer->m_Shared.IsTargetedForPasstimePass();
+
+ if ( m_pTextBox )
+ {
+ m_pTextBox->SetVisible( true );
+ }
+
+ if ( m_pPassLockIndicator )
+ {
+ m_pPassLockIndicator->SetVisible( bTargeted );
+ }
+
+ if ( m_pSpeechIndicator )
+ {
+ m_pSpeechIndicator->SetVisible( pLocalPlayer->m_Shared.AskForBallTime() > gpGlobals->curtime );
+ }
+
+ if ( bTargeted )
+ {
+ if ( m_pPassLockIndicator )
+ {
+ m_pPassLockIndicator->SetDrawColor( GetTeamColor( pLocalPlayer->GetTeamNumber() ) );
+ }
+
+ if ( m_pTextLockedOn )
+ {
+ m_pTextLockedOn->SetVisible( true );
+ }
+
+ if ( m_pTextPassIncoming )
+ {
+ m_pTextPassIncoming->SetVisible( false );
+ }
+
+ if ( m_pTextInPassRange )
+ {
+ m_pTextInPassRange->SetVisible( false );
+ }
+ }
+ else if ( bTargetable )
+ {
+ if ( m_pTextLockedOn )
+ {
+ m_pTextLockedOn->SetVisible( false );
+ }
+
+ if ( m_pTextPassIncoming )
+ {
+ m_pTextPassIncoming->SetVisible( false );
+ }
+
+ if ( m_pTextInPassRange )
+ {
+ m_pTextInPassRange->SetVisible( true );
+ }
+ }
+ //else if ( !bTargetable )
+ //{
+ // m_pTopText->SetText( "CAN'T SEE YOU" );
+ //}
+
+ if ( m_pTextPlayerName )
+ {
+ m_pTextPlayerName->SetText( pBallCarrier->GetPlayerName() );
+ }
+
+ if ( m_pTextBox )
+ {
+ m_pTextBox->SetBorder( m_pTextBoxBorderNormal );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// CTFHudPasstimeEventText
+//-----------------------------------------------------------------------------
+
+namespace HudPasstimeEventText
+{
+ static const float flInSec = 0.1f;
+ static const float flOutSec = 0.25f;
+ static const float flShowSec = 3.0f;
+ static const float flPauseSec = 0.1f;
+ static const int iQueueDepthPanic = 3;
+ static char const * const pKeyTeam = "team";
+ static char const * const pKeySubject = "subject";
+ static char const * const pKeySource = "source";
+}
+
+//-----------------------------------------------------------------------------
+CTFHudPasstimeEventText::QueueElement::QueueElement()
+{
+ title[0] = (wchar_t)0;
+ detail[0] = (wchar_t)0;
+ bonus[0] = (wchar_t)0;
+}
+
+//-----------------------------------------------------------------------------
+CTFHudPasstimeEventText::CTFHudPasstimeEventText()
+ : m_localizeKeys( "" )
+{
+ m_bValid = false;
+ m_pTitleLabel = 0;
+ m_pDetailLabel = 0;
+ m_pBonusLabel = 0;
+ m_state = State::Idle;
+}
+
+//-----------------------------------------------------------------------------
+CTFHudPasstimeEventText::~CTFHudPasstimeEventText()
+{
+ Clear();
+}
+
+//-----------------------------------------------------------------------------
+// static
+void CTFHudPasstimeEventText::SetLabelText( vgui::Label *pLabel, const wchar_t *pText )
+{
+ if ( pText && pText[0] )
+ {
+ pLabel->SetVisible( true );
+ pLabel->SetText( pText );
+ }
+ else
+ {
+ pLabel->SetVisible( false );
+ pLabel->SetText( L"" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::EnterState( State state, float duration )
+{
+ m_state = state;
+
+ // move things faster if the queue is backlogged, but State::Pause is always the same duration
+ if ( (state != State::Pause) && (m_queue.Count() >= HudPasstimeEventText::iQueueDepthPanic) )
+ {
+ duration /= 2.0f;
+ }
+ m_displayTimer.Start( duration );
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::SetAlpha( int ia )
+{
+ if ( m_pTitleLabel )
+ {
+ m_pTitleLabel->SetAlpha( ia );
+ m_pTitleLabel->SetVisible( ia != 0 );
+ }
+
+ if ( m_pDetailLabel )
+ {
+ m_pDetailLabel->SetAlpha( ia );
+ m_pDetailLabel->SetVisible( ia != 0 );
+ }
+
+ if ( m_pBonusLabel )
+ {
+ m_pBonusLabel->SetAlpha( ia );
+ m_pBonusLabel->SetVisible( ia != 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::Tick()
+{
+ if ( !m_bValid )
+ return;
+
+ switch( m_state )
+ {
+ case State::Idle:
+ if ( !m_queue.IsEmpty() )
+ {
+ SetAlpha( 1 );
+ auto msg = m_queue.RemoveAtHead();
+ if ( m_pTitleLabel )
+ {
+ SetLabelText( m_pTitleLabel, msg.title );
+ }
+ if ( m_pDetailLabel )
+ {
+ SetLabelText( m_pDetailLabel, msg.detail );
+ }
+ if ( m_pBonusLabel )
+ {
+ SetLabelText( m_pBonusLabel, msg.bonus );
+ }
+ EnterState( State::In, HudPasstimeEventText::flInSec );
+ }
+ else
+ {
+ SetAlpha( 0 );
+ }
+ break;
+
+ case State::In:
+ if ( m_displayTimer.IsElapsed() )
+ {
+ EnterState( State::Show, HudPasstimeEventText::flShowSec );
+ SetAlpha( 255 );
+ }
+ else
+ {
+ // animate alpha
+ // note: GetElapsedTime()
+ float flFrac = m_displayTimer.GetElapsedTime() / m_displayTimer.GetCountdownDuration();
+ SetAlpha( (int) (flFrac * flFrac * 255.0f) );
+ }
+ break;
+
+ case State::Show:
+ if ( m_displayTimer.IsElapsed() )
+ {
+ EnterState( State::Out, HudPasstimeEventText::flOutSec );
+ }
+ break;
+
+ case State::Out:
+ if ( m_displayTimer.IsElapsed() )
+ {
+ EnterState( State::Pause, HudPasstimeEventText::flPauseSec );
+ SetAlpha( 0 );
+ }
+ else
+ {
+ // animate alpha
+ // note: GetRemainingTime()
+ float flFrac = m_displayTimer.GetRemainingTime() / m_displayTimer.GetCountdownDuration();
+ SetAlpha( (int) (flFrac * flFrac * 255.0f) );
+ }
+ break;
+
+ case State::Pause:
+ if ( m_displayTimer.IsElapsed() )
+ {
+ m_state = State::Idle;
+ }
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::Clear()
+{
+ while( !m_queue.IsEmpty() )
+ {
+ m_queue.RemoveAtTail();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::SetControls( vgui::Label *pTitleLabel, vgui::Label *pDetailLabel, vgui::Label *pBonusLabel )
+{
+ m_pTitleLabel = pTitleLabel;
+ m_pDetailLabel = pDetailLabel;
+ m_pBonusLabel = pBonusLabel;
+ m_bValid = pTitleLabel && pDetailLabel && pBonusLabel;
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::SetPlayerName( C_TFPlayer *pPlayer, const char *pKey )
+{
+ if ( pPlayer )
+ {
+ g_pVGuiLocalize->ConvertANSIToUnicode( pPlayer->GetPlayerName(), m_pwcsBuf, sizeof(m_pwcsBuf) );
+ m_localizeKeys->SetWString( pKey, m_pwcsBuf );
+ }
+ else
+ {
+ m_localizeKeys->SetWString( pKey, L"" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::SetTeam( C_TFPlayer *pPlayer )
+{
+ if ( pPlayer )
+ {
+ C_TFTeam *pTeam = GetGlobalTFTeam( pPlayer->GetTeamNumber() );
+ m_localizeKeys->SetWString( "team", pTeam ? pTeam->Get_Localized_Name() : L"" );
+ }
+ else
+ {
+ m_localizeKeys->SetWString( "team", L"" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+template< int TArraySize >
+void CTFHudPasstimeEventText::ConstructNewString( const char *pLocTag, wchar_t (&out)[TArraySize] )
+{
+ // FIXME calling find is redundant
+ if ( pLocTag && pLocTag[0] && g_pVGuiLocalize->Find( pLocTag ) )
+ {
+ g_pVGuiLocalize->ConstructString_safe( out, pLocTag, m_localizeKeys );
+ }
+ else
+ {
+ out[0] = (wchar_t)0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::Enqueue( C_TFPlayer *pSource, C_TFPlayer *pSubject, const char *pTitle, const char *pDetail, const char *pBonus )
+{
+ if ( !m_bValid || !pSubject )
+ return;
+
+ SetTeam( pSubject );
+ SetPlayerName( pSubject, HudPasstimeEventText::pKeySubject );
+ SetPlayerName( pSource, HudPasstimeEventText::pKeySource );
+
+ auto *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ auto bShowBonus = (pSubject == pLocalPlayer)
+ || (pLocalPlayer->IsObserver() && pLocalPlayer->GetObserverTarget() == pLocalPlayer);
+
+ QueueElement e;
+ ConstructNewString( pTitle, e.title );
+ ConstructNewString( pDetail, e.detail );
+ ConstructNewString( bShowBonus ? pBonus : nullptr, e.bonus );
+ m_queue.Insert( e );
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::EnqueueGeneric( const char *pTitle, const char *pDetail, const char *pBonus )
+{
+ QueueElement e;
+ ConstructNewString( pTitle, e.title );
+ ConstructNewString( pDetail, e.detail );
+ ConstructNewString( pBonus, e.bonus );
+ m_queue.Insert( e );
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::EnqueueSteal( C_TFPlayer *pVictim, C_TFPlayer *pStealer )
+{
+ Enqueue( pVictim, pStealer, "#Msg_PasstimeEventStealTitle", "#Msg_PasstimeEventStealDetail", "#Msg_PasstimeEventStealBonus" );
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::EnqueuePass( C_TFPlayer *pThrower, C_TFPlayer *pCatcher )
+{
+ Enqueue( pThrower, pCatcher, "#Msg_PasstimeEventPassTitle", "#Msg_PasstimeEventPassDetail", "#Msg_PasstimeEventPassBonus" );
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::EnqueueInterception( C_TFPlayer *pThrower, C_TFPlayer *pCatcher )
+{
+ Enqueue( pThrower, pCatcher, "#Msg_PasstimeEventInterceptTitle", "#Msg_PasstimeEventInterceptDetail", "#Msg_PasstimeEventInterceptBonus" );
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeEventText::EnqueueScore( C_TFPlayer *pThrower, C_TFPlayer *pAssister )
+{
+ if ( pAssister )
+ Enqueue( pAssister, pThrower, "#Msg_PasstimeEventScoreTitle", "#Msg_PasstimeEventScoreDetail_Assist", "#Msg_PasstimeEventScoreBonus" );
+ else
+ Enqueue( pAssister, pThrower, "#Msg_PasstimeEventScoreTitle", "#Msg_PasstimeEventScoreDetail_NoAssist", "#Msg_PasstimeEventScoreBonus" );
+}
+
+//-----------------------------------------------------------------------------
+// CTFHudPasstimeBallStatus
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+CTFHudPasstimeBallStatus::CTFHudPasstimeBallStatus( Panel *pParent )
+ : CTFHudPasstimePanel( pParent, "HudPasstimeBallStatus" )
+{
+ m_bInitialized = false;
+ m_bReset = false;
+ m_bGoalsFound = false;
+ m_iXBlueProgress = -100.0f;
+ m_iXRedProgress = -100.0f;
+ m_iYBlueProgress = -100.0f;
+ m_iYRedProgress = -100.0f;
+ memset( m_pGoalIconsBlue, 0, sizeof( m_pGoalIconsBlue ) );
+ memset( m_pGoalIconsRed, 0, sizeof( m_pGoalIconsRed ) );
+ memset( m_pPlayerIcons, 0, sizeof( m_pPlayerIcons ) );
+ m_pProgressBall = 0;
+ m_pProgressBallCarrierName = 0;
+ m_pProgressLevelBar = 0;
+ m_pSelfPlayerIcon = 0;
+ m_pEventText = new CTFHudPasstimeEventText();
+ m_pBallPowerMeterFillContainer = nullptr;
+ m_pBallPowerMeterFill = nullptr;
+ m_pBallPowerMeterFrame = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+CTFHudPasstimeBallStatus::~CTFHudPasstimeBallStatus()
+{
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+ m_bInitialized = false;
+
+ LoadControlSettings( "resource/UI/HudPasstimeBallStatus.res", NULL, NULL, NULL );
+ m_pPowerCluster = FindControl<EditablePanel>( "BallPowerCluster" );
+ m_pProgressBall = FindControl<ImagePanel>( "ProgressBallIcon" );
+ m_pProgressBallCarrierName = FindControl<Label>( "ProgressBallCarrierName" );
+ m_pProgressLevelBar = FindControl<Panel>( "ProgressLevelBar" );
+ m_pSelfPlayerIcon = FindControl<ImagePanel>( "ProgressSelfPlayerIcon" );
+
+ Panel *pBallPowerRoot = FindControl<Panel>( "BallPowerCluster" );
+ if ( !pBallPowerRoot )
+ {
+ pBallPowerRoot = this;
+ }
+ m_pBallPowerMeterFillContainer = pBallPowerRoot->FindControl<Panel>( "BallPowerMeterFillContainer", true );
+ m_pBallPowerMeterFill = pBallPowerRoot->FindControl<ImagePanel>( "BallPowerMeterFill", true );
+ m_pBallPowerMeterFrame = pBallPowerRoot->FindControl<Panel>( "BallPowerMeterFrame", true );
+ m_pBallPowerMeterFinalSection = pBallPowerRoot->FindControl<Panel>( "BallPowerMeterFinalSectionContainer", true );
+
+ m_pEventText->SetControls( FindControl<Label>( "EventTitleLabel" ),
+ FindControl<Label>( "EventDetailLabel" ),
+ FindControl<Label>( "EventBonusLabel" ) );
+
+ m_bInitialized = m_pProgressBall
+ && m_pProgressBallCarrierName
+ && m_pProgressLevelBar
+ && m_pSelfPlayerIcon
+ && m_pBallPowerMeterFillContainer
+ && m_pBallPowerMeterFill
+ && m_pBallPowerMeterFinalSection
+ && m_pBallPowerMeterFrame;
+
+ if ( !m_bInitialized )
+ {
+ // just bail if the res file is missing required controls
+ // this prevents a lot of stupid null checks in the future
+ return;
+ }
+
+ // set up some ballpower stuff
+ m_iBallPowerMeterFillWidth = m_pBallPowerMeterFillContainer ? m_pBallPowerMeterFillContainer->GetWide() : 0;
+ int iFinalSectionWidth = (int)(m_iBallPowerMeterFillWidth * 0.2f);
+ if ( m_pBallPowerMeterFinalSection )
+ {
+ m_pBallPowerMeterFinalSection->SetWide( iFinalSectionWidth );
+ m_pBallPowerMeterFinalSection->SetPos( m_pBallPowerMeterFinalSection->GetXPos() + (m_iBallPowerMeterFillWidth - iFinalSectionWidth), m_pBallPowerMeterFinalSection->GetYPos() );
+ }
+ m_iPrevBallPower = g_pPasstimeLogic
+ ? g_pPasstimeLogic->GetBallPower()
+ : 0;
+
+ // find the left/right markers in the res files
+ {
+ auto *pRedEnd = FindChildByName( "RedProgressEnd" );
+ auto *pBlueEnd = FindChildByName( "BlueProgressEnd" );
+ if ( pRedEnd && pBlueEnd )
+ {
+ pRedEnd->GetPos( m_iXRedProgress, m_iYRedProgress );
+ pBlueEnd->GetPos( m_iXBlueProgress, m_iYBlueProgress );
+ }
+ else
+ {
+ // no markers in the res file, just give it offscreen but valid coords
+ m_iXBlueProgress = -100.0f;
+ m_iXRedProgress = -100.0f;
+ m_iYBlueProgress = -100.0f;
+ m_iYRedProgress = -100.0f;
+ }
+ }
+
+ // find all the goal icon image panels
+ {
+ char buf[16];
+ for ( auto i = 0; i < NumGoalIcons; ++i )
+ {
+ V_sprintf_safe( buf, "GoalRed%i", i );
+ m_pGoalIconsRed[i] = FindControl<vgui::ImagePanel>( buf );
+ V_sprintf_safe( buf, "GoalBlue%i", i );
+ m_pGoalIconsBlue[i] = FindControl<vgui::ImagePanel>( buf );
+ }
+ }
+
+ ListenForGameEvent( PasstimeGameEvents::BallFree::s_eventName );
+ ListenForGameEvent( PasstimeGameEvents::BallGet::s_eventName );
+ ListenForGameEvent( PasstimeGameEvents::BallStolen::s_eventName );
+ ListenForGameEvent( PasstimeGameEvents::PassCaught::s_eventName );
+ ListenForGameEvent( PasstimeGameEvents::Score::s_eventName );
+ Reset(); // this ensures players will try to guess game state for the hud if they join a game in progress
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+
+ // find all the player icon image panels
+ {
+ char controlname[32];
+ for ( auto i = 0; i < MAX_PLAYERS; ++i )
+ {
+ V_sprintf_safe( controlname, "playericon%i", i ); // ugh
+ m_pPlayerIcons[i] = FindControl<vgui::ImagePanel>( controlname );
+ Assert( m_pPlayerIcons[i] );
+ m_pPlayerIcons[i]->SetEnabled( true );
+ m_pPlayerIcons[i]->SetShouldScaleImage( true );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool CTFHudPasstimeBallStatus::BShouldDraw() const
+{
+ CBasePlayer *pPlayer = CBasePlayer::GetLocalPlayer();
+ if ( !pPlayer || !pPlayer->IsAlive() || ( pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ) )
+ {
+ return false;
+ }
+
+ gamerules_roundstate_t roundstate = TFGameRules()->State_Get();
+ return ((roundstate == GR_STATE_RND_RUNNING) || (roundstate == GR_STATE_STALEMATE))
+ && !TFGameRules()->InSetup();
+}
+
+//-----------------------------------------------------------------------------
+// FIXME copypasta with tf_passtime_logic.cpp
+static float CalcProgressFrac( const Vector& vecOrigin )
+{
+ Assert( g_pPasstimeLogic && (g_pPasstimeLogic->GetNumSections() > 0) );
+
+ Vector arrTrackPoints[16];
+ g_pPasstimeLogic->GetTrackPoints( arrTrackPoints );
+
+ float flBestDist = FLT_MAX;
+ float flBestLen = 0;
+ float flTotalLen = 1; // don't set 0 so div by zero is impossible
+
+ Vector vecThisPoint;
+ Vector vecPointOnLine;
+ Vector vecPrevPoint = arrTrackPoints[0];
+ float flThisFrac = 0;
+ float flThisLen = 0;
+ float flThisDist = 0;
+ for ( int i = 1; i < ARRAYSIZE(arrTrackPoints); ++i )
+ {
+ vecThisPoint = arrTrackPoints[i];
+ if ( vecThisPoint.IsZero() )
+ {
+ break;
+ }
+ flThisLen = (vecThisPoint - vecPrevPoint).Length();
+ flTotalLen += flThisLen;
+ CalcClosestPointOnLineSegment( vecOrigin, vecPrevPoint, vecThisPoint, vecPointOnLine, &flThisFrac );
+ flThisDist = (vecPointOnLine - vecOrigin).Length();
+ if ( flThisDist < flBestDist )
+ {
+ flBestDist = flThisDist;
+ flBestLen = flTotalLen - (flThisLen * (1.0f - flThisFrac));
+ }
+ vecPrevPoint = vecThisPoint;
+ }
+
+ return (float)(flBestLen / flTotalLen);
+}
+
+//-----------------------------------------------------------------------------
+// FIXME copypasta with tf_passtime_logic.cpp
+static float CalcBallProgressFrac()
+{
+ Assert( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() && (g_pPasstimeLogic->GetNumSections() > 0) );
+ CPasstimeBall *pBall = g_pPasstimeLogic->GetBall();
+ CTFPlayer *pCarrier = pBall->GetCarrier();
+ return CalcProgressFrac( pCarrier
+ ? pCarrier->GetNetworkOrigin()
+ : pBall->GetNetworkOrigin() );
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::UpdateGoalIcon( vgui::ImagePanel *pIcon, C_FuncPasstimeGoal *pGoal )
+{
+ Assert( m_bInitialized );
+ // TODO: animations on enable/disable?
+ if ( !pIcon )
+ {
+ return;
+ }
+
+ if ( !pGoal )
+ {
+ pIcon->SetVisible( false );
+ return;
+ }
+
+ pIcon->SetVisible( true );
+
+ const auto iDisabledAlpha = 50;
+ const auto iEnabledAlpha = 255;
+ pIcon->SetAlpha( ( pGoal->BGoalTriggerDisabled() && ( pGoal->GetGoalType() != C_FuncPasstimeGoal::TYPE_TOWER ) )
+ ? iDisabledAlpha
+ : iEnabledAlpha );
+
+ // TODO don't call SetImage(char*) every frame, wtf.
+ pIcon->SetImage( GetProgressGoalImage( pGoal ) );
+
+ const float flProgressFrac = CalcProgressFrac( pGoal->GetAbsOrigin() );
+ const int iActualBarHalfHeight = m_pProgressLevelBar ? m_pProgressLevelBar->GetTall() / 6 : 0; // magic number because NPOT waste in image
+ int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - ( pIcon->GetWide() / 2 );
+ int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - iActualBarHalfHeight;
+ pIcon->SetPos( iX, iY );
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::OnTickHidden()
+{
+ m_bGoalsFound = false;
+
+ if ( m_pProgressBall )
+ {
+ m_pProgressBall->SetVisible( false );
+ }
+
+ if ( m_pProgressBallCarrierName )
+ {
+ m_pProgressBallCarrierName->SetVisible( false );
+ }
+
+ if ( m_pSelfPlayerIcon )
+ {
+ m_pSelfPlayerIcon->SetVisible( false );
+ }
+
+ if ( m_pProgressLevelBar )
+ {
+ m_pProgressLevelBar->SetVisible( false );
+ }
+
+ if ( m_pPowerCluster )
+ {
+ m_pPowerCluster->SetVisible( false );
+ }
+
+ for ( int i = 0; i < MAX_PLAYERS; ++i )
+ {
+ m_pPlayerIcons[i]->SetVisible( false );
+ }
+
+ m_iPrevBallPower = g_pPasstimeLogic
+ ? g_pPasstimeLogic->GetBallPower()
+ : 0;
+
+ HideGoalIcons();
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::OnTickVisible( C_TFPlayer *pLocalPlayer, C_PasstimeBall *pBall)
+{
+ if ( m_pProgressLevelBar )
+ {
+ m_pProgressLevelBar->SetVisible( true );
+ }
+
+ //
+ // Update ball icon
+ //
+ float flBallProgressFrac = CalcBallProgressFrac();
+ int iX_Ball = m_pProgressBall ? Lerp( flBallProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - ( m_pProgressBall->GetWide() / 2 ) : 0;
+ int iY_Ball = m_pProgressBall ? Lerp( flBallProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - ( m_pProgressBall->GetTall() / 2 ) : 0;
+
+ if ( m_pProgressBall )
+ {
+ m_pProgressBall->SetVisible( true );
+ m_pProgressBall->SetPos( iX_Ball, iY_Ball );
+ }
+
+ // todo setimage from event, not every frame
+ // todo look in to letting entities manage their own hud elements
+ CTFPlayer *pCarrier = pBall->GetCarrier();
+ int iTeam = pCarrier
+ ? pCarrier->GetTeamNumber()
+ : pBall->GetTeamNumber();
+ if ( m_pProgressBall )
+ {
+ m_pProgressBall->SetImage( GetProgressBallImageForTeam( iTeam ) );
+ }
+
+ //
+ // Update ball power
+ //
+ if ( m_pPowerCluster )
+ {
+ m_pPowerCluster->SetVisible( true );
+ }
+ {
+ // update the power bar
+ int iCurPower = g_pPasstimeLogic->GetBallPower();
+ int iThreshold = tf_passtime_powerball_threshold.GetInt();
+ int iAlpha = (iCurPower > iThreshold)
+ ? FLerp( 150, 255, (1 + sin(gpGlobals->curtime*5)) / 2.0f)
+ : 222;
+ if ( m_pBallPowerMeterFill )
+ {
+ m_pBallPowerMeterFill->SetDrawColor( GetTeamColor( iTeam, iAlpha ) );
+ }
+ float flPowerFrac = (iCurPower / 100.f);
+ if ( m_pBallPowerMeterFillContainer )
+ {
+ m_pBallPowerMeterFillContainer->SetWide( m_iBallPowerMeterFillWidth * flPowerFrac );
+ }
+
+ // show power up/down notifications
+ // this should really be done with a game event but this is a temp experimental feature for now
+ bool bWasAboveThreshold = m_iPrevBallPower > iThreshold;
+ bool bIsAboveThreshold = iCurPower > iThreshold;
+ m_iPrevBallPower = iCurPower;
+ if ( bWasAboveThreshold && !bIsAboveThreshold )
+ {
+ m_pEventText->Clear(); // push everything else out of the way
+ m_pEventText->EnqueueGeneric( "#Msg_PasstimeEventPowerDownTitle", "#Msg_PasstimeEventPowerDownDetail", "#Msg_PasstimeEventPowerDownBonus" );
+ }
+ else if ( !bWasAboveThreshold && bIsAboveThreshold )
+ {
+ m_pEventText->Clear(); // push everything else out of the way
+ m_pEventText->EnqueueGeneric( "#Msg_PasstimeEventPowerUpTitle", "#Msg_PasstimeEventPowerUpDetail", "#Msg_PasstimeEventPowerUpBonus" );
+ }
+ }
+
+ //
+ // Update player icons
+ // TODO make less bad
+ //
+ C_TFPlayer *pSpecTarget = ToTFPlayer( pLocalPlayer->GetObserverTarget() );
+ if ( m_pSelfPlayerIcon )
+ {
+ m_pSelfPlayerIcon->SetVisible( false );
+ }
+ const int iActualBarHalfHeight = m_pProgressLevelBar ? m_pProgressLevelBar->GetTall() / 7 : 0; // magic number because NPOT waste in image
+ for ( int iEntIndex = 1; iEntIndex <= MAX_PLAYERS; iEntIndex++ )
+ {
+ vgui::ImagePanel *pIcon = m_pPlayerIcons[iEntIndex - 1];
+ if ( !g_TF_PR->IsConnected( iEntIndex ) )
+ {
+ pIcon->SetVisible( false );
+ continue;
+ }
+
+ int iEntTeam = g_TF_PR->GetTeam( iEntIndex );
+ if ( !g_TF_PR->IsAlive( iEntIndex ) // no dead people; not the same as IsDead
+ || ( ( iEntTeam != TF_TEAM_RED ) && ( iEntTeam != TF_TEAM_BLUE) ) ) // no spectators
+ {
+ pIcon->SetVisible( false );
+ continue;
+ }
+
+ // needed for origin
+ // dormant means "not available on this client right now"
+ C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iEntIndex ) );
+ if ( !pPlayer || pPlayer->IsDormant() )
+ {
+ pIcon->SetVisible( false );
+ continue;
+ }
+
+ // don't show cloaked spies
+ bool bSpy = g_TF_PR->GetPlayerClass( iEntIndex ) == TF_CLASS_SPY;
+ if ( bSpy )
+ {
+ if ( pPlayer->m_Shared.IsFullyInvisible() )
+ {
+ pIcon->SetVisible( false );
+ continue;
+ }
+
+ int iDisguiseTeam = pPlayer->m_Shared.GetDisguiseTeam();
+ if ( iDisguiseTeam == TF_TEAM_RED || iDisguiseTeam == TF_TEAM_BLUE )
+ {
+ iEntTeam = iDisguiseTeam;
+ }
+ }
+
+ const float flProgressFrac = CalcProgressFrac( pPlayer->GetNetworkOrigin() );
+ // Player pips
+ {
+ pIcon->SetVisible( true );
+ if ( iEntTeam == TF_TEAM_RED )
+ {
+ int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - (pIcon->GetWide() / 2);
+ int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - pIcon->GetTall() - iActualBarHalfHeight;
+ pIcon->SetPos( iX, iY );
+ pIcon->SetImage( "../passtime/hud/passtime_ballcontrol_team_red" );
+ }
+ else
+ {
+ int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - (pIcon->GetWide() / 2);
+ int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) + iActualBarHalfHeight;
+ pIcon->SetPos( iX, iY );
+ pIcon->SetImage( "../passtime/hud/passtime_ballcontrol_team_blue" );
+ }
+ }
+
+ // Local player image
+ if ( g_TF_PR->IsLocalPlayer( iEntIndex ) || ( pLocalPlayer->IsObserver() && ( pPlayer == pSpecTarget ) ) )
+ {
+ if ( m_pSelfPlayerIcon )
+ {
+ // TODO don't call SetImage(char*) every frame, wtf.
+ if ( pLocalPlayer->IsObserver() && pSpecTarget )
+ {
+ m_pSelfPlayerIcon->SetImage( GetPlayerProgressPortrait( pSpecTarget ) );
+ }
+ else
+ {
+ m_pSelfPlayerIcon->SetImage( GetPlayerProgressPortrait( pLocalPlayer ) );
+ }
+
+ int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - (m_pSelfPlayerIcon->GetWide() / 2);
+ int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - m_pSelfPlayerIcon->GetTall() - iActualBarHalfHeight;
+ m_pSelfPlayerIcon->SetVisible( true );
+ m_pSelfPlayerIcon->SetPos( iX, iY );
+ }
+ }
+ }
+
+ //
+ // Refresh goal icons if necessary
+ //
+ bool bReadyToFindGoals = (g_pPasstimeLogic->GetNumSections() > 0);
+ if ( !m_bGoalsFound && bReadyToFindGoals )
+ {
+ // release any existing handles to goals
+ for ( auto i = 0; i < NumGoalIcons; ++i )
+ {
+ m_hGoalsBlue[i].Term();
+ m_hGoalsRed[i].Term();
+ }
+
+ // Update the goal list and pair hud icons with actual goals
+ const int iMaxSortedGoals = 8; // arbitrary
+ const auto &goals = C_FuncPasstimeGoal::GetAutoList();
+ const int iNumGoals = goals.Count();
+ if ( ( iNumGoals > 1 ) && ( iNumGoals < iMaxSortedGoals ) )
+ {
+ // sort goals by position in world, from blue to red (blue progress is always 0, red is always 1))
+ C_FuncPasstimeGoal* sortedgoals[iMaxSortedGoals];
+ std::copy( goals.begin(), goals.end(), sortedgoals );
+ std::sort( sortedgoals, sortedgoals + iNumGoals, []( C_FuncPasstimeGoal* a, C_FuncPasstimeGoal* b )
+ {
+ // this is wasteful but it should only happen one frame per round
+ // The order of the icons in the hud res file determines which direction to sort these
+ // so that the iteration below visits the goals in the same order.
+ return CalcProgressFrac( a->GetAbsOrigin() ) < CalcProgressFrac( b->GetAbsOrigin() );
+ });
+
+ // Pair goals and icons so the tick function can update the icon state based on the actual goal state
+ // This should work for any number of goals and icons in any order.
+ // If there are more goals than icons, just ignore them, but make sure the hud shows at least
+ // the goals farthest from the middle of the map in a symmetrical way, so iterate from both ends
+ // of the array of sorted goals.
+
+ // pair first N blue goals in left-right order
+ // first icon in array is leftmost blue goal on hud is first goal in sorted array
+ int iRedIcon = 0;
+ int iBlueIcon = 0;
+ for ( int iGoal = 0; iGoal < iNumGoals; ++iGoal )
+ {
+ // NOTE: goal's teams are backwards: the blue-colored goals on the blue side of the map are
+ // on team red because that's where red scores. doh.
+ auto *pGoal = sortedgoals[iGoal];
+ if ( pGoal && ( iBlueIcon < NumGoalIcons ) && ( pGoal->GetTeamNumber() == TF_TEAM_RED ) )
+ {
+ m_hGoalsBlue[iBlueIcon++] = pGoal;
+ }
+
+ pGoal = sortedgoals[iNumGoals - iGoal - 1];
+ if ( pGoal && ( iRedIcon < NumGoalIcons ) && ( pGoal->GetTeamNumber() == TF_TEAM_BLUE ) )
+ {
+ m_hGoalsRed[iRedIcon++] = pGoal;
+ }
+ }
+
+ m_bGoalsFound = true;
+ }
+ }
+
+ //
+ // Update goal icons
+ //
+ if ( m_bGoalsFound )
+ {
+ for ( auto i = 0; i < NumGoalIcons; ++i )
+ {
+ UpdateGoalIcon( m_pGoalIconsBlue[i], m_hGoalsBlue[i].Get() );
+ UpdateGoalIcon( m_pGoalIconsRed[i], m_hGoalsRed[i].Get() );
+ }
+ }
+ else
+ {
+ HideGoalIcons();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::HideGoalIcons()
+{
+ for ( int i = 0; i < NumGoalIcons; ++i )
+ {
+ auto *pIcon = m_pGoalIconsBlue[i];
+ if ( pIcon )
+ pIcon->SetVisible( false );
+ pIcon = m_pGoalIconsRed[i];
+ if ( pIcon )
+ pIcon->SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::OnTick()
+{
+ if ( !g_pPasstimeLogic || !m_bInitialized )
+ {
+ // happens randomly during map load
+ m_pEventText->Clear();
+ return;
+ }
+
+ C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( !pLocalPlayer )
+ {
+ // can happen during exit
+ return;
+ }
+
+ // Tick the event text regardless of the state of the rest of the hud so
+ // that messages can continue to display even after the round ends or whatever.
+ m_pEventText->Tick();
+
+ // I wish there were a reliable way to do this in an event-driven way instead of every frame
+ C_PasstimeBall *pBall = g_pPasstimeLogic->GetBall();
+ if ( !BShouldDraw() || !pBall )
+ {
+ OnTickHidden();
+
+ // this is the easiest way I could find to refresh the goals when switching maps
+ // todo this is dumb
+ m_bGoalsFound = false;
+ }
+ else
+ {
+ OnTickVisible( pLocalPlayer, pBall );
+ }
+
+ //
+ // Try to figure out game state that's usually managed by events if hud was reloaded mid-game
+ // This happens at the end to ensure that the hud is fully initialized before trying to reset
+ // on the first frame a player joins a game in progress
+ //
+ if ( m_bReset )
+ {
+ m_pEventText->Clear();
+
+ // I don't *think* it's possible that this could accidentally end up
+ // running every frame.
+
+ // Order of operations here is important, see functions for details
+ // todo make less bad
+ if ( TryForceBallGet() || TryForceBallFree() )
+ {
+ m_bReset = false;
+ }
+ };
+}
+
+//-----------------------------------------------------------------------------
+bool CTFHudPasstimeBallStatus::TryForceBallGet()
+{
+ Assert( m_bInitialized );
+
+ // The HUD was reset during play, or possibly due to joining a game inprogroess,
+ // so try to get into a BallGet state if possible
+ if ( !g_TF_PR )
+ {
+ // is this even possible?
+ return false;
+ }
+
+ for ( int iPlayer = 1 ; iPlayer <= MAX_PLAYERS; iPlayer++ )
+ {
+ if ( !g_TF_PR->IsConnected( iPlayer ) )
+ {
+ continue;
+ }
+
+ C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayer ) );
+ if ( !pPlayer || !pPlayer->m_Shared.HasPasstimeBall() )
+ {
+ continue;
+ }
+
+ if ( g_TF_PR->IsLocalPlayer( iPlayer ) )
+ {
+ OnBallGetSelf( iPlayer );
+ }
+ else
+ {
+ OnBallGetOther( iPlayer );
+ }
+
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+bool CTFHudPasstimeBallStatus::TryForceBallFree()
+{
+ Assert( m_bInitialized );
+
+ // The HUD was reset during play, or possibly due to joining a game inprogroess,
+ // so try to get into a BallGet state if possible
+ if ( !g_pPasstimeLogic )
+ {
+ return false;
+ }
+
+ C_PasstimeBall *pBall = g_pPasstimeLogic->GetBall();
+ if ( !m_bInitialized || !pBall )
+ {
+ return false;
+ }
+
+ if ( m_pProgressBallCarrierName )
+ {
+ m_pProgressBallCarrierName->SetVisible( false );
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::OnBallGet( int getterIndex )
+{
+ Assert( m_bInitialized && g_PR );
+
+ if ( g_PR->IsLocalPlayer( getterIndex ) )
+ {
+ OnBallGetSelf( getterIndex );
+ }
+ else
+ {
+ OnBallGetOther( getterIndex );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::FireGameEvent( IGameEvent *pEvent )
+{
+ if ( !m_bInitialized || !g_TF_PR || !g_PR )
+ {
+ return;
+ }
+
+ const char *pszEventName = pEvent->GetName();
+ if ( FStrEq( pszEventName, PasstimeGameEvents::BallFree::s_eventName ) )
+ {
+ PasstimeGameEvents::BallFree ev( pEvent );
+ C_TFPlayer *pOwner = ToTFPlayer( UTIL_PlayerByIndex( ev.ownerIndex ) );
+ C_TFPlayer *pAttacker = ToTFPlayer( UTIL_PlayerByIndex( ev.attackerIndex ) );
+ const bool wasMyBall = pOwner == C_TFPlayer::GetLocalPlayer();
+ if ( wasMyBall )
+ {
+ OnBallFreeSelf( pOwner, pAttacker );
+ }
+ else
+ {
+ OnBallFreeOther( pOwner, pAttacker );
+ }
+ }
+ else if ( FStrEq( pszEventName, PasstimeGameEvents::BallGet::s_eventName ) )
+ {
+ OnBallGet( PasstimeGameEvents::BallGet( pEvent ).ownerIndex );
+ }
+ else if ( FStrEq( pszEventName, PasstimeGameEvents::BallStolen::s_eventName ) )
+ {
+ PasstimeGameEvents::BallStolen ballStolenEvent( pEvent );
+ OnBallGet( ballStolenEvent.attackerIndex );
+
+ auto *pAttacker = ToTFPlayer( UTIL_PlayerByIndex(ballStolenEvent.attackerIndex) );
+ auto *pVictim = ToTFPlayer( UTIL_PlayerByIndex(ballStolenEvent.victimIndex) );
+ m_pEventText->EnqueueSteal( pVictim, pAttacker );
+ }
+ else if ( FStrEq( pszEventName, PasstimeGameEvents::PassCaught::s_eventName ) )
+ {
+ PasstimeGameEvents::PassCaught passCaughtEvent( pEvent );
+ OnBallGet( passCaughtEvent.catcherIndex );
+
+ auto *pCatcher = ToTFPlayer( UTIL_PlayerByIndex( passCaughtEvent.catcherIndex ) );
+ auto *pThrower = ToTFPlayer( UTIL_PlayerByIndex( passCaughtEvent.passerIndex ) );
+ if ( pCatcher && pThrower )
+ {
+ if ( pCatcher->GetTeamNumber() == pThrower->GetTeamNumber() )
+ {
+ m_pEventText->EnqueuePass( pThrower, pCatcher );
+ }
+ else
+ {
+ m_pEventText->EnqueueInterception( pThrower, pCatcher );
+ }
+ }
+ }
+ else if ( FStrEq( pszEventName, PasstimeGameEvents::Score::s_eventName ) )
+ {
+ OnBallScore();
+ PasstimeGameEvents::Score scoreEvent( pEvent );
+ auto *pScorer = ToTFPlayer( UTIL_PlayerByIndex( scoreEvent.scorerIndex ) );
+ auto *pAssister = ToTFPlayer( UTIL_PlayerByIndex( scoreEvent.assisterIndex ) );
+ m_pEventText->EnqueueScore( pScorer, pAssister );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::Reset()
+{
+ m_bReset = true;
+
+ // delay the actual reset for a bit because Reset is called before
+ // the game entities are settled into their new state (e.g. the ball
+ // is about to be deleted).
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::OnBallFreeOther( C_TFPlayer *pOwner,
+ C_TFPlayer *pAttacker )
+{
+ Assert( m_bInitialized );
+ if ( m_pProgressBallCarrierName )
+ {
+ m_pProgressBallCarrierName->SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::OnBallFreeSelf( C_TFPlayer *pOwner,
+ C_TFPlayer *pAttacker )
+{
+ Assert( m_bInitialized );
+ if ( m_pProgressBallCarrierName )
+ {
+ m_pProgressBallCarrierName->SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::OnBallGetOther( int iPlayer )
+{
+ Assert( m_bInitialized );
+ Assert( g_PR );
+
+ wchar_t wszFinalText[128];
+ wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH];
+ wchar_t *pwszFormatString = g_pVGuiLocalize->Find( "#TF_Passtime_CarrierName" );
+ if ( !pwszFormatString )
+ {
+ pwszFormatString = L"%s1";
+ }
+ g_pVGuiLocalize->ConvertANSIToUnicode( ( iPlayer > 0 ) ? g_PR->GetPlayerName( iPlayer ) : "", wszPlayerName, sizeof( wszPlayerName ) );
+ g_pVGuiLocalize->ConstructString_safe( wszFinalText, pwszFormatString, 1, wszPlayerName );
+
+ if ( m_pProgressBallCarrierName )
+ {
+ m_pProgressBallCarrierName->SetText( wszFinalText );
+ m_pProgressBallCarrierName->SetVisible( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::OnBallGetSelf( int iPlayer )
+{
+ Assert( m_bInitialized );
+ Assert( g_PR );
+
+ wchar_t wszFinalText[128];
+ wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH];
+ wchar_t *pwszFormatString = g_pVGuiLocalize->Find( "#TF_Passtime_CarrierName" );
+ if ( !pwszFormatString )
+ {
+ pwszFormatString = L"%s1";
+ }
+ g_pVGuiLocalize->ConvertANSIToUnicode( ( iPlayer > 0 ) ? g_PR->GetPlayerName( iPlayer ) : "", wszPlayerName, sizeof( wszPlayerName ) );
+ g_pVGuiLocalize->ConstructString_safe( wszFinalText, pwszFormatString, 1, wszPlayerName );
+
+ if ( m_pProgressBallCarrierName )
+ {
+ m_pProgressBallCarrierName->SetText( wszFinalText );
+ m_pProgressBallCarrierName->SetVisible( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstimeBallStatus::OnBallScore()
+{
+ Assert( m_bInitialized );
+ if ( m_pProgressBallCarrierName )
+ {
+ m_pProgressBallCarrierName->SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// CTFHudPasstime
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+CTFHudPasstime::CTFHudPasstime( Panel *pParent )
+ : CTFHudPasstimePanel( pParent, "HudPasstime" )
+ , m_pBallStatus( new CTFHudPasstimeBallStatus( this ) )
+ , m_pTeamScore( new CTFHudTeamScore( this ) )
+ , m_pBallOffscreenArrow( new CTFHudPasstimeBallOffscreenArrow( this ) )
+ , m_pPassNotify( new CTFHudPasstimePassNotify( this ) )
+{
+ for ( int i = 0; i < MAX_PLAYERS; ++i )
+ {
+ m_pPlayerArrows[i] = new CTFHudPasstimePlayerOffscreenArrow( this, i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+CTFHudPasstime::~CTFHudPasstime()
+{
+ // this is only called when the game quits, so don't bother deleting members
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstime::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+ LoadControlSettings( "resource/UI/HudPasstime.res", NULL, NULL, NULL );
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+ OnTick();
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstime::Reset()
+{
+ if ( m_pBallStatus )
+ {
+ m_pBallStatus->Reset();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFHudPasstime::OnTick()
+{
+ if ( m_pTeamScore )
+ {
+ m_pTeamScore->SetVisible( IsVisible() );
+ }
+}