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.

648 lines
17 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include <vgui_controls/ImagePanel.h>
  9. #include <vgui_controls/RichText.h>
  10. #include <game/client/iviewport.h>
  11. #include <vgui/ILocalize.h>
  12. #include <KeyValues.h>
  13. #include <filesystem.h>
  14. #include "IGameUIFuncs.h" // for key bindings
  15. #include "inputsystem/iinputsystem.h"
  16. #include "ixboxsystem.h"
  17. #include "tf_gamerules.h"
  18. #include "tf_controls.h"
  19. #include "tf_shareddefs.h"
  20. #include "tf_mapinfomenu.h"
  21. #include "video/ivideoservices.h"
  22. using namespace vgui;
  23. const char *GetMapDisplayName( const char *mapName );
  24. //-----------------------------------------------------------------------------
  25. // Purpose: Constructor
  26. //-----------------------------------------------------------------------------
  27. CTFMapInfoMenu::CTFMapInfoMenu( IViewPort *pViewPort ) : Frame( NULL, PANEL_MAPINFO )
  28. {
  29. m_pViewPort = pViewPort;
  30. // load the new scheme early!!
  31. SetScheme( "ClientScheme" );
  32. SetTitleBarVisible( false );
  33. SetMinimizeButtonVisible( false );
  34. SetMaximizeButtonVisible( false );
  35. SetCloseButtonVisible( false );
  36. SetSizeable( false );
  37. SetMoveable( false );
  38. SetProportional( true );
  39. SetVisible( false );
  40. SetKeyBoardInputEnabled( true );
  41. m_pTitle = new CExLabel( this, "MapInfoTitle", " " );
  42. #ifdef _X360
  43. m_pFooter = new CTFFooter( this, "Footer" );
  44. #else
  45. m_pContinue = new CExButton( this, "MapInfoContinue", "#TF_Continue" );
  46. m_pBack = new CExButton( this, "MapInfoBack", "#TF_Back" );
  47. m_pIntro = new CExButton( this, "MapInfoWatchIntro", "#TF_WatchIntro" );
  48. #endif
  49. // info window about this map
  50. m_pMapInfo = new CExRichText( this, "MapInfoText" );
  51. m_pMapImage = new ImagePanel( this, "MapImage" );
  52. m_szMapName[0] = 0;
  53. }
  54. //-----------------------------------------------------------------------------
  55. // Purpose: Destructor
  56. //-----------------------------------------------------------------------------
  57. CTFMapInfoMenu::~CTFMapInfoMenu()
  58. {
  59. }
  60. //-----------------------------------------------------------------------------
  61. // Purpose:
  62. //-----------------------------------------------------------------------------
  63. void CTFMapInfoMenu::ApplySchemeSettings( vgui::IScheme *pScheme )
  64. {
  65. BaseClass::ApplySchemeSettings( pScheme );
  66. if ( ::input->IsSteamControllerActive() )
  67. {
  68. LoadControlSettings( "Resource/UI/MapInfoMenu_SC.res" );
  69. m_pContinueHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "MapInfoContinueHintIcon" ) );
  70. m_pBackHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "MapInfoBackHintIcon" ) );
  71. m_pIntroHintIcon = dynamic_cast< CSCHintIcon* >( FindChildByName( "MapInfoIntroHintIcon" ) );
  72. SetMouseInputEnabled( false );
  73. }
  74. else
  75. {
  76. LoadControlSettings( "Resource/UI/MapInfoMenu.res" );
  77. m_pContinueHintIcon = m_pBackHintIcon = m_pIntroHintIcon = nullptr;
  78. SetMouseInputEnabled( true );
  79. }
  80. CheckIntroState();
  81. CheckBackContinueButtons();
  82. char mapname[MAX_MAP_NAME];
  83. Q_FileBase( engine->GetLevelName(), mapname, sizeof(mapname) );
  84. // Save off the map name so we can re-load the page in ApplySchemeSettings().
  85. Q_strncpy( m_szMapName, mapname, sizeof( m_szMapName ) );
  86. Q_strupr( m_szMapName );
  87. #ifdef _X360
  88. char *pExt = Q_stristr( m_szMapName, ".360" );
  89. if ( pExt )
  90. {
  91. *pExt = '\0';
  92. }
  93. #endif
  94. LoadMapPage();
  95. SetMapTitle();
  96. #ifndef _X360
  97. if ( m_pContinue )
  98. {
  99. m_pContinue->RequestFocus();
  100. }
  101. #endif
  102. SetDialogVariable( "gamemode", g_pVGuiLocalize->Find( GetMapType( m_szMapName ) ) );
  103. }
  104. //-----------------------------------------------------------------------------
  105. // Purpose:
  106. //-----------------------------------------------------------------------------
  107. void CTFMapInfoMenu::ShowPanel( bool bShow )
  108. {
  109. if ( IsVisible() == bShow )
  110. return;
  111. m_KeyRepeat.Reset();
  112. if ( bShow )
  113. {
  114. InvalidateLayout( true, true ); // Force scheme reload since the steam controller state may have changed.
  115. Activate();
  116. CheckIntroState();
  117. }
  118. else
  119. {
  120. SetVisible( false );
  121. }
  122. }
  123. //-----------------------------------------------------------------------------
  124. // Purpose:
  125. //-----------------------------------------------------------------------------
  126. bool CTFMapInfoMenu::CheckForIntroMovie()
  127. {
  128. const char *pVideoFileName = TFGameRules()->GetVideoFileForMap();
  129. if ( pVideoFileName == NULL )
  130. {
  131. return false;
  132. }
  133. VideoSystem_t playbackSystem = VideoSystem::NONE;
  134. char resolvedFile[MAX_PATH];
  135. if ( g_pVideo && g_pVideo->LocatePlayableVideoFile( pVideoFileName, "GAME", &playbackSystem, resolvedFile, sizeof(resolvedFile) ) == VideoResult::SUCCESS )
  136. {
  137. return true;
  138. }
  139. return false;
  140. }
  141. const char *COM_GetModDirectory();
  142. //-----------------------------------------------------------------------------
  143. // Purpose:
  144. //-----------------------------------------------------------------------------
  145. bool CTFMapInfoMenu::HasViewedMovieForMap()
  146. {
  147. return ( UTIL_GetMapKeyCount( "viewed" ) > 0 );
  148. }
  149. //-----------------------------------------------------------------------------
  150. // Purpose:
  151. //-----------------------------------------------------------------------------
  152. void CTFMapInfoMenu::CheckIntroState()
  153. {
  154. if ( CheckForIntroMovie() && HasViewedMovieForMap() )
  155. {
  156. #ifdef _X360
  157. if ( m_pFooter )
  158. {
  159. m_pFooter->ShowButtonLabel( "intro", true );
  160. }
  161. #else
  162. if ( m_pIntro && !m_pIntro->IsVisible() )
  163. {
  164. m_pIntro->SetVisible( true );
  165. if ( m_pIntroHintIcon )
  166. {
  167. m_pIntroHintIcon->SetVisible( true );
  168. }
  169. }
  170. #endif
  171. }
  172. else
  173. {
  174. #ifdef _X360
  175. if ( m_pFooter )
  176. {
  177. m_pFooter->ShowButtonLabel( "intro", false );
  178. }
  179. #else
  180. if ( m_pIntro && m_pIntro->IsVisible() )
  181. {
  182. m_pIntro->SetVisible( false );
  183. if ( m_pIntroHintIcon )
  184. {
  185. m_pIntroHintIcon->SetVisible( false );
  186. }
  187. }
  188. #endif
  189. }
  190. }
  191. //-----------------------------------------------------------------------------
  192. // Purpose:
  193. //-----------------------------------------------------------------------------
  194. void CTFMapInfoMenu::CheckBackContinueButtons()
  195. {
  196. #ifndef _X360
  197. if ( m_pBack && m_pContinue )
  198. {
  199. if ( GetLocalPlayerTeam() == TEAM_UNASSIGNED )
  200. {
  201. m_pBack->SetVisible( true );
  202. if ( m_pBackHintIcon )
  203. {
  204. m_pBackHintIcon->SetVisible( true );
  205. }
  206. m_pContinue->SetText( "#TF_Continue" );
  207. }
  208. else
  209. {
  210. m_pBack->SetVisible( false );
  211. if ( m_pBackHintIcon )
  212. {
  213. m_pBackHintIcon->SetVisible( false );
  214. }
  215. m_pContinue->SetText( "#TF_Close" );
  216. }
  217. }
  218. #endif
  219. }
  220. //-----------------------------------------------------------------------------
  221. // Purpose:
  222. //-----------------------------------------------------------------------------
  223. void CTFMapInfoMenu::OnCommand( const char *command )
  224. {
  225. m_KeyRepeat.Reset();
  226. if ( !Q_strcmp( command, "back" ) )
  227. {
  228. // only want to go back to the Welcome menu if we're not already on a team
  229. if ( !IsX360() && ( GetLocalPlayerTeam() == TEAM_UNASSIGNED ) )
  230. {
  231. m_pViewPort->ShowPanel( this, false );
  232. m_pViewPort->ShowPanel( PANEL_INFO, true );
  233. }
  234. }
  235. else if ( !Q_strcmp( command, "continue" ) )
  236. {
  237. m_pViewPort->ShowPanel( this, false );
  238. if ( CheckForIntroMovie() && !HasViewedMovieForMap() )
  239. {
  240. m_pViewPort->ShowPanel( PANEL_INTRO, true );
  241. UTIL_IncrementMapKey( "viewed" );
  242. }
  243. else
  244. {
  245. // On console, we may already have a team due to the lobby assigning us one.
  246. // We tell the server we're done with the map info menu, and it decides what to do with us.
  247. if ( IsX360() )
  248. {
  249. engine->ClientCmd( "closedwelcomemenu" );
  250. }
  251. else if ( GetLocalPlayerTeam() == TEAM_UNASSIGNED )
  252. {
  253. if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
  254. {
  255. m_pViewPort->ShowPanel( PANEL_ARENA_TEAM, true );
  256. }
  257. else
  258. {
  259. engine->ClientCmd( "team_ui_setup" );
  260. }
  261. }
  262. UTIL_IncrementMapKey( "viewed" );
  263. }
  264. }
  265. else if ( !Q_strcmp( command, "intro" ) )
  266. {
  267. m_pViewPort->ShowPanel( this, false );
  268. if ( CheckForIntroMovie() )
  269. {
  270. m_pViewPort->ShowPanel( PANEL_INTRO, true );
  271. }
  272. else
  273. {
  274. if ( TFGameRules()->IsInArenaMode() == true && tf_arena_use_queue.GetBool() == true )
  275. {
  276. m_pViewPort->ShowPanel( PANEL_ARENA_TEAM, true );
  277. }
  278. else
  279. {
  280. engine->ClientCmd( "team_ui_setup" );
  281. }
  282. }
  283. }
  284. else
  285. {
  286. BaseClass::OnCommand( command );
  287. }
  288. }
  289. //-----------------------------------------------------------------------------
  290. // Purpose:
  291. //-----------------------------------------------------------------------------
  292. void CTFMapInfoMenu::Update()
  293. {
  294. InvalidateLayout( false, true );
  295. }
  296. //-----------------------------------------------------------------------------
  297. // Purpose: chooses and loads the text page to display that describes mapName map
  298. //-----------------------------------------------------------------------------
  299. void CTFMapInfoMenu::LoadMapPage()
  300. {
  301. if ( !m_szMapName[0] )
  302. {
  303. m_pMapInfo->SetText( "" );
  304. m_pMapImage->SetVisible( false );
  305. return;
  306. }
  307. // load the map image (if it exists for the current map)
  308. char szMapImage[ MAX_PATH ];
  309. Q_snprintf( szMapImage, sizeof( szMapImage ), "VGUI/maps/menu_photos_%s", m_szMapName );
  310. Q_strlower( szMapImage );
  311. IMaterial *pMapMaterial = materials->FindMaterial( szMapImage, TEXTURE_GROUP_VGUI, false );
  312. if ( pMapMaterial && !IsErrorMaterial( pMapMaterial ) )
  313. {
  314. if ( m_pMapImage )
  315. {
  316. if ( !m_pMapImage->IsVisible() )
  317. {
  318. m_pMapImage->SetVisible( true );
  319. }
  320. // take off the vgui/ at the beginning when we set the image
  321. Q_snprintf( szMapImage, sizeof( szMapImage ), "maps/menu_photos_%s", m_szMapName );
  322. Q_strlower( szMapImage );
  323. m_pMapImage->SetImage( szMapImage );
  324. }
  325. }
  326. else
  327. {
  328. if ( m_pMapImage && m_pMapImage->IsVisible() )
  329. {
  330. m_pMapImage->SetVisible( false );
  331. }
  332. }
  333. // try loading map descriptions from the localization files first
  334. char mapDescriptionKey[ 64 ];
  335. Q_snprintf( mapDescriptionKey, sizeof( mapDescriptionKey ), "#%s_description", m_szMapName );
  336. Q_strlower( mapDescriptionKey );
  337. wchar_t* wszMapDescription = g_pVGuiLocalize->Find( mapDescriptionKey );
  338. if( wszMapDescription )
  339. {
  340. m_pMapInfo->SetText( wszMapDescription );
  341. }
  342. else
  343. {
  344. // try loading map descriptions from .txt files first
  345. char mapRES[ MAX_PATH ];
  346. char uilanguage[ 64 ];
  347. uilanguage[0] = 0;
  348. engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
  349. Q_snprintf( mapRES, sizeof( mapRES ), "maps/%s_%s.txt", m_szMapName, uilanguage );
  350. // try English if the file doesn't exist for our language
  351. if( !g_pFullFileSystem->FileExists( mapRES, "GAME" ) )
  352. {
  353. Q_snprintf( mapRES, sizeof( mapRES ), "maps/%s_english.txt", m_szMapName );
  354. // if the file doesn't exist for English either, try the filename without any language extension
  355. if( !g_pFullFileSystem->FileExists( mapRES, "GAME" ) )
  356. {
  357. Q_snprintf( mapRES, sizeof( mapRES ), "maps/%s.txt", m_szMapName );
  358. }
  359. }
  360. // if no map specific description exists, load default text
  361. if( g_pFullFileSystem->FileExists( mapRES, "GAME" ) )
  362. {
  363. FileHandle_t f = g_pFullFileSystem->Open( mapRES, "rb" );
  364. // read into a memory block
  365. int fileSize = g_pFullFileSystem->Size(f);
  366. int dataSize = fileSize + sizeof( wchar_t );
  367. if ( dataSize % 2 )
  368. ++dataSize;
  369. wchar_t *memBlock = (wchar_t *)malloc(dataSize);
  370. memset( memBlock, 0x0, dataSize);
  371. int bytesRead = g_pFullFileSystem->Read(memBlock, fileSize, f);
  372. if ( bytesRead < fileSize )
  373. {
  374. // NULL-terminate based on the length read in, since Read() can transform \r\n to \n and
  375. // return fewer bytes than we were expecting.
  376. char *data = reinterpret_cast<char *>( memBlock );
  377. data[ bytesRead ] = 0;
  378. data[ bytesRead+1 ] = 0;
  379. }
  380. #ifndef WIN32
  381. if ( ((ucs2 *)memBlock)[0] == 0xFEFF )
  382. {
  383. // convert the win32 ucs2 data to wchar_t
  384. dataSize*=2;// need to *2 to account for ucs2 to wchar_t (4byte) growth
  385. wchar_t *memBlockConverted = (wchar_t *)malloc(dataSize);
  386. V_UCS2ToUnicode( (ucs2 *)memBlock, memBlockConverted, dataSize );
  387. free(memBlock);
  388. memBlock = memBlockConverted;
  389. }
  390. #else
  391. // null-terminate the stream (redundant, since we memset & then trimmed the transformed buffer already)
  392. memBlock[dataSize / sizeof(wchar_t) - 1] = 0x0000;
  393. #endif
  394. // check the first character, make sure this a little-endian unicode file
  395. #if defined( _X360 )
  396. if ( memBlock[0] != 0xFFFE )
  397. #else
  398. if ( memBlock[0] != 0xFEFF )
  399. #endif
  400. {
  401. // its a ascii char file
  402. m_pMapInfo->SetText( reinterpret_cast<char *>( memBlock ) );
  403. }
  404. else
  405. {
  406. // ensure little-endian unicode reads correctly on all platforms
  407. CByteswap byteSwap;
  408. byteSwap.SetTargetBigEndian( false );
  409. byteSwap.SwapBufferToTargetEndian( memBlock, memBlock, dataSize/sizeof(wchar_t) );
  410. m_pMapInfo->SetText( memBlock+1 );
  411. }
  412. // go back to the top of the text buffer
  413. m_pMapInfo->GotoTextStart();
  414. g_pFullFileSystem->Close( f );
  415. free(memBlock);
  416. }
  417. else
  418. {
  419. // try loading map descriptions from localization files next
  420. const char *pszDescription = NULL;
  421. char mapInfoKey[ 64 ];
  422. if ( TFGameRules() && TFGameRules()->IsPowerupMode() && ( FStrEq( m_szMapName, "ctf_foundry" ) || FStrEq( m_szMapName, "ctf_gorge" ) ) )
  423. {
  424. Q_snprintf( mapInfoKey, sizeof( mapInfoKey ), "#%s_beta", m_szMapName );
  425. }
  426. else
  427. {
  428. Q_snprintf( mapInfoKey, sizeof( mapInfoKey ), "#%s", m_szMapName );
  429. }
  430. Q_strlower( mapInfoKey );
  431. if( !g_pVGuiLocalize->Find( mapInfoKey ) )
  432. {
  433. if ( TFGameRules() )
  434. {
  435. if ( TFGameRules()->IsMannVsMachineMode() )
  436. {
  437. pszDescription = "#default_mvm_description";
  438. }
  439. else
  440. {
  441. switch ( TFGameRules()->GetGameType() )
  442. {
  443. case TF_GAMETYPE_CTF:
  444. pszDescription = "#default_ctf_description";
  445. break;
  446. case TF_GAMETYPE_CP:
  447. if ( TFGameRules()->IsInKothMode() )
  448. {
  449. pszDescription = "#default_koth_description";
  450. }
  451. else
  452. {
  453. pszDescription = "#default_cp_description";
  454. }
  455. break;
  456. case TF_GAMETYPE_ESCORT:
  457. if ( TFGameRules()->HasMultipleTrains() )
  458. {
  459. pszDescription = "#default_payload_race_description";
  460. }
  461. else
  462. {
  463. pszDescription = "#default_payload_description";
  464. }
  465. break;
  466. case TF_GAMETYPE_ARENA:
  467. pszDescription = "#default_arena_description";
  468. break;
  469. case TF_GAMETYPE_RD:
  470. pszDescription = "#default_rd_description";
  471. break;
  472. case TF_GAMETYPE_PASSTIME:
  473. pszDescription = "#default_passtime_description";
  474. break;
  475. case TF_GAMETYPE_PD:
  476. pszDescription = "#default_pd_description";
  477. break;
  478. }
  479. }
  480. }
  481. }
  482. else
  483. {
  484. pszDescription = mapInfoKey;
  485. }
  486. if ( pszDescription && pszDescription[0] )
  487. {
  488. m_pMapInfo->SetText( pszDescription );
  489. }
  490. else
  491. {
  492. m_pMapInfo->SetText( "" );
  493. }
  494. }
  495. }
  496. // we haven't loaded a valid map image for the current map
  497. if ( m_pMapImage && !m_pMapImage->IsVisible() )
  498. {
  499. if ( m_pMapInfo )
  500. {
  501. m_pMapInfo->SetWide( m_pMapInfo->GetWide() + ( m_pMapImage->GetWide() * 0.75 ) ); // add in the extra space the images would have taken
  502. }
  503. }
  504. }
  505. //-----------------------------------------------------------------------------
  506. // Purpose:
  507. //-----------------------------------------------------------------------------
  508. void CTFMapInfoMenu::SetMapTitle()
  509. {
  510. SetDialogVariable( "mapname", GetMapDisplayName( m_szMapName ) );
  511. }
  512. //-----------------------------------------------------------------------------
  513. // Purpose:
  514. //-----------------------------------------------------------------------------
  515. void CTFMapInfoMenu::OnKeyCodePressed( KeyCode code )
  516. {
  517. m_KeyRepeat.KeyDown( code );
  518. if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A )
  519. {
  520. OnCommand( "continue" );
  521. }
  522. else if ( code == STEAMCONTROLLER_B )
  523. {
  524. OnCommand( "back" );
  525. }
  526. else if ( code == KEY_XBUTTON_Y || code == STEAMCONTROLLER_Y )
  527. {
  528. OnCommand( "intro" );
  529. }
  530. else if( code == KEY_XBUTTON_UP || code == KEY_XSTICK1_UP || code == STEAMCONTROLLER_DPAD_UP )
  531. {
  532. // Scroll class info text up
  533. if ( m_pMapInfo )
  534. {
  535. PostMessage( m_pMapInfo, new KeyValues("MoveScrollBarDirect", "delta", 1) );
  536. }
  537. }
  538. else if( code == KEY_XBUTTON_DOWN || code == KEY_XSTICK1_DOWN || code == STEAMCONTROLLER_DPAD_DOWN )
  539. {
  540. // Scroll class info text up
  541. if ( m_pMapInfo )
  542. {
  543. PostMessage( m_pMapInfo, new KeyValues("MoveScrollBarDirect", "delta", -1) );
  544. }
  545. }
  546. else
  547. {
  548. BaseClass::OnKeyCodePressed( code );
  549. }
  550. }
  551. //-----------------------------------------------------------------------------
  552. // Purpose:
  553. //-----------------------------------------------------------------------------
  554. void CTFMapInfoMenu::OnKeyCodeReleased( vgui::KeyCode code )
  555. {
  556. m_KeyRepeat.KeyUp( code );
  557. BaseClass::OnKeyCodeReleased( code );
  558. }
  559. //-----------------------------------------------------------------------------
  560. // Purpose:
  561. //-----------------------------------------------------------------------------
  562. void CTFMapInfoMenu::OnThink()
  563. {
  564. vgui::KeyCode code = m_KeyRepeat.KeyRepeated();
  565. if ( code )
  566. {
  567. OnKeyCodePressed( code );
  568. }
  569. //Always hide the health... this needs to be done every frame because a message from the server keeps resetting this.
  570. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  571. if ( pLocalPlayer )
  572. {
  573. pLocalPlayer->m_Local.m_iHideHUD |= HIDEHUD_HEALTH;
  574. }
  575. BaseClass::OnThink();
  576. }