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.

594 lines
15 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "tf_shareddefs.h"
  8. #include "tf_matchmaking_dashboard.h"
  9. #include "tf_gamerules.h"
  10. #include "ienginevgui.h"
  11. #include "clientmode_tf.h"
  12. #include "tf_hud_disconnect_prompt.h"
  13. #include "tf_gc_client.h"
  14. #include "tf_party.h"
  15. #include "../vgui2/src/VPanel.h"
  16. using namespace vgui;
  17. using namespace GCSDK;
  18. ConVar tf_mm_dashboard_spew_enabled( "tf_mm_dashboard_spew_enabled", "0", FCVAR_ARCHIVE );
  19. #define MMDashboardSpew(...) \
  20. do { \
  21. if ( tf_mm_dashboard_spew_enabled.GetBool() ) \
  22. { \
  23. ConColorMsg( Color( 187, 80, 255, 255 ), "MMDashboard:" __VA_ARGS__ ); \
  24. } \
  25. } while(false) \
  26. extern ConVar tf_mm_next_map_vote_time;
  27. #ifdef STAGING_ONLY
  28. ConVar tf_mm_dashboard_force_show( "tf_mm_dashboard_force_show", "0", 0, "Force the mm dashboard to show" );
  29. ConVar tf_mm_popup_state_override( "tf_mm_popup_state_override", "", 0, "Force state on mm dashboard popup" );
  30. #endif
  31. bool BInEndOfMatch()
  32. {
  33. const bool bInEndOfMatch = TFGameRules() &&
  34. TFGameRules()->State_Get() == GR_STATE_GAME_OVER &&
  35. GTFGCClientSystem()->BConnectedToMatchServer( false );
  36. return bInEndOfMatch;
  37. }
  38. //-----------------------------------------------------------------------------
  39. // Purpose: Pnael that lives on the viewport that is a popup that we parent
  40. // the MM dashboard panels to
  41. //-----------------------------------------------------------------------------
  42. class CMatchMakingHUDPopupContainer : public Panel
  43. {
  44. public:
  45. DECLARE_CLASS_SIMPLE( CMatchMakingHUDPopupContainer, Panel );
  46. CMatchMakingHUDPopupContainer()
  47. : Panel( g_pClientMode->GetViewport(), "MMDashboardPopupContainer" )
  48. {
  49. SetProportional( true );
  50. SetBounds( 0, 0, g_pClientMode->GetViewport()->GetWide(), g_pClientMode->GetViewport()->GetTall() );
  51. MakePopup();
  52. SetMouseInputEnabled( true );
  53. SetKeyBoardInputEnabled( false ); // This can never be true
  54. SetVisible( false );
  55. ivgui()->AddTickSignal( GetVPanel(), 100 );
  56. }
  57. virtual void OnTick()
  58. {
  59. BaseClass::OnThink();
  60. bool bChildrenVisible = false;
  61. int nCount = GetChildCount();
  62. for( int i=0; i < nCount && !bChildrenVisible; ++i )
  63. {
  64. CExpandablePanel* pChild = assert_cast< CExpandablePanel* >( GetChild( i ) );
  65. bChildrenVisible = bChildrenVisible || pChild->BIsExpanded() || ( !pChild->BIsExpanded() && pChild->GetPercentAnimated() != 1.f );
  66. }
  67. SetVisible( bChildrenVisible );
  68. }
  69. };
  70. CMMDashboardParentManager::CMMDashboardParentManager()
  71. : m_bAttachedToGameUI( false )
  72. {
  73. ListenForGameEvent( "gameui_activated" );
  74. ListenForGameEvent( "gameui_hidden" );
  75. m_pHUDPopup = new CMatchMakingHUDPopupContainer();
  76. }
  77. //-----------------------------------------------------------------------------
  78. // Purpose: Update who we need to parent the MM dashboard panels to
  79. //-----------------------------------------------------------------------------
  80. void CMMDashboardParentManager::FireGameEvent( IGameEvent *event )
  81. {
  82. if ( FStrEq( event->GetName(), "gameui_activated" ) )
  83. {
  84. m_bAttachedToGameUI = false;
  85. }
  86. else if ( FStrEq( event->GetName(), "gameui_hidden" ) )
  87. {
  88. m_bAttachedToGameUI = true;
  89. }
  90. UpdateParenting();
  91. }
  92. void CMMDashboardParentManager::AddPanel( CExpandablePanel* pPanel )
  93. {
  94. m_vecPanels.Insert( pPanel );
  95. UpdateParenting();
  96. }
  97. void CMMDashboardParentManager::RemovePanel( CExpandablePanel* pPanel )
  98. {
  99. m_vecPanels.FindAndRemove( pPanel );
  100. m_vecPanels.RedoSort();
  101. UpdateParenting();
  102. }
  103. void CMMDashboardParentManager::PushModalFullscreenPopup( vgui::Panel* pPanel )
  104. {
  105. m_vecFullscreenPopups.AddToTail( pPanel );
  106. UpdateParenting();
  107. }
  108. void CMMDashboardParentManager::PopModalFullscreenPopup( vgui::Panel* pPanel )
  109. {
  110. m_vecFullscreenPopups.FindAndRemove( pPanel );
  111. UpdateParenting();
  112. }
  113. void CMMDashboardParentManager::UpdateParenting()
  114. {
  115. m_bAttachedToGameUI ? AttachToGameUI() : AttachToTopMostPopup();
  116. }
  117. //-----------------------------------------------------------------------------
  118. // Purpose: Parent the MM dashboard panels to the right panels
  119. //-----------------------------------------------------------------------------
  120. void CMMDashboardParentManager::AttachToGameUI()
  121. {
  122. if ( !m_pHUDPopup )
  123. {
  124. return;
  125. }
  126. FOR_EACH_VEC( m_vecPanels, i )
  127. {
  128. CExpandablePanel *pPanel = m_vecPanels[ i ];
  129. bool bKBInput = pPanel->IsKeyBoardInputEnabled();
  130. bool bMouseInput = pPanel->IsMouseInputEnabled();
  131. pPanel->SetParent( (Panel*)m_pHUDPopup );
  132. // Restore mouse, KV input sensitivity because MakePopup forces both to true
  133. pPanel->SetKeyBoardInputEnabled( bKBInput );
  134. pPanel->SetMouseInputEnabled( bMouseInput );
  135. // Don't adopt the parent's proportionalness
  136. pPanel->SetProportional( true );
  137. }
  138. }
  139. void CMMDashboardParentManager::AttachToTopMostPopup()
  140. {
  141. // Not being used. Hide it.
  142. if ( m_pHUDPopup )
  143. {
  144. m_pHUDPopup->SetVisible( false );
  145. }
  146. FOR_EACH_VEC( m_vecPanels, i )
  147. {
  148. Panel *pPanel = m_vecPanels[ i ];
  149. // No longer a popup
  150. surface()->ReleasePanel( pPanel->GetVPanel() );
  151. ((VPanel*)pPanel->GetVPanel())->SetPopup( false );
  152. if ( m_vecFullscreenPopups.Count() )
  153. {
  154. pPanel->SetParent( m_vecFullscreenPopups.Tail() );
  155. }
  156. else
  157. {
  158. pPanel->SetParent( enginevgui->GetPanel( PANEL_GAMEUIDLL ) );
  159. }
  160. pPanel->MoveToFront();
  161. // Don't adopt the parent's proportionalness
  162. pPanel->SetProportional( true );
  163. }
  164. }
  165. //-----------------------------------------------------------------------------
  166. // Purpose: Snag the singleton CMMDashboardParentManager
  167. //-----------------------------------------------------------------------------
  168. CMMDashboardParentManager* GetMMDashboardParentManager()
  169. {
  170. static CMMDashboardParentManager* s_pParentManager = NULL;
  171. if ( !s_pParentManager )
  172. {
  173. s_pParentManager = new CMMDashboardParentManager();
  174. }
  175. return s_pParentManager;
  176. }
  177. void CTFMatchmakingPopup::OnEnter()
  178. {
  179. MMDashboardSpew( "Entering state %s\n", GetName() );
  180. Update();
  181. SetCollapsed( false );
  182. }
  183. void CTFMatchmakingPopup::OnUpdate()
  184. {
  185. }
  186. void CTFMatchmakingPopup::OnExit()
  187. {
  188. MMDashboardSpew( "Exiting state %s\n", GetName() );
  189. SetCollapsed( true );
  190. }
  191. void CTFMatchmakingPopup::Update()
  192. {
  193. }
  194. //-----------------------------------------------------------------------------
  195. // Purpose:
  196. //-----------------------------------------------------------------------------
  197. CTFMatchmakingPopup::CTFMatchmakingPopup( const char* pszName, const char* pszResFile )
  198. : CExpandablePanel( NULL, pszName )
  199. , m_pszResFile( pszResFile )
  200. , m_bActive( false )
  201. {
  202. vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
  203. SetScheme(scheme);
  204. ivgui()->AddTickSignal( GetVPanel(), 100 );
  205. GetMMDashboardParentManager()->AddPanel( this );
  206. SetKeyBoardInputEnabled( false );
  207. SetProportional( true );
  208. ListenForGameEvent( "rematch_failed_to_create" );
  209. ListenForGameEvent( "party_updated" );
  210. }
  211. CTFMatchmakingPopup::~CTFMatchmakingPopup()
  212. {
  213. GetMMDashboardParentManager()->RemovePanel( this );
  214. }
  215. void CTFMatchmakingPopup::ApplySchemeSettings( IScheme *pScheme )
  216. {
  217. BaseClass::ApplySchemeSettings( pScheme );
  218. SetMouseInputEnabled( true );
  219. LoadControlSettings( m_pszResFile );
  220. // This cannot ever be true or else things get weird when in-game
  221. SetKeyBoardInputEnabled( false );
  222. if ( m_bActive )
  223. {
  224. OnEnter();
  225. }
  226. else
  227. {
  228. OnExit();
  229. }
  230. GetMMDashboardParentManager()->UpdateParenting();
  231. }
  232. void CTFMatchmakingPopup::OnThink()
  233. {
  234. BaseClass::OnThink();
  235. // Move us to be touching the bottom of the dashboard panel
  236. // These panels have no relation whatsoever, so we're doing this manually
  237. Panel* pDashboard = GetMMDashboard();
  238. int nNewYPos = Max( pDashboard->GetYPos() + pDashboard->GetTall() - YRES(10), YRES(-5) );
  239. SetPos( GetXPos(), nNewYPos );
  240. if ( m_bActive )
  241. {
  242. OnUpdate();
  243. }
  244. }
  245. void CTFMatchmakingPopup::OnTick()
  246. {
  247. BaseClass::OnTick();
  248. bool bShouldBeActive = ShouldBeActve();
  249. if ( bShouldBeActive != m_bActive )
  250. {
  251. if ( bShouldBeActive )
  252. {
  253. m_bActive = true;
  254. OnEnter();
  255. }
  256. else
  257. {
  258. m_bActive = false;
  259. OnExit();
  260. }
  261. }
  262. SetMouseInputEnabled( ShouldBeActve() );
  263. SetKeyBoardInputEnabled( false ); // Never
  264. }
  265. void CTFMatchmakingPopup::OnCommand( const char *command )
  266. {
  267. if ( FStrEq( "join_match", command ) )
  268. {
  269. JoinMatch();
  270. }
  271. else if ( FStrEq( "abandon_match", command ) )
  272. {
  273. GTFGCClientSystem()->RejoinLobby( false );
  274. }
  275. else if ( FStrEq( "leave_queue", command ) )
  276. {
  277. // Only the leader can leave the queue
  278. if ( GTFGCClientSystem()->BIsPartyLeader() )
  279. {
  280. switch( GTFGCClientSystem()->GetSearchMode() )
  281. {
  282. case TF_Matchmaking_LADDER:
  283. GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER );
  284. break;
  285. case TF_Matchmaking_CASUAL:
  286. GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_CASUAL );
  287. break;
  288. default:
  289. // Unhandled
  290. Assert( false );
  291. break;
  292. };
  293. }
  294. }
  295. else if( FStrEq( "rematch", command ) )
  296. {
  297. if ( !GTFGCClientSystem()->BIsPartyLeader() )
  298. return;
  299. engine->ClientCmd( "rematch_vote 2" );
  300. }
  301. else if ( FStrEq( "new_match", command ) )
  302. {
  303. if ( !GTFGCClientSystem()->BIsPartyLeader() )
  304. return;
  305. GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_SEARCHING );
  306. engine->ClientCmd( "rematch_vote 1" );
  307. }
  308. else if ( FStrEq( "leave_party", command ) )
  309. {
  310. // Leave current party and create a new one!
  311. GTFGCClientSystem()->SendExitMatchmaking( false );
  312. return;
  313. }
  314. }
  315. void CTFMatchmakingPopup::FireGameEvent( IGameEvent *pEvent )
  316. {
  317. if ( FStrEq( pEvent->GetName(), "rematch_failed_to_create" ) )
  318. {
  319. // If the GC failed to create our rematch, then go ahead and requeue
  320. if ( !GTFGCClientSystem()->BIsPartyLeader() )
  321. return;
  322. GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_SEARCHING );
  323. }
  324. else if ( FStrEq( pEvent->GetName(), "party_updated" ) )
  325. {
  326. if ( ShouldBeActve() )
  327. {
  328. Update();
  329. }
  330. }
  331. }
  332. #ifdef STAGING_ONLY
  333. CON_COMMAND( reload_mm_popup, "Reload the mm popup panel. Pass any 2nd argument to recreate the panel." )
  334. {
  335. bool bRecreate = false;
  336. if ( args.ArgC() == 2 )
  337. {
  338. bRecreate = true;
  339. }
  340. auto& vecPopups = CreateMMPopupPanels( bRecreate );
  341. if ( !bRecreate )
  342. {
  343. FOR_EACH_VEC( vecPopups, i )
  344. {
  345. vecPopups[ i ]->InvalidateLayout( true, true );
  346. }
  347. }
  348. }
  349. CON_COMMAND( reload_mm_dashboard, "Reload the mm join panel." )
  350. {
  351. GetMMDashboard()->InvalidateLayout( true, true );
  352. }
  353. #endif
  354. CTFMatchmakingDashboard::CTFMatchmakingDashboard()
  355. : CExpandablePanel( NULL, "MMDashboard" )
  356. {
  357. SetKeyBoardInputEnabled( false );
  358. ivgui()->AddTickSignal( GetVPanel(), 100 );
  359. GetMMDashboardParentManager()->AddPanel( this );
  360. CreateMMPopupPanels();
  361. vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
  362. SetScheme(scheme);
  363. SetProportional( true );
  364. }
  365. CTFMatchmakingDashboard::~CTFMatchmakingDashboard()
  366. {
  367. GetMMDashboardParentManager()->RemovePanel( this );
  368. }
  369. void CTFMatchmakingDashboard::ApplySchemeSettings( vgui::IScheme *pScheme )
  370. {
  371. BaseClass::ApplySchemeSettings( pScheme );
  372. SetMouseInputEnabled( true );
  373. LoadControlSettings( "resource/UI/MatchMakingDashboard.res" );
  374. // This cannot ever be true or else things get weird when in-game
  375. SetKeyBoardInputEnabled( false );
  376. GetMMDashboardParentManager()->UpdateParenting();
  377. }
  378. void CTFMatchmakingDashboard::OnCommand( const char *command )
  379. {
  380. if ( FStrEq( command, "disconnect" ) )
  381. {
  382. CTFParty* pParty = GTFGCClientSystem()->GetParty();
  383. bool bInPartyOfMany = pParty && pParty->GetNumMembers() > 1;
  384. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() );
  385. if ( pMatchDesc && pMatchDesc->BShouldAutomaticallyRequeueOnMatchEnd() && !bInPartyOfMany )
  386. {
  387. // If this is an auto-requeue match type, then assume hitting the "Disconnect" button
  388. // means you are done playing entirely with MM. Close out to the main menu.
  389. bool bNoPenalty = ( GTFGCClientSystem()->GetAssignedMatchAbandonStatus() != k_EAbandonGameStatus_AbandonWithPenalty );
  390. if ( bNoPenalty )
  391. {
  392. GTFGCClientSystem()->EndMatchmaking( true );
  393. }
  394. else
  395. {
  396. // Prompt if this would be considered an abandon with a penalty
  397. CTFDisconnectConfirmDialog *pDialog = BuildDisconnectConfirmDialog();
  398. if ( pDialog )
  399. {
  400. pDialog->Show();
  401. }
  402. }
  403. }
  404. else
  405. {
  406. // Go back to the MM screens. The party leader will request the right wizard state
  407. switch( GTFGCClientSystem()->GetSearchMode() )
  408. {
  409. case TF_Matchmaking_LADDER:
  410. if ( GTFGCClientSystem()->BIsPartyLeader() )
  411. {
  412. GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER );
  413. }
  414. engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby ladder" );
  415. break;
  416. case TF_Matchmaking_CASUAL:
  417. if ( GTFGCClientSystem()->BIsPartyLeader() )
  418. {
  419. GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_CASUAL );
  420. }
  421. engine->ClientCmd_Unrestricted( "OpenMatchmakingLobby casual" );
  422. break;
  423. default:
  424. // Unhandled
  425. GTFGCClientSystem()->EndMatchmaking();
  426. Assert( false );
  427. break;
  428. };
  429. }
  430. // Disconnect from the server
  431. engine->DisconnectInternal();
  432. }
  433. }
  434. //-----------------------------------------------------------------------------
  435. // Purpose: Figure out if we should be visible and require mouse input
  436. //-----------------------------------------------------------------------------
  437. void CTFMatchmakingDashboard::OnTick()
  438. {
  439. bool bInEndOfMatch = TFGameRules() && TFGameRules()->State_Get() == GR_STATE_GAME_OVER;
  440. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules() ? TFGameRules()->GetCurrentMatchGroup() : k_nMatchGroup_Invalid );
  441. bool bShouldBeVisible = GTFGCClientSystem()->BConnectedToMatchServer( false )
  442. && bInEndOfMatch
  443. && pMatchDesc
  444. && pMatchDesc->BUsesDashboard();
  445. #ifdef STAGING_ONLY
  446. bShouldBeVisible |= tf_mm_dashboard_force_show.GetBool();
  447. #endif
  448. if ( BIsExpanded() && !bShouldBeVisible )
  449. {
  450. SetCollapsed( true );
  451. }
  452. else if ( !BIsExpanded() && bShouldBeVisible )
  453. {
  454. SetCollapsed( false );
  455. }
  456. SetKeyBoardInputEnabled( false );
  457. SetMouseInputEnabled( BIsExpanded() );
  458. }
  459. CUtlVector< IMMPopupFactory* > IMMPopupFactory::s_vecPopupFactories;
  460. //-----------------------------------------------------------------------------
  461. // Purpose: Snag the singleton CTFMatchmakingPopup
  462. //-----------------------------------------------------------------------------
  463. CUtlVector< CTFMatchmakingPopup* >& CreateMMPopupPanels( bool bRecreate /*= false*/ )
  464. {
  465. static CUtlVector< CTFMatchmakingPopup* > s_vecPopups;
  466. if ( bRecreate && s_vecPopups.Count() )
  467. {
  468. FOR_EACH_VEC( s_vecPopups, i )
  469. {
  470. s_vecPopups[ i ]->MarkForDeletion();
  471. }
  472. s_vecPopups.Purge();
  473. }
  474. if ( s_vecPopups.IsEmpty() )
  475. {
  476. FOR_EACH_VEC( IMMPopupFactory::s_vecPopupFactories, i )
  477. {
  478. s_vecPopups.AddToTail( IMMPopupFactory::s_vecPopupFactories[i]->Create() );
  479. }
  480. }
  481. return s_vecPopups;
  482. }
  483. //-----------------------------------------------------------------------------
  484. // Purpose: Snag the singleton CTFMatchmakingDashboard
  485. //-----------------------------------------------------------------------------
  486. CTFMatchmakingDashboard* GetMMDashboard()
  487. {
  488. static CTFMatchmakingDashboard* s_pDashboardPanel = NULL;
  489. if ( !s_pDashboardPanel )
  490. {
  491. s_pDashboardPanel = new CTFMatchmakingDashboard();
  492. }
  493. return s_pDashboardPanel;
  494. }