summaryrefslogtreecommitdiff
path: root/game/client/econ
diff options
context:
space:
mode:
Diffstat (limited to 'game/client/econ')
-rw-r--r--game/client/econ/backpack_panel.cpp4272
-rw-r--r--game/client/econ/backpack_panel.h268
-rw-r--r--game/client/econ/base_loadout_panel.cpp803
-rw-r--r--game/client/econ/base_loadout_panel.h115
-rw-r--r--game/client/econ/client_community_market.cpp107
-rw-r--r--game/client/econ/client_community_market.h19
-rw-r--r--game/client/econ/confirm_delete_dialog.cpp37
-rw-r--r--game/client/econ/confirm_delete_dialog.h28
-rw-r--r--game/client/econ/confirm_dialog.cpp932
-rw-r--r--game/client/econ/confirm_dialog.h230
-rw-r--r--game/client/econ/econ_consumables.cpp266
-rw-r--r--game/client/econ/econ_controls.cpp2382
-rw-r--r--game/client/econ/econ_controls.h429
-rw-r--r--game/client/econ/econ_notifications.cpp1436
-rw-r--r--game/client/econ/econ_notifications.h214
-rw-r--r--game/client/econ/econ_sample_rootui.cpp458
-rw-r--r--game/client/econ/econ_sample_rootui.h88
-rw-r--r--game/client/econ/econ_trading.cpp645
-rw-r--r--game/client/econ/econ_trading.h40
-rw-r--r--game/client/econ/econ_ui.h165
-rw-r--r--game/client/econ/iconrenderreceiver.h54
-rw-r--r--game/client/econ/item_confirm_delete_dialog.cpp29
-rw-r--r--game/client/econ/item_confirm_delete_dialog.h37
-rw-r--r--game/client/econ/item_model_panel.cpp3933
-rw-r--r--game/client/econ/item_model_panel.h519
-rw-r--r--game/client/econ/item_pickup_panel.cpp889
-rw-r--r--game/client/econ/item_pickup_panel.h128
-rw-r--r--game/client/econ/item_rental_ui.cpp274
-rw-r--r--game/client/econ/item_rental_ui.h66
-rw-r--r--game/client/econ/item_selection_panel.cpp1493
-rw-r--r--game/client/econ/item_selection_panel.h215
-rw-r--r--game/client/econ/item_style_select_dialog.cpp209
-rw-r--r--game/client/econ/item_style_select_dialog.h71
-rw-r--r--game/client/econ/local_steam_shared_object_listener.cpp24
-rw-r--r--game/client/econ/local_steam_shared_object_listener.h31
-rw-r--r--game/client/econ/store/store_page.cpp2233
-rw-r--r--game/client/econ/store/store_page.h424
-rw-r--r--game/client/econ/store/store_page_halloween.cpp85
-rw-r--r--game/client/econ/store/store_page_halloween.h49
-rw-r--r--game/client/econ/store/store_page_new.cpp232
-rw-r--r--game/client/econ/store/store_page_new.h107
-rw-r--r--game/client/econ/store/store_panel.cpp2175
-rw-r--r--game/client/econ/store/store_panel.h236
-rw-r--r--game/client/econ/store/store_preview_item.cpp418
-rw-r--r--game/client/econ/store/store_preview_item.h100
-rw-r--r--game/client/econ/store/store_viewcart.cpp387
-rw-r--r--game/client/econ/store/store_viewcart.h75
-rw-r--r--game/client/econ/tool_items/custom_texture_cache.cpp1111
-rw-r--r--game/client/econ/tool_items/custom_texture_cache.h83
-rw-r--r--game/client/econ/tool_items/custom_texture_tool.cpp2766
-rw-r--r--game/client/econ/tool_items/decoder_ring_tool.cpp213
-rw-r--r--game/client/econ/tool_items/decoder_ring_tool.h14
-rw-r--r--game/client/econ/tool_items/gift_wrap_tool.cpp241
-rw-r--r--game/client/econ/tool_items/gift_wrap_tool.h14
-rw-r--r--game/client/econ/tool_items/paint_can_tool.cpp205
-rw-r--r--game/client/econ/tool_items/paint_can_tool.h15
-rw-r--r--game/client/econ/tool_items/rename_tool_ui.cpp306
-rw-r--r--game/client/econ/tool_items/rename_tool_ui.h74
-rw-r--r--game/client/econ/tool_items/tool_items.cpp968
-rw-r--r--game/client/econ/tool_items/tool_items.h58
-rw-r--r--game/client/econ/trading_start_dialog.cpp713
-rw-r--r--game/client/econ/trading_start_dialog.h115
62 files changed, 34323 insertions, 0 deletions
diff --git a/game/client/econ/backpack_panel.cpp b/game/client/econ/backpack_panel.cpp
new file mode 100644
index 0000000..eb90d6b
--- /dev/null
+++ b/game/client/econ/backpack_panel.cpp
@@ -0,0 +1,4272 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "backpack_panel.h"
+#include "item_confirm_delete_dialog.h"
+#include "vgui/ISurface.h"
+#include "gamestringpool.h"
+#include "iclientmode.h"
+#include "econ_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/ScalableImagePanel.h"
+#include "vgui/IInput.h"
+#include "econ/tool_items/tool_items.h"
+#include "econ_gcmessages.h"
+#include "item_style_select_dialog.h"
+#include "econ_item_system.h"
+#include "econ_item_tools.h"
+#include "econ_ui.h"
+#include "gc_clientsystem.h"
+#include "econ_store.h"
+#include "rtime.h"
+#include "econ_item_description.h"
+#include "dynamic_recipe_subpanel.h"
+#include "item_slot_panel.h"
+#include "crate_detail_panels.h"
+#include "tf_warinfopanel.h"
+#include "character_info_panel.h"
+#include "trading_start_dialog.h"
+#include "vgui_controls/MenuItem.h"
+#include "tf_duckleaderboard.h"
+#include "tf_item_inventory.h"
+#include "store/store_panel.h"
+#include "strange_count_transfer_panel.h"
+#include "collection_crafting_panel.h"
+#include "halloween_offering_panel.h"
+#include "store/v2/tf_store_preview_item2.h"
+#include "item_ad_panel.h"
+#include "client_community_market.h"
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+#ifdef STAGING_ONLY
+extern ConVar tf_use_card_tooltips;
+extern ConVar tf_weapon_force_allow_inspect;
+#endif // STAGING_ONLY
+
+ConVar tf_trade_up_use_count( "tf_trade_up_use_count", "3", FCVAR_ARCHIVE | FCVAR_HIDDEN );
+
+
+void UseConsumableItem( CEconItemView *pItem, vgui::Panel* pParent );
+
+const ItemSortTypeData_t g_BackpackSortTypes[] =
+{
+ { "#Backpack_SortBy_Header", kGCItemSort_NoSort },
+ { "#Backpack_SortBy_Rarity", kGCItemSort_SortByRarity },
+ { "#Backpack_SortBy_Type", kGCItemSort_SortByType },
+ { "#Backpack_SortBy_Class", kTFGCItemSort_SortByClass },
+ { "#Backpack_SortBy_Slot", kTFGCItemSort_SortBySlot },
+ { "#Backpack_SortBy_Date", kGCItemSort_SortByDate },
+};
+
+// Array of borders for rarities. Three borders for each rarity: Base, Mouseover, and Selected
+const char *g_szItemBorders[][5] =
+{
+ { "BackpackItemBorder", "BackpackItemMouseOverBorder", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder", "BackpackItemGreyedOutSelectedBorder" }, // AE_NORMAL = 0
+ { "BackpackItemBorder_1", "BackpackItemMouseOverBorder_1", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_1", "BackpackItemGreyedOutSelectedBorder_1" }, // AE_RARITY1 = 1
+ { "BackpackItemBorder_2", "BackpackItemMouseOverBorder_2", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_2", "BackpackItemGreyedOutSelectedBorder_2" }, // AE_RARITY2 = 2
+ { "BackpackItemBorder_Vintage", "BackpackItemMouseOverBorder_Vintage", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_Vintage", "BackpackItemGreyedOutSelectedBorder_Vintage" }, // AE_VINTAGE = 3
+ { "BackpackItemBorder_3", "BackpackItemMouseOverBorder_3", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_3", "BackpackItemGreyedOutSelectedBorder_3" }, // AE_RARITY3
+ { "BackpackItemBorder_4", "BackpackItemMouseOverBorder_4", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_4", "BackpackItemGreyedOutSelectedBorder_4" }, // AE_RARITY4
+ { "BackpackItemBorder_Unique", "BackpackItemMouseOverBorder_Unique", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_Unique", "BackpackItemGreyedOutSelectedBorder_Unique" }, // AE_UNIQUE
+ { "BackpackItemBorder_Community", "BackpackItemMouseOverBorder_Community", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_Community", "BackpackItemGreyedOutSelectedBorder_Community" }, // AE_COMMUNITY
+ { "BackpackItemBorder_Developer", "BackpackItemMouseOverBorder_Developer", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_Developer", "BackpackItemGreyedOutSelectedBorder_Developer" }, // AE_DEVELOPER
+ { "BackpackItemBorder_SelfMade", "BackpackItemMouseOverBorder_SelfMade", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_SelfMade", "BackpackItemGreyedOutSelectedBorder_SelfMade" }, // AE_SELFMADE
+ { "BackpackItemBorder_Customized", "BackpackItemMouseOverBorder_Customized", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_Customized", "BackpackItemGreyedOutSelectedBorder_Customized" }, // AE_CUSTOMIZED
+ { "BackpackItemBorder_Strange", "BackpackItemMouseOverBorder_Strange", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_Strange", "BackpackItemGreyedOutSelectedBorder_Strange" }, // AE_STRANGE
+ { "BackpackItemBorder_Completed", "BackpackItemMouseOverBorder_Completed", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_Completed", "BackpackItemGreyedOutSelectedBorder_Completed" }, // AE_COMPLETED
+ { "BackpackItemBorder_Haunted", "BackpackItemMouseOverBorder_Haunted", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_Haunted", "BackpackItemGreyedOutSelectedBorder_Haunted" }, // AE_HAUNTED
+ { "BackpackItemBorder_Collectors", "BackpackItemMouseOverBorder_Collectors", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_Collectors", "BackpackItemGreyedOutSelectedBorder_Collectors" }, // AE_COLLECTORS
+
+ { "BackpackItemBorder_PaintkitWeapon", "BackpackItemMouseOverBorder_PaintkitWeapon", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_PaintkitWeapon", "BackpackItemGreyedOutSelectedBorder_PaintkitWeapon" }, // AE_Paintkit
+ { "BackpackItemBorder_RarityDefault", "BackpackItemMouseOverBorder_RarityDefault", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_RarityDefault", "BackpackItemGreyedOutSelectedBorder_RarityDefault" }, // AE_RARITY_DEFAULT,
+ { "BackpackItemBorder_RarityCommon", "BackpackItemMouseOverBorder_RarityCommon", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_RarityCommon", "BackpackItemGreyedOutSelectedBorder_RarityCommon" }, // AE_RARITY_COMMON,
+ { "BackpackItemBorder_RarityUncommon", "BackpackItemMouseOverBorder_RarityUncommon", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_RarityUncommon", "BackpackItemGreyedOutSelectedBorder_RarityUncommon" }, // AE_RARITY_UNCOMMON,
+ { "BackpackItemBorder_RarityRare", "BackpackItemMouseOverBorder_RarityRare", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_RarityRare", "BackpackItemGreyedOutSelectedBorder_RarityRare" }, // AE_RARITY_RARE,
+ { "BackpackItemBorder_RarityMythical", "BackpackItemMouseOverBorder_RarityMythical", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_RarityMythical", "BackpackItemGreyedOutSelectedBorder_RarityMythical" }, // AE_RARITY_MYTHICAL,
+ { "BackpackItemBorder_RarityLegendary", "BackpackItemMouseOverBorder_RarityLegendary", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_RarityLegendary", "BackpackItemGreyedOutSelectedBorder_RarityLegendary" }, // AE_RARITY_LEGENDARY,
+ { "BackpackItemBorder_RarityAncient", "BackpackItemMouseOverBorder_RarityAncient", "BackpackItemSelectedBorder", "BackpackItemGreyedOutBorder_RarityAncient", "BackpackItemGreyedOutSelectedBorder_RarityAncient" }, // AE_RARITY_ANCIENT,
+};
+
+COMPILE_TIME_ASSERT( ARRAYSIZE(g_szItemBorders) == AE_MAX_TYPES );
+
+enum { kNoUserData = -1 };
+
+static bool HasPaint ( const CEconItemView *pEconItemView, const char *, int )
+{
+ static CSchemaAttributeDefHandle pAttrDef_PaintRGB( "set item tint RGB" );
+ static CSchemaAttributeDefHandle pAttrDef_PaintRGB2( "set item tint RGB 2" );
+
+ return pEconItemView->FindAttribute( pAttrDef_PaintRGB )
+ || pEconItemView->FindAttribute( pAttrDef_PaintRGB2 );
+}
+
+static bool HasCustomAttribute ( const CEconItemView *pEconItemView, const char *szAttrName, int )
+{
+ const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinitionByName( szAttrName );
+
+ return pAttrDef
+ ? pEconItemView->FindAttribute( pAttrDef )
+ : NULL;
+}
+
+static bool HasCustomUserAttribute ( const CEconItemView *pEconItemView, const char *, int iUserData )
+{
+ Assert( iUserData != kNoUserData );
+
+ CCountUserGeneratedAttributeIterator countIterator;
+ pEconItemView->IterateAttributes( &countIterator );
+
+ return countIterator.GetCount() > iUserData;
+}
+
+static bool HasRemovableCustomName ( const CEconItemView *pEconItemView, const char *, int )
+{
+ if ( !pEconItemView->GetItemDefinition() )
+ return false;
+
+ if ( pEconItemView->GetQuality() == AE_UNIQUE && pEconItemView->GetItemDefinition()->GetArmoryDescString() && !V_stricmp( pEconItemView->GetItemDefinition()->GetArmoryDescString(), "stockitem" ) )
+ return false;
+
+ return pEconItemView->GetSOCData() && pEconItemView->GetSOCData()->GetCustomName();
+}
+
+static bool HasRemovableCustomDesc ( const CEconItemView *pEconItemView, const char *, int )
+{
+ if ( !pEconItemView->GetItemDefinition() )
+ return false;
+
+ if ( pEconItemView->GetQuality() == AE_UNIQUE && pEconItemView->GetItemDefinition()->GetArmoryDescString() && !V_stricmp( pEconItemView->GetItemDefinition()->GetArmoryDescString(), "stockitem" ) )
+ return false;
+
+ return pEconItemView->GetSOCData() && pEconItemView->GetSOCData()->GetCustomDesc();
+}
+
+enum EItemCustomizationRemoveType
+{
+ kCustomizationRemove_Paint,
+ kCustomizationRemove_Name,
+ kCustomizationRemove_Desc,
+ kCustomizationRemove_CustomTexture,
+ kCustomizationRemove_MakersMark,
+ kCustomizationRemove_UniqueCraftIndex,
+ kCustomizationRemove_StrangePart,
+ kCustomizationRemove_StrangeScores,
+ kCustomizationRemove_UpgradeCard,
+ kCustomizationRemove_KillStreak,
+ kCustomizationRemove_GiftedBy,
+ kCustomizationRemove_Festivizer,
+};
+
+typedef bool (* HasRefurbishablePropertyFunc_t)( const CEconItemView *pEconItemView, const char *pArg, int iUserData );
+
+void GetCustomDialogToken_PaintName( const CEconItemView *pEconItemView, int iUserData, CUtlConstWideString& out_String )
+{
+ extern const CEconItemDefinition *GetPaintItemDefinitionForPaintedItem( const IEconItemInterface *pEconItem );
+
+ Assert( iUserData == kNoUserData );
+
+ const CEconItemDefinition *pPaintItemDef = GetPaintItemDefinitionForPaintedItem( pEconItemView );
+ if ( !pPaintItemDef )
+ {
+ out_String = L"";
+ return;
+ }
+
+ out_String = GLocalizationProvider()->Find( pPaintItemDef->GetItemBaseName() );
+}
+
+void GetCustomDialogToken_StrangePartName( const CEconItemView *pEconItemView, int iUserData, CUtlConstWideString& out_String )
+{
+ extern uint32 GetScoreTypeForKillEaterAttr( const IEconItemInterface *pEconItem, const CEconItemAttributeDefinition *pAttribDef );
+ extern const wchar_t *GetLocalizedStringForKillEaterTypeAttr( const CLocalizationProvider *pLocalizationProvider, uint32 unKillEaterEventType ); // return type changed from locchar_t * because the backpack panel only exists on the client
+
+ uint32 unKillEaterBaseType = GetScoreTypeForKillEaterAttr( pEconItemView, GetKillEaterAttr_Type( iUserData ) );
+
+ out_String = GetLocalizedStringForKillEaterTypeAttr( GLocalizationProvider(), unKillEaterBaseType );
+}
+
+void GetCustomDialogToken_UserAttributeName( const CEconItemView *pEconItemView, int iUserData, CUtlConstWideString& out_String )
+{
+ Assert( pEconItemView );
+
+ const CEconItemAttributeDefinition *pAttrDef = GetCardUpgradeForIndex( pEconItemView, iUserData );
+ if ( !pAttrDef )
+ {
+ out_String = L"unknown";
+ return;
+ }
+
+ attrib_value_t attrVal;
+ Verify( pEconItemView->FindAttribute( pAttrDef, &attrVal ) );
+ CEconAttributeDescription attrDesc( GLocalizationProvider(), pAttrDef, attrVal );
+ out_String = attrDesc.GetShortDescription();
+}
+
+typedef void (* GetCustomDialogLocalizationTokenFunc_t)( const CEconItemView *pEconItemView, int iUserData, CUtlConstWideString& out_String );
+
+struct RefurbishableProperty
+{
+ HasRefurbishablePropertyFunc_t m_pFunc;
+ GetCustomDialogLocalizationTokenFunc_t m_pGetCustomDialogLocalizationTokenFunc;
+ const char *m_szArg;
+ const char *m_pszSelectionUILocalizationToken;
+ const char *m_szDialogTitle;
+ const char *m_szDialogDesc;
+ EItemCustomizationRemoveType m_eRemovalType;
+ int m_iUserData;
+};
+
+// TODO: Add Gifted by Tag here
+static RefurbishableProperty g_RemoveableAttributes[] =
+{
+ { &HasRemovableCustomName, NULL, NULL, "#RefurbishItem_RemoveNameCombo", "#RefurbishItem_RemoveNameTitle", "#RefurbishItem_RemoveName", kCustomizationRemove_Name, kNoUserData }, // does this item have a custom name?
+ { &HasRemovableCustomDesc, NULL, NULL, "#RefurbishItem_RemoveDescCombo", "#RefurbishItem_RemoveDescTitle", "#RefurbishItem_RemoveDesc", kCustomizationRemove_Desc, kNoUserData }, // does this item have a custom description?
+ { &HasPaint, &GetCustomDialogToken_PaintName, "set item tint rgb", "#RefurbishItem_RemovePaintCombo", "#RefurbishItem_RemovePaintTitle", "#RefurbishItem_RemovePaint", kCustomizationRemove_Paint, kNoUserData }, // is this item painted?
+ { &HasCustomAttribute, NULL, "custom texture hi", "#RefurbishItem_RemoveCustomTextureCombo", "#RefurbishItem_RemoveCustomTextureTitle", "#RefurbishItem_RemoveCustomTexture", kCustomizationRemove_CustomTexture, kNoUserData }, // does this have a custom texture applied?
+ { &HasCustomAttribute, NULL, "makers mark id", "#RefurbishItem_RemoveMakersMarkCombo", "#RefurbishItem_RemoveMakersMarkTitle", "#RefurbishItem_RemoveMakersMark", kCustomizationRemove_MakersMark, kNoUserData }, // was this item crafted by a specific dude?
+ { &HasCustomAttribute, NULL, "killstreak tier", "#RefurbishItem_RemoveKillStreakCombo", "#RefurbishItem_RemoveKillStreakTitle", "#RefurbishItem_RemoveKillStreak", kCustomizationRemove_KillStreak, kNoUserData }, // Killstreak Effect
+ { &HasCustomAttribute, NULL, "gifter account id", "#RefurbishItem_RemoveGifterCombo", "#RefurbishItem_RemoveGifterTitle", "#RefurbishItem_RemoveGifter", kCustomizationRemove_GiftedBy, kNoUserData }, // Gifted by
+ { &HasCustomAttribute, NULL, "is_festivized", "#RefurbishItem_RemoveFestivizerCombo", "#RefurbishItem_RemoveFestivizerTitle", "#RefurbishItem_RemoveFestivizer", kCustomizationRemove_Festivizer, kNoUserData }, // Festivizer
+
+ //"gifter account id", // who gifted us this item? (will also remove "event date")
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Look over this weapon to see if it has any strange stat counters to reset optionally.
+//-----------------------------------------------------------------------------
+static bool HasResettableScoreAttributes ( const CEconItemView *pEconItemView, const char *, int )
+{
+ if ( !pEconItemView )
+ return false;
+
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ uint32 unScore;
+ if ( pEconItemView->FindAttribute( GetKillEaterAttr_Score( i ), &unScore ) && unScore > 0 )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int GetRemovableAttributesCount()
+{
+ return ARRAYSIZE( g_RemoveableAttributes )
+ + GetKillEaterAttrCount()
+ + GetMaxCardUpgradesPerItem() // remove card upgrades
+ + 1; // strange quality item score reset
+}
+
+RefurbishableProperty RemovableAttributes_GetAttributeDetails( int i )
+{
+ Assert( i >= 0 );
+ Assert( i < GetRemovableAttributesCount() );
+
+ if ( i < ARRAYSIZE( g_RemoveableAttributes ) )
+ return g_RemoveableAttributes[i];
+
+ // Which attribute in particular are we looking for?
+ int iStrangePartIndex = i - ARRAYSIZE( g_RemoveableAttributes );
+ if ( iStrangePartIndex < GetKillEaterAttrCount() )
+ {
+ int iKillEaterAttrIndex = (GetKillEaterAttrCount() - GetKillEaterAttrCount()) + iStrangePartIndex;
+
+ // if we're looking at strange attributes...
+ if ( GetKillEaterAttr_IsUserCustomizable( iKillEaterAttrIndex ) )
+ {
+ // Common properties for all strange part attributes.
+ static RefurbishableProperty sStrangePartProperty = { &HasCustomAttribute, &GetCustomDialogToken_StrangePartName, NULL, "#RefurbishItem_RemoveStrangePartCombo", "#RefurbishItem_RemoveStrangePartTitle", "#RefurbishItem_RemoveStrangePart", kCustomizationRemove_StrangePart, kNoUserData };
+
+ RefurbishableProperty partReturnProp = sStrangePartProperty;
+ partReturnProp.m_szArg = GetKillEaterAttr_Score( iKillEaterAttrIndex )->GetDefinitionName(); // ...then we check for the presence of a score attribute if this slot is a strange part...
+ partReturnProp.m_iUserData = iKillEaterAttrIndex;
+
+ return partReturnProp;
+ }
+
+ // ...or the presence of a restriction attribute if this slot is a base slot that might have a filter
+ static RefurbishableProperty sStrangeFilterProperty = { &HasCustomAttribute, &GetCustomDialogToken_StrangePartName, NULL, "#RefurbishItem_RemoveStrangeFilterCombo", "#RefurbishItem_RemoveStrangeFilterTitle", "#RefurbishItem_RemoveStrangeFilter", kCustomizationRemove_StrangePart, kNoUserData };
+
+ RefurbishableProperty filterReturnProp = sStrangeFilterProperty;
+ filterReturnProp.m_szArg = GetKillEaterAttr_Restriction( iKillEaterAttrIndex )->GetDefinitionName();
+ filterReturnProp.m_iUserData = iKillEaterAttrIndex;
+
+ return filterReturnProp;
+ }
+
+ // Look for any properties that were user-assigned. We allow users to remove them.
+ int iCardUpgradeIndex = iStrangePartIndex - GetKillEaterAttrCount();
+ if ( iCardUpgradeIndex < GetMaxCardUpgradesPerItem() )
+ {
+ // Common properties for all card upgrade attributes.
+ static RefurbishableProperty sCardUpgradeProperty = { &HasCustomUserAttribute, &GetCustomDialogToken_UserAttributeName, NULL, "#RefurbishItem_RemoveSpellCombo", "#RefurbishItem_RemoveSpellTitle", "#RefurbishItem_RemoveSpellUpgrade", kCustomizationRemove_UpgradeCard, kNoUserData };
+
+ RefurbishableProperty returnProp = sCardUpgradeProperty;
+ // FIX THIS FOR CARDS / SPELLS?
+ // returnProp.m_szArg = GetCustomDialogToken_UserAttributeName ?
+ returnProp.m_iUserData = iCardUpgradeIndex;
+
+ return returnProp;
+ }
+
+ // We might also be trying to reset the strange score counters.
+ Assert( iStrangePartIndex == GetKillEaterAttrCount() + GetMaxCardUpgradesPerItem() );
+ Assert( i == GetRemovableAttributesCount() - 1 );
+
+ static RefurbishableProperty sStrangeScoreReset = { &HasResettableScoreAttributes, NULL, NULL, "#RefurbishItem_RemoveStrangeScoresCombo", "#RefurbishItem_RemoveStrangeScoresTitle", "#RefurbishItem_RemoveStrangeScores", kCustomizationRemove_StrangeScores, kNoUserData };
+ return sStrangeScoreReset;
+}
+
+bool RemovableAttributes_DoesAttributeApply( int i, const CEconItemView *pEconItemView )
+{
+ static CSchemaAttributeDefHandle pAttr_CannotRestore( "cannot restore" );
+ if ( pEconItemView->FindAttribute( pAttr_CannotRestore ) )
+ return false;
+
+ RefurbishableProperty attr = RemovableAttributes_GetAttributeDetails( i );
+
+ return attr.m_pFunc( pEconItemView, attr.m_szArg, attr.m_iUserData );
+}
+
+bool RemovableAttributes_DoAnyAttributesApply( const CEconItemView *pEconItemView )
+{
+ static CSchemaAttributeDefHandle pAttr_CannotRestore( "cannot restore" );
+ if ( pEconItemView->FindAttribute( pAttr_CannotRestore ) )
+ return false;
+
+ for ( int i = 0; i < GetRemovableAttributesCount(); i++ )
+ {
+ if ( RemovableAttributes_DoesAttributeApply( i, pEconItemView ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+ConVar cl_showbackpackrarities( "cl_showbackpackrarities", "0", FCVAR_ARCHIVE, "0 = Show no backpack icon border colors. 1 = Show item rarities within the backpack. 2 = Show item rarities only for Market-listable items." );
+ConVar cl_show_market_data_on_items( "cl_show_market_data_on_items", "1", FCVAR_ARCHIVE, "0 = Never. 1 = Only when showing borders for Market-listable items. 2 = Always." );
+
+ConVar tf_explanations_backpackpanel( "tf_explanations_backpackpanel", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." );
+
+ConVar tf_backpack_page_button_delay( "tf_backpack_page_button_delay", "0.5", FCVAR_ARCHIVE, "Amount of time the mouse cursor needs to hover over the page button to select the page." );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBackpackPanel::CBackpackPanel( vgui::Panel *parent, const char *panelName ) : CBaseLoadoutPanel( parent, panelName )
+{
+ m_nQuickOpenTxn = 0;
+ m_pContextMenu = NULL;
+ m_pPageButtonKVs = NULL;
+ m_mapSeenItems.SetLessFunc( DefLessFunc( itemid_t ) );
+ m_bInitializedSeenItems = false;
+ m_pNameFilterTextEntry = NULL;
+ m_flFilterItemTime = 0.f;
+ m_mapFilteringItems.SetLessFunc( DefLessFunc(int) );
+
+ m_pNextPageButton = NULL;
+ m_pPrevPageButton = NULL;
+ m_pShowExplanationsButton = NULL;
+ m_pCurPageLabel = NULL;
+ m_pSortByComboBox = NULL;
+ m_pShowRarityComboBox = NULL;
+ m_pShowBaseItemsCheckbox = NULL;
+ m_pDragToNextPageButton = NULL;
+ m_pDragToPrevPageButton = NULL;
+ m_flPreventDragPageSwitchUntil = 0;
+ m_flStartExplanationsAt = 0;
+
+ m_flMouseDownTime = 0;
+ m_pItemDraggedFromPanel = NULL;
+ m_iDraggedFromPage = 0;
+ m_bMouseDownOnItemPanel = false;
+ m_bDragging = false;
+ m_iMouseDownX = m_iMouseDownY = 0;
+
+ m_pMouseDragItemPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "mousedragitempanel" ) );
+ m_pCancelToolButton = NULL;
+ m_pCraftButton = NULL;
+ m_bShowBaseItems = false;
+ m_pConfirmDeleteDialog = NULL;
+ m_pToolIcon = NULL;
+ m_eSelectionMode = StandardSelection;
+ m_nLastToolPage = 0;
+ m_pDynamicRecipePanel = NULL;
+ m_pItemSlotPanel = NULL;
+ m_pStrangeToolPanel = NULL;
+
+ m_nNumActivePages = 0;
+
+ m_pInspectPanel = new CTFItemInspectionPanel( this, "InspectionPanel" );
+ m_pInspectCosmeticPanel = new CTFStorePreviewItemPanel2( this, "Resource/UI/econ/InspectionPanel_Cosmetic.res", "storepreviewitem", NULL );
+ m_pCollectionCraftPanel = NULL;
+ m_pHalloweenOfferingPanel = NULL;
+ m_pMannCoTradePanel = NULL;
+
+ CancelToolSelection();
+
+ ListenForGameEvent( "gc_connected" );
+}
+
+CBackpackPanel::~CBackpackPanel()
+{
+ if ( m_pPageButtonKVs )
+ {
+ m_pPageButtonKVs->deleteThis();
+ m_pPageButtonKVs = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ if ( !m_pSortByComboBox && UsesRarityControls() )
+ {
+ m_pSortByComboBox = new vgui::ComboBox( this, "SortByComboBox", 5, false );
+ m_pSortByComboBox->AddActionSignalTarget( this );
+ }
+
+ LoadControlSettings( GetResFile() );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pNameFilterTextEntry = FindControl<vgui::TextEntry>( "NameFilterTextEntry" );
+ if ( m_pNameFilterTextEntry )
+ {
+ m_pNameFilterTextEntry->AddActionSignalTarget( this );
+ }
+
+ m_pCancelToolButton = dynamic_cast<CExButton*>( FindChildByName("CancelApplyToolButton") );
+ m_pCraftButton = dynamic_cast<CExButton*>( FindChildByName("CraftButton") );
+ m_pToolIcon = dynamic_cast<vgui::ScalableImagePanel*>( FindChildByName("tool_icon") );
+
+ m_pNextPageButton = dynamic_cast<CExButton*>( FindChildByName("NextPageButton") );
+ m_pPrevPageButton = dynamic_cast<CExButton*>( FindChildByName("PrevPageButton") );
+ m_pShowExplanationsButton = dynamic_cast<CExButton*>( FindChildByName("ShowExplanationsButton") );
+ m_pDragToNextPageButton = dynamic_cast<CExButton*>( FindChildByName("DragToNextPageButton") );
+ m_pDragToPrevPageButton = dynamic_cast<CExButton*>( FindChildByName("DragToPrevPageButton") );
+ m_pCurPageLabel = dynamic_cast<vgui::Label*>( FindChildByName("CurPageLabel") );
+ m_pShowRarityComboBox = dynamic_cast<vgui::ComboBox*>( FindChildByName( "ShowRarityComboBox" ) );
+ if ( m_pShowRarityComboBox )
+ {
+ m_pShowRarityComboBox->AddActionSignalTarget( this );
+
+ m_pShowRarityComboBox->AddItem( "#TF_Backpack_ShowNoBorders", NULL );
+ m_pShowRarityComboBox->AddItem( "#TF_Backpack_ShowQualityBorders", NULL );
+ m_pShowRarityComboBox->AddItem( "#TF_Backpack_ShowMarketableBorders", NULL );
+
+ m_pShowRarityComboBox->ActivateItemByRow( cl_showbackpackrarities.GetInt() );
+ }
+ m_pShowBaseItemsCheckbox = dynamic_cast<vgui::CheckButton*>( FindChildByName( "ShowBaseItemsCheckbox" ) );
+ if ( m_pShowBaseItemsCheckbox )
+ {
+ m_pShowBaseItemsCheckbox->AddActionSignalTarget( this );
+ m_pShowBaseItemsCheckbox->SetSelected( m_bShowBaseItems );
+ }
+
+ m_pMouseDragItemPanel->SetBorder( pScheme->GetBorder("BackpackItemMouseOverBorder") );
+
+ // Setup our combo box
+ if ( m_pSortByComboBox )
+ {
+ m_pSortByComboBox->RemoveAll();
+ vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true );
+ m_pSortByComboBox->SetFont( hFont );
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ for ( int i = 0; i < ARRAYSIZE(g_BackpackSortTypes); i++ )
+ {
+ pKeyValues->SetInt( "sortby", i );
+ m_pSortByComboBox->AddItem( g_BackpackSortTypes[i].szSortDesc, pKeyValues );
+ }
+ pKeyValues->deleteThis();
+ m_pSortByComboBox->ActivateItemByRow( 0 );
+ m_pSortByComboBox->GetMenu()->SetNumberOfVisibleItems( ARRAYSIZE(g_BackpackSortTypes) );
+ }
+
+ // Create page buttons
+ const int nNumMaxPages = GetNumMaxPages();
+ for ( int i=m_Pages.Count(); i<nNumMaxPages; ++i )
+ {
+ EditablePanel *pPage = vgui::SETUP_PANEL( new EditablePanel( this, CFmtStr( "page_%d", i ) ) );
+ m_Pages.AddToTail( pPage );
+ }
+
+ if ( m_pInspectCosmeticPanel )
+ {
+ // Force it to load it's scheme now, because it needs to be done before we set it's visibility below
+ m_pInspectCosmeticPanel->InvalidateLayout( false, true );
+ m_pInspectCosmeticPanel->SetVisible( false );
+ }
+}
+
+void CBackpackPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "pagebuttons_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pPageButtonKVs )
+ {
+ m_pPageButtonKVs->deleteThis();
+ }
+ m_pPageButtonKVs = new KeyValues("pagebuttons_kv");
+ pItemKV->CopySubkeys( m_pPageButtonKVs );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::AddNewItemPanel( int iPanelIndex )
+{
+ BaseClass::AddNewItemPanel( iPanelIndex );
+
+ // Store a position for our new panel
+ m_ItemModelPanelPos.AddToTail();
+ m_ItemModelPanelPos[iPanelIndex].x = m_ItemModelPanelPos[iPanelIndex].y = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemModelPanel *CBackpackPanel::GetItemPanelAtPos( int x, int y )
+{
+ if ( !m_pItemModelPanels.Count() )
+ return NULL;
+
+ int iW = m_pItemModelPanels[0]->GetWide();
+ int iH = m_pItemModelPanels[0]->GetTall();
+ for ( int i = 0; i < m_ItemModelPanelPos.Count(); i++ )
+ {
+ if ( (x < m_ItemModelPanelPos[i].x) || (x > (m_ItemModelPanelPos[i].x + iW)) )
+ continue;
+ if ( (y < m_ItemModelPanelPos[i].y) || (y > (m_ItemModelPanelPos[i].y + iH)) )
+ continue;
+ return m_pItemModelPanels[i];
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBackpackPanel::GetPageButtonIndexAtPos( int x, int y )
+{
+ if ( !m_Pages.Count() )
+ return -1;
+
+ int iW = m_Pages[0]->GetWide();
+ int iH = m_Pages[0]->GetTall();
+ for ( int i = 0; i < m_PageButtonPos.Count(); i++ )
+ {
+ if ( (x < m_PageButtonPos[i].x) || (x > (m_PageButtonPos[i].x + iW)) )
+ continue;
+ if ( (y < m_PageButtonPos[i].y) || (y > (m_PageButtonPos[i].y + iH)) )
+ continue;
+ return m_Pages[i]->IsVisible() ? i : -1;
+ }
+
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Change the text color on the page buttons based on the context of the
+// page they represent.
+//-----------------------------------------------------------------------------
+void CBackpackPanel::SetPageButtonTextColorBasedOnContents()
+{
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+
+ if ( m_Pages.Count() == 0 )
+ return;
+
+ if ( !pScheme )
+ return;
+
+ const Color& colorEmpty = pScheme->GetColor( "TanDarker", Color( 235, 226, 202, 255 ) );
+ const Color& colorPartial = Color( 170, 161, 137, 255 );
+ const Color& colorFull = pScheme->GetColor( "TanLight", Color( 235, 226, 202, 255 ) );
+ const Color& colorSelected = pScheme->GetColor( "TFOrange", Color( 145, 73, 59, 255 ) );
+
+ CUtlVector<int> vecPageCount;
+ CUtlVector<int> vecNewPageCount;
+ vecPageCount.EnsureCount( m_Pages.Count() );
+ vecNewPageCount.EnsureCount( m_Pages.Count() );
+ // Initialize to 0
+ FOR_EACH_VEC( vecPageCount, i )
+ {
+ vecPageCount[i] = 0;
+ vecNewPageCount[i] = 0;
+ }
+
+ CPlayerInventory *pInv = InventoryManager()->GetLocalInventory();
+ Assert( pInv );
+ // Tally up how many items are on each page
+ if ( pInv )
+ {
+ for ( int i = 0 ; i < pInv->GetItemCount() ; ++i )
+ {
+ CEconItemView *pItem = pInv->GetItem( i );
+ const int nSlot = InventoryManager()->GetBackpackPositionFromBackend( pItem->GetInventoryPosition() ) - 1;
+ const int nPage = nSlot / GetNumSlotsPerPage();
+ if ( nPage >= 0 && nPage < m_Pages.Count() )
+ {
+ vecPageCount[ nPage ] = vecPageCount[ nPage ] + 1;
+
+ // Unackknowledged items technically are on the 1st page, so dont count them
+ if ( m_mapSeenItems.Find( pItem->GetItemID() ) == m_mapSeenItems.InvalidIndex()
+ && IsUnacknowledged( pItem->GetInventoryPosition() ) == false && !m_bShowBaseItems && !HasNameFilter() )
+ {
+ vecNewPageCount[ nPage ] = vecNewPageCount[ nPage ] + 1;
+ }
+ }
+ }
+ }
+
+ // Set the color for each page button
+ FOR_EACH_VEC( m_Pages, i )
+ {
+ const int nNewCount = vecNewPageCount[i];
+ const int nCount = vecPageCount[i];
+ CExButton* pButton = dynamic_cast<CExButton*>( m_Pages[i]->FindChildByName( "Button" ) );
+ if ( pButton )
+ {
+ Color setColor = colorEmpty;
+ const Color& bgColor = GetCurrentPage() == i ? colorSelected : pButton->GetButtonDefaultBgColor();
+
+ if ( nCount == GetNumSlotsPerPage() )
+ setColor = colorFull;
+ else if ( nCount > 0 )
+ setColor = colorPartial;
+
+ pButton->SetSelectedColor( setColor, pButton->GetButtonSelectedBgColor() );
+ pButton->SetDefaultColor( setColor, bgColor );
+ pButton->SetArmedColor( setColor, pButton->GetButtonArmedBgColor() );
+ pButton->SetDepressedColor( setColor, pButton->GetButtonDepressedBgColor() );
+ }
+
+ // Show our "NEW!" label if there's any unseen items on that page
+ CExLabel* pNew = dynamic_cast<CExLabel*>( m_Pages[i]->FindChildByName( "New" ) );
+ if ( pNew )
+ {
+ pNew->SetVisible( nNewCount > 0 );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::MarkItemIDDirty( itemid_t itemID )
+{
+ if ( m_vecDirtyItems.Find( itemID ) == m_vecDirtyItems.InvalidIndex() )
+ {
+ m_vecDirtyItems.AddToTail( itemID );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::PositionItemPanel( CItemModelPanel *pPanel, int iIndex )
+{
+ int iCenter = GetWide() * 0.5;
+ int iButtonX = (iIndex % GetNumColumns());
+ int iButtonY = (iIndex / GetNumColumns());
+ int iXPos = (iCenter + m_iItemBackpackOffcenterX) + (iButtonX * m_pItemModelPanels[iIndex]->GetWide()) + (m_iItemBackpackXDelta * iButtonX);
+ int iYPos = m_iItemYPos + (iButtonY * m_pItemModelPanels[iIndex]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY);
+
+ m_pItemModelPanels[iIndex]->SetPos( iXPos, iYPos );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ // Viewing the backpack. Layout all the buttons and hide the class image.
+ m_pItemModelPanels[i]->SetVisible( true );
+ m_pItemModelPanels[i]->SetNoItemText( "#SelectNoItemSlot" );
+
+ PositionItemPanel( m_pItemModelPanels[i], i );
+
+ // Cache off where we put the panel
+ m_pItemModelPanels[i]->GetPos( m_ItemModelPanelPos[i].x, m_ItemModelPanelPos[i].y );
+
+ // Take into account parent's position
+ Panel* pParent = m_pItemModelPanels[i]->GetParent();
+ if( pParent )
+ {
+ int x = 0,y = 0;
+ pParent->GetPos( x, y );
+ m_ItemModelPanelPos[i].x += x;
+ m_ItemModelPanelPos[i].y += y;
+ }
+ }
+
+ // adjust page buttons
+ {
+ m_nNumActivePages = GetNumPages();
+
+ int iCenter = GetWide() * 0.5;
+
+ int iPageBarWidth = 2 * abs( m_iItemBackpackOffcenterX );
+ int iPageButtonWidth = ( iPageBarWidth - ( m_iPageButtonPerRow - 1 ) * m_iPageButtonXDelta ) / m_iPageButtonPerRow;
+ int iPageButtonWidthPlusDelta = iPageButtonWidth + m_iPageButtonXDelta;
+ int iPageButtonHeightPlusDelta = m_iPageButtonHeight + m_iPageButtonYDelta;
+ int iStart = iCenter + m_iItemBackpackOffcenterX;
+
+ m_PageButtonPos.EnsureCount( m_Pages.Count() );
+ for ( int i=0; i<m_Pages.Count(); ++i )
+ {
+ EditablePanel *pPage = m_Pages[i];
+ if ( pPage )
+ {
+ // Apply control settings here
+ if ( m_pPageButtonKVs )
+ pPage->ApplySettings( m_pPageButtonKVs );
+ CExButton* pButton = dynamic_cast<CExButton*>( pPage->FindChildByName( "Button" ) );
+ pPage->InvalidateLayout( true, true );
+ // Make the button have the right command and send it's signals to us
+ if ( pButton )
+ {
+ pButton->SetSelected( false );
+ pButton->SetCommand( CFmtStr( "goto_page_%d", i ) );
+ pButton->AddActionSignalTarget( this );
+ }
+ pPage->SetDialogVariable( "page", i+1 );
+
+ bool bVisible = i < m_nNumActivePages;
+ if ( bVisible )
+ {
+ int iRow = i /m_iPageButtonPerRow;
+ int iColumn = i % m_iPageButtonPerRow;
+ pPage->SetBounds( iStart + iColumn * iPageButtonWidthPlusDelta, m_iPageButtonYPos + iRow * iPageButtonHeightPlusDelta, iPageButtonWidth, m_iPageButtonHeight );
+ pPage->GetPos( m_PageButtonPos[i].x , m_PageButtonPos[i].y );
+ }
+ pPage->SetVisible( bVisible );
+ }
+ }
+
+ // Update colors and the "NEW!" labels
+ SetPageButtonTextColorBasedOnContents();
+ }
+
+ if ( m_pNextPageButton )
+ {
+ m_pNextPageButton->SetVisible( true );
+ }
+ if ( m_pPrevPageButton )
+ {
+ m_pPrevPageButton->SetVisible( true );
+ }
+ if ( m_pCurPageLabel )
+ {
+ m_pCurPageLabel->SetVisible( true );
+ }
+
+ if ( m_pSortByComboBox )
+ {
+ m_pSortByComboBox->SetVisible( !InToolSelectionMode() );
+ }
+ if ( m_pShowRarityComboBox )
+ {
+ m_pShowRarityComboBox->SetVisible( true );
+ }
+
+ if ( m_pNextPageButton )
+ {
+ m_pNextPageButton->SetEnabled( GetNumPages() > 1 );
+ }
+ if ( m_pPrevPageButton )
+ {
+ m_pPrevPageButton->SetEnabled( GetNumPages() > 1 );
+ }
+
+ if ( !m_bDragging )
+ {
+ if ( m_pDragToNextPageButton && m_pDragToPrevPageButton )
+ {
+ m_pDragToNextPageButton->SetVisible( false );
+ m_pDragToPrevPageButton->SetVisible( false );
+ }
+ }
+
+ bool bShowActions = (!m_bItemsOnly && !InToolSelectionMode());
+ if ( m_pCraftButton )
+ {
+ m_pCraftButton->SetVisible( bShowActions );
+ }
+ if ( m_pCancelToolButton )
+ {
+ m_pCancelToolButton->SetVisible( InToolSelectionMode() );
+ }
+
+ if ( m_pShowExplanationsButton )
+ {
+ m_pShowExplanationsButton->SetVisible( !m_bItemsOnly );
+ }
+ if ( m_pShowBaseItemsCheckbox )
+ {
+ m_pShowBaseItemsCheckbox->SetVisible( !m_bItemsOnly );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::FireGameEvent( IGameEvent *event )
+{
+ static CSchemaItemDefHandle pItemDef_BasePaintCan( "Paint Can" );
+ const char *type = event->GetName();
+ if ( Q_strcmp( "gc_connected", type ) == 0 )
+ {
+ if ( !m_bInitializedSeenItems )
+ {
+ CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
+ if ( pInventory )
+ {
+ for ( int i = 0; i < pInventory->GetItemCount(); i++ )
+ {
+ CEconItemView *pItem = pInventory->GetItem(i);
+ m_mapSeenItems.Insert( pItem->GetItemID() );
+ }
+ }
+
+ m_bInitializedSeenItems = true;
+ }
+
+ m_vecPaintCans.Purge();
+ m_vecStrangeParts.Purge();
+ const CEconItemSchema::ToolsItemDefinitionMap_t &toolDefs = GetItemSchema()->GetToolsItemDefinitionMap();
+
+ // Store all of the active paint can item defs
+ FOR_EACH_MAP_FAST( toolDefs, i )
+ {
+ const CEconItemDefinition *pItemDef = toolDefs[i];
+ const IEconTool *pEconTool = pItemDef->GetEconTool();
+ if ( !pEconTool )
+ continue;
+
+ // Paint can list
+ // Ignore the stock paintcan thats only for armory purposes
+ if ( !V_strcmp( pEconTool->GetTypeName(), "paint_can" ) && pItemDef_BasePaintCan != pItemDef )
+ {
+ // Paint Can
+ m_vecPaintCans.AddToTail( pItemDef->GetDefinitionIndex() );
+ }
+ // Strange Parts List
+ else if ( !V_strcmp( pEconTool->GetTypeName(), "strange_part" ) )
+ {
+ m_vecStrangeParts.AddToTail( pItemDef->GetDefinitionIndex() );
+ }
+ }
+ }
+
+ BaseClass::FireGameEvent( event );
+}
+
+void CBackpackPanel::CheckForQuickOpenKey()
+{
+ if ( !m_hQuickOpenCrate )
+ return;
+
+ // We only want to continue if it's the transaction we're listening for
+ if ( EconUI()->GetStorePanel()->GetMostRecentSuccessfulTransactionID() != m_nQuickOpenTxn )
+ {
+ return;
+ }
+
+ CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
+ if ( pInventory )
+ {
+ for ( int i = 0; i < pInventory->GetItemCount(); i++ )
+ {
+ CEconItemView *pInvItem = pInventory->GetItem( i );
+
+ uint32 iPosition = pInvItem->GetInventoryPosition();
+ if ( IsUnacknowledged( iPosition ) == false )
+ continue;
+
+ if ( InventoryManager()->GetBackpackPositionFromBackend( iPosition ) != 0 )
+ continue;
+
+ // Now make sure we haven't got a clientside saved ack for this item.
+ if ( InventoryManager()->HasBeenAckedByClient( pInvItem ) )
+ continue;
+
+ // If item is not a drop we want to show the notification otherwise they'll get the notification on death
+ int iFoundMethod = GetUnacknowledgedReason( iPosition );
+ if ( iFoundMethod != UNACK_ITEM_PURCHASED )
+ continue;
+
+ if ( !pInvItem->GetStaticData()->IsTool() )
+ continue;
+
+ if( !CEconSharedToolSupport::ToolCanApplyTo( pInvItem, m_hQuickOpenCrate ) )
+ continue;
+
+ if ( !pInvItem->GetStaticData()->GetEconTool() )
+ continue;
+
+ if ( !Q_strcmp( pInvItem->GetStaticData()->GetEconTool()->GetTypeName(), "decoder_ring" ) == 0 )
+ continue;
+
+ ApplyTool( this, pInvItem, m_hQuickOpenCrate );
+ CloseStoreStatusDialog();
+
+ m_hQuickOpenCrate = NULL;
+ m_nQuickOpenTxn = 0;
+ return;
+ }
+ }
+
+ m_hQuickOpenCrate = NULL;
+ m_nQuickOpenTxn = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: When the store get's a new transaction ID, it comes here as well
+//-----------------------------------------------------------------------------
+void CBackpackPanel::SetCurrentTransactionID( uint64 nTxnID )
+{
+ // If we've got a quick open crate st, and no quick open transaction ID,
+ // then we want to capture the incoming transaction ID so that we can
+ // compare future incoming successful transactions to see if they have
+ // the key we're expecting
+ if ( m_hQuickOpenCrate && m_nQuickOpenTxn == 0 )
+ {
+ m_nQuickOpenTxn = nTxnID;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnShowPanel( bool bVisible, bool bReturningFromArmory )
+{
+ if ( bVisible )
+ {
+ m_pMouseDragItemPanel->SetVisible( false );
+
+ if( m_pDynamicRecipePanel )
+ {
+ m_pDynamicRecipePanel->SetVisible( false );
+ }
+
+ if ( m_pItemSlotPanel )
+ {
+ m_pItemSlotPanel->SetVisible( false );
+ }
+
+ m_bShowBaseItems = false;
+ if ( m_pShowBaseItemsCheckbox )
+ {
+ m_pShowBaseItemsCheckbox->SetSelected( m_bShowBaseItems );
+ }
+
+ if ( !bReturningFromArmory )
+ {
+ SetCurrentPage( 0 );
+ CancelToolSelection();
+ }
+
+ m_nNumActivePages = 0;
+
+#ifdef STAGING_ONLY
+ // Reset pinned-state of the card
+ m_pMouseOverCardPanel->PinCard( false );
+#endif
+ }
+ else
+ {
+ if ( m_bDragging )
+ {
+ StopDrag( false );
+ }
+ }
+
+ if ( m_pInspectPanel )
+ {
+ m_pInspectPanel->SetVisible( false );
+ }
+
+ if ( m_pInspectCosmeticPanel )
+ {
+ m_pInspectCosmeticPanel->SetVisible( false );
+ }
+
+ if ( m_pCollectionCraftPanel )
+ {
+ m_pCollectionCraftPanel->SetVisible( false );
+ }
+
+ if ( m_pHalloweenOfferingPanel )
+ {
+ m_pHalloweenOfferingPanel->SetVisible( false );
+ }
+
+ if ( m_pMannCoTradePanel )
+ {
+ m_pMannCoTradePanel->SetVisible( false );
+ }
+
+ if ( m_pStrangeToolPanel )
+ {
+ m_pStrangeToolPanel->MarkForDeletion();
+ m_pStrangeToolPanel = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::PostShowPanel( bool bVisible )
+{
+ if ( bVisible )
+ {
+ DeSelectAllBackpackItemPanels();
+
+ RequestFocus();
+
+ // Clear out text field
+ ClearNameFilter( true );
+ }
+
+ // If this is the first time we've opened the loadout, start the loadout explanations
+ ConVar *pConVar = GetExplanationConVar();
+ if ( bVisible && pConVar && !pConVar->GetBool() && ShouldShowExplanations() )
+ {
+ m_flStartExplanationsAt = Plat_FloatTime() + 0.5;
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBackpackPanel::GetNumPages( void )
+{
+ int iMaxItems = InventoryManager()->GetLocalInventory()->GetMaxItemCount();
+ return (int)(ceil((float)iMaxItems / (float)BACKPACK_SLOTS_PER_PAGE));
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::AssignItemToPanel( CItemModelPanel *pPanel, int iIndex )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ static int iItemBackpackPos = 0;
+ if ( iIndex == 0 )
+ {
+ iItemBackpackPos = 0;
+ }
+ int iPanelBackpackPos = GetBackpackPosForPanelIndex(iIndex);
+
+ static int iLastMapItem = -1;
+
+ pPanel->SetShowQuantity( true );
+
+ const wchar_t* wszFilter = GetNameFilter();
+ bool bInToolSelection = InToolSelectionMode() && m_ToolSelectionItem.IsValid();
+
+ CEconItemView *pItemData = NULL;
+ CEconItemView tempItem;
+ if ( m_bShowBaseItems )
+ {
+ const CEconItemDefinition* pItemDef = NULL;
+
+ const CEconItemSchema::BaseItemDefinitionMap_t& mapItems = GetItemSchema()->GetBaseItemDefinitionMap();
+ int iStart = iIndex == 0 ? mapItems.FirstInorder() : mapItems.NextInorder( iLastMapItem );
+ for ( int it = iStart; it != mapItems.InvalidIndex(); it = mapItems.NextInorder( it ) )
+ {
+ iLastMapItem = it;
+
+ if ( mapItems[it]->IsBaseItem() && !mapItems[it]->IsHidden() )
+ {
+ // Instead of linking to this base item definition, link to the definition of what it will become
+ // when we customize it.
+ CFmtStr fmtStrCustomizedDefName( "Upgradeable %s", mapItems[it]->GetDefinitionName() );
+ pItemDef = GetItemSchema()->GetItemDefinitionByName( fmtStrCustomizedDefName.Access() );
+
+ // If we don't have an upgradeable version, we assume that we can't upgrade it and link to the base
+ // definition instead. We expect this to only happen if the item won't actually be useable for whatever
+ // purpose (name tags, etc.). We sanity-check this on the GC.
+ if ( !pItemDef )
+ {
+ pItemDef = mapItems[it];
+ }
+
+ tempItem.Init( pItemDef->GetDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+
+ // skip this item if the tool cannot be applied to it
+ if ( bInToolSelection && !CEconSharedToolSupport::ToolCanApplyTo( &m_ToolSelectionItem, &tempItem ) )
+ {
+ pItemDef = NULL;
+ continue;
+ }
+
+ if ( DoesItemPassSearchFilter( tempItem.GetDescription(), wszFilter ) )
+ {
+ break;
+ }
+ }
+
+ pItemDef = NULL;
+ }
+
+ if ( pItemDef )
+ {
+ pItemData = &tempItem;
+
+ ++iItemBackpackPos;
+ }
+ }
+ else if ( HasNameFilter() )
+ {
+ int iStart = iIndex == 0 ? m_mapFilteringItems.FirstInorder() : m_mapFilteringItems.NextInorder( iLastMapItem );
+ for ( int it = iStart; it != m_mapFilteringItems.InvalidIndex(); it = m_mapFilteringItems.NextInorder( it ) )
+ {
+ iLastMapItem = it;
+
+ CEconItemView *pItem = m_mapFilteringItems[it];
+
+ // skip this item if the tool cannot be applied to it
+ if ( bInToolSelection && !CEconSharedToolSupport::ToolCanApplyTo( &m_ToolSelectionItem, pItem ) )
+ {
+ continue;
+ }
+
+ if ( !DoesItemPassSearchFilter( pItem->GetDescription(), wszFilter ) )
+ {
+ continue;
+ }
+
+ if ( ++iItemBackpackPos != iPanelBackpackPos )
+ {
+ continue;
+ }
+
+ pItemData = pItem;
+ break;
+ }
+ }
+ else if ( bInToolSelection )
+ {
+ CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
+ if ( pInventory )
+ {
+ // Backpack positions start from 1
+ Assert( iPanelBackpackPos > 0 && iPanelBackpackPos <= pInventory->GetMaxItemCount() );
+ int iStart = iIndex == 0 ? 0 : iLastMapItem + 1;
+ for ( int i = iStart; i < pInventory->GetItemCount(); i++ )
+ {
+ iLastMapItem = i;
+
+ CEconItemView *pItem = pInventory->GetItem(i);
+
+ if ( m_ToolSelectionItem.GetStaticData()->IsTool() )
+ {
+ if ( !CEconSharedToolSupport::ToolCanApplyTo( &m_ToolSelectionItem, pItem ) )
+ {
+ continue;
+ }
+ }
+ else
+ {
+ if ( !pItem->GetStaticData()->IsTool() )
+ {
+ continue;
+ }
+
+ if ( !CEconSharedToolSupport::ToolCanApplyTo( pItem, &m_ToolSelectionItem ) )
+ {
+ continue;
+ }
+
+ if ( ( m_ToolSelectionItem.GetStaticData()->GetCapabilities() & ITEM_CAP_DECODABLE ) && pItem->GetStaticData()->GetEconTool() && ( Q_strcmp( pItem->GetStaticData()->GetEconTool()->GetTypeName(), "decoder_ring" ) != 0 ) )
+ {
+ continue;
+ }
+ }
+
+ if ( ++iItemBackpackPos != iPanelBackpackPos )
+ {
+ continue;
+ }
+
+ pItemData = pItem;
+ break;
+ }
+ }
+ }
+ else
+ {
+ pItemData = InventoryManager()->GetItemByBackpackPosition( iPanelBackpackPos );
+ iItemBackpackPos = iPanelBackpackPos;
+
+ if ( pItemData == NULL && pPanel->GetItem() == NULL )
+ {
+ return;
+ }
+
+ int nDirtyIndex = pItemData ? m_vecDirtyItems.Find( pItemData->GetItemID() ) : m_vecDirtyItems.InvalidIndex();
+
+ if ( pItemData // Want to put in an item
+ && pPanel->GetItem() // Panel has an item
+ && pItemData->GetItemID() == pPanel->GetItem()->GetItemID() // That panel has the same item that we want to put in
+ && nDirtyIndex == m_vecDirtyItems.InvalidIndex() ) // And that item is not dirtied.
+ {
+ // We dont do anything
+ return;
+ }
+
+ if ( nDirtyIndex != m_vecDirtyItems.InvalidIndex() )
+ {
+ m_vecDirtyItems.Remove( nDirtyIndex );
+ }
+ }
+
+ if ( iItemBackpackPos != iPanelBackpackPos )
+ {
+ pItemData = NULL;
+ }
+
+ pPanel->SetItem( pItemData );
+
+ bool bSeen = true;
+ // Have we not seen this item before?
+ if ( !m_bShowBaseItems && pItemData && m_mapSeenItems.Find( pItemData->GetItemID() ) == m_mapSeenItems.InvalidIndex() )
+ {
+ bSeen = false;
+ }
+
+ // Show our "NEW!" label if this item hasnt been seen
+ CExLabel *pNewPanel = dynamic_cast< CExLabel* >( pPanel->FindChildByName( "New" ) );
+ if ( pNewPanel )
+ {
+ pNewPanel->SetVisible( !bSeen );
+ }
+
+ pPanel->DirtyDescription();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::ClearNameFilter( bool bUpdateModelPanels )
+{
+ if ( m_wNameFilter.Count() == 0 )
+ return;
+
+ m_wNameFilter.RemoveAll();
+ if( m_pNameFilterTextEntry )
+ {
+ m_pNameFilterTextEntry->SetText( "" );
+ }
+
+ if ( bUpdateModelPanels )
+ {
+ m_flFilterItemTime = gpGlobals->curtime + 0.1f;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::UpdateFilteringItems()
+{
+ m_mapFilteringItems.RemoveAll();
+
+ if ( !HasNameFilter() )
+ return;
+
+ CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
+ if ( !pInventory )
+ return;
+
+ for ( int i = 0; i < pInventory->GetItemCount(); i++ )
+ {
+ CEconItemView *pItem = pInventory->GetItem(i);
+
+ if ( pItem->GetItemDefinition()->IsHidden() )
+ continue;
+
+ int iBackpackPosition = InventoryManager()->GetBackpackPositionFromBackend( pItem->GetInventoryPosition() );
+ m_mapFilteringItems.Insert( iBackpackPosition, pItem );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::UpdateModelPanels( void )
+{
+ tmZone( TELEMETRY_LEVEL1, TMZF_NONE, "%s", __FUNCTION__ );
+
+ UpdateFilteringItems();
+
+ // We're showing the backpack. Show all the items in our inventory
+ FOR_EACH_VEC( m_pItemModelPanels, i )
+ {
+ m_pItemModelPanels[i]->SetShowEquipped( true );
+ m_pItemModelPanels[i]->SetShowGreyedOutTooltip( true );
+ AssignItemToPanel( m_pItemModelPanels[i], i );
+
+ if ( !m_pItemModelPanels[i]->HasItem() && m_pItemModelPanels[i]->IsSelected() )
+ {
+ m_pItemModelPanels[i]->SetSelected( false );
+ }
+
+ SetBorderForItem( m_pItemModelPanels[i], false );
+ }
+
+ // Clean out. We just did all the heavy lifting.
+ m_vecDirtyItems.Purge();
+
+ if ( InToolSelectionMode() && m_ToolSelectionItem.IsValid() )
+ {
+ wchar_t wTemp[256];
+ g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find( "BackpackApplyTool" ), 1, m_ToolSelectionItem.GetItemName() );
+ SetDialogVariable( "loadoutclass", wTemp );
+ }
+ else
+ {
+ SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( "BackpackTitle" ) );
+ }
+
+ char szTmp[16];
+ V_sprintf_safe( szTmp, "%d/%d", GetCurrentPage()+1, GetNumPages() );
+ SetDialogVariable( "backpackpage", szTmp );
+
+ // Now layout again to position our item buttons
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Mark visited item model panels as seen
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnItemPanelEntered( vgui::Panel *panel )
+{
+ if ( m_pContextMenu && m_pContextMenu->IsVisible() )
+ return;
+
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+ if ( pItemPanel )
+ {
+ // Hide the "NEW!" label
+ CExLabel *pNewPanel = dynamic_cast< CExLabel* >( pItemPanel->FindChildByName( "New" ) );
+ if ( pNewPanel )
+ {
+ pNewPanel->SetVisible( false );
+ }
+
+ // Mark this item as "seen"
+ CEconItemView *pItem = pItemPanel->GetItem();
+ if ( pItem )
+ {
+ if ( m_mapSeenItems.Find( pItem->GetItemID() ) == m_mapSeenItems.InvalidIndex() )
+ {
+ m_mapSeenItems.Insert( pItem->GetItemID() );
+ SetPageButtonTextColorBasedOnContents();
+ }
+ }
+ }
+
+ BaseClass::OnItemPanelEntered( panel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnItemPanelMousePressed( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() && pItemPanel->IsGreyedOut() == false && AllowDragging( pItemPanel ) )
+ {
+ m_flMouseDownTime = gpGlobals->curtime;
+ m_iMouseDownX = m_iMouseDownY = 0;
+ m_pItemDraggedFromPanel = pItemPanel;
+ m_iDraggedFromPage = GetCurrentPage();
+ m_bDragging = false;
+ m_bMouseDownOnItemPanel = true;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle the escape key since it doesn't show up in OnKeyCodePressed
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnKeyCodeTyped(vgui::KeyCode code)
+{
+ if ( code == KEY_ESCAPE && InToolSelectionMode() )
+ {
+ CancelToolSelection();
+ }
+ else if ( code == KEY_ENTER )
+ {
+ // Do nothing. This gets hit frequently when people type in the filter
+ // text entry and then hit 'Enter', expecting it to execute the filter.
+ // We automatically apply it, so let's just eat 'Enter', which was causing
+ // us to activate some button on the main menu.
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles key press events in the backpack
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnKeyCodePressed( vgui::KeyCode code )
+{
+ // Ignore key events while the confirm delete dialog is up
+ if( m_pConfirmDeleteDialog )
+ return;
+
+ // let our parent class handle all the arrow key/dpad stuff
+ if( HandleItemSelectionKeyPressed( code ) )
+ {
+ return;
+ }
+
+ // Handle close here, CBasePanel parent doesn't support "DialogClosing" command
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if ( (nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_B) && InToolSelectionMode() )
+ {
+ CancelToolSelection();
+ }
+ else if( code == KEY_PAGEDOWN )
+ {
+ OnCommand( "nextpage" );
+ }
+ else if( code == KEY_PAGEUP )
+ {
+ OnCommand( "prevpage" );
+ }
+ else if ( ( nButtonCode == KEY_XBUTTON_A || code == KEY_ENTER || nButtonCode == STEAMCONTROLLER_A ) )
+ {
+ if( InToolSelectionMode() )
+ {
+ HandleToolItemSelection( GetFirstSelectedItem() );
+ }
+ else
+ {
+ OpenContextMenu();
+ }
+ }
+ else if ( nButtonCode == KEY_XBUTTON_X || nButtonCode == STEAMCONTROLLER_X )
+ {
+ if( !InToolSelectionMode() )
+ {
+ OnCommand( "deleteitem" );
+ }
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles key press events in the backpack
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnKeyCodeReleased( vgui::KeyCode code )
+{
+ if( ! HandleItemSelectionKeyReleased( code ) )
+ BaseClass::OnKeyCodeReleased( code );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnMouseCaptureLost( void )
+{
+ if ( m_bDragging )
+ {
+ StopDrag( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnMouseReleased(vgui::MouseCode code)
+{
+ if ( code == MOUSE_LEFT )
+ {
+ if ( m_bDragging )
+ {
+ // When we're dragging, we have mouse capture, so the item panels aren't getting mouse input.
+ // We need to find out what item panel we're over, and let it know.
+ if ( m_pPrevDragOverItemPanel )
+ {
+ OnItemPanelMouseReleased( m_pPrevDragOverItemPanel );
+ }
+ else
+ {
+ StopDrag( false );
+ }
+ }
+ }
+
+ BaseClass::OnMouseReleased( code );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnConfirmDelete( KeyValues *data )
+{
+ // Delete all the selected item
+ if ( data )
+ {
+ int iConfirmed = data->GetInt( "confirmed", 0 );
+ if ( iConfirmed )
+ {
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i]->IsSelected() && m_pItemModelPanels[i]->HasItem() )
+ {
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_DELETED, m_pItemModelPanels[i]->GetItem() );
+ InventoryManager()->DropItem( m_pItemModelPanels[i]->GetItem()->GetItemID() );
+ }
+ }
+ DeSelectAllBackpackItemPanels();
+ }
+ }
+
+ m_pConfirmDeleteDialog = NULL;
+
+ // If we're embedded in the discard item panel, it needs to know we made room. Send a message to our parent that it can catch.
+ PostMessage( GetParent(), new KeyValues("ConfirmDlgResult", "confirmed", 2 ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnItemPanelMouseReleased( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ if ( InToolSelectionMode() )
+ {
+ // They're selecting the item they'd like to apply a tool to
+ HandleToolItemSelection( pItemPanel->GetItem() );
+ }
+ else if ( !m_bDragging )
+ {
+ // If they're not holding down ctrl, deselect all existing selections
+ if ( !vgui::input()->IsKeyDown(KEY_LCONTROL) && !vgui::input()->IsKeyDown(KEY_RCONTROL) )
+ {
+ DeSelectAllBackpackItemPanels();
+ }
+
+ // Quick clicks just select the item
+ ToggleSelectBackpackItemPanel( pItemPanel );
+
+ if ( pItemPanel->IsSelected() )
+ {
+ OpenContextMenu();
+ }
+ }
+ else
+ {
+ int iPanelIndex = GetBackpackPositionForPanel( pItemPanel );
+ if ( !CanDragTo(pItemPanel, iPanelIndex) )
+ {
+ StopDrag(false);
+ }
+ else
+ {
+ StopDrag( true );
+ if ( (pItemPanel != m_pItemDraggedFromPanel || m_iDraggedFromPage != GetCurrentPage() ) && m_pMouseDragItemPanel->HasItem() )
+ {
+ HandleDragTo( pItemPanel, iPanelIndex );
+ }
+ else if ( m_iDraggedFromPage == GetCurrentPage() )
+ {
+ m_pItemDraggedFromPanel->SetItem( m_pMouseDragItemPanel->GetItem() );
+ }
+ }
+ }
+
+ m_pItemDraggedFromPanel = NULL;
+ }
+}
+
+
+bool GetDecodedByItemDefIndex( const CEconItemView *pItem, uint32 *pDecodedBy = NULL )
+{
+ static CSchemaAttributeDefHandle pAttrDef_DecodedBy( "decoded by itemdefindex" );
+
+ if ( pDecodedBy )
+ {
+ return pItem->FindAttribute( pAttrDef_DecodedBy, pDecodedBy );
+ }
+ else
+ {
+ return pItem->FindAttribute( pAttrDef_DecodedBy );
+ }
+}
+
+CEconItemView* GetFirstCompatibleKeyForCrate( const CEconItemView *pItem )
+{
+ // Check if we have any decoder rings that can be applied onto this
+ CPlayerInventory *pInv = InventoryManager()->GetLocalInventory();
+ Assert( pInv );
+ if ( pInv )
+ {
+ for ( int i = 0; i < pInv->GetItemCount(); ++i )
+ {
+ CEconItemView *pInvItem = pInv->GetItem( i );
+
+ if ( pInvItem->GetQuality() == AE_SELFMADE )
+ continue;
+
+ if ( pInvItem->GetStaticData()->IsTool() && CEconSharedToolSupport::ToolCanApplyTo( pInvItem, pItem ) && pInvItem->GetStaticData()->GetEconTool() && ( Q_strcmp( pInvItem->GetStaticData()->GetEconTool()->GetTypeName(), "decoder_ring" ) == 0 ) )
+ {
+ return pInvItem;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+bool CanInventoryItemsApplyTo( const CEconItemView *pItem )
+{
+ // Check if we have any tools that can be applied onto this
+ CPlayerInventory *pInv = InventoryManager()->GetLocalInventory();
+ Assert( pInv );
+ if ( pInv )
+ {
+ for ( int i = 0 ; i < pInv->GetItemCount() ; ++i )
+ {
+ CEconItemView *pInvItem = pInv->GetItem( i );
+ if ( pInvItem->GetStaticData()->IsTool() && CEconSharedToolSupport::ToolCanApplyTo( pInvItem, pItem ) )
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+//-----------------------------------------------------------------------------
+bool CreateMarketPriceString( item_definition_index_t iDefIndex, wchar_t *pszString, int iBufferSize )
+{
+ // Get Market Price
+ steam_market_gc_identifier_t ident;
+ ident.m_unDefIndex = iDefIndex;
+ ident.m_unQuality = AE_UNIQUE; // Get this from default item def?
+
+ const client_market_data_t *pClientMarketData = GetClientMarketData( ident );
+ if ( !pClientMarketData )
+ return false;
+
+ const ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+
+ // Set that price into the button
+ wchar_t pszCurrencyString[kLocalizedPriceSizeInChararacters];
+ MakeMoneyString( pszCurrencyString, ARRAYSIZE( pszCurrencyString ), pClientMarketData->m_unLowestPrice, eCurrency );
+
+ wchar_t pszConstructed[kLocalizedPriceSizeInChararacters];
+ g_pVGuiLocalize->ConstructString_safe( pszConstructed, g_pVGuiLocalize->Find( "#TF_MarketPrice" ), 1, pszCurrencyString );
+
+ // copy result;
+ V_wcsncpy( pszString, pszConstructed, iBufferSize );
+ return true;
+}
+//-----------------------------------------------------------------------------
+bool CreateStorePriceString( item_definition_index_t iDefIndex, wchar_t *pszString, int iBufferSize )
+{
+ // Get Market Price
+ steam_market_gc_identifier_t ident;
+ ident.m_unDefIndex = iDefIndex;
+ ident.m_unQuality = AE_UNIQUE; // Get this from default item def?
+
+ // Get the price of the item
+ const econ_store_entry_t *pEntry = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( iDefIndex );
+ if ( !pEntry )
+ return false;
+
+ const ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+
+ // Set that price into the button
+ wchar_t pszCurrencyString[kLocalizedPriceSizeInChararacters];
+ MakeMoneyString( pszCurrencyString, ARRAYSIZE( pszCurrencyString ), pEntry->GetCurrentPrice( eCurrency ), eCurrency );
+
+ wchar_t pszConstructed[kLocalizedPriceSizeInChararacters];
+ g_pVGuiLocalize->ConstructString_safe( pszConstructed, g_pVGuiLocalize->Find( "#TF_StorePrice" ), 1, pszCurrencyString );
+
+ // copy result;
+ V_wcsncpy( pszString, pszConstructed, iBufferSize );
+ return true;
+}
+//-----------------------------------------------------------------------------
+void CBackpackPanel::AddCommerceSubmenus( Menu *pSubMenu, item_definition_index_t iItemDef, const char* pszActionFmt )
+{
+ wchar_t wPriceListing[256];
+ // Store
+ if ( CreateStorePriceString( iItemDef, wPriceListing, sizeof( wPriceListing ) ) )
+ {
+ int nIndex = pSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", CFmtStr( "%s%s%d", "store_", pszActionFmt, iItemDef ) ), this );
+ vgui::MenuItem *pMenuItem = pSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( wPriceListing );
+ pMenuItem->InvalidateLayout( true, false );
+ }
+
+ // Market
+ if ( CreateMarketPriceString( iItemDef, wPriceListing, sizeof( wPriceListing ) ) )
+ {
+ int nIndex = pSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", CFmtStr( "%s%s%d", "market_", pszActionFmt, iItemDef ) ), this );
+ vgui::MenuItem *pMenuItem = pSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( wPriceListing );
+ pMenuItem->InvalidateLayout( true, false );
+ }
+ else
+ {
+ int nIndex = pSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", CFmtStr( "%s%s%d", "market_", pszActionFmt, iItemDef ) ), this );
+ vgui::MenuItem *pMenuItem = pSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( g_pVGuiLocalize->Find( "#TF_MarketUnavailable" ) );
+ pMenuItem->InvalidateLayout( true, false );
+ }
+
+}
+//-----------------------------------------------------------------------------
+void CBackpackPanel::AddPaintToContextMenu( Menu *pPaintSubMenu, item_definition_index_t iPaintDef, bool bAddCommerce )
+{
+ GameItemDefinition_t * pPaintCanDef = dynamic_cast<GameItemDefinition_t*>( GEconItemSchema().GetItemDefinition( iPaintDef ) );
+ if ( !pPaintCanDef )
+ return;
+
+ wchar_t wBuff[256];
+ char cBuff[256];
+ V_swprintf_safe( wBuff, L" %ls", g_pVGuiLocalize->Find( pPaintCanDef->GetItemBaseName() ) );
+
+ char szItemName[256];
+ g_pVGuiLocalize->ConvertUnicodeToANSI( g_pVGuiLocalize->Find( pPaintCanDef->GetItemBaseName() ), szItemName, sizeof( szItemName ) );
+ V_sprintf_safe( cBuff, " %s", szItemName );
+
+ uint32 unPaintRGB0 = 0;
+ uint32 unPaintRGB1 = 0;
+
+ static CSchemaAttributeDefHandle pAttrDef_PaintRGB( "set item tint RGB" );
+ static CSchemaAttributeDefHandle pAttrDef_PaintRGB2( "set item tint RGB 2" );
+
+ float fRGB = 0.0f;
+
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pPaintCanDef, pAttrDef_PaintRGB, &fRGB ) && fRGB != 0.0f )
+ {
+ unPaintRGB0 = fRGB;
+
+ // We may or may not have a secondary paint color as well. If we don't, we just use the primary
+ // paint color to fill both slots.
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pPaintCanDef, pAttrDef_PaintRGB2, &fRGB ) )
+ {
+ unPaintRGB1 = fRGB;
+ }
+ else
+ {
+ unPaintRGB1 = unPaintRGB0;
+ }
+ }
+
+ if ( !bAddCommerce )
+ {
+ int nIndex = pPaintSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", CFmtStr( "paint%d", iPaintDef ) ), this );
+ vgui::MenuItem *pMenuItem = pPaintSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( wBuff );
+ pMenuItem->InvalidateLayout( true, false );
+
+ CItemMaterialCustomizationIconPanel *pCustomPanel = new CItemMaterialCustomizationIconPanel( pMenuItem, "paint" );
+ pCustomPanel->SetZPos( -100 );
+ pCustomPanel->SetTall( 30 );
+ pCustomPanel->SetWide( 30 );
+ pCustomPanel->m_colPaintColors.AddToTail( Color( clamp( ( unPaintRGB0 & 0xFF0000 ) >> 16, 0, 255 ), clamp( ( unPaintRGB0 & 0xFF00 ) >> 8, 0, 255 ), clamp( ( unPaintRGB0 & 0xFF ), 0, 255 ), 255 ) );
+ pCustomPanel->m_colPaintColors.AddToTail( Color( clamp( ( unPaintRGB1 & 0xFF0000 ) >> 16, 0, 255 ), clamp( ( unPaintRGB1 & 0xFF00 ) >> 8, 0, 255 ), clamp( ( unPaintRGB1 & 0xFF ), 0, 255 ), 255 ) );
+ }
+ else
+ {
+ //
+ const char *pszContextMenuBorder = "NotificationDefault";
+ const char *pszContextMenuFont = "HudFontMediumSecondary";
+
+ Menu *pSubMenu = new Menu( this, "PaintSubMenu" );
+ pSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+ int iPos = pPaintSubMenu->AddCascadingMenuItem( cBuff, this, pSubMenu );
+
+ CItemMaterialCustomizationIconPanel *pCustomPanel = new CItemMaterialCustomizationIconPanel( pPaintSubMenu, "paint" );
+ pCustomPanel->SetZPos( 100 );
+ pCustomPanel->SetPos( 0, iPos * pPaintSubMenu->GetMenuItemHeight() );
+ pCustomPanel->SetTall( 30 );
+ pCustomPanel->SetWide( 30 );
+ pCustomPanel->m_colPaintColors.AddToTail( Color( clamp( ( unPaintRGB0 & 0xFF0000 ) >> 16, 0, 255 ), clamp( ( unPaintRGB0 & 0xFF00 ) >> 8, 0, 255 ), clamp( ( unPaintRGB0 & 0xFF ), 0, 255 ), 255 ) );
+ pCustomPanel->m_colPaintColors.AddToTail( Color( clamp( ( unPaintRGB1 & 0xFF0000 ) >> 16, 0, 255 ), clamp( ( unPaintRGB1 & 0xFF00 ) >> 8, 0, 255 ), clamp( ( unPaintRGB1 & 0xFF ), 0, 255 ), 255 ) );
+
+ AddCommerceSubmenus( pSubMenu, iPaintDef, "paint" );
+ }
+}
+//
+// Add commerce context options for an item. Adds 'Store' and 'Market' options if appropriate (and Pricing) other wise just click to use
+//
+void CBackpackPanel::AddCommerceToContextMenu( Menu *pMenu, const char* pszActionFmt, item_definition_index_t iItemDefIndex, bool bAddMarket, bool bAddStore )
+{
+ GameItemDefinition_t * pItemDef = dynamic_cast<GameItemDefinition_t*>( GEconItemSchema().GetItemDefinition( iItemDefIndex ) );
+ if ( !pItemDef )
+ return;
+
+ //
+ if ( !bAddMarket && !bAddStore )
+ {
+ int nIndex = pMenu->AddMenuItem( "", new KeyValues( "Command", "command", CFmtStr( "%s%d", pszActionFmt, iItemDefIndex ) ), this );
+ vgui::MenuItem *pMenuItem = pMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( g_pVGuiLocalize->Find( pItemDef->GetItemBaseName() ) );
+ pMenuItem->InvalidateLayout( true, false );
+ }
+ else
+ {
+ //
+ const char *pszContextMenuBorder = "NotificationDefault";
+ const char *pszContextMenuFont = "HudFontMediumSecondary";
+
+ Menu *pSubMenu = new Menu( this, "CommerceSubMenu" );
+ pSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+ pMenu->AddCascadingMenuItem( pItemDef->GetItemBaseName(), this, pSubMenu );
+
+ AddCommerceSubmenus( pSubMenu, iItemDefIndex, pszActionFmt );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Opens a context menu with actions relevant for the passed in item
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OpenContextMenu()
+{
+ CUtlVector<CEconItemView*> vecSelectedItems;
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i]->IsSelected() && m_pItemModelPanels[i]->GetItem() )
+ {
+ vecSelectedItems.AddToTail( m_pItemModelPanels[i]->GetItem() );
+ }
+ }
+
+ if ( m_pContextMenu )
+ delete m_pContextMenu;
+
+ m_pContextMenu = new Menu( this, "ContextMenu" );
+ MenuBuilder contextMenuBuilder( m_pContextMenu, this );
+ const char *pszContextMenuBorder = "NotificationDefault";
+ const char *pszContextMenuFont = "HudFontMediumSecondary";
+ m_pContextMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ m_pContextMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+
+ if ( vecSelectedItems.Count() == 1 )
+ {
+ const CEconItemView *pItem = vecSelectedItems.Head();
+ const CTFItemDefinition *pItemDef = pItem->GetStaticData();
+ static CSchemaItemDefHandle DuckBadgeItemDef( "Duck Badge" );
+ static CSchemaItemDefHandle StrangeCountTransferItemDef( "Strange Count Transfer Tool" );
+
+ // Tools of any kind can't be used if they are in escrow.
+ static CSchemaAttributeDefHandle pAttrib_ToolEscrowUntil( "tool escrow until date" );
+ uint32 unEscrowTime;
+ const bool bToolIsInEscrow = pItem->FindAttribute( pAttrib_ToolEscrowUntil, &unEscrowTime )
+ && unEscrowTime > CRTime::RTime32TimeCur();
+
+ const IEconTool *pEconTool = pItem->GetItemDefinition()->GetEconTool();
+
+ const bool bIsTool = pItem->GetStaticData()->IsTool() && (pEconTool != NULL);
+ const bool bIsGCConsumable = ( ( pItem->GetStaticData()->GetCapabilities() & ITEM_CAP_USABLE_GC ) != 0 );
+ bool bSkipAddTrade = false; // Hack: We should really ask the tool if the command supplants trade.
+
+ // Tool usage goes first. The cursor starts on this element, so double-clicks will work like how they used to.
+ // Strange Count Transfer
+ if ( StrangeCountTransferItemDef == pItem->GetItemDefinition() )
+ {
+ contextMenuBuilder.AddMenuItem( "#ApplyOnItem", new KeyValues( "Context_OpenStrangeCountTransfer" ), "primaryaction" );
+ }
+ else if ( pItem->GetStaticData()->IsTool() && pEconTool == NULL )
+ {
+ // do nothing. not a real tool (basic balloons with color that we don't want to 'remove' the paint)
+ }
+ else if ( (bIsTool || bIsGCConsumable) && !bToolIsInEscrow && pEconTool->CanBeUsedNow( pItem ) )
+ {
+ Assert( pEconTool );
+
+ const int nTokens = pEconTool->GetUseCommandCount( pItem );
+ for ( int i = 0; i < nTokens; ++i )
+ {
+ const char *pszToolUsageString = pEconTool->GetUseCommandLocalizationToken( pItem, i );
+
+ // If we didn't have a custom usage string, fall back to a sane default based on whether or
+ // not we're a consumable or not.
+ if ( !pszToolUsageString )
+ {
+ pszToolUsageString = bIsGCConsumable ? "#ConsumeItem" : "#ApplyOnItem";
+ }
+
+ const char *pszContext = pEconTool->GetUseCommand( pItem, i );
+ contextMenuBuilder.AddMenuItem( pszToolUsageString, new KeyValues( pszContext ), "primaryaction" );
+ }
+
+ // Hack: We should really ask the tool if the command supplants trade. For now, if we have two
+ // things, then one of them is trade, so skip it.
+ bSkipAddTrade = nTokens > 1;
+ }
+ else if ( pItem->GetItemDefinition()->GetCapabilities() & ITEM_CAP_DECODABLE )
+ {
+
+ static CSchemaAttributeDefHandle pAttrDef_CanShuffleCrateContents( "can shuffle crate contents" );
+
+ if ( pItem->FindAttribute( pAttrDef_CanShuffleCrateContents ) )
+ {
+ contextMenuBuilder.AddMenuItem( "#ShuffleContents", new KeyValues( "Context_Shuffle" ), "primaryaction" );
+ }
+
+ if ( GetFirstCompatibleKeyForCrate( pItem ) != NULL )
+ {
+ contextMenuBuilder.AddMenuItem( "#UseKey", new KeyValues( "Context_OpenCrateWithKey" ), "primaryaction" );
+ }
+
+ if ( GetDecodedByItemDefIndex( pItem ) )
+ {
+ contextMenuBuilder.AddMenuItem( "#GetKey", new KeyValues( "Context_GetItemFromStore" ), "primaryaction" );
+ contextMenuBuilder.AddMenuItem( "#BuyAndUseKey", new KeyValues( "Context_BuyKeyAndOpenCrate" ), "primaryaction" );
+ }
+ }
+ else if ( pItem->GetItemDefinition()->GetCapabilities() & ITEM_CAP_HAS_SLOTS )
+ {
+ // check if we have at least 1 slot criteria
+ static CSchemaAttributeDefHandle pAttrDef_Slot( "item slot criteria 1" );
+ if ( pItem->FindAttribute( pAttrDef_Slot ) )
+ {
+ contextMenuBuilder.AddMenuItem( "#EditSlots", new KeyValues( "Context_EditSlot" ), "primaryaction" );
+ }
+ }
+ else if ( DuckBadgeItemDef == pItem->GetItemDefinition() )
+ {
+ contextMenuBuilder.AddMenuItem( "#Duck_ViewLeaderboards", new KeyValues( "Context_OpenDuckLeaderboards" ), "primaryaction" );
+
+ if ( CanInventoryItemsApplyTo( pItem ) )
+ {
+ contextMenuBuilder.AddMenuItem( "#UseDuckToken", new KeyValues( "Context_ApplyByItem" ), "primaryaction" );
+ }
+
+ if ( GetDecodedByItemDefIndex( pItem ) )
+ {
+ contextMenuBuilder.AddMenuItem( "#GetDuckToken", new KeyValues( "Context_GetItemFromStore" ), "primaryaction" );
+ }
+ }
+
+ // 3D Inspect
+ float flInspect = 0;
+ static CSchemaAttributeDefHandle pAttrib_WeaponAllowInspect( "weapon_allow_inspect" );
+ static CSchemaAttributeDefHandle pAttrib_CosmeticAllowInspect( "cosmetic_allow_inspect" );
+ if ( pItem && pItem->IsValid() &&
+ ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttrib_WeaponAllowInspect, &flInspect ) || FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttrib_CosmeticAllowInspect, &flInspect )
+#ifdef STAGING_ONLY
+ || tf_weapon_force_allow_inspect.GetBool()
+#endif
+ ) )
+ {
+ if ( flInspect != 0
+#ifdef STAGING_ONLY
+ || tf_weapon_force_allow_inspect.GetBool()
+#endif
+ )
+ {
+ contextMenuBuilder.AddMenuItem( "#Context_InspectModel", new KeyValues( "Context_InspectModel" ), "primaryaction" );
+ }
+ }
+
+ // Add equip sub menu
+ {
+ Menu *pEquipSubMenu = NULL;
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
+ {
+ if ( !pItemDef->CanBeUsedByClass( iClass ) )
+ continue;
+
+ if ( pEquipSubMenu == NULL )
+ {
+ pEquipSubMenu = new Menu( this, "EquipMenu" );
+ pEquipSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pEquipSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+
+ contextMenuBuilder.AddCascadingMenuItem( "#Context_Equip", pEquipSubMenu, "primaryaction" );
+ }
+
+ const char *pszClassName = NULL;
+ switch ( iClass )
+ {
+ case TF_CLASS_SCOUT: pszClassName = "#TF_Class_Name_Scout"; break;
+ case TF_CLASS_SNIPER: pszClassName = "#TF_Class_Name_Sniper"; break;
+ case TF_CLASS_SOLDIER: pszClassName = "#TF_Class_Name_Soldier"; break;
+ case TF_CLASS_DEMOMAN: pszClassName = "#TF_Class_Name_Demoman"; break;
+ case TF_CLASS_MEDIC: pszClassName = "#TF_Class_Name_Medic"; break;
+ case TF_CLASS_HEAVYWEAPONS: pszClassName = "#TF_Class_Name_HWGuy"; break;
+ case TF_CLASS_PYRO: pszClassName = "#TF_Class_Name_Pyro"; break;
+ case TF_CLASS_SPY: pszClassName = "#TF_Class_Name_Spy"; break;
+ case TF_CLASS_ENGINEER: pszClassName = "#TF_Class_Name_Engineer"; break;
+ }
+
+ pEquipSubMenu->AddMenuItem( pszClassName, new KeyValues( "Command", "command", CFmtStr( "equipclass%d", iClass ) ), this );
+ }
+ }
+
+ // For customizable items only
+ if ( !pItem->IsTemporaryItem() )
+ {
+ bool bCanCraftUp = GetCollectionCraftingInvalidReason(pItem, NULL) == NULL;
+ bool bCanStatClockTrade = GetCraftCommonStatClockInvalidReason(pItem, NULL) == NULL;
+ Menu *pMannCoTradeSubMenu = NULL;
+
+ if ( bCanCraftUp || bCanStatClockTrade )
+ {
+ pMannCoTradeSubMenu = new Menu(this, "MannCoTradeSubMenu");
+ pMannCoTradeSubMenu->SetBorder(scheme()->GetIScheme(GetScheme())->GetBorder(pszContextMenuBorder));
+ pMannCoTradeSubMenu->SetFont(scheme()->GetIScheme(GetScheme())->GetFont(pszContextMenuFont));
+ contextMenuBuilder.AddCascadingMenuItem("#Context_MannCoTrade", pMannCoTradeSubMenu, "customization");
+
+ if ( bCanCraftUp )
+ {
+ int nIndex = pMannCoTradeSubMenu->AddMenuItem("", new KeyValues("Command", "command", "Context_CraftUpCollection"), this);
+ vgui::MenuItem *pMenuItem = pMannCoTradeSubMenu->GetMenuItem(nIndex);
+ pMenuItem->SetText("#Context_TradeUp");
+ pMenuItem->InvalidateLayout(true, false);
+ }
+
+ if ( bCanStatClockTrade )
+ {
+ int nIndex = pMannCoTradeSubMenu->AddMenuItem("", new KeyValues("Command", "command", "Context_CraftCommonStatClock"), this);
+ vgui::MenuItem *pMenuItem = pMannCoTradeSubMenu->GetMenuItem(nIndex);
+ pMenuItem->SetText("#Context_CommonStatClock");
+ pMenuItem->InvalidateLayout(true, false);
+ }
+ }
+
+
+
+ // Campaign coin access trades
+ static CSchemaAttributeDefHandle pAttrDef_IsOperationPass( "is_operation_pass" );
+ if ( pItem->FindAttribute( pAttrDef_IsOperationPass ) )
+ {
+ Menu *pMannCoCoinTradeSubMenu = new Menu( this, "MannCoTradeSubMenu" );
+ pMannCoCoinTradeSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pMannCoCoinTradeSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+ contextMenuBuilder.AddCascadingMenuItem( "#Context_MannCoTrade", pMannCoCoinTradeSubMenu, "customization" );
+
+ int nIndex = pMannCoCoinTradeSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", "Context_CraftUpCollection" ), this );
+ vgui::MenuItem *pMenuItem = pMannCoCoinTradeSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( "#Context_TradeUp" );
+ pMenuItem->InvalidateLayout( true, false );
+
+ nIndex = pMannCoCoinTradeSubMenu->AddMenuItem("", new KeyValues("Command", "command", "Context_CraftCommonStatClock"), this);
+ pMenuItem = pMannCoCoinTradeSubMenu->GetMenuItem(nIndex);
+ pMenuItem->SetText("#Context_CommonStatClock");
+ pMenuItem->InvalidateLayout(true, false);
+ }
+
+ // Halloween trade up offering.
+ // Needs two attrs
+ static CSchemaAttributeDefHandle pAttrDef_HalloweenOffering( "allow_halloween_offering" );
+ static CSchemaAttributeDefHandle pAttrDef_DeactiveDate( "deactive date" );
+
+ // Check the date
+ uint32 unDeactiveDate = 0;
+ uint32 unCurrentDate = CRTime::RTime32TimeCur();
+ if ( pAttrDef_HalloweenOffering && pItem->FindAttribute( pAttrDef_HalloweenOffering ) && pItem->FindAttribute( pAttrDef_DeactiveDate, &unDeactiveDate ) && unDeactiveDate > unCurrentDate )
+ {
+ vgui::MenuItem *pMenuItem = contextMenuBuilder.AddMenuItem( "#Context_HalloweenOffering", new KeyValues( "Context_HalloweenOffering" ), "customization" );
+
+ ImagePanel *pNewImage = new ImagePanel( pMenuItem, "new" );
+ pNewImage->SetZPos( 100 );
+ pNewImage->SetWide( 40 );
+ pNewImage->SetTall( 40 );
+ pNewImage->SetPos( 220, -5 );
+ pNewImage->SetMouseInputEnabled( false );
+ pNewImage->SetShouldScaleImage( true );
+ pNewImage->SetImage( "new" );
+ }
+
+ // Change name
+ GameItemDefinition_t * pNameTagDef = dynamic_cast<GameItemDefinition_t*>( GEconItemSchema().GetItemDefinitionByName( "Name Tag" ) );
+ if ( CEconSharedToolSupport::ToolCanApplyToDefinition( dynamic_cast<const GameItemDefinition_t *>( pNameTagDef ), pItemDef ) )
+ {
+ contextMenuBuilder.AddMenuItem( "#Context_Rename", new KeyValues( "DoRename" ), "customization" );
+ }
+
+ // Change description
+ GameItemDefinition_t * pDescTagDef = dynamic_cast<GameItemDefinition_t*>( GEconItemSchema().GetItemDefinitionByName( "Description Tag" ) );
+ if ( CEconSharedToolSupport::ToolCanApplyToDefinition( dynamic_cast<const GameItemDefinition_t *>( pDescTagDef ), pItemDef ) )
+ {
+ contextMenuBuilder.AddMenuItem( "#Context_Description", new KeyValues( "DoDescription" ), "customization" );
+ }
+
+ // Add paint options sub menu
+ if ( m_vecPaintCans.Count() > 0 )
+ {
+ GameItemDefinition_t * pPaintCanDef = dynamic_cast<GameItemDefinition_t*>( GEconItemSchema().GetItemDefinition( m_vecPaintCans[0] ) );
+ if ( pPaintCanDef && CEconSharedToolSupport::ToolCanApplyToDefinition( dynamic_cast<const GameItemDefinition_t *>( pPaintCanDef ), pItemDef ) )
+ {
+ Menu *pPaintSubMenu = NULL;
+ pPaintSubMenu = new Menu( this, "PaintSubMenu" );
+ pPaintSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pPaintSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+ contextMenuBuilder.AddCascadingMenuItem( "#Context_Paint", pPaintSubMenu, "customization" );
+
+ CUtlVector<item_definition_index_t> vecOwnedPaints;
+ CUtlVector<item_definition_index_t> vecStorePaints;
+
+ // Find out if the user owns this item or not and place in the proper bucket
+ CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
+
+ FOR_EACH_VEC( m_vecPaintCans, i )
+ {
+ if ( pLocalInv && pLocalInv->FindFirstItembyItemDef( m_vecPaintCans[i] ) )
+ {
+ vecOwnedPaints.AddToTail( m_vecPaintCans[i] );
+ }
+ else
+ {
+ vecStorePaints.AddToTail( m_vecPaintCans[i] );
+ }
+ }
+
+ if ( vecOwnedPaints.Count() > 0 )
+ {
+ // Add Header and loop
+ int nIndex = pPaintSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", "" ), this );
+ vgui::MenuItem *pMenuItem = pPaintSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( g_pVGuiLocalize->Find( "#TF_Owned" ) );
+ pMenuItem->InvalidateLayout( true, false );
+
+ FOR_EACH_VEC( vecOwnedPaints, i )
+ {
+ AddPaintToContextMenu( pPaintSubMenu, vecOwnedPaints[i], false );
+ }
+ }
+
+ pPaintSubMenu->AddSeparator();
+ if ( vecStorePaints.Count() > 0 )
+ {
+ // Add Header and loop
+ int nIndex = pPaintSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", "" ), this );
+ vgui::MenuItem *pMenuItem = pPaintSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( g_pVGuiLocalize->Find( "#TF_Commerce" ) );
+ pMenuItem->InvalidateLayout( true, false );
+
+ FOR_EACH_VEC( vecStorePaints, i )
+ {
+ AddPaintToContextMenu( pPaintSubMenu, vecStorePaints[i], true );
+ }
+ }
+ }
+ }
+
+ // Strange Parts
+ if ( BIsItemStrange( pItem ) )
+ {
+ Menu *pStrangePartsSubMenu = NULL;
+ CUtlVector<item_definition_index_t> vecOwnedParts;
+ CUtlVector<item_definition_index_t> vecStoreParts;
+
+ // Find out if the user owns this item or not and place in the proper bucket
+ CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
+ FOR_EACH_VEC( m_vecStrangeParts, i )
+ {
+ // Determine if this can be applied
+ //GameItemDefinition_t *pStrangePartDef = dynamic_cast<GameItemDefinition_t*>( GEconItemSchema().GetItemDefinition( m_vecStrangeParts[i] ) );
+ CEconItemView partItemView;
+ partItemView.Init( m_vecStrangeParts[i], AE_USE_SCRIPT_VALUE, 1 );
+ if ( CEconSharedToolSupport::ToolCanApplyTo( &partItemView, pItem ) )
+ {
+ // Create menu
+ if ( !pStrangePartsSubMenu )
+ {
+ pStrangePartsSubMenu = new Menu( this, "StrangePartsSubMenu" );
+ pStrangePartsSubMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) );
+ pStrangePartsSubMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) );
+ contextMenuBuilder.AddCascadingMenuItem( "#Context_StrangeParts", pStrangePartsSubMenu, "customization" );
+ }
+
+ if ( pLocalInv && pLocalInv->FindFirstItembyItemDef( m_vecStrangeParts[i] ) )
+ {
+ vecOwnedParts.AddToTail( m_vecStrangeParts[i] );
+ }
+ else
+ {
+ vecStoreParts.AddToTail( m_vecStrangeParts[i] );
+ }
+ }
+ }
+
+ if ( pStrangePartsSubMenu )
+ {
+ if ( vecOwnedParts.Count() > 0 )
+ {
+ // Add Header and loop
+ int nIndex = pStrangePartsSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", "" ), this );
+ vgui::MenuItem *pMenuItem = pStrangePartsSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( g_pVGuiLocalize->Find( "#TF_Owned" ) );
+ pMenuItem->InvalidateLayout( true, false );
+
+ FOR_EACH_VEC( vecOwnedParts, i )
+ {
+ AddCommerceToContextMenu( pStrangePartsSubMenu, "strangepart_", vecOwnedParts[i], false, false );
+ }
+ }
+
+ pStrangePartsSubMenu->AddSeparator();
+ if ( vecStoreParts.Count() > 0 )
+ {
+ // Add Header and loop
+ int nIndex = pStrangePartsSubMenu->AddMenuItem( "", new KeyValues( "Command", "command", "" ), this );
+ vgui::MenuItem *pMenuItem = pStrangePartsSubMenu->GetMenuItem( nIndex );
+ pMenuItem->SetText( g_pVGuiLocalize->Find( "#TF_Market" ) );
+ pMenuItem->InvalidateLayout( true, false );
+
+ FOR_EACH_VEC( vecStoreParts, i )
+ {
+ AddCommerceToContextMenu( pStrangePartsSubMenu, "strangepart_", vecStoreParts[i], true, false );
+ }
+ }
+ }
+ }
+
+ if ( pItem->IsMarketable() )
+ {
+ contextMenuBuilder.AddMenuItem( "#Context_MarketPlaceSell", new KeyValues( "DoSellMarketplace" ), "economy" );
+ }
+
+ // Trade to another player
+ if ( pItem->IsTradable() && !bSkipAddTrade )
+ {
+ contextMenuBuilder.AddMenuItem( "#Context_Trade", new KeyValues( "DoTradeToPlayer" ), "economy" );
+ }
+
+ if ( pItem->GetItemDefinition()->GetCapabilities() & ITEM_CAP_CAN_BE_RESTORED )
+ {
+ if ( RemovableAttributes_DoAnyAttributesApply( pItem ) )
+ {
+ contextMenuBuilder.AddMenuItem( "#RefurbishItem", new KeyValues( "Context_RefurbishItem" ), "destructive" );
+ }
+ }
+ }
+ }
+ else
+ {
+ // Check if ALL selected items can be crafted together
+ bool bCanCraftUp = true;
+ for( int i=0; i < COLLECTION_CRAFTING_ITEM_COUNT && i < vecSelectedItems.Count(); ++i )
+ {
+ CEconItemView* pPrevItem = ( i - 1 ) < 0 ? NULL : vecSelectedItems[ i - 1 ];
+ bCanCraftUp &= GetCollectionCraftingInvalidReason( vecSelectedItems[ i ], pPrevItem ) == NULL;
+ }
+
+ bool bCanStatClockTrade = true;
+ for (int i = 0; i < COLLECTION_CRAFTING_ITEM_COUNT && i < vecSelectedItems.Count(); ++i)
+ {
+ CEconItemView* pPrevItem = (i - 1) < 0 ? NULL : vecSelectedItems[i - 1];
+ bCanStatClockTrade &= GetCraftCommonStatClockInvalidReason(vecSelectedItems[i], pPrevItem) == NULL;
+ }
+
+ Menu *pMannCoTradeSubMenu = NULL;
+
+ if ( bCanCraftUp || bCanStatClockTrade )
+ {
+ pMannCoTradeSubMenu = new Menu(this, "MannCoTradeSubMenu");
+ pMannCoTradeSubMenu->SetBorder(scheme()->GetIScheme(GetScheme())->GetBorder(pszContextMenuBorder));
+ pMannCoTradeSubMenu->SetFont(scheme()->GetIScheme(GetScheme())->GetFont(pszContextMenuFont));
+ contextMenuBuilder.AddCascadingMenuItem("#Context_MannCoTrade", pMannCoTradeSubMenu, "customization");
+
+ if (bCanCraftUp)
+ {
+ int nIndex = pMannCoTradeSubMenu->AddMenuItem("", new KeyValues("Command", "command", "Context_CraftUpCollection"), this);
+ vgui::MenuItem *pMenuItem = pMannCoTradeSubMenu->GetMenuItem(nIndex);
+ pMenuItem->SetText("#Context_TradeUp");
+ pMenuItem->InvalidateLayout(true, false);
+ }
+
+ if (bCanStatClockTrade)
+ {
+ int nIndex = pMannCoTradeSubMenu->AddMenuItem("", new KeyValues("Command", "command", "Context_CraftCommonStatClock"), this);
+ vgui::MenuItem *pMenuItem = pMannCoTradeSubMenu->GetMenuItem(nIndex);
+ pMenuItem->SetText("#Context_CommonStatClock");
+ pMenuItem->InvalidateLayout(true, false);
+ }
+ }
+ }
+
+ if ( !m_bShowBaseItems )
+ {
+ bool bDeleteAvailable = true;
+ // Check that all of the selected items are deletable
+ for( int i=0; i < vecSelectedItems.Count() && bDeleteAvailable; ++i )
+ {
+ static CSchemaAttributeDefHandle pAttrDef_NoDelete( "cannot delete" );
+ bDeleteAvailable &= !vecSelectedItems[i]->FindAttribute( pAttrDef_NoDelete );
+ }
+
+ // Only show the delete button if every slected item is deletable
+ if ( bDeleteAvailable )
+ {
+ contextMenuBuilder.AddMenuItem( "#TF_SteamWorkshop_Delete", new KeyValues( "DoDelete" ), "destructive" );
+ }
+ }
+
+ // Position to the cursor's position
+ int nX, nY;
+ g_pVGuiInput->GetCursorPosition( nX, nY );
+ m_pContextMenu->SetPos( nX - 1, nY - 1 );
+
+ m_pContextMenu->SetVisible(true);
+ m_pContextMenu->AddActionSignalTarget(this);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnItemPanelMouseRightRelease( vgui::Panel *panel )
+{
+#ifdef STAGING_ONLY
+ if ( tf_use_card_tooltips.GetBool() )
+ {
+ m_pMouseOverCardPanel->PinCard( true );
+ }
+ else
+#endif
+ {
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+ if ( pItemPanel && pItemPanel->IsVisible() )
+ {
+ // If they're not holding down ctrl, deselect all existing selections
+ if ( !vgui::input()->IsKeyDown(KEY_LCONTROL) && !vgui::input()->IsKeyDown(KEY_RCONTROL) )
+ {
+ DeSelectAllBackpackItemPanels();
+ ToggleSelectBackpackItemPanel( pItemPanel );
+ }
+ else if ( AllowSelection() && !pItemPanel->IsGreyedOut() )
+ {
+ if ( !pItemPanel->IsSelected() && pItemPanel->HasItem() )
+ {
+ pItemPanel->SetSelected( true );
+ }
+ SetBorderForItem( pItemPanel, false );
+ }
+
+ OpenContextMenu();
+ }
+ }
+}
+
+void CBackpackPanel::OnMouseMismatchedRelease( MouseCode code, Panel* pPressedPanel )
+{
+ if ( pPressedPanel )
+ {
+ OnMouseReleased( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::StartDrag( int x, int y )
+{
+ // don't allow item drag if there's a filter
+ if ( HasNameFilter() )
+ return;
+
+ m_bDragging = true;
+ HideMouseOverPanel();
+
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+
+ m_pMouseDragItemPanel->SetItem( m_pItemDraggedFromPanel->GetItem() );
+ m_pMouseDragItemPanel->InvalidateLayout( true );
+
+ m_pItemDraggedFromPanel->Dragged( true );
+
+ // Calculate the mouse offset from the top left of the panel we're going to drag
+ m_iDragOffsetX = m_pMouseDragItemPanel->GetWide() * 0.5f;
+ m_iDragOffsetY = m_pMouseDragItemPanel->GetTall() * 0.5f;
+ m_flPreventDragPageSwitchUntil = 0;
+
+ m_pMouseDragItemPanel->SetVisible( true );
+
+ m_pItemDraggedFromPanel->SetItem( NULL );
+ SetBorderForItem( m_pItemDraggedFromPanel, false );
+ m_pPrevDragOverItemPanel = NULL;
+
+ vgui::input()->SetMouseCapture( GetVPanel() );
+
+ if ( m_pDragToNextPageButton && m_pDragToPrevPageButton )
+ {
+ m_pDragToNextPageButton->SetVisible( GetNumPages() > 1 );
+ m_pDragToPrevPageButton->SetVisible( GetNumPages() > 1 );
+ }
+
+ // play pickup sound
+ CEconItemView *item = m_pMouseDragItemPanel->GetItem();
+ if ( item )
+ {
+ const char *soundFilename = item->GetDefinitionString( "pickup_sound", "" );
+ if ( soundFilename[0] )
+ {
+ vgui::surface()->PlaySound( soundFilename );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::StopDrag( bool bSucceeded )
+{
+ if ( !m_pItemDraggedFromPanel )
+ return;
+
+ if ( !bSucceeded )
+ {
+ if ( m_iDraggedFromPage == GetCurrentPage() )
+ {
+ m_pItemDraggedFromPanel->SetItem( m_pMouseDragItemPanel->GetItem() );
+ }
+ m_pItemDraggedFromPanel = NULL;
+ }
+
+ m_pMouseDragItemPanel->SetVisible( false );
+ m_bDragging = false;
+
+ vgui::input()->SetMouseCapture( NULL );
+
+ if ( m_pDragToNextPageButton && m_pDragToPrevPageButton )
+ {
+ m_pDragToNextPageButton->SetVisible( false );
+ m_pDragToPrevPageButton->SetVisible( false );
+ }
+
+ // play drop sound
+ CEconItemView *item = m_pMouseDragItemPanel->GetItem();
+ if ( item )
+ {
+ const char *soundFilename = item->GetDefinitionString( "drop_sound", "ui/item_default_drop.wav" );
+ vgui::surface()->PlaySound( soundFilename );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBackpackPanel::GetBackpackPositionForPanel( CItemModelPanel *pItemPanel )
+{
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i] == pItemPanel )
+ return i;
+ }
+ return -1;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::HandleDragTo( CItemModelPanel *pItemPanel, int iPanelIndex )
+{
+ // Find the position based on the panel we're dragging to
+ if ( iPanelIndex != -1 )
+ {
+ // If the current panel is selected, unselect it
+ if ( m_pItemModelPanels[iPanelIndex]->IsSelected() )
+ {
+ ToggleSelectBackpackItemPanel( m_pItemModelPanels[iPanelIndex] );
+ }
+ if ( m_pItemDraggedFromPanel->IsSelected() )
+ {
+ ToggleSelectBackpackItemPanel( m_pItemDraggedFromPanel );
+ }
+
+ // We "move" the items in the backpack immediately, because when the messages come back
+ // from steam they'll fix the positions if the move fails for some reason.
+ CEconItemView *pItem = NULL;
+ if ( m_pItemModelPanels[iPanelIndex]->HasItem() )
+ {
+ // We need to copy it because it's about to get stomped by the other item
+ pItem = new CEconItemView( *m_pItemModelPanels[iPanelIndex]->GetItem() );
+ }
+ m_pItemModelPanels[iPanelIndex]->SetItem( m_pMouseDragItemPanel->GetItem() );
+
+ if ( m_iDraggedFromPage == GetCurrentPage() )
+ {
+ m_pItemDraggedFromPanel->SetItem( pItem );
+ }
+
+ if ( pItem )
+ {
+ delete pItem;
+ }
+
+ // Tell the inventory to move the item
+ // Translate it to the right page
+ int iBackpackPosition = GetBackpackPosForPanelIndex( iPanelIndex );
+ InventoryManager()->MoveItemToBackpackPosition( m_pMouseDragItemPanel->GetItem(), iBackpackPosition );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ bool bNeedsTick = false;
+ if ( m_flStartExplanationsAt && m_flStartExplanationsAt < Plat_FloatTime() )
+ {
+ m_flStartExplanationsAt = 0;
+
+ if ( ShouldShowExplanations() )
+ {
+ ConVar *pConVar = GetExplanationConVar();
+ if ( pConVar )
+ {
+ pConVar->SetValue( 1 );
+ }
+
+ CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( FindChildByName("StartExplanation") );
+ if ( pPopup )
+ {
+ pPopup->Popup();
+ }
+ }
+ }
+ else
+ {
+ bNeedsTick = true;
+ }
+
+ // To handle page movement while holding the mouse still over the page buttons,
+ // we need to keep calling OnCursorMoved() whenever we're dragging.
+ if ( m_bDragging && m_pMouseDragItemPanel && m_pItemDraggedFromPanel && IsVisible() )
+ {
+ int mx,my;
+ vgui::input()->GetCursorPos( mx, my );
+ ScreenToLocal( mx, my );
+ OnCursorMoved( mx,my );
+
+ bNeedsTick = true;
+ }
+
+ if ( !bNeedsTick && !NeedsDerivedTickSignal() )
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnThink( void )
+{
+ BaseClass::OnThink();
+
+ if ( m_flFilterItemTime > 0 && gpGlobals->curtime >= m_flFilterItemTime )
+ {
+ SetCurrentPage( 0 );
+ DeSelectAllBackpackItemPanels();
+ UpdateModelPanels();
+
+ m_flFilterItemTime = 0.0f;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnCursorMoved( int x, int y )
+{
+ if ( !m_pItemDraggedFromPanel )
+ return;
+
+ if ( m_bDragging && m_pMouseDragItemPanel )
+ {
+ m_pMouseDragItemPanel->SetPos( x - m_iDragOffsetX, y - m_iDragOffsetY );
+
+ // When we're dragging, we have mouse capture, so the item panels aren't getting mouse input.
+ // We need to find out what item panel we're over, and let it know.
+
+ if ( m_flPreventDragPageSwitchUntil < Plat_FloatTime() )
+ {
+ // First, are we over the page turning areas?
+ bool bDragNext = false;
+ if ( m_pDragToNextPageButton && m_pDragToNextPageButton->IsVisible() )
+ {
+ int iDragX, iDragY;
+ m_pDragToNextPageButton->GetPos( iDragX, iDragY );
+ bDragNext = ( x >= iDragX && x <= (iDragX + m_pDragToNextPageButton->GetWide() ) );
+ }
+ if ( !bDragNext && m_pNextPageButton && m_pNextPageButton->IsEnabled() )
+ {
+ int iDragX, iDragY;
+ m_pNextPageButton->GetPos( iDragX, iDragY );
+ bDragNext = ( x >= iDragX && x <= (iDragX + m_pNextPageButton->GetWide()) && y >= iDragY && y <= (iDragY + m_pNextPageButton->GetTall()) );
+ }
+ if ( bDragNext )
+ {
+ OnCommand( "nextpage" );
+ m_flPreventDragPageSwitchUntil = Plat_FloatTime() + tf_backpack_page_button_delay.GetFloat();
+ return;
+ }
+
+ bool bDragPrev = false;
+ if ( m_pDragToPrevPageButton && m_pDragToPrevPageButton->IsVisible() )
+ {
+ int iDragX, iDragY;
+ m_pDragToPrevPageButton->GetPos( iDragX, iDragY );
+ bDragPrev = ( x >= iDragX && x <= (iDragX + m_pDragToPrevPageButton->GetWide() ) );
+ }
+ if ( !bDragPrev && m_pPrevPageButton && m_pPrevPageButton->IsEnabled() )
+ {
+ int iDragX, iDragY;
+ m_pPrevPageButton->GetPos( iDragX, iDragY );
+ bDragPrev = ( x >= iDragX && x <= (iDragX + m_pPrevPageButton->GetWide()) && y >= iDragY && y <= (iDragY + m_pPrevPageButton->GetTall()) );
+ }
+ if ( bDragPrev )
+ {
+ OnCommand( "prevpage" );
+ m_flPreventDragPageSwitchUntil = Plat_FloatTime() + tf_backpack_page_button_delay.GetFloat();
+ return;
+ }
+ }
+
+ // check if we're hovering page button
+ static int iPrevHoveringPage = -1;
+ static float flLastPageButtonEnterTime = 0.f;
+ int iHoveringPage = GetPageButtonIndexAtPos( x, y );
+ if ( iHoveringPage != -1 )
+ {
+ if ( iHoveringPage == GetCurrentPage() )
+ {
+ iPrevHoveringPage = -1;
+ flLastPageButtonEnterTime = 0.f;
+ }
+ else if ( iPrevHoveringPage != iHoveringPage )
+ {
+ iPrevHoveringPage = iHoveringPage;
+ flLastPageButtonEnterTime = Plat_FloatTime() + tf_backpack_page_button_delay.GetFloat();
+ }
+ else if ( flLastPageButtonEnterTime > 0 && flLastPageButtonEnterTime < Plat_FloatTime() )
+ {
+ flLastPageButtonEnterTime = 0.f;
+ SetCurrentPage( iHoveringPage );
+ UpdateModelPanels();
+ }
+ return;
+ }
+ else
+ {
+ // reset hovering page buttons data
+ iPrevHoveringPage = -1;
+ flLastPageButtonEnterTime = 0.f;
+ }
+
+ CItemModelPanel *pOverPanel = GetItemPanelAtPos( x, y );
+ if ( m_pPrevDragOverItemPanel != pOverPanel )
+ {
+ if ( m_pPrevDragOverItemPanel )
+ {
+ OnItemPanelExited( m_pPrevDragOverItemPanel );
+ }
+
+ m_pPrevDragOverItemPanel = pOverPanel;
+
+ if ( m_pPrevDragOverItemPanel )
+ {
+ OnItemPanelEntered( m_pPrevDragOverItemPanel );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnItemPanelCursorMoved( int x, int y )
+{
+ if ( !m_pItemDraggedFromPanel )
+ return;
+
+ if ( !m_bDragging && m_pItemDraggedFromPanel->HasItem() && !InToolSelectionMode() )
+ {
+ // Don't drag instantly, so it's easy to select
+ if ( (gpGlobals->curtime - m_flMouseDownTime) > 0.3 )
+ {
+ StartDrag( x,y );
+ }
+ else
+ {
+ if ( !m_iMouseDownX )
+ {
+ m_iMouseDownX = x;
+ m_iMouseDownY = y;
+ }
+ else if ( abs(m_iMouseDownX - x) > XRES(10) || abs(m_iMouseDownY - y) > YRES(10) )
+ {
+ StartDrag( x,y );
+ }
+ }
+ }
+
+ OnCursorMoved( x, y );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::ToggleSelectBackpackItemPanel( CItemModelPanel *pPanel )
+{
+ if ( !AllowSelection() || pPanel->IsGreyedOut() )
+ return;
+
+ if ( pPanel->IsSelected() || !pPanel->HasItem() )
+ {
+ pPanel->SetSelected( false );
+ }
+ else
+ {
+ pPanel->SetSelected( true );
+ }
+ SetBorderForItem( pPanel, false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DeSelectAllBackpackItemPanels( void )
+{
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i]->IsSelected() )
+ {
+ m_pItemModelPanels[i]->SetSelected( false );
+ SetBorderForItem( m_pItemModelPanels[i], false );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called whenever the selection changes in the item loadout panel
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnItemContentsChanged( CEconItemView *pEconItemView )
+{
+ Assert( pEconItemView );
+
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ CEconItemView *pInternalItem = m_pItemModelPanels[i] && m_pItemModelPanels[i]->HasItem()
+ ? m_pItemModelPanels[i]->GetItem()
+ : NULL;
+
+ if ( *pInternalItem == *pEconItemView )
+ {
+ m_pItemModelPanels[i]->DirtyDescription();
+ OnItemSelectionChanged();
+ return;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when text changes in combo box
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnTextChanged( KeyValues *data )
+{
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
+
+ vgui::TextEntry *pTextEntry = dynamic_cast<vgui::TextEntry *>( pPanel );
+ if ( pTextEntry )
+ {
+ if ( pTextEntry == m_pNameFilterTextEntry )
+ {
+ m_wNameFilter.RemoveAll();
+ if ( m_pNameFilterTextEntry->GetTextLength() )
+ {
+ m_wNameFilter.EnsureCount( m_pNameFilterTextEntry->GetTextLength() + 1 );
+ m_pNameFilterTextEntry->GetText( m_wNameFilter.Base(), m_wNameFilter.Count() * sizeof(wchar_t) );
+ V_wcslower( m_wNameFilter.Base() );
+ }
+ m_flFilterItemTime = gpGlobals->curtime + 0.5f;
+ return;
+ }
+ }
+
+ vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel );
+ if ( pComboBox )
+ {
+ if ( pComboBox == m_pSortByComboBox )
+ {
+ // the class selection combo box changed, update class details
+ KeyValues *pUserData = m_pSortByComboBox->GetActiveItemUserData();
+ if ( !pUserData )
+ return;
+
+ enum { kSortType_Dummy = -1 };
+ int iSortTypeSelectionIndex = pUserData->GetInt( "sortby", kSortType_Dummy );
+ if ( iSortTypeSelectionIndex != kSortType_Dummy )
+ {
+ uint32 iSortType = g_BackpackSortTypes[iSortTypeSelectionIndex].iSortType;
+ if ( iSortType != kGCItemSort_NoSort )
+ {
+ InventoryManager()->SortBackpackBy( iSortType );
+
+ // Now go back to the "Sort by" header, and move the focus to the close button.
+ m_pSortByComboBox->ActivateItemByRow( 0 );
+ }
+ }
+ }
+ else if ( pComboBox == m_pShowRarityComboBox )
+ {
+ cl_showbackpackrarities.SetValue( m_pShowRarityComboBox->GetActiveItem() );
+
+ // Refresh all item borders
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ SetBorderForItem( m_pItemModelPanels[i], false );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnButtonChecked( KeyValues *pData )
+{
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( pData->GetPtr("panel") );
+
+ if ( m_bShowBaseItems != m_pShowBaseItemsCheckbox->IsSelected() && m_pShowBaseItemsCheckbox == pPanel && IsVisible() )
+ {
+ SetShowBaseItems( m_pShowBaseItemsCheckbox->IsSelected() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnCancelSelection( void )
+{
+ if ( m_pConfirmDeleteDialog )
+ {
+ m_pConfirmDeleteDialog->MarkForDeletion();
+ m_pConfirmDeleteDialog = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CBackpackPanel::GetGreyOutItemPanelReason( CItemModelPanel *pItemPanel )
+{
+ if ( InToolSelectionMode() )
+ {
+ bool bIsSelectedTool = (m_ToolSelectionItem.IsValid() && pItemPanel->HasItem()) ? (m_ToolSelectionItem == *pItemPanel->GetItem()) : false;
+ if ( !bIsSelectedTool )
+ {
+ if ( m_ToolSelectionItem.GetStaticData()->IsTool() )
+ {
+ if ( !CEconSharedToolSupport::ToolCanApplyTo( &m_ToolSelectionItem, pItemPanel->GetItem() ) )
+ {
+ return "#Econ_GreyOutReason_ToolCannotApply";
+ }
+ }
+ else if ( pItemPanel->GetItem() && pItemPanel->GetItem()->GetStaticData()->IsTool() )
+ {
+ if ( !CEconSharedToolSupport::ToolCanApplyTo( pItemPanel->GetItem(), &m_ToolSelectionItem ) )
+ {
+ return "#Econ_GreyOutReason_ToolCannotApply";
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver )
+{
+ tmZone( TELEMETRY_LEVEL1, TMZF_NONE, "%s", __FUNCTION__ );
+
+ if ( !pItemPanel )
+ return;
+
+ const char *pszBorder = NULL;
+
+ bool bIsSelectedTool = (m_ToolSelectionItem.IsValid() && pItemPanel->HasItem()) ? (m_ToolSelectionItem == *pItemPanel->GetItem()) : false;
+
+ // Handle grey out
+ const char *pszGreyOutReason = GetGreyOutItemPanelReason( pItemPanel );
+ const bool bGreyOut = pszGreyOutReason != NULL;
+
+ pItemPanel->SetGreyedOut( pszGreyOutReason );
+
+ int iRarity = GetItemQualityForBorder( pItemPanel );
+
+ if ( InToolSelectionMode() && bIsSelectedTool )
+ {
+ // We're in tool application mode, and this panel is the tool being used
+ pszBorder = "BackpackItemBorder_SelfMade";
+
+ if ( m_pToolIcon )
+ {
+ int iX, iY;
+ pItemPanel->GetPos( iX, iY );
+ m_pToolIcon->SetPos( iX, iY );
+ m_pToolIcon->SetVisible( true );
+ }
+ }
+ else if ( bGreyOut )
+ {
+ if( pItemPanel->IsSelected() )
+ {
+ pszBorder = g_szItemBorders[iRarity][4];
+ }
+ else
+ {
+ pszBorder = g_szItemBorders[iRarity][3];
+ }
+ }
+ else
+ {
+
+ if ( pItemPanel->IsSelected() )
+ {
+ pszBorder = g_szItemBorders[iRarity][2];
+ }
+ else if ( bMouseOver )
+ {
+ pszBorder = g_szItemBorders[iRarity][1];
+ }
+ else
+ {
+ pszBorder = g_szItemBorders[iRarity][0];
+ }
+ }
+
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) );
+}
+
+//-----------------------------------------------------------------------------
+class CTFRemoveItemCustomizationConfirmDialog : public CTFGenericConfirmDialog
+{
+ DECLARE_CLASS_SIMPLE( CTFRemoveItemCustomizationConfirmDialog, CTFGenericConfirmDialog );
+public:
+ CTFRemoveItemCustomizationConfirmDialog( const RefurbishableProperty& prop, CEconItemView *pItem )
+ : CTFGenericConfirmDialog( prop.m_szDialogTitle, // dialog title
+ prop.m_szDialogDesc, // dialog text
+ "#RefurbishItem_Yes", // confirm button text
+ "#RefurbishItem_No", // cancel button text
+ NULL, // callback
+ NULL ) // parent
+ , m_prop( prop )
+ , m_Item( *pItem ) // copy in case our UI changes behind us
+ {
+ GetCustomDialogLocalizationTokenFunc_t m_pDialogCustomTokenFunc = m_prop.m_pGetCustomDialogLocalizationTokenFunc;
+ if ( m_pDialogCustomTokenFunc )
+ {
+ CUtlConstWideString wsDialogCustomToken;
+ (*m_pDialogCustomTokenFunc)( &m_Item, m_prop.m_iUserData, wsDialogCustomToken );
+ if ( !wsDialogCustomToken.IsEmpty() )
+ {
+ AddStringToken( "confirm_dialog_token", wsDialogCustomToken.Get() );
+ }
+ }
+ }
+
+ virtual ~CTFRemoveItemCustomizationConfirmDialog() { }
+
+ virtual void OnCommand( const char *command );
+
+private:
+ RefurbishableProperty m_prop;
+ CEconItemView m_Item;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void SendGCSimpleAttributeRemovalMessage( CEconItemView *pEconItemView, const char *szDesc, EGCItemMsg eItemMsg )
+{
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_REMOVED_ATTRIB, pEconItemView, szDesc );
+ GCSDK::CProtoBufMsg<CMsgGCRemoveCustomizationAttributeSimple> msg( eItemMsg );
+ msg.Body().set_item_id( pEconItemView->GetItemID() );
+ GCClientSystem()->BSendMessage( msg );
+}
+
+void CTFRemoveItemCustomizationConfirmDialog::OnCommand( const char *command )
+{
+ BaseClass::OnCommand( command );
+
+ // Did the user say "yes, remove this particular attribute"? If so, notify the GC. We can't
+ // remove multiple attributes at a time because each removal will cause a new item to be created,
+ // invalidating the item reference we've got in this dialog.
+ if ( !Q_strnicmp( command, "confirm", 7 ) )
+ {
+ // remove the attribute
+ switch( m_prop.m_eRemovalType )
+ {
+ case kCustomizationRemove_Paint:
+ SendGCSimpleAttributeRemovalMessage( &m_Item, "paint", k_EMsgGCRemoveItemPaint );
+ break;
+
+ case kCustomizationRemove_Name:
+ {
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_REMOVED_ATTRIB, &m_Item, "name" );
+ GCSDK::CGCMsg< MsgGCRemoveItemName_t > msg( k_EMsgGCRemoveItemName );
+ msg.Body().m_unItemID = m_Item.GetItemID();
+ msg.Body().m_bDescription = false;
+ GCClientSystem()->BSendMessage( msg );
+ }
+ break;
+
+ case kCustomizationRemove_Desc:
+ {
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_REMOVED_ATTRIB, &m_Item, "description" );
+ GCSDK::CGCMsg< MsgGCRemoveItemName_t > msg( k_EMsgGCRemoveItemName );
+ msg.Body().m_unItemID = m_Item.GetItemID();
+ msg.Body().m_bDescription = true;
+ GCClientSystem()->BSendMessage( msg );
+ }
+ break;
+
+ case kCustomizationRemove_CustomTexture:
+ SendGCSimpleAttributeRemovalMessage( &m_Item, "custom_texture", k_EMsgGCRemoveCustomTexture );
+ break;
+
+ case kCustomizationRemove_MakersMark:
+ SendGCSimpleAttributeRemovalMessage( &m_Item, "makers_mark", k_EMsgGCRemoveMakersMark );
+ break;
+
+ case kCustomizationRemove_StrangePart:
+ {
+ Assert( m_prop.m_iUserData != kNoUserData );
+ int iKillEaterAttrIndex = m_prop.m_iUserData;
+
+ // What attribute did we select?
+ const CEconItemAttributeDefinition *pAttrDef = GetKillEaterAttr_Type( iKillEaterAttrIndex );
+ Assert( pAttrDef );
+
+ // Make sure this item has this attribute.
+ float fScoreType = kKillEaterEvent_PlayerKill;
+ Verify( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( &m_Item, pAttrDef, &fScoreType ) || iKillEaterAttrIndex == 0 );
+
+ // Dispatch message.
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_REMOVED_ATTRIB, &m_Item, "strange_part" );
+ GCSDK::CProtoBufMsg<CMsgGCRemoveStrangePart> msg( k_EMsgGCRemoveStrangePart );
+
+ msg.Body().set_item_id( m_Item.GetItemID() );
+ msg.Body().set_strange_part_score_type( (int)fScoreType );
+ GCClientSystem()->BSendMessage( msg );
+ }
+ break;
+
+ case kCustomizationRemove_StrangeScores:
+ {
+ Assert( m_prop.m_iUserData == kNoUserData );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_RESET_STRANGE_COUNTERS, &m_Item );
+ GCSDK::CProtoBufMsg<CMsgGCResetStrangeScores> msg( k_EMsgGCResetStrangeScores );
+ msg.Body().set_item_id( m_Item.GetItemID() );
+ GCClientSystem()->BSendMessage( msg );
+ }
+ break;
+
+ case kCustomizationRemove_UpgradeCard:
+ {
+ Assert( m_prop.m_iUserData != kNoUserData );
+
+ // Make sure we selected a valid attribute that this item has.
+ const CEconItemAttributeDefinition *pAttrDef = GetCardUpgradeForIndex( &m_Item, m_prop.m_iUserData );
+ Assert( pAttrDef );
+ Verify( m_Item.FindAttribute( pAttrDef ) );
+
+ // Dispatch message.
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_REMOVED_ATTRIB, &m_Item, "upgrade_card" );
+ GCSDK::CProtoBufMsg<CMsgGCRemoveUpgradeCard> msg( k_EMsgGCRemoveUpgradeCard );
+
+ msg.Body().set_item_id( m_Item.GetItemID() );
+ msg.Body().set_attribute_index( pAttrDef->GetDefinitionIndex() );
+ GCClientSystem()->BSendMessage( msg );
+ }
+ break;
+
+ case kCustomizationRemove_KillStreak:
+ SendGCSimpleAttributeRemovalMessage( &m_Item, "killstreak", k_EMsgGCRemoveKillStreak );
+ break;
+ case kCustomizationRemove_GiftedBy:
+ SendGCSimpleAttributeRemovalMessage( &m_Item, "giftedby", k_EMsgGCRemoveGiftedBy );
+ break;
+ case kCustomizationRemove_Festivizer:
+ SendGCSimpleAttributeRemovalMessage( &m_Item, "festivizer", k_EMsgGCRemoveFestivizer );
+ break;
+ default:
+ AssertMsg( false, "Unknown item customization removal type!" );
+ break;
+ }
+ }
+
+ SetVisible( false );
+ MarkForDeletion();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CRefurbishItemDialog : public CComboBoxBackpackOverlayDialogBase
+{
+public:
+ DECLARE_CLASS_SIMPLE( CRefurbishItemDialog, CComboBoxBackpackOverlayDialogBase );
+
+public:
+ CRefurbishItemDialog( vgui::Panel *pParent, CEconItemView *m_pItem ) : CComboBoxBackpackOverlayDialogBase( pParent, m_pItem ) { }
+
+private:
+ virtual void PopulateComboBoxOptions()
+ {
+ Assert( m_pItem );
+
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ for ( int i = 0; i < GetRemovableAttributesCount(); i++ )
+ {
+ if ( RemovableAttributes_DoesAttributeApply( i, m_pItem ) )
+ {
+ pKeyValues->SetInt( "data", i );
+
+ RefurbishableProperty prop = RemovableAttributes_GetAttributeDetails( i );
+
+ CUtlConstWideString wsDialogCustomToken;
+ if ( prop.m_pGetCustomDialogLocalizationTokenFunc )
+ {
+ (*prop.m_pGetCustomDialogLocalizationTokenFunc)( m_pItem, prop.m_iUserData, wsDialogCustomToken );
+ }
+ CConstructLocalizedString localizedUI( GLocalizationProvider()->Find( prop.m_pszSelectionUILocalizationToken ), wsDialogCustomToken.IsEmpty() ? L"" : wsDialogCustomToken.Get() );
+ GetComboBox()->AddItem( localizedUI, pKeyValues );
+ }
+ }
+ pKeyValues->deleteThis();
+
+ Assert( GetComboBox()->GetItemCount() > 0 );
+
+ GetComboBox()->ActivateItemByRow( 0 );
+ }
+
+ virtual void OnComboBoxApplication()
+ {
+ if ( !m_pItem )
+ return;
+
+ KeyValues *pKVActiveUserData = GetComboBox()->GetActiveItemUserData();
+ int iIndex = pKVActiveUserData ? pKVActiveUserData->GetInt( "data", -1 ) : -1;
+ if ( iIndex < 0 )
+ return;
+
+ const RefurbishableProperty RefurbProp = RemovableAttributes_GetAttributeDetails( iIndex );
+
+ CTFRemoveItemCustomizationConfirmDialog *pDialog = new CTFRemoveItemCustomizationConfirmDialog( RefurbProp, m_pItem );
+ if ( pDialog )
+ {
+ pDialog->Show();
+ }
+ }
+
+ virtual const char *GetTitleLabelLocalizationToken() const { return "#TF_Item_RefurbishItemHeader"; }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles item selection while in tool selection mode
+//-----------------------------------------------------------------------------
+void CBackpackPanel::HandleToolItemSelection( CEconItemView *pItem )
+{
+ if ( !InToolSelectionMode() )
+ {
+ // must be in tool selection mode
+ Assert( InToolSelectionMode() );
+ return;
+ }
+
+ if ( pItem )
+ {
+ // Check if we should bring up the shuffle dialog instead of directly using the tool
+ static CSchemaAttributeDefHandle pAttrDef_CanShuffleCrateContents( "can shuffle crate contents" );
+ if ( pItem->FindAttribute( pAttrDef_CanShuffleCrateContents ) )
+ {
+ CInputStringForItemBackpackOverlayDialog *pDialog = vgui::SETUP_PANEL( new CInputStringForItemBackpackOverlayDialog( this, pItem, &m_ToolSelectionItem ) );
+ if ( pDialog )
+ {
+ pDialog->Show();
+ CancelToolSelection();
+ }
+ }
+ else if ( m_ToolSelectionItem.FindAttribute( pAttrDef_CanShuffleCrateContents ) )
+ {
+ CInputStringForItemBackpackOverlayDialog *pDialog = vgui::SETUP_PANEL( new CInputStringForItemBackpackOverlayDialog( this, &m_ToolSelectionItem, pItem ) );
+ if ( pDialog )
+ {
+ pDialog->Show();
+ CancelToolSelection();
+ }
+ }
+ // is a tool being applied onto this item
+ else if ( m_ToolSelectionItem.GetStaticData()->IsTool() && ApplyTool( this, &m_ToolSelectionItem, pItem ) )
+ {
+ CancelToolSelection();
+ UpdateModelPanels();
+ }
+ // is this item a tool that can be applied on a selected item
+ else if ( pItem->GetStaticData()->IsTool() && ApplyTool( this, pItem, &m_ToolSelectionItem ) )
+ {
+ CancelToolSelection();
+ UpdateModelPanels();
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tries to use the item. This might switch the panel to a tool item
+// selection mode, or launch the recipe crafting panel.
+//-----------------------------------------------------------------------------
+void CBackpackPanel::SetupToolSelectionItem()
+{
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i]->IsSelected() && m_pItemModelPanels[i]->HasItem() )
+ {
+ m_ToolSelectionItem = *m_pItemModelPanels[i]->GetItem();
+ break;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Open up the trade dialog
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoTradeToPlayer()
+{
+ OpenTradingStartDialog( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Use the trade dialog to send a gift to a player.
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoGiftToPlayer()
+{
+ OpenTradingStartDialog( this, &m_ToolSelectionItem );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Open up the overlay to sell the selected item
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoSellMarketplace()
+{
+ CUtlVector< CItemModelPanel* > m_vecSelected;
+ GetSelectedPanels( SELECT_FIRST, m_vecSelected );
+ Assert( m_vecSelected.Count() );
+ if( !m_vecSelected.Count() )
+ return;
+
+ if ( m_vecSelected.Count() && steamapicontext && steamapicontext->SteamFriends() && steamapicontext->SteamUtils() )
+ {
+ CEconItemView *pItem = m_vecSelected.Head()->GetItem();
+ const char *pszPrefix = "";
+ if ( GetUniverse() == k_EUniverseBeta )
+ {
+ pszPrefix = "beta.";
+ }
+ uint32 nAssetContext = 2; // k_EEconContextBackpack
+ char szURL[512];
+ V_snprintf( szURL, sizeof(szURL), "http://%ssteamcommunity.com/my/inventory/?sellOnLoad=1#%d_%d_%llu", pszPrefix, engine->GetAppID(), nAssetContext, pItem->GetItemID() );
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( szURL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Use a description tag, or offer to buy one
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoDescription()
+{
+ static CSchemaItemDefHandle pItemDef_DescTag( "Description Tag" );
+ if ( !AttemptToUseItem( pItemDef_DescTag->GetDefinitionIndex() ) )
+ {
+ AttemptToShowItemInStore( pItemDef_DescTag->GetDefinitionIndex() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Use a name tag, or offer to buy one
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoRename()
+{
+ static CSchemaItemDefHandle pItemDef_NameTag( "Name Tag" );
+ if ( !AttemptToUseItem( pItemDef_NameTag->GetDefinitionIndex() ) )
+ {
+ AttemptToShowItemInStore( pItemDef_NameTag->GetDefinitionIndex() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Delete the selected items
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoDelete()
+{
+ // Hide the mouseover panel
+ HideMouseOverPanel();
+
+ int iItemsToDelete = 0;
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i]->IsSelected() && m_pItemModelPanels[i]->HasItem() )
+ {
+ iItemsToDelete++;
+ }
+ }
+
+ // Bring up confirm dialog
+ CConfirmDeleteItemDialog *pConfirm = vgui::SETUP_PANEL( new CConfirmDeleteItemDialog( this, ( iItemsToDelete > 1 ) ) );
+ if ( pConfirm )
+ {
+ pConfirm->Show();
+
+ m_pConfirmDeleteDialog = pConfirm;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Use the selected tool
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoApplyOnItem()
+{
+ SetupToolSelectionItem();
+
+ if ( m_ToolSelectionItem.IsValid() )
+ {
+ const IEconTool *pEconTool = m_ToolSelectionItem.GetItemDefinition()->GetEconTool();
+
+ // Gather all quest objective attributes
+ CRecipeComponentMatchingIterator recipeIterator( NULL, NULL );
+ if ( m_ToolSelectionItem.GetSOCData() )
+ {
+ m_ToolSelectionItem.GetSOCData()->IterateAttributes( &recipeIterator );
+ }
+
+ if( pEconTool && recipeIterator.GetMatchingComponentInputs().Count() > 0 )
+ {
+ // Launch new crafting window if we need to
+ if( m_pDynamicRecipePanel == NULL )
+ {
+ m_pDynamicRecipePanel = vgui::SETUP_PANEL( new CDynamicRecipePanel( this, "dynamic_recipe_panel", &m_ToolSelectionItem ) );
+ }
+
+ // Set recipe item into panel
+ if ( m_pDynamicRecipePanel )
+ {
+ m_pDynamicRecipePanel->SetVisible( true );
+ m_pDynamicRecipePanel->SetNewRecipe( &m_ToolSelectionItem );
+ }
+ return;
+ }
+
+ // Check if we actually have any items we can use this tool on
+ bool bHasValidTargetItem = false;
+ CPlayerInventory *pInv = InventoryManager()->GetLocalInventory();
+ Assert( pInv );
+ if ( pInv )
+ {
+ for ( int i = 0 ; i < pInv->GetItemCount() ; ++i )
+ {
+ CEconItemView *pItem = pInv->GetItem( i );
+ if ( CEconSharedToolSupport::ToolCanApplyTo( &m_ToolSelectionItem, pItem ) )
+ {
+ bHasValidTargetItem = true;
+ break;
+ }
+ }
+
+ // If no applicable items, try stock items
+ if ( !bHasValidTargetItem )
+ {
+ // this is really inefficient, maybe have a list of baseitems somewhere
+ CEconItemView tempItem;
+ const CEconItemDefinition* pItemDef = NULL;
+
+ const CEconItemSchema::SortedItemDefinitionMap_t& mapItems = GetItemSchema()->GetSortedItemDefinitionMap();
+ for ( int it = mapItems.FirstInorder(); it != mapItems.InvalidIndex(); it = mapItems.NextInorder( it ) )
+ {
+ if ( mapItems[it]->IsBaseItem() && !mapItems[it]->IsHidden() )
+ {
+ CFmtStr fmtStrCustomizedDefName( "Upgradeable %s", mapItems[it]->GetDefinitionName() );
+ pItemDef = GetItemSchema()->GetItemDefinitionByName( fmtStrCustomizedDefName.Access() );
+ if ( pItemDef )
+ {
+ tempItem.Init( pItemDef->GetDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ if ( CEconSharedToolSupport::ToolCanApplyTo( &m_ToolSelectionItem, &tempItem ) )
+ {
+ bHasValidTargetItem = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( bHasValidTargetItem )
+ {
+ // automatically switch to stock items
+ OnCommand( "showbaseitems" );
+ }
+ }
+ }
+
+ if ( !bHasValidTargetItem )
+ {
+ ShowMessageBox( NULL, "#ToolNoTargetItems", "#GameUI_OK" );
+ return;
+ }
+
+ m_eSelectionMode = ToolSelection;
+ m_nLastToolPage = GetCurrentPage();
+
+ if ( m_pMouseOverTooltip )
+ {
+ m_pMouseOverTooltip->HideTooltip();
+ }
+
+ ClearNameFilter( true );
+ SetCurrentPage( 0 );
+ UpdateModelPanels();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: use a consumable item directly from the backpack
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoUseConsumableItem()
+{
+ SetupToolSelectionItem();
+
+#ifdef TF_CLIENT_DLL
+ UseConsumableItem( &m_ToolSelectionItem, this );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Use the tool to unwrap an item
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoUnwrapItem()
+{
+ DoUseConsumableItem();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Use the tool to deliver an item
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoDeliverItem()
+{
+ SetupToolSelectionItem();
+
+#ifdef TF_CLIENT_DLL
+ const CEconTool_WrappedGift *pWrappedGiftTool = m_ToolSelectionItem.GetItemDefinition()
+ ? m_ToolSelectionItem.GetItemDefinition()->GetTypedEconTool<CEconTool_WrappedGift>()
+ : NULL;
+
+ if ( pWrappedGiftTool && pWrappedGiftTool->BIsDirectGift() )
+ {
+ DoUseConsumableItem();
+ return;
+ }
+ else if ( pWrappedGiftTool && pWrappedGiftTool->BIsGlobalGift() )
+ {
+ // If this is a global gift, we don't let the user pick a target so we're done as of now.
+ extern void UseUntargetedGiftConfirm( bool bConfirmed, void *pContext );
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_DeliverGiftDialog_Title", "#TF_DeliverGiftDialog_Random_Text",
+ "#TF_DeliverGiftDialog_Confirm", "#TF_DeliverGiftDialog_Cancel",
+ &UseUntargetedGiftConfirm );
+
+ pDialog->SetContext( &m_ToolSelectionItem );
+ return;
+ }
+
+ DoGiftToPlayer();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Show
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoApplyByItem()
+{
+ SetupToolSelectionItem();
+
+ m_eSelectionMode = ToolSelection;
+ m_nLastToolPage = GetCurrentPage();
+
+ if ( m_pMouseOverTooltip )
+ {
+ m_pMouseOverTooltip->HideTooltip();
+ }
+
+ ClearNameFilter( true );
+ SetCurrentPage( 0 );
+ UpdateModelPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: open shuffle items dialog
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoShuffle()
+{
+ SetupToolSelectionItem();
+
+ CInputStringForItemBackpackOverlayDialog *pDialog = vgui::SETUP_PANEL( new CInputStringForItemBackpackOverlayDialog( this, &m_ToolSelectionItem ) );
+ if ( pDialog )
+ {
+ pDialog->Show();
+ }
+}
+
+void CBackpackPanel::DoEditSlot()
+{
+ SetupToolSelectionItem();
+
+ // Launch new slot window if we need to
+ if( m_pItemSlotPanel == NULL )
+ {
+ m_pItemSlotPanel = vgui::SETUP_PANEL( new CItemSlotPanel( this ) );
+ }
+
+ // Set item into panel
+ if ( m_pItemSlotPanel )
+ {
+ m_pItemSlotPanel->SetVisible( true );
+ m_pItemSlotPanel->SetItem( m_ToolSelectionItem.GetSOCData() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Refurbish item
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoRefurbishItem()
+{
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i]->IsSelected() && m_pItemModelPanels[i]->HasItem() )
+ {
+ m_ComboBoxOverlaySelectionItem = *m_pItemModelPanels[i]->GetItem();
+ break;
+ }
+ }
+
+ CRefurbishItemDialog *pRefurbishDialog = vgui::SETUP_PANEL( new CRefurbishItemDialog( this, &m_ComboBoxOverlaySelectionItem ) );
+ if ( pRefurbishDialog )
+ {
+ pRefurbishDialog->Show();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Deode by item
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoGetItemFromStore()
+{
+ SetupToolSelectionItem();
+
+ uint32 iDecoableItemDef = 0;
+ if ( GetDecodedByItemDefIndex( &m_ToolSelectionItem, &iDecoableItemDef ) )
+ {
+ // casting to the proper type since our econ system is dumb
+ const float& value_as_float = (float&)iDecoableItemDef;
+ CEconItemDefinition * pDefIndex = GetItemSchema()->GetItemDefinition( (int)value_as_float );
+
+ EconUI()->GetStorePanel()->AddToCartAndCheckoutImmediately( pDefIndex->GetDefinitionIndex() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Open End of the Line duck leaderboards
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoOpenDuckLeaderboards()
+{
+ CCharacterInfoPanel* pCharInfo = dynamic_cast< CCharacterInfoPanel* >( EconUI() );
+ CDucksLeaderboardManager *pDuckLeaderboards = vgui::SETUP_PANEL( new CDucksLeaderboardManager( pCharInfo, "DucksLeaderboardPanel" ) );
+ pDuckLeaderboards->SetVisible( true );
+}
+
+//-----------------------------------------------------------------------------
+// Strange Count Transfer Dialog
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoStrangeCountTransfer()
+{
+ CUtlVector< CItemModelPanel* > vecSelected;
+ GetSelectedPanels( SELECT_FIRST, vecSelected );
+
+ Assert( vecSelected.Count() );
+ if ( vecSelected.IsEmpty() )
+ return;
+
+ m_pStrangeToolPanel = vgui::SETUP_PANEL( new CStrangeCountTransferPanel( this, vecSelected[0]->GetItem() ) );
+}
+
+//-----------------------------------------------------------------------------
+// Collection crafting
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoCraftUpCollection()
+{
+ CUtlVector< CItemModelPanel* > vecSelected;
+ GetSelectedPanels( SELECT_ALL, vecSelected );
+
+ //Assert( vecSelected.Count() );
+ //if ( vecSelected.IsEmpty() )
+ // return;
+
+ // Get all the items that were selected
+ CUtlVector< const CEconItemView* > vecSelectedItems;
+ FOR_EACH_VEC( vecSelected, i )
+ {
+ if ( vecSelected[ i ]->GetItem() && GetCollectionCraftingInvalidReason( vecSelected[ i ]->GetItem(), NULL ) == NULL )
+ {
+ vecSelectedItems.AddToTail( vecSelected[ i ]->GetItem() );
+ }
+ }
+
+ // For tracking how many times they've opened this menu
+ tf_trade_up_use_count.SetValue( tf_trade_up_use_count.GetInt() - 1 );
+
+ // Open it up!
+ GetCollectionCraftPanel()->Show( vecSelectedItems );
+}
+
+//-----------------------------------------------------------------------------
+// Collection crafting
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoHalloweenOffering()
+{
+ // Open it up!
+ if ( !m_pHalloweenOfferingPanel )
+ {
+ m_pHalloweenOfferingPanel = vgui::SETUP_PANEL( new CHalloweenOfferingPanel( this, m_pMouseOverTooltip ) );
+ m_pHalloweenOfferingPanel->InvalidateLayout( true, true );
+ }
+ // empty
+ CUtlVector< const CEconItemView* > vecSelectedItems;
+ m_pHalloweenOfferingPanel->Show( vecSelectedItems );
+}
+
+//-----------------------------------------------------------------------------
+// Craft Common StatClock
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoCraftCommonStatClock()
+{
+ // Open it up!
+ if ( !m_pMannCoTradePanel )
+ {
+ m_pMannCoTradePanel = vgui::SETUP_PANEL( new CCraftCommonStatClockPanel( this, m_pMouseOverTooltip ) ); // make this more generic
+ m_pMannCoTradePanel->InvalidateLayout( true, true );
+ }
+
+ CUtlVector< CItemModelPanel* > vecSelected;
+ GetSelectedPanels(SELECT_ALL, vecSelected);
+
+ CUtlVector< const CEconItemView* > vecSelectedItems;
+ FOR_EACH_VEC(vecSelected, i)
+ {
+ if (vecSelected[i]->GetItem() && GetCraftCommonStatClockInvalidReason(vecSelected[i]->GetItem(), NULL) == NULL)
+ {
+ vecSelectedItems.AddToTail(vecSelected[i]->GetItem());
+ }
+ }
+
+ m_pMannCoTradePanel->Show( vecSelectedItems );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Bring up the 3D inspect panel for the selected item
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoInspectModel()
+{
+ CUtlVector< CItemModelPanel* > vecSelected;
+ GetSelectedPanels( SELECT_FIRST, vecSelected );
+
+ if ( vecSelected.IsEmpty() )
+ return;
+
+ CEconItemView *pItem = vecSelected[0]->GetItem();
+ if ( pItem )
+ {
+ float flInspect = 0;
+ static CSchemaAttributeDefHandle pAttrib_WeaponAllowInspect( "weapon_allow_inspect" );
+ if ( ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttrib_WeaponAllowInspect, &flInspect ) && flInspect != 0.f )
+#ifdef STAGING_ONLY
+ || tf_weapon_force_allow_inspect.GetBool()
+#endif
+ )
+ {
+ m_pInspectPanel->SetVisible( true );
+ m_pInspectPanel->SetItemCopy( vecSelected[0]->GetItem() );
+ }
+ else
+ {
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; ++iClass )
+ {
+ if ( pItem->GetStaticData()->CanBeUsedByClass( iClass ) )
+ {
+ m_pInspectCosmeticPanel->PreviewItem( iClass, pItem );
+ break;
+ }
+ }
+ m_pInspectCosmeticPanel->SetVisible( true );
+ }
+ }
+}
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OpenInspectModelPanelAndCopyItem( CEconItemView *pItemView )
+{
+ if ( !pItemView )
+ return;
+
+ EconUI()->OpenEconUI( ECONUI_BACKPACK );
+
+ // Figure out which preview to show
+ float flInspect = 0;
+ static CSchemaAttributeDefHandle pAttrib_WeaponAllowInspect( "weapon_allow_inspect" );
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItemView, pAttrib_WeaponAllowInspect, &flInspect ) && flInspect != 0.f )
+ {
+ m_pInspectPanel->SetVisible( true );
+ m_pInspectPanel->SetItemCopy( pItemView );
+ }
+ else
+ {
+ for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; ++iClass )
+ {
+ if ( pItemView->GetStaticData()->CanBeUsedByClass( iClass ) )
+ {
+ m_pInspectCosmeticPanel->PreviewItemCopy( iClass, pItemView );
+ m_pInspectCosmeticPanel->SetVisible( true );
+ break;
+ }
+ }
+ }
+}
+
+CCollectionCraftingPanel* CBackpackPanel::GetCollectionCraftPanel()
+{
+ if ( !m_pCollectionCraftPanel )
+ {
+ m_pCollectionCraftPanel = vgui::SETUP_PANEL( new CCollectionCraftingPanel( this, m_pMouseOverTooltip ) );
+ m_pCollectionCraftPanel->InvalidateLayout( true, true );
+ }
+
+ return m_pCollectionCraftPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Buy a key, and then immediately use it on the selected crate once
+// tbe store transaction completes
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoBuyKeyAndOpenCrate()
+{
+ CUtlVector< CItemModelPanel* > vecSelected;
+ GetSelectedPanels( SELECT_FIRST, vecSelected );
+
+ if ( vecSelected.IsEmpty() )
+ return;
+
+ m_hQuickOpenCrate.SetItem( vecSelected.Head()->GetItem() );
+
+ uint32 iDecoableItemDef = 0;
+ if ( GetDecodedByItemDefIndex( m_hQuickOpenCrate, &iDecoableItemDef ) )
+ {
+ // casting to the proper type since our econ system is dumb
+ const float& value_as_float = (float&)iDecoableItemDef;
+ CEconItemDefinition * pDefIndex = GetItemSchema()->GetItemDefinition( (int)value_as_float );
+
+ EconUI()->GetStorePanel()->AddToCartAndCheckoutImmediately( pDefIndex->GetDefinitionIndex() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the first compatible key in our inventory, and use it on the
+// selected crate
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoOpenCrateWithKey()
+{
+ CUtlVector< CItemModelPanel* > vecSelected;
+ GetSelectedPanels( SELECT_FIRST, vecSelected );
+
+ if ( vecSelected.IsEmpty() )
+ return;
+
+ CEconItemView *pCrate = vecSelected.Head()->GetItem();
+
+ CEconItemView *pKey = GetFirstCompatibleKeyForCrate( pCrate );
+ if ( !pKey )
+ return;
+
+ ApplyTool( this, pKey, pCrate );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Open up the loadout for a class
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoEquipForClass( int nClass )
+{
+ // Negative because reasons
+ EconUI()->OpenEconUI( -nClass );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Given a paint can index, offer to use one or buy one
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoPaint( int nPaintItemIndex, bool bUseStore, bool bUseMarket )
+{
+ if ( !bUseStore && !bUseMarket )
+ {
+ AttemptToUseItem( nPaintItemIndex );
+ }
+
+ if ( bUseStore )
+ {
+ AttemptToShowItemInStore( nPaintItemIndex );
+ }
+ else if ( bUseMarket )
+ {
+ AttemptToShowItemInMarket( nPaintItemIndex );
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose: Given a strange part index, offer to use one or buy one (Market)
+//-----------------------------------------------------------------------------
+void CBackpackPanel::DoStrangePart( int nStrangePartIndex, bool bUseMarket )
+{
+ if ( !bUseMarket )
+ {
+ AttemptToUseItem( nStrangePartIndex );
+ }
+
+ if ( bUseMarket )
+ {
+ AttemptToShowItemInMarket( nStrangePartIndex );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Try to find the first item with the passed-in item name in our inventory
+// and try to use it on the selected panel's item. If we dont have an item
+// matching the name passed in, go to the store and prompt the user to buy one.
+//-----------------------------------------------------------------------------
+bool CBackpackPanel::AttemptToUseItem( item_definition_index_t iItemDefIndex )
+{
+ CUtlVector< CItemModelPanel* > m_vecSelected;
+ GetSelectedPanels( SELECT_FIRST, m_vecSelected );
+ Assert( m_vecSelected.Count() );
+ if ( !m_vecSelected.Count() )
+ return false;
+
+ CEconItemView *pSelectedItem = m_vecSelected.Head()->GetItem();
+ Assert( pSelectedItem );
+ if ( !pSelectedItem )
+ return false;
+
+ CEconItemView *pNameTag = CTFPlayerInventory::GetFirstItemOfItemDef( iItemDefIndex );
+ if ( pNameTag )
+ {
+ if ( ApplyTool( this, pNameTag, pSelectedItem ) )
+ {
+ CancelToolSelection();
+ UpdateModelPanels();
+ }
+ return true;
+ }
+ return false;
+}
+//-----------------------------------------------------------------------------
+void CBackpackPanel::AttemptToShowItemInStore( item_definition_index_t iItemDefIndex )
+{
+ CUtlVector< CItemModelPanel* > m_vecSelected;
+ GetSelectedPanels( SELECT_FIRST, m_vecSelected );
+ Assert( m_vecSelected.Count() );
+ if( !m_vecSelected.Count() )
+ return;
+
+ CEconItemView *pSelectedItem = m_vecSelected.Head()->GetItem();
+ Assert( pSelectedItem );
+ if ( !pSelectedItem )
+ return;
+
+ EconUI()->OpenStorePanel( iItemDefIndex, false );
+}
+//-----------------------------------------------------------------------------
+void CBackpackPanel::AttemptToShowItemInMarket( item_definition_index_t iItemDefIndex )
+{
+ CUtlVector< CItemModelPanel* > m_vecSelected;
+ GetSelectedPanels( SELECT_FIRST, m_vecSelected );
+ Assert( m_vecSelected.Count() );
+ if ( !m_vecSelected.Count() )
+ return;
+
+ CEconItemView *pSelectedItem = m_vecSelected.Head()->GetItem();
+ Assert( pSelectedItem );
+ if ( !pSelectedItem )
+ return;
+
+ CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( iItemDefIndex );
+ Assert( pItemDef );
+ if ( !pItemDef )
+ return;
+
+ if ( !CBaseAdPanel::CheckForRequiredSteamComponents( "#StoreUpdate_SteamRequired", "#MMenu_OverlayRequired" ) )
+ return;
+
+ if ( pItemDef && steamapicontext && steamapicontext->SteamFriends() )
+ {
+ const char *pszPrefix = "";
+ if ( GetUniverse() == k_EUniverseBeta )
+ {
+ pszPrefix = "beta.";
+ }
+
+ static char pszItemName[256];
+ g_pVGuiLocalize->ConvertUnicodeToANSI( g_pVGuiLocalize->Find( pItemDef->GetItemBaseName() ), pszItemName, sizeof( pszItemName ) );
+
+ char szURL[512];
+ V_snprintf( szURL, sizeof( szURL ), "http://%ssteamcommunity.com/market/listings/%d/%s", pszPrefix, engine->GetAppID(), pszItemName );
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( szURL );
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose: Get the first, or all selected item model panels
+//-----------------------------------------------------------------------------
+void CBackpackPanel::GetSelectedPanels( ESelection eSelection, CUtlVector< CItemModelPanel* >& m_vecSelected ) const
+{
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i]->IsSelected() && m_pItemModelPanels[i]->HasItem() )
+ {
+ m_vecSelected.AddToTail( m_pItemModelPanels[i] );
+
+ if ( eSelection == SELECT_FIRST )
+ {
+ return;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OnCommand( const char *command )
+{
+ if ( V_strncasecmp( command, "goto_page_", V_strlen( "goto_page_" ) ) == 0 )
+ {
+ int iPage = V_atoi( &command[ V_strlen( "goto_page_" ) ] );
+ SetCurrentPage( iPage );
+ UpdateModelPanels();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "nextpage", 8 ) )
+ {
+ if ( !m_bDragging )
+ {
+ DeSelectAllBackpackItemPanels();
+ }
+
+ SetCurrentPage( GetCurrentPage() + 1 );
+ UpdateModelPanels();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "prevpage", 8 ) )
+ {
+ if ( !m_bDragging )
+ {
+ DeSelectAllBackpackItemPanels();
+ }
+
+ SetCurrentPage( GetCurrentPage() - 1 );
+ UpdateModelPanels();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "useitem", 7 ) )
+ {
+ AssertMsg( 0, "Everything should be going through the context menu. Fix the calling code." );
+ return;
+ }
+ else if ( !Q_strnicmp( command, "showbackpackitems", 17 ) )
+ {
+ SetShowBaseItems( false );
+ if ( m_pShowBaseItemsCheckbox )
+ {
+ m_pShowBaseItemsCheckbox->SetSelected( false );
+ }
+ return;
+ }
+ else if ( !Q_strnicmp( command, "showbaseitems", 13 ) )
+ {
+ SetShowBaseItems( true );
+ if ( m_pShowBaseItemsCheckbox )
+ {
+ m_pShowBaseItemsCheckbox->SetSelected( true );
+ }
+ return;
+ }
+ else if ( !Q_strnicmp( command, "canceltool", 10 ) )
+ {
+ CancelToolSelection();
+ UpdateModelPanels();
+ return;
+ }
+ else if ( !Q_stricmp( command, "show_explanations" ) )
+ {
+ if ( !m_flStartExplanationsAt )
+ {
+ m_flStartExplanationsAt = Plat_FloatTime();
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+ }
+ RequestFocus();
+ }
+ else if ( !Q_stricmp( command, "showdetails" ) )
+ {
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i]->IsSelected() && m_pItemModelPanels[i]->HasItem() )
+ {
+ OpenArmory( m_pItemModelPanels[i]->GetItem() );
+ break;
+ }
+ }
+ UpdateModelPanels();
+ return;
+ }
+ else if ( !V_strnicmp( command, "equipclass", 10 ) )
+ {
+ int nClass = atoi( command + 10 );
+ DoEquipForClass( nClass );
+ }
+ else if ( !V_strnicmp( command, "paint", 5 ) )
+ {
+ int nIndex = atoi( command + 5 );
+ DoPaint( nIndex, false, false );
+ }
+ else if ( !V_strnicmp( command, "market_paint", 12 ) )
+ {
+ int nIndex = atoi( command + 12 );
+ DoPaint( nIndex, false, true );
+ }
+ else if ( !V_strnicmp( command, "store_paint", 11 ) )
+ {
+ int nIndex = atoi( command + 11 );
+ DoPaint( nIndex, true, false );
+ }
+ else if ( !V_strnicmp( command, "strangepart_", 12 ) )
+ {
+ int nIndex = atoi( command + 12 );
+ DoStrangePart( nIndex, false );
+ }
+ else if ( !V_strnicmp( command, "market_strangepart_", 19 ) )
+ {
+ int nIndex = atoi( command + 19 );
+ DoStrangePart( nIndex, true );
+ }
+ else if ( !V_strnicmp( command, "Context_CraftUpCollection", 25 ) )
+ {
+ DoCraftUpCollection();
+ }
+ else if ( !V_strnicmp( command, "Context_CraftCommonStatClock", 25 ) )
+ {
+ DoCraftCommonStatClock();
+ }
+#ifdef STAGING_ONLY
+ else if ( !V_strnicmp( command, "unpin", 5 ) )
+ {
+ m_pMouseOverCardPanel->PinCard( false );
+ }
+#endif
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::OpenArmory( CEconItemView* item )
+{
+ PostMessage( GetParent()->GetParent()->GetParent(), new KeyValues("OpenArmoryDirect", "itemdef", item->GetItemDefIndex() ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::CancelToolSelection( void )
+{
+ if ( m_eSelectionMode == StandardSelection )
+ return;
+
+ m_eSelectionMode = StandardSelection;
+
+ ClearNameFilter( false );
+
+ OnCommand( "showbackpackitems" );
+
+ SetCurrentPage( m_nLastToolPage );
+ m_nLastToolPage = 0;
+
+ m_ToolSelectionItem.Invalidate();
+ if ( m_pToolIcon )
+ {
+ m_pToolIcon->SetVisible( false );
+ }
+ DeSelectAllBackpackItemPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::SetShowBaseItems( bool bShow )
+{
+ bool bGoToFirstPage = m_bShowBaseItems != bShow;
+ m_bShowBaseItems = bShow;
+ if( bGoToFirstPage )
+ {
+ SetCurrentPage( 0 );
+ }
+
+ DeSelectAllBackpackItemPanels();
+ if ( m_pToolIcon )
+ {
+ m_pToolIcon->SetVisible( false );
+ }
+ UpdateModelPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+ConVar *CBackpackPanel::GetExplanationConVar( void )
+{
+ return &tf_explanations_backpackpanel;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBackpackPanel::SetCurrentPage( int nNewPage )
+{
+ if( m_pToolIcon )
+ {
+ m_pToolIcon->SetVisible( false );
+ }
+
+ if ( nNewPage < 0 )
+ {
+ nNewPage = GetNumPages() - 1;
+ }
+ else if ( nNewPage >= GetNumPages() )
+ {
+ nNewPage = 0;
+ }
+
+ // deselect old page button
+ if ( m_Pages.Count() > GetCurrentPage() && m_Pages[GetCurrentPage()] )
+ {
+ CExButton* pButton = dynamic_cast<CExButton*>( m_Pages[GetCurrentPage()]->FindChildByName( "Button" ) );
+ if ( pButton )
+ {
+ pButton->SetSelected( false );
+ }
+ }
+
+ BaseClass::SetCurrentPage( nNewPage );
+
+ // mark new page button as selected
+ if ( m_Pages.Count() > GetCurrentPage() && m_Pages[GetCurrentPage()] )
+ {
+ CExButton* pButton = dynamic_cast<CExButton*>( m_Pages[GetCurrentPage()]->FindChildByName( "Button" ) );
+ if ( pButton )
+ {
+ pButton->SetSelected( true );
+ }
+ }
+}
+
+
+int CBackpackPanel::GetItemQualityForBorder( CItemModelPanel* pItemPanel ) const
+{
+ if ( pItemPanel->HasItem() && ( cl_showbackpackrarities.GetInt() > 0 || m_bForceShowBackpackRarities )
+ && ( cl_showbackpackrarities.GetInt() < 2 || pItemPanel->GetItem()->IsMarketable() ) )
+ {
+ uint8 nRarity = pItemPanel->GetItem()->GetItemDefinition()->GetRarity();
+ if ( ( nRarity != k_unItemRarity_Any ) && ( pItemPanel->GetItem()->GetItemQuality() != AE_SELFMADE ) )
+ {
+ // translate this quality to rarity
+ return nRarity + AE_RARITY_DEFAULT;
+ }
+
+ return pItemPanel->GetItem()->GetItemQuality();
+ }
+
+ return 0;
+}
diff --git a/game/client/econ/backpack_panel.h b/game/client/econ/backpack_panel.h
new file mode 100644
index 0000000..49ea3c3
--- /dev/null
+++ b/game/client/econ/backpack_panel.h
@@ -0,0 +1,268 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef BACKPACK_PANEL_H
+#define BACKPACK_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "base_loadout_panel.h"
+#include "tf_item_inspection_panel.h"
+
+#define BACKPACK_SLOTS_PER_PAGE 50
+#define BACKPACK_ROWS 5
+#define BACKPACK_COLUMNS (BACKPACK_SLOTS_PER_PAGE / BACKPACK_ROWS)
+#define BACKPACK_MAX_PAGES (MAX_NUM_BACKPACK_SLOTS / BACKPACK_SLOTS_PER_PAGE)
+
+class CDynamicRecipePanel;
+class CItemSlotPanel;
+class CStrangeCountTransferPanel;
+class CCollectionCraftingPanel;
+class CHalloweenOfferingPanel;
+class CCraftCommonStatClockPanel;
+class CTFStorePreviewItemPanel2;
+
+//-----------------------------------------------------------------------------
+// An inventory screen that handles displaying the backpack
+//-----------------------------------------------------------------------------
+class CBackpackPanel : public CBaseLoadoutPanel
+{
+ DECLARE_CLASS_SIMPLE( CBackpackPanel, CBaseLoadoutPanel );
+public:
+ CBackpackPanel( vgui::Panel *parent, const char *panelName );
+ virtual ~CBackpackPanel();
+
+ virtual const char *GetResFile( void ) { return "Resource/UI/econ/BackpackPanel.res"; }
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout( void ) OVERRIDE;
+ virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
+
+ virtual void UpdateModelPanels( void );
+ virtual int GetNumItemPanels( void ) { return BACKPACK_SLOTS_PER_PAGE; };
+ virtual void OnShowPanel( bool bVisible, bool bReturningFromArmory );
+ virtual void PostShowPanel( bool bVisible );
+ virtual bool UsesRarityControls( void ) { return true; }
+ virtual bool AllowSelection( void ) { return true; }
+ virtual bool AllowDragging( CItemModelPanel *panel ) { return true; }
+
+ virtual int GetNumSlotsPerPage( void ) OVERRIDE { return BACKPACK_SLOTS_PER_PAGE; }
+ virtual int GetNumColumns( void ) OVERRIDE { return BACKPACK_COLUMNS; }
+ virtual int GetNumRows( void ) OVERRIDE { return BACKPACK_ROWS; }
+ virtual int GetNumPages( void ) OVERRIDE;
+ virtual void SetCurrentPage( int nNewPage ) OVERRIDE;
+
+ virtual void AssignItemToPanel( CItemModelPanel *pPanel, int iIndex );
+
+ virtual void OnItemPanelEntered( vgui::Panel *panel ) OVERRIDE;
+ virtual void OpenContextMenu();
+ MESSAGE_FUNC_PTR( OnItemPanelMousePressed, "ItemPanelMousePressed", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelMouseRightRelease, "ItemPanelMouseRightRelease", panel );
+ MESSAGE_FUNC_INT_INT( OnCursorMoved, "OnCursorMoved", x, y );
+ MESSAGE_FUNC_INT_INT( OnItemPanelCursorMoved, "ItemPanelCursorMoved", x, y );
+ MESSAGE_FUNC_PARAMS( OnConfirmDelete, "ConfirmDlgResult", data );
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+ MESSAGE_FUNC_PARAMS( OnButtonChecked, "CheckButtonChecked", pData );
+ MESSAGE_FUNC( OnCancelSelection, "CancelSelection" );
+ MESSAGE_FUNC( DoTradeToPlayer, "DoTradeToPlayer" );
+ MESSAGE_FUNC( DoSellMarketplace, "DoSellMarketplace" );
+ MESSAGE_FUNC( DoDescription, "DoDescription" );
+ MESSAGE_FUNC( DoRename, "DoRename" );
+ MESSAGE_FUNC( DoDelete, "DoDelete" );
+ MESSAGE_FUNC( DoApplyOnItem, "Context_ApplyOnItem" );
+ MESSAGE_FUNC( DoUseConsumableItem, "Context_UseConsumableItem" );
+ MESSAGE_FUNC( DoUnwrapItem, "Context_UnwrapItem" );
+ MESSAGE_FUNC( DoDeliverItem, "Context_DeliverItem" );
+ MESSAGE_FUNC( DoApplyByItem, "Context_ApplyByItem" );
+ MESSAGE_FUNC( DoShuffle, "Context_Shuffle" );
+ MESSAGE_FUNC( DoEditSlot, "Context_EditSlot" );
+ MESSAGE_FUNC( DoRefurbishItem, "Context_RefurbishItem" );
+ MESSAGE_FUNC( DoGetItemFromStore, "Context_GetItemFromStore" );
+ MESSAGE_FUNC( DoOpenDuckLeaderboards, "Context_OpenDuckLeaderboards" );
+ MESSAGE_FUNC( DoInspectModel, "Context_InspectModel" );
+ MESSAGE_FUNC( DoBuyKeyAndOpenCrate, "Context_BuyKeyAndOpenCrate" );
+ MESSAGE_FUNC( DoOpenCrateWithKey, "Context_OpenCrateWithKey" );
+ MESSAGE_FUNC( DoStrangeCountTransfer, "Context_OpenStrangeCountTransfer" );
+ MESSAGE_FUNC( DoCraftUpCollection, "Context_CraftUpCollection" );
+ MESSAGE_FUNC( DoHalloweenOffering, "Context_HalloweenOffering" );
+ MESSAGE_FUNC( DoCraftCommonStatClock, "Context_CraftCommonStatClock" );
+ void DoEquipForClass( int nClass );
+ void DoPaint( int nPaintItemIndex, bool bUseStore, bool bUseMarket );
+ void DoStrangePart( int nStrangePartIndex, bool bUseMarket );
+ enum ESelection
+ {
+ SELECT_FIRST,
+ SELECT_ALL
+ };
+ bool AttemptToUseItem( item_definition_index_t iItemDefIndex );
+ void AttemptToShowItemInStore( item_definition_index_t iItemDefIndex );
+ void AttemptToShowItemInMarket( item_definition_index_t iItemDefIndex );
+ void GetSelectedPanels( ESelection eSelection, CUtlVector< CItemModelPanel* >& m_vecSelected ) const;
+ virtual void OnCommand( const char *command );
+ virtual void OnTick( void );
+ virtual void OnThink( void );
+ virtual void OnKeyCodePressed( vgui::KeyCode code ) OVERRIDE;
+ virtual void OnKeyCodeReleased( vgui::KeyCode code ) OVERRIDE;
+ virtual void OnKeyCodeTyped(vgui::KeyCode code) OVERRIDE;
+
+ virtual void OnMouseReleased(vgui::MouseCode code) OVERRIDE;
+ virtual void OnMouseMismatchedRelease( vgui::MouseCode code, Panel* pPressedPanel ) OVERRIDE;
+ virtual void OnMouseCaptureLost() OVERRIDE;
+
+ void OnItemContentsChanged( CEconItemView *pEconItemView );
+
+ virtual void OpenArmory( CEconItemView* item );
+
+ void ToggleSelectBackpackItemPanel( CItemModelPanel *pPanel );
+ void DeSelectAllBackpackItemPanels( void );
+
+ CEconItemView* GetComboBoxOverlayUISeletionItem() { return &m_ComboBoxOverlaySelectionItem; }
+ void SetComboBoxOverlaySelectionItem( const CEconItemView *pEconItemView ) { m_ComboBoxOverlaySelectionItem = *pEconItemView; }
+
+ void SetCurrentTransactionID( uint64 nTxnID );
+ void CheckForQuickOpenKey();
+
+ void MarkItemIDDirty( itemid_t itemID );
+
+ void OpenInspectModelPanelAndCopyItem( CEconItemView *pItemView );
+ CCollectionCraftingPanel *GetCollectionCraftPanel();
+
+protected:
+ virtual void StartDrag( int x, int y );
+ virtual void StopDrag( bool bSucceeded );
+ virtual bool CanDragTo( CItemModelPanel *pItemPanel, int iPanelIndex ) { return true; }
+ virtual void HandleDragTo( CItemModelPanel *pItemPanel, int iPanelIndex );
+ virtual int GetBackpackPosForPanelIndex( int iPanelIndex ) { return iPanelIndex + 1 + (GetCurrentPage() * GetNumSlotsPerPage()); }
+ virtual bool NeedsDerivedTickSignal( void ) { return false; }
+
+ int GetBackpackPositionForPanel( CItemModelPanel *pItemPanel );
+ virtual const char *GetGreyOutItemPanelReason( CItemModelPanel *pItemPanel );
+ virtual void SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver );
+ virtual bool IsIgnoringItemPanelEnters( void ) { return m_bDragging; }
+ virtual void AddNewItemPanel( int iPanelIndex );
+ virtual CItemModelPanel *GetItemPanelAtPos( int x, int y );
+ virtual void PositionItemPanel( CItemModelPanel *pPanel, int iIndex );
+
+ void CancelToolSelection( void );
+ void SetShowBaseItems( bool bShow );
+
+ virtual ConVar *GetExplanationConVar( void );
+ bool ShouldShowExplanations( void ) { return (!m_bItemsOnly && !InToolSelectionMode()); }
+
+ bool InToolSelectionMode() const { return m_eSelectionMode != StandardSelection; }
+ void SetupToolSelectionItem();
+ void HandleToolItemSelection( CEconItemView *pItem );
+
+ void ClearNameFilter( bool bUpdateModelPanels );
+ bool HasNameFilter() const { return m_wNameFilter.Count() > 0; }
+ const wchar_t* GetNameFilter() const { return HasNameFilter() ? m_wNameFilter.Base() : NULL; }
+ void UpdateFilteringItems();
+
+ int GetItemQualityForBorder( CItemModelPanel* pItemPanel ) const;
+
+ int GetNumMaxPages() const { return BACKPACK_MAX_PAGES; }
+ int GetPageButtonIndexAtPos( int x, int y );
+ void SetPageButtonTextColorBasedOnContents();
+
+ void AddPaintToContextMenu( Menu *pPaintSubMenu, item_definition_index_t iPaintDef, bool bAddCommerce );
+ void AddCommerceToContextMenu( Menu *pMenu, const char* pszActionFmt, item_definition_index_t iItemDefIndex, bool bAddMarket, bool bAddStore );
+ void AddCommerceSubmenus( Menu *pSubMenu, item_definition_index_t iItemDef, const char* pszActionFmt );
+ void DoGiftToPlayer( );
+
+protected:
+ vgui::TextEntry *m_pNameFilterTextEntry;
+ CUtlVector<wchar_t> m_wNameFilter;
+ float m_flFilterItemTime;
+ CUtlMap< int, CEconItemView*, int > m_mapFilteringItems;
+ CUtlMap< itemid_t, char > m_mapSeenItems;
+ bool m_bInitializedSeenItems;
+ CUtlVector< itemid_t > m_vecDirtyItems;
+
+ CExButton *m_pNextPageButton;
+ CExButton *m_pPrevPageButton;
+ CExButton *m_pShowExplanationsButton;
+ vgui::Label *m_pCurPageLabel;
+ vgui::ComboBox *m_pSortByComboBox;
+ vgui::ComboBox *m_pShowRarityComboBox;
+ vgui::CheckButton *m_pShowBaseItemsCheckbox;
+ CExButton *m_pDragToNextPageButton;
+ CExButton *m_pDragToPrevPageButton;
+ float m_flPreventDragPageSwitchUntil;
+ float m_flStartExplanationsAt;
+
+ // Dragging support
+ float m_flMouseDownTime;
+ int m_iMouseDownX;
+ int m_iMouseDownY;
+ CItemModelPanel *m_pItemDraggedFromPanel;
+ int m_iDraggedFromPage;
+ bool m_bMouseDownOnItemPanel;
+ bool m_bDragging;
+ CItemModelPanel *m_pMouseDragItemPanel;
+ int m_iDragOffsetX;
+ int m_iDragOffsetY;
+ CItemModelPanel *m_pPrevDragOverItemPanel;
+
+ // Deletion
+ vgui::EditablePanel *m_pConfirmDeleteDialog;
+
+ // Tool support
+ enum SelectionMode_t
+ {
+ StandardSelection,
+ ToolSelection,
+ };
+ SelectionMode_t m_eSelectionMode;
+ int m_nLastToolPage;
+ CEconItemView m_ToolSelectionItem;
+ CExButton *m_pCancelToolButton;
+ vgui::ScalableImagePanel *m_pToolIcon;
+
+ CEconItemView m_ComboBoxOverlaySelectionItem;
+
+ CExButton *m_pCraftButton;
+
+ // base items or backpack items
+ bool m_bShowBaseItems;
+
+ // positions of all our item panels, so we can handle drag & drop
+ struct backpackitempos_t
+ {
+ int x,y;
+ };
+ CUtlVector<backpackitempos_t> m_ItemModelPanelPos;
+
+ KeyValues *m_pPageButtonKVs;
+ int m_nNumActivePages;
+ CUtlVector< EditablePanel* > m_Pages;
+ CUtlVector<backpackitempos_t> m_PageButtonPos;
+
+ CDynamicRecipePanel* m_pDynamicRecipePanel;
+ CItemSlotPanel* m_pItemSlotPanel;
+ CUtlVector< item_definition_index_t > m_vecPaintCans;
+ CUtlVector< item_definition_index_t > m_vecStrangeParts;
+
+ DHANDLE<CStrangeCountTransferPanel> m_pStrangeToolPanel;
+ DHANDLE<CCollectionCraftingPanel> m_pCollectionCraftPanel;
+ DHANDLE<CHalloweenOfferingPanel> m_pHalloweenOfferingPanel;
+ DHANDLE<CCraftCommonStatClockPanel> m_pMannCoTradePanel; // Make this Panel Generic
+ CTFItemInspectionPanel *m_pInspectPanel;
+ CTFStorePreviewItemPanel2 *m_pInspectCosmeticPanel;
+ vgui::Menu *m_pContextMenu;
+ CEconItemViewHandle m_hQuickOpenCrate;
+ uint64 m_nQuickOpenTxn;
+
+ CPanelAnimationVarAliasType( int, m_iPageButtonYPos, "page_button_y", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iPageButtonXDelta, "page_button_x_delta", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iPageButtonYDelta, "page_button_y_delta", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iPageButtonPerRow, "page_button_per_row", "20", "int" );
+ CPanelAnimationVarAliasType( int, m_iPageButtonHeight, "page_button_height", "0", "proportional_int" );
+};
+
+#endif // BACKPACK_PANEL_H
diff --git a/game/client/econ/base_loadout_panel.cpp b/game/client/econ/base_loadout_panel.cpp
new file mode 100644
index 0000000..d4a64e5
--- /dev/null
+++ b/game/client/econ/base_loadout_panel.cpp
@@ -0,0 +1,803 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "base_loadout_panel.h"
+#include "item_confirm_delete_dialog.h"
+#include "vgui/ISurface.h"
+#include "gamestringpool.h"
+#include "iclientmode.h"
+#include "econ_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/IInput.h"
+#include "econ_ui.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+#ifdef STAGING_ONLY
+ConVar tf_use_card_tooltips( "tf_use_card_tooltips", "0", FCVAR_ARCHIVE );
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseLoadoutPanel::CBaseLoadoutPanel( vgui::Panel *parent, const char *panelName ) : EditablePanel(parent, panelName )
+{
+ SetParent( parent );
+
+ // Use the client scheme
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ m_pItemModelPanelKVs = NULL;
+ m_pMouseOverItemPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "mouseoveritempanel" ) );
+ m_pMouseOverTooltip = new CItemModelPanelToolTip( this );
+ m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel );
+
+#ifdef STAGING_ONLY
+ m_pMouseOverCardPanel = vgui::SETUP_PANEL( new CTFItemCardPanel( this, "mouseovercardpanel" ) );
+ m_pMouseOverCardTooltip = new CItemCardPanelToolTip( this );
+ m_pMouseOverCardTooltip->SetupPanels( this, m_pMouseOverCardPanel );
+#endif
+
+ m_pItemPanelBeingMousedOver = NULL;
+ m_pCaratLabel = NULL;
+ m_pClassLabel = NULL;
+ m_nCurrentPage = 0;
+ m_bTooltipKeyPressed = false;
+
+ SetMouseInputEnabled( true );
+ SetKeyBoardInputEnabled( true );
+
+ ListenForGameEvent( "inventory_updated" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseLoadoutPanel::~CBaseLoadoutPanel()
+{
+ if ( m_pItemModelPanelKVs )
+ {
+ m_pItemModelPanelKVs->deleteThis();
+ m_pItemModelPanelKVs = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pCaratLabel = dynamic_cast<vgui::Label*>( FindChildByName("CaratLabel") );
+ m_pClassLabel = dynamic_cast<vgui::Label*>( FindChildByName("ClassLabel") );
+
+ m_bReapplyItemKVs = true;
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ SetBorderForItem( m_pItemModelPanels[i], false );
+ }
+
+ m_pMouseOverItemPanel->SetBorder( pScheme->GetBorder("LoadoutItemPopupBorder") );
+
+ CreateItemPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "modelpanels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pItemModelPanelKVs )
+ {
+ m_pItemModelPanelKVs->deleteThis();
+ }
+ m_pItemModelPanelKVs = new KeyValues("modelpanels_kv");
+ pItemKV->CopySubkeys( m_pItemModelPanelKVs );
+ }
+}
+
+extern const char *g_szItemBorders[AE_MAX_TYPES][5];
+extern ConVar cl_showbackpackrarities;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver )
+{
+ if ( !pItemPanel )
+ return;
+
+ const char *pszBorder = NULL;
+
+ if ( pItemPanel->IsGreyedOut() )
+ {
+ if( pItemPanel->IsSelected() )
+ {
+ pszBorder = "BackpackItemGrayedOut_Selected";
+ }
+ else
+ {
+ pszBorder = "BackpackItemGrayedOut";
+ }
+ }
+ else
+ {
+ int iRarity = 0;
+ if ( pItemPanel->HasItem() && cl_showbackpackrarities.GetBool() )
+ {
+ iRarity = pItemPanel->GetItem()->GetItemQuality() ;
+
+ uint8 nRarity = pItemPanel->GetItem()->GetItemDefinition()->GetRarity();
+ if ( ( nRarity != k_unItemRarity_Any ) && ( iRarity != AE_SELFMADE ) )
+ {
+ // translate this quality to rarity
+ iRarity = nRarity + AE_RARITY_DEFAULT;
+ }
+ }
+
+ if ( pItemPanel->IsSelected() )
+ {
+ pszBorder = g_szItemBorders[iRarity][2];
+ }
+ if ( bMouseOver )
+ {
+ pszBorder = g_szItemBorders[iRarity][1];
+ }
+ else
+ {
+ pszBorder = g_szItemBorders[iRarity][0];
+ }
+ }
+
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ pItemPanel->SetBorder( pScheme->GetBorder( pszBorder ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::ApplyKVsToItemPanels( void )
+{
+ if ( m_pItemModelPanelKVs )
+ {
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ m_pItemModelPanels[i]->ApplySettings( m_pItemModelPanelKVs );
+ SetBorderForItem( m_pItemModelPanels[i], false );
+ m_pItemModelPanels[i]->InvalidateLayout();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::PerformLayout( void )
+{
+ if ( m_bReapplyItemKVs )
+ {
+ m_bReapplyItemKVs = false;
+ ApplyKVsToItemPanels();
+ }
+
+ BaseClass::PerformLayout();
+
+ // If we're items only, we hide various elements
+ if ( m_pCaratLabel )
+ {
+ m_pCaratLabel->SetVisible( !m_bItemsOnly );
+ }
+
+ if ( m_pClassLabel )
+ {
+ m_pClassLabel->SetVisible( !m_bItemsOnly );
+ }
+
+ if ( m_pMouseOverItemPanel->IsVisible() )
+ {
+ // The mouseover panel was visible. Fake a panel entry into the original panel to get it to show up again properly.
+ if ( m_pItemPanelBeingMousedOver )
+ {
+ OnItemPanelEntered( m_pItemPanelBeingMousedOver );
+ }
+ else
+ {
+ HideMouseOverPanel();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::AddNewItemPanel( int iPanelIndex )
+{
+ CItemModelPanel *pPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, VarArgs("modelpanel%d", iPanelIndex) ) );
+ pPanel->SetActAsButton( true, true );
+ m_pItemModelPanels.AddToTail( pPanel );
+
+#ifdef STAGING_ONLY
+ if ( tf_use_card_tooltips.GetBool() )
+ {
+ pPanel->SetTooltip( m_pMouseOverCardTooltip, "" );
+ }
+ else
+#endif
+ pPanel->SetTooltip( m_pMouseOverTooltip, "" );
+
+ Assert( iPanelIndex == (m_pItemModelPanels.Count()-1) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::CreateItemPanels( void )
+{
+ int iNumPanels = GetNumItemPanels();
+ if ( m_pItemModelPanels.Count() < iNumPanels )
+ {
+ for ( int i = m_pItemModelPanels.Count(); i < iNumPanels; i++ )
+ {
+ AddNewItemPanel(i);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::ShowPanel( int iClass, bool bBackpack, bool bReturningFromArmory )
+{
+ bool bShow = (iClass != 0 || bBackpack);
+ OnShowPanel( bShow, bReturningFromArmory );
+
+ SetVisible( bShow );
+
+ if ( bShow )
+ {
+ HideMouseOverPanel();
+
+ CreateItemPanels();
+
+ UpdateModelPanels();
+
+ // make the first slot be selected so controller input will work
+ static ConVarRef joystick( "joystick" );
+ if( joystick.IsValid() && joystick.GetBool() && m_pItemModelPanels.Count() && m_pItemModelPanels[0] )
+ {
+ m_pItemModelPanels[0]->SetSelected( true );
+ m_pItemModelPanels[0]->RequestFocus();
+ }
+ }
+ else
+ {
+ // clear items from panels to make sure that items get invalidate on show panel
+ FOR_EACH_VEC( m_pItemModelPanels, i )
+ {
+ m_pItemModelPanels[i]->SetItem( NULL );
+ }
+ }
+
+ if ( !bReturningFromArmory )
+ {
+ PostShowPanel( bShow );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::OnCommand( const char *command )
+{
+ engine->ClientCmd( const_cast<char *>( command ) );
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::FireGameEvent( IGameEvent *event )
+{
+ // If we're not visible, ignore all events
+ if ( !IsVisible() )
+ return;
+
+ const char *type = event->GetName();
+ if ( Q_strcmp( "inventory_updated", type ) == 0 )
+ {
+ // We need to refresh our model panels, because the items may have changed.
+ UpdateModelPanels();
+ }
+}
+
+CItemModelPanel *CBaseLoadoutPanel::FindBestPanelNavigationForDirection( const CItemModelPanel *pCurrentPanel, const Vector2D &vPos, const Vector2D &vDirection )
+{
+ CItemModelPanel *pBestPanel = NULL;
+
+ // Start with the worst allowable score
+ float flDistance = GetWide() + GetTall();
+ float flDot = -1.0f;
+ float flClosenessScore = flDistance * ( 1.5f - flDot );
+
+ for ( int j = 0; j < m_pItemModelPanels.Count(); j++ )
+ {
+ CItemModelPanel *pTempPanel = m_pItemModelPanels[ j ];
+ if ( !pTempPanel || pTempPanel == pCurrentPanel )
+ continue;
+
+ // Get temp center position
+ int nX, nY;
+ pTempPanel->GetPos( nX, nY );
+ nX += pTempPanel->GetWide() / 2;
+ nY += pTempPanel->GetTall() / 2;
+ Vector2D vTempPos( nX, nY );
+
+ // Get distance and dot
+ Vector2D vDiff = vTempPos - vPos;
+ float flTempDistance = Vector2DNormalize( vDiff );
+ float flTempDot = vDiff.Dot( vDirection );
+
+ // Must be somewhat in the correct direction
+ if ( flTempDot <= 0.0f )
+ continue;
+
+ float flTempScore = flTempDistance * ( 1.5f - flTempDot );
+
+ if ( flClosenessScore > flTempScore )
+ {
+ flClosenessScore = flTempScore;
+ flDistance = flTempDistance;
+ flDot = flTempDot;
+ pBestPanel = pTempPanel;
+ }
+ }
+
+ return pBestPanel;
+}
+
+void CBaseLoadoutPanel::LinkModelPanelControllerNavigation( bool bForceRelink )
+{
+ if ( m_pItemModelPanels.Count() < 2 )
+ return;
+
+ // first unlink everything
+ if( bForceRelink )
+ {
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ CItemModelPanel *pCurrentPanel = m_pItemModelPanels[ i ];
+ if ( !pCurrentPanel )
+ continue;
+
+ pCurrentPanel->SetNavUp( (vgui::Panel*)NULL );
+ pCurrentPanel->SetNavDown( (vgui::Panel*)NULL );
+ pCurrentPanel->SetNavLeft( (vgui::Panel*)NULL );
+ pCurrentPanel->SetNavRight( (vgui::Panel*)NULL );
+ }
+ }
+
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ CItemModelPanel *pCurrentPanel = m_pItemModelPanels[ i ];
+ if ( !pCurrentPanel )
+ continue;
+
+ // Get center position
+ int nX, nY;
+ pCurrentPanel->GetPos( nX, nY );
+ nX += pCurrentPanel->GetWide() / 2;
+ nY += pCurrentPanel->GetTall() / 2;
+ Vector2D vPos( nX, nY );
+
+ if ( !pCurrentPanel->GetNavUpName() || pCurrentPanel->GetNavUpName()[ 0 ] == '\0' )
+ {
+ CItemModelPanel *pBestPanel = FindBestPanelNavigationForDirection( pCurrentPanel, vPos, Vector2D( 0, -1 ) );
+ if ( pBestPanel )
+ {
+ pCurrentPanel->SetNavUp( pBestPanel->GetName() );
+ pBestPanel->SetNavDown( pCurrentPanel->GetName() );
+ }
+ }
+
+ if ( !pCurrentPanel->GetNavDownName() || pCurrentPanel->GetNavDownName()[ 0 ] == '\0' )
+ {
+ CItemModelPanel *pBestPanel = FindBestPanelNavigationForDirection( pCurrentPanel, vPos, Vector2D( 0, 1 ) );
+ if ( pBestPanel )
+ {
+ pCurrentPanel->SetNavDown( pBestPanel->GetName() );
+ pBestPanel->SetNavUp( pCurrentPanel->GetName() );
+ }
+ }
+
+ if ( !pCurrentPanel->GetNavLeftName() || pCurrentPanel->GetNavLeftName()[ 0 ] == '\0' )
+ {
+ CItemModelPanel *pBestPanel = FindBestPanelNavigationForDirection( pCurrentPanel, vPos, Vector2D( -1, 0 ) );
+ if ( pBestPanel )
+ {
+ pCurrentPanel->SetNavLeft( pBestPanel->GetName() );
+ pBestPanel->SetNavRight( pCurrentPanel->GetName() );
+ }
+ }
+
+ if ( !pCurrentPanel->GetNavRightName() || pCurrentPanel->GetNavRightName()[ 0 ] == '\0' )
+ {
+ CItemModelPanel *pBestPanel = FindBestPanelNavigationForDirection( pCurrentPanel, vPos, Vector2D( 1, 0 ) );
+ if ( pBestPanel )
+ {
+ pCurrentPanel->SetNavRight( pBestPanel->GetName() );
+ pBestPanel->SetNavLeft( pCurrentPanel->GetName() );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::OnItemPanelEntered( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ CEconItemView *pItem = pItemPanel->GetItem();
+ if ( pItem && !IsIgnoringItemPanelEnters() && !pItemPanel->IsGreyedOut() )
+ {
+ m_pItemPanelBeingMousedOver = pItemPanel;
+ }
+
+ if ( !pItemPanel->IsSelected() )
+ {
+ SetBorderForItem( pItemPanel, true );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::OnItemPanelExited( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ if ( !pItemPanel->IsSelected() )
+ {
+ SetBorderForItem( pItemPanel, false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::HideMouseOverPanel( void )
+{
+ if ( m_pMouseOverItemPanel->IsVisible() )
+ {
+ m_pMouseOverItemPanel->SetVisible( false );
+ m_pItemPanelBeingMousedOver = NULL;
+ }
+
+#ifdef STAGING_ONLY
+ if ( m_pMouseOverCardPanel->IsVisible() )
+ {
+ m_pMouseOverCardPanel->SetVisible( false );
+ m_pItemPanelBeingMousedOver = NULL;
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the index of the first selected item.
+//-----------------------------------------------------------------------------
+int CBaseLoadoutPanel::GetFirstSelectedItemIndex( bool bIncludeEmptySlots )
+{
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ if ( m_pItemModelPanels[i]->IsSelected() && ( bIncludeEmptySlots || m_pItemModelPanels[i]->HasItem() ) )
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the first selected item model panel or NULL if there is no
+// such panel.
+//-----------------------------------------------------------------------------
+CItemModelPanel *CBaseLoadoutPanel::GetFirstSelectedItemModelPanel (bool bIncludeEmptySlots )
+{
+ int i = GetFirstSelectedItemIndex( bIncludeEmptySlots );
+ if( i == -1 )
+ return NULL;
+ else
+ return m_pItemModelPanels[ i ];
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the first selected econ item view or NULL if there is no
+// selected item
+//-----------------------------------------------------------------------------
+CEconItemView *CBaseLoadoutPanel::GetFirstSelectedItem()
+{
+ CItemModelPanel *pItemModelPanel = GetFirstSelectedItemModelPanel( false );
+ if( pItemModelPanel )
+ return pItemModelPanel->GetItem();
+ else
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the next item in the specified direction, possibly switching
+// pages to get there
+//-----------------------------------------------------------------------------
+bool CBaseLoadoutPanel::GetAdjacentItemIndex( int nIndex, int nPage, int *pnNewIndex, int *pnNewPage, int dx, int dy )
+{
+ // if we don't have a valid index the right answer is always the first item on the first page
+ if( nIndex == -1 )
+ {
+ *pnNewIndex = 0;
+ *pnNewPage = nPage;
+ return true;
+ }
+
+ int nRow = nIndex / GetNumColumns() + dy;
+ int nColumn = nIndex % GetNumColumns() + dx;
+
+ // just limit us to the top and bottom edges
+ if( nRow < 0 || nRow >= GetNumRows() )
+ return false;
+
+ // for columns, try to switch pages
+ int nNewPage = nPage;
+ while( nColumn < 0 )
+ {
+ if( nNewPage == 0 )
+ break;
+
+ nNewPage--;
+ nColumn += GetNumColumns();
+ }
+
+ while( nColumn >= GetNumColumns() )
+ {
+ if( nNewPage == GetNumPages() - 1 )
+ break;
+
+ nNewPage++;
+ nColumn -= GetNumColumns();
+ }
+
+ if( nColumn < 0 )
+ {
+ if( nNewPage != nPage )
+ {
+ nColumn = 0;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if( nColumn >= GetNumColumns() )
+ {
+ if( nNewPage != nPage )
+ {
+ nColumn = GetNumColumns() - 1;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ // never change to an invisible panel
+ int nNewIndex = nRow * GetNumColumns() + nColumn;
+ if( nNewIndex >= m_pItemModelPanels.Count() || !m_pItemModelPanels[ nNewIndex ]->IsVisible() )
+ {
+ // try to find a model panel that's still valid so we find the last one on the last valid row
+ while( nNewIndex >= 0 && !m_pItemModelPanels[ nNewIndex ]->IsVisible() )
+ nNewIndex--;
+
+ if( nNewIndex < 0 || nNewIndex == nIndex )
+ return false;
+ }
+
+ *pnNewPage = nNewPage;
+ *pnNewIndex = nNewIndex;
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: selects the next item in the specified direction, possibly switching
+// pages to get there
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::SelectAdjacentItem( int dx, int dy )
+{
+ int nSelected = GetFirstSelectedItemIndex( true );
+ int nNewPage, nNewSelected;
+ bool bFoundNext = GetAdjacentItemIndex( nSelected, m_nCurrentPage, &nNewSelected, &nNewPage, dx, dy );
+ if( !bFoundNext )
+ {
+ vgui::surface()->PlaySound( "player/suit_denydevice.wav" );
+ return;
+ }
+
+ // change pages
+ if( nNewPage != m_nCurrentPage )
+ {
+ Assert( nNewPage >= 0 && nNewPage < GetNumPages() );
+ SetCurrentPage( nNewPage );
+ UpdateModelPanels();
+ }
+
+ // select the new model
+ if( nSelected != nNewSelected )
+ {
+ if( nSelected != -1 && m_pItemModelPanels[ nSelected ]->IsSelected() )
+ {
+ m_pItemModelPanels[ nSelected ]->SetSelected( false );
+ SetBorderForItem( m_pItemModelPanels[ nSelected ], false );
+ }
+ if( nNewSelected != -1 && !m_pItemModelPanels[ nNewSelected ]->IsSelected() )
+ {
+ m_pItemModelPanels[ nNewSelected ]->SetSelected( true );
+ SetBorderForItem( m_pItemModelPanels[ nNewSelected ], false );
+
+ if( m_bTooltipKeyPressed )
+ {
+ if( m_pItemModelPanels[ nNewSelected ]->HasItem() )
+ {
+ m_pMouseOverTooltip->ShowTooltip( m_pItemModelPanels[ nNewSelected ] );
+ }
+ else
+ {
+ m_pMouseOverTooltip->HideTooltip();
+ }
+ }
+
+ }
+ }
+
+ OnItemSelectionChanged();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Processes up/down/left/right keys for selecting items in the panel
+//-----------------------------------------------------------------------------
+bool CBaseLoadoutPanel::HandleItemSelectionKeyPressed( vgui::KeyCode code )
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if ( nButtonCode == KEY_XBUTTON_UP ||
+ nButtonCode == KEY_XSTICK1_UP ||
+ nButtonCode == KEY_XSTICK2_UP ||
+ nButtonCode == KEY_UP )
+ {
+ SelectAdjacentItem( 0, -1 );
+ return true;
+ }
+ else if ( nButtonCode == KEY_XBUTTON_DOWN ||
+ nButtonCode == KEY_XSTICK1_DOWN ||
+ nButtonCode == KEY_XSTICK2_DOWN ||
+ nButtonCode == STEAMCONTROLLER_DPAD_DOWN ||
+ nButtonCode == KEY_DOWN )
+ {
+ SelectAdjacentItem( 0, 1 );
+ return true;
+ }
+ else if ( nButtonCode == KEY_XBUTTON_RIGHT ||
+ nButtonCode == KEY_XSTICK1_RIGHT ||
+ nButtonCode == KEY_XSTICK2_RIGHT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_RIGHT ||
+ nButtonCode == KEY_RIGHT )
+ {
+ SelectAdjacentItem( 1, 0 );
+ return true;
+ }
+ else if ( nButtonCode == KEY_XBUTTON_LEFT ||
+ nButtonCode == KEY_XSTICK1_LEFT ||
+ nButtonCode == KEY_XSTICK2_LEFT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_LEFT ||
+ nButtonCode == KEY_LEFT )
+ {
+ SelectAdjacentItem( -1, 0 );
+ return true;
+ }
+ else if ( code == KEY_PAGEDOWN ||
+ nButtonCode == KEY_XBUTTON_RIGHT_SHOULDER )
+ {
+ if( m_nCurrentPage < GetNumPages() - 1 )
+ {
+ SetCurrentPage( m_nCurrentPage + 1 );
+ UpdateModelPanels();
+ }
+ return true;
+ }
+ else if ( code == KEY_PAGEUP ||
+ nButtonCode == KEY_XBUTTON_LEFT_SHOULDER )
+ {
+ if( m_nCurrentPage > 0 )
+ {
+ SetCurrentPage( m_nCurrentPage - 1 );
+ UpdateModelPanels();
+ }
+ return true;
+ }
+ else if ( nButtonCode == KEY_XBUTTON_Y )
+ {
+ m_bTooltipKeyPressed = true;
+ CItemModelPanel *pSelection = GetFirstSelectedItemModelPanel( false );
+ if( pSelection )
+ {
+ m_pMouseOverTooltip->ResetDelay();
+ m_pMouseOverTooltip->ShowTooltip( pSelection );
+ }
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Processes up/down/left/right keys for selecting items in the panel
+//-----------------------------------------------------------------------------
+bool CBaseLoadoutPanel::HandleItemSelectionKeyReleased( vgui::KeyCode code )
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+ if( nButtonCode == KEY_XBUTTON_Y )
+ {
+ m_bTooltipKeyPressed = false;
+ m_pMouseOverTooltip->HideTooltip();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseLoadoutPanel::SetCurrentPage( int nNewPage )
+{
+ if( nNewPage < 0 || nNewPage >= GetNumPages() )
+ return;
+
+ m_nCurrentPage = nNewPage;
+}
+
+
diff --git a/game/client/econ/base_loadout_panel.h b/game/client/econ/base_loadout_panel.h
new file mode 100644
index 0000000..07a2012
--- /dev/null
+++ b/game/client/econ/base_loadout_panel.h
@@ -0,0 +1,115 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef BASE_LOADOUT_PANEL_H
+#define BASE_LOADOUT_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include "econ_controls.h"
+#include "item_pickup_panel.h"
+#include "GameEventListener.h"
+#include "tf_item_card_panel.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CBaseLoadoutPanel : public vgui::EditablePanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CBaseLoadoutPanel, vgui::EditablePanel );
+public:
+ CBaseLoadoutPanel( vgui::Panel *parent, const char *panelName );
+ virtual ~CBaseLoadoutPanel();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ void ShowPanel( int iClass, bool bBackpack, bool bReturningFromArmory = false );
+ virtual void FireGameEvent( IGameEvent *event );
+
+ virtual int GetNumSlotsPerPage( void ) { return 1; }
+ virtual int GetNumColumns( void ) { return 99; }
+ virtual int GetNumRows( void ) { return 99; }
+ virtual int GetNumPages( void ) { return 1; }
+ virtual int GetCurrentPage() const { return m_nCurrentPage; }
+ virtual void SetCurrentPage( int nNewPage );
+
+ virtual int GetNumItemPanels( void ) { Assert(0); return 0; };
+ virtual void OnShowPanel( bool bVisible, bool bReturningFromArmory ) { return; }
+ virtual void PostShowPanel( bool bVisible ) { return; }
+ CItemModelPanel *FindBestPanelNavigationForDirection( const CItemModelPanel *pCurrentPanel, const Vector2D &vPos, const Vector2D &vDirection );
+ void LinkModelPanelControllerNavigation( bool bForceRelink );
+
+ virtual void AddNewItemPanel( int iPanelIndex );
+
+ MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelExited, "ItemPanelExited", panel );
+
+ void HideMouseOverPanel( void );
+ CItemModelPanel *GetMouseOverPanel( void ) { return m_pMouseOverItemPanel; }
+ CItemModelPanelToolTip *GetMouseOverToolTipPanel( void ) { return m_pMouseOverTooltip; }
+
+protected:
+ virtual void UpdateModelPanels( void ) { return; }
+ virtual void SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver );
+ virtual bool IsIgnoringItemPanelEnters( void ) { return false; }
+ virtual void ApplyKVsToItemPanels( void );
+ virtual void CreateItemPanels( void );
+ virtual void OnItemSelectionChanged() {}
+ bool HandleItemSelectionKeyPressed( vgui::KeyCode code ) ;
+ bool HandleItemSelectionKeyReleased( vgui::KeyCode code ) ;
+
+ // helpers to get selected items
+ int GetFirstSelectedItemIndex( bool bIncludeEmptySlots );
+ CItemModelPanel *GetFirstSelectedItemModelPanel( bool bIncludeEmptySlots );
+ CEconItemView *GetFirstSelectedItem();
+ bool GetAdjacentItemIndex( int nIndex, int nPage, int *pnNewIndex, int *pnNewPage, int dx, int dy );
+ void SelectAdjacentItem( int dx, int dy );
+
+protected:
+ CUtlVector<CItemModelPanel*> m_pItemModelPanels;
+ vgui::Label *m_pTitleLabel;
+
+ KeyValues *m_pItemModelPanelKVs;
+ bool m_bReapplyItemKVs;
+ bool m_bTooltipKeyPressed;
+ int m_nCurrentPage;
+
+ vgui::Label *m_pCaratLabel;
+ vgui::Label *m_pClassLabel;
+
+ CPanelAnimationVarAliasType( int, m_iItemXPosOffcenterA, "item_xpos_offcenter_a", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemXPosOffcenterB, "item_xpos_offcenter_b", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemYPos, "item_ypos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemYDelta, "item_ydelta", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iButtonXPosOffcenter, "button_xpos_offcenter", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iButtonYPos, "button_ypos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iButtonYDelta, "button_ydelta", "0", "proportional_int" );
+
+ CPanelAnimationVarAliasType( int, m_iItemBackpackOffcenterX, "item_backpack_offcenter_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemBackpackXDelta, "item_backpack_xdelta", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemBackpackYDelta, "item_backpack_ydelta", "0", "proportional_int" );
+ CPanelAnimationVar( bool, m_bItemsOnly, "items_only", "0" );
+ CPanelAnimationVar( bool, m_bForceShowBackpackRarities, "force_show_backpack_rarities", "0" );
+
+ CPanelAnimationVarAliasType( int, m_iDeleteButtonXPos, "button_override_delete_xpos", "0", "proportional_int" );
+
+protected:
+ CItemModelPanel *m_pMouseOverItemPanel;
+ CItemModelPanelToolTip *m_pMouseOverTooltip;
+ CItemModelPanel *m_pItemPanelBeingMousedOver;
+
+#ifdef STAGING_ONLY
+ CTFItemCardPanel *m_pMouseOverCardPanel;
+ CItemCardPanelToolTip *m_pMouseOverCardTooltip;
+#endif
+};
+
+#endif // BASE_LOADOUT_PANEL_H
diff --git a/game/client/econ/client_community_market.cpp b/game/client/econ/client_community_market.cpp
new file mode 100644
index 0000000..e1aa9cc
--- /dev/null
+++ b/game/client/econ/client_community_market.cpp
@@ -0,0 +1,107 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#include "cbase.h"
+#include "econ_gcmessages.h"
+#include "econ_item_system.h"
+#include "econ_ui.h"
+#include "store/store_panel.h"
+#include "gc_clientsystem.h"
+#include "client_community_market.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static const float s_fUpdateTimeInSeconds = 60.0f * 15.0f;
+
+typedef CUtlMap< steam_market_gc_identifier_t, client_market_data_t, unsigned int > ClientMarketDataMap_t;
+static ClientMarketDataMap_t s_mapClientMarketData;
+static float g_fClientMarketDataLastUpdateTime = -s_fUpdateTimeInSeconds;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void ClientMarketData_Refresh()
+{
+ if ( !EconUI() || !EconUI()->GetStorePanel() )
+ return;
+
+ GCSDK::CProtoBufMsg<CMsgGCClientMarketDataRequest> msg( k_EMsgGCClientRequestMarketData );
+ msg.Body().set_user_currency( EconUI()->GetStorePanel()->GetCurrency() );
+ GCClientSystem()->BSendMessage( msg );
+
+ g_fClientMarketDataLastUpdateTime = engine->Time();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const client_market_data_t *GetClientMarketData( const steam_market_gc_identifier_t& ident )
+{
+ // If our data is out of date, request fresh data from the GC. We don't need up-to-the-minute
+ // numbers but we don't want to fall too far behind. THe GC itself doesn't update in realtime
+ // so constantly querying for updates isn't really useful. We'll still use whatever data if any
+ // we have for this call.
+ if ( (engine->Time() - g_fClientMarketDataLastUpdateTime) >= s_fUpdateTimeInSeconds )
+ {
+ ClientMarketData_Refresh();
+ }
+
+ // Not having any data on this item isn't an error. We might be requesting something for an
+ // unlistable item, or we might not have current information from the GC yet.
+ if ( s_mapClientMarketData.Count() == 0 )
+ return NULL;
+
+ // Remap this index?
+ steam_market_gc_identifier_t searchIdent = ident;
+ searchIdent.m_unDefIndex = GetItemSchema()->GetCommunityMarketRemappedDefinitionIndex( ident.m_unDefIndex );
+
+ ClientMarketDataMap_t::IndexType_t index = s_mapClientMarketData.Find( searchIdent );
+ if ( index == s_mapClientMarketData.InvalidIndex() )
+ return NULL;
+
+ return &s_mapClientMarketData[index];
+}
+//-----------------------------------------------------------------------------
+const client_market_data_t *GetClientMarketData( item_definition_index_t iItemDef, uint8 unQuality )
+{
+ steam_market_gc_identifier_t ident;
+ ident.m_unDefIndex = iItemDef;
+ ident.m_unQuality = unQuality;
+
+ return GetClientMarketData( ident );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CGCClientRequestMarketDataResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientRequestMarketDataResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CProtoBufMsg<CMsgGCClientMarketData> msg( pNetPacket );
+
+ s_mapClientMarketData.RemoveAll();
+ s_mapClientMarketData.SetLessFunc( DefLessFunc( ClientMarketDataMap_t::KeyType_t ) );
+
+ for ( int i = 0; i < msg.Body().entries_size(); i++ )
+ {
+ const CMsgGCClientMarketDataEntry& entry = msg.Body().entries( i );
+
+ steam_market_gc_identifier_t ident;
+ ident.m_unDefIndex = entry.item_def_index();
+ ident.m_unQuality = entry.item_quality();
+
+ client_market_data_t data;
+ data.m_unQuantityAvailable = entry.item_sell_listings();
+ data.m_unLowestPrice = entry.price_in_local_currency();
+
+ s_mapClientMarketData.Insert( ident, data );
+ }
+
+ return true;
+ }
+
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCClientRequestMarketDataResponse, "CGCClientRequestMarketDataResponse", k_EMsgGCClientRequestMarketDataResponse, GCSDK::k_EServerTypeGCClient ); \ No newline at end of file
diff --git a/game/client/econ/client_community_market.h b/game/client/econ/client_community_market.h
new file mode 100644
index 0000000..78e161a
--- /dev/null
+++ b/game/client/econ/client_community_market.h
@@ -0,0 +1,19 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#ifndef CLIENT_COMMUNITY_MARKET_H
+#define CLIENT_COMMUNITY_MARKET_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+struct client_market_data_t
+{
+ uint32 m_unQuantityAvailable;
+ float m_unLowestPrice;
+};
+
+const client_market_data_t *GetClientMarketData( item_definition_index_t iItemDef, uint8 unQuality );
+
+const client_market_data_t *GetClientMarketData( const steam_market_gc_identifier_t& ident );
+
+#endif // CLIENT_COMMUNITY_MARKET_H \ No newline at end of file
diff --git a/game/client/econ/confirm_delete_dialog.cpp b/game/client/econ/confirm_delete_dialog.cpp
new file mode 100644
index 0000000..0cd8cbb
--- /dev/null
+++ b/game/client/econ/confirm_delete_dialog.cpp
@@ -0,0 +1,37 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "confirm_delete_dialog.h"
+#include "vgui_controls/TextImage.h"
+#include "econ_controls.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmDeleteDialog::CConfirmDeleteDialog( vgui::Panel *parent )
+: BaseClass(parent)
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDeleteDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ // Set the X to be bright, and the rest dull
+ if ( m_pConfirmButton )
+ {
+ m_pConfirmButton->SetText( "#X_DeleteConfirmButton" );
+ SetXToRed( m_pConfirmButton );
+ }
+} \ No newline at end of file
diff --git a/game/client/econ/confirm_delete_dialog.h b/game/client/econ/confirm_delete_dialog.h
new file mode 100644
index 0000000..6bac18e
--- /dev/null
+++ b/game/client/econ/confirm_delete_dialog.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CONFIRM_DELETE_DIALOG_H
+#define CONFIRM_DELETE_DIALOG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "confirm_dialog.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: A generic delete confirmation dialog - see CConfirmDialog.
+//-----------------------------------------------------------------------------
+class CConfirmDeleteDialog : public CConfirmDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmDeleteDialog,CConfirmDialog );
+public:
+ CConfirmDeleteDialog( vgui::Panel *parent );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+};
+
+#endif // CONFIRM_DELETE_DIALOG_H
diff --git a/game/client/econ/confirm_dialog.cpp b/game/client/econ/confirm_dialog.cpp
new file mode 100644
index 0000000..f9d0f77
--- /dev/null
+++ b/game/client/econ/confirm_dialog.cpp
@@ -0,0 +1,932 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+
+#include "confirm_dialog.h"
+
+#include "ienginevgui.h"
+#include "econ_controls.h"
+#include "vgui/IInput.h"
+#include "vgui/ISurface.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/CheckButton.h"
+#include "econ_ui.h"
+#include "store/store_panel.h"
+#ifdef TF_CLIENT_DLL
+#include "tf_playerpanel.h"
+#endif // TF_CLIENT_DLL
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+static const wchar_t* GetSCGlyph( const char* action )
+{
+ auto origin = g_pInputSystem->GetSteamControllerActionOrigin( action, GAME_ACTION_SET_FPSCONTROLS );
+ return g_pInputSystem->GetSteamControllerFontCharacterForActionOrigin( origin );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmDialog::CConfirmDialog( vgui::Panel *parent )
+: BaseClass( parent, "ConfirmDialog" ),
+ m_pCancelButton( NULL ),
+ m_pConfirmButton( NULL ),
+ m_pIcon( NULL )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( GetResFile(), "GAME" );
+
+ SetBorder( pScheme->GetBorder("EconItemBorder") );
+
+ // Cache off button ptrs
+ m_pConfirmButton = dynamic_cast< CExButton* >( FindChildByName( "ConfirmButton" ) );
+ m_pCancelButton = dynamic_cast< CExButton* >( FindChildByName( "CancelButton" ) );
+ m_pIcon = dynamic_cast< vgui::ImagePanel* >( FindChildByName( "Icon" ) );
+
+ SetDialogVariable( "text", GetText() );
+
+ if ( ::input->IsSteamControllerActive() )
+ {
+ auto iconConfirm = GetSCGlyph( "cl_trigger_first_notification" );
+ auto iconCancel = GetSCGlyph( "cl_decline_first_notification" );
+ auto confirmHint = dynamic_cast< CExLabel* >( FindChildByName( "ConfirmButtonHintIcon" ) );
+ auto cancelHint = dynamic_cast< CExLabel* >( FindChildByName( "CancelButtonHintIcon" ) );
+ if ( confirmHint )
+ {
+ confirmHint->SetText( iconConfirm );
+ }
+
+ if ( cancelHint )
+ {
+ cancelHint->SetText( iconCancel );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDialog::Show( bool bMakePopup )
+{
+ SetVisible( true );
+ if ( bMakePopup )
+ {
+ MakePopup();
+ }
+ MoveToFront();
+ SetKeyBoardInputEnabled( true );
+
+ InvalidateLayout( true, true );
+
+ if ( ::input->IsSteamControllerActive() )
+ {
+ auto iconConfirm = GetSCGlyph( "vote_option1" );
+ auto iconCancel = GetSCGlyph( "vote_option2" );
+ bool bControllerMapped = iconConfirm[0] && iconCancel[0];
+ if ( bControllerMapped )
+ {
+ SetMouseInputEnabled( false );
+ }
+ else
+ {
+ SetMouseInputEnabled( true );
+ }
+ }
+ else
+ {
+ SetMouseInputEnabled( true );
+ }
+
+ TFModalStack()->PushModal( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDialog::SetIconImage( const char *pszIcon )
+{
+ Assert( m_pIcon );
+ if ( m_pIcon )
+ {
+ m_pIcon->SetImage( pszIcon );
+ m_pIcon->SetVisible( ( pszIcon ? true : false ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDialog::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "cancel", 6 ) )
+ {
+ FinishUp();
+ PostMessage( GetParent(), new KeyValues( "ConfirmDlgResult", "confirmed", 0 ) );
+ }
+ else if ( !Q_strnicmp( command, "confirm", 7 ) )
+ {
+ FinishUp();
+ PostMessage( GetParent(), new KeyValues( "ConfirmDlgResult", "confirmed", 1 ) );
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDialog::OnKeyCodeTyped( vgui::KeyCode code )
+{
+ if( code == KEY_ESCAPE )
+ {
+ OnCommand( "cancel" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+///-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDialog::OnKeyCodePressed( vgui::KeyCode code )
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ // We map the voting action buttons to the pseudo-buttons F1/F2 so that players can use them to interact with dialogs on the fly
+ if( nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_F2 || nButtonCode == STEAMCONTROLLER_B )
+ {
+ OnCommand( "cancel" );
+ }
+ else if ( nButtonCode == KEY_ENTER || nButtonCode == KEY_SPACE || nButtonCode == KEY_XBUTTON_A || nButtonCode == STEAMCONTROLLER_F1 || nButtonCode == STEAMCONTROLLER_A )
+ {
+ OnCommand( "confirm" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CConfirmDialog::GetResFile()
+{
+ if ( ::input->IsSteamControllerActive() )
+ {
+ return "Resource/UI/econ/ConfirmDialog_SC.res";
+ }
+ else
+ {
+ return "Resource/UI/econ/ConfirmDialog.res";
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Hide the panel, mark for deletion, remove from modal stack.
+//-----------------------------------------------------------------------------
+void CConfirmDialog::FinishUp()
+{
+ SetVisible( false );
+ TFModalStack()->PopModal( this );
+ MarkForDeletion();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDialog::OnSizeChanged( int nNewWide, int nNewTall )
+{
+ int nX, nY;
+
+ // Shift buttons up
+ if ( m_pCancelButton )
+ {
+ m_pCancelButton->GetPos( nX, nY );
+ m_pCancelButton->SetPos( nX, nNewTall - m_pCancelButton->GetTall() - YRES(15) );
+ }
+
+ if ( m_pConfirmButton )
+ {
+ m_pConfirmButton->GetPos( nX, nY );
+ m_pConfirmButton->SetPos( nX, nNewTall - m_pConfirmButton->GetTall() - YRES(15) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFGenericConfirmDialog::CTFGenericConfirmDialog( const char *pTitle, const char *pTextKey,
+ const char *pConfirmBtnText, const char *pCancelBtnText,
+ GenericConfirmDialogCallback callback, vgui::Panel *pParent )
+: BaseClass( pParent ),
+ m_pTextKey( pTextKey )
+{
+ CommonInit( pTitle, pConfirmBtnText, pCancelBtnText, callback, pParent );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFGenericConfirmDialog::CTFGenericConfirmDialog( const char *pTitle, const wchar_t *pText,
+ const char *pConfirmBtnText, const char *pCancelBtnText,
+ GenericConfirmDialogCallback callback, vgui::Panel *pParent )
+: BaseClass( pParent ),
+ m_pTextKey( NULL )
+{
+ CommonInit( pTitle, pConfirmBtnText, pCancelBtnText, callback, pParent );
+
+ V_wcsncpy( m_wszBuffer, pText, sizeof( m_wszBuffer ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGenericConfirmDialog::CommonInit( const char *pTitle, const char *pConfirmBtnText, const char *pCancelBtnText,
+ GenericConfirmDialogCallback callback, vgui::Panel *pParent )
+{
+ if ( pParent == NULL )
+ {
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+ }
+
+ m_pTitle = pTitle;
+ m_pConfirmBtnText = pConfirmBtnText;
+ m_pCancelBtnText = pCancelBtnText;
+ m_pCallback = callback;
+ m_pContext = NULL;
+ m_pKeyValues = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFGenericConfirmDialog::~CTFGenericConfirmDialog()
+{
+ if ( m_pKeyValues )
+ {
+ m_pKeyValues->deleteThis();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const wchar_t *CTFGenericConfirmDialog::GetText()
+{
+ if ( m_pTextKey )
+ {
+ g_pVGuiLocalize->ConstructString_safe( m_wszBuffer, m_pTextKey, m_pKeyValues );
+ return m_wszBuffer;
+ }
+
+ return m_wszBuffer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGenericConfirmDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ if ( m_pConfirmButton && m_pConfirmBtnText )
+ {
+ m_pConfirmButton->SetText( m_pConfirmBtnText );
+ }
+
+ if ( m_pCancelButton && m_pCancelBtnText )
+ {
+ m_pCancelButton->SetText (m_pCancelBtnText );
+ }
+
+ SetXToRed( m_pConfirmButton );
+ SetXToRed( m_pCancelButton );
+
+ CExLabel *pTitle = dynamic_cast< CExLabel* >( FindChildByName( "TitleLabel" ) );
+ if ( pTitle )
+ {
+ pTitle->SetText( m_pTitle );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGenericConfirmDialog::PerformLayout()
+{
+ // Center it, keeping requested size
+ int x, y, ww, wt, wide, tall;
+ vgui::surface()->GetWorkspaceBounds( x, y, ww, wt );
+ GetSize(wide, tall);
+ SetPos(x + ((ww - wide) / 2), y + ((wt - tall) / 2));
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGenericConfirmDialog::OnCommand( const char *command )
+{
+ bool bFinishUp = false;
+ bool bConfirmed = false;
+
+ if ( !Q_strnicmp( command, "cancel", 6 ) )
+ {
+ bConfirmed = false;
+ bFinishUp = true;
+ }
+ else if ( !Q_strnicmp( command, "confirm", 7 ) )
+ {
+ bConfirmed = true;
+ bFinishUp = true;
+ }
+
+ if ( bFinishUp )
+ {
+ FinishUp();
+ if ( m_pCallback )
+ {
+ m_pCallback( bConfirmed, m_pContext );
+ }
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGenericConfirmDialog::SetStringTokens( KeyValues *pKeyValues )
+{
+ if ( m_pKeyValues != NULL )
+ {
+ m_pKeyValues->deleteThis();
+ }
+ m_pKeyValues = pKeyValues->MakeCopy();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGenericConfirmDialog::AddStringToken( const char* pToken, const wchar_t* pValue )
+{
+ if ( m_pKeyValues == NULL )
+ {
+ m_pKeyValues = new KeyValues( "GenericConfirmDialog" );
+ }
+ m_pKeyValues->SetWString( pToken, pValue );
+ InvalidateLayout( false, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGenericConfirmDialog::SetContext( void *pContext )
+{
+ m_pContext = pContext;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFGenericConfirmOptOutDialog::CTFGenericConfirmOptOutDialog( const char *pTitle,
+ const char *pText,
+ const char *pConfirmBtnText,
+ const char *pCancelBtnText,
+ const char *pOptOutText,
+ const char *pOptOutConVarName,
+ GenericConfirmDialogCallback callback,
+ vgui::Panel *parent ) :
+ CTFGenericConfirmDialog( pTitle, pText, pConfirmBtnText, pCancelBtnText, callback, parent )
+{
+ m_optOutText = pOptOutText;
+ m_optOutCheckbox = NULL;
+ m_optOutConVarName = pOptOutConVarName;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGenericConfirmOptOutDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_optOutCheckbox = dynamic_cast< vgui::CheckButton * >( FindChildByName( "OptOutCheckbox" ) );
+
+ if ( m_optOutCheckbox && m_optOutText )
+ {
+ m_optOutCheckbox->SetMouseInputEnabled( true );
+ m_optOutCheckbox->SetText( m_optOutText );
+
+ // center horizontally
+ vgui::Panel *parent = m_optOutCheckbox->GetParent();
+ if ( parent )
+ {
+ float parentWidth = parent->GetWide();
+
+ int checkBoxWidth, checkBoxHeight;
+ m_optOutCheckbox->GetContentSize( checkBoxWidth, checkBoxHeight );
+
+ // fudge in checkbox width
+ checkBoxWidth += 34.0f;
+
+ int checkX, checkY;
+ m_optOutCheckbox->GetPos( checkX, checkY );
+
+ m_optOutCheckbox->SetPos( ( parentWidth - checkBoxWidth ) / 2.0f, checkY );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFGenericConfirmOptOutDialog::GetResFile()
+{
+ return "Resource/UI/econ/ConfirmDialogOptOut.res";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFGenericConfirmOptOutDialog::OnButtonChecked( KeyValues *pData )
+{
+ ConVarRef var( m_optOutConVarName );
+ if ( !var.IsValid() )
+ return;
+
+ if ( !m_optOutCheckbox )
+ return;
+
+ var.SetValue( m_optOutCheckbox->IsSelected() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFUpgradeBoxDialog::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "upgrade" ) )
+ {
+ FinishUp();
+
+ // Open the store, and show the upgrade advice
+ EconUI()->CloseEconUI();
+ EconUI()->OpenStorePanel( STOREPANEL_SHOW_UPGRADESTEPS, false );
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFGenericConfirmDialog *ShowConfirmDialog( const char *pTitle, const char *pText, const char *pConfirmBtnText, const char *pCancelBtnText, GenericConfirmDialogCallback callback,
+ vgui::Panel *parent/*=NULL*/, void *pContext/*=NULL*/, const char *pSound/*=NULL*/ )
+{
+ CTFGenericConfirmDialog *pDialog = vgui::SETUP_PANEL(
+ new CTFGenericConfirmDialog(
+ pTitle, pText,
+ pConfirmBtnText, pCancelBtnText,
+ callback, parent
+ )
+ );
+
+ if ( pDialog )
+ {
+ pDialog->Show();
+
+ // Play a sound, if one was supplied.
+ if ( pSound && pSound[0] )
+ {
+ vgui::surface()->PlaySound( pSound );
+ }
+ }
+
+ if ( pContext )
+ {
+ pDialog->SetContext( pContext );
+ }
+
+ return pDialog;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMessageBoxDialog *ShowMessageBox( const char *pTitle, const char *pText, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent, void *pContext )
+{
+ return ShowMessageBox( pTitle, pText, NULL, pConfirmBtnText, callback, parent, pContext );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMessageBoxDialog *ShowMessageBox( const char *pTitle, const wchar_t *pText, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent , void *pContext)
+{
+ CTFMessageBoxDialog *pDialog = vgui::SETUP_PANEL(
+ new CTFMessageBoxDialog(
+ pTitle, pText,
+ pConfirmBtnText,
+ callback, parent
+ )
+ );
+
+ if ( pDialog )
+ {
+ if ( pContext )
+ {
+ pDialog->SetContext( pContext );
+ }
+
+ pDialog->Show();
+ }
+
+ return pDialog;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMessageBoxDialog *ShowMessageBox( const char *pTitle, const char *pText, KeyValues *pKeyValues, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent , void *pContext)
+{
+ CTFMessageBoxDialog *pDialog = vgui::SETUP_PANEL( new CTFMessageBoxDialog( pTitle, pText,
+ pConfirmBtnText,
+ callback, parent ) );
+
+ if ( pDialog )
+ {
+ if ( pContext )
+ {
+ pDialog->SetContext( pContext );
+ }
+
+ if ( pKeyValues )
+ {
+ pDialog->SetStringTokens( pKeyValues );
+ pDialog->SetDialogVariable( "text", pDialog->GetText() );
+ }
+
+ pDialog->Show();
+ }
+
+ return pDialog;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pop up a model yes/no dialog with an "opt out" checkbox that persists via a ConVar
+//-----------------------------------------------------------------------------
+CTFGenericConfirmOptOutDialog *ShowConfirmOptOutDialog( const char *pTitle, const char *pText, const char *pConfirmBtnText, const char *pCancelBtnText, const char *pOptOutText, const char *pOptOutConVarName, GenericConfirmDialogCallback callback, vgui::Panel *parent)
+{
+ CTFGenericConfirmOptOutDialog *pDialog = vgui::SETUP_PANEL( new CTFGenericConfirmOptOutDialog( pTitle, pText,
+ pConfirmBtnText, pCancelBtnText,
+ pOptOutText, pOptOutConVarName,
+ callback, parent ) );
+ if ( pDialog )
+ {
+ pDialog->Show();
+ }
+
+ return pDialog;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMessageBoxDialog *ShowUpgradeMessageBox( const char *pTitle, const char *pText,
+ const char *pConfirmBtnText,
+ GenericConfirmDialogCallback callback,
+ vgui::Panel *parent, void *pContext )
+{
+ CTFMessageBoxDialog *pDialog = vgui::SETUP_PANEL(
+ new CTFUpgradeBoxDialog(
+ pTitle, pText,
+ pConfirmBtnText, callback, parent
+ )
+ );
+
+ if ( pDialog )
+ {
+ pDialog->SetContext( pContext );
+ pDialog->Show();
+ }
+
+ return pDialog;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pop up a dialog prompting the player to go to the store to upgrade
+//-----------------------------------------------------------------------------
+CTFMessageBoxDialog *ShowUpgradeMessageBox( const char *pTitle, const char *pText )
+{
+ return ShowUpgradeMessageBox( pTitle, pText, "#GameUI_OK", NULL, NULL, NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMessageBoxDialogWithSound *ShowMessageBoxWithSound( const char *pTitle, const char *pText, const char *pszSound, float flDelay, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent, void *pContext )
+{
+ return ShowMessageBoxWithSound( pTitle, pText, NULL, pszSound, flDelay, pConfirmBtnText, callback, parent, pContext );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMessageBoxDialogWithSound *ShowMessageBoxWithSound( const char *pTitle, const wchar_t *pText, const char *pszSound, float flDelay, const char *pConfirmBtnText , GenericConfirmDialogCallback callback, vgui::Panel *parent, void *pContext )
+{
+ CTFMessageBoxDialogWithSound *pDialog = vgui::SETUP_PANEL( new CTFMessageBoxDialogWithSound( pTitle, pText, pszSound, flDelay, pConfirmBtnText, callback, parent ) );
+
+ if ( pDialog )
+ {
+ if ( pContext )
+ {
+ pDialog->SetContext( pContext );
+ }
+
+ pDialog->Show();
+ }
+
+ return pDialog;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMessageBoxDialogWithSound *ShowMessageBoxWithSound( const char *pTitle, const char *pText, KeyValues *pKeyValues, const char *pszSound, float flDelay, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent, void *pContext )
+{
+ CTFMessageBoxDialogWithSound *pDialog = vgui::SETUP_PANEL( new CTFMessageBoxDialogWithSound( pTitle, pText, pszSound, flDelay, pConfirmBtnText, callback, parent ) );
+
+ if ( pDialog )
+ {
+ if ( pContext )
+ {
+ pDialog->SetContext( pContext );
+ }
+
+ if ( pKeyValues )
+ {
+ pDialog->SetStringTokens( pKeyValues );
+ pDialog->SetDialogVariable( "text", pDialog->GetText() );
+ }
+
+ pDialog->Show();
+ }
+
+ return pDialog;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMessageBoxDialogWithSound::CTFMessageBoxDialogWithSound( const char *pTitle, const char *pText, const char *pszSound, float flDelay, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent )
+ : CTFMessageBoxDialog( pTitle, pText, pConfirmBtnText, callback, parent )
+{
+ m_szSound[0] = 0;
+
+ if ( pszSound )
+ {
+ V_strcpy_safe( m_szSound, pszSound );
+ }
+
+ m_flSoundTime = gpGlobals->curtime + flDelay;
+ m_bPlayedSound = false;
+
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 50 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFMessageBoxDialogWithSound::CTFMessageBoxDialogWithSound( const char *pTitle, const wchar_t *pText, const char *pszSound, float flDelay, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent )
+ : CTFMessageBoxDialog( pTitle, pText, pConfirmBtnText, callback, parent )
+{
+ m_szSound[0] = 0;
+
+ if ( pszSound )
+ {
+ V_strcpy_safe( m_szSound, pszSound );
+ }
+
+ m_flSoundTime = gpGlobals->curtime + flDelay;
+ m_bPlayedSound = false;
+
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 50 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFMessageBoxDialogWithSound::OnTick()
+{
+ BaseClass::OnTick();
+
+ if ( !m_bPlayedSound && ( m_flSoundTime < gpGlobals->curtime ) )
+ {
+ m_bPlayedSound = true;
+
+ if ( Q_strlen( m_szSound ) > 0 )
+ {
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer )
+ {
+ pLocalPlayer->EmitSound( m_szSound );
+ }
+ }
+ }
+}
+
+#ifdef TF_CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFReviveDialog::CTFReviveDialog( const char *pTitle, const char *pText, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent )
+: CTFMessageBoxDialog( pTitle, pText, pConfirmBtnText, callback, parent )
+{
+ m_pTargetHealth = new CTFSpectatorGUIHealth( this, "SpectatorGUIHealth" );
+ m_pTargetHealth->SetAllowAnimations( false );
+ m_pTargetHealth->HideHealthBonusImage();
+
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 50 );
+ OnTick();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFReviveDialog::PerformLayout()
+{
+ // Skipping base class
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFReviveDialog::OnTick()
+{
+ BaseClass::OnTick();
+
+ if ( !m_pTargetHealth )
+ return;
+
+ if ( !m_hEntity )
+ return;
+
+ float flHealth = m_hEntity->GetHealth();
+ if ( flHealth != m_flPrevHealth )
+ {
+ float flMaxHealth = m_hEntity->GetMaxHealth();
+ m_pTargetHealth->SetHealth( flHealth, flMaxHealth, flMaxHealth );
+ m_flPrevHealth = flHealth;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFReviveDialog::SetOwner( CBaseEntity *pEntity )
+{
+ if ( pEntity )
+ {
+ m_hEntity = pEntity;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: In-game dialog that avoids the crosshair area and is much smaller
+//-----------------------------------------------------------------------------
+CTFReviveDialog *ShowRevivePrompt( CBaseEntity *pOwner,
+ const char *pTitle,
+ const char *pText,
+ const char *pConfirmBtnText,
+ GenericConfirmDialogCallback callback,
+ vgui::Panel *parent, void *pContext )
+{
+ CTFReviveDialog *pDialog = vgui::SETUP_PANEL( new CTFReviveDialog( pTitle, pText, pConfirmBtnText, callback, parent ) );
+ if ( pDialog )
+ {
+ if ( pContext )
+ {
+ pDialog->SetContext( pContext );
+ }
+
+ pDialog->SetOwner( pOwner );
+ pDialog->Show();
+ }
+
+ return pDialog;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CEconRequirementDialog::CEconRequirementDialog( const char *pTitle, const char *pTextKey, const char *pItemDefName )
+ : CTFGenericConfirmDialog( pTitle, pTextKey, NULL, NULL, NULL, NULL )
+ , m_hItemDef( pItemDefName )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CEconRequirementDialog::GetResFile()
+{
+ return "Resource/UI/MvMEconRequirementDialog.res";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconRequirementDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ vgui::ImagePanel *pItemImagePanel = dynamic_cast<vgui::ImagePanel *>( FindChildByName( "ItemImagePanel", true ) ); Assert( pItemImagePanel );
+ Assert( pItemImagePanel );
+ if ( pItemImagePanel && m_hItemDef )
+ {
+ pItemImagePanel->SetImage( CFmtStr( "../%s_large", m_hItemDef->GetInventoryImage() ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconRequirementDialog::OnCommand( const char *command )
+{
+ if ( m_hItemDef && !Q_stricmp( command, "show_in_store" ) )
+ {
+ FinishUp();
+
+ // Open the store, and show the upgrade advice
+ EconUI()->CloseEconUI();
+ EconUI()->OpenStorePanel( m_hItemDef->GetDefinitionIndex(), false );
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void ShowEconRequirementDialog( const char *pTitle, const char *pText, const char *pItemDefName )
+{
+ CEconRequirementDialog *pDialog = vgui::SETUP_PANEL( new CEconRequirementDialog( pTitle, pText, pItemDefName ) );
+ if ( pDialog )
+ {
+ pDialog->Show();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the correct res file to use (depends on Steam Controller state)
+//-----------------------------------------------------------------------------
+const char* CTFMessageBoxDialog::GetResFile()
+{
+ if ( ::input->IsSteamControllerActive() )
+ {
+ return "Resource/UI/econ/MessageBoxDialog_SC.res";
+ }
+ else
+ {
+ return "Resource/UI/econ/MessageBoxDialog.res";
+ }
+}
+
+
+#endif // TF_CLIENT_DLL
diff --git a/game/client/econ/confirm_dialog.h b/game/client/econ/confirm_dialog.h
new file mode 100644
index 0000000..dac868a
--- /dev/null
+++ b/game/client/econ/confirm_dialog.h
@@ -0,0 +1,230 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#ifndef CONFIRM_DIALOG_H
+#define CONFIRM_DIALOG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/CheckButton.h"
+#include "inputsystem/iinputsystem.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// - Basic confirm dialog - derive from this and implement GetText().
+// - The user will have two options, essentially yes or no.
+// - A "ConfirmDlgResult" message is sent to the parent with the result.
+// Check the "confirmed" parameter.
+// - Panel deletes itself.
+// - See CConfirmDeleteDialog for a generic delete confirmation dialog.
+//-----------------------------------------------------------------------------
+class CExButton;
+#ifdef TF_CLIENT_DLL
+class CTFSpectatorGUIHealth;
+#endif // TF_CLIENT_DLL
+
+class CConfirmDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CConfirmDialog, vgui::EditablePanel );
+public:
+ CConfirmDialog( vgui::Panel *parent );
+
+ virtual const wchar_t *GetText() = 0;
+
+ void Show( bool bMakePopup = true );
+ void SetIconImage( const char *pszIcon );
+
+protected:
+ virtual void OnSizeChanged(int nNewWide, int nNewTall );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+ virtual void OnKeyCodeTyped( vgui::KeyCode code );
+ virtual void OnKeyCodePressed( vgui::KeyCode code );
+ virtual const char *GetResFile();
+
+ void FinishUp(); // Hide the panel, mark for deletion, remove from modal stack.
+
+ CExButton *m_pConfirmButton;
+ CExButton *m_pCancelButton;
+ vgui::ImagePanel *m_pIcon;
+};
+
+//-----------------------------------------------------------------------------
+
+typedef void (*GenericConfirmDialogCallback)( bool bConfirmed, void *pContext );
+
+// An implementation of the Confirm Dialog that is "generic"
+class CTFGenericConfirmDialog : public CConfirmDialog
+{
+ DECLARE_CLASS_SIMPLE( CTFGenericConfirmDialog, CConfirmDialog );
+public:
+ CTFGenericConfirmDialog( const char *pTitle, const char *pTextKey, const char *pConfirmBtnText,
+ const char *pCancelBtnText, GenericConfirmDialogCallback callback, vgui::Panel *pParent );
+ CTFGenericConfirmDialog( const char *pTitle, const wchar_t *pText, const char *pConfirmBtnText,
+ const char *pCancelBtnText, GenericConfirmDialogCallback callback, vgui::Panel *pParent );
+ virtual ~CTFGenericConfirmDialog();
+
+ virtual const wchar_t *GetText();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout();
+ virtual void OnCommand( const char *command );
+
+ void SetStringTokens( KeyValues *pKeyValues );
+ void AddStringToken( const char* pToken, const wchar_t* pValue );
+ void SetContext( void *pContext );
+
+protected:
+ void CommonInit( const char *pTitle, const char *pConfirmBtnText, const char *pCancelBtnText,
+ GenericConfirmDialogCallback callback, vgui::Panel *pParent );
+
+ const char *m_pTitle;
+ const char *m_pTextKey;
+ const char *m_pConfirmBtnText;
+ const char *m_pCancelBtnText;
+
+ KeyValues *m_pKeyValues;
+ wchar_t m_wszBuffer[1024];
+ GenericConfirmDialogCallback m_pCallback;
+ void *m_pContext;
+};
+
+// A generic message dialog, which is just a generic confirm dialog w/o the cancel button
+class CTFMessageBoxDialog : public CTFGenericConfirmDialog
+{
+ DECLARE_CLASS_SIMPLE( CTFMessageBoxDialog, CTFGenericConfirmDialog );
+public:
+ CTFMessageBoxDialog( const char *pTitle, const char *pText, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent )
+ : CTFGenericConfirmDialog( pTitle, pText, pConfirmBtnText, NULL, callback, parent ) {}
+
+ CTFMessageBoxDialog( const char *pTitle, const wchar_t *pText, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent )
+ : CTFGenericConfirmDialog( pTitle, pText, pConfirmBtnText, NULL, callback, parent ) {}
+
+ virtual const char* GetResFile();
+};
+
+// A generic message dialog, which is just a generic confirm dialog w/o the cancel button that plays a sound with optional delay
+class CTFMessageBoxDialogWithSound : public CTFMessageBoxDialog
+{
+ DECLARE_CLASS_SIMPLE( CTFMessageBoxDialogWithSound, CTFMessageBoxDialog );
+public:
+ CTFMessageBoxDialogWithSound( const char *pTitle, const char *pText, const char *pszSound, float flDelay, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent );
+ CTFMessageBoxDialogWithSound( const char *pTitle, const wchar_t *pText, const char *pszSound, float flDelay, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent );
+ virtual void OnTick() OVERRIDE;
+
+private:
+ char m_szSound[MAX_PATH];
+ float m_flSoundTime;
+ bool m_bPlayedSound;
+};
+
+// A dialog with an upgrade button that takes them to the mann co store
+class CTFUpgradeBoxDialog : public CTFMessageBoxDialog
+{
+ DECLARE_CLASS_SIMPLE( CTFUpgradeBoxDialog, CTFMessageBoxDialog );
+public:
+ CTFUpgradeBoxDialog( const char *pTitle, const char *pText, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent )
+ : CTFMessageBoxDialog( pTitle, pText, pConfirmBtnText, callback, parent ) {}
+
+ CTFUpgradeBoxDialog( const char *pTitle, const wchar_t *pText, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent )
+ : CTFMessageBoxDialog( pTitle, pText, pConfirmBtnText, callback, parent ) {}
+
+ virtual const char *GetResFile()
+ {
+ return "Resource/UI/UpgradeBoxDialog.res";
+ }
+ virtual void OnCommand( const char *command );
+};
+
+
+// An implementation of the Confirm Dialog with a persistant "opt out" checkbox stored via ConVar
+class CTFGenericConfirmOptOutDialog : public CTFGenericConfirmDialog
+{
+ DECLARE_CLASS_SIMPLE( CTFGenericConfirmOptOutDialog, CTFGenericConfirmDialog );
+public:
+ CTFGenericConfirmOptOutDialog( const char *pTitle, const char *pText, const char *pConfirmBtnText, const char *pCancelBtnText, const char *pOptOutText, const char *pOptOutConVarName, GenericConfirmDialogCallback callback, vgui::Panel *parent ) ;
+ virtual ~CTFGenericConfirmOptOutDialog() { }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ MESSAGE_FUNC_PARAMS( OnButtonChecked, "CheckButtonChecked", pData );
+
+protected:
+ virtual const char *GetResFile();
+
+ const char *m_optOutText;
+
+ vgui::CheckButton *m_optOutCheckbox;
+ const char *m_optOutConVarName;
+};
+
+#ifdef TF_CLIENT_DLL
+// A dialog presented to dead players when being revived
+class CTFReviveDialog : public CTFMessageBoxDialog
+{
+ DECLARE_CLASS_SIMPLE( CTFReviveDialog, CTFMessageBoxDialog );
+public:
+ CTFReviveDialog( const char *pTitle, const char *pText, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent );
+ virtual ~CTFReviveDialog() { }
+
+ virtual void PerformLayout() OVERRIDE;
+ virtual void OnTick() OVERRIDE;
+ virtual const char *GetResFile() OVERRIDE { return "Resource/UI/ReviveDialog.res"; }
+ void SetOwner( CBaseEntity *pEntity );
+
+ CTFSpectatorGUIHealth *m_pTargetHealth;
+ CHandle< C_BaseEntity > m_hEntity;
+ float m_flPrevHealth;
+};
+
+CTFReviveDialog *ShowRevivePrompt( CBaseEntity *pOwner,
+ const char *pTitle = "#TF_Prompt_Revive_Title",
+ const char *pText = "#TF_Prompt_Revive_Message",
+ const char *pConfirmBtnText = "#TF_Prompt_Revive_Cancel",
+ GenericConfirmDialogCallback callback = NULL,
+ vgui::Panel *parent = NULL,
+ void *pContext = NULL );
+
+
+// A generic message dialog, which is just a generic confirm dialog w/o the cancel button
+class CEconRequirementDialog : public CTFGenericConfirmDialog
+{
+ DECLARE_CLASS_SIMPLE( CEconRequirementDialog, CTFGenericConfirmDialog );
+public:
+ CEconRequirementDialog( const char *pTitle, const char *pTextKey, const char *pItemDefName );
+
+ virtual const char *GetResFile() OVERRIDE;
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ CSchemaItemDefHandle m_hItemDef;
+};
+
+void ShowEconRequirementDialog( const char *pTitle, const char *pText, const char *pItemDefName );
+#endif // TF_CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+
+CTFGenericConfirmOptOutDialog *ShowConfirmOptOutDialog( const char *pTitle, const char *pText, const char *pConfirmBtnText, const char *pCancelBtnText, const char *pOptOutText, const char *pOptOutConVarName, GenericConfirmDialogCallback callback, vgui::Panel *parent = NULL );
+
+//-----------------------------------------------------------------------------
+
+CTFGenericConfirmDialog *ShowConfirmDialog( const char *pTitle, const char *pText, const char *pConfirmBtnText, const char *pCancelBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent = NULL, void *pContext = NULL, const char *pSound = NULL );
+
+//-----------------------------------------------------------------------------
+
+CTFMessageBoxDialog *ShowMessageBox( const char *pTitle, const char *pText, const char *pConfirmBtnText = "#GameUI_OK", GenericConfirmDialogCallback callback = NULL, vgui::Panel *parent = NULL, void *pContext = NULL );
+CTFMessageBoxDialog *ShowMessageBox( const char *pTitle, const wchar_t *pText, const char *pConfirmBtnText = "#GameUI_OK", GenericConfirmDialogCallback callback = NULL, vgui::Panel *parent = NULL, void *pContext = NULL );
+CTFMessageBoxDialog *ShowMessageBox( const char *pTitle, const char *pText, KeyValues *pKeyValues, const char *pConfirmBtnText = "#GameUI_OK", GenericConfirmDialogCallback callback = NULL, vgui::Panel *parent = NULL, void *pContext = NULL );
+CTFMessageBoxDialog *ShowUpgradeMessageBox( const char *pTitle, const char *pText );
+CTFMessageBoxDialog *ShowUpgradeMessageBox( const char *pTitle, const char *pText, const char *pConfirmBtnText, GenericConfirmDialogCallback callback, vgui::Panel *parent = NULL, void *pContext = NULL );
+
+//-----------------------------------------------------------------------------
+CTFMessageBoxDialogWithSound *ShowMessageBoxWithSound( const char *pTitle, const char *pText, const char *pszSound, float flDelay = 0.0, const char *pConfirmBtnText = "#GameUI_OK", GenericConfirmDialogCallback callback = NULL, vgui::Panel *parent = NULL, void *pContext = NULL );
+CTFMessageBoxDialogWithSound *ShowMessageBoxWithSound( const char *pTitle, const wchar_t *pText, const char *pszSound, float flDelay = 0.0, const char *pConfirmBtnText = "#GameUI_OK", GenericConfirmDialogCallback callback = NULL, vgui::Panel *parent = NULL, void *pContext = NULL );
+CTFMessageBoxDialogWithSound *ShowMessageBoxWithSound( const char *pTitle, const char *pText, KeyValues *pKeyValues, const char *pszSound, float flDelay = 0.0, const char *pConfirmBtnText = "#GameUI_OK", GenericConfirmDialogCallback callback = NULL, vgui::Panel *parent = NULL, void *pContext = NULL );
+
+#endif // CONFIRM_DIALOG_H
diff --git a/game/client/econ/econ_consumables.cpp b/game/client/econ/econ_consumables.cpp
new file mode 100644
index 0000000..15581e6
--- /dev/null
+++ b/game/client/econ/econ_consumables.cpp
@@ -0,0 +1,266 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#include "cbase.h"
+
+#include "econ_item_tools.h"
+
+//---------------------------------------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------------------------------------
+const char *IEconTool::GetUseCommandLocalizationToken( const IEconItemInterface *pItem, int i ) const
+{
+ Assert( i == 0 ); // Default only has 1 use, so this should be 0.
+ Assert( pItem );
+ Assert( pItem->GetItemDefinition() );
+ Assert( pItem->GetItemDefinition()->GetEconTool() == this );
+
+ // If we have a custom schema-specified use string, use that.
+ return GetUseString();
+}
+
+//---------------------------------------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------------------------------------
+const char* IEconTool::GetUseCommand( const IEconItemInterface *pItem, int i ) const
+{
+ Assert( i == 0 ); // Default only has 1 use, so this should be 0.
+ Assert( pItem );
+ Assert( pItem->GetItemDefinition() );
+ Assert( pItem->GetItemDefinition()->GetEconTool() == this );
+
+ const bool bIsGCConsumable = ( ( pItem->GetItemDefinition()->GetCapabilities() & ITEM_CAP_USABLE_GC ) != 0 );
+ return bIsGCConsumable ? "Context_UseConsumableItem" : "Context_ApplyOnItem";
+}
+
+//---------------------------------------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------------------------------------
+bool IsLocalPlayerWrappedGift( const IEconItemInterface *pItem )
+{
+ Assert( pItem );
+ Assert( pItem->GetItemDefinition() );
+ Assert( pItem->GetItemDefinition()->GetTypedEconTool<CEconTool_WrappedGift>() );
+
+ static CSchemaAttributeDefHandle pAttr_GifterAccountID( "gifter account id" );
+
+ uint32 unGifterAccountID;
+ if ( !pItem->FindAttribute( pAttr_GifterAccountID, &unGifterAccountID ) )
+ return false;
+
+ const uint32 unLocalAccountID = steamapicontext->SteamUser()->GetSteamID().GetAccountID();
+
+ return unGifterAccountID == unLocalAccountID;
+}
+
+//---------------------------------------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------------------------------------
+bool CEconTool_WrappedGift::CanBeUsedNow( const IEconItemInterface *pItem ) const
+{
+ static CSchemaItemDefHandle pItemDef_WrappedGiftapultPackage( "Wrapped Giftapult Package" );
+ static CSchemaItemDefHandle pItemDef_DeliveredGiftapultPackage( "Delivered Giftapult Package" );
+ static CSchemaItemDefHandle pItemDef_CompetitiveBetaPassGift( "Competitive Matchmaking Beta Giftable Invite" );
+
+ Assert( pItem );
+ Assert( pItem->GetItemDefinition() );
+ Assert( pItem->GetItemDefinition()->GetEconTool() == this );
+
+ if ( ( pItem->GetItemDefinition() == pItemDef_WrappedGiftapultPackage ) ||
+ ( pItem->GetItemDefinition() == pItemDef_CompetitiveBetaPassGift ) ||
+ ( pItem->GetItemDefinition() == pItemDef_DeliveredGiftapultPackage ) )
+ return true;
+
+ return pItem->IsTradable();
+}
+
+//---------------------------------------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------------------------------------
+bool CEconTool_WrappedGift::ShouldShowContainedItemPanel( const IEconItemInterface *pItem ) const
+{
+ Assert( pItem );
+ Assert( pItem->GetItemDefinition() );
+ Assert( pItem->GetItemDefinition()->GetEconTool() == this );
+
+ return IsLocalPlayerWrappedGift( pItem );
+}
+
+//---------------------------------------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------------------------------------
+const char *CEconTool_WrappedGift::GetUseCommandLocalizationToken( const IEconItemInterface *pItem, int i ) const
+{
+ Assert( pItem );
+ Assert( pItem->GetItemDefinition() );
+ Assert( pItem->GetItemDefinition()->GetEconTool() == this );
+
+ Assert( i == 0 || ( IsLocalPlayerWrappedGift( pItem ) && i == 1 ) );
+
+ // NOTE! Keep in sync with CEconTool_WrappedGift::GetUseCommand
+ if ( BIsDirectGift() ||
+ ( IsLocalPlayerWrappedGift( pItem ) && i == 0 ) )
+ return "#DeliverGift";
+ return "#UnwrapGift";
+}
+
+//---------------------------------------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------------------------------------
+int CEconTool_WrappedGift::GetUseCommandCount( const IEconItemInterface *pItem ) const
+{
+ Assert( pItem );
+ Assert( pItem->GetItemDefinition() );
+ Assert( pItem->GetItemDefinition()->GetEconTool() == this );
+
+ if ( IsLocalPlayerWrappedGift( pItem ) )
+ return 2;
+ return 1;
+}
+
+//---------------------------------------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------------------------------------
+const char* CEconTool_WrappedGift::GetUseCommand( const IEconItemInterface *pItem, int i ) const
+{
+ // NOTE! Keep in sync with CEconTool_WrappedGift::GetUseCommandLocalizationToken
+ Assert( pItem );
+ Assert( pItem->GetItemDefinition() );
+ Assert( pItem->GetItemDefinition()->GetEconTool() == this );
+
+ Assert( i == 0 || ( IsLocalPlayerWrappedGift( pItem ) && i == 1 ) );
+
+ // NOTE! Keep in sync with CEconTool_WrappedGift::GetUseCommand
+ if ( BIsDirectGift() ||
+ ( IsLocalPlayerWrappedGift( pItem ) && i == 0 ) )
+ return "Context_DeliverItem";
+ return "Context_UnwrapItem";
+}
+
+//---------------------------------------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------------------------------------
+const char *CEconTool_WeddingRing::GetUseCommandLocalizationToken( const IEconItemInterface *pItem, int i ) const
+{
+ Assert( i == 0 ); // We only have one action.
+ Assert( pItem );
+ Assert( pItem->GetItemDefinition() );
+ Assert( pItem->GetItemDefinition()->GetEconTool() == this );
+
+ // If the wedding ring has been gifted to us, we can use it to accept/reject the proposal.
+ // If it hasn't been gifted we can't use it at all.
+ static CSchemaAttributeDefHandle pAttrDef_GifterAccountID( "gifter account id" );
+
+ if ( !pItem->FindAttribute( pAttrDef_GifterAccountID ) )
+ return NULL;
+
+ return "#ToolAction_WeddingRing_AcceptReject";
+}
+
+#ifndef TF_CLIENT_DLL
+//---------------------------------------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------------------------------------
+void CEconTool_Noisemaker::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_Noisemaker::OnClientUseConsumable() is unimplemented!" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_WrappedGift::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_WrappedGift::OnClientUseConsumable() is unimplemented!" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_WeddingRing::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_WeddingRing::OnClientUseConsumable() is unimplemented!" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_BackpackExpander::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_BackpackExpander::OnClientUseConsumable() is unimplemented!" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_AccountUpgradeToPremium::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_AccountUpgradeToPremium::OnClientUseConsumable() is unimplemented!" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_ClaimCode::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_ClaimCode::OnClientUseConsumable() is unimplemented!" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_Collection::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_Collection::OnClientUseConsumable() is unimplemented!" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_StrangifierBase::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_StrangifierBase::OnClientUseConsumable() is unimplemented!" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_PaintCan::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_PaintCan::OnClientUseConsumable() is unimplemented!" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_Gift::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_Gift::OnClientUseConsumable() is unimplemented!" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_DuelingMinigame::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_DuelingMinigame::OnClientUseConsumable() is unimplemented!" );
+}
+
+//-----------------------------------------------------------------------------
+void CEconTool_DuckToken::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_DuckToken::OnClientUseConsumable() is unimplemented!" );
+}
+//-----------------------------------------------------------------------------
+void CEconTool_GrantOperationPass::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_DuckToken::CEconTool_GrantOperationPass() is unimplemented!" );
+}
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_Default::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
+{
+ Assert( !"CEconTool_Default::OnClientUseConsumable() is unimplemented!" );
+}
+
+#endif // !defined( TF_CLIENT_DLL )
diff --git a/game/client/econ/econ_controls.cpp b/game/client/econ/econ_controls.cpp
new file mode 100644
index 0000000..c78dd36
--- /dev/null
+++ b/game/client/econ/econ_controls.cpp
@@ -0,0 +1,2382 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+#include "cbase.h"
+
+#include "ienginevgui.h"
+#include <vgui_controls/ScrollBarSlider.h>
+#include "vgui/ILocalize.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "econ_controls.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/PropertyPage.h"
+#include "econ_item_system.h"
+#include "econ_item_tools.h"
+#include "iachievementmgr.h"
+#include "econ_item_description.h"
+
+#if defined(TF_DLL) || defined(TF_CLIENT_DLL)
+#include "tf_shareddefs.h"
+#endif
+
+using namespace vgui;
+
+DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CExButton, CExButton );
+DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CExImageButton, CExImageButton );
+DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CExLabel, CExLabel );
+DECLARE_BUILD_FACTORY( CExRichText );
+DECLARE_BUILD_FACTORY( CRichTextWithScrollbarBorders );
+DECLARE_BUILD_FACTORY( CEconItemDetailsRichText );
+DECLARE_BUILD_FACTORY( CExplanationPopup );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool SetChildPanelVisible( vgui::Panel *pParent, const char *pChildName, bool bVisible, bool bSearchForChildRecursively )
+{
+ vgui::Panel *pPanel = pParent->FindChildByName( pChildName, bSearchForChildRecursively );
+ if ( pPanel )
+ {
+ if ( pPanel->IsVisible() != bVisible )
+ {
+ pPanel->SetVisible( bVisible );
+ }
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool SetChildPanelEnabled( vgui::Panel *pParent, const char *pChildName, bool bEnabled, bool bSearchForChildRecursively )
+{
+ vgui::Panel *pPanel = pParent->FindChildByName( pChildName, bSearchForChildRecursively );
+ if ( pPanel )
+ {
+ if ( pPanel->IsEnabled() != bEnabled )
+ {
+ pPanel->SetEnabled( bEnabled );
+ }
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool SetChildButtonSelected( vgui::Panel *pParent, const char *pChildName, bool bSelected, bool bSearchForChildRecursively )
+{
+ vgui::Button *pPanel = dynamic_cast< vgui::Button* >( pParent->FindChildByName( pChildName, bSearchForChildRecursively ) );
+ if ( pPanel )
+ {
+ if ( pPanel->IsSelected() != bSelected )
+ {
+ pPanel->SetSelected( bSelected );
+ }
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool IsChildButtonSelected( vgui::Panel *pParent, const char *pChildName, bool bSearchForChildRecursively )
+{
+ vgui::Button *pPanel = dynamic_cast< vgui::Button* >( pParent->FindChildByName( pChildName, bSearchForChildRecursively ) );
+ if ( pPanel )
+ {
+ return pPanel->IsSelected();
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool AddChildActionSignalTarget( vgui::Panel *pParent, const char *pChildName, Panel *messageTarget, bool bSearchForChildRecursively )
+{
+ vgui::Panel *pPanel = pParent->FindChildByName( pChildName, bSearchForChildRecursively );
+ if ( pPanel )
+ {
+ pPanel->AddActionSignalTarget( messageTarget );
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool SetXToRed( vgui::Label *pPanel )
+{
+ if ( !pPanel )
+ return false;
+
+ wchar_t wszConfirmText[256];
+ pPanel->GetText( wszConfirmText, sizeof( wszConfirmText ) );
+
+ if ( ( wszConfirmText[0] == L'x' || wszConfirmText[0] == L'X' ) && wszConfirmText[1] == L' ' )
+ {
+ pPanel->GetTextImage()->ClearColorChangeStream();
+ pPanel->GetTextImage()->AddColorChange( Color(200,80,60,255), 0 );
+ pPanel->GetTextImage()->AddColorChange( pPanel->GetFgColor(), 1 );
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExButton::CExButton( Panel *parent, const char *name, const char *text, vgui::Panel *pActionSignalTarget, const char *cmd ) : Button( parent, name, text, pActionSignalTarget, cmd )
+{
+ m_szFont[0] = '\0';
+ m_szColor[0] = '\0';
+ m_pArmedBorder = NULL;
+ m_pDefaultBorderOverride = NULL;
+ m_pSelectedBorder = NULL;
+ m_pDisabledBorder = NULL;
+ m_bbCursorEnterExitEvent = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExButton::CExButton( Panel *parent, const char *name, const wchar_t *wszText, vgui::Panel *pActionSignalTarget, const char *cmd ) : Button( parent, name, wszText, pActionSignalTarget, cmd )
+{
+ m_szFont[0] = '\0';
+ m_szColor[0] = '\0';
+ m_pArmedBorder = NULL;
+ m_pDefaultBorderOverride = NULL;
+ m_pSelectedBorder = NULL;
+ m_pDisabledBorder = NULL;
+ m_bbCursorEnterExitEvent = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExButton::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ SetFontStr( inResourceData->GetString( "font", "Default" ) );
+ SetColorStr( inResourceData->GetString( "fgcolor", "Button.TextColor" ) );
+
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+
+ const char *pszBorder = inResourceData->GetString( "border_default", "" );
+ if ( *pszBorder )
+ {
+ m_pDefaultBorderOverride = pScheme->GetBorder( pszBorder );
+ }
+
+ pszBorder = inResourceData->GetString( "border_armed", "" );
+ if ( *pszBorder )
+ {
+ m_pArmedBorder = pScheme->GetBorder( pszBorder );
+ }
+
+ pszBorder = inResourceData->GetString( "border_disabled", "" );
+ if ( *pszBorder )
+ {
+ m_pDisabledBorder = pScheme->GetBorder( pszBorder );
+ }
+
+ const char *pszSelectedBorder = inResourceData->GetString( "border_selected", "" );
+ if ( *pszSelectedBorder )
+ {
+ m_pSelectedBorder = pScheme->GetBorder( pszSelectedBorder );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+vgui::IBorder *CExButton::GetBorder(bool depressed, bool armed, bool selected, bool keyfocus)
+{
+ if ( !IsEnabled() && m_pDisabledBorder )
+ return m_pDisabledBorder;
+
+ if ( selected && m_pSelectedBorder )
+ return m_pSelectedBorder;
+
+ if ( armed && m_pArmedBorder )
+ return m_pArmedBorder;
+
+ if ( m_pDefaultBorderOverride )
+ return m_pDefaultBorderOverride;
+
+ return BaseClass::GetBorder( depressed, armed, selected, keyfocus );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExButton::SetFontStr( const char *pFont )
+{
+ V_strcpy_safe( m_szFont, pFont );
+
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ SetFont( pScheme->GetFont( m_szFont, true ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExButton::SetColorStr( const char *pColor )
+{
+ V_strcpy_safe( m_szColor, pColor );
+
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ SetFgColor( pScheme->GetColor( m_szColor, Color( 255, 255, 255, 255 ) ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExButton::OnMouseFocusTicked()
+{
+ BaseClass::OnMouseFocusTicked();
+
+ if ( m_hMouseTickTarget )
+ {
+ KeyValues *pMessage = new KeyValues("MouseFocusTicked");
+ vgui::ipanel()->SendMessage( m_hMouseTickTarget, pMessage, GetVPanel());
+ pMessage->deleteThis();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExButton::OnCursorEntered()
+{
+ BaseClass::OnCursorEntered();
+
+ if ( m_hMouseTickTarget && m_bbCursorEnterExitEvent )
+ {
+ KeyValues *pMessage = new KeyValues("CursorEntered");
+ vgui::ipanel()->SendMessage( m_hMouseTickTarget, pMessage, GetVPanel());
+ pMessage->deleteThis();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExButton::OnCursorExited()
+{
+ BaseClass::OnCursorExited();
+
+ if ( m_hMouseTickTarget && m_bbCursorEnterExitEvent )
+ {
+ KeyValues *pMessage = new KeyValues("CursorExited");
+ vgui::ipanel()->SendMessage( m_hMouseTickTarget, pMessage, GetVPanel());
+ pMessage->deleteThis();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExImageButton::CExImageButton( Panel *parent, const char *name, const char *text, vgui::Panel *pActionSignalTarget, const char *cmd ) : CExButton( parent, name, text, pActionSignalTarget, cmd )
+{
+ m_ImageDrawColor = Color(255,255,255,255);
+ m_ImageArmedColor = Color(255,255,255,255);
+ m_ImageDepressedColor = Color(255,255,255,255);
+ m_ImageDisabledColor = Color(255,255,255,255);
+ m_ImageSelectedColor = Color(255,255,255,255);
+ m_pEmbeddedImagePanel = new vgui::ImagePanel( this, "SubImage" );
+ m_szImageDefault[0] = '\0';
+ m_szImageArmed[0] = '\0';
+ m_szImageSelected[0] = '\0';
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExImageButton::CExImageButton( Panel *parent, const char *name, const wchar_t *wszText, vgui::Panel *pActionSignalTarget, const char *cmd ) : CExButton( parent, name, wszText, pActionSignalTarget, cmd )
+{
+ m_ImageDrawColor = Color(255,255,255,255);
+ m_ImageArmedColor = Color(255,255,255,255);
+ m_ImageDepressedColor = Color(255,255,255,255);
+ m_ImageDisabledColor = Color(255,255,255,255);
+ m_ImageSelectedColor = Color(255,255,255,255);
+ m_pEmbeddedImagePanel = new vgui::ImagePanel( this, "SubImage" );
+ m_szImageDefault[0] = '\0';
+ m_szImageArmed[0] = '\0';
+ m_szImageSelected[0] = '\0';
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExImageButton::~CExImageButton( void )
+{
+ m_pEmbeddedImagePanel->MarkForDeletion();
+ m_pEmbeddedImagePanel = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExImageButton::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ int r,g,b,a;
+
+ const char *pszDrawColor = inResourceData->GetString("image_drawcolor", "");
+ if (*pszDrawColor)
+ {
+ if (sscanf(pszDrawColor, "%d %d %d %d", &r, &g, &b, &a) >= 3)
+ {
+ m_ImageDrawColor = Color(r, g, b, a);
+ }
+ }
+
+ pszDrawColor = inResourceData->GetString("image_armedcolor", "");
+ if (*pszDrawColor)
+ {
+ if (sscanf(pszDrawColor, "%d %d %d %d", &r, &g, &b, &a) >= 3)
+ {
+ m_ImageArmedColor = Color(r, g, b, a);
+ }
+ }
+
+ pszDrawColor = inResourceData->GetString("image_depressedcolor", "");
+ if (*pszDrawColor)
+ {
+ if (sscanf(pszDrawColor, "%d %d %d %d", &r, &g, &b, &a) >= 3)
+ {
+ m_ImageDepressedColor = Color(r, g, b, a);
+ }
+ }
+
+ pszDrawColor = inResourceData->GetString("image_disabledcolor", "");
+ if (*pszDrawColor)
+ {
+ if (sscanf(pszDrawColor, "%d %d %d %d", &r, &g, &b, &a) >= 3)
+ {
+ m_ImageDisabledColor = Color(r, g, b, a);
+ }
+ }
+
+ pszDrawColor = inResourceData->GetString( "image_selectedcolor", "" );
+ if (*pszDrawColor)
+ {
+ if (sscanf(pszDrawColor, "%d %d %d %d", &r, &g, &b, &a) >= 3)
+ {
+ m_ImageSelectedColor = Color(r, g, b, a);
+ }
+ }
+
+ KeyValues *pButtonKV = inResourceData->FindKey( "SubImage" );
+ if ( pButtonKV )
+ {
+ m_pEmbeddedImagePanel->ApplySettings( pButtonKV );
+ }
+
+ const char *pszImageDefault = inResourceData->GetString("image_default", "");
+ if (*pszImageDefault)
+ {
+ SetImageDefault( pszImageDefault );
+ }
+
+ const char *pszImageArmed = inResourceData->GetString("image_armed", "");
+ if (*pszImageArmed)
+ {
+ SetImageArmed( pszImageArmed );
+ }
+
+ const char *pszImageSelected = inResourceData->GetString("image_selected", "");
+ if (*pszImageSelected)
+ {
+ SetImageSelected( pszImageSelected );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Color CExImageButton::GetImageColor( void )
+{
+ if ( !IsEnabled() )
+ return m_ImageDisabledColor;
+ if ( IsSelected() )
+ return m_ImageSelectedColor;
+ if ( IsDepressed() )
+ return m_ImageDepressedColor;
+ if ( IsArmed() )
+ return m_ImageArmedColor;
+ return m_ImageDrawColor;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExImageButton::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+ m_pEmbeddedImagePanel->SetMouseInputEnabled( false );
+ m_pEmbeddedImagePanel->SetDrawColor( GetImageColor() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExImageButton::SetArmed(bool state)
+{
+ BaseClass::SetArmed( state );
+
+ if ( m_pEmbeddedImagePanel )
+ {
+ m_pEmbeddedImagePanel->SetDrawColor( GetImageColor() );
+
+ const char *pszImage = state ? m_szImageArmed : m_szImageDefault;
+ if ( *pszImage )
+ {
+ SetSubImage( pszImage );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExImageButton::SetEnabled(bool state)
+{
+ BaseClass::SetEnabled( state );
+
+ if ( m_pEmbeddedImagePanel )
+ {
+ m_pEmbeddedImagePanel->SetDrawColor( GetImageColor() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExImageButton::SetSelected(bool state)
+{
+ BaseClass::SetSelected( state );
+
+ if ( m_pEmbeddedImagePanel )
+ {
+ m_pEmbeddedImagePanel->SetDrawColor( GetImageColor() );
+
+ const char *pszImage = state ? m_szImageSelected : m_szImageDefault;
+ if ( *pszImage )
+ {
+ SetSubImage( pszImage );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExImageButton::SetSubImage( const char *pszImage )
+{
+ m_pEmbeddedImagePanel->SetImage( pszImage );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExImageButton::SetImageDefault( const char *pszImageDefault )
+{
+ V_strcpy_safe( m_szImageDefault, pszImageDefault );
+ if ( !IsArmed() )
+ {
+ SetSubImage( pszImageDefault );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExImageButton::SetImageArmed( const char *pszImageArmed )
+{
+ V_strcpy_safe( m_szImageArmed, pszImageArmed );
+ if ( IsArmed() )
+ {
+ SetSubImage( m_szImageArmed );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExImageButton::SetImageSelected( const char *pszImageSelected )
+{
+ V_strcpy_safe( m_szImageSelected, pszImageSelected );
+ if ( IsSelected() )
+ {
+ SetSubImage( m_szImageSelected );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExLabel::CExLabel( Panel *parent, const char *name, const char *text ) : Label( parent, name, text )
+{
+ m_szColor[0] = '\0';
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExLabel::CExLabel( Panel *parent, const char *name, const wchar_t *wszText ) : Label( parent, name, wszText )
+{
+ m_szColor[0] = '\0';
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExLabel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ SetColorStr( inResourceData->GetString( "fgcolor", "Label.TextColor" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExLabel::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ // Reapply our custom color, so we stomp the base scheme's
+ SetColorStr( m_szColor );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExLabel::SetColorStr( const char *pColor )
+{
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ SetColorStr( pScheme->GetColor( pColor, Color( 0, 255, 0, 255 ) ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExLabel::SetColorStr( Color cColor )
+{
+ Q_snprintf( m_szColor, ARRAYSIZE(m_szColor), "%d %d %d %d", cColor.r(), cColor.g(), cColor.b(), cColor.a() );
+ SetFgColor( cColor );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExRichText::CExRichText( Panel *parent, const char *name ) : RichText( parent, name )
+{
+ m_szFont[0] = '\0';
+ m_szColor[0] = '\0';
+ m_szImageUpArrow[0] = '\0';
+ m_szImageDownArrow[0] = '\0';
+ m_szImageLine[0] = '\0';
+ m_szImageBox[0] = '\0';
+ m_bUseImageBorders = false;
+ m_pBox = NULL;
+ m_pLine = NULL;
+
+ SetCursor(dc_arrow);
+
+ m_pUpArrow = new CExImageButton( this, "UpArrow", "" );
+ if ( m_pUpArrow )
+ {
+ m_pUpArrow->AddActionSignalTarget( _vertScrollBar );
+ m_pUpArrow->SetCommand(new KeyValues("ScrollButtonPressed", "index", 0));
+ m_pUpArrow->GetImage()->SetShouldScaleImage( true );
+ m_pUpArrow->SetFgColor( Color( 255, 255, 255, 255 ) );
+ m_pUpArrow->SetAlpha( 255 );
+ m_pUpArrow->SetPaintBackgroundEnabled( false );
+ m_pUpArrow->SetVisible( false );
+ }
+
+ m_pDownArrow = new CExImageButton( this, "DownArrow", "" );
+ if ( m_pDownArrow )
+ {
+ m_pDownArrow->AddActionSignalTarget( _vertScrollBar );
+ m_pDownArrow->SetCommand(new KeyValues("ScrollButtonPressed", "index", 1));
+ m_pDownArrow->GetImage()->SetShouldScaleImage( true );
+ m_pDownArrow->SetFgColor( Color( 255, 255, 255, 255 ) );
+ m_pDownArrow->SetAlpha( 255 );
+ m_pDownArrow->SetPaintBackgroundEnabled( false );
+ m_pDownArrow->SetVisible( false );
+ }
+
+ _vertScrollBar->SetOverriddenButtons( m_pUpArrow, m_pDownArrow );
+ m_pUpArrow->PassMouseTicksTo( _vertScrollBar );
+ m_pDownArrow->PassMouseTicksTo( _vertScrollBar );
+
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExRichText::CreateImagePanels( void )
+{
+ if ( m_pBox || m_pLine )
+ return;
+
+ if ( m_bUseImageBorders )
+ {
+ m_pLine = new vgui::Panel( this, "Line" );
+ m_pBox = new vgui::Panel( this, "Box" );
+ }
+ else
+ {
+ m_pLine = new vgui::ImagePanel( this, "Line" );
+ m_pBox = new vgui::ImagePanel( this, "Box" );
+
+ dynamic_cast<vgui::ImagePanel *>(m_pBox)->SetShouldScaleImage( true );
+ dynamic_cast<vgui::ImagePanel *>(m_pLine)->SetShouldScaleImage( true );
+ }
+ m_pBox->SetVisible( false );
+ m_pLine->SetVisible( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExRichText::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ SetFontStr( inResourceData->GetString( "font", "Default" ) );
+ SetColorStr( inResourceData->GetString( "fgcolor", "RichText.TextColor" ) );
+
+ SetCustomImage( m_pUpArrow->GetImage(), inResourceData->GetString( "image_up_arrow", "chalkboard_scroll_up" ), m_szImageUpArrow );
+ SetCustomImage( m_pDownArrow->GetImage(), inResourceData->GetString( "image_down_arrow", "chalkboard_scroll_down" ), m_szImageDownArrow );
+ SetCustomImage( m_pLine, inResourceData->GetString( "image_line", "chalkboard_scroll_line" ), m_szImageLine );
+ SetCustomImage( m_pBox, inResourceData->GetString( "image_box", "chalkboard_scroll_box" ), m_szImageBox );
+
+ const char *pszMouseover = inResourceData->GetString( "image_up_arrow_mouseover", NULL );
+ if ( pszMouseover )
+ {
+ m_pUpArrow->SetImageArmed( pszMouseover );
+ m_pUpArrow->SetImageDefault( m_szImageUpArrow );
+ }
+ pszMouseover = inResourceData->GetString( "image_down_arrow_mouseover", NULL );
+ if ( pszMouseover )
+ {
+ m_pDownArrow->SetImageArmed( pszMouseover );
+ m_pDownArrow->SetImageDefault( m_szImageDownArrow );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExRichText::SetFontStr( const char *pFont )
+{
+ if ( pFont != m_szFont )
+ {
+ V_strcpy_safe( m_szFont, pFont );
+ }
+
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ SetFont( pScheme->GetFont( m_szFont, true ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExRichText::SetColorStr( const char *pColor )
+{
+ if ( pColor != m_szColor )
+ {
+ V_strcpy_safe( m_szColor, pColor );
+ }
+
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ SetFgColor( pScheme->GetColor( m_szColor, Color( 255, 255, 255, 255 ) ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExRichText::SetCustomImage( vgui::Panel *pImage, const char *pszImage, char *pszStorage )
+{
+ if ( pszStorage )
+ {
+ V_strcpy( pszStorage, pszImage );
+ }
+ if ( !pImage )
+ return;
+
+ if ( m_bUseImageBorders )
+ {
+ vgui::IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ IBorder *pBorder = pScheme->GetBorder( pszImage );
+
+ if ( pBorder )
+ {
+ pImage->SetBorder( pBorder );
+ return;
+ }
+ }
+
+ vgui::ImagePanel *pImagePanel = dynamic_cast<vgui::ImagePanel *>(pImage);
+ if ( pImagePanel )
+ {
+ pImagePanel->SetImage( pszImage );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExRichText::ApplySchemeSettings( IScheme *pScheme )
+{
+ CreateImagePanels();
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ // Reapply any custom font/color, so we stomp the base scheme's
+ SetFontStr( m_szFont );
+ SetColorStr( m_szColor );
+ SetCustomImage( m_pUpArrow->GetImage(), m_szImageUpArrow, NULL );
+ SetCustomImage( m_pDownArrow->GetImage(), m_szImageDownArrow, NULL );
+ SetCustomImage( m_pLine, m_szImageLine, NULL );
+ SetCustomImage( m_pBox, m_szImageBox, NULL );
+
+ SetBorder( pScheme->GetBorder( "NoBorder" ) );
+ SetBgColor( pScheme->GetColor( "Blank", Color( 0,0,0,0 ) ) );
+ SetPanelInteractive( false );
+ SetUnusedScrollbarInvisible( true );
+
+ if ( m_pDownArrow )
+ {
+ m_pDownArrow->SetFgColor( Color( 255, 255, 255, 255 ) );
+ }
+
+ if ( m_pUpArrow )
+ {
+ m_pUpArrow->SetFgColor( Color( 255, 255, 255, 255 ) );
+ }
+
+ SetScrollBarImagesVisible( false );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExRichText::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( _vertScrollBar )
+ {
+ _vertScrollBar->SetZPos( 500 );
+ m_pUpArrow->SetZPos( 501 );
+ m_pDownArrow->SetZPos( 501 );
+
+ // turn off painting the vertical scrollbar
+ _vertScrollBar->SetPaintBackgroundEnabled( false );
+ _vertScrollBar->SetPaintBorderEnabled( false );
+ _vertScrollBar->SetPaintEnabled( false );
+ _vertScrollBar->SetScrollbarButtonsVisible( false );
+ _vertScrollBar->GetButton(0)->SetMouseInputEnabled( false );
+ _vertScrollBar->GetButton(1)->SetMouseInputEnabled( false );
+
+ if ( _vertScrollBar->IsVisible() )
+ {
+ int nMin, nMax;
+ _vertScrollBar->GetRange( nMin, nMax );
+ _vertScrollBar->SetValue( nMin );
+
+ int nScrollbarWide = _vertScrollBar->GetWide();
+
+ int wide, tall;
+ GetSize( wide, tall );
+
+ if ( m_pUpArrow )
+ {
+ m_pUpArrow->SetBounds( wide - nScrollbarWide, 0, nScrollbarWide, nScrollbarWide );
+ m_pUpArrow->GetImage()->SetSize( nScrollbarWide, nScrollbarWide );
+ }
+
+ if ( m_pLine )
+ {
+ m_pLine->SetBounds( wide - nScrollbarWide, nScrollbarWide, nScrollbarWide, tall - ( 2 * nScrollbarWide ) );
+ }
+
+ if ( m_pBox )
+ {
+ m_pBox->SetBounds( wide - nScrollbarWide, nScrollbarWide, nScrollbarWide, nScrollbarWide );
+ }
+
+ if ( m_pDownArrow )
+ {
+ m_pDownArrow->SetBounds( wide - nScrollbarWide, tall - nScrollbarWide, nScrollbarWide, nScrollbarWide );
+ m_pDownArrow->GetImage()->SetSize( nScrollbarWide, nScrollbarWide );
+ }
+
+ SetScrollBarImagesVisible( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExRichText::SetText( const wchar_t *text )
+{
+ wchar_t buffer[2048];
+ Q_wcsncpy( buffer, text, sizeof( buffer ) );
+
+ // transform '\r' to ' ' to eliminate double-spacing on line returns
+ for ( wchar_t *ch = buffer; *ch != 0; ch++ )
+ {
+ if ( *ch == '\r' )
+ {
+ *ch = ' ';
+ }
+ }
+
+ BaseClass::SetText( buffer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExRichText::SetText( const char *text )
+{
+ char buffer[2048];
+ Q_strncpy( buffer, text, sizeof( buffer ) );
+
+ // transform '\r' to ' ' to eliminate double-spacing on line returns
+ for ( char *ch = buffer; *ch != 0; ch++ )
+ {
+ if ( *ch == '\r' )
+ {
+ *ch = ' ';
+ }
+ }
+
+ BaseClass::SetText( buffer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExRichText::SetScrollBarImagesVisible( bool visible )
+{
+ if ( m_pDownArrow && m_pDownArrow->IsVisible() != visible )
+ {
+ m_pDownArrow->SetVisible( visible );
+ m_pDownArrow->SetEnabled( visible );
+ }
+
+ if ( m_pUpArrow && m_pUpArrow->IsVisible() != visible )
+ {
+ m_pUpArrow->SetVisible( visible );
+ m_pUpArrow->SetEnabled( visible );
+ }
+
+ if ( m_pLine && m_pLine->IsVisible() != visible )
+ {
+ m_pLine->SetVisible( visible );
+ }
+
+ if ( m_pBox && m_pBox->IsVisible() != visible )
+ {
+ m_pBox->SetVisible( visible );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExRichText::OnTick()
+{
+ if ( !IsVisible() )
+ return;
+
+ if ( m_pDownArrow && m_pUpArrow && m_pLine && m_pBox )
+ {
+ if ( _vertScrollBar && _vertScrollBar->IsVisible() )
+ {
+ // turn on our own images
+ SetScrollBarImagesVisible ( true );
+
+ // set the alpha on the up arrow
+ int nMin, nMax;
+ _vertScrollBar->GetRange( nMin, nMax );
+ int nScrollPos = _vertScrollBar->GetValue();
+ int nRangeWindow = _vertScrollBar->GetRangeWindow();
+ int nBottom = nMax - nRangeWindow;
+ if ( nBottom < 0 )
+ {
+ nBottom = 0;
+ }
+
+ // set the alpha on the up arrow
+ int nAlpha = ( nScrollPos - nMin <= 0 ) ? 90 : 255;
+ m_pUpArrow->SetAlpha( nAlpha );
+
+ // set the alpha on the down arrow
+ nAlpha = ( nScrollPos >= nBottom ) ? 90 : 255;
+ m_pDownArrow->SetAlpha( nAlpha );
+
+ ScrollBarSlider *pSlider = _vertScrollBar->GetSlider();
+ if ( pSlider && pSlider->GetRangeWindow() > 0 )
+ {
+ int x, y, w, t, min, max;
+ m_pLine->GetBounds( x, y, w, t );
+ pSlider->GetNobPos( min, max );
+
+ m_pBox->SetBounds( x, y + min, w, ( max - min ) );
+ }
+ }
+ else
+ {
+ // turn off our images
+ SetScrollBarImagesVisible ( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Rich text control that knows how to fill itself with information
+// that describes a specific item definition.
+//-----------------------------------------------------------------------------
+CEconItemDetailsRichText::CEconItemDetailsRichText( vgui::Panel *parent, const char *panelName )
+: BaseClass( parent, panelName ),
+ m_bAllowItemSetLinks( false ),
+ m_bLimitedItem( false ),
+ m_hLinkFont( INVALID_FONT )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ const char *pszHighlightColor = inResourceData->GetString( "highlight_color", "Orange" );
+ const char *pszItemSetColor = inResourceData->GetString( "itemset_color", "Blue" );
+ const char *pszLinkColor = inResourceData->GetString( "link_color", "LightOrange" );
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ m_colTextHighlight = pScheme->GetColor( pszHighlightColor, Color( 255, 255, 255, 255 ) );
+ m_colItemSet = pScheme->GetColor( pszItemSetColor, Color( 255, 255, 255, 255 ) );
+ m_colLink = pScheme->GetColor( pszLinkColor, Color( 255, 255, 255, 255 ) );
+ m_hLinkFont = pScheme->GetFont( "Link", true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetUnderlineFont( m_hLinkFont );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::UpdateDetailsForItem( const CEconItemDefinition *pDef )
+{
+ SetText( "" );
+
+ if ( !m_ToolList.Count() )
+ {
+ UpdateToolList();
+ }
+
+ DataText_AppendStoreFlags( pDef );
+ DataText_AppendItemData( pDef );
+ DataText_AppendAttributeData( pDef );
+ DataText_AppendUsageData( pDef );
+ DataText_AppendToolUsage( pDef );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::AddDataText( const char *pszText, bool bAddPostLines, const wchar_t *wpszArg, const wchar_t *wpszArg2, const int *pItemDefIndex )
+{
+ static wchar_t wszConstructedString[4096];
+ static wchar_t wszText[4096];
+
+ if ( pszText[0] != '#' )
+ {
+ InsertString( pszText );
+ return;
+ }
+
+ wchar_t *pLocText = g_pVGuiLocalize->Find( pszText );
+ if ( wpszArg && pLocText )
+ {
+ if ( wpszArg2 )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wszConstructedString, pLocText, 2, wpszArg, wpszArg2 );
+ }
+ else
+ {
+ g_pVGuiLocalize->ConstructString_safe( wszConstructedString, pLocText, 1, wpszArg );
+ }
+ pLocText = wszConstructedString;
+ }
+
+ if ( pLocText )
+ {
+ enum
+ {
+ STATE_COLOR_NORMAL = 1,
+ STATE_COLOR_HINT,
+ STATE_COLOR_ITEMSET,
+ STATE_LINK_START,
+ STATE_LINK_STOP,
+ };
+
+ Color color = GetFgColor();
+ Color newColor = color;
+ int startIdx = 0;
+ int endIdx = 0;
+ bool bContinue = true;
+ bool bInLink = false;
+ while ( bContinue )
+ {
+ bool bSetText = false;
+ bool bEnd = false;
+
+ switch ( pLocText[endIdx] )
+ {
+ case 0:
+ bContinue = false;
+ bSetText = true;
+ bEnd = true;
+ break;
+ case STATE_COLOR_NORMAL:
+ newColor = GetFgColor();
+ bSetText = true;
+ break;
+ case STATE_COLOR_HINT:
+ newColor = m_colTextHighlight;
+ bSetText = true;
+ break;
+ case STATE_COLOR_ITEMSET:
+ newColor = m_colItemSet;
+ bSetText = true;
+ break;
+ case STATE_LINK_START:
+ bInLink = true;
+ break;
+ }
+
+ if ( startIdx != endIdx )
+ {
+ if ( bSetText )
+ {
+ // copy the colored text to wide
+ int len = endIdx - startIdx + 1;
+ wcsncpy( wszText, pLocText + startIdx, len );
+ wszText[len-1] = 0;
+
+ // If the next character isn't the end of a link, insert the string
+ if ( bInLink && pItemDefIndex )
+ {
+ InsertItemLink( wszText, *pItemDefIndex, &color );
+ }
+ else
+ {
+ InsertColorChange( color );
+ InsertString( wszText );
+ }
+ bInLink = false;
+ color = newColor;
+
+ // skip past the color change character
+ startIdx = endIdx + 1;
+ }
+ }
+ ++endIdx;
+ }
+
+ if ( bAddPostLines )
+ {
+ InsertString( L"\n\n" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::DataText_AppendUsageData( const CEconItemDefinition *pBaseDef )
+{
+ // Don't show class/slot usage for class/slot tokens
+ if ( pBaseDef->GetItemClass() && ( !V_strcmp( pBaseDef->GetItemClass(), "class_token" ) || !V_strcmp( pBaseDef->GetItemClass(), "slot_token" ) ) )
+ return;
+
+#if defined(TF_DLL) || defined(TF_CLIENT_DLL)
+ const CTFItemDefinition *pDef = dynamic_cast< const CTFItemDefinition *>( pBaseDef );
+ if ( !pDef )
+ return;
+
+ // Class usage
+ if ( pDef->CanBeUsedByAllClasses() )
+ {
+ if ( pDef->GetBundleInfo() != NULL )
+ {
+ AddDataText( "#TF_Armory_Item_ClassUsageAllBundle", false );
+ }
+ else
+ {
+ AddDataText( "#TF_Armory_Item_ClassUsageAll", false );
+ }
+ AddDataText( "\n" );
+ }
+ else
+ {
+ bool bFirst = true;
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ if ( pDef->CanBeUsedByClass(i) )
+ {
+ if ( bFirst )
+ {
+ bFirst = false;
+
+ if ( pDef->GetBundleInfo() != NULL )
+ {
+ AddDataText( "#TF_Armory_Item_ClassUsageBundle", false );
+ }
+ else
+ {
+ AddDataText( "#TF_Armory_Item_ClassUsage", false );
+ }
+ }
+ else
+ {
+ AddDataText( ", " );
+ }
+
+ const wchar_t *pwszClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[i] );
+ if ( pwszClassName )
+ {
+ InsertColorChange( m_colTextHighlight );
+ InsertString( pwszClassName );
+ InsertColorChange( GetFgColor() );
+ }
+ }
+ }
+ if ( !bFirst )
+ {
+ AddDataText( ".\n" );
+ }
+ }
+
+ // Slot usage. First, find out if everyone uses it in the same slot, or whether it's used in different slots per class
+ bool bHasPerClassSlots = false;
+ int iDefaultSlot = pDef->GetDefaultLoadoutSlot();
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ if ( !pDef->CanBeUsedByClass(i) )
+ continue;
+
+ int iClassSlot = pDef->GetLoadoutSlot(i);
+ if ( iClassSlot != iDefaultSlot )
+ {
+ bHasPerClassSlots = true;
+ break;
+ }
+ }
+ // Now print the easy line, or the per-class lines
+ if ( !bHasPerClassSlots )
+ {
+ if ( iDefaultSlot != -1 )
+ {
+ AddDataText( "#TF_Armory_Item_SlotUsageAll", true, g_pVGuiLocalize->Find( ItemSystem()->GetItemSchema()->GetLoadoutStringsForDisplay( pDef->GetEquipType() )[iDefaultSlot] ) );
+ }
+ }
+ else
+ {
+ bool bFirst = true;
+ for ( int i = TF_FIRST_NORMAL_CLASS; i < TF_LAST_NORMAL_CLASS; i++ )
+ {
+ if ( !pDef->CanBeUsedByClass(i) )
+ continue;
+
+ if ( bFirst )
+ {
+ bFirst = false;
+ AddDataText( "#TF_Armory_Item_SlotUsageClassHeader", false );
+ }
+ else
+ {
+ AddDataText( ", " );
+ }
+
+ int iClassSlot = pDef->GetLoadoutSlot(i);
+ AddDataText( "#TF_Armory_Item_SlotUsageClass", false, g_pVGuiLocalize->Find( ItemSystem()->GetItemSchema()->GetLoadoutStringsForDisplay( pDef->GetEquipType() )[iClassSlot] ), g_pVGuiLocalize->Find( g_aPlayerClassNames[i] ) );
+ }
+ if ( !bFirst )
+ {
+ AddDataText( ".\n\n" );
+ }
+ }
+#endif // #if defined(TF_DLL) || defined(TF_CLIENT_DLL)
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::DataText_AppendToolUsage( const CEconItemDefinition *pDef )
+{
+ // Loop through the tools, and list any that can be applied to this item
+ bool bFirstTool = true;
+ for ( int i = 0; i < m_ToolList.Count(); i++ )
+ {
+ const GameItemDefinition_t *pToolDef = dynamic_cast<const GameItemDefinition_t *>( GetItemSchema()->GetItemDefinition( m_ToolList[i] ) );
+
+ if ( !CEconSharedToolSupport::ToolCanApplyToDefinition( pToolDef, dynamic_cast<const GameItemDefinition_t *>( pDef ) ) )
+ continue;
+
+ if ( bFirstTool )
+ {
+ bFirstTool = false;
+ AddDataText( "#TF_Armory_Item_ToolUsage", false );
+ }
+ else
+ {
+ AddDataText( ", " );
+ }
+
+ // Create a link to the item
+ {
+ // we need an econ item view here for just the item name
+ CEconItemView tmpTool;
+ tmpTool.Init( m_ToolList[i], AE_USE_SCRIPT_VALUE, AE_USE_SCRIPT_VALUE, true );
+
+ InsertItemLink( tmpTool.GetItemName(), pToolDef->GetDefinitionIndex() );
+ }
+ }
+ if ( !bFirstTool )
+ {
+ AddDataText( ".\n" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::DataText_AppendStoreFlags( const CEconItemDefinition *pDef )
+{
+ if ( !ItemSystem() || !ItemSystem()->GetItemSchema() )
+ return;
+
+ const bool bHolidayRestriction = pDef->GetHolidayRestriction() != NULL && V_strlen( pDef->GetHolidayRestriction() ) > 0;
+ if ( bHolidayRestriction )
+ {
+ wchar_t *pRestrictedText = g_pVGuiLocalize->Find( "#Store_HolidayRestrictionText" );
+
+ if ( pRestrictedText )
+ {
+ InsertColorChange( Color( 200, 80, 60, 255 ) );
+ InsertString( pRestrictedText );
+ InsertString( L".\n\n" );
+ }
+ }
+
+ if ( m_bLimitedItem )
+ {
+ wchar_t *pLocText = bHolidayRestriction
+ ? g_pVGuiLocalize->Find( "#TF_Armory_Item_Limited_Holiday" )
+ : g_pVGuiLocalize->Find( "#TF_Armory_Item_Limited" );
+
+ if ( pLocText )
+ {
+ InsertColorChange( Color( 255, 140, 0, 255 ) );
+ InsertString ( pLocText );
+ InsertString( L"\n\n" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::DataText_AppendItemData( const CEconItemDefinition *pDef )
+{
+ if ( !GetItemSchema() )
+ return;
+
+ // Start by looking for a specified armory desc string
+ const char *pDesc = pDef->GetArmoryDescString();
+ if ( pDesc && pDesc[0] )
+ {
+ const ArmoryStringDict_t &ArmoryItemData = ItemSystem()->GetItemSchema()->GetArmoryDataItems();
+
+ // Tokenize it, and look for localization strings for each token
+ CUtlVector< char * > vecArmoryKeys;
+ Q_SplitString( pDesc, " ", vecArmoryKeys );
+ FOR_EACH_VEC( vecArmoryKeys, i )
+ {
+ int iIdx = ArmoryItemData.Find( vecArmoryKeys[i] );
+ if ( ArmoryItemData.IsValidIndex( iIdx ) )
+ {
+ const char *pLoc = ArmoryItemData.Element( iIdx ).Get();
+ AddDataText( pLoc );
+ }
+ }
+ vecArmoryKeys.PurgeAndDeleteElements();
+ }
+
+ // Is this item part of a set?
+ if ( pDef->GetItemSetDefinition() )
+ {
+ DataText_AppendSetData( pDef );
+ }
+
+ if ( pDef->GetBundleInfo() != NULL )
+ {
+ DataText_AppendBundleData( pDef );
+ }
+
+ // Does this item type have data associated with it?
+ const ArmoryStringDict_t &ArmoryItemTypeData = GetItemSchema()->GetArmoryDataItemTypes();
+ int iIdx = ArmoryItemTypeData.Find( pDef->GetItemTypeName() );
+ if ( ArmoryItemTypeData.IsValidIndex( iIdx ) )
+ {
+ const char *pLoc = ArmoryItemTypeData.Element( iIdx ).Get();
+ AddDataText( pLoc );
+ }
+
+ // Does this item class have data associated with it?
+ const ArmoryStringDict_t &ArmoryItemClassData = GetItemSchema()->GetArmoryDataItemClasses();
+ iIdx = pDef->GetItemClass() ? ArmoryItemClassData.Find( pDef->GetItemClass() ) : ArmoryItemClassData.InvalidIndex();
+ if ( ArmoryItemClassData.IsValidIndex( iIdx ) )
+ {
+ if ( !pDef->GetDefinitionKey( "hack_disable_armory_type_desc" ) )
+ {
+ const char *pLoc = ArmoryItemClassData.Element( iIdx ).Get();
+ AddDataText( pLoc );
+ }
+ }
+
+ // Can this item be earned by an achievement?
+ const AchievementAward_t *pAchievementAward = GetItemSchema()->GetAchievementRewardByDefIndex( pDef->GetDefinitionIndex() );
+ if( pAchievementAward )
+ {
+ wchar_t *pszAchName = ACHIEVEMENT_LOCALIZED_NAME_FROM_STR( pAchievementAward->m_sNativeName.String() );
+ if ( pszAchName )
+ {
+ AddDataText( "#TF_Armory_Item_AchievementReward", true, pszAchName );
+ }
+ }
+
+ // Is this a Holiday item?
+ if ( pDef->GetHolidayRestriction() )
+ {
+ AddDataText( "#TF_Armory_Item_HolidayRestriction" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::DataText_AppendBundleData( const CEconItemDefinition *pDef )
+{
+ bool bFirstItem = true;
+
+ const bundleinfo_t *pBundleInfo = pDef->GetBundleInfo();
+ FOR_EACH_VEC( pBundleInfo->vecItemDefs, i )
+ {
+ CEconItemDefinition *pBundledItem = pBundleInfo->vecItemDefs[i];
+ if ( pBundledItem )
+ {
+ if ( bFirstItem )
+ {
+ bFirstItem = false;
+ AddDataText( "#TF_Armory_Item_Bundle", false );
+ }
+ else
+ {
+ AddDataText( ", " );
+ }
+
+ CEconItemView bundleItemData;
+ bundleItemData.Init( pBundledItem->GetDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+
+ InsertItemLink( bundleItemData.GetItemName(), bundleItemData.GetItemDefIndex() );
+ }
+ }
+
+ if ( !bFirstItem )
+ {
+ AddDataText( ".\n\n" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::DataText_AppendAttributeData( const CEconItemDefinition *pDef )
+{
+ if ( !ItemSystem() || !ItemSystem()->GetItemSchema() )
+ return;
+
+ const ArmoryStringDict_t &ArmoryAttribData = ItemSystem()->GetItemSchema()->GetArmoryDataAttributes();
+
+ CVarBitVec m_AttribsShown;
+ m_AttribsShown.Resize( ArmoryAttribData.Count() );
+ m_AttribsShown.ClearAll();
+
+ const CUtlVector<static_attrib_t> &vecStaticAttribs = pDef->GetStaticAttributes();
+ FOR_EACH_VEC( vecStaticAttribs, i )
+ {
+ const static_attrib_t &attrib = vecStaticAttribs[i];
+ CEconItemAttributeDefinition *pAttributeDef = ItemSystem()->GetStaticDataForAttributeByDefIndex( attrib.iDefIndex );
+ if ( !pAttributeDef )
+ continue;
+ if ( pAttributeDef->IsHidden() )
+ continue;
+
+ const char *pDesc = pAttributeDef->GetArmoryDescString();
+ if ( !pDesc || !pDesc[0] )
+ continue;
+
+ // Tokenize it, and look for localization strings for each token
+ CUtlVector< char * > vecArmoryKeys;
+ Q_SplitString( pDesc, " ", vecArmoryKeys );
+ FOR_EACH_VEC( vecArmoryKeys, iKey )
+ {
+ int iIdx = ArmoryAttribData.Find( vecArmoryKeys[iKey] );
+ if ( ArmoryAttribData.IsValidIndex( iIdx ) )
+ {
+ if ( m_AttribsShown[iIdx] == false )
+ {
+ const char *pLoc = ArmoryAttribData.Element( iIdx ).Get();
+ AddDataText( pLoc );
+
+ m_AttribsShown.Set( iIdx );
+ }
+ }
+ }
+
+ vecArmoryKeys.PurgeAndDeleteElements();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::DataText_AppendSetData( const CEconItemDefinition *pDef )
+{
+ if ( !ItemSystem() || !ItemSystem()->GetItemSchema() )
+ return;
+
+ CEconItemSchema *pSchema = ItemSystem()->GetItemSchema();
+ if ( pSchema )
+ {
+ const CEconItemSetDefinition *pItemSet = pDef->GetItemSetDefinition();
+ if ( pItemSet )
+ {
+ // Does this set provide bonus attributes when completely worn?
+ if ( pItemSet->m_iAttributes.Count() > 0 )
+ {
+ // Used for grabbing display colors.
+ vgui::HScheme hScheme = vgui::scheme()->GetScheme( "ClientScheme" );
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( hScheme );
+
+ Assert( pScheme );
+
+ // Insert the set description
+ wchar_t *pLocText = g_pVGuiLocalize->Find( pItemSet->m_pszLocalizedName );
+ AddDataText( "#TF_Armory_Item_InSet", false, pLocText, NULL, m_bAllowItemSetLinks ? &pItemSet->m_iBundleItemDef : NULL );
+
+ for ( int i = 0; i < pItemSet->m_iAttributes.Count(); i++ )
+ {
+ const CEconItemAttributeDefinition *pAttrDef = GetItemSchema()->GetAttributeDefinition( pItemSet->m_iAttributes[i].m_iAttribDefIndex );
+ if ( !pAttrDef )
+ continue;
+
+ CEconAttributeDescription AttrDesc( GLocalizationProvider(), pAttrDef, pItemSet->m_iAttributes[i].m_flValue );
+ if ( !AttrDesc.GetDescription().IsEmpty() )
+ {
+ InsertColorChange( pScheme->GetColor( GetColorNameForAttribColor( AttrDesc.GetDefaultColor() ), Color(255, 255, 255, 255) ) );
+ AddDataText( " " );
+ InsertString( AttrDesc.GetDescription().Get() );
+ AddDataText( "\n" );
+ }
+ }
+
+ AddDataText( "\n" );
+ }
+ // This set is visual and provides no additional bonuses when worn completely.
+ else
+ {
+ // Insert the set description
+ wchar_t *pLocText = g_pVGuiLocalize->Find( pItemSet->m_pszLocalizedName );
+ AddDataText( "#TF_Armory_Item_InSet_NoBonus", false, pLocText, NULL, m_bAllowItemSetLinks ? &pItemSet->m_iBundleItemDef : NULL );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::UpdateToolList( void )
+{
+ m_ToolList.Purge();
+
+ // Find all the tool types in our items list
+ const CEconItemSchema::ToolsItemDefinitionMap_t &mapItemDefs = ItemSystem()->GetItemSchema()->GetToolsItemDefinitionMap();
+ FOR_EACH_MAP( mapItemDefs, i )
+ {
+ const CEconItemDefinition *pDef = mapItemDefs[i];
+ if ( !pDef->GetItemClass() )
+ continue;
+
+ if ( !pDef->IsTool() )
+ continue;
+
+ const IEconTool *pEconTool = pDef->GetEconTool();
+ if ( !pEconTool )
+ continue;
+
+ if ( !pEconTool->ShouldDisplayAsUseableOnItemsInArmory() )
+ continue;
+
+ // Now make sure it doesn't have the same type as an existing tool
+ bool bAlreadyFound = false;
+
+ FOR_EACH_VEC( m_ToolList, tool )
+ {
+ CEconItemDefinition *pOtherDef = ItemSystem()->GetStaticDataForItemByDefIndex( m_ToolList[tool] );
+ Assert( pOtherDef );
+
+ const IEconTool *pOtherEconTool = pOtherDef->GetEconTool();
+ Assert( pOtherEconTool );
+
+ bAlreadyFound = !V_strcmp( pEconTool->GetTypeName(), pOtherEconTool->GetTypeName() );
+ if ( bAlreadyFound )
+ {
+ break;
+ }
+ }
+
+ if ( !bAlreadyFound )
+ {
+ m_ToolList.AddToTail( pDef->GetDefinitionIndex() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconItemDetailsRichText::InsertItemLink( const wchar_t *pwzItemName, int iItemDef, Color *pColorOverride )
+{
+ char szTmpToolName[256];
+ ::ILocalize::ConvertUnicodeToANSI(pwzItemName, szTmpToolName, sizeof( szTmpToolName ));
+ char szToolStoreURL[256];
+ V_snprintf( szToolStoreURL, sizeof( szToolStoreURL ), "<a href=item://%u>%s</a>", iItemDef, szTmpToolName );
+ InsertPossibleURLString( szToolStoreURL, pColorOverride ? *pColorOverride : m_colLink, GetFgColor() );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExplanationPopup::CExplanationPopup(Panel *parent, const char *panelName) : vgui::EditablePanel( parent, panelName )
+{
+ m_pCallout = new CExplanationPopupCalloutArrow( parent );
+ m_pCallout->SetVisible( false );
+ m_pCallout->SetAutoDelete( false );
+
+ m_szPrevExplanation[0] = '\0';
+ m_szNextExplanation[0] = '\0';
+ m_bFinishedPopup = false;
+
+ ListenForGameEvent( "gameui_hidden" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CExplanationPopup::~CExplanationPopup( void )
+{
+ m_pCallout->MarkForDeletion();
+ m_pCallout = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExplanationPopup::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "close" ) )
+ {
+ Hide( 0 );
+ }
+ else if ( !Q_stricmp( command, "nextexplanation" ) )
+ {
+ Hide( 1 );
+ }
+ else if ( !Q_stricmp( command, "prevexplanation" ) )
+ {
+ Hide( -1 );
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExplanationPopup::Hide( int iExplanationDelta )
+{
+ int iPos = m_iPositionInChain;
+ const char *pszMoveTo = NULL;
+ if ( iExplanationDelta == -1 )
+ {
+ if ( !m_szPrevExplanation || m_szPrevExplanation[ 0 ] == '\0' )
+ return;
+
+ pszMoveTo = m_szPrevExplanation;
+ iPos--;
+ }
+ else if ( iExplanationDelta == 1 )
+ {
+ if ( !m_szNextExplanation || m_szNextExplanation[ 0 ] == '\0' )
+ return;
+
+ pszMoveTo = m_szNextExplanation;
+ iPos++;
+ }
+
+ SetVisible( false );
+ m_pCallout->SetVisible( false );
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+
+ if ( m_bForceClose )
+ {
+ TFModalStack()->PopModal( this );
+ }
+
+ if ( iExplanationDelta == 0 )
+ {
+ if ( GetParent() )
+ {
+ GetParent()->NavigateTo();
+ }
+
+ return;
+ }
+
+ CExplanationPopup *pPopup = dynamic_cast<CExplanationPopup*>( GetParent()->FindChildByName( pszMoveTo ) );
+ if ( pPopup )
+ {
+ pPopup->Popup( iPos, m_iTotalInChain );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExplanationPopup::Popup( int iPosition, int iTotalPanels )
+{
+ // Parent this to our parent. Doing it in our constructor doesn't work because
+ // the parent passed in there hasn't been initialized properly.
+ m_pCallout->SetParent( GetParent() );
+ m_pCallout->SetZPos( GetZPos() - 1 );
+
+ if ( m_bForceClose )
+ {
+ TFModalStack()->PushModal( this );
+ }
+
+ // If they don't specify X,Y,W,H, we start tiny on the callout position
+ if ( !m_iStartX && !m_iStartY )
+ {
+ m_iStartX = m_iCalloutInParentsX;
+ m_iStartY = m_iCalloutInParentsY;
+ }
+ if ( !m_iStartW && !m_iStartH )
+ {
+ m_iStartW = 1;
+ m_iStartH = 1;
+ }
+
+ // If we weren't given a position, we're the first in a chain. Figure out
+ // how many there are in the total chain.
+ m_iPositionInChain = iPosition;
+ m_iTotalInChain = iTotalPanels;
+ if ( !m_iTotalInChain )
+ {
+ m_iTotalInChain = 0;
+ m_iPositionInChain = 1;
+ CExplanationPopup *pPopup = this;
+ while ( pPopup )
+ {
+ m_iTotalInChain++;
+
+ const char *pszNext = pPopup->GetNextExplanation();
+ if ( !pszNext[0] )
+ break;
+
+ const char *pszPrev = pPopup->GetName();
+ pPopup = dynamic_cast<CExplanationPopup*>( GetParent()->FindChildByName( pszNext ) );
+ if ( pPopup )
+ {
+ pPopup->SetPrevExplanation( pszPrev );
+ }
+ }
+ }
+
+ // Now assemble our position label
+ char szTmp[16];
+ Q_snprintf(szTmp, 16, "%d/%d", m_iPositionInChain, m_iTotalInChain );
+ SetDialogVariable( "explanationnumber", szTmp );
+
+ SetBounds( m_iStartX, m_iStartY, m_iStartW, m_iStartH );
+ SetVisible( true );
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+
+ m_flStartTime = Plat_FloatTime();
+ m_flEndTime = m_flStartTime + 0.5;
+ m_bFinishedPopup = false;
+
+ // If our endX & endW is going to result in us being off the side of the screen, move back on
+ if ( m_iEndX < 0 )
+ {
+ m_iEndX = XRES(5);
+ }
+ else if ( (m_iEndX + m_iEndW) > ScreenWidth() )
+ {
+ m_iEndX = ScreenWidth() - m_iEndW - XRES(5);
+ }
+
+ // Figure out what side of the bubble we should have the arrow attached to
+ m_iCalloutSide = EXC_SIDE_TOP;
+ Vector vecCallout( m_iCalloutInParentsX, m_iCalloutInParentsY, 0 );
+ Vector vecMins( m_iEndX, m_iEndY, 0 );
+ Vector vecMaxs( m_iEndX + m_iEndW, m_iEndY + m_iEndH, 0 );
+ Vector vecPoint;
+ CalcClosestPointOnAABB( vecMins, vecMaxs, vecCallout, vecPoint );
+
+ if ( vecPoint.x == vecMins.x && vecCallout.x != vecMins.x )
+ {
+ m_iCalloutSide = EXC_SIDE_LEFT;
+ }
+ else if ( vecPoint.y == vecMins.y && vecCallout.y != vecMins.y )
+ {
+ m_iCalloutSide = EXC_SIDE_TOP;
+ }
+ else if ( vecPoint.x == vecMaxs.x && vecCallout.x != vecMaxs.x )
+ {
+ m_iCalloutSide = EXC_SIDE_RIGHT;
+ }
+ else
+ {
+ m_iCalloutSide = EXC_SIDE_BOTTOM;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExplanationPopup::OnTick( void )
+{
+ float flElapsed = Plat_FloatTime() - m_flStartTime;
+ float flTotal = m_flEndTime - m_flStartTime;
+ float flBias = Bias( RemapValClamped( flElapsed, 0.f, flTotal, 0.f, 1.f ), 0.7f );
+ flElapsed = flTotal * flBias;
+
+ if ( flElapsed >= flTotal )
+ {
+ //vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ if ( !m_bFinishedPopup )
+ {
+ SetBounds( m_iEndX, m_iEndY, m_iEndW, m_iEndH );
+ PositionCallout( 1.0 );
+ m_bFinishedPopup = true;
+ }
+
+ // If we've lost focus, or been hidden, release our modal lock
+ if ( !ipanel()->IsFullyVisible( GetVPanel() ))
+ {
+ Hide(0);
+ }
+ return;
+ }
+
+ int iExpandW = XRES(30);
+ int iExpandH = YRES(30);
+ int iExpandedW = m_iEndW + iExpandW;
+ int iExpandedH = m_iEndH + iExpandH;
+ int iExpandedX = m_iEndX - (iExpandW * 0.5);
+ int iExpandedY = m_iEndY - (iExpandH * 0.5);
+
+ int iW, iH, iX, iY;
+ float flExpandTime = (flTotal * 0.66);
+
+ PositionCallout( RemapVal( flElapsed, 0, flTotal, 0, 1 ) );
+
+// iW = RemapValClamped( flElapsed, 0, flTotal, m_iStartW, m_iEndW );
+// iH = RemapValClamped( flElapsed, 0, flTotal, m_iStartH, m_iEndH );
+// iX = RemapValClamped( flElapsed, 0, flTotal, m_iStartX, m_iEndX );
+// iY = RemapValClamped( flElapsed, 0, flTotal, m_iStartY, m_iEndY );
+// SetBounds( iX, iY, iW, iH );
+// return;
+
+ if ( flElapsed < flExpandTime )
+ {
+ // Expand to greater than the end size
+ iW = RemapValClamped( flElapsed, 0, flExpandTime, m_iStartW, iExpandedW );
+ iH = RemapValClamped( flElapsed, 0, flExpandTime, m_iStartH, iExpandedH );
+ iX = RemapValClamped( flElapsed, 0, flExpandTime, m_iStartX, iExpandedX );
+ iY = RemapValClamped( flElapsed, 0, flExpandTime, m_iStartY, iExpandedY );
+ }
+ else
+ {
+ // Contract to the end size
+ iW = RemapValClamped( flElapsed, flExpandTime, flTotal, iExpandedW, m_iEndW );
+ iH = RemapValClamped( flElapsed, flExpandTime, flTotal, iExpandedH, m_iEndH );
+ iX = RemapValClamped( flElapsed, flExpandTime, flTotal, iExpandedX, m_iEndX );
+ iY = RemapValClamped( flElapsed, flExpandTime, flTotal, iExpandedY, m_iEndY );
+ }
+ SetBounds( iX, iY, iW, iH );
+}
+
+void CExplanationPopup::OnKeyCodeTyped( vgui::KeyCode code )
+{
+ if ( IsVisible() && m_pCallout && m_pCallout->IsVisible() )
+ {
+ // swallow all keys
+ if ( code == KEY_ESCAPE )
+ {
+ OnCommand( "close" );
+ return;
+ }
+ }
+
+ BaseClass::OnKeyCodePressed( code );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExplanationPopup::OnKeyCodePressed( vgui::KeyCode code )
+{
+ if ( IsVisible() && m_pCallout && m_pCallout->IsVisible() )
+ {
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ // swallow all keys
+ if ( nButtonCode == KEY_XBUTTON_B )
+ {
+ OnCommand( "close" );
+ return;
+ }
+ else if ( nButtonCode == KEY_XBUTTON_LEFT ||
+ nButtonCode == KEY_XSTICK1_LEFT ||
+ nButtonCode == KEY_XSTICK2_LEFT ||
+ code == KEY_LEFT )
+ {
+ OnCommand( "prevexplanation" );
+ return;
+ }
+ else if ( nButtonCode == KEY_XBUTTON_RIGHT ||
+ nButtonCode == KEY_XSTICK1_RIGHT ||
+ nButtonCode == KEY_XSTICK2_RIGHT ||
+ code == KEY_RIGHT )
+ {
+ OnCommand( "nextexplanation" );
+ return;
+ }
+ }
+
+ BaseClass::OnKeyCodePressed( code );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExplanationPopup::PositionCallout( float flElapsed )
+{
+ // Size and position the callout
+ if ( !m_pCallout->IsVisible() )
+ {
+ m_pCallout->SetVisible( true );
+ }
+
+ int iCalloutSize = 20;
+ int iIndent = 15;
+
+ int iMyPos[2];
+ GetPos( iMyPos[0], iMyPos[1] );
+ int iMySize[2];
+ iMySize[0] = GetWide();
+ iMySize[1] = GetTall();
+
+ int iCalloutPos[2];
+ iCalloutPos[0] = m_iCalloutInParentsX;
+ iCalloutPos[1] = m_iCalloutInParentsY;
+
+ // We need to figure out the three corners of the callout triangle, in parent space
+ int iArrowA[2];
+ int iArrowB[2];
+ int iW, iH, iX, iY;
+
+ // Determine which axis the arrow's extruding along
+ int x = (m_iCalloutSide == EXC_SIDE_TOP || m_iCalloutSide == EXC_SIDE_BOTTOM) ? 0 : 1;
+ int y = !x;
+
+ // Figure out where the center will be of the arrow on the edge that the arrow's extruding (ensure it's always somewhat indented from the corners)
+ iArrowA[x] = iMyPos[x] + clamp( iCalloutPos[x] - iMyPos[x] - XRES(iCalloutSize * 0.5), XRES(iIndent), iMySize[x] - XRES(iIndent) - XRES(iCalloutSize) );
+ iArrowB[x] = iArrowA[x] + XRES(iCalloutSize);
+ iArrowA[y] = iMyPos[y] + (( iCalloutPos[y] > iMyPos[y] ) ? iMySize[y] : 0);
+ iArrowB[y] = iMyPos[y] + (( iCalloutPos[y] > iMyPos[y] ) ? iMySize[y] : 0);
+
+ // Slide the arrow out towards the callout over time.
+ for ( int i = 0; i < 2; i++ )
+ {
+ iCalloutPos[i] = RemapValClamped( flElapsed, 0, 1, iArrowA[i] + (iArrowB[i] - iArrowA[i]), iCalloutPos[i] );
+ }
+
+ // Assemble a bounding box that contains the arrow points
+ iX = MIN( MIN( iCalloutPos[0], iArrowA[0] ), iArrowB[0] );
+ iW = MAX( MAX( iCalloutPos[0], iArrowA[0] ), iArrowB[0] ) - iX;
+ iY = MIN( MIN( iCalloutPos[1], iArrowA[1] ), iArrowB[1] );
+ iH = MAX( MAX( iCalloutPos[1], iArrowA[1] ), iArrowB[1] ) - iY;
+ m_pCallout->SetBounds( iX, iY, iW+1, iH+1 );
+
+ //Msg("CALLOUT: %d %d, %d %d\n", iX,iY,iW,iH );
+
+ // Tell the callout where its points are, so it can draw the triangle (make sure the triangle is facing the camera)
+ if ( m_iCalloutSide == EXC_SIDE_TOP || m_iCalloutSide == EXC_SIDE_RIGHT )
+ {
+ m_pCallout->SetArrowPoints( iCalloutPos[0]-iX, iCalloutPos[1]-iY, iArrowB[0]-iX, iArrowB[1]-iY, iArrowA[0]-iX, iArrowA[1]-iY );
+ }
+ else
+ {
+ m_pCallout->SetArrowPoints( iCalloutPos[0]-iX, iCalloutPos[1]-iY, iArrowA[0]-iX, iArrowA[1]-iY, iArrowB[0]-iX, iArrowB[1]-iY );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExplanationPopupCalloutArrow::Paint( void )
+{
+ int x,y;
+ vgui::ipanel()->GetAbsPos(GetVPanel(), x,y );
+
+ CMatRenderContextPtr pRenderContext( materials );
+ pRenderContext->Bind( materials->FindMaterial( "vgui/callout_tail", TEXTURE_GROUP_OTHER ), NULL );
+ IMesh *pMesh = pRenderContext->GetDynamicMesh();
+ CMeshBuilder meshBuilder;
+ meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, 1 );
+
+ meshBuilder.Color4ub( 255, 255, 255, 255 );
+ meshBuilder.TexCoord2f( 0, 1.0,0.5 );
+ meshBuilder.Position3f( x + m_iArrowA[0], y + m_iArrowA[1], 1 );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Color4ub( 255, 255, 255, 255 );
+ meshBuilder.TexCoord2f( 0, 0,1 );
+ meshBuilder.Position3f( x + m_iArrowB[0], y + m_iArrowB[1], 1 );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Color4ub( 255, 255, 255, 255 );
+ meshBuilder.TexCoord2f( 0, 0,0 );
+ meshBuilder.Position3f( x + m_iArrowC[0], y + m_iArrowC[1], 1 );
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.End();
+ pMesh->Draw();
+
+// vgui::surface()->DrawSetColor( Color(0,255,0,255) );
+// vgui::surface()->DrawLine( m_iArrowA[0], m_iArrowA[1], m_iArrowB[0], m_iArrowB[1] );
+// vgui::surface()->DrawLine( m_iArrowA[0], m_iArrowA[1], m_iArrowC[0], m_iArrowC[1] );
+// vgui::surface()->DrawLine( m_iArrowB[0], m_iArrowB[1], m_iArrowC[0], m_iArrowC[1] );
+// vgui::surface()->DrawOutlinedRect( 0,0, GetWide(), GetTall() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExplanationPopup::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ Q_strncpy( m_szNextExplanation, inResourceData->GetString( "next_explanation", "" ), sizeof( m_szNextExplanation ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExplanationPopup::SetPrevExplanation( const char *pszPrev )
+{
+ m_szPrevExplanation[0] = '\0';
+ if ( pszPrev && pszPrev[0] )
+ {
+ Q_strncpy( m_szPrevExplanation, pszPrev, sizeof( m_szPrevExplanation ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CExplanationPopup::FireGameEvent( IGameEvent *event )
+{
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ if ( IsVisible() )
+ {
+ Hide( 0 );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CPanelModalStack g_ModalStack;
+CPanelModalStack *TFModalStack( void )
+{
+ return &g_ModalStack;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPanelModalStack::PushModal( vgui::Panel *pDialog )
+{
+ VPanelHandle hHandle;
+ hHandle.Set( pDialog->GetVPanel() );
+
+ FOR_EACH_VEC( m_pDialogs, i )
+ {
+ if ( m_pDialogs[i] == hHandle )
+ return;
+ }
+
+ m_pDialogs.AddToHead( hHandle );
+
+ vgui::input()->SetAppModalSurface( pDialog->GetVPanel() );
+ pDialog->RequestFocus();
+ pDialog->MoveToFront();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPanelModalStack::PopModal( vgui::Panel *pDialog )
+{
+ bool bFound = false;
+ FOR_EACH_VEC_BACK( m_pDialogs, i )
+ {
+ if ( m_pDialogs[i].Get() == pDialog->GetVPanel() )
+ {
+ PopModal( i );
+ bFound = true;
+ }
+ }
+
+ AssertMsg( bFound, "CPanelModalStack::PopModal() failed to find the given dialog." );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPanelModalStack::PopModal( int iIdx )
+{
+ bool bRecalcLock = false;
+
+ // Only release the modal lock if we had it
+ VPANEL hPanel = vgui::input()->GetAppModalSurface();
+ if ( !hPanel || m_pDialogs[iIdx].Get() == hPanel )
+ {
+ bRecalcLock = true;
+ }
+
+ m_pDialogs.Remove(iIdx);
+
+ if ( bRecalcLock )
+ {
+ if ( m_pDialogs.Count() )
+ {
+ vgui::input()->SetAppModalSurface( m_pDialogs[0] );
+ }
+ else
+ {
+ vgui::input()->SetAppModalSurface( NULL );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPanelModalStack::Update( void )
+{
+ if ( m_pDialogs.Count() <= 0 )
+ return;
+
+ // Don't run this logic if the game UI isn't visible
+ if ( !enginevgui->IsGameUIVisible() )
+ return;
+
+ // Safety check: If the app model surface dialog is in our list, make sure it's usable
+ VPANEL hPanel = vgui::input()->GetAppModalSurface();
+
+ FOR_EACH_VEC_BACK( m_pDialogs, i )
+ {
+ // Pop dialogs that didn't correctly remove themselves on delete
+ if ( m_pDialogs[i].Get() == 0 )
+ {
+ PopModal( i );
+ continue;
+ }
+
+ if ( m_pDialogs[i].Get() == hPanel )
+ {
+ Assert( vgui::ipanel()->IsFullyVisible(hPanel) );
+
+ // Backup hack: If our modal window is no longer visible, make it visible
+ if ( !vgui::ipanel()->IsFullyVisible(hPanel) )
+ {
+ vgui::ipanel()->SetVisible( hPanel, true );
+ vgui::ipanel()->MoveToFront( hPanel );
+ vgui::ipanel()->RequestFocus( hPanel );
+
+ // Make sure all our parents are visible too
+ VPANEL hParent = vgui::ipanel()->GetParent( hPanel );
+ while ( hParent != INVALID_PANEL )
+ {
+ vgui::Panel *pParentPanel = vgui::ipanel()->GetPanel(hParent, "ClientDLL");
+ if ( !pParentPanel )
+ break;
+
+ vgui::ipanel()->SetVisible( hParent, true );
+ hParent = vgui::ipanel()->GetParent( hParent );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+vgui::VPanelHandle CPanelModalStack::Top()
+{
+ if ( m_pDialogs.Count() == 0 )
+ {
+ return VPanelHandle(); // Defaults to INVALID_PANEL
+ }
+
+ return m_pDialogs[0];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CPanelModalStack::IsEmpty() const
+{
+ return m_pDialogs.Count() == 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CGenericWaitingDialog::CGenericWaitingDialog( vgui::Panel *pParent )
+ : BaseClass( pParent, "GenericWaitingDialog" )
+ , m_bAnimateEllipses(false)
+ , m_iNumEllipses(0)
+{
+ if ( pParent == NULL )
+ {
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+ }
+}
+
+void CGenericWaitingDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( GetResFile(), GetResFilePathId() );
+}
+
+void CGenericWaitingDialog::Close()
+{
+ OnCommand( "close" );
+}
+
+void CGenericWaitingDialog::OnCommand( const char *command )
+{
+ bool bClose = false;
+
+ if ( !Q_stricmp( command, "close" ) )
+ {
+ bClose = true;
+ }
+ else if ( !Q_stricmp( command, "user_close" ) )
+ {
+ OnUserClose();
+ bClose = true;
+ }
+
+ if ( bClose )
+ {
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+void CGenericWaitingDialog::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ if ( !IsVisible() )
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+
+ if ( m_bAnimateEllipses )
+ {
+ 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;
+ }
+ }
+
+ if ( m_timer.HasStarted() )
+ {
+ // @note Tom Bui: showing 0 is weird, so just show nothing...
+ int iSecondsRemaining = (int)m_timer.GetRemainingTime();
+ if ( iSecondsRemaining == 0 )
+ {
+ SetDialogVariable( "duration", "" );
+ }
+ else
+ {
+ SetDialogVariable( "duration", iSecondsRemaining );
+ }
+ if ( m_timer.IsElapsed() )
+ {
+ OnCommand( "close" );
+ OnTimeout();
+ }
+ }
+}
+
+void CGenericWaitingDialog::ShowStatusUpdate( bool bAnimateEllipses, bool bAllowClose, float flMaxWaitTime )
+{
+ CExButton *pButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") );
+ if ( pButton )
+ {
+ pButton->SetVisible( bAllowClose );
+ pButton->SetEnabled( bAllowClose );
+ }
+
+ m_bAnimateEllipses = bAnimateEllipses;
+ if ( flMaxWaitTime > 0 )
+ {
+ m_timer.Start( flMaxWaitTime );
+ SetDialogVariable( "duration", (int)flMaxWaitTime );
+ }
+ else
+ {
+ m_timer.Invalidate();
+ SetDialogVariable( "duration", L"" );
+ }
+
+ if ( m_bAnimateEllipses )
+ {
+ m_iNumEllipses = 0;
+ }
+
+ if ( flMaxWaitTime > 0 || m_bAnimateEllipses )
+ {
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 500 );
+ }
+
+ SetDialogVariable( "ellipses", L"" );
+}
+
+void CGenericWaitingDialog::OnTimeout()
+{
+}
+
+void CGenericWaitingDialog::OnUserClose()
+{
+}
+
+static vgui::DHANDLE< CGenericWaitingDialog > g_WaitingDialog;
+
+void ShowWaitingDialog( CGenericWaitingDialog *pWaitingDialog, const char* pUpdateText, bool bAnimate, bool bShowCancel, float flMaxDuration )
+{
+ CloseWaitingDialog();
+ if ( pWaitingDialog )
+ {
+ g_WaitingDialog = vgui::SETUP_PANEL( pWaitingDialog );
+ g_WaitingDialog->SetVisible( true );
+ g_WaitingDialog->MakePopup();
+ g_WaitingDialog->MoveToFront();
+ g_WaitingDialog->SetKeyBoardInputEnabled(true);
+ g_WaitingDialog->SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( g_WaitingDialog );
+
+ if ( pUpdateText != NULL )
+ {
+ g_WaitingDialog->SetDialogVariable( "updatetext", g_pVGuiLocalize->Find( pUpdateText ) );
+ }
+ g_WaitingDialog->ShowStatusUpdate( bAnimate, bShowCancel, flMaxDuration );
+ }
+}
+
+void CloseWaitingDialog()
+{
+ if ( g_WaitingDialog.Get() )
+ {
+ g_WaitingDialog->Close();
+ g_WaitingDialog = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
diff --git a/game/client/econ/econ_controls.h b/game/client/econ/econ_controls.h
new file mode 100644
index 0000000..da4b8e4
--- /dev/null
+++ b/game/client/econ/econ_controls.h
@@ -0,0 +1,429 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ECON_CONTROLS_H
+#define ECON_CONTROLS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui/IScheme.h>
+#include <vgui/KeyCode.h>
+#include <KeyValues.h>
+#include <vgui/IVGui.h>
+#include <vgui_controls/ScrollBar.h>
+#include <vgui_controls/EditablePanel.h>
+#include <vgui_controls/Button.h>
+#include <vgui_controls/Label.h>
+#include <vgui_controls/RichText.h>
+#include <vgui_controls/ImagePanel.h>
+#include "utlvector.h"
+#include "vgui_controls/PHandle.h"
+#include <vgui_controls/Tooltip.h>
+#include "GameEventListener.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Changes the visibility of the child panel if it is different.
+// Returns true if the child exists, false otherwise.
+//-----------------------------------------------------------------------------
+bool SetChildPanelVisible( vgui::Panel *pParent, const char *pChildName, bool bVisible, bool bSearchForChildRecursively = false );
+
+//-----------------------------------------------------------------------------
+// Purpose: Changes the enable state of the child panel if it is different.
+// Returns true if the child exists, false otherwise.
+//-----------------------------------------------------------------------------
+bool SetChildPanelEnabled( vgui::Panel *pParent, const char *pChildName, bool bEnabled, bool bSearchForChildRecursively = false );
+
+//-----------------------------------------------------------------------------
+// Purpose: Changes the selected state of the child button if it is different.
+// Returns true if the child exists, false otherwise.
+//-----------------------------------------------------------------------------
+bool SetChildButtonSelected( vgui::Panel *pParent, const char *pChildName, bool bSelected, bool bSearchForChildRecursively = false );
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if the child button exists and is selected, false otherwise.
+//-----------------------------------------------------------------------------
+bool IsChildButtonSelected( vgui::Panel *pParent, const char *pChildName, bool bSearchForChildRecursively = false );
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds the child panel as an action signal target. Returns true if the child exists, false otherwise.
+//-----------------------------------------------------------------------------
+bool AddChildActionSignalTarget( vgui::Panel *pParent, const char *pChildName, vgui::Panel *messageTarget, bool bSearchForChildRecursively = false );
+
+//-----------------------------------------------------------------------------
+// Purpose: Modify the color of a label/button's text - if it starts with "X "
+// or "x ", set the X to red.
+//-----------------------------------------------------------------------------
+bool SetXToRed( vgui::Label *pPanel );
+
+//-----------------------------------------------------------------------------
+// Purpose: Simple panel tooltip. Just calls setvisible on the other panel.
+// Ignores all other input.
+//-----------------------------------------------------------------------------
+class CSimplePanelToolTip : public vgui::BaseTooltip
+{
+ DECLARE_CLASS_SIMPLE( CSimplePanelToolTip, vgui::BaseTooltip );
+public:
+ CSimplePanelToolTip(vgui::Panel *parent, const char *text = NULL) : vgui::BaseTooltip( parent, text )
+ {
+ m_pControlledPanel = NULL;
+ }
+ void SetText(const char *text) { return; }
+ const char *GetText() { return NULL; }
+
+ virtual void ShowTooltip( vgui::Panel *currentPanel ) { if ( m_pControlledPanel ) m_pControlledPanel->SetVisible( true ); }
+ virtual void HideTooltip() { if ( m_pControlledPanel ) m_pControlledPanel->SetVisible( false ); }
+ void SetControlledPanel( vgui::EditablePanel *pPanel ) { m_pControlledPanel = pPanel; }
+
+protected:
+ vgui::Panel *m_pControlledPanel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Expanded Button class that allows font & color overriding in .res files
+//-----------------------------------------------------------------------------
+class CExButton : public vgui::Button
+{
+public:
+ DECLARE_CLASS_SIMPLE( CExButton, vgui::Button );
+
+ CExButton( vgui::Panel *parent, const char *name, const char *text, vgui::Panel *pActionSignalTarget = NULL, const char *cmd = NULL );
+ CExButton( vgui::Panel *parent, const char *name, const wchar_t *wszText, vgui::Panel *pActionSignalTarget = NULL, const char *cmd = NULL );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+
+ void SetFontStr( const char *pFont );
+ void SetColorStr( const char *pColor );
+
+ virtual vgui::IBorder *GetBorder(bool depressed, bool armed, bool selected, bool keyfocus);
+
+ virtual void OnMouseFocusTicked() OVERRIDE;
+ virtual void OnCursorEntered() OVERRIDE;
+ virtual void OnCursorExited() OVERRIDE;
+ void PassMouseTicksTo( vgui::Panel *pPanel, bool bCursorEnterExitEvent = false )
+ {
+ m_hMouseTickTarget.Set( pPanel ? pPanel->GetVPanel() : NULL );
+ m_bbCursorEnterExitEvent = bCursorEnterExitEvent;
+ }
+
+private:
+ char m_szFont[64];
+ char m_szColor[64];
+
+ vgui::IBorder *m_pArmedBorder;
+ vgui::IBorder *m_pDefaultBorderOverride;
+ vgui::IBorder *m_pSelectedBorder;
+ vgui::IBorder *m_pDisabledBorder;
+ vgui::VPanelHandle m_hMouseTickTarget;
+ bool m_bbCursorEnterExitEvent;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Expanded image button, that handles images per button state, and color control in the .res file
+//-----------------------------------------------------------------------------
+class CExImageButton : public CExButton
+{
+public:
+ DECLARE_CLASS_SIMPLE( CExImageButton, CExButton );
+
+ CExImageButton( vgui::Panel *parent, const char *name, const char *text = "", vgui::Panel *pActionSignalTarget = NULL, const char *cmd = NULL );
+ CExImageButton( vgui::Panel *parent, const char *name, const wchar_t *wszText = L"", vgui::Panel *pActionSignalTarget = NULL, const char *cmd = NULL );
+ ~CExImageButton( void );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ virtual void SetArmed(bool state);
+ virtual void SetEnabled(bool state);
+ virtual void SetSelected(bool state);
+ void SetSubImage( const char *pszImage );
+
+ void SetImageDefault( const char *pszImageDefault );
+ void SetImageArmed( const char *pszImageArmed );
+ void SetImageSelected( const char *pszImageSelected );
+
+ Color GetImageColor( void );
+
+ vgui::ImagePanel *GetImage( void ) { return m_pEmbeddedImagePanel; }
+
+private:
+ // Embedded image panels
+ vgui::ImagePanel *m_pEmbeddedImagePanel;
+ Color m_ImageDrawColor;
+ Color m_ImageArmedColor;
+ Color m_ImageDisabledColor;
+ Color m_ImageSelectedColor;
+ Color m_ImageDepressedColor;
+ char m_szImageDefault[MAX_PATH];
+ char m_szImageArmed[MAX_PATH];
+ char m_szImageSelected[MAX_PATH];
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Expanded Label class that allows color control in .res files
+//-----------------------------------------------------------------------------
+class CExLabel : public vgui::Label
+{
+public:
+ DECLARE_CLASS_SIMPLE( CExLabel, vgui::Label );
+
+ CExLabel( vgui::Panel *parent, const char *panelName, const char *text );
+ CExLabel( vgui::Panel *parent, const char *panelName, const wchar_t *wszText );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ void SetColorStr( const char *pColor );
+ void SetColorStr( Color cColor );
+
+private:
+ char m_szColor[64];
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Expanded Richtext control that allows customization of scrollbar display, font, and color .res controls.
+//-----------------------------------------------------------------------------
+class CExRichText : public vgui::RichText
+{
+public:
+ DECLARE_CLASS_SIMPLE( CExRichText, vgui::RichText );
+
+ CExRichText( vgui::Panel *parent, const char *panelName );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout();
+ virtual void SetText( const char *text );
+ virtual void SetText( const wchar_t *text );
+
+ virtual void OnTick( void );
+ void SetScrollBarImagesVisible( bool visible );
+
+ void SetFontStr( const char *pFont );
+ void SetColorStr( const char *pColor );
+ void SetCustomImage( vgui::Panel *pImage, const char *pszImage, char *pszStorage );
+
+ void CreateImagePanels( void );
+
+protected:
+ char m_szFont[64];
+ char m_szColor[64];
+ char m_szImageUpArrow[MAX_PATH];
+ char m_szImageDownArrow[MAX_PATH];
+ char m_szImageLine[MAX_PATH];
+ char m_szImageBox[MAX_PATH];
+ bool m_bUseImageBorders;
+
+ CExImageButton *m_pUpArrow;
+ vgui::Panel *m_pLine;
+ CExImageButton *m_pDownArrow;
+ vgui::Panel *m_pBox;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Rich text control that knows how to fill itself with information
+// that describes a specific item definition.
+//-----------------------------------------------------------------------------
+class CRichTextWithScrollbarBorders : public CExRichText
+{
+public:
+ DECLARE_CLASS_SIMPLE( CRichTextWithScrollbarBorders, CExRichText );
+
+ CRichTextWithScrollbarBorders( vgui::Panel *parent, const char *panelName ) : BaseClass( parent, panelName )
+ {
+ m_bUseImageBorders = true;
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Rich text control that knows how to fill itself with information
+// that describes a specific item definition.
+//-----------------------------------------------------------------------------
+class CEconItemDetailsRichText : public CRichTextWithScrollbarBorders
+{
+public:
+ DECLARE_CLASS_SIMPLE( CEconItemDetailsRichText, CRichTextWithScrollbarBorders );
+
+ CEconItemDetailsRichText( vgui::Panel *parent, const char *panelName );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ void UpdateDetailsForItem( const CEconItemDefinition *pDef );
+
+ void AllowItemSetLinks( bool bAllow ) { m_bAllowItemSetLinks = bAllow; }
+
+ void SetLimitedItem( bool bLimited ) { m_bLimitedItem = bLimited; }
+
+private:
+ void InsertItemLink( const wchar_t *pwzItemName, int nItemIndex, Color *pColorOverride = NULL );
+ void AddDataText( const char *pszText, bool bAddPostLines = true, const wchar_t *wpszArg = NULL, const wchar_t *wpszArg2 = NULL, const int *pItemDefIndex = NULL );
+ void DataText_AppendStoreFlags( const CEconItemDefinition *pDef );
+ void DataText_AppendItemData( const CEconItemDefinition *pDef );
+ void DataText_AppendBundleData( const CEconItemDefinition *pDef );
+ void DataText_AppendUsageData( const CEconItemDefinition *pBaseDef );
+ void DataText_AppendAttributeData( const CEconItemDefinition *pDef );
+ void DataText_AppendSetData( const CEconItemDefinition *pDef );
+ void DataText_AppendToolUsage( const CEconItemDefinition *pDef );
+ void UpdateToolList( void );
+
+private:
+ Color m_colTextHighlight;
+ Color m_colItemSet;
+ Color m_colLink;
+ bool m_bAllowItemSetLinks;
+ vgui::HFont m_hLinkFont;
+ CUtlVector<item_definition_index_t> m_ToolList;
+
+ bool m_bLimitedItem;
+};
+
+
+#define EXC_SIDE_TOP 0
+#define EXC_SIDE_RIGHT 1
+#define EXC_SIDE_BOTTOM 2
+#define EXC_SIDE_LEFT 3
+
+//-----------------------------------------------------------------------------
+// Purpose: A small callout arrow that's created by a CExplanationPopup to
+// connect to the point that the explanation is referring to.
+//-----------------------------------------------------------------------------
+class CExplanationPopupCalloutArrow : public vgui::Panel
+{
+public:
+ CExplanationPopupCalloutArrow( Panel *parent ) : vgui::Panel( parent, "calloutarrow" )
+ {
+ SetPaintBackgroundEnabled( false );
+ SetMouseInputEnabled( false );
+ PrecacheMaterial( "vgui/callout_tail" );
+ }
+
+ void SetArrowPoints( int iAx, int iAy, int iBx, int iBy, int iCx, int iCy )
+ {
+ m_iArrowA[0] = iAx;
+ m_iArrowA[1] = iAy;
+ m_iArrowB[0] = iBx;
+ m_iArrowB[1] = iBy;
+ m_iArrowC[0] = iCx;
+ m_iArrowC[1] = iCy;
+ }
+
+ virtual void Paint( void );
+
+private:
+ int m_iArrowA[2];
+ int m_iArrowB[2];
+ int m_iArrowC[2];
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A bubble that contains a blob of text and an arrow to a specific place onscreen
+//-----------------------------------------------------------------------------
+class CExplanationPopup : public vgui::EditablePanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CExplanationPopup, vgui::EditablePanel );
+public:
+ CExplanationPopup(Panel *parent, const char *panelName);
+ ~CExplanationPopup( void );
+
+ void SetCalloutInParentsX( int nXPos ) { m_iCalloutInParentsX = nXPos; }
+ void SetCalloutInParentsY( int nYPos ) { m_iCalloutInParentsY = nYPos; }
+ void Popup( int iPosition = 0, int iTotalPanels = 0 );
+ void Hide( int iExplanationDelta = 0 );
+ const char *GetNextExplanation( void ) { return m_szNextExplanation; }
+ void SetPrevExplanation( const char *pszPrev );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void OnCommand( const char *command );
+ virtual void OnTick( void );
+ virtual void OnKeyCodeTyped( vgui::KeyCode code );
+ virtual void OnKeyCodePressed( vgui::KeyCode code );
+
+ void PositionCallout( float flElapsed );
+ virtual void FireGameEvent( IGameEvent *event );
+
+private:
+ int m_iCalloutSide;
+ float m_flStartTime;
+ float m_flEndTime;
+ char m_szNextExplanation[128];
+ char m_szPrevExplanation[128];
+ CExplanationPopupCalloutArrow *m_pCallout;
+ int m_iPositionInChain;
+ int m_iTotalInChain;
+ bool m_bFinishedPopup;
+
+ CPanelAnimationVar( bool, m_bForceClose, "force_close", "0" );
+
+ CPanelAnimationVarAliasType( int, m_iCalloutInParentsX, "callout_inparents_x", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iCalloutInParentsY, "callout_inparents_y", "0", "proportional_ypos" );
+
+ CPanelAnimationVarAliasType( int, m_iStartX, "start_x", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iStartY, "start_y", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iStartW, "start_wide", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iStartH, "start_tall", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iEndX, "end_x", "0", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iEndY, "end_y", "0", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iEndW, "end_wide", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iEndH, "end_tall", "0", "proportional_int" );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A stack to keep track of the modal dialogs that have been popped up.
+//-----------------------------------------------------------------------------
+class CPanelModalStack
+{
+public:
+ void PushModal( vgui::Panel *pDialog );
+ void PopModal( vgui::Panel *pDialog );
+
+ void Update( void );
+
+ vgui::VPanelHandle Top();
+
+ bool IsEmpty() const;
+
+private:
+ void PopModal( int iIdx );
+
+private:
+ CUtlVector<vgui::VPanelHandle> m_pDialogs;
+};
+
+CPanelModalStack *TFModalStack( void );
+
+//-----------------------------------------------------------------------------
+// Purpose: Generic waiting dialog
+//-----------------------------------------------------------------------------
+class CGenericWaitingDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CGenericWaitingDialog, vgui::EditablePanel );
+
+public:
+ CGenericWaitingDialog( vgui::Panel *pParent );
+
+ void Close();
+ void ShowStatusUpdate( bool bAnimateEllipses, bool bAllowClose, float flMaxWaitTime = 0 );
+
+protected:
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+ virtual void OnTick( void );
+ virtual void OnTimeout();
+ virtual void OnUserClose();
+ virtual const char *GetResFile() const { return "resource/UI/econ/GenericWaitingDialog.res"; }
+ virtual const char *GetResFilePathId() const { return "MOD"; }
+
+ bool m_bAnimateEllipses;
+ int m_iNumEllipses;
+ CountdownTimer m_timer;
+};
+
+void ShowWaitingDialog( CGenericWaitingDialog *pWaitingDialog, const char* pUpdateText, bool bAnimate, bool bShowCancel, float flMaxDuration );
+void CloseWaitingDialog();
+
+#endif // ECON_CONTROLS_H
diff --git a/game/client/econ/econ_notifications.cpp b/game/client/econ/econ_notifications.cpp
new file mode 100644
index 0000000..824a07c
--- /dev/null
+++ b/game/client/econ/econ_notifications.cpp
@@ -0,0 +1,1436 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+#include "cbase.h"
+
+#include "econ_notifications.h"
+
+#include "hudelement.h"
+#include "iclientmode.h"
+#include "ienginevgui.h"
+#include "vgui_avatarimage.h"
+#include "vgui_controls/Controls.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui/ILocalize.h"
+#include "vgui/ISurface.h"
+#include "vgui/IVGui.h"
+#include "rtime.h"
+#include "econ_controls.h"
+#include "hud_basechat.h"
+#include "hud_vote.h"
+#include "inputsystem/iinputsystem.h"
+#include "iinput.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+ConVar cl_notifications_show_ingame( "cl_notifications_show_ingame", "1", FCVAR_ARCHIVE, "Whether notifications should show up in-game." );
+ConVar cl_notifications_max_num_visible( "cl_notifications_max_num_visible", "3", FCVAR_ARCHIVE, "How many notifications are visible in-game." );
+ConVar cl_notifications_move_time( "cl_notifications_move_time", "0.5", FCVAR_ARCHIVE, "How long it takes for a notification to move." );
+
+// notification queue holds all the notifications
+class CEconNotificationQueue
+{
+public:
+ CEconNotificationQueue();
+ ~CEconNotificationQueue();
+
+ int AddNotification( CEconNotification *pNotification );
+ void RemoveAllNotifications();
+ void RemoveNotification( int iID );
+ void RemoveNotification( CEconNotification *pNotification );
+ void RemoveNotifications( NotificationFilterFunc func );
+ int CountNotifications( NotificationFilterFunc func );
+ void VisitNotifications( CEconNotificationVisitor &visitor );
+ CEconNotification *GetNotification( int iID );
+ CEconNotification *GetNotificationByIndex( int idx );
+ void Update();
+ bool HasItems() { return m_vecNotifications.Count() != 0; }
+ const CUtlVector< CEconNotification *> &GetItems() { return m_vecNotifications; }
+
+private:
+ int m_iIDGenerator;
+ CUtlVector< CEconNotification *> m_vecNotifications;
+};
+static CEconNotificationQueue g_notificationQueue;
+
+CEconNotificationQueue::CEconNotificationQueue()
+ : m_iIDGenerator(0)
+{
+}
+
+CEconNotificationQueue::~CEconNotificationQueue()
+{
+}
+
+int CEconNotificationQueue::AddNotification( CEconNotification *pNotification )
+{
+ int iID = ++m_iIDGenerator;
+ pNotification->m_iID = iID;
+ m_vecNotifications.AddToTail( pNotification );
+ return iID;
+}
+
+void CEconNotificationQueue::RemoveAllNotifications()
+{
+ m_vecNotifications.PurgeAndDeleteElements();
+}
+
+void CEconNotificationQueue::RemoveNotification( int iID )
+{
+ FOR_EACH_VEC( m_vecNotifications, i )
+ {
+ CEconNotification *pNotification = m_vecNotifications[i];
+ if ( pNotification->GetID() == iID )
+ {
+ delete pNotification;
+ m_vecNotifications.Remove( i );
+ return;
+ }
+ }
+}
+
+void CEconNotificationQueue::RemoveNotification( CEconNotification *pNotification )
+{
+ if ( pNotification )
+ {
+ RemoveNotification( pNotification->GetID() );
+ }
+}
+
+void CEconNotificationQueue::RemoveNotifications( NotificationFilterFunc func )
+{
+ for ( int i = 0; i < m_vecNotifications.Count(); ++i)
+ {
+ CEconNotification *pNotification = m_vecNotifications[i];
+ if ( func( pNotification ) )
+ {
+ pNotification->MarkForDeletion();
+ }
+ }
+}
+
+int CEconNotificationQueue::CountNotifications( NotificationFilterFunc func )
+{
+ int nResult = 0;
+ for ( int i = 0; i < m_vecNotifications.Count(); ++i)
+ {
+ CEconNotification *pNotification = m_vecNotifications[i];
+ if ( func( pNotification ) )
+ {
+ ++nResult;
+ }
+ }
+
+ return nResult;
+}
+
+void CEconNotificationQueue::VisitNotifications( CEconNotificationVisitor &visitor )
+{
+ for ( int i = 0; i < m_vecNotifications.Count(); ++i )
+ {
+ CEconNotification *pNotification = m_vecNotifications[i];
+ visitor.Visit( *pNotification );
+ }
+}
+
+CEconNotification *CEconNotificationQueue::GetNotification( int iID )
+{
+ FOR_EACH_VEC( m_vecNotifications, i )
+ {
+ CEconNotification *pNotification = m_vecNotifications[i];
+ if ( pNotification->GetID() == iID )
+ {
+ return pNotification;
+ }
+ }
+ return NULL;
+}
+
+CEconNotification *CEconNotificationQueue::GetNotificationByIndex( int idx )
+{
+ if ( idx < 0 || idx >= m_vecNotifications.Count() )
+ {
+ Assert( !"Invalid index passed to GetNotificationByIndex" );
+ return NULL;
+ }
+ return m_vecNotifications[idx];
+}
+
+void CEconNotificationQueue::Update()
+{
+ float flNowTime = engine->Time();
+ for ( int i = 0; i < m_vecNotifications.Count(); )
+ {
+ CEconNotification *pNotification = m_vecNotifications[i];
+ if ( pNotification->GetIsInUse() == false && pNotification->GetExpireTime() >= 0 && pNotification->GetExpireTime() < flNowTime )
+ {
+ pNotification->Expired();
+ delete pNotification;
+ m_vecNotifications.Remove( i );
+ continue;
+ }
+ pNotification->UpdateTick();
+ ++i;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+static void ColorizeText( CEconNotification *pNotification, CExLabel *pControl, const wchar_t* wszText )
+{
+ static wchar_t wszStrippedText[2048];
+
+ if ( pControl == NULL )
+ return;
+
+ pControl->GetTextImage()->ClearColorChangeStream();
+
+ if ( wszText == NULL )
+ {
+ pControl->SetText( L"" );
+ return;
+ }
+
+ Color newColor = pControl->GetFgColor();
+ int endIdx = 0;
+ int insertIdx = 0;
+ bool bContinue = true;
+ while ( bContinue )
+ {
+ bool bSetColor = false;
+ switch ( wszText[endIdx] )
+ {
+ case 0:
+ bContinue = false;
+ break;
+ case COLOR_NORMAL:
+ case COLOR_USEOLDCOLORS:
+ newColor = pControl->GetFgColor();
+ bSetColor = true;
+ break;
+ case COLOR_PLAYERNAME:
+ newColor = g_ColorYellow;
+ bSetColor = true;
+ break;
+ case COLOR_LOCATION:
+ newColor = g_ColorDarkGreen;
+ bSetColor = true;
+ break;
+ case COLOR_ACHIEVEMENT:
+ {
+ vgui::IScheme *pSourceScheme = vgui::scheme()->GetIScheme( vgui::scheme()->GetScheme( "SourceScheme" ) );
+ if ( pSourceScheme )
+ {
+ newColor = pSourceScheme->GetColor( "SteamLightGreen", pControl->GetBgColor() );
+ }
+ else
+ {
+ newColor = pControl->GetFgColor();
+ }
+ bSetColor = true;
+ }
+ break;
+ case COLOR_CUSTOM:
+ newColor = pControl->GetFgColor();
+ KeyValues *pKeyValues = pNotification->GetKeyValues();
+ if ( pKeyValues )
+ {
+ KeyValues* pColor = pKeyValues->FindKey( "custom_color" );
+ if ( pColor )
+ {
+ newColor = pColor->GetColor();
+ }
+ }
+ bSetColor = true;
+ break;
+ }
+ if ( bSetColor )
+ {
+ pControl->GetTextImage()->AddColorChange( newColor, insertIdx );
+ }
+ else
+ {
+ wszStrippedText[insertIdx++] = wszText[endIdx];
+ }
+ ++endIdx;
+ }
+ pControl->SetText( wszStrippedText );
+}
+
+// generic "toast" for notifications
+class CGenericNotificationToast : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CGenericNotificationToast, vgui::EditablePanel );
+public:
+ CGenericNotificationToast( vgui::Panel *parent, int iNotificationID, bool bMainMenu );
+ virtual ~CGenericNotificationToast();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout();
+protected:
+ int m_iNotificationID;
+ vgui::Panel *m_pAvatarBG;
+ CAvatarImagePanel *m_pAvatar;
+ bool m_bMainMenu;
+};
+
+CGenericNotificationToast::CGenericNotificationToast( vgui::Panel *parent, int iNotificationID, bool bMainMenu )
+ : BaseClass( parent, "GenericNotificationToast" )
+ , m_iNotificationID( iNotificationID )
+ , m_pAvatar( NULL )
+ , m_pAvatarBG( NULL )
+ , m_bMainMenu( bMainMenu )
+{
+}
+
+CGenericNotificationToast::~CGenericNotificationToast()
+{
+}
+
+void CGenericNotificationToast::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ CEconNotification *pNotification = NotificationQueue_Get( m_iNotificationID );
+ bool bHighPriority = pNotification && pNotification->BHighPriority();
+ KeyValues *pConditions = NULL;
+
+ if ( bHighPriority )
+ {
+ pConditions = new KeyValues( "conditions" );
+ if ( bHighPriority )
+ {
+ KeyValues *pSubKey = new KeyValues( "if_high_priority" );
+ pConditions->AddSubKey( pSubKey );
+ }
+ }
+
+ if ( m_bMainMenu )
+ {
+ LoadControlSettings( "Resource/UI/Econ/GenericNotificationToastMainMenu.res", NULL, NULL, pConditions );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/Econ/GenericNotificationToast.res", NULL, NULL, pConditions );
+ }
+
+ if ( pConditions )
+ {
+ pConditions->deleteThis();
+ }
+
+ m_pAvatar = dynamic_cast< CAvatarImagePanel *>( FindChildByName("AvatarImage") );
+ m_pAvatarBG = FindChildByName("AvatarBGPanel");
+
+ if ( pNotification )
+ {
+ if ( pNotification->GetSteamID() == CSteamID() )
+ {
+ ColorizeText( pNotification, dynamic_cast< CExLabel* >( FindChildByName( "TextLabel" ) ), pNotification->GetText() );
+ }
+ else
+ {
+ ColorizeText( pNotification, dynamic_cast< CExLabel* >( FindChildByName( "AvatarTextLabel" ) ), pNotification->GetText() );
+ }
+ }
+}
+
+void CGenericNotificationToast::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ CSteamID steamID;
+ CEconNotification *pNotification = NotificationQueue_Get( m_iNotificationID );
+ if ( pNotification )
+ {
+ steamID = pNotification->GetSteamID();
+ }
+
+ int iMinHeight = 0;
+ if ( m_pAvatar )
+ {
+ if ( steamID != CSteamID() )
+ {
+ m_pAvatar->SetVisible( true );
+ m_pAvatar->SetShouldDrawFriendIcon( false );
+ m_pAvatar->SetPlayer( steamID, k_EAvatarSize64x64 );
+ // make sure there's a minimum height
+ // note we use iY to ensure that there's a buffer below too
+ int iX, iY, iWidth, iHeight;
+ m_pAvatar->GetBounds( iX, iY, iWidth, iHeight );
+ iMinHeight = 2 * iY + iHeight;
+ }
+ else
+ {
+ m_pAvatar->SetVisible( false );
+ m_pAvatar->ClearAvatar();
+ }
+ }
+ if ( m_pAvatarBG )
+ {
+ m_pAvatarBG->SetVisible( m_pAvatar != NULL && m_pAvatar->IsVisible() );
+ }
+
+ const char *pTextLabelName = steamID != CSteamID() ? "AvatarTextLabel" : "TextLabel";
+ CExLabel* pText = dynamic_cast< CExLabel *>( FindChildByName( pTextLabelName ) );
+ if ( pText )
+ {
+ pText->SetVisible( true );
+ pText->InvalidateLayout( true, false );
+ int iWidth, iHeight;
+ pText->GetSize( iWidth, iHeight );
+ int iContentWidth, iContentHeight;
+ pText->GetContentSize( iContentWidth, iContentHeight );
+ pText->SetSize( iWidth, iContentHeight );
+ int iDelta = iContentHeight - iHeight;
+ // resize ourselves to fit
+ int iContainerWidth, iContainerHeight;
+ GetSize( iContainerWidth, iContainerHeight );
+ SetSize( iContainerWidth, MAX( iContainerHeight + iDelta, iMinHeight ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+class CNotificationToastControl : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CNotificationToastControl, vgui::EditablePanel );
+public:
+ CNotificationToastControl( vgui::EditablePanel *pParent, vgui::EditablePanel *pNotificationToast, int iNotificationID, bool bAddControls )
+ : BaseClass( pParent, bAddControls ? "NotificationToastControl" : "NotificationToastContainer" )
+ , m_pChild( pNotificationToast )
+ , m_iNotificationID( iNotificationID )
+ , m_bAddControls( bAddControls )
+ , m_pTriggerButton( NULL )
+ , m_pAcceptButton( NULL )
+ , m_pDeclineButton( NULL )
+ , m_iOverrideHeight( 0 )
+ {
+ m_pChild->SetParent( this );
+ }
+
+ virtual ~CNotificationToastControl()
+ {
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme )
+ {
+ CEconNotification *pNotification = g_notificationQueue.GetNotification( m_iNotificationID );
+
+ // It is not entirely clear why pNotification is allowed to be NULL. Weapon switching
+ // with pyro was causing crashes because of pNotification being NULL, and there were
+ // previously existing checks, but it's not clear why.
+ CEconNotification::EType eNotificationType = pNotification ? pNotification->NotificationType() \
+ : CEconNotification::eType_Basic;
+
+ bool bHighPriority = pNotification && pNotification->BHighPriority();
+ bool bCanDelete = false;
+ bool bCanAcceptDecline = false;
+ bool bCanTrigger = false;
+ bool bOneButton = false;
+
+ switch ( eNotificationType )
+ {
+ case CEconNotification::eType_AcceptDecline:
+ bCanAcceptDecline = true;
+ break;
+ case CEconNotification::eType_Basic:
+ bCanDelete = true;
+ bOneButton = true;
+ break;
+ case CEconNotification::eType_MustTrigger:
+ bCanTrigger = true;
+ bOneButton = true;
+ break;
+ case CEconNotification::eType_Trigger:
+ bCanTrigger = true;
+ bCanDelete = true;
+ break;
+ default:
+ Assert( !"Unhandled enum type" );
+ }
+
+ KeyValues *pConditions = NULL;
+
+ if ( bOneButton || bHighPriority )
+ {
+ pConditions = new KeyValues( "conditions" );
+ if ( bOneButton )
+ {
+ KeyValues *pSubKey = new KeyValues( "if_one_button" );
+ pConditions->AddSubKey( pSubKey );
+ }
+ if ( bHighPriority )
+ {
+ KeyValues *pSubKey = new KeyValues( "if_high_priority" );
+ pConditions->AddSubKey( pSubKey );
+ }
+ }
+
+ if ( m_bAddControls )
+ {
+ LoadControlSettings( "Resource/UI/Econ/NotificationToastControl.res", NULL, NULL, pConditions );
+ }
+ else
+ {
+ LoadControlSettings( "Resource/UI/Econ/NotificationToastContainer.res", NULL, NULL, pConditions );
+ }
+
+ if ( pConditions )
+ {
+ pConditions->deleteThis();
+ }
+
+ BaseClass::ApplySchemeSettings( scheme );
+
+ GetSize( m_iOriginalWidth, m_iOriginalHeight );
+
+ CExButton *pDeleteButton = dynamic_cast< CExButton *>( FindChildByName( "DeleteButton" ) );
+
+ if ( pDeleteButton && bCanDelete )
+ {
+ pDeleteButton->AddActionSignalTarget( this );
+ pDeleteButton->SetVisible ( pNotification != NULL );
+ }
+
+ if ( pNotification == NULL )
+ return;
+
+ if ( bCanAcceptDecline )
+ {
+ m_pAcceptButton = dynamic_cast< CExButton * >( FindChildByName( "AcceptButton" ) );
+ m_pDeclineButton = dynamic_cast< CExButton * >( FindChildByName( "DeclineButton" ) );
+ if ( m_pAcceptButton && m_pDeclineButton )
+ {
+ m_pAcceptButton->AddActionSignalTarget( this );
+ m_pDeclineButton->AddActionSignalTarget( this );
+ m_pAcceptButton->SetVisible( true );
+ m_pDeclineButton->SetVisible( true );
+ int posX, posY;
+ m_pAcceptButton->GetPos( posX, posY );
+ m_iButtonOffsetY = GetTall() - posY;
+ }
+ }
+ if ( bCanTrigger )
+ {
+ m_pTriggerButton = dynamic_cast< CExButton *>( FindChildByName( "TriggerButton" ) );
+ if ( m_pTriggerButton )
+ {
+ m_pTriggerButton->AddActionSignalTarget( this );
+ m_pTriggerButton->SetVisible( true );
+ int posX, posY;
+ m_pTriggerButton->GetPos( posX, posY );
+ m_iButtonOffsetY = GetTall() - posY;
+ }
+ }
+ }
+
+ virtual void PerformLayout()
+ {
+ BaseClass::PerformLayout();
+ m_pChild->PerformLayout();
+ int iWidth, iHeight;
+ m_pChild->GetSize( iWidth, iHeight );
+
+ // position control buttons
+ if ( iHeight + m_iButtonOffsetY > m_iOriginalHeight )
+ {
+ if ( m_pAcceptButton && m_pDeclineButton )
+ {
+ int posX, posY;
+ m_pAcceptButton->GetPos( posX, posY );
+// int newPosY = iHeight;
+// iHeight += m_iButtonOffsetY;
+// m_pAcceptButton->SetPos( posX, newPosY );
+// m_pDeclineButton->GetPos( posX, posY );
+// m_pDeclineButton->SetPos( posX, newPosY );
+ }
+ else if ( m_pTriggerButton )
+ {
+ int posX, posY;
+ m_pTriggerButton->GetPos( posX, posY );
+// posY = iHeight;
+// iHeight += m_iButtonOffsetY;
+// m_pTriggerButton->SetPos( posX, posY );
+ }
+ }
+
+ // position help label
+ CEconNotification *pNotification = g_notificationQueue.GetNotification( m_iNotificationID );
+ CExLabel *pHelpLabel = dynamic_cast< CExLabel* >( FindChildByName( "HelpTextLabel" ) );
+ if ( pHelpLabel )
+ {
+ if ( pNotification )
+ {
+ const wchar_t *pszText = NULL;
+ const char *pszTextKey = pNotification->GetUnlocalizedHelpText();
+ if ( pszTextKey )
+ {
+ pszText = g_pVGuiLocalize->Find( pszTextKey );
+ }
+ if ( pszText )
+ {
+ wchar_t wzFinal[512] = L"";
+ if ( ::input->IsSteamControllerActive() )
+ {
+ UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ), GAME_ACTION_SET_FPSCONTROLS );
+ }
+ else
+ {
+ UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ) );
+ }
+ ColorizeText( pNotification, pHelpLabel, wzFinal );
+ }
+ }
+
+ pHelpLabel->InvalidateLayout( true, false );
+ int posX, posY;
+ pHelpLabel->GetPos( posX, posY );
+ int iContentWidth, iContentHeight;
+ pHelpLabel->GetContentSize( iContentWidth, iContentHeight );
+ int iLabelWidth, iLabelHeight;
+ pHelpLabel->GetSize( iLabelWidth, iLabelHeight );
+ int iTextInsetX, iTextInsetY;
+ pHelpLabel->GetTextInset( &iTextInsetX, &iTextInsetY );
+ pHelpLabel->SetSize( iLabelWidth, iContentHeight + iTextInsetY );
+ posY = iHeight;
+ pHelpLabel->SetPos( posX, posY - iTextInsetY );
+ iHeight += iContentHeight + iTextInsetY;
+ }
+
+ // resize ourselves to fit the child height wise
+ int iContainerHeight = MAX( m_iOriginalHeight, iHeight );
+ SetSize( m_iOriginalWidth, m_iOverrideHeight != 0 ? m_iOverrideHeight : iContainerHeight );
+ }
+
+ virtual void OnCommand( const char *command )
+ {
+ CEconNotification *pNotification = g_notificationQueue.GetNotification( m_iNotificationID );
+
+ if ( pNotification != NULL )
+ {
+ if ( !Q_strncmp( command, "delete", ARRAYSIZE( "delete" ) ) )
+ {
+ pNotification->Deleted();
+ g_notificationQueue.RemoveNotification( m_iNotificationID );
+ return;
+ }
+ else if ( !Q_strncmp( command, "trigger", ARRAYSIZE( "trigger" ) ) )
+ {
+ pNotification->Trigger();
+ }
+ else if ( !Q_strncmp( command, "accept", ARRAYSIZE( "accept" ) ) )
+ {
+ pNotification->Accept();
+ }
+ else if ( !Q_strncmp( command, "decline", ARRAYSIZE( "decline" ) ) )
+ {
+ pNotification->Decline();
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+ }
+
+ int GetOverrideHeight() const
+ {
+ return m_iOverrideHeight;
+ }
+
+ void SetOverrideHeight( int iHeight )
+ {
+ m_iOverrideHeight = iHeight;
+ }
+
+private:
+ vgui::EditablePanel* m_pChild;
+ vgui::Panel* m_pTriggerButton;
+ vgui::Panel* m_pAcceptButton;
+ vgui::Panel* m_pDeclineButton;
+ int m_iNotificationID;
+ int m_iOriginalWidth;
+ int m_iOriginalHeight;
+ int m_iButtonOffsetY;
+ int m_iOverrideHeight;
+ bool m_bAddControls;
+};
+
+//-----------------------------------------------------------------------------
+
+struct NotificationUIInfo_t
+{
+ CNotificationToastControl *m_pPanel;
+ int m_iStartPosX;
+ int m_iStartPosY;
+};
+
+// notification queue panel that is a HUD element
+// this is the visualization of the notifications while in game
+class CNotificationQueuePanel : public CHudElement, public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CNotificationQueuePanel, vgui::EditablePanel );
+public:
+ CNotificationQueuePanel( const char *pElementName )
+ : CHudElement( pElementName )
+ , BaseClass( NULL, "NotificationQueuePanel" )
+ , m_mapNotificationPanels( DefLessFunc(int) )
+ , m_flInvalidateTime( 0.0f )
+ , m_bInvalidated( false )
+ {
+ vgui::Panel *pParent = g_pClientMode->GetViewport();
+ SetParent( pParent );
+
+ SetHiddenBits( HIDEHUD_MISCSTATUS );
+ }
+
+ virtual ~CNotificationQueuePanel()
+ {
+ }
+
+ virtual bool ShouldDraw( void )
+ {
+ if ( !CHudElement::ShouldDraw() )
+ {
+ return false;
+ }
+
+ if ( engine->IsPlayingDemo() )
+ {
+ return false;
+ }
+
+ if ( cl_notifications_show_ingame.GetInt() == 0 )
+ {
+ return false;
+ }
+
+ CHudVote *pHudVote = GET_HUDELEMENT( CHudVote );
+ if ( pHudVote && pHudVote->IsVoteUIActive() )
+ {
+ return false;
+ }
+
+ return m_mapNotificationPanels.Count() > 0 || g_notificationQueue.HasItems();
+ }
+
+ virtual void PerformLayout( void )
+ {
+ BaseClass::PerformLayout();
+
+ // Get filtered list of only the notifications that show some in-game content
+ CUtlVector< CEconNotification *> notifications;
+ GetNotifications( notifications );
+
+ const float flMoveTime = cl_notifications_move_time.GetFloat();
+ float lerpPercentage = flMoveTime > 0 ? clamp( ( flMoveTime - m_flInvalidateTime ) / flMoveTime, 0.0f, 1.0f ) : 1.0f;
+ float flCurrTime = engine->Time();
+
+ // move the notifications around
+ const int kMaxVisibleNotifications = cl_notifications_max_num_visible.GetInt();
+
+ int iPosY = MIN( notifications.Count() - 1, kMaxVisibleNotifications - 1 ) * m_iOverlapOffset_Y;
+ int iPosX = MIN( notifications.Count() - 1, kMaxVisibleNotifications - 1 ) * m_iOverlapOffset_X;
+ int zpos = 100;
+ int iPreviousHeight = 0;
+ for ( int i = 0; i < notifications.Count(); ++i )
+ {
+ CEconNotification *pNotification = notifications[i];
+ int mapIdx = m_mapNotificationPanels.Find( pNotification->GetID() );
+ if ( m_mapNotificationPanels.IsValidIndex( mapIdx ) == false || pNotification->GetInGameLifeTime() < flCurrTime )
+ {
+ continue;
+ }
+ NotificationUIInfo_t &info = m_mapNotificationPanels[mapIdx];
+ CNotificationToastControl *pPanel = info.m_pPanel;
+ if ( pPanel )
+ {
+ if ( pPanel->IsVisible() == false )
+ {
+ pPanel->SetVisible( true );
+ }
+ if ( i == 0 && pPanel->GetOverrideHeight() != 0 )
+ {
+ pPanel->SetOverrideHeight( 0 );
+ pPanel->InvalidateLayout( true, false );
+ }
+ int iPanelX;
+ int iPanelY;
+ pPanel->GetPos( iPanelX, iPanelY );
+ if ( m_bInvalidated )
+ {
+ info.m_iStartPosX = iPanelX;
+ info.m_iStartPosY = iPanelY;
+ }
+ int iNewPosX = iPosX;
+ int iNewPosY = iPosY;
+ iNewPosX = Lerp( lerpPercentage, info.m_iStartPosX, iNewPosX );
+ iNewPosY = Lerp( lerpPercentage, info.m_iStartPosY, iNewPosY );
+ pPanel->SetPos( iNewPosX, iNewPosY );
+ pPanel->SetZPos( --zpos );
+ bool bStoppedMoving = iNewPosX == iPanelX && iNewPosY == iPosY;
+ // only show panels that are more than we want visible if they are moving
+ if ( i > kMaxVisibleNotifications - 1 )
+ {
+ if ( bStoppedMoving )
+ {
+ pPanel->SetVisible( false );
+ }
+ continue;
+ }
+ // don't poke out underneath if we are visible and stopped moving
+ if ( i != 0 && pPanel->GetOverrideHeight() == 0 && bStoppedMoving )
+ {
+ pPanel->SetOverrideHeight( MIN( pPanel->GetTall(), iPreviousHeight ) );
+ pPanel->InvalidateLayout( true, false );
+ }
+ iPreviousHeight = pPanel->GetTall();
+
+ iPosY = MAX( 0, iPosY - m_iOverlapOffset_Y );
+ iPosX = MAX( 0, iPosX - m_iOverlapOffset_X );
+ }
+ }
+ m_bInvalidated = false;
+ SetTall( ScreenHeight() - m_iOriginalY );
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme )
+ {
+ LoadControlSettings( "Resource/UI/Econ/NotificationQueuePanel.res" );
+ GetBounds( m_iOriginalX, m_iOriginalY, m_iOriginalWidth, m_iOriginalHeight );
+
+ BaseClass::ApplySchemeSettings( scheme );
+ }
+
+ virtual void OnThink()
+ {
+ BaseClass::OnThink();
+
+ if ( IsVisible() == false )
+ {
+ return;
+ }
+
+ // Get filtered list of only the notifications that show some in-game content
+ CUtlVector< CEconNotification *> notifications;
+ GetNotifications( notifications );
+
+ float flCurrTime = engine->Time();
+
+ // check to see if we have a panel for each notification
+ int i = 0;
+ for ( i = 0; i < notifications.Count(); ++i )
+ {
+ CEconNotification *pNotification = notifications[i];
+ int mapIdx = m_mapNotificationPanels.Find( pNotification->GetID() );
+ if ( m_mapNotificationPanels.IsValidIndex( mapIdx ) == false || pNotification->GetInGameLifeTime() < flCurrTime )
+ {
+ m_bInvalidated = true;
+ // create the panel and add it to the UI
+ // have it slide from the bottom
+ CNotificationToastControl *pControl = NULL;
+ int iPosX = 0, iPosY = 0;
+ vgui::EditablePanel *pPanel = pNotification->CreateUIElement( false );
+ if ( pPanel )
+ {
+ pControl = new CNotificationToastControl( this, pPanel, pNotification->GetID(), false );
+ pControl->GetPos( iPosX, iPosY );
+ pControl->SetPos( iPosX, ScreenHeight() );
+ iPosY = ScreenHeight();
+ }
+ NotificationUIInfo_t info = { pControl, iPosX, iPosY };
+ m_mapNotificationPanels.Insert( pNotification->GetID(), info );
+ }
+ }
+
+ // now check to see if we have panels and there is no matching notification
+ i = m_mapNotificationPanels.FirstInorder();
+ while ( m_mapNotificationPanels.IsValidIndex( i ) )
+ {
+ int idx = i;
+ i = m_mapNotificationPanels.NextInorder( i );
+ int iID = m_mapNotificationPanels.Key( idx );
+
+ CEconNotification *pNotification = g_notificationQueue.GetNotification( iID );
+ if ( pNotification == NULL || pNotification->GetInGameLifeTime() < flCurrTime )
+ {
+ // fade here, cause we don't really want to re-layout
+ NotificationUIInfo_t &info = m_mapNotificationPanels[idx];
+ vgui::EditablePanel *pPanel = info.m_pPanel;
+ if ( pPanel )
+ {
+ pPanel->MarkForDeletion();
+ }
+ m_mapNotificationPanels.RemoveAt( idx );
+ m_bInvalidated = true;
+ }
+ }
+
+ if ( m_bInvalidated )
+ {
+ m_flInvalidateTime = MAX( cl_notifications_move_time.GetFloat(), 0.0f );
+ }
+ if ( m_flInvalidateTime > 0 )
+ {
+ m_flInvalidateTime -= gpGlobals->frametime;
+ InvalidateLayout( true, false );
+ }
+ }
+
+protected:
+ typedef CUtlMap< int, NotificationUIInfo_t > tNotificationPanels;
+ tNotificationPanels m_mapNotificationPanels;
+ int m_iOriginalX;
+ int m_iOriginalY;
+ int m_iOriginalWidth;
+ int m_iOriginalHeight;
+ float m_flInvalidateTime;
+ bool m_bInvalidated;
+
+ CPanelAnimationVar( int, m_iVisibleBuffer, "buffer_between_visible", "5" );
+ CPanelAnimationVar( int, m_iOverlapOffset_X, "overlap_offset_x", "10" );
+ CPanelAnimationVar( int, m_iOverlapOffset_Y, "overlap_offset_y", "10" );
+
+ void GetNotifications(CUtlVector< CEconNotification *> &notifications )
+ {
+ const CUtlVector< CEconNotification *> &allNotifications = g_notificationQueue.GetItems();
+
+ for (int i = 0 ; i < allNotifications.Count() ; ++i )
+ {
+ CEconNotification *pNotification = allNotifications[i];
+ if ( pNotification->BShowInGameElements() )
+ {
+ notifications.AddToTail( pNotification );
+ }
+ }
+ }
+};
+
+DECLARE_HUDELEMENT( CNotificationQueuePanel );
+
+//-----------------------------------------------------------------------------
+
+CEconNotification::CEconNotification()
+ : m_pText("")
+ , m_pSoundFilename( NULL )
+ , m_flExpireTime( engine->Time() + 10.0f )
+ , m_pKeyValues( NULL )
+ , m_bInUse( false )
+ , m_steamID()
+{
+}
+
+CEconNotification::~CEconNotification()
+{
+ if ( m_pKeyValues )
+ {
+ m_pKeyValues->deleteThis();
+ }
+}
+
+void CEconNotification::SetText( const char *pText )
+{
+ m_pText = pText;
+}
+
+void CEconNotification::AddStringToken( const char* pToken, const wchar_t* pValue )
+{
+ if ( m_pKeyValues == NULL )
+ {
+ m_pKeyValues = new KeyValues( "CEconNotification" );
+ }
+ m_pKeyValues->SetWString( pToken, pValue );
+}
+
+void CEconNotification::SetKeyValues( KeyValues *pKeyValues )
+{
+ if ( m_pKeyValues != NULL )
+ {
+ m_pKeyValues->deleteThis();
+ }
+ m_pKeyValues = pKeyValues->MakeCopy();
+}
+
+KeyValues *CEconNotification::GetKeyValues() const
+{
+ return m_pKeyValues;
+}
+
+const wchar_t *CEconNotification::GetText()
+{
+ g_pVGuiLocalize->ConstructString_safe( m_wszBuffer, m_pText, m_pKeyValues );
+ return m_wszBuffer;
+}
+
+int CEconNotification::GetID() const
+{
+ return m_iID;
+}
+
+void CEconNotification::SetLifetime( float flSeconds )
+{
+ m_flExpireTime = engine->Time() + flSeconds;
+}
+
+float CEconNotification::GetExpireTime() const
+{
+ return m_flExpireTime;
+}
+
+float CEconNotification::GetInGameLifeTime() const
+{
+ return m_flExpireTime; // default's to passed in time unless otherwise set (for derived classes)
+}
+
+void CEconNotification::SetIsInUse( bool bInUse)
+{
+ m_bInUse = bInUse;
+}
+
+bool CEconNotification::GetIsInUse() const
+{
+ return m_bInUse;
+}
+
+void CEconNotification::SetSteamID( const CSteamID &steamID )
+{
+ m_steamID = steamID;
+}
+
+const CSteamID &CEconNotification::GetSteamID() const
+{
+ return m_steamID;
+}
+
+void CEconNotification::MarkForDeletion()
+{
+ // to be deleted ASAP
+ m_flExpireTime = 0.0f;
+}
+
+CEconNotification::EType CEconNotification::NotificationType()
+{
+ return eType_Basic;
+}
+
+bool CEconNotification::BHighPriority()
+{
+ return false;
+}
+
+void CEconNotification::Trigger()
+{
+}
+
+void CEconNotification::Accept()
+{
+}
+
+void CEconNotification::Decline()
+{
+}
+
+void CEconNotification::Deleted()
+{
+}
+
+void CEconNotification::Expired()
+{
+}
+vgui::EditablePanel *CEconNotification::CreateUIElement( bool bMainMenu ) const
+{
+ CGenericNotificationToast *pToast = new CGenericNotificationToast( NULL, m_iID, bMainMenu );
+ return pToast;
+}
+
+const char *CEconNotification::GetUnlocalizedHelpText()
+{
+ switch ( NotificationType() )
+ {
+ case eType_AcceptDecline:
+ return "#Notification_AcceptOrDecline_Help";
+ case eType_MustTrigger:
+ case eType_Trigger:
+ return "#Notification_CanTrigger_Help";
+ default:
+ Assert( !"Unhandled enum value" );
+ // ---v
+ case eType_Basic:
+ return "#Notification_Remove_Help";
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+
+class CMainMenuNotificationsControl : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CMainMenuNotificationsControl, vgui::EditablePanel );
+public:
+ CMainMenuNotificationsControl( vgui::EditablePanel *pParent, const char *pElementName )
+ : BaseClass( pParent, pElementName )
+ , m_mapNotificationPanels( DefLessFunc(int) )
+ , m_iNumItems( 0 )
+ {
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 250 );
+ }
+
+ virtual ~CMainMenuNotificationsControl()
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ }
+
+ virtual void PerformLayout( void )
+ {
+ BaseClass::PerformLayout();
+
+ const CUtlVector< CEconNotification *> &notifications = g_notificationQueue.GetItems();
+
+ // position the notifications around
+ // grow down
+ int iTotalHeight = 0;
+ const int kBuffer = 5;
+ for ( int i = 0; i < notifications.Count(); ++i )
+ {
+ CEconNotification *pNotification = notifications[i];
+ int mapIdx = m_mapNotificationPanels.Find( pNotification->GetID() );
+ if ( m_mapNotificationPanels.IsValidIndex( mapIdx ) == false )
+ {
+ continue;
+ }
+ NotificationUIInfo_t &info = m_mapNotificationPanels[mapIdx];
+ vgui::EditablePanel *pPanel = info.m_pPanel;
+ if ( pPanel )
+ {
+ int iPanelX;
+ int iPanelY;
+ int iWidth;
+ int iHeight;
+ pPanel->GetBounds( iPanelX, iPanelY, iWidth, iHeight );
+ int iNewPosX = iPanelX;
+ int iNewPosY = iTotalHeight;
+ pPanel->SetPos( iNewPosX, iNewPosY );
+ iTotalHeight += iHeight + kBuffer;
+ }
+ }
+ int iWidth, iHeight;
+ GetSize( iWidth, iHeight );
+ SetSize( iWidth, iTotalHeight );
+ if ( iTotalHeight != iHeight )
+ {
+ GetParent()->InvalidateLayout( false, false );
+ }
+ }
+
+ virtual void OnTick()
+ {
+ if ( m_iNumItems != g_notificationQueue.GetItems().Count() )
+ {
+ m_iNumItems = g_notificationQueue.GetItems().Count();
+ PostActionSignal( new KeyValues("Command", "command", "notifications_update" ) );
+ }
+ }
+
+ virtual void OnThink()
+ {
+ BaseClass::OnThink();
+
+ if ( IsVisible() == false )
+ {
+ return;
+ }
+
+ const CUtlVector< CEconNotification *> &notifications = g_notificationQueue.GetItems();
+ bool bInvalidated = false;
+
+ // check to see if we have a panel for each notification
+ int i = 0;
+ for ( i = 0; i < notifications.Count(); ++i )
+ {
+ CEconNotification *pNotification = notifications[i];
+ int mapIdx = m_mapNotificationPanels.Find( pNotification->GetID() );
+ if ( m_mapNotificationPanels.IsValidIndex( mapIdx ) == false )
+ {
+ bInvalidated = true;
+ CNotificationToastControl *pControl = NULL;
+ vgui::EditablePanel *pPanel = pNotification->CreateUIElement( true );
+ if ( pPanel )
+ {
+ pControl = new CNotificationToastControl( this, pPanel, pNotification->GetID(), true );
+ }
+ NotificationUIInfo_t info = { pControl, 0, 0 };
+ m_mapNotificationPanels.Insert( pNotification->GetID(), info );
+ }
+ }
+
+ // now check to see if we have panels and there is no matching notification
+ i = m_mapNotificationPanels.FirstInorder();
+ while ( m_mapNotificationPanels.IsValidIndex( i ) )
+ {
+ int idx = i;
+ i = m_mapNotificationPanels.NextInorder( i );
+ int iID = m_mapNotificationPanels.Key( idx );
+ if ( g_notificationQueue.GetNotification( iID ) == NULL )
+ {
+ NotificationUIInfo_t &info = m_mapNotificationPanels[idx];
+ vgui::EditablePanel *pPanel = info.m_pPanel;
+ if ( pPanel )
+ {
+ pPanel->MarkForDeletion();
+ }
+ m_mapNotificationPanels.RemoveAt( idx );
+ bInvalidated = true;
+ }
+ }
+
+ if ( bInvalidated )
+ {
+ InvalidateLayout( true, false );
+ }
+ }
+
+protected:
+ typedef CUtlMap< int, NotificationUIInfo_t > tNotificationPanels;
+ tNotificationPanels m_mapNotificationPanels;
+ int m_iNumItems;
+};
+
+// Show in UI
+class CNotificationsPresentPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CNotificationsPresentPanel, vgui::EditablePanel );
+public:
+ CNotificationsPresentPanel( vgui::Panel *pParent, const char* pElementName ) : vgui::EditablePanel( pParent, "NotificationsPresentPanel" )
+ {
+ SetMouseInputEnabled( true );
+ }
+
+ virtual ~CNotificationsPresentPanel()
+ {
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/Econ/NotificationsPresentPanel.res" );
+
+ for ( int i = 0; i < GetChildCount(); i++ )
+ {
+ vgui::Panel *pChild = GetChild( i );
+ pChild->SetMouseInputEnabled( false );
+ }
+ }
+
+ virtual void OnMousePressed(vgui::MouseCode code)
+ {
+ if ( code != MOUSE_LEFT )
+ return;
+
+ PostActionSignal( new KeyValues("Close") );
+
+ // audible feedback
+ const char *soundFilename = "ui/buttonclick.wav";
+
+ vgui::surface()->PlaySound( soundFilename );
+ }
+};
+
+DECLARE_BUILD_FACTORY( CNotificationsPresentPanel );
+
+//-----------------------------------------------------------------------------
+// External interface for the notification queue
+
+int NotificationQueue_Add( CEconNotification *pNotification )
+{
+ if ( !engine->IsInGame() || (cl_notifications_show_ingame.GetBool() && pNotification->BShowInGameElements()) )
+ {
+ vgui::surface()->PlaySound( pNotification->GetSoundFilename() );
+ }
+ return g_notificationQueue.AddNotification( pNotification );
+}
+
+CEconNotification *NotificationQueue_Get( int iID )
+{
+ return g_notificationQueue.GetNotification( iID );
+}
+
+CEconNotification *NotificationQueue_GetByIndex( int idx )
+{
+ return g_notificationQueue.GetNotificationByIndex( idx );
+}
+
+void NotificationQueue_RemoveAll()
+{
+ g_notificationQueue.RemoveAllNotifications();
+}
+
+void NotificationQueue_Remove( int iID )
+{
+ g_notificationQueue.RemoveNotification( iID );
+}
+
+void NotificationQueue_Remove( CEconNotification *pNotification )
+{
+ g_notificationQueue.RemoveNotification( pNotification );
+}
+
+void NotificationQueue_Remove( NotificationFilterFunc func )
+{
+ g_notificationQueue.RemoveNotifications( func );
+}
+
+int NotificationQueue_Count( NotificationFilterFunc func )
+{
+ return g_notificationQueue.CountNotifications( func );
+}
+
+void NotificationQueue_Visit( CEconNotificationVisitor &visitor )
+{
+ g_notificationQueue.VisitNotifications( visitor );
+}
+
+void NotificationQueue_Update()
+{
+ g_notificationQueue.Update();
+}
+
+int NotificationQueue_GetNumNotifications()
+{
+ return g_notificationQueue.GetItems().Count();
+}
+
+vgui::EditablePanel* NotificationQueue_CreateMainMenuUIElement( vgui::EditablePanel *pParent, const char *pElementName )
+{
+ CMainMenuNotificationsControl *pControl = new CMainMenuNotificationsControl( pParent, pElementName );
+ pControl->AddActionSignalTarget( pParent );
+ return pControl;
+}
+
+//-----------------------------------------------------------------------------
+
+CON_COMMAND( cl_trigger_first_notification, "Tries to accept/trigger the first notification" )
+{
+ const CUtlVector< CEconNotification *> &notifications = g_notificationQueue.GetItems();
+ if ( notifications.Count() > 0 )
+ {
+ CEconNotification *pNotification = notifications[0];
+ switch ( pNotification->NotificationType() )
+ {
+ case CEconNotification::eType_AcceptDecline:
+ pNotification->Accept();
+ break;
+ case CEconNotification::eType_MustTrigger:
+ case CEconNotification::eType_Trigger:
+ pNotification->Trigger();
+ case CEconNotification::eType_Basic:
+ break;
+ default:
+ Assert( !"Unhandled enum value" );
+ }
+ }
+}
+
+CON_COMMAND( cl_decline_first_notification, "Tries to decline/remove the first notification" )
+{
+ const CUtlVector< CEconNotification *> &notifications = g_notificationQueue.GetItems();
+ if ( notifications.Count() > 0 )
+ {
+ CEconNotification *pNotification = notifications[0];
+ switch ( pNotification->NotificationType() )
+ {
+ case CEconNotification::eType_AcceptDecline:
+ pNotification->Decline();
+ break;
+ case CEconNotification::eType_MustTrigger:
+ break; // YOU MUUUSSTTTTT
+ case CEconNotification::eType_Trigger:
+ case CEconNotification::eType_Basic:
+ pNotification->Deleted();
+ pNotification->MarkForDeletion();
+ break;
+ default:
+ Assert( !"Unhandled enum value" );
+ }
+ }
+}
+
+#ifdef _DEBUG
+
+#include "confirm_dialog.h"
+
+class CTFTestNotification : public CEconNotification
+{
+public:
+ CTFTestNotification( const char* pText, EType eType )
+ : CEconNotification()
+ , m_pText( pText )
+ , m_eType( eType )
+ {
+ }
+
+ virtual EType NotificationType() OVERRIDE { return m_eType; }
+
+ virtual void Trigger() OVERRIDE
+ {
+ ShowMessageBox( "", m_pText, "#GameUI_OK" );
+ MarkForDeletion();
+ }
+ virtual void Accept() OVERRIDE
+ {
+ ShowMessageBox( "Accept", m_pText, "#GameUI_OK" );
+ MarkForDeletion();
+ }
+ virtual void Decline() OVERRIDE
+ {
+ ShowMessageBox( "Decline", m_pText, "#GameUI_OK" );
+ MarkForDeletion();
+ }
+private:
+ const char *m_pText;
+ EType m_eType;
+};
+
+CON_COMMAND( cl_add_notification, "Adds a notification" )
+{
+ if ( args.ArgC() >= 2 )
+ {
+ CEconNotification::EType eType = CEconNotification::eType_Basic;
+ if ( args.ArgC() >= 5 )
+ {
+ eType = (CEconNotification::EType)atoi( args[4] );
+ }
+
+ CEconNotification *pNotification = new CTFTestNotification( args[1], eType );
+
+ pNotification->SetText( args[1] );
+ if ( args.ArgC() >= 3 )
+ {
+ int iLifetime = atoi( args[2] );
+ pNotification->SetLifetime( iLifetime );
+ }
+ if ( args.ArgC() >= 4 )
+ {
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+ pNotification->SetSteamID( steamapicontext->SteamUser()->GetSteamID() );
+ }
+ }
+ int id = NotificationQueue_Add( pNotification );
+ Msg( "Added notification %d\n", id);
+ }
+}
+
+#endif
diff --git a/game/client/econ/econ_notifications.h b/game/client/econ/econ_notifications.h
new file mode 100644
index 0000000..bf1f460
--- /dev/null
+++ b/game/client/econ/econ_notifications.h
@@ -0,0 +1,214 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+#ifndef ECON_NOTIFICATIONS_H
+#define ECON_NOTIFICATIONS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+// forward declarations
+class KeyValues;
+namespace vgui
+{
+ class EditablePanel;
+};
+class CEconNotificationQueue;
+
+/**
+ * Base class for notifications, but is generic enough to use
+ */
+class CEconNotification
+{
+public:
+ CEconNotification();
+ virtual ~CEconNotification();
+
+ void SetText( const char *pText );
+ void AddStringToken( const char* pToken, const wchar_t* pValue );
+
+ void SetKeyValues( KeyValues *pKeyValues );
+ KeyValues *GetKeyValues() const;
+
+ const char* GetUnlocalizedText() { return m_pText; }
+ const wchar_t *GetText();
+ int GetID() const;
+
+ virtual void SetLifetime( float flSeconds );
+ virtual float GetExpireTime() const;
+
+ virtual float GetInGameLifeTime() const;
+
+ void SetIsInUse( bool bInUse );
+ bool GetIsInUse() const;
+
+ void SetSteamID( const CSteamID &steamID );
+ const CSteamID &GetSteamID() const;
+
+ virtual bool BShowInGameElements() const { return true; }
+
+ virtual void MarkForDeletion();
+
+ enum EType {
+ // Can only be deleted
+ eType_Basic,
+ // Can be accept or declined
+ eType_AcceptDecline,
+ // Can be triggered or deleted
+ eType_Trigger,
+ // Can only be triggered
+ eType_MustTrigger,
+ };
+
+ virtual EType NotificationType();
+
+ // Is this an important/high priority notification. Triggers higher visibility etc..
+ virtual bool BHighPriority();
+
+ // eType_Trigger or eType_MustTrigger
+ virtual void Trigger();
+
+ // eType_AcceptDecline
+ virtual void Accept();
+ virtual void Decline();
+
+ // eType_Basic or eType_Trigger
+ virtual void Deleted();
+
+ // All types, if expire time is set
+ virtual void Expired();
+
+
+
+ virtual void UpdateTick() { }
+
+ virtual const char *GetUnlocalizedHelpText();
+
+ virtual vgui::EditablePanel *CreateUIElement( bool bMainMenu ) const;
+
+ void SetSoundFilename( const char *filename )
+ {
+ m_pSoundFilename = filename;
+ }
+
+ const char *GetSoundFilename() const
+ {
+ if ( m_pSoundFilename )
+ {
+ return m_pSoundFilename;
+ }
+
+ return "ui/notification_alert.wav";
+ }
+
+protected:
+ const char *m_pText;
+ const char *m_pSoundFilename;
+ float m_flExpireTime;
+ KeyValues *m_pKeyValues;
+ wchar_t m_wszBuffer[1024];
+ CSteamID m_steamID;
+
+private:
+ friend class CEconNotificationQueue;
+ int m_iID;
+ bool m_bInUse;
+};
+
+
+
+/**
+ * Filter function for CEconNotification's, used to remove them
+ * @return true if the notification matches, false otherwise
+ */
+typedef bool (*NotificationFilterFunc)( CEconNotification *pNotification );
+
+/**
+ * Visitor object for notifications.
+ */
+class CEconNotificationVisitor
+{
+public:
+ virtual void Visit( CEconNotification &notification ) = 0;
+};
+
+/**
+ * Adds the notification to the notification queue
+ * @param pNotification
+ * @return id to retrieve the notification later if necessary
+ */
+int NotificationQueue_Add( CEconNotification *pNotification );
+
+/**
+ * Retrieves a notification by ID
+ * @param iID id of the notification
+ * @return the CEconNotification, NULL if not found
+ */
+CEconNotification *NotificationQueue_Get( int iID );
+
+/**
+ * Retrieves a notification by index
+ * @param idx Index of the notification relative to GetNumNotifications
+ * @return the CEconNotification, NULL if not found
+ */
+CEconNotification *NotificationQueue_GetByIndex( int idx );
+
+/**
+ * Removes all notifications from the queue and deletes them
+ * @param iID
+ */
+void NotificationQueue_RemoveAll();
+
+/**
+ * Removes the notification from the queue and deletes it
+ * @param iID
+ */
+void NotificationQueue_Remove( int iID );
+
+/**
+ * Removes the notification from the queue and deletes it
+ * @param pNotification
+ */
+void NotificationQueue_Remove( CEconNotification *pNotification );
+
+/**
+ * Removes notifications that pass the specified filter
+ * @param func
+ */
+void NotificationQueue_Remove( NotificationFilterFunc func );
+
+/**
+ * Count up how many notifications of the given kind are already in the queue
+ * @param func
+ */
+int NotificationQueue_Count( NotificationFilterFunc func );
+
+/**
+ * The visitor object will "visit" each notification and perform any work necessary.
+ * @param visitor object
+ */
+void NotificationQueue_Visit( CEconNotificationVisitor &visitor );
+
+/**
+ * Update the notification queue
+ */
+void NotificationQueue_Update();
+
+/**
+ * @return the number of notifications
+ */
+int NotificationQueue_GetNumNotifications();
+
+/**
+ * Create the main menu ui element
+ * @param pParent
+ * @param pElementName
+ * @return the control that was created
+ */
+vgui::EditablePanel* NotificationQueue_CreateMainMenuUIElement( vgui::EditablePanel *pParent, const char *pElementName );
+
+#endif // endif
diff --git a/game/client/econ/econ_sample_rootui.cpp b/game/client/econ/econ_sample_rootui.cpp
new file mode 100644
index 0000000..8b751eb
--- /dev/null
+++ b/game/client/econ/econ_sample_rootui.cpp
@@ -0,0 +1,458 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "econ_sample_rootui.h"
+#include "vgui/IInput.h"
+#include <vgui/ILocalize.h>
+#include "ienginevgui.h"
+#include "econ_item_inventory.h"
+#include "backpack_panel.h"
+#include "item_pickup_panel.h"
+#include "store/store_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+static vgui::DHANDLE<CEconSampleRootUI> g_EconSampleRootUIPanel;
+
+#if !defined (TF_CLIENT_DLL)
+IEconRootUI *EconUI( void )
+{
+ if (!g_EconSampleRootUIPanel.Get())
+ {
+ g_EconSampleRootUIPanel = vgui::SETUP_PANEL( new CEconSampleRootUI( NULL ) );
+ g_EconSampleRootUIPanel->InvalidateLayout( false, true );
+ }
+ return g_EconSampleRootUIPanel;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Basic help dialog
+//-----------------------------------------------------------------------------
+CEconSampleRootUI::CEconSampleRootUI( vgui::Panel *parent ) : vgui::Frame(parent, "econ_sample_rootui")
+{
+ // Econ UI is parented to the game UI panel (not the client DLL panel).
+ vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL );
+ SetParent( gameuiPanel );
+
+ // We don't want the gameui to delete us, or things get messy
+ SetAutoDelete( false );
+
+ SetMoveable( false );
+ SetSizeable( false );
+ SetCloseButtonVisible( false );
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ // Create our subpanels
+ m_pBackpackPanel = new CBackpackPanel( this, "backpack_panel" );
+
+ // Start with just the base UI visible
+ m_nVisiblePanel = ECONUI_BASEUI;
+ UpdateSubPanelVisibility();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CEconSampleRootUI::~CEconSampleRootUI()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconSampleRootUI::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/Econ/RootUI.res" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconSampleRootUI::PerformLayout( void )
+{
+ if ( GetVParent() )
+ {
+ int w,h;
+ vgui::ipanel()->GetSize( GetVParent(), w, h );
+ SetBounds(0,0,w,h);
+ }
+
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconSampleRootUI::ShowPanel(bool bShow)
+{
+ m_bPreventClosure = false;
+ SetVisible( bShow );
+
+ if ( bShow )
+ {
+ MoveToFront();
+ m_nVisiblePanel = ECONUI_BASEUI;
+ UpdateSubPanelVisibility();
+
+ InventoryManager()->ShowItemsPickedUp( true, false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconSampleRootUI::FireGameEvent( IGameEvent *event )
+{
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ if ( m_bPreventClosure )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ }
+ else
+ {
+ ShowPanel( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconSampleRootUI::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "close" ) )
+ {
+ ShowPanel( false );
+
+ // If we're connected to a game server, we also close the game UI.
+ if ( engine->IsInGame() )
+ {
+ bool bClose = true;
+ if ( m_bCheckForRoomOnExit )
+ {
+ // Check to make sure the player has room for all his items. If not, bring up the discard panel. Otherwise, go away.
+ // We need to do this to catch players who used the "Change Loadout" button in the pickup panel, and may be out of room.
+ bClose = !InventoryManager()->CheckForRoomAndForceDiscard();
+ }
+
+ if ( bClose )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+ }
+ }
+ }
+ else if ( !Q_stricmp( command, "back" ) )
+ {
+ ShowPanel( true );
+ }
+ else if ( !Q_stricmp( command, "loadout" ) )
+ {
+ // Ignore selection while we don't have a steam connection
+ if ( !InventoryManager()->GetLocalInventory()->RetrievedInventoryFromSteam() )
+ return;
+
+ OpenSubPanel( ECONUI_LOADOUT );
+ }
+ else if ( !Q_strnicmp( command, "backpack", 8 ) )
+ {
+ OpenSubPanel( ECONUI_BACKPACK );
+ }
+ else if ( !Q_strnicmp( command, "crafting", 8 ) )
+ {
+ OpenSubPanel( ECONUI_CRAFTING );
+ }
+ else if ( !Q_strnicmp( command, "armory", 6 ) )
+ {
+ OpenSubPanel( ECONUI_ARMORY );
+ }
+ else if ( !Q_strnicmp( command, "store", 5 ) )
+ {
+ EconUI()->OpenStorePanel( 0, false );
+ }
+ else if ( !Q_strnicmp( command, "trading", 7 ) )
+ {
+// if ( IsFreeTrialAccount() )
+// {
+// ShowMessageBox( "#TF_Trial_CannotTrade_Title", "#TF_Trial_CannotTrade_Text", "#GameUI_OK" );
+// return;
+// }
+
+ OpenTradingStartDialog();
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconSampleRootUI::OnKeyCodeTyped(vgui::KeyCode code)
+{
+ if ( code == KEY_ESCAPE )
+ {
+ if ( !m_bPreventClosure )
+ {
+ ShowPanel( false );
+ }
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+IEconRootUI *CEconSampleRootUI::OpenEconUI( int iDirectToPage, bool bCheckForInventorySpaceOnExit )
+{
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ ShowPanel( true );
+
+ if ( iDirectToPage == ECONUI_BACKPACK )
+ {
+ }
+ else if ( iDirectToPage == ECONUI_CRAFTING )
+ {
+ }
+ else if ( iDirectToPage == ECONUI_ARMORY )
+ {
+ }
+
+ SetCheckForRoomOnExit( bCheckForInventorySpaceOnExit );
+
+ return this;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconSampleRootUI::CloseEconUI( void )
+{
+ if ( IsVisible() )
+ {
+ ShowPanel( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CEconSampleRootUI::IsUIPanelVisible( EconBaseUIPanels_t iPanel )
+{
+ if ( !IsVisible() )
+ return false;
+
+ switch ( iPanel )
+ {
+ case ECONUI_BASEUI:
+ return true;
+
+ case ECONUI_BACKPACK:
+ return (GetBackpackPanel() && GetBackpackPanel()->IsVisible());
+
+ case ECONUI_CRAFTING:
+ //return (GetCraftingPanel() && GetCraftingPanel()->IsVisible());
+ break;
+
+ case ECONUI_ARMORY:
+ break;
+
+ case ECONUI_TRADING:
+ break;
+ case ECONUI_LOADOUT:
+ break;
+
+ default:
+ Assert(0);
+ break;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconSampleRootUI::OpenSubPanel( EconBaseUIPanels_t nPanel )
+{
+ m_nVisiblePanel = nPanel;
+ UpdateSubPanelVisibility();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconSampleRootUI::OpenTradingStartDialog( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconSampleRootUI::UpdateSubPanelVisibility( void )
+{
+ bool bBackpackVisible = (m_nVisiblePanel == ECONUI_BACKPACK);
+ if ( m_pBackpackPanel->IsVisible() != bBackpackVisible )
+ {
+ m_pBackpackPanel->ShowPanel( false, bBackpackVisible );
+ }
+
+ //m_pClassLoadoutPanel->SetVisible( false );
+ //m_pArmoryPanel->SetVisible( false );
+ //m_pCraftingPanel->ShowPanel( m_iCurrentClassIndex, true, (m_iPrevShowingPanel == CHAP_ARMORY) );
+}
+
+static vgui::DHANDLE<CItemPickupPanel> g_ItemPickupPanel;
+static vgui::DHANDLE<CItemDiscardPanel> g_ItemDiscardPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemPickupPanel *CEconSampleRootUI::OpenItemPickupPanel( void )
+{
+ if (!g_ItemPickupPanel.Get())
+ {
+ g_ItemPickupPanel = vgui::SETUP_PANEL( new CItemPickupPanel( NULL ) );
+ g_ItemPickupPanel->InvalidateLayout( false, true );
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ g_ItemPickupPanel->ShowPanel( true );
+
+ return g_ItemPickupPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemDiscardPanel *CEconSampleRootUI::OpenItemDiscardPanel( void )
+{
+ if (!g_ItemDiscardPanel.Get())
+ {
+ g_ItemDiscardPanel = vgui::SETUP_PANEL( new CItemDiscardPanel( NULL ) );
+ g_ItemDiscardPanel->InvalidateLayout( false, true );
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ g_ItemDiscardPanel->ShowPanel( true );
+
+ return g_ItemDiscardPanel;
+}
+
+static vgui::DHANDLE<CStorePanel> g_StorePanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconSampleRootUI::CreateStorePanel( void )
+{
+ // Clean up previous store panel?
+ if ( g_StorePanel.Get() != NULL )
+ {
+ g_StorePanel->MarkForDeletion();
+ }
+
+ // Create the store panel
+ g_StorePanel = vgui::SETUP_PANEL( new CStorePanel( NULL ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePanel *CEconSampleRootUI::OpenStorePanel( int iItemDef, bool bAddToCart )
+{
+ // Make sure we've got the appropriate connections to Steam
+ if ( !steamapicontext || !steamapicontext->SteamUtils() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_SteamRequired", true, false );
+ return NULL;
+ }
+
+ if ( !steamapicontext->SteamUtils()->IsOverlayEnabled() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_OverlayRequired", true, false );
+ return NULL;
+ }
+
+ if ( !CStorePanel::IsPricesheetLoaded() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_Loading", false, false );
+
+ CStorePanel::SetShouldShowWarnings( true );
+ CStorePanel::RequestPricesheet();
+ return NULL;
+ }
+
+ if ( !g_StorePanel )
+ return NULL;
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+
+ if ( iItemDef )
+ {
+ g_StorePanel->StartAtItemDef( iItemDef, bAddToCart );
+ }
+
+ g_StorePanel->ShowPanel( true );
+
+ return g_StorePanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePanel *CEconSampleRootUI::GetStorePanel( void )
+{
+ return g_StorePanel;
+}
+
+#ifdef _DEBUG
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Open_EconUI( const CCommand &args )
+{
+ EconUI()->OpenEconUI();
+}
+ConCommand open_econui( "open_econui", Open_EconUI );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Open_EconUIBackpack( const CCommand &args )
+{
+ EconUI()->OpenEconUI( ECONUI_BACKPACK );
+}
+ConCommand open_econui_backpack( "open_econui_backpack", Open_EconUIBackpack, "Open the backpack.", FCVAR_NONE );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Open_EconUICrafting( const CCommand &args )
+{
+ EconUI()->OpenEconUI( ECONUI_CRAFTING );
+}
+ConCommand open_econui_crafting( "open_econui_crafting", Open_EconUICrafting, "Open the crafting screen.", FCVAR_NONE );
+#endif // _DEBUG
diff --git a/game/client/econ/econ_sample_rootui.h b/game/client/econ/econ_sample_rootui.h
new file mode 100644
index 0000000..a80380c
--- /dev/null
+++ b/game/client/econ/econ_sample_rootui.h
@@ -0,0 +1,88 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ECON_SAMPLE_ROOTUI_H
+#define ECON_SAMPLE_ROOTUI_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "econ_ui.h"
+#include "vgui_controls/Frame.h"
+#include "GameEventListener.h"
+#include "backpack_panel.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CEconSampleRootUI : public vgui::Frame, public IEconRootUI, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CEconSampleRootUI, vgui::Frame );
+public:
+ CEconSampleRootUI( vgui::Panel *parent );
+ virtual ~CEconSampleRootUI();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void ShowPanel( bool bShow );
+ virtual void OnKeyCodeTyped(vgui::KeyCode code);
+
+ void SetCheckForRoomOnExit( bool bCheck ) { m_bCheckForRoomOnExit = bCheck; }
+
+ void FireGameEvent( IGameEvent *event );
+
+ //---------------------------------------
+ // IEconRootUI
+ virtual IEconRootUI *OpenEconUI( int iDirectToPage = 0, bool bCheckForInventorySpaceOnExit = false );
+ virtual void CloseEconUI( void );
+ virtual bool IsUIPanelVisible( EconBaseUIPanels_t iPanel );
+ virtual void SetPreventClosure( bool bPrevent ) { m_bPreventClosure = bPrevent; }
+
+ // Sub panel access.
+ // These are panels that are parented to the root EconUI.
+ virtual CBackpackPanel *GetBackpackPanel( void ) { return NULL; }
+ virtual CCraftingPanel *GetCraftingPanel( void ) { return NULL; }
+
+ // Gamestats access
+ virtual void Gamestats_ItemTransaction( int eventID, CEconItemView *item, const char *pszReason = NULL, int iQuality = 0 ) { return; }
+ virtual void Gamestats_Store( int eventID, CEconItemView* item=NULL, const char* panelName=NULL,
+ int classId=0, const cart_item_t* in_cartItem=NULL, int in_checkoutAttempts=0, const char* storeError=NULL, int in_totalPrice=0, int in_currencyCode=0 ) { return; }
+ virtual void SetExperimentValue( uint64 experimentValue ) { return; }
+
+ // Open separate economy panels (they're not parented to the root EconUI)
+ // This is here so that games can customize the implementation of these panels.
+ CItemPickupPanel *OpenItemPickupPanel( void );
+ CItemDiscardPanel *OpenItemDiscardPanel( void );
+ // Store
+ virtual void CreateStorePanel( void );
+ virtual CStorePanel *OpenStorePanel( int iItemDef, bool bAddToCart );
+ virtual CStorePanel *GetStorePanel( void );
+
+ // When the root UI is closed, send an "EconUIClosed" message to pListener.
+ virtual void AddPanelCloseListener( vgui::Panel *pListener ) { AssertMsg( 0, "Implement me!" ); }
+
+ // The panel at which we want back to actually close the UI - defaults to the root panel - a negative value can be passed in for class loadout panels
+ virtual void SetClosePanel( int iPanel ) { AssertMsg( 0, "Implement me!" ); }
+
+ // Call this to set which team the class loadout should display
+ virtual void SetDefaultTeam( int iTeam ) { AssertMsg( 0, "Implement me!" ); }
+
+protected:
+ void OpenSubPanel( EconBaseUIPanels_t nPanel );
+ void UpdateSubPanelVisibility( void );
+ void OpenTradingStartDialog( void );
+
+private:
+ bool m_bPreventClosure;
+ bool m_bCheckForRoomOnExit;
+ EconBaseUIPanels_t m_nVisiblePanel;
+
+ CBackpackPanel *m_pBackpackPanel;
+};
+
+#endif // ECON_SAMPLE_ROOTUI_H
diff --git a/game/client/econ/econ_trading.cpp b/game/client/econ/econ_trading.cpp
new file mode 100644
index 0000000..3fb532b
--- /dev/null
+++ b/game/client/econ/econ_trading.cpp
@@ -0,0 +1,645 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+#include "cbase.h"
+
+#include "econ_trading.h"
+
+// for messaging with the GC
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "econ_gcmessages.h"
+#include "econ_ui.h"
+
+// other
+#include "c_baseplayer.h"
+#include "c_playerresource.h"
+
+// UI
+#include "confirm_dialog.h"
+#include "econ_controls.h"
+#include "vgui/ILocalize.h"
+#include "econ_notifications.h"
+
+#ifdef TF_CLIENT_DLL
+#include "c_tf_gamestats.h"
+#endif
+
+#include "gc_clientsystem.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+const char *g_FriendRelationship[] =
+{
+ "none"
+ "blocked",
+ "request_recipient",
+ "friend",
+ "request_initiator",
+ "ignored",
+ "ignored_friend"
+};
+
+const char *g_RejectedReasons[] =
+{
+ "accepted",
+ "declined",
+ "vac_banned_initiator",
+ "vac_banned_target",
+ "target_already_trading",
+ "trading_disabled",
+ "user_not_logged_in"
+};
+
+const char *g_ClosedReasons[] =
+{
+ "traded",
+ "canceled",
+ "error",
+ "does_not_own_items",
+ "untradable_items",
+ "no_items",
+ "trading_disabled"
+};
+
+enum
+{
+ kShowTradeRequestsFrom_FriendsOnly = 1,
+ kShowTradeRequestsFrom_FriendsAndCurrentServer = 2,
+ kShowTradeRequestsFrom_Anyone = 3,
+ kShowTradeRequestsFrom_NoOne = 4,
+};
+
+ConVar cl_trading_show_requests_from( "cl_trading_show_requests_from", "3", FCVAR_ARCHIVE, "View trade requests from a certain group only." );
+
+static bool sbTestingSelfTrade = false;
+
+static int iTradeRequests = 0;
+static int iTradeAttempts = 0;
+static int iTradeOffers = 0;
+static int iGiftsGiven = 0;
+
+const uint32 kTradeRequestLifetime = 30.0f;
+
+// waiting dialog
+class CTradingWaitDialog : public CGenericWaitingDialog
+{
+public:
+ CTradingWaitDialog( const char *pText = "#TF_Trading_Timeout_Text", const wchar_t *pPlayerName = NULL )
+ : CGenericWaitingDialog( NULL )
+ , m_pText( pText )
+ , m_pKeyValues( NULL )
+ {
+ if ( pPlayerName != NULL && wcsicmp( pPlayerName, L"" ) != 0 )
+ {
+ m_pKeyValues = new KeyValues( "CTradingWaitDialog" );
+ m_pKeyValues->SetWString( "other_player", pPlayerName );
+ }
+ }
+
+ virtual ~CTradingWaitDialog()
+ {
+ if ( m_pKeyValues != NULL )
+ {
+ m_pKeyValues->deleteThis();
+ }
+ }
+
+protected:
+ virtual void OnTimeout()
+ {
+ ShowMessageBox( "#TF_Trading_Timeout_Title", m_pText, m_pKeyValues, "#GameUI_OK" );
+ }
+
+ virtual void OnUserClose()
+ {
+ GCSDK::CGCMsg< MsgGCTrading_CancelSession_t > msg( k_EMsgGCTrading_CancelSession );
+ GCClientSystem()->BSendMessage( msg );
+ // @note not sure we need to wait for the GC here, but we will just in case...
+ ShowWaitingDialog( new CTradingWaitDialog(), "#TF_Trading_WaitingForCancel", true, false, 20.0f );
+ }
+
+ KeyValues *m_pKeyValues;
+ const char* m_pText;
+};
+
+// used by the waiting dialogs
+static void TradeCompleteDialogClosed( bool bConfirmed, void *pContext )
+{
+ if ( bConfirmed )
+ {
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// jobs
+class CTFTradeRequestNotification : public CEconNotification
+{
+public:
+ CTFTradeRequestNotification( uint64 ulInitiatorSteamID, uint32 unTradeRequestID, const char* pPlayerName )
+ : CEconNotification()
+ , m_unTradeRequestID( unTradeRequestID )
+ {
+ SetSteamID( ulInitiatorSteamID );
+ g_pVGuiLocalize->ConvertANSIToUnicode( pPlayerName, m_wszPlayerName, sizeof(m_wszPlayerName) );
+ SetLifetime( kTradeRequestLifetime );
+ SetText( "#TF_Trading_JoinText" );
+ AddStringToken( "initiator", m_wszPlayerName );
+ }
+
+ virtual EType NotificationType() { return eType_AcceptDecline; }
+
+ /// XXX(JohnS): Dead code? Was always accept/decline AFAICT
+ virtual void Trigger()
+ {
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_Trading_JoinTitle", "#TF_Trading_JoinText", "#GameUI_OK", "#TF_Trading_JoinCancel", &ConfirmJoinTradeSession );
+ pDialog->SetContext( this );
+ pDialog->AddStringToken( "initiator", m_wszPlayerName );
+ // so we aren't deleted
+ SetIsInUse( true );
+ }
+
+ virtual void Accept()
+ {
+ ConfirmJoinTradeSession( true, this );
+ }
+ virtual void Decline()
+ {
+ ConfirmJoinTradeSession( false, this );
+ }
+ static void ConfirmJoinTradeSession( bool bConfirmed, void *pContext )
+ {
+ CTFTradeRequestNotification *pNotification = (CTFTradeRequestNotification*)pContext;
+ GCSDK::CGCMsg< MsgGCTrading_InitiateTradeResponse_t > msg( k_EMsgGCTrading_InitiateTradeResponse );
+ msg.Body().m_eResponse = bConfirmed ? k_EGCMsgInitiateTradeResponse_Accepted : k_EGCMsgInitiateTradeResponse_Declined;
+ msg.Body().m_unTradeRequestID = pNotification->m_unTradeRequestID;
+ GCClientSystem()->BSendMessage( msg );
+ if ( bConfirmed )
+ {
+ ShowWaitingDialog( new CTradingWaitDialog(), "#TF_Trading_WaitingForServer", true, false, kTradeRequestLifetime );
+ }
+ // now we can be deleted
+ pNotification->SetIsInUse( false );
+ pNotification->MarkForDeletion();
+ }
+
+ static bool IsTradingNotification( CEconNotification * pNotification )
+ {
+ return dynamic_cast< CTFTradeRequestNotification * >( pNotification ) != NULL;
+ }
+
+public:
+ uint32 m_unTradeRequestID;
+ wchar_t m_wszPlayerName[MAX_PLAYER_NAME_LENGTH];
+};
+
+// request from party A (through GC) to start trading
+class CGCTrading_InitiateTradeRequest : public GCSDK::CGCClientJob
+{
+public:
+ CGCTrading_InitiateTradeRequest( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ void SendDeclinedMessage( uint32 unTradeRequestID )
+ {
+ GCSDK::CGCMsg< MsgGCTrading_InitiateTradeResponse_t > msgResponse( k_EMsgGCTrading_InitiateTradeResponse );
+ msgResponse.Body().m_eResponse = k_EGCMsgInitiateTradeResponse_Declined;
+ msgResponse.Body().m_unTradeRequestID = unTradeRequestID;
+ GCClientSystem()->BSendMessage( msgResponse );
+ }
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCTrading_InitiateTradeRequest_t> msg( pNetPacket );
+ CUtlString playerName;
+ msg.BReadStr( &playerName );
+
+ if ( sbTestingSelfTrade )
+ {
+ CloseWaitingDialog();
+ }
+ else if ( msg.Body().m_ulOtherSteamID == Trading_GetLocalPlayerSteamID().ConvertToUint64() )
+ {
+ CloseWaitingDialog();
+ }
+
+ iTradeRequests++;
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_RECEIVED, msg.Body().m_ulOtherSteamID, iTradeRequests );
+#endif
+
+ // auto-decline for ignored or blocked peoples
+ if ( steamapicontext == NULL || steamapicontext->SteamFriends() == NULL )
+ {
+ return true;
+ }
+ CSteamID steamIDOther( msg.Body().m_ulOtherSteamID );
+ EFriendRelationship eRelationship = steamapicontext->SteamFriends()->GetFriendRelationship( steamIDOther );
+ switch ( eRelationship )
+ {
+ case k_EFriendRelationshipBlocked:
+ case k_EFriendRelationshipIgnored:
+ case k_EFriendRelationshipIgnoredFriend:
+ {
+ SendDeclinedMessage( msg.Body().m_unTradeRequestID );
+ return true;
+ }
+ break;
+ } // switch
+
+ switch ( cl_trading_show_requests_from.GetInt() )
+ {
+ case kShowTradeRequestsFrom_FriendsOnly:
+ {
+ if ( eRelationship != k_EFriendRelationshipFriend )
+ {
+ SendDeclinedMessage( msg.Body().m_unTradeRequestID );
+ return true;
+ }
+ }
+ break;
+ case kShowTradeRequestsFrom_FriendsAndCurrentServer:
+ {
+ if ( eRelationship == k_EFriendRelationshipFriend )
+ break;
+ bool bInCurrentGame = false;
+ if ( engine->IsInGame() )
+ {
+ // otherwise, test if they are in the current game
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ if ( g_PR && g_PR->IsConnected( iPlayerIndex ) )
+ {
+ player_info_t pi;
+ if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
+ continue;
+ if ( !pi.friendsID )
+ continue;
+
+ CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
+ if ( steamID == steamIDOther )
+ {
+ bInCurrentGame = true;
+ break;
+ }
+ }
+ }
+ }
+ if ( bInCurrentGame == false )
+ {
+ SendDeclinedMessage( msg.Body().m_unTradeRequestID );
+ return true;
+ }
+ }
+ break;
+ case kShowTradeRequestsFrom_Anyone:
+ {
+ // nothing to check
+ }
+ break;
+ case kShowTradeRequestsFrom_NoOne:
+ {
+ SendDeclinedMessage( msg.Body().m_unTradeRequestID );
+ return true;
+ }
+ break;
+ }
+
+ NotificationQueue_Add( new CTFTradeRequestNotification( msg.Body().m_ulOtherSteamID, msg.Body().m_unTradeRequestID, playerName.Get() ) );
+ return true;
+ }
+protected:
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCTrading_InitiateTradeRequest, "CGCTrading_InitiateTradeRequest", k_EMsgGCTrading_InitiateTradeRequest, GCSDK::k_EServerTypeGCClient );
+
+#ifdef _DEBUG
+#ifdef CLIENT_DLL
+CON_COMMAND( cl_trading_test, "Tests the trade ui notification." )
+{
+ if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL )
+ return;
+
+ CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
+ NotificationQueue_Add( new CTFTradeRequestNotification( steamID.ConvertToUint64(), steamID.ConvertToUint64(), "Biff" ) );
+}
+#endif
+#endif // _DEBUG
+
+/**
+ * Remove notification that matches the trade request
+ */
+class CEconNotificationVisitor_RemoveTradeRequest : public CEconNotificationVisitor
+{
+public:
+ CEconNotificationVisitor_RemoveTradeRequest( uint32 unTradeRequestID ) : m_unTradeRequestID( unTradeRequestID ) {}
+ virtual void Visit( CEconNotification &notification )
+ {
+ if ( CTFTradeRequestNotification::IsTradingNotification( &notification ) )
+ {
+ CTFTradeRequestNotification &tradeNotification = dynamic_cast< CTFTradeRequestNotification& >( notification );
+ if ( tradeNotification.m_unTradeRequestID == m_unTradeRequestID )
+ {
+ tradeNotification.MarkForDeletion();
+ }
+ }
+ }
+private:
+ uint32 m_unTradeRequestID;
+};
+
+// GC notification of trade request status
+static const char *g_pszTradeResponseDescLocKeys[] =
+{
+ "#TF_Trading_BusyText", // k_EGCMsgInitiateTradeResponse_Accepted (should never be used!)
+ "#TF_Trading_DeclinedText", // k_EGCMsgInitiateTradeResponse_Declined
+ "#TF_Trading_VACBannedText", // k_EGCMsgInitiateTradeResponse_VAC_Banned_Initiator
+ "#TF_Trading_VACBanned2Text", // k_EGCMsgInitiateTradeResponse_VAC_Banned_Target
+ "#TF_Trading_BusyText", // k_EGCMsgInitiateTradeResponse_Target_Already_Trading
+ "#TF_Trading_DisabledText", // k_EGCMsgInitiateTradeResponse_Disabled
+ "#TF_Trading_NotLoggedIn", // k_EGCMsgInitiateTradeResponse_NotLoggedIn
+ "#TF_Trading_BusyText", // k_EGCMsgInitiateTradeResponse_Cancel (should never be used!)
+ "#TF_Trading_TooSoon", // k_EGCMsgInitiateTradeResponse_TooSoon
+ "#TF_Trading_TooSoonPenalty", // k_EGCMsgInitiateTradeResponse_TooSoonPenalty
+ "#TF_Trading_TradeBannedText", // (was k_EGCMsgInitiateTradeResponse_Free_Account_Initiator_DEPRECATED)
+ "#TF_Trading_TradeBanned2Text", // k_EGCMsgInitiateTradeResponse_Trade_Banned_Target
+ "#TF_Trading_FreeAccountInitiate", // k_EGCMsgInitiateTradeResponse_Free_Account_Initiator
+ "#TF_Trading_SharedAccountInitiate", // k_EGCMsgInitiateTradeResponse_Shared_Account_Initiator
+ "#TF_Trading_Service_Unavailable", // k_EGCMsgInitiateTradeResponse_Service_Unavailable
+ "#TF_Trading_YouBlockedThem", // k_EGCMsgInitiateTradeResponse_Target_Blocked
+ "#TF_Trading_NeedVerifiedEmail", // k_EGCMsgInitiateTradeResponse_NeedVerifiedEmail
+ "#TF_Trading_NeedSteamGuard", // k_EGCMsgInitiateTradeResponse_NeedSteamGuard
+ "#TF_Trading_SteamGuardDuration", // k_EGCMsgInitiateTradeResponse_SteamGuardDuration
+ "#TF_Trading_TheyCannotTrade", // k_EGCMsgInitiateTradeResponse_TheyCannotTrade
+ "#TF_Trading_PasswordChanged", // k_EGCMsgInitiateTradeResponse_Recent_Password_Reset = 20,
+ "#TF_Trading_NewDevice", // k_EGCMsgInitiateTradeResponse_Using_New_Device = 21,
+ "#TF_Trading_InvalidCookie" // k_EGCMsgInitiateTradeResponse_Sent_Invalid_Cookie = 22,
+};
+
+class CGCTrading_InitiateTradeResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCTrading_InitiateTradeResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ // If this assertion fails it probably means you added a new value to EGCMsgInitiateTradeResponse
+ // but didn't add a string to the array that tracks the user-facing response strings.
+ Assert( ARRAYSIZE( g_pszTradeResponseDescLocKeys ) == k_EGCMsgInitiateTradeResponse_Count );
+
+ CloseWaitingDialog();
+
+ GCSDK::CGCMsg<MsgGCTrading_InitiateTradeResponse_t> msg( pNetPacket );
+ const uint32 eResponse = msg.Body().m_eResponse;
+ switch ( eResponse )
+ {
+ case k_EGCMsgInitiateTradeResponse_Accepted:
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_ACCEPTED, iTradeRequests, g_RejectedReasons[msg.Body().m_eResponse] );
+#endif
+ ShowWaitingDialog( new CTradingWaitDialog(), "#TF_Trading_WaitingForStart", true, false, 30.0f );
+ return true; // !
+
+ case k_EGCMsgInitiateTradeResponse_Cancel:
+ {
+ CEconNotificationVisitor_RemoveTradeRequest visitor( msg.Body().m_unTradeRequestID );
+ NotificationQueue_Visit( visitor );
+ break;
+ }
+
+ case k_EGCMsgInitiateTradeResponse_Declined:
+ case k_EGCMsgInitiateTradeResponse_VAC_Banned_Initiator:
+ case k_EGCMsgInitiateTradeResponse_VAC_Banned_Target:
+ case k_EGCMsgInitiateTradeResponse_Target_Already_Trading:
+ case k_EGCMsgInitiateTradeResponse_Disabled:
+ case k_EGCMsgInitiateTradeResponse_NotLoggedIn:
+ case k_EGCMsgInitiateTradeResponse_TooSoon:
+ case k_EGCMsgInitiateTradeResponse_TooSoonPenalty:
+ case k_EGCMsgInitiateTradeResponse_Trade_Banned_Initiator:
+ case k_EGCMsgInitiateTradeResponse_Trade_Banned_Target:
+ case k_EGCMsgInitiateTradeResponse_Shared_Account_Initiator:
+ case k_EGCMsgInitiateTradeResponse_Service_Unavailable:
+ case k_EGCMsgInitiateTradeResponse_Target_Blocked:
+ case k_EGCMsgInitiateTradeResponse_NeedVerifiedEmail:
+ case k_EGCMsgInitiateTradeResponse_NeedSteamGuard:
+ case k_EGCMsgInitiateTradeResponse_TheyCannotTrade:
+ case k_EGCMsgInitiateTradeResponse_Recent_Password_Reset:
+ case k_EGCMsgInitiateTradeResponse_Using_New_Device:
+ case k_EGCMsgInitiateTradeResponse_Sent_Invalid_Cookie:
+
+ ShowMessageBox( "#TF_Trading_StatusTitle", g_pszTradeResponseDescLocKeys[eResponse], "#GameUI_OK" );
+ break;
+
+ case k_EGCMsgInitiateTradeResponse_SteamGuardDuration:
+ {
+
+ KeyValuesAD kvTokens( "CTradingWaitDialog" );
+ kvTokens->SetWString( "days", L"15" ); // Ideally this would come from the GC, which would get the value from Steam
+ ShowMessageBox( "#TF_Trading_StatusTitle", g_pszTradeResponseDescLocKeys[eResponse], kvTokens, "#GameUI_OK" );
+ break;
+ }
+
+ default:
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_REJECTED, iTradeRequests, "unknown" );
+#endif
+ ShowMessageBox( "#TF_Trading_StatusTitle", "#TF_Trading_BusyText", "#GameUI_OK" );
+ return true;
+ } // switch
+
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_REJECTED, iTradeRequests, g_RejectedReasons[msg.Body().m_eResponse] );
+#endif
+
+ return true;
+ }
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCTrading_InitiateTradeResponse, "CGCTrading_InitiateTradeResponse", k_EMsgGCTrading_InitiateTradeResponse, GCSDK::k_EServerTypeGCClient );
+
+// start trading session
+class CGCTrading_StartSession : public GCSDK::CGCClientJob
+{
+public:
+ CGCTrading_StartSession( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCTrading_StartSession_t> msg( pNetPacket );
+
+ steamapicontext->SteamFriends()->ActivateGameOverlayToUser( "jointrade", msg.Body().m_ulSteamIDPartyB );
+
+ CloseWaitingDialog();
+
+ // remove all trading notifications
+ NotificationQueue_Remove( &CTFTradeRequestNotification::IsTradingNotification );
+
+ return true;
+ }
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCTrading_StartSession, "CGCTrading_StartSession", k_EMsgGCTrading_StartSession, GCSDK::k_EServerTypeGCClient );
+
+
+//-----------------------------------------------------------------------------
+// External interface
+
+CSteamID Trading_GetLocalPlayerSteamID()
+{
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+ return steamapicontext->SteamUser()->GetSteamID();
+ }
+ return CSteamID();
+}
+
+void Trading_RequestTrade( int iPlayerIdx )
+{
+ CSteamID steamID;
+ C_BasePlayer *pPlayer = ToBasePlayer( UTIL_PlayerByIndex( iPlayerIdx ) );
+ if ( pPlayer && pPlayer->GetSteamID( &steamID ) )
+ {
+ Trading_RequestTrade( steamID );
+ }
+}
+
+void Trading_RequestTrade( const CSteamID &steamID )
+{
+ sbTestingSelfTrade = false;
+ GCSDK::CGCMsg< MsgGCTrading_InitiateTradeRequest_t > msg( k_EMsgGCTrading_InitiateTradeRequest );
+ msg.Body().m_ulOtherSteamID = steamID.ConvertToUint64();
+ bool bSent = GCClientSystem()->BSendMessage( msg );
+ if ( bSent )
+ {
+ iTradeRequests++;
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_SENT, msg.Body().m_ulOtherSteamID, iTradeRequests );
+#endif
+
+ const char* pPlayerName = InventoryManager()->PersonaName_Get( steamID.GetAccountID() );
+ if ( pPlayerName != NULL && FStrEq( pPlayerName, "" ) == false )
+ {
+ wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH];
+ g_pVGuiLocalize->ConvertANSIToUnicode( pPlayerName, wszPlayerName, sizeof(wszPlayerName) );
+ CTradingWaitDialog *pDialog = new CTradingWaitDialog( "#TF_Trading_TimeoutPartyB_Named", wszPlayerName );
+ ShowWaitingDialog( pDialog, "#TF_Trading_WaitingForPartyB", true, true, 30.0f );
+ wchar_t wszConstructedString[1024];
+ g_pVGuiLocalize->ConstructString_safe( wszConstructedString, g_pVGuiLocalize->Find( "#TF_Trading_WaitingForPartyB_Named" ), 1, wszPlayerName );
+ pDialog->SetDialogVariable( "updatetext", wszConstructedString );
+ }
+ else
+ {
+ CTradingWaitDialog *pDialog = new CTradingWaitDialog( "#TF_Trading_TimeoutPartyB" );
+ ShowWaitingDialog( pDialog, "#TF_Trading_WaitingForPartyB", true, true, 30.0f );
+ }
+ }
+}
+
+const char* UniverseToCommunityURL( EUniverse universe )
+{
+ switch( universe )
+ {
+ default: // return public if we don't have a better guess.
+ case k_EUniversePublic: return "https://steamcommunity.com";
+ case k_EUniverseBeta: return "https://beta.steamcommunity.com";
+ case k_EUniverseDev: return "https://localhost/community";
+ }
+
+ // Should never get here.
+ return UniverseToCommunityURL( k_EUniversePublic );
+}
+
+const char* GetCommunityURL()
+{
+ if ( GetUniverse() == k_EUniverseInvalid )
+ {
+ Assert( !"calling GetCommunityURL when not connected. This is allowed, but will return public universe." );
+ return UniverseToCommunityURL( k_EUniversePublic );
+ }
+
+ return UniverseToCommunityURL( GetUniverse() );
+}
+
+void Trading_SendGift( const CSteamID& steamID, const CEconItemView& giftItem )
+{
+ if ( !steamapicontext || !steamapicontext->SteamFriends() )
+ {
+ // TODO: Error dialog.
+ return;
+ }
+
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_ITEM_GIFTED, steamID.ConvertToUint64(), iGiftsGiven );
+#endif
+
+ // Build up the steam URL and send it over.
+ // Should look like this: https://steamcommunity.com/trade/1/sendgift/?appid=&contextid=&assetid=&steamid_target=
+
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage(
+ CFmtStrMax( "%s/trade/1/sendgift/?appid=%d&contextid=%d&assetid=%llu&steamid_target=%llu",
+ GetCommunityURL(),
+ engine->GetAppID(),
+ 2, // k_EEconContextBackpack
+ giftItem.GetItemID(),
+ steamID.ConvertToUint64()
+ )
+ );
+
+}
+
+CON_COMMAND( cl_trade, "Trade with a person by player name" )
+{
+ if ( args.ArgC() < 2 )
+ return;
+
+ if ( GetUniverse() == k_EUniverseInvalid )
+ return;
+
+ int iLocalPlayerIndex = GetLocalPlayerIndex();
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) )
+ {
+ player_info_t pi;
+ if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
+ continue;
+ if ( !pi.friendsID )
+ continue;
+
+ if ( FStrEq( pi.name, args[1] ) == false )
+ continue;
+
+ CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
+ Trading_RequestTrade( steamID );
+ return;
+ }
+ }
+}
+
+CON_COMMAND( cl_trade_steamid, "Trade with a person by steam id" )
+{
+ if ( args.ArgC() < 2 )
+ return;
+
+ if ( GetUniverse() == k_EUniverseInvalid )
+ return;
+
+ const char *pInput = args[1];
+ if ( pInput[0] >= '0' && pInput[0] <= '9' )
+ {
+ CSteamID steamID;
+ steamID.SetFromString( pInput, GetUniverse() );
+ if ( steamID.IsValid() )
+ Trading_RequestTrade( steamID );
+ }
+}
+
+#ifdef _DEBUG
+CON_COMMAND( cl_trading_test_self_trade, "Test self-trading" )
+{
+ Trading_RequestTrade( Trading_GetLocalPlayerSteamID() );
+ sbTestingSelfTrade = true;
+}
+#endif
diff --git a/game/client/econ/econ_trading.h b/game/client/econ/econ_trading.h
new file mode 100644
index 0000000..9557dd9
--- /dev/null
+++ b/game/client/econ/econ_trading.h
@@ -0,0 +1,40 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Interface for the client to interact with the CTradingSession
+//
+// $NoKeywords: $
+//=============================================================================
+
+#ifndef TF_TRADING_H
+#define TF_TRADING_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+class CEconItemView;
+
+/**
+ * @return CSteamID of the client
+ */
+CSteamID Trading_GetLocalPlayerSteamID();
+
+/**
+ * Request a trade session with the player by player index (i.e. in the same game)
+ * @param iPlayerIdx
+ */
+void Trading_RequestTrade( int iPlayerIdx );
+
+/**
+ * Request a trade session with the player by CSteamID
+ * @param steamID
+ */
+void Trading_RequestTrade( const CSteamID &steamID );
+
+/**
+ * Sends a gift to the player with the given steamID
+ * @param steamID
+ * @param giftItem
+ */
+void Trading_SendGift( const CSteamID &steamID, const CEconItemView& giftItem );
+
+#endif // TF_TRADING_H
diff --git a/game/client/econ/econ_ui.h b/game/client/econ/econ_ui.h
new file mode 100644
index 0000000..99c86e0
--- /dev/null
+++ b/game/client/econ/econ_ui.h
@@ -0,0 +1,165 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ECON_UI_H
+#define ECON_UI_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+enum EconBaseUIPanels_t
+{
+ ECONUI_BASEUI = 0,
+ ECONUI_BACKPACK,
+ ECONUI_CRAFTING,
+ ECONUI_ARMORY,
+ ECONUI_TRADING,
+ ECONUI_LOADOUT,
+
+ ECONUI_FIRST_PANEL = ECONUI_BASEUI,
+ ECONUI_LAST_PANEL = ECONUI_LOADOUT
+};
+
+class CBackpackPanel;
+class CCraftingPanel;
+class CItemPickupPanel;
+class CItemDiscardPanel;
+class CStorePanel;
+struct cart_item_t;
+namespace vgui
+{
+ class Panel;
+};
+
+// Interface used to connect to the game specific implementations of the Economy UI
+abstract_class IEconRootUI
+{
+public:
+ // Open the EconUI, optionally to a specific page (Backpack/Crafting/etc)
+ // If bCheckForInventorySpaceOnExit is set, On closing the EconUI should make sure the user doesn't
+ // have to throw out any items to make room in their inventory.
+ virtual IEconRootUI *OpenEconUI( int iDirectToPage = 0, bool bCheckForInventorySpaceOnExit = false ) = 0;
+
+ // Close the EconUI, and any associated sub panels.
+ virtual void CloseEconUI( void ) = 0;
+
+ // Return true if the specified EconUI sub panel is currently visible.
+ virtual bool IsUIPanelVisible( EconBaseUIPanels_t iPanel ) = 0;
+
+ // Some part of the EconUI might be in a state where they want to prevent the user
+ // from being able to close the EconUI (in the middle of a trade, for instance)
+ virtual void SetPreventClosure( bool bPrevent ) = 0;
+
+ // Sub panel access.
+ // These are panels that are parented to the root EconUI.
+ virtual CBackpackPanel *GetBackpackPanel( void ) = 0;
+ virtual CCraftingPanel *GetCraftingPanel( void ) = 0;
+
+ // Gamestats access (We should replace these with an Econ Gamestats)
+ virtual void Gamestats_ItemTransaction( int eventID, CEconItemView *item, const char *pszReason = NULL, int iQuality = 0 ) = 0;
+ virtual void Gamestats_Store( int eventID, CEconItemView* item=NULL, const char* panelName=NULL,
+ int classId=0, const cart_item_t* in_cartItem=NULL, int in_checkoutAttempts=0, const char* storeError=NULL, int in_totalPrice=0, int in_currencyCode=0 ) = 0;
+ virtual void SetExperimentValue( uint64 experimentValue ) = 0;
+
+ // Open separate economy panels (they're not parented to the root EconUI)
+ // This is here so that games can customize the implementation of these panels.
+ virtual CItemPickupPanel *OpenItemPickupPanel( void ) = 0;
+ virtual CItemDiscardPanel *OpenItemDiscardPanel( void ) = 0;
+ // Store
+ virtual void CreateStorePanel( void ) = 0;
+ virtual CStorePanel *OpenStorePanel( int iItemDef, bool bAddToCart ) = 0;
+ virtual CStorePanel *GetStorePanel( void ) = 0;
+
+ // When the root UI is closed, send an "EconUIClosed" message to pListener.
+ virtual void AddPanelCloseListener( vgui::Panel *pListener ) = 0;
+
+ // The panel at which we want back to actually close the UI - defaults to the root panel - a negative value can be passed in for class loadout panels
+ virtual void SetClosePanel( int iPanel ) = 0;
+
+ // Call this to set which team the class loadout should display
+ virtual void SetDefaultTeam( int iTeam ) = 0;
+};
+
+extern IEconRootUI *EconUI( void );
+
+
+// IDs for Item related events in Gamestats tracking
+enum ITEMEVENTS
+{
+ // STORE EVENTS
+ IE_STORE_ENTERED,
+ IE_STORE_EXITED,
+ IE_STORE_TAB_CHANGED,
+ IE_STORE_ITEM_SELECTED,
+ IE_STORE_ITEM_PREVIEWED,
+ IE_STORE_ITEM_ADDED_TO_CART,
+ IE_STORE_ITEM_REMOVED_FROM_CART,
+ IE_STORE_CHECKOUT_ATTEMPT,
+ IE_STORE_CHECKOUT_FAILURE,
+ IE_STORE_CHECKOUT_SUCCESS,
+ IE_STORE_CHECKOUT_ITEM,
+
+ // LOADOUT EVENTS
+ IE_LOADOUT_ENTERED,
+ IE_LOADOUT_EXITED,
+
+ // TRADING EVENTS
+ IE_TRADING_ENTERED,
+ IE_TRADING_EXITED,
+ IE_TRADING_WENT_TO_ARMORY,
+ IE_TRADING_RETURNED_FROM_ARMORY,
+ IE_TRADING_REQUEST_SENT,
+ IE_TRADING_REQUEST_RECEIVED,
+ IE_TRADING_REQUEST_REJECTED,
+ IE_TRADING_REQUEST_ACCEPTED,
+ IE_TRADING_TRADE_NEGOTIATED,
+ IE_TRADING_TRADE_SUCCESS,
+ IE_TRADING_TRADE_FAILURE,
+ IE_TRADING_ITEM_GIVEN,
+ IE_TRADING_ITEM_RECEIVED,
+ IE_TRADING_ITEM_GIFTED,
+
+ // CRAFTING EVENTS
+ IE_CRAFTING_ENTERED,
+ IE_CRAFTING_EXITED,
+ IE_CRAFTING_WENT_TO_ARMORY,
+ IE_CRAFTING_RETURNED_FROM_ARMORY,
+ IE_CRAFTING_VIEW_BLUEPRINTS,
+ IE_CRAFTING_TIMEOUT,
+ IE_CRAFTING_FAILURE,
+ IE_CRAFTING_SUCCESS,
+ IE_CRAFTING_NO_RECIPE_MATCH,
+ IE_CRAFTING_ATTEMPT,
+ IE_CRAFTING_RECIPE_FOUND,
+
+ // ARMORY EVENTS
+ IE_ARMORY_ENTERED,
+ IE_ARMORY_EXITED,
+ IE_ARMORY_SELECT_ITEM,
+ IE_ARMORY_BROWSE_WIKI,
+ IE_ARMORY_CHANGE_FILTER,
+
+ // TRANSACTION EVENTS
+ IE_ITEM_RECEIVED,
+ IE_ITEM_DISCARDED,
+ IE_ITEM_DELETED,
+ IE_ITEM_USED_TOOL,
+ IE_ITEM_USED_CONSUMABLE,
+ IE_ITEM_REMOVED_ATTRIB,
+ IE_ITEM_CHANGED_STYLE,
+
+ // NEW STORE EVENTS
+ IE_STORE2_ENTERED, // This gets written *in addition* to IE_STORE_ENTERED
+
+ // THESE STORED AS INTEGERS IN THE DATABASE SO THESE ARE NEW
+ IE_ITEM_RESET_STRANGE_COUNTERS,
+ IE_ITEM_PUT_INTO_COLLECTION,
+
+ IE_COUNT,
+};
+
+#endif // ECON_UI_H
diff --git a/game/client/econ/iconrenderreceiver.h b/game/client/econ/iconrenderreceiver.h
new file mode 100644
index 0000000..d3911fc
--- /dev/null
+++ b/game/client/econ/iconrenderreceiver.h
@@ -0,0 +1,54 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Holds the result of an AsyncCreateTextureFromRenderTarget for VGUI. Don't reuse these.
+//
+//=============================================================================
+#ifndef ICON_RENDER_RECEIVER_H
+#define ICON_RENDER_RECEIVER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "materialsystem/itexture.h"
+
+class CIconRenderReceiver : public IAsyncTextureOperationReceiver
+{
+public:
+ CIconRenderReceiver() : m_pTex( NULL ) { }
+
+protected:
+ virtual ~CIconRenderReceiver() { SafeRelease( &m_pTex ); }
+
+public:
+ virtual int AddRef() OVERRIDE
+ {
+ return ++m_nReferenceCount;
+ }
+
+ virtual int Release() OVERRIDE
+ {
+ int retVal = --m_nReferenceCount;
+ if ( retVal == 0 )
+ delete this;
+ return retVal;
+ }
+
+ virtual void OnAsyncCreateComplete( ITexture* pTex, void *pRtSrc ) OVERRIDE
+ {
+ SafeAssign( &m_pTex, pTex );
+ }
+
+ virtual void OnAsyncFindComplete( ITexture* pTex, void * ) OVERRIDE { Assert( !"Should never be called." ); }
+ virtual void OnAsyncMapComplete( ITexture* pTex, void* pExtraArgs, void* pMemory, int pPitch ) OVERRIDE { Assert( !"Should never be called." ); }
+ virtual void OnAsyncReadbackBegin( ITexture* pDst, ITexture* pSrc, void* pExtraArgs ) OVERRIDE { Assert( !"Should never be called." ); }
+
+ virtual int GetRefCount() const OVERRIDE { return m_nReferenceCount; }
+
+ ITexture* GetTexture() const { return m_pTex; }
+
+private:
+ CInterlockedInt m_nReferenceCount;
+ ITexture* m_pTex;
+};
+
+#endif // ICON_RENDER_RECEIVER_H
diff --git a/game/client/econ/item_confirm_delete_dialog.cpp b/game/client/econ/item_confirm_delete_dialog.cpp
new file mode 100644
index 0000000..43bf81e
--- /dev/null
+++ b/game/client/econ/item_confirm_delete_dialog.cpp
@@ -0,0 +1,29 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "item_confirm_delete_dialog.h"
+#include <vgui/ILocalize.h>
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmDeleteItemDialog::CConfirmDeleteItemDialog( vgui::Panel *parent, bool bMultiItem ) : BaseClass(parent)
+{
+ m_bMultiItem = bMultiItem;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const wchar_t *CConfirmDeleteItemDialog::GetText()
+{
+ return m_bMultiItem ? g_pVGuiLocalize->Find("MultiDeleteItemConfirmText") : g_pVGuiLocalize->Find("DeleteItemConfirmText");
+}
diff --git a/game/client/econ/item_confirm_delete_dialog.h b/game/client/econ/item_confirm_delete_dialog.h
new file mode 100644
index 0000000..76e84b4
--- /dev/null
+++ b/game/client/econ/item_confirm_delete_dialog.h
@@ -0,0 +1,37 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ITEM_CONFIRM_DELETE_DIALOG_H
+#define ITEM_CONFIRM_DELETE_DIALOG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "confirm_delete_dialog.h"
+#include "vgui_controls/EditablePanel.h"
+#include "econ_controls.h"
+#include "item_pickup_panel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "GameEventListener.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CConfirmDeleteItemDialog : public CConfirmDeleteDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmDeleteItemDialog, CConfirmDeleteDialog );
+public:
+ CConfirmDeleteItemDialog( vgui::Panel *parent, bool bMultiItem = false );
+
+ void SetMultiItem( bool bMultiItem ) { m_bMultiItem = bMultiItem; }
+ virtual const wchar_t *GetText();
+
+private:
+ bool m_bMultiItem;
+};
+
+#endif // ITEM_CONFIRM_DELETE_DIALOG_H
diff --git a/game/client/econ/item_model_panel.cpp b/game/client/econ/item_model_panel.cpp
new file mode 100644
index 0000000..3cbe994
--- /dev/null
+++ b/game/client/econ/item_model_panel.cpp
@@ -0,0 +1,3933 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "vgui/IInput.h"
+#include <vgui/IVGui.h>
+#include <vgui/IScheme.h>
+#include "item_model_panel.h"
+#include "iclientmode.h"
+#include "baseviewport.h"
+#include "econ_entity.h"
+#include "gamestringpool.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui_controls/Label.h"
+#include "vgui_controls/Button.h"
+#include "econ_item_system.h"
+#include "ienginevgui.h"
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#include "renderparm.h"
+#include "vgui_controls/ScalableImagePanel.h"
+#include "engine/IEngineSound.h"
+#include "econ/tool_items/tool_items.h"
+#include "econ_item_description.h"
+#include "econ_item_tools.h"
+#include "tool_items/custom_texture_cache.h"
+#include "econ_dynamic_recipe.h"
+#include "materialsystem/imaterialvar.h"
+#include "materialsystem/itexturecompositor.h"
+#include "bone_setup.h"
+#include "animation.h"
+#include "iconrenderreceiver.h"
+
+#ifdef TF_CLIENT_DLL
+#include "tf_shareddefs.h"
+#include "tf_gamerules.h"
+#endif // TF_CLIENT_DLL
+#include "KeyValues.h"
+
+ConVar tf_time_loading_item_panels( "tf_time_loading_item_panels", "0.0005", FCVAR_ARCHIVE, "The time to spend per frame loading data for item panels" );
+#ifdef STAGING_ONLY
+ConVar tf_paint_kit_show_unique_icon( "tf_paint_kit_show_unique_icon", "1" );
+ConVar tf_test_loading_panels( "tf_test_loading_panels", "0" );
+ConVar tf_force_highres_item_image( "tf_force_highres_item_image", "0" );
+ConVar tf_unique_icon_perf_debug( "tf_unique_icon_perf_debug", "0" );
+#endif
+
+const char* g_ItemModelPanelRenderTargetNames[] =
+{
+ "_rt_ItemModelPanel0",
+ "_rt_ItemModelPanel1",
+ "_rt_ItemModelPanel2"
+};
+COMPILE_TIME_ASSERT( ITEM_MODEL_IMAGE_CACHE_SIZE == ARRAYSIZE( g_ItemModelPanelRenderTargetNames ) );
+
+CItemMaterialCustomizationIconPanel::CItemMaterialCustomizationIconPanel( vgui::Panel *pParent, const char *pName )
+ : BaseClass( pParent, pName )
+{
+ m_iPaintSplat = -1;
+}
+
+CItemMaterialCustomizationIconPanel::~CItemMaterialCustomizationIconPanel()
+{
+ if ( vgui::surface() )
+ {
+ if ( m_iPaintSplat != -1 )
+ {
+ vgui::surface()->DestroyTextureID( m_iPaintSplat );
+ m_iPaintSplat = -1;
+ }
+ }
+}
+
+// Custom painting
+void CItemMaterialCustomizationIconPanel::PaintBackground( void )
+{
+ // Draw custom texture, if we have one
+ if ( m_hUGCId != 0 )
+ {
+ // Request it from the cache, and get filename, if it's downloaded
+ // and ready
+ int iCustomTexture = GetCustomTextureGuiHandle( m_hUGCId );
+ if ( iCustomTexture != 0 )
+ {
+ surface()->DrawSetTexture( iCustomTexture );
+ DrawQuad( 0, 1 );
+ surface()->DrawSetColor(COLOR_WHITE);
+ }
+ }
+
+ for ( int i = 0; i < m_colPaintColors.Size(); i++ )
+ {
+ const Color& c = m_colPaintColors[i];
+
+ if ( m_iPaintSplat == -1 )
+ {
+ m_iPaintSplat = surface()->CreateNewTextureID();
+ surface()->DrawSetTextureFile( m_iPaintSplat, "vgui/backpack_jewel_paint_splatter", true, false);
+ }
+ surface()->DrawSetTexture( m_iPaintSplat );
+ surface()->DrawSetColor( c.r(), c.g(), c.b(), GetAlpha() );
+ DrawQuad( i, m_colPaintColors.Size() );
+ surface()->DrawSetColor(COLOR_WHITE);
+ }
+
+ // Clean up
+ vgui::surface()->DrawSetTexture(0);
+}
+
+// Draw a quad that fills our extents
+void CItemMaterialCustomizationIconPanel::DrawQuad( int iSubtileIndex, int iSubtileCount )
+{
+ int iWide, iTall;
+ GetSize( iWide, iTall );
+
+ // All of this math is to accomplish the following: allow us to split our single "icon"
+ // into some number of equivalent columns. Then take each column and angle the divider so
+ // it goes from the left image to the right image:
+ //
+ // +-----+-----+ +------+----+
+ // | | | | / |
+ // | | | | | |
+ // | | | | / |
+ // +-----+-----+ +--- +------+
+ //
+ // ...because the angle is prettier than a straight vertical cut.
+ //
+ // My hope is that this code is so awful I'm never allowed to write UI code again.
+ float fXScale = 1.0f / (float)iSubtileCount,
+ fXOffsetL = (float)iSubtileIndex * fXScale,
+ fXOffsetR = (float)(iSubtileIndex + 1) * fXScale,
+ fXUpperLowerOffset = fXScale * 0.65f;
+
+ // We shift our coordinates on the top slightly to the right (by fXUpperLowerOffset) and on
+ // the bottom slightly to the left (also by fXUpperLowerOffset). The far left side can't move
+ // away from 0 and the far right side can't move away from 1, so the edge case handling makes
+ // this look uglier than it really is.
+ float fXUL = iSubtileIndex == 0 ? fXOffsetL : fXOffsetL + fXUpperLowerOffset,
+ fXUR = iSubtileIndex == iSubtileCount - 1 ? fXOffsetR : fXOffsetR + fXUpperLowerOffset,
+ fXBL = iSubtileIndex == 0 ? fXOffsetL : fXOffsetL - fXUpperLowerOffset,
+ fXBR = iSubtileIndex == iSubtileCount - 1 ? fXOffsetR : fXOffsetR - fXUpperLowerOffset;
+
+ Vector2D uv11( fXUL, 0.0f );
+ Vector2D uv21( fXUR, 0.0f );
+ Vector2D uv22( fXBR, 1.0f );
+ Vector2D uv12( fXBL, 1.0f );
+
+ vgui::Vertex_t verts[4];
+ verts[0].Init( Vector2D( iWide * fXUL, 0 ), uv11 );
+ verts[1].Init( Vector2D( iWide * fXUR, 0 ), uv21 );
+ verts[2].Init( Vector2D( iWide * fXBR, iTall ), uv22 );
+ verts[3].Init( Vector2D( iWide * fXBL, iTall ), uv12 );
+
+ vgui::surface()->DrawTexturedPolygon( 4, verts );
+}
+
+DECLARE_BUILD_FACTORY( CItemModelPanel );
+DECLARE_BUILD_FACTORY( CEmbeddedItemModelPanel );
+DECLARE_BUILD_FACTORY( CItemMaterialCustomizationIconPanel );
+
+item_model_cache_t g_ItemModelImageCache[ITEM_MODEL_IMAGE_CACHE_SIZE];
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CEmbeddedItemModelPanel::CEmbeddedItemModelPanel( vgui::Panel *pParent, const char *pName ) : BaseClass( pParent, pName )
+{
+ m_bUseItemRenderTarget = false;
+ m_bForceUseModel = false;
+ m_pItem = NULL;
+ m_pszToolTargetItemImage = NULL;
+ m_iTextureID = -1;
+ m_iToolTargetItemTextureID = -1;
+ m_iOverlayTextureIDs.SetLessFunc( DefLessFunc(int) );
+ m_iOverlayTextureIDs.Purge();
+ m_bImageNotLoaded = false;
+ m_bGreyedOut = false;
+ m_bModelIsHidden = false;
+ m_bUseRenderTargetAsIcon = false;
+
+ m_bWeaponAllowInspect = false;
+ m_pCachedWeaponIcon = NULL;
+ m_pCachedWeaponMaterial = NULL;
+ m_iCachedTextureID = -1;
+
+ m_flModelRotateYawSpeed = 0;
+
+ m_bUsePedestal = false;
+ m_bOfflineIconGeneration = false;
+
+ m_pItemParticle = NULL;
+
+#ifdef STAGING_ONLY
+ m_flStartUpdateTime = 0.0;
+#endif // STAGING_ONLY
+}
+
+
+CEmbeddedItemModelPanel::~CEmbeddedItemModelPanel()
+{
+ CleanUpCachedWeaponIcon();
+
+ SafeDeleteParticleData( &m_pItemParticle );
+}
+
+
+void CEmbeddedItemModelPanel::CleanUpCachedWeaponIcon()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ SafeRelease( &m_pCachedWeaponIcon );
+ SafeRelease( &m_pCachedWeaponMaterial );
+
+ if ( m_iCachedTextureID != -1 )
+ {
+ surface()->DeleteTextureByID( m_iCachedTextureID );
+ m_iCachedTextureID = -1;
+ }
+
+ // If we match a cache here, clear it so we redraw once when we appear.
+ for ( int i = 0; i < ITEM_MODEL_IMAGE_CACHE_SIZE; i++ )
+ {
+ bool bMatch = g_ItemModelImageCache[i].m_hModelPanelLock.Get() == this;
+ if ( bMatch )
+ {
+ g_ItemModelImageCache[i].Clear();
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEmbeddedItemModelPanel::UpdateCameraForIcon()
+{
+ if ( m_iCameraAttachment == -1 )
+ return;
+
+ studiohdr_t *pItemStudioHdr = m_RootMDL.m_MDL.GetStudioHdr();
+ if ( pItemStudioHdr )
+ {
+ matrix3x4_t matBoneToWorld[MAXSTUDIOBONES];
+ m_RootMDL.m_MDL.SetUpBones( m_RootMDL.m_MDLToWorld, MAXSTUDIOBONES, matBoneToWorld );
+
+ // Get attachment transform
+ mstudioattachment_t attach = pItemStudioHdr->pAttachment( m_iCameraAttachment );
+ matrix3x4_t matLocalToWorld;
+ ConcatTransforms( matBoneToWorld[ attach.localbone ], attach.local, matLocalToWorld );
+
+ QAngle angCameraAngles;
+ Vector vecCameraPos;
+ MatrixAngles( matLocalToWorld, angCameraAngles, vecCameraPos );
+ SetCameraOffset( vec3_origin );
+ SetCameraPositionAndAngles( vecCameraPos, angCameraAngles );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEmbeddedItemModelPanel::SetItem( CEconItemView *pItem )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ m_iTextureID = -1;
+ m_iToolTargetItemTextureID = -1;
+ m_iOverlayTextureIDs.Purge();
+ CleanUpCachedWeaponIcon();
+ SafeDeleteParticleData( &m_pItemParticle );
+
+ // reset all models
+ SetMDL( MDLHANDLE_INVALID );
+ m_ItemModel.m_bDisabled = true;
+ m_ItemModel.m_MDL.SetMDL( MDLHANDLE_INVALID );
+ m_StatTrackModel.m_bDisabled = true;
+ m_StatTrackModel.m_MDL.SetMDL( MDLHANDLE_INVALID );
+
+ m_AttachedModels.Purge();
+
+ m_iCameraAttachment = -1;
+
+ m_pItem = pItem;
+
+ if ( !m_pItem )
+ return;
+
+ const char* pszInventoryImage = m_pItem->IsValid() ? m_pItem->GetInventoryImage() : NULL;
+ if ( ( pszInventoryImage && pszInventoryImage[0] && !g_pMaterialSystem->IsMaterialLoaded( pszInventoryImage ) )
+#ifdef STAGING_ONLY
+ || tf_test_loading_panels.GetBool()
+#endif
+ )
+ {
+ m_bImageNotLoaded = true;
+ }
+
+ if ( !m_pItem->IsValid() )
+ return;
+
+ float flValue;
+ static CSchemaAttributeDefHandle pAttrib_ToolTarget( "tool target item" );
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( m_pItem, pAttrib_ToolTarget, &flValue ) )
+ {
+ const CEconItemDefinition *pTargetDef = GetItemSchema()->GetItemDefinition( flValue );
+
+ m_pszToolTargetItemImage = pTargetDef->GetInventoryImage();
+ }
+ else
+ {
+ m_pszToolTargetItemImage = NULL;
+ }
+
+#ifdef STAGING_ONLY
+ if ( tf_paint_kit_show_unique_icon.GetBool() )
+#endif // STAGING_ONLY
+ {
+ float flInspect = 0;
+ static CSchemaAttributeDefHandle pAttrib_WeaponAllowInspect( "weapon_allow_inspect" );
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( m_pItem, pAttrib_WeaponAllowInspect, &flInspect ) )
+ {
+ m_bWeaponAllowInspect = flInspect != 0;
+
+#ifdef STAGING_ONLY
+ if ( m_flStartUpdateTime == 0 )
+ m_flStartUpdateTime = Plat_FloatTime();
+#endif // STAGING_ONLY
+ }
+ else
+ {
+ m_bWeaponAllowInspect = false;
+
+#ifdef STAGING_ONLY
+ m_flStartUpdateTime = 0.0;
+#endif // STAGING_ONLY
+ }
+ }
+
+ float flUseCacheIcon = 0.f;
+ static CSchemaAttributeDefHandle pAttrib_UseModelCacheIcon( "use_model_cache_icon" );
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( m_pItem, pAttrib_UseModelCacheIcon, &flUseCacheIcon ) && flUseCacheIcon != 0.f )
+ {
+ m_bUseRenderTargetAsIcon = true;
+ }
+ else
+ {
+ m_bUseRenderTargetAsIcon = false;
+ }
+
+ if ( !m_bModelIsHidden )
+ {
+ if ( !m_pItem->GetInventoryImage() || IsForcingModelUsage() || m_bWeaponAllowInspect || UseRenderTargetAsIcon() )
+ {
+ const char *pszModelName = m_pItem->GetPlayerDisplayModel( 0, 0 );
+ if ( pszModelName )
+ {
+ CMDL *pMDL = NULL;
+#ifndef PORTAL2 // DOTA COME BACK
+ if ( m_bUsePedestal )
+ {
+ MDLHandle_t hPedestalMDL = mdlcache->FindMDL( "models/weapons/pedestal/pedestal.mdl" );
+ SetMDL( hPedestalMDL, NULL );
+ mdlcache->Release( hPedestalMDL ); // counterbalance addref from within FindMDL
+
+ MDLHandle_t hItemMDL = mdlcache->FindMDL( pszModelName );
+ if ( mdlcache->IsErrorModel( hItemMDL ) )
+ {
+ hItemMDL = MDLHANDLE_INVALID;
+ }
+ m_ItemModel.m_MDL.SetMDL( hItemMDL );
+ mdlcache->Release( hItemMDL ); // counterbalance addref from within FindMDL
+
+ pMDL = &m_ItemModel.m_MDL;
+ }
+ else
+ {
+ MDLHandle_t hMDL = mdlcache->FindMDL( pszModelName );
+ SetMDL( hMDL, static_cast<IClientRenderable*>( m_pItem ) );
+ mdlcache->Release( hMDL ); // counterbalance addref from within FindMDL
+
+ pMDL = &m_RootMDL.m_MDL;
+ }
+#endif
+
+ if ( pMDL )
+ {
+ studiohdr_t *pItemStudioHdr = pMDL->GetStudioHdr();
+ if ( pItemStudioHdr )
+ {
+ // Get the appropriate attachment
+ CStudioHdr HDR( pItemStudioHdr, g_pMDLCache );
+ if ( m_bUsePedestal )
+ {
+ m_iPedestalAttachment = Studio_FindAttachment( &HDR, "pedestal_0" );
+ if ( m_iPedestalAttachment != -1 )
+ {
+ m_ItemModel.m_MDL.m_pProxyData = static_cast<IClientRenderable*>(m_pItem);
+ m_ItemModel.m_bDisabled = false;
+ m_ItemModel.m_MDL.m_nSequence = ACT_IDLE;
+ SetIdentityMatrix( m_ItemModel.m_MDLToWorld );
+ }
+ }
+ else
+ {
+ m_iCameraAttachment = Studio_FindAttachment( &HDR, "icon_camera" );
+ UpdateCameraForIcon();
+ }
+
+ // should we override this model bodygroup
+ const CEconStyleInfo *pStyle = m_pItem->GetItemDefinition()->GetStyleInfo( m_pItem->GetStyle() );
+ if ( pStyle && pStyle->GetBodygroupName() != NULL )
+ {
+ int iBodyGroup = ::FindBodygroupByName( &HDR, pStyle->GetBodygroupName() );
+ if ( iBodyGroup != -1 )
+ {
+ ::SetBodygroup( &HDR, pMDL->m_nBody, iBodyGroup, pStyle->GetBodygroupSubmodelIndex() );
+ }
+ }
+ }
+ }
+
+ // Attach Models
+ // Attach the models for the item
+ {
+ int iTeam = m_pItem->GetItemDefinition()->GetBestVisualTeamData( m_pItem->GetTeamNumber() );
+ {
+ // Set attached models if viewable third-person.
+ const int iNumAttachedModels = m_pItem->GetItemDefinition()->GetNumAttachedModels( iTeam );
+ for ( int i = 0; i < iNumAttachedModels; ++i )
+ {
+ attachedmodel_t *pModel = m_pItem->GetItemDefinition()->GetAttachedModelData( iTeam, i );
+ LoadAttachedModel( pModel );
+ }
+ }
+
+ // Festive
+ static CSchemaAttributeDefHandle pAttr_is_festivized( "is_festivized" );
+ if ( pAttr_is_festivized && m_pItem->FindAttribute( pAttr_is_festivized ) )
+ {
+ const int iNumAttachedModels = m_pItem->GetItemDefinition()->GetNumAttachedModelsFestivized( iTeam );
+ for ( int i = 0; i < iNumAttachedModels; ++i )
+ {
+ attachedmodel_t *pModel = m_pItem->GetItemDefinition()->GetAttachedModelDataFestivized( iTeam, i );
+ LoadAttachedModel( pModel );
+ }
+ }
+ }
+
+ // Stattrak
+ CAttribute_String attrModule;
+ static CSchemaAttributeDefHandle pAttr_module( "weapon_uses_stattrak_module" );
+ if ( m_pItem->FindAttribute( pAttr_module, &attrModule ) && attrModule.has_value() )
+ {
+ // Allow for already strange items
+ bool bIsStrange = false;
+ if ( m_pItem->GetQuality() == AE_STRANGE )
+ {
+ bIsStrange = true;
+ }
+
+ if ( !bIsStrange )
+ {
+ // Go over the attributes of the item, if it has any strange attributes the item is strange and don't apply
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ if ( m_pItem->FindAttribute( GetKillEaterAttr_Score( i ) ) )
+ {
+ bIsStrange = true;
+ break;
+ }
+ }
+ }
+
+ if ( bIsStrange )
+ {
+ static CSchemaAttributeDefHandle pAttr_moduleScale( "weapon_stattrak_module_scale" );
+ // Does it have a stat track module
+ m_flStatTrackScale = 1.0f;
+ uint32 unFloatAsUint32 = 1;
+ if ( m_pItem->FindAttribute( pAttr_moduleScale, &unFloatAsUint32 ) )
+ {
+ m_flStatTrackScale = (float&)unFloatAsUint32;
+ }
+
+ MDLHandle_t hStatTrackMDL = mdlcache->FindMDL( "models/weapons/c_models/stattrack.mdl" );
+ if ( mdlcache->IsErrorModel( hStatTrackMDL ) )
+ {
+ hStatTrackMDL = MDLHANDLE_INVALID;
+ }
+ m_StatTrackModel.m_MDL.SetMDL( hStatTrackMDL );
+ mdlcache->Release( hStatTrackMDL ); // counterbalance addref from within FindMDL
+
+ m_StatTrackModel.m_MDL.m_pProxyData = static_cast<IClientRenderable*>(pItem);
+ m_StatTrackModel.m_bDisabled = false;
+ m_StatTrackModel.m_MDL.m_nSequence = ACT_IDLE;
+ SetIdentityMatrix( m_StatTrackModel.m_MDLToWorld );
+ }
+ }
+
+ int iTeam = GetLocalPlayerTeam(),
+ iSkin = iTeam;
+
+#ifdef TF_CLIENT_DLL
+ // If we aren't in a game we default to previewing the red team skin.
+ if ( iTeam == TEAM_UNASSIGNED )
+ {
+ iTeam = TF_TEAM_RED;
+ }
+#endif // TF_CLIENT_DLL
+
+ if ( iSkin != TEAM_UNASSIGNED )
+ {
+ // Use the first skin for the first team, and the second skin for the other (but default to 0)
+ iSkin = (iSkin == (FIRST_GAME_TEAM+1)) ? 1 : 0;
+ }
+
+ // Handle styles/visuals overriding the skin.
+ int iOverrideSkin = m_pItem->GetSkin( iTeam );
+ if ( iOverrideSkin != -1 )
+ {
+ iSkin = iOverrideSkin;
+ }
+
+ SetSkin( iSkin );
+
+ if ( m_bUsePedestal )
+ {
+ m_ItemModel.m_MDL.m_nSkin = iSkin;
+ }
+ }
+ }
+ }
+}
+
+void CEmbeddedItemModelPanel::LoadAttachedModel( attachedmodel_t *pModel )
+{
+ if ( !( pModel->m_iModelDisplayFlags & kAttachedModelDisplayFlag_WorldModel ) )
+ return;
+
+ if ( !pModel->m_pszModelName )
+ {
+ Warning( "econ item definition '%s' attachment has no model\n", m_pItem->GetItemDefinition()->GetDefinitionName() );
+ return;
+ }
+
+ int iIndex = m_AttachedModels.AddToTail();
+ MDLHandle_t hMDL = mdlcache->FindMDL( pModel->m_pszModelName );
+ if ( mdlcache->IsErrorModel( hMDL ) )
+ {
+ hMDL = MDLHANDLE_INVALID;
+ }
+ m_AttachedModels[iIndex].m_MDL.SetMDL( hMDL );
+ mdlcache->Release( hMDL ); // counterbalance addref from within FindMDL
+
+ m_AttachedModels[iIndex].m_MDL.m_pProxyData = static_cast<IClientRenderable*>( m_pItem );
+ m_AttachedModels[iIndex].m_bDisabled = false;
+ m_AttachedModels[iIndex].m_MDL.m_nSequence = ACT_IDLE;
+ SetIdentityMatrix( m_AttachedModels[iIndex].m_MDLToWorld );
+}
+
+bool CEmbeddedItemModelPanel::IsLoadingWeaponSkin( void ) const
+{
+ static ConVarRef mat_dxlevel( "mat_dxlevel" );
+ if ( mat_dxlevel.GetInt() < 90 )
+ return false;
+
+ if ( m_bForceUseModel )
+ return false;
+
+ if ( m_pItem && m_pItem->IsValid() )
+ {
+ if ( m_bWeaponAllowInspect && m_pItem->GetCustomPainkKitDefinition() )
+ {
+ return m_pItem->GetWeaponSkinBaseCompositor() != NULL || !m_pCachedWeaponIcon || !m_pCachedWeaponIcon->GetTexture();
+ }
+ else if ( UseRenderTargetAsIcon() )
+ {
+ return !m_pCachedWeaponIcon || !m_pCachedWeaponIcon->GetTexture();
+ }
+ }
+
+ return false;
+}
+
+
+bool CEmbeddedItemModelPanel::IsImageNotLoaded( void ) const
+{
+ if ( m_bForceUseModel )
+ return false;
+
+ if ( m_bImageNotLoaded && m_pItem && m_pItem->IsValid() )
+ return true;
+
+ return false;
+}
+
+
+IMaterial* GetMaterialForImage( CEmbeddedItemModelPanel::InventoryImageType_t eImageType, const char* pszBaseName )
+{
+ IMaterial *pMaterial = NULL;
+
+ Assert( pszBaseName );
+ if ( !pszBaseName )
+ return NULL;
+
+#ifdef STAGING_ONLY
+ if ( eImageType == CEmbeddedItemModelPanel::IMAGETYPE_SMALL && tf_force_highres_item_image.GetBool() )
+ {
+ eImageType = CEmbeddedItemModelPanel::IMAGETYPE_LARGE;
+ }
+#endif // STAGING_ONLY
+
+ switch ( eImageType )
+ {
+ case CEmbeddedItemModelPanel::IMAGETYPE_SMALL:
+ pMaterial = g_pMaterialSystem->FindMaterial( pszBaseName, TEXTURE_GROUP_VGUI );
+ break;
+ case CEmbeddedItemModelPanel::IMAGETYPE_DETAILED:
+ pMaterial = g_pMaterialSystem->FindMaterial( CFmtStr("%s_detail",pszBaseName).Access(), TEXTURE_GROUP_VGUI, false );
+ break;
+ case CEmbeddedItemModelPanel::IMAGETYPE_LARGE:
+ pMaterial = g_pMaterialSystem->FindMaterial( CFmtStr("%s_large",pszBaseName).Access(), TEXTURE_GROUP_VGUI );
+ break;
+ default:
+ Assert(0);
+ }
+
+ Assert( pMaterial && !IsErrorMaterial( pMaterial ) );
+
+ return pMaterial;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEmbeddedItemModelPanel::LoadInventoryImage()
+{
+ InventoryImageType_t type = (InventoryImageType_t)m_iInventoryImageType;
+ if ( m_iInventoryImageType == IMAGETYPE_DETAILED && !m_pItem->GetStaticData()->HasDetailedIcon() )
+ {
+ type = IMAGETYPE_LARGE;
+ }
+ GetMaterialForImage( type, m_pItem->GetInventoryImage() );
+ m_bImageNotLoaded = false;
+ m_iTextureID = -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEmbeddedItemModelPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ CleanUpCachedWeaponIcon();
+
+ // Nive the "player pos" to the defined distance
+ if ( m_pItem && m_pItem->IsValid() )
+ {
+ if ( m_bUsePedestal )
+ {
+ Vector vecOffset = m_BMPResData.m_vecOriginOffset;
+ vecOffset.x = m_pItem->GetItemDefinition()->GetInspectPanelDistance();
+ // reset model angle and pos to initial values
+ SetModelAnglesAndPosition( m_BMPResData.m_angModelPoseRot, vecOffset );
+ }
+ else if ( m_iCameraAttachment != -1 )
+ {
+ UpdateCameraForIcon();
+ }
+ }
+}
+
+#ifdef STAGING_ONLY
+static double s_min_time = FLT_MAX;
+static double s_max_time = 0.f;
+static double s_total_time = 0.f;
+#endif // STAGING_ONLY
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEmbeddedItemModelPanel::Paint( void )
+{
+ if ( !m_pItem || !m_pItem->IsValid() )
+ return;
+
+ if ( m_bModelIsHidden )
+ {
+ BaseClass::Paint();
+ return;
+ }
+
+ // Don't even try to render backpack icon if we're not loaded
+ if ( m_bImageNotLoaded && !m_bWeaponAllowInspect && !UseRenderTargetAsIcon() )
+ return;
+
+ const char *pszInventoryImage = m_pItem->GetInventoryImage();
+
+ CMatRenderContextPtr pRenderContext( materials );
+
+ int iWidth = GetWide();
+ int iHeight = GetTall();
+ float flTexW = 1.0;
+ float flTexH = 1.0;
+ float flTexX = 0.0;
+ float flTexY = 0.0;
+ int x = 0;
+ int y = 0;
+
+ // First, try and use the inventory image instead of the model.
+ bool bIsLoadingWeaponSkin = IsLoadingWeaponSkin();
+
+ int iTexture = -1;
+ if ( !bIsLoadingWeaponSkin && !m_bForceUseModel )
+ {
+ // should we override material with cache texture
+ if ( m_pCachedWeaponIcon && m_pCachedWeaponIcon->GetTexture() )
+ {
+ // Clear out the composited texture--we're finished with it.
+ m_pItem->SetWeaponSkinBase( NULL );
+ // The compositor should have been cleaned up by the material proxy.
+ Assert( m_pItem->GetWeaponSkinBaseCompositor() == NULL );
+
+ if ( !m_pCachedWeaponMaterial && g_pMaterialSystem )
+ {
+ const char *pszTextureName = m_pCachedWeaponIcon->GetTexture()->GetName();
+ KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
+ pVMTKeyValues->SetString( "$basetexture", pszTextureName );
+ pVMTKeyValues->SetInt( "$translucent", 1 );
+ pVMTKeyValues->SetInt( "$vertexcolor", 1 );
+ IMaterial *pMaterial = g_pMaterialSystem->FindProceduralMaterial( pszTextureName, TEXTURE_GROUP_VGUI, pVMTKeyValues );
+ SafeAssign( &m_pCachedWeaponMaterial, pMaterial );
+
+ bool bFound = false;
+ IMaterialVar *pVar = m_pCachedWeaponMaterial->FindVar( "$basetexture", &bFound );
+ if ( bFound && pVar )
+ {
+ pVar->SetTextureValue( m_pCachedWeaponIcon->GetTexture() );
+ m_pCachedWeaponMaterial->RefreshPreservingMaterialVars();
+ }
+ }
+
+ if ( m_iCachedTextureID == -1 )
+ {
+ //m_pCachedWeaponIcon->GetTexture()->SaveToFile( CFmtStr( "%d_weapon_skin_cache.tga", m_pItem->GetItemDefIndex() ) );
+ m_iCachedTextureID = g_pMatSystemSurface->DrawGetTextureId( m_pCachedWeaponIcon->GetTexture() );
+ g_pMatSystemSurface->DrawSetTextureMaterial( m_iCachedTextureID, m_pCachedWeaponMaterial );
+ }
+ iTexture = m_iCachedTextureID;
+ x = 0;
+ y = 0;
+ flTexX = flTexY = 0.f;
+
+ int iMappingWidth = m_pCachedWeaponMaterial->GetMappingWidth();
+ int iMappingHeight = m_pCachedWeaponMaterial->GetMappingHeight();
+ if ( iWidth > iMappingWidth || iHeight > iMappingHeight )
+ {
+ flTexW = 1.f;
+ flTexH = 1.f;
+ }
+ else
+ {
+ flTexW = (float)iWidth / iMappingWidth;
+ flTexH = (float)iHeight / iMappingHeight;
+ }
+
+#ifdef STAGING_ONLY
+ if ( tf_unique_icon_perf_debug.GetBool() && m_flStartUpdateTime != 0 )
+ {
+ double flTimeTaken = Plat_FloatTime() - m_flStartUpdateTime;
+ m_flStartUpdateTime = 0.0;
+ s_min_time = MIN( s_min_time, flTimeTaken );
+ s_max_time = MAX( s_max_time, flTimeTaken );
+ s_total_time += flTimeTaken;
+ DevMsg( "took %.3f with min %.3f max %.3f with total %.3f\n", flTimeTaken, s_min_time, s_max_time, s_total_time );
+ }
+#endif // STAGING_ONLY
+ }
+ else if ( pszInventoryImage )
+ {
+ // Look up the material (use the large one if we've been told to)
+ IMaterial *pMaterial = GetMaterialForImage( (CEmbeddedItemModelPanel::InventoryImageType_t)m_iInventoryImageType, pszInventoryImage );
+
+ int iCenter[2];
+ iCenter[0] = x + (iWidth * 0.5);
+ iCenter[1] = y + (iHeight * 0.5);
+
+ // Maintain image aspect ratios. Fit to height.
+ int iPosition[2] = {0,0};
+ int iSize[2] = {0,0};
+ m_pItem->GetInventoryImageData( iPosition, iSize );
+
+ if ( m_bForceSquareImage )
+ {
+ iSize[0] = MAX( iSize[0], iSize[1] );
+ iSize[1] = iSize[0];
+ }
+ if ( !iSize[0] && !iSize[1] )
+ {
+ iSize[0] = pMaterial->GetMappingWidth();
+ iSize[1] = pMaterial->GetMappingHeight();
+ }
+ else
+ {
+ bool bForceHighRes = false;
+#ifdef STAGING_ONLY
+ bForceHighRes = tf_force_highres_item_image.GetBool();
+#endif // STAGING_ONLY
+ if ( m_iInventoryImageType != IMAGETYPE_SMALL || bForceHighRes )
+ {
+ // Normal is 128*128, large is 512x512
+ iSize[0] *= 4;
+ iSize[1] *= 4;
+ }
+
+ flTexW = ((float)iSize[0] / (float)pMaterial->GetMappingWidth());
+ flTexH = ((float)iSize[1] / (float)pMaterial->GetMappingHeight());
+ flTexX = ( 1.0 - flTexW ) * 0.5;
+ flTexY = ( 1.0 - flTexH ) * 0.5;
+ }
+ if ( iPosition[0] || iPosition[1] )
+ {
+ x += XRES(iPosition[0]);
+ y += YRES(iPosition[1]);
+ }
+
+ float flRatio = ((float)iSize[0] / (float)iSize[1]);
+ if ( flRatio != ((float)iWidth / (float)iHeight) )
+ {
+ // Fit to the height
+ int iCenterX = x + (iWidth * 0.5);
+ iWidth = iHeight * flRatio;
+ x = iCenterX - (iWidth * 0.5);
+ }
+
+ // Reload our texture, if we need to
+ if ( m_iTextureID == -1 )
+ {
+ m_iTextureID = vgui::surface()->DrawGetTextureId( pMaterial->GetName() );
+
+ // If we didn't find it, create a new one
+ if ( m_iTextureID == -1 )
+ {
+ m_iTextureID = vgui::surface()->CreateNewTextureID();
+ g_pMatSystemSurface->DrawSetTextureMaterial( m_iTextureID, pMaterial );
+ }
+ }
+
+ iTexture = m_iTextureID;
+ }
+ }
+
+ // draw texture if we have a valid texture
+ if ( iTexture != -1 )
+ {
+ surface()->DrawSetTexture( iTexture );
+
+ if ( m_bGreyedOut )
+ {
+ surface()->DrawSetColor( 96, 96, 96, 255 );
+ }
+ else
+ {
+ surface()->DrawSetColor( 255, 255, 255, 255 );
+ }
+ surface()->DrawTexturedSubRect( x, y, x + iWidth, y + iHeight, flTexX, flTexY, flTexX + flTexW, flTexY + flTexH );
+
+ // Draw the overlay image now, and tint it by the tint attribute (if we have one)
+ for ( int i=0; i<m_pItem->GetInventoryOverlayImageCount(); i++ )
+ {
+ const char *pszInventoryOverlayImage = m_pItem->GetInventoryOverlayImage( i );
+ IMaterial *pOverlayMaterial = GetMaterialForImage( (CEmbeddedItemModelPanel::InventoryImageType_t)m_iInventoryImageType, pszInventoryOverlayImage );
+
+ if ( !pOverlayMaterial )
+ continue;
+
+ int iTextureIDIdx = m_iOverlayTextureIDs.Find(i);
+ if ( (iTextureIDIdx == m_iOverlayTextureIDs.InvalidIndex()
+ || m_iOverlayTextureIDs[iTextureIDIdx] == -1 ) )
+ {
+ int iTextureID = vgui::surface()->DrawGetTextureId( pOverlayMaterial->GetName() );
+
+ // If we didn't find it, create a new one
+ if ( iTextureID == -1 )
+ {
+ iTextureID = vgui::surface()->CreateNewTextureID();
+ g_pMatSystemSurface->DrawSetTextureMaterial( iTextureID, pOverlayMaterial );
+ }
+
+ m_iOverlayTextureIDs.Insert( i, iTextureID );
+ }
+
+ surface()->DrawSetTexture( m_iOverlayTextureIDs[m_iOverlayTextureIDs.Find( i )] );
+
+ int iRGB = m_pItem->GetModifiedRGBValue( i == 0 );
+ Color col;
+ col.SetColor( clamp( (iRGB & 0xFF0000) >> 16, 0, 255 ), clamp( (iRGB & 0xFF00) >> 8, 0, 255 ), clamp( (iRGB & 0xFF), 0, 255 ), 255 );
+ // Dim this color if the item is currently greyed out
+ float flColorScale = m_bGreyedOut ? 96.f / 255.f : 1.f;
+ col.SetColor( col.r() * flColorScale, col.g() * flColorScale, col.b() * flColorScale, col.a() );
+
+ surface()->DrawSetColor( col );
+
+ surface()->DrawTexturedSubRect( x, y, x + iWidth, y + iHeight, flTexX, flTexY, flTexX + flTexW, flTexY + flTexH );
+ }
+
+ // Draw strangifier item on top of strangifier bottles
+ if ( m_pszToolTargetItemImage && m_pszToolTargetItemImage[0] )
+ {
+ IMaterial* pToolTargetItemMaterial = GetMaterialForImage( (CEmbeddedItemModelPanel::InventoryImageType_t)m_iInventoryImageType, m_pszToolTargetItemImage );
+
+ if ( m_iToolTargetItemTextureID == -1 )
+ {
+ m_iToolTargetItemTextureID = vgui::surface()->DrawGetTextureId( pToolTargetItemMaterial->GetName() );
+
+ // If we didn't find it, create a new one
+ if ( m_iToolTargetItemTextureID == -1 )
+ {
+ m_iToolTargetItemTextureID = vgui::surface()->CreateNewTextureID();
+ g_pMatSystemSurface->DrawSetTextureMaterial( m_iToolTargetItemTextureID, pToolTargetItemMaterial );
+ }
+
+ CAttribute_String attrToolTargetItemIconOffset;
+ static CSchemaAttributeDefHandle pAttrDef_ToolTargetItemIconOffset( "tool_target_item_icon_offset" );
+ if ( m_pItem->FindAttribute( pAttrDef_ToolTargetItemIconOffset, &attrToolTargetItemIconOffset ) && attrToolTargetItemIconOffset.has_value() )
+ {
+ UTIL_StringToVector( m_vecToolTargetItemImageOffset.Base(), attrToolTargetItemIconOffset.value().c_str() );
+ }
+ }
+
+ surface()->DrawSetTexture( m_iToolTargetItemTextureID );
+
+ int iStrangeX = x + ( iWidth * m_vecToolTargetItemImageOffset.x );
+ int iStrangeY = y + ( iHeight * m_vecToolTargetItemImageOffset.y );
+ float flScale = m_vecToolTargetItemImageOffset.z;
+
+ surface()->DrawTexturedSubRect( iStrangeX,
+ iStrangeY,
+ iStrangeX + (iWidth * flScale),
+ iStrangeY + (iHeight * flScale),
+ flTexX,
+ flTexY,
+ flTexX + (flTexW ),
+ flTexY + (flTexH ) );
+ }
+
+ return;
+ }
+
+ item_model_cache_t *pCacheRenderTarget = NULL;
+ const char *pszCacheRenderTargetName = NULL;
+ // find available render target
+ for ( int i=0; i<ITEM_MODEL_IMAGE_CACHE_SIZE; ++i )
+ {
+ CEmbeddedItemModelPanel *pLockPanel = g_ItemModelImageCache[i].m_hModelPanelLock.Get();
+
+ // found available render target?
+ if ( pLockPanel == NULL )
+ {
+ pszCacheRenderTargetName = m_bOfflineIconGeneration ? "offline_icon_generation" : g_ItemModelPanelRenderTargetNames[i];
+ pCacheRenderTarget = &g_ItemModelImageCache[i];
+ break;
+ }
+ else
+ {
+ // waiting for async copy to finish
+ if ( pLockPanel->m_pCachedWeaponIcon && pLockPanel->m_pCachedWeaponIcon->GetTexture() )
+ {
+ g_ItemModelImageCache[i].Clear();
+
+ pszCacheRenderTargetName = m_bOfflineIconGeneration ? "offline_icon_generation" : g_ItemModelPanelRenderTargetNames[i];
+ pCacheRenderTarget = &g_ItemModelImageCache[i];
+ break;
+ }
+ }
+ }
+
+ // can't find available cache render target, don't do anything
+ if ( !pszCacheRenderTargetName || !pCacheRenderTarget )
+ {
+ BaseClass::Paint();
+ return;
+ }
+
+ // Turn off depth-write to dest alpha so that we get white there instead. The code that uses
+ // the render target needs a mask of where stuff was rendered.
+ pRenderContext->SetIntRenderingParameter( INT_RENDERPARM_WRITE_DEPTH_TO_DESTALPHA, false );
+
+ bool bUseRenderTarget = !m_bForceUseModel && ( UseRenderTargetAsIcon() || bIsLoadingWeaponSkin );
+ bool bRenderToTexture = m_bRenderToTexture;
+ if ( bUseRenderTarget )
+ {
+ g_pMatSystemSurface->Set3DPaintTempRenderTarget( pszCacheRenderTargetName );
+ }
+ else if ( m_bUseParticle )
+ {
+ // we want to render particle with this model. don't render to texture
+ m_bRenderToTexture = false;
+ }
+
+ // make sure the weapon skin is ready before we render the model
+ bool bDrawWeaponWithSkin = bIsLoadingWeaponSkin && m_pCachedWeaponIcon == NULL && m_pItem->GetWeaponSkinBase();
+
+ m_pItem->SetWeaponSkinBaseCreateFlags( TEX_COMPOSITE_CREATE_FLAGS_NO_COMPRESSION | TEX_COMPOSITE_CREATE_FLAGS_NO_MIPMAPS );
+
+ BaseClass::Paint();
+
+ m_bRenderToTexture = bRenderToTexture;
+
+ // copy the rendered weapon skin from the render target
+ if ( !m_bForceUseModel && ( UseRenderTargetAsIcon() || bDrawWeaponWithSkin ) && !m_pCachedWeaponIcon )
+ {
+ char buffer[_MAX_PATH];
+ V_sprintf_safe( buffer, "proc/icon/item%d_id%lld_w%d_h%d", m_pItem->GetItemDefIndex(), m_pItem->GetID(), iWidth, iHeight );
+ SafeAssign( &m_pCachedWeaponIcon, new CIconRenderReceiver() );
+
+ // If the icon still exists in the material system, don't bother regenerating it.
+ if ( materials->IsTextureLoaded( buffer ) )
+ {
+ ITexture* resTexture = materials->FindTexture( buffer, TEXTURE_GROUP_RUNTIME_COMPOSITE, false, 0 );
+ if ( resTexture && resTexture->IsError() == false )
+ {
+ m_pCachedWeaponIcon->OnAsyncCreateComplete( resTexture, NULL );
+ }
+ }
+ else
+ {
+ // No icon available yet, need to create it.
+ ITexture *pRenderTarget = g_pMaterialSystem->FindTexture( pszCacheRenderTargetName, TEXTURE_GROUP_RENDER_TARGET );
+ if ( pRenderTarget )
+ {
+ pRenderContext->AsyncCreateTextureFromRenderTarget( pRenderTarget, buffer, IMAGE_FORMAT_RGBA8888, false, 0, m_pCachedWeaponIcon, NULL );
+
+ pCacheRenderTarget->iItemID = m_pItem->GetItemID();
+ pCacheRenderTarget->iItemDefinitionIndex = m_pItem->GetItemDefIndex();
+ pCacheRenderTarget->iWidth = iWidth;
+ pCacheRenderTarget->iHeight = iHeight;
+ pCacheRenderTarget->m_hModelPanelLock = this;
+ }
+ }
+ }
+
+ if ( bUseRenderTarget )
+ {
+ g_pMatSystemSurface->Reset3DPaintTempRenderTarget();
+ }
+
+ if ( m_flModelRotateYawSpeed != 0 )
+ {
+ m_angPlayer[YAW] += m_flModelRotateYawSpeed * gpGlobals->frametime;
+ SetModelAnglesAndPosition( m_angPlayer, m_vecPlayerPos );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+ITexture *CEmbeddedItemModelPanel::GetCachedGeneratedIcon()
+{
+ return m_pCachedWeaponIcon ? m_pCachedWeaponIcon->GetTexture() : NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CEmbeddedItemModelPanel::UpdateParticle(
+ IMatRenderContext *pRenderContext,
+ CStudioHdr *pStudioHdr,
+ MDLHandle_t mdlHandle,
+ matrix3x4_t *pWorldMatrix
+ )
+{
+ if ( !m_bUseParticle )
+ return false;
+
+ if ( m_pItemParticle && m_pItemParticle->m_bIsUpdateToDate )
+ return false;
+
+ if ( !m_pItem || !m_pItem->IsValid() )
+ return false;
+
+ attachedparticlesystem_t *pParticleSystem = NULL;
+
+ // do community_sparkle effect if this is a community item?
+ const int iQualityParticleType = m_pItem->GetQualityParticleType();
+ if ( iQualityParticleType > 0 )
+ {
+ pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iQualityParticleType );
+ }
+
+ if ( !pParticleSystem )
+ {
+ // does this hat even have a particle effect
+ static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
+ uint32 iValue = 0;
+ if ( !m_pItem->FindAttribute( pAttrDef_AttachParticleEffect, &iValue ) )
+ {
+ return false;
+ }
+
+ const float& value_as_float = (float&)iValue;
+ pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( value_as_float );
+ }
+
+ // failed to find any particle effect
+ if ( !pParticleSystem )
+ {
+ return false;
+ }
+
+ // Team Color
+ if ( m_pItem->GetTeamNumber() == TF_TEAM_BLUE && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_red" ))
+ {
+ static char pBlue[256];
+ V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
+ pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pBlue );
+ if ( !pParticleSystem )
+ {
+ return false;
+ }
+ }
+
+ // if this thing has a bip_head or prp_helmet (aka a hat)
+ int iBone = Studio_BoneIndexByName( pStudioHdr, "bip_head" );
+ if ( iBone < 0 )
+ {
+ iBone = Studio_BoneIndexByName( pStudioHdr, "prp_helmet" );
+ if ( iBone < 0 )
+ {
+ iBone = Studio_BoneIndexByName( pStudioHdr, "prp_hat" );
+ }
+ }
+
+ // default to root
+ if ( iBone < 0 )
+ {
+ iBone = 0;
+ }
+
+ // Get Use Head Origin
+ CUtlVector< int > vecAttachments;
+ static CSchemaAttributeDefHandle pAttrDef_UseHead( "particle effect use head origin" );
+ uint32 iUseHead = 0;
+ if ( !m_pItem->FindAttribute( pAttrDef_UseHead, &iUseHead ) || !iUseHead == 0 )
+ {
+ // not using head? try searching for attachment points
+ for ( int i=0; i<ARRAYSIZE( pParticleSystem->pszControlPoints ); ++i )
+ {
+ const char *pszAttachmentName = pParticleSystem->pszControlPoints[i];
+ if ( pszAttachmentName && pszAttachmentName[0] )
+ {
+ int iAttachment = Studio_FindAttachment( pStudioHdr, pszAttachmentName );
+ if ( iAttachment < 0 )
+ continue;
+
+ vecAttachments.AddToTail( iAttachment );
+ }
+ }
+ }
+
+ static char pszFullname[256];
+ const char* pszSystemName = pParticleSystem->pszSystemName;
+ // Weapon Remap for a Base Effect to be used on a specific weapon
+ if ( pParticleSystem->bUseSuffixName && m_pItem && m_pItem->GetItemDefinition()->GetParticleSuffix() )
+ {
+ V_strcpy_safe( pszFullname, pParticleSystem->pszSystemName );
+ V_strcat_safe( pszFullname, "_" );
+ V_strcat_safe( pszFullname, m_pItem->GetItemDefinition()->GetParticleSuffix() );
+ pszSystemName = pszFullname;
+ }
+
+ // Update the Particles and render them
+ if ( m_pItemParticle )
+ {
+ // Check if its a new particle system
+ if ( V_strcmp( m_pItemParticle->m_pParticleSystem->GetName(), pszSystemName ) )
+ {
+ SafeDeleteParticleData( &m_pItemParticle );
+ m_pItemParticle = CreateParticleData( pszSystemName );
+ }
+ }
+ else
+ {
+ // create
+ m_pItemParticle = CreateParticleData( pszSystemName );
+ }
+
+ // Particle system does not exist
+ if ( !m_pItemParticle )
+ return false;
+
+ // Get offset if it exists (and if we're using head offset)
+ static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" );
+ uint32 iOffset = 0;
+ Vector vecParticleOffset( 0, 0, 0 );
+ if ( iUseHead > 0 && m_pItem->FindAttribute( pAttrDef_VerticalOffset, &iOffset ) )
+ {
+ vecParticleOffset.z = (float&)iOffset;
+ }
+
+ m_pItemParticle->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, 0, vecParticleOffset );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CEmbeddedItemModelPanel::RenderStatTrack( CStudioHdr *pStudioHdr, matrix3x4_t *pWorldMatrix )
+{
+ // Draw the merge MDLs.
+ if ( !m_StatTrackModel.m_bDisabled )
+ {
+ matrix3x4_t matMergeBoneToWorld[MAXSTUDIOBONES];
+
+ // Get the merge studio header.
+ studiohdr_t *pStatTrackStudioHdr = m_StatTrackModel.m_MDL.GetStudioHdr();
+ matrix3x4_t *pMergeBoneToWorld = &matMergeBoneToWorld[0];
+
+ // If we have a valid mesh, bonemerge it. If we have an invalid mesh we can't bonemerge because
+ // it'll crash trying to pull data from the missing header.
+ if ( pStatTrackStudioHdr != NULL )
+ {
+ CStudioHdr mergeHdr( pStatTrackStudioHdr, g_pMDLCache );
+ m_StatTrackModel.m_MDL.SetupBonesWithBoneMerge( &mergeHdr, pMergeBoneToWorld, pStudioHdr, pWorldMatrix, m_StatTrackModel.m_MDLToWorld );
+ for ( int i=0; i<mergeHdr.numbones(); ++i )
+ {
+ MatrixScaleBy( m_flStatTrackScale, pMergeBoneToWorld[i] );
+ }
+ m_StatTrackModel.m_MDL.Draw( m_StatTrackModel.m_MDLToWorld, pMergeBoneToWorld );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool CEmbeddedItemModelPanel::RenderAttachedModels( CStudioHdr *pStudioHdr, matrix3x4_t *pWorldMatrix )
+{
+ // Draw the merge MDLs.
+ FOR_EACH_VEC( m_AttachedModels, iModel )
+ {
+ matrix3x4_t matMergeBoneToWorld[MAXSTUDIOBONES];
+
+ // Get the merge studio header.
+ studiohdr_t *pAttachedStudioHdr = m_AttachedModels[iModel].m_MDL.GetStudioHdr();
+ matrix3x4_t *pMergeBoneToWorld = &matMergeBoneToWorld[0];
+
+ // If we have a valid mesh, bonemerge it. If we have an invalid mesh we can't bonemerge because
+ // it'll crash trying to pull data from the missing header.
+ if ( pAttachedStudioHdr != NULL )
+ {
+ CStudioHdr mergeHdr( pAttachedStudioHdr, g_pMDLCache );
+ m_AttachedModels[iModel].m_MDL.SetupBonesWithBoneMerge( &mergeHdr, pMergeBoneToWorld, pStudioHdr, pWorldMatrix, m_AttachedModels[iModel].m_MDLToWorld );
+ m_AttachedModels[iModel].m_MDL.Draw( m_AttachedModels[iModel].m_MDLToWorld, pMergeBoneToWorld );
+ }
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEmbeddedItemModelPanel::RenderingRootModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix )
+{
+ // No model? Bail
+ if ( m_ItemModel.m_bDisabled )
+ {
+ // no model means not using pedestal. just use pStudioHdr to find the attachment points
+ UpdateParticle( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix );
+ RenderStatTrack( pStudioHdr, pWorldMatrix );
+ RenderAttachedModels( pStudioHdr, pWorldMatrix );
+ return;
+ }
+
+ studiohdr_t *pItemStudioHdr = m_ItemModel.m_MDL.GetStudioHdr();
+
+ if ( pItemStudioHdr != NULL )
+ {
+ matrix3x4_t matIdentity;
+ SetIdentityMatrix( matIdentity );
+
+ matrix3x4_t *pBoneToWorld = g_pStudioRender->LockBoneMatrices( pItemStudioHdr->numbones );
+ m_ItemModel.m_MDL.SetUpBones( matIdentity, pItemStudioHdr->numbones, pBoneToWorld );
+
+ // Get attachment transform
+ mstudioattachment_t attach = pItemStudioHdr->pAttachment( m_iPedestalAttachment );
+ matrix3x4_t matLocalToWorld;
+ matrix3x4_t matWorldToLocal;
+ matrix3x4_t matTransform;
+
+ ConcatTransforms( pBoneToWorld[ attach.localbone ], attach.local, matLocalToWorld );
+ MatrixInvert( matLocalToWorld, matWorldToLocal );
+ ConcatTransforms( m_RootMDL.m_MDLToWorld, matWorldToLocal, matTransform );
+
+ m_ItemModel.m_MDL.SetUpBones( matTransform, pItemStudioHdr->numbones, pBoneToWorld );
+
+ g_pStudioRender->UnlockBoneMatrices();
+
+ IMaterial* pOverrideMaterial = GetOverrideMaterial( m_ItemModel.m_MDL.GetMDL() );
+ if ( pOverrideMaterial != NULL )
+ g_pStudioRender->ForcedMaterialOverride( pOverrideMaterial );
+
+ m_ItemModel.m_MDL.Draw( m_ItemModel.m_MDLToWorld, pBoneToWorld );
+
+ if ( pOverrideMaterial != NULL )
+ g_pStudioRender->ForcedMaterialOverride( NULL );
+
+ CStudioHdr HDR( pItemStudioHdr, g_pMDLCache );
+
+ // update particle with the actual item model pItemStudioHdr
+ UpdateParticle( pRenderContext, &HDR, mdlHandle, pBoneToWorld );
+ RenderStatTrack( &HDR, pBoneToWorld );
+ RenderAttachedModels( &HDR, pBoneToWorld );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+IMaterial *CEmbeddedItemModelPanel::GetOverrideMaterial( MDLHandle_t mdlHandle )
+{
+ // This matches the check in RenderingRootModel, if we're not on a pedestal
+ // then we expect mdlHandle to not match m_ItemModel and that's fine--we should
+ // just get the override from the m_pItem
+ if ( !m_ItemModel.m_bDisabled && m_ItemModel.m_MDL.GetMDL() != mdlHandle )
+ return NULL;
+
+ if ( !m_pItem )
+ return NULL;
+
+ int iTeam = GetLocalPlayerTeam();
+
+#ifdef TF_CLIENT_DLL
+ // If we aren't in a game we default to previewing the red team skin.
+ if ( iTeam == TEAM_UNASSIGNED )
+ {
+ iTeam = TF_TEAM_RED;
+ }
+#endif
+
+ return m_pItem->GetMaterialOverride( iTeam );
+}
+
+
+float CItemModelPanel::sm_flLoadingTimeThisFrame = 0.0f;
+int CItemModelPanel::sm_nCurrentDecriptionUpdateFrame = 0;
+CItemModelPanel::eLoadingType_t CItemModelPanel::se_CurrentLoadingTask = LOADING_ICONS;
+int CItemModelPanel::sai_NumLoadingRequests[NUM_LOADING_TYPES] = {0,0,0};
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemModelPanel::CItemModelPanel( vgui::Panel *parent, const char *name ) : vgui::EditablePanel( parent, name )
+{
+ m_pModelPanel = NULL;
+ m_pItemNameLabel = NULL;
+ m_pPaintIcon = NULL;
+ m_pTF2Icon = NULL;
+ m_pItemAttribLabel = NULL;
+ m_pItemCollectionNameLabel = NULL;
+ m_pItemCollectionListLabel = NULL;
+ m_pItemCollectionHighlight = NULL;
+ m_pItemEquippedLabel = NULL;
+ m_pItemQuantityLabel = NULL;
+ m_pVisionRestrictionImage = NULL;
+ m_pIsStrangeImage = NULL;
+ m_pIsUnusualImage = NULL;
+ m_pIsLoanerImage = NULL;
+ m_pSeriesLabel = NULL;
+ m_pMainContentContainer = NULL;
+ m_pLoadingSpinner = NULL;
+// m_ItemData = NULL;
+ m_nCollectionItemLoaded = LOADED_COLLECTION_NONE;
+ m_pFontNameSmallest = vgui::INVALID_FONT;
+ m_pFontNameSmall = vgui::INVALID_FONT;
+ m_pFontNameLarge = vgui::INVALID_FONT;
+ m_pFontAttribSmallest = vgui::INVALID_FONT;
+ m_pFontAttribSmall = vgui::INVALID_FONT;
+ m_pFontAttribLarge = vgui::INVALID_FONT;
+
+ m_pszNoItemText = NULL;
+ m_pwcNoItemText = NULL;
+ m_pwcNoItemAttrib = NULL;
+ REGISTER_COLOR_AS_OVERRIDABLE( m_NoItemTextColor, "noitem_textcolor" );
+
+ m_bClickable = false;
+ m_bMouseOver = false;
+ m_bSelected = false;
+ m_bShowEquipped = false;
+ m_bForceShowEquipped = false;
+ m_bShowQuantity = false;
+ m_pszGreyedOutReason = NULL;
+ m_bShowGreyedOutTooltip = false;
+ m_bShouldSendPanelEnterExits = false;
+ m_bContainedItem = false;
+ m_bShowOthersGiftWrappedItems = false;
+ m_bDescriptionDirty = false;
+ m_nRecipeMatchingIndex = 0;
+
+ m_pContainedItemPanel = NULL;
+
+ m_bFakeButton = false;
+
+ m_mapMatchingAttributes.SetLessFunc( DefLessFunc( attrib_definition_index_t ) );
+
+ SetActAsButton( false, false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemModelPanel::~CItemModelPanel( void )
+{
+ CleanupNoItemWChars();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+ // These pointers must be zeroed here because their memory may be freed
+ // by LoadControlSettings *and* if they remain non-zero they may be dereferenced
+ // before LoadControlSettings returns. This causes reliable crashes if you
+ // go to the store, shop, change resolutions, then return to the store -- and
+ // if you use pageheap/AppVerifier.
+ // This set of pointers is simply the set of pointers that are initialized after
+ // LoadControlSettings.
+ m_pModelPanel = NULL;
+ m_pItemNameLabel = NULL;
+ m_pItemAttribLabel = NULL;
+ m_pItemCollectionNameLabel = NULL;
+ m_pItemCollectionListLabel = NULL;
+ m_pItemEquippedLabel = NULL;
+ m_pItemQuantityLabel = NULL;
+ m_pVisionRestrictionImage = NULL;
+ m_pIsStrangeImage = NULL;
+ m_pIsUnusualImage = NULL;
+ m_pIsLoanerImage = NULL;
+ m_pSeriesLabel = NULL;
+ m_pMatchesLabel = NULL;
+ m_pPaintIcon = NULL;
+ m_pTF2Icon = NULL;
+ m_pFontNameSmallest = NULL;
+ m_pFontNameSmall = NULL;
+ m_pFontNameLarge = NULL;
+ m_pFontNameLarger = NULL;
+ m_pFontAttribSmallest = NULL;
+ m_pFontAttribSmall = NULL;
+ m_pFontAttribLarge = NULL;
+ m_pFontAttribLarger = NULL;
+ m_pContainedItemPanel = NULL;
+ m_pMainContentContainer = NULL;
+ m_pLoadingSpinner = NULL;
+ m_nCollectionItemLoaded = LOADED_COLLECTION_NONE;
+ LoadResFileForCurrentItem( true );
+
+ m_pFontNameSmallest = pScheme->GetFont( "ItemFontNameSmallest", true );
+ m_pFontNameSmall = pScheme->GetFont( "ItemFontNameSmall", true );
+ m_pFontNameLarge = pScheme->GetFont( "ItemFontNameLarge", true );
+ m_pFontNameLarger = pScheme->GetFont( "ItemFontNameLarger", true );
+ m_pFontAttribSmallest = pScheme->GetFont( "ItemFontAttribSmallest", true );
+ m_pFontAttribSmall = pScheme->GetFont( "ItemFontAttribSmallv2", true );
+ m_pFontAttribLarge = pScheme->GetFont( "ItemFontAttribLarge", true );
+ m_pFontAttribLarger = pScheme->GetFont( "ItemFontAttribLarger", true );
+
+ if ( m_bContainedItem )
+ {
+ // SetBorder( pScheme->GetBorder("TFThinLineBorder") );
+ }
+ else
+ {
+ SetBorder( pScheme->GetBorder( "TFFatLineBorder" ) );
+ }
+
+ if ( m_pModelPanel )
+ {
+ m_pModelPanel->SetBorder( pScheme->GetBorder( "TFFatLineBorder" ) );
+ }
+}
+
+void CItemModelPanel::ApplySettings( KeyValues *inResourceData )
+{
+ if ( !inResourceData )
+ return;
+
+ BaseClass::ApplySettings( inResourceData );
+
+ // Pass the itemmodelpanel KVs to the actual model panel
+ KeyValues* pItemModelPanelKVs = inResourceData->FindKey( "itemmodelpanel" );
+ if ( m_pModelPanel && pItemModelPanelKVs )
+ {
+ m_pModelPanel->ApplySettings( pItemModelPanelKVs );
+ }
+
+ // We can get our settings applied AFTER we've already setup our
+ // panel, so re-update.
+ UpdatePanels();
+}
+
+void CItemModelPanel::LoadResFileForCurrentItem( bool bForceLoad )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ bool bCollectionMouseover = ( m_bIsMouseOverPanel && GetItem() && GetItem()->GetItemDefinition()->GetItemCollectionDefinition() );
+ if ( bCollectionMouseover )
+ {
+ float flInspect = 0;
+ static CSchemaAttributeDefHandle pAttrib_WeaponAllowInspect( "weapon_allow_inspect" );
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( GetItem(), pAttrib_WeaponAllowInspect, &flInspect ) && flInspect != 0.f )
+ {
+ if ( bForceLoad || m_nCollectionItemLoaded != LOADED_COLLECTION_WEAPON )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s ItemModelPanelCollectionItem", __FUNCTION__ );
+ LoadControlSettings( "Resource/UI/econ/ItemModelPanelCollectionItem.res" );
+ m_nCollectionItemLoaded = LOADED_COLLECTION_WEAPON;
+ }
+ }
+ else
+ {
+ if ( bForceLoad || m_nCollectionItemLoaded != LOADED_COLLECTION_COSMETIC )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s ItemModelPanelCollectionCosmeticItem", __FUNCTION__ );
+ LoadControlSettings( "Resource/UI/econ/ItemModelPanelCollectionCosmeticItem.res" );
+ m_nCollectionItemLoaded = LOADED_COLLECTION_COSMETIC;
+ }
+ }
+ m_bHideModel = false; // Hack
+ }
+ else
+ {
+ if ( bForceLoad || m_nCollectionItemLoaded != LOADED_COLLECTION_NONE )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s ItemModelPanel", __FUNCTION__ );
+ LoadControlSettings( "Resource/UI/econ/ItemModelPanel.res" );
+ }
+ m_bHideModel = m_bHideModelDefault;
+ m_nCollectionItemLoaded = LOADED_COLLECTION_NONE;
+ }
+
+ m_pModelPanel = dynamic_cast<CEmbeddedItemModelPanel*>( FindChildByName( "itemmodelpanel", true ) );
+ SetModelIsHidden( m_bHideModel );
+ if ( m_bIsMouseOverPanel && m_pModelPanel )
+ {
+ m_pModelPanel->SetInventoryImageType( CEmbeddedItemModelPanel::IMAGETYPE_LARGE );
+ }
+
+ m_pItemNameLabel = dynamic_cast<CExLabel*>( FindChildByName( "namelabel", true ) );
+ m_pItemAttribLabel = dynamic_cast<vgui::Label*>( FindChildByName( "attriblabel", true ) );
+ m_pItemCollectionNameLabel = dynamic_cast<CExLabel*>( FindChildByName( "collectionnamelabel", true ) );
+ m_pItemCollectionListLabel = dynamic_cast<vgui::Label*>( FindChildByName( "collectionlistlabel", true ) );
+ m_pItemCollectionHighlight = dynamic_cast<vgui::EditablePanel*>( FindChildByName( "collectionhighlight", true ) );
+ m_pItemEquippedLabel = dynamic_cast<vgui::Label*>( FindChildByName( "equippedlabel", true ) );
+ m_pItemQuantityLabel = dynamic_cast<vgui::Label*>( FindChildByName( "quantitylabel", true ) );
+ m_pVisionRestrictionImage = dynamic_cast<vgui::ImagePanel*>( FindChildByName( "vision_restriction_icon", true ) );
+
+ m_pIsStrangeImage = dynamic_cast<vgui::ImagePanel*>( FindChildByName( "is_strange_icon", true ) );
+ m_pIsUnusualImage = dynamic_cast<vgui::ImagePanel*>( FindChildByName( "is_unusual_icon", true ) );
+ m_pIsLoanerImage = dynamic_cast<vgui::ImagePanel*>( FindChildByName( "is_loaner_icon", true ) );
+
+ m_pSeriesLabel = dynamic_cast<vgui::Label*>( FindChildByName( "serieslabel", true ) );
+ m_pMatchesLabel = dynamic_cast<vgui::Label*>( FindChildByName( "matcheslabel", true ) );
+ m_pMainContentContainer = dynamic_cast<vgui::EditablePanel*>( FindChildByName( "MainContentsContainer" ) );
+ m_pLoadingSpinner = dynamic_cast<vgui::ImagePanel*>( FindChildByName( "LoadingSpinner" ) );
+
+ if ( m_pItemEquippedLabel )
+ {
+ m_pItemEquippedLabel->SetKeyBoardInputEnabled( false );
+ m_pItemEquippedLabel->SetMouseInputEnabled( false );
+ }
+ if ( m_pItemQuantityLabel )
+ {
+ m_pItemQuantityLabel->SetKeyBoardInputEnabled( false );
+ m_pItemQuantityLabel->SetMouseInputEnabled( false );
+ }
+ if ( m_pVisionRestrictionImage )
+ {
+ m_pVisionRestrictionImage->SetKeyBoardInputEnabled( false );
+ m_pVisionRestrictionImage->SetMouseInputEnabled( false );
+ }
+ if ( m_pIsStrangeImage )
+ {
+ m_pIsStrangeImage->SetKeyBoardInputEnabled( false );
+ m_pIsStrangeImage->SetMouseInputEnabled( false );
+ }
+ if ( m_pIsUnusualImage )
+ {
+ m_pIsUnusualImage->SetKeyBoardInputEnabled( false );
+ m_pIsUnusualImage->SetMouseInputEnabled( false );
+ }
+ if ( m_pIsLoanerImage )
+ {
+ m_pIsLoanerImage->SetKeyBoardInputEnabled( false );
+ m_pIsLoanerImage->SetMouseInputEnabled( false );
+ }
+
+ if ( m_pSeriesLabel )
+ {
+ m_pSeriesLabel->SetKeyBoardInputEnabled( false );
+ m_pSeriesLabel->SetMouseInputEnabled( false );
+ }
+ if ( m_pMatchesLabel )
+ {
+ m_pMatchesLabel->SetKeyBoardInputEnabled( false );
+ m_pMatchesLabel->SetMouseInputEnabled( false );
+ }
+
+ m_pPaintIcon = dynamic_cast<CItemMaterialCustomizationIconPanel*>( FindChildByName( "paint_icon", true ) );
+ if ( m_pPaintIcon )
+ {
+ m_pPaintIcon->SetMouseInputEnabled( false );
+ }
+ m_pTF2Icon = dynamic_cast<vgui::ScalableImagePanel*>( FindChildByName( "tf2_icon", true ) );
+ if ( m_pTF2Icon )
+ {
+ m_pTF2Icon->SetMouseInputEnabled( false );
+ }
+
+ if ( m_bContainedItem )
+ {
+ SetPaintBackgroundEnabled( true );
+ }
+ else
+ {
+ SetPaintBackgroundEnabled( false );
+ }
+
+ if ( m_pModelPanel )
+ {
+ m_pModelPanel->SetBgColor( Color( 0, 0, 0, 255 ) );
+ m_pModelPanel->AddActionSignalTarget( this );
+ m_pModelPanel->SetMouseInputEnabled( false );
+ }
+
+ if ( m_pItemNameLabel )
+ {
+ m_OrgItemTextColor = m_pItemNameLabel->GetFgColor();
+ m_pItemNameLabel->SetMouseInputEnabled( false );
+ m_pItemNameLabel->AddActionSignalTarget( this );
+ m_pItemNameLabel->InvalidateLayout( true, true );
+ }
+
+ if ( m_pItemAttribLabel )
+ {
+ m_pItemAttribLabel->SetMouseInputEnabled( false );
+ m_pItemAttribLabel->AddActionSignalTarget( this );
+ m_pItemAttribLabel->InvalidateLayout( true, true );
+ }
+
+ if ( m_pItemCollectionNameLabel )
+ {
+ m_pItemCollectionNameLabel->SetMouseInputEnabled( false );
+ m_pItemCollectionNameLabel->AddActionSignalTarget( this );
+ m_pItemCollectionNameLabel->InvalidateLayout( true, true );
+ }
+
+ if ( m_pItemCollectionListLabel )
+ {
+ m_pItemCollectionListLabel->SetMouseInputEnabled( false );
+ m_pItemCollectionListLabel->AddActionSignalTarget( this );
+ m_pItemCollectionListLabel->InvalidateLayout( true, true );
+ }
+
+ if ( m_pItemCollectionHighlight )
+ {
+ m_pItemCollectionHighlight->SetMouseInputEnabled( false );
+ m_pItemCollectionHighlight->AddActionSignalTarget( this );
+ m_pItemCollectionHighlight->InvalidateLayout( true, true );
+ }
+
+ m_pContainedItemPanel = dynamic_cast<CItemModelPanel*>( FindChildByName( "contained_item_panel", true ) );
+
+ // Dont eat mouse input
+ if ( m_pMainContentContainer )
+ {
+ m_pMainContentContainer->SetMouseInputEnabled( false );
+ }
+
+ UpdatePanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::PerformLayout( void )
+{
+ int w,h;
+ GetSize( w, h );
+ w = m_iBaseWide ? m_iBaseWide : w;
+ h = m_iBaseTall ? m_iBaseTall : h;
+
+ int iTextW = GetAttribWide(w);
+ int iModelW = m_iModelWide && m_iModelWide < w ? m_iModelWide : w;
+ int iModelT = m_iModelTall && m_iModelTall < h ? m_iModelTall : h;
+ int iModelX = m_bModelCenterX ? ( ( w - iModelW ) * 0.5 ) : m_iModelXPos;
+ int iModelY = m_bModelCenterY ? ( ( h - iModelT ) * 0.5 ) : m_iModelYPos;
+
+ ResizeLabels();
+
+ if ( m_pModelPanel )
+ {
+ m_pModelPanel->SetBounds( iModelX, iModelY, iModelW, iModelT );
+ }
+ if ( m_pLoadingSpinner )
+ {
+ int nWidthHeight = Max( iModelW, iModelT );
+ int xOffset = int( w / 2.f ) - int( nWidthHeight / 2.f );
+ int yOffset = int( h / 2.f ) - int( nWidthHeight / 2.f );
+ m_pLoadingSpinner->SetBounds( xOffset, yOffset, nWidthHeight, nWidthHeight );
+ }
+
+ if ( m_bNoItemFullPanel )
+ {
+ // We want the "no item" text to use the entire panel, and hide the attribs entirely
+ if ( m_pItemNameLabel && m_pItemAttribLabel )
+ {
+ m_pItemNameLabel->SetBounds( XRES(4), 0, GetWide() - XRES(8), GetTall() );
+ }
+ }
+ else if ( m_pItemNameLabel && m_pItemAttribLabel && !m_bModelOnly )
+ {
+ // Force the labels to layout now, and get their height.
+ m_pItemNameLabel->InvalidateLayout( true );
+ m_pItemAttribLabel->InvalidateLayout( true );
+ m_pItemNameLabel->SizeToContents();
+ m_pItemAttribLabel->SizeToContents();
+
+ if ( m_pItemCollectionNameLabel )
+ {
+ m_pItemCollectionNameLabel->InvalidateLayout( true );
+ m_pItemCollectionNameLabel->SizeToContents();
+ }
+
+ if ( m_pItemCollectionListLabel )
+ {
+ m_pItemCollectionListLabel->InvalidateLayout( true );
+ m_pItemCollectionListLabel->SizeToContents();
+ }
+
+ // "" strings still size themselves as one font-heighth tall, but 0 wide. If there's no
+ // text in the attribute, we want 0 tall as well, so we don't get blank lines.
+ int iCollectionTall = m_pItemCollectionListLabel ? m_pItemCollectionListLabel->GetTall() : 0;
+ int iAttribTall = (m_pItemAttribLabel->GetWide() ? m_pItemAttribLabel->GetTall() : 0);
+ iAttribTall = Max( iAttribTall, iCollectionTall );
+
+ int iNameTall = m_pItemNameLabel->GetTall();
+
+ int iCollectionNameTall = m_pItemCollectionNameLabel ? m_pItemCollectionNameLabel->GetTall() : 0;
+
+ if ( m_bAttribOnly )
+ {
+ iNameTall = 0;
+ }
+
+ if ( m_bTextCenterX )
+ {
+ m_pItemNameLabel->SetSize( iTextW, iNameTall );
+ m_pItemAttribLabel->SetSize( iTextW, iAttribTall );
+ }
+ else if ( m_iTextYPos )
+ {
+ m_pItemNameLabel->SetSize( iTextW, iNameTall );
+ m_pItemAttribLabel->SetSize( iTextW, (m_pItemAttribLabel->GetWide() ? m_pItemAttribLabel->GetTall() : 0) );
+ if ( m_pItemCollectionNameLabel )
+ m_pItemCollectionNameLabel->SetSize( iTextW, iCollectionNameTall );
+ if ( m_pItemCollectionListLabel )
+ m_pItemCollectionListLabel->SetSize( iTextW, iCollectionTall );
+ }
+ else if ( m_bTextCenter )
+ {
+ m_pItemNameLabel->SetSize( iTextW, iNameTall );
+ m_pItemAttribLabel->SetSize( iTextW, iAttribTall );
+ }
+ else
+ {
+ m_pItemNameLabel->SetSize( iTextW, iNameTall );
+ m_pItemAttribLabel->SetSize( iTextW, iAttribTall );
+ }
+
+ m_pItemNameLabel->InvalidateLayout( true );
+
+ // Force attrib layout to update now in its new size.
+ m_pItemAttribLabel->InvalidateLayout( true );
+ m_pItemAttribLabel->SizeToContents();
+
+ // Reget sizes, wtf
+ iCollectionTall = m_pItemCollectionListLabel ? m_pItemCollectionListLabel->GetTall() : 0;
+ iAttribTall = ( m_pItemAttribLabel->GetWide() ? m_pItemAttribLabel->GetTall() : 0 );
+ // HACK: Now we resize it again. Sets our height properly. Ridiculous.
+ m_pItemAttribLabel->SetSize( iTextW, iAttribTall );
+ m_pItemNameLabel->SetSize( iTextW, iNameTall );
+
+ // Ignore attributes if we're only showing the name
+ if ( m_bNameOnly || (!HasItem() && m_pszNoItemText && m_pszNoItemText[0]) )
+ {
+ iAttribTall = 0;
+ }
+
+ int iLabelOffset = 0;
+ if ( m_bResizeToText )
+ {
+ h = m_iTextYPos + iNameTall + iAttribTall + m_iHPadding;
+
+ // Must be at least tall enough to fit the image (if visible)
+ if ( !m_bHideModel )
+ {
+ //h = MAX( h, (iModelT + (iModelY * 2)) );
+ h = Max( h + iModelT + iModelY, m_iTextYPos + iCollectionNameTall + iCollectionTall + m_iHPadding);
+ iLabelOffset = iModelT + iModelY;
+ }
+ }
+
+ // If we don't have a specific X pos, or attrib width, indent ourselves
+ int iTextXPos = (m_iTextXPos || m_iTextWide) ? m_iTextXPos : ATTRIB_LABEL_INDENT;
+
+ if ( iCollectionNameTall && iCollectionTall && m_iTextXPosCollection )
+ {
+ iTextXPos = m_iTextXPosCollection;
+ }
+
+ // Position the name label now we know where our attrib label is
+ // If we've got a Y pos, use it. Otherwise, stack up from the bottom of the panel.
+ if ( m_bTextCenterX )
+ {
+ m_pItemNameLabel->SizeToContents();
+ m_pItemAttribLabel->SizeToContents();
+
+ m_pItemNameLabel->SetPos( ( w - m_pItemNameLabel->GetWide() ) * 0.5f, m_iTextYPos + iLabelOffset );
+ m_pItemAttribLabel->SetPos( ( w - m_pItemAttribLabel->GetWide() ) * 0.5f, m_iTextYPos + iNameTall + iLabelOffset );
+ }
+ else if ( m_iTextYPos )
+ {
+ m_pItemNameLabel->SetPos( iTextXPos, m_iTextYPos + iLabelOffset );
+ m_pItemAttribLabel->SetPos( iTextXPos, m_iTextYPos + iNameTall + iLabelOffset);
+ if ( m_pItemCollectionNameLabel )
+ m_pItemCollectionNameLabel->SetPos( m_iCollectionListXPos, m_iTextYPos );
+ if ( m_pItemCollectionListLabel )
+ m_pItemCollectionListLabel->SetPos( m_iCollectionListXPos, m_iTextYPos + iCollectionNameTall );
+ }
+ else if ( m_bTextCenter )
+ {
+ int iYTop = (h - (iNameTall + iAttribTall)) * 0.5;
+ if ( iYTop < 0 )
+ {
+ iYTop = 0;
+ }
+ m_pItemNameLabel->SetPos( iTextXPos, iYTop );
+ m_pItemAttribLabel->SetPos( iTextXPos, iYTop + iNameTall );
+ //m_pItemCollectionLabel->SetPos( iTextXPos + iTextW, iYTop );
+ }
+ else
+ {
+ int iOffsetY = (m_iTextYOffset != 0) ? m_iTextYOffset : YRES(8);
+ m_pItemNameLabel->SetPos( iTextXPos, h - iAttribTall - iNameTall - iOffsetY + m_iHPadding );
+ m_pItemAttribLabel->SetPos( iTextXPos, h - iAttribTall - iOffsetY + m_iHPadding );
+ //m_pItemCollectionLabel->SetPos( iTextXPos + iTextW, h - iAttribTall - iNameTall - iOffsetY + m_iHPadding );
+ }
+
+ if ( m_bResizeToText )
+ {
+ if ( m_bIsMouseOverPanel && GetItem() && GetItem()->GetItemDefinition()->GetItemCollectionDefinition() && !m_bHideCollectionPanel )
+ {
+ if ( m_pItemCollectionListLabel && m_pItemCollectionNameLabel && m_pItemCollectionHighlight )
+ {
+ m_pItemCollectionListLabel->SizeToContents();
+ m_pItemCollectionNameLabel->SizeToContents();
+ int iContentW = Max( m_pItemCollectionNameLabel->GetWide(), m_pItemCollectionListLabel->GetWide() );
+ w = iContentW + m_iCollectionListXPos + m_iTextXPosCollection;
+ m_pItemCollectionHighlight->SetWide( iContentW );
+ }
+ }
+ SetSize( w, h );
+ }
+ }
+
+ // pin icons to top right corner
+ int xpos = m_iBaseWide - XRES(1);
+ int ypos = YRES(1);
+
+ if ( m_pPaintIcon && m_pPaintIcon->IsVisible() )
+ {
+ m_pPaintIcon->SetPos( xpos - m_pPaintIcon->GetWide(), ypos );
+ ypos += m_pPaintIcon->GetTall() * 0.9;
+ }
+ if ( m_pTF2Icon && m_pTF2Icon->IsVisible() )
+ {
+ m_pTF2Icon->SetPos( xpos - m_pTF2Icon->GetWide() + m_iTF2IconOffsetX, ypos + m_iTF2IconOffsetY );
+ ypos += m_pTF2Icon->GetTall() * 0.9;
+ }
+ if ( m_pVisionRestrictionImage && m_pVisionRestrictionImage->IsVisible() )
+ {
+ m_pVisionRestrictionImage->SetPos( xpos - m_pVisionRestrictionImage->GetWide(), ypos );
+ ypos += m_pVisionRestrictionImage->GetTall() * 0.9;
+ }
+ if ( m_pIsUnusualImage && m_pIsUnusualImage->IsVisible() )
+ {
+ m_pIsUnusualImage->SetPos( xpos - m_pIsUnusualImage->GetWide(), ypos );
+ ypos += m_pIsUnusualImage->GetTall() * 0.9;
+ }
+ if ( m_pIsStrangeImage && m_pIsStrangeImage->IsVisible() )
+ {
+ m_pIsStrangeImage->SetPos( xpos - m_pIsStrangeImage->GetWide(), ypos );
+ ypos += m_pIsStrangeImage->GetTall() * 0.9;
+ }
+ if ( m_pIsLoanerImage && m_pIsLoanerImage->IsVisible() )
+ {
+ m_pIsLoanerImage->SetPos( xpos - m_pIsLoanerImage->GetWide(), ypos );
+ ypos += m_pIsLoanerImage->GetTall() * 0.9;
+ }
+
+
+ if ( m_pItemNameLabel )
+ {
+ //m_pItemNameLabel->SetContentAlignment( (vgui::Label::Alignment) m_iNameLabelAlignment );
+ }
+
+ if ( m_bModelOnly )
+ {
+ if ( m_pItemNameLabel )
+ {
+ m_pItemNameLabel->SetVisible( false );
+ }
+ if ( m_pItemAttribLabel )
+ {
+ m_pItemAttribLabel->SetVisible( false );
+ }
+ if ( m_pItemCollectionNameLabel )
+ {
+ m_pItemCollectionNameLabel->SetVisible( false );
+ }
+ if ( m_pItemCollectionListLabel )
+ {
+ m_pItemCollectionListLabel->SetVisible( false );
+ }
+ if ( m_pItemCollectionHighlight )
+ {
+ m_pItemCollectionHighlight->SetVisible( false );
+ }
+ }
+
+ BaseClass::PerformLayout();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::PaintTraverse( bool forceRepaint, bool allowForce )
+{
+ if ( m_bFakeButton )
+ return;
+
+ BaseClass::PaintTraverse( forceRepaint, allowForce );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::OnSizeChanged( int newWide, int newTall )
+{
+ BaseClass::OnSizeChanged( newWide, newTall );
+ InvalidateLayout( true );
+
+ if ( m_bModelOnly )
+ return;
+
+ if ( m_pItemNameLabel && m_pItemNameLabel->GetTextImage() )
+ {
+ m_pItemNameLabel->GetTextImage()->RecalculateNewLinePositions();
+ }
+ if ( m_pItemAttribLabel && m_pItemAttribLabel->GetTextImage() )
+ {
+ m_pItemAttribLabel->GetTextImage()->RecalculateNewLinePositions();
+ }
+ if ( m_pItemCollectionNameLabel && m_pItemCollectionNameLabel->GetTextImage() )
+ {
+ m_pItemCollectionNameLabel->GetTextImage()->RecalculateNewLinePositions();
+ }
+ if ( m_pItemCollectionListLabel && m_pItemCollectionListLabel->GetTextImage() )
+ {
+ m_pItemCollectionListLabel->GetTextImage()->RecalculateNewLinePositions();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::ResizeLabels( void )
+{
+ if ( !m_pItemNameLabel || !m_pItemAttribLabel || m_bModelOnly )
+ return;
+
+ int w,h;
+ GetSize( w, h );
+ int iTextW = GetAttribWide(w);
+
+ if ( m_iMaxTextHeight )
+ {
+ h = m_iMaxTextHeight;
+ }
+
+ // HACK to get the item model panel on the main menu to have its fonts.
+ vgui::IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ if ( !m_pFontNameSmallest )
+ m_pFontNameSmallest = pScheme->GetFont( "ItemFontNameSmallest", true );
+ if ( !m_pFontNameSmall )
+ m_pFontNameSmall = pScheme->GetFont( "ItemFontNameSmall", true );
+ if ( !m_pFontNameLarge )
+ m_pFontNameLarge = pScheme->GetFont( "ItemFontNameLarge", true );
+ if ( !m_pFontNameLarger )
+ m_pFontNameLarger = pScheme->GetFont( "ItemFontNameLarger", true );
+ if ( !m_pFontAttribSmallest )
+ m_pFontAttribSmallest = pScheme->GetFont( "ItemFontAttribSmallest", true );
+ if ( !m_pFontAttribSmall )
+ m_pFontAttribSmall = pScheme->GetFont( "ItemFontAttribSmallv2", true );
+ if ( !m_pFontAttribLarge )
+ m_pFontAttribLarge = pScheme->GetFont( "ItemFontAttribLarge", true );
+ if ( !m_pFontAttribLarger )
+ m_pFontAttribLarger = pScheme->GetFont( "ItemFontAttribLarger", true );
+
+ if ( m_iForceTextSize && m_iForceTextSize <= 4 )
+ {
+ // Leave center wrap on if the noitem text is getting to use the whole panel
+ if ( !m_bNoItemFullPanel )
+ {
+ m_pItemNameLabel->SetCenterWrap( false );
+ }
+ m_pItemNameLabel->InvalidateLayout( true, true );
+ m_pItemAttribLabel->InvalidateLayout( true, true );
+ if ( m_pItemCollectionNameLabel )
+ m_pItemCollectionNameLabel->InvalidateLayout( true, true );
+ if ( m_pItemCollectionListLabel )
+ m_pItemCollectionListLabel->InvalidateLayout( true, true );
+
+ switch ( m_iForceTextSize )
+ {
+ case 1:
+ m_pItemNameLabel->SetFont( m_pFontNameLarge );
+ m_pItemAttribLabel->SetFont( m_pFontAttribLarge );
+ if ( m_pItemCollectionNameLabel )
+ m_pItemCollectionNameLabel->SetFont( m_pFontNameLarge );
+ if ( m_pItemCollectionListLabel )
+ m_pItemCollectionListLabel->SetFont( m_pFontAttribLarge );
+ break;
+
+ case 2:
+ m_pItemNameLabel->SetFont( m_pFontNameSmall );
+ m_pItemAttribLabel->SetFont( m_pFontAttribSmall );
+ if ( m_pItemCollectionNameLabel )
+ m_pItemCollectionNameLabel->SetFont( m_pFontNameSmall );
+ if ( m_pItemCollectionListLabel )
+ m_pItemCollectionListLabel->SetFont( m_pFontAttribSmall );
+ break;
+
+ case 3:
+ m_pItemNameLabel->SetFont( m_pFontNameSmallest );
+ m_pItemAttribLabel->SetFont( m_pFontAttribSmallest );
+ if ( m_pItemCollectionNameLabel )
+ m_pItemCollectionNameLabel->SetFont( m_pFontNameSmallest );
+ if ( m_pItemCollectionListLabel )
+ m_pItemCollectionListLabel->SetFont( m_pFontAttribSmallest );
+ break;
+
+ case 4:
+ m_pItemNameLabel->SetFont( m_pFontNameLarger );
+ m_pItemAttribLabel->SetFont( m_pFontAttribLarger );
+ if ( m_pItemCollectionNameLabel )
+ m_pItemCollectionNameLabel->SetFont( m_pFontNameLarger );
+ if ( m_pItemCollectionListLabel )
+ m_pItemCollectionListLabel->SetFont( m_pFontAttribLarger );
+ break;
+ }
+
+ m_pItemNameLabel->SizeToContents();
+ m_pItemAttribLabel->SizeToContents();
+ if ( m_pItemCollectionNameLabel )
+ m_pItemCollectionNameLabel->SizeToContents();
+ if ( m_pItemCollectionListLabel )
+ m_pItemCollectionListLabel->SizeToContents();
+ }
+ else
+ {
+ m_pItemNameLabel->SetFont( m_pFontNameLarge );
+ m_pItemNameLabel->SetCenterWrap( false );
+ m_pItemNameLabel->SizeToContents();
+ m_pItemAttribLabel->SetFont( m_pFontAttribLarge );
+ m_pItemAttribLabel->SizeToContents();
+ if ( m_pItemCollectionNameLabel )
+ {
+ m_pItemCollectionNameLabel->SetFont( m_pFontNameLarge );
+ m_pItemCollectionNameLabel->SetCenterWrap( false );
+ m_pItemCollectionNameLabel->SizeToContents();
+ }
+ if ( m_pItemCollectionListLabel )
+ {
+ m_pItemCollectionListLabel->SetFont( m_pFontAttribLarge );
+ m_pItemCollectionListLabel->SizeToContents();
+ }
+
+ if ( !m_bResizeToText )
+ {
+ int iAttribTall = m_pItemAttribLabel->GetWide() ? m_pItemAttribLabel->GetTall() : 0;
+ int iNameTall = m_bAttribOnly ? 0 : m_pItemNameLabel->GetTall();
+ int iTotalH = iAttribTall + iNameTall;
+
+ // If these fonts won't fit, use the smaller ones
+ if ( m_pItemNameLabel->GetWide() > iTextW || (!m_bNameOnly && m_pItemAttribLabel->GetWide() > iTextW) || (iTotalH > h) )
+ {
+ m_pItemNameLabel->SetFont( m_pFontNameSmall );
+ m_pItemAttribLabel->SetFont( m_pFontAttribSmall );
+
+ m_pItemNameLabel->InvalidateLayout( true );
+ m_pItemNameLabel->SizeToContents();
+
+ iAttribTall = m_pItemAttribLabel->GetWide() ? m_pItemAttribLabel->GetTall() : 0;
+ iNameTall = m_pItemNameLabel->GetTall();
+ iTotalH = iAttribTall + iNameTall;
+
+ // If they don't fit, go to the smallest
+ if ( m_pItemNameLabel->GetWide() > iTextW || (!m_bNameOnly && m_pItemAttribLabel->GetWide() > iTextW) || (iTotalH > h) )
+ {
+ m_pItemNameLabel->SetFont( m_pFontNameSmallest );
+ m_pItemAttribLabel->SetFont( m_pFontAttribSmallest );
+
+ m_pItemNameLabel->InvalidateLayout( true );
+ m_pItemNameLabel->SizeToContents();
+ }
+ }
+ }
+ }
+
+ // If it still doesn't fit, turn on wrap and pray
+ if ( m_pItemNameLabel->GetWide() > iTextW )
+ {
+ m_pItemNameLabel->SetCenterWrap( true );
+ }
+
+ if ( m_pItemAttribLabel->GetWide() > iTextW )
+ {
+ m_pItemAttribLabel->SetWrap( true );
+ }
+
+ // Now restore the sizes
+ m_pItemNameLabel->SetSize( iTextW, m_pItemNameLabel->GetTall() );
+ m_pItemAttribLabel->SetSize( iTextW, m_pItemAttribLabel->GetTall() );
+
+ if ( m_pItemEquippedLabel )
+ {
+ m_pItemEquippedLabel->SetPos( GetWide() - m_pItemEquippedLabel->GetWide() - m_iEquippedInsetX, GetTall() - m_pItemEquippedLabel->GetTall() - m_iEquippedInsetY );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::SetItem( const CEconItemView *pItem )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ HideContainedItemPanel();
+
+ bool bMatch = false;
+
+ if ( pItem && pItem->IsValid() )
+ {
+ if ( m_ItemData.IsValid() )
+ {
+ if ( m_ItemData.GetItemID() != INVALID_ITEM_ID )
+ {
+ bool bUseIndexCompare = false;
+
+#ifdef TF_CLIENT_DLL
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ if ( ( m_ItemData.GetItemID() == 1 ) && ( m_ItemData.GetItemID() == pItem->GetItemID() ) )
+ {
+ // Items the bots carry in MvM all have itemID of 1, so we need to compare the item index
+ bUseIndexCompare = true;
+ }
+ }
+#endif
+
+ if ( bUseIndexCompare )
+ {
+ bMatch = m_ItemData.GetItemDefIndex() == pItem->GetItemDefIndex();
+ }
+ else
+ {
+ // Our current item is non-base. We need to match global indices.
+ bMatch = ( m_ItemData.GetItemID() == pItem->GetItemID() );
+ }
+ }
+ else if ( pItem->GetItemID() == INVALID_ITEM_ID )
+ {
+ static CSchemaFieldHandle<CEconItemAttributeDefinition> pAttrib_ToolTarget( "tool target item" );
+ bMatch &= pItem->FindAttribute( pAttrib_ToolTarget );
+
+ // Our current item is a base item. Our new item needs to be base too, and match item indices and quality
+ bMatch &= ( m_ItemData.GetItemDefIndex() == pItem->GetItemDefIndex() ) &&
+ ( m_ItemData.GetItemQuality() == pItem->GetItemQuality() ) &&
+ ( m_ItemData.GetSOCData() == pItem->GetSOCData() );
+ }
+ }
+
+ // if we match item so far, check for strange
+ if ( bMatch )
+ {
+ // Are we tracking alternate stats as well?
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ const CEconItemAttributeDefinition *pKillEaterAltAttrDef = GetKillEaterAttr_Score( i ),
+ *pKillEaterAltScoreTypeAttrDef = GetKillEaterAttr_Type( i );
+ if ( !pKillEaterAltAttrDef || !pKillEaterAltScoreTypeAttrDef )
+ continue;
+
+ uint32 unNewScore = 0;
+ uint32 unOldScore = 0;
+ bool bNewFoundAttr = pItem->FindAttribute( pKillEaterAltAttrDef, &unNewScore );
+ bool bOldFoundAttr = m_ItemData.FindAttribute( pKillEaterAltAttrDef, &unOldScore );
+ if ( bNewFoundAttr != bOldFoundAttr || unNewScore != unOldScore )
+ {
+ // different score
+ bMatch = false;
+ break;
+ }
+
+ float flNewType = 0.f;
+ float flOldType = 0.f;
+ bNewFoundAttr = FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pKillEaterAltScoreTypeAttrDef, &flNewType );
+ bOldFoundAttr = FindAttribute_UnsafeBitwiseCast<attrib_value_t>( &m_ItemData, pKillEaterAltScoreTypeAttrDef, &flOldType );
+ if ( bNewFoundAttr != bOldFoundAttr || flNewType != flOldType )
+ {
+ // different score
+ bMatch = false;
+ break;
+ }
+ }
+ }
+
+ if ( !bMatch )
+ {
+ // cancel weapon skin composition for old item
+ if ( m_ItemData.IsValid() )
+ {
+ m_ItemData.CancelWeaponSkinComposite();
+ }
+
+ m_ItemData = *pItem;
+
+ if ( m_bIsMouseOverPanel )
+ {
+ LoadResFileForCurrentItem( false );
+ }
+
+ // If the item hasn't built its attribute string, go ahead and do that.
+ m_ItemData.SetGrayedOutReason( GetGreyedOutReason() );
+ }
+ else
+ {
+ // The rest of the data may match, but we still need the inventory position updates
+ m_ItemData.SetInventoryPosition( pItem->GetInventoryPosition() );
+ }
+
+ ShowContainedItemPanel( pItem );
+ }
+ else
+ {
+ // cancel weapon skin composition for old item
+ if ( m_ItemData.IsValid() )
+ {
+ m_ItemData.CancelWeaponSkinComposite();
+ }
+ else
+ {
+ bMatch = true;
+ }
+
+ m_ItemData.GetAttributeList()->DestroyAllAttributes();
+ m_ItemData.Invalidate();
+ }
+
+ // only update panels when item is not matched
+ if ( !bMatch )
+ {
+ UpdatePanels();
+ }
+
+ // TODO: Update only description for strange item in the same panel
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::Dragged( bool bDragging )
+{
+ if ( m_pContainedItemPanel )
+ {
+ if ( bDragging )
+ {
+ m_pContainedItemPanel->SetActAsButton( false, false );
+ m_pContainedItemPanel->SetVisible( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::ShowContainedItemPanel( const CEconItemView *pItem )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ // If this item contains another item, create an interior item model panel.
+ if ( pItem->GetSOCData() && pItem->GetSOCData()->GetInteriorItem() && m_pContainedItemPanel )
+ {
+ if ( !m_pContainedItemPanel->IsContainedItem() )
+ {
+ m_pContainedItemPanel->SetContainedItem( true );
+ m_pContainedItemPanel->InvalidateLayout( false, true );
+ }
+
+ CEconItem *pInteriorItem = pItem->GetSOCData()->GetInteriorItem();
+ if ( !pInteriorItem )
+ return;
+
+ const IEconTool *pEconTool = pItem->GetItemDefinition()
+ ? pItem->GetItemDefinition()->GetEconTool()
+ : NULL;
+ if ( !pEconTool )
+ return;
+
+ if ( !pEconTool->ShouldShowContainedItemPanel( pItem ) )
+ {
+ // Only show this to non-local, non-wrapping players if we've been told to (usually in trading panel)
+ if ( !m_bShowOthersGiftWrappedItems )
+ return;
+ }
+
+ SetNeedsToLoad();
+
+ m_pContainedItemPanel->SetEconItem( pInteriorItem );
+ m_pContainedItemPanel->SetVisible( true );
+ m_pContainedItemPanel->SetActAsButton( false, true );
+ m_pContainedItemPanel->SetTooltip( GetTooltip(), "" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::HideContainedItemPanel()
+{
+ if ( m_pContainedItemPanel && m_pContainedItemPanel->IsVisible() )
+ {
+ m_pContainedItemPanel->SetActAsButton( false, false );
+ m_pContainedItemPanel->SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::SetEconItem( CEconItem* pItem )
+{
+ m_ItemData.SetItemDefIndex( pItem->GetDefinitionIndex() );
+ m_ItemData.SetItemQuality( pItem->GetQuality() );
+ m_ItemData.SetItemLevel( pItem->GetItemLevel() );
+ m_ItemData.SetItemID( pItem->GetItemID() );
+ m_ItemData.SetNonSOEconItem( pItem );
+ m_ItemData.SetInitialized( true );
+
+#ifdef CLIENT_DLL
+ m_ItemData.SetIsTradeItem( false );
+ m_ItemData.SetItemQuantity( pItem->GetQuantity() );
+#endif
+
+ m_ItemData.GetAttributeList()->DestroyAllAttributes();
+
+ if ( m_pModelPanel )
+ {
+ m_pModelPanel->SetItem( &m_ItemData );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::SetNoItemText( const char *pszText )
+{
+ m_pszNoItemText = pszText;
+ CleanupNoItemWChars();
+
+ if ( !HasItem() )
+ {
+ UpdatePanels();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::CleanupNoItemWChars( void )
+{
+ if ( m_pwcNoItemText )
+ {
+ delete m_pwcNoItemText;
+ m_pwcNoItemText = NULL;
+ }
+ if ( m_pwcNoItemAttrib )
+ {
+ delete m_pwcNoItemAttrib;
+ m_pwcNoItemAttrib = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CItemModelPanel::UpdateSeriesLabel()
+{
+ // hijacking m_bShowQuantity here to indicate "show things where the quantity counter goes", in this case meaning "show crate series indicator"
+ if ( m_pSeriesLabel && m_bShowQuantity )
+ {
+ static CSchemaAttributeDefHandle pAttrDef_CrateSeries( "set supply crate series" );
+ static CSchemaAttributeDefHandle pAttrDef_HideSeries( "hide crate series number" );
+
+ float fCrateSeries;
+ if ( pAttrDef_CrateSeries && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( &m_ItemData, pAttrDef_CrateSeries, &fCrateSeries ) && pAttrDef_HideSeries && !m_ItemData.FindAttribute( pAttrDef_HideSeries ) )
+ {
+ wchar_t wszSeries[16]=L"";
+ _snwprintf( wszSeries, ARRAYSIZE( wszSeries ), L"#%i", (int)fCrateSeries );
+ m_pSeriesLabel->SetVisible( true );
+ m_pSeriesLabel->SetText( wszSeries );
+
+ return true;
+ }
+ else
+ {
+ m_pSeriesLabel->SetVisible( false );
+
+ return false;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Read through a few items and see if they match the recipe's criteria
+// Show elipses while still tallying. Remove our tick once all items
+// are tallied.
+//-----------------------------------------------------------------------------
+bool CItemModelPanel::CheckRecipeMatches()
+{
+ // Don't do this if either we or our parent are invisible
+ if( !IsVisible() || ( GetParent() && !GetParent()->IsVisible() ) )
+ return false;
+
+ const IEconTool* pTool = m_ItemData.GetStaticData()->GetEconTool();
+
+ // If this isnt a dynamic recipe tool, dont show or do any of this
+ if( !pTool
+ || V_stricmp( m_ItemData.GetStaticData()->GetEconTool()->GetTypeName() , "dynamic_recipe")
+ || m_ItemData.GetStaticData()->GetDefaultLoadoutSlot() != INVALID_EQUIPPED_SLOT )
+ {
+ if( m_pMatchesLabel )
+ {
+ m_pMatchesLabel->SetVisible( false );
+ }
+
+ return false;
+ }
+
+ bool bStillWorking = true;
+ if( m_pMatchesLabel && m_bShowQuantity )
+ {
+ CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
+ if ( pLocalInv == NULL )
+ return false;
+
+ // We still need to match recipe components
+ if ( m_nRecipeMatchingIndex < pLocalInv->GetItemCount() )
+ sai_NumLoadingRequests[LOADING_RECIPE_MATCHES]++;
+
+ if ( se_CurrentLoadingTask == LOADING_RECIPE_MATCHES )
+ {
+ // Go through our entire backpack and check for matches, but only go through a few at a time
+ while ( m_nRecipeMatchingIndex < pLocalInv->GetItemCount() && sm_flLoadingTimeThisFrame < tf_time_loading_item_panels.GetFloat() )
+ {
+ // Mark this time
+ float flTime = Plat_FloatTime();
+
+ CEconItemView *pItem = pLocalInv->GetItem( m_nRecipeMatchingIndex );
+ Assert( pItem );
+
+ // Check each item
+ CRecipeComponentMatchingIterator matchingIterator( &m_ItemData, pItem );
+ m_ItemData.IterateAttributes( &matchingIterator );
+ const CUtlVector< const CEconItemAttributeDefinition* >& matchingAttribs = matchingIterator.GetMatchingComponentInputs();
+ Assert( matchingAttribs.Count() <= 1 );
+ FOR_EACH_VEC( matchingAttribs, j )
+ {
+ CAttribute_DynamicRecipeComponent value;
+ const CEconItemAttributeDefinition* pAttrib = matchingAttribs[j];
+ attrib_definition_index_t nIndex = pAttrib->GetDefinitionIndex();
+ m_ItemData.FindAttribute( pAttrib, &value );
+
+ // Add this entry if it doesnt exist in out map yet
+ if( m_mapMatchingAttributes.Find( nIndex ) == m_mapMatchingAttributes.InvalidIndex() )
+ {
+ m_mapMatchingAttributes.Insert( nIndex );
+ m_mapMatchingAttributes[ m_mapMatchingAttributes.Find( nIndex ) ] = 0;
+ }
+
+ // Increment this value if it's less than the max needed
+ int &nCount = m_mapMatchingAttributes[ m_mapMatchingAttributes.Find( nIndex ) ];
+ if( (unsigned)nCount < ( value.num_required() - value.num_fulfilled() ) )
+ {
+ ++nCount;
+ }
+ }
+
+ m_nRecipeMatchingIndex++;
+ // Accumulate time
+ sm_flLoadingTimeThisFrame += ( Plat_FloatTime() - flTime );
+ }
+ }
+
+ bStillWorking = m_nRecipeMatchingIndex != pLocalInv->GetItemCount();
+ wchar_t wszMatches[16]=L"...";
+
+ if( !bStillWorking )
+ {
+ CRecipeComponentMatchingIterator matchingIterator( &m_ItemData, NULL );
+ m_ItemData.IterateAttributes( &matchingIterator );
+ int nTotalAttribs = matchingIterator.GetTotalInputs() - matchingIterator.GetInputsFulfilled();
+ int nMatchingAttribs = 0;
+
+ unsigned short index = m_mapMatchingAttributes.FirstInorder();
+ while( index != m_mapMatchingAttributes.InvalidIndex() )
+ {
+ nMatchingAttribs += m_mapMatchingAttributes[ index ];
+ index = m_mapMatchingAttributes.NextInorder( index );
+ }
+
+ // Fill out the actual number of matches
+ _snwprintf( wszMatches, ARRAYSIZE( wszMatches ), L"%i/%i", nMatchingAttribs, nTotalAttribs );
+ }
+
+ m_pMatchesLabel->SetVisible( true );
+ m_pMatchesLabel->SetText( wszMatches );
+ }
+
+ return bStillWorking;
+}
+
+void CItemModelPanel::UpdateDescription()
+{
+ if ( !m_bDescriptionDirty )
+ return;
+
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ m_bDescriptionDirty = false;
+
+ enum { kAttribBufferSize = 4 * 1024 };
+ wchar_t wszAttribBuffer[ kAttribBufferSize ] = L"";
+
+ enum { kCollectionBufferSize = 4 * 1024 };
+ wchar_t wszCollectionListBuffer[kCollectionBufferSize] = L"";
+
+ wchar_t wszCollectionNameBuffer[512] = L"";
+
+ if ( !m_bNameOnly )
+ {
+ const CEconItemDescription *pDescription = m_ItemData.GetDescription();
+ if ( pDescription )
+ {
+ unsigned int unWrittenLines = 0;
+ unsigned int unWrittenCollectionLines = 0;
+ for ( unsigned int i = 0; i < pDescription->GetLineCount(); i++ )
+ {
+ const econ_item_description_line_t& line = pDescription->GetLine(i);
+
+ // m_bSpecialAttributesOnly, only show purple and orange text, ignore rest
+ if ( m_bSpecialAttributesOnly )
+ {
+ if ( line.eColor == ATTRIB_COL_UNUSUAL || line.eColor == ATTRIB_COL_STRANGE )
+ {
+ V_wcscat_safe( wszAttribBuffer, unWrittenLines++ == 0 ? L"" : L"\n" ); // add empty lines everywhere except before the first line
+ V_wcscat_safe( wszAttribBuffer, line.sText.Get() );
+ }
+ }
+ else if ( ( line.unMetaType & kDescLineFlag_CollectionName ) != 0 )
+ {
+ // Ignore name spacers
+ if ( !( line.unMetaType & kDescLineFlag_Empty) )
+ {
+ V_wcscat_safe( wszCollectionNameBuffer, line.sText.Get() );
+ }
+ }
+ else if ( ( line.unMetaType & kDescLineFlag_Collection ) != 0 )
+ {
+ V_wcscat_safe( wszCollectionListBuffer, unWrittenCollectionLines++ == 0 ? L"" : L"\n" ); // add empty lines everywhere except before the first line
+ V_wcscat_safe( wszCollectionListBuffer, line.sText.Get() );
+ }
+ else if ( (line.unMetaType & kDescLineFlag_Name ) == 0 )
+ {
+ V_wcscat_safe( wszAttribBuffer, unWrittenLines++ == 0 ? L"" : L"\n" ); // add empty lines everywhere except before the first line
+ V_wcscat_safe( wszAttribBuffer, line.sText.Get() );
+ }
+ }
+
+ // If we have an unknown name, we should try to rebuild for "awhile"
+ m_bDescriptionDirty |= pDescription->HasUnknownPlayer();
+ }
+ }
+
+ if( m_pMainContentContainer )
+ {
+ m_pMainContentContainer->SetDialogVariable( "attriblist", wszAttribBuffer );
+ m_pMainContentContainer->SetDialogVariable( "collectionname", wszCollectionNameBuffer );
+ m_pMainContentContainer->SetDialogVariable( "collectionlist", wszCollectionListBuffer );
+ m_pMainContentContainer->SetDialogVariable( "itemname", m_ItemData.GetItemName() );
+ }
+
+ if ( m_pItemNameLabel )
+ {
+ // Set the name to the quality color
+ // Rarity Econ Colorization
+ EEconItemQuality eQuality = (EEconItemQuality)m_ItemData.GetItemQuality();
+ if ( GetItemSchema()->GetRarityColor( m_ItemData.GetItemDefinition()->GetRarity() ) && eQuality != AE_SELFMADE )
+ {
+ m_pItemNameLabel->SetColorStr( GetItemSchema()->GetRarityColor( m_ItemData.GetItemDefinition()->GetRarity() ) );
+ }
+ else
+ {
+ const char *pszQualityColorString = EconQuality_GetColorString( eQuality );
+ if ( m_ItemData.IsValid() && !m_bStandardTextColor && pszQualityColorString )
+ {
+ m_pItemNameLabel->SetColorStr( pszQualityColorString );
+ }
+ else
+ {
+ m_pItemNameLabel->SetColorStr( m_OrgItemTextColor );
+ }
+ }
+ m_pItemNameLabel->SetVisible( !m_bAttribOnly );
+ }
+
+ if ( m_pItemAttribLabel )
+ {
+ m_pItemAttribLabel->SetVisible( !m_bNameOnly );
+ }
+
+ bool bCollectionVisible = m_bHideCollectionPanel ? false : !m_bNameOnly;
+
+ if ( m_pItemCollectionNameLabel )
+ {
+ m_pItemCollectionNameLabel->SetVisible( bCollectionVisible );
+ }
+ if ( m_pItemCollectionListLabel )
+ {
+ m_pItemCollectionListLabel->SetVisible( bCollectionVisible );
+ }
+ if ( m_pItemCollectionHighlight )
+ {
+ m_pItemCollectionHighlight->SetVisible( bCollectionVisible );
+ }
+
+ InvalidateLayout( true );
+
+ // Now that we've built the attribute description, give the attribute colors to our label
+ if ( m_pItemAttribLabel && !m_bNameOnly && m_pItemAttribLabel->GetTextImage() && m_ItemData.GetDescription() )
+ {
+ const CEconItemDescription *pDescription = m_ItemData.GetDescription();
+ vgui::TextImage *pAttrTextImage = m_pItemAttribLabel->GetTextImage();
+ pAttrTextImage->ClearColorChangeStream();
+
+ vgui::TextImage *pCollectionNameTextImage = m_pItemCollectionNameLabel ? m_pItemCollectionNameLabel->GetTextImage() : NULL;
+ if ( pCollectionNameTextImage )
+ pCollectionNameTextImage->ClearColorChangeStream();
+
+ vgui::TextImage *pCollectionListTextImage = m_pItemCollectionListLabel ? m_pItemCollectionListLabel->GetTextImage() : NULL;
+ if ( pCollectionListTextImage )
+ pCollectionListTextImage->ClearColorChangeStream();
+
+ vgui::IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+
+ Color prevAttrColor(0,0,0);
+ Color prevCollectionColor(0,0,0);
+ unsigned int unCurrentAttrTextStreamIndex = 0;
+ unsigned int unCurrentCollectionNameTextStreamIndex = 0;
+ unsigned int unCurrentCollectionListTextStreamIndex = 0;
+ int iCollectionLineCount = 0;
+
+ if ( m_pItemCollectionHighlight )
+ {
+ m_pItemCollectionHighlight->SetVisible( false );
+ }
+
+ for ( unsigned int i = 0; i < pDescription->GetLineCount(); i++ )
+ {
+ const econ_item_description_line_t& line = pDescription->GetLine(i);
+
+ // Ignore the name line, it was added above
+ if ( ( line.unMetaType & kDescLineFlag_Name ) != 0 )
+ {
+ continue;
+ }
+
+ // collection
+ int fontHeight = surface()->GetFontTall( m_pFontAttribSmall );
+ if ( ( line.unMetaType & (kDescLineFlag_Collection | kDescLineFlag_CollectionName | kDescLineFlag_CollectionCurrentItem ) ) != 0 && pCollectionNameTextImage && pCollectionListTextImage )
+ {
+ bool bIsCollectionName = ( line.unMetaType & kDescLineFlag_CollectionName ) != 0;
+ vgui::TextImage *pTextImage = bIsCollectionName ? pCollectionNameTextImage : pCollectionListTextImage;
+ unsigned int &unCurrentCollectionTextStreamIndex = bIsCollectionName ? unCurrentCollectionNameTextStreamIndex : unCurrentCollectionListTextStreamIndex;
+
+ bool bIsCurrentItem = ( line.unMetaType & kDescLineFlag_CollectionCurrentItem ) != 0;
+ // use bg color as text color for current item for a better highlight
+ Color col = bIsCurrentItem ? Color( 0, 0, 0, 255 ) : pScheme->GetColor( GetColorNameForAttribColor( line.eColor ), Color( 255, 255, 255, 255 ) );
+ // Output a color change if necessary.
+ if ( i == 0 || prevCollectionColor != col )
+ {
+ pTextImage->AddColorChange( col, unCurrentCollectionTextStreamIndex );
+ prevCollectionColor = col;
+ }
+
+ unCurrentCollectionTextStreamIndex += StringFuncs<locchar_t>::Length( line.sText.Get() ) + 1; // add one character to deal with newlines
+
+ if ( bIsCollectionName )
+ {
+ continue;
+ }
+
+ // Current line highlight
+ if ( bIsCurrentItem && m_pItemCollectionHighlight )
+ {
+ // use text color as bg color for the current item for a better highlight
+ Color bgColor = pScheme->GetColor( GetColorNameForAttribColor( line.eColor ), Color( 255, 255, 255, 255 ) );
+
+ // Get the current ypos
+ int x, y;
+ m_pItemCollectionListLabel->GetPos( x, y );
+ m_pItemCollectionHighlight->SetPos( x, y + iCollectionLineCount * fontHeight );
+ m_pItemCollectionHighlight->SetBgColor( bgColor );
+ m_pItemCollectionHighlight->SetVisible( bCollectionVisible );
+ }
+ iCollectionLineCount++;
+ }
+ else
+ {
+ Color col = pScheme->GetColor( GetColorNameForAttribColor( line.eColor ), Color( 255, 255, 255, 255 ) );
+
+ // m_bSpecialAttributesOnly, only show purple and orange text, ignore rest
+ if ( m_bSpecialAttributesOnly )
+ {
+ if ( ( line.eColor != ATTRIB_COL_UNUSUAL && line.eColor != ATTRIB_COL_STRANGE ) )
+ {
+ continue;
+ }
+ }
+
+ // Output a color change if necessary.
+ if ( i == 0 || prevAttrColor != col )
+ {
+ pAttrTextImage->AddColorChange( col, unCurrentAttrTextStreamIndex );
+ prevAttrColor = col;
+ }
+ unCurrentAttrTextStreamIndex += StringFuncs<locchar_t>::Length( line.sText.Get() ) + 1; // add one character to deal with newlines
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::DirtyDescription()
+{
+ m_bDescriptionDirty = true;
+
+ if ( HasItem() )
+ GetItem()->OnAttributeValuesChanged();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CItemModelPanel::UpdateMatchesLabel()
+{
+ const IEconTool* pTool = m_ItemData.GetStaticData()->GetEconTool();
+
+ if( !pTool || Q_stricmp( m_ItemData.GetStaticData()->GetEconTool()->GetTypeName() , "dynamic_recipe") )
+ {
+ return false;
+ }
+
+ m_nRecipeMatchingIndex = 0;
+ m_mapMatchingAttributes.Purge();
+ SetNeedsToLoad();
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CItemModelPanel::UpdateQuantityLabel()
+{
+ if ( m_pItemQuantityLabel )
+ {
+ bool bVisible = m_bShowQuantity && m_ItemData.GetStaticData() != NULL;
+ if ( bVisible )
+ {
+ const IEconTool *pEconTool = m_ItemData.GetStaticData()->GetEconTool();
+ if ( pEconTool && pEconTool->ShouldDisplayQuantity( &m_ItemData ) )
+ {
+ wchar_t wszQuantity[16]=L"";
+ _snwprintf( wszQuantity, ARRAYSIZE( wszQuantity ), L"%i", m_ItemData.GetQuantity() );
+ m_pItemQuantityLabel->SetVisible( true );
+ m_pItemQuantityLabel->SetText( wszQuantity );
+ }
+ else
+ {
+ bVisible = false;
+ }
+ }
+ m_pItemQuantityLabel->SetVisible( bVisible );
+
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Simple utility function to allocate memory and duplicate a wide string
+ */
+inline wchar_t *CloneWString( const wchar_t *str )
+{
+ const int nLen = V_wcslen(str)+1;
+ wchar_t *cloneStr = new wchar_t [ nLen ];
+ const int nSize = nLen * sizeof( wchar_t );
+ V_wcsncpy( cloneStr, str, nSize );
+ return cloneStr;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::SetNoItemText( const wchar_t *pwszTitleOverride, const wchar_t *pwszAttribs, int iNegAttribsBegin )
+{
+ static CSchemaColorDefHandle pColorDef_DescAttribPositive( "desc_attrib_positive" );
+ static CSchemaColorDefHandle pColorDef_DescAttribNegative( "ItemAttribNegative" );
+
+ CleanupNoItemWChars();
+
+ m_pwcNoItemText = CloneWString( pwszTitleOverride );
+ m_pszNoItemText = NULL;
+
+ if ( pwszAttribs )
+ {
+ m_pwcNoItemAttrib = CloneWString( pwszAttribs );
+
+ if ( m_pItemAttribLabel && m_pItemAttribLabel->GetTextImage() )
+ {
+ m_pItemAttribLabel->GetTextImage()->ClearColorChangeStream();
+
+ if ( iNegAttribsBegin )
+ {
+ vgui::IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+
+ if ( pColorDef_DescAttribPositive )
+ {
+ Color col = pScheme->GetColor( pColorDef_DescAttribPositive->GetColorName(), Color(255,255,255,255) );
+ m_pItemAttribLabel->GetTextImage()->AddColorChange( col, 0 );
+ }
+
+ if ( pColorDef_DescAttribNegative )
+ {
+ Color col = pScheme->GetColor( pColorDef_DescAttribNegative->GetColorName(), Color(255,255,255,255) );
+ m_pItemAttribLabel->GetTextImage()->AddColorChange( col, iNegAttribsBegin );
+ }
+ }
+ }
+ }
+
+ if ( !HasItem() )
+ {
+ UpdatePanels();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::HideAllModifierIcons()
+{
+ if ( m_pPaintIcon )
+ {
+ m_pPaintIcon->SetVisible( false );
+ }
+ if ( m_pTF2Icon )
+ {
+ m_pTF2Icon->SetVisible( false );
+ }
+ if ( m_pItemEquippedLabel )
+ {
+ m_pItemEquippedLabel->SetVisible( false );
+ }
+ if ( m_pItemQuantityLabel )
+ {
+ m_pItemQuantityLabel->SetVisible( false );
+ }
+ if ( m_pVisionRestrictionImage )
+ {
+ m_pVisionRestrictionImage->SetVisible( false );
+ }
+ if ( m_pIsStrangeImage )
+ {
+ m_pIsStrangeImage->SetVisible( false );
+ }
+ if ( m_pIsUnusualImage )
+ {
+ m_pIsUnusualImage->SetVisible( false );
+ }
+ if ( m_pIsLoanerImage )
+ {
+ m_pIsLoanerImage->SetVisible( false );
+ }
+ if ( m_pSeriesLabel )
+ {
+ m_pSeriesLabel->SetVisible( false );
+ }
+ if ( m_pMatchesLabel )
+ {
+ m_pMatchesLabel->SetVisible( false );
+ }
+ if ( m_pItemEquippedLabel && m_bForceShowEquipped )
+ {
+ m_pItemEquippedLabel->SetVisible( m_bForceShowEquipped );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::UpdatePanels( void )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ if ( !m_pModelPanel )
+ return;
+
+ m_pModelPanel->SetModelHidden( m_bHideModel );
+
+ // By default we dont need a tick.
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+
+ // Default to loading icons
+ se_CurrentLoadingTask = LOADING_ICONS;
+
+ m_pModelPanel->SetItem( &m_ItemData );
+ // We need to load if our image isn't in memory already.
+ if ( !m_bHideModel && ( m_pModelPanel->IsImageNotLoaded() || m_pModelPanel->IsLoadingWeaponSkin() ) )
+ {
+ if ( m_pMainContentContainer )
+ {
+ m_pMainContentContainer->SetVisible( false );
+ }
+
+ // Show the spinner
+ if ( m_pLoadingSpinner )
+ {
+ m_pLoadingSpinner->SetVisible( true );
+ }
+
+ SetNeedsToLoad();
+ }
+ else
+ {
+ // hide the spinner
+ if ( m_pLoadingSpinner )
+ {
+ m_pLoadingSpinner->SetVisible( false );
+ }
+ }
+
+ if ( !HasItem() )
+ {
+ if ( m_bModelOnly )
+ {
+ if ( m_pItemNameLabel )
+ {
+ m_pItemNameLabel->SetVisible( false );
+ }
+ if ( m_pItemAttribLabel )
+ {
+ m_pItemAttribLabel->SetVisible( false );
+ }
+ }
+ else
+ {
+ if ( m_pItemNameLabel )
+ {
+ const wchar_t *wcNOText = NULL;
+ if ( m_pszNoItemText && m_pszNoItemText[0] )
+ {
+ wcNOText = g_pVGuiLocalize->Find( m_pszNoItemText );
+ }
+ else if ( m_pwcNoItemText )
+ {
+ wcNOText = m_pwcNoItemText;
+ }
+
+ if ( wcNOText && wcNOText[0] )
+ {
+ if ( m_pMainContentContainer )
+ m_pMainContentContainer->SetDialogVariable( "itemname", wcNOText );
+ m_pItemNameLabel->SetVisible( true );
+ m_pItemNameLabel->SetColorStr( m_NoItemTextColor );
+
+ m_pItemNameLabel->InvalidateLayout(true,true);
+ if ( m_iForceTextSize && m_iForceTextSize <= 3 )
+ {
+ // Leave center wrap on if the noitem text is getting to use the whole panel
+ if ( !m_bNoItemFullPanel )
+ {
+ m_pItemNameLabel->SetCenterWrap( false );
+ }
+
+ switch ( m_iForceTextSize )
+ {
+ case 1:
+ m_pItemNameLabel->SetFont( m_pFontNameLarge );
+ break;
+
+ case 2:
+ m_pItemNameLabel->SetFont( m_pFontNameSmall );
+ break;
+
+ case 3:
+ m_pItemNameLabel->SetFont( m_pFontNameSmallest );
+ break;
+ }
+ }
+ }
+ else
+ {
+ m_pItemNameLabel->SetVisible( false );
+ }
+ }
+
+ if ( !m_bNameOnly && m_pwcNoItemAttrib )
+ {
+ if ( m_pMainContentContainer )
+ m_pMainContentContainer->SetDialogVariable( "attriblist", m_pwcNoItemAttrib );
+ if ( m_pItemAttribLabel && !m_pItemAttribLabel->IsVisible() )
+ {
+ m_pItemAttribLabel->SetVisible( true );
+ }
+ }
+ else
+ {
+ if ( m_pItemAttribLabel && m_pItemAttribLabel->IsVisible() )
+ {
+ m_pItemAttribLabel->SetVisible( false );
+ }
+ }
+ }
+
+ HideAllModifierIcons();
+ return;
+ }
+
+ if ( m_bHideModifierIcons )
+ {
+ HideAllModifierIcons();
+ return;
+ }
+
+ if ( m_pPaintIcon )
+ {
+ m_pPaintIcon->SetVisible( false );
+ if ( !m_bHideModel && !m_bHidePaintIcon )
+ {
+ // Empty out our list of paint colors. We may or may not put things back in -- an empty
+ // list at the end means "don't draw the paint icon".
+ m_pPaintIcon->m_colPaintColors.RemoveAll();
+
+ // Fetch custom texture, if any
+ m_pPaintIcon->m_hUGCId = m_ItemData.GetCustomUserTextureID();
+ if ( m_pPaintIcon->m_hUGCId != 0 )
+ m_pPaintIcon->SetVisible( true );
+
+ // Don't show paint icons on any tools, their icon contains the color
+ const bool bIsEconTool = m_ItemData.GetItemDefinition()->IsTool();
+
+ // Has the item been painted?
+ int iRGB0 = m_ItemData.GetModifiedRGBValue( false ),
+ iRGB1 = m_ItemData.GetModifiedRGBValue( true );
+
+ if ( !bIsEconTool && (iRGB0 != 0 || iRGB1 != 0))
+ {
+ m_pPaintIcon->SetVisible( true );
+ m_pPaintIcon->m_colPaintColors.AddToTail( Color( clamp( (iRGB0 & 0xFF0000) >> 16, 0, 255 ), clamp( (iRGB0 & 0xFF00) >> 8, 0, 255 ), clamp( (iRGB0 & 0xFF), 0, 255 ), 255 ) );
+ if ( iRGB0 != iRGB1 )
+ {
+ m_pPaintIcon->m_colPaintColors.AddToTail( Color( clamp( (iRGB1 & 0xFF0000) >> 16, 0, 255 ), clamp( (iRGB1 & 0xFF00) >> 8, 0, 255 ), clamp( (iRGB1 & 0xFF), 0, 255 ), 255 ) );
+ }
+ }
+ }
+ }
+
+ if ( m_pTF2Icon )
+ {
+ if ( m_bHideModel || m_bHidePaintIcon )
+ {
+ m_pTF2Icon->SetVisible( false );
+ }
+ else
+ {
+ m_pTF2Icon->SetVisible( m_ItemData.GetSOCData() && m_ItemData.GetSOCData()->IsForeign() );
+ }
+ }
+
+ if ( m_bNoItemFullPanel )
+ {
+ // If we're a noitem-fullpanel mode, we don't show strings when we have an item.
+ m_pItemNameLabel->SetVisible( false );
+ m_pItemAttribLabel->SetVisible( false );
+ }
+ else if ( m_bModelOnly )
+ {
+ if ( m_pItemNameLabel )
+ {
+ m_pItemNameLabel->SetVisible( false );
+ }
+ if ( m_pItemAttribLabel )
+ {
+ m_pItemAttribLabel->SetVisible( false );
+ }
+ }
+ else
+ {
+ // deferred description loading
+ m_bDescriptionDirty = true;
+ SetNeedsToLoad();
+ if ( m_pMainContentContainer )
+ m_pMainContentContainer->SetDialogVariable( "itemname", "" );
+ }
+
+ if ( m_pItemEquippedLabel )
+ {
+ m_pItemEquippedLabel->SetVisible( m_bForceShowEquipped || (m_bShowEquipped && IsEquipped()) );
+ }
+
+ // Hide all of these labels
+ if( m_pMatchesLabel )
+ {
+ m_pMatchesLabel->SetVisible( false );
+ }
+
+ if( m_pSeriesLabel )
+ {
+ m_pSeriesLabel->SetVisible( false );
+ }
+
+ if( m_pItemQuantityLabel )
+ {
+ m_pItemQuantityLabel->SetVisible( false );
+ }
+
+
+ // Update that number in the top right
+ if ( !UpdateMatchesLabel() )
+ {
+ if ( !UpdateSeriesLabel() )
+ {
+ UpdateQuantityLabel();
+ }
+ }
+
+ if ( m_pVisionRestrictionImage )
+ {
+ int nVisionFilterFlags = 0;
+ const CEconItemDefinition *pData = m_ItemData.GetItemDefinition();
+ if ( !m_bModelOnly && pData )
+ {
+ nVisionFilterFlags = pData->GetVisionFilterFlags();
+
+ // Add support for all the holidays and "vision" mode restrictions
+ if ( pData->GetHolidayRestriction() )
+ {
+ int iHolidayRestriction = UTIL_GetHolidayForString( pData->GetHolidayRestriction() );
+ switch ( iHolidayRestriction )
+ {
+ default:
+ case kHoliday_None:
+ case kHoliday_TFBirthday:
+ case kHoliday_Christmas:
+ case kHoliday_Valentines:
+ case kHoliday_MeetThePyro:
+ case kHoliday_AprilFools:
+ case kHoliday_EOTL:
+ case kHoliday_CommunityUpdate:
+ break;
+
+ case kHoliday_Halloween:
+ case kHoliday_FullMoon:
+ case kHoliday_HalloweenOrFullMoon:
+ case kHoliday_HalloweenOrFullMoonOrValentines:
+ #ifdef TF_CLIENT_DLL
+ nVisionFilterFlags |= TF_VISION_FILTER_HALLOWEEN;
+ #endif
+ break;
+ }
+ }
+ }
+
+ switch ( nVisionFilterFlags )
+ {
+ default:
+ AssertMsg1( false, "Unexpected vision restriction flags %d", nVisionFilterFlags );
+ case 0:
+ m_pVisionRestrictionImage->SetVisible( false );
+ break;
+#ifdef TF_CLIENT_DLL
+ case 1:
+ m_pVisionRestrictionImage->SetImage( "viewmode_pyrovision" );
+ m_pVisionRestrictionImage->SetVisible( true );
+ break;
+ case 2:
+ // Check if most players who have not specifically opted in will see the item.
+ if ( TFGameRules() ? TFGameRules()->IsHolidayActive( kHoliday_HalloweenOrFullMoon ) : TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) )
+ {
+ m_pVisionRestrictionImage->SetImage( "viewmode_spooky" );
+ }
+ else
+ {
+ m_pVisionRestrictionImage->SetImage( "viewmode_spooky_off" );
+ }
+ m_pVisionRestrictionImage->SetVisible( true );
+ break;
+ case 4:
+ m_pVisionRestrictionImage->SetVisible( false );
+ break;
+#endif
+ }
+ }
+
+ // Strange Icon
+ static CSchemaAttributeDefHandle pAttrDef_StatTrakModule( "weapon_uses_stattrak_module" );
+ if ( m_pIsStrangeImage )
+ {
+ m_pIsStrangeImage->SetVisible( false );
+
+ if ( !m_bIsMouseOverPanel )
+ {
+ // Allow for already strange items
+ bool bIsStrange = false;
+ if ( m_ItemData.GetQuality() == AE_STRANGE )
+ {
+ bIsStrange = true;
+ }
+
+ if ( !bIsStrange )
+ {
+ // Go over the attributes of the item, if it has any strange attributes the item is strange and don't apply
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ if ( m_ItemData.FindAttribute( GetKillEaterAttr_Score( i ) ) )
+ {
+ bIsStrange = true;
+ break;
+ }
+ }
+ }
+ if ( bIsStrange )
+ {
+ if ( pAttrDef_StatTrakModule && m_ItemData.FindAttribute( pAttrDef_StatTrakModule ) )
+ {
+ m_pIsStrangeImage->SetImage( "viewmode_statclock" );
+ }
+ else
+ {
+ m_pIsStrangeImage->SetImage( "viewmode_strange" );
+
+ }
+ m_pIsStrangeImage->SetVisible( true );
+ }
+ }
+ }
+
+ // Unusual Icon
+ if ( m_pIsUnusualImage )
+ {
+ m_pIsUnusualImage->SetVisible( false );
+
+ static CSchemaAttributeDefHandle pAttrDef_ParticleEffect( "attach particle effect" );
+ static CSchemaAttributeDefHandle pAttrDef_TauntParticle( "on taunt attach particle index" );
+ if ( pAttrDef_ParticleEffect && pAttrDef_TauntParticle && !m_bIsMouseOverPanel )
+ {
+ // Cant use quality cause of old legacy items. Quality is just a quick test
+ if ( m_ItemData.FindAttribute( pAttrDef_ParticleEffect ) || m_ItemData.FindAttribute( pAttrDef_TauntParticle ) )
+ {
+ m_pIsUnusualImage->SetImage( "viewmode_unusual" );
+ m_pIsUnusualImage->SetVisible( true );
+ }
+ }
+ }
+
+ if ( m_pIsLoanerImage )
+ {
+ m_pIsLoanerImage->SetVisible( false );
+ if ( !m_bIsMouseOverPanel && GetAssociatedQuestItemID( &m_ItemData ) != INVALID_ITEM_ID )
+ {
+ m_pIsLoanerImage->SetImage( "viewmode_loaner" );
+ m_pIsLoanerImage->SetVisible( true );
+ }
+ }
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CItemModelPanel::IsEquipped( void )
+{
+ if ( !HasItem() )
+ return false;
+
+ return m_ItemData.IsEquipped();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::SetGreyedOut( const char *pszGreyedOutReason )
+{
+ m_pszGreyedOutReason = pszGreyedOutReason;
+ if ( m_pModelPanel )
+ {
+ m_pModelPanel->SetGreyedOut( m_pszGreyedOutReason != NULL );
+ }
+ UpdateEquippedLabel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CItemModelPanel::HasItem( void )
+{
+ return m_ItemData.IsValid();
+}
+
+void CItemModelPanel::SetModelIsHidden( bool bHideModel )
+{
+ m_bHideModel = bHideModel;
+ if ( m_pModelPanel )
+ {
+ m_pModelPanel->SetModelHidden( bHideModel );
+ }
+}
+
+void CItemModelPanel::OnTick()
+{
+ bool bStillWorking = LoadData();
+ if ( m_pContainedItemPanel )
+ {
+ bStillWorking |= m_pContainedItemPanel->LoadData();
+ }
+
+ // If we're done working, we dont need to tick anymore
+ if ( !bStillWorking )
+ {
+ LoadDataCompleted();
+ }
+
+ BaseClass::OnTick();
+}
+
+void CItemModelPanel::SetNeedsToLoad()
+{
+ vgui::ivgui()->AddTickSignalToHead( GetVPanel() );
+}
+
+bool CItemModelPanel::LoadData()
+{
+ // Different frame?
+ if ( sm_nCurrentDecriptionUpdateFrame != gpGlobals->framecount )
+ {
+ // Reset
+ sm_nCurrentDecriptionUpdateFrame = gpGlobals->framecount;
+ sm_flLoadingTimeThisFrame = 0.f;
+
+ // Figure out which loading we're going to do. We want to load
+ // certain things sooner (visual things, ie icons) than we start
+ // figuring out recipe matches.
+ eLoadingType_t type = NUM_LOADING_TYPES;
+ for( int i=0; i < NUM_LOADING_TYPES; ++i )
+ {
+ if ( sai_NumLoadingRequests[i] > 0 && eLoadingType_t(i) < type )
+ {
+ type = eLoadingType_t(i);
+ }
+ sai_NumLoadingRequests[i] = 0;
+ }
+
+ se_CurrentLoadingTask = type;
+ }
+
+ bool bStillWorking = CheckRecipeMatches();
+
+ if ( !m_bHideModel && m_pModelPanel )
+ {
+ bool bImageLoaded = true;
+ bool bLoadingWeaponSkin = m_pModelPanel->IsLoadingWeaponSkin();
+ bool bLoadingBackpackIcon = m_pModelPanel->IsImageNotLoaded();
+
+ if ( bLoadingWeaponSkin || bLoadingBackpackIcon )
+ {
+ // We still need to load icons
+ sai_NumLoadingRequests[LOADING_ICONS]++;
+ if ( sm_flLoadingTimeThisFrame < tf_time_loading_item_panels.GetFloat() && se_CurrentLoadingTask == LOADING_ICONS )
+ {
+ float flTime = Plat_FloatTime();
+
+ // no need to load texture if we're doing composite weapon skin
+ if ( bLoadingWeaponSkin )
+ {
+ g_pMatSystemSurface->BeginSkinCompositionPainting();
+ m_pModelPanel->Paint();
+ g_pMatSystemSurface->EndSkinCompositionPainting();
+ }
+
+ if ( bLoadingBackpackIcon )
+ {
+ m_pModelPanel->LoadInventoryImage();
+ }
+
+ // Accumulate time
+ sm_flLoadingTimeThisFrame += ( Plat_FloatTime() - flTime );
+ }
+
+ bStillWorking = m_pModelPanel->IsLoadingWeaponSkin() || m_pModelPanel->IsImageNotLoaded();
+ bImageLoaded = !bStillWorking;
+ }
+
+ // Hide the spinner and show the main container
+ if ( bImageLoaded )
+ {
+ if ( m_pMainContentContainer && !m_pMainContentContainer->IsVisible() )
+ {
+ m_pMainContentContainer->SetVisible( true );
+ }
+
+ if ( m_pLoadingSpinner && m_pLoadingSpinner->IsVisible() )
+ {
+ m_pLoadingSpinner->SetVisible( false );
+ }
+ }
+ }
+
+ if ( m_bDescriptionDirty && !IsContainedItem() )
+ {
+ // We still need to load our description
+ sai_NumLoadingRequests[LOADING_DESCRIPTIONS]++;
+ // Check if we're clear to update. We only want to eat up a little slice of time.
+ if ( sm_flLoadingTimeThisFrame < tf_time_loading_item_panels.GetFloat() && se_CurrentLoadingTask == LOADING_DESCRIPTIONS )
+ {
+ float flTime = Plat_FloatTime();
+ // Update!
+ UpdateDescription();
+
+ // Accumulate time
+ sm_flLoadingTimeThisFrame += ( Plat_FloatTime() - flTime );
+ }
+
+ bStillWorking |= m_bDescriptionDirty;
+ }
+
+ return bStillWorking;
+}
+
+void CItemModelPanel::LoadDataCompleted()
+{
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::SetActAsButton( bool bClickable, bool bMouseOver )
+{
+ m_bClickable = bClickable;
+ m_bMouseOver = bMouseOver;
+
+ SetMouseInputEnabled( m_bClickable || m_bMouseOver );
+}
+
+void CItemModelPanel::NavigateTo()
+{
+ BaseClass::NavigateTo();
+
+ if ( IsPC() )
+ {
+ RequestFocus( 0 );
+ }
+}
+
+void CItemModelPanel::NavigateFrom()
+{
+ BaseClass::NavigateFrom();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::OnCursorEntered( void )
+{
+ if ( !m_bMouseOver )
+ return;
+
+ if ( m_bShouldSendPanelEnterExits )
+ {
+ PostActionSignal( new KeyValues("ItemPanelEntered") );
+ }
+
+ if ( IsEnabled() && !IsSelected() )
+ {
+ NavigateTo();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::OnCursorExited( void )
+{
+ if ( !m_bMouseOver )
+ return;
+
+ if ( m_bShouldSendPanelEnterExits )
+ {
+ PostActionSignal( new KeyValues("ItemPanelExited") );
+ }
+
+ if ( IsSelected() )
+ {
+ NavigateFrom();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+extern ISoundEmitterSystemBase *soundemitterbase;
+void CItemModelPanel::OnMousePressed(vgui::MouseCode code)
+{
+ if ( code == MOUSE_RIGHT )
+ {
+ PostActionSignal( new KeyValues("ItemPanelMouseRightRelease") );
+ }
+
+ if ( !m_bClickable || code != MOUSE_LEFT )
+ return;
+
+ PostActionSignal( new KeyValues("ItemPanelMousePressed") );
+
+ // audible feedback
+ const char *soundFilename = "ui/buttonclick.wav";
+
+ if ( m_bUseItemSounds )
+ {
+ CEconItemView *item = GetItem();
+ if ( item )
+ {
+ soundFilename = item->GetDefinitionString( "mouse_pressed_sound", "ui/item_default_pickup.wav" );
+ }
+ }
+
+ const char *pszSound = UTIL_GetRandomSoundFromEntry( soundFilename );
+ if ( pszSound && pszSound[0] )
+ {
+ vgui::surface()->PlaySound( pszSound );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::OnMouseReleased(vgui::MouseCode code)
+{
+ if ( !m_bClickable || code != MOUSE_LEFT )
+ return;
+
+ PostActionSignal( new KeyValues("ItemPanelMouseReleased") );
+
+ // audible feedback
+ // we're not using item sounds here because they are better handled by the drag/drop code elsewhere
+ if ( !m_bUseItemSounds )
+ {
+ vgui::surface()->PlaySound( "ui/buttonclickrelease.wav" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::OnMouseDoublePressed(vgui::MouseCode code)
+{
+ if ( !m_bClickable || code != MOUSE_LEFT )
+ return;
+
+ PostActionSignal( new KeyValues("ItemPanelMouseDoublePressed") );
+
+ // audible feedback
+ const char *soundFilename = "ui/buttonclickrelease.wav";
+
+ if ( m_bUseItemSounds )
+ {
+ CEconItemView *item = GetItem();
+ if ( item )
+ {
+ soundFilename = item->GetDefinitionString( "mouse_double_pressed_sound", "ui/item_default_drop.wav" );
+ }
+ }
+
+ vgui::surface()->PlaySound( soundFilename );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::OnCursorMoved( int x, int y )
+{
+ if ( !m_bClickable )
+ return;
+
+ // Add our own xpos/ypos offset
+ int iXPos;
+ int iYPos;
+ GetPos( iXPos, iYPos );
+ PostActionSignal( new KeyValues("ItemPanelCursorMoved", "x", x + iXPos, "y", y + iYPos) );
+}
+
+void CItemModelPanel::OnKeyCodePressed( vgui::KeyCode code )
+{
+ BaseClass::OnKeyCodePressed( code );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::OnCommand( const char *command )
+{
+ if ( FStrEq( command, "sellitem" ) )
+ {
+ if ( HasItem() && steamapicontext && steamapicontext->SteamFriends() && steamapicontext->SteamUtils() )
+ {
+ const char *pszPrefix = "";
+ if ( GetUniverse() == k_EUniverseBeta )
+ {
+ pszPrefix = "beta.";
+ }
+ uint32 nAssetContext = 2; // k_EEconContextBackpack
+ char szURL[512];
+ V_snprintf( szURL, sizeof(szURL), "http://%ssteamcommunity.com/my/inventory/?sellOnLoad=1#%d_%d_%llu", pszPrefix, engine->GetAppID(), nAssetContext, GetItem()->GetItemID() );
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( szURL );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::UpdateEquippedLabel( void )
+{
+ if ( !m_pItemEquippedLabel )
+ return;
+
+ if ( IsGreyedOut() )
+ {
+ m_pItemEquippedLabel->SetFgColor( Color(96,96,96,255) );
+ }
+ else
+ {
+ m_pItemEquippedLabel->SetFgColor( Color(200,80,60,255) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanel::SetSkin( int iSkin )
+{
+ if ( m_pModelPanel )
+ {
+ m_pModelPanel->SetSkin( iSkin );
+ }
+}
+
+
+itempanel_tooltippos_t g_iTooltipStrategies[NUM_IPTTP_STRATEGIES][NUM_POSITIONS_PER_STRATEGY] =
+{
+ { IPTTP_LEFT, IPTTP_LEFT_CENTERED, IPTTP_ABOVE, IPTTP_BELOW, IPTTP_RIGHT_CENTERED, IPTTP_RIGHT }, // IPTTP_LEFT_SIDE
+ { IPTTP_RIGHT, IPTTP_RIGHT_CENTERED, IPTTP_ABOVE, IPTTP_BELOW, IPTTP_LEFT_CENTERED, IPTTP_LEFT }, // IPTTP_RIGHT_SIDE
+ { IPTTP_ABOVE, IPTTP_LEFT_CENTERED, IPTTP_RIGHT_CENTERED, IPTTP_LEFT, IPTTP_RIGHT, IPTTP_ABOVE }, // IPTTP_TOP_SIDE
+ { IPTTP_BELOW, IPTTP_LEFT_CENTERED, IPTTP_RIGHT_CENTERED, IPTTP_LEFT, IPTTP_RIGHT, IPTTP_ABOVE }, // IPTTP_BOTTOM_SIDE
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemModelPanelToolTip::CItemModelPanelToolTip( vgui::Panel *parent, const char *text )
+: vgui::BaseTooltip( parent, text )
+, m_pMouseOverItemPanel( NULL )
+, m_iPositioningStrategy( IPTTP_BOTTOM_SIDE )
+{
+ m_hCurrentPanel = NULL;
+ SetTooltipDelay( 100 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanelToolTip::GetPosition( itempanel_tooltippos_t iTooltipPosition, CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos )
+{
+ switch ( iTooltipPosition )
+ {
+ case IPTTP_LEFT:
+ *iXPos = (iItemX - m_pMouseOverItemPanel->GetWide() + XRES(18));
+ *iYPos = iItemY - YRES(7);
+ break;
+ case IPTTP_RIGHT:
+ *iXPos = (iItemX + pItemPanel->GetWide() - XRES(20));
+ *iYPos = iItemY - YRES(7);
+ break;
+ case IPTTP_LEFT_CENTERED:
+ *iXPos = (iItemX - m_pMouseOverItemPanel->GetWide()) - XRES(4);
+ *iYPos = (iItemY - (m_pMouseOverItemPanel->GetTall() * 0.5));
+ break;
+ case IPTTP_RIGHT_CENTERED:
+ *iXPos = (iItemX + pItemPanel->GetWide()) + XRES(4);
+ *iYPos = (iItemY - (m_pMouseOverItemPanel->GetTall() * 0.5));
+ break;
+ case IPTTP_ABOVE:
+ *iXPos = (iItemX + (pItemPanel->GetWide() * 0.5)) - (m_pMouseOverItemPanel->GetWide() * 0.5);
+ *iYPos = (iItemY - m_pMouseOverItemPanel->GetTall() - YRES(4));
+ break;
+ case IPTTP_BELOW:
+ *iXPos = (iItemX + (pItemPanel->GetWide() * 0.5)) - (m_pMouseOverItemPanel->GetWide() * 0.5);
+ *iYPos = (iItemY + pItemPanel->GetTall() + YRES(4));
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CItemModelPanelToolTip::ValidatePosition( CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos )
+{
+ bool bSucceeded = true;
+
+ // Make sure the popup stays onscreen.
+ if ( *iXPos < 0 )
+ {
+ *iXPos = 0;
+ }
+ else if ( (*iXPos + m_pMouseOverItemPanel->GetWide()) > m_pParentPanel->GetWide() )
+ {
+ int iXPosNew = m_pParentPanel->GetWide() - m_pMouseOverItemPanel->GetWide();
+ // make sure it is still on the screen
+ if ( iXPosNew >= 0 )
+ {
+ *iXPos = iXPosNew;
+ }
+ else
+ {
+ bSucceeded = false;
+ }
+ }
+
+ if ( *iYPos < 0 )
+ {
+ *iYPos = 0;
+ }
+ else if ( (*iYPos + m_pMouseOverItemPanel->GetTall() + YRES(32)) > m_pParentPanel->GetTall() )
+ {
+ // Move it up above our item
+ int iYPosNew = iItemY - m_pMouseOverItemPanel->GetTall() - YRES(4);
+ // make sure it is still on the screen
+ if ( iYPosNew >= 0 )
+ {
+ *iYPos = iYPosNew;
+ }
+ else
+ {
+ bSucceeded = false;
+ }
+ }
+
+ if ( bSucceeded )
+ {
+ // We also fail if moving it to keep it on screen moved it over the item panel itself
+ Vector2D vecToolTipMin, vecToolTipMax, vecItemMin, vecItemMax;
+ vecToolTipMin.x = *iXPos;
+ vecToolTipMin.y = *iYPos;
+ vecToolTipMax.x = vecToolTipMin.x + m_pMouseOverItemPanel->GetWide();
+ vecToolTipMax.y = vecToolTipMin.y + m_pMouseOverItemPanel->GetTall();
+
+ vecItemMin.x = iItemX;
+ vecItemMin.y = iItemY;
+ vecItemMax.x = vecItemMin.x + m_hCurrentPanel->GetWide();
+ vecItemMax.y = vecItemMin.y + m_hCurrentPanel->GetTall();
+
+ bSucceeded = !( vecToolTipMin.x < vecItemMax.x && vecToolTipMax.x > vecItemMin.x && vecToolTipMin.y < vecItemMax.y && vecToolTipMax.y > vecItemMin.y );
+ }
+
+ return bSucceeded;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanelToolTip::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( !ShouldLayout() )
+ return;
+
+ _isDirty = false;
+
+ CItemModelPanel *pItemPanel = m_hCurrentPanel.Get();
+ if ( m_pMouseOverItemPanel && pItemPanel )
+ {
+ CEconItemView *pItem = pItemPanel->GetItem();
+ if ( pItem && pItemPanel->ShouldShowTooltip() /*&& !IsIgnoringItemPanelEnters()*/ )
+ {
+ m_pMouseOverItemPanel->SetGreyedOut( pItemPanel->GetGreyedOutReason() );
+ m_pMouseOverItemPanel->SetItem( pItem );
+ m_pMouseOverItemPanel->DirtyDescription(); // Force rebuilding the description when we first display
+ m_pMouseOverItemPanel->UpdateDescription();
+ m_pMouseOverItemPanel->HideContainedItemPanel();
+ m_pMouseOverItemPanel->InvalidateLayout(true);
+
+ int x,y;
+
+ // If the panel is somewhere in a derived class, we need to get its position in our space
+ if ( pItemPanel->GetParent() != m_pMouseOverItemPanel->GetParent() )
+ {
+ int iItemAbsX, iItemAbsY;
+ vgui::ipanel()->GetAbsPos( pItemPanel->GetVPanel(), iItemAbsX, iItemAbsY );
+ int iParentAbsX, iParentAbsY;
+ vgui::ipanel()->GetAbsPos( m_pMouseOverItemPanel->GetParent()->GetVPanel(), iParentAbsX, iParentAbsY );
+
+ x = (iItemAbsX - iParentAbsX);
+ y = (iItemAbsY - iParentAbsY);
+ }
+ else
+ {
+ pItemPanel->GetPos( x, y );
+ }
+
+ int iXPos = 0;
+ int iYPos = 0;
+
+ // Loop through the positions in our strategy, and hope we find a valid spot
+ for ( int i = 0; i < NUM_POSITIONS_PER_STRATEGY; i++ )
+ {
+ itempanel_tooltippos_t iPos = g_iTooltipStrategies[m_iPositioningStrategy][i];
+ GetPosition( iPos, pItemPanel, x, y, &iXPos, &iYPos );
+
+ if ( ValidatePosition( pItemPanel, x, y, &iXPos, &iYPos ) )
+ break;
+ }
+
+ m_pMouseOverItemPanel->SetPos( iXPos, iYPos );
+ m_pMouseOverItemPanel->SetVisible( true );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanelToolTip::ShowTooltip( Panel *currentPanel )
+{
+ if ( m_pMouseOverItemPanel && currentPanel != m_hCurrentPanel.Get() )
+ {
+ CItemModelPanel *pItemPanel = assert_cast<CItemModelPanel *>(currentPanel);
+ m_hCurrentPanel.Set( pItemPanel );
+ pItemPanel->PostActionSignal( new KeyValues("ItemPanelEntered") );
+ vgui::surface()->PlaySound( "ui/item_info_mouseover.wav" );
+
+ m_pMouseOverItemPanel->HideContainedItemPanel();
+ }
+ BaseClass::ShowTooltip( currentPanel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemModelPanelToolTip::HideTooltip()
+{
+ if ( m_pMouseOverItemPanel )
+ {
+ m_pMouseOverItemPanel->SetVisible( false );
+ }
+
+ if ( m_hCurrentPanel )
+ {
+ m_hCurrentPanel.Get()->PostActionSignal( new KeyValues("ItemPanelExited") );
+ m_hCurrentPanel = NULL;
+ }
+}
diff --git a/game/client/econ/item_model_panel.h b/game/client/econ/item_model_panel.h
new file mode 100644
index 0000000..5494448
--- /dev/null
+++ b/game/client/econ/item_model_panel.h
@@ -0,0 +1,519 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ITEM_MODEL_PANEL_H
+#define ITEM_MODEL_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Panel.h>
+#include <vgui_controls/Frame.h>
+#include <vgui_controls/Tooltip.h>
+#include "basemodel_panel.h"
+#include "basemodelpanel.h"
+#include "econ_controls.h"
+
+#define ITEM_MODEL_IMAGE_CACHE_SIZE 3
+
+#define ATTRIB_LABEL_INDENT XRES(5)
+
+class CEconItemView;
+class CItemModelPanel;
+class CItemMaterialCustomizationIconPanel;
+class CIconRenderReceiver;
+class CEmbeddedItemModelPanel;
+
+enum itempanel_tooltippos_t
+{
+ IPTTP_LEFT,
+ IPTTP_RIGHT,
+ IPTTP_LEFT_CENTERED,
+ IPTTP_RIGHT_CENTERED,
+ IPTTP_ABOVE,
+ IPTTP_BELOW,
+
+ NUM_POSITIONS_PER_STRATEGY
+};
+
+
+enum itempanel_tooltip_strategies_t
+{
+ IPTTP_LEFT_SIDE,
+ IPTTP_RIGHT_SIDE,
+ IPTTP_TOP_SIDE,
+ IPTTP_BOTTOM_SIDE,
+
+ NUM_IPTTP_STRATEGIES,
+};
+extern itempanel_tooltippos_t g_iTooltipStrategies[NUM_IPTTP_STRATEGIES][NUM_POSITIONS_PER_STRATEGY];
+
+struct item_model_cache_t
+{
+ item_model_cache_t()
+ {
+ Clear();
+ }
+ void Clear()
+ {
+ iItemID = INVALID_ITEM_ID;
+ iItemDefinitionIndex = INVALID_ITEM_DEF_INDEX;
+ iWidth = 0;
+ iHeight = 0;
+ m_hModelPanelLock = NULL;
+ }
+
+ itemid_t iItemID;
+ item_definition_index_t iItemDefinitionIndex;
+ int iWidth;
+ int iHeight;
+ vgui::DHANDLE<CEmbeddedItemModelPanel> m_hModelPanelLock;
+};
+
+class CItemMaterialCustomizationIconPanel : public vgui::Panel
+{
+ DECLARE_CLASS_SIMPLE( CItemMaterialCustomizationIconPanel, vgui::Panel );
+public:
+ CItemMaterialCustomizationIconPanel( vgui::Panel *pParent, const char *pName );
+ virtual ~CItemMaterialCustomizationIconPanel();
+
+ // Custom painting
+ virtual void PaintBackground( void );
+ void DrawQuad( int iSubtileIndex, int iSubtileCount );
+
+ int m_iPaintSplat;
+
+ // UGC file of custom texture we are using. 0 in the more common case of none.
+ uint64 m_hUGCId;
+
+ // Paint color.
+ CUtlVector<Color> m_colPaintColors;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: The model panel that's embedded inside the CItemModelPanel vgui panel
+//-----------------------------------------------------------------------------
+class CEmbeddedItemModelPanel : public CBaseModelPanel
+{
+ DECLARE_CLASS_SIMPLE( CEmbeddedItemModelPanel, CBaseModelPanel );
+
+public:
+ CEmbeddedItemModelPanel( vgui::Panel *pParent, const char *pName );
+ virtual ~CEmbeddedItemModelPanel();
+
+ virtual void PerformLayout( void ) OVERRIDE;
+
+ virtual void Paint( void );
+ virtual void RenderingRootModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix ) OVERRIDE;
+ virtual IMaterial *GetOverrideMaterial( MDLHandle_t mdlHandle ) OVERRIDE;
+
+ CEconItemView* GetItem() const { return m_pItem; }
+ void SetItem( CEconItemView *pItem );
+ bool IsForcingModelUsage( void ) { return m_bForceUseModel; }
+ void SetForceModelUsage( bool bUseModel ) { m_bForceUseModel = bUseModel; }
+ bool IsImageNotLoaded( void ) const;
+ bool IsLoadingWeaponSkin( void ) const;
+
+ enum InventoryImageType_t
+ {
+ IMAGETYPE_SMALL,
+ IMAGETYPE_LARGE,
+ IMAGETYPE_DETAILED, // show in detailed view if present -- defaults to "large" image if not
+ };
+
+ InventoryImageType_t GetInventoryImageType() const { return static_cast<InventoryImageType_t>( m_iInventoryImageType ); }
+ void SetInventoryImageType( InventoryImageType_t eNewImageType ) { m_iInventoryImageType = (int)eNewImageType; }
+ void LoadInventoryImage();
+
+ void SetGreyedOut( bool bGreyedOut ) { m_bGreyedOut = bGreyedOut; }
+ void SetModelHidden( bool bModelHidden ) { m_bModelIsHidden = bModelHidden; }
+
+ ITexture *GetCachedGeneratedIcon();
+ bool m_bOfflineIconGeneration;
+
+private:
+ bool UseRenderTargetAsIcon() const { return m_bUseRenderTargetAsIcon || m_bUseItemRenderTarget; }
+
+ CEconItemView *m_pItem; // For directly specifying the item associated with this panel.
+ int m_iTextureID;
+ CUtlMap<int, int> m_iOverlayTextureIDs;
+ const char* m_pszToolTargetItemImage;
+ int m_iToolTargetItemTextureID;
+ Vector m_vecToolTargetItemImageOffset;
+
+ bool m_bImageNotLoaded;
+
+ bool m_bGreyedOut;
+ bool m_bModelIsHidden;
+
+ bool m_bUseRenderTargetAsIcon; // same as m_bUseItemRenderTarget but set by attribute instead of res file
+
+ void CleanUpCachedWeaponIcon();
+ bool m_bWeaponAllowInspect;
+ int m_iCachedTextureID;
+ CIconRenderReceiver *m_pCachedWeaponIcon;
+ IMaterial *m_pCachedWeaponMaterial;
+
+ MDLData_t m_ItemModel;
+ int m_iPedestalAttachment;
+
+ void UpdateCameraForIcon();
+ int m_iCameraAttachment;
+
+ bool RenderStatTrack( CStudioHdr *pStudioHdr, matrix3x4_t *pWorldMatrix );
+ MDLData_t m_StatTrackModel;
+
+ bool RenderAttachedModels( CStudioHdr *pStudioHdr, matrix3x4_t *pWorldMatrix );
+ void LoadAttachedModel( attachedmodel_t *pModel );
+
+ CUtlVector< MDLData_t > m_AttachedModels;
+
+ float m_flStatTrackScale;
+
+ CPanelAnimationVar( bool, m_bForceUseModel, "force_use_model", "0" );
+ CPanelAnimationVar( bool, m_bUseItemRenderTarget, "use_item_rendertarget", "0" );
+ CPanelAnimationVar( int, m_iInventoryImageType, "inventory_image_type", "0" );
+ CPanelAnimationVar( bool, m_bForceSquareImage, "force_square_image", "0" );
+ CPanelAnimationVar( float, m_flModelRotateYawSpeed, "model_rotate_yaw_speed", "0" );
+ CPanelAnimationVar( bool, m_bUsePedestal, "use_pedestal", "0" );
+
+ bool UpdateParticle(
+ IMatRenderContext *pRenderContext,
+ CStudioHdr *pStudioHdr,
+ MDLHandle_t mdlHandle,
+ matrix3x4_t *pWorldMatrix
+ );
+
+ particle_data_t *m_pItemParticle;
+
+#ifdef STAGING_ONLY
+ double m_flStartUpdateTime;
+#endif // STAGING_ONLY
+};
+
+IMaterial* GetMaterialForImage( CEmbeddedItemModelPanel::InventoryImageType_t eImageType, const char* pszBaseName );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CItemModelPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CItemModelPanel, vgui::EditablePanel );
+public:
+ CItemModelPanel( vgui::Panel *parent, const char *name );
+ virtual ~CItemModelPanel( void );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+ virtual void ApplySettings( KeyValues *inResourceData ) OVERRIDE;
+ virtual void PerformLayout( void ) OVERRIDE;
+ virtual void PaintTraverse( bool forceRepaint, bool allowForce ) OVERRIDE;
+ virtual void OnSizeChanged( int newWide, int newTall );
+
+ void ResizeLabels( void );
+ virtual void SetItem( const CEconItemView *pItem );
+ void SetEconItem( CEconItem* pItem );
+ bool HasItem( void );
+ CEconItemView *GetItem( void ) { return ( m_ItemData.IsValid() ? &m_ItemData : NULL ); }
+ bool ModelIsHidden( void ) const { return m_bHideModel; }
+ void SetModelIsHidden( bool bHideModel );
+ virtual void OnTick() OVERRIDE;
+ void SetNeedsToLoad();
+ bool LoadData();
+ void LoadDataCompleted();
+
+ CExLabel *GetNameLabel( void ) { return m_pItemNameLabel; }
+
+ // Functionality to allow custom blobs of text to be displayed
+ void SetNoItemText( const char *pszText );
+ void SetNoItemText( const wchar_t *pwszTitleOverride, const wchar_t *pwszAttribs = NULL, int iNegAttribsBegin = 0 );
+
+ // Button functionality
+ void SetActAsButton( bool bClickable, bool bMouseOver );
+ virtual void NavigateTo();
+ virtual void NavigateFrom();
+ virtual void OnCursorEntered();
+ virtual void OnCursorExited();
+ virtual void OnMousePressed(vgui::MouseCode code);
+ virtual void OnMouseDoublePressed(vgui::MouseCode code);
+ virtual void OnMouseReleased(vgui::MouseCode code);
+ virtual void OnKeyCodePressed( vgui::KeyCode code );
+ MESSAGE_FUNC_INT_INT( OnCursorMoved, "OnCursorMoved", x, y );
+
+ void SetSelected( bool bSelected ) { m_bSelected = bSelected; }
+ bool IsSelected( void ) { return m_bSelected; }
+
+ void SetAttribOnly( bool bAttribs ) { m_bAttribOnly = bAttribs; }
+ void SetTextYPos( int iPos ) { m_iTextYPos = iPos; }
+ bool IsEquipped( void );
+ void SetShowEquipped( bool bShow ) { m_bShowEquipped = bShow; UpdateEquippedLabel(); }
+ void SetForceShowEquipped( bool bForce ) { m_bForceShowEquipped = bForce; UpdateEquippedLabel(); }
+ void SetGreyedOut( const char *pszGreyedOutReason ); // pass in NULL for "not greyed out"
+ void SetShowGreyedOutTooltip( bool bShow ) { m_bShowGreyedOutTooltip = bShow; }
+ bool IsGreyedOut( void ) const { return m_pszGreyedOutReason != NULL; }
+ const char *GetGreyedOutReason( void ) const { return m_pszGreyedOutReason; }
+ bool ShouldShowTooltip( void ) { return (!IsGreyedOut() || m_bShowGreyedOutTooltip); }
+ void SetShowQuantity( bool bShow ) { m_bShowQuantity = bShow; }
+
+ void UpdatePanels( void );
+
+ void SendPanelEnterExits( bool bSend ) { m_bShouldSendPanelEnterExits = bSend; }
+
+ void SetShouldShowOthersGiftWrappedItems( bool bShow ) { m_bShowOthersGiftWrappedItems = bShow; }
+ void Dragged( bool bDragging );
+ void HideContainedItemPanel();
+ void ShowContainedItemPanel( const CEconItemView *pItem );
+
+ bool IsContainedItem() { return m_bContainedItem; }
+ void SetContainedItem( bool bVal ) { m_bContainedItem = bVal; }
+
+ void SetSkin( int iSkin );
+ void SetItemStyle( style_index_t unStyle ) { m_ItemData.SetItemStyleOverride( unStyle ); }
+ void SetNameOnly( bool bNameOnly ) { m_bNameOnly = bNameOnly; }
+ void SetSpecialAttributesOnly( bool bSpecialOnly ) { m_bSpecialAttributesOnly = bSpecialOnly; }
+
+ CEmbeddedItemModelPanel::InventoryImageType_t GetInventoryImageType() /*const*/ { return m_pModelPanel->GetInventoryImageType(); }
+ void SetInventoryImageType( CEmbeddedItemModelPanel::InventoryImageType_t eNewImageType ) { m_pModelPanel->SetInventoryImageType( eNewImageType ); }
+
+ void UpdateDescription();
+ void DirtyDescription();
+
+ virtual void OnCommand( const char *command ) OVERRIDE;
+
+ void MakeFakeButton() { m_bFakeButton = true; }
+
+private:
+ void UpdateEquippedLabel( void );
+ void CleanupNoItemWChars( void );
+
+ bool UpdateSeriesLabel();
+ bool UpdateMatchesLabel();
+ bool UpdateQuantityLabel();
+
+ bool CheckRecipeMatches();
+
+ int GetAttribWide( int iMaxWide ) { return (m_iTextWide ? m_iTextWide : (iMaxWide - (ATTRIB_LABEL_INDENT * 2))); }
+
+ void LoadResFileForCurrentItem( bool bForceLoad );
+
+ void HideAllModifierIcons();
+
+ enum eLoadingType_t
+ {
+ LOADING_ICONS = 0,
+ LOADING_DESCRIPTIONS,
+ LOADING_RECIPE_MATCHES,
+
+ NUM_LOADING_TYPES
+ };
+
+ enum eLoadedCollectionType_t
+ {
+ LOADED_COLLECTION_NONE,
+ LOADED_COLLECTION_WEAPON,
+ LOADED_COLLECTION_COSMETIC
+ };
+
+ vgui::DHANDLE<CEmbeddedItemModelPanel> m_pModelPanel;
+ CExLabel *m_pItemNameLabel;
+ vgui::Label *m_pItemAttribLabel;
+ CExLabel *m_pItemCollectionNameLabel;
+ vgui::Label *m_pItemCollectionListLabel;
+ vgui::EditablePanel *m_pItemCollectionHighlight;
+ eLoadedCollectionType_t m_nCollectionItemLoaded;
+ vgui::Label *m_pItemEquippedLabel;
+ vgui::Label *m_pItemQuantityLabel;
+ vgui::ImagePanel *m_pVisionRestrictionImage;
+ vgui::ImagePanel *m_pIsStrangeImage;
+ vgui::ImagePanel *m_pIsUnusualImage;
+ vgui::ImagePanel *m_pIsLoanerImage;
+ vgui::Label *m_pSeriesLabel;
+ vgui::Label *m_pMatchesLabel;
+ vgui::EditablePanel *m_pMainContentContainer;
+ vgui::ImagePanel *m_pLoadingSpinner;
+ CEconItemView m_ItemData;
+ vgui::HFont m_pFontNameSmallest;
+ vgui::HFont m_pFontNameSmall;
+ vgui::HFont m_pFontNameLarge;
+ vgui::HFont m_pFontNameLarger;
+ vgui::HFont m_pFontAttribSmallest;
+ vgui::HFont m_pFontAttribSmall;
+ vgui::HFont m_pFontAttribLarge;
+ vgui::HFont m_pFontAttribLarger;
+ const char *m_pszNoItemText;
+ const wchar_t *m_pwcNoItemText;
+ const wchar_t *m_pwcNoItemAttrib;
+ Color m_NoItemTextColor;
+ Color m_OrgItemTextColor;
+ bool m_bClickable;
+ bool m_bMouseOver;
+ bool m_bSelected;
+ bool m_bShowEquipped;
+ bool m_bForceShowEquipped;
+ const char *m_pszGreyedOutReason;
+ bool m_bShowGreyedOutTooltip;
+ bool m_bShouldSendPanelEnterExits;
+ bool m_bShowQuantity;
+ bool m_bContainedItem;
+ bool m_bShowOthersGiftWrappedItems;
+ bool m_bDescriptionDirty;
+ int m_nRecipeMatchingIndex;
+ static float sm_flLoadingTimeThisFrame;
+ static int sm_nCurrentDecriptionUpdateFrame;
+ static int sai_NumLoadingRequests[NUM_LOADING_TYPES];
+ static eLoadingType_t se_CurrentLoadingTask;
+ CUtlMap< attrib_definition_index_t, int > m_mapMatchingAttributes;
+ CItemMaterialCustomizationIconPanel *m_pPaintIcon;
+ vgui::ScalableImagePanel *m_pTF2Icon;
+
+ CItemModelPanel *m_pContainedItemPanel;
+
+ CPanelAnimationVar( bool, m_bSpecialAttributesOnly, "special_attributes_only", "0" );
+
+ CPanelAnimationVarAliasType( int, m_iModelXPos, "model_xpos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iModelYPos, "model_ypos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iModelWide, "model_wide", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iModelTall, "model_tall", "0", "proportional_int" );
+ CPanelAnimationVar( bool, m_bModelCenterX, "model_center_x", "0" );
+ CPanelAnimationVar( bool, m_bModelCenterY, "model_center_y", "0" );
+ CPanelAnimationVarAliasType( int, m_iTF2IconOffsetX, "tf2_icon_offset_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTF2IconOffsetY, "tf2_icon_offset_y", "0", "proportional_int" );
+
+ CPanelAnimationVar( bool, m_bNoItemFullPanel, "noitem_use_fullpanel", "0" );
+ CPanelAnimationVar( bool, m_bTextCenter, "text_center", "0" );
+ CPanelAnimationVar( bool, m_bTextCenterX, "text_center_x", "0" );
+ CPanelAnimationVar( bool, m_bUseItemSounds, "use_item_sounds", "0" );
+ CPanelAnimationVar( bool, m_bNameOnly, "name_only", "0" );
+ CPanelAnimationVar( bool, m_bAttribOnly, "attrib_only", "0" );
+ CPanelAnimationVar( bool, m_bModelOnly, "model_only", "0" );
+ CPanelAnimationVar( bool, m_bHideModelDefault, "model_hide", "0" );
+ bool m_bHideModel;
+ CPanelAnimationVar( bool, m_bHidePaintIcon, "paint_icon_hide", "0" );
+ CPanelAnimationVar( bool, m_bResizeToText, "resize_to_text", "0" );
+ CPanelAnimationVar( int, m_iNameLabelAlignment, "name_label_alignment", "4" /*a_center*/ );
+ CPanelAnimationVarAliasType( int, m_iTextXPos, "text_xpos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTextYPos, "text_ypos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTextWide, "text_wide", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTextYOffset, "text_yoffset", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iHPadding, "padding_height", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iMaxTextHeight, "max_text_height", "0", "proportional_int" );
+ CPanelAnimationVar( int, m_iForceTextSize, "text_forcesize", "0" );
+
+ CPanelAnimationVarAliasType( int, m_iEquippedInsetX, "inset_eq_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iEquippedInsetY, "inset_eq_y", "0", "proportional_int" );
+
+ CPanelAnimationVar( bool, m_bStandardTextColor, "standard_text_color", "0" );
+
+ CPanelAnimationVar( bool, m_bIsMouseOverPanel, "is_mouseover", "0" );
+ CPanelAnimationVarAliasType( int, m_iBaseWide, "wide", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iBaseTall, "tall", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iCollectionListXPos, "collection_list_xpos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iTextXPosCollection, "text_xpos_collection", "0", "proportional_int" );
+ CPanelAnimationVar( bool, m_bHideCollectionPanel, "hide_collection_panel", "0" );
+ CPanelAnimationVar( bool, m_bHideModifierIcons, "hide_modifier_icons", "0" );
+
+ bool m_bFakeButton;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Item model panel tooltip. Calls setvisible on the controlled panel
+// and positions it below/above the current panel.
+//-----------------------------------------------------------------------------
+class CItemModelPanelToolTip : public vgui::BaseTooltip
+{
+ DECLARE_CLASS_SIMPLE( CItemModelPanelToolTip, vgui::BaseTooltip );
+public:
+ CItemModelPanelToolTip(vgui::Panel *parent, const char *text = NULL);
+
+ void SetText(const char *text) { return; }
+ const char *GetText() { return NULL; }
+
+ virtual void PerformLayout();
+ virtual void ShowTooltip( vgui::Panel *currentPanel );
+ virtual void HideTooltip();
+
+ void SetupPanels( vgui::Panel *pParentPanel, CItemModelPanel *pMouseOverItemPanel ) { m_pParentPanel = pParentPanel; m_pMouseOverItemPanel = pMouseOverItemPanel; }
+ void SetPositioningStrategy( itempanel_tooltip_strategies_t iStrat ) { m_iPositioningStrategy = iStrat; }
+
+private:
+ void GetPosition( itempanel_tooltippos_t iTooltipPosition, CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos );
+ bool ValidatePosition( CItemModelPanel *pItemPanel, int iItemX, int iItemY, int *iXPos, int *iYPos );
+
+private:
+ CItemModelPanel *m_pMouseOverItemPanel; // This is the tooltip panel we make visible. Must be a CItemModelPanel.
+ vgui::Panel *m_pParentPanel; // This is the panel that we send item entered/exited messages to
+ vgui::DHANDLE<CItemModelPanel> m_hCurrentPanel;
+
+ itempanel_tooltip_strategies_t m_iPositioningStrategy;
+ bool m_bHorizontalPreferLeft;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Generate a list of items that can be equipped for a given set of
+// restrictions (class/slot/region/etc.) and other data (count of dupes, etc.).
+// Used for crafting, loadout selection, quickswitch, and elsewhere.
+//-----------------------------------------------------------------------------
+class CEquippableItemsForSlotGenerator
+{
+public:
+ // Flags that control how the data is generated.
+ enum
+ {
+ kSlotGenerator_None = 0x00,
+ kSlotGenerator_ShowDuplicates = 0x01,
+ kSlotGenerator_EquippedSpecialHandling = 0x02,
+ };
+
+ enum EItemDisplayType
+ {
+ kSlotDisplay_Normal,
+ kSlotDisplay_Disabled_EquipRegionConflict,
+ kSlotDisplay_Invalid, // just used to tag unitialized instances
+ };
+
+ struct CEquippableResult
+ {
+ CEquippableResult()
+ : m_pEconItemView( NULL )
+ , m_eDisplayType( kSlotDisplay_Invalid )
+ {
+ //
+ }
+
+ CEquippableResult( CEconItemView *pEconItemView, EItemDisplayType eDisplayType = kSlotDisplay_Normal )
+ : m_pEconItemView( pEconItemView )
+ , m_eDisplayType( eDisplayType )
+ {
+ //
+ }
+
+ // This only exists to support Find() and ignores display state.
+ bool operator==( const CEquippableResult& other ) const
+ {
+ return m_pEconItemView == other.m_pEconItemView;
+ }
+
+ CEconItemView *m_pEconItemView;
+ EItemDisplayType m_eDisplayType;
+ };
+
+ typedef CUtlMap<struct item_stack_type_t, int> DuplicateCountMap_t;
+ typedef CUtlVector<CEquippableResult> EquippableResultsVec_t;
+
+ CEquippableItemsForSlotGenerator( int iClass, int iSlot, equip_region_mask_t unUsedEquipRegionMask, unsigned int unFlags );
+
+ const EquippableResultsVec_t& GetDisplayItems() const { return m_vecDisplayItems; }
+ CEconItemView *GetEquippedItem() const { return m_pEquippedItemView; }
+ const DuplicateCountMap_t& GetDuplicateCountMap() const { return m_DuplicateCountsMap; }
+
+private:
+ EquippableResultsVec_t m_vecDisplayItems;
+ DuplicateCountMap_t m_DuplicateCountsMap;
+ CEconItemView *m_pEquippedItemView; // if kSlotGenerator_EquippedSpecialHandling is passed in, this will store our equipped item; otherwise NULL
+};
+
+#endif // ITEM_MODEL_PANEL_H
diff --git a/game/client/econ/item_pickup_panel.cpp b/game/client/econ/item_pickup_panel.cpp
new file mode 100644
index 0000000..4ff2811
--- /dev/null
+++ b/game/client/econ/item_pickup_panel.cpp
@@ -0,0 +1,889 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "vgui/IInput.h"
+#include <vgui/IVGui.h>
+#include <vgui/IScheme.h>
+#include <vgui/ILocalize.h>
+#include "vgui_controls/TextImage.h"
+#include "ienginevgui.h"
+#include "iclientmode.h"
+#include "baseviewport.h"
+#include "item_pickup_panel.h"
+#include "econ_ui.h"
+#include "econ_item_inventory.h"
+#include "econ_item_constants.h"
+#include "item_confirm_delete_dialog.h"
+#include "backpack_panel.h"
+
+#ifdef TF_CLIENT_DLL
+#include "c_tf_freeaccount.h"
+#include "clientmode_tf.h"
+#include "quest_log_panel.h"
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemPickupPanel::CItemPickupPanel( Panel *parent ) : Frame( parent, "item_pickup" )
+{
+ vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL );
+ SetParent( gameuiPanel );
+
+ // We don't want the gameui to delete us, or things get messy
+ SetAutoDelete( false );
+
+ SetMoveable( false );
+ SetSizeable( false );
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ m_pNextButton = new vgui::Button( this, "NextButton", "#Next" );
+ m_pPrevButton = new vgui::Button( this, "PrevButton", "#Prev" );
+ m_pDiscardButton = NULL;
+ m_pDiscardedLabel = NULL;
+ m_pOpenLoadoutButton = NULL;
+ m_pConfirmDeleteDialog = NULL;
+ m_pModelPanelsKV = NULL;
+ m_bRandomizePickupMethods = false;
+ m_pItemFoundMethod = NULL;
+ m_pCloseButton = NULL;
+
+ for ( int i = 0; i < ITEMPICKUP_NUM_MODELPANELS; i++ )
+ {
+ m_aModelPanels[i] = new CItemModelPanel( this, VarArgs("modelpanel%d", i) );
+ }
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ vgui::EditablePanel *pToolTipPanel = new vgui::EditablePanel( this, "DiscardButtonTooltip" );
+ m_pToolTip = new CSimplePanelToolTip( this );
+ m_pToolTip->SetControlledPanel( pToolTipPanel );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemPickupPanel::~CItemPickupPanel()
+{
+ if ( m_pModelPanelsKV )
+ {
+ m_pModelPanelsKV->deleteThis();
+ m_pModelPanelsKV = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "Resource/UI/econ/ItemPickupPanel.res" );
+
+ m_pItemsFoundLabel = dynamic_cast<vgui::Label*>(FindChildByName( "ItemsFoundLabel" ));
+ m_pDiscardedLabel = dynamic_cast<vgui::Label*>( FindChildByName("DiscardedLabel") );
+ m_pItemFoundMethod = dynamic_cast<vgui::Label*>(FindChildByName( "SelectedItemFoundMethodLabel" ));
+
+ for ( int i = 0; i < ITEMPICKUP_NUM_MODELPANELS; i++ )
+ {
+ m_aModelPanels[i]->SetPaintBackgroundType( 2 );
+ }
+
+ m_pDiscardButton = dynamic_cast<CExImageButton*>( FindChildByName("DiscardButton") );
+ if ( m_pDiscardButton )
+ {
+ m_pDiscardButton->SetTooltip( m_pToolTip, "" );
+ }
+
+ m_pOpenLoadoutButton = dynamic_cast<vgui::Button*>( FindChildByName("OpenLoadoutButton") );
+ m_pCloseButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pModelKV = inResourceData->FindKey( "modelpanelskv" );
+ if ( pModelKV )
+ {
+ if ( m_pModelPanelsKV )
+ {
+ m_pModelPanelsKV->deleteThis();
+ }
+ m_pModelPanelsKV = new KeyValues("modelpanelkv");
+ pModelKV->CopySubkeys( m_pModelPanelsKV );
+
+ for ( int i = 0; i < ITEMPICKUP_NUM_MODELPANELS; i++ )
+ {
+ m_aModelPanels[i]->ApplySettings( m_pModelPanelsKV );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::PerformLayout( void )
+{
+ if ( GetVParent() )
+ {
+ int w,h;
+ vgui::ipanel()->GetSize( GetVParent(), w, h );
+ SetBounds(0,0,w,h);
+ }
+
+ BaseClass::PerformLayout();
+
+ // Now lay out our model panels
+ for ( int i = 0; i < ITEMPICKUP_NUM_MODELPANELS; i++ )
+ {
+ int iPos = (i - ITEMPICKUP_MODELPANEL_CENTER);
+ int iXPos = (GetWide() * 0.5) + (iPos * m_iModelPanelSpacing) + (iPos * m_iModelPanelW) - (m_iModelPanelW * 0.5);
+ m_aModelPanels[i]->SetBounds( iXPos, m_iModelPanelY, m_iModelPanelW, m_iModelPanelH );
+ m_aModelPanels[i]->SetVisible( m_aModelPanels[i]->HasItem() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::ShowPanel(bool bShow)
+{
+ if ( bShow )
+ {
+ // Start with the first item centered, and the rest off to the right.
+ m_iSelectedItem = 0;
+ UpdateModelPanels();
+ MoveToFront(); // Any open dialogs from the main menu (options, servers, etc.) showing up sometimes - this should fix.
+ }
+ else
+ {
+ for ( int i = 0; i < m_aItems.Count(); i++ )
+ {
+ if ( !m_aItems[i].bDiscarded )
+ {
+ // if not a real item, ignore
+ if ( m_aItems[i].pItem.GetSOCData() == NULL )
+ {
+ continue;
+ }
+
+ // ignore items that are purchased or are store promotional items
+ CEconItem *pEconItem = m_aItems[i].pItem.GetSOCData();
+ switch ( pEconItem->GetOrigin() )
+ {
+ case kEconItemOrigin_PreviewItem:
+ case kEconItemOrigin_Purchased:
+ case kEconItemOrigin_StorePromotion:
+ continue;
+ }
+ }
+ }
+
+ if ( m_pConfirmDeleteDialog )
+ {
+ m_pConfirmDeleteDialog->MarkForDeletion();
+ m_pConfirmDeleteDialog = NULL;
+ }
+
+ m_aItems.Purge();
+ }
+
+ SetMouseInputEnabled( bShow );
+ SetVisible( bShow );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::FireGameEvent( IGameEvent *event )
+{
+ if ( !IsVisible() )
+ return;
+
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ ShowPanel( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "vguicancel" ) )
+ {
+ AcknowledgeItems();
+ ShowPanel( false );
+ }
+ else if ( !Q_stricmp( command, "changeloadout" ) )
+ {
+ AcknowledgeItems();
+ ShowPanel( false );
+ EconUI()->OpenEconUI( ECONUI_LOADOUT, true );
+ }
+ else if ( !Q_stricmp( command, "nextitem" ) )
+ {
+ m_iSelectedItem = clamp( m_iSelectedItem+1, 0, m_aItems.Count()-1 );
+ UpdateModelPanels();
+ }
+ else if ( !Q_stricmp( command, "previtem" ) )
+ {
+ m_iSelectedItem = clamp( m_iSelectedItem-1, 0, m_aItems.Count()-1 );
+ UpdateModelPanels();
+ }
+ else if ( !Q_stricmp( command, "discarditem" ) )
+ {
+ // Bring up confirm dialog
+ CConfirmDeleteItemDialog *pConfirm = vgui::SETUP_PANEL( new CConfirmDeleteItemDialog( this ) );
+ if ( pConfirm )
+ {
+ pConfirm->Show();
+
+ m_pConfirmDeleteDialog = pConfirm;
+ }
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::UpdateModelPanels( void )
+{
+ for ( int i = 0; i < ITEMPICKUP_NUM_MODELPANELS; i++ )
+ {
+ int iPos = (i - ITEMPICKUP_MODELPANEL_CENTER);
+ int iItem = m_iSelectedItem + iPos;
+ if ( iItem < 0 || iItem >= m_aItems.Count() )
+ {
+ m_aModelPanels[i]->SetItem( NULL );
+ m_aModelPanels[i]->SetVisible( false );
+ continue;
+ }
+
+ m_aModelPanels[i]->SetItem( &m_aItems[iItem].pItem );
+ m_aModelPanels[i]->SetVisible( true );
+ }
+
+ // Enable next & prev buttons if there are other items to the sides
+ bool bCanScrollRight = (m_iSelectedItem + 1) < m_aItems.Count();
+ m_pNextButton->SetVisible( bCanScrollRight );
+ m_pNextButton->SetEnabled( bCanScrollRight );
+ bool bCanScrollLeft = ((m_iSelectedItem - 1) >= 0) && m_aItems.Count();
+ m_pPrevButton->SetVisible( bCanScrollLeft );
+ m_pPrevButton->SetEnabled( bCanScrollLeft );
+
+ bool bDiscarded = false;
+ if ( m_iSelectedItem >= 0 && m_iSelectedItem < m_aItems.Count() )
+ {
+ bDiscarded = m_aItems[m_iSelectedItem].bDiscarded;
+ }
+
+ bool bAllowDiscard = true;
+
+ int iFoundMethod = 0;
+ if ( m_bRandomizePickupMethods )
+ {
+ iFoundMethod = RandomInt( 0, ARRAYSIZE(g_pszItemPickupMethodStrings)-1 );
+ }
+ else if ( m_iSelectedItem >= 0 && m_iSelectedItem < m_aItems.Count() )
+ {
+ iFoundMethod = GetUnacknowledgedReason( m_aItems[m_iSelectedItem].pItem.GetInventoryPosition() );
+ bAllowDiscard = ( iFoundMethod <= UNACK_ITEM_DROPPED );
+ iFoundMethod--;
+
+ if ( iFoundMethod < 0 || iFoundMethod >= ARRAYSIZE(g_pszItemPickupMethodStrings) )
+ {
+ iFoundMethod = 0;
+ }
+ }
+
+ // Hide the discard button if it's not a random find
+ m_pDiscardButton->SetVisible( !bDiscarded && bAllowDiscard );
+ m_pOpenLoadoutButton->SetVisible( !bDiscarded );
+ m_pDiscardedLabel->SetVisible( bDiscarded );
+
+ if ( m_pItemFoundMethod )
+ {
+ enum
+ {
+ COLOR_NORMAL = 1,
+ COLOR_HIGHLIGHTED = 2,
+ };
+
+ m_pItemFoundMethod->GetTextImage()->ClearColorChangeStream();
+
+ wchar_t wszTmp[1024];
+ wchar_t *pLocText = g_pVGuiLocalize->Find( g_pszItemPickupMethodStrings[iFoundMethod] );
+ if ( pLocText )
+ {
+ // Loop through and replace the color changes
+ Color baseColor = m_pItemFoundMethod->GetFgColor();
+ wchar_t *wpszChar = pLocText;
+ int iOutPos = 0;
+ while ( *wpszChar )
+ {
+ if ( *wpszChar == COLOR_NORMAL )
+ {
+ m_pItemFoundMethod->GetTextImage()->AddColorChange( baseColor, iOutPos );
+ }
+ else if ( *wpszChar == COLOR_HIGHLIGHTED )
+ {
+ m_pItemFoundMethod->GetTextImage()->AddColorChange( Color(200,80,60,255), iOutPos );
+ }
+ else
+ {
+ wszTmp[iOutPos] = *wpszChar;
+ iOutPos++;
+ }
+ wpszChar++;
+
+ if ( iOutPos >= (ARRAYSIZE(wszTmp) - 1) )
+ {
+ wszTmp[iOutPos] = L'\0';
+ break;
+ }
+ }
+
+ if ( iOutPos < (ARRAYSIZE(wszTmp) - 1) )
+ {
+ wszTmp[iOutPos] = L'\0';
+ }
+
+ m_pItemFoundMethod->SetText( wszTmp );
+ }
+ }
+
+ if ( m_pItemsFoundLabel )
+ {
+ if ( m_aItems.Count() > 1 )
+ {
+ m_pItemsFoundLabel->SetText( "#NewItemsAcquired" );
+ SetDialogVariable( "numitems", m_aItems.Count() );
+ }
+ else
+ {
+ m_pItemsFoundLabel->SetText( "#NewItemAcquired" );
+ }
+ }
+
+ SetDialogVariable( "selecteditem", m_iSelectedItem+1 );
+
+ // Update the loadout button as appropriate
+ if ( m_iSelectedItem >= 0 && m_iSelectedItem < m_aItems.Count() )
+ {
+ if ( m_aItems[m_iSelectedItem].pItem.IsValid() && !bDiscarded )
+ {
+ SetDialogVariable("loadouttext", g_pVGuiLocalize->Find( "#OpenGeneralLoadout" ) );
+ }
+ }
+
+ if ( m_pCloseButton )
+ {
+ const char *pszCloseString = (m_bReturnToGame && engine->IsInGame()) ? "#CloseItemPanel" : "#GameUI_OK";
+ m_pCloseButton->SetText( g_pVGuiLocalize->Find( pszCloseString ) );
+ }
+ m_pCloseButton->RequestFocus();
+}
+
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::AcknowledgeItems( void )
+{
+ // On command, AckKnowledge all these items
+ for ( int i = 0; i < m_aItems.Count(); i++ )
+ {
+ InventoryManager()->AcknowledgeItem( &m_aItems[i].pItem, false );
+ }
+
+ InventoryManager()->SaveAckFile();
+
+ // If we were crafting, and the craft panel is up, we return to that instead.
+ if ( EconUI()->IsUIPanelVisible( ECONUI_CRAFTING ) )
+ return;
+
+ // Check to make sure the player has room for all his items. If not, bring up the discard panel. Otherwise, go away.
+ if ( !InventoryManager()->CheckForRoomAndForceDiscard( ) )
+ {
+#ifdef TF_CLIENT_DLL
+ // If the quest log is up, we just go back to that
+ if ( GetQuestLog() && GetQuestLog()->IsVisible() )
+ return;
+#endif
+ // If we're connected to a game server, we also close the game UI.
+ if ( m_bReturnToGame && engine->IsInGame() )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles the escape key
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::OnKeyCodeTyped( vgui::KeyCode code )
+{
+ if( code == KEY_ESCAPE )
+ {
+ OnCommand( "vguicancel" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles button press code for controllers and enter
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::OnKeyCodePressed( vgui::KeyCode code )
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if( code == KEY_ENTER || nButtonCode == KEY_XBUTTON_A || nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_A || nButtonCode == STEAMCONTROLLER_B )
+ {
+ OnCommand( "vguicancel" );
+ }
+ else if( code == KEY_HOME || nButtonCode == KEY_XBUTTON_Y || nButtonCode == STEAMCONTROLLER_Y )
+ {
+ OnCommand( "changeloadout" );
+ }
+
+ else if ( nButtonCode == KEY_XBUTTON_RIGHT ||
+ nButtonCode == KEY_XSTICK1_RIGHT ||
+ nButtonCode == KEY_XSTICK2_RIGHT ||
+ nButtonCode == KEY_RIGHT )
+ {
+ OnCommand( "nextitem" );
+ }
+ else if ( nButtonCode == KEY_XBUTTON_LEFT ||
+ nButtonCode == KEY_XSTICK1_LEFT ||
+ nButtonCode == KEY_XSTICK2_LEFT ||
+ nButtonCode == KEY_LEFT )
+ {
+ OnCommand( "previtem" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::AddItem( CEconItemView *pItem )
+{
+ // Guard against duplicates
+ FOR_EACH_VEC( m_aItems, i )
+ {
+ if ( m_aItems[i].pItem.GetItemID() == pItem->GetItemID() )
+ return;
+ }
+
+ int iIdx = m_aItems.AddToTail();
+ m_aItems[iIdx].pItem = *pItem;
+ m_aItems[iIdx].bDiscarded = false;
+
+ if ( IsVisible() )
+ {
+ UpdateModelPanels();
+ InvalidateLayout();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemPickupPanel::OnConfirmDelete( KeyValues *data )
+{
+ m_pConfirmDeleteDialog = NULL;
+ m_pCloseButton->RequestFocus();
+
+ int iConfirmed = data->GetInt( "confirmed", 0 );
+ if ( !iConfirmed )
+ return;
+
+ if ( m_iSelectedItem >= 0 && m_iSelectedItem < m_aItems.Count() )
+ {
+ if ( m_aItems[m_iSelectedItem].pItem.IsValid() )
+ {
+ CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
+ if ( pInventory )
+ {
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_DISCARDED, &m_aItems[m_iSelectedItem].pItem );
+
+ InventoryManager()->DropItem( m_aItems[m_iSelectedItem].pItem.GetItemID() );
+ InvalidateLayout();
+
+ m_aItems[m_iSelectedItem].bDiscarded = true;
+ m_pDiscardButton->SetVisible( false );
+ m_pOpenLoadoutButton->SetVisible( false );
+ m_pDiscardedLabel->SetVisible( true );
+
+ // If we've discarded all our items, we exit immediately
+ bool bFoundUndiscarded = false;
+ for ( int i = 0; i < m_aItems.Count(); i++ )
+ {
+ if ( !m_aItems[i].bDiscarded )
+ {
+ bFoundUndiscarded = true;
+ break;
+ }
+ }
+
+ if ( !bFoundUndiscarded )
+ {
+ OnCommand( "vguicancel" );
+ }
+ }
+ }
+ }
+}
+
+static vgui::DHANDLE<CItemPickupPanel> g_ItemPickupPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemPickupPanel *OpenItemPickupPanel( void )
+{
+ if (!g_ItemPickupPanel.Get())
+ {
+ g_ItemPickupPanel = vgui::SETUP_PANEL( new CItemPickupPanel( NULL ) );
+ g_ItemPickupPanel->InvalidateLayout( false, true );
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ g_ItemPickupPanel->ShowPanel( true );
+
+ return g_ItemPickupPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemPickupPanel *GetItemPickupPanel( void )
+{
+ return g_ItemPickupPanel.Get();
+}
+
+//=======================================================================================================================================================
+// ITEM DISCARD PANEL
+//=======================================================================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemDiscardPanel::CItemDiscardPanel( Panel *parent ) : Frame( parent, "item_discard" )
+{
+ vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL );
+ SetParent( gameuiPanel );
+
+ // We don't want the gameui to delete us, or things get messy
+ SetAutoDelete( false );
+
+ SetMoveable( false );
+ SetSizeable( false );
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ m_pModelPanel = new CItemModelPanel( this, "modelpanel" );
+
+ m_pBackpackPanel = new CBackpackPanel( this, "backpack_panel" );
+ m_pConfirmDeleteDialog = NULL;
+
+ m_bDiscardedNewItem = false;
+ m_bMadeRoom = false;
+ m_pDiscardedLabelCarat = NULL;
+ m_pDiscardedLabel = NULL;
+ m_pDiscardButton = NULL;
+ m_pCloseButton = NULL;
+
+ m_pItemMouseOverPanel = new CItemModelPanel( this, "ItemMouseOverItemPanel" );
+ m_pItemToolTip = new CSimplePanelToolTip( this );
+ m_pItemToolTip->SetControlledPanel( m_pItemMouseOverPanel );
+ m_pModelPanel->SetTooltip( m_pItemToolTip, "" );
+ m_pModelPanel->SetActAsButton( false, true );
+
+ ListenForGameEvent( "gameui_hidden" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemDiscardPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pItemMouseOverPanel->InvalidateLayout( true, true );
+ m_pModelPanel->InvalidateLayout( true, true );
+ LoadControlSettings( "Resource/UI/econ/ItemDiscardPanel.res" );
+
+ // Highlight the X on the discard button
+ m_pDiscardButton = dynamic_cast<CExButton*>( FindChildByName("DiscardButton") );
+ if ( m_pDiscardButton )
+ {
+ SetXToRed( m_pDiscardButton );
+ }
+ m_pCloseButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") );
+
+ m_pDiscardedLabel = dynamic_cast<vgui::Label*>( FindChildByName("DiscardedLabel") );
+ m_pDiscardedLabelCarat = dynamic_cast<vgui::Label*>( FindChildByName("DiscardedCaratLabel") );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemDiscardPanel::PerformLayout( void )
+{
+ if ( GetVParent() )
+ {
+ int w,h;
+ vgui::ipanel()->GetSize( GetVParent(), w, h );
+ SetBounds(0,0,w,h);
+ }
+
+ BaseClass::PerformLayout();
+
+ m_pDiscardedLabelCarat->SetVisible( m_bDiscardedNewItem );
+ m_pDiscardedLabel->SetVisible( m_bDiscardedNewItem );
+ m_pDiscardButton->SetVisible( !m_bDiscardedNewItem && !m_bMadeRoom );
+ m_pCloseButton->SetVisible( m_bDiscardedNewItem || m_bMadeRoom );
+
+ if ( m_pItemMouseOverPanel->IsVisible() )
+ {
+ m_pItemMouseOverPanel->SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemDiscardPanel::ShowPanel(bool bShow)
+{
+ if ( bShow )
+ {
+ m_bDiscardedNewItem = false;
+ m_bMadeRoom = false;
+ m_pBackpackPanel->ShowPanel( 0, true );
+ InvalidateLayout();
+ }
+ else
+ {
+ if ( m_pConfirmDeleteDialog )
+ {
+ m_pConfirmDeleteDialog->MarkForDeletion();
+ m_pConfirmDeleteDialog = NULL;
+ }
+ }
+
+ SetMouseInputEnabled( bShow );
+ SetVisible( bShow );
+
+#ifdef TF_CLIENT_DLL
+ // If the player made room and is a trial account, suggest that they upgrade to get more space.
+ if ( bShow && IsFreeTrialAccount() )
+ {
+ ShowUpgradeMessageBox( "#TF_TrialNeedSpace_Title", "#TF_TrialNeedSpace_Text" );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemDiscardPanel::FireGameEvent( IGameEvent *event )
+{
+ if ( !IsVisible() )
+ return;
+
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ // If they haven't discarded down to <MAX items, bring us right back up again
+ if ( InventoryManager()->CheckForRoomAndForceDiscard() )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ }
+ else
+ {
+ ShowPanel( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemDiscardPanel::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "vguicancel" ) )
+ {
+ ShowPanel( false );
+
+ // Check to make sure the player has room for all his items. If not, bring up the discard panel. Otherwise, go away.
+ if ( !InventoryManager()->CheckForRoomAndForceDiscard() )
+ {
+ // If we're connected to a game server, we also close the game UI.
+ if ( engine->IsInGame() )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+ }
+ }
+ }
+ else if ( !Q_stricmp( command, "discarditem" ) )
+ {
+ // Bring up confirm dialog
+ CConfirmDeleteItemDialog *pConfirm = vgui::SETUP_PANEL( new CConfirmDeleteItemDialog( this ) );
+ if ( pConfirm )
+ {
+ pConfirm->Show();
+
+ m_pConfirmDeleteDialog = pConfirm;
+ }
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemDiscardPanel::SetItem( CEconItemView *pItem )
+{
+ if ( m_pModelPanel )
+ {
+ m_pModelPanel->SetItem( pItem );
+ }
+
+ if ( m_pItemMouseOverPanel )
+ {
+ m_pItemMouseOverPanel->SetItem( pItem );
+ IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
+ m_pItemMouseOverPanel->InvalidateLayout(true);
+ m_pItemMouseOverPanel->SetBorder( pScheme->GetBorder("MainMenuBGBorder") );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemDiscardPanel::OnConfirmDelete( KeyValues *data )
+{
+ if ( !m_bDiscardedNewItem && !m_bMadeRoom && data && m_pModelPanel->HasItem() )
+ {
+ int iConfirmed = data->GetInt( "confirmed", 0 );
+ if ( iConfirmed == 2 )
+ {
+ // Player discarded an item from the backpack to make room
+ m_bMadeRoom = true;
+ InvalidateLayout();
+ }
+ else if ( iConfirmed )
+ {
+ CPlayerInventory *pInventory = InventoryManager()->GetLocalInventory();
+ if ( !pInventory )
+ return;
+
+ InventoryManager()->DropItem( m_pModelPanel->GetItem()->GetItemID() );
+ m_bDiscardedNewItem = true;
+ InvalidateLayout();
+ }
+
+ m_pBackpackPanel->RequestFocus();
+ }
+
+ m_pConfirmDeleteDialog = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles the escape key
+//-----------------------------------------------------------------------------
+void CItemDiscardPanel::OnKeyCodeTyped( vgui::KeyCode code )
+{
+ if( code == KEY_ESCAPE )
+ {
+ OnCommand( "vguicancel" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles button press code for controllers and enter
+//-----------------------------------------------------------------------------
+void CItemDiscardPanel::OnKeyCodePressed( vgui::KeyCode code )
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if( nButtonCode == KEY_XBUTTON_B )
+ {
+ OnCommand( "vguicancel" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+
+
+static vgui::DHANDLE<CItemDiscardPanel> g_ItemDiscardPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemDiscardPanel *OpenItemDiscardPanel( void )
+{
+ if (!g_ItemDiscardPanel.Get())
+ {
+ g_ItemDiscardPanel = vgui::SETUP_PANEL( new CItemDiscardPanel( NULL ) );
+ g_ItemDiscardPanel->InvalidateLayout( false, true );
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ g_ItemDiscardPanel->ShowPanel( true );
+
+ return g_ItemDiscardPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemDiscardPanel *GetItemDiscardPanel( void )
+{
+ return g_ItemDiscardPanel.Get();
+}
+
+
diff --git a/game/client/econ/item_pickup_panel.h b/game/client/econ/item_pickup_panel.h
new file mode 100644
index 0000000..5ac3031
--- /dev/null
+++ b/game/client/econ/item_pickup_panel.h
@@ -0,0 +1,128 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ITEM_PICKUP_PANEL_H
+#define ITEM_PICKUP_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Panel.h>
+#include <vgui_controls/Frame.h>
+#include "GameEventListener.h"
+#include "basemodel_panel.h"
+#include "basemodelpanel.h"
+#include "econ_item_inventory.h"
+#include "econ_item_view.h"
+#include "item_model_panel.h"
+#include "vgui_controls/TextEntry.h"
+#include "econ_controls.h"
+
+class CEconItemView;
+class CBackpackPanel;
+
+#define ITEMPICKUP_NUM_MODELPANELS 5 // 1 in the center of the screen, 2 to each side to handle smooth sliding.
+#define ITEMPICKUP_MODELPANEL_CENTER 2 // Panel that's in the center of the queue
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CItemPickupPanel : public vgui::Frame, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CItemPickupPanel, vgui::Frame );
+public:
+ CItemPickupPanel( Panel *parent );
+ virtual ~CItemPickupPanel();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void ShowPanel( bool bShow );
+ virtual void FireGameEvent( IGameEvent *event );
+ virtual void OnKeyCodeTyped( vgui::KeyCode code );
+ virtual void OnKeyCodePressed( vgui::KeyCode code );
+
+ void AddItem( CEconItemView *pItem );
+ void SetReturnToGame( bool bReturn ) { m_bReturnToGame = bReturn; }
+
+#ifdef DEBUG
+ void DebugRandomizePickupMethods( void ) { m_bRandomizePickupMethods = true; }
+#endif
+
+ MESSAGE_FUNC_PARAMS( OnConfirmDelete, "ConfirmDlgResult", data );
+
+protected:
+ virtual void UpdateModelPanels( void );
+ virtual void AcknowledgeItems ( void );
+
+protected:
+ int m_iSelectedItem;
+
+ struct founditem_t
+ {
+ CEconItemView pItem;
+ bool bDiscarded;
+ };
+ CUtlVector<founditem_t> m_aItems;
+ CItemModelPanel *m_aModelPanels[ITEMPICKUP_NUM_MODELPANELS];
+ vgui::Button *m_pNextButton;
+ vgui::Button *m_pPrevButton;
+ vgui::Button *m_pOpenLoadoutButton;
+ CExButton *m_pCloseButton;
+ CExImageButton *m_pDiscardButton;
+ vgui::Label *m_pItemsFoundLabel;
+ vgui::Label *m_pItemFoundMethod;
+ KeyValues *m_pModelPanelsKV;
+ vgui::EditablePanel *m_pConfirmDeleteDialog;
+ vgui::Label *m_pDiscardedLabel;
+ bool m_bRandomizePickupMethods;
+ CSimplePanelToolTip *m_pToolTip;
+ bool m_bReturnToGame;
+
+ CPanelAnimationVarAliasType( float, m_iModelPanelSpacing, "modelpanels_spacing", "40", "proportional_float" );
+ CPanelAnimationVarAliasType( float, m_iModelPanelW, "modelpanels_width", "128", "proportional_float" );
+ CPanelAnimationVarAliasType( float, m_iModelPanelH, "modelpanels_height", "80", "proportional_float" );
+ CPanelAnimationVarAliasType( float, m_iModelPanelY, "modelpanels_ypos", "120", "proportional_float" );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CItemDiscardPanel : public vgui::Frame, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CItemDiscardPanel, vgui::Frame );
+public:
+ CItemDiscardPanel( Panel *parent );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void ShowPanel( bool bShow );
+ virtual void FireGameEvent( IGameEvent *event );
+ virtual void OnKeyCodeTyped( vgui::KeyCode code );
+ virtual void OnKeyCodePressed( vgui::KeyCode code );
+
+ void SetItem( CEconItemView *pItem );
+
+ MESSAGE_FUNC_PARAMS( OnConfirmDelete, "ConfirmDlgResult", data );
+
+protected:
+ CItemModelPanel *m_pModelPanel;
+ CBackpackPanel *m_pBackpackPanel;
+ vgui::EditablePanel *m_pConfirmDeleteDialog;
+ vgui::Label *m_pDiscardedLabel;
+ vgui::Label *m_pDiscardedLabelCarat;
+ bool m_bDiscardedNewItem;
+ bool m_bMadeRoom;
+ CExButton *m_pDiscardButton;
+ CExButton *m_pCloseButton;
+ CSimplePanelToolTip *m_pItemToolTip;
+ CItemModelPanel *m_pItemMouseOverPanel;
+};
+
+#endif // ITEM_PICKUP_PANEL_H
diff --git a/game/client/econ/item_rental_ui.cpp b/game/client/econ/item_rental_ui.cpp
new file mode 100644
index 0000000..44025a5
--- /dev/null
+++ b/game/client/econ/item_rental_ui.cpp
@@ -0,0 +1,274 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "rtime.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "item_rental_ui.h"
+
+#ifdef TF_CLIENT_DLL
+#include "c_tf_gamestats.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm item preview.
+//-----------------------------------------------------------------------------
+class CConfirmItemPreviewDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmItemPreviewDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmItemPreviewDialog( vgui::Panel *pParent, CEconItemView *pPreviewItem );
+ ~CConfirmItemPreviewDialog();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void Apply( void );
+
+private:
+
+ CEconItemView* m_pPreviewItem;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmItemPreviewDialog::CConfirmItemPreviewDialog( vgui::Panel *parent, CEconItemView *pPreviewItem ) : CBaseToolUsageDialog( parent, "ConfirmItemPreviewDialog", pPreviewItem, pPreviewItem )
+{
+ m_pPreviewItem = pPreviewItem;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmItemPreviewDialog::~CConfirmItemPreviewDialog()
+{
+ delete m_pPreviewItem;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmItemPreviewDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ConfirmItemPreviewDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pTitleLabel = dynamic_cast<vgui::Label*>( FindChildByName("TitleLabel") );
+ if ( m_pTitleLabel )
+ {
+ wchar_t *pszBaseString = g_pVGuiLocalize->Find( "ItemPreviewDialogTitle" );
+ if ( pszBaseString )
+ {
+ wchar_t wTemp[256];
+ g_pVGuiLocalize->ConstructString_safe( wTemp, pszBaseString, 1, m_pToolModelPanel->GetItem()->GetItemName() );
+ m_pTitleLabel->SetText( wTemp );
+ m_pTitleLabel->GetTextImage()->ClearColorChangeStream();
+ }
+ }
+
+ m_pSubjectModelPanel->SetVisible( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmItemPreviewDialog::Apply( void )
+{
+ // Notify the GC that the player wants to preview this item.
+ GCSDK::CGCMsg< MsgGCItemPreviewRequest_t > msg( k_EMsgGCItemPreviewRequest );
+
+ msg.Body().m_unItemDefIndex = m_pToolModelPanel->GetItem()->GetItemDefIndex();
+
+ // OGS LOGGING HERE
+
+ GCClientSystem()->BSendMessage( msg );
+
+ EconUI()->SetPreventClosure( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the item preview query response.
+//-----------------------------------------------------------------------------
+class CGCItemPreviewStatusResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCItemPreviewStatusResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCItemPreviewCheckStatusResponse_t> msg( pNetPacket );
+
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+ if ( !pStorePanel )
+ return true;
+
+ if ( msg.Body().m_eResponse == k_EGCMsgResponseOK )
+ {
+ // We can preview the item.
+ CEconItemView *pPreviewItem = new CEconItemView();
+ pPreviewItem->Init( msg.Body().m_unItemDefIndex, AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ CConfirmItemPreviewDialog *dialog = vgui::SETUP_PANEL( new CConfirmItemPreviewDialog( pStorePanel->GetPropertySheet()->GetActivePage(), pPreviewItem ) );
+ MakeModalAndBringToFront( dialog );
+ }
+ else
+ {
+#ifdef TF_CLIENT_DLL
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_preview_item_denied", CFmtStr( "%i", msg.Body().m_unItemDefIndex ).Access() );
+#endif
+
+ // We aren't allowed to preview an item right now.
+ CTFMessageBoxDialog* pDialog = ShowMessageBox( "#ItemPreview_PreviewStartFailedTitle", "#ItemPreview_PreviewStartFailedText", "#GameUI_OK" );
+ RTime32 nextTime = msg.Body().m_timePreviewTime + EconUI()->GetStorePanel()->GetPriceSheet()->GetPreviewPeriod();
+
+ locchar_t wzValue[64];
+ char time_buf[k_RTimeRenderBufferSize];
+ GLocalizationProvider()->ConvertUTF8ToLocchar( CRTime::Render( nextTime, time_buf ), wzValue, sizeof( wzValue ) );
+ pDialog->AddStringToken( "date_time", wzValue );
+ }
+
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCItemPreviewStatusResponse, "CGCItemPreviewStatusResponse", k_EMsgGCItemPreviewStatusResponse, GCSDK::k_EServerTypeGCClient );
+
+//-----------------------------------------------------------------------------
+// Purpose: The GC is telling us our request has been granted.
+//-----------------------------------------------------------------------------
+class CGCItemPreviewRequestResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCItemPreviewRequestResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ static void OnPreviewItemConfirm( bool bConfirmed, void *pContext )
+ {
+ InventoryManager()->ShowItemsPickedUp( true, false );
+
+ CStorePage* pStorePage = dynamic_cast<CStorePage*>( EconUI()->GetStorePanel()->GetActivePage() );
+ if ( pStorePage )
+ {
+ pStorePage->UpdateModelPanels();
+ }
+ }
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCItemPreviewRequestResponse_t> msg( pNetPacket );
+
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+ if ( !pStorePanel )
+ return true;
+
+ if ( msg.Body().m_eResponse == k_EGCMsgResponseOK )
+ {
+ // The preview has started.
+ ShowMessageBox( "#ItemPreview_PreviewStartedTitle", "#ItemPreview_PreviewStartedText", "#GameUI_OK", OnPreviewItemConfirm );
+ }
+ else
+ {
+ // The preview cannot start right now for some reason.
+ }
+
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCItemPreviewRequestResponse, "CGCItemPreviewRequestResponse", k_EMsgGCItemPreviewRequestResponse, GCSDK::k_EServerTypeGCClient );
+
+void OpenStoreToItem( bool bConfirmed, void *pContext )
+{
+ CEconPreviewExpiredNotification* pNotification = (CEconPreviewExpiredNotification*) pContext;
+ if ( pNotification )
+ {
+ pNotification->SetIsInUse( false );
+ pNotification->MarkForDeletion();
+ if ( bConfirmed )
+ {
+ EconUI()->OpenStorePanel( pNotification->GetItemDefIndex(), true );
+ }
+ }
+}
+
+CEconPreviewNotification::CEconPreviewNotification( uint64 ulSteamID, uint32 iItemDef )
+ : CEconNotification()
+{
+ SetSteamID( ulSteamID );
+ SetLifetime( 20.0f );
+
+ m_pItemDef = GetItemSchema()->GetItemDefinition( iItemDef );
+ if ( !m_pItemDef )
+ return;
+
+ AddStringToken( "item_name", g_pVGuiLocalize->Find(m_pItemDef->GetItemBaseName()) );
+}
+
+void CEconPreviewExpiredNotification::Trigger()
+{
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_PreviewItem_Expired_Title", "#TF_PreviewItem_Expired_Text", "#TF_PreviewItem_BuyIt", "#TF_PreviewItem_NotNow", &OpenStoreToItem );
+ pDialog->SetContext( this );
+ pDialog->AddStringToken( "item_name", g_pVGuiLocalize->Find(m_pItemDef->GetItemBaseName()) );
+ SetIsInUse( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The GC is telling us our preview item has expired.
+//-----------------------------------------------------------------------------
+class CGCItemPreviewExpireNotification : public GCSDK::CGCClientJob
+{
+public:
+ CGCItemPreviewExpireNotification( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCItemPreviewExpireNotification_t> msg( pNetPacket );
+
+ CEconPreviewExpiredNotification *pNotification = new CEconPreviewExpiredNotification( msg.Hdr().m_ulSteamID, msg.Body().m_unItemDefIndex );
+ pNotification->SetText( "#TF_PreviewItem_Expired" );
+ NotificationQueue_Add( pNotification );
+
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCItemPreviewExpireNotification, "CGCItemPreviewExpireNotification", k_EMsgGCItemPreviewExpireNotification, GCSDK::k_EServerTypeGCClient );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: The GC is telling us we bought our preview item!
+//-----------------------------------------------------------------------------
+class CGCItemPreviewItemBoughtNotification : public GCSDK::CGCClientJob
+{
+public:
+ CGCItemPreviewItemBoughtNotification( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CProtoBufMsg<CMsgGCItemPreviewItemBoughtNotification> msg( pNetPacket );
+
+ CEconPreviewItemBoughtNotification *pNotification = new CEconPreviewItemBoughtNotification( msg.Hdr().client_steam_id(), msg.Body().item_def_index() );
+ pNotification->SetText( "#TF_PreviewItem_ItemBought" );
+ NotificationQueue_Add( pNotification );
+
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCItemPreviewItemBoughtNotification, "CGCItemPreviewItemBoughtNotification", k_EMsgGCItemPreviewItemBoughtNotification, GCSDK::k_EServerTypeGCClient ); \ No newline at end of file
diff --git a/game/client/econ/item_rental_ui.h b/game/client/econ/item_rental_ui.h
new file mode 100644
index 0000000..e610381
--- /dev/null
+++ b/game/client/econ/item_rental_ui.h
@@ -0,0 +1,66 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ITEM_RENTAL_UI_H
+#define ITEM_RENTAL_UI_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "econ_ui.h"
+#include "vgui/ISurface.h"
+#include "econ_controls.h"
+#include "gc_clientsystem.h"
+#include "tool_items/tool_items.h"
+#include "store/store_panel.h"
+#include "vgui_controls/PropertySheet.h"
+#include "confirm_dialog.h"
+#include "econ_notifications.h"
+
+class CEconPreviewNotification : public CEconNotification
+{
+public:
+ CEconPreviewNotification( uint64 ulSteamID, uint32 iItemDef );
+
+ virtual EType NotificationType() { return eType_Trigger; }
+
+ virtual void Trigger() {}
+
+ int GetItemDefIndex()
+ {
+ return m_pItemDef->GetDefinitionIndex();
+ }
+
+public:
+ const CEconItemDefinition *m_pItemDef;
+};
+
+class CEconPreviewExpiredNotification : public CEconPreviewNotification
+{
+public:
+ CEconPreviewExpiredNotification( uint64 ulSteamID, uint32 iItemDef ) : CEconPreviewNotification( ulSteamID, iItemDef ) {}
+
+ virtual EType NotificationType() { return eType_Trigger; }
+
+ virtual void Trigger();
+};
+
+class CEconPreviewItemBoughtNotification : public CEconPreviewNotification
+{
+public:
+ CEconPreviewItemBoughtNotification( uint64 ulSteamID, uint32 iItemDef ) : CEconPreviewNotification( ulSteamID, iItemDef ) {}
+
+ virtual EType NotificationType() { return eType_Trigger; }
+
+ virtual void Trigger()
+ {
+ EconUI()->OpenEconUI( ECONUI_BACKPACK );
+ MarkForDeletion();
+ }
+};
+
+#endif // ITEM_RENTAL_UI_H
diff --git a/game/client/econ/item_selection_panel.cpp b/game/client/econ/item_selection_panel.cpp
new file mode 100644
index 0000000..62a8bf0
--- /dev/null
+++ b/game/client/econ/item_selection_panel.cpp
@@ -0,0 +1,1493 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "item_selection_panel.h"
+#include "vgui/ISurface.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/IInput.h"
+#include "item_model_panel.h"
+#include "econ_item_constants.h"
+#include "econ_item_system.h"
+#include "econ_item_description.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+ConVar tf_item_selection_panel_sort_type( "tf_item_selection_panel_sort_type", 0, FCVAR_NONE, "0 - Sort is off, 1 - Sort is Alphabet (Pub)" );
+
+const char *g_szEquipSlotHeader[] =
+{
+ "#ItemSel_PRIMARY", // LOADOUT_POSITION_PRIMARY = 0,
+ "#ItemSel_SECONDARY", // LOADOUT_POSITION_SECONDARY,
+ "#ItemSel_MELEE", // LOADOUT_POSITION_MELEE,
+ "#ItemSel_UTILITY", // LOADOUT_POSITION_UTILITY // Staging
+ "#ItemSel_PDA", // LOADOUT_POSITION_BUILDING,
+ "#ItemSel_PDA", // LOADOUT_POSITION_PDA,
+ "#ItemSel_PDA", // LOADOUT_POSITION_PDA2
+ "#ItemSel_MISC", // LOADOUT_POSITION_HEAD
+ "#ItemSel_MISC", // LOADOUT_POSITION_MISC
+ "#ItemSel_ACTION", // LOADOUT_POSITION_ACTION
+ "#ItemSel_MISC", // LOADOUT_POSITION_MISC2
+ "#ItemSel_TAUNT", // LOADOUT_POSITION_TAUNT
+ "#ItemSel_TAUNT", // LOADOUT_POSITION_TAUNT2
+ "#ItemSel_TAUNT", // LOADOUT_POSITION_TAUNT3
+ "#ItemSel_TAUNT", // LOADOUT_POSITION_TAUNT4
+ "#ItemSel_TAUNT", // LOADOUT_POSITION_TAUNT5
+ "#ItemSel_TAUNT", // LOADOUT_POSITION_TAUNT6
+ "#ItemSel_TAUNT", // LOADOUT_POSITION_TAUNT7
+ "#ItemSel_TAUNT", // LOADOUT_POSITION_TAUNT8
+#ifdef STAGING_ONLY
+ "#ItemSel_PDA_ADDON1", // LOADOUT_POSITION_PDA_ADDON1
+ "#ItemSel_PDA_ADDON2", // LOADOUT_POSITION_PDA_ADDON2
+ "", // LOADOUT_POSITION_PDA3,
+ "", // LOADOUT_POSITION_BUILDING2,
+#endif // STAGING_ONLY
+};
+COMPILE_TIME_ASSERT( ARRAYSIZE( g_szEquipSlotHeader ) == CLASS_LOADOUT_POSITION_COUNT );
+
+static bool ShouldItemNotStack( CEconItemView *pItemData )
+{
+ CEconItem *pSOCData = pItemData->GetSOCData();
+ if ( pSOCData && pSOCData->BHasDynamicAttributes() )
+ {
+ return true;
+ }
+
+ return false;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemSelectionPanel::CItemSelectionPanel(Panel *parent) : CBaseLoadoutPanel(parent, "ItemSelectionPanel")
+{
+ m_pCaller = parent;
+ m_pSelectionItemModelPanelKVs = NULL;
+ m_pDuplicateLabelKVs = NULL;
+ m_bShowingEntireBackpack = false;
+ m_iItemsInSelection = 0;
+
+ m_bShowDuplicates = false;
+ m_bForceBackpack = false;
+ m_pOnlyAllowUniqueQuality = NULL;
+ m_pShowBackpack = NULL;
+ m_pShowSelection = NULL;
+ m_pNextPageButton = NULL;
+ m_pPrevPageButton = NULL;
+ m_pCurPageLabel = NULL;
+ m_pNoItemsInSelectionLabel = NULL;
+ m_bGotMousePressed = false;
+ m_pNameFilterTextEntry = NULL;
+
+ m_DuplicateCounts.SetLessFunc( DefLessFunc( DuplicateCountsMap_t::KeyType_t ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemSelectionPanel::~CItemSelectionPanel()
+{
+ if ( m_pSelectionItemModelPanelKVs )
+ {
+ m_pSelectionItemModelPanelKVs->deleteThis();
+ m_pSelectionItemModelPanelKVs = NULL;
+ }
+ if ( m_pDuplicateLabelKVs )
+ {
+ m_pDuplicateLabelKVs->deleteThis();
+ m_pDuplicateLabelKVs = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ m_pNameFilterTextEntry = NULL;
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( GetSchemeFile() );
+
+ m_pNoItemsInSelectionLabel = dynamic_cast<vgui::Label*>( FindChildByName("NoItemsLabel") );
+ m_pOnlyAllowUniqueQuality = dynamic_cast<vgui::CheckButton *>( FindChildByName("OnlyAllowUniqueQuality") );
+ m_pShowBackpack = dynamic_cast<CExButton*>( FindChildByName("ShowBackpack") );
+ m_pShowSelection = dynamic_cast<CExButton*>( FindChildByName("ShowSelection") );
+ m_pNextPageButton = dynamic_cast<CExButton*>( FindChildByName("NextPageButton") );
+ m_pPrevPageButton = dynamic_cast<CExButton*>( FindChildByName("PrevPageButton") );
+ m_pCurPageLabel = dynamic_cast<vgui::Label*>( FindChildByName("CurPageLabel") );
+
+ // Give individual selection panels the ability to specify whether or not they want to show the
+ // checkbox controlling quality filtering. It really only makes sense for things like crafting,
+ // not equipment selection.
+ if ( m_pOnlyAllowUniqueQuality )
+ {
+ m_pOnlyAllowUniqueQuality->SetVisible( DisplayOnlyAllowUniqueQualityCheckbox() );
+
+ // By default, if the checkbox is visible, it's enabled. Users can disable it manually if
+ // they want to craft potentially-more-valuable items.
+ if ( m_pOnlyAllowUniqueQuality->IsVisible() )
+ {
+ m_pOnlyAllowUniqueQuality->SetSelected( true );
+ }
+ }
+
+
+ m_pNameFilterTextEntry = FindControl<vgui::TextEntry>( "NameFilterTextEntry" );
+ if ( m_pNameFilterTextEntry )
+ {
+ m_pNameFilterTextEntry->AddActionSignalTarget( this );
+ }
+
+ UpdateModelPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "modelpanels_selection_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pSelectionItemModelPanelKVs )
+ {
+ m_pSelectionItemModelPanelKVs->deleteThis();
+ }
+ m_pSelectionItemModelPanelKVs = new KeyValues("modelpanels_selection_kv");
+ pItemKV->CopySubkeys( m_pSelectionItemModelPanelKVs );
+ }
+
+ KeyValues *pLabelKV = inResourceData->FindKey( "duplicatelabels_kv" );
+ if ( pLabelKV )
+ {
+ if ( m_pDuplicateLabelKVs )
+ {
+ m_pDuplicateLabelKVs->deleteThis();
+ }
+ m_pDuplicateLabelKVs = new KeyValues("duplicatelabels_kv");
+ pLabelKV->CopySubkeys( m_pDuplicateLabelKVs );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::ApplyKVsToItemPanels( void )
+{
+ BaseClass::ApplyKVsToItemPanels();
+
+ if ( !m_bShowingEntireBackpack )
+ {
+ if ( m_pSelectionItemModelPanelKVs )
+ {
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ m_pItemModelPanels[i]->ApplySettings( m_pSelectionItemModelPanelKVs );
+ m_pItemModelPanels[i]->UpdatePanels();
+ }
+ }
+
+ if ( m_pDuplicateLabelKVs )
+ {
+ FOR_EACH_VEC( m_pDuplicateCountLabels, i )
+ {
+ if ( !m_pDuplicateCountLabels[i]->IsVisible() )
+ continue;
+
+ m_pDuplicateCountLabels[i]->ApplySettings( m_pDuplicateLabelKVs );
+ m_pDuplicateCountLabels[i]->SetMouseInputEnabled( false );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ // In backpack mode we show empty slots. Otherwise we don't.
+ bool bVisible = m_bShowingEntireBackpack;
+ if ( !bVisible )
+ {
+ bVisible = (i < GetNumSlotsPerPage()) && ShouldItemPanelBeVisible( m_pItemModelPanels[i], i );
+ }
+ m_pItemModelPanels[i]->SetVisible( bVisible );
+
+ if ( bVisible )
+ {
+ PositionItemPanel( m_pItemModelPanels[i], i );
+ }
+
+ UpdateDuplicateCounts();
+ }
+
+ FOR_EACH_VEC( m_pDuplicateCountLabels, i )
+ {
+ if ( m_pDuplicateCountLabels[i]->IsVisible() )
+ {
+ int iXPos, iYPos;
+ m_pItemModelPanels[i]->GetPos( iXPos, iYPos );
+ m_pDuplicateCountLabels[i]->SetPos( iXPos, iYPos );
+ }
+ }
+
+ m_pShowBackpack->SetVisible( !m_bShowingEntireBackpack && !m_bForceBackpack );
+ m_pShowSelection->SetVisible( m_bShowingEntireBackpack && !m_bForceBackpack );
+
+ m_pNextPageButton->SetVisible( true );
+ m_pPrevPageButton->SetVisible( true );
+ m_pCurPageLabel->SetVisible( true );
+ m_pNextPageButton->SetEnabled( GetNumPages() > 1 );
+ m_pPrevPageButton->SetEnabled( GetNumPages() > 1 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::OnThink( void )
+{
+ BaseClass::OnThink();
+
+ if ( m_flFilterItemTime && gpGlobals->curtime >= m_flFilterItemTime )
+ {
+ SetCurrentPage( 0 );
+ //DeSelectAllBackpackItemPanels();
+ UpdateModelPanels();
+
+ m_flFilterItemTime = 0.0f;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "vguicancel" ) )
+ {
+ PostMessageSelectionReturned( INVALID_ITEM_ID );
+
+ OnClose();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "nextpage", 8 ) )
+ {
+ HideMouseOverPanel();
+ SetCurrentPage( GetCurrentPage() + 1 );
+ UpdateModelPanels();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "prevpage", 8 ) )
+ {
+ HideMouseOverPanel();
+ SetCurrentPage( GetCurrentPage() - 1 );
+ UpdateModelPanels();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "show_backpack", 8 ) )
+ {
+ m_bReapplyItemKVs = true;
+ m_bShowingEntireBackpack = true;
+ UpdateModelPanels();
+ //Repaint();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "show_selection", 8 ) )
+ {
+ m_bReapplyItemKVs = true;
+ m_bShowingEntireBackpack = false;
+ UpdateModelPanels();
+ //Repaint();
+ return;
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::OnButtonChecked( KeyValues *pData )
+{
+ Assert( reinterpret_cast<vgui::Panel *>( pData->GetPtr("panel") ) == m_pOnlyAllowUniqueQuality );
+ UpdateModelPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle the escape key because it doesn't come through as "pressed"
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::OnKeyCodeTyped(vgui::KeyCode code)
+{
+ if ( code == KEY_ESCAPE )
+ {
+ // 0 implies do nothing, INVALID_ITEM_ID means stock and we dont want to equip stock
+ PostMessageSelectionReturned( 0 );
+ OnClose();
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles keypresses in the item selection panel
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::OnKeyCodePressed( vgui::KeyCode code )
+{
+ // let our parent class handle all the arrow key/dpad stuff
+ if( HandleItemSelectionKeyPressed( code ) )
+ {
+ return;
+ }
+
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if( nButtonCode == KEY_XBUTTON_A || code == KEY_ENTER || nButtonCode == STEAMCONTROLLER_A )
+ {
+ CItemModelPanel *pItemPanel = GetFirstSelectedItemModelPanel( true );
+ if( pItemPanel && !pItemPanel->IsGreyedOut() )
+ {
+ NotifySelectionReturned( pItemPanel );
+ }
+ }
+ else if( nButtonCode == KEY_XBUTTON_B )
+ {
+ PostMessageSelectionReturned( INVALID_ITEM_ID );
+ OnClose();
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles key release events in the backpack
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::OnKeyCodeReleased( vgui::KeyCode code )
+{
+ if( ! HandleItemSelectionKeyReleased( code ) )
+ BaseClass::OnKeyCodeReleased( code );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::OnClose( void )
+{
+ BaseClass::OnClose();
+
+ if ( ShouldDeleteOnClose() )
+ {
+ // Delete ourself now that we're done
+ MarkForDeletion();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::SetVisible( bool bState )
+{
+ BaseClass::SetVisible( bState );
+
+ if( bState )
+ {
+ m_wNameFilter.RemoveAll();
+ if( m_pNameFilterTextEntry )
+ {
+ m_pNameFilterTextEntry->SetText( "" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::OnItemPanelMousePressed( vgui::Panel *panel )
+{
+ // This is annoying. Why do I get mouse released events for releases that started with a push before I was visible?
+ m_bGotMousePressed = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::OnItemPanelMouseReleased( vgui::Panel *panel )
+{
+ if ( !m_bGotMousePressed )
+ return;
+ m_bGotMousePressed = false;
+
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ NotifySelectionReturned( pItemPanel );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Lets the parent know what the selection was
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::NotifySelectionReturned( CItemModelPanel *pItemPanel )
+{
+ if ( pItemPanel && IsVisible() )
+ {
+ CEconItemView *pItemData = pItemPanel->GetItem();
+ if ( GetItemNotSelectableReason( pItemData ) != NULL )
+ return;
+
+ if ( DisableItemSelectionFromGrayedOutPanels() && pItemPanel->IsGreyedOut() )
+ return;
+
+ itemid_t ulItemID = INVALID_ITEM_ID;
+ if ( pItemData && pItemData->IsValid() )
+ {
+ ulItemID = pItemData->GetItemID();
+ }
+
+ PostMessageSelectionReturned( ulItemID );
+ }
+
+ OnClose();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::UpdateModelPanels( void )
+{
+ // If we're showing the whole backpack, go through the inventory like the backpack does.
+ if ( m_bShowingEntireBackpack )
+ {
+ UpdateModelPanelsForSelection();
+
+ if ( m_pNoItemsInSelectionLabel )
+ {
+ m_pNoItemsInSelectionLabel->SetVisible( false );
+ }
+ }
+ else
+ {
+ // Clear the dupe counts.
+ m_DuplicateCounts.Purge();
+ UpdateModelPanelsForSelection();
+
+ if ( m_pNoItemsInSelectionLabel )
+ {
+ m_pNoItemsInSelectionLabel->SetVisible( m_iItemsInSelection == 0 );
+ }
+ }
+
+ // Update the current backpack page
+ char szTmp[16];
+ Q_snprintf(szTmp, 16, "%d/%d", GetCurrentPage()+1, GetNumPages() );
+ SetDialogVariable( "backpackpage", szTmp );
+
+ // And we're done!
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::UpdateDuplicateCounts( void )
+{
+ bool bShow = (m_bShowDuplicates && !m_bShowingEntireBackpack);
+ if ( !bShow )
+ {
+ FOR_EACH_VEC( m_pDuplicateCountLabels, i )
+ {
+ m_pDuplicateCountLabels[i]->SetVisible( false );
+ }
+ return;
+ }
+
+ FOR_EACH_VEC( m_pItemModelPanels, i )
+ {
+ if ( i >= GetNumSlotsPerPage() )
+ break;
+
+ if ( !m_pItemModelPanels[i]->IsVisible() )
+ {
+ m_pDuplicateCountLabels[i]->SetVisible( false );
+ continue;
+ }
+
+ if ( m_pDuplicateCountLabels.Count() <= i )
+ {
+ CExLabel *pLabel = new CExLabel( this, "", "x1" );
+ m_pDuplicateCountLabels.AddToTail( pLabel );
+
+ if ( m_pDuplicateLabelKVs )
+ {
+ pLabel->ApplySettings( m_pDuplicateLabelKVs );
+ }
+ pLabel->MakeReadyForUse();
+ pLabel->InvalidateLayout( true );
+ pLabel->SetMouseInputEnabled( false );
+ }
+
+ CEconItemView *pItem = m_pItemModelPanels[i]->GetItem();
+ if ( !pItem || !pItem->IsValid() || ShouldItemNotStack( pItem ) )
+ {
+ m_pDuplicateCountLabels[i]->SetVisible( false );
+ continue;
+ }
+
+ int iIndex = m_DuplicateCounts.Find( item_stack_type_t( pItem->GetItemDefIndex(), pItem->GetQuality() ) );
+ if ( iIndex == m_DuplicateCounts.InvalidIndex() || m_DuplicateCounts[iIndex] <= 1 )
+ {
+ m_pDuplicateCountLabels[i]->SetVisible( false );
+ continue;
+ }
+
+ wchar_t wzCost[10];
+ _snwprintf( wzCost, ARRAYSIZE( wzCost ), L"x%d", m_DuplicateCounts[iIndex] );
+ m_pDuplicateCountLabels[i]->SetText( wzCost );
+ m_pDuplicateCountLabels[i]->SetVisible( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::CreateItemPanels( void )
+{
+ // Always create the maximum number of panels
+ int iNumPanels = BACKPACK_SLOTS_PER_PAGE;
+ if ( m_pItemModelPanels.Count() < iNumPanels )
+ {
+ for ( int i = m_pItemModelPanels.Count(); i < iNumPanels; i++ )
+ {
+ AddNewItemPanel(i);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CItemSelectionPanel::GetNumPages( void )
+{
+ int iNumItems = 0;
+ if ( m_bShowingEntireBackpack )
+ {
+ iNumItems = InventoryManager()->GetLocalInventory()->GetMaxItemCount();
+ }
+ else
+ {
+ iNumItems = m_iItemsInSelection;
+ }
+
+ return (int)(ceil((float)iNumItems / (float)GetNumItemPanels()));
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::SetCurrentPage( int nNewPage )
+{
+ if ( nNewPage < 0 )
+ {
+ nNewPage = GetNumPages() - 1;
+ }
+ else if ( nNewPage >= GetNumPages() )
+ {
+ nNewPage = 0;
+ }
+
+ BaseClass::SetCurrentPage( nNewPage );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::PositionItemPanel( CItemModelPanel *pPanel, int iIndex )
+{
+ int iCenter = GetWide() * 0.5;
+ int iButtonX = (iIndex % GetNumColumns());
+ int iButtonY = (iIndex / GetNumColumns());
+ int iXPos = (iCenter + m_iItemBackpackOffcenterX) + (iButtonX * m_pItemModelPanels[iIndex]->GetWide()) + (m_iItemBackpackXDelta * iButtonX);
+ int iYPos = m_iItemYPos + (iButtonY * m_pItemModelPanels[iIndex]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY);
+
+ m_pItemModelPanels[iIndex]->SetPos( iXPos, iYPos );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::PostMessageSelectionReturned( itemid_t ulItemID )
+{
+ KeyValues *pKey = new KeyValues( "SelectionReturned" );
+ pKey->SetUint64( "itemindex", ulItemID );
+ PostMessage( m_pCaller, pKey );
+}
+
+//=====================================================================================================================
+// EQUIP SLOT ITEM SELECTION PANEL
+//=====================================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CEquipSlotItemSelectionPanel::CEquipSlotItemSelectionPanel(Panel *parent, int iClass, int iSlot) : CItemSelectionPanel( parent )
+{
+ m_iClass = iClass;
+ m_iSlot = iSlot;
+
+ m_pWeaponLabel = NULL;
+
+ m_flFilterItemTime = 0.f;
+
+ m_iCurrentItemID = INVALID_ITEM_ID;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEquipSlotItemSelectionPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pWeaponLabel = dynamic_cast<vgui::Label*>( FindChildByName("ItemSlotLabel") );
+
+ TFPlayerClassData_t *pData = GetPlayerClassData( m_iClass );
+ SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( pData->m_szLocalizableName ) );
+
+ if ( m_pWeaponLabel )
+ {
+ m_pWeaponLabel->SetText( g_szEquipSlotHeader[m_iSlot] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEquipSlotItemSelectionPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CEquipSlotItemSelectionPanel::ShouldItemPanelBeVisible( CItemModelPanel *pPanel, int iPanelIndex )
+{
+ // If we don't have an item, but we're the first model panel on a slot
+ // that has no base item, we still want to be visible because we're the
+ // panel that allows players to select "Empty" for the slot.
+ return ( pPanel->HasItem() || (iPanelIndex == 0 && !TFInventoryManager()->SlotContainsBaseItems( GEconItemSchema().GetEquipTypeFromClassIndex( m_iClass ), m_iSlot )) );
+}
+
+//-----------------------------------------------------------------------------
+// Helper classes/functions for CItemSelectionPanel::UpdateModelPanels().
+//-----------------------------------------------------------------------------
+// Used to sort/verify uniqueness of user-facing items.
+struct RarityEconIdKey
+{
+ int m_iQualitySort;
+ item_definition_index_t m_defIndex;
+ uint32 m_unKillEaterScore;
+
+ RarityEconIdKey ( )
+ : m_iQualitySort( -1 )
+ , m_defIndex( INVALID_ITEM_DEF_INDEX )
+ , m_unKillEaterScore( 0 )
+ {
+ //
+ }
+
+ RarityEconIdKey ( int iQuality, item_definition_index_t defIndex, uint32 unKillEaterScore )
+ : m_iQualitySort( EconQuality_GetRarityScore( (EEconItemQuality)iQuality ) )
+ , m_defIndex( defIndex )
+ , m_unKillEaterScore( unKillEaterScore )
+ {
+ //
+ }
+
+ bool operator< ( const RarityEconIdKey& rhs ) const
+ {
+ return m_defIndex < rhs.m_defIndex
+ || m_iQualitySort < rhs.m_iQualitySort
+ || m_unKillEaterScore < rhs.m_unKillEaterScore;
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static int SortRarityEconIdKeysBackpack ( CEconItemView *const *a, CEconItemView *const *b )
+{
+ Assert( a );
+ Assert( *a );
+ Assert( b );
+ Assert( *b );
+
+ // Sorting by the backpack order doesn't need to check any subproperties like level because
+ // every item should have a unique backpack slot already/
+ return ExtractBackpackPositionFromBackend( (*a)->GetInventoryPosition() ) < ExtractBackpackPositionFromBackend( (*b)->GetInventoryPosition() )
+ ? -1
+ : 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static int SortRarityEconIdKeysAlphabetical_Views ( CEconItemView *const *a, CEconItemView *const *b )
+{
+ Assert( a );
+ Assert( *a );
+ Assert( b );
+ Assert( *b );
+
+ // First pass -- sort backpack items by user-visible display name.
+ // Note: locale-savvy string sorting uses wcscoll, not wcscmp
+ int iStrCmpRes = wcscoll( (*a)->GetItemName(), (*b)->GetItemName() );
+ if ( iStrCmpRes != 0 )
+ return iStrCmpRes;
+
+ // Sort by kill eater score as a last-ditch ordering attempt.
+ static CSchemaAttributeDefHandle pAttrDef_KillEaterScore( "kill eater" );
+
+ uint32 unKillEaterScoreA = 0,
+ unKillEaterScoreB = 0;
+
+ (*a)->FindAttribute( pAttrDef_KillEaterScore, &unKillEaterScoreA );
+ (*b)->FindAttribute( pAttrDef_KillEaterScore, &unKillEaterScoreB );
+
+ // Our names match so sort by quality for similarly-named items.
+ if ( EconQuality_GetRarityScore( (EEconItemQuality)(*a)->GetItemQuality() ) < EconQuality_GetRarityScore( (EEconItemQuality)(*b)->GetItemQuality() ) ||
+ unKillEaterScoreA < unKillEaterScoreB ||
+ (*a)->GetItemLevel() < (*b)->GetItemLevel() )
+ {
+ return -1;
+ }
+
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static int SortRarityEconIdKeysAlphabetical ( const CEquippableItemsForSlotGenerator::CEquippableResult *a, const CEquippableItemsForSlotGenerator::CEquippableResult *b )
+{
+ return SortRarityEconIdKeysAlphabetical_Views( &a->m_pEconItemView, &b->m_pEconItemView );
+}
+
+//-----------------------------------------------------------------------------
+static int SortRarityEconIdKeysDate( const CEquippableItemsForSlotGenerator::CEquippableResult *a, const CEquippableItemsForSlotGenerator::CEquippableResult *b )
+{
+ return ( a->m_pEconItemView->GetID() > b->m_pEconItemView->GetID() ) ? -1 : ( a->m_pEconItemView->GetID() < b->m_pEconItemView->GetID() ) ? 1 : 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: figure out what items should be displayed to the user for a specific
+// loadout and what order they should appear in.
+//-----------------------------------------------------------------------------
+CEquippableItemsForSlotGenerator::CEquippableItemsForSlotGenerator( int iClass, int iSlot, equip_region_mask_t unUsedEquipRegionMask, unsigned int unFlags )
+ : m_pEquippedItemView( NULL )
+{
+ m_DuplicateCountsMap.SetLessFunc( DefLessFunc( DuplicateCountMap_t::KeyType_t ) );
+
+ // Misc and building slot items have multiple positions they can be assigned to in the loadout
+ // screen but internally they're all tagged as "misc slot" items so that's what we search for.
+ int iSearchSlot = iSlot;
+ if ( GEconItemSchema().GetAccountIndex() == iClass )
+ {
+ if ( IsQuestSlot( iSearchSlot ) )
+ {
+ iSearchSlot = ACCOUNT_LOADOUT_POSITION_ACCOUNT1;
+ }
+ }
+ else
+ {
+ if ( IsMiscSlot( iSearchSlot ) )
+ {
+ iSearchSlot = LOADOUT_POSITION_MISC;
+ }
+ else if ( IsBuildingSlot( iSearchSlot ) )
+ {
+ iSearchSlot = LOADOUT_POSITION_BUILDING;
+ }
+ else if ( IsTauntSlot( iSearchSlot ) )
+ {
+ iSearchSlot = LOADOUT_POSITION_TAUNT;
+ }
+ }
+
+
+
+ // To start with, generate a list of all potentially-useable items that we want to consider for
+ // the UI. We'll strip this down based on duplicates/gameplay restrictions and sort it at the end.
+ CUtlVector<CEconItemView*> vecItems;
+ int iNumItems = TFInventoryManager()->GetAllUsableItemsForSlot( iClass, iSearchSlot, &vecItems );
+
+ CEconItemView *pEquippedItem = NULL;
+
+ typedef CUtlMap<RarityEconIdKey, CEquippableItemsForSlotGenerator::CEquippableResult> HighestLevelMap_t;
+ HighestLevelMap_t mapHighestLevel;
+ SetDefLessFunc( mapHighestLevel );
+
+ // We also prevent items with different kill eater scores from stacking.
+ static CSchemaAttributeDefHandle pAttrDef_KillEaterScore( "kill eater" );
+
+ // Iterate over the list of all the items that we consider as potential display candidates.
+ for ( int i = 0; i < iNumItems; i++ )
+ {
+ CEconItemView *pItem = vecItems[i];
+
+ // Before doing any sort of culling, count the number of unique instances of this particular
+ // definition.
+ {
+ const item_stack_type_t stackType( pItem->GetItemDefIndex(), pItem->GetQuality() );
+ DuplicateCountMap_t::IndexType_t iIndex = m_DuplicateCountsMap.Find( stackType );
+ if ( iIndex == m_DuplicateCountsMap.InvalidIndex() )
+ {
+ m_DuplicateCountsMap.Insert( stackType, 1 );
+ }
+ else
+ {
+ m_DuplicateCountsMap[ iIndex ]++;
+ }
+ }
+
+ // Track whether this is our currently-equipped item.
+ CEconItemView *pCurItemData = TFInventoryManager()->GetItemInLoadoutForClass( iClass, iSlot );
+ if ( pCurItemData && pCurItemData->GetItemID() && pCurItemData->GetItemID() == pItem->GetItemID() )
+ {
+ pEquippedItem = pItem;
+ }
+
+ // If this item conflicts with items we already have equipped, we note that so that it shows up
+ // differently.
+ CEquippableItemsForSlotGenerator::EItemDisplayType eDisplayType = kSlotDisplay_Normal;
+
+ if ( pItem->GetItemDefinition()->GetEquipRegionMask() & unUsedEquipRegionMask )
+ {
+ eDisplayType = kSlotDisplay_Disabled_EquipRegionConflict;
+ }
+
+ // If we're listing *all* items, including duplicates, we just add everything to the list at once and
+ // move on. We still do the above equipped-item specialcasing.
+ if ( unFlags & kSlotGenerator_ShowDuplicates )
+ {
+ m_vecDisplayItems.AddToTail( CEquippableItemsForSlotGenerator::CEquippableResult( pItem, eDisplayType ) );
+ continue;
+ }
+
+ // Has this item been modified by the user in some way? If so, always list.
+ if ( ShouldItemNotStack( pItem ) )
+ {
+ m_vecDisplayItems.AddToTail( CEquippableItemsForSlotGenerator::CEquippableResult( pItem, eDisplayType ) );
+ continue;
+ }
+
+ // Throw this item into the running map of the highest level item of this type we've seen so far.
+ // "Of this type" means "has a matching rarity and item definition index", so uniques will be sorted
+ // differently from unusuals, but both will show their highest-level item.
+ //
+ // This code does make the assumption that nothing in the key will be able to affect the display type.
+ // (ie., a higher-level item will never be equippable where a lower-level item is not)
+ {
+ uint32 unKillEaterScore = 0;
+ pItem->FindAttribute( pAttrDef_KillEaterScore, &unKillEaterScore );
+
+ RarityEconIdKey keyEconId( pItem->GetItemQuality(), pItem->GetItemDefIndex(), unKillEaterScore );
+ HighestLevelMap_t::IndexType_t iIndex = mapHighestLevel.Find( keyEconId );
+ if ( iIndex == mapHighestLevel.InvalidIndex() || pItem->GetItemLevel() > mapHighestLevel[iIndex].m_pEconItemView->GetItemLevel() )
+ {
+ mapHighestLevel.InsertOrReplace( keyEconId, CEquippableItemsForSlotGenerator::CEquippableResult( pItem, eDisplayType ) );
+ }
+ }
+ }
+
+ // Take the resulting list of items we've force-added and items we've taken the highest-level representative
+ // from and put them all into our unsorted display list.
+ FOR_EACH_MAP( mapHighestLevel, i )
+ {
+ m_vecDisplayItems.AddToTail( mapHighestLevel[i] );
+ }
+
+ // Always leave a base item in the list. We don't have to worry about level changes or other weird overlaps
+ // for base weapons. If we don't have an equipped item, this must be the equipped item.
+ CEconItemView *pBaseItem = TFInventoryManager()->GetBaseItemForClass( iClass, iSlot );
+ if ( pBaseItem && pBaseItem->IsValid() )
+ {
+ if ( !pEquippedItem )
+ {
+ pEquippedItem = pBaseItem;
+ }
+ m_vecDisplayItems.AddToTail( pBaseItem );
+ }
+
+ // If the equipped item is in the list, remove it if we're going to manually add it at the top of the display
+ // list later. We add it to the list above and remove it here to make sure that we don't get weird effects
+ // when the equipped item would be the highest level example of an item, etc.
+ if ( pEquippedItem )
+ {
+ if ( unFlags & kSlotGenerator_EquippedSpecialHandling )
+ {
+ m_vecDisplayItems.FindAndFastRemove( pEquippedItem );
+ m_pEquippedItemView = pEquippedItem;
+ }
+ // Special case adding our equipped item even if it doesn't meet the ordinary display criteria. It might
+ // not be the highest level or rarity, but the user equipped it somehow so make sure it shows up.
+ else if ( m_vecDisplayItems.Find( pEquippedItem ) == -1 )
+ {
+ m_vecDisplayItems.AddToTail( pEquippedItem );
+ }
+ }
+
+ // Remove duplicates and sort to put it into the order they'll be displayed to the user.
+ // Sort the items based on selection type
+ int iSortType = tf_item_selection_panel_sort_type.GetInt();
+ switch ( iSortType )
+ {
+ case 0: m_vecDisplayItems.Sort( &SortRarityEconIdKeysDate ); break;
+ case 1: m_vecDisplayItems.Sort( &SortRarityEconIdKeysAlphabetical ); break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+equip_region_mask_t GenerateEquipRegionConflictMask( int iClass, int iUpToSlot, int iIgnoreSlot )
+{
+ Assert( iUpToSlot <= CLASS_LOADOUT_POSITION_COUNT );
+
+ equip_region_mask_t unEquippedRegionMask = 0;
+ for ( int i = 0; i < iUpToSlot; i++ )
+ {
+ if ( i == iIgnoreSlot )
+ continue;
+
+ const CEconItemView *pEquippedItemView = TFInventoryManager()->GetItemInLoadoutForClass( iClass, i );
+ if ( !pEquippedItemView )
+ continue;
+
+ unEquippedRegionMask |= pEquippedItemView->GetItemDefinition()->GetEquipRegionConflictMask();
+ }
+
+ return unEquippedRegionMask;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEquipSlotItemSelectionPanel::UpdateModelPanelsForSelection( void )
+{
+ Assert( !DisplayOnlyAllowUniqueQualityCheckbox() );
+
+ const bool bShowEquippedItemFirst = true;
+
+ CEquippableItemsForSlotGenerator::EquippableResultsVec_t vecDisplayItems;
+ const wchar_t* wscFilter = m_wNameFilter.Count() ? m_wNameFilter.Base() : NULL;
+
+ // Generate a mask that is "every equip region we're using except for whatever is in this current slot" and use
+ // that to generate a list of everything we could possibly equip for this current slot.
+ const equip_region_mask_t unUsedEquipRegionMask = GenerateEquipRegionConflictMask( m_iClass, CLASS_LOADOUT_POSITION_COUNT, m_iSlot );
+
+ CEquippableItemsForSlotGenerator equippableItems( m_iClass,
+ m_iSlot,
+ unUsedEquipRegionMask,
+ bShowEquippedItemFirst ? CEquippableItemsForSlotGenerator::kSlotGenerator_EquippedSpecialHandling : CEquippableItemsForSlotGenerator::kSlotGenerator_None );
+
+
+ if( m_bShowingEntireBackpack )
+ {
+ int iNumItems = TFInventoryManager()->GetLocalTFInventory()->GetMaxItemCount();
+ for ( int i = 1; i <= iNumItems; i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetItemByBackpackPosition(i);
+ if ( pItemData && pItemData->IsValid() )
+ {
+ if( !DoesItemPassSearchFilter( pItemData->GetDescription(), wscFilter ) )
+ continue;
+
+ CEquippableItemsForSlotGenerator::CEquippableResult& result = vecDisplayItems[ vecDisplayItems.AddToTail( pItemData ) ];
+ result.m_eDisplayType = GetItemNotSelectableReason( pItemData ) ? CEquippableItemsForSlotGenerator::kSlotDisplay_Invalid : CEquippableItemsForSlotGenerator::kSlotDisplay_Normal;
+ }
+ }
+ }
+ else
+ {
+ // Copy the generated data back into our local structures.
+ DeepCopyMap( equippableItems.GetDuplicateCountMap(), &m_DuplicateCounts );
+
+ const CEquippableItemsForSlotGenerator::EquippableResultsVec_t& allDisplayItems = equippableItems.GetDisplayItems();
+ for ( int i=0; i<allDisplayItems.Count(); ++i )
+ {
+ if ( DoesItemPassSearchFilter( allDisplayItems[i].m_pEconItemView->GetDescription(), wscFilter ) )
+ {
+ vecDisplayItems.AddToTail( allDisplayItems[i] );
+ }
+ }
+ }
+
+ CEconItemView *pEquippedItem = equippableItems.GetEquippedItem();
+ if ( pEquippedItem )
+ {
+ if ( bShowEquippedItemFirst && DoesItemPassSearchFilter( pEquippedItem->GetDescription(), wscFilter ) )
+ {
+ vecDisplayItems.AddToHead( pEquippedItem );
+ }
+
+ m_iCurrentItemID = pEquippedItem->GetItemID();
+ }
+
+ // If the loadout slot is one that players can have empty, put a "nothing" entry at the end of the list.
+ if ( !TFInventoryManager()->SlotContainsBaseItems( GEconItemSchema().GetEquipTypeFromClassIndex( m_iClass ), m_iSlot ) )
+ {
+ vecDisplayItems.AddToHead( NULL );
+ }
+
+ m_iItemsInSelection = vecDisplayItems.Count();
+
+ // Make sure we're not on an invalid page.
+ if ( GetCurrentPage() >= GetNumPages() )
+ {
+ SetCurrentPage( 0 );
+ }
+
+ int nOldSelection = GetFirstSelectedItemIndex( true );
+ int nPageStart = GetCurrentPage() * GetNumSlotsPerPage();
+ nOldSelection += nPageStart;
+
+ static ConVarRef joystick( "joystick" );
+ if ( joystick.IsValid() && joystick.GetBool() )
+ {
+ if( nOldSelection == -1 || nOldSelection >= vecDisplayItems.Count() )
+ nOldSelection = nPageStart;
+ }
+
+
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ int iItemIndex = i + nPageStart;
+
+ // We only show the equipped state for the equipped items, below
+ m_pItemModelPanels[i]->SetShowEquipped( false );
+ m_pItemModelPanels[i]->SetShowGreyedOutTooltip( true );
+ m_pItemModelPanels[i]->SetGreyedOut( NULL );
+ m_pItemModelPanels[i]->SetNoItemText( "#SelectNoItemSlot" );
+ bool bSelected = joystick.IsValid() && joystick.GetBool() && iItemIndex == nOldSelection;
+ m_pItemModelPanels[i]->SetSelected( bSelected );
+ m_pItemModelPanels[i]->SetShowQuantity( true );
+ m_pItemModelPanels[i]->SetForceShowEquipped( false );
+
+ bool bShowEquipped = false;
+ if ( vecDisplayItems.Count() > iItemIndex )
+ {
+ const char* pszGreyOutReason = NULL;
+ if( m_bShowingEntireBackpack )
+ {
+ pszGreyOutReason = GetItemNotSelectableReason( vecDisplayItems[iItemIndex].m_pEconItemView );
+ }
+ else
+ {
+ pszGreyOutReason = vecDisplayItems[iItemIndex].m_eDisplayType == CEquippableItemsForSlotGenerator::kSlotDisplay_Normal ? NULL : "#Econ_GreyOutReason_EquipRegionConflict";
+ }
+ // Show equipped state on base items too
+ bShowEquipped = false;
+ // Check if this item is already equipped, potentially in another slot
+ if ( vecDisplayItems[iItemIndex].m_pEconItemView )
+ {
+ bShowEquipped |= vecDisplayItems[iItemIndex].m_pEconItemView->IsEquippedForClass( m_iClass );
+ }
+
+ // Check if this item is the currently equipped item
+ if ( pEquippedItem )
+ {
+ bShowEquipped |= pEquippedItem == vecDisplayItems[iItemIndex].m_pEconItemView;
+ }
+
+ m_pItemModelPanels[i]->SetForceShowEquipped( bShowEquipped );
+ m_pItemModelPanels[i]->SetItem( vecDisplayItems[iItemIndex].m_pEconItemView );
+ m_pItemModelPanels[i]->SetGreyedOut( pszGreyOutReason );
+ }
+ else
+ {
+ m_pItemModelPanels[i]->SetItem( NULL );
+ }
+
+ SetBorderForItem( m_pItemModelPanels[i], false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CEquipSlotItemSelectionPanel::GetItemNotSelectableReason( const CEconItemView *pItem ) const
+{
+ if ( !pItem )
+ return NULL;
+
+ CTFItemDefinition *pItemData = pItem->GetStaticData();
+
+ if ( !pItemData->CanBeUsedByClass(m_iClass) )
+ return "#Econ_GreyOutReason_CannotBeUsedByThisClass";
+
+ extern bool AreSlotsConsideredIdentical( EEquipType_t eEquipType, int iBaseSlot, int iTestSlot );
+ if ( !AreSlotsConsideredIdentical( pItem->GetStaticData()->GetEquipType(), pItemData->GetLoadoutSlot(m_iClass), m_iSlot ) )
+ return "#Econ_GreyOutReason_CannotBeUsedInThisSlot";
+
+ // Should we gray out this item? This will happen if we're coming from the loadout and we have equip region
+ // conflicts of some kind.
+ const equip_region_mask_t unUsedEquipRegionMask = GenerateEquipRegionConflictMask( m_iClass, CLASS_LOADOUT_POSITION_COUNT, m_iSlot );
+ if ( pItemData->GetEquipRegionMask() & unUsedEquipRegionMask )
+ return "#Econ_GreyOutReason_EquipRegionConflict";
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEquipSlotItemSelectionPanel::OnBackPressed()
+{
+ Assert( m_iCurrentItemID != INVALID_ITEM_DEF_INDEX );
+ PostMessageSelectionReturned( m_iCurrentItemID );
+ OnClose();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemSelectionPanel::OnTextChanged( KeyValues *data )
+{
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
+
+ vgui::TextEntry *pTextEntry = dynamic_cast<vgui::TextEntry *>( pPanel );
+ if ( pTextEntry )
+ {
+ if ( pTextEntry == m_pNameFilterTextEntry )
+ {
+ m_wNameFilter.RemoveAll();
+ if ( m_pNameFilterTextEntry->GetTextLength() )
+ {
+ m_wNameFilter.EnsureCount( m_pNameFilterTextEntry->GetTextLength() + 1 );
+ m_pNameFilterTextEntry->GetText( m_wNameFilter.Base(), m_wNameFilter.Count() * sizeof(wchar_t) );
+ V_wcslower( m_wNameFilter.Base() );
+ }
+ m_flFilterItemTime = gpGlobals->curtime + 0.5f;
+ return;
+ }
+ }
+}
+
+
+//=====================================================================================================================
+// ITEM CRITERIA BASED SELECTION PANEL
+//=====================================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CItemCriteriaSelectionPanel::CItemCriteriaSelectionPanel(Panel *parent, const CItemSelectionCriteria *pCriteria, itemid_t pExceptions[], int iNumExceptions ) : CItemSelectionPanel( parent )
+{
+ m_pCriteria = pCriteria;
+
+ UpdateExceptions( pExceptions, iNumExceptions );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemCriteriaSelectionPanel::UpdateExceptions( itemid_t pExceptions[], int iNumExceptions )
+{
+ m_Exceptions.Purge();
+
+ for ( int i = 0; i < iNumExceptions; i++ )
+ {
+ m_Exceptions.AddToTail( pExceptions[i] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemCriteriaSelectionPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ vgui::Label *pLabel = dynamic_cast<vgui::Label*>( FindChildByName("ItemSlotLabel") );
+ if ( pLabel )
+ {
+ pLabel->SetVisible( false );
+ }
+ SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( "#Craft_SelectItemPanel" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CItemCriteriaSelectionPanel::ShouldItemPanelBeVisible( CItemModelPanel *pPanel, int iPanelIndex )
+{
+ // First "Empty" panel is always visible.
+ return ( pPanel->HasItem() || iPanelIndex == 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CItemCriteriaSelectionPanel::UpdateModelPanelsForSelection( void )
+{
+ CUtlVector<CEconItemView*> vecDisplayItems;
+
+ CUtlVector<item_stack_type_t> vecDefsFound;
+
+ const wchar_t* wscFilter = m_wNameFilter.Count() ? m_wNameFilter.Base() : NULL;
+
+ int iNumItems = TFInventoryManager()->GetLocalTFInventory()->GetMaxItemCount();
+ for ( int i = 1; i <= iNumItems; i++ )
+ {
+ CEconItemView *pItemData = TFInventoryManager()->GetItemByBackpackPosition(i);
+ bool bAdd = false;
+ if ( pItemData && pItemData->IsValid() )
+ {
+ // If this is a valid item, we want to show this if we're showing our entire backpack
+ // or we doing the tailored list and this item matches.
+ if ( m_bShowingEntireBackpack || GetItemNotSelectableReason( pItemData ) == NULL )
+ {
+ // The item also needs to pass the text search filter as well
+ if( DoesItemPassSearchFilter( pItemData->GetDescription(), wscFilter ) )
+ {
+ bAdd = true;
+ }
+ }
+ }
+ // When showing our entire backpack we take everything so long as we arent filtering
+ else if( wscFilter == NULL && m_bShowingEntireBackpack )
+ {
+ bAdd = true;
+ }
+
+ if( bAdd )
+ {
+ // For actual items see if we should stack. Only do so if we're NOT showing
+ // the entire backpack
+ if( pItemData && !m_bShowingEntireBackpack )
+ {
+ // Has this item been modified by the user in some way? If so, always list,
+ // but don't add it to our duplicate count, or our "found indices" list.
+ item_definition_index_t iDefIndex = pItemData->GetItemDefIndex();
+ if ( ShouldItemNotStack( pItemData ) )
+ {
+ vecDisplayItems.AddToTail( pItemData );
+ continue;
+ }
+
+ item_stack_type_t stackType( iDefIndex, pItemData->GetQuality() );
+ int iIndex = m_DuplicateCounts.Find( stackType );
+ if ( iIndex == m_DuplicateCounts.InvalidIndex() )
+ {
+ m_DuplicateCounts.Insert( stackType, 1 );
+ }
+ else
+ {
+ m_DuplicateCounts[ iIndex ]++;
+ }
+
+ if ( vecDefsFound.Find( stackType ) != vecDefsFound.InvalidIndex() )
+ continue;
+
+ vecDefsFound.AddToTail( stackType );
+ }
+
+ vecDisplayItems.AddToTail( pItemData );
+ }
+ }
+
+ // Sort them alphabetically if not viewing entire backpack
+ if( !m_bShowingEntireBackpack )
+ {
+ vecDisplayItems.Sort( &SortRarityEconIdKeysAlphabetical_Views );
+ }
+
+ // Add an "Empty" item to the start
+ vecDisplayItems.AddToHead( NULL );
+
+ m_iItemsInSelection = vecDisplayItems.Count();
+
+ // Make sure we're not on an invalid page.
+ if ( GetCurrentPage() >= GetNumPages() )
+ {
+ SetCurrentPage( 0 );
+ }
+
+ for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
+ {
+ int iItemIndex = i + (GetCurrentPage() * GetNumSlotsPerPage());
+ if ( vecDisplayItems.Count() > iItemIndex )
+ {
+ m_pItemModelPanels[i]->SetItem( vecDisplayItems[iItemIndex] );
+ }
+ else
+ {
+ m_pItemModelPanels[i]->SetItem( NULL );
+ }
+
+ // We only show the equipped state for the equipped items, below
+ m_pItemModelPanels[i]->SetShowEquipped( true );
+ m_pItemModelPanels[i]->SetShowGreyedOutTooltip( true );
+ m_pItemModelPanels[i]->SetGreyedOut( GetItemNotSelectableReason( m_pItemModelPanels[i]->GetItem() ) );
+ m_pItemModelPanels[i]->SetNoItemText( "#SelectNoItemSlot" );
+
+ SetBorderForItem( m_pItemModelPanels[i], false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CItemCriteriaSelectionPanel::GetItemNotSelectableReason( const CEconItemView *pItem ) const
+{
+ if ( !pItem )
+ return NULL;
+
+ // Ignore it if it's in our exceptions list
+ if ( m_Exceptions.Find( pItem->GetItemID() ) != m_Exceptions.InvalidIndex() )
+ return "";
+
+ // Matching all items?
+ if ( !m_pCriteria )
+ return NULL;
+
+ CTFItemDefinition *pItemData = pItem->GetStaticData();
+ return m_pCriteria->BEvaluate( pItemData ) ? NULL : "";
+}
+
+//=====================================================================================================================
+// CRAFTING SELECTION PANEL
+//=====================================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CCraftingItemSelectionPanel::CCraftingItemSelectionPanel(Panel *parent )
+ : CItemCriteriaSelectionPanel( parent, NULL, NULL, 0 )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCraftingItemSelectionPanel::UpdateOnShow( const CItemSelectionCriteria *pCriteria, bool bForceBackpack, itemid_t pExceptions[], int iNumExceptions )
+{
+ m_pCriteria = pCriteria;
+ UpdateExceptions( pExceptions, iNumExceptions );
+
+ if ( m_bShowingEntireBackpack != bForceBackpack )
+ {
+ SetCurrentPage( 0 );
+ m_bShowingEntireBackpack = bForceBackpack;
+ m_bReapplyItemKVs = true;
+ }
+ m_bForceBackpack = bForceBackpack;
+
+ if ( !m_bForceBackpack )
+ {
+ SetCurrentPage( 0 );
+ }
+
+ UpdateModelPanels();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CCraftingItemSelectionPanel::GetItemNotSelectableReason( const CEconItemView *pItem ) const
+{
+ if ( !pItem )
+ return NULL;
+
+ // Must not be marked no-craft
+ if ( !pItem->IsUsableInCrafting() )
+ return "#Econ_GreyOutReason_ItemNotCraftable";
+
+ // Are we filtering out items of non-unique quality that we might not want to accidentally craft?
+ if ( m_pOnlyAllowUniqueQuality && m_pOnlyAllowUniqueQuality->IsSelected() && pItem->GetQuality() != AE_UNIQUE )
+ return "#Econ_GreyOutReason_ItemSpecialQuality";
+
+ return BaseClass::GetItemNotSelectableReason( pItem );
+}
+
+
+//=====================================================================================================================
+// ACCOUNT SLOT ITEM SELECTION PANEL
+//=====================================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CAccountSlotItemSelectionPanel::CAccountSlotItemSelectionPanel( Panel *pParent, int iSlot, const char *pszTitleToken )
+ : CEquipSlotItemSelectionPanel( pParent, GEconItemSchema().GetAccountIndex(), iSlot )
+ , m_pszTitleToken( pszTitleToken )
+{}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAccountSlotItemSelectionPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pWeaponLabel = dynamic_cast<vgui::Label*>( FindChildByName("ItemSlotLabel") );
+ if ( m_pWeaponLabel )
+ {
+ m_pWeaponLabel->SetVisible( false );
+ }
+
+ SetDialogVariable( "loadoutclass", g_pVGuiLocalize->Find( m_pszTitleToken ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CAccountSlotItemSelectionPanel::GetItemNotSelectableReason( const CEconItemView *pItem ) const
+{
+ if ( !pItem )
+ return NULL;
+
+ CTFItemDefinition *pItemData = pItem->GetStaticData();
+
+ if ( pItemData->GetEquipType() != EEquipType_t::EQUIP_TYPE_ACCOUNT )
+ return "#Econ_GreyOutReason_CannotBeUsedByThisClass";
+
+ extern bool AreSlotsConsideredIdentical( EEquipType_t eEquipType, int iBaseSlot, int iTestSlot );
+ if ( !AreSlotsConsideredIdentical( pItem->GetStaticData()->GetEquipType(), pItemData->GetLoadoutSlot(m_iClass), m_iSlot ) )
+ return "#Econ_GreyOutReason_CannotBeUsedInThisSlot";
+
+ return NULL;
+} \ No newline at end of file
diff --git a/game/client/econ/item_selection_panel.h b/game/client/econ/item_selection_panel.h
new file mode 100644
index 0000000..f0ba474
--- /dev/null
+++ b/game/client/econ/item_selection_panel.h
@@ -0,0 +1,215 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ITEM_SELECTION_PANEL_H
+#define ITEM_SELECTION_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include "econ_controls.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "backpack_panel.h"
+#include "base_loadout_panel.h"
+
+class CItemModelPanel;
+
+#define SELECTION_DISPLAY_SLOTS_PER_PAGE 18
+#define SELECTION_DISPLAY_ROWS 3
+#define SELECTION_DISPLAY_COLUMNS (SELECTION_DISPLAY_SLOTS_PER_PAGE / SELECTION_DISPLAY_ROWS)
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+struct item_stack_type_t
+{
+ item_stack_type_t() : m_nDefIndex( INVALID_ITEM_DEF_INDEX ), m_nQuality( (uint8)-1 ) { }
+ item_stack_type_t( item_definition_index_t nDefIndex, uint8 nQuality ) : m_nDefIndex( nDefIndex ), m_nQuality( nQuality ) { }
+
+ bool operator<( const item_stack_type_t& other ) const { return m_nDefIndex < other.m_nDefIndex || m_nQuality < other.m_nQuality; }
+ bool operator==( const item_stack_type_t& other ) const { return m_nDefIndex == other.m_nDefIndex && m_nQuality == other.m_nQuality; }
+
+ item_definition_index_t m_nDefIndex;
+ uint8 m_nQuality;
+};
+
+class CItemSelectionPanel : public CBaseLoadoutPanel
+{
+ DECLARE_CLASS_SIMPLE( CItemSelectionPanel, CBaseLoadoutPanel );
+public:
+ CItemSelectionPanel(Panel *parent);
+ virtual ~CItemSelectionPanel();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void PerformLayout( void );
+ virtual void OnThink( void );
+ virtual void OnCommand( const char *command );
+ virtual void OnClose( void );
+ virtual void SetVisible( bool bState );
+ virtual bool ShouldDeleteOnClose( void ) { return true; }
+ virtual void OnKeyCodePressed( vgui::KeyCode code ) OVERRIDE;
+ virtual void OnKeyCodeReleased( vgui::KeyCode code ) OVERRIDE;
+ virtual void OnKeyCodeTyped( vgui::KeyCode code) OVERRIDE;
+ MESSAGE_FUNC_PARAMS( OnButtonChecked, "CheckButtonChecked", pData );
+
+ virtual int GetNumItemPanels( void ) { return m_bShowingEntireBackpack ? BACKPACK_SLOTS_PER_PAGE : SELECTION_DISPLAY_SLOTS_PER_PAGE; };
+ virtual void PositionItemPanel( CItemModelPanel *pPanel, int iIndex );
+ virtual bool AllowSelection( void ) { return true; }
+ virtual bool AllowDragging( CItemModelPanel *panel ) { return false; }
+
+ virtual int GetNumSlotsPerPage( void ) OVERRIDE { return m_bShowingEntireBackpack ? BACKPACK_SLOTS_PER_PAGE : SELECTION_DISPLAY_SLOTS_PER_PAGE; }
+ virtual int GetNumColumns( void ) OVERRIDE { return m_bShowingEntireBackpack ? BACKPACK_COLUMNS : SELECTION_DISPLAY_COLUMNS; }
+ virtual int GetNumRows( void ) OVERRIDE { return m_bShowingEntireBackpack ? BACKPACK_ROWS : SELECTION_DISPLAY_ROWS; }
+ virtual int GetNumPages( void ) OVERRIDE;
+ virtual void SetCurrentPage( int nNewPage ) OVERRIDE;
+
+ void UpdateModelPanels( void );
+ virtual void ApplyKVsToItemPanels( void );
+ virtual void CreateItemPanels( void );
+
+ void ShowDuplicateCounts( bool bShow ) { m_bShowDuplicates = bShow; }
+ void UpdateDuplicateCounts( void );
+
+ MESSAGE_FUNC_PTR( OnItemPanelMousePressed, "ItemPanelMousePressed", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel );
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+
+ // Derived panels need to override these with the custom selection behavior
+ virtual const char *GetSchemeFile( void ) = 0;
+ virtual bool ShouldItemPanelBeVisible( CItemModelPanel *pPanel, int iPanelIndex ) = 0;
+ virtual void UpdateModelPanelsForSelection( void ) = 0;
+ virtual const char *GetItemNotSelectableReason( const CEconItemView *pItem ) const = 0;
+
+ virtual bool DisableItemSelectionFromGrayedOutPanels( void ) const { return false; }
+ void NotifySelectionReturned( CItemModelPanel *pItemPanel );
+ void SetCaller( Panel* pCaller ) { m_pCaller = pCaller; }
+
+ virtual bool DisplayOnlyAllowUniqueQualityCheckbox() const { return false; }
+
+protected:
+ void PostMessageSelectionReturned( itemid_t ulItemID );
+
+ bool m_bShowingEntireBackpack;
+
+ KeyValues *m_pSelectionItemModelPanelKVs;
+ KeyValues *m_pDuplicateLabelKVs;
+ vgui::CheckButton *m_pOnlyAllowUniqueQuality;
+ CExButton *m_pShowBackpack;
+ CExButton *m_pShowSelection;
+ bool m_bForceBackpack;
+
+ CExButton *m_pNextPageButton;
+ CExButton *m_pPrevPageButton;
+ vgui::Label *m_pCurPageLabel;
+ vgui::Label *m_pNoItemsInSelectionLabel;
+
+ int m_iItemsInSelection;
+
+ bool m_bShowDuplicates;
+ CUtlVector<CExLabel*> m_pDuplicateCountLabels;
+
+ typedef CUtlMap< item_stack_type_t, int > DuplicateCountsMap_t;
+ DuplicateCountsMap_t m_DuplicateCounts; // A map of item def indices to item counts. Derived classes should fill this out.
+
+ bool m_bGotMousePressed;
+
+ Panel *m_pCaller;
+ vgui::TextEntry *m_pNameFilterTextEntry;
+ CUtlVector<wchar_t> m_wNameFilter;
+ float m_flFilterItemTime;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CEquipSlotItemSelectionPanel : public CItemSelectionPanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CEquipSlotItemSelectionPanel, CItemSelectionPanel );
+public:
+ CEquipSlotItemSelectionPanel(Panel *parent, int iClass, int iSlot);
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+
+ virtual const char *GetSchemeFile( void ) { return "Resource/UI/ItemSelectionPanel.res"; }
+ virtual bool ShouldItemPanelBeVisible( CItemModelPanel *pPanel, int iPanelIndex );
+ virtual void UpdateModelPanelsForSelection( void );
+ virtual const char *GetItemNotSelectableReason( const CEconItemView *pItem ) const;
+
+ virtual bool DisableItemSelectionFromGrayedOutPanels( void ) const { return true; }
+
+ void OnBackPressed();
+
+protected:
+ int m_iClass; // Class of the player we're selecting an item for
+ int m_iSlot; // Slot on the player that we're selecting an item for
+
+ itemid_t m_iCurrentItemID;
+
+ vgui::Label *m_pWeaponLabel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Selection panel that uses an Item Criteria block to do selection
+//-----------------------------------------------------------------------------
+class CItemCriteriaSelectionPanel : public CItemSelectionPanel
+{
+ DECLARE_CLASS_SIMPLE( CItemCriteriaSelectionPanel, CItemSelectionPanel );
+public:
+ CItemCriteriaSelectionPanel(Panel *parent, const CItemSelectionCriteria *pCriteria, itemid_t pExceptions[] = NULL, int iNumExceptions = 0 );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ void UpdateExceptions( itemid_t pExceptions[], int iNumExceptions );
+
+ virtual const char *GetSchemeFile( void ) { return "Resource/UI/ItemSelectionPanel.res"; }
+ virtual bool ShouldItemPanelBeVisible( CItemModelPanel *pPanel, int iPanelIndex );
+ virtual void UpdateModelPanelsForSelection( void );
+ virtual const char *GetItemNotSelectableReason( const CEconItemView *pItem ) const;
+
+protected:
+ const CItemSelectionCriteria *m_pCriteria;
+ CUtlVector<itemid_t> m_Exceptions;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Selection panel for crafting
+//-----------------------------------------------------------------------------
+class CCraftingItemSelectionPanel : public CItemCriteriaSelectionPanel
+{
+ DECLARE_CLASS_SIMPLE( CCraftingItemSelectionPanel, CItemCriteriaSelectionPanel );
+public:
+ CCraftingItemSelectionPanel(Panel *parent );
+
+ virtual const char *GetItemNotSelectableReason( const CEconItemView *pItem ) const;
+ virtual bool ShouldDeleteOnClose( void ) { return false; }
+
+ void UpdateOnShow( const CItemSelectionCriteria *pCriteria, bool bForceBackpack, itemid_t pExceptions[] = NULL, int iNumExceptions = 0 );
+
+ virtual bool DisplayOnlyAllowUniqueQualityCheckbox() const { return true; }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CAccountSlotItemSelectionPanel : public CEquipSlotItemSelectionPanel
+{
+ DECLARE_CLASS_SIMPLE( CAccountSlotItemSelectionPanel, CEquipSlotItemSelectionPanel );
+public:
+ CAccountSlotItemSelectionPanel( Panel *pParent, int iSlot, const char *pszTitleToken );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE;
+
+ virtual const char *GetItemNotSelectableReason( const CEconItemView *pItem ) const OVERRIDE;
+
+protected:
+ const char * m_pszTitleToken;
+};
+
+#endif // ITEM_SELECTION_PANEL_H
diff --git a/game/client/econ/item_style_select_dialog.cpp b/game/client/econ/item_style_select_dialog.cpp
new file mode 100644
index 0000000..0bcba8f
--- /dev/null
+++ b/game/client/econ/item_style_select_dialog.cpp
@@ -0,0 +1,209 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/ComboBox.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "item_style_select_dialog.h"
+#include "econ_gcmessages.h"
+#include "backpack_panel.h"
+#include "econ_ui.h"
+#include "gc_clientsystem.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CComboBoxBackpackOverlayDialogBase::CComboBoxBackpackOverlayDialogBase( vgui::Panel *parent, CEconItemView *pItem )
+ : vgui::EditablePanel( parent, "ComboBoxBackpackOverlayDialogBase" )
+ , m_pPreviewModelPanel( NULL )
+ , m_pItem( pItem )
+{
+ if ( m_pItem )
+ {
+ m_pPreviewModelPanel = new CItemModelPanel( this, "preview_model" );
+ m_pPreviewModelPanel->SetItem( m_pItem );
+ }
+
+ m_pComboBox = new vgui::ComboBox( this, "ComboBox", 5, false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CComboBoxBackpackOverlayDialogBase::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ComboBoxBackpackOverlayDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ if ( m_pPreviewModelPanel )
+ {
+ if ( m_pPreviewModelPanel && m_pItem )
+ {
+ m_pPreviewModelPanel->SetItem( m_pItem );
+ }
+
+ m_pPreviewModelPanel->SetActAsButton( true, false );
+ }
+
+ if ( m_pComboBox )
+ {
+ m_pComboBox->RemoveAll();
+
+ vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true );
+ m_pComboBox->SetFont( hFont );
+
+ PopulateComboBoxOptions();
+
+ m_pComboBox->AddActionSignalTarget( this );
+ }
+
+ CExLabel *pTitleLabel = dynamic_cast<CExLabel *>( FindChildByName( "TitleLabel" ) );
+ if ( pTitleLabel )
+ {
+ pTitleLabel->SetText( GetTitleLabelLocalizationToken() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CComboBoxBackpackOverlayDialogBase::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "cancel", 6 ) )
+ {
+ TFModalStack()->PopModal( this );
+
+ SetVisible( false );
+ MarkForDeletion();
+ }
+ else if ( !Q_strnicmp( command, "apply", 5 ) )
+ {
+ OnComboBoxApplication();
+
+ TFModalStack()->PopModal( this );
+
+ SetVisible( false );
+ MarkForDeletion();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CComboBoxBackpackOverlayDialogBase::Show()
+{
+ SetVisible( true );
+ MakePopup();
+ MoveToFront();
+ SetKeyBoardInputEnabled( true );
+ SetMouseInputEnabled( true );
+ TFModalStack()->PushModal( this );
+
+ // Special-case the path where we only wind up with one option in the combo box. If
+ // this happens we just pretend the user selected it and move on to the next step. For
+ // restoration, this will bring up a confirmation dialog, and for setting styles... well,
+ // we'd expect it to never happen because the option to bring up the UI wouldn't be enabled
+ // if there was only a single style.
+ if ( m_pComboBox && m_pComboBox->GetItemCount() == 1 )
+ {
+ OnCommand( "apply" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when text changes in combo box
+//-----------------------------------------------------------------------------
+void CComboBoxBackpackOverlayDialogBase::OnTextChanged( KeyValues *data )
+{
+ if ( !m_pComboBox )
+ return;
+
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
+ vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel );
+
+ if ( pComboBox == m_pComboBox )
+ {
+ OnComboBoxChanged( m_pComboBox->GetActiveItem() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStyleSelectDialog::PopulateComboBoxOptions()
+{
+ CEconItemView* pItem = GetPreviewModelPanel()->GetItem();
+ Assert( pItem );
+
+ if ( pItem->GetStaticData()->GetNumStyles() )
+ {
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ for ( style_index_t i=0; i<pItem->GetStaticData()->GetNumStyles(); i++ )
+ {
+ const CEconStyleInfo *pStyle = pItem->GetStaticData()->GetStyleInfo( i );
+ if ( pStyle && pStyle->IsSelectable() )
+ {
+ pKeyValues->SetInt( "style_index", i );
+ GetComboBox()->AddItem( pItem->GetStaticData()->GetStyleInfo( i )->GetName(), pKeyValues );
+ }
+ }
+ pKeyValues->deleteThis();
+
+ GetComboBox()->ActivateItemByRow( pItem->GetItemStyle() );
+ }
+ else
+ {
+ GetComboBox()->ActivateItemByRow( 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStyleSelectDialog::OnComboBoxApplication()
+{
+ KeyValues *pKV = GetComboBox()->GetActiveItemUserData();
+ int iNewStyle = pKV->GetInt( "style_index", 0 );
+ CEconItemView* pItem = GetPreviewModelPanel()->GetItem();
+
+ if ( pItem )
+ {
+ const char* pszStyleName = pItem->GetStaticData()->GetStyleInfo( iNewStyle )->GetName();
+
+ // Tell the GC to update the style.
+ GCSDK::CGCMsg< MsgGCSetItemStyle_t > msg( k_EMsgGCSetItemStyle );
+ msg.Body().m_unItemID = pItem->GetItemID();
+ msg.Body().m_iStyle = iNewStyle;
+ GCClientSystem()->BSendMessage( msg );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_CHANGED_STYLE, pItem, pszStyleName /* stored unlocalized here intentionally */, iNewStyle );
+
+ // Tell our parent about the change
+ if ( pItem && pItem->IsValid() )
+ {
+ KeyValues *pKey = new KeyValues( "SelectionReturned" );
+ pKey->SetUint64( "itemindex", pItem->GetItemID() );
+ PostMessage( GetParent(), pKey );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStyleSelectDialog::OnComboBoxChanged( int iNewSelection )
+{
+ GetPreviewModelPanel()->SetItemStyle( GetComboBox()->GetActiveItem() );
+ GetPreviewModelPanel()->UpdatePanels();
+} \ No newline at end of file
diff --git a/game/client/econ/item_style_select_dialog.h b/game/client/econ/item_style_select_dialog.h
new file mode 100644
index 0000000..46d45d5
--- /dev/null
+++ b/game/client/econ/item_style_select_dialog.h
@@ -0,0 +1,71 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef ITEM_STYLE_SELECT_DIALOG_H
+#define ITEM_STYLE_SELECT_DIALOG_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+class CItemModelPanel;
+
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/ComboBox.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CComboBoxBackpackOverlayDialogBase : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CComboBoxBackpackOverlayDialogBase, vgui::EditablePanel );
+
+protected:
+ CComboBoxBackpackOverlayDialogBase( vgui::Panel *pParent, CEconItemView *pItem );
+
+public:
+ virtual ~CComboBoxBackpackOverlayDialogBase() { }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void OnCommand( const char *command );
+ void Show();
+
+protected:
+ CItemModelPanel *GetPreviewModelPanel() { return m_pPreviewModelPanel; }
+ vgui::ComboBox *GetComboBox() { return m_pComboBox; }
+
+ CEconItemView *m_pItem;
+private:
+ virtual void PopulateComboBoxOptions() = 0;
+ virtual void OnComboBoxApplication() = 0;
+ virtual void OnComboBoxChanged( int iNewSelection ) { }
+ virtual const char *GetTitleLabelLocalizationToken() const = 0;
+
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+
+ CItemModelPanel *m_pPreviewModelPanel;
+ vgui::ComboBox *m_pComboBox;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStyleSelectDialog : public CComboBoxBackpackOverlayDialogBase
+{
+ DECLARE_CLASS_SIMPLE( CStyleSelectDialog, CComboBoxBackpackOverlayDialogBase );
+
+public:
+ CStyleSelectDialog( vgui::Panel *pParent, CEconItemView *pItem ) : CComboBoxBackpackOverlayDialogBase( pParent, pItem ) { }
+
+protected:
+ virtual void PopulateComboBoxOptions();
+ virtual void OnComboBoxApplication();
+ virtual void OnComboBoxChanged( int iNewSelection );
+ virtual const char *GetTitleLabelLocalizationToken() const { return "#TF_Item_SelectStyle"; }
+};
+
+#endif // ITEM_STYLE_SELECT_DIALOG_H
diff --git a/game/client/econ/local_steam_shared_object_listener.cpp b/game/client/econ/local_steam_shared_object_listener.cpp
new file mode 100644
index 0000000..ec2a426
--- /dev/null
+++ b/game/client/econ/local_steam_shared_object_listener.cpp
@@ -0,0 +1,24 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "local_steam_shared_object_listener.h"
+#include "tf_gc_client.h"
+
+
+CLocalSteamSharedObjectListener::CLocalSteamSharedObjectListener()
+{
+ // Delayed add in case something derives from us and isn't fully constructed yet
+ // so their SOCreated functions are setup in the VTable yet.
+ GTFGCClientSystem()->AddLocalPlayerSOListener( this, false );
+}
+
+
+CLocalSteamSharedObjectListener::~CLocalSteamSharedObjectListener()
+{
+ GTFGCClientSystem()->RemoveLocalPlayerSOListener( this );
+}
diff --git a/game/client/econ/local_steam_shared_object_listener.h b/game/client/econ/local_steam_shared_object_listener.h
new file mode 100644
index 0000000..77d9dff
--- /dev/null
+++ b/game/client/econ/local_steam_shared_object_listener.h
@@ -0,0 +1,31 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#ifndef LOCAL_STEAM_SHARED_OBJECT_LISTENER
+#define LOCAL_STEAM_SHARED_OBJECT_LISTENER
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "gcsdk/gcclient_sharedobjectcache.h"
+
+class CLocalSteamSharedObjectListener : public GCSDK::ISharedObjectListener
+{
+public:
+ CLocalSteamSharedObjectListener();
+ virtual ~CLocalSteamSharedObjectListener();
+
+ virtual void SOCreated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE {}
+ virtual void PreSOUpdate( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE {}
+ virtual void SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE {}
+ virtual void PostSOUpdate( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE {}
+ virtual void SODestroyed( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent ) OVERRIDE {}
+ virtual void SOCacheSubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE {}
+ virtual void SOCacheUnsubscribed( const CSteamID & steamIDOwner, GCSDK::ESOCacheEvent eEvent ) OVERRIDE {}
+};
+
+#endif // LOCAL_STEAM_SHARED_OBJECT_LISTENER
diff --git a/game/client/econ/store/store_page.cpp b/game/client/econ/store/store_page.cpp
new file mode 100644
index 0000000..a60212f
--- /dev/null
+++ b/game/client/econ/store/store_page.cpp
@@ -0,0 +1,2233 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/store_page.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "gamestringpool.h"
+#include "econ_item_inventory.h"
+#include "econ_item_system.h"
+#include "item_model_panel.h"
+#include "store/store_panel.h"
+#include "store/store_preview_item.h"
+#include "store/store_viewcart.h"
+#include "rtime.h"
+#include "econ_ui.h"
+#include "store/store_page_new.h"
+#include "gc_clientsystem.h"
+#include "confirm_dialog.h"
+
+#ifdef TF_CLIENT_DLL
+#include "c_tf_gamestats.h"
+#include "c_tf_freeaccount.h"
+#endif // TF_CLIENT_DLL
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+#ifdef TF_CLIENT_DLL
+void AddSubKeyNamed( KeyValues *pKeys, const char *pszName );
+#endif
+
+DECLARE_BUILD_FACTORY( CStorePreviewItemIcon );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStoreItemControlsPanel::CStoreItemControlsPanel( vgui::Panel *pParent, const char *pPanelName, CItemModelPanel *pItemModelPanel )
+: vgui::EditablePanel( pParent, pPanelName ),
+ m_pItemModelPanel( pItemModelPanel ),
+ m_pEntry( NULL ),
+ m_bItemPanelEntered( false ),
+ m_bButtonsVisible( false )
+{
+}
+
+void CStoreItemControlsPanel::SetMouseHoverHandler( Panel *pHandler )
+{
+ m_pMouseHoverHandler = pHandler;
+}
+
+void CStoreItemControlsPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings(
+ ShouldUseNewStore() ?
+ "Resource/UI/econ/store/v2/StoreItemControls.res" :
+ "Resource/UI/econ/store/v1/StoreItemControls.res"
+ );
+}
+
+const econ_store_entry_t *CStoreItemControlsPanel::GetItem() const
+{
+ return m_pEntry;
+}
+
+void CStoreItemControlsPanel::SetItem( const econ_store_entry_t *pEntry )
+{
+ m_pEntry = pEntry;
+}
+
+void CStoreItemControlsPanel::SetButtonsVisible( bool bVisible )
+{
+ m_bButtonsVisible = bVisible;
+
+ for ( int i = 0; i < GetChildCount(); ++i )
+ {
+ CExButton *pButton = dynamic_cast< CExButton* >( GetChild( i ) );
+ if ( pButton )
+ {
+ pButton->SetVisible( bVisible );
+ pButton->SetArmed( false );
+ }
+ }
+}
+
+void CStoreItemControlsPanel::OnCursorEntered()
+{
+ BaseClass::OnCursorEntered();
+
+ if ( m_pItemModelPanel && m_pItemModelPanel->HasItem() )
+ {
+ SetButtonsVisible( true );
+ }
+}
+
+void CStoreItemControlsPanel::OnCursorExited()
+{
+ BaseClass::OnCursorExited();
+}
+
+void CStoreItemControlsPanel::OnItemPanelEntered()
+{
+ m_bItemPanelEntered = true;
+ SetButtonsVisible( true );
+}
+
+void CStoreItemControlsPanel::OnItemPanelExited()
+{
+ m_bItemPanelEntered = false;
+}
+
+void CStoreItemControlsPanel::OnThink()
+{
+ if ( !m_bItemPanelEntered )
+ {
+ if ( !IsCursorOver() )
+ {
+ SetButtonsVisible( false );
+ }
+ }
+
+ if ( m_pMouseHoverHandler.Get() )
+ {
+ KeyValues *pMsg = new KeyValues( "StoreItemControlsPanelHover", "entered", m_bButtonsVisible );
+ pMsg->SetPtr( "entry", (void *)m_pEntry );
+ PostMessage( m_pMouseHoverHandler.Get(), pMsg );
+ }
+}
+
+void CStoreItemControlsPanel::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "addtocart", 9 ) )
+ {
+ PostActionSignal( new KeyValues( "ItemAddToCart" ) );
+ }
+ else if ( !Q_strnicmp( command, "preview_item", 12 ) )
+ {
+ PostActionSignal( new KeyValues( "ItemPreview" ) );
+ }
+ else if ( !Q_strnicmp( command, "details", 7 ) )
+ {
+ PostActionSignal( new KeyValues( "ItemDetails" ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemIcon::OnItemPanelMouseReleased( vgui::Panel *panel )
+{
+ PostActionSignal(new KeyValues("ItemIconSelected", "icon", m_iIconIndex));
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel::CStorePricePanel( vgui::Panel *pParent, const char *pPanelName )
+ : vgui::EditablePanel( pParent, pPanelName )
+{
+ m_bOldDiscountVisibility = false;
+ m_pPrice = NULL;
+ m_pDiscount = NULL;
+ m_pNew = NULL;
+ m_pSale = NULL;
+ m_pSaleBorder = NULL;
+ m_pOGPrice = NULL;
+ m_pCrossout = NULL;
+ m_pLimited = NULL;
+ m_pHighlighted = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel::~CStorePricePanel()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char* CStorePricePanel::GetPanelResFile()
+{
+ return "Resource/UI/econ/store/v1/StorePrice.res";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( GetPanelResFile() );
+
+ m_pPrice = dynamic_cast< CExLabel* >( FindChildByName( "Price" ) );
+ m_pDiscount = dynamic_cast< CExLabel* >( FindChildByName( "Discount" ) );
+ m_pHighlighted = dynamic_cast< CExLabel* >( FindChildByName( "Highlighted" ) );
+ m_pNew = dynamic_cast< CExLabel* >( FindChildByName( "NewLarge" ) );
+ if ( !m_pNew )
+ {
+ m_pNew = dynamic_cast< CExLabel* >( FindChildByName( "New" ) );
+ }
+ m_pSale = dynamic_cast< CExLabel* >( FindChildByName( "Sale" ) );
+ m_pSaleBorder = dynamic_cast< vgui::EditablePanel* >( FindChildByName( "StorePriceBorder" ) );
+ m_pOGPrice = dynamic_cast< CExLabel* >( FindChildByName( "OG_Price" ) );
+ m_pCrossout = FindChildByName( "OG_Price_CrossOut" );
+
+ // Only support one "limited"
+ m_pLimited = FindChildByName( "LimitedLarge" );
+ if ( !m_pLimited )
+ {
+ m_pLimited = FindChildByName( "Limited" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( m_pPrice )
+ {
+ int contentWidth, contentHeight;
+ m_pPrice->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pPrice->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pPrice->SetWide( contentWidth + iTextInsetX );
+ m_pPrice->SetPos( GetWide() - m_pPrice->GetWide(), GetTall() - m_pPrice->GetTall() );
+ }
+
+ if ( m_pPrice && m_pDiscount && m_pOGPrice )
+ {
+ int contentWidth, contentHeight;
+ m_pDiscount->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pDiscount->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pDiscount->SetWide( contentWidth + iTextInsetX );
+ m_pDiscount->SetPos( 0, GetTall() - m_pDiscount->GetTall() );
+
+ // Place original price in bottom-right corner, above the price label
+ int aPricePos[2];
+ m_pPrice->GetPos( aPricePos[0], aPricePos[1] );
+ m_pOGPrice->SetWide( GetWide() );
+ m_pOGPrice->GetContentSize( contentWidth, contentHeight );
+ int aOGPricePos[2] = { 0, aPricePos[1] - contentHeight };
+ m_pOGPrice->SetPos( aOGPricePos[0], aOGPricePos[1] );
+
+ // Place crossout over original price, halfway down from its vertical starting position
+ m_pCrossout->SetBounds(
+ aOGPricePos[0] + m_pOGPrice->GetWide() - contentWidth,
+ aOGPricePos[1] + contentHeight/2, contentWidth, m_pCrossout->GetTall()
+ );
+ }
+
+ if ( m_pNew )
+ {
+ int contentWidth, contentHeight;
+ m_pNew->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pNew->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pNew->SetWide( contentWidth + iTextInsetX );
+ int iPosX, iPosY;
+ m_pNew->GetPos( iPosX, iPosY );
+ m_pNew->SetPos( GetWide() - m_pNew->GetWide(), iPosY );
+ }
+
+ if ( m_pHighlighted )
+ {
+ int contentWidth, contentHeight;
+ m_pHighlighted->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pHighlighted->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pHighlighted->SetWide( contentWidth + iTextInsetX );
+ int iPosX, iPosY;
+ m_pHighlighted->GetPos( iPosX, iPosY );
+ m_pHighlighted->SetPos( GetWide() - m_pHighlighted->GetWide(), iPosY );
+ }
+
+ if ( m_pSale )
+ {
+ int contentWidth, contentHeight;
+ m_pSale->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pSale->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pSale->SetWide( contentWidth + iTextInsetX );
+ int iPosX, iPosY;
+ m_pSale->GetPos( iPosX, iPosY );
+ m_pSale->SetPos( GetWide() - m_pSale->GetWide(), iPosY );
+ }
+
+ if ( m_pLimited )
+ {
+ int iPosX, iPosY;
+ Panel *pRefPanel = ( m_pSale && m_pSale->IsVisible() ) ? m_pSale : ( m_pNew && m_pNew->IsVisible() ) ? m_pNew : NULL;
+ if ( pRefPanel && pRefPanel->IsVisible() )
+ {
+ pRefPanel->GetPos( iPosX, iPosY );
+ m_pLimited->SetPos( GetWide() - m_pLimited->GetWide() - XRES( 3 ), iPosY + pRefPanel->GetTall() + YRES( 3 ) );
+ }
+ else
+ {
+ m_pLimited->GetPos( iPosX, iPosY );
+ m_pLimited->SetPos( GetWide() - m_pLimited->GetWide() - XRES( 3 ), iPosY );
+ }
+ }
+
+ if ( m_pSaleBorder )
+ {
+ m_pSaleBorder->SetSize( GetWide(), GetTall() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel::SetPriceText( int iPrice, const char *pVariable, const econ_store_entry_t *pEntry )
+{
+ if ( iPrice == 0 )
+ {
+ if ( pEntry->m_bIsMarketItem )
+ {
+ SetDialogVariable( pVariable, g_pVGuiLocalize->Find( "#Store_Market" ) );
+ }
+ else
+ {
+ SetDialogVariable( pVariable, "" );
+ }
+ return;
+ }
+
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), iPrice, EconUI()->GetStorePanel()->GetCurrency() );
+
+ if ( pEntry->m_bIsMarketItem )
+ {
+ wchar_t wzMarketString[96];
+ g_pVGuiLocalize->ConstructString_safe(
+ wzMarketString,
+ LOCCHAR( "%s1 %s2" ),
+ 2,
+ g_pVGuiLocalize->Find( "#Store_Market" ),
+ wzLocalizedPrice );
+
+ SetDialogVariable( pVariable, wzMarketString );
+ }
+ else
+ {
+ SetDialogVariable( pVariable, wzLocalizedPrice );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static bool IsItemPreviewed( const econ_store_entry_t *pEntry, ECurrency eCurrency )
+{
+ return (pEntry->GetItemDefinitionIndex() == InventoryManager()->GetLocalInventory()->GetPreviewItemDef())
+ && !pEntry->IsOnSale( eCurrency );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void AddItemToCartHelper( const char *pszContext, const econ_store_entry_t *pEntry, ECartItemType eSelectedCartItemType )
+{
+ Assert( pEntry );
+
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ const ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+
+ // If this is the item we've previewing *and* it's the first one we've added
+ // to the cart then we note that it's a preview item purchase and so we may
+ // get a discount.
+ ECartItemType eCartItemType = eSelectedCartItemType == kCartItem_Purchase && IsItemPreviewed( pEntry, eCurrency ) && !pCart->ContainsItemDefinition( pEntry->GetItemDefinitionIndex() )
+ ? kCartItem_TryOutUpgrade
+ : eSelectedCartItemType;
+
+ pCart->AddToCart( pEntry, pszContext, eCartItemType );
+ EconUI()->GetStorePanel()->OnAddToCart();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void AddItemToCartHelper( const char *pszContext, item_definition_index_t unItemDef, ECartItemType eSelectedCartItemType )
+{
+ const econ_store_entry_t *pEntry = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( unItemDef );
+ if ( pEntry )
+ {
+ AddItemToCartHelper( pszContext, pEntry, eSelectedCartItemType );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel::SetItem( const econ_store_entry_t *pEntry )
+{
+ const ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+
+ item_price_t unPrice = pEntry->GetCurrentPrice( eCurrency );
+ SetPriceText( unPrice, "price", pEntry );
+
+ const bool bIsItemPreviewed = IsItemPreviewed( pEntry, eCurrency );
+
+ if ( bIsItemPreviewed )
+ {
+ // Make sure we're doing the math we think we're doing -- the item isn't on sale and so
+ // we'll be setting a new price based on the base price.
+ Assert( pEntry->GetCurrentPrice( eCurrency ) == pEntry->GetBasePrice( eCurrency ) );
+ Assert( unPrice == pEntry->GetBasePrice( eCurrency ) );
+
+ // Apply the preview period discount.
+ unPrice *= EconUI()->GetStorePanel()->GetPriceSheet()->GetPreviewPeriodDiscount();
+ }
+
+ item_price_t unBasePrice;
+ const bool bIsDiscounted = pEntry->HasDiscount( eCurrency, &unBasePrice );
+
+ if ( m_pDiscount && m_pOGPrice )
+ {
+ // and discount
+ if ( bIsDiscounted == false )
+ {
+ m_pDiscount->SetVisible( false );
+ m_pOGPrice->SetVisible( false );
+ }
+ else
+ {
+ SetPriceText( unBasePrice, "og_price", pEntry );
+
+ // set the discount and size
+ float flDiscountPercentage = 1.0f - ( float(unPrice) / float(unBasePrice) );
+ wchar_t wszDiscount[16];
+ _snwprintf( wszDiscount, ARRAYSIZE( wszDiscount ), L"-%.0f%%", flDiscountPercentage * 100.0f );
+ m_pDiscount->SetText( wszDiscount );
+
+ m_pDiscount->SetVisible( true );
+ m_pOGPrice->SetVisible( true );
+ }
+ }
+
+ if ( m_pCrossout && m_pOGPrice )
+ {
+ m_pCrossout->SetVisible( bIsDiscounted );
+ }
+
+ if ( m_pNew )
+ {
+ m_pNew->SetVisible( pEntry->m_bNew );
+ }
+
+ if ( m_pHighlighted )
+ {
+ m_pHighlighted->SetVisible( pEntry->m_bHighlighted );
+
+
+ }
+
+ if ( m_pSale )
+ {
+ bool bSaleVisible = false;
+
+ // We don't check explicitly for "is on sale" here because other things like item previews can
+ // adjust the price we're going to display to the user without adjusting the actual store entry.
+ if ( unPrice != pEntry->GetBasePrice( eCurrency ) && ( m_pNew == NULL || !m_pNew->IsVisible() ) )
+ {
+ if ( bIsItemPreviewed )
+ {
+ m_pSale->SetText( "#TF_PreviewDiscount" );
+ }
+
+ m_pSale->SetVisible( true );
+ bSaleVisible = true;
+ }
+ else
+ {
+ m_pSale->SetVisible( false );
+ }
+
+ if ( m_pSaleBorder )
+ {
+ m_pSaleBorder->SetVisible( !ShouldUseNewStore() && bSaleVisible );
+ }
+ }
+
+ if ( m_pLimited )
+ {
+ if ( pEntry->m_bLimited )
+ {
+ m_pLimited->SetVisible( true );
+ }
+ else
+ {
+ m_pLimited->SetVisible( false );
+ }
+ }
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel::OnStoreItemControlsPanelHover( KeyValues *data )
+{
+ // We don't care if there's no discount label to deal with
+ if ( !m_pDiscount )
+ return;
+
+ // Should the discount label be visible?
+ const econ_store_entry_t *pEntry = (const econ_store_entry_t *)data->GetPtr( "entry" );
+ if ( !pEntry )
+ return;
+
+ ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+ if ( !pEntry->HasDiscount( eCurrency, NULL ) )
+ return;
+
+ bool bEntered = data->GetInt( "entered" ) == 1;
+ m_pDiscount->SetVisible( !bEntered );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePage::CStorePage(Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile ) : vgui::PropertyPage(parent, "StorePage")
+{
+ m_pPageData = pPageData;
+
+ m_pItemModelPanelKVs = NULL;
+ m_pModelPanelLabelsKVs = NULL;
+ m_pCartModelPanelKVs = NULL;
+ m_pCartQuantityLabelKVs = NULL;
+
+ m_pFeaturedItemPanel = NULL;
+
+ m_pItemBackdropPanel = new EditablePanel( this, "ItemBackdrop" );
+ m_pMouseOverItemPanel = new CItemModelPanel( this, "mouseoveritempanel" );
+ m_pMouseOverTooltip = new CItemModelPanelToolTip( this );
+ m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel );
+ m_pMouseOverTooltip->SetPositioningStrategy( IPTTP_BOTTOM_SIDE );
+
+ if ( IsHomePage() )
+ {
+ if ( !ShouldUseNewStore() )
+ {
+ m_pFeaturedItemPanel = new CItemModelPanel( this, "featured_item_panel" );
+ m_pFeaturedItemPanel->SetActAsButton( true, true );
+ m_pFeaturedItemPanel->SetTooltip( m_pMouseOverTooltip, "" );
+ }
+
+ m_pFilterComboBox = NULL;
+ }
+ else
+ {
+ m_pFilterComboBox = new vgui::ComboBox( this, "ClassFilterComboBox", 11, false );
+ m_pFilterComboBox->SetVisible( false );
+ m_pFilterComboBox->AddActionSignalTarget( this );
+ }
+
+ m_pPreviewItemResFile = pPreviewItemResFile;
+ m_pPreviewPanel = NULL;
+ m_pSelectedPanel = NULL;
+ m_pNextPageButton = NULL;
+ m_pPrevPageButton = NULL;
+ m_pCheckoutButton = NULL;
+ m_pPreviewItemButton = NULL;
+ m_pAddToCartButtonPanel = NULL;
+ m_iCurrentFilter = 0;
+ m_pCartButton = NULL;
+ m_pBackpackLabel = NULL;
+ m_iSelectedItemDef = 0;
+ m_iSelectDefOnPageShow = 0;
+ m_iSelectPageOnPageShow = 0;
+ m_iOldSelectedItemDef = 0;
+ m_bShouldDeletePreviewPanel = false;
+ m_bFilterDirty = true;
+
+ ListenForGameEvent( "cart_updated" );
+
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colItemPanelBG, "item_panel_bgcolor" );
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colItemPanelBGMouseover, "item_panel_bgcolor_mouseover" );
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colItemPanelBGSelected, "item_panel_bgcolor_selected" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePage::~CStorePage()
+{
+ if ( m_pItemModelPanelKVs )
+ {
+ m_pItemModelPanelKVs->deleteThis();
+ m_pItemModelPanelKVs = NULL;
+ }
+ if ( m_pCartModelPanelKVs )
+ {
+ m_pCartModelPanelKVs->deleteThis();
+ m_pCartModelPanelKVs = NULL;
+ }
+ if ( m_pCartQuantityLabelKVs )
+ {
+ m_pCartQuantityLabelKVs->deleteThis();
+ m_pCartQuantityLabelKVs = NULL;
+ }
+ if ( m_pModelPanelLabelsKVs )
+ {
+ m_pModelPanelLabelsKVs->deleteThis();
+ m_pModelPanelLabelsKVs = NULL;
+ }
+ if ( m_bShouldDeletePreviewPanel && m_pPreviewPanel )
+ {
+ delete m_pPreviewPanel;
+ m_pPreviewPanel = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnPostCreate()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CStorePage::GetPageResFile( void )
+{
+ return m_pPageData->m_pchPageRes;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePreviewItemPanel *CStorePage::CreatePreviewPanel( void )
+{
+ return new CStorePreviewItemPanel( this, m_pPreviewItemResFile, "storepreviewitem", this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ // First time through, create our preview panel
+ if ( ( ShouldUseNewStore() || !IsHomePage() ) && !m_pPreviewPanel )
+ {
+ m_pPreviewPanel = CreatePreviewPanel();
+
+ // Force it to load it's scheme now, because it needs to be done before we set it's visibility below
+ m_pPreviewPanel->InvalidateLayout( false, true );
+ m_pPreviewPanel->SetVisible( false );
+ }
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ KeyValues *pConditions = NULL;
+ #ifdef TF_CLIENT_DLL
+ const char *pszHoliday = UTIL_GetActiveHolidayString();
+ if ( pszHoliday && pszHoliday[0] )
+ {
+ pConditions = new KeyValues( "conditions" );
+
+ char szCondition[64];
+ Q_snprintf( szCondition, sizeof( szCondition ), "if_%s", pszHoliday );
+ AddSubKeyNamed( pConditions, szCondition );
+ }
+#endif
+
+ LoadControlSettings( GetPageResFile(), NULL, NULL, pConditions );
+
+ if ( pConditions )
+ {
+ pConditions->deleteThis();
+ }
+
+ m_bReapplyItemKVs = true;
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ SetBorderForItem( m_vecItemPanels[i].m_pItemModelPanel, false );
+ }
+
+ m_pMouseOverItemPanel->SetBorder( pScheme->GetBorder("LoadoutItemPopupBorder") );
+
+ m_pNextPageButton = dynamic_cast<CExButton*>( FindChildByName("NextPageButton") );
+ m_pPrevPageButton = dynamic_cast<CExButton*>( FindChildByName("PrevPageButton") );
+ m_pCheckoutButton = dynamic_cast<CExButton*>( FindChildByName("CheckoutButton") );
+ m_pPreviewItemButton = dynamic_cast<CExButton*>( FindChildByName("PreviewItemButton") );
+ m_pAddToCartButtonPanel = dynamic_cast<vgui::EditablePanel*>( FindChildByName("AddToCartButton") );
+ if ( m_pAddToCartButtonPanel )
+ {
+ CExButton *pButton = dynamic_cast<CExButton*>( m_pAddToCartButtonPanel->FindChildByName("SubButton") );
+ if ( pButton )
+ {
+ pButton->AddActionSignalTarget( GetVPanel() );
+ }
+ }
+ m_pCurPageLabel = dynamic_cast<vgui::Label*>( FindChildByName("CurPageLabel") );
+ m_pCartButton = dynamic_cast<CExButton*>( FindChildByName("CartButton") );
+ m_pBackpackLabel = dynamic_cast<vgui::Label*>( FindChildByName("BackpackSpaceLabel") );
+ if ( m_pBackpackLabel )
+ {
+ m_colBackpackOrg = m_pBackpackLabel->GetFgColor();
+ }
+
+ m_pItemDetailsButtonPanel = dynamic_cast<vgui::EditablePanel*>( FindChildByName("ItemDetailsButton") );
+ if ( m_pItemDetailsButtonPanel )
+ {
+ CExButton *pButton = dynamic_cast<CExButton*>( m_pItemDetailsButtonPanel->FindChildByName("SubButton") );
+ if ( pButton )
+ {
+ pButton->AddActionSignalTarget( GetVPanel() );
+ }
+ }
+ m_pItemPreviewButtonPanel = dynamic_cast<vgui::EditablePanel*>( FindChildByName("ItemPreviewButton") );
+ if ( m_pItemPreviewButtonPanel )
+ {
+ CExButton *pButton = dynamic_cast<CExButton*>( m_pItemPreviewButtonPanel->FindChildByName("SubButton") );
+ if ( pButton )
+ {
+ pButton->AddActionSignalTarget( GetVPanel() );
+ }
+ }
+
+ m_pCartFeaturedItemImage = dynamic_cast<vgui::ImagePanel*>( FindChildByName("CartFeaturedItemSymbol") );
+ if ( m_pCartFeaturedItemImage )
+ {
+ m_pCartFeaturedItemImage->SetMouseInputEnabled( false );
+ m_pCartFeaturedItemImage->SetKeyBoardInputEnabled( false );
+ }
+
+ vgui::Panel *pPanel = FindChildByName("CartImage");
+ if ( pPanel )
+ {
+ pPanel->SetMouseInputEnabled( false );
+ pPanel->SetKeyBoardInputEnabled( false );
+ }
+ pPanel = FindChildByName("FeaturedItemSymbol");
+ if ( pPanel )
+ {
+ pPanel->SetMouseInputEnabled( false );
+ pPanel->SetKeyBoardInputEnabled( false );
+ }
+ pPanel = FindChildByName("FeaturedItemLabel");
+ if ( pPanel )
+ {
+ pPanel->SetMouseInputEnabled( false );
+ pPanel->SetKeyBoardInputEnabled( false );
+ }
+ pPanel = FindChildByName("FeaturedItemPrice");
+ if ( pPanel )
+ {
+ pPanel->SetMouseInputEnabled( false );
+ pPanel->SetKeyBoardInputEnabled( false );
+ }
+
+ if ( m_pFilterComboBox )
+ {
+ vgui::HFont hFont = pScheme->GetFont( "HudFontSmallestBold", true );
+ m_pFilterComboBox->SetFont( hFont );
+ UpdateFilteredItems();
+ UpdateFilterComboBox();
+
+ // Move to "All items" selected
+ m_pFilterComboBox->SilentActivateItemByRow( 0 );
+ }
+
+ if ( m_pItemBackdropPanel )
+ {
+ m_pItemBackdropPanel->SetBgColor( m_colItemBackdropPanel );
+ }
+
+ SetDetailsVisible( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ m_colItemBackdropPanel = inResourceData->GetColor( "item_backdrop_color" );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "modelpanels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pItemModelPanelKVs )
+ {
+ m_pItemModelPanelKVs->deleteThis();
+ }
+ m_pItemModelPanelKVs = new KeyValues("modelpanels_kv");
+ pItemKV->CopySubkeys( m_pItemModelPanelKVs );
+ }
+
+ pItemKV = inResourceData->FindKey( "modelpanel_labels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pModelPanelLabelsKVs )
+ {
+ m_pModelPanelLabelsKVs->deleteThis();
+ }
+ m_pModelPanelLabelsKVs = new KeyValues("modelpanel_labels_kv");
+ pItemKV->CopySubkeys( m_pModelPanelLabelsKVs );
+ }
+
+ pItemKV = inResourceData->FindKey( "cart_modelpanels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pCartModelPanelKVs )
+ {
+ m_pCartModelPanelKVs->deleteThis();
+ }
+ m_pCartModelPanelKVs = new KeyValues("cart_modelpanels_kv");
+ pItemKV->CopySubkeys( m_pCartModelPanelKVs );
+ }
+
+ pItemKV = inResourceData->FindKey( "cart_labels_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pCartQuantityLabelKVs )
+ {
+ m_pCartQuantityLabelKVs->deleteThis();
+ }
+ m_pCartQuantityLabelKVs = new KeyValues("cart_labels_kv");
+ pItemKV->CopySubkeys( m_pCartQuantityLabelKVs );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::PerformLayout( void )
+{
+ if ( m_bReapplyItemKVs )
+ {
+ m_bReapplyItemKVs = false;
+
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_pItemModelPanelKVs )
+ {
+ m_vecItemPanels[i].m_pItemModelPanel->ApplySettings( m_pItemModelPanelKVs );
+ SetBorderForItem( m_vecItemPanels[i].m_pItemModelPanel, false );
+ m_vecItemPanels[i].m_pItemModelPanel->InvalidateLayout();
+ }
+ m_vecItemPanels[i].m_pStorePricePanel->InvalidateLayout();
+ }
+
+ if ( m_pCartModelPanelKVs )
+ {
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ m_pCartModelPanels[i]->ApplySettings( m_pCartModelPanelKVs );
+ SetBorderForItem( m_pCartModelPanels[i], false );
+ m_pCartModelPanels[i]->InvalidateLayout();
+ }
+ }
+
+ if ( m_pCartQuantityLabelKVs )
+ {
+ FOR_EACH_VEC( m_pCartQuantityLabels, i )
+ {
+ m_pCartQuantityLabels[i]->ApplySettings( m_pCartQuantityLabelKVs );
+ m_pCartQuantityLabels[i]->InvalidateLayout();
+ }
+ }
+
+ if ( m_pModelPanelLabelsKVs )
+ {
+ FOR_EACH_VEC( m_pCartQuantityLabels, i )
+ {
+ m_pCartQuantityLabels[i]->ApplySettings( m_pModelPanelLabelsKVs );
+ m_pCartQuantityLabels[i]->InvalidateLayout();
+ }
+ }
+ }
+
+ BaseClass::PerformLayout();
+
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ CItemModelPanel *pItemModelPanel = m_vecItemPanels[i].m_pItemModelPanel;
+ CStorePricePanel *pItemPricePanel = m_vecItemPanels[i].m_pStorePricePanel;
+ CStoreItemControlsPanel *pItemControlsPanel = m_vecItemPanels[i].m_pItemControlsPanel;
+ pItemModelPanel->SetVisible( true );
+ pItemModelPanel->SetNoItemText( "#SelectNoItemSlot" );
+
+ PositionItemPanel(pItemModelPanel, i );
+
+ int iX,iY,iW,iH;
+ pItemModelPanel->GetBounds( iX, iY, iW, iH );
+ // Position our price label and controls
+ pItemPricePanel->SetVisible( pItemModelPanel->HasItem() );
+ pItemPricePanel->SetBounds( iX, iY, iW, iH );
+
+ pItemPricePanel->InvalidateLayout( true );
+
+ pItemControlsPanel->SetPos( iX + m_iItemControlsXOffset, iY + iH - pItemControlsPanel->GetTall() - m_iItemControlsYOffset );
+ }
+
+ if ( m_pItemBackdropPanel && m_vecItemPanels.Count() >= 2 )
+ {
+ CItemModelPanel *pTopLeftPanel = m_vecItemPanels.Head().m_pItemModelPanel;
+ CItemModelPanel *pBottomRightPanel = m_vecItemPanels.Tail().m_pItemModelPanel;
+
+ int aItemBackdropBounds[4];
+ if ( pTopLeftPanel && pBottomRightPanel )
+ {
+ int nX, nY;
+ pTopLeftPanel->GetPos( nX, nY );
+
+ aItemBackdropBounds[0] = nX - m_iItemBackdropLeftMargin;
+ aItemBackdropBounds[1] = nY - m_iItemBackdropTopMargin;
+
+ pBottomRightPanel->GetPos( nX, nY );
+ aItemBackdropBounds[2] = nX + pBottomRightPanel->GetWide() + m_iItemBackdropRightMargin - aItemBackdropBounds[0];
+ aItemBackdropBounds[3] = nY + pBottomRightPanel->GetTall() + m_iItemBackdropBottomMargin - aItemBackdropBounds[1];
+
+ m_pItemBackdropPanel->SetBounds( aItemBackdropBounds[0], aItemBackdropBounds[1], aItemBackdropBounds[2], aItemBackdropBounds[3] );
+
+ m_pItemBackdropPanel->SetPaintBackgroundType( m_iItemBackdropPaintBackgroundType );
+ m_pItemBackdropPanel->SetZPos( m_iItemBackdropZPos );
+ }
+ }
+
+ if ( m_pCartModelPanels.Count() > 0 )
+ {
+ bool bFeaturedImagePanelVisible = false;
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+
+ int iCartX, iCartY;
+ m_pCartButton->GetPos( iCartX, iCartY );
+ int iCartModelWide = m_pCartModelPanels[0]->GetWide();
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ if ( m_pCartModelPanels[i]->HasItem() )
+ {
+ m_pCartModelPanels[i]->SetVisible( true );
+ m_pCartQuantityLabels[i]->SetVisible( true );
+
+ int iX = iCartX + m_pCartButton->GetWide() + (XRES(4) * (i+1)) + (iCartModelWide * i);
+ m_pCartModelPanels[i]->SetPos( iX, iCartY );
+ int iY = iCartY + m_pCartModelPanels[i]->GetTall() - m_pCartQuantityLabels[i]->GetTall();
+ m_pCartQuantityLabels[i]->SetPos( iX + iCartModelWide - m_pCartQuantityLabels[i]->GetWide(), iY );
+
+ // If we're the featured item, show it
+ cart_item_t *pCartItem = pCart->GetItem(i);
+ if ( pCartItem && ( pCartItem->pEntry == EconUI()->GetStorePanel()->GetFeaturedEntry() ) )
+ {
+ bFeaturedImagePanelVisible = true;
+
+ if ( m_pCartFeaturedItemImage )
+ {
+ m_pCartFeaturedItemImage->SetPos( iX - XRES(4), iY - YRES(10) );
+ }
+ }
+ }
+ }
+
+ if ( m_pCartFeaturedItemImage && m_pCartFeaturedItemImage->IsVisible() != bFeaturedImagePanelVisible )
+ {
+ m_pCartFeaturedItemImage->SetVisible( bFeaturedImagePanelVisible );
+ }
+ }
+
+ if ( m_pCurPageLabel )
+ {
+ bool bMultiplePages = (GetNumPages() > 1);
+ m_pCurPageLabel->SetVisible( bMultiplePages );
+ m_pNextPageButton->SetVisible( bMultiplePages );
+ m_pPrevPageButton->SetVisible( bMultiplePages );
+ if ( bMultiplePages )
+ {
+ m_pNextPageButton->SetEnabled( m_iCurrentPage < (GetNumPages()-1) );
+ m_pPrevPageButton->SetEnabled( m_iCurrentPage > 0 );
+ }
+ }
+
+ if ( IsHomePage() )
+ {
+ const store_promotion_spend_for_free_item_t *pPromotion = EconUI()->GetStorePanel()->GetPriceSheet()->GetStorePromotion_SpendForFreeItem();
+ wchar_t wszText[1024];
+ wchar_t wszPriceThreshold[ kLocalizedPriceSizeInChararacters ];
+ ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+ AssertMsg( eCurrency >= k_ECurrencyUSD && eCurrency < k_ECurrencyMax, "Invalid currency!" );
+
+ int iPriceThreshold = pPromotion->m_rgusPriceThreshold[ eCurrency ];
+ MakeMoneyString( wszPriceThreshold, ARRAYSIZE( wszPriceThreshold ), iPriceThreshold, EconUI()->GetStorePanel()->GetCurrency() );
+ bool bIsFreeTrial = false;
+#ifdef TF_CLIENT_DLL
+ bIsFreeTrial = IsFreeTrialAccount();
+#endif
+ const char *pszLocString = bIsFreeTrial ? "#Store_FreeTrial_BonusText" : "#Store_Promotion_SpendForGift";
+ const char *pszElementName = bIsFreeTrial ? "BonusTextLabel" : "PromotionLabel_BonusItem";
+
+ g_pVGuiLocalize->ConstructString_safe( wszText, g_pVGuiLocalize->Find( pszLocString ), 1, wszPriceThreshold );
+ CExLabel *pPromotionText = dynamic_cast< CExLabel* >( FindChildByName( pszElementName, true ) );
+ if ( pPromotionText )
+ {
+ pPromotionText->SetText( wszText );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::PositionItemPanel( CItemModelPanel *pPanel, int iIndex )
+{
+ CItemModelPanel *pRealPanel = m_vecItemPanels[iIndex].m_pItemModelPanel;
+
+ int iOffsetIndex = iIndex;
+ int iYPosOffset = 0;
+ int iCenter = GetWide() * 0.5;
+ int iButtonX = (iOffsetIndex % GetNumColumns());
+ int iButtonY = (iOffsetIndex / GetNumColumns());
+ int iXPos = m_iItemXPos + (iCenter + m_iItemOffcenterX) + (iButtonX * pRealPanel->GetWide()) + (m_iItemXDelta * iButtonX);
+ int iYPos = m_iItemYPos + (iButtonY * pRealPanel->GetTall() ) + (m_iItemYDelta * iButtonY) + iYPosOffset;
+
+ pRealPanel->SetPos( iXPos, iYPos );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnPageShow( void )
+{
+ m_iCurrentPage = m_iSelectPageOnPageShow;
+ m_iSelectPageOnPageShow = 0;
+
+ // !KLUDGE!
+ SetDetailsVisible( !ShouldUseNewStore() );
+
+ m_bReapplyItemKVs = true;
+ BaseClass::OnPageShow();
+
+ if ( !IsHomePage() )
+ {
+ EconUI()->Gamestats_Store( IE_STORE_TAB_CHANGED, NULL, GetPageName() );
+ }
+
+ m_pMouseOverItemPanel->SetVisible( false );
+
+ CreateItemPanels();
+
+ if ( m_pFilterComboBox )
+ {
+ SetFilter( 0 );
+ m_pFilterComboBox->SilentActivateItemByRow( 0 );
+// m_pFilterComboBox->SetVisible( !IsHomePage() );
+ }
+
+ // Setup sort by newest
+ if ( m_pPageData && !ShouldUseNewStore() )
+ {
+ eEconStoreSortType iSortType = kEconStoreSortType_DateNewest;
+ CEconStorePriceSheet *pPriceSheet = EconUI()->GetStorePanel()->GetPriceSheetForEdit();
+ pPriceSheet->SetEconStoreSortType( iSortType );
+
+ CEconStoreCategoryManager::StoreCategory_t *pPageData = const_cast< CEconStoreCategoryManager::StoreCategory_t * >( m_pPageData );
+ pPageData->m_vecEntries.SetLessContext( pPriceSheet );
+ pPageData->m_vecEntries.RedoSort( true );
+
+ UpdateFilteredItems();
+ }
+
+ UpdateModelPanels();
+
+ if ( m_pCheckoutButton )
+ {
+ m_pCheckoutButton->RequestFocus();
+ }
+
+ if ( m_iSelectDefOnPageShow )
+ {
+ m_iSelectDefOnPageShow = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel* CStorePage::CreatePricePanel( int iIndex )
+{
+ if ( m_pPageData && !Q_strcmp( m_pPageData->m_pchPageClass, "CStorePage_Popular" ) )
+ return vgui::SETUP_PANEL( new CStorePricePanel_Popular( this, "StorePrice", iIndex + 1 ) );
+
+ if ( m_pPageData && !Q_strcmp( m_pPageData->m_pchPageClass, "CStorePage_New" ) )
+ return vgui::SETUP_PANEL( new CStorePricePanel_New( this, "StorePrice" ) );
+
+ if ( m_pPageData && !Q_strcmp( m_pPageData->m_pchPageClass, "CStorePage_Bundles" ) )
+ return vgui::SETUP_PANEL( new CStorePricePanel_Bundles( this, "StorePrice" ) );
+
+ return vgui::SETUP_PANEL( new CStorePricePanel( this, "StorePrice" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OrderItemsForDisplay( CUtlVector<const econ_store_entry_t *>& vecItems ) const
+{
+ /*
+ // See how I tread upon all the holy concepts of OOP.
+ if ( m_pPageData &&
+ !Q_strcmp( m_pPageData->m_pchPageClass, "CStorePage_Bundles" ) &&
+ !ShouldUseNewStore() )
+ {
+ vecItems.Sort( &ItemDisplayOrderSort_UseSortOverride );
+ }
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::CreateItemPanels( void )
+{
+ int iNumPanels = GetNumItemPanels();
+ if ( m_pPageData && m_vecItemPanels.Count() < iNumPanels )
+ {
+ for ( int i = m_vecItemPanels.Count(); i < iNumPanels; i++ )
+ {
+ int idx = m_vecItemPanels.AddToTail();
+ item_panel &itempanel = m_vecItemPanels[idx];
+ CItemModelPanel *pPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, VarArgs("modelpanel%d", i) ) );
+ pPanel->SetShowQuantity( true );
+ pPanel->SetActAsButton( true, true );
+ itempanel.m_pItemModelPanel = pPanel;
+
+ pPanel->SetTooltip( m_pMouseOverTooltip, "" );
+
+ // Create our price panel too
+ CStorePricePanel *pPricePanel = CreatePricePanel( i );
+ pPricePanel->SetMouseInputEnabled( false );
+ pPricePanel->SetKeyBoardInputEnabled( false );
+ itempanel.m_pStorePricePanel = pPricePanel;
+
+ // and controls
+ CStoreItemControlsPanel *pControlsPanel = vgui::SETUP_PANEL( new CStoreItemControlsPanel( this, "StoreItemControls", pPanel ) );
+ //pControlsPanel->AddActionSignalTarget( this );
+ if ( ShouldUseNewStore() )
+ {
+ pControlsPanel->SetMouseHoverHandler( pPricePanel );
+ }
+ itempanel.m_pItemControlsPanel = pControlsPanel;
+ }
+
+ m_EntryIndices.SetCountNonDestructively( m_vecItemPanels.Count() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "nextpage", 8 ) )
+ {
+ if ( m_iCurrentPage < (GetNumPages()-1) )
+ {
+ m_iCurrentPage++;
+ UpdateModelPanels();
+ }
+ return;
+ }
+ else if ( !Q_strnicmp( command, "prevpage", 8 ) )
+ {
+ if ( m_iCurrentPage > 0 )
+ {
+ m_iCurrentPage--;
+ UpdateModelPanels();
+ }
+ return;
+ }
+ else if ( !Q_strnicmp( command, "preview_item", 12 ) )
+ {
+ PreviewSelectionItem();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "addtocart", 9 ) )
+ {
+ AddSelectionToCart();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "viewcart", 8 ) )
+ {
+ OpenStoreViewCartPanel();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "startshopping", 8 ) )
+ {
+ PostMessage( EconUI()->GetStorePanel(), new KeyValues("StartShopping") );
+ return;
+ }
+ else if ( !Q_strnicmp( command, "checkout", 8 ) )
+ {
+ EconUI()->GetStorePanel()->InitiateCheckout( false );
+ return;
+ }
+ else if ( !Q_stricmp( command, "show_details" ) )
+ {
+ if ( m_pSelectedPanel )
+ {
+ CEconItemView *pItem = m_pSelectedPanel->GetItem();
+ if ( pItem )
+ {
+ SetDetailsVisible( true );
+ }
+ }
+ return;
+ }
+ else if ( !Q_stricmp( command, "show_preview" ) )
+ {
+ SetDetailsVisible( false );
+ return;
+ }
+ else if ( !Q_strnicmp( command, "marketplace", 8 ) )
+ {
+ if ( steamapicontext && steamapicontext->SteamFriends() )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "http://steamcommunity.com/market/search?appid=440" );
+ }
+ return;
+ }
+ else if ( !Q_stricmp( command, "reloadscheme" ) )
+ {
+ InvalidateLayout( false, true );
+ SetVisible( true );
+ UpdateSelectionInfoPanel();
+ return;
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::FireGameEvent( IGameEvent *event )
+{
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "cart_updated") == 0 )
+ {
+ UpdateCart();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnMouseWheeled( int delta )
+{
+ if ( m_vecItemPanels.Count() == 0 )
+ {
+ // on home page, likely
+ return;
+ }
+
+ int oldSelectionIndex = -1;
+ int currentSelectionIndex = -1;
+
+ // deselect everything
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_vecItemPanels[i].m_pItemModelPanel->IsSelected() )
+ {
+ oldSelectionIndex = i;
+ m_vecItemPanels[i].m_pItemModelPanel->SetSelected( false );
+ SetBorderForItem( m_vecItemPanels[i].m_pItemModelPanel, false );
+ }
+ }
+
+ // step selection ahead
+ if ( delta < 0 )
+ {
+ currentSelectionIndex = oldSelectionIndex+1;
+
+ if ( currentSelectionIndex >= m_vecItemPanels.Count() )
+ {
+ if ( m_iCurrentPage < (GetNumPages()-1) )
+ {
+ currentSelectionIndex = 0;
+ m_iCurrentPage++;
+ UpdateModelPanels();
+ }
+ else
+ {
+ currentSelectionIndex = m_vecItemPanels.Count();
+ }
+ }
+ else if ( !m_vecItemPanels[ currentSelectionIndex ].m_pItemModelPanel->HasItem() )
+ {
+ // don't move into empty slots
+ currentSelectionIndex = oldSelectionIndex;
+ }
+ }
+ else if ( delta > 0 )
+ {
+ currentSelectionIndex = oldSelectionIndex-1;
+
+ if ( currentSelectionIndex < 0 )
+ {
+ if ( m_iCurrentPage > 0 )
+ {
+ currentSelectionIndex = m_vecItemPanels.Count()-1;
+ m_iCurrentPage--;
+ UpdateModelPanels();
+ }
+ else
+ {
+ currentSelectionIndex = 0;
+ }
+ }
+ else if ( !m_vecItemPanels[ currentSelectionIndex ].m_pItemModelPanel->HasItem() )
+ {
+ // don't move into empty slots
+ currentSelectionIndex = oldSelectionIndex;
+ }
+ }
+ else
+ {
+ // no actual wheel movement
+ return;
+ }
+
+ // sanity check
+ currentSelectionIndex = clamp( currentSelectionIndex, 0, m_vecItemPanels.Count()-1 );
+
+ m_pSelectedPanel = m_vecItemPanels[ currentSelectionIndex ].m_pItemModelPanel;
+ m_pSelectedPanel->SetSelected( ShouldUseNewStore() );
+ SetBorderForItem( m_pSelectedPanel, false );
+ UpdateSelectionInfoPanel();
+
+ if ( currentSelectionIndex != oldSelectionIndex )
+ {
+ vgui::surface()->PlaySound( "ui/buttonclick.wav" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CStorePage::AssignItemToPanel( CItemModelPanel *pPanel, int iIndex )
+{
+ iIndex += (m_iCurrentPage * GetNumItemPanels());
+ if ( iIndex >= 0 && iIndex < m_FilteredEntries.Count() )
+ {
+ CEconItemView ItemData;
+ ItemData.Init( m_FilteredEntries[iIndex]->GetItemDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ ItemData.SetItemQuantity( m_FilteredEntries[iIndex]->GetQuantity() );
+ ItemData.SetClientItemFlags( kEconItemFlagClient_Preview | kEconItemFlagClient_StoreItem );
+ pPanel->SetItem( &ItemData );
+
+ return iIndex;
+ }
+
+ pPanel->SetItem( NULL );
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CStorePage::GetNumPages( void )
+{
+ return ceil( (float)m_FilteredEntries.Count() / (float)GetNumItemPanels() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+/* static */ int CStorePage::ItemDisplayOrderSort_UseSortOverride( const econ_store_entry_t *const *ppA, const econ_store_entry_t *const *ppB )
+{
+ static CSchemaAttributeDefHandle pAttribDef_StoreSortOverride( "store sort override" );
+
+ const GameItemDefinition_t *pDefA = ItemSystem()->GetStaticDataForItemByDefIndex( (*ppA)->GetItemDefinitionIndex() ),
+ *pDefB = ItemSystem()->GetStaticDataForItemByDefIndex( (*ppB)->GetItemDefinitionIndex() );
+
+ // We expect only items with valid definition indices to make it into the list to
+ // be sorted.
+ Assert( pDefA );
+ Assert( pDefB );
+
+ // Sort based on: our sort key if we have one; otherwise our definition index.
+ float flValue;
+ int unSortKeyA = ( pAttribDef_StoreSortOverride && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pDefA, pAttribDef_StoreSortOverride, &flValue ) )
+ ? (int)flValue
+ : pDefA->GetDefinitionIndex(),
+ unSortKeyB = ( pAttribDef_StoreSortOverride && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pDefB, pAttribDef_StoreSortOverride, &flValue ) )
+ ? (int)flValue
+ : pDefB->GetDefinitionIndex();
+
+ return unSortKeyA - unSortKeyB;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update our internal list of entries based on our filters, and count items in each filter
+//-----------------------------------------------------------------------------
+void CStorePage::UpdateFilteredItems( void )
+{
+ if ( !m_bFilterDirty )
+ return;
+
+ m_FilteredEntries.Purge();
+ m_vecFilterCounts.SetCount( GetNumPrimaryFilters() );
+ if ( !m_vecFilterCounts.Count() )
+ return;
+
+ FOR_EACH_VEC( m_vecFilterCounts, i )
+ {
+ m_vecFilterCounts[i] = 0;
+ }
+
+ for ( int i = 0; i < m_pPageData->m_vecEntries.Count(); i++ )
+ {
+ const econ_store_entry_t *pEntry = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( m_pPageData->m_vecEntries[i] );
+ GameItemDefinition_t *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( pEntry->GetItemDefinitionIndex() );
+ if ( pDef )
+ {
+ // Get a list of applicable filters for the current item definition
+ CUtlVector<int> filterList;
+ GetFiltersForDef( pDef, &filterList );
+
+ bool bPassesClassFilter = false;
+
+ FOR_EACH_VEC( filterList, iFL )
+ {
+ int iFilter = filterList[iFL];
+
+ m_vecFilterCounts[iFilter]++;
+
+ if ( m_iCurrentFilter == iFilter )
+ {
+ bPassesClassFilter = true;
+ }
+ }
+
+ // If the item passes both filters, add it.
+ // NOTE: DoesEntryFilterPassSecondaryFilter() returns true by default.
+ if ( bPassesClassFilter && DoesEntryFilterPassSecondaryFilter( pEntry ) )
+ {
+ m_FilteredEntries.AddToTail( pEntry );
+ }
+ }
+ }
+
+ // Sort our full list of entries however this store page wants it.
+ OrderItemsForDisplay( m_FilteredEntries );
+
+ m_bFilterDirty = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::UpdateModelPanels( void )
+{
+ DeSelectAllItemPanels();
+ UpdateSelectionInfoPanel();
+ UpdateCart();
+
+ if ( m_pPageData != NULL )
+ {
+ UpdateFilteredItems();
+ UpdateFilterComboBox();
+
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ CItemModelPanel *pItemModelPanel = m_vecItemPanels[i].m_pItemModelPanel;
+ pItemModelPanel->SetShowEquipped( true );
+ m_EntryIndices[i] = AssignItemToPanel( pItemModelPanel, i );
+ SetBorderForItem( pItemModelPanel, false );
+
+ int iEntry = m_EntryIndices[i];
+ if ( iEntry >= 0 && iEntry < m_FilteredEntries.Count() )
+ {
+ // Set the price label
+ m_vecItemPanels[i].m_pStorePricePanel->SetItem( m_FilteredEntries[ iEntry ] );
+ m_vecItemPanels[i].m_pItemControlsPanel->SetItem( m_FilteredEntries[ iEntry ] );
+ }
+ }
+ }
+
+ char szTmp[16];
+ Q_snprintf(szTmp, 16, "%d/%d", m_iCurrentPage+1, GetNumPages() );
+ SetDialogVariable( "backpackpage", szTmp );
+
+ UpdateBackpackLabel();
+
+ // Now layout again to position our item buttons
+ InvalidateLayout();
+
+ if ( m_pFilterComboBox )
+ {
+ m_pFilterComboBox->GetComboButton()->SetFgColor( Color( 117,107,94,255 ) );
+ m_pFilterComboBox->GetComboButton()->SetDefaultColor( Color( 117,107,94,255), Color( 0,0,0,0) );
+ m_pFilterComboBox->GetComboButton()->SetArmedColor( Color( 117,107,94,255), Color( 0,0,0,0) );
+ m_pFilterComboBox->GetComboButton()->SetDepressedColor( Color( 117,107,94,255), Color( 0,0,0,0) );
+ }
+
+ // If we're not the home page, start with the first item selected already
+ if ( m_vecItemPanels.Count() )
+ {
+ ToggleSelectItemPanel( m_vecItemPanels[m_iSelectDefOnPageShow].m_pItemModelPanel );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnItemPanelMouseReleased( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() && pItemPanel->HasItem() )
+ {
+ if ( IsHomePage() )
+ {
+ // On the homepage, they've clicked the featured item. Find it in a store tab and move to it.
+ PostMessage( EconUI()->GetStorePanel(), new KeyValues("FindAndSelectFeaturedItem") );
+ }
+ else if ( !pItemPanel->IsSelected() )
+ {
+ ToggleSelectItemPanel( pItemPanel );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnItemPanelMouseDoublePressed( vgui::Panel *panel )
+{
+ if ( IsHomePage() )
+ {
+ // On the homepage, they've clicked the featured item. Find it in a store tab and move to it.
+ PostMessage( EconUI()->GetStorePanel(), new KeyValues("FindAndSelectFeaturedItem") );
+ return;
+ }
+
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() && pItemPanel->HasItem() )
+ {
+ // Make sure this panel is selected
+ if ( !pItemPanel->IsSelected() )
+ {
+ ToggleSelectItemPanel( pItemPanel );
+ }
+
+ // Double clicking on an item in the cart takes you to the view cart page
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ if ( m_pCartModelPanels[i] == pItemPanel )
+ {
+ OpenStoreViewCartPanel();
+ return;
+ }
+ }
+
+ // Not a cart panel, so add to cart.
+ OnCommand("addtocart");
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::DeSelectAllItemPanels( void )
+{
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_vecItemPanels[i].m_pItemModelPanel->IsSelected() )
+ {
+ m_vecItemPanels[i].m_pItemModelPanel->SetSelected( false );
+ SetBorderForItem( m_vecItemPanels[i].m_pItemModelPanel, false );
+ }
+ }
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ if ( m_pCartModelPanels[i]->IsSelected() )
+ {
+ m_pCartModelPanels[i]->SetSelected( false );
+ SetBorderForItem( m_pCartModelPanels[i], false );
+ }
+ }
+
+ m_pSelectedPanel = NULL;
+ if ( m_pFeaturedItemPanel && m_pFeaturedItemPanel->IsSelected() )
+ {
+ m_pFeaturedItemPanel->SetSelected( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::ToggleSelectItemPanel( CItemModelPanel *pPanel )
+{
+ if ( ShouldUseNewStore() )
+ return;
+
+ if ( pPanel->IsSelected() || !pPanel->HasItem() )
+ {
+ pPanel->SetSelected( false );
+ m_pSelectedPanel = NULL;
+ }
+ else
+ {
+ DeSelectAllItemPanels();
+ pPanel->SetSelected( true );
+ m_pSelectedPanel = pPanel;
+ }
+ SetBorderForItem( pPanel, false );
+ UpdateSelectionInfoPanel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::SelectItemPanel( CItemModelPanel *pPanel )
+{
+ DeSelectAllItemPanels();
+ pPanel->SetSelected( true );
+ m_pSelectedPanel = pPanel;
+ SetBorderForItem( pPanel, false );
+ UpdateSelectionInfoPanel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnItemPanelEntered( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ CEconItemView *pItem = pItemPanel->GetItem();
+ if ( !pItemPanel->IsSelected() )
+ {
+ SetBorderForItem( pItemPanel, pItem != NULL );
+ }
+ if ( pItemPanel->HasItem() )
+ {
+ // make related controls visible
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ item_panel &itempanel = m_vecItemPanels[i];
+ if ( itempanel.m_pItemModelPanel == pItemPanel )
+ {
+ itempanel.m_pItemControlsPanel->OnItemPanelEntered();
+ break;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnItemPanelExited( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ if ( !pItemPanel->IsSelected() )
+ {
+ SetBorderForItem( pItemPanel, false );
+ }
+ if ( pItemPanel->HasItem() )
+ {
+ // make related controls visible
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ item_panel &itempanel = m_vecItemPanels[i];
+ if ( itempanel.m_pItemModelPanel == pItemPanel )
+ {
+ itempanel.m_pItemControlsPanel->OnItemPanelExited();
+ break;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnItemAddToCart( vgui::Panel *panel )
+{
+ CStoreItemControlsPanel *pControlsPanel = dynamic_cast< CStoreItemControlsPanel * >( panel );
+ if ( pControlsPanel )
+ {
+ const econ_store_entry_t *pEntry = pControlsPanel->GetItem();
+ if ( pEntry )
+ {
+ if ( !ShouldUseNewStore() )
+ {
+ SelectItemPanel( pControlsPanel->GetItemModelPanel() );
+ }
+ else
+ {
+#if defined( TF_CLIENT_DLL )
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "store_add_to_cart", "minibutton" );
+#endif
+ }
+ AddItemToCartHelper( GetPageName(), pEntry, kCartItem_Purchase );
+ UpdateCart();
+ }
+
+ // Turn the free slots indicator red if we can't fit everything.
+ UpdateBackpackLabel();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver )
+{
+ if ( !pItemPanel || pItemPanel == m_pFeaturedItemPanel )
+ return;
+
+ // Store panels use backgrounds instead of borders
+ pItemPanel->SetBorder( NULL );
+ pItemPanel->SetPaintBackgroundEnabled( true );
+
+ if ( pItemPanel->IsSelected() )
+ {
+ pItemPanel->SetBgColor( m_colItemPanelBGSelected );
+ }
+ else if ( bMouseOver )
+ {
+ pItemPanel->SetBgColor( m_colItemPanelBGMouseover );
+ }
+ else
+ {
+ pItemPanel->SetBgColor( m_colItemPanelBG );
+ }
+
+ const CEconStorePriceSheet *pPriceSheet = EconUI()->GetStorePanel()->GetPriceSheet();
+
+ if ( pItemPanel->GetItem() && pPriceSheet )
+ {
+ const econ_store_entry_t *pEntry = pPriceSheet->GetEntry( pItemPanel->GetItem()->GetItemDefIndex() );
+
+ if (pEntry && pEntry->m_bHighlighted && !bMouseOver )
+ {
+ pItemPanel->SetBorder( vgui::scheme()->GetIScheme( GetScheme() )->GetBorder( "StoreHighlightedBackgroundBorder" ) );
+ pItemPanel->SetPaintBorderEnabled( true );
+ pItemPanel->SetPaintBackgroundEnabled( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::CalculateItemButtonPos( CItemModelPanel *pItemPanel, int x, int y, int *iXPos, int *iYPos )
+{
+ *iXPos = x;
+ *iYPos = (y + pItemPanel->GetTall() + YRES(4));
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::UpdateSelectionInfoPanel( void )
+{
+ // Home page doesn't support item selections
+ if ( IsHomePage() )
+ return;
+
+ if ( m_pPreviewItemButton )
+ {
+ m_pPreviewItemButton->SetVisible( false );
+ }
+
+ if ( m_pSelectedPanel )
+ {
+ const econ_store_entry_t *pEntry = GetSelectedEntry();
+ CEconItemView *pItem = m_pSelectedPanel->GetItem();
+ if ( pItem && pEntry )
+ {
+ if ( m_pPreviewItemButton )
+ {
+ m_pPreviewItemButton->SetVisible( pEntry->CanPreview() );
+ }
+
+ m_iOldSelectedItemDef = m_iSelectedItemDef;
+ m_iSelectedItemDef = pItem->GetItemDefIndex();
+
+ if ( m_iSelectedItemDef != m_iOldSelectedItemDef )
+ {
+ EconUI()->Gamestats_Store( IE_STORE_ITEM_SELECTED, pItem, GetPageName() );
+ }
+
+ CEconItemDefinition *pItemData = pItem->GetStaticData();
+ if ( pItemData )
+ {
+ ShowPreview( 0, pEntry );
+ InvalidateLayout();
+
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ int iPrice = pEntry->GetCurrentPrice( EconUI()->GetStorePanel()->GetCurrency() );
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), iPrice, EconUI()->GetStorePanel()->GetCurrency() );
+ SetDialogVariable("selectionprice", wzLocalizedPrice );
+
+ if ( m_pAddToCartButtonPanel )
+ {
+ m_pAddToCartButtonPanel->SetVisible( true );
+ }
+
+ return;
+ }
+ }
+ }
+
+ SetDialogVariable("selectionprice", "" );
+
+ if ( m_pAddToCartButtonPanel )
+ {
+ m_pAddToCartButtonPanel->SetVisible( false );
+ }
+ m_iSelectedItemDef = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when text changes in combo box
+//-----------------------------------------------------------------------------
+void CStorePage::OnTextChanged( KeyValues *data )
+{
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
+ vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel );
+
+ m_bFilterDirty = true;
+
+ if ( pComboBox == m_pFilterComboBox )
+ {
+ // the class selection combo box changed, update class details
+ KeyValues *pUserData = m_pFilterComboBox->GetActiveItemUserData();
+ if ( !pUserData )
+ return;
+
+ int iFilter = pUserData->GetInt( "filter", 0 );
+
+ // If there are no items for that class, refuse to switch
+ if ( iFilter && m_vecFilterCounts[iFilter] <= 0 )
+ {
+ m_pFilterComboBox->ActivateItemByRow( m_iCurrentFilter ? m_iCurrentFilter+1 : 0 );
+ return;
+ }
+
+ SetFilter( iFilter );
+ m_iCurrentPage = 0;
+ UpdateModelPanels();
+
+ m_pCheckoutButton->RequestFocus();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::SetFilter( int iFilter )
+{
+ if ( iFilter != m_iCurrentFilter )
+ m_bFilterDirty = true;
+
+ m_iCurrentFilter = iFilter;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::ShowPreview( int iClass, const econ_store_entry_t* pEntry )
+{
+ if ( !m_pPreviewPanel )
+ return;
+
+ CEconItemView itemData;
+ itemData.Init( m_iSelectedItemDef, AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ itemData.SetClientItemFlags( kEconItemFlagClient_Preview );
+
+ m_pPreviewPanel->PreviewItem( iClass, &itemData, pEntry );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::SetDetailsVisible( bool bVisible )
+{
+ if ( m_pPreviewPanel )
+ {
+ m_pPreviewPanel->SetState( bVisible ? PS_DETAILS : PS_ITEM );
+ }
+
+ if ( m_pItemPreviewButtonPanel && m_pItemDetailsButtonPanel )
+ {
+ vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() );
+ if ( bVisible )
+ {
+ m_pItemPreviewButtonPanel->SetBorder( pScheme->GetBorder("StorePreviewTabUnselected") );
+ m_pItemDetailsButtonPanel->SetBorder( pScheme->GetBorder("StorePreviewTabSelected") );
+ }
+ else
+ {
+ m_pItemPreviewButtonPanel->SetBorder( pScheme->GetBorder("StorePreviewTabSelected") );
+ m_pItemDetailsButtonPanel->SetBorder( pScheme->GetBorder("StorePreviewTabUnselected") );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CStorePage::FindAndSelectEntry( const econ_store_entry_t *pEntry )
+{
+ // We can't search if we haven't created our item panels & filtered.
+ CreateItemPanels();
+ SetFilter( FILTER_ALL_ITEMS );
+ UpdateFilteredItems();
+
+ FOR_EACH_VEC( m_FilteredEntries, i )
+ {
+ if ( m_FilteredEntries[i]->GetItemDefinitionIndex() == pEntry->GetItemDefinitionIndex() )
+ {
+ // Figure out what page it'll be on
+ int iPage = floor( (float)i / (float)GetNumItemPanels() );
+
+ // Switch to that page
+ m_iCurrentPage = iPage;
+ UpdateModelPanels();
+ m_iSelectPageOnPageShow = iPage;
+
+ // Then select the item model panel for this item
+ FOR_EACH_VEC( m_vecItemPanels, p )
+ {
+ CEconItemView *pItem = m_vecItemPanels[p].m_pItemModelPanel->GetItem();
+ if ( pItem && pItem->GetItemDefIndex() == pEntry->GetItemDefinitionIndex() )
+ {
+ // We can't select here, because the pageshow will stomp it.
+ // Remember that this is the panel we'd like to have selected.
+ m_iSelectDefOnPageShow = p;
+ break;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const econ_store_entry_t *CStorePage::GetSelectedEntry( void )
+{
+ // Get the entry for the panel.
+ int iEntry = -1;
+
+ if ( m_pFeaturedItemPanel == m_pSelectedPanel )
+ return EconUI()->GetStorePanel()->GetFeaturedEntry();
+
+ FOR_EACH_VEC( m_vecItemPanels, i )
+ {
+ if ( m_vecItemPanels[i].m_pItemModelPanel == m_pSelectedPanel )
+ {
+ iEntry = m_EntryIndices[i];
+ if ( iEntry >= 0 && iEntry < m_FilteredEntries.Count() )
+ return m_FilteredEntries[iEntry];
+ }
+ }
+
+ // It's probably something already in our cart.
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ if ( m_pCartModelPanels[i] == m_pSelectedPanel )
+ {
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ if ( i < pCart->GetNumEntries() )
+ {
+ cart_item_t *pCartItem = pCart->GetItem(i);
+ return pCartItem->pEntry;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::AddSelectionToCart( void )
+{
+ if ( !m_pSelectedPanel )
+ return;
+
+ // Get the entry for the panel.
+ const econ_store_entry_t *pEntry = GetSelectedEntry();
+ if ( pEntry )
+ {
+ AddItemToCartHelper( GetPageName(), pEntry, kCartItem_Purchase );
+ UpdateCart();
+ }
+
+ // Turn the free slots indicator red if we can't fit everything.
+ UpdateBackpackLabel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::UpdateCart( void )
+{
+ if ( !IsVisible() || ( !ShouldUseNewStore() && IsHomePage() ) )
+ return;
+
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ int iNumEntriesInCart = pCart->GetNumEntries();
+
+ // Now update the item icons next to the cart.
+ if ( m_pCartModelPanels.Count() < iNumEntriesInCart )
+ {
+ // Support a max of 10 items in the cart quickview right now
+ for ( int i = m_pCartModelPanels.Count(); (i < iNumEntriesInCart) && (i < m_iMaxCartModelPanels); i++ )
+ {
+ CItemModelPanel *pPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, VarArgs("cartmodelpanel%d", i) ) );
+ pPanel->SetActAsButton( true, true );
+ pPanel->ApplySettings( m_pCartModelPanelKVs );
+ SetBorderForItem( pPanel, false );
+ m_pCartModelPanels.AddToTail( pPanel );
+
+ pPanel->SetTooltip( m_pMouseOverTooltip, "" );
+
+ CExLabel *pLabel = vgui::SETUP_PANEL( new CExLabel( this, VarArgs("cartquantitylabel%d", i), "X" ) );
+ pLabel->ApplySettings( m_pCartQuantityLabelKVs );
+ pLabel->SetMouseInputEnabled( false );
+ pLabel->SetKeyBoardInputEnabled( false );
+ m_pCartQuantityLabels.AddToTail( pLabel );
+ }
+ }
+
+ UpdateBackpackLabel();
+
+ InvalidateLayout();
+
+ CEconItemView *pItemData = new CEconItemView();
+
+ // Assign the items in the cart to the panels
+ FOR_EACH_VEC( m_pCartModelPanels, i )
+ {
+ if ( i >= iNumEntriesInCart )
+ {
+ m_pCartModelPanels[i]->SetItem( NULL );
+ m_pCartModelPanels[i]->SetVisible( false );
+ m_pCartQuantityLabels[i]->SetVisible( false );
+ continue;
+ }
+
+ cart_item_t *pCartItem = pCart->GetItem(i);
+ pItemData->Init( pCartItem->pEntry->GetItemDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ pItemData->SetClientItemFlags( kEconItemFlagClient_Preview | kEconItemFlagClient_StoreItem );
+ m_pCartModelPanels[i]->SetItem( pItemData );
+ m_pCartModelPanels[i]->SetVisible( true );
+
+ m_pCartQuantityLabels[i]->SetVisible( true );
+ m_pCartQuantityLabels[i]->SetText( VarArgs("%d",pCartItem->iQuantity) );
+ }
+
+ delete pItemData;
+
+ // Update the item count
+ wchar_t wszCount[16];
+ wchar_t wzLocalized[512];
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", pCart->GetTotalItems() );
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_Cart" ), 1, wszCount );
+ SetDialogVariable("storecart", wzLocalized );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+ConVar econ_never_show_items_in_cart_count( "econ_never_show_items_in_cart_count", "1", FCVAR_DEVELOPMENTONLY );
+
+void CStorePage::UpdateBackpackLabel( void )
+{
+ wchar_t wszBackpackSlotCount[16];
+ wchar_t wszLocalized[512];
+
+ // How many slots do we have free in our current backpack? This won't take into
+ // consideration expanders, account upgrades, etc.
+ const int iMaxItemCount = InventoryManager()->GetLocalInventory()->GetMaxItemCount(),
+ iCurItemCount = InventoryManager()->GetLocalInventory()->GetItemCount();
+ AssertMsg( iMaxItemCount - iCurItemCount >= 0, "You have a negative number of backpack slots available - fix me!" );
+ const int iBaseFreeSlots = MAX( 0, iMaxItemCount - iCurItemCount );
+ _snwprintf( wszBackpackSlotCount, ARRAYSIZE( wszBackpackSlotCount ), L"%d", iBaseFreeSlots );
+
+ // Breaking out bundles into individual items, etc., how many backpack slots will the
+ // items in our cart take up?
+ const int iItemsInCart = EconUI()->GetStorePanel()->GetCart()->GetTotalConcreteItems();
+
+ if ( iItemsInCart == 0 || econ_never_show_items_in_cart_count.GetBool() )
+ {
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#Store_FreeBackpackSpace" ), 1, wszBackpackSlotCount );
+ }
+ else
+ {
+ wchar_t wszCartCount[16];
+ _snwprintf( wszCartCount, ARRAYSIZE( wszCartCount ), L"%d", iItemsInCart );
+
+#if defined( TF_CLIENT_DLL )
+ if ( IsFreeTrialAccount() )
+ {
+ wchar_t wszUpgradeSlotCount[16];
+ _snwprintf( wszUpgradeSlotCount, ARRAYSIZE( wszUpgradeSlotCount ), L"%d", DEFAULT_NUM_BACKPACK_SLOTS - DEFAULT_NUM_BACKPACK_SLOTS_FREE_TRIAL_ACCOUNT );
+
+ // We're a free trial account so we show the number of backpack slots we really have,
+ // the number of slots we get as a bonus when purchasing, and then the number of items
+ // in our cart.
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#Store_FreeBackpackSpace_WithCartItems_WithUpgrade" ), 3, wszBackpackSlotCount, wszCartCount, wszUpgradeSlotCount );
+ }
+ else
+#endif // defined( TF_CLIENT_DLL )
+ {
+ // We aren't a free trial account, so there is no account upgrade included in
+ // this purchase, so fall back to showing the number of items in our cart.
+ g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#Store_FreeBackpackSpace_WithCartItems" ), 2, wszBackpackSlotCount, wszCartCount );
+ }
+ }
+
+ SetDialogVariable( "freebackpackspace", wszLocalized );
+
+ if ( m_pBackpackLabel )
+ {
+ const Color clrTooMany = ShouldUseNewStore() ? Color(200,80,60,255) : Color(255,0,0,255);
+ m_pBackpackLabel->SetFgColor( InventoryManager()->GetLocalInventory()->CanPurchaseItems( iItemsInCart ) ? m_colBackpackOrg : clrTooMany );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::UpdateFilterComboBox( void )
+{
+ if ( !m_pFilterComboBox )
+ return;
+
+ m_pFilterComboBox->RemoveAll();
+
+ // All items
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ pKeyValues->SetInt( "filter", FILTER_ALL_ITEMS );
+ m_pFilterComboBox->AddItem( "#Store_ClassFilter_None", pKeyValues );
+
+ pKeyValues->deleteThis();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::PreviewSelectionItem( void )
+{
+ if ( !m_pSelectedPanel )
+ return;
+
+ // Get the entry for the panel.
+ const econ_store_entry_t *pEntry = GetSelectedEntry();
+ if ( !pEntry )
+ return;
+
+ if ( !pEntry->CanPreview() )
+ return;
+
+ DoPreviewItem( pEntry->GetItemDefinitionIndex() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::DoPreviewItem( item_definition_index_t usItemDef )
+{
+#ifdef TF_CLIENT_DLL
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( "do_try_out_item", CFmtStr( "%i", usItemDef ).Access() );
+#endif
+
+ if ( usItemDef == InventoryManager()->GetLocalInventory()->GetPreviewItemDef() )
+ {
+ ShowMessageBox( "#ItemPreview_AlreadyPreviewTitle", "#ItemPreview_AlreadyPreviewText", "#GameUI_OK" );
+ return;
+ }
+
+ // Send a message to the GC asking if this player can preview an item.
+ GCSDK::CGCMsg< MsgGCCheckItemPreviewStatus_t > msg( k_EMsgGCItemPreviewCheckStatus );
+ msg.Body().m_unItemDefIndex = usItemDef;
+
+ // OGS LOGGING HERE
+
+ GCClientSystem()->BSendMessage( msg );
+
+ // Response is handled in item_rental_ui.cpp.
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePage::OnPreviewItem( KeyValues *pData )
+{
+ DoPreviewItem( pData->GetInt( "item_def_index" ) );
+}
diff --git a/game/client/econ/store/store_page.h b/game/client/econ/store/store_page.h
new file mode 100644
index 0000000..49745cb
--- /dev/null
+++ b/game/client/econ/store/store_page.h
@@ -0,0 +1,424 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PAGE_H
+#define STORE_PAGE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <game/client/iviewport.h>
+#include "vgui_controls/PropertyPage.h"
+#include <vgui_controls/Button.h>
+#include <vgui_controls/ComboBox.h>
+#include <vgui_controls/ImagePanel.h>
+#include "econ_controls.h"
+#include "econ_ui.h"
+#include "econ_store.h"
+#include "item_model_panel.h"
+#include "econ_storecategory.h"
+
+class CItemModelPanel;
+class CItemModelPanelToolTip;
+class CStorePreviewItemPanel;
+class CStoreItemControlsPanel;
+
+#define FILTER_ALL_ITEMS 0
+
+//-----------------------------------------------------------------------------
+// Purpose: Base class for the preview icons in the store's item preview panel
+//-----------------------------------------------------------------------------
+class CBaseStorePreviewIcon : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CBaseStorePreviewIcon, vgui::EditablePanel );
+public:
+ CBaseStorePreviewIcon( vgui::Panel *parent, const char *name ) : vgui::EditablePanel(parent,name)
+ {
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colPanelBG, "panel_bgcolor" );
+ REGISTER_COLOR_AS_OVERRIDABLE( m_colPanelBGMouseover, "panel_bgcolor_mouseover" );
+ m_bHover = false;
+ m_bSelected = false;
+ }
+
+ void SetSelected( bool bSelected )
+ {
+ m_bSelected = bSelected;
+ UpdateBgColor();
+ }
+
+ void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+ SetBgColor( m_colPanelBG );
+ }
+
+ virtual void PerformLayout( void )
+ {
+ BaseClass::PerformLayout();
+
+ int iWide = GetWide() - (m_iImageIndent * 2);
+ int iTall = GetTall() - (m_iImageIndent * 2);
+ SetInternalImageBounds( m_iImageIndent, m_iImageIndent, iWide, iTall );
+ }
+
+ virtual void OnCursorEntered()
+ {
+ BaseClass::OnCursorEntered();
+ m_bHover = true;
+ UpdateBgColor();
+ }
+ virtual void OnCursorExited()
+ {
+ BaseClass::OnCursorExited();
+ m_bHover = false;
+ UpdateBgColor();
+ }
+
+ virtual void SetInternalImageBounds( int iX, int iY, int iWide, int iTall ) = 0;
+
+private:
+ Color m_colPanelBG;
+ Color m_colPanelBGMouseover;
+ CPanelAnimationVarAliasType( int, m_iImageIndent, "image_indent", "0", "proportional_int" );
+
+ bool m_bHover;
+ bool m_bSelected;
+
+ void UpdateBgColor()
+ {
+ if ( m_bHover || m_bSelected )
+ {
+ SetBgColor( m_colPanelBGMouseover );
+ }
+ else
+ {
+ SetBgColor( m_colPanelBG );
+ }
+ }
+
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: An item preview icon in the store's item preview panel
+//-----------------------------------------------------------------------------
+class CStorePreviewItemIcon : public CBaseStorePreviewIcon
+{
+ DECLARE_CLASS_SIMPLE( CStorePreviewItemIcon, CBaseStorePreviewIcon );
+public:
+ CStorePreviewItemIcon( vgui::Panel *parent, const char *name ) : CBaseStorePreviewIcon(parent,name)
+ {
+ m_pItemPanel = new CItemModelPanel( this, "itempanel" );
+ m_pItemPanel->AddActionSignalTarget( this );
+ m_pItemPanel->SendPanelEnterExits( true );
+ m_pItemPanel->SetActAsButton( true, true );
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+ vgui::EditablePanel *pTmp = dynamic_cast<vgui::EditablePanel*>( FindChildByName("bgblockout") );
+ if ( pTmp )
+ {
+ pTmp->SetMouseInputEnabled( false );
+ }
+ }
+
+ virtual void OnCursorEntered()
+ {
+ BaseClass::OnCursorEntered();
+ PostActionSignal(new KeyValues("ShowItemIconMouseover", "icon", m_iIconIndex));
+ }
+ virtual void OnCursorExited()
+ {
+ BaseClass::OnCursorExited();
+ PostActionSignal(new KeyValues("HideItemIconMouseover"));
+ }
+ virtual void OnMouseReleased(vgui::MouseCode code)
+ {
+ BaseClass::OnMouseReleased(code);
+ PostActionSignal(new KeyValues("ItemIconSelected", "icon", m_iIconIndex));
+ }
+
+ MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel );
+
+ virtual void SetInternalImageBounds( int iX, int iY, int iWide, int iTall )
+ {
+ m_pItemPanel->SetBounds( iX, iY, iWide, iTall );
+ }
+
+ MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel )
+ {
+ BaseClass::OnCursorEntered();
+ }
+
+ MESSAGE_FUNC_PTR( OnItemPanelExited, "ItemPanelExited", panel )
+ {
+ BaseClass::OnCursorExited();
+ }
+
+ void SetItem( int iIconIndex, int iItemDef )
+ {
+ m_iIconIndex = iIconIndex;
+
+ CEconItemView itemData;
+ itemData.Init( iItemDef, AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ m_pItemPanel->SetItem( &itemData );
+ }
+
+ void SetItem( int iIconIndex, CEconItemView *pItem )
+ {
+ m_iIconIndex = iIconIndex;
+ m_pItemPanel->SetItem( pItem );
+ }
+
+ CItemModelPanel *GetItemPanel( void ) { return m_pItemPanel; }
+
+private:
+ CItemModelPanel *m_pItemPanel;
+ int m_iIconIndex;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStoreItemControlsPanel;
+class CStoreItemControlsPanel : public vgui::EditablePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStoreItemControlsPanel, vgui::EditablePanel );
+
+ CStoreItemControlsPanel( vgui::Panel *pParent, const char *pPanelName, CItemModelPanel *pItemModelPanel );
+ virtual ~CStoreItemControlsPanel() {}
+
+ void SetMouseHoverHandler( Panel *pHandler );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+
+ const econ_store_entry_t *GetItem() const;
+ void SetItem( const econ_store_entry_t *pEntry );
+ void SetButtonsVisible( bool bVisible );
+
+ virtual void OnCursorEntered();
+ virtual void OnCursorExited();
+
+ void OnItemPanelEntered();
+ void OnItemPanelExited();
+
+ virtual void OnThink();
+ virtual void OnCommand( const char *command );
+
+ CItemModelPanel *GetItemModelPanel() { return m_pItemModelPanel; }
+
+protected:
+ CItemModelPanel *m_pItemModelPanel;
+ const econ_store_entry_t *m_pEntry;
+ bool m_bButtonsVisible;
+ bool m_bItemPanelEntered;
+ vgui::DHANDLE< Panel > m_pMouseHoverHandler;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePricePanel : public vgui::EditablePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStorePricePanel, vgui::EditablePanel );
+
+ CStorePricePanel( vgui::Panel *pParent, const char *pPanelName );
+ virtual ~CStorePricePanel();
+
+ virtual const char* GetPanelResFile();
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout();
+
+ void SetPriceText( int iPrice, const char *pVariable, const econ_store_entry_t *pEntry );
+ virtual void SetItem( const econ_store_entry_t *pEntry );
+
+ MESSAGE_FUNC_PARAMS( OnStoreItemControlsPanelHover, "StoreItemControlsPanelHover", data );
+
+protected:
+ bool m_bOldDiscountVisibility;
+ CExLabel *m_pPrice;
+ CExLabel *m_pDiscount;
+ CExLabel *m_pNew;
+ CExLabel *m_pHighlighted;
+ CExLabel *m_pSale;
+ EditablePanel *m_pSaleBorder;
+ CExLabel *m_pOGPrice;
+ Panel *m_pCrossout;
+ Panel *m_pLimited;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePage : public vgui::PropertyPage, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CStorePage, vgui::PropertyPage );
+public:
+ CStorePage( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData, const char *pPreviewItemResFile = NULL );
+ virtual ~CStorePage();
+
+ virtual void OnPostCreate();
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void OnCommand( const char *command );
+ virtual void PerformLayout( void );
+ virtual void FireGameEvent( IGameEvent *event );
+ virtual void OnMouseWheeled( int delta );
+
+ virtual CStorePricePanel* CreatePricePanel( int iIndex );
+
+ void SetBorderForItem( CItemModelPanel *pItemPanel, bool bMouseOver );
+ void CalculateItemButtonPos( CItemModelPanel *pItemPanel, int x, int y, int *iXPos, int *iYPos );
+ int AssignItemToPanel( CItemModelPanel *pPanel, int iIndex );
+ void PositionItemPanel( CItemModelPanel *pPanel, int iIndex );
+
+ void UpdateModelPanels( void );
+ virtual void UpdateSelectionInfoPanel( void );
+ void UpdateCart( void );
+ void AddSelectionToCart( void );
+ void PreviewSelectionItem( void );
+ void DoPreviewItem( item_definition_index_t usItemDef );
+ const econ_store_entry_t *GetSelectedEntry( void );
+
+ int GetNumItemPanels( void ) { return m_iItemPanels; }
+ int GetNumColumns( void ) { return m_iItemColumns; }
+ int GetNumPages( void );
+ virtual void ShowPreview( int iClass, const econ_store_entry_t* pEntry );
+ void SetDetailsVisible( bool bVisible );
+
+ const char* GetPageName( void ) { return m_pPageData ? m_pPageData->m_pchName : NULL; }
+
+ virtual bool FindAndSelectEntry( const econ_store_entry_t *pEntry );
+
+ CItemModelPanelToolTip *GetItemTooltip( void ) { return m_pMouseOverTooltip; }
+
+ MESSAGE_FUNC( OnPageShow, "PageShow" );
+ MESSAGE_FUNC_PTR( OnItemPanelMouseReleased, "ItemPanelMouseReleased", panel ); // Comes from CStoreItemControlsPanel
+ MESSAGE_FUNC_PTR( OnItemPanelMouseDoublePressed, "ItemPanelMouseDoublePressed", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel );
+ MESSAGE_FUNC_PTR( OnItemPanelExited, "ItemPanelExited", panel );
+ MESSAGE_FUNC_PTR( OnItemAddToCart, "ItemAddToCart", panel );
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+ MESSAGE_FUNC_PARAMS( OnPreviewItem, "PreviewItem", data );
+
+ virtual const char *GetPageResFile();
+ virtual CStorePreviewItemPanel *CreatePreviewPanel( void );
+
+protected:
+ // Filtering
+ virtual bool DoesEntryFilterPassSecondaryFilter( const econ_store_entry_t *pEntry ) { return true; } // Allow derived classes to add an additional
+ virtual void UpdateFilteredItems( void );
+ virtual int GetNumPrimaryFilters( void ) { return 1; } // All Items
+ void SetFilter( int iFilter );
+ virtual void UpdateFilterComboBox( void );
+ virtual void GetFiltersForDef( GameItemDefinition_t *pDef, CUtlVector<int> *pVecFilters ) { pVecFilters->AddToTail( FILTER_ALL_ITEMS ); }
+
+ static int ItemDisplayOrderSort_UseSortOverride( const econ_store_entry_t *const *ppA, const econ_store_entry_t *const *ppB );
+ virtual void OrderItemsForDisplay( CUtlVector<const econ_store_entry_t *>& vecItems ) const;
+
+protected:
+ void CreateItemPanels( void );
+ void DeSelectAllItemPanels( void );
+ void ToggleSelectItemPanel( CItemModelPanel *pPanel );
+ void SelectItemPanel( CItemModelPanel *pPanel );
+ void UpdateBackpackLabel( void );
+ bool IsHomePage( void ) { return m_pPageData && m_pPageData->m_bIsHome; }
+
+protected:
+ const CEconStoreCategoryManager::StoreCategory_t *m_pPageData;
+ CStorePreviewItemPanel *m_pPreviewPanel;
+ const char *m_pPreviewItemResFile;
+ vgui::EditablePanel *m_pItemDetailsButtonPanel;
+ vgui::EditablePanel *m_pItemPreviewButtonPanel;
+
+ // Filtering
+ CUtlVector< const econ_store_entry_t* > m_FilteredEntries;
+ vgui::ComboBox *m_pFilterComboBox;
+ int m_iCurrentFilter;
+
+ // Selection info panel
+ int m_iSelectedItemDef;
+ int m_iOldSelectedItemDef;
+ int m_iSelectDefOnPageShow;
+ int m_iSelectPageOnPageShow;
+ CItemModelPanel *m_pSelectedPanel;
+ CItemModelPanel *m_pFeaturedItemPanel;
+ Color m_colBackpackOrg;
+
+ // Item model panels
+ struct item_panel
+ {
+ CItemModelPanel* m_pItemModelPanel;
+ CStorePricePanel* m_pStorePricePanel;
+ CStoreItemControlsPanel* m_pItemControlsPanel;
+ };
+ CUtlVector<item_panel> m_vecItemPanels;
+ CUtlVector<int> m_EntryIndices; // Easy lookup for which model panel is mapped to which entry index
+ CItemModelPanel *m_pMouseOverItemPanel;
+ CItemModelPanelToolTip *m_pMouseOverTooltip;
+ KeyValues *m_pItemModelPanelKVs;
+ KeyValues *m_pModelPanelLabelsKVs;
+ bool m_bReapplyItemKVs;
+
+ // Cart display
+ CExButton *m_pCartButton;
+ CUtlVector<CItemModelPanel*> m_pCartModelPanels;
+ KeyValues *m_pCartModelPanelKVs;
+ CUtlVector<CExLabel*> m_pCartQuantityLabels;
+ KeyValues *m_pCartQuantityLabelKVs;
+ vgui::ImagePanel *m_pCartFeaturedItemImage;
+
+ // Pages
+ int m_iCurrentPage;
+ vgui::Label *m_pCurPageLabel;
+ CExButton *m_pNextPageButton;
+ CExButton *m_pPrevPageButton;
+
+ CExButton *m_pCheckoutButton;
+ CExButton *m_pPreviewItemButton;
+ vgui::EditablePanel *m_pAddToCartButtonPanel;
+ vgui::Label *m_pBackpackLabel;
+ vgui::EditablePanel *m_pItemBackdropPanel;
+
+ bool m_bShouldDeletePreviewPanel; // Set to true by derived classes if the preview panel's panel should be deleted, which is necessary if its parent is NULL
+
+ CPanelAnimationVarAliasType( int, m_iItemOffcenterX, "item_offcenter_x", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemXDelta, "item_xdelta", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemYDelta, "item_ydelta", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemXPos, "item_xpos", "0", "proportional_int" );
+ CPanelAnimationVarAliasType( int, m_iItemYPos, "item_ypos", "0", "proportional_int" );
+ CPanelAnimationVar( int, m_iItemPanels, "item_panels", "35" );
+ CPanelAnimationVar( int, m_iItemColumns, "item_columns", "7" );
+ CPanelAnimationVar( bool, m_bShowItemBgPanel, "show_item_backdrop", "0" );
+ CPanelAnimationVarAliasType( int, m_iItemBackdropLeftMargin, "item_backdrop_left_margin", "20", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iItemBackdropRightMargin, "item_backdrop_right_margin", "20", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iItemBackdropTopMargin, "item_backdrop_top_margin", "20", "proportional_ypos" );
+ CPanelAnimationVarAliasType( int, m_iItemBackdropBottomMargin, "item_backdrop_bottom_margin", "20", "proportional_ypos" );
+ CPanelAnimationVar( int, m_iItemBackdropPaintBackgroundType, "item_backdrop_paintbackgroundtype", "50" );
+ CPanelAnimationVar( int, m_iItemBackdropZPos, "item_backdrop_zpos", "0" );
+ CPanelAnimationVarAliasType( int, m_iItemControlsXOffset, "item_controls_xoffset", "5", "proportional_xpos" );
+ CPanelAnimationVarAliasType( int, m_iItemControlsYOffset, "item_controls_yoffset", "5", "proportional_xpos" );
+ CPanelAnimationVar( int, m_iMaxCartModelPanels, "max_cart_model_panels", "10" );
+
+ Color m_colItemPanelBG;
+ Color m_colItemPanelBGMouseover;
+ Color m_colItemPanelBGSelected;
+ Color m_colItemBackdropPanel;
+
+ // The number of items in each filter options
+ CUtlVector<int> m_vecFilterCounts;
+
+ bool m_bFilterDirty;
+};
+
+void AddItemToCartHelper( const char *pszContext, const econ_store_entry_t *pEntry, ECartItemType eSelectedCartItemType );
+void AddItemToCartHelper( const char *pszContext, item_definition_index_t unItemDef, ECartItemType eSelectedCartItemType );
+
+#endif // STORE_PAGE_H
diff --git a/game/client/econ/store/store_page_halloween.cpp b/game/client/econ/store/store_page_halloween.cpp
new file mode 100644
index 0000000..a039b4d
--- /dev/null
+++ b/game/client/econ/store/store_page_halloween.cpp
@@ -0,0 +1,85 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/store_page_halloween.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "c_tf_player.h"
+#include "gamestringpool.h"
+#include "tf_item_inventory.h"
+#include "econ_item_system.h"
+#include "item_model_panel.h"
+#include "store/store_panel.h"
+#include "store_preview_item.h"
+#include "store_viewcart.h"
+#include "c_tf_gamestats.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePage_SpecialPromo::CTFStorePage_SpecialPromo( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData ) : BaseClass( parent, pPageData )
+{
+ pszResFile = pPageData->m_pchPageRes;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+/*
+void CTFStorePage_SpecialPromo::OrderItemsForDisplay( CUtlVector<const econ_store_entry_t *>& vecItems ) const
+{
+ vecItems.Sort( &ItemDisplayOrderSort_UseSortOverride );
+}
+*/
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFStorePage_Popular::CTFStorePage_Popular( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData ) : BaseClass( parent, pPageData )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The popular page draws its list of items from the overall popular items list.
+//-----------------------------------------------------------------------------
+void CTFStorePage_Popular::UpdateFilteredItems( void )
+{
+ m_FilteredEntries.Purge();
+ m_vecFilterCounts.SetCount( GetNumPrimaryFilters() );
+ if ( !m_vecFilterCounts.Count() )
+ return;
+
+ FOR_EACH_VEC( m_vecFilterCounts, i )
+ {
+ m_vecFilterCounts[i] = 0;
+ }
+
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+ if ( !pStorePanel )
+ return;
+
+ // Add all popular items
+ const CUtlVector<uint32>& popularItems = pStorePanel->GetPopularItems();
+
+ for ( int i=0; i<popularItems.Count(); ++i )
+ {
+ const econ_store_entry_t *pEntry = pStorePanel->GetPriceSheet()->GetEntry( popularItems[i] );
+ m_FilteredEntries.AddToTail( pEntry );
+ }
+
+ FOR_EACH_VEC( m_vecItemPanels, idx )
+ {
+ m_vecItemPanels[idx].m_pItemModelPanel->SetShowQuantity( false );
+ }
+
+ m_pFilterComboBox->SetVisible( false );
+} \ No newline at end of file
diff --git a/game/client/econ/store/store_page_halloween.h b/game/client/econ/store/store_page_halloween.h
new file mode 100644
index 0000000..7ab0c0a
--- /dev/null
+++ b/game/client/econ/store/store_page_halloween.h
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PAGE_SPECIALPROMO_H
+#define STORE_PAGE_SPECIALPROMO_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "store/v1/tf_store_page.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePage_SpecialPromo : public CTFStorePage1
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePage_SpecialPromo, CTFStorePage1 );
+public:
+ CTFStorePage_SpecialPromo( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData );
+
+ virtual const char* GetPageResFile() { return pszResFile; }
+
+protected:
+// virtual void OrderItemsForDisplay( CUtlVector<const econ_store_entry_t *>& vecItems ) const;
+
+private:
+ const char* pszResFile;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CTFStorePage_Popular : public CTFStorePage1
+{
+ DECLARE_CLASS_SIMPLE( CTFStorePage_Popular, CTFStorePage1 );
+public:
+ CTFStorePage_Popular( Panel *parent, const CEconStoreCategoryManager::StoreCategory_t *pPageData );
+
+protected:
+ virtual void UpdateFilteredItems( void );
+};
+
+
+#endif // STORE_PAGE_SPECIALPROMO_H
diff --git a/game/client/econ/store/store_page_new.cpp b/game/client/econ/store/store_page_new.cpp
new file mode 100644
index 0000000..f979236
--- /dev/null
+++ b/game/client/econ/store/store_page_new.cpp
@@ -0,0 +1,232 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store_page_new.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "gamestringpool.h"
+#include "econ_item_inventory.h"
+#include "econ_item_system.h"
+#include "item_model_panel.h"
+#include "store/store_panel.h"
+#include "store_preview_item.h"
+#include "store_viewcart.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+using namespace vgui;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel_New::CStorePricePanel_New( vgui::Panel *pParent, const char *pPanelName )
+: CStorePricePanel( pParent, pPanelName )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_New::SetItem( const econ_store_entry_t *pEntry )
+{
+ BaseClass::SetItem( pEntry );
+
+ CExLabel *pNew = dynamic_cast< CExLabel* >( FindChildByName( "New" ) );
+ if ( pNew )
+ {
+ pNew->SetVisible( false );
+ }
+
+ pNew = dynamic_cast< CExLabel* >( FindChildByName( "NewLarge" ) );
+ if ( pNew )
+ {
+ int contentWidth, contentHeight;
+ pNew->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ pNew->GetTextInset( &iTextInsetX, &iTextInsetY );
+ pNew->SetWide( contentWidth + iTextInsetX );
+ int iPosX, iPosY;
+ pNew->GetPos( iPosX, iPosY );
+ pNew->SetPos( GetWide() - pNew->GetWide(), iPosY );
+ pNew->SetVisible( true );
+ }
+
+ vgui::Panel* pLimited = FindChildByName( "LimitedLarge" );
+ if ( pLimited )
+ {
+ int iPosX, iPosY;
+ pLimited->GetPos( iPosX, iPosY );
+
+ if ( pNew && pEntry->m_bLimited )
+ {
+ iPosY = pNew->GetTall() + YRES( 3 );
+ }
+
+ pLimited->SetPos( GetWide() - pLimited->GetWide() - XRES( 3 ), iPosY );
+ pLimited->SetVisible( pEntry->m_bLimited );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel_Bundles::CStorePricePanel_Bundles( vgui::Panel *pParent, const char *pPanelName )
+: CStorePricePanel( pParent, pPanelName ),
+ m_pLimitedLarge( NULL )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Bundles::SetItem( const econ_store_entry_t *pEntry )
+{
+ BaseClass::SetItem( pEntry );
+
+ if ( m_pLimitedLarge )
+ {
+ m_pLimitedLarge->SetVisible( pEntry->m_bLimited );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Bundles::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pLimitedLarge = dynamic_cast<ImagePanel *>( FindChildByName( "LimitedLarge" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Bundles::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( m_pLimitedLarge )
+ {
+ int aPos[2];
+ m_pLimitedLarge->GetPos( aPos[0], aPos[1] );
+
+ if ( m_pNew && m_pNew->IsVisible() )
+ {
+ aPos[1] = m_pNew->GetTall() + YRES( 3 );
+ }
+
+ m_pLimitedLarge->SetPos( GetWide() - m_pLimitedLarge->GetWide(), aPos[1] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel_Jumbo::CStorePricePanel_Jumbo( vgui::Panel *pParent, const char *pPanelName )
+: CStorePricePanel( pParent, pPanelName )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePricePanel_Popular::CStorePricePanel_Popular( vgui::Panel *pParent, const char *pPanelName, int iPopularityRank )
+: CStorePricePanel( pParent, pPanelName )
+, m_iPopularityRank( iPopularityRank )
+{
+ m_pNewLarge = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Popular::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pNewLarge = dynamic_cast< CExLabel* >( FindChildByName( "NewLarge" ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Popular::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ if ( m_pNewLarge )
+ {
+ int contentWidth, contentHeight;
+ m_pNewLarge->GetContentSize( contentWidth, contentHeight );
+ int iTextInsetX, iTextInsetY;
+ m_pNewLarge->GetTextInset( &iTextInsetX, &iTextInsetY );
+ m_pNewLarge->SetWide( contentWidth + iTextInsetX );
+ int iPosX, iPosY;
+ m_pNewLarge->GetPos( iPosX, iPosY );
+ m_pNewLarge->SetPos( GetWide() - m_pNewLarge->GetWide(), iPosY );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePricePanel_Popular::SetItem( const econ_store_entry_t *pEntry )
+{
+ BaseClass::SetItem( pEntry );
+
+ CExLabel *pNew = dynamic_cast< CExLabel* >( FindChildByName( "New" ) );
+ if ( pNew )
+ {
+ pNew->SetVisible( false );
+ }
+
+ if ( m_pNewLarge )
+ {
+ if ( pEntry->m_bNew )
+ {
+ m_pNewLarge->SetVisible( true );
+ }
+ else
+ {
+ m_pNewLarge->SetVisible( false );
+ }
+ }
+
+ vgui::Panel* pLimited = FindChildByName( "LimitedLarge" );
+ if ( pLimited && m_pNewLarge )
+ {
+ int iPosX, iPosY;
+ pLimited->GetPos( iPosX, iPosY );
+
+ if ( pEntry->m_bLimited && pEntry->m_bNew )
+ {
+ iPosY = m_pNewLarge->GetTall() + YRES( 3 );
+ }
+
+ pLimited->SetPos( GetWide() - m_pNewLarge->GetWide() - 14, iPosY );
+ pLimited->SetVisible( pEntry->m_bLimited );
+ }
+
+ wchar_t wszRank[10];
+ _snwprintf( wszRank, ARRAYSIZE( wszRank ), L"%d", m_iPopularityRank );
+ wchar_t wszText[8];
+ g_pVGuiLocalize->ConstructString_safe( wszText, g_pVGuiLocalize->Find( "TF_Popularity_Rank" ), 1, wszRank );
+ SetDialogVariable( "rank1", wszText );
+ SetDialogVariable( "rank2", wszText );
+
+ // Show rank or rank2 based on old store/new store
+ CExLabel *pRank = dynamic_cast<CExLabel *>( FindChildByName( CFmtStr( "Rank%i", GetStoreVersion() ).Access() ) );
+ if ( pRank )
+ {
+ pRank->SetVisible( true );
+ }
+} \ No newline at end of file
diff --git a/game/client/econ/store/store_page_new.h b/game/client/econ/store/store_page_new.h
new file mode 100644
index 0000000..5c7224f
--- /dev/null
+++ b/game/client/econ/store/store_page_new.h
@@ -0,0 +1,107 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PAGE_NEW_H
+#define STORE_PAGE_NEW_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <game/client/iviewport.h>
+#include "vgui_controls/PropertyPage.h"
+#include <vgui_controls/Button.h>
+#include <vgui_controls/ComboBox.h>
+#include <vgui_controls/ImagePanel.h>
+#include "econ_controls.h"
+#include "econ_store.h"
+#include "item_model_panel.h"
+#include "store_page.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePricePanel_New : public CStorePricePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStorePricePanel_New, CStorePricePanel );
+
+ CStorePricePanel_New( vgui::Panel *pParent, const char *pPanelName );
+
+ virtual const char *GetPanelResFile()
+ {
+ return "Resource/UI/econ/store/v1/StorePrice_New.res";
+ }
+
+ virtual void SetItem( const econ_store_entry_t *pEntry );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePricePanel_Bundles : public CStorePricePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStorePricePanel_Bundles, CStorePricePanel );
+
+ CStorePricePanel_Bundles( vgui::Panel *pParent, const char *pPanelName );
+
+ virtual const char *GetPanelResFile()
+ {
+ return "Resource/UI/econ/store/v1/StorePrice_Bundles.res";
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout();
+
+ virtual void SetItem( const econ_store_entry_t *pEntry );
+
+private:
+ vgui::ImagePanel *m_pLimitedLarge;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePricePanel_Jumbo : public CStorePricePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStorePricePanel_Jumbo, CStorePricePanel );
+
+ CStorePricePanel_Jumbo( vgui::Panel *pParent, const char *pPanelName );
+
+ virtual const char *GetPanelResFile()
+ {
+ return "Resource/UI/econ/store/v1/StorePrice_Jumbo.res";
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePricePanel_Popular : public CStorePricePanel
+{
+public:
+ DECLARE_CLASS_SIMPLE( CStorePricePanel_Popular, CStorePricePanel );
+
+ CStorePricePanel_Popular( vgui::Panel *pParent, const char *pPanelName, int iPopularityRank );
+
+ virtual const char *GetPanelResFile()
+ {
+ return "Resource/UI/econ/store/v1/StorePrice_Popular.res";
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout();
+
+ virtual void SetItem( const econ_store_entry_t *pEntry );
+
+private:
+ int m_iPopularityRank;
+ CExLabel *m_pNewLarge;
+};
+
+#endif // STORE_PAGE_NEW_H
diff --git a/game/client/econ/store/store_panel.cpp b/game/client/econ/store/store_panel.cpp
new file mode 100644
index 0000000..258a657
--- /dev/null
+++ b/game/client/econ/store/store_panel.cpp
@@ -0,0 +1,2175 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store/store_panel.h"
+#include "vgui_controls/PropertySheet.h"
+#include "vgui/IInput.h"
+#include "baseviewport.h"
+#include "iclientmode.h"
+#include "ienginevgui.h"
+#include "econ_item_inventory.h"
+#include <vgui/ILocalize.h>
+#include "econ_item_system.h"
+#include "store_page_new.h"
+#include "store_viewcart.h"
+#include "confirm_dialog.h"
+#include <vgui_controls/AnimationController.h>
+#include "econ_ui.h"
+#include "gc_clientsystem.h"
+#include "steamworks_gamestats.h"
+#include "econ/econ_storecategory.h"
+
+#ifdef TF_CLIENT_DLL
+#include "tf_mapinfo.h"
+#include "c_tf_freeaccount.h"
+#include "tf_hud_statpanel.h"
+#include "rtime.h"
+#include "item_ad_panel.h"
+#include "tf_matchmaking_dashboard.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+
+#ifdef TF_CLIENT_DLL
+// Dec 4st 2012
+#define TF_STORE_STAMP_UPSELL_BASELINE 1354579200
+// TEMP VALUE FOR TESTING! Nov 20th 2012
+//#define TF_STORE_STAMP_UPSELL_BASELINE 1353369600
+
+// 15 days
+#define TF_STORE_STAMP_UPSELL_COOLDOWN ( 60 /*sec*/ * 60 /*min*/ * 24 /*hr*/ * 7 /*day*/ )
+// TEMP VALUE FOR TESTING! 1 day
+//#define TF_STORE_STAMP_UPSELL_COOLDOWN ( 60 /*sec*/ * 60 /*min*/ * 24 /*hr*/ )
+
+#define TF_STORE_STAMP_UPSELL_GROUPS 15
+
+ConVar tf_store_stamp_donation_add_timestamp( "tf_store_stamp_donation_add_timestamp", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE | FCVAR_HIDDEN, "Remember the last time that we offered a stamp donation at checkout." );
+#endif
+
+bool CStorePanel::m_bPricesheetLoaded = false;
+bool CStorePanel::m_bShowWarnings = false;
+
+class CServerNotConnectedToSteamDialog;
+CServerNotConnectedToSteamDialog *OpenServerNotConnectedToSteamDialog( vgui::Panel *pParent );
+
+class CStampUpsellDialog : public CTFGenericConfirmDialog
+{
+ DECLARE_CLASS_SIMPLE( CStampUpsellDialog, CTFGenericConfirmDialog );
+public:
+ CStampUpsellDialog( const char *pTitle, const wchar_t *pTextText, const wchar_t *pTextText2, CSchemaItemDefHandle hMapToken, const char *pItemDefName2 )
+ : CTFGenericConfirmDialog( pTitle, pTextText, NULL, NULL, NULL, NULL )
+ , hItemDef( hMapToken ), hItemDef2( pItemDefName2 )
+ {
+ V_wcsncpy( m_wszBuffer2, pTextText2, sizeof( m_wszBuffer2 ) );
+ m_flCreationTime = gpGlobals->curtime;
+ }
+
+ virtual const char *GetResFile() OVERRIDE
+ {
+ return "Resource/UI/StampDonationAdd.res";
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE
+ {
+ BaseClass::ApplySchemeSettings(pScheme);
+
+ vgui::ImagePanel *pItemImagePanel = dynamic_cast<vgui::ImagePanel *>( FindChildByName( "ItemImagePanel", true ) ); Assert( pItemImagePanel );
+ if ( pItemImagePanel && hItemDef )
+ {
+ pItemImagePanel->SetImage( CFmtStr( "../%s_large", hItemDef->GetInventoryImage() ) );
+ }
+
+ vgui::ImagePanel *pItemImagePanel2 = dynamic_cast<vgui::ImagePanel *>( FindChildByName( "ItemImagePanel2", true ) ); Assert( pItemImagePanel );
+ if ( pItemImagePanel2 && hItemDef2 )
+ {
+ pItemImagePanel2->SetImage( CFmtStr( "../%s_large", hItemDef2->GetInventoryImage() ) );
+ }
+
+ // Now go through the string and find the escape characters telling us where the color changes are
+ ColorizeLabel( static_cast< vgui::Label* >( FindChildByName( "ExplanationLabel" ) ), m_wszBuffer );
+ ColorizeLabel( static_cast< vgui::Label* >( FindChildByName( "ExplanationLabel2" ) ), m_wszBuffer2 );
+
+ CEconItemView itemData;
+ itemData.Init( hItemDef->GetDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ itemData.SetItemQuantity( 1 );
+ itemData.SetClientItemFlags( kEconItemFlagClient_Preview | kEconItemFlagClient_StoreItem );
+
+ const ECurrency eCurrency = EconUI()->GetStorePanel()->GetCurrency();
+ const econ_store_entry_t *pEntry = EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( hItemDef->GetDefinitionIndex() );
+ item_price_t unPrice = pEntry->GetCurrentPrice( eCurrency );
+
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), unPrice, EconUI()->GetStorePanel()->GetCurrency() );
+ SetDialogVariable( "price", wzLocalizedPrice );
+
+ Panel *pConfirmButton = FindChildByName( "ConfirmButton" );
+ pConfirmButton->RequestFocus();
+ }
+
+ virtual void OnCommand( const char *command ) OVERRIDE
+ {
+ int nSecondsVisible = gpGlobals->curtime - m_flCreationTime;
+ if ( V_strcmp( command, "add_stamp_to_cart" ) == 0 )
+ {
+ FinishUp();
+ CStorePanel::ConfirmUpsellStamps( true, hItemDef, nSecondsVisible );
+ }
+ else if ( V_strcmp( command, "nope" ) == 0 )
+ {
+ FinishUp();
+ CStorePanel::ConfirmUpsellStamps( false, hItemDef, nSecondsVisible );
+ }
+ else
+ {
+ BaseClass::OnCommand( command );
+ }
+ }
+
+ virtual void OnKeyCodePressed( vgui::KeyCode code )
+ {
+ // ESC cancels
+ if ( code == KEY_XBUTTON_B )
+ {
+ OnCommand( "nope" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+ }
+
+ void ColorizeLabel( vgui::Label *pLabel, wchar_t *txt )
+ {
+ if ( pLabel )
+ {
+ pLabel->GetTextImage()->ClearColorChangeStream();
+
+ // We change the title's text color to match the colors of the matching model panel backgrounds
+ int iWChars = 0;
+ Color colCustom;
+ while ( txt && *txt )
+ {
+ switch ( *txt )
+ {
+ case 0x01: // Normal color
+ pLabel->GetTextImage()->AddColorChange( Color(200,80,60,255), iWChars );
+ break;
+ case 0x02: // Item 1 color
+ pLabel->GetTextImage()->AddColorChange( Color(255,255,255,255), iWChars );
+ break;
+ default:
+ break;
+ }
+ txt++;
+ iWChars++;
+ }
+ }
+ }
+
+ CSchemaItemDefHandle hItemDef;
+ CSchemaItemDefHandle hItemDef2;
+
+ wchar_t m_wszBuffer2[1024];
+ float m_flCreationTime;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: A dialog used to show the current state of store communication with steam.
+//-----------------------------------------------------------------------------
+class CStoreStatusDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CStoreStatusDialog, vgui::EditablePanel );
+
+public:
+ CStoreStatusDialog( vgui::Panel *pParent, const char *pElementName );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void OnCommand( const char *command );
+ void UpdateSchemeForVersion();
+ void ShowStatusUpdate( bool bAllowed, bool bShowOnExit, bool bCancel );
+
+private:
+ bool m_bShowOnExit;
+ bool m_bNotifyOnCancel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void ContactSupportConfirm( bool bConfirmed, void *pContext )
+{
+ if ( bConfirmed && steamapicontext && steamapicontext->SteamFriends() )
+ {
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "https://support.steampowered.com/" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePanel::CStorePanel( Panel *parent ) : PropertyDialog(parent, "store_panel")
+, m_CallbackMicroTransactionAuthResponse( this, &CStorePanel::OnMicroTransactionAuthResponse )
+{
+ // Store is parented to the game UI panel
+ vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL );
+ SetParent( gameuiPanel );
+
+ // We don't want the gameui to delete us, or things get messy
+ SetAutoDelete( false );
+
+ SetMoveable( false );
+ SetSizeable( false );
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ m_unTransactionID = 0;
+ m_bShouldFinalize = false;
+ m_iStartItemDef = 0;
+ m_bAddStartItemDefToCart = false;
+ m_bPreventClosure = false;
+ m_bOGSLogging = false;
+
+ m_iCheckoutAttempts = 0;
+ m_iLastPurchaseAttemptPrice = 0;
+
+ m_eCurrency = k_ECurrencyUSD;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePanel::~CStorePanel()
+{
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( CFmtStr( "Resource/UI/econ/store/v%i/StorePanel.res", GetStoreVersion() ) );
+
+ SetOKButtonVisible(false);
+ SetCancelButtonVisible(false);
+
+ vgui::ivgui()->AddTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::PerformLayout( void )
+{
+ if ( GetVParent() )
+ {
+ int w,h;
+ vgui::ipanel()->GetSize( GetVParent(), w, h );
+ SetBounds(0,0,w,h);
+ }
+
+#ifdef TF_CLIENT_DLL
+ bool bShowUpsellCheckbox = ( tf_store_stamp_donation_add_timestamp.GetFloat() > 0.0f && HasValidUpsellStamps() );
+ SetControlVisible( "SupportCommunityMapMakersCheckButton", bShowUpsellCheckbox );
+ SetControlVisible( "SupportCommunityMapMakersLabel", bShowUpsellCheckbox );
+#endif
+
+ BaseClass::PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::OnStartShopping( void )
+{
+ // Move to the first tab
+ vgui::Panel *pPage = GetPropertySheet()->GetPage(1);
+ if ( pPage )
+ {
+ GetPropertySheet()->SetActivePage( pPage );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::OnFindAndSelectFeaturedItem( void )
+{
+ const econ_store_entry_t *pEntry = GetFeaturedEntry();
+ if ( !pEntry )
+ return;
+
+ FindAndSelectEntry( pEntry );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::FindAndSelectEntry( const econ_store_entry_t *pEntry )
+{
+ // Find the item in a store page and move to it
+ int iPages = GetPropertySheet()->GetNumPages();
+ for ( int i = 1; i < iPages; i++ )
+ {
+ CStorePage *pPage = dynamic_cast< CStorePage * >( GetPropertySheet()->GetPage(i) );
+ if ( !pPage )
+ continue;
+
+ if ( pPage->FindAndSelectEntry( pEntry ) )
+ {
+ if ( GetPropertySheet()->GetActivePage() != pPage )
+ {
+ GetPropertySheet()->SetActivePage( pPage );
+ }
+ else
+ {
+ // VGUI doesn't tell the starting active page that it's active, so we post a pageshow to it
+ ivgui()->PostMessage( pPage->GetVPanel(), new KeyValues("PageShow"), GetPropertySheet()->GetVPanel() );
+ }
+ return;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Static store page factory.
+//-----------------------------------------------------------------------------
+CStorePage *CStorePanel::CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData )
+{
+ // Default, standard store page.
+ return new CStorePage( this, pPageData );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CStorePanel::ShouldShowDx8PurchaseWarning() const
+{
+ static ConVarRef mat_dxlevel( "mat_dxlevel" );
+ if ( mat_dxlevel.GetInt() >= 90 )
+ return false;
+
+ // List of operations that have features that are not compatible with DX8.
+ const char* cpDX8WarningItems[] = {
+ "Unused Summer 2015 Operation Pass",
+ "Unused Operation Tough Break Pass",
+ NULL
+ };
+
+ for ( int i = 0; cpDX8WarningItems[ i ] != NULL; ++i )
+ {
+ if ( m_Cart.ContainsItemDefinition( ItemSystem()->GetStaticDataForItemByName( cpDX8WarningItems[ i ] )->GetDefinitionIndex() ) )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::OnItemLinkClicked( KeyValues *pParams )
+{
+ // Get the item definition index from the URL
+ const char *pURL = pParams->GetString( "url" );
+ int iItemDef = atoi( pURL + 7 );
+
+ if ( EconUI()->GetStorePanel()->GetPriceSheet() )
+ {
+ // Look up the item definition and see if there is a store remap
+ CEconItemSchema *pSchema = ItemSystem()->GetItemSchema();
+ if ( pSchema )
+ {
+ CEconItemDefinition *pItem = pSchema->GetItemDefinition( iItemDef );
+ if ( pItem )
+ {
+ int iStoreRemap = pItem->GetStoreRemap();
+ if ( iStoreRemap > 0 )
+ {
+ iItemDef = pItem->GetStoreRemap();
+ }
+ }
+ }
+
+ const econ_store_entry_t *pEntry = GetPriceSheet()->GetEntry( iItemDef );
+ if ( pEntry )
+ {
+ FindAndSelectEntry( pEntry );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::ShowPanel(bool bShow)
+{
+ m_bPreventClosure = false;
+
+#ifdef TF_CLIENT_DLL
+ // Keep the MM dashboard on top of us
+ bShow ? GetMMDashboardParentManager()->PushModalFullscreenPopup( this )
+ : GetMMDashboardParentManager()->PopModalFullscreenPopup( this );
+#endif
+
+ if ( bShow )
+ {
+ if ( !m_bOGSLogging )
+ {
+ EconUI()->Gamestats_Store( IE_STORE_ENTERED );
+ m_bOGSLogging = true;
+ }
+
+ ShowStorePanel();
+ }
+ else
+ {
+ if ( m_bOGSLogging )
+ {
+ EconUI()->Gamestats_Store( IE_STORE_EXITED );
+ m_bOGSLogging = false;
+ }
+ }
+
+ SetVisible( bShow );
+
+ if ( bShow && m_bAddStartItemDefToCart )
+ {
+ OpenStoreViewCartPanel();
+ m_bAddStartItemDefToCart = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::FireGameEvent( IGameEvent *event )
+{
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ if ( m_bPreventClosure )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ }
+ else
+ {
+ ShowPanel( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "checkout", 8 ) )
+ {
+ InitiateCheckout( false );
+ return;
+ }
+ else if ( !Q_stricmp( command, "close" ) )
+ {
+ ShowPanel( false );
+
+ // If we're connected to a game server, we also close the game UI.
+ if ( engine->IsInGame() )
+ {
+ engine->ClientCmd_Unrestricted( "gameui_hide" );
+ }
+
+#ifdef TF_CLIENT_DLL
+ if ( IsFreeTrialAccount() )
+ {
+ InventoryManager()->CheckForRoomAndForceDiscard();
+ }
+#endif
+ }
+ else if ( !Q_stricmp( command, "back" ) )
+ {
+ ShowPanel( true );
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::OnKeyCodeTyped(vgui::KeyCode code)
+{
+ if ( code == KEY_ESCAPE )
+ {
+ if ( !m_bPreventClosure )
+ {
+ ShowPanel( false );
+ }
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const econ_store_entry_t *CStorePanel::GetFeaturedEntry( void )
+{
+ const CEconStoreCategoryManager::StoreCategory_t *pFeaturedSection = GEconStoreCategoryManager()->GetFeaturedItems();
+ if ( !pFeaturedSection || pFeaturedSection->m_vecEntries.Count() == 0 )
+ return NULL;
+ uint32 idx = MIN( m_StoreSheet.GetFeaturedItemIndex(), (uint32)( pFeaturedSection->m_vecEntries.Count() - 1 ) );
+ return EconUI()->GetStorePanel()->GetPriceSheet()->GetEntry( pFeaturedSection->m_vecEntries[ idx ] );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Asks the GC to send us the price sheet.
+//-----------------------------------------------------------------------------
+void CStorePanel::RequestPricesheet( void )
+{
+ // Create the store panel the first time we request a price sheet
+ EconUI()->CreateStorePanel();
+
+ CGCClientJobGetUserData *pJob = new CGCClientJobGetUserData( GCClientSystem()->GetGCClient(), 0 );
+ pJob->StartJob( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for getting the price sheet from the GC
+//-----------------------------------------------------------------------------
+bool CGCClientJobGetUserData::BYieldingRunJob( void *pvStartParam )
+{
+ GCSDK::CProtoBufMsg<CMsgStoreGetUserData> msg( k_EMsgGCStoreGetUserData );
+ GCSDK::CProtoBufMsg<CMsgStoreGetUserDataResponse> msgResponse;
+
+ msg.Body().set_price_sheet_version( m_RTimeVersion );
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStoreGetUserDataResponse ) )
+ {
+ // No response from the GC. Show a failure message.
+ if ( CStorePanel::ShouldShowWarnings() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_NoGCResponse", true, false );
+ }
+ return false;
+ }
+
+#ifdef _DEBUG
+ Msg( "CGCClientJobGetUserData - Result: %d\n", msgResponse.Body().result() );
+#endif
+
+ if ( !CStorePanel::CheckMessageResult( (EPurchaseResult)msgResponse.Body().result() ) )
+ return true;
+
+ bool bInitialLoad = !CStorePanel::IsPricesheetLoaded();
+
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+
+ // Create the store panel.
+ if ( bInitialLoad )
+ {
+ // Close the loading status dialog.
+ CloseStoreStatusDialog();
+ }
+ else
+ {
+ pStorePanel->GetPropertySheet()->DeleteAllPages();
+
+ // Indicate to the user what happened.
+ if( pStorePanel->IsVisible() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_NewPriceSheetLoaded", true, false );
+ }
+ }
+
+ // Set the prices & items on the store panel.
+ KeyValuesAD pKVPricesheet( "prices" );
+
+ // Allow a back-door for reading the local price sheet rather than the one from the GC.
+//#define READ_LOCAL_PRICE_SHEET // DO NOT CHECK IN
+#if defined( READ_LOCAL_PRICE_SHEET ) && defined( _DEBUG )
+ pKVPricesheet->LoadFromFile( g_pFullFileSystem, "scripts/items/unencrypted/store.txt", "MOD" );
+#else
+ CUtlBuffer bufRawData( msgResponse.Body().price_sheet().data(), msgResponse.Body().price_sheet().size(), CUtlBuffer::READ_ONLY );
+ pKVPricesheet->ReadAsBinary( bufRawData );
+#endif
+
+ pKVPricesheet->SetInt( "featured_item_index", msgResponse.Body().featured_item_idx() );
+ pKVPricesheet->SetInt( "default_sort_type", msgResponse.Body().default_item_sort() );
+ pStorePanel->LoadPricesheet( &pKVPricesheet );
+
+ // Manually copy over the version number the GC sent down.
+ Assert( pStorePanel->GetPriceSheetForEdit() );
+ pStorePanel->GetPriceSheetForEdit()->SetVersionStamp( msgResponse.Body().price_sheet_version() );
+
+ pStorePanel->ClearPopularItems();
+ for ( int i=0; i<msgResponse.Body().popular_items_size(); ++i )
+ {
+ pStorePanel->AddPopularItem( msgResponse.Body().popular_items(i) );
+ }
+
+ // Store our experiment membership value.
+ EconUI()->SetExperimentValue( msgResponse.Body().experiment_data() );
+
+ // Store the currency and country code.
+ if ( msgResponse.Body().currency() == k_ECurrencyInvalid )
+ {
+ // An invalid currency means the user must contact steam support to have their account set up.
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_ContactSupport", true, false );
+ return true;
+ }
+
+ pStorePanel->SetCurrency( (ECurrency)msgResponse.Body().currency() );
+ pStorePanel->SetCountryCode( msgResponse.Body().country().c_str() );
+ // TODO: Also store the steam provided localization code (not implemented yet in the response).
+
+ // Open the store panel.
+ if ( !bInitialLoad )
+ {
+ // Not an initial load, but store was already up. Re-open it to clean it up.
+ if ( pStorePanel->IsVisible() )
+ {
+ pStorePanel->ShowPanel( true );
+ }
+ }
+ else
+ {
+#ifndef TF_CLIENT_DLL
+ // First time we've loaded the pricesheet, so open the store.
+ // You can remove this if you choose to load the pricesheet on game startup (like TF does)
+ EconUI()->OpenStorePanel( 0, false );
+#endif
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "store_pricesheet_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePage *CStorePanel::AddPageFromPriceSheet( int iPage )
+{
+ CStorePage* pPage = CreateStorePage( GEconStoreCategoryManager()->GetCategoryFromIndex( iPage ) );
+ pPage->OnPostCreate();
+ pPage->AddActionSignalTarget( this );
+ AddPage( pPage, GEconStoreCategoryManager()->GetCategoryFromIndex( iPage )->m_pchName );
+
+ if ( iPage == 0 )
+ {
+ pPage->SetVisible( true );
+ }
+
+ return pPage;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Populates the storepanel with data from the GC
+//-----------------------------------------------------------------------------
+bool CStorePanel::LoadPricesheet( KeyValuesAD* pKVPricesheet )
+{
+ // Read the store KV file in, and parse it.
+ Verify( m_StoreSheet.InitFromKV( *pKVPricesheet ) );
+
+ // Add our pages
+ for ( int i = 0; i < GEconStoreCategoryManager()->GetNumCategories(); i++ )
+ {
+ // Skip subcategories
+ if ( GEconStoreCategoryManager()->GetCategoryFromIndex( i )->BIsSubcategory() )
+ continue;
+
+ AddPageFromPriceSheet( i );
+ }
+
+ // Clear the cart, since we may have new items.
+ GetCart()->EmptyCart();
+
+ m_bPricesheetLoaded = true;
+
+ return true;
+}
+
+#ifdef _DEBUG
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::ReAddPage( int iPage )
+{
+ CStorePage *pPage = AddPageFromPriceSheet( iPage );
+ if ( pPage )
+ {
+ pPage->InvalidateLayout( true, true );
+ pPage->SetVisible( true );
+ GetPropertySheet()->SetActivePage( pPage );
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets currency type.
+//-----------------------------------------------------------------------------
+void CStorePanel::SetCurrency( ECurrency in_currency )
+{
+ // Do any currency related UI work here.
+ m_eCurrency = in_currency;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the country code. Later this will become an enum.
+//-----------------------------------------------------------------------------
+void CStorePanel::SetCountryCode( const char* in_country )
+{
+ V_strcpy_safe( m_rgchCountry, in_country );
+}
+
+bool CStorePanel::ShouldUpsellStamps( void )
+{
+#ifdef TF_CLIENT_DLL
+ bool bForceShow = false;
+
+ CheckButton *pSupportCheckButton = static_cast< CheckButton* >( FindChildByName( "SupportCommunityMapMakersCheckButton" ) );
+ if ( pSupportCheckButton && pSupportCheckButton->IsVisible() && pSupportCheckButton->IsSelected() )
+ {
+ bForceShow = true;
+ }
+
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ if ( !pCart || pCart->GetNumEntries() <= 0 )
+ return false;
+
+ if ( !bForceShow )
+ {
+ for ( int i = 0; i < pCart->GetNumEntries(); ++i )
+ {
+ cart_item_t *pItem = pCart->GetItem( i );
+ if ( pItem && pItem->pEntry && pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Maps ) )
+ {
+ return false;
+ }
+ }
+ }
+
+ if ( !HasValidUpsellStamps() )
+ return false;
+
+ RTime32 rtCurrentTime = CRTime::RTime32TimeCur();
+ RTime32 rtTimeStamp = tf_store_stamp_donation_add_timestamp.GetInt();
+
+ if ( !bForceShow )
+ {
+ if ( rtTimeStamp > rtCurrentTime )
+ {
+ // Time stamp set ahead of current time
+ // Set it back to the current time and save it
+ tf_store_stamp_donation_add_timestamp.SetValue( (int)rtCurrentTime );
+ engine->ClientCmd_Unrestricted( "host_writeconfig" );
+ return false;
+ }
+
+ AccountID_t nAccountID = steamapicontext->SteamUser()->GetSteamID().GetAccountID();
+ int nGroup = nAccountID % TF_STORE_STAMP_UPSELL_GROUPS;
+ RTime32 nBucketTimeOffset = ( TF_STORE_STAMP_UPSELL_COOLDOWN / TF_STORE_STAMP_UPSELL_GROUPS ) * nGroup;
+ RTime32 nBucketedTimeStamp = ( ( rtTimeStamp / TF_STORE_STAMP_UPSELL_COOLDOWN ) + 1 ) * TF_STORE_STAMP_UPSELL_COOLDOWN;
+ nBucketedTimeStamp = MAX( nBucketedTimeStamp, TF_STORE_STAMP_UPSELL_BASELINE );
+ nBucketedTimeStamp += nBucketTimeOffset;
+
+ if ( rtCurrentTime < nBucketedTimeStamp + TF_STORE_STAMP_UPSELL_COOLDOWN )
+ {
+ return false;
+ }
+ }
+
+ tf_store_stamp_donation_add_timestamp.SetValue( (int)rtCurrentTime );
+ engine->ClientCmd_Unrestricted( "host_writeconfig" );
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool CStorePanel::HasValidUpsellStamps( void )
+{
+#ifdef TF_CLIENT_DLL
+ int nTotalDonationLevel = 0;
+
+ for ( int i = 0; i < GetItemSchema()->GetMapCount(); i++ )
+ {
+ const MapDef_t* pMap = GetItemSchema()->GetMasterMapDefByIndex( i );
+ if ( pMap->IsCommunityMap() )
+ {
+ int nDonationLevel = MapInfo_GetDonationAmount( steamapicontext->SteamUser()->GetSteamID().GetAccountID(), pMap->pszMapName );
+ nTotalDonationLevel += nDonationLevel;
+
+ // No need to upsell if they've spent $50 on stamps
+ if ( nTotalDonationLevel >= 50 )
+ return false;
+
+ // No need to upsell this map if they've spent more than $15 on it
+ if ( nDonationLevel > 20 )
+ continue;
+
+ // No need to upsell this map if they've played it less than an hour
+ MapStats_t &mapStats = CTFStatPanel::GetMapStats( pMap->GetStatsIdentifier() );
+ int nNumHours = ( mapStats.accumulated.m_iStat[TFMAPSTAT_PLAYTIME] ) / ( 60 /*sec*/ * 60 /*min*/ );
+
+ // No need to upsell this map if they've played it less than 2 hours
+ if ( nNumHours <= 1 )
+ continue;
+
+ int nRelativeDivisor = nNumHours / 4;
+ float flRelativePayoff = ( nRelativeDivisor == 0 ? 0.0f : static_cast< float >( nDonationLevel ) / nRelativeDivisor );
+
+ // No need to upsell this map if they've spend more than $1 per 10 hours
+ if ( flRelativePayoff >= 1.0f )
+ continue;
+
+ return true;
+ }
+ }
+
+ return false;
+#else
+ return false;
+#endif
+}
+
+void CStorePanel::UpsellStamps( void )
+{
+#if defined( TF_CLIENT_DLL )
+ const MapDef_t *pUpsellMap = NULL;
+ float flUpsellRelativePayoff = 1000.0f;
+ int nUpsellNumHours = 0;
+
+ for ( int i = 0; i < GetItemSchema()->GetMapCount(); i++ )
+ {
+ const MapDef_t* pMap = GetItemSchema()->GetMasterMapDefByIndex( i );
+ if ( pMap->IsCommunityMap() )
+ {
+ int nDonationLevel = MapInfo_GetDonationAmount( steamapicontext->SteamUser()->GetSteamID().GetAccountID(), pMap->pszMapName );
+
+ MapStats_t &mapStats = CTFStatPanel::GetMapStats( pMap->GetStatsIdentifier() );
+ int nNumHours = ( mapStats.accumulated.m_iStat[TFMAPSTAT_PLAYTIME] ) / ( 60 /*sec*/ * 60 /*min*/ );
+
+ // No need to upsell this map if they've played it less than 2 hours
+ if ( nNumHours <= 1 )
+ continue;
+
+ int nRelativeDivisor = nNumHours / 4;
+ float flRelativePayoff = ( nRelativeDivisor == 0 ? 0.0f : static_cast< float >( nDonationLevel ) / nRelativeDivisor );
+
+ if ( flUpsellRelativePayoff > flRelativePayoff || ( flUpsellRelativePayoff == flRelativePayoff && nUpsellNumHours < nNumHours ) )
+ {
+ pUpsellMap = pMap;
+ flUpsellRelativePayoff = flRelativePayoff;
+ nUpsellNumHours = nNumHours;
+ }
+ }
+ }
+
+ Assert( pUpsellMap );
+ if ( !pUpsellMap )
+ {
+ // Should have returned false from ShouldUpsell long before we got here
+ return;
+ }
+
+ wchar_t *pwchMapName = g_pVGuiLocalize->Find( pUpsellMap->pszMapNameLocKey );
+
+ char szMapHours[ 8 ];
+ V_snprintf( szMapHours, sizeof( szMapHours ), "%i", nUpsellNumHours );
+
+ wchar_t wszMapHours[ 8 ];
+ g_pVGuiLocalize->ConvertANSIToUnicode( szMapHours, wszMapHours, sizeof( wszMapHours ) );
+
+ wchar_t wchDonationDescription[ 512 ];
+ g_pVGuiLocalize->ConstructString_safe( wchDonationDescription, g_pVGuiLocalize->Find( "#Store_ConfirmStampDonationAddText" ), 2, pwchMapName, wszMapHours );
+
+ CStampUpsellDialog *pDialog = vgui::SETUP_PANEL( new CStampUpsellDialog( "#Store_ConfirmStampDonationAddTitle",
+ wchDonationDescription, g_pVGuiLocalize->Find( "#Store_ConfirmStampDonationAddText2" ),
+ pUpsellMap->mapStampDef, "World Traveler" ) );
+
+ if ( pDialog )
+ {
+ pDialog->Show();
+ vgui::surface()->PlaySound( "ui/vote_started.wav" );
+ }
+#else
+ return;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attempts to begin a checkout.
+//-----------------------------------------------------------------------------
+void CStorePanel::InitiateCheckout( bool bSkipUpsell )
+{
+ // Check for holiday-restricted items and confirm with user before allowing checkout
+ if ( m_Cart.ContainsHolidayRestrictedItems() )
+ {
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#Store_ConfirmHolidayRestrictionCheckoutTitle", "#Store_ConfirmHolidayRestrictionCheckoutText", "#Store_OK", "#TF_Back", &ConfirmCheckout );
+ if ( pDialog )
+ {
+ pDialog->SetContext( this );
+ }
+ return;
+ }
+ else if ( ShouldShowDx8PurchaseWarning( ) )
+ {
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#Store_ConfirmDx8Summer2015OpPassTitle", "#Store_ConfirmDx8Summer2015OpPassText", "#Store_BuyAnyway", "#Store_NoThanks", &ConfirmCheckout );
+ if ( pDialog )
+ {
+ pDialog->SetContext( this );
+ }
+ return;
+ }
+ else if ( !bSkipUpsell && ShouldUpsellStamps() )
+ {
+ UpsellStamps();
+ return;
+ }
+
+ DoCheckout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+/*static*/ void CStorePanel::ConfirmCheckout( bool bConfirmed, void *pContext )
+{
+ CStorePanel *pStorePanel = ( CStorePanel * )pContext;
+ if ( bConfirmed )
+ {
+ if ( pStorePanel->ShouldUpsellStamps() )
+ {
+ pStorePanel->UpsellStamps();
+ return;
+ }
+ else
+ {
+ pStorePanel->DoCheckout();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+/*static*/ void CStorePanel::ConfirmUpsellStamps( bool bConfirmed, CSchemaItemDefHandle hItemDef, int nSecondsVisible )
+{
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+ if ( bConfirmed )
+ {
+ // Add to cart
+ CStorePage *pPage = dynamic_cast< CStorePage * >( pStorePanel->GetPropertySheet()->GetPage( 3 ) );
+
+ KeyValues *pParams = new KeyValues( "AddItemToCart" );
+ pParams->SetInt( "item_def", hItemDef->GetDefinitionIndex() );
+ pParams->SetInt( "cart_add_type", kCartItem_Purchase );
+ pStorePanel->PostMessage( pPage, pParams );
+
+ pStorePanel->PostMessage( pStorePanel, new KeyValues( "DoCheckout" ), 0.5f );
+ }
+ else
+ {
+ CheckButton *pCheckbox = static_cast< CheckButton* >( pStorePanel->FindChildByName( "SupportCommunityMapMakersCheckButton" ) );
+ if ( pCheckbox )
+ {
+ pCheckbox->SetSelected( false );
+ }
+
+ pStorePanel->DoCheckout();
+ }
+
+#if !defined(NO_STEAM)
+ KeyValues *pKVData = new KeyValues( "TF2StoreStampUpsell" );
+
+ // Create and Send the report
+ // AccountID, AcceptUpsell, CartItemsCount, CartItemsPrice, CartItemsFlags, SecondsVisible, EventTime
+
+ // ID - Auto
+
+ // AcceptUpsell
+ pKVData->SetInt( "AcceptUpsell", bConfirmed ? 1 : 0 );
+
+ // CartItemsCount (up to 100)
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ pKVData->SetInt( "CartItemsCount", pCart ? clamp( pCart->GetNumEntries(), 0, 100 ) : 0 );
+
+ // CartItemsPriceTotal
+ pKVData->SetInt( "CartItemsPrice", pCart ? pCart->GetTotalPrice() : 0 );
+
+ // CartItemsFlags (8-bits)
+ int nCartItemsFlags = 0;
+ if ( pCart )
+ {
+ for ( int i = 0; i < pCart->GetNumEntries(); ++i )
+ {
+ cart_item_t *pItem = pCart->GetItem( i );
+ if ( !pItem || !pItem->pEntry )
+ continue;
+
+ if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Weapons ) ) nCartItemsFlags |= 0x0001;
+// else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Headgear ) ) nCartItemsFlags |= 0x0002; // DEPRECATED
+// else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Misc ) ) nCartItemsFlags |= 0x0004; // DEPRECATED
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Tools ) ) nCartItemsFlags |= 0x0008;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Maps ) ) nCartItemsFlags |= 0x0010;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Bundles ) ) nCartItemsFlags |= 0x0020;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_New ) ) nCartItemsFlags |= 0x0040;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Limited ) ) nCartItemsFlags |= 0x0080;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Cosmetics ) ) nCartItemsFlags |= 0x0100;
+ else if ( pItem->pEntry->IsListedInCategory( CEconStoreCategoryManager::k_CategoryID_Taunts ) ) nCartItemsFlags |= 0x0200;
+ }
+ }
+
+ pKVData->SetInt( "CartItemsFlags", nCartItemsFlags );
+
+ // SecondsVisible (up to 2 minutes)
+ pKVData->SetInt( "SecondsVisible", clamp( nSecondsVisible, 0, 120 ) );
+
+ // EventTime
+ pKVData->SetInt( "EventTime", GetSteamWorksSGameStatsUploader().GetTimeSinceEpoch() );
+
+ // Send to DB
+ GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
+#endif // !defined(NO_STEAM)
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::DoCheckout()
+{
+ m_iCheckoutAttempts++;
+ EconUI()->Gamestats_Store( IE_STORE_CHECKOUT_ATTEMPT, NULL, NULL, 0, NULL, m_iCheckoutAttempts );
+
+ // Create the checkout job.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_Loading", true, false, true );
+ CGCClientJobInitPurchase *pJob = new CGCClientJobInitPurchase( GCClientSystem()->GetGCClient() );
+ pJob->StartJob( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void ShowPurchaseInitError( const char *pszError )
+{
+ OpenStoreStatusDialog( NULL, pszError, true, false );
+ EconUI()->Gamestats_Store( IE_STORE_CHECKOUT_FAILURE, NULL, NULL, 0, NULL, EconUI()->GetStorePanel()->GetCheckoutAttempts(), pszError );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for initiating a checkout from the Steam store.
+//-----------------------------------------------------------------------------
+bool CGCClientJobInitPurchase::BYieldingRunJob( void *pvStartParam )
+{
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseInit> msg( k_EMsgGCStorePurchaseInit );
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseInitResponse> msgResponse;
+
+ if ( !EconUI()->GetStorePanel() )
+ {
+ // This won't happen under the normal process of using the UI.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_Unavailable", true, false );
+ return true;
+ }
+
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ char uilanguage[ 64 ];
+ uilanguage[0] = 0;
+ engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
+
+ // Populate the message.
+ msg.Body().set_currency( EconUI()->GetStorePanel()->GetCurrency() );
+ msg.Body().set_country( EconUI()->GetStorePanel()->GetCountryCode() );
+ msg.Body().set_language( PchLanguageToELanguage( uilanguage ) );
+
+ // We need to ensure there are more than 0 items in the cart, but no more than MAX_CART_ITEMS.
+ int total_items = pCart->GetTotalItems();
+ if ( total_items <= 0 )
+ {
+ ShowPurchaseInitError( "#StoreCheckout_NoItems" );
+ return true;
+ }
+
+ if ( total_items > MAX_CART_ITEMS )
+ {
+ ShowPurchaseInitError( "#StoreCheckout_TooManyItems" );
+ return true;
+ }
+
+ // Can the items we want to buy actually fit inside our backpack?
+ // This check will include items that we have discovered, but haven't been revealed to the player yet.
+ // So they will sometimes get this with apparently empty slots in their backpack.
+ if ( !InventoryManager()->GetLocalInventory()->CanPurchaseItems( pCart->GetTotalConcreteItems() ) )
+ {
+ const bool bInventoryIsAtMaxSize = InventoryManager()->GetLocalInventory()->GetMaxItemCount() == MAX_NUM_BACKPACK_SLOTS;
+
+ ShowPurchaseInitError( bInventoryIsAtMaxSize ? "#StoreCheckout_NotEnoughRoom_MaxSize" : "#StoreCheckout_NotEnoughRoom" );
+ return true;
+ }
+
+ // Add the items we are requesting.
+ int totalPrice = 0;
+ for ( int i=0; i<pCart->GetNumEntries(); i++ )
+ {
+ cart_item_t *pCartItem = pCart->GetItem( i );
+
+ CGCStorePurchaseInit_LineItem *pNewLineItem = msg.Body().add_line_items();
+
+ pNewLineItem->set_item_def_id( pCartItem->pEntry->GetItemDefinitionIndex() );
+ pNewLineItem->set_quantity( pCartItem->iQuantity );
+ pNewLineItem->set_purchase_type( pCartItem->eType );
+
+ int itemPrice = pCartItem->iQuantity * pCartItem->pEntry->GetCurrentPrice( EconUI()->GetStorePanel()->GetCurrency() );
+ pNewLineItem->set_cost_in_local_currency( itemPrice );
+
+ totalPrice += itemPrice;
+ }
+ EconUI()->GetStorePanel()->SetLastPurchaseAttemptPrice( totalPrice );
+
+ // Request to init this purchase.
+ if ( !BYldSendMessageAndGetReply( msg, 30, &msgResponse, k_EMsgGCStorePurchaseInitResponse ) )
+ {
+ // No transaction has been initialized. The GC isn't responding, so abort.
+ ShowPurchaseInitError( "#StoreCheckout_Unavailable" );
+ return false;
+ }
+
+#ifdef _DEBUG
+ Msg( "CGCClientJobInitPurchase - Result: %d, TxnID: %llu\n", msgResponse.Body().result(), msgResponse.Body().txn_id());
+#endif
+
+ // If we fail at this point Steam hasn't opened a transaction that we need to worry about.
+ if ( !CStorePanel::CheckMessageResult( (EPurchaseResult)msgResponse.Body().result() ) )
+ return false;
+
+ EconUI()->GetStorePanel()->SetTransactionID( msgResponse.Body().txn_id() );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Immediately add the item corresponding to the item def passed into
+// the cart and checkout. This doesn't required opening the store front
+// so this can be called anywhere.
+//-----------------------------------------------------------------------------
+void CStorePanel::AddToCartAndCheckoutImmediately( item_definition_index_t nDefIndex )
+{
+ if ( GetPriceSheet() && GetCart() && steamapicontext && steamapicontext->SteamUser() )
+ {
+ // Add a the item to the users cart and checkout
+ GetCart()->EmptyCart();
+ AddItemToCartHelper( NULL, nDefIndex, kCartItem_Purchase );
+ DoCheckout();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Cancels a pending transaction, if possible.
+//-----------------------------------------------------------------------------
+void CStorePanel::CheckoutCancel( void )
+{
+ // The player pressed the CANCEL button on the TF2 GAME UI pop-up that appears over
+ // the store interface while they would be occupied with the overlay.
+ //
+ // If they press the CANCEL button on the overlay's authorize dialog a Steam callback
+ // (OnMicroTransactionAuthResponse) happens not this method.
+ //
+ // We don't expect this to happen! Once the transaction has been finalized we have
+ // a transaction ID and the GC and Steam are already doing the negotiations about
+ // where the money comes from and removing it so we can't back out. Allowing users
+ // to click this button results in a race condition that often results in users
+ // getting their money taken with no items because the GC can't resolve the
+ // "Canceled"/"Succeeded" discrepancy.
+ Assert( GetTransactionID() <= 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attempts to cancel a purchase in progress.
+//-----------------------------------------------------------------------------
+bool CGCClientJobCancelPurchase::BYieldingRunJob( void *pvStartParam )
+{
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseCancel> msg( k_EMsgGCStorePurchaseCancel );
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseCancelResponse> msgResponse;
+
+ if ( !EconUI()->GetStorePanel() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_Unavailable", true, false );
+ return false;
+ }
+
+ msg.Body().set_txn_id( m_ulTxnID );
+
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStorePurchaseCancelResponse ) )
+ {
+ // No response from the GC. Show a failure message.
+ OpenStoreStatusDialog( NULL, "#StoreUpdate_NoGCResponse", true, false );
+ return false;
+ }
+
+#ifdef _DEBUG
+ Msg( "CGCClientJobCancelPurchase Result: %d\n", msgResponse.Body().result() );
+#endif
+
+ // The current transaction has been canceled with the GC.
+ EconUI()->GetStorePanel()->SetTransactionID( 0 );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when the player completes or cancels an in-progress transaction.
+//-----------------------------------------------------------------------------
+void CStorePanel::OnMicroTransactionAuthResponse( MicroTxnAuthorizationResponse_t *pMicroTxnAuthResponse )
+{
+ Assert( steamapicontext->SteamUserStats() );
+ if ( !steamapicontext->SteamUserStats() )
+ return;
+
+ if ( !pMicroTxnAuthResponse->m_bAuthorized )
+ {
+ const char* pszError = "#StoreCheckout_TransactionCanceled";
+ EconUI()->Gamestats_Store( IE_STORE_CHECKOUT_FAILURE, NULL, NULL, 0, NULL, EconUI()->GetStorePanel()->m_iCheckoutAttempts, pszError );
+ OpenStoreStatusDialog( NULL, pszError, true, false );
+ CGCClientJobCancelPurchase *pJob = new CGCClientJobCancelPurchase( GCClientSystem()->GetGCClient(), GetTransactionID() );
+ pJob->StartJob( NULL );
+ SetTransactionID( 0 );
+ }
+ else
+ {
+ // Replace the existing dialog with one that says "we're finalizing!" and only has an "OK" button.
+ // We let users close this if they want, which can be useful in exceptional circumstances like the
+ // GC crashing, but we don't have them do any work on the GC side -- once the finalization is in
+ // flight we can't take it back.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_TransactionFinalizing", true, false, false );
+
+ // Finalize the transaction with the GC.
+ m_bShouldFinalize = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Look to see if we should finalize the open transaction.
+//-----------------------------------------------------------------------------
+void CStorePanel::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ if ( m_bShouldFinalize )
+ {
+ m_bShouldFinalize = false;
+ FinalizeTransaction();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePanel::FinalizeTransaction( void )
+{
+ // Tell the GC to release the items.
+ CGCClientJobFinalizePurchase *pJob = new CGCClientJobFinalizePurchase( GCClientSystem()->GetGCClient(), GetTransactionID() );
+ pJob->StartJob( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell the GC that the purchase is finalized and we should create the items the player bought.
+// TODO: If something goes wrong here we need to inform the player, but tell them
+// that they MAY have been charged. If STEAM authorized & completed the transaction,
+// but we weren't able to finalize with the GC we won't get our items right away
+// but the player will have been charged.
+//-----------------------------------------------------------------------------
+bool CGCClientJobFinalizePurchase::BYieldingRunJob( void *pvStartParam )
+{
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseFinalize> msg( k_EMsgGCStorePurchaseFinalize );
+ GCSDK::CProtoBufMsg<CMsgGCStorePurchaseFinalizeResponse> msgResponse;
+
+ msg.Body().set_txn_id( m_ulTxnID );
+
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStorePurchaseFinalizeResponse ) )
+ {
+ // TODO: This is bad! The store might have taken our money, but we weren't able to finalize. How do we handle this?
+ // The message currently says "Unable to confirm success. If successful, your items will be delivered at a later date."
+ // We need to handle this case more gracefully.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_CompleteButUnfinalized", true, false );
+ // @note Tom Bui & Joe Ludwig: We empty the cart here, just to make sure people don't hit checkout again
+ // and end up with dupes of all their items
+ if ( EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->GetCart() )
+ {
+ EconUI()->GetStorePanel()->GetCart()->EmptyCart();
+ }
+ return false;
+ }
+
+ // Check the message result for errors and handle them.
+ if ( !CStorePanel::CheckMessageResult( (EPurchaseResult)msgResponse.Body().result() ) )
+ return false;
+
+ EconUI()->Gamestats_Store( IE_STORE_CHECKOUT_SUCCESS, NULL, NULL, 0, NULL, EconUI()->GetStorePanel()->GetCheckoutAttempts(), NULL, EconUI()->GetStorePanel()->GetLastPurchaseAttemptPrice(), EconUI()->GetStorePanel()->GetCurrency()+1 );
+ CStoreCart* cart = EconUI()->GetStorePanel()->GetCart();
+ for ( int i=0; i<cart->GetNumEntries(); ++i )
+ {
+ cart_item_t* item = cart->GetItem( i );
+ if ( !item )
+ continue;
+ EconUI()->Gamestats_Store( IE_STORE_CHECKOUT_ITEM, NULL, NULL, 0, item, EconUI()->GetStorePanel()->GetCheckoutAttempts(), NULL, 0, EconUI()->GetStorePanel()->GetCurrency()+1 );
+ }
+
+#ifdef DEBUG
+ Msg( "CGCClientJobFinalizePurchase Result: %d, Num Items: %i, Purchased Items:\n", msgResponse.Body().result(), msgResponse.Body().item_ids_size() );
+ if ( k_EPurchaseResultOK == msgResponse.Body().result() )
+ {
+ for ( int i = 0; i < msgResponse.Body().item_ids_size(); i++ )
+ {
+ Msg( "\t%llu\n", msgResponse.Body().item_ids(i) );
+ }
+ }
+#endif
+
+ EconUI()->GetStorePanel()->SetMostRecentSuccessfulTransactionID( m_ulTxnID );
+
+ // Transaction complete.
+ EconUI()->GetStorePanel()->SetTransactionID( 0 );
+
+ // Clear the cart.
+ EconUI()->GetStorePanel()->GetCart()->EmptyCart();
+
+ // If we were in the cart view, return to the store page
+ CStoreViewCartPanel *pCartPanel = GetStoreViewCartPanel();
+ if ( pCartPanel && pCartPanel->IsVisible() )
+ {
+ pCartPanel->ShowPanel( false );
+ }
+
+ // Let them know everything went well.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_TransactionCompleted", true, true );
+
+ EconUI()->GetStorePanel()->PostTransactionCompleted();
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle an error from the GC.
+//-----------------------------------------------------------------------------
+bool CStorePanel::CheckMessageResult( EPurchaseResult msgResult )
+{
+ // Take action on the result code.
+ switch ( msgResult )
+ {
+ case k_EPurchaseResultOK:
+ return true;
+ break;
+
+ // GC / Steam errors: // These can all be combined into a single general unrecoverable error case.
+ case k_EPurchaseResultFail: // Generic error.
+ case k_EPurchaseResultInvalidParam: // Invalid parameter.
+ if ( CStorePanel::ShouldShowWarnings() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_Unavailable", true, false );
+ }
+ break;
+
+ // Internal error. Default string in English: "Unable to confirm success. If successful, your items will be
+ // delivered at a later date." Basically "something went real bad wrong and we don't know whether you'll get
+ // your items or not".
+ case k_EPurchaseResultInternalError:
+ if ( CStorePanel::ShouldShowWarnings() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_CompleteButUnfinalized", true, false );
+ }
+ break;
+
+ // Is the GC telling us this user does not have enough backpack space? This check takes into account
+ // any additional backpack space a user might get from upgrading to premium.
+ case k_EPurchaseResultNotEnoughBackpackSpace:
+ if ( CStorePanel::ShouldShowWarnings() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_NotEnoughRoom", true, false );
+ }
+ break;
+
+ case k_EPurchaseResultLimitedQuantityItemsUnavailable:
+ if ( CStorePanel::ShouldShowWarnings() )
+ {
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_LimitedQuantityItemsUnavailable", true, false );
+ }
+ break;
+
+ // errors that should never happen
+ case k_EPurchaseResultNotApproved: // Tried to finalize a transaction that has not yet been approved.
+ case k_EPurchaseResultAlreadyCommitted: // Tried to finalize a transaction that has already been committed.
+ case k_EPurchaseResultWrongCurrency: // Microtransaction's currency does not match user's wallet currency.
+ case k_EPurchaseResultAccountError: // User's account does not exist or is temporarily unavailable.
+ case k_EPurchaseResultTxnNotFound: // Could not find the transaction specified
+ default:
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_InternalError", true, false );
+ break;
+
+ case k_EMicroTxnResultFailedFraudChecks: // Steam thinks this transaction might be fraudulent
+ ShowConfirmDialog( "#StoreCheckout_ContactSupport_Dialog_Title", "#StoreCheckout_ContactSupport", "#StoreCheckout_ContactSupport_Dialog_Btn", "#Cancel", &ContactSupportConfirm );
+ break;
+
+ // User errors:
+ case k_EPurchaseResultUserNotLoggedIn: // User is not logged into Steam.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_NotLoggedIn", true, false );
+ break;
+ case k_EPurchaseResultInsufficientFunds: // User does not have wallet funds
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_InsufficientFunds", true, false );
+ // This will happen if the user spends the money out of his wallet between funding & finalizing the transaction.
+ break;
+ case k_EPurchaseResultTimedOut: // Time limit for finalization has been exceeded
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_TimedOut", true, false );
+ // TODO: We should find out what happened to the transaction, since it still may be viable.
+ break;
+ case k_EPurchaseResultAcctDisabled: // Steam account is disabled.
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_SteamAccountDisabled", true, false );
+ break;
+ case k_EPurchaseResultAcctCannotPurchase: // Steam account is not allowed to make a purchase
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_SteamAccountNoPurchase", true, false );
+ break;
+
+ // Client state discrepancies:
+ case k_EPurchaseResultOldPriceSheet: // Information on the purchase didn't match the current price sheet
+ OpenStoreStatusDialog( NULL, "#StoreCheckout_OldPriceSheet", false, false );
+
+ // Request a new price sheet. This will clear the store pages and the user's cart.
+ CStorePanel::RequestPricesheet();
+ break;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shows the store panel.
+//-----------------------------------------------------------------------------
+void CStorePanel::ShowStorePanel( void )
+{
+ GetPropertySheet()->SetVisible( true );
+ m_bShowWarnings = true;
+
+ InvalidateLayout( false, true );
+ Activate();
+
+ if ( !m_bPricesheetLoaded )
+ return;
+
+ bool bStartOnHomePage = true;
+
+ if ( m_bAddStartItemDefToCart )
+ {
+ // Put the specified item in the cart, and go to the cart display screen.
+ GetCart()->EmptyCart();
+
+ CStorePage *pPage = dynamic_cast< CStorePage * >( GetPropertySheet()->GetActivePage() );
+ AddItemToCartHelper( pPage ? pPage->GetPageName() : NULL, m_bAddStartItemDefToCart, kCartItem_Purchase );
+
+ if ( pPage )
+ {
+ pPage->UpdateCart();
+ }
+ }
+ else
+ {
+ if ( m_iStartItemDef == STOREPANEL_SHOW_UPGRADESTEPS )
+ {
+ OpenStoreStatusDialog( NULL, "#TF_Trial_StoreUpgradeExplanation", true, false );
+ }
+ else if ( m_iStartItemDef )
+ {
+ const econ_store_entry_t *pEntry = FindEntryForItemDef( m_iStartItemDef );
+ if ( pEntry )
+ {
+ // !KLUDGE! Post this to a message queue to be handled later, because there
+ // have already been messages posted to scroll to other pages.
+ // VGUI really has a pretty systematic problem of posting WAY too much stuff
+ // to a queue, instead of dispatching it immediately
+ ivgui()->PostMessage( GetVPanel(), new KeyValues("JumpToItem", "ItemDefIndex", m_iStartItemDef), GetVPanel() );
+ //FindAndSelectEntry( pEntry );
+ bStartOnHomePage = false;
+ }
+ }
+
+ CExButton *pCloseButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") );
+ if ( pCloseButton )
+ {
+ pCloseButton->RequestFocus();
+ }
+ }
+ m_iStartItemDef = 0;
+
+ if ( bStartOnHomePage )
+ {
+ vgui::Panel *pPage = GetPropertySheet()->GetPage(0);
+ if ( pPage )
+ {
+ if ( GetPropertySheet()->GetActivePage() != pPage )
+ {
+ GetPropertySheet()->SetActivePage( pPage );
+ }
+ else
+ {
+ // VGUI doesn't tell the starting active page that it's active, so we post a pageshow to it
+ ivgui()->PostMessage( pPage->GetVPanel(), new KeyValues("PageShow"), GetPropertySheet()->GetVPanel() );
+ }
+ }
+ }
+
+ vgui::Panel *pPage = GetPropertySheet()->GetActivePage();
+ if ( pPage )
+ {
+ pPage->SetVisible( true );
+ }
+
+ // 6/13/2011
+ // Note: We no longer check for new items when the player enters
+ // the store. This is confusing and no longer necessary now that
+ // the notification system lets us push item discoveries to the player.
+ // InventoryManager()->ShowItemsPickedUp( true, false );
+}
+
+void CStorePanel::OnJumpToItem( KeyValues *pParams )
+{
+ const econ_store_entry_t *pEntry = FindEntryForItemDef( pParams->GetInt( "ItemDefIndex", -1 ) );
+ Assert( pEntry );
+ if ( pEntry )
+ {
+ FindAndSelectEntry( pEntry );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Open_Store( const CCommand &args )
+{
+ int iItemDef = ( args.ArgC() > 1 ) ? atoi(args[1]) : 0;
+ bool bToFeatured = ( ( ( args.ArgC() > 2 ) ? atoi(args[2]) : 0 ) != 0 );
+
+ EconUI()->OpenStorePanel( iItemDef, bToFeatured );
+}
+ConCommand open_store( "open_store", Open_Store, "Open the in-game store", FCVAR_NONE );
+
+//==================================================================================================
+// CART
+//==================================================================================================
+CStoreCart::CStoreCart( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreCart::AddToCart( const econ_store_entry_t *pEntry, const char* pszPageName, ECartItemType eCartItemType )
+{
+ // Steam doesn't allow purchases with more than 256 items in a single transaction
+ if ( GetTotalItems() >= 255 )
+ return;
+
+ if ( pEntry->m_bIsMarketItem )
+ {
+ CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( pEntry->GetItemDefinitionIndex() );
+ Assert( pItemDef );
+ if ( !pItemDef )
+ return;
+
+ if ( !CBaseAdPanel::CheckForRequiredSteamComponents( "#StoreUpdate_SteamRequired", "#MMenu_OverlayRequired" ) )
+ return;
+
+ if ( pItemDef && steamapicontext && steamapicontext->SteamFriends() )
+ {
+ const char *pszPrefix = "";
+ if ( GetUniverse() == k_EUniverseBeta )
+ {
+ pszPrefix = "beta.";
+ }
+
+ static char pszItemName[256];
+ g_pVGuiLocalize->ConvertUnicodeToANSI( g_pVGuiLocalize->Find( pItemDef->GetItemBaseName() ), pszItemName, sizeof( pszItemName ) );
+
+ char szURL[512];
+ V_snprintf( szURL, sizeof( szURL ), "http://%ssteamcommunity.com/market/listings/%d/%s", pszPrefix, engine->GetAppID(), pszItemName );
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( szURL );
+ }
+ return;
+ }
+
+ int iIndex = GetIndexForEntry( pEntry, eCartItemType );
+ if ( iIndex == m_Items.InvalidIndex() )
+ {
+ iIndex = m_Items.AddToTail();
+ m_Items[iIndex].pEntry = pEntry;
+ m_Items[iIndex].iQuantity = 0;
+ m_Items[iIndex].eType = eCartItemType;
+ }
+
+ m_Items[iIndex].iQuantity++;
+
+ EconUI()->Gamestats_Store( IE_STORE_ITEM_ADDED_TO_CART, NULL, pszPageName, 0, &m_Items[iIndex] );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "cart_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+
+ vgui::surface()->PlaySound( "ui/item_store_add_to_cart.wav" );
+
+ // find the cart button associated with the active page we are using
+ CExButton *pCartButton = dynamic_cast< CExButton * >( EconUI()->GetStorePanel()->GetActivePage()->FindChildByName( "CartButton", true ) );
+ if ( pCartButton )
+ {
+ g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pCartButton->GetParent(), "AddToCartBlink" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreCart::RemoveFromCart( int iIndex )
+{
+ if ( iIndex >= 0 && iIndex < m_Items.Count() )
+ {
+ // play item's "drop" sound
+ if ( m_Items[iIndex].pEntry )
+ {
+ CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( m_Items[iIndex].pEntry->GetItemDefinitionIndex() );
+ const char *soundFilename = pDef->GetDefinitionString( "drop_sound", "ui/item_default_drop.wav" );
+
+ vgui::surface()->PlaySound( soundFilename );
+ }
+
+ m_Items[iIndex].iQuantity--;
+
+ EconUI()->Gamestats_Store( IE_STORE_ITEM_REMOVED_FROM_CART, NULL, NULL, 0, &m_Items[iIndex] );
+
+ if ( m_Items[iIndex].iQuantity <= 0 )
+ {
+ m_Items.Remove(iIndex);
+ }
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "cart_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreCart::EmptyCart( void )
+{
+ m_Items.Purge();
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "cart_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CStoreCart::GetIndexForEntry( const econ_store_entry_t *pEntry, ECartItemType eCartItemType ) const
+{
+ FOR_EACH_VEC( m_Items, i )
+ {
+ if ( m_Items[i].pEntry == pEntry && m_Items[i].eType == eCartItemType )
+ return i;
+ }
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CStoreCart::GetTotalItems( void ) const
+{
+ int iTotal = 0;
+ FOR_EACH_VEC( m_Items, i )
+ {
+ const cart_item_t &item = m_Items[i];
+ iTotal += item.iQuantity;
+ }
+ return iTotal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CStoreCart::GetTotalConcreteItems( void ) const
+{
+ int iTotal = 0;
+ FOR_EACH_VEC( m_Items, i )
+ {
+ const cart_item_t &item = m_Items[i];
+#ifdef TF_CLIENT_DLL
+ CEconItemDefinition *pDef = ItemSystem()->GetStaticDataForItemByDefIndex( item.pEntry->GetItemDefinitionIndex() );
+ iTotal += pDef->GetNumConcreteItems() * item.iQuantity;
+#else
+ // Kyle says: this logic is totally wrong but we don't *have* a CStrike
+ // economy so I don't feel bad using this to get the build working.
+ iTotal += item.iQuantity;
+#endif
+ }
+ return iTotal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+item_price_t cart_item_t::GetDisplayPrice() const
+{
+ const float fPriceScale = eType == kCartItem_TryOutUpgrade
+ ? GetEconPriceSheet()->GetPreviewPeriodDiscount()
+ : IsRentalCartItemType( eType )
+ ? pEntry->GetRentalPriceScale()
+ : 1.0f;
+
+ return (item_price_t)( pEntry->GetCurrentPrice( EconUI()->GetStorePanel()->GetCurrency() ) * fPriceScale ) * iQuantity;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+item_price_t CStoreCart::GetTotalPrice( void ) const
+{
+ item_price_t unTotal = 0;
+ FOR_EACH_VEC( m_Items, i )
+ {
+ unTotal += m_Items[i].GetDisplayPrice();
+ }
+ return unTotal;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CStoreCart::ContainsHolidayRestrictedItems() const
+{
+ FOR_EACH_VEC( m_Items, i )
+ {
+ CEconItemDefinition *pItemDef = ItemSystem()->GetStaticDataForItemByDefIndex( m_Items[i].pEntry->GetItemDefinitionIndex() );
+ if ( !pItemDef )
+ continue;
+
+ // If the string isn't empty, assume it has a restriction
+ if ( pItemDef->GetHolidayRestriction() )
+ return true;
+
+ const bundleinfo_t *pBundle = pItemDef->GetBundleInfo();
+ if ( pBundle )
+ {
+ FOR_EACH_VEC( pBundle->vecItemDefs, j )
+ {
+ if ( pBundle->vecItemDefs[j]->GetHolidayRestriction() )
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CStoreCart::ContainsItemDefinition( item_definition_index_t unItemDef ) const
+{
+ FOR_EACH_VEC( m_Items, i )
+ {
+ if ( m_Items[i].pEntry->GetItemDefinitionIndex() == unItemDef )
+ return true;
+ }
+
+ return false;
+}
+
+//================================================================================================================================
+// STORE STATUS DIALOG
+//================================================================================================================================
+static vgui::DHANDLE<CStoreStatusDialog> g_StoreStatusPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStoreStatusDialog::CStoreStatusDialog( vgui::Panel *pParent, const char *pElementName ) : BaseClass( pParent, "StoreStatusDialog" )
+{
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+ m_bNotifyOnCancel = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreStatusDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/UI/econ/store/v1/StoreStatusDialog.res" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreStatusDialog::OnCommand( const char *command )
+{
+ bool bClose = false;
+
+ if ( !Q_stricmp( command, "close" ) )
+ {
+ bClose = true;
+
+ if ( m_bShowOnExit )
+ {
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+ }
+ else if ( !Q_stricmp( command, "forceclose" ) )
+ {
+ bClose = true;
+ }
+
+ if ( bClose )
+ {
+ if ( m_bNotifyOnCancel )
+ {
+ EconUI()->GetStorePanel()->CheckoutCancel();
+ m_bNotifyOnCancel = false;
+ }
+
+ m_bShowOnExit = false;
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreStatusDialog::UpdateSchemeForVersion()
+{
+ InvalidateLayout( false, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreStatusDialog::ShowStatusUpdate( bool bAllowClose, bool bShowOnExit, bool bCancel )
+{
+ CExButton *pButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") );
+ if ( pButton )
+ {
+ pButton->SetVisible( bAllowClose );
+ pButton->SetEnabled( bAllowClose );
+ if ( bCancel )
+ {
+ pButton->SetText( "#Store_CANCEL" );
+ m_bNotifyOnCancel = true;
+ }
+ else
+ {
+ pButton->SetText( "#Store_OK" );
+ m_bNotifyOnCancel = false;
+ }
+ }
+
+ m_bShowOnExit = bShowOnExit;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void SetupStoreStatusDialog( vgui::Panel *pParent )
+{
+ // If we parent it to something, we get problems when another panel pops over it,
+ // because the modal dialog is no longer visible. So prevent parenting status dialogs.
+ pParent = NULL;
+
+ if ( !g_StoreStatusPanel.Get() )
+ {
+ g_StoreStatusPanel = vgui::SETUP_PANEL( new CStoreStatusDialog( pParent, NULL ) );
+ }
+ g_StoreStatusPanel->SetVisible( true );
+ if ( !pParent )
+ {
+ g_StoreStatusPanel->MakePopup();
+ }
+
+ g_StoreStatusPanel->MoveToFront();
+ g_StoreStatusPanel->SetKeyBoardInputEnabled(true);
+ g_StoreStatusPanel->SetMouseInputEnabled(true);
+ TFModalStack()->PushModal( g_StoreStatusPanel );
+}
+
+void OpenStoreStatusDialog( vgui::Panel *pParent, const char *pszText, bool bAllowClose, bool bShowOnExit, bool bCancel )
+{
+ // Figure out who we should be parented to
+ if ( !pParent )
+ {
+ CStoreViewCartPanel *pCartPanel = GetStoreViewCartPanel();
+ if ( pCartPanel && pCartPanel->IsVisible() )
+ {
+ pParent = pCartPanel;
+ }
+ else if ( EconUI()->GetStorePanel() && EconUI()->GetStorePanel()->IsVisible() )
+ {
+ pParent = EconUI()->GetStorePanel();
+ }
+ }
+
+ SetupStoreStatusDialog( pParent );
+ g_StoreStatusPanel->UpdateSchemeForVersion();
+ g_StoreStatusPanel->SetDialogVariable( "updatetext", g_pVGuiLocalize->Find( pszText ) );
+ g_StoreStatusPanel->ShowStatusUpdate( bAllowClose, bShowOnExit, bCancel );
+}
+
+void CloseStoreStatusDialog( void )
+{
+ if ( g_StoreStatusPanel )
+ {
+ g_StoreStatusPanel->OnCommand( "forceclose" );
+ }
+}
+
+#ifdef _DEBUG
+CON_COMMAND( re_add_store_page, "" )
+{
+ CStorePanel *pStorePanel = EconUI()->GetStorePanel();
+ if ( !pStorePanel )
+ return;
+
+ if ( args.ArgC() != 2 )
+ {
+ Warning( "Need a page index argument.\n" );
+ return;
+ }
+
+ pStorePanel->ReAddPage( atoi( args[ 1 ] ) );
+}
+#endif
+
+// these are disabled because they don't work anymore.
+#ifdef ENABLE_TEST_PURCHASE_COMMANDS
+
+//================================================================================================================================
+//================================================================================================================================
+//================================================================================================================================
+// TEST JOBS
+//================================================================================================================================
+//================================================================================================================================
+//================================================================================================================================
+
+
+
+
+CON_COMMAND( store_getuserdata, "Gets the latest pricesheet from the GC" )
+{
+ RTime32 rTimeVersion = 0;
+ if ( args.ArgC() > 1 )
+ {
+ rTimeVersion = V_atoi( args[1] );
+ }
+
+ CGCClientJobTESTGetUserData *pJob = new CGCClientJobTESTGetUserData( GCClientSystem()->GetGCClient(), rTimeVersion );
+ pJob->StartJob( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dev-only command and job for initiating a purchase
+//-----------------------------------------------------------------------------
+class CGCClientJobTESTInitPurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobTESTInitPurchase( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam )
+ {
+ GCSDK::CGCMsg<MsgGCStorePurchaseInit_t> msg( k_EMsgGCStorePurchaseInit );
+ GCSDK::CGCMsg<MsgGCStorePurchaseInitResponse_t> msgResponse;
+
+ CStorePanel *pStore = GetStorePanel();
+ if ( !pStore )
+ {
+ Msg( "store_initpurchase: Store has not been initialized\n" );
+ return false;
+ }
+
+ CStoreCart *pCart = pStore->GetCart();
+
+ msg.Body().m_eCurrency = pStore->GetCurrency();
+ V_strncpy( msg.Body().m_rgchCountry, pStore->GetCountryCode(), sizeof( msg.Body().m_rgchCountry ) );
+ msg.Body().m_eLanguage = steamapicontext->SteamApps() ? PchLanguageToELanguage( steamapicontext->SteamApps()->GetCurrentGameLanguage() ) : k_Lang_English;
+ msg.Body().m_cLineItems = pCart->GetNumEntries();
+
+ // We really should check for zero items here and not let the purchase go through.
+ // Also, GetTotalItems() needs to be < 256
+
+ for ( int i = 0; i < pCart->GetNumEntries(); i++ )
+ {
+ cart_item_t *pCartItem = pCart->GetItem( i );
+ msg.AddUint16Data( pCartItem->pEntry->m_usDefIndex );
+ msg.AddUint8Data( pCartItem->iQuantity );
+ msg.AddUint16Data( pCartItem->iQuantity * pCartItem->pEntry->GetPrice( (ECurrency)msg.Body().m_eCurrency ) );
+ }
+
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStorePurchaseInitResponse ) )
+ {
+ Msg( "store_initpurchase: No response from the GC\n" );
+ return false;
+ }
+
+ Msg( "Got response. Result: %d, TxnID: %llu\n", msgResponse.Body().m_eResult, msgResponse.Body().m_unTxnID );
+
+ return true;
+ }
+};
+
+CON_COMMAND( store_initpurchase, "Simulates pressing the checkout button in the store" )
+{
+ CGCClientJobTESTInitPurchase *pJob = new CGCClientJobTESTInitPurchase( GCClientSystem()->GetGCClient() );
+ pJob->StartJob( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dev-only command and job for canceling a purchase
+//-----------------------------------------------------------------------------
+class CGCClientJobTESTCancelPurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobTESTCancelPurchase( GCSDK::CGCClient *pGCClient, uint64 ulTxnID ) : GCSDK::CGCClientJob( pGCClient ), m_ulTxnID( ulTxnID ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam )
+ {
+ GCSDK::CGCMsg<MsgGCStorePurchaseCancel_t> msg( k_EMsgGCStorePurchaseCancel );
+ GCSDK::CGCMsg<MsgGCStorePurchaseCancelResponse_t> msgResponse;
+
+ msg.Body().m_ulTxnID = m_ulTxnID;
+
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStorePurchaseCancelResponse ) )
+ {
+ Msg( "store_cancelpurchase: No response from the GC\n" );
+ return false;
+ }
+
+ Msg( "Got response. Result: %d\n", msgResponse.Body().m_eResult );
+ return true;
+ }
+
+private:
+ uint64 m_ulTxnID;
+};
+
+CON_COMMAND( store_cancelpurchase, "<TxnID> Simulates cancelling a purchase" )
+{
+
+ if ( args.ArgC() < 2 )
+ {
+ Msg( store_cancelpurchase_command.GetHelpText() );
+ }
+
+ uint64 ulTxnID;
+ ulTxnID = V_atoui64( args[1] );
+
+ CGCClientJobTESTCancelPurchase *pJob = new CGCClientJobTESTCancelPurchase( GCClientSystem()->GetGCClient(), ulTxnID );
+ pJob->StartJob( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dev-only command and job for finalizing a purchase
+//-----------------------------------------------------------------------------
+class CGCClientJobTESTFinalizePurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobTESTFinalizePurchase( GCSDK::CGCClient *pGCClient, uint64 ulTxnID ) : GCSDK::CGCClientJob( pGCClient ), m_ulTxnID( ulTxnID ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam )
+ {
+ GCSDK::CGCMsg<MsgGCStorePurchaseFinalize_t> msg( k_EMsgGCStorePurchaseFinalize );
+ GCSDK::CGCMsg<MsgGCStorePurchaseFinalizeResponse_t> msgResponse;
+
+ msg.Body().m_ulTxnID = m_ulTxnID;
+
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgResponse, k_EMsgGCStorePurchaseFinalizeResponse ) )
+ {
+ Msg( "store_finalizepurchase: No response from the GC\n" );
+ return false;
+ }
+
+ Msg( "Got response. Result: %d, Num Items: %d, Purchased Items:\n", msgResponse.Body().m_eResult, msgResponse.Body().m_cItemIDs );
+ if ( k_EPurchaseResultOK == msgResponse.Body().m_eResult )
+ {
+ for ( uint32 i = 0; i < msgResponse.Body().m_cItemIDs; i++ )
+ {
+ uint64 ulItemID;
+ if ( !msgResponse.BReadUint64Data( &ulItemID ) )
+ {
+ Msg( "Error: Underflow in msgResponse\n" );
+ break;
+ }
+
+ Msg( "\t%llu\n", ulItemID );
+ }
+ }
+
+ return true;
+ }
+
+private:
+ uint64 m_ulTxnID;
+};
+
+CON_COMMAND( store_finalizepurchase, "<TxnID> Simulates finalizing a purchase" )
+{
+
+ if ( args.ArgC() < 2 )
+ {
+ Msg( store_finalizepurchase_command.GetHelpText() );
+ }
+
+ uint64 ulTxnID;
+ ulTxnID = V_atoui64( args[1] );
+
+ CGCClientJobTESTFinalizePurchase *pJob = new CGCClientJobTESTFinalizePurchase( GCClientSystem()->GetGCClient(), ulTxnID );
+ pJob->StartJob( NULL );
+}
+
+#endif
diff --git a/game/client/econ/store/store_panel.h b/game/client/econ/store/store_panel.h
new file mode 100644
index 0000000..8ac0f0c
--- /dev/null
+++ b/game/client/econ/store/store_panel.h
@@ -0,0 +1,236 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PANEL_H
+#define STORE_PANEL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/PropertyDialog.h"
+#include "econ_ui.h"
+#include "GameEventListener.h"
+#include "store_page.h"
+#include "econ_store.h"
+#include "econ_gcmessages.h"
+#include "steam/isteamuser.h"
+
+#define MAX_CART_ITEMS 256
+
+#define STOREPANEL_SHOW_UPGRADESTEPS -1
+
+class CStorePage;
+
+// An "item" in the cart.
+struct cart_item_t
+{
+ const econ_store_entry_t *pEntry;
+ int iQuantity;
+ ECartItemType eType;
+
+ item_price_t GetDisplayPrice() const;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: The cart that contains items the player is purchasing
+//-----------------------------------------------------------------------------
+class CStoreCart
+{
+public:
+ CStoreCart( void );
+
+ void AddToCart( const econ_store_entry_t *pEntry, const char* pszPageName, ECartItemType eCartItemType );
+ void RemoveFromCart( int iEntryIndex );
+ void EmptyCart( void );
+
+ // Returns the total number of items in the cart
+ int GetTotalItems( void ) const;
+ int GetTotalConcreteItems( void ) const;
+ // Returns the number of different entries in the cart (ignoring quantities)
+ int GetNumEntries( void ) const { return m_Items.Count(); }
+ cart_item_t *GetItem( int iIndex ) { return ( ( GetNumEntries() > 0 ) ? &m_Items[iIndex] : NULL ); }
+
+ item_price_t GetTotalPrice( void ) const;
+
+ bool ContainsHolidayRestrictedItems() const;
+ bool ContainsItemDefinition( item_definition_index_t unItemDef ) const;
+
+private:
+ int GetIndexForEntry( const econ_store_entry_t *pEntry, ECartItemType eCartItemType ) const;
+
+private:
+ CUtlVector<cart_item_t> m_Items;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePanel : public vgui::PropertyDialog, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CStorePanel, vgui::PropertyDialog );
+public:
+ CStorePanel( Panel *parent );
+ virtual ~CStorePanel();
+
+#ifdef _DEBUG
+ void ReAddPage( int iPage );
+#endif
+
+ // UI Layout
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void ShowPanel( bool bShow );
+ virtual void OnKeyCodeTyped(vgui::KeyCode code);
+ virtual void FireGameEvent( IGameEvent *event );
+ void SetPreventClosure( bool bPrevent ) { m_bPreventClosure = bPrevent; }
+ void StartAtItemDef( int iItemDef, bool bAddToCart ) { m_iStartItemDef = iItemDef; m_bAddStartItemDefToCart = bAddToCart; };
+
+ virtual void OnTick();
+
+ // Steam Interaction
+ STEAM_CALLBACK( CStorePanel, OnMicroTransactionAuthResponse, MicroTxnAuthorizationResponse_t, m_CallbackMicroTransactionAuthResponse );
+
+ // GC Management
+ static bool CheckMessageResult( EPurchaseResult msgResult );
+ void FinalizeTransaction( void );
+ virtual void PostTransactionCompleted( void ) { return; }
+
+ // Cart Management
+ CStoreCart *GetCart( void ) { return &m_Cart; }
+ void ShowStorePanel( void );
+ bool ShouldUpsellStamps( void );
+ bool HasValidUpsellStamps( void );
+ void UpsellStamps( void );
+ static void ConfirmUpsellStamps( bool bConfirmed, CSchemaItemDefHandle hItemDef, int nSecondsVisible );
+ void InitiateCheckout( bool bSkipUpsell );
+ void CheckoutCancel( void );
+ virtual void OnAddToCart( void ) {}
+ void AddToCartAndCheckoutImmediately( item_definition_index_t nDefIndex );
+
+ // Pricesheet Management
+ static bool IsPricesheetLoaded( void ) { return CStorePanel::m_bPricesheetLoaded; }
+ static bool ShouldShowWarnings( void ) { return CStorePanel::m_bShowWarnings; }
+ static void SetShouldShowWarnings( bool bShow ) { CStorePanel::m_bShowWarnings = bShow; }
+ static void RequestPricesheet( void );
+
+ const CEconStorePriceSheet *GetPriceSheet( void ) { return &m_StoreSheet; }
+ CEconStorePriceSheet *GetPriceSheetForEdit( void ) { return &m_StoreSheet; }
+ bool LoadPricesheet( KeyValuesAD* pKVPricesheet );
+ void SetCurrency( ECurrency in_currency );
+ ECurrency GetCurrency( void ) { return m_eCurrency; }
+ void SetCountryCode( const char* in_country );
+ char* GetCountryCode( void ) { return m_rgchCountry; }
+ const econ_store_entry_t *GetFeaturedEntry( void );
+ void SetMostRecentSuccessfulTransactionID( uint64 inID ) { m_unMostRecentSuccessfulTransaction = inID; }
+ uint64 GetMostRecentSuccessfulTransactionID() const { return m_unMostRecentSuccessfulTransaction; }
+ virtual void SetTransactionID( uint64 inID ) { m_unTransactionID = inID; }
+ uint64 GetTransactionID( void ) { return m_unTransactionID; }
+
+ int GetCheckoutAttempts() { return m_iCheckoutAttempts; }
+ void SetLastPurchaseAttemptPrice( int totalPrice ) { m_iLastPurchaseAttemptPrice = totalPrice; }
+ int GetLastPurchaseAttemptPrice() { return m_iLastPurchaseAttemptPrice; }
+
+ void ClearPopularItems( void ) { m_vPopularItems.Purge(); }
+ void AddPopularItem( uint32 iItemDef ) { m_vPopularItems.AddToTail(iItemDef); }
+ const CUtlVector<uint32>& GetPopularItems( void ) const { return m_vPopularItems; }
+
+ MESSAGE_FUNC( OnStartShopping, "StartShopping" );
+ MESSAGE_FUNC( OnFindAndSelectFeaturedItem, "FindAndSelectFeaturedItem" );
+ MESSAGE_FUNC_PARAMS( OnItemLinkClicked, "URLClicked", pParams );
+ MESSAGE_FUNC_PARAMS( OnJumpToItem, "JumpToItem", pParams );
+ MESSAGE_FUNC( DoCheckout, "DoCheckout" );
+
+protected:
+ void ParseStoreKV( void );
+ CStorePage *AddPageFromPriceSheet( int iPage );
+
+ void FindAndSelectEntry( const econ_store_entry_t *pEntry );
+ const econ_store_entry_t *FindEntryForItemDef( int iItemDef ) { return m_StoreSheet.GetEntry( iItemDef ); }
+
+ virtual CStorePage *CreateStorePage( const CEconStoreCategoryManager::StoreCategory_t *pPageData );
+
+ bool ShouldShowDx8PurchaseWarning( ) const;
+
+protected:
+ static void ConfirmCheckout( bool bConfirmed, void *pContext );
+
+ static bool m_bPricesheetLoaded;
+ static bool m_bShowWarnings;
+ bool m_bPreventClosure;
+ int m_iStartItemDef;
+ bool m_bAddStartItemDefToCart;
+ CStoreCart m_Cart;
+ CEconStorePriceSheet m_StoreSheet;
+
+ ECurrency m_eCurrency;
+ char m_rgchCountry[3]; // This will change to an enum soon.
+ uint64 m_unTransactionID;
+ uint64 m_unMostRecentSuccessfulTransaction;
+
+ bool m_bShouldFinalize;
+ bool m_bOGSLogging;
+
+ int m_iCheckoutAttempts;
+ int m_iLastPurchaseAttemptPrice;
+
+ CUtlVector<uint32> m_vPopularItems;
+};
+
+void OpenStoreStatusDialog( vgui::Panel *pParent, const char *pszText, bool bAllowClose, bool bShowOnExit, bool bCancel=false );
+void CloseStoreStatusDialog( void );
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for getting the price sheet from the GC
+//-----------------------------------------------------------------------------
+class CGCClientJobGetUserData : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobGetUserData( GCSDK::CGCClient *pGCClient, RTime32 rTimeVersion ) : GCSDK::CGCClientJob( pGCClient ), m_RTimeVersion( rTimeVersion ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam );
+
+private:
+ RTime32 m_RTimeVersion;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for initiating a checkout from the Steam store.
+//-----------------------------------------------------------------------------
+class CGCClientJobInitPurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobInitPurchase( GCSDK::CGCClient *pGCClient ) : GCSDK::CGCClientJob( pGCClient ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for canceling a purchase in progress.
+//-----------------------------------------------------------------------------
+class CGCClientJobCancelPurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobCancelPurchase( GCSDK::CGCClient *pGCClient, uint64 ulTxnID ) : GCSDK::CGCClientJob( pGCClient ), m_ulTxnID( ulTxnID ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam );
+
+private:
+ uint64 m_ulTxnID;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Asynchronous job for finalizing a purchase with the GC.
+//-----------------------------------------------------------------------------
+class CGCClientJobFinalizePurchase : public GCSDK::CGCClientJob
+{
+public:
+ CGCClientJobFinalizePurchase( GCSDK::CGCClient *pGCClient, uint64 ulTxnID ) : GCSDK::CGCClientJob( pGCClient ), m_ulTxnID( ulTxnID ) {}
+ virtual bool BYieldingRunJob( void *pvStartParam );
+
+private:
+ uint64 m_ulTxnID;
+};
+
+#endif // STORE_PANEL_H
diff --git a/game/client/econ/store/store_preview_item.cpp b/game/client/econ/store/store_preview_item.cpp
new file mode 100644
index 0000000..5179f1a
--- /dev/null
+++ b/game/client/econ/store/store_preview_item.cpp
@@ -0,0 +1,418 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store_page.h"
+#include "vgui/ISurface.h"
+#include "vgui/IInput.h"
+#include "vgui/ILocalize.h"
+#include "gamestringpool.h"
+#include "econ_item_inventory.h"
+#include "econ_item_system.h"
+#include "store_preview_item.h"
+#include "item_model_panel.h"
+#include "econ_ui.h"
+#include "store/store_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+DECLARE_BUILD_FACTORY_DEFAULT_TEXT( CPreviewRotButton, CPreviewRotButton );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePreviewItemPanel::CStorePreviewItemPanel( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner )
+: EditablePanel( pParent, "storepreviewitem" )
+{
+ m_pOwner = pOwner;
+ m_pResFile = pResFile != NULL ? pResFile : ( ShouldUseNewStore() ? "Resource/UI/econ/store/v2/StorePreviewItemPanel.res" : "Resource/UI/econ/store/v1/StorePreviewItemPanel.res" );
+ m_pDataTextRichText = NULL;
+ m_iCurrentIconPosition = 0;
+ m_iState = PS_ITEM;
+ m_pIconsMoveLeftButton = NULL;
+ m_pIconsMoveRightButton = NULL;
+
+ m_pItemFullImage = new CItemModelPanel( this, "PreviewItemModelPanel" );
+
+ SetDialogVariable("selectiontitle", g_pVGuiLocalize->Find("#TF_NoSelection") );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStorePreviewItemPanel::~CStorePreviewItemPanel()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( m_pResFile );
+
+ // Apply attribute changes to CItemModelPanel
+ m_pItemFullImage->UpdatePanels();
+
+ m_pIconsMoveLeftButton = dynamic_cast<CExButton*>( FindChildByName("IconsMoveLeftButton") );
+ if ( m_pIconsMoveLeftButton )
+ {
+ m_pIconsMoveLeftButton->AddActionSignalTarget( this );
+ }
+ m_pIconsMoveRightButton = dynamic_cast<CExButton*>( FindChildByName("IconsMoveRightButton") );
+ if ( m_pIconsMoveRightButton )
+ {
+ m_pIconsMoveRightButton->AddActionSignalTarget( this );
+ }
+
+ m_pDataTextRichText = dynamic_cast<CEconItemDetailsRichText*>( FindChildByName( "DetailsRichText" ) );
+ if ( m_pDataTextRichText )
+ {
+ m_pDataTextRichText->SetURLClickedHandler( EconUI()->GetStorePanel() );
+ m_pDataTextRichText->AllowItemSetLinks( true );
+ }
+
+ // Then find all our item icons
+ m_pItemIcons.Purge();
+ CStorePreviewItemIcon *pItemIcon = NULL;
+ int iIcon = 1;
+ do
+ {
+ pItemIcon = dynamic_cast<CStorePreviewItemIcon*>( FindChildByName( VarArgs("ItemIcon%d",iIcon)) );
+ if ( pItemIcon )
+ {
+ m_pItemIcons.AddToTail( pItemIcon );
+ if ( m_pOwner )
+ {
+ pItemIcon->GetItemPanel()->SetTooltip( m_pOwner->GetItemTooltip(), "" );
+ }
+ }
+ iIcon++;
+ } while ( pItemIcon );
+
+ // Update our item icons. Hide them all first. The code below will unhide ones used.
+ for ( int i = 0; i < m_pItemIcons.Count(); i++ )
+ {
+ m_pItemIcons[i]->SetVisible( false );
+ }
+
+ // Start with the item itself showing
+ SetState( PS_ITEM );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ // center the icons
+ int iNumItemIcons = 0;
+ FOR_EACH_VEC( m_pItemIcons, i )
+ {
+ if ( m_pItemIcons[i]->IsVisible() )
+ {
+ ++iNumItemIcons;
+ }
+ }
+ if ( iNumItemIcons )
+ {
+ int iCenterX = GetWide() / 2;
+ int interval = XRES(2);
+ int totalWidth = (iNumItemIcons * m_pItemIcons[0]->GetWide()) + (interval * (iNumItemIcons - 1));
+ int iX = iCenterX - ( totalWidth / 2 );
+
+ int posX, posY;
+ m_pItemIcons[0]->GetPos( posX, posY );
+
+ int iButton = 0;
+ for ( int i = 0; i < m_pItemIcons.Count(); i++ )
+ {
+ if ( m_pItemIcons[i]->IsVisible() )
+ {
+ m_pItemIcons[i]->SetPos( iX, posY );
+ iX += m_pItemIcons[i]->GetWide() + interval;
+
+ iButton++;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::OnCommand( const char *command )
+{
+ if ( !Q_strnicmp( command, "close", 5 ) )
+ {
+ PostActionSignal(new KeyValues("HidePreview"));
+ SetVisible( false );
+ return;
+ }
+ else if ( !Q_stricmp( command, "icons_left" ) )
+ {
+ m_iCurrentIconPosition = MAX( m_iCurrentIconPosition - 1, 0 );
+ UpdateIcons();
+ }
+ else if ( !Q_stricmp( command, "icons_right" ) )
+ {
+ // It's only visible if we can still move right.
+ m_iCurrentIconPosition++;
+ UpdateIcons();
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::OnRotButtonDown( KeyValues *data )
+{
+ int iRotDelta = data->GetInt( "rot", 0 );
+ m_iCurrentRotation = iRotDelta;
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 33 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::OnRotButtonUp( void )
+{
+ m_iCurrentRotation = 0;
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry /*= NULL*/ )
+{
+ m_iCurrentIconPosition = 0;
+ m_item = *pItem;
+
+ if ( m_item.IsValid() )
+ {
+ m_pItemFullImage->SetItem( &m_item );
+ if ( m_pDataTextRichText )
+ {
+ m_pDataTextRichText->SetLimitedItem( pEntry && pEntry->m_bLimited );
+ m_pDataTextRichText->UpdateDetailsForItem( m_item.GetItemDefinition() );
+ }
+
+ SetDialogVariable("selectiontitle", m_item.GetItemName() );
+
+ CExButton *pButton = dynamic_cast<CExButton*>( FindChildByName( "AddToCartButton" ) );
+ if ( pButton )
+ {
+ const CEconStorePriceSheet *pPriceSheet = EconUI()->GetStorePanel()->GetPriceSheet();
+ if ( pPriceSheet )
+ {
+ const econ_store_entry_t *pStoreEntry = pPriceSheet->GetEntry( pItem->GetItemDefIndex() );
+ if ( pStoreEntry->m_bIsMarketItem )
+ {
+ SetDialogVariable( "storeaddtocart", g_pVGuiLocalize->Find( "#Store_ViewMarket" ) );
+ }
+ else
+ {
+ SetDialogVariable( "storeaddtocart", g_pVGuiLocalize->Find( "#Store_AddToCart" ) );
+ }
+ }
+ }
+
+ }
+
+ InvalidateLayout();
+ UpdateIcons();
+
+ if ( m_iState == PS_PLAYER )
+ {
+ SetState( PS_ITEM );
+ }
+
+ Panel *pAddToCart = FindChildByName( "AddToCartButton" );
+ if ( pAddToCart )
+ {
+ pAddToCart->RequestFocus();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::SetState( preview_state_t iState )
+{
+ // Only reset the position when moving from to items/details
+ if ( iState == PS_DETAILS || iState == PS_ITEM )
+ {
+ m_iCurrentIconPosition = 0;
+ }
+
+ m_iState = iState;
+
+ if ( m_pDataTextRichText )
+ {
+ m_pDataTextRichText->SetVisible( m_iState == PS_DETAILS );
+ }
+ m_pItemFullImage->SetVisible( m_iState == PS_ITEM );
+
+ UpdateIcons();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::UpdateIcons( void )
+{
+ bool bAdditionalIcons = false;
+
+ // Do the item icons first
+ if ( m_iState == PS_DETAILS )
+ {
+ // Show as many of the items in the bundle as possible
+ const CEconItemDefinition *pItemData = m_item.GetItemDefinition();
+ if ( pItemData )
+ {
+ const bundleinfo_t *pBundleInfo = pItemData->GetBundleInfo();
+ if ( pBundleInfo )
+ {
+ FOR_EACH_VEC( m_pItemIcons, i )
+ {
+ // If we haven't scrolled, the first item is the bundle itself
+ if ( m_iCurrentIconPosition == 0 && i == 0 )
+ {
+ m_pItemIcons[0]->SetItem( 0, &m_item );
+ continue;
+ }
+
+ int iItemPos = (i - 1 + m_iCurrentIconPosition);
+ if ( pBundleInfo->vecItemDefs.Count() > iItemPos && pBundleInfo->vecItemDefs[iItemPos] )
+ {
+ m_pItemIcons[i]->SetItem( i, pBundleInfo->vecItemDefs[iItemPos]->GetDefinitionIndex() );
+ m_pItemIcons[i]->SetVisible( true );
+ }
+ else
+ {
+ m_pItemIcons[i]->SetVisible( false );
+ }
+ }
+
+ bAdditionalIcons = (m_iCurrentIconPosition + m_pItemIcons.Count()) <= pBundleInfo->vecItemDefs.Count();
+ }
+ else if ( m_pItemIcons.Count() > 0 )
+ {
+ m_pItemIcons[0]->SetVisible( true );
+ m_pItemIcons[0]->SetItem( 0, &m_item );
+ FOR_EACH_VEC( m_pItemIcons, i )
+ {
+ if ( i != 0 )
+ {
+ m_pItemIcons[i]->SetVisible( false );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // Hide all item icons first (but not the first if we haven't scrolled)
+ FOR_EACH_VEC( m_pItemIcons, i )
+ {
+ m_pItemIcons[i]->SetVisible( m_iCurrentIconPosition == 0 && i == 0 );
+ }
+
+ // First icon is always the store entry (item/bundle), if we haven't scrolled right
+ if ( m_iCurrentIconPosition == 0 && m_pItemIcons.Count() )
+ {
+ m_pItemIcons[0]->SetItem( 0, &m_item );
+ }
+ }
+
+ if( m_pIconsMoveLeftButton )
+ m_pIconsMoveLeftButton->SetVisible( (m_iCurrentIconPosition > 0) );
+ if( m_pIconsMoveRightButton )
+ m_pIconsMoveRightButton->SetVisible( bAdditionalIcons );
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ if ( !IsVisible() )
+ {
+ vgui::ivgui()->RemoveTickSignal( GetVPanel() );
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStorePreviewItemPanel::OnItemIconSelected( KeyValues *data )
+{
+ if ( m_iState == PS_DETAILS )
+ {
+ int iIcon = data->GetInt( "icon", 0 );
+ CEconItemView *pItem = m_pItemIcons[iIcon]->GetItemPanel()->GetItem();
+ if ( pItem )
+ {
+ if ( m_pDataTextRichText )
+ {
+ m_pDataTextRichText->UpdateDetailsForItem( pItem->GetStaticData() );
+ }
+ SetDialogVariable("selectiontitle", pItem->GetItemName() );
+ }
+ }
+ else
+ {
+ SetState( PS_ITEM );
+ }
+}
+
+//================================================================================================================
+// PREVIEW ROT BUTTON
+//================================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPreviewRotButton::OnMousePressed(vgui::MouseCode code)
+{
+ BaseClass::OnMousePressed( code );
+
+ if ( IsSelected() )
+ {
+ KeyValues *pCommand = GetCommand();
+ PostActionSignal(new KeyValues("RotButtonDown", "rot", pCommand->GetString("command", "0") ));
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CPreviewRotButton::OnMouseReleased(vgui::MouseCode code)
+{
+ if ( IsSelected() )
+ {
+ PostActionSignal(new KeyValues("RotButtonUp"));
+ }
+
+ BaseClass::OnMouseReleased( code );
+}
diff --git a/game/client/econ/store/store_preview_item.h b/game/client/econ/store/store_preview_item.h
new file mode 100644
index 0000000..6493b0c
--- /dev/null
+++ b/game/client/econ/store/store_preview_item.h
@@ -0,0 +1,100 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_PREVIEW_ITEM_H
+#define STORE_PREVIEW_ITEM_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include <vgui_controls/Panel.h>
+#include "econ_controls.h"
+#include "store_page.h"
+
+enum preview_state_t
+{
+ PS_ITEM,
+ PS_PLAYER,
+ PS_DETAILS,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Button that handles the rotation of the preview model.
+//-----------------------------------------------------------------------------
+class CPreviewRotButton : public CExButton
+{
+ DECLARE_CLASS_SIMPLE( CPreviewRotButton, CExButton );
+public:
+ CPreviewRotButton( vgui::Panel *parent, const char *name, const char *text, vgui::Panel *pActionSignalTarget = NULL, const char *cmd = NULL ) :
+ CExButton( parent, name, text, pActionSignalTarget, cmd )
+ {
+ }
+ CPreviewRotButton( vgui::Panel *parent, const char *name, const wchar_t *wszText, vgui::Panel *pActionSignalTarget = NULL, const char *cmd = NULL ) :
+ CExButton( parent, name, wszText, pActionSignalTarget, cmd )
+ {
+ }
+
+ virtual void OnMousePressed(vgui::MouseCode code);
+ virtual void OnMouseReleased(vgui::MouseCode code);
+
+ // Our fire action signal does nothing, because it's all done in mouse pressed/released
+ virtual void FireActionSignal( void ) { return; }
+};
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStorePreviewItemPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CStorePreviewItemPanel, vgui::EditablePanel );
+public:
+ CStorePreviewItemPanel( vgui::Panel *pParent, const char *pResFile, const char *pPanelName, CStorePage *pOwner );
+ virtual ~CStorePreviewItemPanel();
+
+ CStorePage *GetOwningStorePage() { return m_pOwner; }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void OnCommand( const char *command );
+ virtual void PerformLayout( void );
+ virtual void OnTick( void );
+
+ virtual void PreviewItem( int iClass, CEconItemView *pItem, const econ_store_entry_t* pEntry=NULL );
+ virtual void SetState( preview_state_t iState );
+
+ // Subclass interface.
+ virtual int GetPreviewTeam() const { return 0; }
+
+ MESSAGE_FUNC_PARAMS( OnRotButtonDown, "RotButtonDown", data );
+ MESSAGE_FUNC( OnRotButtonUp, "RotButtonUp" );
+
+ MESSAGE_FUNC_PARAMS( OnItemIconSelected, "ItemIconSelected", data );
+
+protected:
+ virtual void UpdateIcons( void );
+
+protected:
+ const char *m_pResFile;
+ CUtlVector<CStorePreviewItemIcon*> m_pItemIcons;
+
+ int m_iCurrentIconPosition;
+
+ CEconItemDetailsRichText *m_pDataTextRichText;
+ CItemModelPanel *m_pItemFullImage;
+
+ CEconItemView m_item;
+ preview_state_t m_iState;
+
+ int m_iCurrentRotation;
+ CExButton *m_pIconsMoveLeftButton;
+ CExButton *m_pIconsMoveRightButton;
+
+ CStorePage *m_pOwner;
+};
+
+#endif // STORE_PREVIEW_ITEM_H
diff --git a/game/client/econ/store/store_viewcart.cpp b/game/client/econ/store/store_viewcart.cpp
new file mode 100644
index 0000000..a071dc5
--- /dev/null
+++ b/game/client/econ/store/store_viewcart.cpp
@@ -0,0 +1,387 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "store_viewcart.h"
+#include "vgui/IInput.h"
+#include "baseviewport.h"
+#include "iclientmode.h"
+#include "ienginevgui.h"
+#include "econ_item_inventory.h"
+#include <vgui/ILocalize.h>
+#include "econ_item_system.h"
+#include "item_model_panel.h"
+#include "vgui_controls/ScrollBarSlider.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+DECLARE_BUILD_FACTORY( CCartViewItemEntry );
+
+//-----------------------------------------------------------------------------
+// Purpose: Basic help dialog
+//-----------------------------------------------------------------------------
+CStoreViewCartPanel::CStoreViewCartPanel( Panel *parent ) : Frame(parent, "store_viewcart_panel")
+{
+ // Store is parented to the game UI panel
+ vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL );
+ SetParent( gameuiPanel );
+
+ // We don't want the gameui to delete us, or things get messy
+ SetAutoDelete( false );
+
+ SetMoveable( false );
+ SetSizeable( false );
+
+ vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
+ SetScheme(scheme);
+ SetProportional( true );
+
+ ListenForGameEvent( "gameui_hidden" );
+ ListenForGameEvent( "cart_updated" );
+
+ m_pItemEntryKVs = NULL;
+ m_pClientArea = new EditablePanel(this, "ClientArea");
+ m_pItemListContainer = new vgui::EditablePanel( this, "ItemListContainer" );
+ m_pItemListContainerScroller = new vgui::ScrollableEditablePanel( m_pClientArea, m_pItemListContainer, "ItemListContainerScroller" );
+ m_pPurchaseFooter = new EditablePanel(m_pItemListContainer, "PurchaseFooter");
+ m_pEmptyCartLabel = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStoreViewCartPanel::~CStoreViewCartPanel()
+{
+ if ( m_pItemEntryKVs )
+ {
+ m_pItemEntryKVs->deleteThis();
+ m_pItemEntryKVs = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( ShouldUseNewStore() ? "Resource/UI/econ/store/v2/StoreViewCartPanel.res" : "Resource/UI/econ/store/v1/StoreViewCartPanel.res" );
+ m_bReapplyItemKVs = true;
+
+ m_pItemListContainerScroller->GetScrollbar()->SetAutohideButtons( true );
+ m_pEmptyCartLabel = dynamic_cast<vgui::Label*>( m_pClientArea->FindChildByName("EmptyCartLabel") );
+
+ m_pFeaturedItemImage = dynamic_cast<vgui::ImagePanel*>( m_pItemListContainer->FindChildByName("FeaturedItemSymbol") );
+ if ( m_pFeaturedItemImage )
+ {
+ m_pFeaturedItemImage->SetMouseInputEnabled( false );
+ m_pFeaturedItemImage->SetKeyBoardInputEnabled( false );
+ }
+
+ CExButton *pCheckoutButton = dynamic_cast<CExButton*>( m_pClientArea->FindChildByName("CheckoutButton") );
+ if ( pCheckoutButton )
+ {
+ pCheckoutButton->AddActionSignalTarget( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "item_entry_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pItemEntryKVs )
+ {
+ m_pItemEntryKVs->deleteThis();
+ }
+ m_pItemEntryKVs = new KeyValues("item_entry_kv");
+ pItemKV->CopySubkeys( m_pItemEntryKVs );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::PerformLayout( void )
+{
+ if ( GetVParent() )
+ {
+ int w,h;
+ vgui::ipanel()->GetSize( GetVParent(), w, h );
+ SetBounds(0,0,w,h);
+ }
+
+ if ( m_bReapplyItemKVs )
+ {
+ m_bReapplyItemKVs = false;
+
+ if ( m_pItemEntryKVs )
+ {
+ FOR_EACH_VEC( m_pItemEntries, i )
+ {
+ m_pItemEntries[i]->ApplySettings( m_pItemEntryKVs );
+ m_pItemEntries[i]->InvalidateLayout();
+ }
+ }
+ }
+
+ BaseClass::PerformLayout();
+
+ if ( m_pItemEntries.Count() )
+ {
+ int iTall = m_pItemEntries[0]->GetTall();
+ m_pItemListContainer->SetSize( m_pItemListContainer->GetWide(), (iTall * m_pItemEntries.Count()) + m_pPurchaseFooter->GetTall() );
+ m_pItemListContainerScroller->InvalidateLayout( true );
+ m_pItemListContainerScroller->GetScrollbar()->InvalidateLayout( true );
+
+ int iX,iY;
+ m_pItemEntries[0]->GetPos( iX, iY );
+ FOR_EACH_VEC( m_pItemEntries, i )
+ {
+ iY = (iTall * i);
+ m_pItemEntries[i]->SetPos( iX, iY );
+ }
+
+ m_pPurchaseFooter->SetVisible( true );
+ m_pPurchaseFooter->SetPos( 0, iTall * m_pItemEntries.Count() );
+ }
+ else
+ {
+ m_pItemListContainer->SetSize( m_pItemListContainer->GetWide(), 100 );
+ m_pItemListContainer->InvalidateLayout( true );
+ m_pItemListContainerScroller->InvalidateLayout( true );
+ m_pPurchaseFooter->SetVisible( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::ShowPanel(bool bShow)
+{
+ if ( bShow )
+ {
+ InvalidateLayout( false, true );
+ Activate();
+
+ CExButton *pCloseButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") );
+ if ( pCloseButton )
+ {
+ pCloseButton->RequestFocus();
+ }
+
+ // don't display the WA sales tax outside of the US
+ vgui::Panel *pPanel = FindChildByName( "WashingtonStateSalesTaxLabel", true );
+ if ( pPanel )
+ {
+ pPanel->SetVisible( FStrEq( EconUI()->GetStorePanel()->GetCountryCode(), "US" ) == true );
+ }
+ }
+
+ SetVisible( bShow );
+
+ if ( bShow )
+ {
+ UpdateCartItemList();
+ m_pItemListContainerScroller->GetScrollbar()->SetValue( 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::FireGameEvent( IGameEvent *event )
+{
+ const char * type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ ShowPanel( false );
+ }
+ else if ( Q_strcmp(type, "cart_updated") == 0 )
+ {
+ UpdateCartItemList();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::UpdateCartItemList( void )
+{
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ int iNumEntriesInCart = pCart->GetNumEntries();
+
+ // Update the item count
+ wchar_t wszCount[16];
+ _snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", pCart->GetTotalItems() );
+ wchar_t wzLocalized[32];
+ g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( "#Store_CartItems" ), 1, wszCount );
+ m_pClientArea->SetDialogVariable("storecart", wzLocalized );
+
+ // Create / Update all the item entries
+ if ( m_pItemEntries.Count() < iNumEntriesInCart )
+ {
+ for ( int i = m_pItemEntries.Count(); i < iNumEntriesInCart; i++ )
+ {
+ CCartViewItemEntry *pPanel = vgui::SETUP_PANEL( new CCartViewItemEntry( m_pItemListContainer, VarArgs("itementry%d", i) ) );
+ pPanel->ApplySettings( m_pItemEntryKVs );
+ m_pItemEntries.AddToTail( pPanel );
+ }
+ }
+ else
+ {
+ for ( int i = m_pItemEntries.Count()-1; i >= iNumEntriesInCart; i-- )
+ {
+ m_pItemEntries[i]->MarkForDeletion();
+ m_pItemEntries.Remove( i );
+ }
+ }
+
+ if ( m_pEmptyCartLabel )
+ {
+ m_pEmptyCartLabel->SetVisible( iNumEntriesInCart == 0 );
+ }
+
+ InvalidateLayout( true );
+
+ if ( !iNumEntriesInCart )
+ return;
+
+ bool bFeaturedImagePanelVisible = false;
+
+ // Set all the entries up
+ FOR_EACH_VEC( m_pItemEntries, i )
+ {
+ if ( i >= iNumEntriesInCart )
+ {
+ m_pItemEntries[i]->SetVisible( false );
+ continue;
+ }
+
+ cart_item_t *pCartItem = pCart->GetItem(i);
+ m_pItemEntries[i]->SetEntry( pCartItem, i );
+ m_pItemEntries[i]->SetVisible( true );
+
+ // If we're the featured item, show it
+ if ( pCartItem && pCartItem->pEntry == EconUI()->GetStorePanel()->GetFeaturedEntry() )
+ {
+ bFeaturedImagePanelVisible = true;
+ int iX, iY;
+ m_pItemEntries[i]->GetPos( iX, iY );
+ m_pFeaturedItemImage->SetPos( iX, iY + m_pItemEntries[i]->GetTall() - m_pFeaturedItemImage->GetTall() );
+ }
+ }
+
+ if ( m_pFeaturedItemImage->IsVisible() != bFeaturedImagePanelVisible )
+ {
+ m_pFeaturedItemImage->SetVisible( bFeaturedImagePanelVisible );
+ }
+
+ // Update total price
+ item_price_t unTotalPrice = pCart->GetTotalPrice();
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), unTotalPrice, EconUI()->GetStorePanel()->GetCurrency() );
+ m_pPurchaseFooter->SetDialogVariable( "totalprice", wzLocalizedPrice );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CStoreViewCartPanel::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "close" ) )
+ {
+ ShowPanel( false );
+ }
+ else if ( !Q_strnicmp( command, "remove", 6 ) )
+ {
+ int iIndex = atoi(command+6);
+ if ( iIndex >= 0 && iIndex < m_pItemEntries.Count() )
+ {
+ CStoreCart *pCart = EconUI()->GetStorePanel()->GetCart();
+ pCart->RemoveFromCart( iIndex );
+ }
+ return;
+ }
+ else if ( !Q_stricmp( command, "checkout" ) )
+ {
+ EconUI()->GetStorePanel()->InitiateCheckout( false );
+ }
+ else
+ {
+ engine->ClientCmd( const_cast<char *>( command ) );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+static vgui::DHANDLE<CStoreViewCartPanel> g_StoreViewCartPanel;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStoreViewCartPanel *OpenStoreViewCartPanel( void )
+{
+ if (!g_StoreViewCartPanel.Get())
+ {
+ g_StoreViewCartPanel = vgui::SETUP_PANEL( new CStoreViewCartPanel( NULL ) );
+ g_StoreViewCartPanel->InvalidateLayout( false, true );
+ }
+
+ engine->ClientCmd_Unrestricted( "gameui_activate" );
+ g_StoreViewCartPanel->ShowPanel( true );
+
+ return g_StoreViewCartPanel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CStoreViewCartPanel *GetStoreViewCartPanel( void )
+{
+ return g_StoreViewCartPanel.Get();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CCartViewItemEntry::SetEntry( cart_item_t *pEntry, int iEntryIndex )
+{
+ m_pEntry = pEntry;
+ SetDialogVariable( "quantity", pEntry->iQuantity );
+
+ int iSubTotal = pEntry->GetDisplayPrice();
+
+ wchar_t wzLocalizedPrice[ kLocalizedPriceSizeInChararacters ];
+ MakeMoneyString( wzLocalizedPrice, ARRAYSIZE( wzLocalizedPrice ), iSubTotal, EconUI()->GetStorePanel()->GetCurrency() );
+ SetDialogVariable("price", wzLocalizedPrice );
+
+ CItemModelPanel *pItemPanel = dynamic_cast<CItemModelPanel*>( FindChildByName("itempanel") );
+ if ( pItemPanel )
+ {
+ CEconItemView ItemData;
+ ItemData.Init( pEntry->pEntry->GetItemDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
+ pItemPanel->SetItem( &ItemData );
+ }
+
+ CExButton *pRemoveButton = dynamic_cast<CExButton*>( FindChildByName("RemoveButton") );
+ if ( pRemoveButton )
+ {
+ pRemoveButton->SetCommand( VarArgs("remove%d",iEntryIndex) );
+ pRemoveButton->AddActionSignalTarget( GetStoreViewCartPanel() );
+ }
+} \ No newline at end of file
diff --git a/game/client/econ/store/store_viewcart.h b/game/client/econ/store/store_viewcart.h
new file mode 100644
index 0000000..76d3586
--- /dev/null
+++ b/game/client/econ/store/store_viewcart.h
@@ -0,0 +1,75 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef STORE_VIEWCART_H
+#define STORE_VIEWCART_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/Frame.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "GameEventListener.h"
+#include "store/store_panel.h"
+
+//-----------------------------------------------------------------------------
+// Purpose: Shows a single item in the cart
+//-----------------------------------------------------------------------------
+class CCartViewItemEntry : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CCartViewItemEntry, vgui::EditablePanel );
+public:
+ CCartViewItemEntry( vgui::Panel *parent, const char *name ) : vgui::EditablePanel(parent,name)
+ {
+ m_pEntry = NULL;
+ }
+
+ void SetEntry( cart_item_t *pEntry, int iEntryIndex );
+ cart_item_t *GetEntry( void ) { return m_pEntry; }
+
+private:
+ cart_item_t *m_pEntry;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CStoreViewCartPanel : public vgui::Frame, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CStoreViewCartPanel, vgui::Frame );
+public:
+ CStoreViewCartPanel( Panel *parent );
+ virtual ~CStoreViewCartPanel();
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void ShowPanel( bool bShow );
+ virtual void FireGameEvent( IGameEvent *event );
+
+ void UpdateCartItemList( void );
+
+private:
+ vgui::EditablePanel *m_pClientArea;
+ vgui::EditablePanel *m_pPurchaseFooter;
+ KeyValues *m_pItemEntryKVs;
+ bool m_bReapplyItemKVs;
+ vgui::Label *m_pEmptyCartLabel;
+ vgui::ImagePanel *m_pFeaturedItemImage;
+
+ vgui::EditablePanel *m_pItemListContainer;
+ vgui::ScrollableEditablePanel *m_pItemListContainerScroller;
+ CUtlVector<CCartViewItemEntry*> m_pItemEntries;
+
+ CPanelAnimationVar( int, m_iSheetInsetBottom, "sheetinset_bottom", "32" );
+};
+
+CStoreViewCartPanel *OpenStoreViewCartPanel( void );
+CStoreViewCartPanel *GetStoreViewCartPanel( void );
+
+#endif // STORE_VIEWCART_H
diff --git a/game/client/econ/tool_items/custom_texture_cache.cpp b/game/client/econ/tool_items/custom_texture_cache.cpp
new file mode 100644
index 0000000..12cdd34
--- /dev/null
+++ b/game/client/econ/tool_items/custom_texture_cache.cpp
@@ -0,0 +1,1111 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+#include "cbase.h"
+#include "custom_texture_cache.h"
+#include "materialsystem/imaterialproxy.h"
+#include "materialsystem/imaterialvar.h"
+#include "materialsystem/itexture.h"
+#include "pixelwriter.h"
+#include "checksum_md5.h"
+#include "imageutils.h"
+#include "toolframework_client.h"
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#include "bitmap/bitmap.h"
+
+using namespace CustomTextureSystem;
+
+ITexture *CustomTextureSystem::g_pPreviewCustomTexture = NULL;
+
+CEconItemView *CustomTextureSystem::g_pPreviewEconItem = NULL;
+
+bool CustomTextureSystem::g_pPreviewCustomTextureDirty = true;
+
+const char CustomTextureSystem::k_rchCustomTextureFilterPreviewImageName[] = "__CustomTextureFilterPreview";
+const char CustomTextureSystem::k_rchCustomTextureFilterPreviewTextureName[] = "vgui/__CustomTextureFilterPreview";
+
+//-----------------------------------------------------------------------------
+
+static ISteamRemoteStorage *GetISteamRemoteStorage()
+{
+ return steamapicontext?steamapicontext->SteamRemoteStorage():NULL;
+// return Steam3Client().SteamRemoteStorage();
+}
+
+static void CalcMD5Ascii( char *szDigestAscii, const void *data, int dataSz )
+{
+ MD5Context_t context;
+ unsigned char digest[ MD5_DIGEST_LENGTH ];
+ MD5Init( &context );
+ MD5Update( &context, (const unsigned char *)data, dataSz );
+ MD5Final( digest, &context );
+ Q_binarytohex( digest, MD5_DIGEST_LENGTH, szDigestAscii, MD5_DIGEST_LENGTH*2+1 );
+}
+
+static bool BReadSteamRemoteFileToBuffer( CUtlBuffer &outBuffer, const char *pchRemoteFilename )
+{
+ outBuffer.Purge();
+
+ ISteamRemoteStorage *pRemoteStorage = GetISteamRemoteStorage();
+ if ( !pRemoteStorage )
+ return false;
+ if ( !pRemoteStorage->FileExists( pchRemoteFilename ))
+ return false;
+ int nFileSize = pRemoteStorage->GetFileSize( pchRemoteFilename );
+ if ( nFileSize <= 0 )
+ return false;
+
+ // Allocate space
+ outBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, nFileSize );
+ int nSizeRead = pRemoteStorage->FileRead( pchRemoteFilename, outBuffer.Base(), nFileSize );
+ return ( nSizeRead == nFileSize );
+}
+
+//-----------------------------------------------------------------------------
+// Local cache of custom images. This cache contains files from the cloud, and
+// and also a few virtual textures that are used during autioning / tweaking
+
+/// Name of cloud-backed config file remembering the custom images that the user
+/// has uploaded to the cloud.
+static const char k_szCustomTextureRecentListFilename[] = "stamped_items_mru.txt";
+
+/// Track a single entry
+struct SCustomImageCacheEntry : private ITextureRegenerator
+{
+
+ /// If this has been assigned a cloud ID, what is it?
+ UGCHandle_t m_hCloudID;
+
+ /// If this is one of our files, then we know the MD5.
+ /// This is empty for other people's files
+ char m_szDigestAscii[ MD5_DIGEST_LENGTH*2 + 4];
+
+ //
+ // Bookkeeping for steam downloads of UGC.
+ //
+
+ /// -1 = failure, 0 = not started, 1 = in progress, 2 = finished OK and image should be in memory
+ int m_nStatus;
+
+ /// Handle to the active download, or k_uAPICallInvalid if not active
+ SteamAPICall_t m_hDownloadApiCall;
+
+ /// Procedural texture object. We hold a reference.
+ ITexture *m_pTexture;
+
+ /// Procedurally-created material. We hold a reference.
+ IMaterial *m_pMaterial;
+
+ /// GUI texture handle. (It's bound to the material.)
+ int m_iVguiHandle;
+
+ /// The raw image
+ Bitmap_t m_image;
+
+ /// Doubly-linked list. We keep it in MRU order so we know what to eject from the cache
+ SCustomImageCacheEntry *m_pPrev;
+ SCustomImageCacheEntry *m_pNext;
+
+ SCustomImageCacheEntry()
+ : m_hCloudID(0)
+ , m_pTexture(NULL)
+ , m_nStatus(0)
+ , m_hDownloadApiCall(k_uAPICallInvalid)
+ , m_pPrev(NULL)
+ , m_pNext(NULL)
+ , m_pMaterial(NULL)
+ , m_iVguiHandle(0)
+ {
+ m_szDigestAscii[0] = '\0';
+ }
+
+ virtual ~SCustomImageCacheEntry()
+ {
+ Clear();
+ }
+
+ // Release texture / VGUI resources. This doesn't free the image we have
+ // loaded or stop any async actions that were in progress. (Use Clear())
+ void ReleaseResources()
+ {
+ if ( m_pTexture )
+ {
+ ITexture *tex = m_pTexture;
+ m_pTexture = NULL; // clear pointer first, to prevent infinite recursion
+ tex->SetTextureRegenerator( NULL );
+ tex->Release();
+ }
+ if ( m_pMaterial )
+ {
+ m_pMaterial->Release();
+ m_pMaterial = NULL;
+ }
+ if ( m_iVguiHandle != 0 )
+ {
+ g_pMatSystemSurface->DestroyTextureID( m_iVguiHandle );
+ m_iVguiHandle = 0;
+ }
+ }
+
+ /// Inherited from ITextureRegenerator
+ ///
+ /// Gets called when our ITextureRegenerator interface gets detached from the texture.
+ /// We should be the only ones doing this --- so that means we had better have already
+ /// cleared our texture at this point!
+ virtual void Release()
+ {
+ Assert( m_pTexture == NULL );
+ }
+
+ /// Inherited from ITextureRegenerator
+ ///
+ /// The main interface function that actually supplies the texture bits
+ virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect )
+ {
+
+ Assert( pVTFTexture->FrameCount() == 1 );
+ Assert( pVTFTexture->FaceCount() == 1 );
+ Assert( pTexture == m_pTexture );
+ Assert( !pTexture->IsMipmapped() );
+
+ int nWidth, nHeight, nDepth;
+ pVTFTexture->ComputeMipLevelDimensions( 0, &nWidth, &nHeight, &nDepth );
+ Assert( nDepth == 1 );
+ Assert( nWidth == m_image.Width() && nHeight == m_image.Height() );
+
+ CPixelWriter pixelWriter;
+ pixelWriter.SetPixelMemory( pVTFTexture->Format(),
+ pVTFTexture->ImageData( 0, 0, 0 ), pVTFTexture->RowSizeInBytes( 0 ) );
+
+ // !SPEED! 'Tis probably DEATHLY slow...
+ for ( int y = 0; y < nHeight; ++y )
+ {
+ pixelWriter.Seek( 0, y );
+ for ( int x = 0; x < nWidth; ++x )
+ {
+ Color c = m_image.GetColor( x, y );
+ pixelWriter.WritePixel( c.r(), c.g(), c.b(), c.a() );
+ }
+ }
+ }
+
+ void Clear()
+ {
+ ReleaseResources();
+ m_image.Clear();
+ m_szDigestAscii[0] = '\0';
+ m_nStatus = 0;
+ m_hCloudID = 0;
+
+ // !KLUDGE! How can I clean this up properly if something is
+ // in progress?
+ m_hDownloadApiCall = k_uAPICallInvalid;
+ }
+
+ /// Poll the entry and update bookeeping if we're busy.
+ void Poll()
+ {
+
+ // We must know our cloud ID
+ if ( m_hCloudID == 0 )
+ {
+ Assert( m_hCloudID != 0 );
+ return;
+ }
+
+ // If texture already exists, then we are definitely done!
+ if ( m_pTexture )
+ {
+ Assert( m_nStatus == 2 );
+ return;
+ }
+
+ // Check if we have not yet initiated anything
+ if ( m_nStatus == 0 )
+ {
+
+ // We'll need to download it.
+ // Start by assuming failure.
+ m_nStatus = -1;
+
+ // Start download
+ ISteamRemoteStorage *pRemoteStorage = GetISteamRemoteStorage();
+ if ( pRemoteStorage )
+ {
+ m_hDownloadApiCall = pRemoteStorage->UGCDownload( m_hCloudID, 0 );
+ if ( m_hDownloadApiCall != k_uAPICallInvalid )
+ {
+ // Mark download as in progress
+ Msg( "Started download of cloud file %08X%08X\n", (uint32)(m_hCloudID>>32), (uint32)m_hCloudID );
+ m_nStatus = 1;
+ }
+ }
+ }
+
+ // If we're in progress, poll the result
+ if ( m_nStatus == 1 )
+ {
+ PollDownload();
+ }
+
+ // If result has completed, then fetch the texture
+ Assert( m_pTexture == NULL );
+ if ( m_nStatus == 2 && m_image.IsValid() )
+ {
+ // Generate the logical texture name
+ char rchTextureName[MAX_PATH];
+ GenerateLocalTextureName( rchTextureName );
+
+ ITexture *pTexture = NULL;
+ if ( g_pMaterialSystem->IsTextureLoaded( rchTextureName ) )
+ {
+ pTexture = g_pMaterialSystem->FindTexture( rchTextureName, TEXTURE_GROUP_VGUI );
+ pTexture->AddRef();
+ Assert( pTexture );
+ }
+ else
+ {
+ pTexture = g_pMaterialSystem->CreateProceduralTexture(
+ rchTextureName,
+ TEXTURE_GROUP_VGUI,
+ k_nCustomImageSize, k_nCustomImageSize,
+ IMAGE_FORMAT_RGBA8888,
+ TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD
+ );
+ Assert( pTexture );
+ }
+ pTexture->SetTextureRegenerator( this ); // note carefully order of operations here. See Release()
+ m_pTexture = pTexture;
+
+ // Upload the data now
+ m_pTexture->Download();
+ }
+ else
+ {
+ Assert( m_nStatus < 2 );
+ Assert( !m_image.IsValid() );
+ }
+ }
+
+ void PollDownload()
+ {
+
+ Assert( m_nStatus == 1 );
+
+ // Sanity check we have everything we need
+ ISteamRemoteStorage *pRemoteStorage = GetISteamRemoteStorage();
+ if ( m_hDownloadApiCall == k_uAPICallInvalid || !steamapicontext || !steamapicontext->SteamUtils() || !pRemoteStorage )
+ {
+ // ???
+ Assert( m_hDownloadApiCall != k_uAPICallInvalid );
+ Assert( steamapicontext && steamapicontext->SteamUtils() );
+ Assert( pRemoteStorage );
+ m_nStatus = -1;
+ return;
+ }
+
+ // Poll progress
+ bool bFailed;
+ RemoteStorageDownloadUGCResult_t result;
+ if ( !steamapicontext->SteamUtils()->GetAPICallResult(m_hDownloadApiCall,
+ &result, sizeof(result), RemoteStorageDownloadUGCResult_t::k_iCallback, &bFailed) )
+ {
+ // Still busy.
+ return;
+ }
+
+ // Make sure we got back the file we were expecting
+ Assert( result.m_hFile == m_hCloudID );
+
+ // Clear status, mark success
+ m_hDownloadApiCall = k_uAPICallInvalid;
+
+ // Completed. Did we succeed?
+ if ( bFailed )
+ {
+ Warning( "Download of custom image file from UFS (UGC=%08X%08X) failed.\n", (uint32)(m_hCloudID >> 32), (uint32)(m_hCloudID) );
+ m_nStatus = -1;
+ return;
+ }
+
+ // Fetch file details
+ AppId_t nAppID;
+ char *pchName;
+ int32 nFileSizeInBytes = -1;
+ CSteamID steamIDOwner;
+ if ( !pRemoteStorage->GetUGCDetails( m_hCloudID, &nAppID, &pchName, &nFileSizeInBytes, &steamIDOwner )
+ || nFileSizeInBytes <= 0 || nFileSizeInBytes >= k_nMaxCustomImageFileSize )
+ {
+ Warning( "GetUGCDetails failed? (UGC=%08X%08X nFileSizeInBytes=%d).\n", (uint32)(m_hCloudID >> 32), (uint32)(m_hCloudID), nFileSizeInBytes );
+ m_nStatus = -1;
+ return;
+ }
+
+ // Load the file data
+ CUtlBuffer fileData;
+ fileData.SeekPut( CUtlBuffer::SEEK_HEAD, nFileSizeInBytes );
+
+ // Read in the data. Phil says this is supposed to be basically a memcpy
+ // or some other fast, local operation.
+ if ( pRemoteStorage->UGCRead( m_hCloudID, fileData.Base( ), nFileSizeInBytes, 0, k_EUGCRead_ContinueReadingUntilFinished ) != nFileSizeInBytes )
+ {
+ Warning( "UGCRead failed? (UGC=%08X%08X).\n", (uint32)(m_hCloudID >> 32), (uint32)(m_hCloudID) );
+ m_nStatus = -1;
+ return;
+ }
+
+ // Parse the PNG file data
+ if ( ImgUtl_LoadPNGBitmapFromBuffer( fileData, m_image ) != CE_SUCCESS )
+ {
+ Warning( "Corrupt PNG file, UGC=%08X%08X.\n", (uint32)(m_hCloudID >> 32), (uint32)(m_hCloudID) );
+ m_nStatus = -1;
+ return;
+ }
+
+ // We have the raw data
+ m_nStatus = 2;
+ }
+
+ int GetGuiHandle()
+ {
+ // Should never be called on entries without a cloud ID
+ if ( m_hCloudID == 0 )
+ {
+ Assert( m_hCloudID != 0 );
+ return 0;
+ }
+
+ // Already have one?
+ if ( m_iVguiHandle != 0 )
+ {
+ return m_iVguiHandle;
+ }
+
+ // Process texture downloading, etc
+ Poll();
+
+ // If we don't have a texture yet, or don't know our logical name, then we cannot draw
+ if ( m_pTexture == NULL )
+ {
+ return 0;
+ }
+
+ // Make a material, if we don't already have one
+ if ( m_pMaterial == NULL )
+ {
+
+ // Generate the material name
+ char rchImageName[MAX_PATH], rchMaterialName[MAX_PATH];
+ GenerateLocalImageNameBase( rchImageName );
+ Q_snprintf( rchMaterialName, MAX_PATH, "vgui/%s.mtl", rchImageName );
+
+ // Does it already exist?
+ if ( g_pMaterialSystem->IsMaterialLoaded( rchMaterialName ) )
+ {
+ m_pMaterial = g_pMaterialSystem->FindMaterial( rchMaterialName, TEXTURE_GROUP_VGUI );
+ Assert( m_pMaterial );
+ }
+ else
+ {
+
+ // Fetch the texture name
+ char rchTextureName[MAX_PATH];
+ GenerateLocalTextureName( rchTextureName );
+
+ // Create dummy material KV data
+ KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
+ pVMTKeyValues->SetString( "$basetexture", rchTextureName );
+ pVMTKeyValues->SetInt( "$vertexcolor", 1 );
+ pVMTKeyValues->SetInt( "$vertexalpha", 1 );
+ pVMTKeyValues->SetInt( "$translucent", 1 );
+
+ // Create the material
+ m_pMaterial = g_pMaterialSystem->CreateMaterial(
+ rchMaterialName,
+ pVMTKeyValues
+ );
+ }
+
+ // Bind the material to a new VGUI texture object
+ m_iVguiHandle = g_pMatSystemSurface->CreateNewTextureID();
+ g_pMatSystemSurface->DrawSetTextureMaterial( m_iVguiHandle, m_pMaterial );
+ }
+
+ return m_iVguiHandle;
+ }
+
+ // Generate logical image name, with no leading materials or vgui directories
+ // nor a file extension.
+ void GenerateLocalImageNameBase( char *result ) const
+ {
+ Assert( m_hCloudID != 0 );
+
+ // Generate the local filenames. !KLUDGE! I'm not sure the platform-safe way
+ // to print a 64-bit int, so I'll just print both halves myself
+ Q_snprintf( result, 64, "cloud_custom_images/%08X%08X", (uint32)(m_hCloudID >> 32), (uint32)(m_hCloudID) );
+ }
+
+ /// Logical texture name, including "vgui" but not "materials"
+ void GenerateLocalTextureName( char *result ) const
+ {
+ char rchImageName[MAX_PATH];
+ GenerateLocalImageNameBase( rchImageName );
+ Q_snprintf( result, MAX_PATH, "vgui/%s.vtf", rchImageName );
+ }
+
+ /// Full local filename, including leading "materials" directory
+ void GenerateLocalFilename( char *result ) const
+ {
+ char szLocalTextureName[MAX_PATH];
+ GenerateLocalTextureName( szLocalTextureName );
+
+ Q_snprintf( result, MAX_PATH, "materials/%s", szLocalTextureName );
+ }
+};
+
+/// Head of linked list of entries, in MRU order
+static SCustomImageCacheEntry *mruCustomImageEntry = NULL;
+
+/// Map of entries, indexed by cloud ID
+typedef CUtlMap<UGCHandle_t, SCustomImageCacheEntry *, int> tCustomTextureInfoMap;
+static tCustomTextureInfoMap g_mapCustomTextureInfoByCloudId( DefLessFunc(UGCHandle_t) );
+
+// Remove from linked list, without deleting. The item must already be in the list
+static void CustomTextureCache_Remove(SCustomImageCacheEntry *pEntry)
+{
+ Assert( pEntry );
+
+ // List had better not be empty. Commence paranoia.
+ Assert( mruCustomImageEntry );
+ Assert( !mruCustomImageEntry->m_pPrev );
+
+ SCustomImageCacheEntry *p = pEntry->m_pPrev;
+ SCustomImageCacheEntry *n = pEntry->m_pNext;
+
+ // Detach from next, if we're not last
+ if ( n != NULL )
+ {
+ Assert( n->m_pPrev == pEntry);
+ n->m_pPrev = p;
+ }
+
+ // At the head?
+ if ( !p )
+ {
+ Assert( mruCustomImageEntry == pEntry );
+ mruCustomImageEntry = n;
+ }
+ else
+ {
+
+ // Detach from previous
+ Assert( p->m_pNext == pEntry );
+ p->m_pNext = n;
+ }
+
+ // Clear pointers
+ pEntry->m_pPrev = pEntry->m_pNext = NULL;
+}
+
+// Insert the item at the head (MRU) slot. The item shouldn't
+// already be in the list
+static void CustomTextureCache_InsertAtHead(SCustomImageCacheEntry *pEntry)
+{
+ Assert( pEntry );
+ Assert( !pEntry->m_pNext );
+ Assert( !pEntry->m_pPrev );
+
+ // Edge case of inserting into empty list
+ if ( mruCustomImageEntry )
+ {
+ Assert( !mruCustomImageEntry->m_pPrev );
+ mruCustomImageEntry->m_pPrev = pEntry;
+ }
+ else
+ {
+ // Inserting into an empty list
+ }
+
+ // Do the head insertion.
+ pEntry->m_pNext = mruCustomImageEntry;
+ mruCustomImageEntry = pEntry;
+}
+
+// Reorder list, setting item at the head (MRU) slot. The item must already
+// be in the list somewhere.
+static void CustomTextureCache_SetMRU(SCustomImageCacheEntry *pEntry)
+{
+ // Note: even if we are already at the head, go through the motions, anyway,
+ // to exercise all of the sanity checking code.
+ CustomTextureCache_Remove(pEntry);
+ CustomTextureCache_InsertAtHead(pEntry);
+}
+
+static SCustomImageCacheEntry *CustomTextureCache_NewEntry()
+{
+
+ SCustomImageCacheEntry *pEntry = new SCustomImageCacheEntry;
+
+ // Go ahead and put us at the head
+ CustomTextureCache_InsertAtHead(pEntry);
+
+ // Return the new entry
+ return pEntry;
+}
+
+static SCustomImageCacheEntry *CustomTextureCache_FindOrAddByCloudId( UGCHandle_t ugcHandle )
+{
+
+ // Locate the bookeeping entry, if one exists
+ int idx = g_mapCustomTextureInfoByCloudId.Find( ugcHandle );
+ SCustomImageCacheEntry *pEntry;
+ if ( g_mapCustomTextureInfoByCloudId.IsValidIndex( idx ) )
+ {
+ pEntry = g_mapCustomTextureInfoByCloudId[idx];
+
+ // We're accessing it, so move it to the head, the MRU slot
+ CustomTextureCache_SetMRU(pEntry);
+ }
+ else
+ {
+ // Grab a new entry
+ pEntry = CustomTextureCache_NewEntry();
+
+ // Assign the cloud ID
+ pEntry->m_hCloudID = ugcHandle;
+
+ // Add it to the map by cloud ID
+ idx = g_mapCustomTextureInfoByCloudId.Insert( ugcHandle );
+ g_mapCustomTextureInfoByCloudId[idx] = pEntry;
+ }
+
+ // Return the entry
+ return pEntry;
+}
+
+// Locate an entry by hash create a new entry if one doesn't already exist
+static SCustomImageCacheEntry *CustomTextureCache_FindOrAddByDigest( const char *szDigestAscii )
+{
+ Assert( strlen(szDigestAscii) == MD5_DIGEST_LENGTH*2 );
+
+ // Brute-force linear search. This should never be called in time-critical
+ // situations
+ SCustomImageCacheEntry *pEntry = mruCustomImageEntry;
+ while ( pEntry )
+ {
+
+ // Match?
+ if ( !Q_stricmp(pEntry->m_szDigestAscii, szDigestAscii) )
+ {
+
+ // Found. Se at MRU and return it.
+ CustomTextureCache_SetMRU(pEntry);
+ return pEntry;
+ }
+
+ // Keep looking
+ pEntry = pEntry->m_pNext;
+ }
+
+ // Not found. Make a new entry
+ pEntry = CustomTextureCache_NewEntry();
+ V_strcpy_safe(pEntry->m_szDigestAscii, szDigestAscii);
+ return pEntry;
+}
+
+//-----------------------------------------------------------------------------
+int GetCustomTextureGuiHandle( uint64 hCloudId )
+{
+
+ // Find or create the entry
+ SCustomImageCacheEntry *pEntry = CustomTextureCache_FindOrAddByCloudId( hCloudId );
+
+ // Poll entry and return GUI handle if it's finally ready
+ return pEntry->GetGuiHandle();
+}
+
+//-----------------------------------------------------------------------------
+
+class CCustomTextureOnItemProxy : public IMaterialProxy
+{
+public:
+ CCustomTextureOnItemProxy();
+ virtual ~CCustomTextureOnItemProxy();
+
+ virtual bool Init( IMaterial* pMaterial, KeyValues *pKeyValues );
+ virtual void OnBind( void *pC_BaseEntity );
+ virtual void Release();
+ virtual IMaterial *GetMaterial();
+
+protected:
+ virtual void OnBindInternal( CEconItemView *pScriptItem );
+
+private:
+ IMaterialVar *m_pBaseTextureVar;
+ ITexture *m_pOriginalTexture;
+};
+
+EXPOSE_INTERFACE( CCustomTextureOnItemProxy, IMaterialProxy, "CustomSteamImageOnModel" IMATERIAL_PROXY_INTERFACE_VERSION );
+
+CCustomTextureOnItemProxy::CCustomTextureOnItemProxy()
+: m_pBaseTextureVar( NULL )
+, m_pOriginalTexture( NULL )
+{
+
+}
+
+CCustomTextureOnItemProxy::~CCustomTextureOnItemProxy()
+{
+}
+
+bool CCustomTextureOnItemProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
+{
+ Release();
+
+ bool found = false;
+ m_pBaseTextureVar = pMaterial->FindVar( "$basetexture", &found );
+ if ( !found )
+ {
+ return false;
+ }
+
+ // No! Don't do this until, because the material/texture might
+ // not have been cached. If we call this, it causes the material
+ // to try to get cached, but instead of loading the texture
+ // synchronously, it just goes into a queue, and we get the error
+ // texture instead. We'll just defer it until later when we know
+ // for sure that everything is ready to go.
+ //m_pOriginalTexture = m_pBaseTextureVar->GetTextureValue();
+ //if ( m_pOriginalTexture )
+ //{
+ // m_pOriginalTexture->AddRef();
+ //}
+ return true;
+}
+
+void CCustomTextureOnItemProxy::OnBind( void *pC_BaseEntity )
+{
+ if ( pC_BaseEntity )
+ {
+ CEconItemView *pScriptItem = NULL;
+ IClientRenderable *pRend = (IClientRenderable *)pC_BaseEntity;
+ C_BaseEntity *pEntity = pRend->GetIClientUnknown()->GetBaseEntity();
+ if ( pEntity )
+ {
+ CEconEntity *pItem = dynamic_cast< CEconEntity* >( pEntity );
+ if ( pItem )
+ {
+ pScriptItem = pItem->GetAttributeContainer()->GetItem();
+ }
+ }
+ else
+ {
+ // Proxy data can be a script created item itself, if we're in a vgui CModelPanel
+ pScriptItem = dynamic_cast< CEconItemView* >( pRend );
+ }
+ if ( pScriptItem )
+ {
+ OnBindInternal( pScriptItem );
+ }
+ }
+}
+
+void CCustomTextureOnItemProxy::Release()
+{
+ if ( m_pOriginalTexture )
+ {
+ m_pOriginalTexture->Release();
+ m_pOriginalTexture = NULL;
+ }
+}
+
+IMaterial *CCustomTextureOnItemProxy::GetMaterial()
+{
+ return m_pBaseTextureVar->GetOwningMaterial();
+}
+
+void CCustomTextureOnItemProxy::OnBindInternal( CEconItemView *pScriptItem )
+{
+ if ( !m_pBaseTextureVar || !m_pBaseTextureVar->IsTexture() )
+ {
+ return;
+ }
+
+ // Snag the original texture object the first time.
+ // And make sure we're 100% ready to go.
+ if ( m_pOriginalTexture == NULL )
+ {
+ m_pOriginalTexture = m_pBaseTextureVar->GetTextureValue();
+ if ( m_pOriginalTexture == NULL )
+ {
+ return;
+ }
+ if ( m_pOriginalTexture->IsError() )
+ {
+ m_pOriginalTexture = NULL;
+ return;
+ }
+
+ // Success! Let's hang on to this guy
+ m_pOriginalTexture->AddRef();
+ }
+ ITexture *texture = m_pOriginalTexture;
+
+ // Fetch the UGC handle from the item
+ UGCHandle_t ugcHandle = pScriptItem->GetCustomUserTextureID();
+
+ // Are we in a preview window?
+ if ( pScriptItem == g_pPreviewEconItem ) // !KLUDGE!
+ {
+ Assert( g_pPreviewCustomTexture );
+ if ( g_pPreviewCustomTexture )
+ {
+ texture = g_pPreviewCustomTexture;
+
+ // Re-fetch the bits if necessary
+ if ( g_pPreviewCustomTextureDirty )
+ {
+ g_pPreviewCustomTexture->Download();
+ Assert( !g_pPreviewCustomTextureDirty );
+ }
+ }
+ }
+ else if (ugcHandle != 0)
+ {
+
+ SCustomImageCacheEntry *pEntry = CustomTextureCache_FindOrAddByCloudId(ugcHandle);
+ pEntry->Poll();
+ texture = pEntry->m_pTexture; // might be NULL if texture isn't ready yet
+ }
+
+ if ( texture )
+ {
+ m_pBaseTextureVar->SetTextureValue( texture );
+ }
+
+ if ( ToolsEnabled() )
+ {
+ ToolFramework_RecordMaterialParams( GetMaterial() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// The custom texture cache needs to init/shutdown and get some frame ticking
+//-----------------------------------------------------------------------------
+class CCustomTextureToolCache : public CBaseGameSystemPerFrame
+{
+public:
+ CCustomTextureToolCache() {}
+ virtual ~CCustomTextureToolCache() {}
+
+ //
+ // CAutoGameSystemPerFrame overrides
+ //
+ virtual char const *Name()
+ {
+ return "CCustomTextureToolCache";
+ }
+ virtual bool Init()
+ {
+ return true;
+ }
+
+ virtual void Shutdown()
+ {
+
+ // Destroy all the cache entries
+ SCustomImageCacheEntry *pEntry = mruCustomImageEntry;
+ mruCustomImageEntry = NULL;
+ while ( pEntry != NULL )
+ {
+ SCustomImageCacheEntry *pNext = pEntry->m_pNext;
+ delete pEntry;
+ pEntry = pNext;
+ }
+ }
+
+ // At level shutdown, release all of our GPU resources.
+ // We'll still hang on to the bitmap data, since it isn't
+ // that large and is in regular virtual memory which is easily
+ // swapped out if stale. But the video RAM we want to be more
+ // agressive at cleaning out.
+ virtual void LevelShutdownPreEntity()
+ {
+ // Destroy all the cache entries
+ for ( SCustomImageCacheEntry *pEntry = mruCustomImageEntry ; pEntry ; pEntry = pEntry->m_pNext )
+ {
+ pEntry->ReleaseResources();
+ }
+ }
+
+// CAutoGameSystemPerFrame defines different stuff depending on which DLL we're building
+#ifdef CLIENT_DLL
+
+ // Do our frame-time processing after rendering
+ virtual void PostRender()
+ {
+ // !FIXME! Here's where we should scan the list and eject
+ // entries that haven't been used recently to limit the
+ // hardware resources we're using.
+ }
+#else
+ // This file shouldn't be compiled outside of client.dll. Right?
+ #error "Say what?"
+#endif
+};
+
+static CCustomTextureToolCache s_CustomTextureToolCache;
+IGameSystem *CustomTextureToolCacheGameSystem()
+{
+ return &s_CustomTextureToolCache;
+}
+
+
+CApplyCustomTextureJob::CApplyCustomTextureJob( itemid_t nToolItemID, itemid_t nSubjectItemID, const void *pPNGData, int nPNGDataBytes )
+: GCSDK::CGCClientJob( GCClientSystem()->GetGCClient() )
+, m_nToolItemID( nToolItemID )
+, m_nSubjectItemID( nSubjectItemID )
+, m_hCloudID( 0 )
+{
+ m_chRemoteStorageName[0] = '\0';
+ m_bufPNGData.Put( pPNGData, nPNGDataBytes );
+}
+
+bool CApplyCustomTextureJob::BYieldingRunGCJob()
+{
+ YieldingRunJob();
+ CleanUp();
+ return true;
+}
+
+void CApplyCustomTextureJob::CleanUp()
+{
+ // If we had a cloud file, delete it from the logical
+ // cloud filespace. We are using the cloud system really just
+ // to get the file into the UGC system and get a handle to it.
+ // But once it's up there, it really isn't this user. They will
+ // fetch it by UGC handle just like any other user. They paid
+ // for this action, and we don't want it taking up any of their
+ // quota.
+ ISteamRemoteStorage *pRemoteStorage = GetISteamRemoteStorage();
+ if ( pRemoteStorage && m_chRemoteStorageName[0] != '\0' )
+ {
+ pRemoteStorage->FileDelete( m_chRemoteStorageName );
+ }
+}
+
+EResult CApplyCustomTextureJob::YieldingRunJob()
+{
+ EResult result = YieldingFindFileIncacheOrUploadFileToCDN();
+ if ( result != k_EResultOK )
+ {
+ return result;
+ }
+ Assert( m_hCloudID != 0 );
+
+ result = YieldingApplyTool();
+ if ( result != k_EResultOK )
+ {
+ return result;
+ }
+
+ // OK!
+ return k_EResultOK;
+}
+
+EResult CApplyCustomTextureJob::YieldingFindFileIncacheOrUploadFileToCDN()
+{
+
+ int nFileSize = m_bufPNGData.TellPut();
+ Assert( nFileSize <= k_nMaxCustomImageFileSize ); // what the heck is out image converter doing?!
+
+ // Generate the hash
+ char szDigestAscii[ MD5_DIGEST_LENGTH*2 + 4];
+ CalcMD5Ascii( szDigestAscii, m_bufPNGData.Base(), nFileSize );
+
+ // Find or create an existing cache entry
+ SCustomImageCacheEntry *pSelectedCacheEntry = CustomTextureCache_FindOrAddByDigest( szDigestAscii );
+
+ KeyValuesAD pkvMruFile( "StampedItems" );
+
+ {
+ // Load up list of images recently used and uploaded
+ CUtlBuffer listFileData;
+ listFileData.SetBufferType( true, true );
+ if ( BReadSteamRemoteFileToBuffer( listFileData, k_szCustomTextureRecentListFilename ) )
+ {
+ if ( !pkvMruFile->LoadFromBuffer( k_szCustomTextureRecentListFilename, listFileData ) )
+ {
+ pkvMruFile->Clear();
+ }
+ }
+ }
+ KeyValues *pkvMruUploadedImages = pkvMruFile->FindKey( "Uploaded", true );
+
+ // !FIXME! Check for duplicates!
+
+ // Make sure we are ready
+ Assert( pSelectedCacheEntry != NULL );
+ Assert( pSelectedCacheEntry->m_hCloudID == 0 );
+ Assert( strlen(pSelectedCacheEntry->m_szDigestAscii) == MD5_DIGEST_LENGTH*2 );
+ Assert( m_bufPNGData.TellPut() > 0 );
+
+ // Generate filename in the cloud file space. Each user has their own
+ // namespace, and Phil requested that we keep the filenames simple
+ // and easily optimizeable by string table. (I.e. don't use the
+ // hash or something else)
+ //
+ // We *could* just always use the same filename, and each file would
+ // be its own "version." But that doesn't seem to be the proper
+ // spirit of the cloud system. So I'll just use a simple integer name,
+ // based on how many images they have uploaded. It isn't critical what
+ // this logical filename is, because once the GC gets a message to tag the file,
+ // that UGC ID should always refer to that version of the file and can never
+ // be changed or deleted, even if we reuse the filename.
+ int iFileIndex = 1;
+ KeyValues *pKey;
+ for ( pKey = pkvMruUploadedImages->GetFirstTrueSubKey() ; pKey ; pKey = pKey->GetNextTrueSubKey() )
+ {
+ int index = atoi(pKey->GetName());
+ iFileIndex = MAX( iFileIndex, index+1 );
+ }
+ Q_snprintf( m_chRemoteStorageName, sizeof( m_chRemoteStorageName ), "my_custom_images/%d.png", iFileIndex );
+
+ // Write the local copy of the file
+ Msg( "Saving %s to cloud....\n", m_chRemoteStorageName );
+ ISteamRemoteStorage *pRemoteStorage = GetISteamRemoteStorage();
+ if ( !pRemoteStorage || !pRemoteStorage->FileWrite( m_chRemoteStorageName, m_bufPNGData.Base(), m_bufPNGData.TellPut() ) )
+ {
+ Warning( "Failed to save local copy of custom image %s\n", m_chRemoteStorageName);
+ return k_EResultFail;
+ }
+
+ // Share it. This initiates the upload to cloud
+ Msg( "Starting upload of %s to UFS....\n", m_chRemoteStorageName );
+ SteamAPICall_t hFileShareApiCall = pRemoteStorage->FileShare( m_chRemoteStorageName );
+ if ( hFileShareApiCall == k_uAPICallInvalid )
+ {
+ return k_EResultFail;
+ }
+
+ bool bFailed;
+ RemoteStorageFileShareResult_t shareResult;
+ while ( !steamapicontext->SteamUtils()->GetAPICallResult(hFileShareApiCall,
+ &shareResult, sizeof(shareResult), RemoteStorageFileShareResult_t::k_iCallback, &bFailed) )
+ {
+ BYield();
+ }
+
+ if ( bFailed || shareResult.m_eResult != k_EResultOK )
+ {
+ Warning( "Custom texture uploaded to cloud FAILED\n" );
+ return k_EResultFail;
+ }
+
+ Msg( "Custom texture uploaded to cloud completed OK, assigned UGC ID %08X%08X\n", (uint32)(shareResult.m_hFile >> 32), (uint32)(shareResult.m_hFile) );
+
+ // Remember the handle to the cloud file
+ m_hCloudID = pSelectedCacheEntry->m_hCloudID = shareResult.m_hFile;
+
+ // Update the MRU list
+ pKey = pkvMruUploadedImages->GetFirstTrueSubKey();
+ while ( pKey )
+ {
+ int index = atoi(pKey->GetName());
+ int mruValue = pKey->GetInt( "mru", 0 );
+ const char *entryDigsetAscii = pKey->GetString("md5", "");
+ UGCHandle_t ugcID = pKey->GetUint64( "ugcid", 0);
+ if ( index <= 0 || mruValue <= 0 || strlen(entryDigsetAscii) != MD5_DIGEST_LENGTH*2 || ugcID == 0 )
+ {
+ // Bah! Bogus data!
+ Assert(false);
+ continue;
+ }
+
+ // Is this the one they selected?
+ if ( ugcID == pSelectedCacheEntry->m_hCloudID )
+ {
+
+ // This *can* happen if the list file gets lost and they reuse an image. It means we are wasting
+ // some of their cloud quota, but should be rare, and it's harmless.
+ Assert( !Q_stricmp(entryDigsetAscii, pSelectedCacheEntry->m_szDigestAscii) );
+ break;
+ }
+
+ pKey = pKey->GetNextTrueSubKey();
+ }
+
+ // Found it?
+ int oldIndex = 0x7fffffff;
+ if ( pKey )
+ {
+
+ // Renumber them in MRU order
+ oldIndex = pKey->GetInt( "mru", 1 );
+ }
+ else
+ {
+
+ // Create a new key
+ pKey = pkvMruUploadedImages->CreateNewKey();
+
+ // Remember hash and cloud file location in subkeys
+ pKey->SetString( "md5", pSelectedCacheEntry->m_szDigestAscii );
+ pKey->SetUint64( "ugcid", pSelectedCacheEntry->m_hCloudID );
+ //pKey->SetString( "remoteStorageName", m_chSelectedRemoteStorageNameBase );
+ }
+ for ( KeyValues *p = pkvMruUploadedImages->GetFirstTrueSubKey() ; p ; p = p->GetNextTrueSubKey() )
+ {
+ if ( p != pKey )
+ {
+ int mruValue = p->GetInt( "mru", 0 );
+ Assert( mruValue > 0 );
+ if (mruValue < oldIndex)
+ {
+ p->SetInt( "mru", mruValue+1 );
+ }
+ }
+ }
+
+ pKey->SetInt( "mru", 1);
+
+ // Re-save the cloud-backed MRU list file
+ Msg( "Saving MRU list file %s\n", k_szCustomTextureRecentListFilename );
+ if ( pRemoteStorage )
+ {
+ CUtlBuffer listFileData;
+ listFileData.SetBufferType( true, true );
+ pkvMruFile->RecursiveSaveToFile( listFileData, 0 );
+ pRemoteStorage->FileWrite( k_szCustomTextureRecentListFilename, listFileData.Base(), listFileData.TellPut() );
+ }
+
+ return k_EResultOK;
+}
+
+EResult CApplyCustomTextureJob::YieldingApplyTool()
+{
+
+ Msg( "Sending tool request to GC.\n" );
+
+ // At this point, we need to know the cloud ID and hash of the image we are applying
+ Assert( m_hCloudID != 0 );
+
+ // Send the message to the GC
+ GCSDK::CGCMsg< MsgGCCustomizeItemTexture_t > msg( k_EMsgGCCustomizeItemTexture );
+ msg.Body().m_unToolItemID = m_nToolItemID;
+ msg.Body().m_unSubjectItemID = m_nSubjectItemID;
+ msg.Body().m_unImageUGCHandle = m_hCloudID;
+
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msgReply;
+ if ( !BYldSendMessageAndGetReply( msg, 10, &msgReply, k_EMsgGCCustomizeItemTextureResponse ) )
+ {
+ Warning( "Customize texture tool failed: Did not get reply from GC\n" );
+ return k_EResultTimeout;
+ }
+
+ // OK!
+ InventoryManager()->ShowItemsPickedUp( true );
+ return k_EResultOK;
+};
+
diff --git a/game/client/econ/tool_items/custom_texture_cache.h b/game/client/econ/tool_items/custom_texture_cache.h
new file mode 100644
index 0000000..c26b94e
--- /dev/null
+++ b/game/client/econ/tool_items/custom_texture_cache.h
@@ -0,0 +1,83 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef CUSTOM_TEXTURE_CACHE_H
+#define CUSTOM_TEXTURE_CACHE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#ifndef GC_CLIENTSYSTEM_H
+ #include "gc_clientsystem.h"
+#endif
+
+class IGameSystem;
+class ITexture;
+class CEconItemView;
+
+/// Given a UGC cloud ID of a custom image, return a VGUI texture handle
+/// that can be used for drawing. Returns 0 on failure
+int GetCustomTextureGuiHandle( uint64 hCloudId );
+
+//-----------------------------------------------------------------------------
+// Purpose: Job to do the async work of uploading the file to the CDN (if
+// necessary) and sending the tool request message
+//-----------------------------------------------------------------------------
+class CApplyCustomTextureJob : public GCSDK::CGCClientJob
+{
+public:
+
+ CApplyCustomTextureJob( itemid_t nToolItemID, itemid_t nSubjectItemID, const void *pPNGData, int nPNGDataBytes );
+
+protected:
+ char m_chRemoteStorageName[ MAX_PATH ];
+
+ virtual bool BYieldingRunGCJob();
+ virtual EResult YieldingRunJob();
+ virtual EResult YieldingFindFileIncacheOrUploadFileToCDN();
+ virtual EResult YieldingApplyTool();
+
+ /// The file data, in PNG format
+ CUtlBuffer m_bufPNGData;
+
+ /// Item that we are applying the texture onto
+ itemid_t m_nSubjectItemID;
+
+ /// Tool that is being applied and will be consumed
+ itemid_t m_nToolItemID;
+
+ /// Cloud file ID
+ uint64 m_hCloudID;
+
+private:
+ void CleanUp();
+};
+
+/// get interface to the game system responsible for managing the custom texture cache
+IGameSystem *CustomTextureToolCacheGameSystem();
+
+/// A few internal things that really shouldn't be public
+namespace CustomTextureSystem
+{
+
+/// If we're auditioning (while selecting a file to apply on a model),
+/// what texture should we display?
+extern ITexture *g_pPreviewCustomTexture;
+
+/// What is the econ item that is being auditioned and so should
+/// use the preview texture
+extern CEconItemView *g_pPreviewEconItem;
+
+/// Do we need to update the bits in the rendering system?
+extern bool g_pPreviewCustomTextureDirty;
+
+extern const char k_rchCustomTextureFilterPreviewImageName[];
+extern const char k_rchCustomTextureFilterPreviewTextureName[];
+
+}
+
+#endif // CUSTOM_TEXTURE_CACHE_H
diff --git a/game/client/econ/tool_items/custom_texture_tool.cpp b/game/client/econ/tool_items/custom_texture_tool.cpp
new file mode 100644
index 0000000..7f9e14a
--- /dev/null
+++ b/game/client/econ/tool_items/custom_texture_tool.cpp
@@ -0,0 +1,2766 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+
+// for the tool
+#include "econ_gcmessages.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "tool_items.h"
+#include "imageutils.h"
+#include "econ_ui.h"
+#include "econ_item_inventory.h"
+#include "econ_item_tools.h"
+#include "checksum_md5.h"
+#include "gc_clientsystem.h"
+#include "materialsystem/itexture.h"
+#include "pixelwriter.h"
+
+#include "filesystem.h"
+
+// for UI
+#include "confirm_dialog.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/FileOpenDialog.h"
+#include "vgui_controls/ImagePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui_controls/RadioButton.h"
+#include "vgui_controls/ComboBox.h"
+#include "vgui_controls/Slider.h"
+#include "vgui/Cursor.h"
+#include "vgui/IInput.h"
+#include "vgui/ISurface.h"
+#include "vgui/IImage.h"
+#include "vgui/IBorder.h"
+#include "VGuiMatSurface/IMatSystemSurface.h"
+#include "bitmap/tgawriter.h"
+#include "bitmap/bitmap.h"
+#include "vgui_bitmappanel.h"
+#include "tool_items/custom_texture_cache.h"
+#include "util_shared.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+using namespace CustomTextureSystem;
+
+
+// Turn this on to run the filters on a bunch of test images when the dialog is opened
+//#define TEST_FILTERS
+
+#define DEFINE_BLEND(code) \
+ for (int y = 0 ; y < imgSource.Height() ; ++y ) \
+ { \
+ for (int x = 0 ; x < imgSource.Width() ; ++x ) \
+ { \
+ Color sc = imgSource.GetColor( x,y ); \
+ Color dc = imgDest.GetColor( x,y ); \
+ float sr = (float)sc.r()/255.0f, sg = (float)sc.g()/255.0f, sb = (float)sc.b()/255.0f, sa = (float)sc.a()/255.0f; \
+ float dr = (float)dc.r()/255.0f, dg = (float)dc.g()/255.0f, db = (float)dc.b()/255.0f, da = (float)dc.a()/255.0f; \
+ float blendPct = sa * flOpacity; \
+ code \
+ imgDest.SetColor( x,y, FloatRGBAToColor( dr*255.0f, dg*255.0f, db*255.0f, da*255.0f ) ); \
+ } \
+ }
+
+static void DoNormalBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity )
+{
+ DEFINE_BLEND(
+ dr += (sr - dr) * blendPct;
+ dg += (sg - dg) * blendPct;
+ db += (sb - db) * blendPct;
+ )
+}
+
+static void DoMultiplyBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity )
+{
+ DEFINE_BLEND(
+ dr += (dr*sr - dr) * blendPct;
+ dg += (dg*sg - dg) * blendPct;
+ db += (db*sb - db) * blendPct;
+ )
+}
+
+static inline float screen( float a, float b )
+{
+ return 1.0f - (1.0f-a)*(1.0f-b);
+}
+
+static void DoScreenBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity )
+{
+ DEFINE_BLEND(
+ dr += (screen(dr,sr) - dr) * blendPct;
+ dg += (screen(dg,sg) - dg) * blendPct;
+ db += (screen(db,sb) - db) * blendPct;
+ )
+}
+
+static inline float overlay( float a, float b )
+{
+ if ( a < .5f )
+ {
+ return a * b * 2.0f;
+ }
+ float t = a * 2.0f - 1.0f;
+ return screen( t, b );
+}
+
+static void DoOverlayBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity )
+{
+ DEFINE_BLEND(
+ dr += (overlay(dr,sr) - dr) * blendPct;
+ dg += (overlay(dg,sg) - dg) * blendPct;
+ db += (overlay(db,sb) - db) * blendPct;
+ )
+}
+
+static void DoReplaceAlphaBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity )
+{
+ DEFINE_BLEND(
+ float k = (sr + sb + sg) / 3.0f;
+ da += (k - da) * blendPct;
+ )
+}
+
+// Custom compositing blend operations. Mostly these are direct translations of standard Photoshop operations.
+// For most operations, the source alpha used as per-pixel blend factor (multiplied by the layer opacity), and
+// the dest alpha is just copied
+enum ELayerBlendOp
+{
+ eLayerBlendOp_Invalid, // Debugging placeholder value
+ eLayerBlendOp_Normal, // Regular blend (lerp)
+ eLayerBlendOp_Multiply, // Multiply color channels
+ eLayerBlendOp_Screen, // 1 - (1-A) * (1-B)
+ eLayerBlendOp_Overlay, // Multiply or screen, depending on source
+ eLayerBlendOp_ReplaceAlpha, // Blend the source alpha channel with the greyscale value from the layer. Color channel is not modified
+};
+
+/// A custom compositing step
+struct SDecalBlendLayer
+{
+
+ /// Which operation to perform?
+ ELayerBlendOp eLayerOp;
+
+ /// The image data
+ Bitmap_t m_image;
+
+ /// Opacity multiplier. The full blend color is calculated by performing the blend
+ /// operation ignoring opacity. Then this result is lerped with the dest fragment by
+ /// the effective blend factor. The effective per-pixel blend factor is taken as the
+ /// source alpha times this value.
+ float m_fLayerOpacity;
+
+ /// Parse from keyvalues.
+ bool FromKV( KeyValues *pkvLayerBlock, CUtlString &errMsg )
+ {
+ const char *op = pkvLayerBlock->GetString( "op", "(none)" );
+ if ( !Q_stricmp( op, "normal" ) ) eLayerOp = eLayerBlendOp_Normal;
+ else if ( !Q_stricmp( op, "multiply" ) ) eLayerOp = eLayerBlendOp_Multiply;
+ else if ( !Q_stricmp( op, "screen" ) ) eLayerOp = eLayerBlendOp_Screen;
+ else if ( !Q_stricmp( op, "overlay" ) ) eLayerOp = eLayerBlendOp_Overlay;
+ else if ( !Q_stricmp( op, "ReplaceAlpha" ) ) eLayerOp = eLayerBlendOp_ReplaceAlpha;
+ else
+ {
+ errMsg.Format( "Invalid blend operation '%s'", op );
+ return false;
+ }
+
+ const char *pszImageFilename = pkvLayerBlock->GetString( "image", NULL );
+ if ( pszImageFilename == NULL )
+ {
+ errMsg = "Must specify 'image'";
+ return false;
+ }
+ if ( ImgUtl_LoadBitmap( pszImageFilename, m_image ) != CE_SUCCESS )
+ {
+ errMsg.Format( "Can't load image '%s'", pszImageFilename );
+ return false;
+ }
+
+ m_fLayerOpacity = pkvLayerBlock->GetFloat( "opacity", 1.0f );
+
+ return true;
+ }
+
+ /// Apply the operation
+ void Apply( Bitmap_t &imgDest ) const
+ {
+ if ( !m_image.IsValid() || !imgDest.IsValid() || imgDest.Width() != m_image.Width() || imgDest.Height() != m_image.Height() )
+ {
+ Assert( m_image.IsValid() );
+ Assert( imgDest.IsValid() );
+ Assert( imgDest.Width() == m_image.Width() );
+ Assert( imgDest.Height() == m_image.Height() );
+ return;
+ }
+
+ switch ( eLayerOp )
+ {
+ default:
+ case eLayerBlendOp_Invalid:
+ Assert( !"Bogus blend op!" );
+ case eLayerBlendOp_Normal:
+ DoNormalBlend( m_image, imgDest, m_fLayerOpacity );
+ break;
+ case eLayerBlendOp_Multiply:
+ DoMultiplyBlend( m_image, imgDest, m_fLayerOpacity );
+ break;
+ case eLayerBlendOp_Screen:
+ DoScreenBlend( m_image, imgDest, m_fLayerOpacity );
+ break;
+ case eLayerBlendOp_Overlay:
+ DoOverlayBlend( m_image, imgDest, m_fLayerOpacity );
+ break;
+ case eLayerBlendOp_ReplaceAlpha:
+ DoReplaceAlphaBlend( m_image, imgDest, m_fLayerOpacity );
+ break;
+ }
+ }
+};
+
+// Note: uses a non-linear non-perceptual color space. But it will be good enough,
+// probably
+inline int ApproxColorDistSq( const Color &a, const Color &b )
+{
+ int dr = (int)a.r() - (int)b.r();
+ int dg = (int)a.g() - (int)b.g();
+ int db = (int)a.b() - (int)b.b();
+ return dr*dr + dg*dg + db*db;
+}
+
+// Return cheesy color distance calculation, approximately normalized from 0...1
+inline float ApproxColorDist( const Color &a, const Color &b )
+{
+ return sqrt( (float)ApproxColorDistSq( a, b ) ) * ( 1.0f / 441.67f );
+}
+
+// Convert linear RGB -> XYZ color space.
+Vector LinearRGBToXYZ( const Vector &rgb )
+{
+
+ // http://en.wikipedia.org/wiki/SRGB
+ Vector xyz;
+ xyz.x = rgb.x * 0.4124 + rgb.y*0.3576 + rgb.z*0.1805;
+ xyz.y = rgb.x * 0.2126 + rgb.y*0.7152 + rgb.z*0.0722;
+ xyz.z = rgb.x * 0.0193 + rgb.y*0.1192 + rgb.z*0.9505;
+ return xyz;
+}
+
+inline float lab_f( float t )
+{
+ if ( t > (6.0/29.0)*(6.0/29.0)*(6.0/29.0) )
+ {
+ return pow( t, .333333f );
+ }
+ return ( (1.0f/3.0f) * (29.0f/6.0f) * (29.0f/6.0f) ) * t + (4.0f/29.0f);
+}
+
+// Convert CIE XYZ -> L*a*b*
+Vector XYZToLab( const Vector &xyz )
+{
+
+ // http://en.wikipedia.org/wiki/Lab_color_space
+ const float X_n = 0.9505;
+ const float Y_n = 1.0000;
+ const float Z_n = 1.0890;
+
+ float f_X = lab_f( xyz.x / X_n );
+ float f_Y = lab_f( xyz.y / Y_n );
+ float f_Z = lab_f( xyz.z / Z_n );
+
+ Vector lab;
+ lab.x = 116.0f*f_Y - 16.0f; // L*
+ lab.y = 500.0f * ( f_X - f_Y ); // a*
+ lab.z = 200.0f * ( f_Y - f_Z ); // b*
+ return lab;
+}
+
+// Convert texture-space RGB values to linear RGB space
+Vector TextureToLinearRGB( Color c )
+{
+ Vector rgb;
+ rgb.x = SrgbGammaToLinear( (float)c.r() / 255.0f );
+ rgb.y = SrgbGammaToLinear( (float)c.g() / 255.0f );
+ rgb.z = SrgbGammaToLinear( (float)c.b() / 255.0f );
+ return rgb;
+}
+
+// Convert texture-space RGB values to perceptually linear L*a*b* space
+Vector TextureToLab( Color c )
+{
+ Vector linearRGB = TextureToLinearRGB( c );
+ Vector xyz = LinearRGBToXYZ( linearRGB );
+ return XYZToLab( xyz );
+}
+
+static void SymmetricNearestNeighborFilter( const Bitmap_t &imgSrc, Bitmap_t &imgDest, int radius, float amount = 1.0f )
+{
+
+ // Make sure image is allocated properly
+ int nWidth = imgSrc.Width();
+ int nHeight = imgSrc.Height();
+ imgDest.Init( nWidth, nHeight, IMAGE_FORMAT_RGBA8888 );
+
+ float flWeightBias = (2 + radius + radius );
+ int filteredBlendWeight = int(amount * 256.0f);
+ int originalBlendWeight = 256 - filteredBlendWeight;
+
+ // For each dest pixel
+ for ( int y = 0 ; y < nHeight ; ++y )
+ {
+ for ( int x = 0 ; x < nWidth ; ++x )
+ {
+ Color c = imgSrc.GetColor( x, y );
+
+ // Iterate over half of the kernel. (Doesn't matter which half.)
+ // Kernel pixels are examined in opposing pairs
+ Vector4D sum(0,0,0,0);
+ float flTotalWeight = 0.0f;
+ for (int ry = 0 ; ry <= radius ; ++ry )
+ {
+ int sy1 = clamp(y + ry, 0, nHeight-1);
+ int sy2 = clamp(y - ry, 0, nHeight-1);
+ for (int rx = (ry == 0) ? 0 : -radius ; rx <= radius ; ++rx )
+ {
+ int sx1 = clamp(x + rx, 0, nWidth-1);
+ int sx2 = clamp(x - rx, 0, nWidth-1);
+
+ Color s1 = imgSrc.GetColor( sx1, sy1 );
+ Color s2 = imgSrc.GetColor( sx2, sy2 );
+
+ // Calculate difference. Here, maybe we should be using
+ // a perceptual difference in linear color space. Who cares.
+ int d1 = ApproxColorDistSq( c, s1 );
+ int d2 = ApproxColorDistSq( c, s2 );
+
+ float weight = flWeightBias - fabs((float)ry) - fabs((float)rx);
+ if ( d1 < d2 )
+ {
+ sum.x += (float)s1.r() * weight;
+ sum.y += (float)s1.g() * weight;
+ sum.z += (float)s1.b() * weight;
+ sum.w += (float)s1.a() * weight;
+ }
+ else
+ {
+ sum.x += (float)s2.r() * weight;
+ sum.y += (float)s2.g() * weight;
+ sum.z += (float)s2.b() * weight;
+ sum.w += (float)s2.a() * weight;
+ }
+ flTotalWeight += weight;
+ }
+ }
+
+ sum /= flTotalWeight;
+ int filterR = (int)clamp(sum.x, 0.0f, 255.0f);
+ int filterG = (int)clamp(sum.y, 0.0f, 255.0f);
+ int filterB = (int)clamp(sum.z, 0.0f, 255.0f);
+ int filterA = (int)clamp(sum.w, 0.0f, 255.0f);
+ Color result(
+ (filterR*filteredBlendWeight + c.r()*originalBlendWeight) >> 8,
+ (filterG*filteredBlendWeight + c.g()*originalBlendWeight) >> 8,
+ (filterB*filteredBlendWeight + c.b()*originalBlendWeight) >> 8,
+ (filterA*filteredBlendWeight + c.a()*originalBlendWeight) >> 8
+ );
+ imgDest.SetColor( x, y, result );
+ }
+ }
+}
+
+static void BilateralFilter( const Bitmap_t &imgSrc, Bitmap_t &imgDest, int radius, float colorDiffThreshold, float amount = 1.0f )
+{
+
+ // Make sure image is allocated properly
+ int nWidth = imgSrc.Width();
+ int nHeight = imgSrc.Height();
+ imgDest.Init( nWidth, nHeight, IMAGE_FORMAT_RGBA8888 );
+
+ float flWeightBias = (2 + radius + radius );
+ int filteredBlendWeight = int(amount * 256.0f);
+ int originalBlendWeight = 256 - filteredBlendWeight;
+
+ // For each dest pixel
+ for ( int y = 0 ; y < nHeight ; ++y )
+ {
+ for ( int x = 0 ; x < nWidth ; ++x )
+ {
+ Color c = imgSrc.GetColor( x, y );
+
+ // Iterate over the kernel
+ Vector4D sum(0,0,0,0);
+ float flTotalWeight = 0.0f;
+ for (int ry = -radius ; ry <= radius ; ++ry )
+ {
+ int sy = clamp(y + ry, 0, nHeight-1);
+ for (int rx = -radius ; rx <= radius ; ++rx )
+ {
+ int sx = clamp(x + rx, 0, nWidth-1);
+
+ Color s = imgSrc.GetColor( sx, sy );
+
+ // Calculate difference. Here, maybe we should be using
+ // a perceptual difference in linear color space. Who cares.
+ float colorDist = ApproxColorDist( c, s );
+
+ // Geometry-based weight
+ float geomWeight = flWeightBias - fabs((float)ry) - fabs((float)rx);
+
+ // Distance-based weight
+ float diffWeight = 1.0f - colorDist - colorDiffThreshold;
+
+ // Total weight
+ float weight = geomWeight * diffWeight;
+ if ( weight > 0.0f )
+ {
+ sum.x += (float)s.r() * weight;
+ sum.y += (float)s.g() * weight;
+ sum.z += (float)s.b() * weight;
+ sum.w += (float)s.a() * weight;
+ flTotalWeight += weight;
+ }
+ }
+ }
+
+ sum /= flTotalWeight;
+ int filterR = (int)clamp(sum.x, 0.0f, 255.0f);
+ int filterG = (int)clamp(sum.y, 0.0f, 255.0f);
+ int filterB = (int)clamp(sum.z, 0.0f, 255.0f);
+ int filterA = (int)clamp(sum.w, 0.0f, 255.0f);
+ Color result(
+ (filterR*filteredBlendWeight + c.r()*originalBlendWeight) >> 8,
+ (filterG*filteredBlendWeight + c.g()*originalBlendWeight) >> 8,
+ (filterB*filteredBlendWeight + c.b()*originalBlendWeight) >> 8,
+ (filterA*filteredBlendWeight + c.a()*originalBlendWeight) >> 8
+ );
+ imgDest.SetColor( x, y, result );
+ }
+ }
+}
+
+// Scan image and replace each pixel with the closest matching swatch
+static void ColorReplace( const Bitmap_t &imgSrc, Bitmap_t &imgDest, int nSwatchCount, const Color *pSwatchList, float amount = 1.0f, const float *pSwatchWeightList = NULL )
+{
+ Assert( nSwatchCount >= 1 );
+
+ CUtlVector<Vector> swatchLab;
+ for ( int i = 0 ; i < nSwatchCount ; ++i )
+ {
+ swatchLab.AddToTail( TextureToLab( pSwatchList[i] ) );
+ }
+
+ // Make sure image is allocated properly
+ int nWidth = imgSrc.Width();
+ int nHeight = imgSrc.Height();
+ imgDest.Init( nWidth, nHeight, IMAGE_FORMAT_RGBA8888 );
+
+ CUtlVector<float> vecDistScale;
+ if ( pSwatchWeightList )
+ {
+ float total = 0.0f;
+ for (int i = 0 ; i < nSwatchCount ; ++i)
+ {
+ total += pSwatchWeightList[i];
+ }
+ total *= 1.05f;
+ for (int i = 0 ; i < nSwatchCount ; ++i)
+ {
+ vecDistScale.AddToTail( total - pSwatchWeightList[i] );
+ }
+ }
+ else
+ {
+ for (int i = 0 ; i < nSwatchCount ; ++i)
+ {
+ vecDistScale.AddToTail( 1.0f );
+ }
+ }
+
+ // For each dest pixel
+ for ( int y = 0 ; y < nHeight ; ++y )
+ {
+ for ( int x = 0 ; x < nWidth ; ++x )
+ {
+ // Fetch source color
+ Color c = imgSrc.GetColor( x, y );
+ Vector lab = TextureToLab( c );
+
+ // Search for the closest matching swatch in the palette
+ Color closestSwatchColor = pSwatchList[0];
+ //int bestDist = ApproxColorDistSq( c, closestSwatchColor );
+ float bestDist = lab.DistTo( swatchLab[0] ) * vecDistScale[0];
+ for ( int i = 1 ; i < nSwatchCount ; ++i )
+ {
+ //int dist = ApproxColorDistSq( c, pSwatchList[i] );
+ float dist = lab.DistTo( swatchLab[i] ) * vecDistScale[i];
+ if ( dist < bestDist )
+ {
+ bestDist = dist;
+ closestSwatchColor = pSwatchList[i];
+ }
+ }
+
+ imgDest.SetColor( x, y, LerpColor( c, closestSwatchColor, amount ) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Custom control for the gradient editing
+//-----------------------------------------------------------------------------
+class CustomTextureStencilGradientMapWidget : public vgui::Panel
+{
+ DECLARE_CLASS_SIMPLE( CustomTextureStencilGradientMapWidget, vgui::Panel );
+
+public:
+ CustomTextureStencilGradientMapWidget(vgui::Panel *parent, const char *panelName);
+
+ // Slam range count, forcing nobs to be spaced evenly
+ void InitRangeCount( int nRangeCount );
+
+ // Set new number of ranges, attempting to adjust nob positions in a "reasonable" way
+ void AdjustRangeCount( int nRangeCount );
+ int GetRangeCount() const { return m_nRangeCount; }
+ int GetNobCount() const { return m_nRangeCount-1; }
+ int GetNobValue( int nNobIndex ) const; // allows virtual "nobs" at indices -1 and m_nRangeCount
+ void SetNobValue( int nNobIndex, int value ); // clamp to adjacent nobs
+ void SlamNobValue( int nNobIndex, int value ); // force nob to particular value, and don't check it
+ void SetRangeColors( const Color *rColors )
+ {
+ memcpy( m_colRangeColor, rColors, m_nRangeCount*sizeof(m_colRangeColor[0]) );
+ ComputeGradient();
+ }
+
+ /// Convert local x coordinate to value
+ int LocalXToVal( int x, bool bClamp = true );
+
+ /// Convert value to local x coordinate
+ int ValToLocalX( int value, bool bClamp = true );
+
+ enum { k_nMaxRangeCount = 4 };
+
+ virtual void OnCursorMoved(int x, int y);
+ virtual void OnMousePressed(vgui::MouseCode code);
+ virtual void OnMouseDoublePressed(vgui::MouseCode code);
+ virtual void OnMouseReleased(vgui::MouseCode code);
+
+ Color m_colorGradient[ 256 ];
+
+protected:
+ virtual void Paint();
+ virtual void PaintBackground();
+ virtual void ApplySchemeSettings(vgui::IScheme *pScheme);
+
+
+ int m_iDraggedNob; // -1 if none
+ int m_nRangeCount;
+ int m_nNobVal[k_nMaxRangeCount-1];
+ Color m_colRangeColor[k_nMaxRangeCount];
+ int m_iNobSizeX;
+ int m_iNobSizeY;
+ int m_iNobRelPosY;
+ int m_iRibbonSizeY;
+ int m_iRibbonRelPosY;
+ int m_iMinVal;
+ int m_iMaxVal;
+ int m_iNobValCushion; // closest that we allow two nobs to be together
+ int m_iClickOffsetX;
+
+ // size (in intensity values on 255 scale) of transition band centered on nob
+ int m_iTransitionBandSize;
+
+ // The regions between the nobs are not *quite* a sold color. They have a slight
+ // gradient in them. This value control the max difference in the ends of this
+ // gradient
+ int m_iRegionGradientRange;
+
+ Color m_TickColor;
+ Color m_TrackColor;
+
+ Color m_DisabledTextColor1;
+ Color m_DisabledTextColor2;
+
+ vgui::IBorder *_sliderBorder;
+ vgui::IBorder *_insetBorder;
+
+ void SendSliderMovedMessage();
+
+ /// Mouse hit testing. Returns index of the nob under the cursor, or
+ /// -1 if none. Coords are local
+ int HitTest( int x, int y, int &outOffsetX );
+
+ /// Fetch local rectangle for given nob
+ void GetNobRect( int iNobIndex, int &x1, int &y1, int &xs, int &ys );
+
+ void GetColorRibbonRect( int &x1, int &y1, int &xs, int &ys );
+
+ void ComputeSizes();
+ void ComputeGradient();
+
+ bool m_bClickOnRanges;
+};
+
+DECLARE_BUILD_FACTORY( CustomTextureStencilGradientMapWidget );
+
+//-----------------------------------------------------------------------------
+CustomTextureStencilGradientMapWidget::CustomTextureStencilGradientMapWidget(Panel *parent, const char *panelName )
+: Panel(parent, panelName)
+{
+ m_iDraggedNob = -1;
+ m_nRangeCount = 4;
+ m_nNobVal[0] = 64;
+ m_nNobVal[1] = 128;
+ m_nNobVal[2] = 192;
+ m_colRangeColor[0] = Color(183,224,252,255);
+ m_colRangeColor[1] = Color(83,109,205,255);
+ m_colRangeColor[2] = Color(98,48,43,255);
+ m_colRangeColor[3] = Color(234,198,113,255);
+ m_iNobSizeX = 4;
+ m_iNobSizeY = 4;
+ m_iNobRelPosY = 0;
+ m_iRibbonSizeY = 0;
+ m_iRibbonRelPosY = 4;
+ m_iMinVal = 0;
+ m_iMaxVal = 255;
+ m_iNobValCushion = 8;
+ m_iClickOffsetX = 0;
+ m_bClickOnRanges = false;
+
+ m_iTransitionBandSize = 8;
+ m_iRegionGradientRange = 8;
+
+ _sliderBorder = NULL;
+ _insetBorder = NULL;
+
+ //AddActionSignalTarget( parent );
+ SetBlockDragChaining( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Send a message to interested parties when the slider moves
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::SendSliderMovedMessage()
+{
+ // send a changed message
+ KeyValues *pParams = new KeyValues("SliderMoved");
+ pParams->SetPtr( "panel", this );
+ PostActionSignal( pParams );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::ApplySchemeSettings(vgui::IScheme *pScheme)
+{
+ BaseClass::ApplySchemeSettings(pScheme);
+
+ SetFgColor(GetSchemeColor("Slider.NobColor", pScheme));
+ // this line is useful for debugging
+ //SetBgColor(GetSchemeColor("0 0 0 255"));
+
+ m_TickColor = pScheme->GetColor( "Slider.TextColor", GetFgColor() );
+ m_TrackColor = pScheme->GetColor( "Slider.TrackColor", GetFgColor() );
+
+ m_DisabledTextColor1 = pScheme->GetColor( "Slider.DisabledTextColor1", GetFgColor() );
+ m_DisabledTextColor2 = pScheme->GetColor( "Slider.DisabledTextColor2", GetFgColor() );
+
+ _sliderBorder = pScheme->GetBorder("ButtonBorder");
+ _insetBorder = pScheme->GetBorder("ButtonDepressedBorder");
+
+ ComputeSizes();
+ ComputeGradient();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draw everything on screen
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::Paint()
+{
+// DrawTicks();
+//
+// DrawTickLabels();
+//
+// // Draw nob last so it draws over ticks.
+// DrawNob();
+
+ // Draw nobs last
+ for ( int i = 0 ; i < GetNobCount() ; ++i )
+ {
+ int x1, y1, xs, ys;
+ GetNobRect( i, x1, y1, xs, ys );
+
+ Color col = GetFgColor();
+ g_pMatSystemSurface->DrawSetColor(col);
+ g_pMatSystemSurface->DrawFilledRect(
+ x1,
+ y1,
+ x1+xs,
+ y1+ys
+ );
+
+ }
+
+// // border
+// if (_sliderBorder)
+// {
+// _sliderBorder->Paint(
+// _nobPos[0],
+// y + tall / 2 - nobheight / 2,
+// _nobPos[1],
+// y + tall / 2 + nobheight / 2);
+// }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draw the slider track
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::PaintBackground()
+{
+ BaseClass::PaintBackground();
+
+ int x1, y1, xs, ys;
+ GetColorRibbonRect( x1, y1, xs, ys );
+
+ // This is utterly terrible. It could be drawn a LOT more efficiently!
+ for ( int x = 0 ; x < xs ; ++x )
+ {
+ int v = x * 256 / xs;
+ vgui::surface()->DrawSetColor( m_colorGradient[v] );
+ vgui::surface()->DrawFilledRect( x1 + x, y1, x1 + x + 1, y1 + ys );
+ }
+
+// int x, y;
+// int wide,tall;
+//
+// GetTrackRect( x, y, wide, tall );
+//
+// surface()->DrawSetColor( m_TrackColor );
+// surface()->DrawFilledRect( x, y, x + wide, y + tall );
+// if (_insetBorder)
+// {
+// _insetBorder->Paint( x, y, x + wide, y + tall );
+// }
+}
+
+void CustomTextureStencilGradientMapWidget::ComputeGradient()
+{
+
+ struct GradientInterpolationPoint
+ {
+ int m_iVal;
+ Color m_color;
+ };
+
+ GradientInterpolationPoint rGradPoints[ k_nMaxRangeCount * 2 ];
+ int nGradPoints = 0;
+
+ // Put two interpolation points per region.
+ for ( int iRange = 0 ; iRange < m_nRangeCount ; ++iRange )
+ {
+ // Get nob values on either side
+ // of the region
+ int lVal = GetNobValue( iRange-1 );
+ int rVal = GetNobValue( iRange );
+
+ // Push them together slightly, to create a small gradient band
+ // around the nobs
+ int d = rVal - lVal;
+ if ( d > 2 )
+ {
+ int iPush = MIN ( d, m_iTransitionBandSize ) / 2;
+ if ( iRange > 0 )
+ {
+ lVal += iPush;
+ }
+ if ( iRange < m_nRangeCount-1 )
+ {
+ rVal -= iPush;
+ }
+ }
+
+ Color lColor = m_colRangeColor[iRange];
+ Color rColor = m_colRangeColor[iRange];
+ // !FIXME! Nudge color towards neighbors
+
+ // Insert interpolation points
+ Assert( nGradPoints+2 <= ARRAYSIZE( rGradPoints ) );
+ rGradPoints[ nGradPoints ].m_iVal = lVal;
+ rGradPoints[ nGradPoints ].m_color = lColor;
+ ++nGradPoints;
+ rGradPoints[ nGradPoints ].m_iVal = rVal;
+ rGradPoints[ nGradPoints ].m_color = rColor;
+ ++nGradPoints;
+ }
+
+ // Now fill in gradient
+ Assert( m_iMinVal == 0 );
+ Assert( m_iMaxVal == 255 );
+ COMPILE_TIME_ASSERT( ARRAYSIZE( m_colorGradient ) == 256 );
+
+ int iRightIndex = 1; // current interpolation point on right hand side
+ for ( int i = 0 ; i < 256 ; ++i )
+ {
+ while ( i >= rGradPoints[ iRightIndex ].m_iVal && iRightIndex < nGradPoints-1)
+ {
+ ++iRightIndex;
+ }
+ int iLeftIndex = iRightIndex-1;
+ int iLeftVal = rGradPoints[ iLeftIndex ].m_iVal;
+ int iRightVal = rGradPoints[ iRightIndex ].m_iVal;
+ Assert( i >= iLeftVal );
+ Assert( i <= iRightVal );
+
+ Color lColor = rGradPoints[ iLeftIndex ].m_color;
+ Color rColor = rGradPoints[ iRightIndex ].m_color;
+
+ if ( i <= iLeftVal )
+ {
+ m_colorGradient[i] = lColor;
+ }
+ else if ( i >= iRightVal )
+ {
+ m_colorGradient[i] = rColor;
+ }
+ else
+ {
+ float pct = float( i - iLeftVal ) / float( iRightVal - iLeftVal );
+ m_colorGradient[i] = LerpColor( lColor, rColor, pct );
+ }
+ }
+
+}
+
+void CustomTextureStencilGradientMapWidget::ComputeSizes()
+{
+ m_iNobSizeX = 5;
+ int sizeY = GetTall();
+
+ m_iNobSizeY = sizeY * 2 / 5;
+ m_iNobRelPosY = sizeY - m_iNobSizeY;
+
+ m_iRibbonRelPosY = 0;
+ m_iRibbonSizeY = m_iNobRelPosY - 1;
+}
+
+void CustomTextureStencilGradientMapWidget::GetColorRibbonRect( int &x1, int &y1, int &xs, int &ys )
+{
+ int controlSizeX, controlSizeY;
+ GetSize( controlSizeX, controlSizeY );
+
+ x1 = 0;
+ xs = controlSizeX;
+ y1 = m_iRibbonRelPosY;
+ ys = m_iRibbonSizeY;
+}
+
+void CustomTextureStencilGradientMapWidget::GetNobRect( int iNobIndex, int &x1, int &y1, int &xs, int &ys )
+{
+
+ Assert( iNobIndex >= 0 );
+ Assert( iNobIndex < GetNobCount() );
+
+ // Fetch x center position
+ int iNobVal = GetNobValue( iNobIndex );
+ int cx = ValToLocalX( iNobVal );
+
+ int controlSizeX, controlSizeY;
+ GetSize( controlSizeX, controlSizeY );
+
+ x1 = cx - m_iNobSizeX/2;
+ xs = m_iNobSizeX;
+ y1 = m_iNobRelPosY;
+ ys = m_iNobSizeY;
+}
+
+int CustomTextureStencilGradientMapWidget::HitTest( int x, int y, int &outOffsetX )
+{
+ int result = -1;
+ const int k_Tol = 3;
+ int bestDist = k_Tol;
+ outOffsetX = 0;
+ for ( int i = 0 ; i < GetNobCount() ; ++i )
+ {
+ int x1, y1, xs, ys;
+ GetNobRect( i, x1, y1, xs, ys );
+
+ // Reject if too far away on Y
+ if ( !m_bClickOnRanges )
+ {
+ y1 = 0;
+ ys = GetTall();
+ }
+ if ( y < y1-k_Tol ) continue;
+ if ( y > y1+ys+k_Tol) continue;
+
+ // Get horizontal error
+ int d = 0;
+ if ( x < x1 ) d = x1 - x;
+ else if ( x > x1+xs) d = x - (x1+xs);
+
+ // Closest match found so far?
+ if ( d < bestDist )
+ {
+ bestDist = d;
+ result = i;
+ outOffsetX = (x1 + xs/2) - x;
+ }
+ }
+
+ return result;
+}
+
+int CustomTextureStencilGradientMapWidget::ValToLocalX( int value, bool bClamp )
+{
+ int w = GetWide();
+ if ( bClamp )
+ {
+ if ( value < m_iMinVal ) return 0;
+ if ( value >= m_iMaxVal ) return w;
+ }
+
+ int r = m_iMaxVal - m_iMinVal;
+
+ // Don't divide by zero
+ if (r < 1 )
+ {
+ return 0;
+ }
+
+ return ( ( value - m_iMinVal ) * w + (w>>1) ) / r;
+}
+
+int CustomTextureStencilGradientMapWidget::LocalXToVal( int x, bool bClamp )
+{
+ int w = GetWide();
+
+ // Don't divide by zero
+ if (w < 1 )
+ {
+ return m_iMinVal;
+ }
+
+ if ( bClamp )
+ {
+ if ( x < 0 ) return m_iMinVal;
+ if ( x >= w ) return m_iMaxVal;
+ }
+
+ int r = m_iMaxVal - m_iMinVal;
+ return m_iMinVal + ( x * r + (r>>1) ) / w;
+}
+
+int CustomTextureStencilGradientMapWidget::GetNobValue( int nNobIndex ) const
+{
+
+ // Sentinel nob to the left?
+ if ( nNobIndex < 0 )
+ {
+ Assert( nNobIndex == -1 );
+ return m_iMinVal;
+ }
+
+ // Sentinel nob to the right?
+ if ( nNobIndex >= GetNobCount() )
+ {
+ Assert( nNobIndex == GetNobCount() );
+ return m_iMaxVal;
+ }
+
+ return m_nNobVal[ nNobIndex ];
+}
+
+void CustomTextureStencilGradientMapWidget::SetNobValue( int nNobIndex, int value )
+{
+ if ( nNobIndex < 0 || nNobIndex >= GetNobCount() )
+ {
+ Assert( nNobIndex >= 0 );
+ Assert( nNobIndex < GetNobCount() );
+ return;
+ }
+
+ // Get neighboring nob values
+ int iValLeft = GetNobValue( nNobIndex-1 );
+ int iValRight = GetNobValue( nNobIndex+1 );
+ Assert( iValLeft < iValRight );
+
+ // Subtract off the cushion
+ iValLeft += m_iNobValCushion;
+ iValRight -= m_iNobValCushion;
+
+ // No wiggle room?!?!
+ if ( iValLeft > iValRight )
+ {
+ Assert( iValLeft <= iValRight );
+
+ // Do the best we can
+ value = (iValLeft + iValRight) / 2;
+ }
+ else
+ {
+ if ( value < iValLeft )
+ {
+ value = iValLeft;
+ }
+ else if ( value > iValRight )
+ {
+ value = iValRight;
+ }
+ }
+
+ // We've clamped the value --- now slam it in place
+ SlamNobValue( nNobIndex, value );
+}
+
+void CustomTextureStencilGradientMapWidget::InitRangeCount( int nRangeCount )
+{
+ m_nRangeCount = clamp( nRangeCount, 2, k_nMaxRangeCount );
+ for ( int i = 0 ; i < GetNobCount() ; ++i )
+ {
+ SlamNobValue( i, m_iMinVal + ( i + 1 ) * ( m_iMaxVal - m_iMinVal ) / m_nRangeCount );
+ }
+}
+
+void CustomTextureStencilGradientMapWidget::AdjustRangeCount( int nRangeCount )
+{
+ nRangeCount = clamp( nRangeCount, 2, k_nMaxRangeCount );
+ Assert( m_nRangeCount >= 2 );
+
+ int oldNobCount = GetNobCount();
+ int oldRangeCount = m_nRangeCount;
+ m_nRangeCount = nRangeCount;
+ if ( m_nRangeCount < oldRangeCount )
+ {
+ // Removing ranges / nobs. Just need to space existing nobs further apart
+ //
+ // Work from back to front, so we won't
+ // conflict with the safety checks in SetNobValue
+ for ( int i = GetNobCount()-1 ; i >= 0 ; --i )
+ {
+ SetNobValue( i, m_iMinVal + ( GetNobValue( i ) - m_iMinVal ) * oldRangeCount / m_nRangeCount );
+ }
+ }
+ else if ( m_nRangeCount > oldRangeCount )
+ {
+ // Adding ranges / nobs. Compress existing nobs, and add the
+ // new once evenly at the top
+
+ // Slam new nob values to be space evenly in the space at the top
+ for ( int i = oldNobCount ; i < GetNobCount() ; ++i )
+ {
+ SlamNobValue( i, m_iMinVal + ( i + 1 ) * ( m_iMaxVal - m_iMinVal ) / m_nRangeCount );
+ }
+
+ // Work from front to back, so we won't
+ // conflict with the safety checks in SetNobValue
+ for ( int i = 0 ; i < oldNobCount ; ++i )
+ {
+ SetNobValue( i, m_iMinVal + ( GetNobValue( i ) - m_iMinVal ) * oldRangeCount / m_nRangeCount );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::SlamNobValue( int nNobIndex, int value )
+{
+ if ( nNobIndex < 0 || nNobIndex >= GetNobCount() )
+ {
+ Assert( nNobIndex >= 0 );
+ Assert( nNobIndex < GetNobCount() );
+ return;
+ }
+
+ Assert( value >= m_iMinVal );
+ Assert( value <= m_iMaxVal );
+ m_nNobVal[ nNobIndex ] = value;
+ ComputeGradient();
+}
+
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::OnCursorMoved(int x,int y)
+{
+ if( m_iDraggedNob < 0 )
+ {
+ return;
+ }
+
+ g_pVGuiInput->GetCursorPosition( x, y );
+ ScreenToLocal(x,y);
+
+ x += m_iClickOffsetX;
+ int v = LocalXToVal( x, true );
+ SetNobValue( m_iDraggedNob, v );
+
+ Repaint();
+ SendSliderMovedMessage();
+}
+
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::OnMousePressed(vgui::MouseCode code)
+{
+ int x,y;
+
+ if (!IsEnabled())
+ return;
+
+ g_pVGuiInput->GetCursorPosition( x, y );
+
+ ScreenToLocal(x,y);
+ RequestFocus();
+
+ m_iDraggedNob = HitTest( x, y, m_iClickOffsetX );
+
+ if ( m_iDraggedNob >= 0 )
+ {
+ // drag the nob
+ g_pVGuiInput->SetMouseCapture(GetVPanel());
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::OnMouseDoublePressed(vgui::MouseCode code)
+{
+ // Just handle double presses like mouse presses
+ OnMousePressed(code);
+}
+
+
+//-----------------------------------------------------------------------------
+void CustomTextureStencilGradientMapWidget::OnMouseReleased(vgui::MouseCode code)
+{
+
+ if ( m_iDraggedNob >= 0 )
+ {
+ m_iDraggedNob = -1;
+ g_pVGuiInput->SetMouseCapture(null);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: UI to select the custom image and confirm tool application
+//-----------------------------------------------------------------------------
+class CConfirmCustomizeTextureDialog : public CBaseToolUsageDialog, private ITextureRegenerator
+{
+ DECLARE_CLASS_SIMPLE( CConfirmCustomizeTextureDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmCustomizeTextureDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject );
+ virtual ~CConfirmCustomizeTextureDialog( void );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void Apply( void );
+ virtual void OnCommand( const char *command );
+ virtual void OnTick( void );
+
+ void ConversionError( ConversionErrorType nError );
+
+ MESSAGE_FUNC_CHARPTR( OnFileSelected, "FileSelected", fullpath );
+ //MESSAGE_FUNC_PTR( OnRadioButtonChecked, "RadioButtonChecked", panel );
+ MESSAGE_FUNC_PTR( OnTextChanged, "TextChanged", panel ); // send by the filter combo box when it changes
+
+ MESSAGE_FUNC_PTR( OnRadioButtonChecked, "RadioButtonChecked", panel )
+ {
+ if ( eCurrentPage != ePage_SelectImage )
+ {
+ Assert( eCurrentPage == ePage_SelectImage );
+ return;
+ }
+ if ( panel == m_pUseAvatarRadioButton )
+ {
+ if ( !m_bUseAvatar )
+ {
+ UseAvatarImage();
+ }
+ }
+ else if ( panel == m_pUseAnyImageRadioButton )
+ {
+ if ( m_bUseAvatar )
+ {
+ m_imgSource.Clear();
+ m_bUseAvatar = false;
+ }
+ MarkSquareImageDirty();
+ WriteSelectImagePageControls();
+ }
+ else
+ {
+ Assert( false ); // who else is talking to us?
+ }
+ }
+
+ MESSAGE_FUNC_PTR( OnSliderMoved, "SliderMoved", panel )
+ {
+ if ( panel == m_pStencilGradientWidget )
+ {
+ MarkFilteredImageDirty();
+ }
+ else
+ {
+ // What other is talking to us?
+ Assert( false );
+ }
+ }
+
+ void OnImageUploadedToCloud( RemoteStorageFileShareResult_t *pResult, bool bIOFailure );
+
+ void CleanSquareImage()
+ {
+ if ( m_bSquareImageDirty )
+ {
+ PerformSquarize();
+ Assert( !m_bSquareImageDirty );
+ Assert( m_bFilteredImageDirty );
+ }
+ }
+
+ void CleanFilteredImage()
+ {
+ CleanSquareImage();
+ if ( m_bFilteredImageDirty )
+ {
+ PerformFilter();
+ Assert( !m_bFilteredImageDirty );
+ }
+ }
+
+ void CloseWithGenericError();
+
+private:
+
+ struct CroppedImagePanel : public CBitmapPanel {
+
+ CroppedImagePanel( CConfirmCustomizeTextureDialog *pDlg, vgui::Panel *parent )
+ : CBitmapPanel( parent, "PreviewCroppedImage" )
+ , m_pDlg(pDlg)
+ {
+ }
+
+ CConfirmCustomizeTextureDialog *m_pDlg;
+
+ void Paint()
+ {
+ m_pDlg->CleanSquareImage();
+ CBitmapPanel::Paint();
+ }
+ };
+
+ struct FilteredImagePanel : public CBitmapPanel {
+
+ FilteredImagePanel( CConfirmCustomizeTextureDialog *pDlg, vgui::Panel *parent )
+ : CBitmapPanel( parent, "PreviewFilteredImage" )
+ , m_pDlg(pDlg)
+ {
+ }
+
+ CConfirmCustomizeTextureDialog *m_pDlg;
+
+ void Paint()
+ {
+ m_pDlg->CleanFilteredImage();
+ CBitmapPanel::Paint();
+ }
+ };
+
+ vgui::FileOpenDialog *m_hImportImageDialog;
+ CBitmapPanel *m_pFilteredTextureImagePanel;
+ CBitmapPanel *m_pCroppedTextureImagePanel;
+ bool m_bFilteredImageDirty;
+ bool m_bSquareImageDirty;
+ bool m_bStencilShapeReducedImageDirty;
+ bool m_bUseAvatar;
+ bool m_bCropToSquare; // if false, we'll stretch
+ int m_nSelectedStencilPalette;
+ CUtlVector< CUtlVector< Color > > m_vecStencilPalettes;
+ CustomTextureStencilGradientMapWidget *m_pStencilGradientWidget;
+
+ enum EPage
+ {
+ ePage_SelectImage,
+ ePage_AdjustFilter,
+ ePage_FinalConfirm,
+ ePage_PerformingAction,
+
+ k_NumPages
+ };
+ EPage eCurrentPage;
+ void SetPage( EPage page );
+
+ // Page container widgets
+ vgui::EditablePanel *m_rpPagePanel[k_NumPages];
+ CItemModelPanel *m_pItemModelPanel;
+
+ Bitmap_t m_imgSource; // original resolution and aspect
+ Bitmap_t m_imgSquare; // cropped/stretched to square at submitted res
+ Bitmap_t m_imgSquareDisplay; // cropped/stretched to square at final res
+ Bitmap_t m_imgFinal; // final output res
+ Bitmap_t m_imgStencilShapeReduced;
+
+ /// Custom compositing steps defined for this item
+ CUtlVector<SDecalBlendLayer> m_vecBlendLayers;
+
+ inline bool IsSourceImageSquare() const
+ {
+ // We must know the size
+ Assert( m_imgSource.IsValid() );
+ return
+ m_imgSource.Width()*99 < m_imgSource.Height()*100
+ && m_imgSource.Height()*99 < m_imgSource.Width()*100;
+ }
+
+ ITexture *m_pCurrentPreviewedTexture;
+
+ void ActivateFileOpenDialog();
+ void PerformSquarize();
+ void PerformFilter();
+
+ vgui::ComboBox *m_pFilterCombo;
+ vgui::ComboBox *m_pSquarizeCombo;
+ vgui::ComboBox *m_pStencilModeCombo;
+ vgui::RadioButton *m_pUseAvatarRadioButton;
+ vgui::RadioButton *m_pUseAnyImageRadioButton;
+
+ enum EFilter
+ {
+ eFilter_Stencil,
+ eFilter_Identity,
+ eFilter_Painterly,
+ };
+
+ void PerformIdentityFilter();
+ void PerformStencilFilter();
+ void PerformPainterlyFilter();
+
+ // From ITextureRegenerator
+ virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect );
+ virtual void Release();
+
+ void MarkSquareImageDirty()
+ {
+ m_bSquareImageDirty = true;
+ MarkStencilShapeReducedImageDirty();
+ MarkFilteredImageDirty();
+ }
+
+ void MarkStencilShapeReducedImageDirty()
+ {
+ m_bStencilShapeReducedImageDirty = true;
+ MarkFilteredImageDirty();
+ }
+
+ void MarkFilteredImageDirty()
+ {
+ m_bFilteredImageDirty = true;
+ g_pPreviewCustomTextureDirty = true;
+ }
+
+ void ShowFilterControls();
+ void WriteSelectImagePageControls();
+ void UseAvatarImage();
+ void SelectStencilPalette( int nPalette );
+
+ // Test harness, for tweaking various values
+ #ifdef TEST_FILTERS
+ void TestFilters();
+ #endif
+};
+
+CConfirmCustomizeTextureDialog::CConfirmCustomizeTextureDialog( vgui::Panel *parent, CEconItemView *pTool, CEconItemView *pToolSubject )
+: CBaseToolUsageDialog( parent, "ConfirmCustomizeTextureDialog", pTool, pToolSubject )
+, m_hImportImageDialog( NULL )
+, m_bFilteredImageDirty(true)
+, m_bStencilShapeReducedImageDirty(true)
+, m_bSquareImageDirty(true)
+, m_bCropToSquare(false)
+, m_bUseAvatar(true)
+, m_pCurrentPreviewedTexture(NULL)
+, m_pFilterCombo(NULL)
+, m_pSquarizeCombo(NULL)
+, m_pStencilModeCombo(NULL)
+, m_pUseAvatarRadioButton(NULL)
+, m_pUseAnyImageRadioButton(NULL)
+, m_pStencilGradientWidget(NULL)
+, m_nSelectedStencilPalette(-1)
+{
+ // clear so that the preview is accurate
+ Assert( g_pPreviewCustomTexture == NULL );
+ Assert( g_pPreviewEconItem == NULL );
+ eCurrentPage = ePage_SelectImage;
+
+ m_pItemModelPanel = new CItemModelPanel( this, "paint_model" );
+ m_pItemModelPanel->SetItem( pToolSubject );
+ m_pItemModelPanel->SetActAsButton( true, false );
+
+ COMPILE_TIME_ASSERT( k_NumPages == 4 );
+ m_rpPagePanel[ePage_SelectImage] = new vgui::EditablePanel( this, "SelectImagePage" );
+ m_rpPagePanel[ePage_AdjustFilter] = new vgui::EditablePanel( this, "AdjustFilterPage" );
+ m_rpPagePanel[ePage_FinalConfirm] = new vgui::EditablePanel( this, "FinalConfirmPage" );
+ m_rpPagePanel[ePage_PerformingAction] = new vgui::EditablePanel( this, "PerformingActionPage" );
+
+ vgui::EditablePanel *pSelectImagePreviewGroupBox = new vgui::EditablePanel( m_rpPagePanel[ePage_SelectImage], "PreviewImageGroupBox" );
+ m_pCroppedTextureImagePanel = new CroppedImagePanel( this, pSelectImagePreviewGroupBox );
+
+ vgui::EditablePanel *pAdjustFilterPreviewGroupBox = new vgui::EditablePanel( m_rpPagePanel[ePage_AdjustFilter], "PreviewImageGroupBox" );
+ m_pFilteredTextureImagePanel = new FilteredImagePanel( this, pAdjustFilterPreviewGroupBox );
+
+ //
+ // Locate / create the procedoral material & texture to show the
+ // results of the filtered texture
+ //
+
+ ITexture *pPreviewTexture = NULL;
+ if ( g_pMaterialSystem->IsTextureLoaded( k_rchCustomTextureFilterPreviewTextureName ) )
+ {
+ pPreviewTexture = g_pMaterialSystem->FindTexture( k_rchCustomTextureFilterPreviewTextureName, TEXTURE_GROUP_VGUI );
+ pPreviewTexture->AddRef();
+ Assert( pPreviewTexture );
+ }
+ else
+ {
+ pPreviewTexture = g_pMaterialSystem->CreateProceduralTexture(
+ k_rchCustomTextureFilterPreviewTextureName,
+ TEXTURE_GROUP_VGUI,
+ k_nCustomImageSize, k_nCustomImageSize,
+ IMAGE_FORMAT_RGBA8888,
+ TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD
+ );
+ Assert( pPreviewTexture );
+ }
+ pPreviewTexture->SetTextureRegenerator( this ); // note carefully order of operations here. See Release()
+ g_pPreviewCustomTexture = pPreviewTexture;
+ g_pPreviewCustomTextureDirty = true;
+ g_pPreviewEconItem = m_pItemModelPanel->GetItem();
+
+ vgui::ivgui()->AddTickSignal( GetVPanel(), 0 );
+
+ // Parse blend operations from the tool definition KV
+ KeyValues *pkvBlendLayers = pToolSubject->GetDefinitionKey( "custom_texture_blend_steps" );
+ if ( pkvBlendLayers )
+ {
+ for ( KeyValues *kvLayer = pkvBlendLayers->GetFirstTrueSubKey() ; kvLayer ; kvLayer = kvLayer->GetNextTrueSubKey() )
+ {
+ int idx = m_vecBlendLayers.AddToTail();
+ CUtlString sErrMsg;
+ if ( !m_vecBlendLayers[idx].FromKV( kvLayer, sErrMsg ) )
+ {
+ Warning( "Bogus custom texture blend layer definition '%s'. %s\n", kvLayer->GetName(), (const char *)sErrMsg );
+ Assert( !"Bogus custom texture blend layer!" );
+ m_vecBlendLayers.Remove( idx );
+ }
+ }
+ }
+
+ // Setup stencil palettes
+
+ CUtlVector<Color> *pPalette;
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 54, 38, 0, 255 ) );
+ pPalette->AddToTail( Color( 236, 236, 217, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 54, 38, 0, 255 ) );
+ pPalette->AddToTail( Color( 137, 131, 116, 255 ) );
+ pPalette->AddToTail( Color( 236, 236, 217, 255 ) );
+ pPalette->AddToTail( Color( 254, 255, 228, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 186, 80, 34, 255 ) );
+ pPalette->AddToTail( Color( 243, 231, 194, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 186, 80, 34, 255 ) );
+ pPalette->AddToTail( Color( 217, 162, 121, 255 ) );
+ pPalette->AddToTail( Color( 243, 231, 194, 255 ) );
+ pPalette->AddToTail( Color( 255, 247, 220, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 101, 72, 54, 255 ) );
+ pPalette->AddToTail( Color( 229, 150, 73, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 101, 72, 54, 255 ) );
+ pPalette->AddToTail( Color( 161, 100, 47, 255 ) );
+ pPalette->AddToTail( Color( 229, 150, 73, 255 ) );
+ pPalette->AddToTail( Color( 255, 207, 154, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 88, 84, 80, 255 ) );
+ pPalette->AddToTail( Color( 160, 84, 72, 255 ) );
+ pPalette->AddToTail( Color( 216, 212, 192, 255 ) );
+
+ pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ pPalette->AddToTail( Color( 54, 38, 0, 255 ) );
+ pPalette->AddToTail( Color( 163, 110, 0, 255 ) );
+ pPalette->AddToTail( Color( 215, 171, 2, 255 ) );
+ pPalette->AddToTail( Color( 197, 192, 171, 255 ) );
+
+ // !TEST! Import the palettes from an image
+ #if 0
+ {
+ m_vecStencilPalettes.RemoveAll();
+ Bitmap_t imgPal;
+ Assert( ImgUtl_LoadBitmap( "d:/decal_tool_palettes_bay.png", imgPal ) == CE_SUCCESS );
+ const int kSwatchSz = 10;
+ for (int y = kSwatchSz/2 ; y < imgPal.Height() ; y += kSwatchSz )
+ {
+ CUtlVector<Color> palette;
+ for (int x = kSwatchSz/2 ; x < imgPal.Width() ; x += kSwatchSz )
+ {
+ palette.AddToTail( imgPal.GetColor( x, y ) );
+ }
+
+ // Strip off solid white entries from the end. (If these are in the palette,
+ // they have to come first!)
+ while ( palette.Count() > 0 && ApproxColorDistSq( palette[palette.Count()-1], Color(255,255,255,255) ) < 12 )
+ {
+ palette.Remove( palette.Count()-1 );
+ }
+ Assert( palette.Count() != 1 ); // only a single entry in the palette? Should be 0, or at least 2
+ if ( palette.Count() > 1 )
+ {
+ // Reverse the palette, so it is ordered dark -> light.
+ for ( int l = 0, r = palette.Count()-1 ; l < r ; ++l, --r )
+ {
+ Color t = palette[l];
+ palette[l] = palette[r];
+ palette[r] = t;
+ }
+
+ CUtlVector<Color> *pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];
+ Msg( "pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];\n" );
+ for (int j = 0 ; j < palette.Count() ; ++j )
+ {
+ pPalette->AddToTail( palette[j] );
+ Msg( "pPalette->AddToTail( Color( %d, %d, %d, 255 ) );\n", palette[j].r(), palette[j].g(), palette[j].b() );
+ }
+ Msg( "\n" );
+ }
+ }
+ }
+ #endif
+
+}
+
+CConfirmCustomizeTextureDialog::~CConfirmCustomizeTextureDialog( void )
+{
+
+ // Clean up filtered texture
+ Release();
+
+ delete m_hImportImageDialog;
+ m_hImportImageDialog = NULL;
+}
+
+void CConfirmCustomizeTextureDialog::SetPage( EPage page )
+{
+ eCurrentPage = page;
+ switch ( eCurrentPage )
+ {
+ default:
+ Assert(false);
+ eCurrentPage = ePage_SelectImage;
+ case ePage_SelectImage:
+ WriteSelectImagePageControls();
+ break;
+
+ case ePage_AdjustFilter:
+ // Make sure proper controls are shown
+ ShowFilterControls();
+ break;
+
+ case ePage_FinalConfirm:
+ break;
+
+ case ePage_PerformingAction:
+ break;
+ }
+
+ // !KLUDGE! We need to hide ourselves while the file open dialog is up
+ //SetVisible( eCurrentPage != ePage_SelectImage );
+
+ for ( int i = 0 ; i < k_NumPages ; ++i )
+ {
+ if ( m_rpPagePanel[i] )
+ {
+ m_rpPagePanel[i]->SetVisible( i == eCurrentPage );
+ }
+ }
+
+ vgui::EditablePanel *pPreviewProupPanel = NULL;
+ if ( m_rpPagePanel[eCurrentPage] )
+ {
+ pPreviewProupPanel = dynamic_cast<vgui::EditablePanel *>( m_rpPagePanel[eCurrentPage]->FindChildByName( "PreviewModelGroupBox" ) );
+ }
+ if ( pPreviewProupPanel )
+ {
+ m_pItemModelPanel->SetVisible( true );
+ m_pItemModelPanel->SetParent( pPreviewProupPanel );
+ m_pItemModelPanel->SetPos( 10, 10 );
+ m_pItemModelPanel->SetSize( pPreviewProupPanel->GetWide() - 20, pPreviewProupPanel->GetTall() - 20 );
+ m_pItemModelPanel->UpdatePanels();
+ }
+ else
+ {
+ m_pItemModelPanel->SetVisible( false );
+ }
+}
+
+void CConfirmCustomizeTextureDialog::ActivateFileOpenDialog()
+{
+ // Create the dialog the first time it's used
+ if (m_hImportImageDialog == NULL)
+ {
+ m_hImportImageDialog = new vgui::FileOpenDialog( NULL, "#ToolCustomizeTextureBrowseDialogTitle", true );
+#ifdef WIN32
+ m_hImportImageDialog->AddFilter( "*.tga,*.jpg,*.png,*.bmp", "#GameUI_All_Images", true );
+#else
+ m_hImportImageDialog->AddFilter( "*.tga,*.jpg,*.png", "#GameUI_All_Images", true );
+#endif
+ m_hImportImageDialog->AddFilter( "*.tga", "#GameUI_TGA_Images", false );
+ m_hImportImageDialog->AddFilter( "*.jpg", "#GameUI_JPEG_Images", false );
+ m_hImportImageDialog->AddFilter( "*.png", "#GameUI_PNG_Images", false );
+#ifdef WIN32
+ m_hImportImageDialog->AddFilter( "*.bmp", "#GameUI_BMP_Images", false );
+#endif
+ m_hImportImageDialog->AddActionSignalTarget( this );
+ }
+
+ // Activate it
+ m_hImportImageDialog->DoModal( false );
+ m_hImportImageDialog->Activate();
+}
+
+void CConfirmCustomizeTextureDialog::OnCommand( const char *command )
+{
+ if (!stricmp( command, "pick_image" ) )
+ {
+ ActivateFileOpenDialog();
+ return;
+ }
+
+ // !KLUDGE! Base class closes window. I don't want to do this.
+ if ( !Q_stricmp( command, "apply" ) )
+ {
+ Apply();
+ return;
+ }
+
+ if ( !Q_stricmp( command, "next_page" ) )
+ {
+ if ( eCurrentPage < ePage_FinalConfirm )
+ {
+ SetPage( (EPage)(eCurrentPage + 1) );
+ }
+ return;
+ }
+
+ if ( !Q_stricmp( command, "prev_page" ) )
+ {
+ if ( eCurrentPage > ePage_SelectImage )
+ {
+ SetPage( (EPage)(eCurrentPage - 1) );
+ }
+ return;
+ }
+
+ if ( !Q_stricmp( command, "next_stencil_palette" ) )
+ {
+ SelectStencilPalette( m_nSelectedStencilPalette + 1 );
+ return;
+ }
+
+ if ( !Q_stricmp( command, "prev_stencil_palette" ) )
+ {
+ SelectStencilPalette( m_nSelectedStencilPalette + m_vecStencilPalettes.Count() - 1 );
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+void CConfirmCustomizeTextureDialog::SelectStencilPalette( int nPalette )
+{
+ while ( nPalette < 0 )
+ {
+ nPalette += m_vecStencilPalettes.Count();
+ }
+ m_nSelectedStencilPalette = nPalette % m_vecStencilPalettes.Count();
+ MarkFilteredImageDirty();
+
+ if ( m_pStencilGradientWidget )
+ {
+ const CUtlVector<Color> &pal = m_vecStencilPalettes[m_nSelectedStencilPalette];
+ m_pStencilGradientWidget->InitRangeCount( pal.Count() );
+ m_pStencilGradientWidget->SetRangeColors( pal.Base() );
+ }
+}
+
+
+static void ListenToControlsRecursive( vgui::Panel *pPanel, vgui::Panel *pListener )
+{
+ if ( pPanel == NULL )
+ {
+ return;
+ }
+ if (
+ dynamic_cast<vgui::Button *>( pPanel )
+ || dynamic_cast<vgui::Slider *>( pPanel )
+ || dynamic_cast<vgui::ComboBox *>( pPanel )
+ || dynamic_cast<CustomTextureStencilGradientMapWidget *>( pPanel )
+ )
+ {
+ pPanel->AddActionSignalTarget( pListener );
+ }
+ else
+ {
+ for ( int i = 0 ; i < pPanel->GetChildCount() ; ++i )
+ {
+ ListenToControlsRecursive( pPanel->GetChild(i), pListener );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmCustomizeTextureDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ConfirmCustomizeTextureDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pFilterCombo = dynamic_cast<vgui::ComboBox *>( FindChildByName("FilterComboBox", true ) );
+ Assert( m_pFilterCombo );
+ if ( m_pFilterCombo )
+ {
+ m_pFilterCombo->RemoveAll();
+
+ COMPILE_TIME_ASSERT( eFilter_Stencil == 0 );
+ m_pFilterCombo->AddItem( "#ToolCustomizeTextureFilterStencil", NULL );
+
+ //COMPILE_TIME_ASSERT( eFilter_Painterly == 0 );
+ //m_pFilterCombo->AddItem( "#ToolCustomizeTextureFilterPainterly", NULL );
+
+ COMPILE_TIME_ASSERT( eFilter_Identity == 1 );
+ #ifdef _DEBUG
+ m_pFilterCombo->AddItem( "None", NULL );
+ #endif
+
+ //m_pFilterCombo->SilentActivateItemByRow( eFilter_Painterly );
+ m_pFilterCombo->SilentActivateItemByRow( eFilter_Stencil );
+ }
+
+ m_pSquarizeCombo = dynamic_cast<vgui::ComboBox *>( FindChildByName("SquarizeComboBox", true ) );
+ Assert( m_pSquarizeCombo );
+
+ m_pStencilModeCombo = dynamic_cast<vgui::ComboBox *>( FindChildByName("StencilModeComboBox", true ) );
+ Assert( m_pStencilModeCombo );
+ if ( m_pStencilModeCombo )
+ {
+ m_pStencilModeCombo->AddItem( "#ToolCustomizeTextureStencilMatchByIntensity", NULL );
+ m_pStencilModeCombo->AddItem( "#ToolCustomizeTextureStencilMatchByColor", NULL );
+ m_pStencilModeCombo->SilentActivateItemByRow( 0 );
+ }
+
+ m_pUseAvatarRadioButton = dynamic_cast<vgui::RadioButton *>( FindChildByName("UseAvatarRadio", true ) );
+ m_pUseAnyImageRadioButton = dynamic_cast<vgui::RadioButton *>( FindChildByName("UseAnyimageRadio", true ) );
+
+ m_pStencilGradientWidget = dynamic_cast<CustomTextureStencilGradientMapWidget *>( FindChildByName("StencilGradientMap", true ) );
+ Assert( m_pStencilGradientWidget );
+
+ for ( int i = 0 ; i < k_NumPages ; ++i )
+ {
+ ListenToControlsRecursive( m_rpPagePanel[i], this );
+ }
+
+ UseAvatarImage();
+
+ SetPage( ePage_SelectImage );
+
+ // Flip this flag to activate the test harness
+ #ifdef TEST_FILTERS
+ TestFilters();
+ #endif
+
+ SelectStencilPalette( 0 );
+}
+
+void CConfirmCustomizeTextureDialog::UseAvatarImage()
+{
+ // assume failure
+ m_imgSource.Clear();
+ m_bUseAvatar = false;
+
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+
+ const int k_nAvatarImageSize = 184;
+ m_imgSource.Init( k_nAvatarImageSize, k_nAvatarImageSize, IMAGE_FORMAT_RGBA8888 );
+ int iAvatar = steamapicontext->SteamFriends()->GetLargeFriendAvatar( steamapicontext->SteamUser()->GetSteamID() );
+ if ( !steamapicontext->SteamUtils()->GetImageRGBA( iAvatar, m_imgSource.GetBits(), k_nAvatarImageSize*k_nAvatarImageSize*4 ) )
+ {
+ m_imgSource.Clear();
+ }
+ else
+ {
+ m_bUseAvatar = true;
+ }
+ }
+
+ WriteSelectImagePageControls();
+ MarkSquareImageDirty();
+}
+
+void CConfirmCustomizeTextureDialog::WriteSelectImagePageControls()
+{
+ if ( !m_pSquarizeCombo )
+ {
+ return;
+ }
+
+ m_pSquarizeCombo->RemoveAll();
+
+ CExButton *pNextButton = dynamic_cast<CExButton *>( m_rpPagePanel[ePage_SelectImage]->FindChildByName( "NextButton", true ) );
+ if ( !pNextButton )
+ {
+ return;
+ }
+
+ if ( m_pUseAvatarRadioButton )
+ {
+ if ( !m_pUseAvatarRadioButton->IsSelected() )
+ {
+ m_pUseAvatarRadioButton->SetSelected( m_bUseAvatar );
+ }
+ }
+ if ( m_pUseAnyImageRadioButton )
+ {
+ if ( !m_pUseAnyImageRadioButton->IsSelected() )
+ {
+ m_pUseAnyImageRadioButton->SetSelected( !m_bUseAvatar );
+ }
+ }
+
+ if ( !m_imgSource.IsValid() )
+ {
+ // No image yet selected
+ m_pSquarizeCombo->SetVisible( false );
+ pNextButton->SetEnabled( false );
+ m_pCroppedTextureImagePanel->SetVisible( false );
+ return;
+ }
+ m_pCroppedTextureImagePanel->SetVisible( true );
+ pNextButton->SetEnabled( true );
+
+ // Nearly square already?
+ if ( IsSourceImageSquare() )
+ {
+ // Nearly square. No need to offer any options
+ m_pSquarizeCombo->SetVisible( false );
+ }
+ else
+ {
+ m_pSquarizeCombo->AddItem( "#ToolCustomizeTextureStretch", NULL );
+ m_pSquarizeCombo->AddItem( "#ToolCustomizeTextureCrop", NULL );
+ m_pSquarizeCombo->SetVisible( true );
+ m_pSquarizeCombo->ActivateItemByRow( m_bCropToSquare ? 1 : 0 );
+ }
+
+
+}
+
+void CConfirmCustomizeTextureDialog::OnTick( void )
+{
+ BaseClass::OnTick();
+
+ // Process, depending on currently selected page
+ switch ( eCurrentPage )
+ {
+ default:
+ Assert(false);
+ eCurrentPage = ePage_SelectImage;
+ case ePage_SelectImage:
+ break;
+
+ case ePage_AdjustFilter:
+ break;
+
+ case ePage_FinalConfirm:
+ break;
+
+ case ePage_PerformingAction:
+ break;
+ }
+}
+
+void CConfirmCustomizeTextureDialog::ShowFilterControls()
+{
+ EFilter f = (EFilter)m_pFilterCombo->GetActiveItem();
+
+ vgui::Panel *p;
+
+ p = m_rpPagePanel[ePage_AdjustFilter]->FindChildByName( "PainterlyOptions", true );
+ if ( p )
+ {
+ p->SetVisible( f == eFilter_Painterly );
+ }
+
+ p = m_rpPagePanel[ePage_AdjustFilter]->FindChildByName( "StencilOptions", true );
+ if ( p )
+ {
+ p->SetVisible( f == eFilter_Stencil );
+ }
+}
+
+void CConfirmCustomizeTextureDialog::PerformSquarize()
+{
+ if ( m_bCropToSquare && !IsSourceImageSquare() )
+ {
+ // Select the smaller dimension as the size
+ int nSize = MIN( m_imgSource.Width(), m_imgSource.Height() );
+
+ // Crop it.
+ // Yeah, the crop and resize could be done all in one step.
+ // And...I don't care.
+ int x0 = ( m_imgSource.Width() - nSize ) / 2;
+ int y0 = ( m_imgSource.Height() - nSize ) / 2;
+ m_imgSquare.Crop( x0, y0, nSize, nSize, &m_imgSource );
+ }
+ else
+ {
+ m_imgSquare.MakeLogicalCopyOf( m_imgSource );
+ }
+
+ // Reduce it for display purposes
+ ImgUtl_ResizeBitmap( m_imgSquareDisplay, k_nCustomImageSize, k_nCustomImageSize, &m_imgSquare );
+
+ // Square image is now up-to-date with options
+ m_bSquareImageDirty = false;
+
+ if ( m_pCroppedTextureImagePanel != NULL )
+ {
+ m_pCroppedTextureImagePanel->SetBitmap( m_imgSquareDisplay );
+ }
+
+ // We need to re-run our filter anytime this changes
+ MarkFilteredImageDirty();
+}
+
+void CConfirmCustomizeTextureDialog::PerformFilter()
+{
+
+ // this can take a while, put up a waiting cursor
+ vgui::surface()->SetCursor( vgui::dc_hourglass );
+
+ switch ( (EFilter)m_pFilterCombo->GetActiveItem() )
+ {
+ case eFilter_Identity:
+ // !FIXME! Only allow while in dev universe?
+ PerformIdentityFilter();
+ break;
+ default:
+ Assert( false );
+ case eFilter_Stencil:
+ PerformStencilFilter();
+ break;
+ case eFilter_Painterly:
+ PerformPainterlyFilter();
+ break;
+ }
+
+ // Now apply the blend layers
+ static bool bDoBlendLayers = true;
+ if ( bDoBlendLayers )
+ {
+ for ( int i = 0; i < m_vecBlendLayers.Size() ; ++i )
+ {
+ m_vecBlendLayers[i].Apply( m_imgFinal );
+ }
+ }
+
+ // And the texture on the 3D model
+ g_pPreviewCustomTextureDirty = true;
+
+ m_bFilteredImageDirty = false;
+
+ if ( m_pFilteredTextureImagePanel != NULL )
+ {
+ m_pFilteredTextureImagePanel->SetBitmap( m_imgFinal );
+ }
+
+ // change the cursor back to normal
+ vgui::surface()->SetCursor( vgui::dc_user );
+}
+
+void CConfirmCustomizeTextureDialog::PerformIdentityFilter()
+{
+ ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &m_imgSquare );
+}
+
+void CConfirmCustomizeTextureDialog::PerformStencilFilter()
+{
+
+ // Check if the shape reduced image is dirty
+ if ( m_bStencilShapeReducedImageDirty )
+ {
+ Bitmap_t imgTemp1, imgTemp2;
+
+// Need a slider to control this. Works OK for color match, poorly for intensity.
+// Best for all cases is to just do nothing
+// // Downsample FIRST to 2X res
+// ImgUtl_ResizeBitmap( imgTemp1, k_nCustomImageSize*2, k_nCustomImageSize*2, &m_imgSquare );
+//
+// // Run the bilateral filter several times
+// static float thresh1 = .7f; static int rad1 = 1; static float amount1 = 1.0f;
+// static float thresh2 = .8f; static int rad2 = 1; static float amount2 = 1.0f;
+// static float thresh3 = .9f; static int rad3 = 2; static float amount3 = 1.0f;
+// Bitmap_t t;
+// BilateralFilter( imgTemp1, imgTemp2, rad1, thresh1, amount1 );
+// static int rounds = 4;
+// for ( int r = 0 ; r < rounds ; ++r )
+// {
+// BilateralFilter( imgTemp2, imgTemp1, rad2, thresh2, amount2 );
+// BilateralFilter( imgTemp1, imgTemp2, rad2, thresh2, amount2 );
+// }
+// //BilateralFilter( imgTemp2, m_imgFinal, rad3, thresh3, amount3 );
+// BilateralFilter( imgTemp2, m_imgStencilShapeReduced, rad3, thresh3, amount3 );
+
+ // Downsample FIRST to 2X res
+ ImgUtl_ResizeBitmap( m_imgStencilShapeReduced, k_nCustomImageSize*2, k_nCustomImageSize*2, &m_imgSquare );
+
+ m_bStencilShapeReducedImageDirty = false;
+ }
+
+ // Color matching
+ {
+// Color swatches[] =
+// {
+// Color( 255, 255, 255 ),
+// Color( 183, 224, 252 ), // sky light
+// Color( 83, 109, 205 ), // sky med
+// Color( 64, 68, 195 ), // sky dark
+// Color( 100, 68, 57 ), // skin demo
+// Color( 139, 101, 84 ), // skin demo light
+// Color( 133, 105, 68 ), // saxton hair
+// Color( 252, 169, 131 ), // skin light
+// Color( 194, 132, 106 ), // skin
+//
+// //Color( 255, 255, 255 ),
+// //Color( 246, 231, 222 ),
+// //Color( 218, 189, 171 ),
+// //Color( 193, 161, 138 ),
+// //
+// //Color( 248, 185, 138 ),
+// //Color( 245, 173, 135 ),
+// //Color( 239, 152, 73 ),
+// //Color( 241, 129, 73 ),
+// //
+// //Color( 106, 69, 52 ),
+// //Color( 145, 58, 31 ),
+// //Color( 189, 58, 58 ),
+// //Color( 157, 48, 47 ),
+// //Color( 69, 44, 37 ),
+// //
+// //Color( 107, 106, 101 ),
+// //Color( 118, 138, 136 ),
+// //Color( 91, 122, 140 ),
+// //Color( 56, 92, 120 ),
+// //Color( 52, 47, 44 ),
+// };
+
+ Bitmap_t imgTemp1;
+
+ static float colorReplacePct = 1.0f;
+
+ // match by color, or intensity?
+ if ( m_pStencilModeCombo && m_pStencilModeCombo->GetActiveItem() == 0 && m_pStencilGradientWidget )
+ {
+ imgTemp1.Init( m_imgStencilShapeReduced.Width(), m_imgStencilShapeReduced.Height(), IMAGE_FORMAT_RGBA8888 );
+ for ( int y = 0 ; y < imgTemp1.Height() ; ++y )
+ {
+ for ( int x = 0 ; x < imgTemp1.Width() ; ++x )
+ {
+ Color c = m_imgStencilShapeReduced.GetColor( x, y );
+ Vector lab = TextureToLab( c );
+ int index = clamp(lab.x * (255.0f/100.0f) + .5f, 0.0, 255.0f);
+ imgTemp1.SetColor( x, y, m_pStencilGradientWidget->m_colorGradient[ index ] );
+ }
+ }
+ }
+ else
+ {
+
+ Assert( m_nSelectedStencilPalette >= 0 );
+ Assert( m_nSelectedStencilPalette < m_vecStencilPalettes.Count() );
+ const CUtlVector<Color> &pal = m_vecStencilPalettes[ m_nSelectedStencilPalette ];
+
+ // Determine "weight" of each swatch, from the relative sizes of the
+ // gradient widget ranges
+ CUtlVector<float> vecSwatchWeight;
+ for ( int i = 0 ; i < pal.Size() ; ++i )
+ {
+ float weight = 1.0f;
+ if ( m_pStencilGradientWidget )
+ {
+ weight = float( m_pStencilGradientWidget->GetNobValue(i) - m_pStencilGradientWidget->GetNobValue(i - 1) );
+ }
+ vecSwatchWeight.AddToTail( weight );
+
+ }
+
+ ColorReplace( m_imgStencilShapeReduced, imgTemp1, pal.Count(), pal.Base(), colorReplacePct, vecSwatchWeight.Base() );
+ }
+
+ // Now downsample to the final size
+ ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &imgTemp1 );\
+ }
+
+// // !KLUDGE!
+// if ( m_pStencilGradientWidget == NULL )
+// {
+// PerformIdentityFilter();
+// return;
+// }
+//
+// // Make sure temp image is properly allocated
+// imgTemp1.Init( m_imgSquare.Width(), m_imgSquare.Height(), IMAGE_FORMAT_RGBA8888 );
+//
+// // Perform stencil operation
+// for ( int y = 0 ; y < m_imgSquare.Height() ; ++y )
+// {
+// for ( int x = 0 ; x < m_imgSquare.Height() ; ++x )
+// {
+// Color c = m_imgSquare.GetColor(x,y);
+//
+// // Compute "value" using simple average. (No visual
+// // weighting for this.)
+// int v = ( (int)c.r() + (int)c.g() + (int)c.g() ) / 3;
+//
+// // Apply gradient map
+// Color result = m_pStencilGradientWidget->m_colorGradient[ v ];
+// imgTemp1.SetColor( x, y, result );
+// }
+// }
+//
+// // Now downsample to the final size
+// ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &m_imgTemp );
+}
+
+const int k_BrushStrokeSize = 64;
+static byte s_bBrushStrokeData[k_BrushStrokeSize][k_BrushStrokeSize] =
+{
+ { 0x8C, 0x86, 0x87, 0x87, 0x86, 0x88, 0x88, 0x87, 0x86, 0x88, 0x8F, 0x8E, 0x8C, 0x8E, 0x8D, 0x8E, 0x8B, 0x8A, 0x8A, 0x94, 0xAE, 0xB6, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB0, 0xB0, 0xB2, 0x9E, 0x9A, 0x9C, 0x9C, 0x9A, 0x99, 0x97, 0x98, 0x96, 0x9A, 0x9D, 0x9F, 0x9E, 0x9D, 0x9E, 0x9F, 0x9B, 0x9A, 0x99, 0x98, 0x95, 0x91, 0x8F, 0x8F, 0x8E, 0x89, 0x88, 0x89, 0x88, 0x85, 0x87, 0x8B },
+ { 0x85, 0x7C, 0x7D, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7D, 0x7F, 0x87, 0x88, 0x88, 0x89, 0x87, 0x86, 0x86, 0x83, 0x81, 0x8B, 0xA9, 0xB2, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xAF, 0xAD, 0xAD, 0xAE, 0x97, 0x92, 0x94, 0x94, 0x93, 0x92, 0x91, 0x92, 0x91, 0x94, 0x98, 0x9B, 0x9A, 0x99, 0x99, 0x99, 0x96, 0x95, 0x96, 0x98, 0x98, 0x98, 0x99, 0x9A, 0x93, 0x86, 0x84, 0x84, 0x80, 0x7D, 0x7D, 0x83 },
+ { 0x86, 0x7D, 0x7E, 0x81, 0x80, 0x7F, 0x7F, 0x82, 0x83, 0x84, 0x8A, 0x89, 0x86, 0x87, 0x88, 0x89, 0x86, 0x87, 0x85, 0x8E, 0xAA, 0xB2, 0xB0, 0xB1, 0xB0, 0xB1, 0xB1, 0xB1, 0xB0, 0xAE, 0xAD, 0xAD, 0x9B, 0x96, 0x96, 0x96, 0x94, 0x95, 0x94, 0x96, 0x97, 0x99, 0x9C, 0x9E, 0x9D, 0x9D, 0x9C, 0x9C, 0x9A, 0x99, 0x99, 0x99, 0x9A, 0x9B, 0x9D, 0x9E, 0x9B, 0x86, 0x85, 0x86, 0x83, 0x80, 0x7F, 0x88 },
+ { 0x84, 0x7D, 0x7F, 0x81, 0x7F, 0x80, 0x83, 0x88, 0x88, 0x88, 0x8B, 0x8A, 0x88, 0x89, 0x89, 0x89, 0x84, 0x85, 0x85, 0x8E, 0xAB, 0xB4, 0xB2, 0xB3, 0xB2, 0xB2, 0xB3, 0xB3, 0xB2, 0xB0, 0xAF, 0xAE, 0x99, 0x94, 0x94, 0x94, 0x93, 0x96, 0x97, 0x99, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x85, 0x85, 0x87, 0x84, 0x82, 0x7E, 0x87 },
+ { 0x85, 0x7D, 0x7E, 0x7E, 0x7F, 0x84, 0x88, 0x8A, 0x8A, 0x88, 0x88, 0x89, 0x87, 0x87, 0x89, 0x88, 0x89, 0x88, 0x86, 0x90, 0xAA, 0xB4, 0xB3, 0xB1, 0xB1, 0xB2, 0xB3, 0xB3, 0xB2, 0xB1, 0xAF, 0xAE, 0x97, 0x93, 0x94, 0x94, 0x95, 0x99, 0x9C, 0x9E, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x97, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9C, 0x9C, 0x86, 0x86, 0x86, 0x85, 0x85, 0x7F, 0x86 },
+ { 0x85, 0x7A, 0x76, 0x75, 0x77, 0x7D, 0x7D, 0x7A, 0x7B, 0x7D, 0x7D, 0x7D, 0x7A, 0x7F, 0x8E, 0x97, 0x90, 0x94, 0x9A, 0xA0, 0xAF, 0xB2, 0xB2, 0xB2, 0xB1, 0xB2, 0xB3, 0xB3, 0xB3, 0xB2, 0xB0, 0xAF, 0x96, 0x92, 0x94, 0x93, 0x93, 0x97, 0x99, 0x9A, 0x9A, 0x9B, 0x9C, 0x9E, 0x9E, 0x9D, 0x9C, 0x9C, 0x9A, 0x9A, 0x9B, 0x9C, 0x9E, 0x9F, 0x9F, 0x9E, 0x9B, 0x8A, 0x87, 0x87, 0x86, 0x85, 0x80, 0x86 },
+ { 0x89, 0x7B, 0x75, 0x74, 0x75, 0x77, 0x75, 0x73, 0x75, 0x77, 0x79, 0x7B, 0x7C, 0x82, 0x92, 0x9C, 0x97, 0x9F, 0xAB, 0xAE, 0xB1, 0xB1, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB3, 0xB2, 0x9A, 0x97, 0x99, 0x98, 0x97, 0x99, 0x98, 0x98, 0x9A, 0x9B, 0x9E, 0x9F, 0x9F, 0x9F, 0x9E, 0x9D, 0x9A, 0x9B, 0x9C, 0x9D, 0xA1, 0xA5, 0xA7, 0xA6, 0xA7, 0x9A, 0x94, 0x94, 0x93, 0x8A, 0x83, 0x89 },
+ { 0x8B, 0x7E, 0x7A, 0x7B, 0x79, 0x79, 0x78, 0x79, 0x75, 0x77, 0x7E, 0x90, 0xA0, 0xA8, 0xAC, 0xA7, 0xA9, 0xAA, 0xB0, 0xB0, 0xB1, 0xB1, 0xB4, 0xB2, 0xB3, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB5, 0xB3, 0x9B, 0x99, 0x9D, 0x9D, 0x9C, 0x9E, 0x9C, 0x9B, 0x9C, 0x9D, 0x9F, 0xA0, 0x9F, 0x9F, 0x9E, 0x9F, 0x9A, 0x9B, 0x9B, 0x9C, 0xA0, 0xA5, 0xA7, 0xA8, 0xA9, 0xA0, 0x9A, 0x9C, 0x9B, 0x8D, 0x85, 0x8B },
+ { 0x8A, 0x7E, 0x7B, 0x77, 0x79, 0x78, 0x77, 0x74, 0x75, 0x70, 0x87, 0xAC, 0xAF, 0xAF, 0xB1, 0xB1, 0xAE, 0xAD, 0xB1, 0xB0, 0xB1, 0xB0, 0xB5, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB4, 0xB1, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9F, 0x9F, 0x9F, 0x9F, 0x9E, 0x9A, 0x9A, 0x9C, 0x9D, 0xA0, 0xA5, 0xA7, 0xA9, 0xA8, 0xA0, 0x9B, 0x9A, 0x99, 0x8D, 0x83, 0x8B },
+ { 0x8B, 0x7D, 0x7A, 0x77, 0x78, 0x77, 0x78, 0x76, 0x77, 0x73, 0x88, 0xA8, 0xAC, 0xAE, 0xB2, 0xB3, 0xB2, 0xB1, 0xB1, 0xB0, 0xB5, 0xB4, 0xB5, 0xB4, 0xB6, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB2, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9D, 0x9E, 0x9E, 0xA2, 0xA7, 0xA7, 0xA7, 0xAB, 0xA0, 0x9A, 0x9B, 0x9B, 0x8E, 0x83, 0x8B },
+ { 0x88, 0x78, 0x77, 0x77, 0x77, 0x76, 0x76, 0x73, 0x72, 0x74, 0x8B, 0xA8, 0xAC, 0xAE, 0xB1, 0xB0, 0xAF, 0xB3, 0xB4, 0xB1, 0xB6, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB6, 0xB6, 0xB3, 0x99, 0x9A, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9D, 0x9D, 0x9E, 0x9E, 0x9D, 0x9E, 0x9F, 0xA3, 0xA8, 0xA8, 0xA9, 0xAB, 0x9F, 0x98, 0x9A, 0x9A, 0x8D, 0x81, 0x88 },
+ { 0x8C, 0x7A, 0x7A, 0x7C, 0x7C, 0x7C, 0x7E, 0x7A, 0x7A, 0x7A, 0x88, 0x97, 0x97, 0x96, 0x98, 0x97, 0x99, 0xA1, 0xA5, 0xA6, 0xB1, 0xB4, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB3, 0x9A, 0x9A, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9D, 0x9D, 0xA0, 0xA1, 0xA5, 0xAA, 0xAB, 0xAD, 0xAD, 0xA3, 0x9D, 0x9E, 0x9E, 0x92, 0x87, 0x8C },
+ { 0x92, 0x7F, 0x7C, 0x7B, 0x7A, 0x7A, 0x7E, 0x7C, 0x7B, 0x7B, 0x82, 0x87, 0x85, 0x83, 0x85, 0x84, 0x84, 0x8A, 0x8E, 0x96, 0xAD, 0xB5, 0xB4, 0xB2, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB5, 0xB5, 0xB2, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9D, 0x9D, 0x9D, 0x9E, 0x9E, 0xA1, 0xA3, 0xA8, 0xAE, 0xAE, 0xAF, 0xB0, 0xAD, 0xA9, 0xA8, 0xA9, 0xA1, 0x97, 0x9B },
+ { 0xA4, 0x92, 0x8D, 0x89, 0x85, 0x82, 0x82, 0x7F, 0x80, 0x81, 0x87, 0x89, 0x8A, 0x88, 0x8A, 0x89, 0x8A, 0x8F, 0x93, 0x9B, 0xAF, 0xB6, 0xB5, 0xB4, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB3, 0x9B, 0x9A, 0x99, 0x99, 0x9A, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9C, 0x9F, 0xA3, 0xAA, 0xB1, 0xB1, 0xB0, 0xB0, 0xB0, 0xAE, 0xAD, 0xAF, 0xAA, 0xA3, 0xA6 },
+ { 0xA8, 0x9E, 0x9F, 0xA0, 0xA2, 0xA1, 0xA1, 0x9F, 0xA0, 0x9E, 0x9E, 0x99, 0x9B, 0x99, 0x9C, 0x9D, 0x9D, 0xA0, 0xA2, 0xA5, 0xB1, 0xB4, 0xB5, 0xB6, 0xB5, 0xB4, 0xB5, 0xB4, 0xB3, 0xB4, 0xB4, 0xB2, 0x99, 0x98, 0x98, 0x97, 0x98, 0x99, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x9E, 0x9F, 0xA0, 0xA3, 0xA6, 0xAA, 0xB0, 0xB0, 0xB0, 0xAE, 0xAD, 0xAC, 0xAC, 0xAF, 0xAB, 0xA4, 0xA9 },
+ { 0xA5, 0x9D, 0x9F, 0xA0, 0xA4, 0xA4, 0xA4, 0xA3, 0xA3, 0xA3, 0xA3, 0xA0, 0xA4, 0xA0, 0xA1, 0xA0, 0xA2, 0xA1, 0xA1, 0xA3, 0xAF, 0xB1, 0xB3, 0xB4, 0xB4, 0xB3, 0xB4, 0xB3, 0xB2, 0xB3, 0xB3, 0xB1, 0x8F, 0x8E, 0x8D, 0x8C, 0x8D, 0x8E, 0x8E, 0x8E, 0x91, 0x92, 0x94, 0x98, 0x9C, 0xA1, 0xA5, 0xA7, 0xA6, 0xA9, 0xAE, 0xAC, 0xAB, 0xAD, 0xAD, 0xB0, 0xB1, 0xAD, 0xAB, 0xAC, 0xB0, 0xAB, 0xA4, 0xA9 },
+ { 0xA9, 0x9A, 0x9C, 0xA4, 0xA5, 0xA6, 0xA2, 0x9D, 0xA0, 0xA0, 0x9F, 0x9F, 0x9F, 0x9F, 0xA0, 0xA0, 0xA0, 0xA1, 0xA0, 0xA2, 0xAA, 0xB0, 0xB2, 0xB4, 0xB3, 0xB2, 0xB0, 0xAF, 0xAF, 0xB1, 0xB2, 0xB1, 0x8B, 0x8A, 0x8A, 0x8C, 0x8D, 0x8F, 0x8F, 0x8A, 0x8B, 0x8E, 0x91, 0x95, 0x9B, 0xA2, 0xA5, 0xA7, 0xA6, 0xAA, 0xAE, 0xB0, 0xB0, 0xAF, 0xB0, 0xB1, 0xB1, 0xAE, 0xAC, 0xAC, 0xAC, 0xA7, 0xA2, 0xA8 },
+ { 0xAA, 0x9B, 0x9D, 0xA5, 0xA6, 0xA7, 0xA4, 0xA0, 0xA2, 0xA2, 0xA2, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA2, 0xA4, 0xA4, 0xA7, 0xAE, 0xB3, 0xB5, 0xB5, 0xB4, 0xB3, 0xB2, 0xB2, 0xB2, 0xB4, 0xB3, 0xB2, 0x96, 0x94, 0x95, 0x96, 0x96, 0x98, 0x97, 0x93, 0x95, 0x97, 0x99, 0x9C, 0xA0, 0xA5, 0xA7, 0xA8, 0xA9, 0xAC, 0xB0, 0xB1, 0xB0, 0xAF, 0xAF, 0xB0, 0xB1, 0xAE, 0xAD, 0xAD, 0xAE, 0xAA, 0xA5, 0xAB },
+ { 0xAA, 0x9C, 0x9D, 0xA5, 0xA6, 0xA7, 0xA6, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA6, 0xA6, 0xA5, 0xA5, 0xA6, 0xA9, 0xAC, 0xAF, 0xB2, 0xB4, 0xB3, 0xB5, 0xB4, 0xB1, 0xAF, 0xB0, 0xB1, 0xB0, 0xAF, 0xA2, 0xA1, 0xA2, 0xA2, 0xA2, 0xA4, 0xA3, 0xA1, 0xA3, 0xA4, 0xA5, 0xA7, 0xA9, 0xAD, 0xAE, 0xAE, 0xAD, 0xB0, 0xB3, 0xB3, 0xB3, 0xB1, 0xB1, 0xB1, 0xB4, 0xB2, 0xB1, 0xB0, 0xB1, 0xAC, 0xA7, 0xAB },
+ { 0xAA, 0x9B, 0x9D, 0xA4, 0xA4, 0xA5, 0xA5, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA6, 0xA7, 0xA7, 0xAA, 0xAD, 0xAC, 0xAE, 0xAF, 0xAD, 0xAF, 0xAC, 0xA6, 0xA2, 0xA3, 0xA4, 0xA4, 0xA4, 0xA1, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA5, 0xA4, 0xA6, 0xA7, 0xA8, 0xA9, 0xAC, 0xAF, 0xB0, 0xB0, 0xB0, 0xB2, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB6, 0xB4, 0xB4, 0xB3, 0xB4, 0xAF, 0xA8, 0xAB },
+ { 0xAB, 0x9C, 0x9D, 0xA4, 0xA3, 0xA4, 0xA4, 0xA5, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xAB, 0xAD, 0xAB, 0xAB, 0xAC, 0xAB, 0xAC, 0xA9, 0xA2, 0x9C, 0x9D, 0x9F, 0x9F, 0xA0, 0xA0, 0xA1, 0xA2, 0xA3, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAB, 0xAE, 0xB0, 0xB0, 0xB0, 0xB1, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB5, 0xB2, 0xAB, 0xAD },
+ { 0xAC, 0x9C, 0x9E, 0xA4, 0xA4, 0xA3, 0xA4, 0xA5, 0xA5, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA8, 0xA8, 0xA8, 0xAB, 0xAD, 0xAC, 0xAC, 0xAD, 0xAE, 0xAE, 0xAD, 0xA6, 0xA2, 0xA4, 0xA5, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA8, 0xA9, 0xA8, 0xA8, 0xAA, 0xA9, 0xAA, 0xAB, 0xAC, 0xAE, 0xB1, 0xB2, 0xB2, 0xB2, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB6, 0xB3, 0xAC, 0xAD },
+ { 0xAC, 0x9C, 0x9D, 0xA5, 0xA4, 0xA3, 0xA4, 0xA5, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA8, 0xA7, 0xA9, 0xAB, 0xAC, 0xAD, 0xAC, 0xAD, 0xAF, 0xAE, 0xAE, 0xA8, 0xA3, 0xA5, 0xA6, 0xA5, 0xA6, 0xA8, 0xA8, 0xA8, 0xA9, 0xAA, 0xA9, 0xA9, 0xAB, 0xAB, 0xAB, 0xAC, 0xAC, 0xAE, 0xB1, 0xB2, 0xB2, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB6, 0xB6, 0xB4, 0xB6, 0xB4, 0xAC, 0xAC },
+ { 0xAC, 0x9B, 0x9C, 0xA4, 0xA4, 0xA3, 0xA4, 0xA5, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA9, 0xAA, 0xAB, 0xAD, 0xAB, 0xAA, 0xAE, 0xAE, 0xAE, 0xA8, 0xA2, 0xA4, 0xA5, 0xA4, 0xA6, 0xA5, 0xA6, 0xA6, 0xA7, 0xA9, 0xA9, 0xA8, 0xAC, 0xAB, 0xAC, 0xAC, 0xAD, 0xAF, 0xB1, 0xB2, 0xB2, 0xB1, 0xB3, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB3, 0xB6, 0xB5, 0xAE, 0xAE },
+ { 0xAC, 0x9B, 0x9F, 0xA3, 0xA5, 0xA4, 0xA4, 0xA3, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA6, 0xA7, 0xA9, 0xAB, 0xAB, 0xAA, 0xAC, 0xAE, 0xAE, 0xAD, 0xA7, 0xA4, 0xA4, 0xA4, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA8, 0xA9, 0xA9, 0xAA, 0xAB, 0xAA, 0xAB, 0xAC, 0xAD, 0xAF, 0xB1, 0xB3, 0xB3, 0xB2, 0xB3, 0xB5, 0xB4, 0xB2, 0xB1, 0xB2, 0xB3, 0xB5, 0xB3, 0xB5, 0xB4, 0xB4, 0xB4, 0xAF, 0xAD },
+ { 0xAC, 0x9A, 0x9F, 0xA2, 0xA4, 0xA4, 0xA5, 0xA5, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA8, 0xAA, 0xAB, 0xAB, 0xAA, 0xAB, 0xAD, 0xAE, 0xAD, 0xA6, 0xA3, 0xA4, 0xA4, 0xA6, 0xA5, 0xA7, 0xA7, 0xA8, 0xA9, 0xA9, 0xA9, 0xAA, 0xAB, 0xAB, 0xAC, 0xAC, 0xAD, 0xAF, 0xB2, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB3, 0xB2, 0xB3, 0xB3, 0xB3, 0xB2, 0xB5, 0xB5, 0xB5, 0xB5, 0xB1, 0xAF },
+ { 0xB5, 0xA3, 0xA5, 0xA7, 0xA7, 0xA6, 0xA6, 0xA6, 0xA7, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA6, 0xA7, 0xA9, 0xAA, 0xAA, 0xA9, 0xAA, 0xAC, 0xAD, 0xAC, 0xA5, 0xA2, 0xA4, 0xA4, 0xA5, 0xA4, 0xA5, 0xA5, 0xA6, 0xA8, 0xA9, 0xA9, 0xAA, 0xAB, 0xAB, 0xAC, 0xAD, 0xAE, 0xB0, 0xB3, 0xB5, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB3, 0xB3, 0xB3, 0xB3, 0xB2, 0xB4, 0xB5, 0xB5, 0xB6, 0xB3, 0xB3 },
+ { 0xB5, 0xA2, 0xA4, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA6, 0xA7, 0xA7, 0xA8, 0xA8, 0xA7, 0xA6, 0xA6, 0xA3, 0xA5, 0xA7, 0xA9, 0xA9, 0xA9, 0xAA, 0xAB, 0xAC, 0xAA, 0xA3, 0xA1, 0xA3, 0xA3, 0xA4, 0xA2, 0xA0, 0xA1, 0xA3, 0xA6, 0xA8, 0xA9, 0xAA, 0xAC, 0xAA, 0xAC, 0xAD, 0xAF, 0xB2, 0xB4, 0xB4, 0xB3, 0xB1, 0xB2, 0xB3, 0xB4, 0xB3, 0xB2, 0xB2, 0xB2, 0xB4, 0xB3, 0xB4, 0xB4, 0xB5, 0xB6, 0xB4, 0xB6 },
+ { 0xB2, 0x9F, 0xA1, 0xA3, 0xA5, 0xA5, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA8, 0xA8, 0xA7, 0xA7, 0xA6, 0xA3, 0xA5, 0xA7, 0xA8, 0xA9, 0xAA, 0xAA, 0xAA, 0xAB, 0xAA, 0xA2, 0xA1, 0xA3, 0xA3, 0xA3, 0xA0, 0x9E, 0x9F, 0xA2, 0xA5, 0xA7, 0xA8, 0xA9, 0xAB, 0xAA, 0xAB, 0xAD, 0xAF, 0xB1, 0xB3, 0xB3, 0xB2, 0xAF, 0xB0, 0xB2, 0xB2, 0xB3, 0xB2, 0xB2, 0xB1, 0xB3, 0xB3, 0xB5, 0xB5, 0xB6, 0xB5, 0xB2, 0xB6 },
+ { 0xB4, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA4, 0xA6, 0xA8, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xAA, 0xA2, 0xA0, 0xA2, 0xA2, 0xA2, 0xA0, 0x9F, 0xA0, 0xA2, 0xA4, 0xA6, 0xA6, 0xA7, 0xA9, 0xA9, 0xAB, 0xAC, 0xAD, 0xAF, 0xB2, 0xB3, 0xB4, 0xB0, 0xB1, 0xB2, 0xB2, 0xB3, 0xB2, 0xB2, 0xB2, 0xB1, 0xB3, 0xB5, 0xB5, 0xB6, 0xB4, 0xB1, 0xB6 },
+ { 0xB5, 0xA0, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA4, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA7, 0xA6, 0xA3, 0xA6, 0xA8, 0xA8, 0xA9, 0xAA, 0xAA, 0xA9, 0xAB, 0xA9, 0xA1, 0x9F, 0xA0, 0xA0, 0xA1, 0x9F, 0xA0, 0xA0, 0xA1, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xAA, 0xAC, 0xAD, 0xAF, 0xB2, 0xB4, 0xB4, 0xB0, 0xB1, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB0, 0xB2, 0xB4, 0xB3, 0xB4, 0xB3, 0xB1, 0xB8 },
+ { 0xBB, 0xA6, 0xA3, 0xA2, 0xA1, 0xA0, 0xA0, 0xA0, 0xA0, 0xA2, 0xA5, 0xA8, 0xA8, 0xA7, 0xA5, 0xA4, 0xA1, 0xA4, 0xA6, 0xA7, 0xA8, 0xAA, 0xAA, 0xA9, 0xAA, 0xA8, 0xA0, 0x9C, 0x9E, 0x9E, 0x9F, 0x9E, 0x9F, 0x9F, 0xA0, 0xA1, 0xA2, 0xA2, 0xA3, 0xA4, 0xA4, 0xA8, 0xAD, 0xAF, 0xB1, 0xB3, 0xB3, 0xB3, 0xB1, 0xB1, 0xB2, 0xB2, 0xB1, 0xB0, 0xB1, 0xB1, 0xB1, 0xB2, 0xB1, 0xB0, 0xB1, 0xB1, 0xB2, 0xBB },
+ { 0xB7, 0xAC, 0xA8, 0xA8, 0xA7, 0xA6, 0xA4, 0xA5, 0xA4, 0xA1, 0xA8, 0xAC, 0xAD, 0xA3, 0x86, 0x88, 0x83, 0x8B, 0x8F, 0x91, 0x96, 0x98, 0x99, 0x9D, 0x9D, 0x9E, 0x9F, 0x9F, 0x9D, 0x9C, 0x9D, 0x9F, 0x9F, 0xA0, 0xA1, 0xA1, 0xA2, 0xA3, 0xA3, 0xA3, 0xA4, 0xA8, 0xAD, 0xB0, 0xB1, 0xB3, 0xB5, 0xB6, 0xB3, 0xB2, 0xB2, 0xB1, 0xB1, 0xB2, 0xB2, 0xB3, 0xB1, 0xB1, 0xB3, 0xB1, 0xB0, 0xB0, 0xB2, 0xBA },
+ { 0xB5, 0xAE, 0xAD, 0xAE, 0xAD, 0xAD, 0xAC, 0xAD, 0xAC, 0xAC, 0xB1, 0xB0, 0xAF, 0xA6, 0x88, 0x83, 0x81, 0x8B, 0x91, 0x93, 0x98, 0x9B, 0x9D, 0xA0, 0x9F, 0xA0, 0xA1, 0xA1, 0xA0, 0x9F, 0xA0, 0xA1, 0xA2, 0xA2, 0xA3, 0xA4, 0xA4, 0xA5, 0xA5, 0xA5, 0xA6, 0xAA, 0xAE, 0xB0, 0xB1, 0xB3, 0xB5, 0xB6, 0xB3, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB4, 0xB2, 0xAF, 0xAE, 0xAE, 0xB5 },
+ { 0xB2, 0xAE, 0xAF, 0xAF, 0xAE, 0xAF, 0xAF, 0xB0, 0xB1, 0xB2, 0xB7, 0xB5, 0xB5, 0xB1, 0x91, 0x84, 0x85, 0x90, 0x96, 0x98, 0x9B, 0x9E, 0x9E, 0xA0, 0xA1, 0xA2, 0xA4, 0xA4, 0xA4, 0xA3, 0xA3, 0xA3, 0xA3, 0xA2, 0xA2, 0xA2, 0xA3, 0xA4, 0xA5, 0xA5, 0xA7, 0xAA, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB6, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB2, 0xB1, 0xAF, 0xAE, 0xB4 },
+ { 0xB1, 0xAE, 0xAE, 0xAC, 0xAB, 0xB0, 0xB2, 0xB3, 0xB5, 0xB5, 0xB6, 0xB5, 0xB6, 0xB2, 0x93, 0x81, 0x82, 0x8D, 0x95, 0x97, 0x9A, 0x9E, 0xA0, 0xA1, 0xA1, 0xA2, 0xA3, 0xA3, 0xA3, 0xA3, 0xA2, 0xA1, 0xA2, 0xA2, 0xA1, 0xA2, 0xA4, 0xA6, 0xA9, 0xAB, 0xAD, 0xAF, 0xB1, 0xB2, 0xB3, 0xB5, 0xB6, 0xB5, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB2, 0xB2, 0xB4, 0xB4, 0xB3, 0xB2, 0xB0, 0xB5 },
+ { 0xB2, 0xAE, 0xAD, 0xAB, 0xAD, 0xB3, 0xB6, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB5, 0xB2, 0x9C, 0x8E, 0x8D, 0x96, 0x9C, 0x9D, 0x9F, 0xA2, 0xA4, 0xA5, 0xA7, 0xA7, 0xA8, 0xA9, 0xA9, 0xA8, 0xA6, 0xA5, 0xA6, 0xA6, 0xA6, 0xA7, 0xA9, 0xAE, 0xB2, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB5, 0xB6, 0xB6, 0xB5, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB3, 0xB5, 0xB4, 0xB4, 0xB3, 0xB0, 0xB4 },
+ { 0xB2, 0xAD, 0xAD, 0xAC, 0xAE, 0xB3, 0xB3, 0xB3, 0xB2, 0xB3, 0xB5, 0xB8, 0xB6, 0xB7, 0xB1, 0xAD, 0xAE, 0xB2, 0xB4, 0xB2, 0xB0, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xB3, 0xB2, 0xB1, 0xB0, 0xAE, 0xAD, 0xAC, 0xAB, 0xAB, 0xAC, 0xAF, 0xB1, 0xB3, 0xB3, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB0, 0xB4 },
+ { 0xB3, 0xAF, 0xB0, 0xAF, 0xAF, 0xB0, 0xAF, 0xAF, 0xB0, 0xB1, 0xB3, 0xB7, 0xB4, 0xB7, 0xB7, 0xB7, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xAF, 0xAE, 0xAE, 0xAE, 0xAF, 0xAF, 0xAF, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB2, 0xB2, 0xB2, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB2, 0xB4, 0xB4, 0xB5, 0xB4, 0xB3, 0xB7 },
+ { 0xB7, 0xB3, 0xB5, 0xB4, 0xB3, 0xB4, 0xB3, 0xB4, 0xB3, 0xB4, 0xB4, 0xB8, 0xB4, 0xB6, 0xB6, 0xB3, 0xB2, 0xB0, 0xB0, 0xB1, 0xB2, 0xB3, 0xB5, 0xB6, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB1, 0xB4, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB2, 0xB2, 0xB1, 0xB1, 0xB1, 0xB2, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB5, 0xB4, 0xB9 },
+ { 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB7, 0xB7, 0xB8, 0xB7, 0xB6, 0xB4, 0xB4, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB7, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB7 },
+ { 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB5, 0xB4, 0xB2, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB4, 0xB3, 0xB2, 0xB4, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB4, 0xB4, 0xB7 },
+ { 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB2, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB3, 0xB2, 0xB2, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB6, 0xB5, 0xB5, 0xB8 },
+ { 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB2, 0xB3, 0xB2, 0xB2, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB6, 0xB9 },
+ { 0xB7, 0xB4, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB4, 0xB4, 0xB5, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB8, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB1, 0xB2, 0xB2, 0xB1, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB9 },
+ { 0xB7, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB6, 0xB2, 0xB3, 0xB3, 0xB2, 0xB5, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xBA },
+ { 0xB8, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB5, 0xB5, 0xB4, 0xB4, 0xB5, 0xB4, 0xB3, 0xB2, 0xB4, 0xB4, 0xAD, 0xAC, 0xAD, 0xAE, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB6, 0xB6, 0xB7, 0xBA },
+ { 0xB8, 0xB5, 0xB4, 0xB5, 0xB4, 0xB4, 0xB4, 0xB2, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB3, 0xB2, 0xB0, 0xB0, 0xAF, 0xA4, 0xA2, 0xA3, 0xA7, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB7, 0xBA },
+ { 0xBA, 0xB5, 0xB5, 0xB6, 0xB5, 0xB6, 0xB4, 0xAE, 0xB0, 0xB1, 0xB2, 0xB3, 0xB3, 0xB3, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB3, 0xAF, 0xAA, 0xAB, 0xAC, 0x9C, 0x99, 0x9B, 0xA2, 0xB3, 0xB2, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB5, 0xB7, 0xB7, 0xB7, 0xB6, 0xB4, 0xBB },
+ { 0xB8, 0xB5, 0xB5, 0xB6, 0xB5, 0xB5, 0xB4, 0xAE, 0xB0, 0xB1, 0xB2, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB3, 0xB0, 0xAC, 0xAC, 0xAD, 0x9C, 0x98, 0x9A, 0xA1, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB7, 0xB5, 0xB6, 0xB5, 0xB6, 0xB7, 0xB6, 0xBB },
+ { 0xB9, 0xB5, 0xB5, 0xB6, 0xB5, 0xB6, 0xB6, 0xB2, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB4, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB2, 0xB0, 0xAD, 0xAF, 0xAF, 0xA0, 0x9D, 0x9E, 0xA2, 0xB2, 0xB3, 0xB4, 0xB5, 0xB5, 0xB6, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB4, 0xB5, 0xB7, 0xB6, 0xB8 },
+ { 0xB1, 0xAD, 0xAD, 0xAF, 0xAF, 0xB2, 0xB6, 0xB5, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xA3, 0x9F, 0x9E, 0xA1, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB2, 0xB3 },
+ { 0xB1, 0xAC, 0xAD, 0xAE, 0xAD, 0xB0, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB7, 0xB7, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB3, 0xB2, 0xB2, 0xB4, 0xB1, 0xA3, 0x9F, 0x9E, 0xA1, 0xAE, 0xB1, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB3, 0xB4, 0xB4 },
+ { 0xB0, 0xAC, 0xAD, 0xAF, 0xAF, 0xB0, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB2, 0xB1, 0xB0, 0xAF, 0xAE, 0xAD, 0xAD, 0xAC, 0xAA, 0xA1, 0x9F, 0x9F, 0xA0, 0xA9, 0xAB, 0xAB, 0xAB, 0xAC, 0xAF, 0xB1, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB4, 0xB3, 0xB4, 0xB1 },
+ { 0x9C, 0x98, 0x9B, 0xA1, 0xA4, 0xA9, 0xB0, 0xB3, 0xB4, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB6, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xA4, 0xA3, 0xA1, 0xA2, 0xA1, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x9C, 0x9E, 0x9E, 0x9C, 0xA0, 0x9F, 0xA0, 0xA0, 0xA1, 0xA5, 0xA9, 0xAD, 0xAD, 0xAD, 0xAC, 0xAE, 0xAD, 0xAD, 0xAD, 0xAE, 0xAB, 0xA0 },
+ { 0x8D, 0x88, 0x8A, 0x91, 0x95, 0x9D, 0xA7, 0xAB, 0xAF, 0xB1, 0xB3, 0xB4, 0xB4, 0xB5, 0xB6, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xA0, 0x9E, 0x9D, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0xA0, 0x9F, 0x9C, 0x9F, 0x9F, 0x9C, 0x9E, 0x9D, 0x9E, 0x9D, 0x9E, 0xA1, 0xA6, 0xA8, 0xA8, 0xA7, 0xA8, 0xAD, 0xAC, 0xAB, 0xAB, 0xAD, 0xA5, 0x92 },
+ { 0x8E, 0x87, 0x8A, 0x91, 0x93, 0x9B, 0xA7, 0xAC, 0xAE, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB8, 0xB6, 0xB5, 0xB6, 0xB3, 0xB3, 0xB8, 0xB7, 0xB7, 0xB8, 0xB7, 0xB6, 0xB6, 0xB7, 0xB5, 0xA2, 0x9F, 0x9F, 0xA0, 0x9F, 0x9F, 0x9F, 0x9F, 0xA0, 0x9F, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9C, 0x9E, 0xA3, 0xA7, 0xA8, 0xA8, 0xA7, 0xA9, 0xAD, 0xAC, 0xAB, 0xAA, 0xAB, 0xA8, 0x94 },
+ { 0x8D, 0x87, 0x89, 0x8E, 0x90, 0x97, 0xA2, 0xA6, 0xAC, 0xAD, 0xAF, 0xAF, 0xB0, 0xB3, 0xB5, 0xB6, 0xB4, 0xB7, 0xB7, 0xB6, 0xB5, 0xB3, 0xB3, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB7, 0xB7, 0xB5, 0xA0, 0x9E, 0x9E, 0x9F, 0x9E, 0x9E, 0x9F, 0x9F, 0x9F, 0x9E, 0x9D, 0x9C, 0x9D, 0x9D, 0x9D, 0x9C, 0x9D, 0x9C, 0x9D, 0xA0, 0xA4, 0xA6, 0xA8, 0xA9, 0xA5, 0xAA, 0xAC, 0xAC, 0xAA, 0xA8, 0x9F, 0x8C },
+ { 0x8C, 0x86, 0x88, 0x8D, 0x8F, 0x95, 0x9E, 0xA2, 0xA5, 0xA7, 0xA8, 0xA8, 0xA8, 0xA9, 0xAB, 0xAC, 0xAF, 0xB2, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB3, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB6, 0xB3, 0x9E, 0x9C, 0x9E, 0xA0, 0x9F, 0xA0, 0xA0, 0xA0, 0xA0, 0x9F, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9D, 0x9C, 0x9C, 0x9D, 0x9F, 0xA1, 0xA3, 0xA5, 0xA6, 0xAA, 0xAA, 0xAB, 0xA8, 0xA7, 0xA9, 0x9D, 0x8C },
+ { 0x87, 0x82, 0x85, 0x8B, 0x8E, 0x95, 0x9D, 0xA1, 0xA5, 0xA7, 0xA9, 0xA9, 0xA9, 0xA9, 0xAA, 0xAA, 0xA6, 0xA8, 0xAA, 0xAC, 0xAF, 0xB2, 0xB3, 0xB1, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB2, 0x99, 0x98, 0x9B, 0x9E, 0x9E, 0x9E, 0x9F, 0x9E, 0xA0, 0x9E, 0x9D, 0x9D, 0x9D, 0x9D, 0x9C, 0x9C, 0x99, 0x9A, 0x9C, 0x9E, 0xA1, 0xA4, 0xA6, 0xA5, 0xA5, 0xA6, 0xAB, 0xA6, 0xA8, 0xAC, 0x98, 0x86 },
+ { 0x84, 0x80, 0x83, 0x8A, 0x8E, 0x94, 0x9C, 0x9F, 0xA3, 0xA6, 0xA8, 0xA8, 0xA9, 0xAA, 0xAC, 0xAC, 0xA8, 0xAC, 0xAD, 0xAD, 0xB0, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB1, 0x97, 0x97, 0x9A, 0x9D, 0x9E, 0x9E, 0x9E, 0x9D, 0x9F, 0x9E, 0x9C, 0x9C, 0x9C, 0x9D, 0x9C, 0x9B, 0x9A, 0x9A, 0x9B, 0x9D, 0xA1, 0xA5, 0xA6, 0xA6, 0xA3, 0xA6, 0xAC, 0xA3, 0xA4, 0xA9, 0x97, 0x89 },
+ { 0x85, 0x80, 0x81, 0x86, 0x88, 0x8A, 0x8E, 0x91, 0x91, 0x93, 0x94, 0x94, 0x93, 0x95, 0x97, 0x99, 0x9C, 0xA4, 0xA9, 0xAD, 0xB3, 0xB3, 0xB1, 0xB2, 0xB1, 0xB1, 0xB3, 0xB4, 0xB3, 0xB4, 0xB3, 0xAF, 0x97, 0x96, 0x99, 0x9C, 0x9C, 0x9C, 0x9C, 0x9B, 0x9C, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9A, 0x9A, 0x97, 0x96, 0x96, 0x99, 0x9E, 0xA1, 0xA4, 0xA6, 0xA6, 0xA7, 0xA4, 0x93, 0x8E, 0x91, 0x89, 0x87 },
+ { 0x85, 0x80, 0x7F, 0x82, 0x81, 0x81, 0x82, 0x83, 0x83, 0x85, 0x87, 0x86, 0x85, 0x85, 0x86, 0x87, 0x84, 0x8B, 0x91, 0x9D, 0xAF, 0xB3, 0xB0, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB2, 0xB3, 0xB2, 0xB0, 0x98, 0x96, 0x97, 0x99, 0x99, 0x98, 0x98, 0x97, 0x98, 0x99, 0x9A, 0x9A, 0x9A, 0x99, 0x98, 0x98, 0x95, 0x93, 0x91, 0x91, 0x8E, 0x8A, 0x89, 0x8B, 0x8D, 0x8E, 0x8B, 0x83, 0x81, 0x82, 0x80, 0x85 },
+ { 0x8A, 0x85, 0x85, 0x87, 0x87, 0x86, 0x87, 0x88, 0x87, 0x8B, 0x8E, 0x8F, 0x8E, 0x8E, 0x8D, 0x8D, 0x89, 0x89, 0x86, 0x94, 0xAF, 0xB9, 0xB5, 0xB7, 0xB5, 0xB5, 0xB6, 0xB5, 0xB4, 0xB4, 0xB5, 0xB3, 0x9E, 0x9B, 0x9B, 0x9C, 0x9B, 0x9A, 0x9A, 0x99, 0x9B, 0x9D, 0x9F, 0x9F, 0x9F, 0x9E, 0x9D, 0x9D, 0x99, 0x98, 0x98, 0x98, 0x92, 0x8A, 0x87, 0x8A, 0x88, 0x87, 0x85, 0x85, 0x88, 0x86, 0x85, 0x8D },
+};
+
+struct CheesyRand
+{
+ CheesyRand( int seed = 12345 )
+ {
+ z = seed | 0x00010000;
+ w = ~seed | 0x00000100;
+ }
+
+ uint32 RandInt()
+ {
+ // http://en.wikipedia.org/wiki/Random_number_generation#Computational_methods
+ z = 36969 * (z & 65535) + (z >> 16);
+ w = 18000 * (w & 65535) + (w >> 16);
+ return (z << 16) + w;
+ }
+
+ inline float RandFloat01()
+ {
+ return RandInt() / 4294970000.0f;
+ }
+
+ inline float RandFloatNeg1To1()
+ {
+ return RandFloat01() * 2.0f - 1.0f;
+ }
+
+ uint32 z, w;
+};
+
+void CConfirmCustomizeTextureDialog::PerformPainterlyFilter()
+{
+
+ // Resample it to 2x the final resolution. Having a fixed resolution
+ // for the "source" image makes it easier, since we can use fixed size
+ // kernels, etc
+ Bitmap_t imageTemp1, imageTemp2;
+ ImgUtl_ResizeBitmap( imageTemp1, k_nCustomImageSize*2, k_nCustomImageSize*2, &m_imgSquare );
+
+ //
+ // Shape correction V1:
+ //
+ #if 1
+ // Perform symmetric nearest neighbor
+ float filterStrength = .95f;
+ SymmetricNearestNeighborFilter( imageTemp1, imageTemp2, 5, filterStrength );
+ BilateralFilter( imageTemp2, imageTemp1, 4, .5, .15 );
+ BilateralFilter( imageTemp1, imageTemp2, 2, .9, .7 );
+ imageTemp1.SetPixelData( imageTemp2 );
+ #endif
+
+ //
+ // Shape correction V2:
+ //
+ #if 0
+ // Perform symmetric nearest neighbor
+ float snnFilterStrength = .7f;
+ SymmetricNearestNeighborFilter( imageTemp1, imageTemp2, 4, snnFilterStrength );
+
+ // And some bilateral filtering to smooth it
+ BilateralFilter( imageTemp2, imageTemp1, 2, .75, .5 );
+ BilateralFilter( imageTemp1, imageTemp2, 3, .7, .3 );
+ BilateralFilter( imageTemp2, imageTemp1, 4, .6, .2 );
+ #endif
+
+// // Load up brush strokes
+// if ( !m_imgBrushStrokes.IsValid() )
+// {
+// m_imgBrushStrokes.Load( "d:\\texture.jpg" );
+//
+// for (int y = 0 ; y < m_imgBrushStrokes.Height() ; ++y )
+// {
+// for (int x = 0 ; x < m_imgBrushStrokes.Width() ; ++x )
+// {
+// Warning("0x%02X, ", m_imgBrushStrokes.GetColor(x,y).r() );
+// }
+// Warning("\n");
+// }
+//
+// m_imgBrushStrokes.Resize( m_imgTemp.Width(), m_imgTemp.Height() );
+// }
+
+ //
+ // Color correction
+ //
+ for ( int y = 0 ; y < imageTemp1.Height() ; ++y )
+ {
+ for ( int x = 0 ; x < imageTemp1.Width() ; ++x )
+ {
+
+ // Fetch original pixel in RGB space
+ Color c = imageTemp1.GetColor( x,y );
+ Vector rgb((float)c.r(), (float)c.g(), (float)c.b());
+
+ // Convert to HSV
+ Vector hsv;
+ RGBtoHSV( rgb, hsv );
+
+ //
+ // Color correction V1
+ //
+ #if 0
+ // Shift towards red, away from blue
+ //rgb.x += rgb.z * .2f;
+ //rgb.z *= 0.7f;
+ // Desaturate
+ float satMult = .65; // desaturate
+ hsv.y *= satMult;
+ #endif
+
+ //
+ // Color correction V2
+ //
+
+ #if 1
+ static const Color swatches[] =
+ {
+ Color( 183, 224, 252, 255 ), // sky light
+ Color( 83, 109, 205, 255 ), // sky med
+ Color( 64, 68, 195, 255 ), // sky dark
+ Color( 100, 68, 57, 255 ), // skin demo
+ Color( 139, 101, 84, 255 ), // skin demo light
+ Color( 133, 105, 68, 255 ), // saxton hair
+ Color( 252, 169, 131, 255 ), // skin light
+ Color( 194, 132, 106, 255 ), // skin
+
+ Color( 255, 255, 255, 255 ),
+ Color( 246, 231, 222, 255 ),
+ Color( 218, 189, 171, 255 ),
+ Color( 193, 161, 138, 255 ),
+
+ Color( 248, 185, 138, 255 ),
+ Color( 245, 173, 135, 255 ),
+ Color( 239, 152, 73, 255 ),
+ Color( 241, 129, 73, 255 ),
+
+ Color( 106, 69, 52, 255 ),
+ Color( 145, 58, 31, 255 ),
+ Color( 189, 58, 58, 255 ),
+ Color( 157, 48, 47, 255 ),
+ Color( 69, 44, 37, 255 ),
+
+ Color( 107, 106, 101, 255 ),
+ Color( 118, 138, 136, 255 ),
+ Color( 91, 122, 140, 255 ),
+ Color( 56, 92, 120, 255 ),
+ Color( 52, 47, 44, 255 ),
+ };
+
+ static float selfWeight = .15f;
+ static float thresh = .60f;
+ Vector rgb2((float)c.r(), (float)c.g(), (float)c.b());
+ float totalWeight = selfWeight;
+ rgb2 *= selfWeight;
+ for ( int i = 0 ; i < ARRAYSIZE(swatches) ; ++i )
+ {
+ float similarity = 1.0f - ApproxColorDist( c, swatches[i] ) - thresh;
+ if ( similarity > 0.0f )
+ {
+ similarity /= (1.0f - thresh); // get in 0...1 scale
+ similarity *= similarity*similarity;
+ rgb2.x += similarity*(float)swatches[i].r();
+ rgb2.y += similarity*(float)swatches[i].g();
+ rgb2.z += similarity*(float)swatches[i].b();
+ totalWeight += similarity;
+ }
+ }
+ rgb2 /= totalWeight;
+
+ // Calc hue for the shifted one
+ Vector hsv2;
+ RGBtoHSV( rgb2, hsv2 );
+
+ // Replace hue and saturation
+ hsv.x = hsv2.x;
+ hsv.y = hsv2.y;
+ #endif
+ // Convert back to RGB space
+ HSVtoRGB( hsv, rgb );
+
+ // Overlay brush stroke noise
+ Vector overlayValue;
+ int brushX = x * k_BrushStrokeSize / imageTemp1.Width();
+ int brushY = y * k_BrushStrokeSize / imageTemp1.Height();
+ //float k = (float)m_imgBrushStrokes.GetColor( x, y ).r() / 255.0f;
+ float k = (float)s_bBrushStrokeData[brushY][brushX] / 255.0f;
+ if ( k < .5f )
+ {
+ overlayValue = rgb * k * 2.0f;
+ }
+ else
+ {
+ Vector kWhite( 255.0f, 255.0f, 255.0f );
+ float q = 2.0f * ( 1.0f - k ); // 0.5 -> 1.0 , 1.0 -> 0
+ overlayValue = kWhite - ( kWhite - rgb ) * q;
+ }
+
+ float overlayStrength = .10f;
+ rgb += (overlayValue - rgb) * overlayStrength;
+
+ // Put back into the image
+ Color result(
+ (unsigned char)clamp(rgb.x, 0.0f, 255.0f),
+ (unsigned char)clamp(rgb.y, 0.0f, 255.0f),
+ (unsigned char)clamp(rgb.z, 0.0f, 255.0f),
+ c.a()
+ );
+ imageTemp2.SetColor( x, y, result );
+ }
+ }
+
+ // Now downsample to the final size
+ ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &imageTemp2 );
+
+ // Add noise to the final image
+ // Use deterministic random number generator (i.e. let's not call rand()),
+ // so uploading the same image twice will produce the same hash
+ CheesyRand noiseRand;
+ for ( int y = 0 ; y < m_imgFinal.Height() ; ++y )
+ {
+ for ( int x = 0 ; x < m_imgFinal.Width() ; ++x )
+ {
+ float noiseStrength = 2.0f;
+ int noise = (int)floor( noiseRand.RandFloatNeg1To1() * noiseStrength + .5f );
+ Color c = m_imgFinal.GetColor( x, y );
+ Color result(
+ clamp( c.r() + noise, 0, 255 ),
+ clamp( c.g() + noise, 0, 255 ),
+ clamp( c.b() + noise, 0, 255 ),
+ c.a()
+ );
+ m_imgFinal.SetColor( x, y, result );
+ }
+ }
+}
+
+#ifdef TEST_FILTERS
+void CConfirmCustomizeTextureDialog::TestFilters()
+{
+ const char *szTestImageFilenames[] =
+ {
+ "d:/custom_images/borat.jpg",
+ "d:/custom_images/cloud_strife-profile.jpg",
+ "d:/custom_images/ladies_man.png",
+ "d:/custom_images/dota_hero.jpg",
+ "d:/custom_images/elmo balls.jpg",
+ "d:/custom_images/halolz-dot-com-teamfortress2-sexyheavy-prematureubers.jpg",
+ "d:/custom_images/doug_loves_movies.jpg",
+ "d:/custom_images/lolcat.jpg",
+ "d:/custom_images/mario_3d.jpg",
+ //"d:/custom_images/pulp_fiction_sam.gif",
+ "d:/custom_images/RainbowBright.jpg",
+ "d:/custom_images/elliot_and_travis.tga",
+ "d:/custom_images/give_peace_a_chance.jpg",
+ };
+ const int k_nTestImages = ARRAYSIZE(szTestImageFilenames);
+
+ Bitmap_t imageOutput;
+ imageOutput.Init( k_nCustomImageSize*3, k_nCustomImageSize*k_nTestImages, IMAGE_FORMAT_RGBA8888 );
+
+ for ( int i = 0 ; i < k_nTestImages ; ++i )
+ {
+ ConversionErrorType nErrorCode = ImgUtl_LoadBitmap( szTestImageFilenames[i], m_imgSource );
+ if ( nErrorCode != CE_SUCCESS )
+ {
+ Assert( nErrorCode == CE_SUCCESS );
+ continue;
+ }
+
+ PerformSquarize();
+ PerformPainterlyFilter();
+ int y = i*k_nCustomImageSize;
+ imageOutput.SetPixelData( m_imgSquareDisplay, 0, y );
+ imageOutput.SetPixelData( m_imgFinal, k_nCustomImageSize, y );
+ }
+
+ CUtlBuffer pngFileData;
+ ImgUtl_SavePNGBitmapToBuffer( pngFileData, imageOutput );
+
+ g_pFullFileSystem->WriteFile( "d:/painterly.png", NULL, pngFileData );
+}
+#endif
+
+void CConfirmCustomizeTextureDialog::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect )
+{
+
+ // Check if we need to redo the filter
+ CleanFilteredImage();
+
+ Assert( pVTFTexture->FrameCount() == 1 );
+ Assert( pVTFTexture->FaceCount() == 1 );
+ Assert( pTexture == g_pPreviewCustomTexture );
+ Assert( !pTexture->IsMipmapped() );
+
+ int nWidth, nHeight, nDepth;
+ pVTFTexture->ComputeMipLevelDimensions( 0, &nWidth, &nHeight, &nDepth );
+ Assert( nDepth == 1 );
+ Assert( nWidth == m_imgFinal.Width() && nHeight == m_imgFinal.Height() );
+
+ CPixelWriter pixelWriter;
+ pixelWriter.SetPixelMemory( pVTFTexture->Format(),
+ pVTFTexture->ImageData( 0, 0, 0 ), pVTFTexture->RowSizeInBytes( 0 ) );
+
+ // !SPEED! 'Tis probably DEATHLY slow...
+ for ( int y = 0; y < nHeight; ++y )
+ {
+ pixelWriter.Seek( 0, y );
+ for ( int x = 0; x < nWidth; ++x )
+ {
+ Color c = m_imgFinal.GetColor( x, y );
+ pixelWriter.WritePixel( c.r(), c.g(), c.b(), c.a() );
+ }
+ }
+
+ // We're no longer dirty
+ g_pPreviewCustomTextureDirty = false;
+}
+
+void CConfirmCustomizeTextureDialog::Release()
+{
+ if ( g_pPreviewCustomTexture )
+ {
+ ITexture *tex = g_pPreviewCustomTexture;
+ g_pPreviewCustomTexture = NULL; // clear pointer first, to prevent infinite recursion
+ tex->SetTextureRegenerator( NULL );
+ tex->Release();
+ }
+ g_pPreviewEconItem = NULL;
+}
+
+class CCustomizeTextureJobDialog : public CApplyCustomTextureJob
+{
+public:
+ CCustomizeTextureJobDialog( const void *pPNGData, int nPNGDataBytes, CConfirmCustomizeTextureDialog *pDlg )
+ : CApplyCustomTextureJob( pDlg->GetToolItem()->GetItemID(), pDlg->GetSubjectItem()->GetItemID(), pPNGData, nPNGDataBytes )
+ , m_pDlg( pDlg )
+ {
+ }
+
+protected:
+
+ virtual EResult YieldingRunJob()
+ {
+ // Base class do the work
+ EResult result = CApplyCustomTextureJob::YieldingRunJob();
+
+ CloseWaitingDialog();
+
+ // Show result
+ if ( result == k_EResultOK )
+ {
+ m_pDlg->OnCommand("close");
+ }
+ else
+ {
+ m_pDlg->CloseWithGenericError();
+ }
+
+ // Return status code
+ return result;
+ }
+
+ CConfirmCustomizeTextureDialog *m_pDlg;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmCustomizeTextureDialog::Apply( void )
+{
+ Assert( m_imgFinal.IsValid() );
+
+ // Throw up a busy dialog
+ SetPage( ePage_PerformingAction );
+
+ // Write PNG data
+ CUtlBuffer bufPNGData;
+ if ( ImgUtl_SavePNGBitmapToBuffer( bufPNGData, m_imgFinal ) != CE_SUCCESS )
+ {
+ Warning( "Failed to write PNG\n" );
+ CloseWithGenericError();
+ return;
+ }
+
+ // Stats
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "customized_texture" );
+
+ // Start a job to do the async work
+ CCustomizeTextureJobDialog *pJob = new CCustomizeTextureJobDialog( bufPNGData.Base(), bufPNGData.TellPut(), this );
+ pJob->StartJob( NULL );
+}
+
+void CConfirmCustomizeTextureDialog::CloseWithGenericError()
+{
+ CloseWaitingDialog();
+
+ // Show error message dialog
+ ShowMessageBox( "#ToolCustomizeTextureError", "#ToolCustomizeTextureErrorMsg", "#GameUI_OK" );
+
+ // Close this window
+ OnCommand("close");
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmCustomizeTextureDialog::ConversionError( ConversionErrorType nError )
+{
+ const char *pErrorText = NULL;
+
+ switch ( nError )
+ {
+ case CE_MEMORY_ERROR:
+ pErrorText = "#GameUI_Spray_Import_Error_Memory";
+ break;
+
+ case CE_CANT_OPEN_SOURCE_FILE:
+ pErrorText = "#GameUI_Spray_Import_Error_Reading_Image";
+ break;
+
+ case CE_ERROR_PARSING_SOURCE:
+ pErrorText = "#GameUI_Spray_Import_Error_Image_File_Corrupt";
+ break;
+
+ case CE_SOURCE_FILE_SIZE_NOT_SUPPORTED:
+ pErrorText = "#GameUI_Spray_Import_Image_Wrong_Size";
+ break;
+
+ case CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED:
+ pErrorText = "#GameUI_Spray_Import_Image_Wrong_Size";
+ break;
+
+ case CE_SOURCE_FILE_TGA_FORMAT_NOT_SUPPORTED:
+ pErrorText = "#GameUI_Spray_Import_Error_TGA_Format_Not_Supported";
+ break;
+
+ case CE_SOURCE_FILE_BMP_FORMAT_NOT_SUPPORTED:
+ pErrorText = "#GameUI_Spray_Import_Error_BMP_Format_Not_Supported";
+ break;
+
+ case CE_ERROR_WRITING_OUTPUT_FILE:
+ pErrorText = "#GameUI_Spray_Import_Error_Writing_Temp_Output";
+ break;
+
+ case CE_ERROR_LOADING_DLL:
+ pErrorText = "#GameUI_Spray_Import_Error_Cant_Load_VTEX_DLL";
+ break;
+ }
+
+ if ( pErrorText )
+ {
+ ShowMessageBox( "#ToolCustomizeTextureError", pErrorText, "#GameUI_OK" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmCustomizeTextureDialog::OnFileSelected(const char *fullpath)
+{
+ // this can take a while, put up a waiting cursor
+ vgui::surface()->SetCursor( vgui::dc_hourglass );
+
+ // they apparently don't want to use their avatar
+ m_bUseAvatar = false;
+
+ // Will need to be restretched/cropped/filtered, no matter what happens next
+ MarkSquareImageDirty();
+
+ // Load up the data as raw RGBA
+ ConversionErrorType nErrorCode = ImgUtl_LoadBitmap( fullpath, m_imgSource );
+ if ( nErrorCode != CE_SUCCESS )
+ {
+ // Report error, if any
+ ConversionError( nErrorCode );
+ }
+
+ // Slam alpha to 255. We do not support images with alpha
+ for ( int y = 0 ; y < m_imgSource.Height() ; ++y )
+ {
+ for ( int x = 0 ; x < m_imgSource.Width() ; ++x )
+ {
+ Color c = m_imgSource.GetColor( x, y );
+ c[3] = 255;
+ m_imgSource.SetColor( x, y, c );
+ }
+ }
+
+ // Show/hide controls as appropriate
+ WriteSelectImagePageControls();
+
+ // Tick the palette entries right now, no matter what else happened
+ //OnTick();
+
+ // change the cursor back to normal
+ vgui::surface()->SetCursor( vgui::dc_user );
+}
+
+void CConfirmCustomizeTextureDialog::OnTextChanged( vgui::Panel *panel )
+{
+ // Check for known controls
+ if ( panel == m_pFilterCombo )
+ {
+
+ // Mark us as dirty
+ MarkFilteredImageDirty();
+
+ // Update controls
+ ShowFilterControls();
+ }
+ else if ( panel == m_pSquarizeCombo )
+ {
+
+ // If image is nearly square, ignore this, there shouldn't
+ // be any options
+ if ( !IsSourceImageSquare() )
+ {
+
+ // Set new option, if it is changing
+ bool bNewOption = ( m_pSquarizeCombo->GetActiveItem() == 1 );
+ if ( !bNewOption != !m_bCropToSquare )
+ {
+ m_bCropToSquare = bNewOption;
+ MarkSquareImageDirty();
+ }
+ }
+ }
+ else if ( panel == m_pStencilModeCombo )
+ {
+ MarkFilteredImageDirty();
+ }
+ else
+ {
+ // Who else is talking to us?
+ Assert( false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_CustomizeTexture::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmCustomizeTextureDialog *dialog = vgui::SETUP_PANEL( new CConfirmCustomizeTextureDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( dialog );
+}
diff --git a/game/client/econ/tool_items/decoder_ring_tool.cpp b/game/client/econ/tool_items/decoder_ring_tool.cpp
new file mode 100644
index 0000000..f2f825d
--- /dev/null
+++ b/game/client/econ/tool_items/decoder_ring_tool.cpp
@@ -0,0 +1,213 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "econ_item_tools.h"
+#include "tool_items.h"
+#include "decoder_ring_tool.h"
+#include "vgui/ISurface.h"
+#include "econ_controls.h"
+#include "econ_ui.h"
+#include "gc_clientsystem.h"
+#include "collection_crafting_panel.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+enum
+{
+ CRATETYPE_NORMAL = 0,
+ CRATETYPE_ROBO = 1,
+};
+
+#define ROBOCRATE_UNLOCKING_SND "/mvm/mvm_tank_deploy.wav"
+#define ROBOCRATE_OPENED_SND "/mvm/mvm_tank_explode.wav"
+
+static int s_iCrateType = CRATETYPE_NORMAL;
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort tool application
+//-----------------------------------------------------------------------------
+class CConfirmDecodeDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmDecodeDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmDecodeDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void Apply( void );
+ virtual void OnCommand( const char *command );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmDecodeDialog::CConfirmDecodeDialog( vgui::Panel *parent, CEconItemView *pTool, CEconItemView *pToolSubject ) : CBaseToolUsageDialog( parent, "ConfirmApplyDecodeDialog", pTool, pToolSubject )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDecodeDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyDecodeDialog.res" );
+
+ const CEconItemView* pCrate = m_pSubjectModelPanel->GetItem();
+
+ s_iCrateType = CRATETYPE_NORMAL;
+ // Check Crate Type
+ if ( pCrate )
+ {
+ if ( pCrate->GetItemDefIndex() == 5635 ) // Robo Crate items_tools_crafting
+ {
+ s_iCrateType = CRATETYPE_ROBO;
+ }
+ }
+
+ if ( V_strstr( pCrate->GetItemDefinition()->GetDefinitionName(), "Case" ) )
+ {
+ SetDialogVariable( "confirm_text", GLocalizationProvider()->Find("#ToolDecodeConfirmCase") );
+ }
+ else
+ {
+ SetDialogVariable( "confirm_text", GLocalizationProvider()->Find("#ToolDecodeConfirm") );
+ }
+
+ const locchar_t *loc_Append = NULL;
+ if ( pCrate && pCrate->GetItemDefinition() && pCrate->GetItemDefinition()->GetHolidayRestriction() )
+ {
+ loc_Append = GLocalizationProvider()->Find( "#ToolDecodeConfirm_OptionalAppend_RestrictedContents" );
+ }
+ if ( !loc_Append )
+ {
+ loc_Append = LOCCHAR( "" );
+ }
+
+ SetDialogVariable( "optional_append", loc_Append );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDecodeDialog::OnCommand( const char *command )
+{
+ if ( FStrEq( "cancel", command ) )
+ {
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmDecodeDialog::Apply( void )
+{
+ static CSchemaAttributeDefHandle pAttrDef_SupplyCrateSeries( "set supply crate series" );
+
+ // Tell the GC to unlock the subject item.
+ GCSDK::CGCMsg< MsgGCUnlockCrate_t > msg( k_EMsgGCUnlockCrate );
+
+ msg.Body().m_unToolItemID = m_pToolModelPanel->GetItem()->GetItemID();
+ msg.Body().m_unSubjectItemID = m_pSubjectModelPanel->GetItem()->GetItemID();
+
+ int iSeries = 0;
+ CEconItemView* pCrate = m_pSubjectModelPanel->GetItem();
+ if ( pCrate )
+ {
+ float fSeries;
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pCrate, pAttrDef_SupplyCrateSeries, &fSeries ) )
+ {
+ iSeries = fSeries;
+ }
+ }
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "unlocked_supply_crate", iSeries );
+
+ GCClientSystem()->BSendMessage( msg );
+
+ CCollectionCraftingPanel *pPanel = EconUI()->GetBackpackPanel()->GetCollectionCraftPanel();
+ if ( pPanel )
+ {
+ pPanel->SetWaitingForItem( kEconItemOrigin_FoundInCrate );
+ }
+}
+
+
+void CEconTool_CrateKey::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmDecodeDialog *dialog = vgui::SETUP_PANEL( new CConfirmDecodeDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( dialog );
+}
+
+
+class CWaitForCrateDialog : public CGenericWaitingDialog
+{
+public:
+ CWaitForCrateDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent )
+ {
+ }
+
+protected:
+ virtual void OnTimeout()
+ {
+ // Play an exciting sound!
+ if ( s_iCrateType == CRATETYPE_ROBO )
+ {
+ vgui::surface()->PlaySound( ROBOCRATE_OPENED_SND );
+ }
+ else
+ {
+ vgui::surface()->PlaySound( "misc/achievement_earned.wav" );
+ }
+
+
+ // Show them their loot!
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the decoder ring response
+//-----------------------------------------------------------------------------
+class CGCUnlockCrateResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCUnlockCrateResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+
+ if ( s_iCrateType == CRATETYPE_ROBO )
+ {
+ vgui::surface()->PlaySound( ROBOCRATE_UNLOCKING_SND );
+ }
+ else
+ {
+ vgui::surface()->PlaySound( "ui/item_open_crate_short.wav" );
+ }
+
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCUnlockCrateResponse, "CGCUnlockCrateResponse", k_EMsgGCUnlockCrateResponse, GCSDK::k_EServerTypeGCClient );
diff --git a/game/client/econ/tool_items/decoder_ring_tool.h b/game/client/econ/tool_items/decoder_ring_tool.h
new file mode 100644
index 0000000..3228521
--- /dev/null
+++ b/game/client/econ/tool_items/decoder_ring_tool.h
@@ -0,0 +1,14 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef DECODER_RING_TOOL_H
+#define DECODER_RING_TOOL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#endif // DECODER_RING_TOOL_H
diff --git a/game/client/econ/tool_items/gift_wrap_tool.cpp b/game/client/econ/tool_items/gift_wrap_tool.cpp
new file mode 100644
index 0000000..c8c5c1d
--- /dev/null
+++ b/game/client/econ/tool_items/gift_wrap_tool.cpp
@@ -0,0 +1,241 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "econ_item_tools.h"
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "tool_items.h"
+#include "gift_wrap_tool.h"
+#include "econ_ui.h"
+#include "vgui/ISurface.h"
+#include "econ_controls.h"
+#include "confirm_dialog.h"
+#include "gc_clientsystem.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort tool application
+//-----------------------------------------------------------------------------
+class CConfirmGiftWrapDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmGiftWrapDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmGiftWrapDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void Apply( void );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Completed wrapping dialog
+//-----------------------------------------------------------------------------
+class CWaitForGiftWrapDialog : public CGenericWaitingDialog
+{
+public:
+ CWaitForGiftWrapDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent )
+ {
+ }
+
+protected:
+ virtual void OnTimeout()
+ {
+ // Play an exciting sound!
+ vgui::surface()->PlaySound( "misc/achievement_earned.wav" );
+
+ // Show them the result item.
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmGiftWrapDialog::CConfirmGiftWrapDialog( vgui::Panel *parent, CEconItemView *pTool, CEconItemView *pToolSubject ) : CBaseToolUsageDialog( parent, "ConfirmApplyGiftWrapDialog", pTool, pToolSubject )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmGiftWrapDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyGiftWrapDialog.res" );
+
+ // We might want to change our label text to explicitly call out that we'll reset strange scores
+ // on gift wrap, but only if we're trying to use this gift wrap on a strange item that has scores
+ // that would be affected by it.
+ CEconItemView *pSubjectItemView = GetSubjectItem();
+ CExLabel *pTextLabel = dynamic_cast<CExLabel *>( FindChildByName( "ConfirmLabel" ) );
+ CExLabel *pTextLabelStrange = dynamic_cast<CExLabel *>( FindChildByName( "ConfirmLabelStrange" ) );
+
+ if ( pSubjectItemView && pTextLabel && pTextLabelStrange )
+ {
+ bool bHasNonZeroScore = false;
+
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ uint32 unScore;
+ if ( pSubjectItemView->FindAttribute( GetKillEaterAttr_Score( i ), &unScore ) && unScore > 0 )
+ {
+ bHasNonZeroScore = true;
+ break;
+ }
+ }
+
+ if ( bHasNonZeroScore )
+ {
+ pTextLabel->SetVisible( false );
+ pTextLabelStrange->SetVisible( true );
+ }
+ }
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmGiftWrapDialog::Apply( void )
+{
+ // Tell the GC to wrap the subject item.
+ GCSDK::CGCMsg< MsgGCGiftWrapItem_t > msg( k_EMsgGCGiftWrapItem );
+
+ msg.Body().m_unToolItemID = m_pToolModelPanel->GetItem()->GetItemID();
+ msg.Body().m_unSubjectItemID = m_pSubjectModelPanel->GetItem()->GetItemID();
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pSubjectModelPanel->GetItem(), "gift_wrap_item" );
+
+ GCClientSystem()->BSendMessage( msg );
+
+ vgui::surface()->PlaySound( "ui/item_gift_wrap_use.wav" );
+ ShowWaitingDialog( new CWaitForGiftWrapDialog( NULL ), "#ToolGiftWrapInProgress", true, false, 5.0f );
+}
+
+// Entry point from the UI.
+void CEconTool_GiftWrap::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmGiftWrapDialog *dialog = vgui::SETUP_PANEL( new CConfirmGiftWrapDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( dialog );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the server response that we've given an item.
+//-----------------------------------------------------------------------------
+class CGCGiftGivenResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCGiftGivenResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CProtoBufMsg<CMsgDeliverGiftResponseGiver> msg( pNetPacket );
+
+ // Pop up a notification to confirm that the gift has been sent.
+ switch ( msg.Body().response_code() )
+ {
+ case k_EGCMsgResponseOK:
+ if ( msg.Body().has_receiver_account_name() )
+ {
+ KeyValues *pkv = new KeyValues( "GiftReceiverParams" );
+ KeyValuesAD kvad( pkv );
+
+ pkv->SetString( "receiver_account_name", msg.Body().receiver_account_name().c_str() );
+ ShowMessageBox( "#TF_DeliverGiftResultDialog_Title", "#TF_DeliverGiftResultDialog_Success_WithAccount", pkv, "#GameUI_OK" );
+ }
+ else
+ {
+ ShowMessageBox( "#TF_DeliverGiftResultDialog_Title", "#TF_DeliverGiftResultDialog_Success", "#GameUI_OK" );
+ }
+ break;
+ case k_EGCMsgResponseDenied:
+ ShowMessageBox( "#TF_DeliverGiftResultDialog_Title", "#TF_DeliverGiftResultDialog_VAC", "#GameUI_OK" );
+ break;
+ default:
+ ShowMessageBox( "#TF_DeliverGiftResultDialog_Title", "#TF_DeliverGiftResultDialog_Fail", "#GameUI_OK" );
+ break;
+ } // switch
+
+ return true;
+ }
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCGiftGivenResponse, "CGCGiftGivenResponse", k_EMsgGCDeliverGiftResponseGiver, GCSDK::k_EServerTypeGCClient );
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the server response that we've received an item.
+//-----------------------------------------------------------------------------
+class CGCGiftReceivedResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCGiftReceivedResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+
+ // If the receiver is online when the gift is sent, they will get this response.
+ InventoryManager()->GetLocalInventory()->NotifyHasNewItems();
+
+ return true;
+ }
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCGiftReceivedResponse, "CGCGiftReceivedResponse", k_EMsgGCDeliverGiftResponseReceiver, GCSDK::k_EServerTypeGCClient );
+
+//-----------------------------------------------------------------------------
+// Purpose: Completed unwrapping...
+//-----------------------------------------------------------------------------
+class CWaitForGiftUnwrapDialog : public CGenericWaitingDialog
+{
+public:
+ CWaitForGiftUnwrapDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent )
+ {
+ }
+
+protected:
+ virtual void OnTimeout()
+ {
+ // Play an exciting sound!
+ vgui::surface()->PlaySound( "misc/achievement_earned.wav" );
+
+ // Show them the result item.
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+};
+
+static void UnwrapGiftConfirm( bool bConfirmed, void *pContext )
+{
+ if ( bConfirmed )
+ {
+ vgui::surface()->PlaySound( "ui/item_gift_wrap_unwrap.wav" );
+ ShowWaitingDialog( new CWaitForGiftWrapDialog( NULL ), "#ToolGiftUnwrapInProgress", true, false, 5.0f );
+
+ CEconItemView *pItem = (CEconItemView*) pContext;
+ GCSDK::CGCMsg< MsgGCUnwrapGiftRequest_t > msg( k_EMsgGCUnwrapGiftRequest );
+ msg.Body().m_unItemID = pItem->GetItemID();
+ GCClientSystem()->BSendMessage( msg );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, pItem, "unwrapped_gift" );
+ }
+}
+
+void PerformToolAction_UnwrapGift( vgui::Panel* pParent, CEconItemView *pGiftItem )
+{
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UnwrapGift_Title", "#TF_UnwrapGift_Text",
+ "#GameUI_OK", "#Cancel",
+ &UnwrapGiftConfirm );
+ pDialog->AddStringToken( "item_name", pGiftItem->GetItemName() );
+ pDialog->SetContext( pGiftItem );
+}
diff --git a/game/client/econ/tool_items/gift_wrap_tool.h b/game/client/econ/tool_items/gift_wrap_tool.h
new file mode 100644
index 0000000..61f54e3
--- /dev/null
+++ b/game/client/econ/tool_items/gift_wrap_tool.h
@@ -0,0 +1,14 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef GIFT_WRAP_TOOL_H
+#define GIFT_WRAP_TOOL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#endif // GIFT_WRAP_TOOL_H
diff --git a/game/client/econ/tool_items/paint_can_tool.cpp b/game/client/econ/tool_items/paint_can_tool.cpp
new file mode 100644
index 0000000..3570c47
--- /dev/null
+++ b/game/client/econ/tool_items/paint_can_tool.cpp
@@ -0,0 +1,205 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "econ_item_tools.h"
+#include "tool_items.h"
+#include "paint_can_tool.h"
+#include "econ_ui.h"
+#include "gc_clientsystem.h"
+
+#ifdef TF_CLIENT_DLL
+#include "tf_shareddefs.h"
+#endif // TF_CLIENT_DLL
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+enum
+{
+#ifdef TF_CLIENT_DLL
+ kTeamID0 = TF_TEAM_RED,
+ kTeamID1 = TF_TEAM_BLUE,
+#else // !defined( TF_CLIENT_DLL )
+ kTeamID0 = -1,
+ kTeamID1 = -1,
+#endif // TF_CLIENT_DLL
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort paint application
+//-----------------------------------------------------------------------------
+class CConfirmApplyPaintCanBaseDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmApplyPaintCanBaseDialog, CBaseToolUsageDialog );
+
+protected:
+ CConfirmApplyPaintCanBaseDialog( vgui::Panel *pParent, const char *szType, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, szType, pTool, pToolSubject )
+ {
+ //
+ }
+
+public:
+ virtual void Apply( void )
+ {
+ // Send the apply request to the GC
+ GCSDK::CGCMsg< MsgGCPaintItem_t > msg( k_EMsgGCPaintItem );
+
+ msg.Body().m_unToolItemID = m_pToolModelPanel->GetItem()->GetItemID();
+ msg.Body().m_unSubjectItemID = m_pSubjectModelPanel->GetItem()->GetItemID();
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "painted_item" );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+
+protected:
+ // Set up a particular panel for a certain item with a certain preview color.
+ static void SetupPaintModelPanel( CItemModelPanel *pPaintModelPanel, CEconItemView *pToolSubjectView, int iTeam, int iPaintRGB )
+ {
+ static CSchemaAttributeDefHandle pAttrDef_ItemTintRGB( "set item tint RGB" );
+
+ if ( !pAttrDef_ItemTintRGB )
+ return;
+
+ // Fake-paint the demonstration items.
+ pPaintModelPanel->SetItem( pToolSubjectView );
+ pPaintModelPanel->GetItem()->GetAttributeList()->SetRuntimeAttributeValue( pAttrDef_ItemTintRGB, iPaintRGB );
+
+ // Set the appropriate skins for each item given the team and the style selected.
+ int iStyleSkin = pToolSubjectView->GetSkin( iTeam );
+
+ if ( iStyleSkin == -1 )
+ {
+ // Fallback case: rely on default skins.
+ pPaintModelPanel->SetSkin( iTeam == kTeamID0 ? 0 : 1 );
+ }
+ else
+ {
+ pPaintModelPanel->SetSkin( iStyleSkin );
+ }
+
+ pPaintModelPanel->SetActAsButton( true, false );
+ pPaintModelPanel->GetItem()->InvalidateColor();
+ pPaintModelPanel->GetItem()->InvalidateOverrideColor();
+ }
+
+ CItemModelPanel *m_pPaintModelPanel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Preview a single-color paint
+//-----------------------------------------------------------------------------
+class CConfirmApplyPaintCanDialog : public CConfirmApplyPaintCanBaseDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmApplyPaintCanDialog, CConfirmApplyPaintCanBaseDialog );
+
+public:
+ CConfirmApplyPaintCanDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CConfirmApplyPaintCanBaseDialog( pParent, "ConfirmApplyPaintCanDialog", pTool, pToolSubject )
+ {
+ m_pPaintModelPanel = new CItemModelPanel( this, "paint_model" );
+ m_pPaintModelPanel->SetItem( pToolSubject );
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyPaintCanDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetupPaintModelPanel( m_pPaintModelPanel, m_pSubjectModelPanel->GetItem(), kTeamID0, m_pToolModelPanel->GetItem()->GetModifiedRGBValue( false ) );
+ }
+
+private:
+ CItemModelPanel *m_pPaintModelPanel;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Preview team-colored paint
+//-----------------------------------------------------------------------------
+class CConfirmApplyTeamColorPaintCanDialog : public CConfirmApplyPaintCanBaseDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmApplyTeamColorPaintCanDialog, CConfirmApplyPaintCanBaseDialog );
+
+public:
+ CConfirmApplyTeamColorPaintCanDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CConfirmApplyPaintCanBaseDialog( pParent, "ConfirmApplyTeamColorPaintCanDialog", pTool, pToolSubject )
+ {
+ m_pPaintModelPanel_Red = new CItemModelPanel( this, "paint_model_red" );
+ m_pPaintModelPanel_Red->SetItem( pToolSubject );
+ m_pPaintModelPanel_Blue = new CItemModelPanel( this, "paint_model_blue" );
+ m_pPaintModelPanel_Blue->SetItem( pToolSubject );
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyTeamColorPaintCanDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetupPaintModelPanel( m_pPaintModelPanel_Red, m_pSubjectModelPanel->GetItem(), kTeamID0, m_pToolModelPanel->GetItem()->GetModifiedRGBValue( false ) );
+ SetupPaintModelPanel( m_pPaintModelPanel_Blue, m_pSubjectModelPanel->GetItem(), kTeamID1, m_pToolModelPanel->GetItem()->GetModifiedRGBValue( true ) );
+
+ // @note Tom Bui: we need to change the global index so that the material does not get
+ // re-used for each item. Should be ok to change the high bit.
+ Assert( m_pPaintModelPanel_Red->GetItem() );
+ m_pPaintModelPanel_Red->GetItem()->SetItemID( m_pPaintModelPanel_Red->GetItem()->GetItemID() | ( 1LL << 63 ) );
+ }
+
+private:
+ CItemModelPanel *m_pPaintModelPanel_Red;
+ CItemModelPanel *m_pPaintModelPanel_Blue;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_PaintCan::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ // Bizarro logic: we have no way of finding out whether an item has two different
+ // colors of paint applied so instead we ask whether both colors are
+ // the same.
+ CBaseToolUsageDialog *dialog = NULL;
+ if ( pTool->GetModifiedRGBValue( true ) != pTool->GetModifiedRGBValue( false ) )
+ {
+ dialog = new CConfirmApplyTeamColorPaintCanDialog( pParent, pTool, pSubject );
+ }
+ else
+ {
+ dialog = new CConfirmApplyPaintCanDialog( pParent, pTool, pSubject );
+ }
+ vgui::SETUP_PANEL( dialog );
+ MakeModalAndBringToFront( dialog );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the paint can response
+//-----------------------------------------------------------------------------
+class CGCPaintItemResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCPaintItemResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+ InventoryManager()->ShowItemsPickedUp( true );
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCPaintItemResponse, "CGCPaintItemResponse", k_EMsgGCPaintItemResponse, GCSDK::k_EServerTypeGCClient );
diff --git a/game/client/econ/tool_items/paint_can_tool.h b/game/client/econ/tool_items/paint_can_tool.h
new file mode 100644
index 0000000..36fa9de
--- /dev/null
+++ b/game/client/econ/tool_items/paint_can_tool.h
@@ -0,0 +1,15 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef PAINT_CAN_TOOL_H
+#define PAINT_CAN_TOOL_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#endif // PAINT_CAN_TOOL_H
diff --git a/game/client/econ/tool_items/rename_tool_ui.cpp b/game/client/econ/tool_items/rename_tool_ui.cpp
new file mode 100644
index 0000000..af76dc0
--- /dev/null
+++ b/game/client/econ/tool_items/rename_tool_ui.cpp
@@ -0,0 +1,306 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui/IInput.h"
+#include "vgui//ILocalize.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "econ_item_tools.h"
+#include "tool_items.h"
+#include "rename_tool_ui.h"
+#include "econ_ui.h"
+#include "gc_clientsystem.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+CNameToolUsageDialog::CNameToolUsageDialog( vgui::Panel *pParent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, bool bDescription )
+: CBaseToolUsageDialog( pParent, pszName, pTool, pToolSubject )
+{
+ m_bDescription = bDescription;
+}
+
+int CNameToolUsageDialog::GetMaxLength()
+{
+ if ( m_bDescription )
+ return MAX_ITEM_CUSTOM_DESC_LENGTH;
+ else
+ return MAX_ITEM_CUSTOM_NAME_LENGTH;
+}
+
+int CNameToolUsageDialog::GetMaxDBSize()
+{
+ if ( m_bDescription )
+ return MAX_ITEM_CUSTOM_DESC_DATABASE_SIZE;
+ else
+ return MAX_ITEM_CUSTOM_NAME_DATABASE_SIZE;
+}
+
+void CEconTool_NameTag::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CRequestNameDialog *dialog = vgui::SETUP_PANEL( new CRequestNameDialog( pParent, "ItemRenameDialog", pTool, pSubject, false ) );
+ MakeModalAndBringToFront( dialog );
+}
+
+//-----------------------------------------------------------------------------
+CRequestNameDialog::CRequestNameDialog( vgui::Panel *parent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, bool bDescription ) :
+ CNameToolUsageDialog( parent, pszName, pTool, pToolSubject, bDescription )
+{
+ m_pCustomNameEntry = new vgui::TextEntry( this, "CustomNameEntry" );
+ m_bDescription = bDescription;
+}
+
+//-----------------------------------------------------------------------------
+void CRequestNameDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "resource/UI/ItemRenameDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ m_pOldNameLabel = dynamic_cast<vgui::Label *>( FindChildByName( "OldItemNameDescLabel" ) );
+ if ( m_pOldNameLabel )
+ {
+ if ( m_bDescription )
+ m_pOldNameLabel->SetText( g_pVGuiLocalize->Find( "#ToolItemRenameOldItemDesc" ) );
+ else
+ m_pOldNameLabel->SetText( g_pVGuiLocalize->Find( "#ToolItemRenameOldItemName" ) );
+ }
+
+ m_pNewNameLabel = dynamic_cast<vgui::Label *>( FindChildByName( "NewItemNameDescLabel" ) );
+ if ( m_pNewNameLabel )
+ {
+ if ( m_bDescription )
+ m_pNewNameLabel->SetText( g_pVGuiLocalize->Find( "#ToolItemRenameNewItemDesc" ) );
+ else
+ m_pNewNameLabel->SetText( g_pVGuiLocalize->Find( "#ToolItemRenameNewItemName" ) );
+ }
+
+ m_pOldName = dynamic_cast<vgui::Label *>( FindChildByName( "OldItemNameLabel" ) );
+ if ( m_pOldName )
+ {
+ if ( m_bDescription )
+ {
+ CEconItem *pSOCData = m_pSubjectModelPanel->GetItem()->GetSOCData();
+ if ( pSOCData && pSOCData->GetCustomDesc() )
+ m_pOldName->SetText( pSOCData->GetCustomDesc() );
+ else
+ m_pOldName->SetText( m_pSubjectModelPanel->GetItem()->GetStaticData()->GetItemDesc() );
+ }
+ else
+ {
+ CEconItem *pSOCData = m_pSubjectModelPanel->GetItem()->GetSOCData();
+ if ( pSOCData && pSOCData->GetCustomName() )
+ m_pOldName->SetText( pSOCData->GetCustomName() );
+ else
+ m_pOldName->SetText( m_pSubjectModelPanel->GetItem()->GetStaticData()->GetItemBaseName() );
+ }
+ }
+
+ CExButton *pOKButton = dynamic_cast< CExButton* >( FindChildByName( "OkButton" ) );
+ if ( pOKButton )
+ {
+ if ( m_bDescription )
+ pOKButton->SetText( "#CraftDescribeOk" );
+ }
+
+ m_pCustomNameEntry->SetMaximumCharCount( GetMaxLength() );
+ m_pCustomNameEntry->SetAllowNonAsciiCharacters( true );
+}
+
+
+//-----------------------------------------------------------------------------
+void CRequestNameDialog::MoveToFront()
+{
+ BaseClass::MoveToFront();
+
+ // do this after MoveToFront so we can force the text box to have focus instead
+ // of the dialog itself
+ m_pCustomNameEntry->RequestFocus();
+}
+
+
+//-----------------------------------------------------------------------------
+void CRequestNameDialog::Apply( void )
+{
+ const int maxNameLength = MAX_ITEM_CUSTOM_DESC_LENGTH + 1;
+ wchar_t inputName[ maxNameLength ];
+
+ m_pCustomNameEntry->GetText( inputName, sizeof(inputName) );
+
+ // pop up modal confirmation dialog
+ CConfirmNameDialog *dialog = vgui::SETUP_PANEL( new CConfirmNameDialog( GetParent(), "ItemRenameConfirmationDialog", m_pToolModelPanel->GetItem(), m_pSubjectModelPanel->GetItem(), inputName, m_bDescription ) );
+ MakeModalAndBringToFront( dialog );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gives focus back to the name entry field after the mouse enters a
+// item model panel
+//-----------------------------------------------------------------------------
+void CRequestNameDialog::OnItemPanelEntered( vgui::Panel *panel )
+{
+ CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
+
+ if ( pItemPanel && IsVisible() )
+ {
+ // The item panel is going to try and steal our focus. Steal it back!
+ m_pCustomNameEntry->RequestFocus();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+CConfirmNameDialog::CConfirmNameDialog( vgui::Panel *parent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, const wchar_t *name, bool bDescription ) :
+ CNameToolUsageDialog( parent, pszName, pTool, pToolSubject, bDescription )
+{
+ Q_wcsncpy( m_name, name, sizeof(m_name) );
+ m_bDescription = bDescription;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// We're going to want to flesh this out to trim off leading/training spaces, etc
+//
+bool CConfirmNameDialog::IsNameValid( void ) const
+{
+ // legal names are 1 or more alphanumeric values (only)
+ const wchar_t *c = m_name;
+ int length = 0;
+ while( *c )
+ {
+ // no leading spaces
+ if ( length == 0 && *c == ' ' )
+ return false;
+
+ ++c;
+ ++length;
+ }
+
+ // no trailing spaces
+ if ( length > 0 && m_name[length-1] == ' ' )
+ return false;
+
+ return (length > 0);
+}
+
+
+//-----------------------------------------------------------------------------
+void CConfirmNameDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ if ( !IsNameValid() )
+ {
+ // pop up bad name dialog
+ LoadControlSettings( "resource/UI/ItemRenameInvalidDialog.res" );
+ }
+ else
+ {
+ // pop up "are you sure" dialog
+ LoadControlSettings( "resource/UI/ItemRenameConfirmationDialog.res" );
+ }
+
+ // Set our dialog name, but pre & post pend it with quotes
+ wchar_t tmpname[ MAX_ITEM_CUSTOM_DESC_LENGTH+3 ];
+ V_wcscpy_safe( tmpname, L"\"" );
+ V_wcscat_safe( tmpname, m_name );
+ V_wcscat_safe( tmpname, L"\"" );
+ SetDialogVariable( "name", tmpname );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+
+//-----------------------------------------------------------------------------
+void CConfirmNameDialog::Apply( void )
+{
+ // the GC stores 8-bit chars, so convert Unicode name to UTF8
+ char* utf8Name = new char[ GetMaxDBSize() ];
+ int count = V_UnicodeToUTF8( m_name, utf8Name, GetMaxDBSize() );
+
+ if ( count > GetMaxDBSize() )
+ {
+ // the encoded name exceeds the GC's storage limit
+ return;
+ }
+
+ if ( m_pSubjectModelPanel->GetItem()->GetItemID() != INVALID_ITEM_ID )
+ {
+ // Name has been confirmed - send message to GC to apply name to item
+ GCSDK::CGCMsg< MsgGCNameItem_t > msg( k_EMsgGCNameItem );
+
+ msg.Body().m_unToolItemID = m_pToolModelPanel->GetItem()->GetItemID();
+ msg.Body().m_unSubjectItemID = m_pSubjectModelPanel->GetItem()->GetItemID();
+ msg.AddStrData( utf8Name );
+ GCClientSystem()->BSendMessage( msg );
+ }
+ else
+ {
+ // Name has been confirmed - send message to GC to apply name to item
+ GCSDK::CGCMsg< MsgGCNameBaseItem_t > msg( k_EMsgGCNameBaseItem );
+
+ msg.Body().m_unToolItemID = m_pToolModelPanel->GetItem()->GetItemID();
+ msg.Body().m_unBaseItemDefinitionID = m_pSubjectModelPanel->GetItem()->GetStaticData()->GetDefinitionIndex();
+ msg.AddStrData( utf8Name );
+ GCClientSystem()->BSendMessage( msg );
+ }
+
+ if ( m_bDescription )
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "redescription_item" );
+ else
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "renamed_item" );
+
+ delete []utf8Name;
+}
+
+//-----------------------------------------------------------------------------
+void CConfirmNameDialog::OnCommand( const char *command )
+{
+ BaseClass::OnCommand( command );
+
+ if ( !Q_stricmp( command, "backfrominvalid" ) )
+ {
+ // Re-open the name dialog
+ CRequestNameDialog *dialog = vgui::SETUP_PANEL( new CRequestNameDialog( GetParent(), "ItemRenameDialog", m_pToolModelPanel->GetItem(), m_pSubjectModelPanel->GetItem(), m_bDescription ) );
+ MakeModalAndBringToFront( dialog );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the name base item response
+//-----------------------------------------------------------------------------
+class CGCNameBaseItemResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCNameBaseItemResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+ InventoryManager()->ShowItemsPickedUp( true );
+ return true;
+ }
+
+};
+
+GC_REG_JOB( GCSDK::CGCClient, CGCNameBaseItemResponse, "CGCNameBaseItemResponse", k_EMsgGCNameBaseItemResponse, GCSDK::k_EServerTypeGCClient );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: UI Hook for applying a new description to items.
+//-----------------------------------------------------------------------------
+void CEconTool_DescTag::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CRequestNameDialog *dialog = vgui::SETUP_PANEL( new CRequestNameDialog( pParent, "ItemRenameDialog", pTool, pSubject, true ) );
+ MakeModalAndBringToFront( dialog );
+} \ No newline at end of file
diff --git a/game/client/econ/tool_items/rename_tool_ui.h b/game/client/econ/tool_items/rename_tool_ui.h
new file mode 100644
index 0000000..c65c09c
--- /dev/null
+++ b/game/client/econ/tool_items/rename_tool_ui.h
@@ -0,0 +1,74 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef RENAME_TOOL_UI_H
+#define RENAME_TOOL_UI_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tool_items.h"
+
+class CNameToolUsageDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CNameToolUsageDialog, CBaseToolUsageDialog );
+
+public:
+ CNameToolUsageDialog( vgui::Panel *pParent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, bool bDescription );
+ virtual int GetMaxLength();
+ virtual int GetMaxDBSize();
+
+protected:
+ bool m_bDescription;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: A dialog used to input a Tool's name payload
+//-----------------------------------------------------------------------------
+class CRequestNameDialog : public CNameToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CRequestNameDialog, CNameToolUsageDialog );
+
+public:
+ CRequestNameDialog( vgui::Panel *pParent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, bool bDescription );
+
+ virtual void MoveToFront();
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void Apply( void );
+
+ MESSAGE_FUNC_PTR( OnItemPanelEntered, "ItemPanelEntered", panel );
+
+private:
+ vgui::TextEntry *m_pCustomNameEntry;
+ vgui::Label *m_pOldNameLabel;
+ vgui::Label *m_pOldName;
+ vgui::Label *m_pNewNameLabel;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm name and commit or reject
+//-----------------------------------------------------------------------------
+class CConfirmNameDialog : public CNameToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmNameDialog, CNameToolUsageDialog );
+
+public:
+ CConfirmNameDialog( vgui::Panel *pParent, const char* pszName, CEconItemView *pTool, CEconItemView *pToolSubject, const wchar_t *name, bool bDescription );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void Apply( void );
+ virtual void OnCommand( const char *command );
+
+private:
+ wchar_t m_name[ MAX_ITEM_CUSTOM_DESC_LENGTH+1 ];
+
+ bool IsNameValid( void ) const;
+};
+
+
+#endif // RENAME_TOOL_UI_H
diff --git a/game/client/econ/tool_items/tool_items.cpp b/game/client/econ/tool_items/tool_items.cpp
new file mode 100644
index 0000000..2fbed7f
--- /dev/null
+++ b/game/client/econ/tool_items/tool_items.cpp
@@ -0,0 +1,968 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui_controls/TextImage.h"
+#include "vgui/IInput.h"
+#include "econ_item_system.h"
+#include "econ_item_constants.h"
+#include "econ_gcmessages.h"
+#include "econ_ui.h"
+#include "tool_items.h"
+#ifdef TF_CLIENT_DLL
+ #include "tf_item_tools.h"
+#else
+ #include "econ_item_tools.h"
+#endif
+#include <vgui/ILocalize.h>
+#include "gc_clientsystem.h"
+#include "item_style_select_dialog.h" // for CComboBoxBackpackOverlayDialogBase
+#include "backpack_panel.h"
+#include "vgui_controls/Controls.h"
+#include "vgui/ISurface.h"
+
+#ifdef TF_CLIENT_DLL
+#include "character_info_panel.h"
+#include "c_tf_gamestats.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseToolUsageDialog::CBaseToolUsageDialog( vgui::Panel *parent, const char *panelName, CEconItemView *pTool, CEconItemView *pToolSubject ) : vgui::EditablePanel( parent, panelName )
+{
+ m_pToolModelPanel = new CItemModelPanel( this, "tool_modelpanel" );
+ m_pToolModelPanel->SetActAsButton( true, true );
+ m_pSubjectModelPanel = new CItemModelPanel( this, "subject_modelpanel" );
+ m_pSubjectModelPanel->SetActAsButton( true, true );
+ m_pMouseOverItemPanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "mouseoveritempanel" ) );
+
+ m_pMouseOverTooltip = new CItemModelPanelToolTip( this );
+ m_pMouseOverTooltip->SetupPanels( this, m_pMouseOverItemPanel );
+ m_pToolModelPanel->SetTooltip( m_pMouseOverTooltip, "" );
+ m_pSubjectModelPanel->SetTooltip( m_pMouseOverTooltip, "" );
+
+ m_pToolModelPanel->SetItem( pTool );
+ m_pToolModelPanel->SetShowEquipped( true );
+ m_pSubjectModelPanel->SetItem( pToolSubject );
+ m_pSubjectModelPanel->SetShowEquipped( true );
+
+ m_pTitleLabel = NULL;
+ m_pszInternalPanelName = panelName ? panelName : "unknown";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseToolUsageDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ SetDialogVariable( "oldname", m_pSubjectModelPanel->GetItem()->GetItemName() );
+
+ m_pTitleLabel = dynamic_cast<vgui::Label*>( FindChildByName("TitleLabel") );
+ if ( m_pTitleLabel )
+ {
+ wchar_t *pszBaseString = g_pVGuiLocalize->Find( "ToolDialogTitle" );
+ if ( pszBaseString )
+ {
+ wchar_t wTemp[256];
+ g_pVGuiLocalize->ConstructString_safe( wTemp, pszBaseString, 2, m_pToolModelPanel->GetItem()->GetItemName(), m_pSubjectModelPanel->GetItem()->GetItemName() );
+ m_pTitleLabel->SetText( wTemp );
+
+ // Now go through the string and find the escape characters telling us where the color changes are
+ m_pTitleLabel->GetTextImage()->ClearColorChangeStream();
+
+ // We change the title's text color to match the colors of the matching model panel backgrounds
+ wchar_t *txt = wTemp;
+ int iWChars = 0;
+ Color colCustom;
+ while ( txt && *txt )
+ {
+ switch ( *txt )
+ {
+ case 0x01: // Normal color
+ m_pTitleLabel->GetTextImage()->AddColorChange( Color(235,226,202,255), iWChars );
+ break;
+ case 0x02: // Item 1 color
+ m_pTitleLabel->GetTextImage()->AddColorChange( Color(112,176,74,255), iWChars );
+ break;
+ case 0x03: // Item 2 color
+ m_pTitleLabel->GetTextImage()->AddColorChange( Color(71,98,145,255), iWChars );
+ break;
+ default:
+ break;
+ }
+ txt++;
+ iWChars++;
+ }
+ }
+ }
+
+ vgui::Panel *pToolIcon = FindChildByName( "tool_icon" );
+ if ( pToolIcon )
+ {
+ pToolIcon->SetMouseInputEnabled( false );
+ pToolIcon->SetKeyBoardInputEnabled( false );
+ }
+ vgui::Panel *pSubjectIcon = FindChildByName( "subject_icon" );
+ if ( pSubjectIcon )
+ {
+ pSubjectIcon->SetMouseInputEnabled( false );
+ pSubjectIcon->SetKeyBoardInputEnabled( false );
+ }
+
+ // @note Tom Bui: because the children have already applied their scheme settings and/or performed their layout before
+ // this dialog has had a chance to load its res file, we need to manually invalidate the layout of these children
+ // to make sure that the settings are valid with respect to what the parent wants
+ m_pToolModelPanel->UpdatePanels();
+ m_pSubjectModelPanel->UpdatePanels();
+ m_pMouseOverItemPanel->SetBorder( pScheme->GetBorder("LoadoutItemPopupBorder") );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseToolUsageDialog::PerformLayout()
+{
+ BaseClass::PerformLayout();
+ // if ( m_pMouseOverItemPanel->IsVisible() )
+ // {
+ // // The mouseover panel was visible. Fake a panel entry into the original panel to get it to show up again properly.
+ // if ( m_pItemPanelBeingMousedOver )
+ // {
+ // OnItemPanelEntered( m_pItemPanelBeingMousedOver );
+ // }
+ // else
+ // {
+ // HideMouseOverPanel();
+ // }
+ // }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseToolUsageDialog::OnCommand( const char *command )
+{
+ // in any case, our dialog is going away
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+
+#ifdef TF_CLIENT_DLL
+ const char *pSubjectBaseName = m_pSubjectModelPanel && m_pSubjectModelPanel->GetItem() && m_pSubjectModelPanel->GetItem()->GetItemDefinition()
+ ? m_pSubjectModelPanel->GetItem()->GetItemDefinition()->GetItemBaseName()
+ : "n/a";
+#endif
+
+ if ( !Q_stricmp( command, "apply" ) )
+ {
+#ifdef TF_CLIENT_DLL
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( CFmtStr( "tool_usage_proceed(%s)", m_pszInternalPanelName ).Access(),
+ pSubjectBaseName );
+#endif
+ // Call before Apply() in case it creates new dialogs that wants to handle prevention
+ EconUI()->SetPreventClosure( false );
+ Apply();
+ }
+ else // "cancel"
+ {
+#ifdef TF_CLIENT_DLL
+ C_CTFGameStats::ImmediateWriteInterfaceEvent( CFmtStr( "tool_usage_cancel(%s)", m_pszInternalPanelName ).Access(),
+ pSubjectBaseName );
+#endif
+
+ EconUI()->SetPreventClosure( false );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "inventory_updated" );
+ if ( event )
+ {
+ gameeventmanager->FireEventClientSide( event );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Utility function
+//-----------------------------------------------------------------------------
+void CBaseToolUsageDialog::OnKeyCodeTyped( vgui::KeyCode code )
+{
+ if( code == KEY_ESCAPE )
+ {
+ OnCommand( "cancel" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodeTyped( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Utility function
+//-----------------------------------------------------------------------------
+void CBaseToolUsageDialog::OnKeyCodePressed( vgui::KeyCode code )
+{
+ ButtonCode_t nButtonCode = GetBaseButtonCode( code );
+
+ if (nButtonCode == KEY_XBUTTON_LEFT ||
+ nButtonCode == KEY_XSTICK1_LEFT ||
+ nButtonCode == KEY_XSTICK2_LEFT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_LEFT ||
+ code == KEY_LEFT ||
+ nButtonCode == KEY_XBUTTON_RIGHT ||
+ nButtonCode == KEY_XSTICK1_RIGHT ||
+ nButtonCode == KEY_XSTICK2_RIGHT ||
+ nButtonCode == STEAMCONTROLLER_DPAD_RIGHT ||
+ code == KEY_RIGHT ||
+ nButtonCode == KEY_XBUTTON_UP ||
+ nButtonCode == KEY_XSTICK1_UP ||
+ nButtonCode == KEY_XSTICK2_UP ||
+ nButtonCode == STEAMCONTROLLER_DPAD_UP ||
+ code == KEY_UP ||
+ nButtonCode == KEY_XBUTTON_DOWN ||
+ nButtonCode == KEY_XSTICK1_DOWN ||
+ nButtonCode == KEY_XSTICK2_DOWN ||
+ nButtonCode == STEAMCONTROLLER_DPAD_DOWN ||
+ code == KEY_DOWN )
+ {
+ // eat all the movement keys so the selection doesn't update behind the dialog
+ }
+ else if( nButtonCode == KEY_XBUTTON_A || code == KEY_ENTER || nButtonCode == STEAMCONTROLLER_A )
+ {
+ OnCommand( "apply" );
+ }
+ else if( nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_B )
+ {
+ OnCommand( "cancel" );
+ }
+ else
+ {
+ BaseClass::OnKeyCodePressed( code );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Utility function
+//-----------------------------------------------------------------------------
+void MakeModalAndBringToFront( vgui::EditablePanel *dialog )
+{
+ dialog->SetVisible( true );
+ if ( dialog->GetParent() == NULL )
+ {
+ dialog->MakePopup();
+ }
+ dialog->SetZPos( 10000 );
+ dialog->MoveToFront();
+ dialog->SetKeyBoardInputEnabled( true );
+ dialog->SetMouseInputEnabled( true );
+ TFModalStack()->PushModal( dialog );
+
+ EconUI()->SetPreventClosure( true );
+}
+
+//-----------------------------------------------------------------------------
+//
+// Given a tool and an item to apply the tool's effects upon,
+// gather required information from the user and
+// send a change request to the GC.
+//
+bool ApplyTool( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+{
+ if ( !pTool || !pToolSubject )
+ return false;
+
+ if ( !CEconSharedToolSupport::ToolCanApplyTo( pTool, pToolSubject ) )
+ return false;
+
+ // this tool can be applied to this subject item
+ const IEconTool *pEconTool = pTool->GetStaticData()->GetEconTool();
+ if ( !pEconTool )
+ return false;
+
+ pEconTool->OnClientApplyTool( pTool, pToolSubject, pParent );
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: STRANGE COUNT TRANSFER
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+class CConfirmStrangeCountTransferApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmStrangeCountTransferApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmStrangeCountTransferApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyStrangeCountTransferDialog", pTool, pToolSubject )
+ {
+ m_pItemSrc = NULL;
+ m_pItemDest = NULL;
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyDuckTokenDialog.res" ); // fix me
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyStrangeCountTransfer> msg( k_EMsgGCApplyStrangeCountTransfer );
+
+ if ( !m_pItemSrc || !m_pItemDest )
+ return;
+
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_item_src_item_id( m_pItemSrc->GetItemID() );
+ msg.Body().set_item_dest_item_id( m_pItemDest->GetItemID() );
+ GCClientSystem()->BSendMessage( msg );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_strangecounttransfer", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+
+ bool SetItems( CEconItemView *pItemSrc, CEconItemView *pItemDest )
+ {
+ if ( !pItemSrc || !pItemDest )
+ return false;
+
+ if ( CEconTool_StrangeCountTransfer::AreItemsEligibleForStrangeCountTransfer( pItemSrc, pItemDest ) )
+ {
+ m_pItemSrc = pItemSrc;
+ m_pItemDest = pItemDest;
+
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ CEconItemView *m_pItemSrc;
+ CEconItemView *m_pItemDest;
+};
+
+/*static */bool CEconTool_StrangeCountTransfer::SetItems( CEconItemView *pItemSrc, CEconItemView *pItemDest )
+{
+ if ( !pItemSrc || !pItemDest )
+ return false;
+
+ if ( CEconTool_StrangeCountTransfer::AreItemsEligibleForStrangeCountTransfer( pItemSrc, pItemDest ) )
+ {
+ CEconTool_StrangeCountTransfer::m_pItemSrc = pItemSrc;
+ CEconTool_StrangeCountTransfer::m_pItemDest = pItemDest;
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_StrangeCountTransfer::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmStrangeCountTransferApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmStrangeCountTransferApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+
+// ****************************************************************************
+// STRANGE PARTS
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort strange part application
+//-----------------------------------------------------------------------------
+class CConfirmStrangePartApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmStrangePartApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmStrangePartApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void Apply( void );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmStrangePartApplicationDialog::CConfirmStrangePartApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyStrangePartApplicationDialog", pTool, pToolSubject )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmStrangePartApplicationDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyStrangePartApplicationDialog.res" );
+
+ int iRemainingStrangePartSlots = 0;
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ if ( !GetKillEaterAttr_IsUserCustomizable( i ) )
+ continue;
+
+ if ( !m_pSubjectModelPanel->GetItem()->FindAttribute( GetKillEaterAttr_Score( i ) ) )
+ ++iRemainingStrangePartSlots;
+ }
+
+ SetDialogVariable( "remaining_strange_part_slots", iRemainingStrangePartSlots );
+ SetDialogVariable( "maximum_strange_part_slots", GetKillEaterAttrCount_UserCustomizable() );
+ SetDialogVariable( "subject_item_def_name", GLocalizationProvider()->Find( m_pSubjectModelPanel->GetItem()->GetItemDefinition()->GetItemBaseName() ) );
+ SetDialogVariable( "slot_singular_plural", GLocalizationProvider()->Find( iRemainingStrangePartSlots == 1 ? "#Econ_FreeSlot_Singular" : "#Econ_FreeSlot_Plural" ) );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmStrangePartApplicationDialog::Apply()
+{
+ GCSDK::CProtoBufMsg<CMsgApplyStrangePart> msg( k_EMsgGCApplyStrangePart );
+
+ msg.Body().set_strange_part_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_item_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_strange_part", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_StrangePart::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmStrangePartApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmStrangePartApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort strange restriction application
+//-----------------------------------------------------------------------------
+class CConfirmStrangeRestrictionApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmStrangeRestrictionApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmStrangeRestrictionApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject, int iStrangeSlot, const char *pszStatLocalizationToken );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void Apply( void );
+
+private:
+ int m_iStrangeSlot;
+ const char *m_pszStatLocalizationToken;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CConfirmStrangeRestrictionApplicationDialog::CConfirmStrangeRestrictionApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject, int iStrangeSlot, const char *pszStatLocalizationToken )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyStrangeRestrictionApplicationDialog", pTool, pToolSubject )
+ , m_iStrangeSlot( iStrangeSlot )
+ , m_pszStatLocalizationToken( pszStatLocalizationToken )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmStrangeRestrictionApplicationDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyStrangeRestrictionApplicationDialog.res" );
+
+ SetDialogVariable( "stat_name", GLocalizationProvider()->Find( m_pszStatLocalizationToken ) );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CConfirmStrangeRestrictionApplicationDialog::Apply()
+{
+ GCSDK::CProtoBufMsg<CMsgApplyStrangeRestriction> msg( k_EMsgGCApplyStrangeRestriction );
+
+ msg.Body().set_strange_part_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_item_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_strange_attr_index( m_iStrangeSlot );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_strange_restriction", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CSelectStrangePartToRestrictDialog : public CComboBoxBackpackOverlayDialogBase
+{
+public:
+ DECLARE_CLASS_SIMPLE( CSelectStrangePartToRestrictDialog, CComboBoxBackpackOverlayDialogBase );
+
+public:
+ CSelectStrangePartToRestrictDialog( vgui::Panel *pParent, CEconItemView *pToolItem, CEconItemView *pSubjectItem )
+ : CComboBoxBackpackOverlayDialogBase( pParent, pSubjectItem )
+ , m_ToolItem( *pToolItem )
+ , m_SubjectItem( *pSubjectItem )
+ {
+ //
+ }
+
+private:
+ virtual void PopulateComboBoxOptions()
+ {
+ const wchar_t *pLocBase = GLocalizationProvider()->Find( "#ApplyStrangeRestrictionCombo" );
+
+ const CEconTool_StrangePartRestriction *pToolRestriction = m_ToolItem.GetItemDefinition()->GetTypedEconTool<CEconTool_StrangePartRestriction>();
+ Assert( pToolRestriction );
+
+ KeyValues *pKeyValues = new KeyValues( "data" );
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ uint32 unScoreType;
+ if ( !GetItemSchema()->BCanStrangeFilterApplyToStrangeSlotInItem( pToolRestriction->GetRestrictionType(), pToolRestriction->GetRestrictionValue(), &m_SubjectItem, i, &unScoreType ) )
+ continue;
+
+ const char *pszTypeLocKey = GetItemSchema()->GetKillEaterScoreTypeLocString( unScoreType );
+ if ( !pszTypeLocKey )
+ continue;
+
+ pKeyValues->SetInt( "data", i );
+ pKeyValues->SetString( "token", pszTypeLocKey );
+
+ GetComboBox()->AddItem( CConstructLocalizedString( pLocBase, GLocalizationProvider()->Find( pszTypeLocKey ) ), pKeyValues );
+ }
+ pKeyValues->deleteThis();
+
+ Assert( GetComboBox()->GetItemCount() > 0 );
+
+ GetComboBox()->ActivateItemByRow( 0 );
+ }
+
+ virtual void OnComboBoxApplication()
+ {
+ KeyValues *pKVActiveUserData = GetComboBox()->GetActiveItemUserData();
+ int iIndex = pKVActiveUserData ? pKVActiveUserData->GetInt( "data", -1 ) : -1;
+ if ( iIndex < 0 )
+ return;
+
+ // FIXME: CConfirmStrangePartApplicationDialog is wrong class
+ CConfirmStrangeRestrictionApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmStrangeRestrictionApplicationDialog( GetParent(), &m_ToolItem, &m_SubjectItem, iIndex, pKVActiveUserData ? pKVActiveUserData->GetString( "token", NULL ) : NULL ) );
+ MakeModalAndBringToFront( pDialog );
+ }
+
+ virtual const char *GetTitleLabelLocalizationToken() const { return "#ApplyStrangeRestrictionPartTitle"; }
+
+private:
+ CEconItemView m_ToolItem;
+ CEconItemView m_SubjectItem;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_StrangePartRestriction::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ if ( EconUI()->GetBackpackPanel() )
+ {
+ EconUI()->GetBackpackPanel()->SetComboBoxOverlaySelectionItem( pSubject );
+ }
+
+ CSelectStrangePartToRestrictDialog *pDialog = vgui::SETUP_PANEL( new CSelectStrangePartToRestrictDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+class CWaitingDialog : public CGenericWaitingDialog
+{
+public:
+ CWaitingDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent )
+ {
+ }
+
+protected:
+ virtual void OnTimeout()
+ {
+ // Play an exciting sound!
+ vgui::surface()->PlaySound( "misc/achievement_earned.wav" );
+
+ // Show them the result item.
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort card upgrade tool application
+//-----------------------------------------------------------------------------
+class CConfirmApplyStrangifierDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmApplyStrangifierDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmApplyStrangifierDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject, const char *pszPromptLocToken, const char *pszTransactionReason, const char *pszUpdatingText = "" )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyStrangifierDialog", pTool, pToolSubject )
+ , m_sPromptLocToken( pszPromptLocToken )
+ , m_sTransactionReason( pszTransactionReason )
+ , m_sUpdatingText( pszUpdatingText )
+ {
+ //
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyStrangifierDialog.res" );
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ CExLabel* pTextLabel = dynamic_cast<CExLabel*>( FindChildByName( "ConfirmLabel" ) );
+ if( pTextLabel && m_pToolModelPanel )
+ {
+ wchar_t *pszBaseString = g_pVGuiLocalize->Find( m_sPromptLocToken );
+ wchar_t wTempFinalString[1024] = { 0 };
+ if ( pszBaseString )
+ {
+ V_wcscpy_safe( wTempFinalString, pszBaseString );
+ }
+
+ // If the strangifier is untradable, add an extra warning in the prompt to let the user know
+ if( m_pToolModelPanel->GetItem() && !m_pToolModelPanel->GetItem()->IsTradable() &&
+ m_pSubjectModelPanel && m_pSubjectModelPanel->GetItem() )
+ {
+ wchar_t *pszUntradableString = g_pVGuiLocalize->Find( "ToolStrangifierUntradableWarning" );
+
+ // Stick the names of the items into the string
+ wchar_t wTempUntradable[1024] = { 0 };
+ g_pVGuiLocalize->ConstructString_safe( wTempUntradable, pszUntradableString, 2, m_pToolModelPanel->GetItem()->GetItemName(), m_pSubjectModelPanel->GetItem()->GetItemName() );
+
+ // Concat onto the the original string
+ V_wcscat_safe( wTempFinalString, wTempUntradable, sizeof( wTempUntradable ) );
+ }
+
+ pTextLabel->SetText( wTempFinalString );
+ }
+
+ }
+
+ virtual void Apply( void )
+ {
+ if ( m_pSubjectModelPanel->GetItem()->GetItemID() != INVALID_ITEM_ID )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToItem> msg( k_EMsgGCApplyXifier );
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+ GCClientSystem()->BSendMessage( msg );
+ }
+ else
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToBaseItem> msg( k_EMsgGCApplyBaseItemXifier );
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_baseitem_def_index( m_pSubjectModelPanel->GetItem()->GetStaticData()->GetDefinitionIndex() );
+ GCClientSystem()->BSendMessage( msg );
+ }
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), m_sTransactionReason.String() );
+
+ if ( *m_sUpdatingText.String() )
+ {
+ vgui::surface()->PlaySound( "ui/item_gift_wrap_use.wav" );
+ ShowWaitingDialog( new CWaitingDialog( NULL ), m_sUpdatingText.String(), true, false, 5.0f );
+ }
+ }
+
+private:
+ CUtlString m_sPromptLocToken;
+ CUtlString m_sTransactionReason;
+ CUtlString m_sUpdatingText;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_Strangifier::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmApplyStrangifierDialog *pDialog = vgui::SETUP_PANEL( new CConfirmApplyStrangifierDialog( pParent, pTool, pSubject, "ToolStrangifierConfirm", "strangified_item" ) );
+ MakeModalAndBringToFront( pDialog );
+}
+//-----------------------------------------------------------------------------
+void CEconTool_KillStreakifier::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmApplyStrangifierDialog *pDialog = vgui::SETUP_PANEL( new CConfirmApplyStrangifierDialog( pParent, pTool, pSubject, "ToolKillStreakifierConfirm", "killstreakified_item" ) );
+ MakeModalAndBringToFront( pDialog );
+}
+//-----------------------------------------------------------------------------
+void CEconTool_Festivizer::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmApplyStrangifierDialog *pDialog = vgui::SETUP_PANEL( new CConfirmApplyStrangifierDialog( pParent, pTool, pSubject, "ToolFestivizerConfirm", "festivized_item", "#ToolFestivizerInProgress" ) );
+ MakeModalAndBringToFront( pDialog );
+}
+//-----------------------------------------------------------------------------
+void CEconTool_Unusualifier::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmApplyStrangifierDialog *pDialog = vgui::SETUP_PANEL( new CConfirmApplyStrangifierDialog( pParent, pTool, pSubject, "ToolUnusualifierConfirm", "unusualified_item", "#ToolUnusualifierInProgress" ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+class CConfirmUseItemEaterRechargerDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmUseItemEaterRechargerDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmUseItemEaterRechargerDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmUseItemEaterRechargerDialog", pTool, pToolSubject )
+ {
+
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmUseItemEaterRechargerDialog.res" );
+
+ // Find the number of charges to add by looking at item tool rescritions.
+ const CEconTool_ItemEaterRecharger *pTool = m_pToolModelPanel->GetItem()->GetItemDefinition()->GetTypedEconTool<CEconTool_ItemEaterRecharger>();
+ if ( pTool )
+ {
+ int iCharges = pTool->GetChargesForItemDefId( m_pSubjectModelPanel->GetItem()->GetItemDefIndex() );
+ SetDialogVariable( "charges_added", iCharges );
+ }
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToItem> msg( k_EMsgGCItemEaterRecharger );
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "recharging_item" );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_ItemEaterRecharger::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmUseItemEaterRechargerDialog *pDialog = vgui::SETUP_PANEL( new CConfirmUseItemEaterRechargerDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Confirm / abort card upgrade tool application
+//-----------------------------------------------------------------------------
+class CConfirmCardUpgradeApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmCardUpgradeApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmCardUpgradeApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyCardUpgradeApplicationDialog", pTool, pToolSubject )
+ {
+ //
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyCardUpgradeApplicationDialog.res" );
+
+ // See how many card upgrades have already been applied
+ CCountUserGeneratedAttributeIterator countIterator;
+ m_pSubjectModelPanel->GetItem()->IterateAttributes( &countIterator );
+
+ int iRemainingStrangePartSlots = GetMaxCardUpgradesPerItem() - countIterator.GetCount();
+
+ SetDialogVariable( "remaining_upgrade_card_slots", iRemainingStrangePartSlots );
+ SetDialogVariable( "subject_item_def_name", GLocalizationProvider()->Find( m_pSubjectModelPanel->GetItem()->GetItemDefinition()->GetItemBaseName() ) );
+ SetDialogVariable( "slot_singular_plural", GLocalizationProvider()->Find( iRemainingStrangePartSlots == 1 ? "#Econ_FreeSlot_Singular" : "#Econ_FreeSlot_Plural" ) );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyUpgradeCard> msg( k_EMsgGCApplyUpgradeCard );
+
+ msg.Body().set_upgrade_card_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_upgrade_card", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_UpgradeCard::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmCardUpgradeApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmCardUpgradeApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CConfirmTransmogrifyApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmTransmogrifyApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmTransmogrifyApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmTransmogrifyApplicationDialog", pTool, pToolSubject )
+ {
+ //
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmTransmogrifyApplicationDialog.res" );
+
+ const CEconTool_ClassTransmogrifier *pTool = m_pToolModelPanel->GetItem()->GetItemDefinition()->GetTypedEconTool<CEconTool_ClassTransmogrifier>();
+ Assert( pTool );
+
+ if ( pTool )
+ {
+ int iOutputClass = pTool->GetOutputClass();
+ if ( iOutputClass > 0 && iOutputClass < LOADOUT_COUNT )
+ {
+ SetDialogVariable( "output_class", GLocalizationProvider()->Find( g_aPlayerClassNames[ iOutputClass ] ) );
+ }
+ }
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToItem> msg( k_EMsgGCApplyClassTransmogrifier );
+
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_transmogrifier", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_ClassTransmogrifier::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmTransmogrifyApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmTransmogrifyApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+#ifdef TF_CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CConfirmSpellbookPageApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmSpellbookPageApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmSpellbookPageApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmSpellbookPageApplicationDialog", pTool, pToolSubject )
+ {
+ //
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmSpellbookPageApplicationDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToItem> msg( k_EMsgGCApplyHalloweenSpellbookPage );
+
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_spellbook_page", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_TFSpellbookPage::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmSpellbookPageApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmSpellbookPageApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+
+//-----------------------------------------------------------------------------
+class CConfirmDuckTokenApplicationDialog : public CBaseToolUsageDialog
+{
+ DECLARE_CLASS_SIMPLE( CConfirmDuckTokenApplicationDialog, CBaseToolUsageDialog );
+
+public:
+ CConfirmDuckTokenApplicationDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject )
+ : CBaseToolUsageDialog( pParent, "ConfirmApplyDuckTokenDialog", pTool, pToolSubject )
+ {
+ //
+ }
+
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme )
+ {
+ LoadControlSettings( "Resource/UI/econ/ConfirmApplyDuckTokenDialog.res" );
+
+ BaseClass::ApplySchemeSettings( pScheme );
+ }
+
+ virtual void Apply( void )
+ {
+ GCSDK::CProtoBufMsg<CMsgApplyToolToItem> msg( k_EMsgGCApplyDuckToken );
+
+ msg.Body().set_tool_item_id( m_pToolModelPanel->GetItem()->GetItemID() );
+ msg.Body().set_subject_item_id( m_pSubjectModelPanel->GetItem()->GetItemID() );
+
+ EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "applied_ducktoken", m_pToolModelPanel->GetItem()->GetItemDefIndex() );
+
+ GCClientSystem()->BSendMessage( msg );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CEconTool_DuckToken::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const
+{
+ CConfirmDuckTokenApplicationDialog *pDialog = vgui::SETUP_PANEL( new CConfirmDuckTokenApplicationDialog( pParent, pTool, pSubject ) );
+ MakeModalAndBringToFront( pDialog );
+}
+#endif // TF_CLIENT_DLL \ No newline at end of file
diff --git a/game/client/econ/tool_items/tool_items.h b/game/client/econ/tool_items/tool_items.h
new file mode 100644
index 0000000..14bfb36
--- /dev/null
+++ b/game/client/econ/tool_items/tool_items.h
@@ -0,0 +1,58 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TOOL_ITEMS_H
+#define TOOL_ITEMS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "item_model_panel.h"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CBaseToolUsageDialog : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CBaseToolUsageDialog, vgui::EditablePanel );
+
+public:
+ CBaseToolUsageDialog( vgui::Panel *pParent, const char *panelName, CEconItemView *pTool, CEconItemView *pToolSubject );
+
+ virtual void ApplySchemeSettings( vgui::IScheme *scheme );
+ virtual void PerformLayout();
+ virtual void OnCommand( const char *command );
+ virtual void OnKeyCodeTyped( vgui::KeyCode code ) OVERRIDE;
+ virtual void OnKeyCodePressed( vgui::KeyCode code ) OVERRIDE;
+
+ virtual void Apply( void ) { return; }
+
+ inline CEconItemView *GetToolItem() { return m_pToolModelPanel->GetItem(); };
+ inline CEconItemView *GetSubjectItem() { return m_pSubjectModelPanel->GetItem(); };
+
+protected:
+ CItemModelPanel *m_pToolModelPanel;
+ CItemModelPanel *m_pSubjectModelPanel;
+ CItemModelPanel *m_pMouseOverItemPanel;
+ CItemModelPanelToolTip *m_pMouseOverTooltip;
+ vgui::Label *m_pTitleLabel;
+
+ const char *m_pszInternalPanelName;
+};
+
+
+// Utility function for tool dialogs.
+void MakeModalAndBringToFront( vgui::EditablePanel *dialog );
+
+bool ToolCanApplyTo( CEconItemView *pTool, CEconItemView *pToolSubject );
+
+// Given a tool and an item to apply the tool's effects upon,
+// gather required information from the user and
+// send a change request to the GC.
+bool ApplyTool( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject );
+
+#endif // TOOL_ITEMS_H
diff --git a/game/client/econ/trading_start_dialog.cpp b/game/client/econ/trading_start_dialog.cpp
new file mode 100644
index 0000000..9d6aea5
--- /dev/null
+++ b/game/client/econ/trading_start_dialog.cpp
@@ -0,0 +1,713 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include <vgui/ILocalize.h>
+#include "vgui_controls/TextEntry.h"
+#include "trading_start_dialog.h"
+#include "econ_controls.h"
+#include "econ_trading.h"
+#include "c_playerresource.h"
+#include "gcsdk/gcmsg.h"
+#include "econ_item_inventory.h"
+#include "econ_gcmessages.h"
+#include "gc_clientsystem.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTradingStartDialog::CTradingStartDialog( vgui::Panel *parent ) : vgui::EditablePanel( parent, "TradingStartDialog" )
+{
+ m_pSelectFromServerButton = NULL;
+ m_pCancelButton = NULL;
+ m_pButtonKV = NULL;
+ m_bReapplyButtonKVs = false;
+ m_pURLFailLabel = NULL;
+ m_pURLSearchingLabel = NULL;
+
+ for ( int i = 0; i < TDS_NUM_STATES; i++ )
+ {
+ m_pStatePanels[i] = new vgui::EditablePanel( this, VarArgs("StatePanel%d",i) );
+ }
+
+ m_pPlayerList = new vgui::EditablePanel( this, "PlayerList" );
+ m_pPlayerListScroller = new vgui::ScrollableEditablePanel( this, m_pPlayerList, "PlayerListScroller" );
+
+ ListenForGameEvent( "gameui_hidden" );
+
+ Reset();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTradingStartDialog::~CTradingStartDialog( void )
+{
+ if ( m_pButtonKV )
+ {
+ m_pButtonKV->deleteThis();
+ m_pButtonKV = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::Reset( void )
+{
+ m_iCurrentState = TDS_SELECTING_PLAYER;
+ m_bGiftMode = false;
+ m_giftItem = CEconItemView();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ KeyValues *pItemKV = inResourceData->FindKey( "button_kv" );
+ if ( pItemKV )
+ {
+ if ( m_pButtonKV )
+ {
+ m_pButtonKV->deleteThis();
+ }
+ m_pButtonKV = new KeyValues("button_kv");
+ pItemKV->CopySubkeys( m_pButtonKV );
+
+ m_bReapplyButtonKVs = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+
+ LoadControlSettings( "resource/ui/econ/TradingStartDialog.res" );
+
+ m_pCancelButton = dynamic_cast<CExButton*>( FindChildByName( "CancelButton" ) );
+
+ // Find all the sub buttons, and set their action signals to point to this panel
+ for ( int i = 0; i < TDS_NUM_STATES; i++ )
+ {
+ int iButton = 0;
+ CExButton *pButton = NULL;
+ do
+ {
+ pButton = dynamic_cast<CExButton*>( m_pStatePanels[i]->FindChildByName( VarArgs("subbutton%d",iButton)) );
+ if ( pButton )
+ {
+ pButton->AddActionSignalTarget( this );
+
+ // The second button on the first state is the server button
+ if ( iButton == 1 )
+ {
+ m_pSelectFromServerButton = pButton;
+ }
+
+ iButton++;
+ }
+ } while (pButton);
+ }
+
+ m_pURLFailLabel = dynamic_cast<vgui::Label*>( m_pStatePanels[TDS_SELECTING_FROM_PROFILE]->FindChildByName( "URLFailLabel" ) );
+ m_pURLSearchingLabel = dynamic_cast<vgui::Label*>( m_pStatePanels[TDS_SELECTING_FROM_PROFILE]->FindChildByName( "URLSearchingLabel" ) );
+
+ UpdateState();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::PerformLayout( void )
+{
+ BaseClass::PerformLayout();
+
+ // Layout the player list buttons
+ if ( m_pPlayerPanels.Count() )
+ {
+ int iButtonH = m_pPlayerPanels[0]->GetTall() + YRES(2);
+ m_pPlayerList->SetSize( m_pPlayerList->GetWide(), YRES(2) + (iButtonH * m_pPlayerPanels.Count()) );
+
+ // These need to all be layout-complete before we can position the player panels,
+ // because the scrollbar will cause the playerlist entries to move when it lays out.
+ m_pPlayerList->InvalidateLayout( true );
+ m_pPlayerListScroller->InvalidateLayout( true );
+ m_pPlayerListScroller->GetScrollbar()->InvalidateLayout( true );
+
+ for ( int i = 0; i < m_pPlayerPanels.Count(); i++ )
+ {
+ m_pPlayerPanels[i]->SetPos( 0, YRES(2) + (iButtonH * i) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::FireGameEvent( IGameEvent *event )
+{
+ const char *type = event->GetName();
+
+ if ( Q_strcmp(type, "gameui_hidden") == 0 )
+ {
+ Close();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::Close( void )
+{
+ TFModalStack()->PopModal( this );
+ SetVisible( false );
+ MarkForDeletion();
+ PostMessage( GetParent(), new KeyValues("CancelSelection") );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::OnCommand( const char *command )
+{
+ if ( !Q_stricmp( command, "cancel" ) )
+ {
+ if ( m_iCurrentState != TDS_SELECTING_PLAYER )
+ {
+ m_iCurrentState = TDS_SELECTING_PLAYER;
+ UpdateState();
+ return;
+ }
+
+ Close();
+ return;
+ }
+ else if ( !Q_stricmp( command, "friends" ) )
+ {
+ m_iCurrentState = TDS_SELECTING_FROM_FRIENDS;
+ UpdateState();
+ return;
+ }
+ else if ( !Q_stricmp( command, "server" ) )
+ {
+ m_iCurrentState = TDS_SELECTING_FROM_SERVER;
+ UpdateState();
+ return;
+ }
+ else if ( !Q_stricmp( command, "profile" ) )
+ {
+ m_iCurrentState = TDS_SELECTING_FROM_PROFILE;
+ UpdateState();
+ return;
+ }
+ else if ( !Q_strnicmp( command, "select_player", 13 ) )
+ {
+ int iPlayer = atoi( command + 13 ) - 1;
+ if ( iPlayer >= 0 && iPlayer < m_PlayerInfoList.Count() )
+ {
+ StartTradeWith( m_PlayerInfoList[iPlayer].m_steamID );
+ }
+ return;
+ }
+ else if ( !Q_stricmp( command, "url_ok" ) )
+ {
+ vgui::TextEntry *pEntry = dynamic_cast<vgui::TextEntry*>( m_pStatePanels[m_iCurrentState]->FindChildByName("URLEntry") );
+ if ( pEntry )
+ {
+ const int maxURLLength = 512;
+ char inputURL[ maxURLLength ];
+ pEntry->GetText( inputURL, maxURLLength );
+
+ if ( m_pURLSearchingLabel )
+ {
+ m_pURLSearchingLabel->SetVisible( false );
+ }
+
+ bool bSuccess = ExtractSteamIDFromURL( inputURL );
+ if ( m_pURLFailLabel )
+ {
+ m_pURLFailLabel->SetVisible( !bSuccess );
+ }
+ }
+
+ return;
+ }
+
+ BaseClass::OnCommand( command );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::SendGiftTo( CSteamID steamID )
+{
+ Trading_SendGift( steamID, m_giftItem );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::StartTradeWith( CSteamID steamID )
+{
+ m_iCurrentState = TDS_SELECTING_PLAYER;
+ OnCommand( "cancel" );
+
+ if ( !m_bGiftMode )
+ {
+ Trading_RequestTrade( steamID );
+ return;
+ }
+
+ Trading_SendGift( steamID, m_giftItem );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTradingStartDialog::ExtractSteamIDFromURL( char *inputURL )
+{
+ if ( !inputURL || !inputURL[0] )
+ return false;
+
+ EUniverse localUniverse = GetUniverse();
+ if ( localUniverse == k_EUniverseInvalid )
+ return false;
+
+ CSteamID steamID;
+ int iLen = Q_strlen(inputURL);
+
+ // First, see if it's a profile link. If it is, clip the SteamID from it.
+ const char *pszProfilePrepend = ( localUniverse == k_EUniversePublic ) ? "http://steamcommunity.com/profiles/" : "http://beta.steamcommunity.com/profiles/";
+ int iProfilePrependLen = Q_strlen(pszProfilePrepend);
+ if ( Q_strnicmp( pszProfilePrepend, inputURL, iProfilePrependLen ) == 0 )
+ {
+ if ( iLen > iProfilePrependLen )
+ {
+ steamID.SetFromString( &inputURL[iProfilePrependLen], localUniverse );
+
+ if ( steamID.IsValid() )
+ {
+ StartTradeWith( steamID );
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // If it's an id link, we download it and extract the steam ID from it.
+ const char *pszIDPrepend = ( localUniverse == k_EUniversePublic ) ? "http://steamcommunity.com/id/" : "http://beta.steamcommunity.com/id/";
+ int iIDPrependLen = Q_strlen(pszIDPrepend);
+ if ( Q_strnicmp( pszIDPrepend, inputURL, iIDPrependLen ) == 0 )
+ {
+ if ( iLen > iIDPrependLen )
+ {
+ // Trim off a trailing slash
+ if ( inputURL[iLen-1] == '/' || inputURL[iLen-1] == '\\' )
+ {
+ inputURL[iLen-1] = '\0';
+ }
+
+ GCSDK::CGCMsg<MsgGCLookupAccount_t> msg( k_EMsgGCLookupAccount );
+ msg.Body().m_uiFindType = GCSDK::k_EFindAccountTypeURL;
+ msg.AddStrData( &inputURL[iIDPrependLen] );
+ GCClientSystem()->BSendMessage( msg );
+
+ // For now, return true and wait.
+ if ( m_pURLSearchingLabel )
+ {
+ m_pURLSearchingLabel->SetVisible( true );
+ }
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::OnLookupAccountResponse( uint64 iAccountID )
+{
+ if ( m_pURLSearchingLabel )
+ {
+ m_pURLSearchingLabel->SetVisible( false );
+ }
+
+ CSteamID steamID( iAccountID );
+ if ( steamID.IsValid() )
+ {
+ if ( m_pURLFailLabel )
+ {
+ m_pURLFailLabel->SetVisible( false );
+ }
+
+ StartTradeWith( steamID );
+ }
+ else
+ {
+ if ( m_pURLFailLabel )
+ {
+ m_pURLFailLabel->SetVisible( true );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets a gift for the dialog to hand out. If pGiftItem is NULL, which is
+// fine, make sure to clear our our own existing gift.
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::SetGift( CEconItemView* pGiftItem )
+{
+ if ( pGiftItem )
+ {
+ m_giftItem = *pGiftItem;
+ m_bGiftMode = true;
+ }
+ else
+ {
+ // Reset to default
+ m_giftItem = CEconItemView();
+ m_bGiftMode = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::OnTextChanged( KeyValues *data )
+{
+ vgui::TextEntry *pEntry = dynamic_cast<vgui::TextEntry*>( m_pStatePanels[m_iCurrentState]->FindChildByName("URLEntry") );
+ if ( !pEntry )
+ return;
+
+ Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
+ if ( pEntry == pPanel )
+ {
+ CExButton *pButton = dynamic_cast<CExButton*>( m_pStatePanels[m_iCurrentState]->FindChildByName( "subbutton0" ) );
+ if ( pButton )
+ {
+ pButton->SetEnabled( pEntry->GetTextLength() > 0 );
+ }
+
+ if ( m_pURLFailLabel )
+ {
+ m_pURLFailLabel->SetVisible( false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::UpdateState( void )
+{
+ for ( int i = 0; i < TDS_NUM_STATES; i++ )
+ {
+ if ( !m_pStatePanels[i] )
+ continue;
+
+ m_pStatePanels[i]->SetVisible( m_iCurrentState == i );
+ }
+
+ if ( m_pSelectFromServerButton )
+ {
+ m_pSelectFromServerButton->SetEnabled( engine->IsInGame() );
+ }
+
+ if ( m_iCurrentState == TDS_SELECTING_PLAYER )
+ {
+ m_pCancelButton->SetText( g_pVGuiLocalize->Find( "#Cancel" ) );
+ }
+ else
+ {
+ m_pCancelButton->SetText( g_pVGuiLocalize->Find( "#TF_Back" ) );
+ }
+
+ switch ( m_iCurrentState )
+ {
+ case TDS_SELECTING_FROM_FRIENDS:
+ SetupSelectFriends();
+ break;
+ case TDS_SELECTING_FROM_SERVER:
+ SetupSelectServer();
+ break;
+ case TDS_SELECTING_FROM_PROFILE:
+ SetupSelectProfile();
+ break;
+ case TDS_SELECTING_PLAYER:
+ default:
+ m_pPlayerListScroller->SetVisible( false );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::SetupSelectFriends( void )
+{
+ m_PlayerInfoList.Purge();
+
+ if ( steamapicontext && steamapicontext->SteamFriends() )
+ {
+ // Get our game info so we can use that to test if our friends are connected to the same game as us
+ FriendGameInfo_t myGameInfo;
+ CSteamID mySteamID = steamapicontext->SteamUser()->GetSteamID();
+ steamapicontext->SteamFriends()->GetFriendGamePlayed( mySteamID, &myGameInfo );
+
+ int iFriends = steamapicontext->SteamFriends()->GetFriendCount( k_EFriendFlagImmediate );
+ for ( int i = 0; i < iFriends; i++ )
+ {
+ CSteamID friendSteamID = steamapicontext->SteamFriends()->GetFriendByIndex( i, k_EFriendFlagImmediate );
+
+ FriendGameInfo_t gameInfo;
+ if ( !steamapicontext->SteamFriends()->GetFriendGamePlayed( friendSteamID, &gameInfo ) )
+ continue;
+
+ // Friends is in-game. Make sure it's TF2.
+ if ( gameInfo.m_gameID.IsValid() && gameInfo.m_gameID == myGameInfo.m_gameID )
+ {
+ const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( friendSteamID );
+ int idx = m_PlayerInfoList.AddToTail();
+ trade_partner_info_t &info = m_PlayerInfoList[idx];
+ info.m_steamID = friendSteamID;
+ info.m_name = pszName;
+ }
+ }
+ }
+
+ UpdatePlayerList();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::SetupSelectServer( void )
+{
+ m_PlayerInfoList.Purge();
+
+ if ( steamapicontext && steamapicontext->SteamUtils() )
+ {
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ // find all players who are on the local player's team
+ int iLocalPlayerIndex = GetLocalPlayerIndex();
+ if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) )
+ {
+ player_info_t pi;
+ if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
+ continue;
+ if ( !pi.friendsID )
+ continue;
+
+ CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
+ int idx = m_PlayerInfoList.AddToTail();
+ trade_partner_info_t &info = m_PlayerInfoList[idx];
+ info.m_steamID = steamID;
+ info.m_name = pi.name;
+ }
+ }
+ }
+
+ UpdatePlayerList();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::SetupSelectProfile( void )
+{
+ vgui::TextEntry *pEntry = dynamic_cast<vgui::TextEntry*>( m_pStatePanels[m_iCurrentState]->FindChildByName("URLEntry") );
+ if ( pEntry )
+ {
+ pEntry->SetText( "" );
+ pEntry->RequestFocus();
+ pEntry->AddActionSignalTarget( this );
+ }
+
+ if ( m_pURLFailLabel )
+ {
+ m_pURLFailLabel->SetVisible( false );
+ }
+ if ( m_pURLSearchingLabel )
+ {
+ m_pURLSearchingLabel->SetVisible( false );
+ }
+
+ CExButton *pButton = dynamic_cast<CExButton*>( m_pStatePanels[m_iCurrentState]->FindChildByName( "subbutton0" ) );
+ if ( pButton )
+ {
+ pButton->SetEnabled( false );
+ }
+
+ m_pPlayerListScroller->SetVisible( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradingStartDialog::UpdatePlayerList( void )
+{
+ vgui::Label *pLabelEmpty = dynamic_cast<vgui::Label*>( m_pStatePanels[m_iCurrentState]->FindChildByName("EmptyPlayerListLabel") );
+ vgui::Label *pLabelQuery = dynamic_cast<vgui::Label*>( m_pStatePanels[m_iCurrentState]->FindChildByName("QueryLabel") );
+
+ // If we have no players in our list, show the no-player label.
+ if ( m_PlayerInfoList.Count() == 0 )
+ {
+ if ( pLabelEmpty )
+ {
+ pLabelEmpty->SetVisible( true );
+ }
+ if ( pLabelQuery )
+ {
+ pLabelQuery->SetVisible( false );
+ }
+ return;
+ }
+
+ // First, reapply any KVs we have to reapply
+ if ( m_bReapplyButtonKVs )
+ {
+ m_bReapplyButtonKVs = false;
+
+ if ( m_pButtonKV )
+ {
+ FOR_EACH_VEC( m_pPlayerPanels, i )
+ {
+ m_pPlayerPanels[i]->ApplySettings( m_pButtonKV );
+ }
+ }
+ }
+
+ // Otherwise, build the player panels from the list of steam IDs
+ for ( int i = 0; i < m_PlayerInfoList.Count(); i++ )
+ {
+ if ( m_pPlayerPanels.Count() <= i )
+ {
+ m_pPlayerPanels.AddToTail();
+ m_pPlayerPanels[i] = new CTradeTargetPanel( m_pPlayerList, VarArgs("player%d",i) );
+ m_pPlayerPanels[i]->GetButton()->SetCommand( VarArgs("select_player%d",i+1) );
+ m_pPlayerPanels[i]->GetButton()->AddActionSignalTarget( this );
+ m_pPlayerPanels[i]->GetAvatar()->SetShouldDrawFriendIcon( false );
+ m_pPlayerPanels[i]->GetAvatar()->SetMouseInputEnabled( false );
+
+ if ( m_pButtonKV )
+ {
+ m_pPlayerPanels[i]->ApplySettings( m_pButtonKV );
+ m_pPlayerPanels[i]->InvalidateLayout( true );
+ }
+ }
+
+ m_pPlayerPanels[i]->SetInfo( m_PlayerInfoList[i].m_steamID, m_PlayerInfoList[i].m_name.Get() );
+ }
+
+ m_pPlayerListScroller->GetScrollbar()->SetAutohideButtons( true );
+ m_pPlayerListScroller->GetScrollbar()->SetValue( 0 );
+
+ // Remove any extra player panels
+ for ( int i = m_pPlayerPanels.Count()-1; i >= m_PlayerInfoList.Count(); i-- )
+ {
+ m_pPlayerPanels[i]->MarkForDeletion();
+ m_pPlayerPanels.Remove(i);
+ }
+
+ if ( pLabelEmpty )
+ {
+ pLabelEmpty->SetVisible( false );
+ }
+ if ( pLabelQuery )
+ {
+ pLabelQuery->SetVisible( true );
+ }
+ m_pPlayerListScroller->SetVisible( true );
+
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTradeTargetPanel::SetInfo( const CSteamID &steamID, const char *pszName )
+{
+ if ( !steamapicontext || !steamapicontext->SteamFriends() )
+ return;
+
+ m_pAvatar->SetPlayer( steamID, k_EAvatarSize64x64 );
+
+ m_pButton->SetText( pszName );
+}
+
+static vgui::DHANDLE<CTradingStartDialog> g_hTradingStartDialog;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTradingStartDialog *OpenTradingStartDialog( vgui::Panel *pParent, CEconItemView* pOptGiftItem )
+{
+ if (!g_hTradingStartDialog.Get())
+ {
+ g_hTradingStartDialog = vgui::SETUP_PANEL( new CTradingStartDialog( pParent ) );
+ }
+ g_hTradingStartDialog->InvalidateLayout( false, true );
+
+ g_hTradingStartDialog->Reset();
+ g_hTradingStartDialog->SetVisible( true );
+ g_hTradingStartDialog->MakePopup();
+ g_hTradingStartDialog->MoveToFront();
+ g_hTradingStartDialog->SetKeyBoardInputEnabled(true);
+ g_hTradingStartDialog->SetMouseInputEnabled(true);
+ g_hTradingStartDialog->SetGift( pOptGiftItem );
+ TFModalStack()->PushModal( g_hTradingStartDialog );
+
+ return g_hTradingStartDialog;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: GC Msg handler to receive the Lookup Account response
+//-----------------------------------------------------------------------------
+class CGCLookupAccountResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCLookupAccountResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
+
+ uint8 iAccounts = 0;
+ uint64 iAccountID = 0;
+ if ( msg.BReadUint8Data( &iAccounts ) )
+ {
+ // We only care about the first account we find in this panel.
+ if ( iAccounts > 0 )
+ {
+ msg.BReadUint64Data( &iAccountID );
+ }
+ }
+
+ if ( g_hTradingStartDialog.Get() )
+ {
+ g_hTradingStartDialog->OnLookupAccountResponse( iAccountID );
+ }
+ return true;
+ }
+
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCLookupAccountResponse, "CGCLookupAccountResponse", k_EMsgGCLookupAccountResponse, GCSDK::k_EServerTypeGCClient );
diff --git a/game/client/econ/trading_start_dialog.h b/game/client/econ/trading_start_dialog.h
new file mode 100644
index 0000000..b5308cd
--- /dev/null
+++ b/game/client/econ/trading_start_dialog.h
@@ -0,0 +1,115 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef TRADING_START_DIALOG_H
+#define TRADING_START_DIALOG_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "vgui_controls/EditablePanel.h"
+#include "vgui_controls/ScrollableEditablePanel.h"
+#include "econ_controls.h"
+#include "vgui_avatarimage.h"
+
+// Trading Dialog states
+enum
+{
+ TDS_SELECTING_PLAYER,
+ TDS_SELECTING_FROM_FRIENDS,
+ TDS_SELECTING_FROM_SERVER,
+ TDS_SELECTING_FROM_PROFILE,
+
+ TDS_NUM_STATES,
+};
+
+// Button that displays the name & avatar image of a potential trade target
+class CTradeTargetPanel : public vgui::EditablePanel
+{
+ DECLARE_CLASS_SIMPLE( CTradeTargetPanel, vgui::EditablePanel );
+public:
+ CTradeTargetPanel( vgui::Panel *parent, const char *name ) : vgui::EditablePanel( parent, name )
+ {
+ m_pAvatar = new CAvatarImagePanel( this, "avatar" );
+ m_pButton = new CExButton( this, "button", "", parent );
+ }
+ ~CTradeTargetPanel( void )
+ {
+ m_pAvatar->MarkForDeletion();
+ m_pButton->MarkForDeletion();
+ }
+
+ void SetInfo( const CSteamID &steamID, const char *pszName );
+
+ CAvatarImagePanel *GetAvatar( void ) { return m_pAvatar; }
+ CExButton *GetButton( void ) { return m_pButton; }
+
+private:
+ // Embedded panels
+ CAvatarImagePanel *m_pAvatar;
+ CExButton *m_pButton;
+};
+
+//-----------------------------------------------------------------------------
+// A dialog that allows users to select who they want to trade with.
+//-----------------------------------------------------------------------------
+class CTradingStartDialog : public vgui::EditablePanel, public CGameEventListener
+{
+ DECLARE_CLASS_SIMPLE( CTradingStartDialog, vgui::EditablePanel );
+public:
+ CTradingStartDialog( vgui::Panel *parent );
+ ~CTradingStartDialog( void );
+
+ virtual void ApplySettings( KeyValues *inResourceData );
+ virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
+ virtual void PerformLayout( void );
+ virtual void OnCommand( const char *command );
+ virtual void FireGameEvent( IGameEvent *event );
+
+ void Close( void );
+ void Reset( void );
+ void UpdateState( void );
+ void SetupSelectFriends( void );
+ void SetupSelectServer( void );
+ void SetupSelectProfile( void );
+ void UpdatePlayerList( void );
+
+ void SendGiftTo( CSteamID steamID );
+ void StartTradeWith( CSteamID steamID );
+ bool ExtractSteamIDFromURL( char *inputURL );
+ void OnLookupAccountResponse( uint64 iAccountID );
+ bool IsInGiftMode( ) const { return m_bGiftMode; }
+ void SetGift( CEconItemView* pGiftItem );
+
+ MESSAGE_FUNC_PARAMS( OnTextChanged, "TextChanged", data );
+
+private:
+ struct trade_partner_info_t
+ {
+ CSteamID m_steamID;
+ CUtlString m_name;
+ };
+ vgui::EditablePanel *m_pStatePanels[TDS_NUM_STATES];
+ int m_iCurrentState;
+ CExButton *m_pSelectFromServerButton;
+ CExButton *m_pCancelButton;
+
+ vgui::EditablePanel *m_pPlayerList;
+ vgui::ScrollableEditablePanel *m_pPlayerListScroller;
+ CUtlVector<trade_partner_info_t> m_PlayerInfoList;
+ CUtlVector<CTradeTargetPanel*> m_pPlayerPanels;
+ KeyValues *m_pButtonKV;
+ bool m_bReapplyButtonKVs;
+ vgui::Label *m_pURLFailLabel;
+ vgui::Label *m_pURLSearchingLabel;
+ CEconItemView m_giftItem;
+ bool m_bGiftMode;
+};
+
+CTradingStartDialog *OpenTradingStartDialog( vgui::Panel *pParent, CEconItemView* pOptGiftItem = NULL );
+
+#endif // TRADING_START_DIALOG_H