//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "quest_log_panel.h" #include "ienginevgui.h" #include "c_tf_gamestats.h" #include "store/store_panel.h" #include "econ/econ_ui.h" #include "clientmode_tf.h" #include "tf_hud_mainmenuoverride.h" #include "vgui_int.h" #include "IGameUIFuncs.h" // for key bindings #include #include "tf_item_inventory.h" #include "vgui/IInput.h" #include "item_ad_panel.h" #include "vgui_controls/ProgressBar.h" // memdbgon must be the last include file in a .cpp file!!! #include void AddSubKeyNamed( KeyValues *pKeys, const char *pszName ); static CItemModelPanelToolTip* g_spItemTooltip = NULL; CQuestTooltip* g_spTextTooltip = NULL; CQuestLogPanel *GetQuestLog() { CQuestLogPanel *pQuestLogPanel = (CQuestLogPanel*)gViewPortInterface->FindPanelByName( PANEL_QUEST_LOG ); return pQuestLogPanel; } //------------------------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CScrollableQuestList::CScrollableQuestList( vgui::Panel *parent, const char *pszPanelName ) : EditablePanel( parent, pszPanelName ) , m_pCompletingPanel( NULL ) , m_bQuestsLayoutDirty( false ) , m_pszNoQuests( NULL ) , m_pszNeedAPass( NULL ) , m_pszNotPossible( NULL ) { m_pContainer = new EditablePanel( this, "Container" ); m_vecQuestItemPanels.SetSize( 2 ); FOR_EACH_VEC( m_vecQuestItemPanels, i ) { m_vecQuestItemPanels[ i ] = new CQuestItemPanel( m_pContainer, "QuestItemPanel", NULL, this ); } } CScrollableQuestList::~CScrollableQuestList() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CScrollableQuestList::ApplySchemeSettings( vgui::IScheme *pScheme ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); BaseClass::ApplySchemeSettings( pScheme ); const char *pszResFile = "Resource/UI/econ/ScrollableQuestList.res"; // Check if the operation wants to override our default res file const auto& mapOperations = GetItemSchema()->GetOperationDefinitions(); FOR_EACH_MAP_FAST( mapOperations, i ) { CEconOperationDefinition* pCurrentOperation = mapOperations[i]; // Take the first active operation's res file that's different than default if ( pCurrentOperation->IsActive() && pCurrentOperation->GetQuestListOverrideResFile() ) { // Use the first found for now pszResFile = pCurrentOperation->GetQuestListOverrideResFile(); break; } } LoadControlSettings( pszResFile ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CScrollableQuestList::ApplySettings( KeyValues *inResourceData ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); BaseClass::ApplySettings( inResourceData ); m_pszNoQuests = inResourceData->GetString( "no_quests", "#QuestLog_NoQuests" ); m_pszNeedAPass = inResourceData->GetString( "need_a_pass", "#QuestLog_NeedPassForContracts" ); m_pszNotPossible = inResourceData->GetString( "not_possible", "#QuestLog_NoContractsPossible" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CScrollableQuestList::PerformLayout( void ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); BaseClass::PerformLayout(); m_pContainer->InvalidateLayout( true ); FOR_EACH_VEC( m_vecQuestItemPanels, i ) { m_vecQuestItemPanels[ i ]->InvalidateLayout( true, true ); m_vecQuestItemPanels[ i ]->SetZPos( 1 + i ); } PositionQuestItemPanels(); UpdateEmptyMessage(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CScrollableQuestList::OnThink() { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); if ( m_bQuestsLayoutDirty ) { m_bQuestsLayoutDirty = false; PositionQuestItemPanels(); } // Conditionally turn mouse input on/off based on where the mouse is FOR_EACH_VEC( m_vecQuestItemPanels, i ) { m_vecQuestItemPanels[i]->SetMouseInputEnabled( m_vecQuestItemPanels[i]->IsCursorOverMainContainer() ); } } void CScrollableQuestList::OnCommand( const char *command ) { if ( FStrEq( command, "deselect_all" ) ) { SetSelected( NULL, false ); } BaseClass::OnCommand( command ); } int QuestSort_AcquiredTime( CQuestItemPanel* const* p1, CQuestItemPanel* const* p2 ) { if ( !(*p1)->GetItem() ) return -1; if ( !(*p2)->GetItem() ) return 1; // Newest items first return (*p1)->GetItem()->GetID() - (*p2)->GetItem()->GetID(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CScrollableQuestList::PositionQuestItemPanels() { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); // We dont do anything when a quest is completing if ( m_pCompletingPanel != NULL ) return; int nVisible = 0; FOR_EACH_VEC( m_vecQuestItemPanels, i ) { CQuestItemPanel* pPanel = m_vecQuestItemPanels[i]; if ( pPanel ) { pPanel->SetVisible( pPanel->GetItem() ); if ( pPanel->GetItem() ) { ++nVisible; } } } CExLabel *pLabel = FindControl( "EmptyLabel", true ); if ( pLabel ) { pLabel->SetVisible( nVisible == 0 ); } // Check for a selected panel const CQuestItemPanel* pSelected = NULL; FOR_EACH_VEC( m_vecQuestItemPanels, i ) { if ( m_vecQuestItemPanels[i]->IsSelected() ) { Assert( pSelected == NULL ); pSelected = m_vecQuestItemPanels[i]; } } struct FolderCommands_t { const char* m_pszSelected; const char* m_pszOtherIsSelected; const char* m_pszNoneSelected; }; const FolderCommands_t folderCommands[] = { { "QuestItem_Back_Selected", "QuestItem_Back_OtherSelected", "QuestItem_Back_NoneSelected" } , { "QuestItem_Front_Selected", "QuestItem_Front_OtherSelected", "QuestItem_Front_NoneSelected" } }; // Update the positions FOR_EACH_VEC( m_vecQuestItemPanels, i ) { // This is the selected panel if ( pSelected == m_vecQuestItemPanels[ i ] ) { g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszSelected ); } else if ( pSelected ) // Some other panel is selected { g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszOtherIsSelected ); } else // No panel is selected { g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_vecQuestItemPanels[ i ], folderCommands[i].m_pszNoneSelected ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CScrollableQuestList::SetSelected( CQuestItemPanel *pItem, bool bImmediately ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); FOR_EACH_VEC( m_vecQuestItemPanels, i ) { m_vecQuestItemPanels[ i ]->SetSelected( m_vecQuestItemPanels[ i ] == pItem, bImmediately ); } PositionQuestItemPanels(); } bool DoesLootlistDropQuests( const CEconLootListDefinition* pLootList ) { FOR_EACH_VEC( pLootList->GetLootListContents(), j ) { const CEconLootListDefinition::drop_item_t& item = pLootList->GetLootListContents()[j]; // 0 and greater means item. Less than 0 means nested lootlist if( item.m_iItemOrLootlistDef >= 0 ) { const GameItemDefinition_t* pItemDef = assert_cast( GetItemSchema()->GetItemDefinition( item.m_iItemOrLootlistDef ) ); if( pItemDef ) { return pItemDef->GetQuestDef(); } } else { // Get the nested lootlist int iLLIndex = (item.m_iItemOrLootlistDef * -1) - 1; const CEconLootListDefinition *pNestedLootList = GetItemSchema()->GetLootListByIndex( iLLIndex ); Assert( pNestedLootList ); if ( !pNestedLootList ) continue; // Dig through all of this lootlist's entries return DoesLootlistDropQuests( pNestedLootList ); } } return false; } //----------------------------------------------------------------------------- // Purpose: Update what message we show when we have no quests //----------------------------------------------------------------------------- void CScrollableQuestList::UpdateEmptyMessage() { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); CCyclingAdContainerPanel* pPassStoreAd = FindControl< CCyclingAdContainerPanel >( "ItemAd", true ); if ( !pPassStoreAd ) return; pPassStoreAd->SetVisible( false ); SetDialogVariable( "noquests", "" ); FOR_EACH_VEC( m_vecQuestItemPanels, i ) { // Case 1 above if ( m_vecQuestItemPanels[ i ]->GetItem() ) return; } // By default, there's no operations going on, and there's no ad to show const char *pszNoQuestsText = m_pszNotPossible; // Find any active quest-dropping operations const auto& mapOperations = GetItemSchema()->GetOperationDefinitions(); FOR_EACH_MAP_FAST( mapOperations, iOperation ) { CEconOperationDefinition *pOperation = mapOperations[ iOperation ]; const CSchemaLootListDefHandle pOperationLootlist( pOperation->GetOperationLootlist() ); // Must still be dropping, and be dropping quests if ( CRTime::RTime32TimeCur() < pOperation->GetStopGivingToPlayerDate() && pOperationLootlist && DoesLootlistDropQuests( pOperationLootlist ) ) { // If there's a required item and a gateway item if ( pOperation->GetRequiredItemDefIndex() != INVALID_ITEM_DEF_INDEX && pOperation->GetGatewayItemDefIndex() != INVALID_ITEM_DEF_INDEX ) { // And the user doesn't have the required item if ( TFInventoryManager()->GetLocalTFInventory()->FindFirstItembyItemDef( pOperation->GetRequiredItemDefIndex() ) == NULL ) { // The user needs to get the item pszNoQuestsText = m_pszNeedAPass; bool bStoreIsReady = EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetPriceSheet() && EconUI()->GetStorePanel()->GetCart() && steamapicontext && steamapicontext->SteamUser(); bool bGatewayItemInStore = false; // Check if the gateway item is in the Mann Co Store if ( bStoreIsReady ) { bGatewayItemInStore = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( pOperation->GetGatewayItemDefIndex() ) != NULL; } CEconItemDefinition* pGatewayItemDef = GetItemSchema()->GetItemDefinition( pOperation->GetGatewayItemDefIndex() ); Assert( pGatewayItemDef ); if ( !pGatewayItemDef ) return; // Cook up KVs for this item ad KeyValuesAD pKVItemAd( "items" ); // The panel will copy these KeyValues* pKVItem = pKVItemAd->CreateNewKey(); pKVItem->SetName( "0" ); pKVItem->SetString( "item", pGatewayItemDef->GetDefinitionName() ); pKVItem->SetInt( "show_market", bGatewayItemInStore ? 0 : 1 ); pPassStoreAd->SetVisible( true ); pPassStoreAd->SetItemKVs( pKVItemAd ); // This is the most important thing to communicate. Don't let other operations stomp it. break; } } pszNoQuestsText = m_pszNoQuests; // Don't break. Give more important operations a chance to present } } SetDialogVariable( "noquests", g_pVGuiLocalize->Find( pszNoQuestsText ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CScrollableQuestList::PopulateQuestLists() { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); // We dont do anything when a quest is completing if ( m_pCompletingPanel != NULL ) return; DirtyQuestLayout(); CUtlVector< CEconItemView * > vecQuestItems; TFInventoryManager()->GetAllQuestItems( &vecQuestItems ); CUtlVector< CQuestItemPanel* > vecAvailablePanels; FOR_EACH_VEC( m_vecQuestItemPanels, i ) { bool bFound = false; if ( m_vecQuestItemPanels[ i ]->GetItem() ) { FOR_EACH_VEC_BACK( vecQuestItems, j ) { // See if the item in the panel is still in the list of items we own if ( m_vecQuestItemPanels[ i ]->GetItem()->GetOriginalID() == vecQuestItems[ j ]->GetOriginalID() ) { // Refresh it m_vecQuestItemPanels[ i ]->InvalidateLayout(); vecQuestItems.Remove( j ); bFound = true; break; } } } // Didn't find it. Clear it out if ( !bFound ) { if ( m_vecQuestItemPanels[ i ]->GetItem() ) { m_vecQuestItemPanels[ i ]->SetItem( NULL ); } if ( m_vecQuestItemPanels[ i ]->IsSelected() ) { SetSelected( m_vecQuestItemPanels[ i ], false ); } vecAvailablePanels.AddToTail( m_vecQuestItemPanels[ i ] ); } } for ( int i = 0 ; i < vecQuestItems.Count(); ++i ) { CEconItemView *pItem = vecQuestItems[i]; if ( i < vecAvailablePanels.Count() ) { vecAvailablePanels[ i ]->SetItem( pItem ); } else if ( i >= m_vecQuestItemPanels.Count() ) { Assert( !"Ran out of quest panels!" ); } } // Sort the panels to make sure they're in order m_vecQuestItemPanels.Sort( &QuestSort_AcquiredTime ); // Sorting is done manually FOR_EACH_VEC( m_vecQuestItemPanels, i ) { m_vecQuestItemPanels[ i ]->SetZPos( i + 1 ); } PositionQuestItemPanels(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CScrollableQuestList::QuestCompletedResponse() { FOR_EACH_VEC( m_vecQuestItemPanels, i ) { m_vecQuestItemPanels[i]->QuestCompletedResponse(); } // PopulateQuestLists(); } //----------------------------------------------------------------------------- // Purpose: Return true if any quest item panels are in the passed in state //----------------------------------------------------------------------------- bool CScrollableQuestList::AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const { FOR_EACH_VEC( m_vecQuestItemPanels, i ) { if ( m_vecQuestItemPanels[i]->GetState() == eState ) return true; } return false; } //------------------------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CQuestLogPanel::CQuestLogPanel( IViewPort *pViewPort ) : EditablePanel( NULL, PANEL_QUEST_LOG ) , m_bWaitingForComplete( false ) , m_pQuestList( NULL ) , m_bInventoryDirty( true ) , m_iQuestLogKey( BUTTON_CODE_INVALID ) { if (g_pVGuiLocalize) { g_pVGuiLocalize->AddFile( "resource/tf_quests_%language%.txt" ); } vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); SetScheme(scheme); SetProportional( true ); ListenForGameEvent( "inventory_updated" ); ListenForGameEvent( "gameui_hidden" ); ListenForGameEvent( "gc_connected" ); // Create the item model panel tooltip m_pMouseOverItemPanel = new CItemModelPanel( this, "mouseoveritempanel" ); m_pMouseOverTooltip = new CItemModelPanelToolTip( this ); m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel ); // Create the text tooltip m_pToolTip = new CQuestTooltip( this ); m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" ); m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false ); m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false ); // m_pToolTipEmbeddedPanel->MakePopup(); m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel ); m_pToolTip->SetTooltipDelay( 0 ); EditablePanel *pMainContainer = new EditablePanel( this, "MainContainer" ); m_pQuestList = new CScrollableQuestList( pMainContainer, "QuestList" ); m_pProgressPanel = new EditablePanel( this, "ProgressPanel" ); m_pDebugButton = new CExButton( pMainContainer, "Options", "Options", this, "open_debug_menu" ); } //----------------------------------------------------------------------------- // Purpose: Look into the moused-over panel and take "tiptext" from its dialog // variables and set it as our own. //----------------------------------------------------------------------------- void CQuestTooltip::ShowTooltip( Panel *pCurrentPanel ) { EditablePanel* pEditableCurrentPanel = dynamic_cast< EditablePanel* >( pCurrentPanel ); if ( pEditableCurrentPanel ) { KeyValues* pKVVariables = pEditableCurrentPanel->GetDialogVariables(); const wchar_t *pwszTipText = pKVVariables->GetWString( "tiptext", L"" ); m_pEmbeddedPanel->SetDialogVariable( "tiptext", pwszTipText ); } BaseClass::ShowTooltip( pCurrentPanel ); } //----------------------------------------------------------------------------- // Purpose: Position ourselves down and to the right as far as posible //----------------------------------------------------------------------------- void CQuestTooltip::PositionWindow( Panel *pTipPanel ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); int iTipW, iTipH; pTipPanel->GetSize( iTipW, iTipH ); int cursorX, cursorY; vgui::input()->GetCursorPos(cursorX, cursorY); int px, py, wide, tall; ipanel()->GetAbsPos( m_pEmbeddedPanel->GetParent()->GetVPanel(), px, py ); m_pEmbeddedPanel->GetParent()->GetSize(wide, tall); if ( !m_pEmbeddedPanel->IsPopup() ) { // Move the cursor into our parent space cursorX -= px; cursorY -= py; } // Dangle as far down and as far right as possible int nXPos = cursorX - Max( 0, ( ( iTipW + cursorX ) - wide ) ); int nYPos = ( cursorY + 20 )- Max( 0, ( ( iTipH + cursorY + 20 ) - tall ) ) ; pTipPanel->SetPos( nXPos, nYPos ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CQuestLogPanel::~CQuestLogPanel() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CQuestLogPanel::AttachToGameUI( void ) { C_CTFGameStats::ImmediateWriteInterfaceEvent( "interface_open", "quest_log_panel" ); if ( GetClientModeTFNormal()->GameUI() ) { GetClientModeTFNormal()->GameUI()->SetMainMenuOverride( GetVPanel() ); } SetKeyBoardInputEnabled( true ); SetMouseInputEnabled( true ); SetCursor(dc_arrow); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CQuestLogPanel::GetName( void ) { return PANEL_QUEST_LOG; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CQuestLogPanel::ApplySchemeSettings( IScheme *pScheme ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); BaseClass::ApplySchemeSettings( pScheme ); const char *pszResFile = "Resource/UI/econ/QuestLogPanel.res"; // Check if the operation wants to override our default res file const auto& mapOperations = GetItemSchema()->GetOperationDefinitions(); FOR_EACH_MAP_FAST( mapOperations, i ) { CEconOperationDefinition* pOperation = mapOperations[i]; if ( pOperation->IsActive() && pOperation->IsCampaign() ) { // Use the first found for now if ( pOperation->GetQuestLogOverrideResFile() ) { pszResFile = pOperation->GetQuestLogOverrideResFile(); } break; } } LoadControlSettings( pszResFile ); g_spItemTooltip = m_pMouseOverTooltip; g_spTextTooltip = m_pToolTip; // The outer dim / close button { Button* pButton = FindControl