summaryrefslogtreecommitdiff
path: root/game/client/tf/vgui/crafting_panel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/client/tf/vgui/crafting_panel.cpp')
-rw-r--r--game/client/tf/vgui/crafting_panel.cpp1738
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 );