Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1670 lines
48 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "c_func_passtime_goal.h"
  9. #include "c_tf_passtime_ball.h"
  10. #include "c_tf_passtime_logic.h"
  11. #include "tf_hud_passtime.h"
  12. #include "tf_hud_passtime_ball_offscreen_arrow.h"
  13. #include "tf_weapon_passtime_gun.h"
  14. #include "passtime_convars.h"
  15. #include "passtime_game_events.h"
  16. #include "tf_hud_freezepanel.h"
  17. #include "tf_gamerules.h"
  18. #include "c_tf_team.h"
  19. #include "c_tf_player.h"
  20. #include "c_tf_playerresource.h"
  21. #include "iclientmode.h"
  22. #include "vgui_controls/AnimationController.h"
  23. #include "vgui_controls/CircularProgressBar.h"
  24. #include "vgui_controls/ProgressBar.h"
  25. #include "vgui/ISurface.h"
  26. #include <algorithm>
  27. // memdbgon must be the last include file in a .cpp file!!!
  28. #include "tier0/memdbgon.h"
  29. //-----------------------------------------------------------------------------
  30. using namespace vgui;
  31. //-----------------------------------------------------------------------------
  32. // The team colors from g_PR are wrong for probably good reasons that I don't understand.
  33. static Color GetTeamColor( int iTeam, byte alpha = 255 )
  34. {
  35. switch ( iTeam )
  36. {
  37. case TF_TEAM_RED: return Color( 159, 55, 34, alpha );
  38. case TF_TEAM_BLUE: return Color( 76, 109, 128, alpha );
  39. default: return Color( 245, 231, 222, alpha );
  40. }
  41. }
  42. //-----------------------------------------------------------------------------
  43. static const char *GetProgressBallImageForTeam( int iTeam )
  44. {
  45. switch( iTeam )
  46. {
  47. case TF_TEAM_RED: return "../passtime/hud/passtime_ballcontrol_red";
  48. case TF_TEAM_BLUE: return "../passtime/hud/passtime_ballcontrol_blue";
  49. default: return "../passtime/hud/passtime_ballcontrol_none";
  50. };
  51. }
  52. static const char *GetProgressBallImageForTeam( C_BaseEntity *pEnt )
  53. {
  54. if ( !pEnt )
  55. {
  56. return "../passtime/hud/passtime_ball";
  57. }
  58. return GetProgressBallImageForTeam( pEnt->GetTeamNumber() );
  59. }
  60. //-----------------------------------------------------------------------------
  61. static const char *GetPlayerProgressPortrait( C_TFPlayer *pPlayer )
  62. {
  63. if ( !pPlayer )
  64. {
  65. return "../passtime/hud/portrait_scout_red";
  66. }
  67. int iTeam = pPlayer->GetTeamNumber();
  68. int iClass = pPlayer->GetPlayerClass()->GetClassIndex();
  69. switch(iClass)
  70. {
  71. case TF_CLASS_SOLDIER:
  72. return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_soldier_red" : "../passtime/hud/portrait_soldier_blu";
  73. case TF_CLASS_SCOUT:
  74. return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_scout_red" : "../passtime/hud/portrait_scout_blu";
  75. case TF_CLASS_SNIPER:
  76. return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_sniper_red" : "../passtime/hud/portrait_sniper_blu";
  77. case TF_CLASS_DEMOMAN:
  78. return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_demo_red" : "../passtime/hud/portrait_demo_blu";
  79. case TF_CLASS_MEDIC:
  80. return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_medic_red" : "../passtime/hud/portrait_medic_blu";
  81. case TF_CLASS_HEAVYWEAPONS:
  82. return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_heavy_red" : "../passtime/hud/portrait_heavy_blu";
  83. case TF_CLASS_PYRO:
  84. return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_pyro_red" : "../passtime/hud/portrait_pyro_blu";
  85. case TF_CLASS_SPY:
  86. return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_spy_red" : "../passtime/hud/portrait_spy_blu";
  87. case TF_CLASS_ENGINEER:
  88. return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_eng_red" : "../passtime/hud/portrait_eng_blu";
  89. default:
  90. return (iTeam == TF_TEAM_RED) ? "../passtime/hud/portrait_scout_red" : "../passtime/hud/portrait_scout_blu";
  91. }
  92. }
  93. //-----------------------------------------------------------------------------
  94. static const char *GetProgressGoalImage( const C_FuncPasstimeGoal *pGoal )
  95. {
  96. if ( !pGoal )
  97. {
  98. return "";
  99. }
  100. // NOTE: keep in mind that a goal that's on team blue is the goal where blue scores
  101. // and should look red on the hud since it's in the red part of the map. oops.
  102. bool bRedIcon = pGoal->GetTeamNumber() == TF_TEAM_BLUE;
  103. if ( pGoal->GetGoalType() == C_FuncPasstimeGoal::TYPE_TOWER )
  104. {
  105. if ( pGoal->BGoalTriggerDisabled() )
  106. {
  107. return bRedIcon
  108. ? "../passtime/hud/passtime_goal_red_locked"
  109. : "../passtime/hud/passtime_goal_blue_locked";
  110. }
  111. else
  112. {
  113. return bRedIcon
  114. ? "../passtime/hud/passtime_goal_red_unlocked"
  115. : "../passtime/hud/passtime_goal_blue_unlocked";
  116. }
  117. }
  118. else if ( pGoal->GetGoalType() == C_FuncPasstimeGoal::TYPE_ENDZONE )
  119. {
  120. return bRedIcon
  121. ? "../passtime/hud/passtime_endzone_red_icon"
  122. : "../passtime/hud/passtime_endzone_blue_icon";
  123. }
  124. else
  125. {
  126. return bRedIcon
  127. ? "../passtime/hud/passtime_goal_red_icon"
  128. : "../passtime/hud/passtime_goal_blue_icon";
  129. }
  130. }
  131. //-----------------------------------------------------------------------------
  132. // CTFHudPasstimePanel
  133. //-----------------------------------------------------------------------------
  134. //-----------------------------------------------------------------------------
  135. CTFHudPasstimePanel::CTFHudPasstimePanel( vgui::Panel *pParent, const char* name )
  136. : EditablePanel( pParent, name )
  137. {}
  138. //-----------------------------------------------------------------------------
  139. bool CTFHudPasstimePanel::IsVisible()
  140. {
  141. if ( IsTakingAFreezecamScreenshot() )
  142. {
  143. return false;
  144. }
  145. return BaseClass::IsVisible();
  146. }
  147. //-----------------------------------------------------------------------------
  148. // CTFHudTeamScore
  149. //-----------------------------------------------------------------------------
  150. //-----------------------------------------------------------------------------
  151. CTFHudTeamScore::CTFHudTeamScore( vgui::Panel *pParent )
  152. : CTFHudPasstimePanel( pParent, "HudTeamScore" )
  153. , m_pPlayingToCluster( 0 )
  154. {
  155. }
  156. //-----------------------------------------------------------------------------
  157. void CTFHudTeamScore::ApplySchemeSettings( vgui::IScheme *pScheme )
  158. {
  159. BaseClass::ApplySchemeSettings( pScheme );
  160. LoadControlSettings( "resource/UI/HudPasstimeTeamScore.res", NULL, NULL, NULL );
  161. m_pPlayingToCluster = FindControl<EditablePanel>( "PlayingToCluster" );
  162. if ( m_pPlayingToCluster )
  163. {
  164. m_pPlayingToCluster->SetVisible( true );
  165. }
  166. OnTick();
  167. vgui::ivgui()->AddTickSignal( GetVPanel() );
  168. }
  169. //-----------------------------------------------------------------------------
  170. void CTFHudTeamScore::OnTick()
  171. {
  172. // I would rather not do this every tick, but i couldn't find a reliable way
  173. // to do it from events.
  174. if( !g_pPasstimeLogic )
  175. {
  176. return;
  177. }
  178. int iBlueScore = 0;
  179. int iRedScore = 0;
  180. C_TFTeam *pTeam = GetGlobalTFTeam( TF_TEAM_BLUE );
  181. if ( pTeam )
  182. {
  183. iBlueScore = pTeam->GetFlagCaptures();
  184. }
  185. pTeam = GetGlobalTFTeam( TF_TEAM_RED );
  186. if ( pTeam )
  187. {
  188. iRedScore = pTeam->GetFlagCaptures();
  189. }
  190. if ( m_pPlayingToCluster )
  191. {
  192. m_pPlayingToCluster->SetDialogVariable( "rounds",
  193. tf_passtime_scores_per_round.GetInt() );
  194. }
  195. SetDialogVariable( "bluescore", iBlueScore );
  196. SetDialogVariable( "redscore", iRedScore );
  197. }
  198. //-----------------------------------------------------------------------------
  199. int CTFHudTeamScore::GetTeamScore( int iTeam )
  200. {
  201. C_TFTeam *pTeam = GetGlobalTFTeam( iTeam );
  202. return pTeam
  203. ? pTeam->Get_Score()
  204. : 0;
  205. }
  206. //-----------------------------------------------------------------------------
  207. // CTFHudPasstimePassNotify
  208. //-----------------------------------------------------------------------------
  209. //-----------------------------------------------------------------------------
  210. CTFHudPasstimePassNotify::CTFHudPasstimePassNotify( vgui::Panel *pParent )
  211. : CTFHudPasstimePanel( pParent, "HudPasstimePassNotify" )
  212. {
  213. }
  214. //-----------------------------------------------------------------------------
  215. void CTFHudPasstimePassNotify::ApplySchemeSettings( vgui::IScheme *pScheme )
  216. {
  217. BaseClass::ApplySchemeSettings( pScheme );
  218. LoadControlSettings( "resource/UI/HudPasstimePassNotify.res", NULL, NULL, NULL );
  219. m_pTextBox = FindControl<EditablePanel>( "TextBox" );
  220. m_pTextInPassRange = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextInPassRange" ) : NULL;
  221. m_pTextLockedOn = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextLockedOn" ) : NULL;
  222. m_pTextPassIncoming = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextPassIncoming" ) : NULL;
  223. m_pTextPlayerName = m_pTextBox ? m_pTextBox->FindControl<Label>( "TextPlayerName" ) : NULL;
  224. m_pSpeechIndicator = FindControl<ImagePanel>( "SpeechIndicator" );
  225. m_pPassLockIndicator = FindControl<ImagePanel>( "PassLockIndicator" );
  226. m_pTextBoxBorderNormal = pScheme->GetBorder( "TFFatLineBorder" );
  227. m_pTextBoxBorderIncomingRed = pScheme->GetBorder( "TFFatLineBorderRedBG" );
  228. m_pTextBoxBorderIncomingBlu = pScheme->GetBorder( "TFFatLineBorderBlueBG" );
  229. vgui::ivgui()->AddTickSignal( GetVPanel() );
  230. Assert( m_pTextInPassRange && m_pTextLockedOn && m_pTextPassIncoming && m_pTextPlayerName
  231. && m_pSpeechIndicator && m_pPassLockIndicator );
  232. }
  233. //-----------------------------------------------------------------------------
  234. void CTFHudPasstimePassNotify::OnTick()
  235. {
  236. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  237. if ( !pLocalPlayer || !g_pPasstimeLogic || !g_pPasstimeLogic->GetBall() )
  238. {
  239. // nothing can work
  240. if ( m_pTextBox )
  241. {
  242. m_pTextBox->SetVisible( false );
  243. }
  244. if ( m_pSpeechIndicator )
  245. {
  246. m_pSpeechIndicator->SetVisible( false );
  247. }
  248. if ( m_pPassLockIndicator )
  249. {
  250. m_pPassLockIndicator->SetVisible( false );
  251. }
  252. return;
  253. }
  254. C_PasstimeBall *pBall = g_pPasstimeLogic->GetBall();
  255. CTFPlayer *pBallCarrier = pBall ? pBall->GetCarrier() : NULL;
  256. if ( !pBallCarrier )
  257. {
  258. if ( pBall && pBall->GetHomingTarget() == pLocalPlayer )
  259. {
  260. //
  261. // Incoming pass
  262. //
  263. if ( m_pSpeechIndicator )
  264. {
  265. m_pSpeechIndicator->SetVisible( false );
  266. }
  267. if ( m_pPassLockIndicator )
  268. {
  269. m_pPassLockIndicator->SetVisible( false );
  270. }
  271. if ( m_pTextBox )
  272. {
  273. m_pTextBox->SetVisible( true );
  274. }
  275. // this should really be GetThrower instead of PrevCarrier,
  276. // but it doesn't exist on the client and this will have the
  277. // desired value anyway.
  278. C_TFPlayer *pThrower = pBall->GetPrevCarrier();
  279. if ( pThrower )
  280. {
  281. if ( m_pTextPlayerName )
  282. {
  283. m_pTextPlayerName->SetText( pThrower->GetPlayerName() );
  284. }
  285. }
  286. else
  287. {
  288. if ( m_pTextPlayerName )
  289. {
  290. m_pTextPlayerName->SetText( "..." );
  291. }
  292. }
  293. if ( m_pTextLockedOn )
  294. {
  295. m_pTextLockedOn->SetVisible( false );
  296. }
  297. if ( m_pTextPassIncoming )
  298. {
  299. m_pTextPassIncoming->SetVisible( true );
  300. }
  301. if ( m_pTextInPassRange )
  302. {
  303. m_pTextInPassRange->SetVisible( false );
  304. }
  305. if ( m_pTextBox )
  306. {
  307. m_pTextBox->SetBorder( (pLocalPlayer->GetTeamNumber() == TF_TEAM_RED)
  308. ? m_pTextBoxBorderIncomingRed
  309. : m_pTextBoxBorderIncomingBlu );
  310. }
  311. return;
  312. }
  313. }
  314. //
  315. // Can't be an incoming pass at this point
  316. //
  317. if( !pBallCarrier
  318. || pLocalPlayer->IsObserver()
  319. || (pBallCarrier == pLocalPlayer)
  320. || (pBallCarrier->GetTeamNumber() != pLocalPlayer->GetTeamNumber()) )
  321. {
  322. //
  323. // No carrier, or carrier is on enemy team, or carrier is local player
  324. //
  325. if ( m_pTextBox )
  326. {
  327. m_pTextBox->SetVisible( false );
  328. }
  329. if ( m_pSpeechIndicator )
  330. {
  331. m_pSpeechIndicator->SetVisible( false );
  332. }
  333. if ( m_pPassLockIndicator )
  334. {
  335. m_pPassLockIndicator->SetVisible( false );
  336. }
  337. return;
  338. }
  339. //
  340. // Carrier has the ball and is on our team
  341. //
  342. float flMaxPassRangeSqr = g_pPasstimeLogic->GetMaxPassRange();
  343. flMaxPassRangeSqr *= flMaxPassRangeSqr;
  344. bool bTargetable = pBallCarrier->EyePosition().DistToSqr( pLocalPlayer->EyePosition() ) < flMaxPassRangeSqr;
  345. if ( bTargetable )
  346. {
  347. trace_t tr;
  348. CTraceFilterIgnorePlayers tracefilter( pLocalPlayer, COLLISION_GROUP_PROJECTILE );
  349. UTIL_TraceLine( pBallCarrier->EyePosition(), pLocalPlayer->EyePosition(), MASK_PLAYERSOLID, &tracefilter, &tr );
  350. bTargetable = tr.fraction == 1;
  351. }
  352. if ( !bTargetable )
  353. {
  354. if ( m_pTextBox )
  355. {
  356. m_pTextBox->SetVisible( false );
  357. }
  358. if ( m_pSpeechIndicator )
  359. {
  360. m_pSpeechIndicator->SetVisible( false );
  361. }
  362. if ( m_pPassLockIndicator )
  363. {
  364. m_pPassLockIndicator->SetVisible( false );
  365. }
  366. return;
  367. }
  368. bool bTargeted = pLocalPlayer->m_Shared.IsTargetedForPasstimePass();
  369. if ( m_pTextBox )
  370. {
  371. m_pTextBox->SetVisible( true );
  372. }
  373. if ( m_pPassLockIndicator )
  374. {
  375. m_pPassLockIndicator->SetVisible( bTargeted );
  376. }
  377. if ( m_pSpeechIndicator )
  378. {
  379. m_pSpeechIndicator->SetVisible( pLocalPlayer->m_Shared.AskForBallTime() > gpGlobals->curtime );
  380. }
  381. if ( bTargeted )
  382. {
  383. if ( m_pPassLockIndicator )
  384. {
  385. m_pPassLockIndicator->SetDrawColor( GetTeamColor( pLocalPlayer->GetTeamNumber() ) );
  386. }
  387. if ( m_pTextLockedOn )
  388. {
  389. m_pTextLockedOn->SetVisible( true );
  390. }
  391. if ( m_pTextPassIncoming )
  392. {
  393. m_pTextPassIncoming->SetVisible( false );
  394. }
  395. if ( m_pTextInPassRange )
  396. {
  397. m_pTextInPassRange->SetVisible( false );
  398. }
  399. }
  400. else if ( bTargetable )
  401. {
  402. if ( m_pTextLockedOn )
  403. {
  404. m_pTextLockedOn->SetVisible( false );
  405. }
  406. if ( m_pTextPassIncoming )
  407. {
  408. m_pTextPassIncoming->SetVisible( false );
  409. }
  410. if ( m_pTextInPassRange )
  411. {
  412. m_pTextInPassRange->SetVisible( true );
  413. }
  414. }
  415. //else if ( !bTargetable )
  416. //{
  417. // m_pTopText->SetText( "CAN'T SEE YOU" );
  418. //}
  419. if ( m_pTextPlayerName )
  420. {
  421. m_pTextPlayerName->SetText( pBallCarrier->GetPlayerName() );
  422. }
  423. if ( m_pTextBox )
  424. {
  425. m_pTextBox->SetBorder( m_pTextBoxBorderNormal );
  426. }
  427. }
  428. //-----------------------------------------------------------------------------
  429. // CTFHudPasstimeEventText
  430. //-----------------------------------------------------------------------------
  431. namespace HudPasstimeEventText
  432. {
  433. static const float flInSec = 0.1f;
  434. static const float flOutSec = 0.25f;
  435. static const float flShowSec = 3.0f;
  436. static const float flPauseSec = 0.1f;
  437. static const int iQueueDepthPanic = 3;
  438. static char const * const pKeyTeam = "team";
  439. static char const * const pKeySubject = "subject";
  440. static char const * const pKeySource = "source";
  441. }
  442. //-----------------------------------------------------------------------------
  443. CTFHudPasstimeEventText::QueueElement::QueueElement()
  444. {
  445. title[0] = (wchar_t)0;
  446. detail[0] = (wchar_t)0;
  447. bonus[0] = (wchar_t)0;
  448. }
  449. //-----------------------------------------------------------------------------
  450. CTFHudPasstimeEventText::CTFHudPasstimeEventText()
  451. : m_localizeKeys( "" )
  452. {
  453. m_bValid = false;
  454. m_pTitleLabel = 0;
  455. m_pDetailLabel = 0;
  456. m_pBonusLabel = 0;
  457. m_state = State::Idle;
  458. }
  459. //-----------------------------------------------------------------------------
  460. CTFHudPasstimeEventText::~CTFHudPasstimeEventText()
  461. {
  462. Clear();
  463. }
  464. //-----------------------------------------------------------------------------
  465. // static
  466. void CTFHudPasstimeEventText::SetLabelText( vgui::Label *pLabel, const wchar_t *pText )
  467. {
  468. if ( pText && pText[0] )
  469. {
  470. pLabel->SetVisible( true );
  471. pLabel->SetText( pText );
  472. }
  473. else
  474. {
  475. pLabel->SetVisible( false );
  476. pLabel->SetText( L"" );
  477. }
  478. }
  479. //-----------------------------------------------------------------------------
  480. void CTFHudPasstimeEventText::EnterState( State state, float duration )
  481. {
  482. m_state = state;
  483. // move things faster if the queue is backlogged, but State::Pause is always the same duration
  484. if ( (state != State::Pause) && (m_queue.Count() >= HudPasstimeEventText::iQueueDepthPanic) )
  485. {
  486. duration /= 2.0f;
  487. }
  488. m_displayTimer.Start( duration );
  489. }
  490. //-----------------------------------------------------------------------------
  491. void CTFHudPasstimeEventText::SetAlpha( int ia )
  492. {
  493. if ( m_pTitleLabel )
  494. {
  495. m_pTitleLabel->SetAlpha( ia );
  496. m_pTitleLabel->SetVisible( ia != 0 );
  497. }
  498. if ( m_pDetailLabel )
  499. {
  500. m_pDetailLabel->SetAlpha( ia );
  501. m_pDetailLabel->SetVisible( ia != 0 );
  502. }
  503. if ( m_pBonusLabel )
  504. {
  505. m_pBonusLabel->SetAlpha( ia );
  506. m_pBonusLabel->SetVisible( ia != 0 );
  507. }
  508. }
  509. //-----------------------------------------------------------------------------
  510. void CTFHudPasstimeEventText::Tick()
  511. {
  512. if ( !m_bValid )
  513. return;
  514. switch( m_state )
  515. {
  516. case State::Idle:
  517. if ( !m_queue.IsEmpty() )
  518. {
  519. SetAlpha( 1 );
  520. auto msg = m_queue.RemoveAtHead();
  521. if ( m_pTitleLabel )
  522. {
  523. SetLabelText( m_pTitleLabel, msg.title );
  524. }
  525. if ( m_pDetailLabel )
  526. {
  527. SetLabelText( m_pDetailLabel, msg.detail );
  528. }
  529. if ( m_pBonusLabel )
  530. {
  531. SetLabelText( m_pBonusLabel, msg.bonus );
  532. }
  533. EnterState( State::In, HudPasstimeEventText::flInSec );
  534. }
  535. else
  536. {
  537. SetAlpha( 0 );
  538. }
  539. break;
  540. case State::In:
  541. if ( m_displayTimer.IsElapsed() )
  542. {
  543. EnterState( State::Show, HudPasstimeEventText::flShowSec );
  544. SetAlpha( 255 );
  545. }
  546. else
  547. {
  548. // animate alpha
  549. // note: GetElapsedTime()
  550. float flFrac = m_displayTimer.GetElapsedTime() / m_displayTimer.GetCountdownDuration();
  551. SetAlpha( (int) (flFrac * flFrac * 255.0f) );
  552. }
  553. break;
  554. case State::Show:
  555. if ( m_displayTimer.IsElapsed() )
  556. {
  557. EnterState( State::Out, HudPasstimeEventText::flOutSec );
  558. }
  559. break;
  560. case State::Out:
  561. if ( m_displayTimer.IsElapsed() )
  562. {
  563. EnterState( State::Pause, HudPasstimeEventText::flPauseSec );
  564. SetAlpha( 0 );
  565. }
  566. else
  567. {
  568. // animate alpha
  569. // note: GetRemainingTime()
  570. float flFrac = m_displayTimer.GetRemainingTime() / m_displayTimer.GetCountdownDuration();
  571. SetAlpha( (int) (flFrac * flFrac * 255.0f) );
  572. }
  573. break;
  574. case State::Pause:
  575. if ( m_displayTimer.IsElapsed() )
  576. {
  577. m_state = State::Idle;
  578. }
  579. break;
  580. }
  581. }
  582. //-----------------------------------------------------------------------------
  583. void CTFHudPasstimeEventText::Clear()
  584. {
  585. while( !m_queue.IsEmpty() )
  586. {
  587. m_queue.RemoveAtTail();
  588. }
  589. }
  590. //-----------------------------------------------------------------------------
  591. void CTFHudPasstimeEventText::SetControls( vgui::Label *pTitleLabel, vgui::Label *pDetailLabel, vgui::Label *pBonusLabel )
  592. {
  593. m_pTitleLabel = pTitleLabel;
  594. m_pDetailLabel = pDetailLabel;
  595. m_pBonusLabel = pBonusLabel;
  596. m_bValid = pTitleLabel && pDetailLabel && pBonusLabel;
  597. }
  598. //-----------------------------------------------------------------------------
  599. void CTFHudPasstimeEventText::SetPlayerName( C_TFPlayer *pPlayer, const char *pKey )
  600. {
  601. if ( pPlayer )
  602. {
  603. g_pVGuiLocalize->ConvertANSIToUnicode( pPlayer->GetPlayerName(), m_pwcsBuf, sizeof(m_pwcsBuf) );
  604. m_localizeKeys->SetWString( pKey, m_pwcsBuf );
  605. }
  606. else
  607. {
  608. m_localizeKeys->SetWString( pKey, L"" );
  609. }
  610. }
  611. //-----------------------------------------------------------------------------
  612. void CTFHudPasstimeEventText::SetTeam( C_TFPlayer *pPlayer )
  613. {
  614. if ( pPlayer )
  615. {
  616. C_TFTeam *pTeam = GetGlobalTFTeam( pPlayer->GetTeamNumber() );
  617. m_localizeKeys->SetWString( "team", pTeam ? pTeam->Get_Localized_Name() : L"" );
  618. }
  619. else
  620. {
  621. m_localizeKeys->SetWString( "team", L"" );
  622. }
  623. }
  624. //-----------------------------------------------------------------------------
  625. template< int TArraySize >
  626. void CTFHudPasstimeEventText::ConstructNewString( const char *pLocTag, wchar_t (&out)[TArraySize] )
  627. {
  628. // FIXME calling find is redundant
  629. if ( pLocTag && pLocTag[0] && g_pVGuiLocalize->Find( pLocTag ) )
  630. {
  631. g_pVGuiLocalize->ConstructString_safe( out, pLocTag, m_localizeKeys );
  632. }
  633. else
  634. {
  635. out[0] = (wchar_t)0;
  636. }
  637. }
  638. //-----------------------------------------------------------------------------
  639. void CTFHudPasstimeEventText::Enqueue( C_TFPlayer *pSource, C_TFPlayer *pSubject, const char *pTitle, const char *pDetail, const char *pBonus )
  640. {
  641. if ( !m_bValid || !pSubject )
  642. return;
  643. SetTeam( pSubject );
  644. SetPlayerName( pSubject, HudPasstimeEventText::pKeySubject );
  645. SetPlayerName( pSource, HudPasstimeEventText::pKeySource );
  646. auto *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  647. auto bShowBonus = (pSubject == pLocalPlayer)
  648. || (pLocalPlayer->IsObserver() && pLocalPlayer->GetObserverTarget() == pLocalPlayer);
  649. QueueElement e;
  650. ConstructNewString( pTitle, e.title );
  651. ConstructNewString( pDetail, e.detail );
  652. ConstructNewString( bShowBonus ? pBonus : nullptr, e.bonus );
  653. m_queue.Insert( e );
  654. }
  655. //-----------------------------------------------------------------------------
  656. void CTFHudPasstimeEventText::EnqueueGeneric( const char *pTitle, const char *pDetail, const char *pBonus )
  657. {
  658. QueueElement e;
  659. ConstructNewString( pTitle, e.title );
  660. ConstructNewString( pDetail, e.detail );
  661. ConstructNewString( pBonus, e.bonus );
  662. m_queue.Insert( e );
  663. }
  664. //-----------------------------------------------------------------------------
  665. void CTFHudPasstimeEventText::EnqueueSteal( C_TFPlayer *pVictim, C_TFPlayer *pStealer )
  666. {
  667. Enqueue( pVictim, pStealer, "#Msg_PasstimeEventStealTitle", "#Msg_PasstimeEventStealDetail", "#Msg_PasstimeEventStealBonus" );
  668. }
  669. //-----------------------------------------------------------------------------
  670. void CTFHudPasstimeEventText::EnqueuePass( C_TFPlayer *pThrower, C_TFPlayer *pCatcher )
  671. {
  672. Enqueue( pThrower, pCatcher, "#Msg_PasstimeEventPassTitle", "#Msg_PasstimeEventPassDetail", "#Msg_PasstimeEventPassBonus" );
  673. }
  674. //-----------------------------------------------------------------------------
  675. void CTFHudPasstimeEventText::EnqueueInterception( C_TFPlayer *pThrower, C_TFPlayer *pCatcher )
  676. {
  677. Enqueue( pThrower, pCatcher, "#Msg_PasstimeEventInterceptTitle", "#Msg_PasstimeEventInterceptDetail", "#Msg_PasstimeEventInterceptBonus" );
  678. }
  679. //-----------------------------------------------------------------------------
  680. void CTFHudPasstimeEventText::EnqueueScore( C_TFPlayer *pThrower, C_TFPlayer *pAssister )
  681. {
  682. if ( pAssister )
  683. Enqueue( pAssister, pThrower, "#Msg_PasstimeEventScoreTitle", "#Msg_PasstimeEventScoreDetail_Assist", "#Msg_PasstimeEventScoreBonus" );
  684. else
  685. Enqueue( pAssister, pThrower, "#Msg_PasstimeEventScoreTitle", "#Msg_PasstimeEventScoreDetail_NoAssist", "#Msg_PasstimeEventScoreBonus" );
  686. }
  687. //-----------------------------------------------------------------------------
  688. // CTFHudPasstimeBallStatus
  689. //-----------------------------------------------------------------------------
  690. //-----------------------------------------------------------------------------
  691. CTFHudPasstimeBallStatus::CTFHudPasstimeBallStatus( Panel *pParent )
  692. : CTFHudPasstimePanel( pParent, "HudPasstimeBallStatus" )
  693. {
  694. m_bInitialized = false;
  695. m_bReset = false;
  696. m_bGoalsFound = false;
  697. m_iXBlueProgress = -100.0f;
  698. m_iXRedProgress = -100.0f;
  699. m_iYBlueProgress = -100.0f;
  700. m_iYRedProgress = -100.0f;
  701. memset( m_pGoalIconsBlue, 0, sizeof( m_pGoalIconsBlue ) );
  702. memset( m_pGoalIconsRed, 0, sizeof( m_pGoalIconsRed ) );
  703. memset( m_pPlayerIcons, 0, sizeof( m_pPlayerIcons ) );
  704. m_pProgressBall = 0;
  705. m_pProgressBallCarrierName = 0;
  706. m_pProgressLevelBar = 0;
  707. m_pSelfPlayerIcon = 0;
  708. m_pEventText = new CTFHudPasstimeEventText();
  709. m_pBallPowerMeterFillContainer = nullptr;
  710. m_pBallPowerMeterFill = nullptr;
  711. m_pBallPowerMeterFrame = nullptr;
  712. }
  713. //-----------------------------------------------------------------------------
  714. CTFHudPasstimeBallStatus::~CTFHudPasstimeBallStatus()
  715. {
  716. }
  717. //-----------------------------------------------------------------------------
  718. void CTFHudPasstimeBallStatus::ApplySchemeSettings( IScheme *pScheme )
  719. {
  720. BaseClass::ApplySchemeSettings( pScheme );
  721. m_bInitialized = false;
  722. LoadControlSettings( "resource/UI/HudPasstimeBallStatus.res", NULL, NULL, NULL );
  723. m_pPowerCluster = FindControl<EditablePanel>( "BallPowerCluster" );
  724. m_pProgressBall = FindControl<ImagePanel>( "ProgressBallIcon" );
  725. m_pProgressBallCarrierName = FindControl<Label>( "ProgressBallCarrierName" );
  726. m_pProgressLevelBar = FindControl<Panel>( "ProgressLevelBar" );
  727. m_pSelfPlayerIcon = FindControl<ImagePanel>( "ProgressSelfPlayerIcon" );
  728. Panel *pBallPowerRoot = FindControl<Panel>( "BallPowerCluster" );
  729. if ( !pBallPowerRoot )
  730. {
  731. pBallPowerRoot = this;
  732. }
  733. m_pBallPowerMeterFillContainer = pBallPowerRoot->FindControl<Panel>( "BallPowerMeterFillContainer", true );
  734. m_pBallPowerMeterFill = pBallPowerRoot->FindControl<ImagePanel>( "BallPowerMeterFill", true );
  735. m_pBallPowerMeterFrame = pBallPowerRoot->FindControl<Panel>( "BallPowerMeterFrame", true );
  736. m_pBallPowerMeterFinalSection = pBallPowerRoot->FindControl<Panel>( "BallPowerMeterFinalSectionContainer", true );
  737. m_pEventText->SetControls( FindControl<Label>( "EventTitleLabel" ),
  738. FindControl<Label>( "EventDetailLabel" ),
  739. FindControl<Label>( "EventBonusLabel" ) );
  740. m_bInitialized = m_pProgressBall
  741. && m_pProgressBallCarrierName
  742. && m_pProgressLevelBar
  743. && m_pSelfPlayerIcon
  744. && m_pBallPowerMeterFillContainer
  745. && m_pBallPowerMeterFill
  746. && m_pBallPowerMeterFinalSection
  747. && m_pBallPowerMeterFrame;
  748. if ( !m_bInitialized )
  749. {
  750. // just bail if the res file is missing required controls
  751. // this prevents a lot of stupid null checks in the future
  752. return;
  753. }
  754. // set up some ballpower stuff
  755. m_iBallPowerMeterFillWidth = m_pBallPowerMeterFillContainer ? m_pBallPowerMeterFillContainer->GetWide() : 0;
  756. int iFinalSectionWidth = (int)(m_iBallPowerMeterFillWidth * 0.2f);
  757. if ( m_pBallPowerMeterFinalSection )
  758. {
  759. m_pBallPowerMeterFinalSection->SetWide( iFinalSectionWidth );
  760. m_pBallPowerMeterFinalSection->SetPos( m_pBallPowerMeterFinalSection->GetXPos() + (m_iBallPowerMeterFillWidth - iFinalSectionWidth), m_pBallPowerMeterFinalSection->GetYPos() );
  761. }
  762. m_iPrevBallPower = g_pPasstimeLogic
  763. ? g_pPasstimeLogic->GetBallPower()
  764. : 0;
  765. // find the left/right markers in the res files
  766. {
  767. auto *pRedEnd = FindChildByName( "RedProgressEnd" );
  768. auto *pBlueEnd = FindChildByName( "BlueProgressEnd" );
  769. if ( pRedEnd && pBlueEnd )
  770. {
  771. pRedEnd->GetPos( m_iXRedProgress, m_iYRedProgress );
  772. pBlueEnd->GetPos( m_iXBlueProgress, m_iYBlueProgress );
  773. }
  774. else
  775. {
  776. // no markers in the res file, just give it offscreen but valid coords
  777. m_iXBlueProgress = -100.0f;
  778. m_iXRedProgress = -100.0f;
  779. m_iYBlueProgress = -100.0f;
  780. m_iYRedProgress = -100.0f;
  781. }
  782. }
  783. // find all the goal icon image panels
  784. {
  785. char buf[16];
  786. for ( auto i = 0; i < NumGoalIcons; ++i )
  787. {
  788. V_sprintf_safe( buf, "GoalRed%i", i );
  789. m_pGoalIconsRed[i] = FindControl<vgui::ImagePanel>( buf );
  790. V_sprintf_safe( buf, "GoalBlue%i", i );
  791. m_pGoalIconsBlue[i] = FindControl<vgui::ImagePanel>( buf );
  792. }
  793. }
  794. ListenForGameEvent( PasstimeGameEvents::BallFree::s_eventName );
  795. ListenForGameEvent( PasstimeGameEvents::BallGet::s_eventName );
  796. ListenForGameEvent( PasstimeGameEvents::BallStolen::s_eventName );
  797. ListenForGameEvent( PasstimeGameEvents::PassCaught::s_eventName );
  798. ListenForGameEvent( PasstimeGameEvents::Score::s_eventName );
  799. Reset(); // this ensures players will try to guess game state for the hud if they join a game in progress
  800. vgui::ivgui()->AddTickSignal( GetVPanel() );
  801. // find all the player icon image panels
  802. {
  803. char controlname[32];
  804. for ( auto i = 0; i < MAX_PLAYERS; ++i )
  805. {
  806. V_sprintf_safe( controlname, "playericon%i", i ); // ugh
  807. m_pPlayerIcons[i] = FindControl<vgui::ImagePanel>( controlname );
  808. Assert( m_pPlayerIcons[i] );
  809. m_pPlayerIcons[i]->SetEnabled( true );
  810. m_pPlayerIcons[i]->SetShouldScaleImage( true );
  811. }
  812. }
  813. }
  814. //-----------------------------------------------------------------------------
  815. bool CTFHudPasstimeBallStatus::BShouldDraw() const
  816. {
  817. CBasePlayer *pPlayer = CBasePlayer::GetLocalPlayer();
  818. if ( !pPlayer || !pPlayer->IsAlive() || ( pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ) )
  819. {
  820. return false;
  821. }
  822. gamerules_roundstate_t roundstate = TFGameRules()->State_Get();
  823. return ((roundstate == GR_STATE_RND_RUNNING) || (roundstate == GR_STATE_STALEMATE))
  824. && !TFGameRules()->InSetup();
  825. }
  826. //-----------------------------------------------------------------------------
  827. // FIXME copypasta with tf_passtime_logic.cpp
  828. static float CalcProgressFrac( const Vector& vecOrigin )
  829. {
  830. Assert( g_pPasstimeLogic && (g_pPasstimeLogic->GetNumSections() > 0) );
  831. Vector arrTrackPoints[16];
  832. g_pPasstimeLogic->GetTrackPoints( arrTrackPoints );
  833. float flBestDist = FLT_MAX;
  834. float flBestLen = 0;
  835. float flTotalLen = 1; // don't set 0 so div by zero is impossible
  836. Vector vecThisPoint;
  837. Vector vecPointOnLine;
  838. Vector vecPrevPoint = arrTrackPoints[0];
  839. float flThisFrac = 0;
  840. float flThisLen = 0;
  841. float flThisDist = 0;
  842. for ( int i = 1; i < ARRAYSIZE(arrTrackPoints); ++i )
  843. {
  844. vecThisPoint = arrTrackPoints[i];
  845. if ( vecThisPoint.IsZero() )
  846. {
  847. break;
  848. }
  849. flThisLen = (vecThisPoint - vecPrevPoint).Length();
  850. flTotalLen += flThisLen;
  851. CalcClosestPointOnLineSegment( vecOrigin, vecPrevPoint, vecThisPoint, vecPointOnLine, &flThisFrac );
  852. flThisDist = (vecPointOnLine - vecOrigin).Length();
  853. if ( flThisDist < flBestDist )
  854. {
  855. flBestDist = flThisDist;
  856. flBestLen = flTotalLen - (flThisLen * (1.0f - flThisFrac));
  857. }
  858. vecPrevPoint = vecThisPoint;
  859. }
  860. return (float)(flBestLen / flTotalLen);
  861. }
  862. //-----------------------------------------------------------------------------
  863. // FIXME copypasta with tf_passtime_logic.cpp
  864. static float CalcBallProgressFrac()
  865. {
  866. Assert( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() && (g_pPasstimeLogic->GetNumSections() > 0) );
  867. CPasstimeBall *pBall = g_pPasstimeLogic->GetBall();
  868. CTFPlayer *pCarrier = pBall->GetCarrier();
  869. return CalcProgressFrac( pCarrier
  870. ? pCarrier->GetNetworkOrigin()
  871. : pBall->GetNetworkOrigin() );
  872. }
  873. //-----------------------------------------------------------------------------
  874. void CTFHudPasstimeBallStatus::UpdateGoalIcon( vgui::ImagePanel *pIcon, C_FuncPasstimeGoal *pGoal )
  875. {
  876. Assert( m_bInitialized );
  877. // TODO: animations on enable/disable?
  878. if ( !pIcon )
  879. {
  880. return;
  881. }
  882. if ( !pGoal )
  883. {
  884. pIcon->SetVisible( false );
  885. return;
  886. }
  887. pIcon->SetVisible( true );
  888. const auto iDisabledAlpha = 50;
  889. const auto iEnabledAlpha = 255;
  890. pIcon->SetAlpha( ( pGoal->BGoalTriggerDisabled() && ( pGoal->GetGoalType() != C_FuncPasstimeGoal::TYPE_TOWER ) )
  891. ? iDisabledAlpha
  892. : iEnabledAlpha );
  893. // TODO don't call SetImage(char*) every frame, wtf.
  894. pIcon->SetImage( GetProgressGoalImage( pGoal ) );
  895. const float flProgressFrac = CalcProgressFrac( pGoal->GetAbsOrigin() );
  896. const int iActualBarHalfHeight = m_pProgressLevelBar ? m_pProgressLevelBar->GetTall() / 6 : 0; // magic number because NPOT waste in image
  897. int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - ( pIcon->GetWide() / 2 );
  898. int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - iActualBarHalfHeight;
  899. pIcon->SetPos( iX, iY );
  900. }
  901. //-----------------------------------------------------------------------------
  902. void CTFHudPasstimeBallStatus::OnTickHidden()
  903. {
  904. m_bGoalsFound = false;
  905. if ( m_pProgressBall )
  906. {
  907. m_pProgressBall->SetVisible( false );
  908. }
  909. if ( m_pProgressBallCarrierName )
  910. {
  911. m_pProgressBallCarrierName->SetVisible( false );
  912. }
  913. if ( m_pSelfPlayerIcon )
  914. {
  915. m_pSelfPlayerIcon->SetVisible( false );
  916. }
  917. if ( m_pProgressLevelBar )
  918. {
  919. m_pProgressLevelBar->SetVisible( false );
  920. }
  921. if ( m_pPowerCluster )
  922. {
  923. m_pPowerCluster->SetVisible( false );
  924. }
  925. for ( int i = 0; i < MAX_PLAYERS; ++i )
  926. {
  927. m_pPlayerIcons[i]->SetVisible( false );
  928. }
  929. m_iPrevBallPower = g_pPasstimeLogic
  930. ? g_pPasstimeLogic->GetBallPower()
  931. : 0;
  932. HideGoalIcons();
  933. }
  934. //-----------------------------------------------------------------------------
  935. void CTFHudPasstimeBallStatus::OnTickVisible( C_TFPlayer *pLocalPlayer, C_PasstimeBall *pBall)
  936. {
  937. if ( m_pProgressLevelBar )
  938. {
  939. m_pProgressLevelBar->SetVisible( true );
  940. }
  941. //
  942. // Update ball icon
  943. //
  944. float flBallProgressFrac = CalcBallProgressFrac();
  945. int iX_Ball = m_pProgressBall ? Lerp( flBallProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - ( m_pProgressBall->GetWide() / 2 ) : 0;
  946. int iY_Ball = m_pProgressBall ? Lerp( flBallProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - ( m_pProgressBall->GetTall() / 2 ) : 0;
  947. if ( m_pProgressBall )
  948. {
  949. m_pProgressBall->SetVisible( true );
  950. m_pProgressBall->SetPos( iX_Ball, iY_Ball );
  951. }
  952. // todo setimage from event, not every frame
  953. // todo look in to letting entities manage their own hud elements
  954. CTFPlayer *pCarrier = pBall->GetCarrier();
  955. int iTeam = pCarrier
  956. ? pCarrier->GetTeamNumber()
  957. : pBall->GetTeamNumber();
  958. if ( m_pProgressBall )
  959. {
  960. m_pProgressBall->SetImage( GetProgressBallImageForTeam( iTeam ) );
  961. }
  962. //
  963. // Update ball power
  964. //
  965. if ( m_pPowerCluster )
  966. {
  967. m_pPowerCluster->SetVisible( true );
  968. }
  969. {
  970. // update the power bar
  971. int iCurPower = g_pPasstimeLogic->GetBallPower();
  972. int iThreshold = tf_passtime_powerball_threshold.GetInt();
  973. int iAlpha = (iCurPower > iThreshold)
  974. ? FLerp( 150, 255, (1 + sin(gpGlobals->curtime*5)) / 2.0f)
  975. : 222;
  976. if ( m_pBallPowerMeterFill )
  977. {
  978. m_pBallPowerMeterFill->SetDrawColor( GetTeamColor( iTeam, iAlpha ) );
  979. }
  980. float flPowerFrac = (iCurPower / 100.f);
  981. if ( m_pBallPowerMeterFillContainer )
  982. {
  983. m_pBallPowerMeterFillContainer->SetWide( m_iBallPowerMeterFillWidth * flPowerFrac );
  984. }
  985. // show power up/down notifications
  986. // this should really be done with a game event but this is a temp experimental feature for now
  987. bool bWasAboveThreshold = m_iPrevBallPower > iThreshold;
  988. bool bIsAboveThreshold = iCurPower > iThreshold;
  989. m_iPrevBallPower = iCurPower;
  990. if ( bWasAboveThreshold && !bIsAboveThreshold )
  991. {
  992. m_pEventText->Clear(); // push everything else out of the way
  993. m_pEventText->EnqueueGeneric( "#Msg_PasstimeEventPowerDownTitle", "#Msg_PasstimeEventPowerDownDetail", "#Msg_PasstimeEventPowerDownBonus" );
  994. }
  995. else if ( !bWasAboveThreshold && bIsAboveThreshold )
  996. {
  997. m_pEventText->Clear(); // push everything else out of the way
  998. m_pEventText->EnqueueGeneric( "#Msg_PasstimeEventPowerUpTitle", "#Msg_PasstimeEventPowerUpDetail", "#Msg_PasstimeEventPowerUpBonus" );
  999. }
  1000. }
  1001. //
  1002. // Update player icons
  1003. // TODO make less bad
  1004. //
  1005. C_TFPlayer *pSpecTarget = ToTFPlayer( pLocalPlayer->GetObserverTarget() );
  1006. if ( m_pSelfPlayerIcon )
  1007. {
  1008. m_pSelfPlayerIcon->SetVisible( false );
  1009. }
  1010. const int iActualBarHalfHeight = m_pProgressLevelBar ? m_pProgressLevelBar->GetTall() / 7 : 0; // magic number because NPOT waste in image
  1011. for ( int iEntIndex = 1; iEntIndex <= MAX_PLAYERS; iEntIndex++ )
  1012. {
  1013. vgui::ImagePanel *pIcon = m_pPlayerIcons[iEntIndex - 1];
  1014. if ( !g_TF_PR->IsConnected( iEntIndex ) )
  1015. {
  1016. pIcon->SetVisible( false );
  1017. continue;
  1018. }
  1019. int iEntTeam = g_TF_PR->GetTeam( iEntIndex );
  1020. if ( !g_TF_PR->IsAlive( iEntIndex ) // no dead people; not the same as IsDead
  1021. || ( ( iEntTeam != TF_TEAM_RED ) && ( iEntTeam != TF_TEAM_BLUE) ) ) // no spectators
  1022. {
  1023. pIcon->SetVisible( false );
  1024. continue;
  1025. }
  1026. // needed for origin
  1027. // dormant means "not available on this client right now"
  1028. C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iEntIndex ) );
  1029. if ( !pPlayer || pPlayer->IsDormant() )
  1030. {
  1031. pIcon->SetVisible( false );
  1032. continue;
  1033. }
  1034. // don't show cloaked spies
  1035. bool bSpy = g_TF_PR->GetPlayerClass( iEntIndex ) == TF_CLASS_SPY;
  1036. if ( bSpy )
  1037. {
  1038. if ( pPlayer->m_Shared.IsFullyInvisible() )
  1039. {
  1040. pIcon->SetVisible( false );
  1041. continue;
  1042. }
  1043. int iDisguiseTeam = pPlayer->m_Shared.GetDisguiseTeam();
  1044. if ( iDisguiseTeam == TF_TEAM_RED || iDisguiseTeam == TF_TEAM_BLUE )
  1045. {
  1046. iEntTeam = iDisguiseTeam;
  1047. }
  1048. }
  1049. const float flProgressFrac = CalcProgressFrac( pPlayer->GetNetworkOrigin() );
  1050. // Player pips
  1051. {
  1052. pIcon->SetVisible( true );
  1053. if ( iEntTeam == TF_TEAM_RED )
  1054. {
  1055. int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - (pIcon->GetWide() / 2);
  1056. int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - pIcon->GetTall() - iActualBarHalfHeight;
  1057. pIcon->SetPos( iX, iY );
  1058. pIcon->SetImage( "../passtime/hud/passtime_ballcontrol_team_red" );
  1059. }
  1060. else
  1061. {
  1062. int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - (pIcon->GetWide() / 2);
  1063. int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) + iActualBarHalfHeight;
  1064. pIcon->SetPos( iX, iY );
  1065. pIcon->SetImage( "../passtime/hud/passtime_ballcontrol_team_blue" );
  1066. }
  1067. }
  1068. // Local player image
  1069. if ( g_TF_PR->IsLocalPlayer( iEntIndex ) || ( pLocalPlayer->IsObserver() && ( pPlayer == pSpecTarget ) ) )
  1070. {
  1071. if ( m_pSelfPlayerIcon )
  1072. {
  1073. // TODO don't call SetImage(char*) every frame, wtf.
  1074. if ( pLocalPlayer->IsObserver() && pSpecTarget )
  1075. {
  1076. m_pSelfPlayerIcon->SetImage( GetPlayerProgressPortrait( pSpecTarget ) );
  1077. }
  1078. else
  1079. {
  1080. m_pSelfPlayerIcon->SetImage( GetPlayerProgressPortrait( pLocalPlayer ) );
  1081. }
  1082. int iX = Lerp( flProgressFrac, m_iXBlueProgress, m_iXRedProgress ) - (m_pSelfPlayerIcon->GetWide() / 2);
  1083. int iY = Lerp( flProgressFrac, m_iYBlueProgress, m_iYRedProgress ) - m_pSelfPlayerIcon->GetTall() - iActualBarHalfHeight;
  1084. m_pSelfPlayerIcon->SetVisible( true );
  1085. m_pSelfPlayerIcon->SetPos( iX, iY );
  1086. }
  1087. }
  1088. }
  1089. //
  1090. // Refresh goal icons if necessary
  1091. //
  1092. bool bReadyToFindGoals = (g_pPasstimeLogic->GetNumSections() > 0);
  1093. if ( !m_bGoalsFound && bReadyToFindGoals )
  1094. {
  1095. // release any existing handles to goals
  1096. for ( auto i = 0; i < NumGoalIcons; ++i )
  1097. {
  1098. m_hGoalsBlue[i].Term();
  1099. m_hGoalsRed[i].Term();
  1100. }
  1101. // Update the goal list and pair hud icons with actual goals
  1102. const int iMaxSortedGoals = 8; // arbitrary
  1103. const auto &goals = C_FuncPasstimeGoal::GetAutoList();
  1104. const int iNumGoals = goals.Count();
  1105. if ( ( iNumGoals > 1 ) && ( iNumGoals < iMaxSortedGoals ) )
  1106. {
  1107. // sort goals by position in world, from blue to red (blue progress is always 0, red is always 1))
  1108. C_FuncPasstimeGoal* sortedgoals[iMaxSortedGoals];
  1109. std::copy( goals.begin(), goals.end(), sortedgoals );
  1110. std::sort( sortedgoals, sortedgoals + iNumGoals, []( C_FuncPasstimeGoal* a, C_FuncPasstimeGoal* b )
  1111. {
  1112. // this is wasteful but it should only happen one frame per round
  1113. // The order of the icons in the hud res file determines which direction to sort these
  1114. // so that the iteration below visits the goals in the same order.
  1115. return CalcProgressFrac( a->GetAbsOrigin() ) < CalcProgressFrac( b->GetAbsOrigin() );
  1116. });
  1117. // Pair goals and icons so the tick function can update the icon state based on the actual goal state
  1118. // This should work for any number of goals and icons in any order.
  1119. // If there are more goals than icons, just ignore them, but make sure the hud shows at least
  1120. // the goals farthest from the middle of the map in a symmetrical way, so iterate from both ends
  1121. // of the array of sorted goals.
  1122. // pair first N blue goals in left-right order
  1123. // first icon in array is leftmost blue goal on hud is first goal in sorted array
  1124. int iRedIcon = 0;
  1125. int iBlueIcon = 0;
  1126. for ( int iGoal = 0; iGoal < iNumGoals; ++iGoal )
  1127. {
  1128. // NOTE: goal's teams are backwards: the blue-colored goals on the blue side of the map are
  1129. // on team red because that's where red scores. doh.
  1130. auto *pGoal = sortedgoals[iGoal];
  1131. if ( pGoal && ( iBlueIcon < NumGoalIcons ) && ( pGoal->GetTeamNumber() == TF_TEAM_RED ) )
  1132. {
  1133. m_hGoalsBlue[iBlueIcon++] = pGoal;
  1134. }
  1135. pGoal = sortedgoals[iNumGoals - iGoal - 1];
  1136. if ( pGoal && ( iRedIcon < NumGoalIcons ) && ( pGoal->GetTeamNumber() == TF_TEAM_BLUE ) )
  1137. {
  1138. m_hGoalsRed[iRedIcon++] = pGoal;
  1139. }
  1140. }
  1141. m_bGoalsFound = true;
  1142. }
  1143. }
  1144. //
  1145. // Update goal icons
  1146. //
  1147. if ( m_bGoalsFound )
  1148. {
  1149. for ( auto i = 0; i < NumGoalIcons; ++i )
  1150. {
  1151. UpdateGoalIcon( m_pGoalIconsBlue[i], m_hGoalsBlue[i].Get() );
  1152. UpdateGoalIcon( m_pGoalIconsRed[i], m_hGoalsRed[i].Get() );
  1153. }
  1154. }
  1155. else
  1156. {
  1157. HideGoalIcons();
  1158. }
  1159. }
  1160. //-----------------------------------------------------------------------------
  1161. void CTFHudPasstimeBallStatus::HideGoalIcons()
  1162. {
  1163. for ( int i = 0; i < NumGoalIcons; ++i )
  1164. {
  1165. auto *pIcon = m_pGoalIconsBlue[i];
  1166. if ( pIcon )
  1167. pIcon->SetVisible( false );
  1168. pIcon = m_pGoalIconsRed[i];
  1169. if ( pIcon )
  1170. pIcon->SetVisible( false );
  1171. }
  1172. }
  1173. //-----------------------------------------------------------------------------
  1174. void CTFHudPasstimeBallStatus::OnTick()
  1175. {
  1176. if ( !g_pPasstimeLogic || !m_bInitialized )
  1177. {
  1178. // happens randomly during map load
  1179. m_pEventText->Clear();
  1180. return;
  1181. }
  1182. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  1183. if ( !pLocalPlayer )
  1184. {
  1185. // can happen during exit
  1186. return;
  1187. }
  1188. // Tick the event text regardless of the state of the rest of the hud so
  1189. // that messages can continue to display even after the round ends or whatever.
  1190. m_pEventText->Tick();
  1191. // I wish there were a reliable way to do this in an event-driven way instead of every frame
  1192. C_PasstimeBall *pBall = g_pPasstimeLogic->GetBall();
  1193. if ( !BShouldDraw() || !pBall )
  1194. {
  1195. OnTickHidden();
  1196. // this is the easiest way I could find to refresh the goals when switching maps
  1197. // todo this is dumb
  1198. m_bGoalsFound = false;
  1199. }
  1200. else
  1201. {
  1202. OnTickVisible( pLocalPlayer, pBall );
  1203. }
  1204. //
  1205. // Try to figure out game state that's usually managed by events if hud was reloaded mid-game
  1206. // This happens at the end to ensure that the hud is fully initialized before trying to reset
  1207. // on the first frame a player joins a game in progress
  1208. //
  1209. if ( m_bReset )
  1210. {
  1211. m_pEventText->Clear();
  1212. // I don't *think* it's possible that this could accidentally end up
  1213. // running every frame.
  1214. // Order of operations here is important, see functions for details
  1215. // todo make less bad
  1216. if ( TryForceBallGet() || TryForceBallFree() )
  1217. {
  1218. m_bReset = false;
  1219. }
  1220. };
  1221. }
  1222. //-----------------------------------------------------------------------------
  1223. bool CTFHudPasstimeBallStatus::TryForceBallGet()
  1224. {
  1225. Assert( m_bInitialized );
  1226. // The HUD was reset during play, or possibly due to joining a game inprogroess,
  1227. // so try to get into a BallGet state if possible
  1228. if ( !g_TF_PR )
  1229. {
  1230. // is this even possible?
  1231. return false;
  1232. }
  1233. for ( int iPlayer = 1 ; iPlayer <= MAX_PLAYERS; iPlayer++ )
  1234. {
  1235. if ( !g_TF_PR->IsConnected( iPlayer ) )
  1236. {
  1237. continue;
  1238. }
  1239. C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayer ) );
  1240. if ( !pPlayer || !pPlayer->m_Shared.HasPasstimeBall() )
  1241. {
  1242. continue;
  1243. }
  1244. if ( g_TF_PR->IsLocalPlayer( iPlayer ) )
  1245. {
  1246. OnBallGetSelf( iPlayer );
  1247. }
  1248. else
  1249. {
  1250. OnBallGetOther( iPlayer );
  1251. }
  1252. return true;
  1253. }
  1254. return false;
  1255. }
  1256. //-----------------------------------------------------------------------------
  1257. bool CTFHudPasstimeBallStatus::TryForceBallFree()
  1258. {
  1259. Assert( m_bInitialized );
  1260. // The HUD was reset during play, or possibly due to joining a game inprogroess,
  1261. // so try to get into a BallGet state if possible
  1262. if ( !g_pPasstimeLogic )
  1263. {
  1264. return false;
  1265. }
  1266. C_PasstimeBall *pBall = g_pPasstimeLogic->GetBall();
  1267. if ( !m_bInitialized || !pBall )
  1268. {
  1269. return false;
  1270. }
  1271. if ( m_pProgressBallCarrierName )
  1272. {
  1273. m_pProgressBallCarrierName->SetVisible( false );
  1274. }
  1275. return true;
  1276. }
  1277. //-----------------------------------------------------------------------------
  1278. void CTFHudPasstimeBallStatus::OnBallGet( int getterIndex )
  1279. {
  1280. Assert( m_bInitialized && g_PR );
  1281. if ( g_PR->IsLocalPlayer( getterIndex ) )
  1282. {
  1283. OnBallGetSelf( getterIndex );
  1284. }
  1285. else
  1286. {
  1287. OnBallGetOther( getterIndex );
  1288. }
  1289. }
  1290. //-----------------------------------------------------------------------------
  1291. void CTFHudPasstimeBallStatus::FireGameEvent( IGameEvent *pEvent )
  1292. {
  1293. if ( !m_bInitialized || !g_TF_PR || !g_PR )
  1294. {
  1295. return;
  1296. }
  1297. const char *pszEventName = pEvent->GetName();
  1298. if ( FStrEq( pszEventName, PasstimeGameEvents::BallFree::s_eventName ) )
  1299. {
  1300. PasstimeGameEvents::BallFree ev( pEvent );
  1301. C_TFPlayer *pOwner = ToTFPlayer( UTIL_PlayerByIndex( ev.ownerIndex ) );
  1302. C_TFPlayer *pAttacker = ToTFPlayer( UTIL_PlayerByIndex( ev.attackerIndex ) );
  1303. const bool wasMyBall = pOwner == C_TFPlayer::GetLocalPlayer();
  1304. if ( wasMyBall )
  1305. {
  1306. OnBallFreeSelf( pOwner, pAttacker );
  1307. }
  1308. else
  1309. {
  1310. OnBallFreeOther( pOwner, pAttacker );
  1311. }
  1312. }
  1313. else if ( FStrEq( pszEventName, PasstimeGameEvents::BallGet::s_eventName ) )
  1314. {
  1315. OnBallGet( PasstimeGameEvents::BallGet( pEvent ).ownerIndex );
  1316. }
  1317. else if ( FStrEq( pszEventName, PasstimeGameEvents::BallStolen::s_eventName ) )
  1318. {
  1319. PasstimeGameEvents::BallStolen ballStolenEvent( pEvent );
  1320. OnBallGet( ballStolenEvent.attackerIndex );
  1321. auto *pAttacker = ToTFPlayer( UTIL_PlayerByIndex(ballStolenEvent.attackerIndex) );
  1322. auto *pVictim = ToTFPlayer( UTIL_PlayerByIndex(ballStolenEvent.victimIndex) );
  1323. m_pEventText->EnqueueSteal( pVictim, pAttacker );
  1324. }
  1325. else if ( FStrEq( pszEventName, PasstimeGameEvents::PassCaught::s_eventName ) )
  1326. {
  1327. PasstimeGameEvents::PassCaught passCaughtEvent( pEvent );
  1328. OnBallGet( passCaughtEvent.catcherIndex );
  1329. auto *pCatcher = ToTFPlayer( UTIL_PlayerByIndex( passCaughtEvent.catcherIndex ) );
  1330. auto *pThrower = ToTFPlayer( UTIL_PlayerByIndex( passCaughtEvent.passerIndex ) );
  1331. if ( pCatcher && pThrower )
  1332. {
  1333. if ( pCatcher->GetTeamNumber() == pThrower->GetTeamNumber() )
  1334. {
  1335. m_pEventText->EnqueuePass( pThrower, pCatcher );
  1336. }
  1337. else
  1338. {
  1339. m_pEventText->EnqueueInterception( pThrower, pCatcher );
  1340. }
  1341. }
  1342. }
  1343. else if ( FStrEq( pszEventName, PasstimeGameEvents::Score::s_eventName ) )
  1344. {
  1345. OnBallScore();
  1346. PasstimeGameEvents::Score scoreEvent( pEvent );
  1347. auto *pScorer = ToTFPlayer( UTIL_PlayerByIndex( scoreEvent.scorerIndex ) );
  1348. auto *pAssister = ToTFPlayer( UTIL_PlayerByIndex( scoreEvent.assisterIndex ) );
  1349. m_pEventText->EnqueueScore( pScorer, pAssister );
  1350. }
  1351. }
  1352. //-----------------------------------------------------------------------------
  1353. void CTFHudPasstimeBallStatus::Reset()
  1354. {
  1355. m_bReset = true;
  1356. // delay the actual reset for a bit because Reset is called before
  1357. // the game entities are settled into their new state (e.g. the ball
  1358. // is about to be deleted).
  1359. }
  1360. //-----------------------------------------------------------------------------
  1361. void CTFHudPasstimeBallStatus::OnBallFreeOther( C_TFPlayer *pOwner,
  1362. C_TFPlayer *pAttacker )
  1363. {
  1364. Assert( m_bInitialized );
  1365. if ( m_pProgressBallCarrierName )
  1366. {
  1367. m_pProgressBallCarrierName->SetVisible( false );
  1368. }
  1369. }
  1370. //-----------------------------------------------------------------------------
  1371. void CTFHudPasstimeBallStatus::OnBallFreeSelf( C_TFPlayer *pOwner,
  1372. C_TFPlayer *pAttacker )
  1373. {
  1374. Assert( m_bInitialized );
  1375. if ( m_pProgressBallCarrierName )
  1376. {
  1377. m_pProgressBallCarrierName->SetVisible( false );
  1378. }
  1379. }
  1380. //-----------------------------------------------------------------------------
  1381. void CTFHudPasstimeBallStatus::OnBallGetOther( int iPlayer )
  1382. {
  1383. Assert( m_bInitialized );
  1384. Assert( g_PR );
  1385. wchar_t wszFinalText[128];
  1386. wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH];
  1387. wchar_t *pwszFormatString = g_pVGuiLocalize->Find( "#TF_Passtime_CarrierName" );
  1388. if ( !pwszFormatString )
  1389. {
  1390. pwszFormatString = L"%s1";
  1391. }
  1392. g_pVGuiLocalize->ConvertANSIToUnicode( ( iPlayer > 0 ) ? g_PR->GetPlayerName( iPlayer ) : "", wszPlayerName, sizeof( wszPlayerName ) );
  1393. g_pVGuiLocalize->ConstructString_safe( wszFinalText, pwszFormatString, 1, wszPlayerName );
  1394. if ( m_pProgressBallCarrierName )
  1395. {
  1396. m_pProgressBallCarrierName->SetText( wszFinalText );
  1397. m_pProgressBallCarrierName->SetVisible( true );
  1398. }
  1399. }
  1400. //-----------------------------------------------------------------------------
  1401. void CTFHudPasstimeBallStatus::OnBallGetSelf( int iPlayer )
  1402. {
  1403. Assert( m_bInitialized );
  1404. Assert( g_PR );
  1405. wchar_t wszFinalText[128];
  1406. wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH];
  1407. wchar_t *pwszFormatString = g_pVGuiLocalize->Find( "#TF_Passtime_CarrierName" );
  1408. if ( !pwszFormatString )
  1409. {
  1410. pwszFormatString = L"%s1";
  1411. }
  1412. g_pVGuiLocalize->ConvertANSIToUnicode( ( iPlayer > 0 ) ? g_PR->GetPlayerName( iPlayer ) : "", wszPlayerName, sizeof( wszPlayerName ) );
  1413. g_pVGuiLocalize->ConstructString_safe( wszFinalText, pwszFormatString, 1, wszPlayerName );
  1414. if ( m_pProgressBallCarrierName )
  1415. {
  1416. m_pProgressBallCarrierName->SetText( wszFinalText );
  1417. m_pProgressBallCarrierName->SetVisible( true );
  1418. }
  1419. }
  1420. //-----------------------------------------------------------------------------
  1421. void CTFHudPasstimeBallStatus::OnBallScore()
  1422. {
  1423. Assert( m_bInitialized );
  1424. if ( m_pProgressBallCarrierName )
  1425. {
  1426. m_pProgressBallCarrierName->SetVisible( false );
  1427. }
  1428. }
  1429. //-----------------------------------------------------------------------------
  1430. // CTFHudPasstime
  1431. //-----------------------------------------------------------------------------
  1432. //-----------------------------------------------------------------------------
  1433. CTFHudPasstime::CTFHudPasstime( Panel *pParent )
  1434. : CTFHudPasstimePanel( pParent, "HudPasstime" )
  1435. , m_pBallStatus( new CTFHudPasstimeBallStatus( this ) )
  1436. , m_pTeamScore( new CTFHudTeamScore( this ) )
  1437. , m_pBallOffscreenArrow( new CTFHudPasstimeBallOffscreenArrow( this ) )
  1438. , m_pPassNotify( new CTFHudPasstimePassNotify( this ) )
  1439. {
  1440. for ( int i = 0; i < MAX_PLAYERS; ++i )
  1441. {
  1442. m_pPlayerArrows[i] = new CTFHudPasstimePlayerOffscreenArrow( this, i );
  1443. }
  1444. }
  1445. //-----------------------------------------------------------------------------
  1446. CTFHudPasstime::~CTFHudPasstime()
  1447. {
  1448. // this is only called when the game quits, so don't bother deleting members
  1449. }
  1450. //-----------------------------------------------------------------------------
  1451. void CTFHudPasstime::ApplySchemeSettings( IScheme *pScheme )
  1452. {
  1453. BaseClass::ApplySchemeSettings( pScheme );
  1454. LoadControlSettings( "resource/UI/HudPasstime.res", NULL, NULL, NULL );
  1455. vgui::ivgui()->AddTickSignal( GetVPanel() );
  1456. OnTick();
  1457. }
  1458. //-----------------------------------------------------------------------------
  1459. void CTFHudPasstime::Reset()
  1460. {
  1461. if ( m_pBallStatus )
  1462. {
  1463. m_pBallStatus->Reset();
  1464. }
  1465. }
  1466. //-----------------------------------------------------------------------------
  1467. void CTFHudPasstime::OnTick()
  1468. {
  1469. if ( m_pTeamScore )
  1470. {
  1471. m_pTeamScore->SetVisible( IsVisible() );
  1472. }
  1473. }