diff options
Diffstat (limited to 'game/client/tf/vgui/crafting_panel.cpp')
| -rw-r--r-- | game/client/tf/vgui/crafting_panel.cpp | 1738 |
1 files changed, 1738 insertions, 0 deletions
diff --git a/game/client/tf/vgui/crafting_panel.cpp b/game/client/tf/vgui/crafting_panel.cpp new file mode 100644 index 0000000..9a503d7 --- /dev/null +++ b/game/client/tf/vgui/crafting_panel.cpp @@ -0,0 +1,1738 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "crafting_panel.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 <vgui/ILocalize.h> +#include "vgui_controls/TextImage.h" +#include "vgui_controls/CheckButton.h" +#include "vgui_controls/ComboBox.h" +#include <vgui_controls/TextEntry.h> +#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" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +ConVar tf_explanations_craftingpanel( "tf_explanations_craftingpanel", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." ); + +struct recipefilter_data_t +{ + const char *pszTooltipString; + const char *pszButtonImage; + const char *pszButtonImageMouseover; +}; +recipefilter_data_t g_RecipeFilters[NUM_RECIPE_CATEGORIES] = +{ + { "#RecipeFilter_Crafting", "crafticon_crafting_items", "crafticon_crafting_items_over" }, // RECIPE_CATEGORY_CRAFTINGITEMS, + { "#RecipeFilter_CommonItems", "crafticon_common_items", "crafticon_common_items_over" }, // RECIPE_CATEGORY_COMMONITEMS, + { "#RecipeFilter_RareItems", "crafticon_rare_items", "crafticon_rare_items_over" }, // RECIPE_CATEGORY_RAREITEMS, + { "#RecipeFilter_Special", "crafticon_special_blueprints", "crafticon_special_blueprints_over" } // RECIPE_CATEGORY_SPECIAL, +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +wchar_t *LocalizeRecipeStringPiece( const char *pszString, wchar_t *pszConverted, int nConvertedSizeInBytes ) +{ + if ( !pszString ) + return L""; + + if ( pszString[0] == '#' ) + return g_pVGuiLocalize->Find( pszString ); + + g_pVGuiLocalize->ConvertANSIToUnicode( pszString, pszConverted, nConvertedSizeInBytes ); + return pszConverted; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SetItemPanelToRecipe( CItemModelPanel *pPanel, const CEconCraftingRecipeDefinition *pRecipeDef, bool bShowName ) +{ + wchar_t wcTmpName[512]; + wchar_t wcTmpDesc[512]; + int iNegAttribsBegin = 0; + + if ( !pRecipeDef ) + { + Q_wcsncpy( wcTmpName, g_pVGuiLocalize->Find( "#Craft_Recipe_Custom" ), sizeof( wcTmpName ) ); + Q_wcsncpy( wcTmpDesc, g_pVGuiLocalize->Find( "#Craft_Recipe_CustomDesc" ), sizeof( wcTmpDesc ) ); + iNegAttribsBegin = Q_wcslen( wcTmpDesc ); + } + else + { + if ( bShowName ) + { + wchar_t *pName_A = g_pVGuiLocalize->Find( pRecipeDef->GetName_A() ); + g_pVGuiLocalize->ConstructString_safe( wcTmpName, g_pVGuiLocalize->Find( pRecipeDef->GetName() ), 1, pName_A ); + } + else + { + wcTmpName[0] = '\0'; + } + + wchar_t wcTmpA[32]; + wchar_t wcTmpB[32]; + wchar_t wcTmpC[32]; + wchar_t wcTmp[512]; + + // Build the input string + wchar_t *pInp_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_A(), wcTmpA, sizeof( wcTmpA ) ); + wchar_t *pInp_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_B(), wcTmpB, sizeof( wcTmpB ) ); + wchar_t *pInp_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_C(), wcTmpC, sizeof( wcTmpC ) ); + g_pVGuiLocalize->ConstructString_safe( wcTmpDesc, g_pVGuiLocalize->Find( pRecipeDef->GetDescInputs() ), 3, pInp_A, pInp_B, pInp_C ); + iNegAttribsBegin = Q_wcslen(wcTmpDesc); + + // Build the output string + wchar_t *pOut_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_A(), wcTmpA, sizeof( wcTmpA ) ); + wchar_t *pOut_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_B(), wcTmpB, sizeof( wcTmpB ) ); + wchar_t *pOut_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_C(), wcTmpC, sizeof( wcTmpC ) ); + g_pVGuiLocalize->ConstructString_safe( wcTmp, g_pVGuiLocalize->Find( pRecipeDef->GetDescOutputs() ), 3, pOut_A, pOut_B, pOut_C ); + + // Concatenate, and mark the text changes + V_wcscat_safe( wcTmpDesc, L"\n" ); + V_wcscat_safe( wcTmpDesc, wcTmp ); + } + + pPanel->SetAttribOnly( !bShowName ); + pPanel->SetTextYPos( 0 ); + pPanel->SetItem( NULL ); + pPanel->SetNoItemText( wcTmpName, wcTmpDesc, iNegAttribsBegin ); + pPanel->InvalidateLayout(true); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void PositionMouseOverPanelForRecipe( vgui::Panel *pScissorPanel, vgui::Panel *pRecipePanel, vgui::ScrollableEditablePanel *pRecipeScroller, CItemModelPanel *pMouseOverItemPanel ) +{ + int x,y; + vgui::ipanel()->GetAbsPos( pRecipePanel->GetVPanel(), x, y ); + int xs,ys; + vgui::ipanel()->GetAbsPos( pMouseOverItemPanel->GetParent()->GetVPanel(), xs, ys ); + x -= xs; + y -= ys; + + int iXPos = (x + (pRecipePanel->GetWide() * 0.5)) - (pMouseOverItemPanel->GetWide() * 0.5); + int iYPos = (y + pRecipePanel->GetTall()); + + // Make sure the popup stays onscreen. + if ( iXPos < 0 ) + { + iXPos = 0; + } + else if ( (iXPos + pMouseOverItemPanel->GetWide()) > pMouseOverItemPanel->GetParent()->GetWide() ) + { + iXPos = pMouseOverItemPanel->GetParent()->GetWide() - pMouseOverItemPanel->GetWide(); + } + + if ( iYPos < 0 ) + { + iYPos = 0; + } + else if ( (iYPos + pMouseOverItemPanel->GetTall() + YRES(32)) > pMouseOverItemPanel->GetParent()->GetTall() ) + { + // Move it up above our item + iYPos = y - pMouseOverItemPanel->GetTall() - YRES(4); + } + + pMouseOverItemPanel->SetPos( iXPos, iYPos ); + pMouseOverItemPanel->SetVisible( true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCraftingPanel::CCraftingPanel( vgui::Panel *parent, const char *panelName ) : CBaseLoadoutPanel( parent, panelName ) +{ + m_pRecipeListContainer = new vgui::EditablePanel( this, "recipecontainer" ); + m_pRecipeListContainerScroller = new vgui::ScrollableEditablePanel( this, m_pRecipeListContainer, "recipecontainerscroller" ); + m_pSelectedRecipeContainer = new vgui::EditablePanel( this, "selectedrecipecontainer" ); + m_pRecipeButtonsKV = NULL; + m_pRecipeFilterButtonsKV = NULL; + m_bEventLogging = false; + m_iCraftingAttempts = 0; + m_iRecipeCategoryFilter = RECIPE_CATEGORY_CRAFTINGITEMS; + m_iCurrentlySelectedRecipe = -1; + CleanupPostCraft( true ); + + m_pToolTip = new CTFTextToolTip( this ); + m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" ); + m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false ); + m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false ); + m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel ); + m_pToolTip->SetTooltipDelay( 0 ); + + m_pSelectionPanel = NULL; + m_iSelectingForSlot = 0; + + m_pCraftButton = NULL; + m_pUpgradeButton = NULL; + m_pFreeAccountLabel = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCraftingPanel::~CCraftingPanel( void ) +{ + if ( m_pRecipeButtonsKV ) + { + m_pRecipeButtonsKV->deleteThis(); + m_pRecipeButtonsKV = NULL; + } + if ( m_pRecipeFilterButtonsKV ) + { + m_pRecipeFilterButtonsKV->deleteThis(); + m_pRecipeFilterButtonsKV = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + LoadControlSettings( GetResFile() ); + + BaseClass::ApplySchemeSettings( pScheme ); + + m_pRecipeListContainerScroller->GetScrollbar()->SetAutohideButtons( true ); + m_pCraftButton = dynamic_cast<CExButton*>( m_pSelectedRecipeContainer->FindChildByName("CraftButton") ); + if ( m_pCraftButton ) + { + m_pCraftButton->AddActionSignalTarget( this ); + } + m_pUpgradeButton = dynamic_cast<CExButton*>( m_pSelectedRecipeContainer->FindChildByName("UpgradeButton") ); + if ( m_pUpgradeButton ) + { + m_pUpgradeButton->AddActionSignalTarget( this ); + } + m_pFreeAccountLabel = dynamic_cast<CExLabel*>( m_pSelectedRecipeContainer->FindChildByName("FreeAccountLabel") ); + + CreateRecipeFilterButtons(); + UpdateRecipeFilter(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pItemKV = inResourceData->FindKey( "recipebuttons_kv" ); + if ( pItemKV ) + { + if ( m_pRecipeButtonsKV ) + { + m_pRecipeButtonsKV->deleteThis(); + } + m_pRecipeButtonsKV = new KeyValues("recipebuttons_kv"); + pItemKV->CopySubkeys( m_pRecipeButtonsKV ); + } + + KeyValues *pButtonKV = inResourceData->FindKey( "recipefilterbuttons_kv" ); + if ( pButtonKV ) + { + if ( m_pRecipeFilterButtonsKV ) + { + m_pRecipeFilterButtonsKV->deleteThis(); + } + m_pRecipeFilterButtonsKV = new KeyValues("recipefilterbuttons_kv"); + pButtonKV->CopySubkeys( m_pRecipeFilterButtonsKV ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + // Need to lay these out before we start making item panels inside them + m_pRecipeListContainer->InvalidateLayout( true ); + m_pRecipeListContainerScroller->InvalidateLayout( true ); + + // Position the recipe filters + FOR_EACH_VEC( m_pRecipeFilterButtons, i ) + { + if ( m_pRecipeFilterButtonsKV ) + { + m_pRecipeFilterButtons[i]->ApplySettings( m_pRecipeFilterButtonsKV ); + m_pRecipeFilterButtons[i]->InvalidateLayout(); + } + + int iButtonW, iButtonH; + m_pRecipeFilterButtons[i]->GetSize( iButtonW, iButtonH ); + + int iXPos = (GetWide() * 0.5) + m_iFilterOffcenterX + ((iButtonW + m_iFilterDeltaX) * i); + int iYPos = m_iFilterYPos;// + ((iButtonH + m_iFilterDeltaY) * i); + m_pRecipeFilterButtons[i]->SetPos( iXPos, iYPos ); + } + + // Position the recipe buttons + for ( int i = 0; i < m_pRecipeButtons.Count(); i++ ) + { + if ( m_pRecipeButtonsKV ) + { + m_pRecipeButtons[i]->ApplySettings( m_pRecipeButtonsKV ); + m_pRecipeButtons[i]->InvalidateLayout(); + } + + int iYDelta = m_pRecipeButtons[0]->GetTall() + YRES(2); + + // Once we've setup our first item, we know how large to make the container + if ( i == 0 ) + { + m_pRecipeListContainer->SetSize( m_pRecipeListContainer->GetWide(), iYDelta * m_pRecipeButtons.Count() ); + } + + int x,y; + m_pRecipeButtons[i]->GetPos( x,y ); + m_pRecipeButtons[i]->SetPos( x, (iYDelta * i) ); + } + + // Now that the container has been sized, tell the scroller to re-evaluate + m_pRecipeListContainerScroller->InvalidateLayout(); + m_pRecipeListContainerScroller->GetScrollbar()->InvalidateLayout(); + + // Then position all our item panels + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + PositionItemPanel( m_pItemModelPanels[i], i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::CreateRecipeFilterButtons( void ) +{ + for ( int i = 0; i < NUM_RECIPE_CATEGORIES; i++ ) + { + if ( m_pRecipeFilterButtons.Count() <= i ) + { + CImageButton *pNewButton = new CImageButton( this, g_RecipeFilters[i].pszTooltipString ); + m_pRecipeFilterButtons.AddToTail( pNewButton ); + } + + m_pRecipeFilterButtons[i]->SetInactiveImage( g_RecipeFilters[i].pszButtonImage ); + m_pRecipeFilterButtons[i]->SetActiveImage( g_RecipeFilters[i].pszButtonImageMouseover ); + m_pRecipeFilterButtons[i]->SetTooltip( m_pToolTip, g_RecipeFilters[i].pszTooltipString ); + const char *pszCommand = VarArgs("selectfilter%d", i ); + m_pRecipeFilterButtons[i]->SetCommand( pszCommand ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::UpdateRecipeFilter( void ) +{ + int iMatchingRecipes = 0; + m_iCurrentlySelectedRecipe = -1; + m_iCurrentRecipeTotalInputs = 0; + m_iCurrentRecipeTotalOutputs = 0; + + FOR_EACH_VEC( m_pRecipeFilterButtons, i ) + { + bool bForceDepressed = ( i == m_iRecipeCategoryFilter ); + m_pRecipeFilterButtons[i]->ForceDepressed( bForceDepressed ); + } + + // Loop through the known recipes, and see which ones match our category filter + for ( int i = 0; i < TFInventoryManager()->GetLocalTFInventory()->GetRecipeCount(); i++ ) + { + const CEconCraftingRecipeDefinition *pRecipeDef = TFInventoryManager()->GetLocalTFInventory()->GetRecipeDef(i); + if ( !pRecipeDef ) + continue; + + if ( pRecipeDef->IsDisabled() ) + continue; + + if ( pRecipeDef->GetCategory() != m_iRecipeCategoryFilter ) + continue; + + wchar_t wTemp[256]; + wchar_t *pName_A = g_pVGuiLocalize->Find( pRecipeDef->GetName_A() ); + g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find( pRecipeDef->GetName() ), 1, pName_A ); + SetButtonToRecipe( iMatchingRecipes, pRecipeDef->GetDefinitionIndex(), wTemp ); + + iMatchingRecipes++; + } + + // Add a "Custom" option to the bottom of the Special recipe list + if ( m_iRecipeCategoryFilter == RECIPE_CATEGORY_SPECIAL ) + { + SetButtonToRecipe( iMatchingRecipes, RECIPE_CUSTOM, g_pVGuiLocalize->Find("#Craft_Recipe_Custom") ); + iMatchingRecipes++; + } + + // Delete excess buttons + for ( int i = m_pRecipeButtons.Count() - 1; i >= iMatchingRecipes; i-- ) + { + m_pRecipeButtons[i]->MarkForDeletion(); + m_pRecipeButtons.Remove( i ); + } + + // Move the scrollbar to the top + m_pRecipeListContainerScroller->GetScrollbar()->SetValue( 0 ); + + UpdateSelectedRecipe( true ); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnCancelSelection( void ) +{ + if ( m_pSelectionPanel ) + { + m_pSelectionPanel->SetVisible( false ); + } + + CloseCraftingStatusDialog(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnSelectionReturned( KeyValues *data ) +{ + if ( data ) + { + uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID ); + if ( ulIndex == INVALID_ITEM_ID ) + { + // should this be INVALID_ITEM_ID? + m_InputItems[m_iSelectingForSlot] = 0; + } + else + { + m_InputItems[m_iSelectingForSlot] = ulIndex; + } + + UpdateModelPanels(); + UpdateCraftButton(); + } + + // It'll have deleted itself, so we don't need to clean it up + OnCancelSelection(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnShowPanel( bool bVisible, bool bReturningFromArmory ) +{ + if ( bVisible ) + { + if ( m_pSelectionPanel ) + { + m_pSelectionPanel->SetVisible( false ); + } + + memset( m_InputItems, 0, sizeof(m_InputItems) ); + memset( m_ItemPanelCriteria, 0, sizeof(m_ItemPanelCriteria) ); + m_iCurrentlySelectedRecipe = -1; + m_iCurrentRecipeTotalInputs = 0; + m_iCurrentRecipeTotalOutputs = 0; + UpdateRecipeFilter(); + + if ( !m_bEventLogging ) + { + m_bEventLogging = true; + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_ENTERED ); + } + } + else + { + CloseCraftingStatusDialog(); + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + } + + BaseClass::OnShowPanel( bVisible, bReturningFromArmory ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnClosing() +{ + if ( m_bEventLogging ) + { + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_EXITED ); + m_bEventLogging = false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::PositionItemPanel( CItemModelPanel *pPanel, int iIndex ) +{ + int iCenter = 0; + int iButtonX, iButtonY, iXPos, iYPos; + + if ( IsInputItemPanel(iIndex) ) + { + iButtonX = (iIndex % CRAFTING_SLOTS_INPUT_COLUMNS); + iButtonY = (iIndex / CRAFTING_SLOTS_INPUT_COLUMNS); + iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * m_pItemModelPanels[iIndex]->GetWide()) + (m_iItemBackpackXDelta * iButtonX); + iYPos = m_iItemYPos + (iButtonY * m_pItemModelPanels[iIndex]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY); + } + else + { + int iButtonIndex = iIndex - CRAFTING_SLOTS_INPUTPANELS; + iButtonX = (iButtonIndex % CRAFTING_SLOTS_OUTPUT_COLUMNS); + iButtonY = (iButtonIndex / CRAFTING_SLOTS_OUTPUT_COLUMNS); + iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * m_pItemModelPanels[iIndex]->GetWide()) + (m_iItemBackpackXDelta * iButtonX); + iYPos = m_iOutputItemYPos + (iButtonY * m_pItemModelPanels[iIndex]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY); + } + + m_pItemModelPanels[iIndex]->SetPos( iXPos, iYPos ); + return; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::UpdateRecipeItems( bool bClearInputItems ) +{ + if ( bClearInputItems ) + { + memset( m_InputItems, 0, sizeof(m_InputItems) ); + } + + memset( m_ItemPanelCriteria, 0, sizeof(m_ItemPanelCriteria) ); + m_iCurrentRecipeTotalInputs = 0; + m_iCurrentRecipeTotalOutputs = 0; + + if ( m_iCurrentlySelectedRecipe == -1 ) + return; + + /* + // Build lists of items divided by class & loadout slot, so recipes can quickly test themselves + CUtlVector<CEconItem*> vecAllItems; + CUtlVector<CEconItem*> vecItemsByClass[ LOADOUT_COUNT ]; + CUtlVector<CEconItem*> vecItemsBySlot[ LOADOUT_POSITION_COUNT ]; + + for ( int i = 1; i <= TFInventoryManager()->GetLocalTFInventory()->GetMaxItemCount(); i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetItemByBackpackPosition(i); + if ( pItemData && pItemData->IsValid() ) + { + CEconItem *pSOCData = pItemData->GetSOCData(); + vecAllItems.AddToTail( pSOCData ); + + CTFItemDefinition *pItemDef = pItemData->GetStaticData(); + + // Put it in class lists for any class that can use it. Use the zeroth list as all-class items. + if ( pItemDef->CanBeUsedByAllClasses() ) + { + vecItemsByClass[0].AddToTail( pSOCData ); + } + for (int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( pItemDef->CanBeUsedByClass(iClass) ) + { + vecItemsByClass[iClass].AddToTail( pSOCData ); + } + } + + // Put it in the slot lists for any slot that it can be equipped in + for (int iSlot = 0; iSlot < LOADOUT_POSITION_COUNT; iSlot++ ) + { + if ( pItemDef->CanBePlacedInSlot( iSlot ) ) + { + vecItemsBySlot[iSlot].AddToTail( pSOCData ); + } + } + } + } + */ + + // Find the items needed for the specified recipe + if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM ) + { + // Custom recipe. Show all open buttons, and let them put anything in there. + m_iCurrentRecipeTotalInputs = CRAFTING_SLOTS_INPUTPANELS; + m_iCurrentRecipeTotalOutputs = 0; + + FOR_EACH_VEC( m_pItemModelPanels, i ) + { + m_pItemModelPanels[i]->SetNoItemText( "" ); + } + } + else + { + const CTFCraftingRecipeDefinition *pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe ); + if ( pRecipeDef ) + { + m_iCurrentRecipeTotalInputs = pRecipeDef->GetTotalInputItemsRequired(); + m_iCurrentRecipeTotalOutputs = pRecipeDef->GetTotalOutputItems(); + + CUtlVector<itemid_t> vecItemsUsed; + + // Set the text in each of the item panels + const CUtlVector<CItemSelectionCriteria> *vecInputCriteria; + vecInputCriteria = pRecipeDef->GetInputItems(); + CUtlVector<uint32> vecInputDupes; + vecInputDupes = pRecipeDef->GetInputItemDupeCounts(); + + int iModelPanel = 0; + FOR_EACH_VEC( *vecInputCriteria, i ) + { + const char *pszNoItemText = GetItemTextForCriteria( &(*vecInputCriteria)[i] ); + + int iNumPanels = vecInputDupes[i] ? vecInputDupes[i] : 1; + for ( int iPanel = 0; iPanel < iNumPanels; iPanel++ ) + { + m_ItemPanelCriteria[iModelPanel] = &(*vecInputCriteria)[i]; + if ( m_pItemModelPanels[iModelPanel] ) + { + m_pItemModelPanels[iModelPanel]->SetNoItemText( pszNoItemText ); + } + iModelPanel++; + } + } + + // Set the output items as well + CUtlVector<CItemSelectionCriteria> vecOutputCriteria; + vecOutputCriteria = pRecipeDef->GetOutputItems(); + FOR_EACH_VEC( vecOutputCriteria, i ) + { + int iOutputPanel = CRAFTING_SLOTS_INPUTPANELS + i; + + CEconItemDefinition *pDef = GetItemDefFromCriteria( &vecOutputCriteria[i] ); + if ( pDef ) + { + //m_pItemModelPanels[iOutputPanel]->SetNoItemText( pszNoItemText ); + CEconItemView *pItemData = new CEconItemView(); + pItemData->Init( pDef->GetDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true ); + if ( m_pItemModelPanels[iOutputPanel] ) + { + m_pItemModelPanels[iOutputPanel]->SetItem( pItemData ); + } + delete pItemData; + continue; + } + + // If we didn't manage to extract an output, just use the recipe output string + wchar_t wcTmpA[32]; + wchar_t wcTmpB[32]; + wchar_t wcTmpC[32]; + wchar_t wcTmp[512]; + wchar_t *pOut_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_A(), wcTmpA, sizeof( wcTmpA ) ); + wchar_t *pOut_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_B(), wcTmpB, sizeof( wcTmpB ) ); + wcTmp[0] = '\0'; + V_wcscat_safe( wcTmp, pOut_A ); + V_wcscat_safe( wcTmp, L" " ); + V_wcscat_safe( wcTmp, pOut_B ); + if ( Q_strnicmp( pRecipeDef->GetDescOutputs(), "#RDO_ABC", 8 ) == 0 ) + { + wchar_t *pOut_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_C(), wcTmpC, sizeof( wcTmpC ) ); + V_wcscat_safe( wcTmp, L" " ); + V_wcscat_safe( wcTmp, pOut_C ); + } + + if ( m_pItemModelPanels[iOutputPanel] ) + { + m_pItemModelPanels[iOutputPanel]->SetItem( NULL ); + m_pItemModelPanels[iOutputPanel]->SetNoItemText( wcTmp ); + } + } + } + } + + // Now check to see if they've got the right items in there + UpdateCraftButton(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::UpdateCraftButton( void ) +{ + if ( m_iCurrentlySelectedRecipe == -1 ) + return; + + bool bAllowedToUse = true; + + const CEconCraftingRecipeDefinition *pRecipeDef = NULL; + if ( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM ) + { + pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe ); + if ( !pRecipeDef ) + return; + + bAllowedToUse = ( !IsFreeTrialAccount() || !pRecipeDef->IsPremiumAccountOnly() ); + } + + if ( m_pCraftButton ) + { + m_pCraftButton->SetVisible( bAllowedToUse ); + } + if ( m_pUpgradeButton ) + { + m_pUpgradeButton->SetVisible( !bAllowedToUse ); + } + if ( m_pFreeAccountLabel ) + { + m_pFreeAccountLabel->SetVisible( !bAllowedToUse ); + } + + if ( !bAllowedToUse ) + return; + + bool bCraftButtonActive = false; + if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM ) + { + // Need at least one item in a slot + for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] ); + if ( pItemData ) + { + bCraftButtonActive = true; + break; + } + } + } + else + { + CUtlVector<CEconItem*> vecAllItems; + for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] ); + if ( pItemData ) + { + vecAllItems.AddToTail( pItemData->GetSOCData() ); + } + } + + bCraftButtonActive = pRecipeDef->ItemListMatchesInputs( &vecAllItems, NULL, false, NULL ); + } + + if ( m_pCraftButton ) + { + m_pCraftButton->SetEnabled( bCraftButtonActive ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CCraftingPanel::GetItemTextForCriteria( const CItemSelectionCriteria *pCriteria ) +{ + // Otherwise, look at the first condition, and see if we can determine what the item is + const char *pszVal = pCriteria->GetValueForFirstConditionOfType( k_EOperator_String_EQ ); + if ( pszVal && pszVal[0] ) + { + // Is it a loadout slot? + int iSlot = StringFieldToInt( pszVal, ItemSystem()->GetItemSchema()->GetLoadoutStrings( EEquipType_t::EQUIP_TYPE_CLASS ), true ); + if ( iSlot != -1 ) + return ItemSystem()->GetItemSchema()->GetLoadoutStringsForDisplay( EEquipType_t::EQUIP_TYPE_CLASS )[iSlot]; + + // Is it a craft material type? + if ( V_stricmp( pszVal, "weapon" ) == 0 ) + { + return "#RI_W"; + } + else if ( V_stricmp( pszVal, "hat" ) == 0 ) + { + return "#RI_Hg"; + } + else if ( V_stricmp( pszVal, "craft_token" ) == 0 ) + { + return "#RI_T"; + } + else if ( V_stricmp( pszVal, "class_token" ) == 0 ) + { + return "#CI_T_C"; + } + else if ( V_stricmp( pszVal, "slot_token" ) == 0 ) + { + return "#CI_T_S"; + } + + // Is it an item name? + CEconItemDefinition *pDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName(pszVal); + if ( pDef ) + return pDef->GetItemBaseName(); + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEconItemDefinition *CCraftingPanel::GetItemDefFromCriteria( const CItemSelectionCriteria *pCriteria ) +{ + // Otherwise, look at the first condition, and see if we can determine what the item is + const char *pszVal = pCriteria->GetValueForFirstConditionOfType( k_EOperator_String_EQ ); + if ( pszVal && pszVal[0] ) + return ItemSystem()->GetItemSchema()->GetItemDefinitionByName(pszVal); + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::AddNewItemPanel( int iPanelIndex ) +{ + BaseClass::AddNewItemPanel( iPanelIndex ); + + // Move the model panels to our selected recipe container + m_pItemModelPanels[iPanelIndex]->SetParent( m_pSelectedRecipeContainer ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::UpdateModelPanels( void ) +{ + BaseClass::UpdateModelPanels(); + + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + if ( IsInputItemPanel(i) ) + { + if ( m_InputItems[i] != 0 ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] ); + m_pItemModelPanels[i]->SetItem( pItemData ); + m_pItemModelPanels[i]->SetVisible( true ); + m_pItemModelPanels[i]->SetShowEquipped( true ); + SetBorderForItem( m_pItemModelPanels[i], false ); + } + else + { + m_pItemModelPanels[i]->SetItem( NULL ); + + // Always show the number of slots that the recipe uses + bool bVisible = (m_iCurrentRecipeTotalInputs > i); + m_pItemModelPanels[i]->SetVisible( bVisible ); + } + } + else + { + bool bVisible = ((m_iCurrentRecipeTotalOutputs + CRAFTING_SLOTS_INPUTPANELS) > i); + m_pItemModelPanels[i]->SetVisible( bVisible ); + } + } + + vgui::Panel *pLabel = m_pSelectedRecipeContainer->FindChildByName("OutputLabel"); + if ( pLabel ) + { + pLabel->SetVisible( m_iCurrentRecipeTotalOutputs > 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::SetButtonToRecipe( int iButton, int iDefIndex, wchar_t *pszText ) +{ + // Re-use existing buttons, or make new ones if we need more + CRecipeButton *pRecipeButton = NULL; + if ( iButton < m_pRecipeButtons.Count() ) + { + pRecipeButton = m_pRecipeButtons[iButton]; + } + else + { + pRecipeButton = new CRecipeButton( m_pRecipeListContainer, "selectrecipe", "", this, "selectrecipe" ); + if ( m_pRecipeButtonsKV ) + { + pRecipeButton->ApplySettings( m_pRecipeButtonsKV ); + } + pRecipeButton->MakeReadyForUse(); + m_pRecipeButtons.AddToTail( pRecipeButton ); + } + + const char *pszCommand = VarArgs("selectrecipe%d", iDefIndex ); + pRecipeButton->SetCommand( pszCommand ); + pRecipeButton->SetText( pszText ); + pRecipeButton->SetDefIndex( iDefIndex ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::UpdateSelectedRecipe( bool bClearInputItems ) +{ + for ( int i = 0; i < m_pRecipeButtons.Count(); i++ ) + { + bool bSelected = m_pRecipeButtons[i]->m_iRecipeDefIndex == m_iCurrentlySelectedRecipe; + m_pRecipeButtons[i]->ForceDepressed( bSelected ); + m_pRecipeButtons[i]->RecalculateDepressedState(); + + if ( bSelected ) + { + wchar_t wszText[1024]; + m_pRecipeButtons[i]->GetText( wszText, ARRAYSIZE( wszText ) ); + m_pSelectedRecipeContainer->SetDialogVariable( "recipetitle", wszText ); + + if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM ) + { + m_pSelectedRecipeContainer->SetDialogVariable( "recipeinputstring", g_pVGuiLocalize->Find("#Craft_Recipe_CustomDesc") ); + } + else + { + const CTFCraftingRecipeDefinition *pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe ); + if ( pRecipeDef ) + { + // Build the input string + wchar_t wcTmpA[32]; + wchar_t wcTmpB[32]; + wchar_t wcTmpC[32]; + wchar_t wcTmpDesc[512]; + wchar_t *pInp_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_A(), wcTmpA, sizeof( wcTmpA ) ); + wchar_t *pInp_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_B(), wcTmpB, sizeof( wcTmpB ) ); + wchar_t *pInp_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_C(), wcTmpC, sizeof( wcTmpC ) ); + g_pVGuiLocalize->ConstructString_safe( wcTmpDesc, g_pVGuiLocalize->Find( pRecipeDef->GetDescInputs() ), 3, pInp_A, pInp_B, pInp_C ); + m_pSelectedRecipeContainer->SetDialogVariable( "recipeinputstring", wcTmpDesc ); + } + } + } + } + + m_pSelectedRecipeContainer->SetVisible( m_iCurrentlySelectedRecipe != -1 ); + + UpdateRecipeItems( bClearInputItems ); + UpdateModelPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnCommand( const char *command ) +{ + if ( !Q_strnicmp( command, "selectrecipe", 12 ) ) + { + const char *pszNum = command+12; + if ( pszNum && pszNum[0] ) + { + m_iCurrentlySelectedRecipe = atoi(pszNum); + UpdateSelectedRecipe( true ); + } + + return; + } + if ( !Q_strnicmp( command, "selectfilter", 12 ) ) + { + const char *pszNum = command+12; + if ( pszNum && pszNum[0] ) + { + m_iRecipeCategoryFilter = (recipecategories_t)atoi(pszNum); + UpdateRecipeFilter(); + } + + return; + } + else if ( !Q_strnicmp( command, "back", 4 ) ) + { + PostMessage( GetParent(), new KeyValues("CraftingClosed") ); + return; + } + else if ( !Q_strnicmp( command, "craft", 5 ) ) + { + if ( CheckForUntradableItems() ) + { + Craft(); + } + return; + } + else if ( !Q_stricmp( command, "upgrade" ) ) + { + EconUI()->CloseEconUI(); + EconUI()->OpenStorePanel( STOREPANEL_SHOW_UPGRADESTEPS, false ); + return; + } + else if ( !Q_stricmp( command, "reloadscheme" ) ) + { + InvalidateLayout( true, true ); + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnRecipePanelEntered( vgui::Panel *panel ) +{ + CRecipeButton *pRecipePanel = dynamic_cast < CRecipeButton * > ( panel ); + + if ( pRecipePanel && IsVisible() && !IsIgnoringItemPanelEnters() ) + { + const CEconCraftingRecipeDefinition *pRecipeDef = NULL; + if ( pRecipePanel->m_iRecipeDefIndex != RECIPE_CUSTOM ) + { + pRecipeDef = TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( pRecipePanel->m_iRecipeDefIndex ); + } + + SetItemPanelToRecipe( GetMouseOverPanel(), pRecipeDef, false ); + PositionMouseOverPanelForRecipe( this, pRecipePanel, m_pRecipeListContainerScroller, GetMouseOverPanel() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnRecipePanelExited( vgui::Panel *panel ) +{ + GetMouseOverPanel()->SetAttribOnly( false ); + GetMouseOverPanel()->SetTextYPos( YRES(20) ); + GetMouseOverPanel()->SetVisible( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CCraftingPanel::GetItemPanelIndex( CItemModelPanel *pItemPanel ) +{ + for ( int i = 0; i < m_pItemModelPanels.Count(); i++ ) + { + if ( m_pItemModelPanels[i] == pItemPanel ) + return i; + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnItemPanelMousePressed( vgui::Panel *panel ) +{ + CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel ); + + if ( pItemPanel && IsVisible() && !pItemPanel->IsGreyedOut() ) + { + int iPos = GetItemPanelIndex(pItemPanel); + if ( IsInputItemPanel(iPos) ) + { + m_iSelectingForSlot = iPos; + + // Create it the first time around + if ( !m_pSelectionPanel ) + { + m_pSelectionPanel = new CCraftingItemSelectionPanel( this ); + } + + if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM ) + { + m_pSelectionPanel->UpdateOnShow( NULL, true, m_InputItems, ARRAYSIZE(m_InputItems) ); + } + else + { + // Clicked on an item in the crafting area. Open up the selection panel. + m_pSelectionPanel->UpdateOnShow( m_ItemPanelCriteria[iPos], false, m_InputItems, ARRAYSIZE(m_InputItems) ); + } + + m_pSelectionPanel->ShowDuplicateCounts( true ); + m_pSelectionPanel->ShowPanel( 0, true ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static void ConfirmCraft( bool bConfirmed, void* pContext ) +{ + CCraftingPanel *pCraftingPanel = ( CCraftingPanel* )pContext; + if ( bConfirmed ) + { + pCraftingPanel->Craft(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCraftingPanel::CheckForUntradableItems( void ) +{ + bool bHasUntradable = false; + for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ ) + { + if ( m_InputItems[i] != 0 ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] ); + if ( pItemData->IsTradable() == false ) + { + bHasUntradable = true; + break; + } + } + } + + if ( bHasUntradable ) + { + CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#Craft_Untradable_Title", "#Craft_Untradable_Text", "#GameUI_OK", "#Cancel", &ConfirmCraft ); + pDialog->SetContext( this ); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::Craft( void ) +{ + // Build our list of items that we're trying to craft + ++m_iCraftingAttempts; + CUtlVector<itemid_t> vecCraftingItems; + for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ ) + { + if ( m_InputItems[i] != 0 ) + { + CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] ); + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_ATTEMPT, pItemData, m_iCraftingAttempts ); + vecCraftingItems.AddToTail( m_InputItems[i] ); + } + } + + if ( !vecCraftingItems.Count() ) + return; + + GCSDK::CGCMsg<MsgGCCraft_t> msg( k_EMsgGCCraft ); + msg.Body().m_nRecipeDefIndex = m_iCurrentlySelectedRecipe; + msg.Body().m_nItemCount = vecCraftingItems.Count(); + for ( int i = 0; i < vecCraftingItems.Count(); i++ ) + { + msg.AddUint64Data( vecCraftingItems[i] ); + } + GCClientSystem()->BSendMessage( msg ); + + OpenCraftingStatusDialog( this, "#CraftUpdate_Start", true, false, false ); + + // Start ticking so we can give up waiting if we don't get a response from the GC + // We use the VGUI time, because we may not be in a game at all. + m_flAbortCraftingAt = vgui::system()->GetCurrentTime() + 10; + m_bWaitingForCraftItems = false; + m_iRecipeIndexTried = m_iCurrentlySelectedRecipe; + + vgui::ivgui()->AddTickSignal( GetVPanel(), 100 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnCraftResponse( EGCMsgResponse eResponse, CUtlVector<uint64> *vecCraftedIndices, int iRecipeUsed ) +{ + switch ( eResponse ) + { + case k_EGCMsgResponseNoMatch: + { + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_NO_RECIPE_MATCH, NULL, m_iCraftingAttempts ); + CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM ); + OpenCraftingStatusDialog( this, "#CraftUpdate_NoMatch", false, true, false ); + } + break; + + case k_EGCMsgResponseDenied: + { + // Craft denied. + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_FAILURE, NULL, m_iCraftingAttempts ); + CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM ); + OpenCraftingStatusDialog( this, "#CraftUpdate_Denied", false, true, false ); + } + break; + + // We've got the list of items crafted. We save off the item list until our item cache has all the items. + case k_EGCMsgResponseOK: + { + // Start ticking, and wait until the cache contains all the items in the list. + m_bWaitingForCraftItems = true; + m_vecNewlyCraftedItems = *vecCraftedIndices; + + if ( iRecipeUsed != m_iRecipeIndexTried && iRecipeUsed != -1 ) + { + m_iNewRecipeIndex = iRecipeUsed; + } + } + break; + + default: + { + // Craft failed in some way. + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_FAILURE, NULL, m_iCraftingAttempts ); + OpenCraftingStatusDialog( this, "#CraftUpdate_Failed", false, true, false ); + CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM ); + } + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::ShowCraftFinish( void ) +{ + TFInventoryManager()->ShowItemsCrafted( &m_vecNewlyCraftedItems ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::OnTick( void ) +{ + BaseClass::OnTick(); + + if ( IsVisible() ) + { + if ( m_flAbortCraftingAt ) + { + if ( m_flAbortCraftingAt < vgui::system()->GetCurrentTime() ) + { + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_TIMEOUT, NULL, m_iCraftingAttempts ); + CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM ); + OpenCraftingStatusDialog( this, "#CraftUpdate_Failed", false, true, false ); + return; + } + } + + if ( m_bWaitingForCraftItems ) + { + // If all the items in our newly crafted list are in the cache, we can show the pickup. + FOR_EACH_VEC_BACK( m_vecNewlyCraftedItems, i ) + { + CEconItemView* pNewItem = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( m_vecNewlyCraftedItems[i] ); + if ( pNewItem == NULL ) + return; + C_CTF_GameStats.Event_Crafting( IE_CRAFTING_SUCCESS, pNewItem, m_iCraftingAttempts ); + } + + m_bWaitingForCraftItems = false; + + // We have all the new items, show the pickup + OpenCraftingStatusDialog( this, "#CraftUpdate_Success", false, true, true ); + CleanupPostCraft( true ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingPanel::CleanupPostCraft( bool bClearInputItems ) +{ + m_flAbortCraftingAt = 0; + m_bWaitingForCraftItems = false; + + UpdateSelectedRecipe( bClearInputItems ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ConVar *CCraftingPanel::GetExplanationConVar( void ) +{ + return &tf_explanations_craftingpanel; +} + +//================================================================================================================================ +// NOT CONNECTED TO STEAM WARNING DIALOG +//================================================================================================================================ +static vgui::DHANDLE<CCraftingStatusDialog> g_CraftingStatusPanel; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCraftingStatusDialog::CCraftingStatusDialog( vgui::Panel *pParent, const char *pElementName ) : BaseClass( pParent, "CraftingStatusDialog" ) +{ + m_pRecipePanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "RecipeItemModelPanel" ) ); + m_bShowNewRecipe = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingStatusDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( m_bShowNewRecipe ) + { + LoadControlSettings( "resource/UI/NewRecipeFoundDialog.res" ); + } + else + { + LoadControlSettings( "resource/UI/CraftingStatusDialog.res" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingStatusDialog::OnCommand( const char *command ) +{ + bool bClose = false; + + if ( !Q_stricmp( command, "close" ) ) + { + // If we were a success, show the player their new crafted items + if ( m_bShowOnExit ) + { + if ( EconUI()->GetCraftingPanel() ) + { + EconUI()->GetCraftingPanel()->ShowCraftFinish(); + } + + m_bShowOnExit = false; + } + + bClose = true; + } + else if ( !Q_stricmp( command, "forceclose" ) ) + { + bClose = true; + } + + if ( bClose ) + { + m_bShowOnExit = false; + TFModalStack()->PopModal( this ); + SetVisible( false ); + MarkForDeletion(); + + EconUI()->SetPreventClosure( false ); + return; + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingStatusDialog::OnTick( void ) +{ + if ( !m_bAnimateEllipses || !IsVisible() ) + { + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + } + else + { + m_iNumEllipses = ((m_iNumEllipses+1) % 4); + } + + switch ( m_iNumEllipses ) + { + case 3: SetDialogVariable( "ellipses", L"..." ); break; + case 2: SetDialogVariable( "ellipses", L".." ); break; + case 1: SetDialogVariable( "ellipses", L"." ); break; + default: SetDialogVariable( "ellipses", L"" ); break; + } + + BaseClass::OnTick(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingStatusDialog::UpdateSchemeForVersion( bool bRecipe ) +{ + m_bShowNewRecipe = bRecipe; + InvalidateLayout( false, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCraftingStatusDialog::ShowStatusUpdate( bool bAnimateEllipses, bool bAllowClose, bool bShowOnExit ) +{ + m_bShowNewRecipe = false; + + CExButton *pButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") ); + if ( pButton ) + { + pButton->SetVisible( bAllowClose ); + pButton->SetEnabled( bAllowClose ); + } + + m_bAnimateEllipses = bAnimateEllipses; + if ( m_bAnimateEllipses ) + { + vgui::ivgui()->AddTickSignal( GetVPanel(), 500 ); + SetDialogVariable( "ellipses", L"" ); + m_iNumEllipses = 0; + } + else + { + vgui::ivgui()->RemoveTickSignal( GetVPanel() ); + SetDialogVariable( "ellipses", L"" ); + } + + m_bShowOnExit = bShowOnExit; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void SetupCraftingStatusDialog( vgui::Panel *pParent ) +{ + if (!g_CraftingStatusPanel.Get()) + { + g_CraftingStatusPanel = vgui::SETUP_PANEL( new CCraftingStatusDialog( pParent, NULL ) ); + } + g_CraftingStatusPanel->SetVisible( true ); + g_CraftingStatusPanel->MakePopup(); + g_CraftingStatusPanel->MoveToFront(); + g_CraftingStatusPanel->SetKeyBoardInputEnabled(true); + g_CraftingStatusPanel->SetMouseInputEnabled(true); + TFModalStack()->PushModal( g_CraftingStatusPanel ); + + EconUI()->SetPreventClosure( true ); +} + +CCraftingStatusDialog *OpenCraftingStatusDialog( vgui::Panel *pParent, const char *pszText, bool bAnimateEllipses, bool bAllowClose, bool bShowOnExit ) +{ + SetupCraftingStatusDialog( pParent ); + g_CraftingStatusPanel->UpdateSchemeForVersion( false ); + g_CraftingStatusPanel->SetDialogVariable( "updatetext", g_pVGuiLocalize->Find( pszText ) ); + g_CraftingStatusPanel->ShowStatusUpdate( bAnimateEllipses, bAllowClose, bShowOnExit ); + return g_CraftingStatusPanel; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CloseCraftingStatusDialog( void ) +{ + if ( g_CraftingStatusPanel ) + { + g_CraftingStatusPanel->OnCommand( "forceclose" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive the craft response +//----------------------------------------------------------------------------- +class CGCCraftResponse : public GCSDK::CGCClientJob +{ +public: + CGCCraftResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket ); + + CUtlVector<uint64> vecCraftedIndices; + uint16 iItems = 0; + if ( !msg.BReadUint16Data( &iItems ) ) + return true; + vecCraftedIndices.SetSize( iItems ); + for ( int i = 0; i < iItems; i++ ) + { + if( !msg.BReadUint64Data( &vecCraftedIndices[i] ) ) + return true; + } + + if ( EconUI()->GetCraftingPanel() ) + { + EconUI()->GetCraftingPanel()->OnCraftResponse( (EGCMsgResponse)msg.Body().m_eResponse, &vecCraftedIndices, msg.Body().m_nResponseIndex ); + } + + //Msg("RECEIVED CGCCraftResponse: %d\n", msg.Body().m_eResponse ); + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CGCCraftResponse, "CGCCraftResponse", k_EMsgGCCraftResponse, GCSDK::k_EServerTypeGCClient ); + + + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive the Golden Wrench broadcast message +//----------------------------------------------------------------------------- +class CGCGoldenWrenchBroadcast : public GCSDK::CGCClientJob +{ +public: + CGCGoldenWrenchBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg<CMsgTFGoldenWrenchBroadcast> msg( pNetPacket ); + + // @todo Tom Bui: should we display this in some other manner? This gets covered up by the crafting panel. + CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel ); + if ( pNotifyPanel ) + { + bool bDeleted = msg.Body().deleted(); + wchar_t szPlayerName[1024]; + g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().user_name().c_str(), szPlayerName, sizeof(szPlayerName) ); + wchar_t szWrenchNumber[16]=L""; + _snwprintf( szWrenchNumber, ARRAYSIZE( szWrenchNumber ), L"%i", msg.Body().wrench_number() ); + wchar_t szNotification[1024]=L""; + g_pVGuiLocalize->ConstructString_safe( szNotification, + g_pVGuiLocalize->Find( bDeleted ? "#TF_HUD_Event_GoldenWrench_D": "#TF_HUD_Event_GoldenWrench_C" ), + 2, szPlayerName, szWrenchNumber ); + pNotifyPanel->SetupNotifyCustom( szNotification, HUD_NOTIFY_GOLDEN_WRENCH, 10.0f ); + + // echo to chat + CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); + if ( pHUDChat ) + { + char szAnsi[1024]; + g_pVGuiLocalize->ConvertUnicodeToANSI( szNotification, szAnsi, sizeof(szAnsi) ); + + pHUDChat->Printf( CHAT_FILTER_NONE, "%s", szAnsi ); + } + + // play a sound + vgui::surface()->PlaySound( bDeleted ? "vo/announcer_failure.mp3" : "vo/announcer_success.mp3" ); + } + + //Msg("RECEIVED CGCCraftResponse: %d\n", msg.Body().m_eResponse ); + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CGCGoldenWrenchBroadcast, "CGCGoldenWrenchBroadcast", k_EMsgGCGoldenWrenchBroadcast, GCSDK::k_EServerTypeGCClient ); + + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive the Saxxy broadcast message +//----------------------------------------------------------------------------- +class CGSaxxyBroadcast : public GCSDK::CGCClientJob +{ +public: + CGSaxxyBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg<CMsgTFSaxxyBroadcast> msg( pNetPacket ); + + CEconNotification *pNotification = new CEconNotification(); + pNotification->SetText( "#TF_Event_Saxxy_Deleted" ); + pNotification->SetLifetime( 30.0f ); + + { + // Who deleted this? + wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ]; + g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().has_user_name() ? msg.Body().user_name().c_str() : NULL, wszPlayerName, sizeof( wszPlayerName ) ); + pNotification->AddStringToken( "owner", wszPlayerName ); + + // What category was the Saxxy for? + char szCategory[MAX_ATTRIBUTE_DESCRIPTION_LENGTH]; + Q_snprintf( szCategory, sizeof( szCategory ), "Replay_Contest_Category%d", msg.Body().category_number() ); + + pNotification->AddStringToken( "category", g_pVGuiLocalize->Find( szCategory ) ); + } + + NotificationQueue_Add( pNotification ); + + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CGSaxxyBroadcast, "CGSaxxyBroadcast", k_EMsgGCSaxxyBroadcast, GCSDK::k_EServerTypeGCClient ); + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive any generic item deletion notification +//----------------------------------------------------------------------------- +class CClientItemBroadcastNotificationJob : public GCSDK::CGCClientJob +{ +public: + CClientItemBroadcastNotificationJob( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg<CMsgGCTFSpecificItemBroadcast> msg( pNetPacket ); + + CEconNotification *pNotification = new CEconNotification(); + pNotification->SetText( msg.Body().was_destruction() ? "#TF_Event_Item_Deleted" : "#TF_Event_Item_Created" ); + pNotification->SetLifetime( 30.0f ); + + // Who deleted this? + wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ]; + g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().has_user_name() ? msg.Body().user_name().c_str() : NULL, wszPlayerName, sizeof( wszPlayerName ) ); + pNotification->AddStringToken( "owner", wszPlayerName ); + + // What type of item was this? + const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( msg.Body().item_def_index() ); + if ( pItemDef ) + { + pNotification->AddStringToken( "item_name", g_pVGuiLocalize->Find( pItemDef->GetItemBaseName() ) ); + + NotificationQueue_Add( pNotification ); + } + + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CClientItemBroadcastNotificationJob, "CClientItemBroadcastNotificationJob", k_EMsgGCTFSpecificItemBroadcast, GCSDK::k_EServerTypeGCClient ); + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive the Saxxy Awarded broadcast message +//----------------------------------------------------------------------------- +class CGSaxxyAwardedBroadcast : public GCSDK::CGCClientJob +{ +private: + // embedded notification for custom trigger + class CSaxxyAwardedNotification : public CEconNotification + { + public: + CSaxxyAwardedNotification() + { + SetSoundFilename( "vo/announcer_success.mp3" ); + } + + virtual EType NotificationType() { return eType_Trigger; } + + virtual void Trigger() + { + if ( steamapicontext && steamapicontext->SteamFriends() ) + { + steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "http://www.teamfortress.com/saxxyawards/winners.php" ); + } + MarkForDeletion(); + } + }; + +public: + + CGSaxxyAwardedBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg< CMsgSaxxyAwarded > msg( pNetPacket ); + + CEconNotification *pNotification = new CSaxxyAwardedNotification(); + pNotification->SetText( "#TF_Event_Saxxy_Awarded" ); + pNotification->SetLifetime( 30.0f ); + + { + // Winners + CFmtStr1024 strWinners; + for ( int i = 0; i < msg.Body().winner_names_size(); ++i ) + { + strWinners.Append( msg.Body().winner_names( i ).c_str() ); + if ( i + 1 < msg.Body().winner_names_size() ) + { + strWinners.Append( "\n" ); + } + } + wchar_t wszPlayerNames[ 1024 ]; + g_pVGuiLocalize->ConvertANSIToUnicode( strWinners.Access(), wszPlayerNames, sizeof( wszPlayerNames ) ); + pNotification->AddStringToken( "winners", wszPlayerNames ); + + // year + CRTime cTime; + cTime.SetToCurrentTime(); + cTime.SetToGMT( false ); + locchar_t wszYear[10]; + loc_sprintf_safe( wszYear, LOCCHAR( "%04u" ), cTime.GetYear() ); + pNotification->AddStringToken( "year", wszYear ); + + // What category was the Saxxy for? + char szCategory[MAX_ATTRIBUTE_DESCRIPTION_LENGTH]; + Q_snprintf( szCategory, sizeof( szCategory ), "Replay_Contest_Category%d", msg.Body().category() ); + pNotification->AddStringToken( "category", g_pVGuiLocalize->Find( szCategory ) ); + } + + NotificationQueue_Add( pNotification ); + + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CGSaxxyAwardedBroadcast, "CGSaxxyAwardedBroadcast", k_EMsgGCSaxxy_Awarded, GCSDK::k_EServerTypeGCClient ); + +//----------------------------------------------------------------------------- +// Purpose: GC Msg handler to receive a generic system broadcast message +//----------------------------------------------------------------------------- +class CGCSystemMessageBroadcast : public GCSDK::CGCClientJob +{ +public: + CGCSystemMessageBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); + if ( !pHUDChat ) + return false; + + GCSDK::CProtoBufMsg<CMsgSystemBroadcast> msg( pNetPacket ); + + // retrieve the text + const char *pchMessage = msg.Body().message().c_str(); + wchar_t *pwMessage = g_pVGuiLocalize->Find( pchMessage ); + wchar_t wszConvertedText[2048] = L""; + if ( pwMessage == NULL ) + { + g_pVGuiLocalize->ConvertANSIToUnicode( pchMessage, wszConvertedText, sizeof( wszConvertedText ) ); + pwMessage = wszConvertedText; + } + + Color color( 0xff, 0xcc, 0x33, 255 ); + KeyValuesAD keyValues( "System Message" ); + keyValues->SetWString( "message", pwMessage ); + keyValues->SetColor( "custom_color", color ); + + // print to chat log + wchar_t wszLocalizedString[2048] = L""; + g_pVGuiLocalize->ConstructString_safe( wszLocalizedString, "#Notification_System_Message", keyValues ); + pHUDChat->SetCustomColor( color ); + pHUDChat->Printf( CHAT_FILTER_NONE, "%ls", wszLocalizedString ); + + // send to notification + CEconNotification* pNotification = new CEconNotification(); + pNotification->SetText( "#Notification_System_Message" ); + pNotification->SetKeyValues( keyValues ); + pNotification->SetLifetime( 30.0f ); + pNotification->SetSoundFilename( "ui/system_message_alert.wav" ); + NotificationQueue_Add( pNotification ); + + return true; + } + +}; + +GC_REG_JOB( GCSDK::CGCClient, CGCSystemMessageBroadcast, "CGCSystemMessageBroadcast", k_EMsgGCSystemMessage, GCSDK::k_EServerTypeGCClient ); |