diff options
Diffstat (limited to 'game/client/econ')
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 *> ¬ifications ) + { + 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 *> ¬ifications = 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 *> ¬ifications = 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 *> ¬ifications = 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 *> ¬ifications = 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 ¬ification ) = 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 ¬ification ) + { + if ( CTFTradeRequestNotification::IsTradingNotification( ¬ification ) ) + { + 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 |