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.

1279 lines
37 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "hud.h"
  9. #include "c_team.h"
  10. #include "tf_playerpanel.h"
  11. #include "tf_spectatorgui.h"
  12. #include "tf_shareddefs.h"
  13. #include "tf_gamerules.h"
  14. #include "tf_hud_objectivestatus.h"
  15. #include "tf_hud_statpanel.h"
  16. #include "iclientmode.h"
  17. #include "c_playerresource.h"
  18. #include "tf_hud_building_status.h"
  19. #include "tf_hud_tournament.h"
  20. #include "tf_hud_winpanel.h"
  21. #include "tf_tips.h"
  22. #include "tf_mapinfomenu.h"
  23. #include "econ_wearable.h"
  24. #include "c_tf_playerresource.h"
  25. #include "playerspawncache.h"
  26. #include "econ_notifications.h"
  27. #include "tf_hud_item_progress_tracker.h"
  28. #include "tf_hud_target_id.h"
  29. #include "c_baseobject.h"
  30. #include "inputsystem/iinputsystem.h"
  31. #if defined( REPLAY_ENABLED )
  32. #include "replay/replay.h"
  33. #include "replay/ireplaysystem.h"
  34. #include "replay/ireplaymanager.h"
  35. #endif // REPLAY_ENABLED
  36. #include <vgui/ILocalize.h>
  37. #include <vgui/ISurface.h>
  38. #include "vgui_avatarimage.h"
  39. using namespace vgui;
  40. extern ConVar _cl_classmenuopen;
  41. extern ConVar tf_max_health_boost;
  42. extern const char *g_pszItemClassImages[];
  43. extern int g_ClassDefinesRemap[];
  44. extern ConVar tf_mvm_buybacks_method;
  45. const char *GetMapDisplayName( const char *mapName );
  46. static const wchar_t* GetSCGlyph( const char* action )
  47. {
  48. auto origin = g_pInputSystem->GetSteamControllerActionOrigin( action, GAME_ACTION_SET_SPECTATOR );
  49. return g_pInputSystem->GetSteamControllerFontCharacterForActionOrigin( origin );
  50. }
  51. //-----------------------------------------------------------------------------
  52. // Purpose:
  53. //-----------------------------------------------------------------------------
  54. CTFSpectatorGUI *GetTFSpectatorGUI()
  55. {
  56. extern CSpectatorGUI *g_pSpectatorGUI;
  57. return dynamic_cast< CTFSpectatorGUI * >( g_pSpectatorGUI );
  58. }
  59. //-----------------------------------------------------------------------------
  60. // Purpose:
  61. //-----------------------------------------------------------------------------
  62. ConVar cl_spec_carrieditems( "cl_spec_carrieditems", "1", FCVAR_ARCHIVE, "Show non-standard items being carried by player you're spectating." );
  63. void HUDTournamentSpecChangedCallBack( IConVar *var, const char *pOldString, float flOldValue )
  64. {
  65. CTFSpectatorGUI *pPanel = (CTFSpectatorGUI*)gViewPortInterface->FindPanelByName( PANEL_SPECGUI );
  66. if ( pPanel )
  67. {
  68. pPanel->InvalidateLayout( true, true );
  69. }
  70. }
  71. ConVar cl_use_tournament_specgui( "cl_use_tournament_specgui", "0", FCVAR_ARCHIVE, "When in tournament mode, use the advanced tournament spectator UI.", HUDTournamentSpecChangedCallBack );
  72. //-----------------------------------------------------------------------------
  73. // Purpose: Constructor
  74. //-----------------------------------------------------------------------------
  75. CTFSpectatorGUI::CTFSpectatorGUI(IViewPort *pViewPort) : CSpectatorGUI(pViewPort)
  76. {
  77. m_flNextTipChangeTime = 0;
  78. m_iTipClass = TF_CLASS_UNDEFINED;
  79. m_nEngBuilds_xpos = m_nEngBuilds_ypos = 0;
  80. m_nSpyBuilds_xpos = m_nSpyBuilds_ypos = 0;
  81. m_nMannVsMachineStatus_xpos = m_nMannVsMachineStatus_ypos = 0;
  82. m_pBuyBackLabel = new CExLabel( this, "BuyBackLabel", "" );
  83. m_pReinforcementsLabel = new Label( this, "ReinforcementsLabel", "" );
  84. m_pClassOrTeamLabel = new Label( this, "ClassOrTeamLabel", "" );
  85. // m_pSwitchCamModeKeyLabel = new Label( this, "SwitchCamModeKeyLabel", "" );
  86. m_pSwitchCamModeKeyLabel = nullptr;
  87. m_pClassOrTeamKeyLabel = nullptr;
  88. m_pCycleTargetFwdKeyLabel = new Label( this, "CycleTargetFwdKeyLabel", "" );
  89. m_pCycleTargetRevKeyLabel = new Label( this, "CycleTargetRevKeyLabel", "" );
  90. m_pMapLabel = new Label( this, "MapLabel", "" );
  91. m_pItemPanel = new CItemModelPanel( this, "itempanel" );
  92. m_pStudentHealth = new CTFSpectatorGUIHealth( this, "StudentGUIHealth" );
  93. m_pAvatar = NULL;
  94. m_flNextItemPanelUpdate = 0;
  95. m_flNextPlayerPanelUpdate = 0;
  96. m_iPrevItemShown = 0;
  97. m_iFirstItemShown = 0;
  98. m_bShownItems = false;
  99. m_hPrevItemPlayer = NULL;
  100. m_pPlayerPanelKVs = NULL;
  101. m_bReapplyPlayerPanelKVs = false;
  102. m_bCoaching = false;
  103. ListenForGameEvent( "spec_target_updated" );
  104. ListenForGameEvent( "player_death" );
  105. }
  106. //-----------------------------------------------------------------------------
  107. // Purpose:
  108. //-----------------------------------------------------------------------------
  109. CTFSpectatorGUI::~CTFSpectatorGUI()
  110. {
  111. if ( m_pPlayerPanelKVs )
  112. {
  113. m_pPlayerPanelKVs->deleteThis();
  114. m_pPlayerPanelKVs = NULL;
  115. }
  116. }
  117. //-----------------------------------------------------------------------------
  118. // Purpose:
  119. //-----------------------------------------------------------------------------
  120. void CTFSpectatorGUI::Reset( void )
  121. {
  122. BaseClass::Reset();
  123. for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
  124. {
  125. m_PlayerPanels[i]->Reset();
  126. }
  127. m_pStudentHealth->Reset();
  128. }
  129. //-----------------------------------------------------------------------------
  130. // Purpose:
  131. //-----------------------------------------------------------------------------
  132. int CTFSpectatorGUI::GetTopBarHeight()
  133. {
  134. int iPlayerPanelHeight = 0;
  135. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  136. {
  137. if ( GetLocalPlayerTeam() == TF_TEAM_PVE_DEFENDERS )
  138. {
  139. if ( m_PlayerPanels.Count() > 0 )
  140. {
  141. iPlayerPanelHeight = m_PlayerPanels[0]->GetTall();
  142. }
  143. }
  144. }
  145. return m_pTopBar->GetTall() + iPlayerPanelHeight;
  146. }
  147. //-----------------------------------------------------------------------------
  148. // Purpose: makes the GUI fill the screen
  149. //-----------------------------------------------------------------------------
  150. void CTFSpectatorGUI::PerformLayout( void )
  151. {
  152. BaseClass::PerformLayout();
  153. if ( m_bReapplyPlayerPanelKVs )
  154. {
  155. m_bReapplyPlayerPanelKVs = false;
  156. if ( m_pPlayerPanelKVs )
  157. {
  158. for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
  159. {
  160. m_PlayerPanels[i]->ApplySettings( m_pPlayerPanelKVs );
  161. m_PlayerPanels[i]->InvalidateLayout( false, true );
  162. }
  163. }
  164. }
  165. if ( m_pStudentHealth )
  166. {
  167. Panel* pHealthPosPanel = FindChildByName( "HealthPositioning" );
  168. if ( pHealthPosPanel )
  169. {
  170. int xPos, yPos, iWide, iTall;
  171. pHealthPosPanel->GetBounds( xPos, yPos, iWide, iTall );
  172. m_pStudentHealth->SetBounds( xPos, yPos, iWide, iTall );
  173. m_pStudentHealth->SetZPos( pHealthPosPanel->GetZPos() );
  174. }
  175. }
  176. UpdatePlayerPanels();
  177. }
  178. //-----------------------------------------------------------------------------
  179. // Purpose:
  180. //-----------------------------------------------------------------------------
  181. void CTFSpectatorGUI::ApplySchemeSettings( vgui::IScheme *pScheme )
  182. {
  183. bool bVisible = IsVisible();
  184. BaseClass::ApplySchemeSettings( pScheme );
  185. m_bReapplyPlayerPanelKVs = true;
  186. m_bPrevTournamentMode = InTournamentGUI();
  187. m_pSwitchCamModeKeyLabel = dynamic_cast< Label* >( FindChildByName( "SwitchCamModeKeyLabel" ) );
  188. m_pAvatar = dynamic_cast<CAvatarImagePanel *>( FindChildByName("AvatarImage") );
  189. if ( ::input->IsSteamControllerActive() )
  190. {
  191. m_pClassOrTeamKeyLabel = dynamic_cast< CExLabel* >( FindChildByName( "ClassOrTeamKeyLabel" ) );
  192. }
  193. else
  194. {
  195. m_pClassOrTeamKeyLabel = nullptr;
  196. }
  197. if ( m_bCoaching )
  198. {
  199. if ( m_pTopBar )
  200. {
  201. m_pTopBar->SetBgColor( Color( 255, 255, 255, 0 ) );
  202. }
  203. if ( m_pBottomBarBlank )
  204. {
  205. m_pBottomBarBlank->SetVisible( false );
  206. }
  207. }
  208. if ( m_bCoaching )
  209. {
  210. if ( m_pClassOrTeamLabel && m_pClassOrTeamLabel->IsVisible() )
  211. {
  212. m_pClassOrTeamLabel->SetVisible( false );
  213. }
  214. if ( m_pClassOrTeamKeyLabel && m_pClassOrTeamKeyLabel->IsVisible() )
  215. {
  216. m_pClassOrTeamKeyLabel->SetVisible( false );
  217. }
  218. }
  219. // Stay the same visibility as before the scheme reload.
  220. SetVisible( bVisible );
  221. }
  222. //-----------------------------------------------------------------------------
  223. // Purpose:
  224. //-----------------------------------------------------------------------------
  225. void CTFSpectatorGUI::ApplySettings( KeyValues *inResourceData )
  226. {
  227. BaseClass::ApplySettings( inResourceData );
  228. KeyValues *pItemKV = inResourceData->FindKey( "playerpanels_kv" );
  229. if ( pItemKV )
  230. {
  231. if ( m_pPlayerPanelKVs )
  232. {
  233. m_pPlayerPanelKVs->deleteThis();
  234. }
  235. m_pPlayerPanelKVs = new KeyValues("playerpanels_kv");
  236. pItemKV->CopySubkeys( m_pPlayerPanelKVs );
  237. }
  238. }
  239. //-----------------------------------------------------------------------------
  240. // Purpose:
  241. //-----------------------------------------------------------------------------
  242. bool CTFSpectatorGUI::NeedsUpdate( void )
  243. {
  244. if ( !C_BasePlayer::GetLocalPlayer() )
  245. return false;
  246. if( IsVisible() )
  247. return true;
  248. return BaseClass::NeedsUpdate();
  249. }
  250. //-----------------------------------------------------------------------------
  251. // Purpose:
  252. //-----------------------------------------------------------------------------
  253. void CTFSpectatorGUI::Update()
  254. {
  255. BaseClass::Update();
  256. UpdateReinforcements();
  257. UpdateKeyLabels();
  258. if ( m_flNextItemPanelUpdate < gpGlobals->curtime )
  259. {
  260. UpdateItemPanel();
  261. }
  262. // If we need to flip tournament mode, do it now
  263. if ( m_bPrevTournamentMode != InTournamentGUI() )
  264. {
  265. InvalidateLayout( false, true );
  266. }
  267. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  268. if ( pLocalPlayer && m_bCoaching != pLocalPlayer->m_bIsCoaching )
  269. {
  270. m_bCoaching = pLocalPlayer->m_bIsCoaching;
  271. InvalidateLayout( false, true );
  272. }
  273. if ( pLocalPlayer && pLocalPlayer->m_hStudent && m_bCoaching )
  274. {
  275. Vector vecTarget = pLocalPlayer->m_hStudent->GetAbsOrigin();
  276. Vector vecDelta = pLocalPlayer->GetAbsOrigin() - vecTarget;
  277. float flDistance = vecDelta.Length();
  278. const float kInchesToMeters = 0.0254f;
  279. int distance = RoundFloatToInt( flDistance * kInchesToMeters );
  280. wchar_t wzValue[32];
  281. _snwprintf( wzValue, ARRAYSIZE( wzValue ), L"%u", distance );
  282. wchar_t wzText[256];
  283. g_pVGuiLocalize->ConstructString_safe( wzText, g_pVGuiLocalize->Find( "#TR_DistanceToStudent" ), 1, wzValue );
  284. SetDialogVariable( "student_distance", wzText );
  285. }
  286. if ( m_flNextPlayerPanelUpdate < gpGlobals->curtime )
  287. {
  288. RecalculatePlayerPanels();
  289. m_flNextPlayerPanelUpdate = gpGlobals->curtime + 0.1f;
  290. }
  291. }
  292. //-----------------------------------------------------------------------------
  293. // Purpose:
  294. //-----------------------------------------------------------------------------
  295. void CTFSpectatorGUI::UpdateReinforcements( void )
  296. {
  297. if( !m_pReinforcementsLabel )
  298. return;
  299. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  300. if ( !pPlayer || pPlayer->IsHLTV() ||
  301. ( pPlayer->GetTeamNumber() != TF_TEAM_RED && pPlayer->GetTeamNumber() != TF_TEAM_BLUE ) ||
  302. ( pPlayer->m_Shared.GetState() != TF_STATE_OBSERVER && pPlayer->m_Shared.GetState() != TF_STATE_DYING ) ||
  303. ( pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ) )
  304. {
  305. m_pReinforcementsLabel->SetVisible( false );
  306. m_pBuyBackLabel->SetVisible( false );
  307. return;
  308. }
  309. bool bBuyBackVisible = false;
  310. wchar_t wLabel[256];
  311. if ( TFGameRules()->InStalemate() )
  312. {
  313. if ( TFGameRules()->IsInArenaMode() == true )
  314. {
  315. g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#TF_Arena_NoRespawning" ), 0 );
  316. }
  317. else
  318. {
  319. g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#game_respawntime_stalemate" ), 0 );
  320. }
  321. }
  322. else if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN )
  323. {
  324. // a team has won the round
  325. g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#game_respawntime_next_round" ), 0 );
  326. }
  327. else
  328. {
  329. float flNextRespawn = 0.f;
  330. bool bQuickSpawn = TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pPlayer->IsPlayerClass( TF_CLASS_SCOUT );
  331. if ( g_TF_PR )
  332. {
  333. flNextRespawn = g_TF_PR->GetNextRespawnTime( pPlayer->entindex() );
  334. }
  335. else if ( !bQuickSpawn )
  336. {
  337. flNextRespawn = TFGameRules()->GetNextRespawnWave( pPlayer->GetTeamNumber(), pPlayer );
  338. }
  339. if ( !flNextRespawn )
  340. {
  341. m_pReinforcementsLabel->SetVisible( false );
  342. m_pBuyBackLabel->SetVisible( false );
  343. return;
  344. }
  345. int iRespawnWait = (flNextRespawn - gpGlobals->curtime);
  346. if ( iRespawnWait <= 0 )
  347. {
  348. g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_now" ), 0 );
  349. }
  350. else if ( iRespawnWait <= 1.0 )
  351. {
  352. g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_in_sec" ), 0 );
  353. }
  354. else
  355. {
  356. char szSecs[6];
  357. wchar_t wSecs[4];
  358. if ( TFGameRules()->IsMannVsMachineMode() )
  359. {
  360. bool bNewMethod = tf_mvm_buybacks_method.GetBool();
  361. bBuyBackVisible = ( bNewMethod ) ? g_TF_PR->GetNumBuybackCredits( pPlayer->entindex() ) : true;
  362. if ( bBuyBackVisible )
  363. {
  364. // When using the new system, we display "Hit '%use_action_slot_item%' to RESPAWN INSTANTLY! (%s1 remaining this wave)"
  365. // When using the old system, we display "Hit '%use_action_slot_item%' to pay %s1 credits and RESPAWN INSTANTLY!"
  366. int nCost = ( bNewMethod ) ? g_TF_PR->GetNumBuybackCredits( pPlayer->entindex() ) : iRespawnWait * MVM_BUYBACK_COST_PER_SEC;
  367. const char *pszString = ( bNewMethod ) ? "#TF_PVE_Buyback_Fixed" : "#TF_PVE_Buyback";
  368. Q_snprintf( szSecs, sizeof( szSecs ), "%d", nCost );
  369. g_pVGuiLocalize->ConvertANSIToUnicode( szSecs, wSecs, sizeof( wSecs ) );
  370. g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( pszString ), 1, wSecs );
  371. wchar_t wBuyBack[256];
  372. UTIL_ReplaceKeyBindings( wLabel, 0, wBuyBack, sizeof( wBuyBack ), GAME_ACTION_SET_SPECTATOR );
  373. m_pBuyBackLabel->SetText( wBuyBack, true );
  374. }
  375. }
  376. Q_snprintf( szSecs, sizeof(szSecs), "%d", iRespawnWait );
  377. g_pVGuiLocalize->ConvertANSIToUnicode(szSecs, wSecs, sizeof(wSecs));
  378. g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_in_secs" ), 1, wSecs );
  379. }
  380. }
  381. m_pReinforcementsLabel->SetVisible( true );
  382. m_pReinforcementsLabel->SetText( wLabel, true );
  383. m_pBuyBackLabel->SetVisible( bBuyBackVisible );
  384. }
  385. //-----------------------------------------------------------------------------
  386. // Purpose:
  387. //-----------------------------------------------------------------------------
  388. void CTFSpectatorGUI::UpdateKeyLabels( void )
  389. {
  390. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  391. bool bSteamController = ::input->IsSteamControllerActive();
  392. if ( InTournamentGUI() == false )
  393. {
  394. // get the desired player class
  395. int iClass = TF_CLASS_UNDEFINED;
  396. bool bIsHLTV = engine->IsHLTV();
  397. if ( pPlayer )
  398. {
  399. iClass = pPlayer->m_Shared.GetDesiredPlayerClassIndex();
  400. }
  401. // if it's time to change the tip, or the player has changed desired class, update the tip
  402. if ( ( gpGlobals->curtime >= m_flNextTipChangeTime ) || ( iClass != m_iTipClass ) )
  403. {
  404. if ( bIsHLTV )
  405. {
  406. const wchar_t *wzTip = g_pVGuiLocalize->Find( "#Tip_HLTV" );
  407. if ( wzTip )
  408. {
  409. SetDialogVariable( "tip", wzTip );
  410. }
  411. }
  412. else
  413. {
  414. wchar_t wzTipLabel[512]=L"";
  415. const wchar_t *wzTip = g_TFTips.GetNextClassTip( iClass );
  416. Assert( wzTip && wzTip[0] );
  417. g_pVGuiLocalize->ConstructString_safe( wzTipLabel, g_pVGuiLocalize->Find( "#Tip_Fmt" ), 1, wzTip );
  418. SetDialogVariable( "tip", wzTipLabel );
  419. }
  420. m_flNextTipChangeTime = gpGlobals->curtime + 10.0f;
  421. m_iTipClass = iClass;
  422. }
  423. if ( m_pClassOrTeamLabel )
  424. {
  425. if ( pPlayer )
  426. {
  427. static wchar_t wzFinal[512] = L"";
  428. const wchar_t *wzTemp = NULL;
  429. const wchar_t *wzIcon = nullptr;
  430. if ( TFGameRules() && TFGameRules()->IsInTraining() )
  431. {
  432. wzTemp = L"";
  433. wzIcon = L"";
  434. }
  435. else if ( bIsHLTV )
  436. {
  437. wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_AutoDirector" );
  438. }
  439. else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR && TFGameRules()->IsInArenaMode() == false )
  440. {
  441. if ( bSteamController )
  442. {
  443. wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeTeam_NoKey" );
  444. wzIcon = GetSCGlyph( "changeteam" );
  445. }
  446. else
  447. {
  448. wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeTeam" );
  449. }
  450. }
  451. else
  452. {
  453. if ( bSteamController )
  454. {
  455. wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeClass_NoKey" );
  456. wzIcon = GetSCGlyph( "changeclass" );
  457. }
  458. else
  459. {
  460. wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeClass" );
  461. }
  462. }
  463. if ( wzTemp )
  464. {
  465. UTIL_ReplaceKeyBindings( wzTemp, 0, wzFinal, sizeof( wzFinal ) );
  466. }
  467. m_pClassOrTeamLabel->SetText( wzFinal, true );
  468. if ( m_pClassOrTeamKeyLabel )
  469. {
  470. if ( wzIcon && m_pClassOrTeamLabel->IsVisible() )
  471. {
  472. m_pClassOrTeamKeyLabel->SetText( wzIcon );
  473. m_pClassOrTeamKeyLabel->SetVisible( true );
  474. }
  475. else
  476. {
  477. m_pClassOrTeamKeyLabel->SetVisible( false );
  478. }
  479. }
  480. }
  481. }
  482. static ConVarRef cl_hud_minmode( "cl_hud_minmode", true );
  483. if ( m_bCoaching == true || ( cl_hud_minmode.IsValid() && ( cl_hud_minmode.GetBool() == false ) ) )
  484. {
  485. if ( m_pSwitchCamModeKeyLabel )
  486. {
  487. if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( ( mp_forcecamera.GetInt() == OBS_ALLOW_TEAM ) || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) || mp_fadetoblack.GetBool() ) )
  488. {
  489. if ( m_pSwitchCamModeKeyLabel->IsVisible() )
  490. {
  491. m_pSwitchCamModeKeyLabel->SetVisible( false );
  492. Label *pLabel = dynamic_cast<Label *>( FindChildByName( "SwitchCamModeLabel" ) );
  493. if ( pLabel )
  494. {
  495. pLabel->SetVisible( false );
  496. }
  497. }
  498. }
  499. else
  500. {
  501. if ( !m_pSwitchCamModeKeyLabel->IsVisible() )
  502. {
  503. m_pSwitchCamModeKeyLabel->SetVisible( true );
  504. Label *pLabel = dynamic_cast<Label *>( FindChildByName( "SwitchCamModeLabel" ) );
  505. if ( pLabel )
  506. {
  507. pLabel->SetVisible( true );
  508. }
  509. }
  510. wchar_t wLabel[256] = L"";
  511. const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_SwitchCamModeKey" );
  512. UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) );
  513. m_pSwitchCamModeKeyLabel->SetText( wLabel, true );
  514. }
  515. }
  516. if ( m_pCycleTargetFwdKeyLabel )
  517. {
  518. if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( mp_fadetoblack.GetBool() || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) ) )
  519. {
  520. if ( m_pCycleTargetFwdKeyLabel->IsVisible() )
  521. {
  522. m_pCycleTargetFwdKeyLabel->SetVisible( false );
  523. Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetFwdLabel" ) );
  524. if ( pLabel )
  525. {
  526. pLabel->SetVisible( false );
  527. }
  528. }
  529. }
  530. else
  531. {
  532. if ( !m_pCycleTargetFwdKeyLabel->IsVisible() )
  533. {
  534. m_pCycleTargetFwdKeyLabel->SetVisible( true );
  535. Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetFwdLabel" ) );
  536. if ( pLabel )
  537. {
  538. pLabel->SetVisible( true );
  539. }
  540. }
  541. if ( !bSteamController )
  542. {
  543. wchar_t wLabel[256] = L"";
  544. const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_CycleTargetFwdKey" );
  545. UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) );
  546. m_pCycleTargetFwdKeyLabel->SetText( wLabel, true );
  547. }
  548. else
  549. {
  550. m_pCycleTargetFwdKeyLabel->SetText( GetSCGlyph( "next_target" ) );
  551. }
  552. }
  553. }
  554. if ( m_pCycleTargetRevKeyLabel )
  555. {
  556. if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( mp_fadetoblack.GetBool() || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) ) )
  557. {
  558. if ( m_pCycleTargetRevKeyLabel->IsVisible() )
  559. {
  560. m_pCycleTargetRevKeyLabel->SetVisible( false );
  561. Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetRevLabel" ) );
  562. if ( pLabel )
  563. {
  564. pLabel->SetVisible( false );
  565. }
  566. }
  567. }
  568. else
  569. {
  570. if ( !m_pCycleTargetRevKeyLabel->IsVisible() )
  571. {
  572. m_pCycleTargetRevKeyLabel->SetVisible( true );
  573. Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetRevLabel" ) );
  574. if ( pLabel )
  575. {
  576. pLabel->SetVisible( true );
  577. }
  578. }
  579. if ( !bSteamController )
  580. {
  581. wchar_t wLabel[256] = L"";
  582. const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_CycleTargetRevKey" );
  583. UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) );
  584. m_pCycleTargetRevKeyLabel->SetText( wLabel, true );
  585. }
  586. else
  587. {
  588. m_pCycleTargetRevKeyLabel->SetText( GetSCGlyph( "prev_target" ) );
  589. }
  590. }
  591. }
  592. if ( m_pMapLabel )
  593. {
  594. wchar_t wMapName[32];
  595. wchar_t wLabel[256];
  596. char szMapName[32];
  597. char tempname[128];
  598. Q_FileBase( engine->GetLevelName(), tempname, sizeof( tempname ) );
  599. Q_strlower( tempname );
  600. if ( IsX360() )
  601. {
  602. char *pExt = Q_stristr( tempname, ".360" );
  603. if ( pExt )
  604. {
  605. *pExt = '\0';
  606. }
  607. }
  608. Q_strncpy( szMapName, GetMapDisplayName( tempname ), sizeof( szMapName ) );
  609. g_pVGuiLocalize->ConvertANSIToUnicode( szMapName, wMapName, sizeof(wMapName));
  610. g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#Spec_Map" ), 1, wMapName );
  611. m_pMapLabel->SetText( wLabel );
  612. }
  613. }
  614. }
  615. // coaching stuff
  616. if ( pPlayer && pPlayer->m_hStudent )
  617. {
  618. int iHealth = 0;
  619. int iMaxHealth = 1;
  620. int iMaxBuffedHealth = 0;
  621. C_TFPlayer *pStudent = pPlayer->m_hStudent;
  622. {
  623. wchar_t wPlayerName[MAX_PLAYER_NAME_LENGTH];
  624. wchar_t wLabel[256];
  625. const char* pStudentName = g_TF_PR->GetPlayerName( pStudent->entindex() );
  626. g_pVGuiLocalize->ConvertANSIToUnicode( pStudentName, wPlayerName, sizeof(wPlayerName));
  627. g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#TF_Coach_Student_Prefix" ), 1, wPlayerName );
  628. SetDialogVariable( "student_name", wLabel );
  629. }
  630. for ( int i = 1; i <= 2; ++i )
  631. {
  632. wchar_t wLabel[256] = L"";
  633. const wchar_t *wzTemp = g_pVGuiLocalize->Find( CFmtStr1024( "#TF_Coach_Slot%uLabel", i ) );
  634. UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) );
  635. SetDialogVariable( CFmtStr1024( "coach_command_%u", i ), wLabel );
  636. }
  637. if ( m_pAvatar )
  638. {
  639. m_pAvatar->SetShouldDrawFriendIcon( false );
  640. if ( steamapicontext && steamapicontext->SteamUser() )
  641. {
  642. CSteamID studentSteamID;
  643. if ( pStudent->GetSteamID( &studentSteamID ) )
  644. {
  645. m_pAvatar->SetPlayer( studentSteamID, k_EAvatarSize64x64 );
  646. }
  647. else
  648. {
  649. m_pAvatar->ClearAvatar();
  650. }
  651. }
  652. }
  653. // don't show crosshair when viewing the world from the student's POV
  654. bool bShowCrosshair = pPlayer->GetObserverMode() != OBS_MODE_IN_EYE;
  655. vgui::Panel *pCrosshair = FindChildByName( "Crosshair" );
  656. if ( pCrosshair && pCrosshair->IsVisible() != bShowCrosshair )
  657. {
  658. pCrosshair->SetVisible( bShowCrosshair );
  659. }
  660. iHealth = pStudent->GetHealth();
  661. iMaxHealth = pStudent->GetMaxHealth();
  662. iMaxBuffedHealth = pStudent->m_Shared.GetMaxBuffedHealth();
  663. if ( m_pStudentHealth )
  664. {
  665. m_pStudentHealth->SetHealth( iHealth, iMaxHealth, iMaxBuffedHealth );
  666. if ( !m_pStudentHealth->IsVisible() )
  667. {
  668. m_pStudentHealth->SetVisible( true );
  669. }
  670. }
  671. }
  672. else
  673. {
  674. if ( m_pStudentHealth && m_pStudentHealth->IsVisible() )
  675. {
  676. m_pStudentHealth->SetVisible( false );
  677. }
  678. }
  679. }
  680. //-----------------------------------------------------------------------------
  681. // Purpose:
  682. //-----------------------------------------------------------------------------
  683. void CTFSpectatorGUI::ShowPanel(bool bShow)
  684. {
  685. if ( bShow != IsVisible() )
  686. {
  687. CTFHudObjectiveStatus *pStatus = GET_HUDELEMENT( CTFHudObjectiveStatus );
  688. CHudBuildingStatusContainer_Engineer *pEngBuilds = GET_NAMED_HUDELEMENT( CHudBuildingStatusContainer_Engineer, BuildingStatus_Engineer );
  689. CHudBuildingStatusContainer_Spy *pSpyBuilds = GET_NAMED_HUDELEMENT( CHudBuildingStatusContainer_Spy, BuildingStatus_Spy );
  690. CHudItemAttributeTracker *pAttribTrackers = GET_HUDELEMENT( CHudItemAttributeTracker );
  691. if ( pAttribTrackers )
  692. {
  693. pAttribTrackers->InvalidateLayout();
  694. }
  695. if ( bShow )
  696. {
  697. int xPos = 0, yPos = 0;
  698. if ( pStatus )
  699. {
  700. pStatus->SetParent( this );
  701. pStatus->SetProportional( true );
  702. }
  703. if ( pEngBuilds )
  704. {
  705. pEngBuilds->GetPos( xPos, yPos );
  706. m_nEngBuilds_xpos = xPos;
  707. m_nEngBuilds_ypos = yPos;
  708. pEngBuilds->SetPos( xPos, GetTopBarHeight() );
  709. }
  710. if ( pSpyBuilds )
  711. {
  712. pSpyBuilds->GetPos( xPos, yPos );
  713. m_nSpyBuilds_xpos = xPos;
  714. m_nSpyBuilds_ypos = yPos;
  715. pSpyBuilds->SetPos( xPos, GetTopBarHeight() );
  716. }
  717. #if defined( REPLAY_ENABLED )
  718. // We don't want to display this message the first time the spectator GUI is shown, since that is right
  719. // when the class menu is shown. We use the player spawn cache here - which is a terrible name for something
  720. // very useful - which will nuke m_nDisplaySaveReplay every time a new map is loaded, which is exactly what
  721. // we want.
  722. int &nDisplaySaveReplay = CPlayerSpawnCache::Instance().m_Data.m_nDisplaySaveReplay;
  723. extern IReplayManager *g_pReplayManager;
  724. CReplay *pCurLifeReplay = ( g_pReplayManager ) ? g_pReplayManager->GetReplayForCurrentLife() : NULL;
  725. if ( g_pReplay->IsRecording() &&
  726. !engine->IsPlayingDemo() &&
  727. !::input->IsSteamControllerActive() &&
  728. nDisplaySaveReplay &&
  729. ( pCurLifeReplay && !pCurLifeReplay->m_bRequestedByUser && !pCurLifeReplay->m_bSaved ) )
  730. {
  731. wchar_t wText[256];
  732. wchar wKeyBind[80];
  733. char szText[256 * sizeof(wchar_t)];
  734. const char *pSaveReplayKey = engine->Key_LookupBinding( "save_replay" );
  735. if ( !pSaveReplayKey )
  736. {
  737. pSaveReplayKey = "< not bound >";
  738. }
  739. g_pVGuiLocalize->ConvertANSIToUnicode( pSaveReplayKey, wKeyBind, sizeof( wKeyBind ) );
  740. g_pVGuiLocalize->ConstructString_safe( wText, g_pVGuiLocalize->Find( "#Replay_SaveThisLifeMsg" ), 1, wKeyBind );
  741. g_pVGuiLocalize->ConvertUnicodeToANSI( wText, szText, sizeof( szText ) );
  742. g_pClientMode->DisplayReplayMessage( szText, -1.0f, false, NULL, false );
  743. }
  744. ++nDisplaySaveReplay;
  745. #endif
  746. m_flNextTipChangeTime = 0; // force a new tip immediately
  747. InvalidateLayout();
  748. }
  749. else
  750. {
  751. if ( pStatus )
  752. {
  753. pStatus->SetParent( g_pClientMode->GetViewport() );
  754. }
  755. if ( pEngBuilds )
  756. {
  757. pEngBuilds->SetPos( m_nEngBuilds_xpos, m_nEngBuilds_ypos );
  758. }
  759. if ( pSpyBuilds )
  760. {
  761. pSpyBuilds->SetPos( m_nSpyBuilds_xpos, m_nSpyBuilds_ypos );
  762. }
  763. }
  764. UpdateKeyLabels();
  765. if ( bShow )
  766. {
  767. m_flNextPlayerPanelUpdate = 0;
  768. m_flNextItemPanelUpdate = 0;
  769. UpdateItemPanel();
  770. RecalculatePlayerPanels();
  771. }
  772. }
  773. BaseClass::ShowPanel( bShow );
  774. }
  775. //-----------------------------------------------------------------------------
  776. // Purpose:
  777. //-----------------------------------------------------------------------------
  778. void CTFSpectatorGUI::FireGameEvent( IGameEvent *event )
  779. {
  780. const char *pEventName = event->GetName();
  781. if ( Q_strcmp( "spec_target_updated", pEventName ) == 0 )
  782. {
  783. UpdateItemPanel();
  784. }
  785. else if ( Q_strcmp( "player_death", pEventName ) == 0 && m_bCoaching )
  786. {
  787. CBaseEntity *pVictim = ClientEntityList().GetEnt( engine->GetPlayerForUserID( event->GetInt("userid") ) );
  788. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  789. if ( pLocalPlayer && ( pVictim == pLocalPlayer->m_hStudent ) )
  790. {
  791. CEconNotification *pNotification = new CEconNotification();
  792. pNotification->SetText( "#TF_Coach_StudentHasDied" );
  793. pNotification->SetLifetime( 10.0f );
  794. pNotification->SetSoundFilename( "coach/coach_student_died.wav" );
  795. NotificationQueue_Add( pNotification );
  796. }
  797. }
  798. }
  799. //-----------------------------------------------------------------------------
  800. // Purpose:
  801. //-----------------------------------------------------------------------------
  802. void CTFSpectatorGUI::UpdateItemPanel( bool bForce )
  803. {
  804. bool bVisible = false;
  805. // Stat panel prevents the item panel from showing.
  806. CTFStatPanel *pStatPanel = GET_HUDELEMENT( CTFStatPanel );
  807. if ( ( pStatPanel && pStatPanel->IsVisible() ) || ( TFGameRules() && TFGameRules()->State_Get() == GR_STATE_TEAM_WIN ) || (!cl_spec_carrieditems.GetBool() && !bForce) )
  808. {
  809. bVisible = false;
  810. }
  811. else
  812. {
  813. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  814. if ( pLocalPlayer )
  815. {
  816. C_TFPlayer *pPlayer = ToTFPlayer( pLocalPlayer->GetObserverTarget() );
  817. if ( pPlayer && pPlayer != pLocalPlayer )
  818. {
  819. if ( m_flNextItemPanelUpdate && m_flNextItemPanelUpdate > gpGlobals->curtime && m_hPrevItemPlayer == pPlayer )
  820. return;
  821. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && ( pPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) )
  822. return;
  823. if ( m_hPrevItemPlayer != pPlayer )
  824. {
  825. m_iPrevItemShown = 0;
  826. m_iFirstItemShown = 0;
  827. m_bShownItems = false;
  828. m_hPrevItemPlayer = pPlayer;
  829. }
  830. m_flNextItemPanelUpdate = gpGlobals->curtime + 8.0;
  831. // Don't reshow the items for a player unless we're being forced to
  832. if ( !m_bShownItems || bForce )
  833. {
  834. // If our killer is using a non-standard item, display its stats.
  835. // Loop through all items and pick one at random, so that we show non-active weapons as well.
  836. CEconItemView *pItemToShow = pPlayer->GetInspectItem( &m_iPrevItemShown );
  837. // If we've looped, we're done. Hide the item.
  838. if ( m_iFirstItemShown && m_iFirstItemShown == m_iPrevItemShown )
  839. {
  840. m_iFirstItemShown = 0;
  841. m_iPrevItemShown = 0;
  842. pItemToShow = NULL;
  843. m_bShownItems = true;
  844. }
  845. if ( pItemToShow )
  846. {
  847. if ( !m_iFirstItemShown )
  848. {
  849. m_iFirstItemShown = m_iPrevItemShown;
  850. }
  851. Label* pItemLabel = m_pItemPanel->FindControl<Label>( "ItemLabel" );
  852. // Change the label text depending on if the original owner is holding the weapon
  853. if ( pItemLabel )
  854. {
  855. CSteamID steamIDOwner;
  856. pPlayer->GetSteamID( &steamIDOwner );
  857. bool bOriginalOwner = steamIDOwner.GetAccountID() == pItemToShow->GetAccountID();
  858. pItemLabel->SetText( bOriginalOwner ? "#FreezePanel_Item" : "#FreezePanel_ItemOtherOwner" );
  859. }
  860. bVisible = true;
  861. m_pItemPanel->SetDialogVariable( "killername", g_TF_PR->GetPlayerName( pPlayer->entindex() ) );
  862. // Set the item owner's name
  863. CBasePlayer *pOriginalOwner = GetPlayerByAccountID( pItemToShow->GetAccountID() );
  864. if ( pOriginalOwner )
  865. {
  866. m_pItemPanel->SetDialogVariable( "ownername", g_TF_PR->GetPlayerName( pOriginalOwner->entindex() ) );
  867. }
  868. m_pItemPanel->SetItem( pItemToShow );
  869. // force update description to get the correct panel size
  870. m_pItemPanel->UpdateDescription();
  871. m_pItemPanel->SetPos( ScreenWidth() - XRES( 10 ) - m_pItemPanel->GetWide(), ScreenHeight() - YRES( 12 ) - m_pItemPanel->GetTall() );
  872. }
  873. }
  874. }
  875. }
  876. }
  877. if ( m_pItemPanel->IsVisible() != bVisible )
  878. {
  879. m_pItemPanel->SetVisible( bVisible );
  880. }
  881. }
  882. //-----------------------------------------------------------------------------
  883. // Purpose:
  884. //-----------------------------------------------------------------------------
  885. void CTFSpectatorGUI::ForceItemPanelCycle( void )
  886. {
  887. m_flNextItemPanelUpdate = 0;
  888. UpdateItemPanel( true );
  889. }
  890. //-----------------------------------------------------------------------------
  891. // Purpose:
  892. //-----------------------------------------------------------------------------
  893. const char *CTFSpectatorGUI::GetResFile( void )
  894. {
  895. if ( m_bCoaching )
  896. {
  897. return "Resource/UI/SpectatorCoach.res";
  898. }
  899. else if ( InTournamentGUI() )
  900. {
  901. return "Resource/UI/SpectatorTournament.res";
  902. }
  903. else if ( ::input->IsSteamControllerActive() )
  904. {
  905. return "Resource/UI/Spectator_SC.res";
  906. }
  907. else
  908. {
  909. return "Resource/UI/Spectator.res";
  910. }
  911. }
  912. //-----------------------------------------------------------------------------
  913. // Purpose:
  914. //-----------------------------------------------------------------------------
  915. bool CTFSpectatorGUI::InTournamentGUI( void )
  916. {
  917. bool bOverride = false;
  918. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  919. {
  920. bOverride = true;
  921. }
  922. return ( TFGameRules()->IsInTournamentMode() && !TFGameRules()->IsCompetitiveMode() && ( cl_use_tournament_specgui.GetBool() || bOverride ) );
  923. }
  924. //-----------------------------------------------------------------------------
  925. // Purpose:
  926. //-----------------------------------------------------------------------------
  927. void CTFSpectatorGUI::RecalculatePlayerPanels( void )
  928. {
  929. if ( !InTournamentGUI() )
  930. {
  931. if ( m_PlayerPanels.Count() > 0 )
  932. {
  933. // Delete any player panels we have, we've turned off the tourney GUI.
  934. for ( int i = m_PlayerPanels.Count()-1; i >= 0; i-- )
  935. {
  936. m_PlayerPanels[i]->MarkForDeletion();
  937. }
  938. m_PlayerPanels.Purge();
  939. }
  940. return;
  941. }
  942. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
  943. if ( !pPlayer || !g_TF_PR || !TFGameRules() )
  944. return;
  945. int iLocalTeam = pPlayer->GetTeamNumber();
  946. bool bMvM = TFGameRules()->IsMannVsMachineMode();
  947. // Calculate the number of players that must be shown. Spectators see all players (except in MvM), team members only see their team.
  948. int iPanel = 0;
  949. for ( int nClass = TF_FIRST_NORMAL_CLASS; nClass <= TF_LAST_NORMAL_CLASS; nClass++ )
  950. {
  951. // we want to sort the images to match the class menu selections
  952. int nCurrentClass = g_ClassDefinesRemap[nClass];
  953. for ( int i = 0; i < MAX_PLAYERS; i++ )
  954. {
  955. int iPlayer = i+1;
  956. if ( !g_TF_PR->IsConnected( iPlayer ) )
  957. continue;
  958. bool bHideBots = false;
  959. #ifndef _DEBUG
  960. if ( bMvM )
  961. {
  962. bHideBots = true;
  963. }
  964. #endif
  965. int iTeam = g_TF_PR->GetTeam( iPlayer );
  966. if ( iTeam != iLocalTeam && iLocalTeam != TEAM_SPECTATOR )
  967. continue;
  968. if ( iTeam != TF_TEAM_RED && iTeam != TF_TEAM_BLUE )
  969. continue;
  970. if ( g_TF_PR->IsFakePlayer( iPlayer ) && bHideBots )
  971. continue;
  972. if ( bMvM && ( iTeam == TF_TEAM_PVE_INVADERS ) )
  973. continue;
  974. if ( g_TF_PR->GetPlayerClass( iPlayer ) != nCurrentClass )
  975. continue;
  976. if ( m_PlayerPanels.Count() <= iPanel )
  977. {
  978. CTFPlayerPanel *pPanel = new CTFPlayerPanel( this, VarArgs("playerpanel%d", i) );
  979. if ( m_pPlayerPanelKVs )
  980. {
  981. pPanel->ApplySettings( m_pPlayerPanelKVs );
  982. }
  983. m_PlayerPanels.AddToTail( pPanel );
  984. }
  985. m_PlayerPanels[iPanel]->SetPlayerIndex( iPlayer );
  986. iPanel++;
  987. }
  988. }
  989. for ( int i = iPanel; i < m_PlayerPanels.Count(); i++ )
  990. {
  991. m_PlayerPanels[i]->SetPlayerIndex( 0 );
  992. }
  993. UpdatePlayerPanels();
  994. }
  995. //-----------------------------------------------------------------------------
  996. // Purpose:
  997. //-----------------------------------------------------------------------------
  998. void CTFSpectatorGUI::UpdatePlayerPanels( void )
  999. {
  1000. if ( !g_TF_PR )
  1001. return;
  1002. uint nVisible = 0;
  1003. bool bNeedsPlayerLayout = false;
  1004. for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
  1005. {
  1006. if ( m_PlayerPanels[i]->Update() )
  1007. {
  1008. bNeedsPlayerLayout = true;
  1009. }
  1010. if ( m_PlayerPanels[i]->IsVisible() )
  1011. {
  1012. nVisible++;
  1013. }
  1014. }
  1015. if ( !bNeedsPlayerLayout )
  1016. return;
  1017. // Try and always put the local player's team on team1, if he's in a team
  1018. int iTeam1 = TF_TEAM_BLUE;
  1019. int iTeam2 = TF_TEAM_RED;
  1020. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  1021. if ( !pLocalPlayer )
  1022. return;
  1023. int iLocalTeam = g_TF_PR->GetTeam( pLocalPlayer->entindex() );
  1024. if ( iLocalTeam == TF_TEAM_RED || iLocalTeam == TF_TEAM_BLUE )
  1025. {
  1026. iTeam1 = iLocalTeam;
  1027. iTeam2 = ( iTeam1 == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE;
  1028. }
  1029. int iTeam1Count = 0;
  1030. int iTeam2Count = 0;
  1031. int iCenter = GetWide() * 0.5;
  1032. int iYPosOverride = 0;
  1033. // We only want to draw the player panels for the defenders in MvM
  1034. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  1035. {
  1036. iTeam1 = TF_TEAM_PVE_DEFENDERS;
  1037. iTeam2 = TF_TEAM_PVE_INVADERS;
  1038. int iNumValidPanels = 0;
  1039. for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
  1040. {
  1041. if ( m_PlayerPanels[i]->GetPlayerIndex() <= 0 )
  1042. {
  1043. continue;
  1044. }
  1045. iNumValidPanels++;
  1046. }
  1047. // we'll center the group of players in MvM
  1048. m_iTeam1PlayerBaseOffsetX = ( iNumValidPanels * m_iTeam1PlayerDeltaX ) * -0.5;
  1049. if ( iLocalTeam > LAST_SHARED_TEAM )
  1050. {
  1051. if ( m_pTopBar )
  1052. {
  1053. iYPosOverride = m_pTopBar->GetTall();
  1054. }
  1055. }
  1056. }
  1057. for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
  1058. {
  1059. int iXPos = 0;
  1060. int iYPos = 0;
  1061. if ( m_PlayerPanels[i]->GetPlayerIndex() <= 0 )
  1062. {
  1063. m_PlayerPanels[i]->SetVisible( false );
  1064. continue;
  1065. }
  1066. int iTeam = g_TF_PR->GetTeam( m_PlayerPanels[i]->GetPlayerIndex() );
  1067. if ( iTeam == iTeam1 )
  1068. {
  1069. iXPos = m_iTeam1PlayerBaseOffsetX ? (iCenter + m_iTeam1PlayerBaseOffsetX) : m_iTeam1PlayerBaseX;
  1070. iXPos += (iTeam1Count * m_iTeam1PlayerDeltaX);
  1071. iYPos = iYPosOverride ? iYPosOverride : m_iTeam1PlayerBaseY + (iTeam1Count * m_iTeam1PlayerDeltaY);
  1072. m_PlayerPanels[i]->SetSpecIndex( 6 - iTeam1Count );
  1073. m_PlayerPanels[i]->SetPos( iXPos, iYPos );
  1074. m_PlayerPanels[i]->SetVisible( true );
  1075. iTeam1Count++;
  1076. }
  1077. else if ( iTeam == iTeam2 )
  1078. {
  1079. iXPos = m_iTeam2PlayerBaseOffsetX ? (iCenter + m_iTeam2PlayerBaseOffsetX) : m_iTeam2PlayerBaseX;
  1080. iXPos += (iTeam2Count * m_iTeam2PlayerDeltaX);
  1081. iYPos = iYPosOverride ? iYPosOverride : m_iTeam2PlayerBaseY + (iTeam2Count * m_iTeam2PlayerDeltaY);
  1082. m_PlayerPanels[i]->SetSpecIndex( 7 + iTeam2Count );
  1083. m_PlayerPanels[i]->SetPos( iXPos, iYPos );
  1084. m_PlayerPanels[i]->SetVisible( true );
  1085. iTeam2Count++;
  1086. }
  1087. }
  1088. }
  1089. //-----------------------------------------------------------------------------
  1090. // Purpose:
  1091. //-----------------------------------------------------------------------------
  1092. void CTFSpectatorGUI::SelectSpec( int iSlot )
  1093. {
  1094. if ( m_bCoaching )
  1095. {
  1096. engine->ClientCmd_Unrestricted( CFmtStr1024( "coach_command %u", iSlot ) );
  1097. return;
  1098. }
  1099. if ( !InTournamentGUI() )
  1100. return;
  1101. for ( int i = 0; i < m_PlayerPanels.Count(); i++ )
  1102. {
  1103. if ( m_PlayerPanels[i]->GetSpecIndex() == iSlot )
  1104. {
  1105. engine->ClientCmd_Unrestricted( VarArgs( "spec_player %d\n", m_PlayerPanels[i]->GetPlayerIndex() ) );
  1106. return;
  1107. }
  1108. }
  1109. }