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.

1182 lines
37 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "quest_log_panel.h"
  8. #include "ienginevgui.h"
  9. #include "c_tf_gamestats.h"
  10. #include "store/store_panel.h"
  11. #include "econ/econ_ui.h"
  12. #include "clientmode_tf.h"
  13. #include "tf_hud_mainmenuoverride.h"
  14. #include "vgui_int.h"
  15. #include "IGameUIFuncs.h" // for key bindings
  16. #include <vgui_controls/AnimationController.h>
  17. #include "tf_item_inventory.h"
  18. #include "vgui/IInput.h"
  19. #include "item_ad_panel.h"
  20. #include "vgui_controls/ProgressBar.h"
  21. // memdbgon must be the last include file in a .cpp file!!!
  22. #include <tier0/memdbgon.h>
  23. void AddSubKeyNamed( KeyValues *pKeys, const char *pszName );
  24. static CItemModelPanelToolTip* g_spItemTooltip = NULL;
  25. CQuestTooltip* g_spTextTooltip = NULL;
  26. CQuestLogPanel *GetQuestLog()
  27. {
  28. CQuestLogPanel *pQuestLogPanel = (CQuestLogPanel*)gViewPortInterface->FindPanelByName( PANEL_QUEST_LOG );
  29. return pQuestLogPanel;
  30. }
  31. //-------------------------------------------------------------------------------------------------------------------
  32. //-----------------------------------------------------------------------------
  33. // Purpose:
  34. //-----------------------------------------------------------------------------
  35. CScrollableQuestList::CScrollableQuestList( vgui::Panel *parent, const char *pszPanelName )
  36. : EditablePanel( parent, pszPanelName )
  37. , m_pCompletingPanel( NULL )
  38. , m_bQuestsLayoutDirty( false )
  39. , m_pszNoQuests( NULL )
  40. , m_pszNeedAPass( NULL )
  41. , m_pszNotPossible( NULL )
  42. {
  43. m_pContainer = new EditablePanel( this, "Container" );
  44. m_vecQuestItemPanels.SetSize( 2 );
  45. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  46. {
  47. m_vecQuestItemPanels[ i ] = new CQuestItemPanel( m_pContainer, "QuestItemPanel", NULL, this );
  48. }
  49. }
  50. CScrollableQuestList::~CScrollableQuestList()
  51. {
  52. }
  53. //-----------------------------------------------------------------------------
  54. // Purpose:
  55. //-----------------------------------------------------------------------------
  56. void CScrollableQuestList::ApplySchemeSettings( vgui::IScheme *pScheme )
  57. {
  58. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  59. BaseClass::ApplySchemeSettings( pScheme );
  60. const char *pszResFile = "Resource/UI/econ/ScrollableQuestList.res";
  61. // Check if the operation wants to override our default res file
  62. const auto& mapOperations = GetItemSchema()->GetOperationDefinitions();
  63. FOR_EACH_MAP_FAST( mapOperations, i )
  64. {
  65. CEconOperationDefinition* pCurrentOperation = mapOperations[i];
  66. // Take the first active operation's res file that's different than default
  67. if ( pCurrentOperation->IsActive() && pCurrentOperation->GetQuestListOverrideResFile() )
  68. {
  69. // Use the first found for now
  70. pszResFile = pCurrentOperation->GetQuestListOverrideResFile();
  71. break;
  72. }
  73. }
  74. LoadControlSettings( pszResFile );
  75. }
  76. //-----------------------------------------------------------------------------
  77. // Purpose:
  78. //-----------------------------------------------------------------------------
  79. void CScrollableQuestList::ApplySettings( KeyValues *inResourceData )
  80. {
  81. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  82. BaseClass::ApplySettings( inResourceData );
  83. m_pszNoQuests = inResourceData->GetString( "no_quests", "#QuestLog_NoQuests" );
  84. m_pszNeedAPass = inResourceData->GetString( "need_a_pass", "#QuestLog_NeedPassForContracts" );
  85. m_pszNotPossible = inResourceData->GetString( "not_possible", "#QuestLog_NoContractsPossible" );
  86. }
  87. //-----------------------------------------------------------------------------
  88. // Purpose:
  89. //-----------------------------------------------------------------------------
  90. void CScrollableQuestList::PerformLayout( void )
  91. {
  92. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  93. BaseClass::PerformLayout();
  94. m_pContainer->InvalidateLayout( true );
  95. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  96. {
  97. m_vecQuestItemPanels[ i ]->InvalidateLayout( true, true );
  98. m_vecQuestItemPanels[ i ]->SetZPos( 1 + i );
  99. }
  100. PositionQuestItemPanels();
  101. UpdateEmptyMessage();
  102. }
  103. //-----------------------------------------------------------------------------
  104. // Purpose:
  105. //-----------------------------------------------------------------------------
  106. void CScrollableQuestList::OnThink()
  107. {
  108. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  109. if ( m_bQuestsLayoutDirty )
  110. {
  111. m_bQuestsLayoutDirty = false;
  112. PositionQuestItemPanels();
  113. }
  114. // Conditionally turn mouse input on/off based on where the mouse is
  115. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  116. {
  117. m_vecQuestItemPanels[i]->SetMouseInputEnabled( m_vecQuestItemPanels[i]->IsCursorOverMainContainer() );
  118. }
  119. }
  120. void CScrollableQuestList::OnCommand( const char *command )
  121. {
  122. if ( FStrEq( command, "deselect_all" ) )
  123. {
  124. SetSelected( NULL, false );
  125. }
  126. BaseClass::OnCommand( command );
  127. }
  128. int QuestSort_AcquiredTime( CQuestItemPanel* const* p1, CQuestItemPanel* const* p2 )
  129. {
  130. if ( !(*p1)->GetItem() )
  131. return -1;
  132. if ( !(*p2)->GetItem() )
  133. return 1;
  134. // Newest items first
  135. return (*p1)->GetItem()->GetID() - (*p2)->GetItem()->GetID();
  136. }
  137. //-----------------------------------------------------------------------------
  138. // Purpose:
  139. //-----------------------------------------------------------------------------
  140. void CScrollableQuestList::PositionQuestItemPanels()
  141. {
  142. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  143. // We dont do anything when a quest is completing
  144. if ( m_pCompletingPanel != NULL )
  145. return;
  146. int nVisible = 0;
  147. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  148. {
  149. CQuestItemPanel* pPanel = m_vecQuestItemPanels[i];
  150. if ( pPanel )
  151. {
  152. pPanel->SetVisible( pPanel->GetItem() );
  153. if ( pPanel->GetItem() )
  154. {
  155. ++nVisible;
  156. }
  157. }
  158. }
  159. CExLabel *pLabel = FindControl<CExLabel>( "EmptyLabel", true );
  160. if ( pLabel )
  161. {
  162. pLabel->SetVisible( nVisible == 0 );
  163. }
  164. // Check for a selected panel
  165. const CQuestItemPanel* pSelected = NULL;
  166. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  167. {
  168. if ( m_vecQuestItemPanels[i]->IsSelected() )
  169. {
  170. Assert( pSelected == NULL );
  171. pSelected = m_vecQuestItemPanels[i];
  172. }
  173. }
  174. struct FolderCommands_t
  175. {
  176. const char* m_pszSelected;
  177. const char* m_pszOtherIsSelected;
  178. const char* m_pszNoneSelected;
  179. };
  180. const FolderCommands_t folderCommands[] = { { "QuestItem_Back_Selected", "QuestItem_Back_OtherSelected", "QuestItem_Back_NoneSelected" }
  181. , { "QuestItem_Front_Selected", "QuestItem_Front_OtherSelected", "QuestItem_Front_NoneSelected" } };
  182. // Update the positions
  183. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  184. {
  185. // This is the selected panel
  186. if ( pSelected == m_vecQuestItemPanels[ i ] )
  187. {
  188. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszSelected );
  189. }
  190. else if ( pSelected ) // Some other panel is selected
  191. {
  192. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszOtherIsSelected );
  193. }
  194. else // No panel is selected
  195. {
  196. g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszNoneSelected );
  197. }
  198. }
  199. }
  200. //-----------------------------------------------------------------------------
  201. // Purpose:
  202. //-----------------------------------------------------------------------------
  203. void CScrollableQuestList::SetSelected( CQuestItemPanel *pItem, bool bImmediately )
  204. {
  205. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  206. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  207. {
  208. m_vecQuestItemPanels[ i ]->SetSelected( m_vecQuestItemPanels[ i ] == pItem, bImmediately );
  209. }
  210. PositionQuestItemPanels();
  211. }
  212. bool DoesLootlistDropQuests( const CEconLootListDefinition* pLootList )
  213. {
  214. FOR_EACH_VEC( pLootList->GetLootListContents(), j )
  215. {
  216. const CEconLootListDefinition::drop_item_t& item = pLootList->GetLootListContents()[j];
  217. // 0 and greater means item. Less than 0 means nested lootlist
  218. if( item.m_iItemOrLootlistDef >= 0 )
  219. {
  220. const GameItemDefinition_t* pItemDef = assert_cast<const GameItemDefinition_t *>( GetItemSchema()->GetItemDefinition( item.m_iItemOrLootlistDef ) );
  221. if( pItemDef )
  222. {
  223. return pItemDef->GetQuestDef();
  224. }
  225. }
  226. else
  227. {
  228. // Get the nested lootlist
  229. int iLLIndex = (item.m_iItemOrLootlistDef * -1) - 1;
  230. const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex );
  231. Assert( pNestedLootList );
  232. if ( !pNestedLootList )
  233. continue;
  234. // Dig through all of this lootlist's entries
  235. return DoesLootlistDropQuests( pNestedLootList );
  236. }
  237. }
  238. return false;
  239. }
  240. //-----------------------------------------------------------------------------
  241. // Purpose: Update what message we show when we have no quests
  242. //-----------------------------------------------------------------------------
  243. void CScrollableQuestList::UpdateEmptyMessage()
  244. {
  245. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  246. CCyclingAdContainerPanel* pPassStoreAd = FindControl< CCyclingAdContainerPanel >( "ItemAd", true );
  247. if ( !pPassStoreAd )
  248. return;
  249. pPassStoreAd->SetVisible( false );
  250. SetDialogVariable( "noquests", "" );
  251. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  252. {
  253. // Case 1 above
  254. if ( m_vecQuestItemPanels[ i ]->GetItem() )
  255. return;
  256. }
  257. // By default, there's no operations going on, and there's no ad to show
  258. const char *pszNoQuestsText = m_pszNotPossible;
  259. // Find any active quest-dropping operations
  260. const auto& mapOperations = GetItemSchema()->GetOperationDefinitions();
  261. FOR_EACH_MAP_FAST( mapOperations, iOperation )
  262. {
  263. CEconOperationDefinition *pOperation = mapOperations[ iOperation ];
  264. const CSchemaLootListDefHandle pOperationLootlist( pOperation->GetOperationLootlist() );
  265. // Must still be dropping, and be dropping quests
  266. if ( CRTime::RTime32TimeCur() < pOperation->GetStopGivingToPlayerDate() && pOperationLootlist && DoesLootlistDropQuests( pOperationLootlist ) )
  267. {
  268. // If there's a required item and a gateway item
  269. if ( pOperation->GetRequiredItemDefIndex() != INVALID_ITEM_DEF_INDEX && pOperation->GetGatewayItemDefIndex() != INVALID_ITEM_DEF_INDEX )
  270. {
  271. // And the user doesn't have the required item
  272. if ( TFInventoryManager()->GetLocalTFInventory()->FindFirstItembyItemDef( pOperation->GetRequiredItemDefIndex() ) == NULL )
  273. {
  274. // The user needs to get the item
  275. pszNoQuestsText = m_pszNeedAPass;
  276. bool bStoreIsReady = EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetPriceSheet() && EconUI()->GetStorePanel()->GetCart() && steamapicontext && steamapicontext->SteamUser();
  277. bool bGatewayItemInStore = false;
  278. // Check if the gateway item is in the Mann Co Store
  279. if ( bStoreIsReady )
  280. {
  281. bGatewayItemInStore = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( pOperation->GetGatewayItemDefIndex() ) != NULL;
  282. }
  283. CEconItemDefinition* pGatewayItemDef = GetItemSchema()->GetItemDefinition( pOperation->GetGatewayItemDefIndex() );
  284. Assert( pGatewayItemDef );
  285. if ( !pGatewayItemDef )
  286. return;
  287. // Cook up KVs for this item ad
  288. KeyValuesAD pKVItemAd( "items" ); // The panel will copy these
  289. KeyValues* pKVItem = pKVItemAd->CreateNewKey();
  290. pKVItem->SetName( "0" );
  291. pKVItem->SetString( "item", pGatewayItemDef->GetDefinitionName() );
  292. pKVItem->SetInt( "show_market", bGatewayItemInStore ? 0 : 1 );
  293. pPassStoreAd->SetVisible( true );
  294. pPassStoreAd->SetItemKVs( pKVItemAd );
  295. // This is the most important thing to communicate. Don't let other operations stomp it.
  296. break;
  297. }
  298. }
  299. pszNoQuestsText = m_pszNoQuests;
  300. // Don't break. Give more important operations a chance to present
  301. }
  302. }
  303. SetDialogVariable( "noquests", g_pVGuiLocalize->Find( pszNoQuestsText ) );
  304. }
  305. //-----------------------------------------------------------------------------
  306. // Purpose:
  307. //-----------------------------------------------------------------------------
  308. void CScrollableQuestList::PopulateQuestLists()
  309. {
  310. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  311. // We dont do anything when a quest is completing
  312. if ( m_pCompletingPanel != NULL )
  313. return;
  314. DirtyQuestLayout();
  315. CUtlVector< CEconItemView * > vecQuestItems;
  316. TFInventoryManager()->GetAllQuestItems( &vecQuestItems );
  317. CUtlVector< CQuestItemPanel* > vecAvailablePanels;
  318. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  319. {
  320. bool bFound = false;
  321. if ( m_vecQuestItemPanels[ i ]->GetItem() )
  322. {
  323. FOR_EACH_VEC_BACK( vecQuestItems, j )
  324. {
  325. // See if the item in the panel is still in the list of items we own
  326. if ( m_vecQuestItemPanels[ i ]->GetItem()->GetOriginalID() == vecQuestItems[ j ]->GetOriginalID() )
  327. {
  328. // Refresh it
  329. m_vecQuestItemPanels[ i ]->InvalidateLayout();
  330. vecQuestItems.Remove( j );
  331. bFound = true;
  332. break;
  333. }
  334. }
  335. }
  336. // Didn't find it. Clear it out
  337. if ( !bFound )
  338. {
  339. if ( m_vecQuestItemPanels[ i ]->GetItem() )
  340. {
  341. m_vecQuestItemPanels[ i ]->SetItem( NULL );
  342. }
  343. if ( m_vecQuestItemPanels[ i ]->IsSelected() )
  344. {
  345. SetSelected( m_vecQuestItemPanels[ i ], false );
  346. }
  347. vecAvailablePanels.AddToTail( m_vecQuestItemPanels[ i ] );
  348. }
  349. }
  350. for ( int i = 0 ; i < vecQuestItems.Count(); ++i )
  351. {
  352. CEconItemView *pItem = vecQuestItems[i];
  353. if ( i < vecAvailablePanels.Count() )
  354. {
  355. vecAvailablePanels[ i ]->SetItem( pItem );
  356. }
  357. else if ( i >= m_vecQuestItemPanels.Count() )
  358. {
  359. Assert( !"Ran out of quest panels!" );
  360. }
  361. }
  362. // Sort the panels to make sure they're in order
  363. m_vecQuestItemPanels.Sort( &QuestSort_AcquiredTime );
  364. // Sorting is done manually
  365. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  366. {
  367. m_vecQuestItemPanels[ i ]->SetZPos( i + 1 );
  368. }
  369. PositionQuestItemPanels();
  370. }
  371. //-----------------------------------------------------------------------------
  372. // Purpose:
  373. //-----------------------------------------------------------------------------
  374. void CScrollableQuestList::QuestCompletedResponse()
  375. {
  376. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  377. {
  378. m_vecQuestItemPanels[i]->QuestCompletedResponse();
  379. }
  380. // PopulateQuestLists();
  381. }
  382. //-----------------------------------------------------------------------------
  383. // Purpose: Return true if any quest item panels are in the passed in state
  384. //-----------------------------------------------------------------------------
  385. bool CScrollableQuestList::AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const
  386. {
  387. FOR_EACH_VEC( m_vecQuestItemPanels, i )
  388. {
  389. if ( m_vecQuestItemPanels[i]->GetState() == eState )
  390. return true;
  391. }
  392. return false;
  393. }
  394. //-------------------------------------------------------------------------------------------------------------------
  395. //-----------------------------------------------------------------------------
  396. // Purpose:
  397. //-----------------------------------------------------------------------------
  398. CQuestLogPanel::CQuestLogPanel( IViewPort *pViewPort )
  399. : EditablePanel( NULL, PANEL_QUEST_LOG )
  400. , m_bWaitingForComplete( false )
  401. , m_pQuestList( NULL )
  402. , m_bInventoryDirty( true )
  403. , m_iQuestLogKey( BUTTON_CODE_INVALID )
  404. {
  405. if (g_pVGuiLocalize)
  406. {
  407. g_pVGuiLocalize->AddFile( "resource/tf_quests_%language%.txt" );
  408. }
  409. vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
  410. SetScheme(scheme);
  411. SetProportional( true );
  412. ListenForGameEvent( "inventory_updated" );
  413. ListenForGameEvent( "gameui_hidden" );
  414. ListenForGameEvent( "gc_connected" );
  415. // Create the item model panel tooltip
  416. m_pMouseOverItemPanel = new CItemModelPanel( this, "mouseoveritempanel" );
  417. m_pMouseOverTooltip = new CItemModelPanelToolTip( this );
  418. m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel );
  419. // Create the text tooltip
  420. m_pToolTip = new CQuestTooltip( this );
  421. m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" );
  422. m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false );
  423. m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false );
  424. // m_pToolTipEmbeddedPanel->MakePopup();
  425. m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel );
  426. m_pToolTip->SetTooltipDelay( 0 );
  427. EditablePanel *pMainContainer = new EditablePanel( this, "MainContainer" );
  428. m_pQuestList = new CScrollableQuestList( pMainContainer, "QuestList" );
  429. m_pProgressPanel = new EditablePanel( this, "ProgressPanel" );
  430. m_pDebugButton = new CExButton( pMainContainer, "Options", "Options", this, "open_debug_menu" );
  431. }
  432. //-----------------------------------------------------------------------------
  433. // Purpose: Look into the moused-over panel and take "tiptext" from its dialog
  434. // variables and set it as our own.
  435. //-----------------------------------------------------------------------------
  436. void CQuestTooltip::ShowTooltip( Panel *pCurrentPanel )
  437. {
  438. EditablePanel* pEditableCurrentPanel = dynamic_cast< EditablePanel* >( pCurrentPanel );
  439. if ( pEditableCurrentPanel )
  440. {
  441. KeyValues* pKVVariables = pEditableCurrentPanel->GetDialogVariables();
  442. const wchar_t *pwszTipText = pKVVariables->GetWString( "tiptext", L"" );
  443. m_pEmbeddedPanel->SetDialogVariable( "tiptext", pwszTipText );
  444. }
  445. BaseClass::ShowTooltip( pCurrentPanel );
  446. }
  447. //-----------------------------------------------------------------------------
  448. // Purpose: Position ourselves down and to the right as far as posible
  449. //-----------------------------------------------------------------------------
  450. void CQuestTooltip::PositionWindow( Panel *pTipPanel )
  451. {
  452. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  453. int iTipW, iTipH;
  454. pTipPanel->GetSize( iTipW, iTipH );
  455. int cursorX, cursorY;
  456. vgui::input()->GetCursorPos(cursorX, cursorY);
  457. int px, py, wide, tall;
  458. ipanel()->GetAbsPos( m_pEmbeddedPanel->GetParent()->GetVPanel(), px, py );
  459. m_pEmbeddedPanel->GetParent()->GetSize(wide, tall);
  460. if ( !m_pEmbeddedPanel->IsPopup() )
  461. {
  462. // Move the cursor into our parent space
  463. cursorX -= px;
  464. cursorY -= py;
  465. }
  466. // Dangle as far down and as far right as possible
  467. int nXPos = cursorX - Max( 0, ( ( iTipW + cursorX ) - wide ) );
  468. int nYPos = ( cursorY + 20 )- Max( 0, ( ( iTipH + cursorY + 20 ) - tall ) ) ;
  469. pTipPanel->SetPos( nXPos, nYPos );
  470. }
  471. //-----------------------------------------------------------------------------
  472. // Purpose:
  473. //-----------------------------------------------------------------------------
  474. CQuestLogPanel::~CQuestLogPanel()
  475. {
  476. }
  477. //-----------------------------------------------------------------------------
  478. // Purpose:
  479. //-----------------------------------------------------------------------------
  480. void CQuestLogPanel::AttachToGameUI( void )
  481. {
  482. C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_open", "quest_log_panel" );
  483. if ( GetClientModeTFNormal()->GameUI() )
  484. {
  485. GetClientModeTFNormal()->GameUI()->SetMainMenuOverride( GetVPanel() );
  486. }
  487. SetKeyBoardInputEnabled( true );
  488. SetMouseInputEnabled( true );
  489. SetCursor(dc_arrow);
  490. }
  491. //-----------------------------------------------------------------------------
  492. // Purpose:
  493. //-----------------------------------------------------------------------------
  494. const char *CQuestLogPanel::GetName( void )
  495. {
  496. return PANEL_QUEST_LOG;
  497. }
  498. //-----------------------------------------------------------------------------
  499. // Purpose:
  500. //-----------------------------------------------------------------------------
  501. void CQuestLogPanel::ApplySchemeSettings( IScheme *pScheme )
  502. {
  503. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  504. BaseClass::ApplySchemeSettings( pScheme );
  505. const char *pszResFile = "Resource/UI/econ/QuestLogPanel.res";
  506. // Check if the operation wants to override our default res file
  507. const auto& mapOperations = GetItemSchema()->GetOperationDefinitions();
  508. FOR_EACH_MAP_FAST( mapOperations, i )
  509. {
  510. CEconOperationDefinition* pOperation = mapOperations[i];
  511. if ( pOperation->IsActive() && pOperation->IsCampaign() )
  512. {
  513. // Use the first found for now
  514. if ( pOperation->GetQuestLogOverrideResFile() )
  515. {
  516. pszResFile = pOperation->GetQuestLogOverrideResFile();
  517. }
  518. break;
  519. }
  520. }
  521. LoadControlSettings( pszResFile );
  522. g_spItemTooltip = m_pMouseOverTooltip;
  523. g_spTextTooltip = m_pToolTip;
  524. // The outer dim / close button
  525. {
  526. Button* pButton = FindControl<Button>( "OutsideCloseButton" );
  527. if ( pButton )
  528. {
  529. pButton->AddActionSignalTarget( this );
  530. pButton->SetPaintBackgroundEnabled( false );
  531. }
  532. }
  533. m_pQuestList->InvalidateLayout( false, true );
  534. Assert( m_pQuestList );
  535. }
  536. //-----------------------------------------------------------------------------
  537. // Purpose:
  538. //-----------------------------------------------------------------------------
  539. void CQuestLogPanel::PerformLayout()
  540. {
  541. BaseClass::PerformLayout();
  542. if ( GetUniverse() != k_EUniversePublic )
  543. {
  544. int x, y;
  545. m_pDebugButton->GetParent()->GetPos( x, y );
  546. int w, h;
  547. m_pDebugButton->GetParent()->GetSize( w, h );
  548. m_pDebugButton->SizeToContents();
  549. m_pDebugButton->SetVisible( true );
  550. m_pDebugButton->SetPos( x + w - m_pDebugButton->GetWide() - 60, y + 15 );
  551. m_pDebugButton->SetZPos( 1000 );
  552. }
  553. else
  554. {
  555. m_pDebugButton->SetVisible( false );
  556. }
  557. UpdateQuestsItemPanels();
  558. }
  559. //-----------------------------------------------------------------------------
  560. // Purpose:
  561. //-----------------------------------------------------------------------------
  562. void CQuestLogPanel::OnCommand( const char *pCommand )
  563. {
  564. if ( FStrEq( pCommand, "close" ) )
  565. {
  566. if ( enginevgui->IsGameUIVisible() )
  567. {
  568. ShowPanel( false );
  569. }
  570. else
  571. {
  572. IViewPortPanel *pQuestLog = ( gViewPortInterface->FindPanelByName( PANEL_QUEST_LOG ) );
  573. if ( pQuestLog )
  574. {
  575. gViewPortInterface->ShowPanel( pQuestLog, false );
  576. }
  577. }
  578. }
  579. else if ( Q_stricmp( "open_debug_menu", pCommand ) == 0 )
  580. {
  581. if ( GetUniverse() == k_EUniverseBeta || GetUniverse() == k_EUniverseDev )
  582. {
  583. const char *pszContextMenuBorder = "NotificationDefault";
  584. const char *pszContextMenuFont = "HudFontMediumSecondary";
  585. Menu *pContextMenu = new Menu( this, "ContextMenu" );
  586. pContextMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
  587. pContextMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
  588. MenuBuilder contextMenuBuilder( pContextMenu, this );
  589. const auto& mapOperations = GetItemSchema()->GetOperationDefinitions();
  590. FOR_EACH_MAP_FAST( mapOperations, iOperation )
  591. {
  592. bool bHasAnyQuests = false;
  593. Menu* pOperationSubMenu = NULL;
  594. CEconOperationDefinition *pOperation = mapOperations[ iOperation ];
  595. const CEconLootListDefinition *pLootListDef = GetItemSchema()->GetLootListByName( pOperation->GetOperationLootlist() );
  596. if ( pLootListDef )
  597. {
  598. auto& vecContents = pLootListDef->GetLootListContents();
  599. FOR_EACH_VEC( vecContents, i )
  600. {
  601. if ( vecContents[i].m_iItemOrLootlistDef > 0 )
  602. {
  603. const GameItemDefinition_t* pItemDef = (GameItemDefinition_t*)GetItemSchema()->GetItemDefinition( vecContents[i].m_iItemOrLootlistDef );
  604. if ( pItemDef && pItemDef->GetQuestDef() )
  605. {
  606. if ( !bHasAnyQuests )
  607. {
  608. bHasAnyQuests = true;
  609. pOperationSubMenu = new Menu( this, "OperationSubMenu" );
  610. pOperationSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
  611. pOperationSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
  612. contextMenuBuilder.AddCascadingMenuItem( pOperation->GetName(), pOperationSubMenu, "operations" );
  613. }
  614. pOperationSubMenu->AddMenuItem( pItemDef->GetItemBaseName(), CFmtStr( "give%s", pItemDef->GetDefinitionName() ), this );
  615. }
  616. }
  617. }
  618. }
  619. }
  620. bool bHasAnyQuests = false;
  621. Menu* pAllSubMenu = NULL;
  622. FOR_EACH_MAP_FAST( GetItemSchema()->GetItemDefinitionMap(), i )
  623. {
  624. const GameItemDefinition_t* pItemDef = (GameItemDefinition_t*)GetItemSchema()->GetItemDefinitionMap()[ i ];
  625. if ( pItemDef->GetQuestDef() )
  626. {
  627. if ( !bHasAnyQuests )
  628. {
  629. bHasAnyQuests = true;
  630. pAllSubMenu = new Menu( this, "AllSubMenu" );
  631. pAllSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
  632. pAllSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
  633. contextMenuBuilder.AddCascadingMenuItem( "all", pAllSubMenu, "all" );
  634. }
  635. pAllSubMenu->AddMenuItem( pItemDef->GetItemBaseName(), CFmtStr( "give%s", pItemDef->GetDefinitionName() ), this );
  636. }
  637. }
  638. // Position to the cursor's position
  639. int nX, nY;
  640. g_pVGuiInput->GetCursorPosition( nX, nY );
  641. pContextMenu->SetPos( nX - 1, nY - 1 );
  642. pContextMenu->SetVisible(true);
  643. pContextMenu->AddActionSignalTarget(this);
  644. }
  645. }
  646. else if ( Q_strnicmp( "give", pCommand, 4 ) == 0 )
  647. {
  648. if ( GetUniverse() != k_EUniversePublic )
  649. {
  650. if ( !steamapicontext || !steamapicontext->SteamUser() )
  651. {
  652. Msg("Not connected to Steam.\n");
  653. return;
  654. }
  655. CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID();
  656. if ( !steamIDForPlayer.IsValid() )
  657. {
  658. Msg("Failed to find a valid steamID for the local player.\n");
  659. return;
  660. }
  661. const char* pszItemToGive = pCommand + 4;
  662. Msg( "Sending request to generate '%s' for Local Player (%llu)\n", pszItemToGive, steamIDForPlayer.ConvertToUint64() );
  663. CItemSelectionCriteria criteria;
  664. GCSDK::CProtoBufMsg<CMsgDevNewItemRequest> msg( k_EMsgGCDev_NewItemRequest );
  665. msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() );
  666. criteria.SetIgnoreEnabledFlag( true );
  667. if ( !criteria.BAddCondition( "name", k_EOperator_String_EQ, pszItemToGive, true ) ||
  668. !criteria.BSerializeToMsg( *msg.Body().mutable_criteria() ) )
  669. {
  670. Msg( "Failed to add condition and/or serialize item grant request. This is probably caused by having a string that's too long.\n" );
  671. return;
  672. }
  673. GCClientSystem()->BSendMessage( msg );
  674. }
  675. }
  676. }
  677. //-----------------------------------------------------------------------------
  678. // Purpose:
  679. //-----------------------------------------------------------------------------
  680. void CQuestLogPanel::FireGameEvent( IGameEvent *event )
  681. {
  682. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  683. // Listen for inventory updates in case our item gets changed while the user
  684. // is looking at us. We want to re-do our entire layout since a quest might
  685. // have been equipped / destroyed / completed and we need to re-categorize
  686. // all the user's quests.
  687. if ( FStrEq( event->GetName(), "inventory_updated" ) && !m_bWaitingForComplete )
  688. {
  689. m_bInventoryDirty = true;
  690. if ( IsVisible() )
  691. {
  692. if ( m_pQuestList )
  693. {
  694. m_pQuestList->PopulateQuestLists();
  695. m_pQuestList->UpdateEmptyMessage();
  696. }
  697. UpdateQuestsItemPanels();
  698. }
  699. }
  700. else if ( FStrEq( event->GetName(), "gameui_hidden" ) )
  701. {
  702. ShowPanel( false );
  703. return;
  704. }
  705. else if ( FStrEq( event->GetName(), "gc_connected" ) )
  706. {
  707. m_bInventoryDirty = true;
  708. InvalidateLayout( false, true );
  709. }
  710. }
  711. //-----------------------------------------------------------------------------
  712. // Purpose:
  713. //-----------------------------------------------------------------------------
  714. void CQuestLogPanel::ShowPanel( bool bShow )
  715. {
  716. // Snag this so we know what to listen for
  717. m_iQuestLogKey = gameuifuncs->GetButtonCodeForBind( "show_quest_log" );
  718. if ( m_pQuestList && bShow )
  719. {
  720. m_pQuestList->SetSelected( NULL, true );
  721. m_pQuestList->DirtyQuestLayout();
  722. }
  723. SetVisible( bShow );
  724. }
  725. //-----------------------------------------------------------------------------
  726. // Purpose:
  727. //-----------------------------------------------------------------------------
  728. void CQuestLogPanel::OnKeyCodePressed( KeyCode code )
  729. {
  730. if ( code == m_iQuestLogKey || code == STEAMCONTROLLER_B )
  731. {
  732. ShowPanel( false );
  733. return;
  734. }
  735. BaseClass::OnKeyCodePressed( code );
  736. }
  737. //-----------------------------------------------------------------------------
  738. // Purpose:
  739. //-----------------------------------------------------------------------------
  740. void CQuestLogPanel::OnKeyCodeTyped( KeyCode code )
  741. {
  742. if ( code == KEY_ESCAPE )
  743. {
  744. if ( IsVisible() )
  745. {
  746. SetVisible( false );
  747. }
  748. }
  749. }
  750. //-----------------------------------------------------------------------------
  751. // Purpose:
  752. //-----------------------------------------------------------------------------
  753. void CQuestLogPanel::SetVisible( bool bState )
  754. {
  755. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  756. // Default to showing the active quests upon opening
  757. if ( bState == true )
  758. {
  759. UpdateQuestsItemPanels();
  760. IGameEvent *event = gameeventmanager->CreateEvent( "questlog_opened" );
  761. if ( event )
  762. {
  763. gameeventmanager->FireEventClientSide( event );
  764. }
  765. if ( enginevgui->IsGameUIVisible() )
  766. {
  767. AttachToGameUI();
  768. }
  769. else
  770. {
  771. ipanel()->SetParent( GetVPanel(), VGui_GetClientDLLRootPanel() );
  772. MakePopup( false, true );
  773. SetKeyBoardInputEnabled( true );
  774. SetMouseInputEnabled( true );
  775. MoveToFront();
  776. }
  777. engine->ClientCmd_Unrestricted( "gameui_preventescapetoshow\n" );
  778. vgui::surface()->PlaySound( "ui/panel_open.wav" );
  779. }
  780. else if ( IsVisible() )
  781. {
  782. // Detach from the GameUI when we hide
  783. IViewPortPanel *pMMOverride = gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE );
  784. if ( pMMOverride )
  785. {
  786. ((CHudMainMenuOverride*)pMMOverride)->AttachToGameUI();
  787. }
  788. engine->ClientCmd_Unrestricted( "gameui_allowescapetoshow\n" );
  789. vgui::surface()->PlaySound( "ui/panel_close.wav" );
  790. }
  791. BaseClass::SetVisible( bState );
  792. }
  793. //-----------------------------------------------------------------------------
  794. // Purpose:
  795. //-----------------------------------------------------------------------------
  796. void CQuestLogPanel::QuestCompletedResponse()
  797. {
  798. m_bWaitingForComplete = false;
  799. m_bInventoryDirty = true;
  800. if ( m_pQuestList )
  801. m_pQuestList->QuestCompletedResponse();
  802. //UpdateQuestsItemPanels();
  803. }
  804. //-----------------------------------------------------------------------------
  805. // Purpose:
  806. //-----------------------------------------------------------------------------
  807. void CQuestLogPanel::UpdateQuestsItemPanels()
  808. {
  809. UpdateBadgeProgressPanels();
  810. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  811. if ( m_bInventoryDirty )
  812. {
  813. m_pQuestList->QuestCompletedResponse();
  814. if ( m_pQuestList )
  815. {
  816. m_pQuestList->PopulateQuestLists();
  817. m_pQuestList->UpdateEmptyMessage();
  818. }
  819. }
  820. m_bInventoryDirty = false;
  821. }
  822. //-----------------------------------------------------------------------------
  823. // Purpose:
  824. //-----------------------------------------------------------------------------
  825. void CQuestLogPanel::MarkQuestsDirty()
  826. {
  827. m_bInventoryDirty = true;
  828. if ( IsVisible() )
  829. {
  830. UpdateQuestsItemPanels();
  831. }
  832. }
  833. //-----------------------------------------------------------------------------
  834. // Purpose: Return true if any quest item panels are in the passed in state
  835. //-----------------------------------------------------------------------------
  836. bool CQuestLogPanel::AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const
  837. {
  838. return m_pQuestList->AnyQuestItemPanelsInState( eState );
  839. }
  840. //-----------------------------------------------------------------------------
  841. // Purpose:
  842. //-----------------------------------------------------------------------------
  843. void CQuestLogPanel::OnCompleteQuest( void )
  844. {
  845. m_bWaitingForComplete = true;
  846. }
  847. //-----------------------------------------------------------------------------
  848. // Purpose:
  849. //-----------------------------------------------------------------------------
  850. void CQuestLogPanel::UpdateBadgeProgressPanels()
  851. {
  852. CEconOperationDefinition *pCurrentOperation = NULL;
  853. const auto& mapOperations = GetItemSchema()->GetOperationDefinitions();
  854. FOR_EACH_MAP_FAST( mapOperations, i )
  855. {
  856. CEconOperationDefinition* pOperation = mapOperations[i];
  857. if ( pOperation->IsActive() && pOperation->IsCampaign() )
  858. {
  859. pCurrentOperation = pOperation;
  860. break;
  861. }
  862. }
  863. if ( pCurrentOperation )
  864. {
  865. CEconItemView *pCoin = TFInventoryManager()->GetLocalTFInventory()->FindFirstItembyItemDef( pCurrentOperation->GetRequiredItemDefIndex() );
  866. if ( pCoin )
  867. {
  868. m_pProgressPanel->SetVisible( true );
  869. CItemModelPanel *pCoinPanel = m_pProgressPanel->FindControl< CItemModelPanel >( "CoinModelPanel" );
  870. if ( pCoinPanel )
  871. {
  872. pCoinPanel->SetItem( pCoin );
  873. }
  874. uint32 nNumCompletedContracts = 0;
  875. {
  876. uint32 nCompletedContractsTemp = 0;
  877. GetKilleaterValueByEvent( pCoin, kKillEaterEvent_CosmeticOperationContractsCompleted, nCompletedContractsTemp );
  878. nNumCompletedContracts = Max( nNumCompletedContracts, nCompletedContractsTemp );
  879. // Halloween has it's own thing for whatever reason
  880. GetKilleaterValueByEvent( pCoin, kKillEaterEvent_HalloweenContractsCompleted, nCompletedContractsTemp );
  881. nNumCompletedContracts = Max( nNumCompletedContracts, nCompletedContractsTemp );
  882. Assert( pCurrentOperation->GetMaxDropCount() > 0 );
  883. }
  884. uint32 nNumContractPoints = 0;
  885. {
  886. uint32 nContractsPointsTemp = 0;
  887. GetKilleaterValueByEvent( pCoin, kKillEaterEvent_CosmeticOperationContractsPoints, nContractsPointsTemp );
  888. nNumContractPoints = Max( nContractsPointsTemp, nNumContractPoints );
  889. // Halloween has it's own thing for whatever reason
  890. GetKilleaterValueByEvent( pCoin, kKillEaterEvent_HalloweenSouls, nContractsPointsTemp );
  891. nNumContractPoints = Max( nContractsPointsTemp, nNumContractPoints );
  892. }
  893. EditablePanel *pBadgeContainer = m_pProgressPanel->FindControl< EditablePanel >( "BadgeMeterContainer" );
  894. if ( pBadgeContainer )
  895. {
  896. ContinuousProgressBar *pBadgeProgressBar = pBadgeContainer->FindControl< ContinuousProgressBar >( "BadgeProgressMeter" );
  897. if ( pBadgeProgressBar )
  898. {
  899. const char *pszLevelingDataName = GetItemSchema()->GetKillEaterScoreTypeLevelingDataName( kKillEaterEvent_HalloweenSouls );
  900. Assert( pszLevelingDataName );
  901. const CUtlVector<CItemLevelingDefinition> *pLevelingData = GetItemSchema()->GetItemLevelingData( pszLevelingDataName );
  902. Assert( pLevelingData );
  903. int nRequiredPointsToNextRank = 0;
  904. int nRequiredPointsToCurrentRank = 0;
  905. const char *pszCurrentLevelName = NULL;
  906. FOR_EACH_VEC( (*pLevelingData), i )
  907. {
  908. pszCurrentLevelName = (*pLevelingData)[i].GetNameLocalizationKey();
  909. const uint32 nRank = (*pLevelingData)[i].GetRequiredScore();
  910. if ( nNumContractPoints < nRank )
  911. {
  912. nRequiredPointsToNextRank = nRank;
  913. break;
  914. }
  915. else
  916. {
  917. nRequiredPointsToCurrentRank = nRank;
  918. }
  919. }
  920. // if no next level, just use max points
  921. if ( nRequiredPointsToNextRank == 0 )
  922. {
  923. // assuming each contract's worth 130 (100 point + 30 bonus)
  924. nRequiredPointsToNextRank = pCurrentOperation->GetMaxDropCount() * 130;
  925. }
  926. float flProgress = (float)( nNumContractPoints - nRequiredPointsToCurrentRank ) / (float)( nRequiredPointsToNextRank - nRequiredPointsToCurrentRank );
  927. pBadgeProgressBar->SetProgress( flProgress );
  928. CExLabel *pLabel = m_pProgressPanel->FindControl< CExLabel >( "BadgeProgressLabel" );
  929. if ( pLabel )
  930. {
  931. pLabel->SetText( CConstructLocalizedString( g_pVGuiLocalize->Find( "QuestLog_BadgeProgress" ), g_pVGuiLocalize->Find( pszCurrentLevelName ) ) );
  932. }
  933. CExLabel *pScoreLabel = pBadgeContainer->FindControl< CExLabel >( "BadgeProgressMeterText" );
  934. if ( pScoreLabel )
  935. {
  936. pScoreLabel->SetText( CFmtStr( "%d/%d", nNumContractPoints, nRequiredPointsToNextRank ) );
  937. }
  938. }
  939. }
  940. EditablePanel *pContractContainer = m_pProgressPanel->FindControl< EditablePanel >( "ContractMeterContainer" );
  941. if ( pContractContainer )
  942. {
  943. ContinuousProgressBar *pContractProgressBar = pContractContainer->FindControl< ContinuousProgressBar >( "ContractsCompletedProgressMeter" );
  944. if ( pContractProgressBar )
  945. {
  946. pContractProgressBar->SetProgress( (float)nNumCompletedContracts / (float)pCurrentOperation->GetMaxDropCount() );
  947. CExLabel *pLabel = pContractContainer->FindControl< CExLabel >( "ContractsCompletedProgressMeterText" );
  948. if ( pLabel )
  949. {
  950. pLabel->SetText( CFmtStr( "%d", nNumCompletedContracts ) );
  951. }
  952. }
  953. }
  954. }
  955. else
  956. {
  957. m_pProgressPanel->SetVisible( false );
  958. }
  959. }
  960. else
  961. {
  962. m_pProgressPanel->SetVisible( false );
  963. }
  964. }
  965. //-----------------------------------------------------------------------------
  966. // Purpose: GC Msg handler for when a quest has been completed
  967. //-----------------------------------------------------------------------------
  968. class CGCCompleteQuestCompleteResponse : public GCSDK::CGCClientJob
  969. {
  970. public:
  971. CGCCompleteQuestCompleteResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  972. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  973. {
  974. GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
  975. itemid_t nNewToolID = 0;
  976. if( !msg.BReadUint64Data( &nNewToolID ) )
  977. return true;
  978. CQuestLogPanel *pQuestLog = GetQuestLog();
  979. if ( pQuestLog )
  980. {
  981. pQuestLog->QuestCompletedResponse();
  982. }
  983. return true;
  984. }
  985. };
  986. GC_REG_JOB( GCSDK::CGCClient, CGCCompleteQuestCompleteResponse, "CGCCompleteQuestCompleteResponse", k_EMsgGCQuestCompleted, GCSDK::k_EServerTypeGCClient );
  987. #ifdef STAGING_ONLY
  988. static void cc_tf_quest_log_reload()
  989. {
  990. CQuestLogPanel *pQuestLog = GetQuestLog();
  991. if ( pQuestLog )
  992. {
  993. pQuestLog->MarkQuestsDirty();
  994. pQuestLog->InvalidateLayout( true, true );
  995. gViewPortInterface->ShowPanel( pQuestLog, true );
  996. }
  997. }
  998. ConCommand tf_quest_log_reload( "tf_quest_log_reload", cc_tf_quest_log_reload );
  999. #endif