Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1738 lines
56 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "crafting_panel.h"
  8. #include "vgui/ISurface.h"
  9. #include "vgui/ISystem.h"
  10. #include "c_tf_player.h"
  11. #include "gamestringpool.h"
  12. #include "iclientmode.h"
  13. #include "tf_item_inventory.h"
  14. #include "ienginevgui.h"
  15. #include <vgui/ILocalize.h>
  16. #include "vgui_controls/TextImage.h"
  17. #include "vgui_controls/CheckButton.h"
  18. #include "vgui_controls/ComboBox.h"
  19. #include <vgui_controls/TextEntry.h>
  20. #include "vgui/IInput.h"
  21. #include "gcsdk/gcclient.h"
  22. #include "gcsdk/gcclientjob.h"
  23. #include "character_info_panel.h"
  24. #include "charinfo_loadout_subpanel.h"
  25. #include "econ_item_system.h"
  26. #include "econ_item_constants.h"
  27. #include "tf_hud_notification_panel.h"
  28. #include "tf_hud_chat.h"
  29. #include "c_tf_gamestats.h"
  30. #include "confirm_dialog.h"
  31. #include "econ_notifications.h"
  32. #include "gc_clientsystem.h"
  33. #include "charinfo_loadout_subpanel.h"
  34. #include "item_selection_criteria.h"
  35. #include "rtime.h"
  36. #include "c_tf_freeaccount.h"
  37. // memdbgon must be the last include file in a .cpp file!!!
  38. #include <tier0/memdbgon.h>
  39. ConVar tf_explanations_craftingpanel( "tf_explanations_craftingpanel", "0", FCVAR_ARCHIVE, "Whether the user has seen explanations for this panel." );
  40. struct recipefilter_data_t
  41. {
  42. const char *pszTooltipString;
  43. const char *pszButtonImage;
  44. const char *pszButtonImageMouseover;
  45. };
  46. recipefilter_data_t g_RecipeFilters[NUM_RECIPE_CATEGORIES] =
  47. {
  48. { "#RecipeFilter_Crafting", "crafticon_crafting_items", "crafticon_crafting_items_over" }, // RECIPE_CATEGORY_CRAFTINGITEMS,
  49. { "#RecipeFilter_CommonItems", "crafticon_common_items", "crafticon_common_items_over" }, // RECIPE_CATEGORY_COMMONITEMS,
  50. { "#RecipeFilter_RareItems", "crafticon_rare_items", "crafticon_rare_items_over" }, // RECIPE_CATEGORY_RAREITEMS,
  51. { "#RecipeFilter_Special", "crafticon_special_blueprints", "crafticon_special_blueprints_over" } // RECIPE_CATEGORY_SPECIAL,
  52. };
  53. //-----------------------------------------------------------------------------
  54. // Purpose:
  55. //-----------------------------------------------------------------------------
  56. wchar_t *LocalizeRecipeStringPiece( const char *pszString, wchar_t *pszConverted, int nConvertedSizeInBytes )
  57. {
  58. if ( !pszString )
  59. return L"";
  60. if ( pszString[0] == '#' )
  61. return g_pVGuiLocalize->Find( pszString );
  62. g_pVGuiLocalize->ConvertANSIToUnicode( pszString, pszConverted, nConvertedSizeInBytes );
  63. return pszConverted;
  64. }
  65. //-----------------------------------------------------------------------------
  66. // Purpose:
  67. //-----------------------------------------------------------------------------
  68. void SetItemPanelToRecipe( CItemModelPanel *pPanel, const CEconCraftingRecipeDefinition *pRecipeDef, bool bShowName )
  69. {
  70. wchar_t wcTmpName[512];
  71. wchar_t wcTmpDesc[512];
  72. int iNegAttribsBegin = 0;
  73. if ( !pRecipeDef )
  74. {
  75. Q_wcsncpy( wcTmpName, g_pVGuiLocalize->Find( "#Craft_Recipe_Custom" ), sizeof( wcTmpName ) );
  76. Q_wcsncpy( wcTmpDesc, g_pVGuiLocalize->Find( "#Craft_Recipe_CustomDesc" ), sizeof( wcTmpDesc ) );
  77. iNegAttribsBegin = Q_wcslen( wcTmpDesc );
  78. }
  79. else
  80. {
  81. if ( bShowName )
  82. {
  83. wchar_t *pName_A = g_pVGuiLocalize->Find( pRecipeDef->GetName_A() );
  84. g_pVGuiLocalize->ConstructString_safe( wcTmpName, g_pVGuiLocalize->Find( pRecipeDef->GetName() ), 1, pName_A );
  85. }
  86. else
  87. {
  88. wcTmpName[0] = '\0';
  89. }
  90. wchar_t wcTmpA[32];
  91. wchar_t wcTmpB[32];
  92. wchar_t wcTmpC[32];
  93. wchar_t wcTmp[512];
  94. // Build the input string
  95. wchar_t *pInp_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_A(), wcTmpA, sizeof( wcTmpA ) );
  96. wchar_t *pInp_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_B(), wcTmpB, sizeof( wcTmpB ) );
  97. wchar_t *pInp_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_C(), wcTmpC, sizeof( wcTmpC ) );
  98. g_pVGuiLocalize->ConstructString_safe( wcTmpDesc, g_pVGuiLocalize->Find( pRecipeDef->GetDescInputs() ), 3, pInp_A, pInp_B, pInp_C );
  99. iNegAttribsBegin = Q_wcslen(wcTmpDesc);
  100. // Build the output string
  101. wchar_t *pOut_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_A(), wcTmpA, sizeof( wcTmpA ) );
  102. wchar_t *pOut_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_B(), wcTmpB, sizeof( wcTmpB ) );
  103. wchar_t *pOut_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_C(), wcTmpC, sizeof( wcTmpC ) );
  104. g_pVGuiLocalize->ConstructString_safe( wcTmp, g_pVGuiLocalize->Find( pRecipeDef->GetDescOutputs() ), 3, pOut_A, pOut_B, pOut_C );
  105. // Concatenate, and mark the text changes
  106. V_wcscat_safe( wcTmpDesc, L"\n" );
  107. V_wcscat_safe( wcTmpDesc, wcTmp );
  108. }
  109. pPanel->SetAttribOnly( !bShowName );
  110. pPanel->SetTextYPos( 0 );
  111. pPanel->SetItem( NULL );
  112. pPanel->SetNoItemText( wcTmpName, wcTmpDesc, iNegAttribsBegin );
  113. pPanel->InvalidateLayout(true);
  114. }
  115. //-----------------------------------------------------------------------------
  116. // Purpose:
  117. //-----------------------------------------------------------------------------
  118. void PositionMouseOverPanelForRecipe( vgui::Panel *pScissorPanel, vgui::Panel *pRecipePanel, vgui::ScrollableEditablePanel *pRecipeScroller, CItemModelPanel *pMouseOverItemPanel )
  119. {
  120. int x,y;
  121. vgui::ipanel()->GetAbsPos( pRecipePanel->GetVPanel(), x, y );
  122. int xs,ys;
  123. vgui::ipanel()->GetAbsPos( pMouseOverItemPanel->GetParent()->GetVPanel(), xs, ys );
  124. x -= xs;
  125. y -= ys;
  126. int iXPos = (x + (pRecipePanel->GetWide() * 0.5)) - (pMouseOverItemPanel->GetWide() * 0.5);
  127. int iYPos = (y + pRecipePanel->GetTall());
  128. // Make sure the popup stays onscreen.
  129. if ( iXPos < 0 )
  130. {
  131. iXPos = 0;
  132. }
  133. else if ( (iXPos + pMouseOverItemPanel->GetWide()) > pMouseOverItemPanel->GetParent()->GetWide() )
  134. {
  135. iXPos = pMouseOverItemPanel->GetParent()->GetWide() - pMouseOverItemPanel->GetWide();
  136. }
  137. if ( iYPos < 0 )
  138. {
  139. iYPos = 0;
  140. }
  141. else if ( (iYPos + pMouseOverItemPanel->GetTall() + YRES(32)) > pMouseOverItemPanel->GetParent()->GetTall() )
  142. {
  143. // Move it up above our item
  144. iYPos = y - pMouseOverItemPanel->GetTall() - YRES(4);
  145. }
  146. pMouseOverItemPanel->SetPos( iXPos, iYPos );
  147. pMouseOverItemPanel->SetVisible( true );
  148. }
  149. //-----------------------------------------------------------------------------
  150. // Purpose:
  151. //-----------------------------------------------------------------------------
  152. CCraftingPanel::CCraftingPanel( vgui::Panel *parent, const char *panelName ) : CBaseLoadoutPanel( parent, panelName )
  153. {
  154. m_pRecipeListContainer = new vgui::EditablePanel( this, "recipecontainer" );
  155. m_pRecipeListContainerScroller = new vgui::ScrollableEditablePanel( this, m_pRecipeListContainer, "recipecontainerscroller" );
  156. m_pSelectedRecipeContainer = new vgui::EditablePanel( this, "selectedrecipecontainer" );
  157. m_pRecipeButtonsKV = NULL;
  158. m_pRecipeFilterButtonsKV = NULL;
  159. m_bEventLogging = false;
  160. m_iCraftingAttempts = 0;
  161. m_iRecipeCategoryFilter = RECIPE_CATEGORY_CRAFTINGITEMS;
  162. m_iCurrentlySelectedRecipe = -1;
  163. CleanupPostCraft( true );
  164. m_pToolTip = new CTFTextToolTip( this );
  165. m_pToolTipEmbeddedPanel = new vgui::EditablePanel( this, "TooltipPanel" );
  166. m_pToolTipEmbeddedPanel->SetKeyBoardInputEnabled( false );
  167. m_pToolTipEmbeddedPanel->SetMouseInputEnabled( false );
  168. m_pToolTip->SetEmbeddedPanel( m_pToolTipEmbeddedPanel );
  169. m_pToolTip->SetTooltipDelay( 0 );
  170. m_pSelectionPanel = NULL;
  171. m_iSelectingForSlot = 0;
  172. m_pCraftButton = NULL;
  173. m_pUpgradeButton = NULL;
  174. m_pFreeAccountLabel = NULL;
  175. }
  176. //-----------------------------------------------------------------------------
  177. // Purpose:
  178. //-----------------------------------------------------------------------------
  179. CCraftingPanel::~CCraftingPanel( void )
  180. {
  181. if ( m_pRecipeButtonsKV )
  182. {
  183. m_pRecipeButtonsKV->deleteThis();
  184. m_pRecipeButtonsKV = NULL;
  185. }
  186. if ( m_pRecipeFilterButtonsKV )
  187. {
  188. m_pRecipeFilterButtonsKV->deleteThis();
  189. m_pRecipeFilterButtonsKV = NULL;
  190. }
  191. }
  192. //-----------------------------------------------------------------------------
  193. // Purpose:
  194. //-----------------------------------------------------------------------------
  195. void CCraftingPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
  196. {
  197. LoadControlSettings( GetResFile() );
  198. BaseClass::ApplySchemeSettings( pScheme );
  199. m_pRecipeListContainerScroller->GetScrollbar()->SetAutohideButtons( true );
  200. m_pCraftButton = dynamic_cast<CExButton*>( m_pSelectedRecipeContainer->FindChildByName("CraftButton") );
  201. if ( m_pCraftButton )
  202. {
  203. m_pCraftButton->AddActionSignalTarget( this );
  204. }
  205. m_pUpgradeButton = dynamic_cast<CExButton*>( m_pSelectedRecipeContainer->FindChildByName("UpgradeButton") );
  206. if ( m_pUpgradeButton )
  207. {
  208. m_pUpgradeButton->AddActionSignalTarget( this );
  209. }
  210. m_pFreeAccountLabel = dynamic_cast<CExLabel*>( m_pSelectedRecipeContainer->FindChildByName("FreeAccountLabel") );
  211. CreateRecipeFilterButtons();
  212. UpdateRecipeFilter();
  213. }
  214. //-----------------------------------------------------------------------------
  215. // Purpose:
  216. //-----------------------------------------------------------------------------
  217. void CCraftingPanel::ApplySettings( KeyValues *inResourceData )
  218. {
  219. BaseClass::ApplySettings( inResourceData );
  220. KeyValues *pItemKV = inResourceData->FindKey( "recipebuttons_kv" );
  221. if ( pItemKV )
  222. {
  223. if ( m_pRecipeButtonsKV )
  224. {
  225. m_pRecipeButtonsKV->deleteThis();
  226. }
  227. m_pRecipeButtonsKV = new KeyValues("recipebuttons_kv");
  228. pItemKV->CopySubkeys( m_pRecipeButtonsKV );
  229. }
  230. KeyValues *pButtonKV = inResourceData->FindKey( "recipefilterbuttons_kv" );
  231. if ( pButtonKV )
  232. {
  233. if ( m_pRecipeFilterButtonsKV )
  234. {
  235. m_pRecipeFilterButtonsKV->deleteThis();
  236. }
  237. m_pRecipeFilterButtonsKV = new KeyValues("recipefilterbuttons_kv");
  238. pButtonKV->CopySubkeys( m_pRecipeFilterButtonsKV );
  239. }
  240. }
  241. //-----------------------------------------------------------------------------
  242. // Purpose:
  243. //-----------------------------------------------------------------------------
  244. void CCraftingPanel::PerformLayout( void )
  245. {
  246. BaseClass::PerformLayout();
  247. // Need to lay these out before we start making item panels inside them
  248. m_pRecipeListContainer->InvalidateLayout( true );
  249. m_pRecipeListContainerScroller->InvalidateLayout( true );
  250. // Position the recipe filters
  251. FOR_EACH_VEC( m_pRecipeFilterButtons, i )
  252. {
  253. if ( m_pRecipeFilterButtonsKV )
  254. {
  255. m_pRecipeFilterButtons[i]->ApplySettings( m_pRecipeFilterButtonsKV );
  256. m_pRecipeFilterButtons[i]->InvalidateLayout();
  257. }
  258. int iButtonW, iButtonH;
  259. m_pRecipeFilterButtons[i]->GetSize( iButtonW, iButtonH );
  260. int iXPos = (GetWide() * 0.5) + m_iFilterOffcenterX + ((iButtonW + m_iFilterDeltaX) * i);
  261. int iYPos = m_iFilterYPos;// + ((iButtonH + m_iFilterDeltaY) * i);
  262. m_pRecipeFilterButtons[i]->SetPos( iXPos, iYPos );
  263. }
  264. // Position the recipe buttons
  265. for ( int i = 0; i < m_pRecipeButtons.Count(); i++ )
  266. {
  267. if ( m_pRecipeButtonsKV )
  268. {
  269. m_pRecipeButtons[i]->ApplySettings( m_pRecipeButtonsKV );
  270. m_pRecipeButtons[i]->InvalidateLayout();
  271. }
  272. int iYDelta = m_pRecipeButtons[0]->GetTall() + YRES(2);
  273. // Once we've setup our first item, we know how large to make the container
  274. if ( i == 0 )
  275. {
  276. m_pRecipeListContainer->SetSize( m_pRecipeListContainer->GetWide(), iYDelta * m_pRecipeButtons.Count() );
  277. }
  278. int x,y;
  279. m_pRecipeButtons[i]->GetPos( x,y );
  280. m_pRecipeButtons[i]->SetPos( x, (iYDelta * i) );
  281. }
  282. // Now that the container has been sized, tell the scroller to re-evaluate
  283. m_pRecipeListContainerScroller->InvalidateLayout();
  284. m_pRecipeListContainerScroller->GetScrollbar()->InvalidateLayout();
  285. // Then position all our item panels
  286. for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
  287. {
  288. PositionItemPanel( m_pItemModelPanels[i], i );
  289. }
  290. }
  291. //-----------------------------------------------------------------------------
  292. // Purpose:
  293. //-----------------------------------------------------------------------------
  294. void CCraftingPanel::CreateRecipeFilterButtons( void )
  295. {
  296. for ( int i = 0; i < NUM_RECIPE_CATEGORIES; i++ )
  297. {
  298. if ( m_pRecipeFilterButtons.Count() <= i )
  299. {
  300. CImageButton *pNewButton = new CImageButton( this, g_RecipeFilters[i].pszTooltipString );
  301. m_pRecipeFilterButtons.AddToTail( pNewButton );
  302. }
  303. m_pRecipeFilterButtons[i]->SetInactiveImage( g_RecipeFilters[i].pszButtonImage );
  304. m_pRecipeFilterButtons[i]->SetActiveImage( g_RecipeFilters[i].pszButtonImageMouseover );
  305. m_pRecipeFilterButtons[i]->SetTooltip( m_pToolTip, g_RecipeFilters[i].pszTooltipString );
  306. const char *pszCommand = VarArgs("selectfilter%d", i );
  307. m_pRecipeFilterButtons[i]->SetCommand( pszCommand );
  308. }
  309. }
  310. //-----------------------------------------------------------------------------
  311. // Purpose:
  312. //-----------------------------------------------------------------------------
  313. void CCraftingPanel::UpdateRecipeFilter( void )
  314. {
  315. int iMatchingRecipes = 0;
  316. m_iCurrentlySelectedRecipe = -1;
  317. m_iCurrentRecipeTotalInputs = 0;
  318. m_iCurrentRecipeTotalOutputs = 0;
  319. FOR_EACH_VEC( m_pRecipeFilterButtons, i )
  320. {
  321. bool bForceDepressed = ( i == m_iRecipeCategoryFilter );
  322. m_pRecipeFilterButtons[i]->ForceDepressed( bForceDepressed );
  323. }
  324. // Loop through the known recipes, and see which ones match our category filter
  325. for ( int i = 0; i < TFInventoryManager()->GetLocalTFInventory()->GetRecipeCount(); i++ )
  326. {
  327. const CEconCraftingRecipeDefinition *pRecipeDef = TFInventoryManager()->GetLocalTFInventory()->GetRecipeDef(i);
  328. if ( !pRecipeDef )
  329. continue;
  330. if ( pRecipeDef->IsDisabled() )
  331. continue;
  332. if ( pRecipeDef->GetCategory() != m_iRecipeCategoryFilter )
  333. continue;
  334. wchar_t wTemp[256];
  335. wchar_t *pName_A = g_pVGuiLocalize->Find( pRecipeDef->GetName_A() );
  336. g_pVGuiLocalize->ConstructString_safe( wTemp, g_pVGuiLocalize->Find( pRecipeDef->GetName() ), 1, pName_A );
  337. SetButtonToRecipe( iMatchingRecipes, pRecipeDef->GetDefinitionIndex(), wTemp );
  338. iMatchingRecipes++;
  339. }
  340. // Add a "Custom" option to the bottom of the Special recipe list
  341. if ( m_iRecipeCategoryFilter == RECIPE_CATEGORY_SPECIAL )
  342. {
  343. SetButtonToRecipe( iMatchingRecipes, RECIPE_CUSTOM, g_pVGuiLocalize->Find("#Craft_Recipe_Custom") );
  344. iMatchingRecipes++;
  345. }
  346. // Delete excess buttons
  347. for ( int i = m_pRecipeButtons.Count() - 1; i >= iMatchingRecipes; i-- )
  348. {
  349. m_pRecipeButtons[i]->MarkForDeletion();
  350. m_pRecipeButtons.Remove( i );
  351. }
  352. // Move the scrollbar to the top
  353. m_pRecipeListContainerScroller->GetScrollbar()->SetValue( 0 );
  354. UpdateSelectedRecipe( true );
  355. InvalidateLayout();
  356. }
  357. //-----------------------------------------------------------------------------
  358. // Purpose:
  359. //-----------------------------------------------------------------------------
  360. void CCraftingPanel::OnCancelSelection( void )
  361. {
  362. if ( m_pSelectionPanel )
  363. {
  364. m_pSelectionPanel->SetVisible( false );
  365. }
  366. CloseCraftingStatusDialog();
  367. }
  368. //-----------------------------------------------------------------------------
  369. // Purpose:
  370. //-----------------------------------------------------------------------------
  371. void CCraftingPanel::OnSelectionReturned( KeyValues *data )
  372. {
  373. if ( data )
  374. {
  375. uint64 ulIndex = data->GetUint64( "itemindex", INVALID_ITEM_ID );
  376. if ( ulIndex == INVALID_ITEM_ID )
  377. {
  378. // should this be INVALID_ITEM_ID?
  379. m_InputItems[m_iSelectingForSlot] = 0;
  380. }
  381. else
  382. {
  383. m_InputItems[m_iSelectingForSlot] = ulIndex;
  384. }
  385. UpdateModelPanels();
  386. UpdateCraftButton();
  387. }
  388. // It'll have deleted itself, so we don't need to clean it up
  389. OnCancelSelection();
  390. }
  391. //-----------------------------------------------------------------------------
  392. // Purpose:
  393. //-----------------------------------------------------------------------------
  394. void CCraftingPanel::OnShowPanel( bool bVisible, bool bReturningFromArmory )
  395. {
  396. if ( bVisible )
  397. {
  398. if ( m_pSelectionPanel )
  399. {
  400. m_pSelectionPanel->SetVisible( false );
  401. }
  402. memset( m_InputItems, 0, sizeof(m_InputItems) );
  403. memset( m_ItemPanelCriteria, 0, sizeof(m_ItemPanelCriteria) );
  404. m_iCurrentlySelectedRecipe = -1;
  405. m_iCurrentRecipeTotalInputs = 0;
  406. m_iCurrentRecipeTotalOutputs = 0;
  407. UpdateRecipeFilter();
  408. if ( !m_bEventLogging )
  409. {
  410. m_bEventLogging = true;
  411. C_CTF_GameStats.Event_Crafting( IE_CRAFTING_ENTERED );
  412. }
  413. }
  414. else
  415. {
  416. CloseCraftingStatusDialog();
  417. vgui::ivgui()->RemoveTickSignal( GetVPanel() );
  418. }
  419. BaseClass::OnShowPanel( bVisible, bReturningFromArmory );
  420. }
  421. //-----------------------------------------------------------------------------
  422. // Purpose:
  423. //-----------------------------------------------------------------------------
  424. void CCraftingPanel::OnClosing()
  425. {
  426. if ( m_bEventLogging )
  427. {
  428. C_CTF_GameStats.Event_Crafting( IE_CRAFTING_EXITED );
  429. m_bEventLogging = false;
  430. }
  431. }
  432. //-----------------------------------------------------------------------------
  433. // Purpose:
  434. //-----------------------------------------------------------------------------
  435. void CCraftingPanel::PositionItemPanel( CItemModelPanel *pPanel, int iIndex )
  436. {
  437. int iCenter = 0;
  438. int iButtonX, iButtonY, iXPos, iYPos;
  439. if ( IsInputItemPanel(iIndex) )
  440. {
  441. iButtonX = (iIndex % CRAFTING_SLOTS_INPUT_COLUMNS);
  442. iButtonY = (iIndex / CRAFTING_SLOTS_INPUT_COLUMNS);
  443. iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * m_pItemModelPanels[iIndex]->GetWide()) + (m_iItemBackpackXDelta * iButtonX);
  444. iYPos = m_iItemYPos + (iButtonY * m_pItemModelPanels[iIndex]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY);
  445. }
  446. else
  447. {
  448. int iButtonIndex = iIndex - CRAFTING_SLOTS_INPUTPANELS;
  449. iButtonX = (iButtonIndex % CRAFTING_SLOTS_OUTPUT_COLUMNS);
  450. iButtonY = (iButtonIndex / CRAFTING_SLOTS_OUTPUT_COLUMNS);
  451. iXPos = (iCenter + m_iItemCraftingOffcenterX) + (iButtonX * m_pItemModelPanels[iIndex]->GetWide()) + (m_iItemBackpackXDelta * iButtonX);
  452. iYPos = m_iOutputItemYPos + (iButtonY * m_pItemModelPanels[iIndex]->GetTall() ) + (m_iItemBackpackYDelta * iButtonY);
  453. }
  454. m_pItemModelPanels[iIndex]->SetPos( iXPos, iYPos );
  455. return;
  456. }
  457. //-----------------------------------------------------------------------------
  458. // Purpose:
  459. //-----------------------------------------------------------------------------
  460. void CCraftingPanel::UpdateRecipeItems( bool bClearInputItems )
  461. {
  462. if ( bClearInputItems )
  463. {
  464. memset( m_InputItems, 0, sizeof(m_InputItems) );
  465. }
  466. memset( m_ItemPanelCriteria, 0, sizeof(m_ItemPanelCriteria) );
  467. m_iCurrentRecipeTotalInputs = 0;
  468. m_iCurrentRecipeTotalOutputs = 0;
  469. if ( m_iCurrentlySelectedRecipe == -1 )
  470. return;
  471. /*
  472. // Build lists of items divided by class & loadout slot, so recipes can quickly test themselves
  473. CUtlVector<CEconItem*> vecAllItems;
  474. CUtlVector<CEconItem*> vecItemsByClass[ LOADOUT_COUNT ];
  475. CUtlVector<CEconItem*> vecItemsBySlot[ LOADOUT_POSITION_COUNT ];
  476. for ( int i = 1; i <= TFInventoryManager()->GetLocalTFInventory()->GetMaxItemCount(); i++ )
  477. {
  478. CEconItemView *pItemData = TFInventoryManager()->GetItemByBackpackPosition(i);
  479. if ( pItemData && pItemData->IsValid() )
  480. {
  481. CEconItem *pSOCData = pItemData->GetSOCData();
  482. vecAllItems.AddToTail( pSOCData );
  483. CTFItemDefinition *pItemDef = pItemData->GetStaticData();
  484. // Put it in class lists for any class that can use it. Use the zeroth list as all-class items.
  485. if ( pItemDef->CanBeUsedByAllClasses() )
  486. {
  487. vecItemsByClass[0].AddToTail( pSOCData );
  488. }
  489. for (int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
  490. {
  491. if ( pItemDef->CanBeUsedByClass(iClass) )
  492. {
  493. vecItemsByClass[iClass].AddToTail( pSOCData );
  494. }
  495. }
  496. // Put it in the slot lists for any slot that it can be equipped in
  497. for (int iSlot = 0; iSlot < LOADOUT_POSITION_COUNT; iSlot++ )
  498. {
  499. if ( pItemDef->CanBePlacedInSlot( iSlot ) )
  500. {
  501. vecItemsBySlot[iSlot].AddToTail( pSOCData );
  502. }
  503. }
  504. }
  505. }
  506. */
  507. // Find the items needed for the specified recipe
  508. if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM )
  509. {
  510. // Custom recipe. Show all open buttons, and let them put anything in there.
  511. m_iCurrentRecipeTotalInputs = CRAFTING_SLOTS_INPUTPANELS;
  512. m_iCurrentRecipeTotalOutputs = 0;
  513. FOR_EACH_VEC( m_pItemModelPanels, i )
  514. {
  515. m_pItemModelPanels[i]->SetNoItemText( "" );
  516. }
  517. }
  518. else
  519. {
  520. const CTFCraftingRecipeDefinition *pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe );
  521. if ( pRecipeDef )
  522. {
  523. m_iCurrentRecipeTotalInputs = pRecipeDef->GetTotalInputItemsRequired();
  524. m_iCurrentRecipeTotalOutputs = pRecipeDef->GetTotalOutputItems();
  525. CUtlVector<itemid_t> vecItemsUsed;
  526. // Set the text in each of the item panels
  527. const CUtlVector<CItemSelectionCriteria> *vecInputCriteria;
  528. vecInputCriteria = pRecipeDef->GetInputItems();
  529. CUtlVector<uint32> vecInputDupes;
  530. vecInputDupes = pRecipeDef->GetInputItemDupeCounts();
  531. int iModelPanel = 0;
  532. FOR_EACH_VEC( *vecInputCriteria, i )
  533. {
  534. const char *pszNoItemText = GetItemTextForCriteria( &(*vecInputCriteria)[i] );
  535. int iNumPanels = vecInputDupes[i] ? vecInputDupes[i] : 1;
  536. for ( int iPanel = 0; iPanel < iNumPanels; iPanel++ )
  537. {
  538. m_ItemPanelCriteria[iModelPanel] = &(*vecInputCriteria)[i];
  539. if ( m_pItemModelPanels[iModelPanel] )
  540. {
  541. m_pItemModelPanels[iModelPanel]->SetNoItemText( pszNoItemText );
  542. }
  543. iModelPanel++;
  544. }
  545. }
  546. // Set the output items as well
  547. CUtlVector<CItemSelectionCriteria> vecOutputCriteria;
  548. vecOutputCriteria = pRecipeDef->GetOutputItems();
  549. FOR_EACH_VEC( vecOutputCriteria, i )
  550. {
  551. int iOutputPanel = CRAFTING_SLOTS_INPUTPANELS + i;
  552. CEconItemDefinition *pDef = GetItemDefFromCriteria( &vecOutputCriteria[i] );
  553. if ( pDef )
  554. {
  555. //m_pItemModelPanels[iOutputPanel]->SetNoItemText( pszNoItemText );
  556. CEconItemView *pItemData = new CEconItemView();
  557. pItemData->Init( pDef->GetDefinitionIndex(), AE_UNIQUE, AE_USE_SCRIPT_VALUE, true );
  558. if ( m_pItemModelPanels[iOutputPanel] )
  559. {
  560. m_pItemModelPanels[iOutputPanel]->SetItem( pItemData );
  561. }
  562. delete pItemData;
  563. continue;
  564. }
  565. // If we didn't manage to extract an output, just use the recipe output string
  566. wchar_t wcTmpA[32];
  567. wchar_t wcTmpB[32];
  568. wchar_t wcTmpC[32];
  569. wchar_t wcTmp[512];
  570. wchar_t *pOut_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_A(), wcTmpA, sizeof( wcTmpA ) );
  571. wchar_t *pOut_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_B(), wcTmpB, sizeof( wcTmpB ) );
  572. wcTmp[0] = '\0';
  573. V_wcscat_safe( wcTmp, pOut_A );
  574. V_wcscat_safe( wcTmp, L" " );
  575. V_wcscat_safe( wcTmp, pOut_B );
  576. if ( Q_strnicmp( pRecipeDef->GetDescOutputs(), "#RDO_ABC", 8 ) == 0 )
  577. {
  578. wchar_t *pOut_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescO_C(), wcTmpC, sizeof( wcTmpC ) );
  579. V_wcscat_safe( wcTmp, L" " );
  580. V_wcscat_safe( wcTmp, pOut_C );
  581. }
  582. if ( m_pItemModelPanels[iOutputPanel] )
  583. {
  584. m_pItemModelPanels[iOutputPanel]->SetItem( NULL );
  585. m_pItemModelPanels[iOutputPanel]->SetNoItemText( wcTmp );
  586. }
  587. }
  588. }
  589. }
  590. // Now check to see if they've got the right items in there
  591. UpdateCraftButton();
  592. }
  593. //-----------------------------------------------------------------------------
  594. // Purpose:
  595. //-----------------------------------------------------------------------------
  596. void CCraftingPanel::UpdateCraftButton( void )
  597. {
  598. if ( m_iCurrentlySelectedRecipe == -1 )
  599. return;
  600. bool bAllowedToUse = true;
  601. const CEconCraftingRecipeDefinition *pRecipeDef = NULL;
  602. if ( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM )
  603. {
  604. pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe );
  605. if ( !pRecipeDef )
  606. return;
  607. bAllowedToUse = ( !IsFreeTrialAccount() || !pRecipeDef->IsPremiumAccountOnly() );
  608. }
  609. if ( m_pCraftButton )
  610. {
  611. m_pCraftButton->SetVisible( bAllowedToUse );
  612. }
  613. if ( m_pUpgradeButton )
  614. {
  615. m_pUpgradeButton->SetVisible( !bAllowedToUse );
  616. }
  617. if ( m_pFreeAccountLabel )
  618. {
  619. m_pFreeAccountLabel->SetVisible( !bAllowedToUse );
  620. }
  621. if ( !bAllowedToUse )
  622. return;
  623. bool bCraftButtonActive = false;
  624. if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM )
  625. {
  626. // Need at least one item in a slot
  627. for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ )
  628. {
  629. CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] );
  630. if ( pItemData )
  631. {
  632. bCraftButtonActive = true;
  633. break;
  634. }
  635. }
  636. }
  637. else
  638. {
  639. CUtlVector<CEconItem*> vecAllItems;
  640. for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ )
  641. {
  642. CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] );
  643. if ( pItemData )
  644. {
  645. vecAllItems.AddToTail( pItemData->GetSOCData() );
  646. }
  647. }
  648. bCraftButtonActive = pRecipeDef->ItemListMatchesInputs( &vecAllItems, NULL, false, NULL );
  649. }
  650. if ( m_pCraftButton )
  651. {
  652. m_pCraftButton->SetEnabled( bCraftButtonActive );
  653. }
  654. }
  655. //-----------------------------------------------------------------------------
  656. // Purpose:
  657. //-----------------------------------------------------------------------------
  658. const char *CCraftingPanel::GetItemTextForCriteria( const CItemSelectionCriteria *pCriteria )
  659. {
  660. // Otherwise, look at the first condition, and see if we can determine what the item is
  661. const char *pszVal = pCriteria->GetValueForFirstConditionOfType( k_EOperator_String_EQ );
  662. if ( pszVal && pszVal[0] )
  663. {
  664. // Is it a loadout slot?
  665. int iSlot = StringFieldToInt( pszVal, ItemSystem()->GetItemSchema()->GetLoadoutStrings( EEquipType_t::EQUIP_TYPE_CLASS ), true );
  666. if ( iSlot != -1 )
  667. return ItemSystem()->GetItemSchema()->GetLoadoutStringsForDisplay( EEquipType_t::EQUIP_TYPE_CLASS )[iSlot];
  668. // Is it a craft material type?
  669. if ( V_stricmp( pszVal, "weapon" ) == 0 )
  670. {
  671. return "#RI_W";
  672. }
  673. else if ( V_stricmp( pszVal, "hat" ) == 0 )
  674. {
  675. return "#RI_Hg";
  676. }
  677. else if ( V_stricmp( pszVal, "craft_token" ) == 0 )
  678. {
  679. return "#RI_T";
  680. }
  681. else if ( V_stricmp( pszVal, "class_token" ) == 0 )
  682. {
  683. return "#CI_T_C";
  684. }
  685. else if ( V_stricmp( pszVal, "slot_token" ) == 0 )
  686. {
  687. return "#CI_T_S";
  688. }
  689. // Is it an item name?
  690. CEconItemDefinition *pDef = ItemSystem()->GetItemSchema()->GetItemDefinitionByName(pszVal);
  691. if ( pDef )
  692. return pDef->GetItemBaseName();
  693. }
  694. return NULL;
  695. }
  696. //-----------------------------------------------------------------------------
  697. // Purpose:
  698. //-----------------------------------------------------------------------------
  699. CEconItemDefinition *CCraftingPanel::GetItemDefFromCriteria( const CItemSelectionCriteria *pCriteria )
  700. {
  701. // Otherwise, look at the first condition, and see if we can determine what the item is
  702. const char *pszVal = pCriteria->GetValueForFirstConditionOfType( k_EOperator_String_EQ );
  703. if ( pszVal && pszVal[0] )
  704. return ItemSystem()->GetItemSchema()->GetItemDefinitionByName(pszVal);
  705. return NULL;
  706. }
  707. //-----------------------------------------------------------------------------
  708. // Purpose:
  709. //-----------------------------------------------------------------------------
  710. void CCraftingPanel::AddNewItemPanel( int iPanelIndex )
  711. {
  712. BaseClass::AddNewItemPanel( iPanelIndex );
  713. // Move the model panels to our selected recipe container
  714. m_pItemModelPanels[iPanelIndex]->SetParent( m_pSelectedRecipeContainer );
  715. }
  716. //-----------------------------------------------------------------------------
  717. // Purpose:
  718. //-----------------------------------------------------------------------------
  719. void CCraftingPanel::UpdateModelPanels( void )
  720. {
  721. BaseClass::UpdateModelPanels();
  722. for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
  723. {
  724. if ( IsInputItemPanel(i) )
  725. {
  726. if ( m_InputItems[i] != 0 )
  727. {
  728. CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] );
  729. m_pItemModelPanels[i]->SetItem( pItemData );
  730. m_pItemModelPanels[i]->SetVisible( true );
  731. m_pItemModelPanels[i]->SetShowEquipped( true );
  732. SetBorderForItem( m_pItemModelPanels[i], false );
  733. }
  734. else
  735. {
  736. m_pItemModelPanels[i]->SetItem( NULL );
  737. // Always show the number of slots that the recipe uses
  738. bool bVisible = (m_iCurrentRecipeTotalInputs > i);
  739. m_pItemModelPanels[i]->SetVisible( bVisible );
  740. }
  741. }
  742. else
  743. {
  744. bool bVisible = ((m_iCurrentRecipeTotalOutputs + CRAFTING_SLOTS_INPUTPANELS) > i);
  745. m_pItemModelPanels[i]->SetVisible( bVisible );
  746. }
  747. }
  748. vgui::Panel *pLabel = m_pSelectedRecipeContainer->FindChildByName("OutputLabel");
  749. if ( pLabel )
  750. {
  751. pLabel->SetVisible( m_iCurrentRecipeTotalOutputs > 0 );
  752. }
  753. }
  754. //-----------------------------------------------------------------------------
  755. // Purpose:
  756. //-----------------------------------------------------------------------------
  757. void CCraftingPanel::SetButtonToRecipe( int iButton, int iDefIndex, wchar_t *pszText )
  758. {
  759. // Re-use existing buttons, or make new ones if we need more
  760. CRecipeButton *pRecipeButton = NULL;
  761. if ( iButton < m_pRecipeButtons.Count() )
  762. {
  763. pRecipeButton = m_pRecipeButtons[iButton];
  764. }
  765. else
  766. {
  767. pRecipeButton = new CRecipeButton( m_pRecipeListContainer, "selectrecipe", "", this, "selectrecipe" );
  768. if ( m_pRecipeButtonsKV )
  769. {
  770. pRecipeButton->ApplySettings( m_pRecipeButtonsKV );
  771. }
  772. pRecipeButton->MakeReadyForUse();
  773. m_pRecipeButtons.AddToTail( pRecipeButton );
  774. }
  775. const char *pszCommand = VarArgs("selectrecipe%d", iDefIndex );
  776. pRecipeButton->SetCommand( pszCommand );
  777. pRecipeButton->SetText( pszText );
  778. pRecipeButton->SetDefIndex( iDefIndex );
  779. }
  780. //-----------------------------------------------------------------------------
  781. // Purpose:
  782. //-----------------------------------------------------------------------------
  783. void CCraftingPanel::UpdateSelectedRecipe( bool bClearInputItems )
  784. {
  785. for ( int i = 0; i < m_pRecipeButtons.Count(); i++ )
  786. {
  787. bool bSelected = m_pRecipeButtons[i]->m_iRecipeDefIndex == m_iCurrentlySelectedRecipe;
  788. m_pRecipeButtons[i]->ForceDepressed( bSelected );
  789. m_pRecipeButtons[i]->RecalculateDepressedState();
  790. if ( bSelected )
  791. {
  792. wchar_t wszText[1024];
  793. m_pRecipeButtons[i]->GetText( wszText, ARRAYSIZE( wszText ) );
  794. m_pSelectedRecipeContainer->SetDialogVariable( "recipetitle", wszText );
  795. if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM )
  796. {
  797. m_pSelectedRecipeContainer->SetDialogVariable( "recipeinputstring", g_pVGuiLocalize->Find("#Craft_Recipe_CustomDesc") );
  798. }
  799. else
  800. {
  801. const CTFCraftingRecipeDefinition *pRecipeDef = (CTFCraftingRecipeDefinition*)TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( m_iCurrentlySelectedRecipe );
  802. if ( pRecipeDef )
  803. {
  804. // Build the input string
  805. wchar_t wcTmpA[32];
  806. wchar_t wcTmpB[32];
  807. wchar_t wcTmpC[32];
  808. wchar_t wcTmpDesc[512];
  809. wchar_t *pInp_A = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_A(), wcTmpA, sizeof( wcTmpA ) );
  810. wchar_t *pInp_B = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_B(), wcTmpB, sizeof( wcTmpB ) );
  811. wchar_t *pInp_C = LocalizeRecipeStringPiece( pRecipeDef->GetDescI_C(), wcTmpC, sizeof( wcTmpC ) );
  812. g_pVGuiLocalize->ConstructString_safe( wcTmpDesc, g_pVGuiLocalize->Find( pRecipeDef->GetDescInputs() ), 3, pInp_A, pInp_B, pInp_C );
  813. m_pSelectedRecipeContainer->SetDialogVariable( "recipeinputstring", wcTmpDesc );
  814. }
  815. }
  816. }
  817. }
  818. m_pSelectedRecipeContainer->SetVisible( m_iCurrentlySelectedRecipe != -1 );
  819. UpdateRecipeItems( bClearInputItems );
  820. UpdateModelPanels();
  821. }
  822. //-----------------------------------------------------------------------------
  823. // Purpose:
  824. //-----------------------------------------------------------------------------
  825. void CCraftingPanel::OnCommand( const char *command )
  826. {
  827. if ( !Q_strnicmp( command, "selectrecipe", 12 ) )
  828. {
  829. const char *pszNum = command+12;
  830. if ( pszNum && pszNum[0] )
  831. {
  832. m_iCurrentlySelectedRecipe = atoi(pszNum);
  833. UpdateSelectedRecipe( true );
  834. }
  835. return;
  836. }
  837. if ( !Q_strnicmp( command, "selectfilter", 12 ) )
  838. {
  839. const char *pszNum = command+12;
  840. if ( pszNum && pszNum[0] )
  841. {
  842. m_iRecipeCategoryFilter = (recipecategories_t)atoi(pszNum);
  843. UpdateRecipeFilter();
  844. }
  845. return;
  846. }
  847. else if ( !Q_strnicmp( command, "back", 4 ) )
  848. {
  849. PostMessage( GetParent(), new KeyValues("CraftingClosed") );
  850. return;
  851. }
  852. else if ( !Q_strnicmp( command, "craft", 5 ) )
  853. {
  854. if ( CheckForUntradableItems() )
  855. {
  856. Craft();
  857. }
  858. return;
  859. }
  860. else if ( !Q_stricmp( command, "upgrade" ) )
  861. {
  862. EconUI()->CloseEconUI();
  863. EconUI()->OpenStorePanel( STOREPANEL_SHOW_UPGRADESTEPS, false );
  864. return;
  865. }
  866. else if ( !Q_stricmp( command, "reloadscheme" ) )
  867. {
  868. InvalidateLayout( true, true );
  869. }
  870. BaseClass::OnCommand( command );
  871. }
  872. //-----------------------------------------------------------------------------
  873. // Purpose:
  874. //-----------------------------------------------------------------------------
  875. void CCraftingPanel::OnRecipePanelEntered( vgui::Panel *panel )
  876. {
  877. CRecipeButton *pRecipePanel = dynamic_cast < CRecipeButton * > ( panel );
  878. if ( pRecipePanel && IsVisible() && !IsIgnoringItemPanelEnters() )
  879. {
  880. const CEconCraftingRecipeDefinition *pRecipeDef = NULL;
  881. if ( pRecipePanel->m_iRecipeDefIndex != RECIPE_CUSTOM )
  882. {
  883. pRecipeDef = TFInventoryManager()->GetLocalTFInventory()->GetRecipeDefByDefIndex( pRecipePanel->m_iRecipeDefIndex );
  884. }
  885. SetItemPanelToRecipe( GetMouseOverPanel(), pRecipeDef, false );
  886. PositionMouseOverPanelForRecipe( this, pRecipePanel, m_pRecipeListContainerScroller, GetMouseOverPanel() );
  887. }
  888. }
  889. //-----------------------------------------------------------------------------
  890. // Purpose:
  891. //-----------------------------------------------------------------------------
  892. void CCraftingPanel::OnRecipePanelExited( vgui::Panel *panel )
  893. {
  894. GetMouseOverPanel()->SetAttribOnly( false );
  895. GetMouseOverPanel()->SetTextYPos( YRES(20) );
  896. GetMouseOverPanel()->SetVisible( false );
  897. }
  898. //-----------------------------------------------------------------------------
  899. // Purpose:
  900. //-----------------------------------------------------------------------------
  901. int CCraftingPanel::GetItemPanelIndex( CItemModelPanel *pItemPanel )
  902. {
  903. for ( int i = 0; i < m_pItemModelPanels.Count(); i++ )
  904. {
  905. if ( m_pItemModelPanels[i] == pItemPanel )
  906. return i;
  907. }
  908. return -1;
  909. }
  910. //-----------------------------------------------------------------------------
  911. // Purpose:
  912. //-----------------------------------------------------------------------------
  913. void CCraftingPanel::OnItemPanelMousePressed( vgui::Panel *panel )
  914. {
  915. CItemModelPanel *pItemPanel = dynamic_cast < CItemModelPanel * > ( panel );
  916. if ( pItemPanel && IsVisible() && !pItemPanel->IsGreyedOut() )
  917. {
  918. int iPos = GetItemPanelIndex(pItemPanel);
  919. if ( IsInputItemPanel(iPos) )
  920. {
  921. m_iSelectingForSlot = iPos;
  922. // Create it the first time around
  923. if ( !m_pSelectionPanel )
  924. {
  925. m_pSelectionPanel = new CCraftingItemSelectionPanel( this );
  926. }
  927. if ( m_iCurrentlySelectedRecipe == RECIPE_CUSTOM )
  928. {
  929. m_pSelectionPanel->UpdateOnShow( NULL, true, m_InputItems, ARRAYSIZE(m_InputItems) );
  930. }
  931. else
  932. {
  933. // Clicked on an item in the crafting area. Open up the selection panel.
  934. m_pSelectionPanel->UpdateOnShow( m_ItemPanelCriteria[iPos], false, m_InputItems, ARRAYSIZE(m_InputItems) );
  935. }
  936. m_pSelectionPanel->ShowDuplicateCounts( true );
  937. m_pSelectionPanel->ShowPanel( 0, true );
  938. }
  939. }
  940. }
  941. //-----------------------------------------------------------------------------
  942. // Purpose:
  943. //-----------------------------------------------------------------------------
  944. static void ConfirmCraft( bool bConfirmed, void* pContext )
  945. {
  946. CCraftingPanel *pCraftingPanel = ( CCraftingPanel* )pContext;
  947. if ( bConfirmed )
  948. {
  949. pCraftingPanel->Craft();
  950. }
  951. }
  952. //-----------------------------------------------------------------------------
  953. // Purpose:
  954. //-----------------------------------------------------------------------------
  955. bool CCraftingPanel::CheckForUntradableItems( void )
  956. {
  957. bool bHasUntradable = false;
  958. for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ )
  959. {
  960. if ( m_InputItems[i] != 0 )
  961. {
  962. CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] );
  963. if ( pItemData->IsTradable() == false )
  964. {
  965. bHasUntradable = true;
  966. break;
  967. }
  968. }
  969. }
  970. if ( bHasUntradable )
  971. {
  972. CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#Craft_Untradable_Title", "#Craft_Untradable_Text", "#GameUI_OK", "#Cancel", &ConfirmCraft );
  973. pDialog->SetContext( this );
  974. return false;
  975. }
  976. return true;
  977. }
  978. //-----------------------------------------------------------------------------
  979. // Purpose:
  980. //-----------------------------------------------------------------------------
  981. void CCraftingPanel::Craft( void )
  982. {
  983. // Build our list of items that we're trying to craft
  984. ++m_iCraftingAttempts;
  985. CUtlVector<itemid_t> vecCraftingItems;
  986. for ( int i = 0; i < CRAFTING_SLOTS_INPUTPANELS; i++ )
  987. {
  988. if ( m_InputItems[i] != 0 )
  989. {
  990. CEconItemView *pItemData = TFInventoryManager()->GetLocalTFInventory()->GetInventoryItemByItemID( m_InputItems[i] );
  991. C_CTF_GameStats.Event_Crafting( IE_CRAFTING_ATTEMPT, pItemData, m_iCraftingAttempts );
  992. vecCraftingItems.AddToTail( m_InputItems[i] );
  993. }
  994. }
  995. if ( !vecCraftingItems.Count() )
  996. return;
  997. GCSDK::CGCMsg<MsgGCCraft_t> msg( k_EMsgGCCraft );
  998. msg.Body().m_nRecipeDefIndex = m_iCurrentlySelectedRecipe;
  999. msg.Body().m_nItemCount = vecCraftingItems.Count();
  1000. for ( int i = 0; i < vecCraftingItems.Count(); i++ )
  1001. {
  1002. msg.AddUint64Data( vecCraftingItems[i] );
  1003. }
  1004. GCClientSystem()->BSendMessage( msg );
  1005. OpenCraftingStatusDialog( this, "#CraftUpdate_Start", true, false, false );
  1006. // Start ticking so we can give up waiting if we don't get a response from the GC
  1007. // We use the VGUI time, because we may not be in a game at all.
  1008. m_flAbortCraftingAt = vgui::system()->GetCurrentTime() + 10;
  1009. m_bWaitingForCraftItems = false;
  1010. m_iRecipeIndexTried = m_iCurrentlySelectedRecipe;
  1011. vgui::ivgui()->AddTickSignal( GetVPanel(), 100 );
  1012. }
  1013. //-----------------------------------------------------------------------------
  1014. // Purpose:
  1015. //-----------------------------------------------------------------------------
  1016. void CCraftingPanel::OnCraftResponse( EGCMsgResponse eResponse, CUtlVector<uint64> *vecCraftedIndices, int iRecipeUsed )
  1017. {
  1018. switch ( eResponse )
  1019. {
  1020. case k_EGCMsgResponseNoMatch:
  1021. {
  1022. C_CTF_GameStats.Event_Crafting( IE_CRAFTING_NO_RECIPE_MATCH, NULL, m_iCraftingAttempts );
  1023. CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM );
  1024. OpenCraftingStatusDialog( this, "#CraftUpdate_NoMatch", false, true, false );
  1025. }
  1026. break;
  1027. case k_EGCMsgResponseDenied:
  1028. {
  1029. // Craft denied.
  1030. C_CTF_GameStats.Event_Crafting( IE_CRAFTING_FAILURE, NULL, m_iCraftingAttempts );
  1031. CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM );
  1032. OpenCraftingStatusDialog( this, "#CraftUpdate_Denied", false, true, false );
  1033. }
  1034. break;
  1035. // We've got the list of items crafted. We save off the item list until our item cache has all the items.
  1036. case k_EGCMsgResponseOK:
  1037. {
  1038. // Start ticking, and wait until the cache contains all the items in the list.
  1039. m_bWaitingForCraftItems = true;
  1040. m_vecNewlyCraftedItems = *vecCraftedIndices;
  1041. if ( iRecipeUsed != m_iRecipeIndexTried && iRecipeUsed != -1 )
  1042. {
  1043. m_iNewRecipeIndex = iRecipeUsed;
  1044. }
  1045. }
  1046. break;
  1047. default:
  1048. {
  1049. // Craft failed in some way.
  1050. C_CTF_GameStats.Event_Crafting( IE_CRAFTING_FAILURE, NULL, m_iCraftingAttempts );
  1051. OpenCraftingStatusDialog( this, "#CraftUpdate_Failed", false, true, false );
  1052. CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM );
  1053. }
  1054. break;
  1055. }
  1056. }
  1057. //-----------------------------------------------------------------------------
  1058. // Purpose:
  1059. //-----------------------------------------------------------------------------
  1060. void CCraftingPanel::ShowCraftFinish( void )
  1061. {
  1062. TFInventoryManager()->ShowItemsCrafted( &m_vecNewlyCraftedItems );
  1063. }
  1064. //-----------------------------------------------------------------------------
  1065. // Purpose:
  1066. //-----------------------------------------------------------------------------
  1067. void CCraftingPanel::OnTick( void )
  1068. {
  1069. BaseClass::OnTick();
  1070. if ( IsVisible() )
  1071. {
  1072. if ( m_flAbortCraftingAt )
  1073. {
  1074. if ( m_flAbortCraftingAt < vgui::system()->GetCurrentTime() )
  1075. {
  1076. C_CTF_GameStats.Event_Crafting( IE_CRAFTING_TIMEOUT, NULL, m_iCraftingAttempts );
  1077. CleanupPostCraft( m_iCurrentlySelectedRecipe != RECIPE_CUSTOM );
  1078. OpenCraftingStatusDialog( this, "#CraftUpdate_Failed", false, true, false );
  1079. return;
  1080. }
  1081. }
  1082. if ( m_bWaitingForCraftItems )
  1083. {
  1084. // If all the items in our newly crafted list are in the cache, we can show the pickup.
  1085. FOR_EACH_VEC_BACK( m_vecNewlyCraftedItems, i )
  1086. {
  1087. CEconItemView* pNewItem = InventoryManager()->GetLocalInventory()->GetInventoryItemByItemID( m_vecNewlyCraftedItems[i] );
  1088. if ( pNewItem == NULL )
  1089. return;
  1090. C_CTF_GameStats.Event_Crafting( IE_CRAFTING_SUCCESS, pNewItem, m_iCraftingAttempts );
  1091. }
  1092. m_bWaitingForCraftItems = false;
  1093. // We have all the new items, show the pickup
  1094. OpenCraftingStatusDialog( this, "#CraftUpdate_Success", false, true, true );
  1095. CleanupPostCraft( true );
  1096. }
  1097. }
  1098. }
  1099. //-----------------------------------------------------------------------------
  1100. // Purpose:
  1101. //-----------------------------------------------------------------------------
  1102. void CCraftingPanel::CleanupPostCraft( bool bClearInputItems )
  1103. {
  1104. m_flAbortCraftingAt = 0;
  1105. m_bWaitingForCraftItems = false;
  1106. UpdateSelectedRecipe( bClearInputItems );
  1107. }
  1108. //-----------------------------------------------------------------------------
  1109. // Purpose:
  1110. //-----------------------------------------------------------------------------
  1111. ConVar *CCraftingPanel::GetExplanationConVar( void )
  1112. {
  1113. return &tf_explanations_craftingpanel;
  1114. }
  1115. //================================================================================================================================
  1116. // NOT CONNECTED TO STEAM WARNING DIALOG
  1117. //================================================================================================================================
  1118. static vgui::DHANDLE<CCraftingStatusDialog> g_CraftingStatusPanel;
  1119. //-----------------------------------------------------------------------------
  1120. // Purpose:
  1121. //-----------------------------------------------------------------------------
  1122. CCraftingStatusDialog::CCraftingStatusDialog( vgui::Panel *pParent, const char *pElementName ) : BaseClass( pParent, "CraftingStatusDialog" )
  1123. {
  1124. m_pRecipePanel = vgui::SETUP_PANEL( new CItemModelPanel( this, "RecipeItemModelPanel" ) );
  1125. m_bShowNewRecipe = false;
  1126. }
  1127. //-----------------------------------------------------------------------------
  1128. // Purpose:
  1129. //-----------------------------------------------------------------------------
  1130. void CCraftingStatusDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
  1131. {
  1132. BaseClass::ApplySchemeSettings( pScheme );
  1133. if ( m_bShowNewRecipe )
  1134. {
  1135. LoadControlSettings( "resource/UI/NewRecipeFoundDialog.res" );
  1136. }
  1137. else
  1138. {
  1139. LoadControlSettings( "resource/UI/CraftingStatusDialog.res" );
  1140. }
  1141. }
  1142. //-----------------------------------------------------------------------------
  1143. // Purpose:
  1144. //-----------------------------------------------------------------------------
  1145. void CCraftingStatusDialog::OnCommand( const char *command )
  1146. {
  1147. bool bClose = false;
  1148. if ( !Q_stricmp( command, "close" ) )
  1149. {
  1150. // If we were a success, show the player their new crafted items
  1151. if ( m_bShowOnExit )
  1152. {
  1153. if ( EconUI()->GetCraftingPanel() )
  1154. {
  1155. EconUI()->GetCraftingPanel()->ShowCraftFinish();
  1156. }
  1157. m_bShowOnExit = false;
  1158. }
  1159. bClose = true;
  1160. }
  1161. else if ( !Q_stricmp( command, "forceclose" ) )
  1162. {
  1163. bClose = true;
  1164. }
  1165. if ( bClose )
  1166. {
  1167. m_bShowOnExit = false;
  1168. TFModalStack()->PopModal( this );
  1169. SetVisible( false );
  1170. MarkForDeletion();
  1171. EconUI()->SetPreventClosure( false );
  1172. return;
  1173. }
  1174. BaseClass::OnCommand( command );
  1175. }
  1176. //-----------------------------------------------------------------------------
  1177. // Purpose:
  1178. //-----------------------------------------------------------------------------
  1179. void CCraftingStatusDialog::OnTick( void )
  1180. {
  1181. if ( !m_bAnimateEllipses || !IsVisible() )
  1182. {
  1183. vgui::ivgui()->RemoveTickSignal( GetVPanel() );
  1184. }
  1185. else
  1186. {
  1187. m_iNumEllipses = ((m_iNumEllipses+1) % 4);
  1188. }
  1189. switch ( m_iNumEllipses )
  1190. {
  1191. case 3: SetDialogVariable( "ellipses", L"..." ); break;
  1192. case 2: SetDialogVariable( "ellipses", L".." ); break;
  1193. case 1: SetDialogVariable( "ellipses", L"." ); break;
  1194. default: SetDialogVariable( "ellipses", L"" ); break;
  1195. }
  1196. BaseClass::OnTick();
  1197. }
  1198. //-----------------------------------------------------------------------------
  1199. // Purpose:
  1200. //-----------------------------------------------------------------------------
  1201. void CCraftingStatusDialog::UpdateSchemeForVersion( bool bRecipe )
  1202. {
  1203. m_bShowNewRecipe = bRecipe;
  1204. InvalidateLayout( false, true );
  1205. }
  1206. //-----------------------------------------------------------------------------
  1207. // Purpose:
  1208. //-----------------------------------------------------------------------------
  1209. void CCraftingStatusDialog::ShowStatusUpdate( bool bAnimateEllipses, bool bAllowClose, bool bShowOnExit )
  1210. {
  1211. m_bShowNewRecipe = false;
  1212. CExButton *pButton = dynamic_cast<CExButton*>( FindChildByName("CloseButton") );
  1213. if ( pButton )
  1214. {
  1215. pButton->SetVisible( bAllowClose );
  1216. pButton->SetEnabled( bAllowClose );
  1217. }
  1218. m_bAnimateEllipses = bAnimateEllipses;
  1219. if ( m_bAnimateEllipses )
  1220. {
  1221. vgui::ivgui()->AddTickSignal( GetVPanel(), 500 );
  1222. SetDialogVariable( "ellipses", L"" );
  1223. m_iNumEllipses = 0;
  1224. }
  1225. else
  1226. {
  1227. vgui::ivgui()->RemoveTickSignal( GetVPanel() );
  1228. SetDialogVariable( "ellipses", L"" );
  1229. }
  1230. m_bShowOnExit = bShowOnExit;
  1231. }
  1232. //-----------------------------------------------------------------------------
  1233. // Purpose:
  1234. //-----------------------------------------------------------------------------
  1235. void SetupCraftingStatusDialog( vgui::Panel *pParent )
  1236. {
  1237. if (!g_CraftingStatusPanel.Get())
  1238. {
  1239. g_CraftingStatusPanel = vgui::SETUP_PANEL( new CCraftingStatusDialog( pParent, NULL ) );
  1240. }
  1241. g_CraftingStatusPanel->SetVisible( true );
  1242. g_CraftingStatusPanel->MakePopup();
  1243. g_CraftingStatusPanel->MoveToFront();
  1244. g_CraftingStatusPanel->SetKeyBoardInputEnabled(true);
  1245. g_CraftingStatusPanel->SetMouseInputEnabled(true);
  1246. TFModalStack()->PushModal( g_CraftingStatusPanel );
  1247. EconUI()->SetPreventClosure( true );
  1248. }
  1249. CCraftingStatusDialog *OpenCraftingStatusDialog( vgui::Panel *pParent, const char *pszText, bool bAnimateEllipses, bool bAllowClose, bool bShowOnExit )
  1250. {
  1251. SetupCraftingStatusDialog( pParent );
  1252. g_CraftingStatusPanel->UpdateSchemeForVersion( false );
  1253. g_CraftingStatusPanel->SetDialogVariable( "updatetext", g_pVGuiLocalize->Find( pszText ) );
  1254. g_CraftingStatusPanel->ShowStatusUpdate( bAnimateEllipses, bAllowClose, bShowOnExit );
  1255. return g_CraftingStatusPanel;
  1256. }
  1257. //-----------------------------------------------------------------------------
  1258. // Purpose:
  1259. //-----------------------------------------------------------------------------
  1260. void CloseCraftingStatusDialog( void )
  1261. {
  1262. if ( g_CraftingStatusPanel )
  1263. {
  1264. g_CraftingStatusPanel->OnCommand( "forceclose" );
  1265. }
  1266. }
  1267. //-----------------------------------------------------------------------------
  1268. // Purpose: GC Msg handler to receive the craft response
  1269. //-----------------------------------------------------------------------------
  1270. class CGCCraftResponse : public GCSDK::CGCClientJob
  1271. {
  1272. public:
  1273. CGCCraftResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1274. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1275. {
  1276. GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket );
  1277. CUtlVector<uint64> vecCraftedIndices;
  1278. uint16 iItems = 0;
  1279. if ( !msg.BReadUint16Data( &iItems ) )
  1280. return true;
  1281. vecCraftedIndices.SetSize( iItems );
  1282. for ( int i = 0; i < iItems; i++ )
  1283. {
  1284. if( !msg.BReadUint64Data( &vecCraftedIndices[i] ) )
  1285. return true;
  1286. }
  1287. if ( EconUI()->GetCraftingPanel() )
  1288. {
  1289. EconUI()->GetCraftingPanel()->OnCraftResponse( (EGCMsgResponse)msg.Body().m_eResponse, &vecCraftedIndices, msg.Body().m_nResponseIndex );
  1290. }
  1291. //Msg("RECEIVED CGCCraftResponse: %d\n", msg.Body().m_eResponse );
  1292. return true;
  1293. }
  1294. };
  1295. GC_REG_JOB( GCSDK::CGCClient, CGCCraftResponse, "CGCCraftResponse", k_EMsgGCCraftResponse, GCSDK::k_EServerTypeGCClient );
  1296. //-----------------------------------------------------------------------------
  1297. // Purpose: GC Msg handler to receive the Golden Wrench broadcast message
  1298. //-----------------------------------------------------------------------------
  1299. class CGCGoldenWrenchBroadcast : public GCSDK::CGCClientJob
  1300. {
  1301. public:
  1302. CGCGoldenWrenchBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1303. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1304. {
  1305. GCSDK::CProtoBufMsg<CMsgTFGoldenWrenchBroadcast> msg( pNetPacket );
  1306. // @todo Tom Bui: should we display this in some other manner? This gets covered up by the crafting panel.
  1307. CHudNotificationPanel *pNotifyPanel = GET_HUDELEMENT( CHudNotificationPanel );
  1308. if ( pNotifyPanel )
  1309. {
  1310. bool bDeleted = msg.Body().deleted();
  1311. wchar_t szPlayerName[1024];
  1312. g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().user_name().c_str(), szPlayerName, sizeof(szPlayerName) );
  1313. wchar_t szWrenchNumber[16]=L"";
  1314. _snwprintf( szWrenchNumber, ARRAYSIZE( szWrenchNumber ), L"%i", msg.Body().wrench_number() );
  1315. wchar_t szNotification[1024]=L"";
  1316. g_pVGuiLocalize->ConstructString_safe( szNotification,
  1317. g_pVGuiLocalize->Find( bDeleted ? "#TF_HUD_Event_GoldenWrench_D": "#TF_HUD_Event_GoldenWrench_C" ),
  1318. 2, szPlayerName, szWrenchNumber );
  1319. pNotifyPanel->SetupNotifyCustom( szNotification, HUD_NOTIFY_GOLDEN_WRENCH, 10.0f );
  1320. // echo to chat
  1321. CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat );
  1322. if ( pHUDChat )
  1323. {
  1324. char szAnsi[1024];
  1325. g_pVGuiLocalize->ConvertUnicodeToANSI( szNotification, szAnsi, sizeof(szAnsi) );
  1326. pHUDChat->Printf( CHAT_FILTER_NONE, "%s", szAnsi );
  1327. }
  1328. // play a sound
  1329. vgui::surface()->PlaySound( bDeleted ? "vo/announcer_failure.mp3" : "vo/announcer_success.mp3" );
  1330. }
  1331. //Msg("RECEIVED CGCCraftResponse: %d\n", msg.Body().m_eResponse );
  1332. return true;
  1333. }
  1334. };
  1335. GC_REG_JOB( GCSDK::CGCClient, CGCGoldenWrenchBroadcast, "CGCGoldenWrenchBroadcast", k_EMsgGCGoldenWrenchBroadcast, GCSDK::k_EServerTypeGCClient );
  1336. //-----------------------------------------------------------------------------
  1337. // Purpose: GC Msg handler to receive the Saxxy broadcast message
  1338. //-----------------------------------------------------------------------------
  1339. class CGSaxxyBroadcast : public GCSDK::CGCClientJob
  1340. {
  1341. public:
  1342. CGSaxxyBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1343. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1344. {
  1345. GCSDK::CProtoBufMsg<CMsgTFSaxxyBroadcast> msg( pNetPacket );
  1346. CEconNotification *pNotification = new CEconNotification();
  1347. pNotification->SetText( "#TF_Event_Saxxy_Deleted" );
  1348. pNotification->SetLifetime( 30.0f );
  1349. {
  1350. // Who deleted this?
  1351. wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ];
  1352. g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().has_user_name() ? msg.Body().user_name().c_str() : NULL, wszPlayerName, sizeof( wszPlayerName ) );
  1353. pNotification->AddStringToken( "owner", wszPlayerName );
  1354. // What category was the Saxxy for?
  1355. char szCategory[MAX_ATTRIBUTE_DESCRIPTION_LENGTH];
  1356. Q_snprintf( szCategory, sizeof( szCategory ), "Replay_Contest_Category%d", msg.Body().category_number() );
  1357. pNotification->AddStringToken( "category", g_pVGuiLocalize->Find( szCategory ) );
  1358. }
  1359. NotificationQueue_Add( pNotification );
  1360. return true;
  1361. }
  1362. };
  1363. GC_REG_JOB( GCSDK::CGCClient, CGSaxxyBroadcast, "CGSaxxyBroadcast", k_EMsgGCSaxxyBroadcast, GCSDK::k_EServerTypeGCClient );
  1364. //-----------------------------------------------------------------------------
  1365. // Purpose: GC Msg handler to receive any generic item deletion notification
  1366. //-----------------------------------------------------------------------------
  1367. class CClientItemBroadcastNotificationJob : public GCSDK::CGCClientJob
  1368. {
  1369. public:
  1370. CClientItemBroadcastNotificationJob( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1371. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1372. {
  1373. GCSDK::CProtoBufMsg<CMsgGCTFSpecificItemBroadcast> msg( pNetPacket );
  1374. CEconNotification *pNotification = new CEconNotification();
  1375. pNotification->SetText( msg.Body().was_destruction() ? "#TF_Event_Item_Deleted" : "#TF_Event_Item_Created" );
  1376. pNotification->SetLifetime( 30.0f );
  1377. // Who deleted this?
  1378. wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ];
  1379. g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().has_user_name() ? msg.Body().user_name().c_str() : NULL, wszPlayerName, sizeof( wszPlayerName ) );
  1380. pNotification->AddStringToken( "owner", wszPlayerName );
  1381. // What type of item was this?
  1382. const CEconItemDefinition *pItemDef = GetItemSchema()->GetItemDefinition( msg.Body().item_def_index() );
  1383. if ( pItemDef )
  1384. {
  1385. pNotification->AddStringToken( "item_name", g_pVGuiLocalize->Find( pItemDef->GetItemBaseName() ) );
  1386. NotificationQueue_Add( pNotification );
  1387. }
  1388. return true;
  1389. }
  1390. };
  1391. GC_REG_JOB( GCSDK::CGCClient, CClientItemBroadcastNotificationJob, "CClientItemBroadcastNotificationJob", k_EMsgGCTFSpecificItemBroadcast, GCSDK::k_EServerTypeGCClient );
  1392. //-----------------------------------------------------------------------------
  1393. // Purpose: GC Msg handler to receive the Saxxy Awarded broadcast message
  1394. //-----------------------------------------------------------------------------
  1395. class CGSaxxyAwardedBroadcast : public GCSDK::CGCClientJob
  1396. {
  1397. private:
  1398. // embedded notification for custom trigger
  1399. class CSaxxyAwardedNotification : public CEconNotification
  1400. {
  1401. public:
  1402. CSaxxyAwardedNotification()
  1403. {
  1404. SetSoundFilename( "vo/announcer_success.mp3" );
  1405. }
  1406. virtual EType NotificationType() { return eType_Trigger; }
  1407. virtual void Trigger()
  1408. {
  1409. if ( steamapicontext && steamapicontext->SteamFriends() )
  1410. {
  1411. steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( "http://www.teamfortress.com/saxxyawards/winners.php" );
  1412. }
  1413. MarkForDeletion();
  1414. }
  1415. };
  1416. public:
  1417. CGSaxxyAwardedBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1418. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1419. {
  1420. GCSDK::CProtoBufMsg< CMsgSaxxyAwarded > msg( pNetPacket );
  1421. CEconNotification *pNotification = new CSaxxyAwardedNotification();
  1422. pNotification->SetText( "#TF_Event_Saxxy_Awarded" );
  1423. pNotification->SetLifetime( 30.0f );
  1424. {
  1425. // Winners
  1426. CFmtStr1024 strWinners;
  1427. for ( int i = 0; i < msg.Body().winner_names_size(); ++i )
  1428. {
  1429. strWinners.Append( msg.Body().winner_names( i ).c_str() );
  1430. if ( i + 1 < msg.Body().winner_names_size() )
  1431. {
  1432. strWinners.Append( "\n" );
  1433. }
  1434. }
  1435. wchar_t wszPlayerNames[ 1024 ];
  1436. g_pVGuiLocalize->ConvertANSIToUnicode( strWinners.Access(), wszPlayerNames, sizeof( wszPlayerNames ) );
  1437. pNotification->AddStringToken( "winners", wszPlayerNames );
  1438. // year
  1439. CRTime cTime;
  1440. cTime.SetToCurrentTime();
  1441. cTime.SetToGMT( false );
  1442. locchar_t wszYear[10];
  1443. loc_sprintf_safe( wszYear, LOCCHAR( "%04u" ), cTime.GetYear() );
  1444. pNotification->AddStringToken( "year", wszYear );
  1445. // What category was the Saxxy for?
  1446. char szCategory[MAX_ATTRIBUTE_DESCRIPTION_LENGTH];
  1447. Q_snprintf( szCategory, sizeof( szCategory ), "Replay_Contest_Category%d", msg.Body().category() );
  1448. pNotification->AddStringToken( "category", g_pVGuiLocalize->Find( szCategory ) );
  1449. }
  1450. NotificationQueue_Add( pNotification );
  1451. return true;
  1452. }
  1453. };
  1454. GC_REG_JOB( GCSDK::CGCClient, CGSaxxyAwardedBroadcast, "CGSaxxyAwardedBroadcast", k_EMsgGCSaxxy_Awarded, GCSDK::k_EServerTypeGCClient );
  1455. //-----------------------------------------------------------------------------
  1456. // Purpose: GC Msg handler to receive a generic system broadcast message
  1457. //-----------------------------------------------------------------------------
  1458. class CGCSystemMessageBroadcast : public GCSDK::CGCClientJob
  1459. {
  1460. public:
  1461. CGCSystemMessageBroadcast( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1462. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1463. {
  1464. CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat );
  1465. if ( !pHUDChat )
  1466. return false;
  1467. GCSDK::CProtoBufMsg<CMsgSystemBroadcast> msg( pNetPacket );
  1468. // retrieve the text
  1469. const char *pchMessage = msg.Body().message().c_str();
  1470. wchar_t *pwMessage = g_pVGuiLocalize->Find( pchMessage );
  1471. wchar_t wszConvertedText[2048] = L"";
  1472. if ( pwMessage == NULL )
  1473. {
  1474. g_pVGuiLocalize->ConvertANSIToUnicode( pchMessage, wszConvertedText, sizeof( wszConvertedText ) );
  1475. pwMessage = wszConvertedText;
  1476. }
  1477. Color color( 0xff, 0xcc, 0x33, 255 );
  1478. KeyValuesAD keyValues( "System Message" );
  1479. keyValues->SetWString( "message", pwMessage );
  1480. keyValues->SetColor( "custom_color", color );
  1481. // print to chat log
  1482. wchar_t wszLocalizedString[2048] = L"";
  1483. g_pVGuiLocalize->ConstructString_safe( wszLocalizedString, "#Notification_System_Message", keyValues );
  1484. pHUDChat->SetCustomColor( color );
  1485. pHUDChat->Printf( CHAT_FILTER_NONE, "%ls", wszLocalizedString );
  1486. // send to notification
  1487. CEconNotification* pNotification = new CEconNotification();
  1488. pNotification->SetText( "#Notification_System_Message" );
  1489. pNotification->SetKeyValues( keyValues );
  1490. pNotification->SetLifetime( 30.0f );
  1491. pNotification->SetSoundFilename( "ui/system_message_alert.wav" );
  1492. NotificationQueue_Add( pNotification );
  1493. return true;
  1494. }
  1495. };
  1496. GC_REG_JOB( GCSDK::CGCClient, CGCSystemMessageBroadcast, "CGCSystemMessageBroadcast", k_EMsgGCSystemMessage, GCSDK::k_EServerTypeGCClient );