//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "crafting_panel.h" #include "dynamic_recipe_subpanel.h" #include "vgui/ISurface.h" #include "vgui/ISystem.h" #include "c_tf_player.h" #include "gamestringpool.h" #include "iclientmode.h" #include "tf_item_inventory.h" #include "ienginevgui.h" #include #include "vgui_controls/TextImage.h" #include "vgui_controls/CheckButton.h" #include "vgui_controls/ComboBox.h" #include #include "vgui/IInput.h" #include "gcsdk/gcclient.h" #include "gcsdk/gcclientjob.h" #include "character_info_panel.h" #include "charinfo_loadout_subpanel.h" #include "econ_item_system.h" #include "econ_item_constants.h" #include "tf_hud_notification_panel.h" #include "tf_hud_chat.h" #include "c_tf_gamestats.h" #include "confirm_dialog.h" #include "econ_notifications.h" #include "gc_clientsystem.h" #include "charinfo_loadout_subpanel.h" #include "item_selection_criteria.h" #include "rtime.h" #include "c_tf_freeaccount.h" #include "econ_dynamic_recipe.h" // memdbgon must be the last include file in a .cpp file!!! #include static CDynamicRecipePanel* g_DynamicRecipePanel = NULL; extern const char *g_szItemBorders[AE_MAX_TYPES][5]; //----------------------------------------------------------------------------- // Purpose: Default to NULL item //----------------------------------------------------------------------------- CRecipeComponentItemModelPanel::CRecipeComponentItemModelPanel( vgui::Panel *parent, const char *name ) : CItemModelPanel( parent, name ) , m_nPageNumber( 0 ) { SetItem( NULL ); } //----------------------------------------------------------------------------- // Purpose: Add recipe to our list of recipes. Each call to this function // effectively adds an item to the next page //----------------------------------------------------------------------------- void CRecipeComponentItemModelPanel::AddRecipe( itemid_t nRecipe ) { RecipeItem_t& recipeItem = m_vecRecipes[m_vecRecipes.AddToTail()]; recipeItem.m_nRecipeIndex = nRecipe; UpdateRecipeItem( &recipeItem ); } //----------------------------------------------------------------------------- // Purpose: Wipe all recipes from all pages //----------------------------------------------------------------------------- void CRecipeComponentItemModelPanel::DeleteRecipes() { m_vecDefaultItems.Purge(); m_vecRecipes.Purge(); SetItem( NULL ); } //----------------------------------------------------------------------------- // Purpose: Override to set the item to be our default item if NULL is passed in. // Also handles greying out the panels //----------------------------------------------------------------------------- void CRecipeComponentItemModelPanel::SetItem( const CEconItemView *pItem ) { // Use the default item if they set NULL if( pItem == NULL ) { SetBlankState(); } else { BaseClass::SetItem( pItem ); SetGreyedOut( NULL ); } InvalidateLayout( true ); } void CRecipeComponentItemModelPanel::SetBlankState() { CEconItemView* pDefaultItem = NULL; if( m_nPageNumber < m_vecDefaultItems.Count() ) pDefaultItem = m_vecDefaultItems[ m_nPageNumber ]; BaseClass::SetItem( pDefaultItem ); SetGreyedOut( "" ); } //----------------------------------------------------------------------------- // Purpose: Move a recipe item to a specific page //----------------------------------------------------------------------------- void CRecipeComponentItemModelPanel::SetRecipeItem( itemid_t nRecipeItem, int nPageNumber ) { Assert( nPageNumber < m_vecRecipes.Count() ); m_vecRecipes[ nPageNumber ].m_nRecipeIndex = nRecipeItem; UpdateRecipeItem( &m_vecRecipes[ nPageNumber ] ); // Use the item that this item ID maps to SetItem( m_vecRecipes[ nPageNumber ].m_pRecipeItem ); } //----------------------------------------------------------------------------- // Purpose: Add a default item to a page //----------------------------------------------------------------------------- void CRecipeComponentItemModelPanel::AddDefaultItem( CEconItemView *pItem ) { m_vecDefaultItems[ m_vecDefaultItems.AddToTail() ] = pItem; } //----------------------------------------------------------------------------- // Purpose: Get a recipe item on a given page //----------------------------------------------------------------------------- CEconItemView* CRecipeComponentItemModelPanel::GetRecipeItem( int nPageNumber ) const { if( nPageNumber < m_vecRecipes.Count() ) { return m_vecRecipes[nPageNumber].m_pRecipeItem; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Get the recipe index from a specific page //----------------------------------------------------------------------------- itemid_t CRecipeComponentItemModelPanel::GetRecipeIndex( int nPageNumber ) const { if( nPageNumber < m_vecRecipes.Count() ) { return m_vecRecipes[nPageNumber].m_nRecipeIndex; } return INVALID_ITEM_ID; } //----------------------------------------------------------------------------- // Purpose: Iterate through all attributes on a item and turn recipe attributes // into input and output items. Store those items in vectors for inputs // and outputs. Inputs are sorted from least common to most common // so that the later pages are filled with more repeats than the early pages //----------------------------------------------------------------------------- bool CDynamicRecipePanel::CRecipeComponentAttributeCounter::OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) { static CSchemaAttributeDefHandle pAttrib_CannotTrade( "cannot trade" ); Assert( pAttrib_CannotTrade ); unsigned nCount = value.num_required() - value.num_fulfilled(); if( value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_OUTPUT ) { CEconItem* pItem = m_vecTempEconItems[m_vecTempEconItems.AddToTail( new CEconItem() )]; DecodeItemFromEncodedAttributeString( value, pItem ); for( unsigned i=0; i < nCount; ++i ) { CEconItemView& item = m_vecOutputItems[ m_vecOutputItems.AddToTail() ]; item.SetItemDefIndex( pItem->GetDefinitionIndex() ); item.SetItemQuality( pItem->GetQuality() ); item.SetItemLevel( pItem->GetItemLevel() ); item.SetItemID( pItem->GetItemID() ); item.SetNonSOEconItem( pItem ); // Set the item into the econ item view. item.SetInitialized( true ); item.SetItemOriginOverride( kEconItemOrigin_RecipeOutput ); // Spoof where we came from // Set the untradable flag if the attribute says so if( value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_UNTRADABLE ) { item.GetAttributeList()->SetRuntimeAttributeValue( pAttrib_CannotTrade, 0.0f ); // value doesn't matter -- we only check for presence/absence } } } else { m_nInputCount += nCount; CEconItem* pItem = m_vecTempEconItems[m_vecTempEconItems.AddToTail( new CEconItem() )]; DecodeItemFromEncodedAttributeString( value, pItem ); CUtlVector& inputSeries = m_vecInputItems[ m_vecInputItems.AddToTail() ]; for( unsigned i=0; i < nCount; ++i ) { InputComponent_t& item = inputSeries[ inputSeries.AddToTail() ]; item.m_ItemView.SetItemDefIndex( pItem->GetDefinitionIndex() ); item.m_ItemView.SetItemQuality( pItem->GetQuality() ); item.m_ItemView.SetItemLevel( 0 ); item.m_ItemView.SetItemID( pItem->GetItemID() ); item.m_ItemView.SetNonSOEconItem( pItem ); // Set the item into the econ item view. item.m_ItemView.SetInitialized( true ); item.m_ItemView.SetItemOriginOverride( kEconItemOrigin_RecipeOutput ); // Spoof where we came from item.m_pAttrib = pAttrDef; } m_vecInputItems.Sort( LeastCommonInputSortFunc ); } return true; } //----------------------------------------------------------------------------- // Purpose: Get the output item on a given page //----------------------------------------------------------------------------- CEconItemView* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetOutputItem( int i ) { if( i >= 0 && i < m_vecOutputItems.Count() ) return &m_vecOutputItems[i]; return NULL; } //----------------------------------------------------------------------------- // Purpose: Get the input item on a given page //----------------------------------------------------------------------------- CEconItemView* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetInputItem( int i ) { InputComponent_t* pInputComponent = GetInputComponent( i ); if( pInputComponent ) { return &pInputComponent->m_ItemView; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Get the attribute that the item in a given panel maps to //----------------------------------------------------------------------------- const CEconItemAttributeDefinition* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetInputAttrib( int i ) { InputComponent_t* pInputComponent = GetInputComponent( i ); if( pInputComponent ) { return pInputComponent->m_pAttrib; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Sort input vectors based on count. Fewer first. //----------------------------------------------------------------------------- int CDynamicRecipePanel::CRecipeComponentAttributeCounter::LeastCommonInputSortFunc( const CCopyableUtlVector *p1, const CCopyableUtlVector *p2 ) { return p1->Count() > p2->Count(); } //----------------------------------------------------------------------------- // Purpose: Find input component i from our 2D vector of input items //----------------------------------------------------------------------------- CDynamicRecipePanel::CRecipeComponentAttributeCounter::InputComponent_t* CDynamicRecipePanel::CRecipeComponentAttributeCounter::GetInputComponent( int i ) { int nAccum = 0; FOR_EACH_VEC( m_vecInputItems, nIndex ) { int nCount = m_vecInputItems[ nIndex ].Count(); if( i < nAccum + nCount ) { return &m_vecInputItems[ nIndex ][ i - nAccum ]; } nAccum += nCount; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Reset all data in the iterator //----------------------------------------------------------------------------- void CDynamicRecipePanel::CRecipeComponentAttributeCounter::Reset() { m_vecInputItems.Purge(); m_vecOutputItems.Purge(); m_vecTempEconItems.PurgeAndDeleteElements(); m_nInputCount = 0; } //----------------------------------------------------------------------------- // Purpose: Compare Check if m_pItemToMatch passes the criteria of any of the // attributes on m_pSourceItem. //----------------------------------------------------------------------------- bool CDynamicRecipePanel::CDynamicRecipeItemMatchFind::OnIterateAttributeValue( const CEconItemAttributeDefinition *pAttrDef, const CAttribute_DynamicRecipeComponent& value ) { // Can't match ourself if( m_pSourceItem && m_pItemToMatch && m_pSourceItem->GetID() == m_pItemToMatch->GetID() ) return true; if( value.component_flags() & DYNAMIC_RECIPE_FLAG_IS_OUTPUT ) return true; if( !DefinedItemAttribMatch( value, m_pItemToMatch ) ) return true; // Must be useable in crafting. Expensive -- do this last. if( !m_pItemToMatch || !m_pItemToMatch->IsUsableInCrafting() ) return true; // A match! m_bMatchesAny = true; return false; } //----------------------------------------------------------------------------- // Purpose: Delete all recipe data //----------------------------------------------------------------------------- void CInputPanelItemModelPanel::DeleteRecipes() { CRecipeComponentItemModelPanel::DeleteRecipes(); m_vecAttrDef.Purge(); } //----------------------------------------------------------------------------- // Purpose: Add component info to a new page //----------------------------------------------------------------------------- void CInputPanelItemModelPanel::AddComponentInfo( const CEconItemAttributeDefinition *pComponentAttrib ) { m_vecAttrDef[ m_vecAttrDef.AddToTail() ] = pComponentAttrib; AddRecipe( NULL ); } //----------------------------------------------------------------------------- // Purpose: Check if a passed in database item matches the desired item in this // item panel. Default to the current page //----------------------------------------------------------------------------- bool CInputPanelItemModelPanel::MatchesAttribCriteria( itemid_t itemID ) const { return MatchesAttribCriteria( itemID, m_nPageNumber ); } //----------------------------------------------------------------------------- // Purpose: Check if a passed in database item matches the desired item in this // item panel on the specified page. //----------------------------------------------------------------------------- bool CInputPanelItemModelPanel::MatchesAttribCriteria( itemid_t itemID, int nPageNumber ) const { if( !m_pDynamicRecipeItem ) return false; CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); if ( !pLocalInv ) return false; const CEconItemView* pItem = pLocalInv->GetInventoryItemByItemID( itemID ); if( !pItem || !pItem->IsUsableInCrafting() ) return false; const CEconItemAttributeDefinition* pAttrDef = GetAttrib( nPageNumber ); CAttribute_DynamicRecipeComponent attribValue; if( m_pDynamicRecipeItem->FindAttribute( pAttrDef, &attribValue ) ) { return DefinedItemAttribMatch( attribValue, pItem ); } return false; } //----------------------------------------------------------------------------- // Purpose: Get the attribute that this panel represents //----------------------------------------------------------------------------- const CEconItemAttributeDefinition* CInputPanelItemModelPanel::GetAttrib( int nPageNumber ) const { if( nPageNumber >= 0 && nPageNumber < m_vecAttrDef.Count() ) return m_vecAttrDef[ nPageNumber ]; return NULL; } void CInputPanelItemModelPanel::SetBlankState() { // Get the default item CEconItemView* pDefaultItem = NULL; if( m_nPageNumber < m_vecDefaultItems.Count() ) pDefaultItem = m_vecDefaultItems[ m_nPageNumber ]; // Grey out SetGreyedOut( "" ); // Check for the "item name text override" attribute on the default item static CSchemaAttributeDefHandle pAttrDef_ItemNameTextOverride( "item name text override" ); CAttribute_String attrItemNameTextOverride; if ( pDefaultItem ) { pDefaultItem->FindAttribute( pAttrDef_ItemNameTextOverride, &attrItemNameTextOverride ); if ( FStrEq( attrItemNameTextOverride.value().c_str(), "#TF_ItemName_Item" ) ) { // This is a dummy item. Dont display an icon. Just the name of the item CItemModelPanel::SetItem( NULL ); SetAttribOnly( true ); SetTextYPos( 0 ); //SetNoItemText( pDefaultItem->GetItemName(), NULL, NULL ); const wchar_t *pszItemname = pDefaultItem->GetItemName(); if ( V_wcscmp( pszItemname, g_pVGuiLocalize->Find( "#TF_ItemName_Item" ) ) == 0 && pDefaultItem->GetQuality() == AE_PAINTKITWEAPON ) { SetNoItemText( g_pVGuiLocalize->Find( "paintkitweapon" ), NULL, NULL ); } else { SetNoItemText( pDefaultItem->GetItemName(), NULL, NULL ); } } else { BaseClass::SetItem( pDefaultItem ); } } else { // Construct an item from the attribute that describes this input CEconItem tempItem; const CEconItemAttributeDefinition* pAttrDef = GetAttrib( m_nPageNumber ); CAttribute_DynamicRecipeComponent attribValue; if( m_pDynamicRecipeItem->FindAttribute( pAttrDef, &attribValue ) ) { DecodeItemFromEncodedAttributeString( attribValue, &tempItem ); } // Shove it into an econitemview CEconItemView tempView; tempView.Init( tempItem.GetItemDefIndex(), tempItem.GetQuality(), 0 ); tempView.SetNonSOEconItem( &tempItem ); // Set its name as the text for this item model panel CItemModelPanel::SetItem( NULL ); SetAttribOnly( true ); SetTextYPos( 0 ); SetNoItemText( tempView.GetItemName(), NULL, NULL ); } } //----------------------------------------------------------------------------- // Purpose: Is this in play //----------------------------------------------------------------------------- bool CRecipeComponentItemModelPanel::IsSlotAvailable( int nPageNumber ) { return nPageNumber < m_vecRecipes.Count(); } //----------------------------------------------------------------------------- // Purpose: Sets the passed in recipe item and changes the item we show //----------------------------------------------------------------------------- void CRecipeComponentItemModelPanel::UpdateRecipeItem( RecipeItem_t* pRecipeItem ) { Assert( pRecipeItem ); CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); if ( pLocalInv == NULL ) return; pRecipeItem->m_pRecipeItem = pLocalInv->GetInventoryItemByItemID( pRecipeItem->m_nRecipeIndex ); SetItem( pRecipeItem->m_pRecipeItem ); } //----------------------------------------------------------------------------- // Purpose: Update the item we show for the current page //----------------------------------------------------------------------------- void CRecipeComponentItemModelPanel::UpdateDisplayItem() { if( m_nPageNumber < m_vecRecipes.Count() ) { UpdateRecipeItem( &m_vecRecipes[ m_nPageNumber ] ); } } //----------------------------------------------------------------------------- // Purpose: Set the current page //----------------------------------------------------------------------------- void CRecipeComponentItemModelPanel::SetPageNumber( int nPageNumber ) { Assert( nPageNumber >= 0 ); m_nPageNumber = nPageNumber; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CDynamicRecipePanel::CDynamicRecipePanel( vgui::Panel *parent, const char *panelName, CEconItemView* pRecipeItem ) : CBackpackPanel( parent, panelName ) , m_pDynamicRecipeItem( pRecipeItem ) , m_pRecipeCraftButton( NULL ) , m_nNumRecipeItems( 0 ) , m_bAllRecipePanelsFilled( false ) , m_bInputPanelsDirty( false ) , m_nInputPage( 0 ) , m_nOutputPage( 0 ) , m_pCurInputPageLabel( NULL ) , m_pNextInputPageButton( NULL ) , m_pPrevInputPageButton( NULL ) , m_flAbortCraftingAt( 0 ) , m_pMouseOverItemPanel( NULL ) , m_pNoMatchesLabel( NULL ) , m_pUntradableOutputsLabel( NULL ) , m_bShowUntradable( false ) { g_DynamicRecipePanel = this; m_pRecipeContainer = new vgui::EditablePanel( this, "recipecontainer" ); m_pInventoryContainer = new vgui::EditablePanel( this, "inventorycontainer" ); m_pShowUntradableItemsCheckbox = new vgui::CheckButton( m_pInventoryContainer, "untradablecheckbox", "#Dynamic_Recipe_Untradable_Checkbox" ); m_pShowUntradableItemsCheckbox->AddActionSignalTarget( this ); m_pInputsLabel = new CExLabel( m_pRecipeContainer, "InputLabel", "#Craft_Recipe_Inputs" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CDynamicRecipePanel::~CDynamicRecipePanel( void ) { } //----------------------------------------------------------------------------- // Purpose: Get all our controls //----------------------------------------------------------------------------- void CDynamicRecipePanel::ApplySchemeSettings( vgui::IScheme *pScheme ) { LoadControlSettings( GetResFile() ); // This calls AddNewItemPanel BaseClass::ApplySchemeSettings( pScheme ); CExButton* pCancelButton = dynamic_cast( m_pInventoryContainer->FindChildByName("CancelButton") ); if ( pCancelButton ) pCancelButton->AddActionSignalTarget( this ); m_pRecipeCraftButton = dynamic_cast( m_pRecipeContainer->FindChildByName("CraftButton") ); if ( m_pRecipeCraftButton ) m_pRecipeCraftButton->AddActionSignalTarget( this ); m_pNextPageButton = dynamic_cast( m_pInventoryContainer->FindChildByName("NextPageButton") ); if( m_pNextPageButton ) m_pNextPageButton->AddActionSignalTarget( this ); m_pPrevPageButton = dynamic_cast( m_pInventoryContainer->FindChildByName("PrevPageButton") ); if( m_pPrevPageButton ) m_pPrevPageButton->AddActionSignalTarget( this ); m_pCurPageLabel = dynamic_cast( m_pInventoryContainer->FindChildByName("CurPageLabel") ); Assert( m_pCurPageLabel ); m_pNoMatchesLabel = dynamic_cast( m_pInventoryContainer->FindChildByName( "NoMatches" ) ); Assert( m_pNoMatchesLabel ); m_pUntradableOutputsLabel = dynamic_cast( m_pRecipeContainer->FindChildByName( "UntradableLabel" ) ); Assert( m_pUntradableOutputsLabel ); m_pOutputsLabel = dynamic_cast( m_pRecipeContainer->FindChildByName( "OutputLabel" ) ); Assert( m_pOutputsLabel ); m_pCurInputPageLabel = dynamic_cast( m_pRecipeContainer->FindChildByName( "CurInputPageLabel" ) ); m_pNextInputPageButton = dynamic_cast( m_pRecipeContainer->FindChildByName( "NextInputPageButton" ) ); if( m_pNextInputPageButton ) m_pNextInputPageButton->AddActionSignalTarget( this ); m_pPrevInputPageButton = dynamic_cast( m_pRecipeContainer->FindChildByName( "PrevInputPageButton" ) ); if( m_pPrevInputPageButton ) m_pPrevInputPageButton->AddActionSignalTarget( this ); #ifdef STAGING_ONLY m_pDevGiveInputsButton = new CExButton( m_pInventoryContainer, "dev_giveinputsbutton", "[Debug] Give Inputs", this, "dev_giveinputs" ); m_pDevGiveInputsButton->SetEnabled( true ); m_pDevGiveInputsButton->SetVisible( true ); #endif InvalidateLayout(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDynamicRecipePanel::ApplySettings( KeyValues *inResourceData ) { BaseClass::ApplySettings( inResourceData ); } //----------------------------------------------------------------------------- // Purpose: Update all the item panels and page buttons and labels //----------------------------------------------------------------------------- void CDynamicRecipePanel::PerformLayout( void ) { BaseClass::PerformLayout(); #ifdef STAGING_ONLY if( m_pDevGiveInputsButton ) { m_pDevGiveInputsButton->SetPos( XRES(0) , YRES(290) ); m_pDevGiveInputsButton->SetWide( 210 ); m_pDevGiveInputsButton->SetTall( 40 ); m_pDevGiveInputsButton->MoveToFront(); m_pDevGiveInputsButton->SetEnabled( true ); m_pDevGiveInputsButton->SetVisible( true ); } #endif if( m_pSortByComboBox ) { m_pSortByComboBox->SetVisible( false ); } UpdateModelPanels(); bool bNoMatches = m_nNumRecipeItems == 0; bool bMultiplePagesOfMatches = m_nNumRecipeItems > (unsigned)GetNumBackpackPanelsPerPage(); // Some panels show and hide based on the number of matches if( m_pNoMatchesLabel) m_pNoMatchesLabel->SetVisible( bNoMatches ); if( m_pNextPageButton ) m_pNextPageButton->SetVisible( bMultiplePagesOfMatches ); if( m_pPrevPageButton ) m_pPrevPageButton->SetVisible( bMultiplePagesOfMatches ); if( m_pCurPageLabel ) m_pCurPageLabel->SetVisible( bMultiplePagesOfMatches ); bool bMultiplePagesOfInputs = m_RecipeIterator.GetInputCount() > GetNumInputPanelsPerPage(); if( m_pNextInputPageButton ) m_pNextInputPageButton->SetVisible( bMultiplePagesOfInputs ); if( m_pNextInputPageButton ) m_pNextInputPageButton->SetEnabled( m_nInputPage < GetNumInputPages() - 1 ); if( m_pCurInputPageLabel ) m_pCurInputPageLabel->SetVisible( bMultiplePagesOfInputs ); if( m_pPrevInputPageButton ) m_pPrevInputPageButton->SetVisible( bMultiplePagesOfInputs ); if( m_pPrevInputPageButton ) m_pPrevInputPageButton->SetEnabled( m_nInputPage > 0 ); if( m_pUntradableOutputsLabel ) m_pUntradableOutputsLabel->SetVisible( m_pDynamicRecipeItem ? !m_pDynamicRecipeItem->IsTradable() : false ); } //----------------------------------------------------------------------------- // Purpose: Handle commands //----------------------------------------------------------------------------- void CDynamicRecipePanel::OnCommand( const char *command ) { if ( !Q_strnicmp( command, "back", 4 ) ) { PostMessage( GetParent(), new KeyValues("CraftingClosed") ); return; } else if ( !Q_strnicmp( command, "craft", 5 ) ) { // Check if we should warn about partial completion if( WarnAboutPartialCompletion() ) { if( CheckForUntradableItems() ) { Craft(); } } return; } else if ( !Q_stricmp( command, "reloadscheme" ) ) { InvalidateLayout( true, true ); // deliberatly fallthrough to baseclass } else if( !Q_stricmp( command, "cancel" ) ) { SetVisible( false ); return; } else if ( !Q_strnicmp( command, "nextpage", 8 ) ) { InvalidateLayout(); // deliberatly fallthrough to baseclass } else if ( !Q_strnicmp( command, "prevpage", 8 ) ) { InvalidateLayout(); // deliberatly fallthrough to baseclass } else if( !Q_strnicmp( command, "nextinputpage", 13 ) ) { if( m_nInputPage < GetNumInputPages() ) m_nInputPage++; InvalidateLayout(); return; } else if( !Q_strnicmp( command, "previnputpage", 13 ) ) { if( m_nInputPage > 0 ) m_nInputPage--; InvalidateLayout(); return; } else if( !Q_strnicmp( command, "deleteitem", 10 ) || !Q_strnicmp( command, "useitem", 7 ) ) { // Gobble up these commands return; } #ifdef STAGING_ONLY else if( !Q_strnicmp( command, "dev_giveinputs", 14 ) ) { Debug_GiveRequiredInputs(); if( m_pDevGiveInputsButton ) m_pDevGiveInputsButton->SetEnabled( false ); } #endif BaseClass::OnCommand( command ); } //----------------------------------------------------------------------------- // Purpose: Gobble up all keyboard input for now! //----------------------------------------------------------------------------- void CDynamicRecipePanel::OnKeyCodePressed( vgui::KeyCode /*code*/ ) { return; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CDynamicRecipePanel::OnButtonChecked( KeyValues *pData ) { Panel *pPanel = reinterpret_cast( pData->GetPtr("panel") ); if ( m_pShowUntradableItemsCheckbox == pPanel ) { if ( m_bShowUntradable != m_pShowUntradableItemsCheckbox->IsSelected() ) { m_bShowUntradable = m_pShowUntradableItemsCheckbox->IsSelected(); InitItemPanels(); UpdateModelPanels(); } } } int CDynamicRecipePanel::GetNumItemPanels( void ) { return DYNAMIC_RECIPE_INPUT_COUNT + DYNAMIC_RECIPE_OUTPUT_COUNT + DYNAMIC_RECIPE_PACKPACK_COUNT_PER_PAGE; } //----------------------------------------------------------------------------- // Purpose: Check to see that each and every input panel has a recipe item in it //----------------------------------------------------------------------------- bool CDynamicRecipePanel::AllRecipePanelsFilled( void ) { // Need to recalculate if( m_bInputPanelsDirty ) { // Assume all filled, and go through and try to find one that's empty m_bAllRecipePanelsFilled = true; FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) { CInputPanelItemModelPanel* pInputPanel = m_vecRecipeInputModelPanels[i]; for( int j=0; j < GetNumInputPages(); ++j ) { if( pInputPanel->GetRecipeItem( j ) == NULL && pInputPanel->GetAttrib( j ) != NULL ) { m_bAllRecipePanelsFilled = false; break; } } } } return m_bAllRecipePanelsFilled; } //----------------------------------------------------------------------------- // Purpose: Callback for the confirm partial completion dialog //----------------------------------------------------------------------------- void ConfirmDestroyItems( bool bConfirmed, void* pContext ) { CDynamicRecipePanel *pRecipePanel = ( CDynamicRecipePanel* )pContext; if ( pRecipePanel && bConfirmed ) { if( pRecipePanel->CheckForUntradableItems() ) { pRecipePanel->Craft(); } } } //----------------------------------------------------------------------------- // Purpose: Callback for the conrim untradable dialog //----------------------------------------------------------------------------- static void ConfirmUntradableCraft( bool bConfirmed, void* pContext ) { CDynamicRecipePanel *pRecipePanel = ( CDynamicRecipePanel* )pContext; if ( pRecipePanel && bConfirmed ) { pRecipePanel->Craft(); } } //----------------------------------------------------------------------------- // Purpose: Go through each input panel and find out if any of them contain an // item that is untradeable //----------------------------------------------------------------------------- bool CDynamicRecipePanel::CheckForUntradableItems( void ) { // We dont care if the recipe itself is already not tradable if( !m_pDynamicRecipeItem->IsTradable() ) { return true; } bool bHasUntradable = false; for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; ++i ) { CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i]; for( int j = 0; j < GetNumInputPages(); ++j ) { itemid_t nRecipeItemID = pPanel->GetRecipeIndex( j ); if ( nRecipeItemID != 0 && nRecipeItemID != INVALID_ITEM_ID ) { CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( nRecipeItemID ); if ( pItemData->IsTradable() == false ) { bHasUntradable = true; break; } } } } if ( bHasUntradable ) { enum { kWarningLength = 512 }; locchar_t wszWarning[ kWarningLength ] = LOCCHAR(""); loc_scpy_safe( wszWarning, CConstructLocalizedString( GLocalizationProvider()->Find( "Dynamic_Recipe_Untradable_Text" ), m_pDynamicRecipeItem->GetItemName() ) ); CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#Craft_Untradable_Title", wszWarning, "#GameUI_OK", "#Cancel", &ConfirmUntradableCraft, NULL ); if ( pDialog ) { pDialog->SetContext( this ); pDialog->Show(); } return false; } return true; } //----------------------------------------------------------------------------- // Purpose: Check if not all of the inputs have items set into them. Put up // a prompt and return false if so. //----------------------------------------------------------------------------- bool CDynamicRecipePanel::WarnAboutPartialCompletion( void ) { if( !AllRecipePanelsFilled() ) { enum { kWarningLength = 512 }; locchar_t wszWarning[ kWarningLength ] = LOCCHAR(""); loc_scpy_safe( wszWarning, CConstructLocalizedString( GLocalizationProvider()->Find( "Dynamic_Recipe_Partial_Completion_Warning" ), m_pDynamicRecipeItem->GetItemName() ) ); CTFGenericConfirmDialog *pDialog = new CTFGenericConfirmDialog( "#Craft_Untradable_Title", wszWarning, "#GameUI_OK", "#Cancel", &ConfirmDestroyItems, NULL ); if ( pDialog ) { pDialog->SetContext( this ); pDialog->Show(); } return false; } return true; } //----------------------------------------------------------------------------- // Purpose: They hit the craft button! Cook up a message that we're going to send // to the GC that contains all of the item_ids and attributes they are // to apply to. Open a crafting status dialog when after we send the message. //----------------------------------------------------------------------------- void CDynamicRecipePanel::Craft() { GCSDK::CProtoBufMsg msg( k_EMsgGCFulfillDynamicRecipeComponent ); msg.Body().set_tool_item_id( m_pDynamicRecipeItem->GetItemID() ); FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) { CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i]; for( int j=0; j < GetNumInputPages(); ++j ) { itemid_t nRecipeItemID = pPanel->GetRecipeIndex( j ); if( nRecipeItemID != 0 && nRecipeItemID != INVALID_ITEM_ID ) { if( !pPanel->MatchesAttribCriteria( nRecipeItemID, j ) ) { AssertMsg( 0, "Input panel has recipe item that does not pass its criteria" ); // Something bad happened. Return this item to the backpack. ReturnRecipeItemToBackpack( nRecipeItemID, pPanel, j ); continue; } // Add component CMsgRecipeComponent* pComponent = msg.Body().add_consumption_components(); pComponent->set_subject_item_id( nRecipeItemID ); const CEconItemAttributeDefinition* pAttribute = pPanel->GetAttrib( j ); if( !pAttribute ) { AssertMsg( 0, "NULL attribute in panel what attempting to craft" ); // Something bad happened. Return this item to the backpack. ReturnRecipeItemToBackpack( nRecipeItemID, pPanel, j ); continue; } pComponent->set_attribute_index( pAttribute->GetDefinitionIndex() ); } } } EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pDynamicRecipeItem, "consumed_item" ); GCClientSystem()->BSendMessage( msg ); // Open a craft status window to take focus away and let the user know something is happening OpenCraftingStatusDialog( this, "#CraftUpdate_Start", true, false, false ); m_flAbortCraftingAt = vgui::system()->GetCurrentTime() + 10; vgui::ivgui()->AddTickSignal( GetVPanel(), 100 ); } //----------------------------------------------------------------------------- // Purpose: Think when we're waiting for a craft response //----------------------------------------------------------------------------- void CDynamicRecipePanel::OnTick( void ) { BaseClass::OnTick(); if( IsVisible() ) { if( m_flAbortCraftingAt != 0.f ) { // Timeout for crafting. Let them know we failed. if( m_flAbortCraftingAt < vgui::system()->GetCurrentTime() ) { OpenCraftingStatusDialog( this, "#CraftUpdate_Failed", false, true, false ); m_flAbortCraftingAt = 0; } } } } //----------------------------------------------------------------------------- // Purpose: Close the craft status window if we close //----------------------------------------------------------------------------- void CDynamicRecipePanel::OnShowPanel( bool bVisible, bool bReturningFromArmory ) { if ( !bVisible ) { CloseCraftingStatusDialog(); vgui::ivgui()->RemoveTickSignal( GetVPanel() ); } BaseClass::OnShowPanel( bVisible, bReturningFromArmory ); } //----------------------------------------------------------------------------- // Purpose: Add the new panels into vectors for each group, and set parents to // appropriate frames //----------------------------------------------------------------------------- void CDynamicRecipePanel::AddNewItemPanel( int iPanelIndex ) { if( IsInputPanel( iPanelIndex ) ) { // Input item int nIndex = m_vecRecipeInputModelPanels.AddToTail(); CInputPanelItemModelPanel* pPanel = vgui::SETUP_PANEL( new CInputPanelItemModelPanel( this, VarArgs("modelpanel%d", iPanelIndex), m_pDynamicRecipeItem )); m_pItemModelPanels.AddToTail( pPanel ); pPanel->SetParent( m_pRecipeContainer ); m_vecRecipeInputModelPanels[nIndex] = pPanel; } else if( IsOutputPanel( iPanelIndex ) ) { // Output item CItemModelPanel* pPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, VarArgs("modelpanel%d", iPanelIndex) ) ); m_vecRecipeOutputModelPanels.AddToTail( pPanel ); m_pItemModelPanels.AddToTail( pPanel ); pPanel->SetParent( m_pRecipeContainer ); } else { // Inventory item int nIndex = m_vecBackpackModelPanels.AddToTail(); CRecipeComponentItemModelPanel* pPanel = vgui::SETUP_PANEL( new CRecipeComponentItemModelPanel( this, VarArgs("modelpanel%d", iPanelIndex) ) ); m_vecBackpackModelPanels[nIndex] = pPanel; m_pItemModelPanels.AddToTail( pPanel ); pPanel->SetParent( m_pInventoryContainer ); } CItemModelPanel* pPanel = m_pItemModelPanels.Tail(); pPanel->SetActAsButton( true, true ); pPanel->SetTooltip( m_pMouseOverTooltip, "" ); pPanel->SetShowGreyedOutTooltip( true ); pPanel->SetShowEquipped( true ); // Store a position for our new panel m_ItemModelPanelPos.AddToTail(); m_ItemModelPanelPos[iPanelIndex].x = m_ItemModelPanelPos[iPanelIndex].y = 0; Assert( iPanelIndex == (m_pItemModelPanels.Count()-1) ); } //----------------------------------------------------------------------------- // Purpose: Is this index an input panel //----------------------------------------------------------------------------- bool CDynamicRecipePanel::IsInputPanel( int iPanelIndex ) const { return iPanelIndex < DYNAMIC_RECIPE_INPUT_COUNT; } //----------------------------------------------------------------------------- // Purpose: Is this index an output panel //----------------------------------------------------------------------------- bool CDynamicRecipePanel::IsOutputPanel( int iPanelIndex) const { return iPanelIndex < ( DYNAMIC_RECIPE_OUTPUT_COUNT + DYNAMIC_RECIPE_INPUT_COUNT ) && !IsInputPanel(iPanelIndex); } //----------------------------------------------------------------------------- // Purpose: Is this index a backpack panel //----------------------------------------------------------------------------- bool CDynamicRecipePanel::IsBackpackPanel( int iPanelIndex ) const { return ( iPanelIndex >= ( DYNAMIC_RECIPE_INPUT_COUNT + DYNAMIC_RECIPE_OUTPUT_COUNT ) && !IsInputPanel( iPanelIndex ) && !IsOutputPanel( iPanelIndex ) ); } //----------------------------------------------------------------------------- // Purpose: Is this backpack panel on this page //----------------------------------------------------------------------------- bool CDynamicRecipePanel::IsInvPanelOnThisPage( unsigned nIndex ) const { unsigned nNumPerPage = DYNAMIC_RECIPE_BACKPACK_ROWS * DYNAMIC_RECIPE_BACKPACK_COLS; unsigned nMinIndex = nNumPerPage * GetCurrentPage(); unsigned nMaxIndex = nNumPerPage * (GetCurrentPage() + 1); return nIndex >= nMinIndex && nIndex < nMaxIndex; } //----------------------------------------------------------------------------- // Purpose: Given the number of items that match the recipe, how many pages // do we need to show //----------------------------------------------------------------------------- int CDynamicRecipePanel::GetNumPages() { return ceil( float(m_nNumRecipeItems) / float(GetNumBackpackPanelsPerPage()) ); } //----------------------------------------------------------------------------- // Purpose: Sets the page of the backpack panels //----------------------------------------------------------------------------- void CDynamicRecipePanel::SetCurrentPage( int nNewPage ) { if ( nNewPage < 0 ) { nNewPage = GetNumPages() - 1; } else if ( nNewPage >= GetNumPages() ) { nNewPage = 0; } FOR_EACH_VEC( m_vecBackpackModelPanels, i ) { m_vecBackpackModelPanels[i]->SetPageNumber( nNewPage ); } BaseClass::SetCurrentPage( nNewPage ); } //----------------------------------------------------------------------------- // Purpose: Chance the current page for all input panels //----------------------------------------------------------------------------- void CDynamicRecipePanel::SetCurrentInputPage( int nNewPage ) { m_nInputPage = nNewPage; FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) { m_vecRecipeInputModelPanels[i]->SetPageNumber( nNewPage ); } } //----------------------------------------------------------------------------- // Purpose: Given the number of input items, how many input pages do we need to show //----------------------------------------------------------------------------- int CDynamicRecipePanel::GetNumInputPages() const { return ceil( float(m_RecipeIterator.GetInputCount()) / float(GetNumInputPanelsPerPage()) ); } //----------------------------------------------------------------------------- // Purpose: Given the number of output items, how many output pages do we need to show //----------------------------------------------------------------------------- int CDynamicRecipePanel::GetNumOutputPage() const { return ceil( float(m_RecipeIterator.GetOutputCount()) / float(GetNumOutputPanelsPerPage()) ); } //----------------------------------------------------------------------------- // Purpose: Reset all of the item panels, their pages, their info and re-evaluate // the recipe item for attributes and the backpack for matching items //----------------------------------------------------------------------------- void CDynamicRecipePanel::InitItemPanels() { // Clear out every panel's items FOR_EACH_VEC( m_pItemModelPanels, i ) { m_pItemModelPanels[i]->SetItem( NULL ); } // Go through and set the item to all inventory panels to NULL FOR_EACH_VEC( m_vecBackpackModelPanels, i ) { m_vecBackpackModelPanels[i]->DeleteRecipes(); } // Go through our backpack and repopulate our backpack and matching components FindPossibleBackpackItems(); // Reset to the first page SetCurrentInputPage( 0 ); SetCurrentPage( 0 ); // Go through and set default items on input panels FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) { CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i]; // Clear out any recipes we have pPanel->DeleteRecipes(); // Clear out stale info pPanel->SetDynamicRecipeItem( m_pDynamicRecipeItem ); } for( int i=0; i < m_RecipeIterator.GetInputCount(); ++i ) { CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[ i % m_vecRecipeInputModelPanels.Count() ]; pPanel->AddDefaultItem( m_RecipeIterator.GetInputItem( i ) ); pPanel->AddComponentInfo( m_RecipeIterator.GetInputAttrib( i ) ); } // Go through and set items into output panels FOR_EACH_VEC( m_vecRecipeOutputModelPanels, i ) { CItemModelPanel* pPanel = m_vecRecipeOutputModelPanels[i]; // Set appropriate item for output panel if( i < m_RecipeIterator.GetOutputCount() ) { int nOutputIndex = (m_nOutputPage * DYNAMIC_RECIPE_OUTPUT_COUNT) + i; pPanel->SetItem( m_RecipeIterator.GetOutputItem( nOutputIndex ) ); } } // Go through all the recipe items and set them into inventory panels PopulatePanelsForCurrentPage(); UpdateModelPanels(); } //----------------------------------------------------------------------------- // Purpose: Fills in the backpack slots with the items for the current page //----------------------------------------------------------------------------- void CDynamicRecipePanel::PopulatePanelsForCurrentPage() { // Go through all the recipe items and set them into inventory panels FOR_EACH_VEC( m_vecBackpackModelPanels, i ) { CRecipeComponentItemModelPanel* pPanel = m_vecBackpackModelPanels[i]; // Update the item we display. Our inventory might have shifted around pPanel->UpdateDisplayItem(); bool bIsSlotOpen = pPanel->IsSlotAvailable( GetCurrentPage() ); pPanel->SetVisible( bIsSlotOpen ); pPanel->SetEnabled( bIsSlotOpen ); pPanel->InvalidateLayout(); SetBorderForItem( pPanel, false ); } FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) { CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i]; pPanel->SetPageNumber( m_nInputPage ); pPanel->UpdateDisplayItem(); bool bIsSlotOpen = pPanel->IsSlotAvailable( m_nInputPage ); pPanel->SetVisible( bIsSlotOpen ); pPanel->SetEnabled( bIsSlotOpen ); pPanel->InvalidateLayout(); SetBorderForItem( pPanel, false ); } } //----------------------------------------------------------------------------- // Purpose: Go through the player's entire backpack and check if each of them // passes any of the input panel's attributes criteria //----------------------------------------------------------------------------- void CDynamicRecipePanel::FindPossibleBackpackItems() { Assert( m_pDynamicRecipeItem->GetSOCData() ); // Iterate through the attributes on our recipe item // and create all of our input and output items. m_RecipeIterator.Reset(); CEconItem* pEconItem = m_pDynamicRecipeItem->GetSOCData() ; pEconItem->IterateAttributes( &m_RecipeIterator ); CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); if ( pLocalInv == NULL ) return; m_nNumRecipeItems = 0; // Go through our backpack and filter items that match our input criteria for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i ) { CEconItemView *pItem = pLocalInv->GetItem( i ); Assert( pItem ); // If we're not showing untradable items, and this item is untradeable // then we just skip it if ( !pItem->IsTradable() && !m_bShowUntradable ) continue; CDynamicRecipeItemMatchFind matchingIterator( m_pDynamicRecipeItem, pItem ); m_pDynamicRecipeItem->IterateAttributes( &matchingIterator ); if( matchingIterator.MatchesAnyAttributes() ) { // Set in the recipe m_vecBackpackModelPanels[ m_nNumRecipeItems % GetNumBackpackPanelsPerPage() ]->AddRecipe( pItem->GetItemID() ); ++m_nNumRecipeItems; } } InvalidateLayout(); } //----------------------------------------------------------------------------- // Purpose: Layout the item panels //----------------------------------------------------------------------------- void CDynamicRecipePanel::PositionItemPanel( CItemModelPanel *pPanel, int iIndex ) { int iCenter = 0; int iButtonX = 0, iButtonY = 0, iXPos = 0, iYPos = 0; // Position all of the panels if( IsInputPanel( iIndex ) ) { iButtonX = (iIndex % CRAFTING_SLOTS_INPUT_COLUMNS); iButtonY = (iIndex / CRAFTING_SLOTS_INPUT_COLUMNS); iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * pPanel->GetWide()) + (m_iItemBackpackXDelta * iButtonX); iYPos = m_iItemYPos + (iButtonY * pPanel->GetTall() ) + (m_iItemBackpackYDelta * iButtonY); pPanel->SetPos( iXPos, iYPos ); } else if( IsOutputPanel( iIndex ) ) { int iButtonIndex = iIndex - DYNAMIC_RECIPE_INPUT_COUNT; iButtonX = (iButtonIndex % CRAFTING_SLOTS_OUTPUT_COLUMNS); iButtonY = (iButtonIndex / CRAFTING_SLOTS_OUTPUT_COLUMNS); iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * pPanel->GetWide()) + (m_iItemBackpackXDelta * iButtonX); iYPos = m_iOutputItemYPos + (iButtonY * pPanel->GetTall() ) + (m_iItemBackpackYDelta * iButtonY); pPanel->SetPos( iXPos, iYPos ); pPanel->SetItem( m_RecipeIterator.GetOutputItem( iButtonIndex ) ); } else if( IsBackpackPanel( iIndex ) ) { int iButtonIndex = iIndex - DYNAMIC_RECIPE_INPUT_COUNT - DYNAMIC_RECIPE_OUTPUT_COUNT; iButtonX = (iButtonIndex % DYNAMIC_RECIPE_BACKPACK_COLS); iButtonY = (iButtonIndex / DYNAMIC_RECIPE_BACKPACK_COLS); iXPos = (iCenter + m_iInventoryXPos) + (iButtonX * pPanel->GetWide()) + (m_iItemBackpackXDelta * iButtonX); iYPos = m_iInventoryYPos + (iButtonY * pPanel->GetTall() ) + (m_iItemBackpackYDelta * iButtonY); pPanel->SetPos( iXPos, iYPos ); } } //----------------------------------------------------------------------------- // Purpose: Update each panel to see if it should show, what item to show, and if // it's greyed out. Update the page labels here too. //----------------------------------------------------------------------------- void CDynamicRecipePanel::UpdateModelPanels( void ) { // Check if any input panels have recipe items in them bool bAnyInputHasRecipe = false; FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) { CInputPanelItemModelPanel* pPanel = m_vecRecipeInputModelPanels[i]; // Update the item we display. Our inventory might have shifted around pPanel->UpdateDisplayItem(); SetBorderForItem( pPanel, false ); // Check for a recipe for( int j=0; j < GetNumInputPages(); ++j ) { CEconItemView* pRecipe = pPanel->GetRecipeItem( j ); bAnyInputHasRecipe |= pRecipe != NULL; } // Input panels are visible and enabled if their recipe slot is available pPanel->SetEnabled( pPanel->GetDefaultItem() != NULL ); pPanel->SetVisible( pPanel->GetDefaultItem() != NULL ); } static CSchemaAttributeDefHandle pAttrib_NoPartialComplete( "recipe no partial complete" ); bool bPartialCompletionAllowed = !m_pDynamicRecipeItem->FindAttribute( pAttrib_NoPartialComplete ); m_pInputsLabel->SetText( bPartialCompletionAllowed ? "#Craft_Recipe_Inputs" : "#Dynamic_Recipe_Outputs_No_Partial_Complete" ); // If any of the input panels have a recipe in them, then crafting is available if ( m_pRecipeCraftButton ) { bool bCraftEnabled = ( bPartialCompletionAllowed && bAnyInputHasRecipe ) || AllRecipePanelsFilled(); m_pRecipeCraftButton->SetEnabled( bCraftEnabled ); } if ( m_pRecipeCraftButton ) { m_pRecipeCraftButton->SetText( AllRecipePanelsFilled() ? "#CraftConfirm" : "#ToolCustomizeTextureOKButton" ); } if ( m_pOutputsLabel ) { m_pOutputsLabel->SetText( AllRecipePanelsFilled() ? "#Craft_Recipe_Outputs" : "#Dynamic_Recipe_Outputs_Not_Complete"); m_pOutputsLabel->SetFgColor( AllRecipePanelsFilled() ? Color( 200, 80, 60, 255 ) : Color( 117, 107, 94, 255 ) ); } FOR_EACH_VEC( m_vecRecipeOutputModelPanels, i ) { CItemModelPanel* pPanel = m_vecRecipeOutputModelPanels[i]; SetBorderForItem( pPanel, false ); // Output panels are visible and enabled if they have an item in them pPanel->SetVisible( pPanel->GetItem() != NULL ); pPanel->SetEnabled( pPanel->GetItem() != NULL ); } PopulatePanelsForCurrentPage(); // Update the current backpack page numbers char szTmp[16]; Q_snprintf(szTmp, 16, "%d/%d", GetCurrentPage() + 1, GetNumPages() ); m_pInventoryContainer->SetDialogVariable( "backpackpage", szTmp ); // Update the current input page numbers Q_snprintf(szTmp, 16, "%d/%d", m_nInputPage + 1, GetNumInputPages() ); m_pRecipeContainer->SetDialogVariable( "inputpage", szTmp ); DeSelectAllBackpackItemPanels(); } //----------------------------------------------------------------------------- // Purpose: If this is a backpack panel, send the item into the first accepting // input pane, if one exists, or else do nothing. If this is a input // panel with a backpack item in it, send the backpack item back to the // backpack. //----------------------------------------------------------------------------- void CDynamicRecipePanel::OnItemPanelMouseDoublePressed( vgui::Panel *panel ) { CRecipeComponentItemModelPanel *pSrcPanel = dynamic_cast < CRecipeComponentItemModelPanel * > ( panel ); if( !pSrcPanel ) return; int iIndex = GetBackpackPositionForPanel( pSrcPanel ); // Send from an input panel to the first open backpack panel, even on a different page if( IsInputPanel( iIndex ) ) { CInputPanelItemModelPanel *pInputPanel = assert_cast( pSrcPanel ); Assert( pInputPanel ); int nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( pInputPanel->GetPageNumber() ); CEconItemView* pRecipeItem = pInputPanel->GetRecipeItem( pInputPanel->GetPageNumber() ); if( pRecipeItem ) { // Just put it in the first open slot. ReturnRecipeItemToBackpack( nSrcRecipeIndex, pInputPanel, pInputPanel->GetPageNumber() ); } } else if( IsBackpackPanel( iIndex ) ) { // Sending from the backpack panel to the first matching input panel that's on the current input page. int nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( GetCurrentPage() ); FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) { CInputPanelItemModelPanel *pDstPanel = m_vecRecipeInputModelPanels[i]; if( pDstPanel->GetRecipeItem( pDstPanel->GetPageNumber() ) == NULL && pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) ) { // Next check if we just want to move the item SetRecipeComponentIntoPanel( nSrcRecipeIndex, pSrcPanel, pSrcPanel->GetPageNumber(), pDstPanel, pDstPanel->GetPageNumber() ); break; } } } m_bInputPanelsDirty = true; // Force panels to update UpdateModelPanels(); } //----------------------------------------------------------------------------- // Purpose: Border highlight if the panel has an item in it //----------------------------------------------------------------------------- void CDynamicRecipePanel::OnItemPanelEntered( vgui::Panel *panel ) { CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); m_pMouseOverItemPanel = pItemPanel; CEconItemView *pItem = pItemPanel->GetItem(); // Recalc the borders on item panels FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) { SetBorderForItem( m_vecRecipeInputModelPanels[i], false ); } if ( pItemPanel && IsVisible() ) { SetBorderForItem( pItemPanel, pItem != NULL ); } } //----------------------------------------------------------------------------- // Purpose: Turn of border highlights //----------------------------------------------------------------------------- void CDynamicRecipePanel::OnItemPanelExited( vgui::Panel *panel ) { CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); m_pMouseOverItemPanel = NULL; // Recalc the borders on item panels FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) { SetBorderForItem( m_vecRecipeInputModelPanels[i], false ); } if ( pItemPanel && !pItemPanel->IsSelected() ) { SetBorderForItem( pItemPanel, false ); } } //----------------------------------------------------------------------------- // Purpose: We got a craft response! Close out this window because we're going // to show the user their loot //----------------------------------------------------------------------------- void CDynamicRecipePanel::OnRecipeCompleted() { SetVisible( false ); m_flAbortCraftingAt = 0.f; } //----------------------------------------------------------------------------- // Purpose: Set the border for the item. Input panels highlight when a dragged // item matches its criteria. Output items highlight when all inputs // are fulfilled. //----------------------------------------------------------------------------- void CDynamicRecipePanel::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver ) { if ( !pItemPanel || !pItemPanel->IsVisible() ) return; int iIndex = GetBackpackPositionForPanel( pItemPanel ); if( IsInputPanel( iIndex ) ) { // Special case for input panels. They need to highlight when a dragged item // matches their criteria. CItemModelPanel* pPanel = m_bDragging ? m_pMouseDragItemPanel : m_pMouseOverItemPanel; CEconItemView* pPanelItem = pPanel ? pPanel->GetItem() : NULL; CInputPanelItemModelPanel* pInputPanel = dynamic_cast( pItemPanel ); Assert( pInputPanel ); // If this panel doesnt have a recipe, then we want some sort of greyed out look if( pInputPanel->GetRecipeItem( pInputPanel->GetPageNumber() ) == NULL ) { const char *pszBorder = NULL; int iRarity = GetItemQualityForBorder( pItemPanel ); // We only want backpack panels and the drag panel to be highlight sources bool bValidHighlightSourcePanel = pPanel == m_pMouseDragItemPanel || m_vecBackpackModelPanels.Find( static_cast(pPanel) ) != m_vecBackpackModelPanels.InvalidIndex(); // If this panel can accept whatever the source panel is, we want to highlight a bit if( bValidHighlightSourcePanel && pPanelItem && pInputPanel->MatchesAttribCriteria( pPanelItem->GetItemID() ) ) { if ( pItemPanel->GetItem() ) { pszBorder = g_szItemBorders[iRarity][3]; pItemPanel->SetGreyedOut( NULL ); } else { pszBorder = g_szItemBorders[iRarity][0]; } } else // Look gloomy and uninviting if not { pszBorder = g_szItemBorders[iRarity][3]; pItemPanel->SetGreyedOut( "" ); } vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) ); return; } } else if( IsOutputPanel( iIndex ) ) { const char *pszBorder = NULL; int iRarity = GetItemQualityForBorder( pItemPanel ); // The output panel greys out when any of the input panels are not fulfilled, // and lights up when they are all fulfilled if ( iRarity >= 0 && iRarity < ARRAYSIZE( g_szItemBorders ) ) { if( AllRecipePanelsFilled() ) { pszBorder = g_szItemBorders[iRarity][0]; pItemPanel->SetGreyedOut( NULL ); } else { pszBorder = g_szItemBorders[iRarity][3]; pItemPanel->SetGreyedOut( NULL ); } } vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) ); return; } // Backpack and output panels get default treatment BaseClass::SetBorderForItem( pItemPanel, bMouseOver ); } //----------------------------------------------------------------------------- // Purpose: Swap recipe items in srcpanel and dstpanel //----------------------------------------------------------------------------- void CDynamicRecipePanel::SetRecipeComponentIntoPanel( itemid_t nSrcRecipeIndex, CRecipeComponentItemModelPanel* pSrcPanel, int nSrcPage, CRecipeComponentItemModelPanel* pDstPanel, int nDstPage ) { Assert( nSrcRecipeIndex != 0 ); Assert( pSrcPanel ); Assert( pDstPanel ); Assert( pSrcPanel->GetRecipeItem( nSrcPage ) ); // Get the recipe from the destination panel itemid_t pDstRecipeIndex = pDstPanel->GetRecipeIndex( nDstPage ); // Swap recipes pSrcPanel->SetRecipeItem( pDstRecipeIndex, nSrcPage ); pDstPanel->SetRecipeItem( nSrcRecipeIndex, nDstPage ); } //----------------------------------------------------------------------------- // Purpose: Check if a given panel is allowed to be dragged. //----------------------------------------------------------------------------- bool CDynamicRecipePanel::AllowDragging( CItemModelPanel *pPanel ) { int iIndex = GetBackpackPositionForPanel( pPanel ); // If this isn't an input or backpack panel, abort if( !IsInputPanel( iIndex ) && !IsBackpackPanel( iIndex ) ) return false; CRecipeComponentItemModelPanel* pRecipePanel = dynamic_cast< CRecipeComponentItemModelPanel* >( pPanel ); Assert( pRecipePanel ); if( !pRecipePanel ) return false; int nPageNumber = 0; if( IsBackpackPanel( iIndex ) ) { nPageNumber = GetCurrentPage(); } else if ( IsInputPanel( iIndex ) ) { nPageNumber = m_nInputPage; } // Get the recipe item out of this panel CEconItemView* pRecipe = pRecipePanel->GetRecipeItem( nPageNumber ); // If no recipe, abort if( !pRecipe ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: Start dragging! //----------------------------------------------------------------------------- void CDynamicRecipePanel::StartDrag( int x, int y ) { BaseClass::StartDrag( x, y ); // Recalc the borders on item panels FOR_EACH_VEC( m_pItemModelPanels, i ) { SetBorderForItem( m_pItemModelPanels[i], false ); } } //----------------------------------------------------------------------------- // Purpose: Stop dragging //----------------------------------------------------------------------------- void CDynamicRecipePanel::StopDrag( bool bSucceeded ) { BaseClass::StopDrag( bSucceeded ); // Recalc the borders on item panels FOR_EACH_VEC( m_pItemModelPanels, i ) { SetBorderForItem( m_pItemModelPanels[i], false ); } } //----------------------------------------------------------------------------- // Purpose: Helper function to find out if an input panel can accept an item //----------------------------------------------------------------------------- bool CDynamicRecipePanel::InputPanelCanAcceptItem( CItemModelPanel* pPanel, itemid_t nItemID ) { Assert( IsInputPanel( GetBackpackPositionForPanel( pPanel ) ) ); CInputPanelItemModelPanel* pInputPanel = dynamic_cast( pPanel ); Assert( pInputPanel ); return pInputPanel->MatchesAttribCriteria( nItemID ); } //----------------------------------------------------------------------------- // Purpose: Check if the dragged item can be dropped into a given panel //----------------------------------------------------------------------------- bool CDynamicRecipePanel::CanDragTo( CItemModelPanel *pItemPanel, int iPanelIndex ) { // From input to backpack panel int iDraggedFromPos = GetBackpackPositionForPanel(m_pItemDraggedFromPanel); if( IsInputPanel( iDraggedFromPos ) && IsBackpackPanel( iPanelIndex ) ) return true; itemid_t itemID = m_pMouseDragItemPanel->GetItem() ? m_pMouseDragItemPanel->GetItem()->GetItemID() : 0; // From backpack to input panel that can accept the item if( IsBackpackPanel( iDraggedFromPos ) && IsInputPanel( iPanelIndex ) && InputPanelCanAcceptItem( pItemPanel,itemID ) ) return true; // From inoput to other input that can also accept the item if( IsInputPanel( iDraggedFromPos ) && IsInputPanel( iPanelIndex ) && InputPanelCanAcceptItem( pItemPanel, itemID ) ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: Handle the user releasing their drag on a given panel. Only valid // from backpack<->backpack, input<->input, backpack<->input //----------------------------------------------------------------------------- void CDynamicRecipePanel::HandleDragTo( CItemModelPanel * /*pItemPanel*/, int iPanelIndex ) { int iDraggedFromPos = GetBackpackPositionForPanel(m_pItemDraggedFromPanel); // The destination panel needs to exist, be enabled and be visible or else we dont consider it. CItemModelPanel* pDestinationPanel = m_pItemModelPanels[ iPanelIndex ]; if( !pDestinationPanel || !pDestinationPanel->IsEnabled() || !pDestinationPanel->IsVisible() ) { return; } m_bInputPanelsDirty = true; // If we dragged from a inventory panel onto an input panel if( IsInputPanel( iPanelIndex ) && IsBackpackPanel( iDraggedFromPos ) ) { CRecipeComponentItemModelPanel* pSrcPanel = dynamic_cast( m_pItemModelPanels[iDraggedFromPos] ); CInputPanelItemModelPanel* pDstPanel = dynamic_cast( m_pItemModelPanels[iPanelIndex] ); if( pSrcPanel && pDstPanel ) { // They dragged an item from the backpack into an input slot. Check if this is ok. itemid_t nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( GetCurrentPage() ); Assert( nSrcRecipeIndex != 0 ); if( nSrcRecipeIndex != 0 && pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) ) { SetRecipeComponentIntoPanel( nSrcRecipeIndex, pSrcPanel, GetCurrentPage(), pDstPanel, pDstPanel->GetPageNumber() ); } } } else if( IsInputPanel( iDraggedFromPos ) && IsBackpackPanel( iPanelIndex ) ) { CRecipeComponentItemModelPanel* pSrcPanel = dynamic_cast( m_pItemModelPanels[iDraggedFromPos] ); CRecipeComponentItemModelPanel* pDstPanel = dynamic_cast( m_pItemModelPanels[iPanelIndex] ); // If we dragged from an input panel to a backpack panel, return the item // to its original backpack slot regardless of where they dragged it to if( pSrcPanel && pDstPanel ) { // They dragged from an input panel to inventory panel. Check if there's something in // the inventory slot already. If so, just move to the first open spot. itemid_t pSrcRecipeIndex = pSrcPanel->GetRecipeIndex( pSrcPanel->GetPageNumber() ); itemid_t pDstRecipeIndex = pDstPanel->GetRecipeIndex( GetCurrentPage() ); Assert( pSrcRecipeIndex != 0 ); if( pSrcRecipeIndex != 0 && pDstRecipeIndex != 0 ) { // There's already a recipe item in there. Just put it in the first open slot. ReturnRecipeItemToBackpack( pSrcRecipeIndex, pSrcPanel, 0 ); } else if( pSrcRecipeIndex ) { // It's open! Place in there SetRecipeComponentIntoPanel( pSrcRecipeIndex, pSrcPanel, pSrcPanel->GetPageNumber(), pDstPanel, GetCurrentPage() ); } } } else if( IsInputPanel( iDraggedFromPos ) && IsInputPanel( iPanelIndex ) ) { CInputPanelItemModelPanel* pSrcPanel = dynamic_cast( m_pItemModelPanels[iDraggedFromPos] ); CInputPanelItemModelPanel* pDstPanel = dynamic_cast( m_pItemModelPanels[iPanelIndex] ); // Dragged from an input panel to an input panel. if( pSrcPanel && pDstPanel ) { itemid_t nSrcRecipeIndex = pSrcPanel->GetRecipeIndex( pSrcPanel->GetPageNumber() ); itemid_t nDstRecipeIndex = pDstPanel->GetRecipeIndex( pDstPanel->GetPageNumber() ); // First see if we can swap our inputs if( nSrcRecipeIndex != 0 && nDstRecipeIndex != 0 ) { if( pSrcPanel->MatchesAttribCriteria( nDstRecipeIndex ) && pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) ) { SetRecipeComponentIntoPanel( nDstRecipeIndex, pSrcPanel, 0, pDstPanel, 0 ); } } else if( nSrcRecipeIndex != 0 && nDstRecipeIndex == 0 && pDstPanel->MatchesAttribCriteria( nSrcRecipeIndex ) ) { // Next check if we just want to move the item SetRecipeComponentIntoPanel( nSrcRecipeIndex, pSrcPanel, pSrcPanel->GetPageNumber(), pDstPanel, pDstPanel->GetPageNumber() ); } } } else { AssertMsg( 0, "Unhandled drag case!" ); } UpdateModelPanels(); } //----------------------------------------------------------------------------- // Purpose: Return a given item to the first open slot in out backpack //----------------------------------------------------------------------------- void CDynamicRecipePanel::ReturnRecipeItemToBackpack( itemid_t nItemID, CRecipeComponentItemModelPanel* pSrcPanel, int nSrcPage ) { // For each page, check each recipe slot, on each panel for an opening for( int i=0; i < GetNumPages(); ++i ) { FOR_EACH_VEC( m_vecBackpackModelPanels, j ) { CRecipeComponentItemModelPanel* pDstPanel = m_vecBackpackModelPanels[j]; CEconItemView* pDstRecipe = pDstPanel->GetRecipeItem( i ); bool bSlotIsOpen = pDstPanel->IsSlotAvailable( i ); if( bSlotIsOpen && pDstRecipe == NULL ) { SetRecipeComponentIntoPanel( nItemID, pSrcPanel, nSrcPage, pDstPanel, i ); return; } } } AssertMsg( 0, "No open backpack slot found when returning item to backpack!" ); } //----------------------------------------------------------------------------- // Purpose: Set in a new recipe item. Update labels and reset panels. //----------------------------------------------------------------------------- void CDynamicRecipePanel::SetNewRecipe( CEconItemView* pNewRecipeItem ) { m_flAbortCraftingAt = 0.f; m_pDynamicRecipeItem = pNewRecipeItem; // Update recipe title m_pRecipeContainer->SetDialogVariable( "recipetitle", m_pDynamicRecipeItem->GetItemName() ); // By default, dont show untradable items m_pShowUntradableItemsCheckbox->SetSelected( false ); // Repopulate the panels InitItemPanels(); UpdateModelPanels(); } class CWaitForConsumeDialog : public CGenericWaitingDialog { public: CWaitForConsumeDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent ) { } protected: virtual void OnTimeout() { // Play an exciting sound! vgui::surface()->PlaySound( "misc/achievement_earned.wav" ); // Show them their loot! InventoryManager()->ShowItemsPickedUp( true ); } }; void CDynamicRecipePanel::OnCraftResponse( itemid_t nNewToolID, EGCMsgResponse eResponse ) { // We got a response. We dont need to time-out m_flAbortCraftingAt = 0; switch( eResponse ) { case k_EGCMsgResponseOK: { // If a new tool id comes back, that means we only partially completed the recipe if( nNewToolID != 0 ) { OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_Success", false, true, false ); // Play a sound letting them know the item is gone const char *pszSoundFilename = m_pDynamicRecipeItem->GetDefinitionString( "recipe_partial_complete_sound", "ui/chem_set_add_element.wav" ); vgui::surface()->PlaySound( pszSoundFilename ); CEconItemView* pNewRecipe = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( nNewToolID ); Assert( pNewRecipe ); // Set the new recipe into the panel. This resets all the item panels within. SetNewRecipe( pNewRecipe ); } else { CloseCraftingStatusDialog(); // No new tool, so we completed the recipe! const char *pszSoundFilename = m_pDynamicRecipeItem->GetDefinitionString( "recipe_complete_sound", "ui/chem_set_creation.wav" ); vgui::surface()->PlaySound( pszSoundFilename ); // Show the "Completing consumption" dialog for 5 seconds ShowWaitingDialog( new CWaitForConsumeDialog( NULL ), "#ToolConsumptionInProgress", true, false, 5.0f ); // Hide the dynamic recipe panel after 6 seconds g_DynamicRecipePanel->PostMessage( g_DynamicRecipePanel, new KeyValues("RecipeCompleted"), 6.f ); } } break; case k_EGCMsgResponseInvalid: { // Something was bad in the request. OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_Invalid", false, true, false ); InitItemPanels(); UpdateModelPanels(); } break; case k_EGCMsgResponseNoMatch: { // One or more of the items sent in didnt match OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_NoMatch", false, true, false ); InitItemPanels(); UpdateModelPanels(); } break; default: { OpenCraftingStatusDialog( this, "#Dynamic_Recipe_Response_Default", false, true, false ); InitItemPanels(); UpdateModelPanels(); } } } //----------------------------------------------------------------------------- // Purpose: GC Msg handler to receive the dynamic recipe //----------------------------------------------------------------------------- class CGCCompleteDynamicRecipeResponse : public GCSDK::CGCClientJob { public: CGCCompleteDynamicRecipeResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CGCMsg msg( pNetPacket ); itemid_t nNewToolID = INVALID_ITEM_ID; if( !msg.BReadUint64Data( &nNewToolID ) ) return true; g_DynamicRecipePanel->OnCraftResponse( nNewToolID, (EGCMsgResponse)msg.Body().m_eResponse ); return true; } }; GC_REG_JOB( GCSDK::CGCClient, CGCCompleteDynamicRecipeResponse, "CGCCompleteDynamicRecipeResponse", k_EMsgGCFulfillDynamicRecipeComponentResponse, GCSDK::k_EServerTypeGCClient ); #ifdef STAGING_ONLY void CDynamicRecipePanel::Debug_GiveRequiredInputs() const { 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; } FOR_EACH_VEC( m_vecRecipeInputModelPanels, i ) { CInputPanelItemModelPanel *pInputPanel = m_vecRecipeInputModelPanels[i]; int nPage = 0; for( const CEconItemAttributeDefinition *pAttrDef = pInputPanel->GetAttrib( nPage ); pAttrDef != NULL; pAttrDef = pInputPanel->GetAttrib( ++nPage ) ) { CAttribute_DynamicRecipeComponent attribValue; if( m_pDynamicRecipeItem->FindAttribute( pAttrDef, &attribValue ) ) { GCSDK::CProtoBufMsg msg( k_EMsgGCDev_NewItemRequest ); msg.Body().set_receiver( steamIDForPlayer.ConvertToUint64() ); CItemSelectionCriteria criteria; if( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_ITEM_DEF_SET ) { criteria.BAddCondition( "name", k_EOperator_String_EQ, GetItemSchema()->GetItemDefinition( attribValue.def_index() )->GetDefinitionName(), true ); } if( attribValue.component_flags() & DYNAMIC_RECIPE_FLAG_PARAM_QUALITY_SET ) { criteria.SetQuality( attribValue.item_quality() ); } criteria.SetIgnoreEnabledFlag( true ); criteria.BSerializeToMsg( *msg.Body().mutable_criteria() ); GCClientSystem()->BSendMessage( msg ); } } } } #endif