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.

703 lines
21 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include <KeyValues.h>
  9. #include <vgui/IVGui.h>
  10. #include <vgui/ISurface.h>
  11. #include <filesystem.h>
  12. #include <vgui_controls/AnimationController.h>
  13. #include "iclientmode.h"
  14. #include "clientmode_shared.h"
  15. #include "shareddefs.h"
  16. #include "tf_shareddefs.h"
  17. #include "tf_controls.h"
  18. #include "tf_gamerules.h"
  19. #ifdef WIN32
  20. #include "winerror.h"
  21. #endif
  22. #include "ixboxsystem.h"
  23. #include "intromenu.h"
  24. #include "tf_intromenu.h"
  25. #include "inputsystem/iinputsystem.h"
  26. // used to determine the action the intro menu should take when OnTick handles a think for us
  27. enum
  28. {
  29. INTRO_NONE,
  30. INTRO_STARTVIDEO,
  31. INTRO_BACK,
  32. INTRO_CONTINUE,
  33. };
  34. using namespace vgui;
  35. // sort function for the list of captions that we're going to show
  36. int CaptionsSort( CVideoCaption* const *p1, CVideoCaption* const *p2 )
  37. {
  38. // check the start time
  39. if ( (*p2)->m_flStartTime < (*p1)->m_flStartTime )
  40. {
  41. return 1;
  42. }
  43. return -1;
  44. }
  45. //-----------------------------------------------------------------------------
  46. // Purpose: Constructor
  47. //-----------------------------------------------------------------------------
  48. CTFIntroMenu::CTFIntroMenu( IViewPort *pViewPort ) : BaseClass( pViewPort )
  49. {
  50. m_pVideo = new CTFVideoPanel( this, "VideoPanel" );
  51. m_pModel = new CModelPanel( this, "MenuBG" );
  52. m_pCaptionLabel = new CExLabel( this, "VideoCaption", "" );
  53. #ifdef _X360
  54. m_pFooter = new CTFFooter( this, "Footer" );
  55. #else
  56. m_pBack = new CExButton( this, "Back", "" );
  57. m_pOK = new CExButton( this, "Skip", "" );
  58. m_pReplayVideo = new CExButton( this, "ReplayVideo", "" );
  59. m_pContinue = new CExButton( this, "Continue", "" );
  60. #endif
  61. m_iCurrentCaption = 0;
  62. m_flVideoStartTime = 0;
  63. m_flActionThink = -1;
  64. m_iAction = INTRO_NONE;
  65. //=============================================================================
  66. // HPE_BEGIN
  67. // [msmith] Flag for weather or not we're playing an in game video.
  68. //=============================================================================
  69. m_bPlayingInGameVideo = false;
  70. //=============================================================================
  71. // HPE_END
  72. //=============================================================================
  73. vgui::ivgui()->AddTickSignal( GetVPanel() );
  74. }
  75. //-----------------------------------------------------------------------------
  76. // Purpose: Destructor
  77. //-----------------------------------------------------------------------------
  78. CTFIntroMenu::~CTFIntroMenu()
  79. {
  80. m_Captions.PurgeAndDeleteElements();
  81. }
  82. //-----------------------------------------------------------------------------
  83. // Purpose:
  84. //-----------------------------------------------------------------------------
  85. void CTFIntroMenu::ApplySchemeSettings( IScheme *pScheme )
  86. {
  87. BaseClass::ApplySchemeSettings( pScheme );
  88. if ( ::input->IsSteamControllerActive() )
  89. {
  90. LoadControlSettings( "Resource/UI/IntroMenu_SC.res" );
  91. SetMouseInputEnabled( false );
  92. }
  93. else
  94. {
  95. LoadControlSettings( "Resource/UI/IntroMenu.res" );
  96. SetMouseInputEnabled( true );
  97. }
  98. }
  99. //-----------------------------------------------------------------------------
  100. // Purpose:
  101. //-----------------------------------------------------------------------------
  102. void CTFIntroMenu::SetNextThink( float flActionThink, int iAction )
  103. {
  104. m_flActionThink = flActionThink;
  105. m_iAction = iAction;
  106. }
  107. //-----------------------------------------------------------------------------
  108. // Purpose:
  109. //-----------------------------------------------------------------------------
  110. void CTFIntroMenu::OnTick()
  111. {
  112. // @note Tom Bui: (yuck)
  113. // in training, never show the back button
  114. // we do this late, because there's a race condition for when IsInTraining() will return true
  115. if ( m_pBack->IsVisible() && TFGameRules() && TFGameRules()->IsInTraining() )
  116. {
  117. m_pBack->SetVisible(false);
  118. }
  119. //=============================================================================
  120. // HPE_BEGIN
  121. // [msmith] Used to play a movie during a map. For training videos.
  122. //=============================================================================
  123. if ( PendingInGameVideo() && !BaseClass::IsVisible() )
  124. {
  125. m_pViewPort->ShowPanel( this, true );
  126. }
  127. //=============================================================================
  128. // HPE_END
  129. //=============================================================================
  130. // do we have anything special to do?
  131. else if ( m_flActionThink > 0 && m_flActionThink < gpGlobals->curtime )
  132. {
  133. if ( m_iAction == INTRO_STARTVIDEO )
  134. {
  135. //=============================================================================
  136. // HPE_BEGIN
  137. // [msmith] Pulled start video into a separate function.
  138. //=============================================================================
  139. StartVideo();
  140. //=============================================================================
  141. // HPE_END
  142. //=============================================================================
  143. }
  144. else if ( m_iAction == INTRO_BACK )
  145. {
  146. m_pViewPort->ShowPanel( this, false );
  147. m_pViewPort->ShowPanel( PANEL_MAPINFO, true );
  148. }
  149. else if ( m_iAction == INTRO_CONTINUE )
  150. {
  151. m_pViewPort->ShowPanel( this, false );
  152. //=============================================================================
  153. // HPE_BEGIN
  154. // [msmith] Used for the client to tell the server that we're whatching a movie or not
  155. //=============================================================================
  156. tf_training_client_message.SetValue( "" );
  157. tf_training_client_message.SetValue( TRAINING_CLIENT_MESSAGE_NONE );
  158. //=============================================================================
  159. // HPE_END
  160. //=============================================================================
  161. if ( GetLocalPlayerTeam() == TEAM_UNASSIGNED )
  162. {
  163. if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
  164. {
  165. m_pViewPort->ShowPanel( PANEL_ARENA_TEAM, true );
  166. }
  167. else if ( TFGameRules()->IsMannVsMachineMode() || TFGameRules()->IsCompetitiveMode() )
  168. {
  169. engine->ClientCmd( "autoteam" );
  170. }
  171. else
  172. {
  173. m_pViewPort->ShowPanel( PANEL_TEAM, true );
  174. }
  175. }
  176. else
  177. {
  178. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  179. // only open the class menu if they're not on team Spectator and they haven't already picked a class
  180. if ( pPlayer &&
  181. ( GetLocalPlayerTeam() != TEAM_SPECTATOR ) &&
  182. ( pPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_UNDEFINED ) )
  183. {
  184. if ( tf_arena_force_class.GetBool() == false )
  185. {
  186. switch( GetLocalPlayerTeam() )
  187. {
  188. case TF_TEAM_RED:
  189. m_pViewPort->ShowPanel( PANEL_CLASS_RED, true );
  190. break;
  191. case TF_TEAM_BLUE:
  192. m_pViewPort->ShowPanel( PANEL_CLASS_BLUE, true );
  193. break;
  194. }
  195. }
  196. }
  197. }
  198. }
  199. // reset our think
  200. SetNextThink( -1, INTRO_NONE );
  201. }
  202. // check if we need to update our captions
  203. if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() )
  204. {
  205. UpdateCaptions();
  206. }
  207. }
  208. //-----------------------------------------------------------------------------
  209. // Purpose:
  210. //-----------------------------------------------------------------------------
  211. void CTFIntroMenu::OnThink()
  212. {
  213. //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this.
  214. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  215. if ( pLocalPlayer )
  216. {
  217. pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH;
  218. }
  219. BaseClass::OnThink();
  220. }
  221. //-----------------------------------------------------------------------------
  222. // Purpose:
  223. //-----------------------------------------------------------------------------
  224. bool CTFIntroMenu::LoadCaptions( void )
  225. {
  226. bool bSuccess = false;
  227. // clear any current captions
  228. m_Captions.PurgeAndDeleteElements();
  229. m_iCurrentCaption = 0;
  230. if ( m_pCaptionLabel )
  231. {
  232. const char *szVideoFileName = GetVideoFileName( false );
  233. KeyValues *kvCaptions = NULL;
  234. char strFullpath[MAX_PATH];
  235. if ( szVideoFileName != NULL )
  236. {
  237. //=============================================================================
  238. // HPE_BEGIN
  239. // [msmith] The video may now be either a map video or an in game video.
  240. // Made a function to decide which video name to give back.
  241. //=============================================================================
  242. Q_strncpy( strFullpath, szVideoFileName, MAX_PATH ); // Assume we must play out of the media directory
  243. //=============================================================================
  244. // HPE_END
  245. //=============================================================================
  246. Q_strncat( strFullpath, ".res", MAX_PATH ); // Assume we're a .res extension type
  247. if ( g_pFullFileSystem->FileExists( strFullpath ) )
  248. {
  249. kvCaptions = new KeyValues( strFullpath );
  250. if ( kvCaptions )
  251. {
  252. if ( kvCaptions->LoadFromFile( g_pFullFileSystem, strFullpath ) )
  253. {
  254. for ( KeyValues *pData = kvCaptions->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() )
  255. {
  256. CVideoCaption *pCaption = new CVideoCaption;
  257. if ( pCaption )
  258. {
  259. pCaption->m_pszString = ReadAndAllocStringValue( pData, "string" );
  260. pCaption->m_flStartTime = pData->GetFloat( "start", 0.0 );
  261. pCaption->m_flDisplayTime = pData->GetFloat( "length", 3.0 );
  262. m_Captions.AddToTail( pCaption );
  263. // we have at least one caption to show
  264. bSuccess = true;
  265. }
  266. }
  267. }
  268. kvCaptions->deleteThis();
  269. }
  270. }
  271. }
  272. }
  273. if ( bSuccess )
  274. {
  275. // sort the captions so we show them in the correct order (they're not necessarily in order in the .res file)
  276. m_Captions.Sort( CaptionsSort );
  277. }
  278. return bSuccess;
  279. }
  280. //-----------------------------------------------------------------------------
  281. // Purpose:
  282. //-----------------------------------------------------------------------------
  283. void CTFIntroMenu::UpdateCaptions( void )
  284. {
  285. //=============================================================================
  286. // HPE_BEGIN
  287. // [msmith] Timing should be realtime when playing in game becase the curtime is paused.
  288. //=============================================================================
  289. float testTime = m_bPlayingInGameVideo ? gpGlobals->realtime : gpGlobals->curtime;
  290. //=============================================================================
  291. // HPE_END
  292. //=============================================================================
  293. if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() && ( m_Captions.Count() > 0 ) )
  294. {
  295. CVideoCaption *pCaption = m_Captions[m_iCurrentCaption];
  296. if ( pCaption )
  297. {
  298. if ( ( pCaption->m_flCaptionStart >= 0 ) && ( pCaption->m_flCaptionStart + pCaption->m_flDisplayTime < testTime ) )
  299. {
  300. // fade out the caption
  301. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "VideoCaptionFadeOut" );
  302. // move to the next caption
  303. m_iCurrentCaption++;
  304. if ( !m_Captions.IsValidIndex( m_iCurrentCaption ) )
  305. {
  306. // we're done showing captions
  307. m_pCaptionLabel->SetVisible( false );
  308. }
  309. }
  310. // is it time to show the caption?
  311. else if ( m_flVideoStartTime + pCaption->m_flStartTime < testTime )
  312. {
  313. // have we already started this video?
  314. if ( pCaption->m_flCaptionStart < 0 )
  315. {
  316. m_pCaptionLabel->SetText( pCaption->m_pszString );
  317. pCaption->m_flCaptionStart = testTime;
  318. // fade in the next caption
  319. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "VideoCaptionFadeIn" );
  320. }
  321. }
  322. }
  323. }
  324. }
  325. //-----------------------------------------------------------------------------
  326. // Purpose:
  327. //-----------------------------------------------------------------------------
  328. void CTFIntroMenu::ShowPanel( bool bShow )
  329. {
  330. //=============================================================================
  331. // HPE_BEGIN:
  332. // [msmith] Don't show the back button when in training. You can only skip intro
  333. // movies.
  334. //=============================================================================
  335. m_pBack->SetVisible(true);
  336. if ( TFGameRules() && TFGameRules()->IsInTraining() )
  337. {
  338. m_pBack->SetVisible( false );
  339. if ( PendingInGameVideo() == false )
  340. {
  341. VideoSystem_t playbackSystem = VideoSystem::NONE;
  342. char resolvedFile[MAX_PATH];
  343. if ( g_pVideo != NULL && g_pVideo->LocatePlayableVideoFile( GetVideoFileName(), "GAME", &playbackSystem, resolvedFile, sizeof(resolvedFile) ) != VideoResult::SUCCESS )
  344. {
  345. //If we have no movie, no need to show the intro screen on a training mission.
  346. bShow = false;
  347. }
  348. }
  349. }
  350. //=============================================================================
  351. // HPE_END
  352. //=============================================================================
  353. if ( BaseClass::IsVisible() == bShow )
  354. return;
  355. // reset our think
  356. SetNextThink( -1, INTRO_NONE );
  357. if ( bShow )
  358. {
  359. InvalidateLayout( true, true );
  360. Activate();
  361. if ( m_pVideo )
  362. {
  363. //=============================================================================
  364. // HPE_BEGIN
  365. // [msmith] Pulled shutting down the video into a separate function.
  366. // If we're showing an in game video, we need to enable pausing so that
  367. // we can pause the game during the video.
  368. // If we're showing an intro training movie, we also need to tell the server that
  369. // we're whatching the intro movie so that the round does not start until it's over.
  370. // If we are watching an in game video, we do NOT send a message for that because
  371. // tf_training_client_message will contain the name of the video we're watching.
  372. //=============================================================================
  373. ShutdownVideo();
  374. SetNextThink( gpGlobals->curtime + m_pVideo->GetStartDelay(), INTRO_STARTVIDEO );
  375. if ( TFGameRules() && TFGameRules()->IsInTraining() )
  376. {
  377. if ( PendingInGameVideo() )
  378. {
  379. engine->ClientCmd( "sv_pausable 1" );
  380. }
  381. else
  382. {
  383. tf_training_client_message.SetValue( TRAINING_CLIENT_MESSAGE_WATCHING_INTRO_MOVIE );
  384. }
  385. }
  386. //=============================================================================
  387. // HPE_END
  388. //=============================================================================
  389. }
  390. if ( m_pModel )
  391. {
  392. m_pModel->SetPanelDirty();
  393. }
  394. }
  395. else
  396. {
  397. Shutdown();
  398. SetVisible( false );
  399. //=============================================================================
  400. // HPE_BEGIN
  401. // [msmith] We must disable the ability to pause. If we don't, it looks like
  402. // some other function in TF2 causes the entire game to pause if sv_pausable is enabled.
  403. //=============================================================================
  404. if ( TFGameRules() && TFGameRules()->IsInTraining() )
  405. {
  406. engine->ClientCmd( "sv_pausable 0" );
  407. }
  408. //=============================================================================
  409. // HPE_END
  410. //=============================================================================
  411. }
  412. }
  413. //-----------------------------------------------------------------------------
  414. // Purpose:
  415. //-----------------------------------------------------------------------------
  416. void CTFIntroMenu::OnIntroFinished( void )
  417. {
  418. // in training we want to give the user the ability to replay the movie
  419. if ( TFGameRules() && TFGameRules()->IsInTraining() )
  420. {
  421. m_pReplayVideo->SetVisible( true );
  422. m_pContinue->SetVisible( true );
  423. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "IntroMovieContinueBlink" );
  424. m_pOK->SetVisible( false );
  425. }
  426. else
  427. {
  428. float flTime = gpGlobals->curtime;
  429. if ( m_pModel && m_pModel->SetSequence( "UpSlow" ) )
  430. {
  431. // wait for the model sequence to finish before going to the next menu
  432. flTime = gpGlobals->curtime + m_pVideo->GetEndDelay();
  433. }
  434. Shutdown();
  435. SetNextThink( flTime, INTRO_CONTINUE );
  436. }
  437. }
  438. //-----------------------------------------------------------------------------
  439. // Purpose:
  440. //-----------------------------------------------------------------------------
  441. void CTFIntroMenu::OnCommand( const char *command )
  442. {
  443. if ( !Q_strcmp( command, "back" ) )
  444. {
  445. float flTime = gpGlobals->curtime;
  446. Shutdown();
  447. // try to play the screenup sequence
  448. if ( m_pModel && m_pModel->SetSequence( "Up" ) )
  449. {
  450. flTime = gpGlobals->curtime + 0.35f;
  451. }
  452. // wait for the model sequence to finish before going back to the mapinfo menu
  453. SetNextThink( flTime, INTRO_BACK );
  454. }
  455. else if ( !Q_strcmp( command, "skip" ) )
  456. {
  457. Shutdown();
  458. // continue right now
  459. SetNextThink( gpGlobals->curtime, INTRO_CONTINUE );
  460. }
  461. else if ( !Q_strcmp( command, "replayVideo" ) )
  462. {
  463. ShutdownVideo();
  464. SetNextThink( gpGlobals->curtime, INTRO_STARTVIDEO );
  465. }
  466. else
  467. {
  468. BaseClass::OnCommand( command );
  469. }
  470. }
  471. //-----------------------------------------------------------------------------
  472. // Purpose:
  473. //-----------------------------------------------------------------------------
  474. void CTFIntroMenu::OnKeyCodePressed( KeyCode code )
  475. {
  476. if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A )
  477. {
  478. OnCommand( "skip" );
  479. }
  480. else if ( code == KEY_XBUTTON_B || code == STEAMCONTROLLER_B )
  481. {
  482. OnCommand( "back" );
  483. }
  484. else
  485. {
  486. BaseClass::OnKeyCodePressed( code );
  487. }
  488. }
  489. //-----------------------------------------------------------------------------
  490. // Purpose:
  491. //-----------------------------------------------------------------------------
  492. void CTFIntroMenu::Shutdown( void )
  493. {
  494. //=============================================================================
  495. // HPE_BEGIN
  496. // [msmith] Refactored the shutdown video logic into a containing function.
  497. //=============================================================================
  498. ShutdownVideo();
  499. //=============================================================================
  500. // HPE_END
  501. //=============================================================================
  502. if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() )
  503. {
  504. m_pCaptionLabel->SetVisible( false );
  505. }
  506. m_iCurrentCaption = 0;
  507. m_flVideoStartTime = 0;
  508. }
  509. //=============================================================================
  510. // HPE_BEGIN
  511. // [msmith] New helper functions
  512. //=============================================================================
  513. void CTFIntroMenu::ShutdownVideo()
  514. {
  515. if ( m_pVideo )
  516. {
  517. m_pVideo->Shutdown(); // make sure we're not currently running
  518. }
  519. //Make sure we unpause the game if it was paused from an in game play of a video.
  520. if ( m_bPlayingInGameVideo )
  521. {
  522. UnpauseGame();
  523. }
  524. m_bPlayingInGameVideo = false;
  525. }
  526. bool CTFIntroMenu::PendingInGameVideo( void )
  527. {
  528. if ( TFGameRules() && TFGameRules()->IsInTraining() )
  529. {
  530. //If the message is a string, it's a video name.
  531. return strlen( tf_training_client_message.GetString() ) > 3;
  532. }
  533. return false;
  534. }
  535. const char *CTFIntroMenu::GetVideoFileName( bool withExtension )
  536. {
  537. if ( PendingInGameVideo() )
  538. {
  539. return TFGameRules()->FormatVideoName( tf_training_client_message.GetString(), withExtension );
  540. }
  541. if ( TFGameRules() && TFGameRules()->IsInTraining() )
  542. {
  543. ConVarRef training_map_video("training_map_video");
  544. if ( strlen( training_map_video.GetString() ) > 3 )
  545. {
  546. return TFGameRules()->FormatVideoName( training_map_video.GetString(), withExtension );
  547. }
  548. }
  549. return TFGameRules()->GetVideoFileForMap( withExtension );
  550. }
  551. void CTFIntroMenu::StartVideo()
  552. {
  553. m_pOK->SetVisible( true );
  554. m_pReplayVideo->SetVisible( false );
  555. m_pContinue->SetVisible( false );
  556. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "IntroMovieContinueBlinkStop" );
  557. if ( m_pVideo )
  558. {
  559. // turn on the captions if we have them
  560. if ( LoadCaptions() )
  561. {
  562. if ( m_pCaptionLabel && !m_pCaptionLabel->IsVisible() )
  563. {
  564. m_pCaptionLabel->SetText( " " );
  565. m_pCaptionLabel->SetVisible( true );
  566. //Make sure the label is fully faded in when starting to play.
  567. //It could have been faded out from a prior animation event form an animation effect in a previous video instance.
  568. m_pCaptionLabel->SetAlpha( 255 );
  569. }
  570. }
  571. else
  572. {
  573. if ( m_pCaptionLabel && m_pCaptionLabel->IsVisible() )
  574. {
  575. m_pCaptionLabel->SetVisible( false );
  576. }
  577. }
  578. m_pVideo->Activate();
  579. if ( PendingInGameVideo() )
  580. {
  581. m_pVideo->BeginPlayback( GetVideoFileName() );
  582. PauseGame();
  583. m_bPlayingInGameVideo = true;
  584. //Since we have started playing the video, we can reset the message string to empty.
  585. tf_training_client_message.SetValue( "" );
  586. }
  587. else
  588. {
  589. m_pVideo->BeginPlayback( GetVideoFileName() );
  590. }
  591. m_pVideo->MoveToFront();
  592. m_flVideoStartTime = m_bPlayingInGameVideo ? gpGlobals->realtime : gpGlobals->curtime;
  593. }
  594. }
  595. void CTFIntroMenu::UnpauseGame( void )
  596. {
  597. if ( TFGameRules() && TFGameRules()->IsInTraining() )
  598. {
  599. engine->ClientCmd( "unpause" );
  600. }
  601. }
  602. void CTFIntroMenu::PauseGame( void )
  603. {
  604. if ( TFGameRules() && TFGameRules()->IsInTraining() )
  605. {
  606. engine->ClientCmd( "pause" );
  607. }
  608. }
  609. //=============================================================================
  610. // HPE_END
  611. //=============================================================================