diff options
Diffstat (limited to 'game/client/tf/vgui/quest_log_panel.cpp')
| -rw-r--r-- | game/client/tf/vgui/quest_log_panel.cpp | 1182 |
1 files changed, 1182 insertions, 0 deletions
diff --git a/game/client/tf/vgui/quest_log_panel.cpp b/game/client/tf/vgui/quest_log_panel.cpp new file mode 100644 index 0000000..e65c7b0 --- /dev/null +++ b/game/client/tf/vgui/quest_log_panel.cpp @@ -0,0 +1,1182 @@ +//========= 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 <vgui_controls/AnimationController.h> +#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 <tier0/memdbgon.h> + +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<CExLabel>( "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<const GameItemDefinition_t *>( 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<Button>( "OutsideCloseButton" ); + if ( pButton ) + { + pButton->AddActionSignalTarget( this ); + pButton->SetPaintBackgroundEnabled( false ); + } + } + + m_pQuestList->InvalidateLayout( false, true ); + + Assert( m_pQuestList ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( GetUniverse() != k_EUniversePublic ) + { + int x, y; + m_pDebugButton->GetParent()->GetPos( x, y ); + int w, h; + m_pDebugButton->GetParent()->GetSize( w, h ); + m_pDebugButton->SizeToContents(); + m_pDebugButton->SetVisible( true ); + m_pDebugButton->SetPos( x + w - m_pDebugButton->GetWide() - 60, y + 15 ); + m_pDebugButton->SetZPos( 1000 ); + } + else + { + m_pDebugButton->SetVisible( false ); + } + + UpdateQuestsItemPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::OnCommand( const char *pCommand ) +{ + if ( FStrEq( pCommand, "close" ) ) + { + if ( enginevgui->IsGameUIVisible() ) + { + ShowPanel( false ); + } + else + { + IViewPortPanel *pQuestLog = ( gViewPortInterface->FindPanelByName( PANEL_QUEST_LOG ) ); + if ( pQuestLog ) + { + gViewPortInterface->ShowPanel( pQuestLog, false ); + } + } + } + else if ( Q_stricmp( "open_debug_menu", pCommand ) == 0 ) + { + if ( GetUniverse() == k_EUniverseBeta || GetUniverse() == k_EUniverseDev ) + { + const char *pszContextMenuBorder = "NotificationDefault"; + const char *pszContextMenuFont = "HudFontMediumSecondary"; + + Menu *pContextMenu = new Menu( this, "ContextMenu" ); + pContextMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + pContextMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + + + MenuBuilder contextMenuBuilder( pContextMenu, this ); + + const auto& mapOperations = GetItemSchema()->GetOperationDefinitions(); + FOR_EACH_MAP_FAST( mapOperations, iOperation ) + { + bool bHasAnyQuests = false; + Menu* pOperationSubMenu = NULL; + + CEconOperationDefinition *pOperation = mapOperations[ iOperation ]; + const CEconLootListDefinition *pLootListDef = GetItemSchema()->GetLootListByName( pOperation->GetOperationLootlist() ); + if ( pLootListDef ) + { + auto& vecContents = pLootListDef->GetLootListContents(); + FOR_EACH_VEC( vecContents, i ) + { + if ( vecContents[i].m_iItemOrLootlistDef > 0 ) + { + const GameItemDefinition_t* pItemDef = (GameItemDefinition_t*)GetItemSchema()->GetItemDefinition( vecContents[i].m_iItemOrLootlistDef ); + if ( pItemDef && pItemDef->GetQuestDef() ) + { + if ( !bHasAnyQuests ) + { + bHasAnyQuests = true; + + pOperationSubMenu = new Menu( this, "OperationSubMenu" ); + pOperationSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + pOperationSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + + contextMenuBuilder.AddCascadingMenuItem( pOperation->GetName(), pOperationSubMenu, "operations" ); + } + + pOperationSubMenu->AddMenuItem( pItemDef->GetItemBaseName(), CFmtStr( "give%s", pItemDef->GetDefinitionName() ), this ); + } + } + } + } + } + + bool bHasAnyQuests = false; + Menu* pAllSubMenu = NULL; + + FOR_EACH_MAP_FAST( GetItemSchema()->GetItemDefinitionMap(), i ) + { + const GameItemDefinition_t* pItemDef = (GameItemDefinition_t*)GetItemSchema()->GetItemDefinitionMap()[ i ]; + if ( pItemDef->GetQuestDef() ) + { + if ( !bHasAnyQuests ) + { + bHasAnyQuests = true; + + pAllSubMenu = new Menu( this, "AllSubMenu" ); + pAllSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); + pAllSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); + + contextMenuBuilder.AddCascadingMenuItem( "all", pAllSubMenu, "all" ); + } + + pAllSubMenu->AddMenuItem( pItemDef->GetItemBaseName(), CFmtStr( "give%s", pItemDef->GetDefinitionName() ), this ); + } + } + + // Position to the cursor's position + int nX, nY; + g_pVGuiInput->GetCursorPosition( nX, nY ); + pContextMenu->SetPos( nX - 1, nY - 1 ); + + pContextMenu->SetVisible(true); + pContextMenu->AddActionSignalTarget(this); + } + } + else if ( Q_strnicmp( "give", pCommand, 4 ) == 0 ) + { + if ( GetUniverse() != k_EUniversePublic ) + { + if ( !steamapicontext || !steamapicontext->SteamUser() ) + { + Msg("Not connected to Steam.\n"); + return; + } + + CSteamID steamIDForPlayer = steamapicontext->SteamUser()->GetSteamID(); + if ( !steamIDForPlayer.IsValid() ) + { + Msg("Failed to find a valid steamID for the local player.\n"); + return; + } + + const char* pszItemToGive = pCommand + 4; + Msg( "Sending request to generate '%s' for Local Player (%llu)\n", pszItemToGive, steamIDForPlayer.ConvertToUint64() ); + + CItemSelectionCriteria criteria; + + GCSDK::CProtoBufMsg<CMsgDevNewItemRequest> msg( k_EMsgGCDev_NewItemRequest ); + msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() ); + + criteria.SetIgnoreEnabledFlag( true ); + if ( !criteria.BAddCondition( "name", k_EOperator_String_EQ, pszItemToGive, true ) || + !criteria.BSerializeToMsg( *msg.Body().mutable_criteria() ) ) + { + Msg( "Failed to add condition and/or serialize item grant request. This is probably caused by having a string that's too long.\n" ); + return; + } + GCClientSystem()->BSendMessage( msg ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::FireGameEvent( IGameEvent *event ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + // Listen for inventory updates in case our item gets changed while the user + // is looking at us. We want to re-do our entire layout since a quest might + // have been equipped / destroyed / completed and we need to re-categorize + // all the user's quests. + if ( FStrEq( event->GetName(), "inventory_updated" ) && !m_bWaitingForComplete ) + { + m_bInventoryDirty = true; + + if ( IsVisible() ) + { + if ( m_pQuestList ) + { + m_pQuestList->PopulateQuestLists(); + m_pQuestList->UpdateEmptyMessage(); + } + + UpdateQuestsItemPanels(); + } + } + else if ( FStrEq( event->GetName(), "gameui_hidden" ) ) + { + ShowPanel( false ); + return; + } + else if ( FStrEq( event->GetName(), "gc_connected" ) ) + { + m_bInventoryDirty = true; + InvalidateLayout( false, true ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::ShowPanel( bool bShow ) +{ + // Snag this so we know what to listen for + m_iQuestLogKey = gameuifuncs->GetButtonCodeForBind( "show_quest_log" ); + + if ( m_pQuestList && bShow ) + { + m_pQuestList->SetSelected( NULL, true ); + m_pQuestList->DirtyQuestLayout(); + } + + SetVisible( bShow ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::OnKeyCodePressed( KeyCode code ) +{ + if ( code == m_iQuestLogKey || code == STEAMCONTROLLER_B ) + { + ShowPanel( false ); + return; + } + + BaseClass::OnKeyCodePressed( code ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::OnKeyCodeTyped( KeyCode code ) +{ + if ( code == KEY_ESCAPE ) + { + if ( IsVisible() ) + { + SetVisible( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::SetVisible( bool bState ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + // Default to showing the active quests upon opening + if ( bState == true ) + { + UpdateQuestsItemPanels(); + + IGameEvent *event = gameeventmanager->CreateEvent( "questlog_opened" ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } + + if ( enginevgui->IsGameUIVisible() ) + { + AttachToGameUI(); + } + else + { + ipanel()->SetParent( GetVPanel(), VGui_GetClientDLLRootPanel() ); + + MakePopup( false, true ); + SetKeyBoardInputEnabled( true ); + SetMouseInputEnabled( true ); + MoveToFront(); + } + + engine->ClientCmd_Unrestricted( "gameui_preventescapetoshow\n" ); + vgui::surface()->PlaySound( "ui/panel_open.wav" ); + } + else if ( IsVisible() ) + { + // Detach from the GameUI when we hide + IViewPortPanel *pMMOverride = gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ); + if ( pMMOverride ) + { + ((CHudMainMenuOverride*)pMMOverride)->AttachToGameUI(); + } + + engine->ClientCmd_Unrestricted( "gameui_allowescapetoshow\n" ); + vgui::surface()->PlaySound( "ui/panel_close.wav" ); + } + + BaseClass::SetVisible( bState ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::QuestCompletedResponse() +{ + m_bWaitingForComplete = false; + m_bInventoryDirty = true; + + if ( m_pQuestList ) + m_pQuestList->QuestCompletedResponse(); + + //UpdateQuestsItemPanels(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::UpdateQuestsItemPanels() +{ + UpdateBadgeProgressPanels(); + + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + if ( m_bInventoryDirty ) + { + m_pQuestList->QuestCompletedResponse(); + + if ( m_pQuestList ) + { + m_pQuestList->PopulateQuestLists(); + m_pQuestList->UpdateEmptyMessage(); + } + } + + m_bInventoryDirty = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::MarkQuestsDirty() +{ + m_bInventoryDirty = true; + + if ( IsVisible() ) + { + UpdateQuestsItemPanels(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if any quest item panels are in the passed in state +//----------------------------------------------------------------------------- +bool CQuestLogPanel::AnyQuestItemPanelsInState( CQuestItemPanel::EItemPanelState_t eState ) const +{ + return m_pQuestList->AnyQuestItemPanelsInState( eState ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::OnCompleteQuest( void ) +{ + m_bWaitingForComplete = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuestLogPanel::UpdateBadgeProgressPanels() +{ + CEconOperationDefinition *pCurrentOperation = NULL; + const auto& mapOperations = GetItemSchema()->GetOperationDefinitions(); + FOR_EACH_MAP_FAST( mapOperations, i ) + { + CEconOperationDefinition* pOperation = mapOperations[i]; + if ( pOperation->IsActive() && pOperation->IsCampaign() ) + { + pCurrentOperation = pOperation; + break; + } + } + + if ( pCurrentOperation ) + { + CEconItemView *pCoin = TFInventoryManager()->GetLocalTFInventory()->FindFirstItembyItemDef( pCurrentOperation->GetRequiredItemDefIndex() ); + if ( pCoin ) + { + m_pProgressPanel->SetVisible( true ); + CItemModelPanel *pCoinPanel = m_pProgressPanel->FindControl< CItemModelPanel >( "CoinModelPanel" ); + if ( pCoinPanel ) + { + pCoinPanel->SetItem( pCoin ); + } + + uint32 nNumCompletedContracts = 0; + { + uint32 nCompletedContractsTemp = 0; + GetKilleaterValueByEvent( pCoin, kKillEaterEvent_CosmeticOperationContractsCompleted, nCompletedContractsTemp ); + nNumCompletedContracts = Max( nNumCompletedContracts, nCompletedContractsTemp ); + // Halloween has it's own thing for whatever reason + GetKilleaterValueByEvent( pCoin, kKillEaterEvent_HalloweenContractsCompleted, nCompletedContractsTemp ); + nNumCompletedContracts = Max( nNumCompletedContracts, nCompletedContractsTemp ); + Assert( pCurrentOperation->GetMaxDropCount() > 0 ); + } + + uint32 nNumContractPoints = 0; + { + uint32 nContractsPointsTemp = 0; + GetKilleaterValueByEvent( pCoin, kKillEaterEvent_CosmeticOperationContractsPoints, nContractsPointsTemp ); + nNumContractPoints = Max( nContractsPointsTemp, nNumContractPoints ); + // Halloween has it's own thing for whatever reason + GetKilleaterValueByEvent( pCoin, kKillEaterEvent_HalloweenSouls, nContractsPointsTemp ); + nNumContractPoints = Max( nContractsPointsTemp, nNumContractPoints ); + } + + EditablePanel *pBadgeContainer = m_pProgressPanel->FindControl< EditablePanel >( "BadgeMeterContainer" ); + if ( pBadgeContainer ) + { + ContinuousProgressBar *pBadgeProgressBar = pBadgeContainer->FindControl< ContinuousProgressBar >( "BadgeProgressMeter" ); + if ( pBadgeProgressBar ) + { + const char *pszLevelingDataName = GetItemSchema()->GetKillEaterScoreTypeLevelingDataName( kKillEaterEvent_HalloweenSouls ); + Assert( pszLevelingDataName ); + + const CUtlVector<CItemLevelingDefinition> *pLevelingData = GetItemSchema()->GetItemLevelingData( pszLevelingDataName ); + Assert( pLevelingData ); + + int nRequiredPointsToNextRank = 0; + int nRequiredPointsToCurrentRank = 0; + const char *pszCurrentLevelName = NULL; + FOR_EACH_VEC( (*pLevelingData), i ) + { + pszCurrentLevelName = (*pLevelingData)[i].GetNameLocalizationKey(); + + const uint32 nRank = (*pLevelingData)[i].GetRequiredScore(); + if ( nNumContractPoints < nRank ) + { + nRequiredPointsToNextRank = nRank; + break; + } + else + { + nRequiredPointsToCurrentRank = nRank; + } + } + + // if no next level, just use max points + if ( nRequiredPointsToNextRank == 0 ) + { + // assuming each contract's worth 130 (100 point + 30 bonus) + nRequiredPointsToNextRank = pCurrentOperation->GetMaxDropCount() * 130; + } + + float flProgress = (float)( nNumContractPoints - nRequiredPointsToCurrentRank ) / (float)( nRequiredPointsToNextRank - nRequiredPointsToCurrentRank ); + pBadgeProgressBar->SetProgress( flProgress ); + + CExLabel *pLabel = m_pProgressPanel->FindControl< CExLabel >( "BadgeProgressLabel" ); + if ( pLabel ) + { + pLabel->SetText( CConstructLocalizedString( g_pVGuiLocalize->Find( "QuestLog_BadgeProgress" ), g_pVGuiLocalize->Find( pszCurrentLevelName ) ) ); + } + + CExLabel *pScoreLabel = pBadgeContainer->FindControl< CExLabel >( "BadgeProgressMeterText" ); + if ( pScoreLabel ) + { + pScoreLabel->SetText( CFmtStr( "%d/%d", nNumContractPoints, nRequiredPointsToNextRank ) ); + } + } + } + + EditablePanel *pContractContainer = m_pProgressPanel->FindControl< EditablePanel >( "ContractMeterContainer" ); + if ( pContractContainer ) + { + ContinuousProgressBar *pContractProgressBar = pContractContainer->FindControl< ContinuousProgressBar >( "ContractsCompletedProgressMeter" ); + if ( pContractProgressBar ) + { + pContractProgressBar->SetProgress( (float)nNumCompletedContracts / (float)pCurrentOperation->GetMaxDropCount() ); + + CExLabel *pLabel = pContractContainer->FindControl< CExLabel >( "ContractsCompletedProgressMeterText" ); + if ( pLabel ) + { + pLabel->SetText( CFmtStr( "%d", nNumCompletedContracts ) ); + } + } + } + } + else + { + m_pProgressPanel->SetVisible( false ); + } + } + else + { + m_pProgressPanel->SetVisible( false ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler for when a quest has been completed +//----------------------------------------------------------------------------- +class CGCCompleteQuestCompleteResponse : public GCSDK::CGCClientJob +{ +public: + CGCCompleteQuestCompleteResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket ); + + itemid_t nNewToolID = 0; + if( !msg.BReadUint64Data( &nNewToolID ) ) + return true; + + CQuestLogPanel *pQuestLog = GetQuestLog(); + if ( pQuestLog ) + { + pQuestLog->QuestCompletedResponse(); + } + + return true; + } +}; + +GC_REG_JOB( GCSDK::CGCClient, CGCCompleteQuestCompleteResponse, "CGCCompleteQuestCompleteResponse", k_EMsgGCQuestCompleted, GCSDK::k_EServerTypeGCClient ); + + +#ifdef STAGING_ONLY +static void cc_tf_quest_log_reload() +{ + CQuestLogPanel *pQuestLog = GetQuestLog(); + if ( pQuestLog ) + { + pQuestLog->MarkQuestsDirty(); + pQuestLog->InvalidateLayout( true, true ); + gViewPortInterface->ShowPanel( pQuestLog, true ); + } +} +ConCommand tf_quest_log_reload( "tf_quest_log_reload", cc_tf_quest_log_reload ); +#endif |