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.

2233 lines
78 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. #include "cbase.h"
  8. // for messaging with the GC
  9. #include "econ_gcmessages.h"
  10. #include "econ_item_inventory.h"
  11. #include "tf_gcmessages.h"
  12. #include "tf_duel_summary.h"
  13. #include "gc_clientsystem.h"
  14. // other
  15. #include "c_playerresource.h"
  16. #include "c_tf_player.h"
  17. #include "tf_item_wearable.h"
  18. #include "econ_notifications.h"
  19. #include "tf_hud_chat.h"
  20. #include "c_tf_gamestats.h"
  21. #include "tf_gamerules.h"
  22. #include "tf_item_tools.h"
  23. #include "c_tf_freeaccount.h"
  24. #include "tf_item_powerup_bottle.h"
  25. #include "tf_weapon_grapplinghook.h"
  26. // for UI
  27. #include "clientmode_tf.h"
  28. #include "confirm_dialog.h"
  29. #include "select_player_dialog.h"
  30. #include "econ_notifications.h"
  31. #include "vgui/ISurface.h"
  32. #include "vgui/character_info_panel.h"
  33. #include "tf_hud_mainmenuoverride.h"
  34. #include "econ_ui.h"
  35. #include "backpack_panel.h"
  36. #include "store/v1/tf_store_page.h"
  37. #include "econ_item_description.h"
  38. #include "weapon_selection.h"
  39. // memdbgon must be the last include file in a .cpp file!!!
  40. #include <tier0/memdbgon.h>
  41. //-----------------------------------------------------------------------------
  42. // Wrapped Gift Declarations
  43. void UseGift( CEconItemView* pItem, CSteamID targetID );
  44. extern void PerformToolAction_UnwrapGift( vgui::Panel* pParent, CEconItemView *pGiftItem );
  45. extern void ShowWaitingDialog( CGenericWaitingDialog *pWaitingDialog, const char* pUpdateText, bool bAnimate, bool bShowCancel, float flMaxDuration );
  46. class CDeliverGiftSelectDialog;
  47. CDeliverGiftSelectDialog *OpenDeliverGiftDialog( vgui::Panel *pParent, CEconItemView *pItem );
  48. //-----------------------------------------------------------------------------
  49. //-----------------------------------------------------------------------------
  50. // Duel Declarations
  51. bool DuelMiniGame_IsDuelingLocalPlayer( C_TFPlayer *pPlayer );
  52. bool DuelMiniGame_IsDueling();
  53. //-----------------------------------------------------------------------------
  54. class CWaitForPackageDialog : public CGenericWaitingDialog
  55. {
  56. public:
  57. CWaitForPackageDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent )
  58. {
  59. }
  60. protected:
  61. virtual void OnTimeout()
  62. {
  63. // Play an exciting sound!
  64. vgui::surface()->PlaySound( "misc/achievement_earned.wav" );
  65. // Show them their loot!
  66. InventoryManager()->ShowItemsPickedUp( true );
  67. }
  68. };
  69. bool IgnoreRequestFromUser( const CSteamID &steamID )
  70. {
  71. // ignore blocked players
  72. if ( steamapicontext && steamapicontext->SteamFriends() )
  73. {
  74. EFriendRelationship eRelationship = steamapicontext->SteamFriends()->GetFriendRelationship( steamID );
  75. switch ( eRelationship )
  76. {
  77. case k_EFriendRelationshipBlocked:
  78. {
  79. return true;
  80. }
  81. }
  82. }
  83. return false;
  84. }
  85. static void ShowSelectDuelTargetDialog( uint64 iItemID );
  86. enum EServerPlayersGCSend
  87. {
  88. kServerPlayers_DontSend,
  89. kServerPlayers_Send,
  90. };
  91. struct CUseItemConfirmContext
  92. {
  93. public:
  94. CUseItemConfirmContext( CEconItemView *pEconItemView, EServerPlayersGCSend eSendServerPlayers, const char* pszConfirmUseSound = NULL )
  95. : m_pEconItemView( pEconItemView )
  96. , m_bSendServerPlayers( eSendServerPlayers == kServerPlayers_Send )
  97. , m_pszConfirmUseSound( pszConfirmUseSound )
  98. {
  99. Assert( eSendServerPlayers == kServerPlayers_DontSend || eSendServerPlayers == kServerPlayers_Send );
  100. }
  101. void OnConfirmUse()
  102. {
  103. if ( m_pszConfirmUseSound && *m_pszConfirmUseSound )
  104. {
  105. vgui::surface()->PlaySound( m_pszConfirmUseSound );
  106. }
  107. }
  108. const char* m_pszConfirmUseSound;
  109. CEconItemView *m_pEconItemView;
  110. bool m_bSendServerPlayers;
  111. };
  112. static void UseItemConfirm( bool bConfirmed, void *pContext )
  113. {
  114. static CSchemaAttributeDefHandle pAttrDef_UnlimitedUse( "unlimited quantity" );
  115. CUseItemConfirmContext *pConfirmContext = (CUseItemConfirmContext *)pContext;
  116. CEconItemView *pEconItemView = pConfirmContext->m_pEconItemView;
  117. if ( bConfirmed )
  118. {
  119. pConfirmContext->OnConfirmUse();
  120. if ( pEconItemView && !pEconItemView->FindAttribute( pAttrDef_UnlimitedUse ) )
  121. {
  122. GCSDK::CProtoBufMsg<CMsgUseItem> msg( k_EMsgGCUseItemRequest );
  123. msg.Body().set_item_id( pConfirmContext->m_pEconItemView->GetItemID() );
  124. if ( pConfirmContext->m_bSendServerPlayers )
  125. {
  126. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  127. {
  128. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  129. if ( pPlayer == NULL )
  130. continue;
  131. CSteamID steamIDPlayer;
  132. if ( !pPlayer->GetSteamID( &steamIDPlayer ) )
  133. continue;
  134. msg.Body().add_gift__potential_targets( steamIDPlayer.GetAccountID() );
  135. }
  136. }
  137. GCClientSystem()->BSendMessage( msg );
  138. }
  139. EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_CONSUMABLE, pEconItemView, NULL );
  140. // CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat );
  141. // if ( pHUDChat )
  142. // {
  143. // char szAnsi[1024];
  144. // Q_snprintf( szAnsi, 1024, "Using item: %ull", pItem->GetItemID() );
  145. // pHUDChat->Printf( CHAT_FILTER_NONE, "%s", szAnsi );
  146. // }
  147. }
  148. delete pConfirmContext;
  149. }
  150. static void OpenPass( bool bConfirmed, void *pContext )
  151. {
  152. if ( bConfirmed )
  153. {
  154. vgui::surface()->PlaySound( "ui/item_gift_wrap_unwrap.wav" );
  155. ShowWaitingDialog( new CWaitForPackageDialog( NULL ), "#ToolRedeemingPass", true, false, 5.0f );
  156. }
  157. UseItemConfirm( bConfirmed, pContext );
  158. }
  159. static void PrintTextToChat( const char *pText, KeyValues *pKeyValues )
  160. {
  161. GetClientModeTFNormal()->PrintTextToChat( pText, pKeyValues );
  162. }
  163. void GetPlayerNameBySteamID( const CSteamID &steamID, OUT_Z_CAP(maxLenInChars) char *pDestBuffer, int maxLenInChars )
  164. {
  165. // always attempt to precache this user's name -- we may need it later after they leave the server, for instance,
  166. // even if that's where they are now
  167. InventoryManager()->PersonaName_Precache( steamID.GetAccountID() );
  168. // first, look through players on our connected gameserver if available -- we already have this information and
  169. // if there's a disagreement between Steam and the gameserver the gameserver view is what the player is probably
  170. // expecting anyway
  171. if ( engine->IsInGame() )
  172. {
  173. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  174. {
  175. if ( g_PR->IsConnected( iPlayerIndex ) )
  176. {
  177. player_info_t pi;
  178. if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
  179. continue;
  180. if ( !pi.friendsID )
  181. continue;
  182. CSteamID steamIDTemp( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
  183. if ( steamIDTemp == steamID )
  184. {
  185. V_strncpy( pDestBuffer, pi.name, maxLenInChars );
  186. return;
  187. }
  188. }
  189. }
  190. }
  191. // try the persona name cache
  192. // this goes to steam if necessary
  193. const char *pszName = InventoryManager()->PersonaName_Get( steamID.GetAccountID() );
  194. if ( pszName != NULL )
  195. {
  196. V_strncpy( pDestBuffer, pszName, maxLenInChars );
  197. return;
  198. }
  199. // otherwise, return what we would normally return
  200. V_strncpy( pDestBuffer, steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ), maxLenInChars );
  201. }
  202. static bool IsGCUseableItem( const GameItemDefinition_t *pItemDef )
  203. {
  204. Assert( pItemDef );
  205. return (pItemDef->GetCapabilities() & ITEM_CAP_USABLE_GC) != 0;
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Purpose: Implementation of the local response for someone who used a dueling minigame
  209. //-----------------------------------------------------------------------------
  210. void CEconTool_DuelingMinigame::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  211. {
  212. Assert( pItem );
  213. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  214. Assert( pLocalPlayer );
  215. if ( DuelMiniGame_IsDueling() )
  216. {
  217. // can't duel if already dueling
  218. ShowMessageBox( "#TF_Duel_Title", "#TF_Duel_InADuel_Initiator", "#GameUI_OK" );
  219. return;
  220. }
  221. if ( pLocalPlayer->GetTeamNumber() != TF_TEAM_RED && pLocalPlayer->GetTeamNumber() != TF_TEAM_BLUE )
  222. {
  223. // can't duel from spectator mode, etc.
  224. ShowMessageBox( "#TF_UseFail_NotOnTeam_Title", "#TF_UseFail_NotOnTeam", "#GameUI_OK" );
  225. return;
  226. }
  227. if ( TFGameRules() && !TFGameRules()->CanInitiateDuels() )
  228. {
  229. ShowMessageBox( "#TF_Duel_Title", "#TF_Duel_CannotUse", "#GameUI_OK" );
  230. return;
  231. }
  232. ShowSelectDuelTargetDialog( pItem->GetItemID() );
  233. }
  234. //-----------------------------------------------------------------------------
  235. // Purpose: Implementation of the local response for someone who used a noisemaker
  236. //-----------------------------------------------------------------------------
  237. void CEconTool_Noisemaker::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  238. {
  239. Assert( pItem );
  240. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  241. Assert( pLocalPlayer );
  242. if ( gpGlobals->curtime < pLocalPlayer->m_Shared.GetNextNoiseMakerTime() )
  243. return;
  244. if ( !pLocalPlayer->IsAlive() )
  245. return;
  246. if ( pLocalPlayer->GetTeamNumber() < FIRST_GAME_TEAM )
  247. return;
  248. // This may not be ideal. We're going to have the game server do the noise effect,
  249. // without checking the GC to see whether we have charges available. Querying the
  250. // GC would cause a significant delay before the item was used, so we simulate.
  251. // Tell the game server to play the sound.
  252. KeyValues *kv = new KeyValues( "use_action_slot_item_server" );
  253. engine->ServerCmdKeyValues( kv );
  254. // Tell the GC to consume a charge.
  255. CUseItemConfirmContext *context = new CUseItemConfirmContext( pItem, kServerPlayers_DontSend );
  256. UseItemConfirm( true, context );
  257. // Notify the player that they used their last charge.
  258. if ( pItem->GetItemQuantity() <= 1 )
  259. {
  260. CEconNotification *pNotification = new CEconNotification();
  261. pNotification->SetText( "#TF_NoiseMaker_Exhausted" );
  262. pNotification->SetLifetime( 7.0f );
  263. NotificationQueue_Add( pNotification );
  264. }
  265. }
  266. //-----------------------------------------------------------------------------
  267. // Purpose: Implementation of the local response for someone who used a gift-wrapped item
  268. //-----------------------------------------------------------------------------
  269. void UseUntargetedGiftConfirm( bool bConfirmed, void *pContext )
  270. {
  271. if ( bConfirmed )
  272. {
  273. UseGift( static_cast<CEconItemView *>( pContext ), k_steamIDNil );
  274. }
  275. }
  276. void CEconTool_WrappedGift::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  277. {
  278. Assert( pItem );
  279. if ( BIsDirectGift() )
  280. {
  281. OpenDeliverGiftDialog( pParent, pItem );
  282. }
  283. else
  284. {
  285. // ...otherwise, we should try and open the gift!
  286. PerformToolAction_UnwrapGift( pParent, pItem );
  287. }
  288. }
  289. //-----------------------------------------------------------------------------
  290. // Purpose:
  291. //-----------------------------------------------------------------------------
  292. void CEconTool_WeddingRing::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  293. {
  294. Assert( pItem );
  295. // Don't do anything if we haven't been gifted already -- we don't expect to
  296. // ever get in here, really.
  297. static CSchemaAttributeDefHandle pAttrDef_GifterAccountID( "gifter account id" );
  298. uint32 unAccountID;
  299. if ( !pItem->FindAttribute( pAttrDef_GifterAccountID, &unAccountID ) )
  300. return;
  301. // We have been gifted, so pop up a dialog box to allow the user to accept/reject
  302. // the proposal.
  303. CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UseWeddingRing_Title", "#TF_UseWeddingRing_Text",
  304. "#TF_WeddingRing_AcceptProposal", "#TF_WeddingRing_RejectProposal",
  305. &UseItemConfirm );
  306. pDialog->AddStringToken( "item_name", pItem->GetItemName() );
  307. CUtlConstWideString sProposerPersonaName;
  308. GLocalizationProvider()->ConvertUTF8ToLocchar( TFInventoryManager()->PersonaName_Get( unAccountID ), &sProposerPersonaName );
  309. pDialog->AddStringToken( "proposer_name", sProposerPersonaName.Get() );
  310. pDialog->SetContext( new CUseItemConfirmContext( pItem, kServerPlayers_DontSend ) );
  311. }
  312. //-----------------------------------------------------------------------------
  313. // Purpose: Implementation of the local response for someone who used a backpack expander
  314. //-----------------------------------------------------------------------------
  315. void CEconTool_BackpackExpander::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  316. {
  317. Assert( pItem );
  318. // first validate that they aren't already at max inventory size and can use the item
  319. uint32 unExtraSlots =GetBackpackSlots();
  320. if ( unExtraSlots == 0 )
  321. {
  322. return;
  323. }
  324. uint32 unNewNumSlots = TFInventoryManager()->GetLocalTFInventory()->GetMaxItemCount() + unExtraSlots;
  325. if ( unNewNumSlots > MAX_NUM_BACKPACK_SLOTS )
  326. {
  327. ShowMessageBox( "#TF_UseBackpackExpanderFail_Title", "#TF_UseBackpackExpanderFail_Text", "#GameUI_OK" );
  328. return;
  329. }
  330. // Free Trials can use expanders but max out at a smaller value since premium gains a bunch of free slots
  331. if ( IsFreeTrialAccount() && unNewNumSlots > MAX_NUM_BACKPACK_SLOTS - (DEFAULT_NUM_BACKPACK_SLOTS - DEFAULT_NUM_BACKPACK_SLOTS_FREE_TRIAL_ACCOUNT) )
  332. {
  333. ShowMessageBox( "#TF_UseBackpackExpanderFail_Title", "#TF_UseBackpackExpanderFail_Text", "#GameUI_OK" );
  334. return;
  335. }
  336. CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UseBackpackExpander_Title", "#TF_UseBackpackExpander_Text",
  337. "#GameUI_OK", "#Cancel",
  338. &UseItemConfirm );
  339. pDialog->AddStringToken( "item_name", pItem->GetItemName() );
  340. wchar_t wszUsesLeft[32];
  341. _snwprintf( wszUsesLeft, ARRAYSIZE(wszUsesLeft), L"%d", pItem->GetItemQuantity() );
  342. pDialog->AddStringToken( "uses_left", wszUsesLeft );
  343. wchar_t wszNewSize[32];
  344. _snwprintf( wszNewSize, ARRAYSIZE(wszNewSize), L"%d", unNewNumSlots );
  345. pDialog->AddStringToken( "new_size", wszNewSize );
  346. pDialog->SetContext( new CUseItemConfirmContext( pItem, kServerPlayers_DontSend ) );
  347. }
  348. //-----------------------------------------------------------------------------
  349. // Purpose: Implementation of the local response for someone who used an account upgrade
  350. //-----------------------------------------------------------------------------
  351. void CEconTool_AccountUpgradeToPremium::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  352. {
  353. Assert( pItem );
  354. // if the account is already premium, abort here
  355. if ( !IsFreeTrialAccount() )
  356. {
  357. ShowMessageBox( "#TF_UseAccountUpgradeToPremiumFail_Title", "#TF_UseAccountUpgradeToPremiumFail_Text", "#GameUI_OK" );
  358. return;
  359. }
  360. // show a confirmation dialog to make sure they want to consume the charge
  361. CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UseAccountUpgradeToPremium_Title", "#TF_UseAccountUpgradeToPremium_Text",
  362. "#GameUI_OK", "#Cancel",
  363. &UseItemConfirm );
  364. pDialog->AddStringToken( "item_name", pItem->GetItemName() );
  365. wchar_t wszUsesLeft[32];
  366. _snwprintf( wszUsesLeft, ARRAYSIZE(wszUsesLeft), L"%d", pItem->GetItemQuantity() );
  367. pDialog->AddStringToken( "uses_left", wszUsesLeft );
  368. pDialog->SetContext( new CUseItemConfirmContext( pItem, kServerPlayers_DontSend ) );
  369. }
  370. //-----------------------------------------------------------------------------
  371. // Purpose: Implementation of the local response for someone who used a claim code item
  372. //-----------------------------------------------------------------------------
  373. void CEconTool_ClaimCode::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  374. {
  375. Assert( pItem );
  376. CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UseClaimCode_Title", "#TF_UseClaimCode_Text",
  377. "#GameUI_OK", "#Cancel",
  378. &UseItemConfirm );
  379. pDialog->AddStringToken( "item_name", pItem->GetItemName() );
  380. const char *pszClaimValue = GetClaimType();
  381. if ( pszClaimValue )
  382. {
  383. wchar_t wszClaimType[128];
  384. KeyValuesAD pkvDummy( "dummy" );
  385. g_pVGuiLocalize->ConstructString_safe( wszClaimType, pszClaimValue, pkvDummy );
  386. pDialog->AddStringToken( "claim_type", wszClaimType );
  387. }
  388. pDialog->SetContext( new CUseItemConfirmContext( pItem, kServerPlayers_DontSend ) );
  389. }
  390. //-----------------------------------------------------------------------------
  391. // Purpose: Implementation of the local response for someone who used an item that doesn't have
  392. // special-case handling (ie., paint); called into from other code
  393. //-----------------------------------------------------------------------------
  394. static bool s_bConsumableToolOpeningGift = false;
  395. static void ClientConsumableTool_Generic( CEconItemView *pItem, vgui::Panel *pParent )
  396. {
  397. Assert( pItem );
  398. Assert( pItem->GetItemDefinition() );
  399. Assert( pItem->GetItemDefinition()->GetEconTool() );
  400. CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UseItem_Title", "#TF_UseItem_Text",
  401. "#GameUI_OK", "#Cancel",
  402. &UseItemConfirm );
  403. pDialog->AddStringToken( "item_name", pItem->GetItemName() );
  404. wchar_t wszUsesLeft[32];
  405. _snwprintf( wszUsesLeft, ARRAYSIZE(wszUsesLeft), L"%d", pItem->GetItemQuantity() );
  406. pDialog->AddStringToken( "uses_left", wszUsesLeft );
  407. pDialog->SetContext(
  408. new CUseItemConfirmContext( pItem,
  409. pItem->GetItemDefinition()->GetTypedEconTool<CEconTool_Gift>()
  410. ? kServerPlayers_Send
  411. : kServerPlayers_DontSend ) );
  412. // Minor Hack to get sound to play differently. Add a look up table
  413. s_bConsumableToolOpeningGift = false;
  414. const CEconTool_Gift *pGiftTool = pItem->GetItemDefinition()->GetTypedEconTool<CEconTool_Gift>();
  415. if ( pGiftTool && pGiftTool->GetTargetRule() == kGiftTargetRule_OnlySelf)
  416. {
  417. s_bConsumableToolOpeningGift = true;
  418. }
  419. }
  420. //-----------------------------------------------------------------------------
  421. // Purpose: Generic Response
  422. //-----------------------------------------------------------------------------
  423. void CEconTool_Xifier::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  424. {
  425. ClientConsumableTool_Generic( pItem, pParent );
  426. }
  427. //-----------------------------------------------------------------------------
  428. void CEconTool_ItemEaterRecharger::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  429. {
  430. // Tell the GC to consume a charge.
  431. CUseItemConfirmContext *context = new CUseItemConfirmContext( pItem, kServerPlayers_DontSend );
  432. UseItemConfirm( true, context );
  433. }
  434. //-----------------------------------------------------------------------------
  435. // Purpose: Implementation of the local response for someone who used (tried to redeem) a collection
  436. //-----------------------------------------------------------------------------
  437. void CEconTool_PaintCan::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  438. {
  439. ClientConsumableTool_Generic( pItem, pParent );
  440. }
  441. //-----------------------------------------------------------------------------
  442. // Purpose:
  443. //-----------------------------------------------------------------------------
  444. void CEconTool_Gift::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  445. {
  446. ClientConsumableTool_Generic( pItem, pParent );
  447. }
  448. //-----------------------------------------------------------------------------
  449. // Purpose: Implementation of the local response for someone who used (tried to redeem) a collection
  450. //-----------------------------------------------------------------------------
  451. void CEconTool_Default::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  452. {
  453. ClientConsumableTool_Generic( pItem, pParent );
  454. }
  455. //-----------------------------------------------------------------------------
  456. // Purpose:
  457. //-----------------------------------------------------------------------------
  458. void CEconTool_TFEventEnableHalloween::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  459. {
  460. Assert( pItem );
  461. // Tell the GC we want to use this item.
  462. GCSDK::CProtoBufMsg<CMsgGC_Client_UseServerModificationItem> msg( k_EMsgGC_Client_UseServerModificationItem );
  463. msg.Body().set_item_id( pItem->GetItemID() );
  464. GCClientSystem()->BSendMessage( msg );
  465. }
  466. //-----------------------------------------------------------------------------
  467. void CEconTool_DuckToken::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  468. {
  469. ClientConsumableTool_Generic( pItem, pParent );
  470. }
  471. //-----------------------------------------------------------------------------
  472. void CEconTool_GrantOperationPass::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const
  473. {
  474. Assert( pItem );
  475. const CEconTool_GrantOperationPass *pEconToolOperationPass = pItem->GetItemDefinition()->GetTypedEconTool<CEconTool_GrantOperationPass>();
  476. if ( !pEconToolOperationPass )
  477. {
  478. ShowMessageBox( "#TF_UseOperationPassFail_Title", "#TF_UseOperationPassFail_Text", "#GameUI_OK" );
  479. return;
  480. }
  481. // Check that the player doesn't already have an active pass
  482. const char *szPassName = pEconToolOperationPass->m_pOperationPassName;
  483. CEconItemDefinition *pActivePassItemDef = GetItemSchema()->GetItemDefinitionByName( szPassName );
  484. CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
  485. if ( !pLocalInv || !pActivePassItemDef )
  486. {
  487. ShowMessageBox( "#TF_UseOperationPassFail_Title", "#TF_UseOperationPassFail_Text", "#GameUI_OK" );
  488. return;
  489. }
  490. for ( int i = 0; i < pLocalInv->GetItemCount(); ++i )
  491. {
  492. CEconItemView *pItemLocal = pLocalInv->GetItem( i );
  493. Assert( pItemLocal );
  494. if ( pItemLocal->GetItemDefinition() == pActivePassItemDef )
  495. {
  496. ShowMessageBox( "#TF_UseOperationPassAlreadyActive_Title", "#TF_UseOperationPassAlreadyActive_Text", "#GameUI_OK" );
  497. return;
  498. }
  499. }
  500. vgui::surface()->PlaySound( "ui/quest_operation_pass_buy.wav" );
  501. const char *pszTitle = "#TF_UseOperationPass_Title";
  502. const char *pszBody = "#TF_UseOperationPass_Text";
  503. static CSchemaItemDefHandle pItemDef_InvasionPass( "Unused Invasion Pass" );
  504. if ( pItem->GetItemDefinition() == pItemDef_InvasionPass )
  505. {
  506. pszBody = "#TF_UseInvasionPass_Text";
  507. }
  508. // show a confirmation dialog to make sure they want to consume the charge
  509. CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( pszTitle, pszBody,
  510. "#GameUI_OK", "#Cancel",
  511. &OpenPass );
  512. pDialog->AddStringToken( "item_name", pItem->GetItemName() );
  513. wchar_t wszUsesLeft[32];
  514. _snwprintf( wszUsesLeft, ARRAYSIZE( wszUsesLeft ), L"%d", pItem->GetItemQuantity() );
  515. pDialog->AddStringToken( "uses_left", wszUsesLeft );
  516. pDialog->SetContext( new CUseItemConfirmContext( pItem, kServerPlayers_DontSend, "ui/quest_operation_pass_use.wav" ) );
  517. }
  518. //-----------------------------------------------------------------------------
  519. // Purpose:
  520. //-----------------------------------------------------------------------------
  521. class CGCEventEnableResponse : public GCSDK::CGCClientJob
  522. {
  523. public:
  524. CGCEventEnableResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  525. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  526. {
  527. GCSDK::CProtoBufMsg<CMsgGC_Client_UseServerModificationItem_Response> msg( pNetPacket );
  528. switch ( msg.Body().response_code() )
  529. {
  530. case CMsgGC_Client_UseServerModificationItem_Response::kServerModificationItemResponse_AlreadyInUse:
  531. ShowMessageBox( "#TF_ServerEnchantmentType", "#TF_Eternaween__AlreadyInUse", (KeyValues *)NULL );
  532. break;
  533. case CMsgGC_Client_UseServerModificationItem_Response::kServerModificationItemResponse_NotOnAuthenticatedServer:
  534. ShowMessageBox( "#TF_ServerEnchantmentType", "#TF_Eternaween__AuthenticatedServerRequired", (KeyValues *)NULL );
  535. break;
  536. case CMsgGC_Client_UseServerModificationItem_Response::kServerModificationItemResponse_ServerReject:
  537. ShowMessageBox( "#TF_ServerEnchantmentType", "#TF_Eternaween__ServerReject", (KeyValues *)NULL );
  538. break;
  539. case CMsgGC_Client_UseServerModificationItem_Response::kServerModificationItemResponse_InternalError:
  540. ShowMessageBox( "#TF_ServerEnchantmentType", "#TF_Eternaween__InternalError", (KeyValues *)NULL );
  541. break;
  542. case CMsgGC_Client_UseServerModificationItem_Response::kServerModificationItemResponse_EventAlreadyActive:
  543. ShowMessageBox( "#TF_ServerEnchantmentType", "#TF_Eternaween__EventAlreadyActive", (KeyValues *)NULL );
  544. break;
  545. }
  546. return true;
  547. }
  548. };
  549. GC_REG_JOB( GCSDK::CGCClient, CGCEventEnableResponse, "CGCEventEnableResponse", k_EMsgGC_Client_UseServerModificationItem_Response, GCSDK::k_EServerTypeGCClient );
  550. // invoked when the local player attempts to consume the given item
  551. void UseConsumableItem( CEconItemView *pItem, vgui::Panel *pParent )
  552. {
  553. Assert( pItem );
  554. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  555. const GameItemDefinition_t *pItemDef = pItem->GetStaticData();
  556. Assert( pItemDef );
  557. bool bUsableOutOfGame = (pItemDef->GetCapabilities() & ITEM_CAP_USABLE_OUT_OF_GAME) != 0;
  558. // if we aren't useable outside of the game then make sure that we're in a game and that
  559. // we have a local player we can use
  560. if ( !bUsableOutOfGame )
  561. {
  562. if ( !engine->IsInGame() )
  563. {
  564. ShowMessageBox( "#TF_UseFail_NotInGame_Title", "#TF_UseFail_NotInGame", "#GameUI_OK" );
  565. return;
  566. }
  567. if ( pLocalPlayer == NULL )
  568. return;
  569. }
  570. // make sure this item meets our baseline useable criteria
  571. if ( pItem->GetItemQuantity() <= 0 )
  572. return;
  573. if ( !IsGCUseableItem( pItemDef ) )
  574. return;
  575. const IEconTool *pEconTool = pItemDef->GetEconTool();
  576. if ( !pEconTool )
  577. return;
  578. // do whatever client work needs to be done, send a request to the GC to use the item, etc.
  579. pEconTool->OnClientUseConsumable( pItem, pParent );
  580. }
  581. // Called from the trade dialog when the player selects a target user ID.
  582. void UseGift( CEconItemView* pItem, CSteamID targetID )
  583. {
  584. // Validate pItem...
  585. if ( !pItem )
  586. return;
  587. const GameItemDefinition_t *pItemDef = pItem->GetItemDefinition();
  588. if ( !pItemDef )
  589. return;
  590. if ( !IsGCUseableItem( pItemDef ) )
  591. return;
  592. if ( !pItemDef->GetTypedEconTool<CEconTool_WrappedGift>() )
  593. return;
  594. GCSDK::CGCMsg< MsgGCDeliverGift_t > msg( k_EMsgGCDeliverGift );
  595. msg.Body().m_unGiftID = pItem->GetItemID();
  596. msg.Body().m_ulTargetSteamID = targetID.ConvertToUint64();
  597. GCClientSystem()->BSendMessage( msg );
  598. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  599. C_CTF_GameStats.Event_Trading( IE_TRADING_ITEM_GIFTED, pItem, true,
  600. steamID.ConvertToUint64(), targetID.ConvertToUint64() );
  601. }
  602. class CDeliverGiftSelectDialog : public CSelectPlayerDialog
  603. {
  604. public:
  605. CDeliverGiftSelectDialog( vgui::Panel *parent );
  606. virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
  607. void SetItem( CEconItemView* pItem ) { m_pItem = pItem; }
  608. virtual bool AllowOutOfGameFriends() { return true; }
  609. virtual void OnSelectPlayer( const CSteamID &steamID )
  610. {
  611. UseGift( m_pItem, steamID );
  612. }
  613. private:
  614. CEconItemView* m_pItem;
  615. };
  616. CDeliverGiftSelectDialog::CDeliverGiftSelectDialog( vgui::Panel *parent )
  617. : CSelectPlayerDialog( parent )
  618. {
  619. }
  620. void CDeliverGiftSelectDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
  621. {
  622. CSelectPlayerDialog::ApplySchemeSettings( pScheme );
  623. SetDialogVariable( "title", g_pVGuiLocalize->Find( "TF_DeliverGiftDialog_Title" ) );
  624. }
  625. static vgui::DHANDLE<CDeliverGiftSelectDialog> g_hDeliverGiftDialog;
  626. CDeliverGiftSelectDialog *OpenDeliverGiftDialog( vgui::Panel *pParent, CEconItemView *pItem )
  627. {
  628. if (!g_hDeliverGiftDialog.Get())
  629. {
  630. g_hDeliverGiftDialog = vgui::SETUP_PANEL( new CDeliverGiftSelectDialog( pParent ) );
  631. }
  632. g_hDeliverGiftDialog->InvalidateLayout( false, true );
  633. g_hDeliverGiftDialog->Reset();
  634. g_hDeliverGiftDialog->SetVisible( true );
  635. g_hDeliverGiftDialog->MakePopup();
  636. g_hDeliverGiftDialog->MoveToFront();
  637. g_hDeliverGiftDialog->SetKeyBoardInputEnabled(true);
  638. g_hDeliverGiftDialog->SetMouseInputEnabled(true);
  639. g_hDeliverGiftDialog->SetItem( pItem );
  640. TFModalStack()->PushModal( g_hDeliverGiftDialog );
  641. return g_hDeliverGiftDialog;
  642. }
  643. // This is the command the user will execute.
  644. // We want this to happen on the client, before forwarding to the game server, since we don't trust
  645. // the game server.
  646. static bool g_bUsedGCItem = false;
  647. static void StartUseActionSlotItem( const CCommand &args )
  648. {
  649. if ( !engine->IsInGame() )
  650. {
  651. return;
  652. }
  653. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  654. if ( pLocalPlayer == NULL )
  655. {
  656. return;
  657. }
  658. pLocalPlayer->SetUsingActionSlot( true );
  659. // Ghosts cant use action items!
  660. if ( pLocalPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
  661. {
  662. return;
  663. }
  664. // If we're in Mann Vs MAchine, and we're dead, we can use this to respawn instantly.
  665. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pLocalPlayer->IsObserver() )
  666. {
  667. float flNextRespawn = TFGameRules()->GetNextRespawnWave( pLocalPlayer->GetTeamNumber(), pLocalPlayer );
  668. if ( flNextRespawn )
  669. {
  670. int iRespawnWait = (flNextRespawn - gpGlobals->curtime);
  671. if ( iRespawnWait > 1.0 )
  672. {
  673. engine->ClientCmd_Unrestricted( "td_buyback\n" );
  674. return;
  675. }
  676. }
  677. }
  678. // trying to pick up a dropped weapon?
  679. if ( pLocalPlayer->GetDroppedWeaponInRange() != NULL )
  680. {
  681. KeyValues *kv = new KeyValues( "+use_action_slot_item_server" );
  682. engine->ServerCmdKeyValues( kv );
  683. return;
  684. }
  685. if ( TFGameRules() && TFGameRules()->IsUsingGrapplingHook() )
  686. {
  687. CTFGrapplingHook *pGrapplingHook = dynamic_cast< CTFGrapplingHook* >( pLocalPlayer->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
  688. if ( pGrapplingHook )
  689. {
  690. if ( pLocalPlayer->GetActiveTFWeapon() != pGrapplingHook )
  691. {
  692. pLocalPlayer->Weapon_Switch( pGrapplingHook );
  693. }
  694. KeyValues *kv = new KeyValues( "+use_action_slot_item_server" );
  695. engine->ServerCmdKeyValues( kv );
  696. return;
  697. }
  698. }
  699. // send a request to the GC to use the item
  700. g_bUsedGCItem = false;
  701. CEconItemView *pItem = CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( pLocalPlayer, LOADOUT_POSITION_ACTION );
  702. if( pItem )
  703. {
  704. const IEconTool *pEconTool = pItem->GetItemDefinition()->GetEconTool();
  705. bool bIsRecharger = ( pEconTool && FStrEq( pEconTool->GetTypeName(), "item_eater_recharger" ) );
  706. if ( IsGCUseableItem( pItem->GetItemDefinition() ) && pItem->GetItemQuantity() >= 1 && !bIsRecharger )
  707. {
  708. UseConsumableItem( pItem, NULL );
  709. g_bUsedGCItem = true;
  710. }
  711. }
  712. // otherwise, forward to game server
  713. if ( !g_bUsedGCItem )
  714. {
  715. KeyValues *kv = new KeyValues( "+use_action_slot_item_server" );
  716. engine->ServerCmdKeyValues( kv );
  717. }
  718. }
  719. static ConCommand start_use_action_slot_item( "+use_action_slot_item", StartUseActionSlotItem, "Use the item in the action slot." );
  720. static void EndUseActionSlotItem( const CCommand &args )
  721. {
  722. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  723. if ( !pLocalPlayer )
  724. return;
  725. pLocalPlayer->SetUsingActionSlot( false );
  726. if ( TFGameRules() && TFGameRules()->IsUsingGrapplingHook() && pLocalPlayer->GetActiveTFWeapon() )
  727. {
  728. // if we're using the hook, switch back to the last weapon
  729. if ( pLocalPlayer->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_GRAPPLINGHOOK )
  730. {
  731. KeyValues *kv = new KeyValues( "-use_action_slot_item_server" );
  732. engine->ServerCmdKeyValues( kv );
  733. C_BaseCombatWeapon* pLastWeapon = pLocalPlayer->GetLastWeapon();
  734. // switch away from the hook
  735. if ( pLastWeapon && pLocalPlayer->Weapon_CanSwitchTo( pLastWeapon ) )
  736. {
  737. pLocalPlayer->Weapon_Switch( pLastWeapon );
  738. }
  739. else
  740. {
  741. // in case we failed to switch back to last weapon for some reason, just find the next best
  742. pLocalPlayer->SwitchToNextBestWeapon( pLastWeapon );
  743. }
  744. return;
  745. }
  746. }
  747. // tell the game server we let go of the button if this wasn't a GC item
  748. if ( !g_bUsedGCItem )
  749. {
  750. KeyValues *kv = new KeyValues( "-use_action_slot_item_server" );
  751. engine->ServerCmdKeyValues( kv );
  752. }
  753. }
  754. static ConCommand end_use_action_slot_item( "-use_action_slot_item", EndUseActionSlotItem );
  755. static void StartContextAction( const CCommand &args )
  756. {
  757. // Assume we're going to taunt
  758. bool bDoTaunt = true;
  759. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  760. {
  761. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  762. if ( pLocalPlayer )
  763. {
  764. CTFPowerupBottle *pPowerupBottle = dynamic_cast< CTFPowerupBottle* >( pLocalPlayer->GetEquippedWearableForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
  765. if ( pPowerupBottle && pPowerupBottle->GetNumCharges() > 0 )
  766. {
  767. // They're in MvM and have a bottle with a charge, so do an action instead
  768. bDoTaunt = false;
  769. }
  770. if ( pLocalPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && pLocalPlayer->GetActiveTFWeapon() && pLocalPlayer->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_MINIGUN )
  771. {
  772. int iRage = 0;
  773. CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalPlayer, iRage, generate_rage_on_dmg );
  774. if ( iRage )
  775. {
  776. if ( pLocalPlayer->m_Shared.GetRageMeter() >= 100.f && !pLocalPlayer->m_Shared.IsRageDraining() )
  777. {
  778. // They have rage ready to go, do the taunt
  779. bDoTaunt = true;
  780. }
  781. }
  782. }
  783. }
  784. }
  785. if ( bDoTaunt )
  786. {
  787. // Taunt
  788. engine->ClientCmd_Unrestricted( "+taunt\n" );
  789. }
  790. else
  791. {
  792. // Action item
  793. StartUseActionSlotItem( args );
  794. }
  795. }
  796. static ConCommand start_context_action( "+context_action", StartContextAction, "Use the item in the action slot." );
  797. static void EndContextAction( const CCommand &args )
  798. {
  799. // Undo both to be on the safe side
  800. EndUseActionSlotItem( args );
  801. engine->ClientCmd_Unrestricted( "-taunt\n" );
  802. }
  803. static ConCommand end_context_action( "-context_action", EndContextAction );
  804. //-----------------------------------------------------------------------------
  805. class CTFGiftNotification : public CEconNotification
  806. {
  807. public:
  808. CTFGiftNotification( GCSDK::CProtoBufMsg<CMsgGCGiftedItems> &msg )
  809. : CEconNotification()
  810. {
  811. const EUniverse eUniverse = GetUniverse();
  812. Assert( msg.Body().recipient_account_ids_size() > 0 );
  813. SetLifetime( 30.0f );
  814. m_bRandomPerson = msg.Body().has_was_random_person()
  815. && msg.Body().was_random_person();
  816. const CSteamID gifterSteamID( msg.Body().gifter_steam_id(), eUniverse, k_EAccountTypeIndividual );
  817. SetSteamID( gifterSteamID );
  818. if ( m_bRandomPerson )
  819. {
  820. const CSteamID recipientSteamID( msg.Body().recipient_account_ids(0), eUniverse, k_EAccountTypeIndividual );
  821. if ( msg.Body().recipient_account_ids_size() > 0 )
  822. {
  823. // This might not really be a random gift but might instead be a gift they opened
  824. // themselves (ie., a shipment box).
  825. if ( recipientSteamID == gifterSteamID )
  826. {
  827. SetText( "#TF_GifterText_SelfOpen" );
  828. }
  829. else
  830. {
  831. SetText( "#TF_GifterText_Random" );
  832. char szRecipientName[ MAX_PLAYER_NAME_LENGTH ];
  833. GetPlayerNameBySteamID( recipientSteamID, szRecipientName, sizeof( szRecipientName ) );
  834. g_pVGuiLocalize->ConvertANSIToUnicode( szRecipientName, m_wszPlayerName, sizeof( m_wszPlayerName ) );
  835. AddStringToken( "recipient", m_wszPlayerName );
  836. m_vecSteamIDRecipients.AddToTail( recipientSteamID );
  837. }
  838. }
  839. }
  840. else
  841. {
  842. SetText( "#TF_GifterText_All" );
  843. for ( int i = 0; i < msg.Body().recipient_account_ids_size(); ++i )
  844. {
  845. const CSteamID recipientSteamID( msg.Body().recipient_account_ids(i), eUniverse, k_EAccountTypeIndividual );
  846. m_vecSteamIDRecipients.AddToTail( recipientSteamID );
  847. }
  848. }
  849. char szGifterName[ MAX_PLAYER_NAME_LENGTH ];
  850. GetPlayerNameBySteamID( m_steamID, szGifterName, sizeof( szGifterName ) );
  851. g_pVGuiLocalize->ConvertANSIToUnicode( szGifterName, m_wszPlayerName, sizeof( m_wszPlayerName ) );
  852. AddStringToken( "giver", m_wszPlayerName );
  853. PrintToChatLog();
  854. SetSoundFilename( "misc/happy_birthday.wav" );
  855. }
  856. void PrintToChatLog()
  857. {
  858. CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat );
  859. if ( pHUDChat )
  860. {
  861. wchar_t *pFormat = g_pVGuiLocalize->Find( "TF_GiftedItems" );
  862. if ( pFormat == NULL )
  863. {
  864. return;
  865. }
  866. FOR_EACH_VEC( m_vecSteamIDRecipients, i )
  867. {
  868. const CSteamID &steamIDRecipient = m_vecSteamIDRecipients[i];
  869. char szRecipientName[ MAX_PLAYER_NAME_LENGTH ];
  870. szRecipientName[0] = '\0';
  871. GetPlayerNameBySteamID( steamIDRecipient, szRecipientName, sizeof( szRecipientName ) );
  872. if ( szRecipientName[0] == '\0' )
  873. {
  874. continue;
  875. }
  876. wchar_t wszRecipientName[MAX_PLAYER_NAME_LENGTH];
  877. g_pVGuiLocalize->ConvertANSIToUnicode( szRecipientName, wszRecipientName, sizeof( wszRecipientName ) );
  878. wchar_t wszNotification[1024]=L"";
  879. g_pVGuiLocalize->ConstructString_safe( wszNotification,
  880. pFormat,
  881. 2, m_wszPlayerName, wszRecipientName );
  882. char szAnsi[1024];
  883. g_pVGuiLocalize->ConvertUnicodeToANSI( wszNotification, szAnsi, sizeof(szAnsi) );
  884. pHUDChat->Printf( CHAT_FILTER_NONE, "%s", szAnsi );
  885. }
  886. }
  887. }
  888. virtual EType NotificationType() { return eType_Basic; }
  889. wchar_t m_wszPlayerName[ MAX_PLAYER_NAME_LENGTH ];
  890. bool m_bRandomPerson;
  891. CUtlVector< CSteamID > m_vecSteamIDRecipients;
  892. };
  893. //#ifdef _DEBUG
  894. //CON_COMMAND( cl_gifts_test, "tests the gift ui." )
  895. //{
  896. // if ( !engine->IsInGame() )
  897. // {
  898. // return;
  899. // }
  900. //
  901. // C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  902. // if ( pLocalPlayer == NULL )
  903. // {
  904. // return;
  905. // }
  906. //
  907. // if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL )
  908. // {
  909. // return;
  910. // }
  911. //
  912. // CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  913. // GCSDK::CProtoBufMsg< CMsgGCGiftedItems > msg( k_EMsgGCGiftedItems );
  914. // msg.Body().m_ulGifterSteamID = steamID.ConvertToUint64();
  915. // msg.Body().m_bRandomPerson = ( args.ArgC() >= 2 );
  916. // msg.Body().m_unNumGiftRecipients = msg.Body().m_bRandomPerson ? 1 : 31;
  917. // for ( int i = 0; i < msg.Body().m_unNumGiftRecipients; ++i )
  918. // {
  919. // msg.AddUint64Data( steamID.ConvertToUint64() );
  920. // }
  921. // msg.ResetReadPtr();
  922. // NotificationQueue_Add( new CTFGiftNotification( msg ) );
  923. //}
  924. //#endif
  925. //-----------------------------------------------------------------------------
  926. // Purpose: Feedback to the local player who used an item
  927. //-----------------------------------------------------------------------------
  928. class CTFUseItemNotification : public CEconNotification
  929. {
  930. public:
  931. CTFUseItemNotification( EGCMsgUseItemResponse eResponse)
  932. : CEconNotification()
  933. {
  934. switch ( eResponse )
  935. {
  936. case k_EGCMsgUseItemResponse_ItemUsed:
  937. SetText( "#TF_UseItem_Success" );
  938. break;
  939. case k_EGCMsgUseItemResponse_GiftNoOtherPlayers:
  940. SetText( "#TF_UseItem_GiftNoPlayers" );
  941. break;
  942. case k_EGCMsgUseItemResponse_ServerError:
  943. SetText( "#TF_UseItem_Error" );
  944. break;
  945. case k_EGCMsgUseItemResponse_MiniGameAlreadyStarted:
  946. SetText( "#TF_UseItem_MiniGameAlreadyStarted" );
  947. break;
  948. case k_EGCMsgUseItemResponse_CannotBeUsedByAccount:
  949. SetText( "#TF_UseItem_CannotBeUsedByAccount" );
  950. break;
  951. default:
  952. Assert( !"Unknown response in CTFUseItemNotification!" );
  953. }
  954. SetLifetime( 20.0f );
  955. }
  956. virtual EType NotificationType() { return eType_Basic; }
  957. };
  958. //-----------------------------------------------------------------------------
  959. // Purpose: Local player used an item and the GC responded with the status of that request
  960. //-----------------------------------------------------------------------------
  961. class CGCUseItemResponse : public GCSDK::CGCClientJob
  962. {
  963. public:
  964. CGCUseItemResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  965. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  966. {
  967. GCSDK::CGCMsg<MsgGCUseItemResponse_t> msg( pNetPacket );
  968. EGCMsgUseItemResponse eResponse = (EGCMsgUseItemResponse)msg.Body().m_eResponse;
  969. if ( eResponse == k_EGCMsgUseItemResponse_ItemUsed_ItemsGranted )
  970. {
  971. if ( s_bConsumableToolOpeningGift )
  972. {
  973. vgui::surface()->PlaySound( "ui/item_gift_wrap_unwrap.wav" );
  974. }
  975. else
  976. {
  977. vgui::surface()->PlaySound( "ui/item_open_crate.wav" );
  978. }
  979. ShowWaitingDialog( new CWaitForPackageDialog( NULL ), "#ToolDecodeInProgress", true, false, 5.0f );
  980. }
  981. else if ( eResponse != k_EGCMsgUseItemResponse_ItemUsed )
  982. {
  983. NotificationQueue_Add( new CTFUseItemNotification( (EGCMsgUseItemResponse)msg.Body().m_eResponse ) );
  984. }
  985. else
  986. {
  987. // refresh the backpack
  988. if ( EconUI()->GetBackpackPanel() )
  989. {
  990. EconUI()->GetBackpackPanel()->UpdateModelPanels();
  991. }
  992. }
  993. return true;
  994. }
  995. };
  996. GC_REG_JOB( GCSDK::CGCClient, CGCUseItemResponse, "CGCUseItemResponse", k_EMsgGCUseItemResponse, GCSDK::k_EServerTypeGCClient );
  997. //-----------------------------------------------------------------------------
  998. // Purpose: A player has gifted items
  999. //-----------------------------------------------------------------------------
  1000. class CGCGiftedItems : public GCSDK::CGCClientJob
  1001. {
  1002. public:
  1003. CGCGiftedItems( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1004. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1005. {
  1006. GCSDK::CProtoBufMsg<CMsgGCGiftedItems> msg( pNetPacket );
  1007. if ( steamapicontext == NULL || steamapicontext->SteamFriends() == NULL )
  1008. {
  1009. return true;
  1010. }
  1011. char szGifterName[ MAX_PLAYER_NAME_LENGTH ];
  1012. szGifterName[0] = '\0';
  1013. GetPlayerNameBySteamID( CSteamID( msg.Body().gifter_steam_id(), GetUniverse(), k_EAccountTypeIndividual ), szGifterName, sizeof( szGifterName ) );
  1014. if ( szGifterName[0] == '\0' )
  1015. {
  1016. return true;
  1017. }
  1018. // notify UI
  1019. NotificationQueue_Add( new CTFGiftNotification( msg ) );
  1020. return true;
  1021. }
  1022. };
  1023. GC_REG_JOB( GCSDK::CGCClient, CGCGiftedItems, "CGCGiftedItems", k_EMsgGCGiftedItems, GCSDK::k_EServerTypeGCClient );
  1024. //-----------------------------------------------------------------------------
  1025. // Purpose: A player has used a claim code item
  1026. //-----------------------------------------------------------------------------
  1027. class CGCUsedClaimCodeItem : public GCSDK::CGCClientJob
  1028. {
  1029. public:
  1030. CGCUsedClaimCodeItem( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1031. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1032. {
  1033. GCSDK::CGCMsg<MsgGCUsedClaimCodeItem_t> msg( pNetPacket );
  1034. if ( steamapicontext == NULL )
  1035. {
  1036. return true;
  1037. }
  1038. CUtlString url;
  1039. if ( msg.BReadStr( &url ) )
  1040. {
  1041. steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( url.Get() );
  1042. IViewPortPanel *pMMOverride = ( gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ) );
  1043. if ( pMMOverride )
  1044. {
  1045. ((CHudMainMenuOverride*)pMMOverride)->UpdatePromotionalCodes();
  1046. }
  1047. }
  1048. return true;
  1049. }
  1050. };
  1051. GC_REG_JOB( GCSDK::CGCClient, CGCUsedClaimCodeItem, "CGCUsedClaimCodeItem", k_EMsgGCUsedClaimCodeItem, GCSDK::k_EServerTypeGCClient );
  1052. //-----------------------------------------------------------------------------
  1053. // Duel mini-game
  1054. //-----------------------------------------------------------------------------
  1055. class CDuelMiniGameEventListener;
  1056. struct duel_minigame_local_data_t
  1057. {
  1058. duel_minigame_local_data_t()
  1059. : m_pEventListener( NULL )
  1060. , m_steamIDOpponent()
  1061. , m_unMyScore( 0 )
  1062. , m_unOpponentScore( 0 )
  1063. , m_iRequiredPlayerClass( TF_CLASS_UNDEFINED )
  1064. {
  1065. }
  1066. uint32 m_unMyScore;
  1067. uint32 m_unOpponentScore;
  1068. int m_iRequiredPlayerClass;
  1069. CDuelMiniGameEventListener *m_pEventListener;
  1070. CSteamID m_steamIDOpponent;
  1071. };
  1072. static duel_minigame_local_data_t gDuelMiniGameLocalData;
  1073. bool DuelMiniGame_IsDueling()
  1074. {
  1075. return gDuelMiniGameLocalData.m_steamIDOpponent != CSteamID();
  1076. }
  1077. int DuelMiniGame_GetRequiredPlayerClass()
  1078. {
  1079. return gDuelMiniGameLocalData.m_steamIDOpponent != CSteamID() ? gDuelMiniGameLocalData.m_iRequiredPlayerClass : TF_CLASS_UNDEFINED;
  1080. }
  1081. static void DuelMiniGame_Reset();
  1082. static bool RemoveRelatedDuelNotifications( CEconNotification* pNotification );
  1083. /**
  1084. * Duel info notification
  1085. */
  1086. class CTFDuelInfoNotification : public CEconNotification
  1087. {
  1088. public:
  1089. CTFDuelInfoNotification()
  1090. {
  1091. }
  1092. static bool IsDuelInfoNotification( CEconNotification *pNotification )
  1093. {
  1094. return dynamic_cast< CTFDuelInfoNotification* >( pNotification ) != NULL;
  1095. }
  1096. };
  1097. /**
  1098. * Duel Notification
  1099. */
  1100. class CTFDuelRequestNotification : public CEconNotification, public CGameEventListener
  1101. {
  1102. public:
  1103. CTFDuelRequestNotification( const char *pInitiatorName, const CSteamID &steamIDInitiator, const CSteamID &steamIDTarget, const int iRequiredPlayerClass )
  1104. : CEconNotification()
  1105. , m_steamIDInitiator( steamIDInitiator )
  1106. , m_steamIDTarget( steamIDTarget )
  1107. , m_iRequiredPlayerClass( iRequiredPlayerClass )
  1108. {
  1109. g_pVGuiLocalize->ConvertANSIToUnicode( pInitiatorName, m_wszPlayerName, sizeof(m_wszPlayerName) );
  1110. ListenForGameEvent( "teamplay_round_win" );
  1111. ListenForGameEvent( "teamplay_round_stalemate" );
  1112. }
  1113. virtual EType NotificationType()
  1114. {
  1115. CSteamID localSteamID;
  1116. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  1117. if ( pLocalPlayer && pLocalPlayer->GetSteamID( &localSteamID ) && localSteamID == m_steamIDTarget )
  1118. {
  1119. return eType_AcceptDecline;
  1120. }
  1121. return eType_Basic;
  1122. }
  1123. // XXX(JohnS): Is there something that manually calls trigger here or is this dead code?
  1124. virtual void Trigger()
  1125. {
  1126. CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_Duel_Title", "#TF_Duel_Request", "#GameUI_OK", "#TF_Duel_JoinCancel", &ConfirmDuel );
  1127. pDialog->SetContext( this );
  1128. pDialog->AddStringToken( "initiator", m_wszPlayerName );
  1129. // so we aren't deleted
  1130. SetIsInUse( true );
  1131. }
  1132. virtual void Accept()
  1133. {
  1134. ConfirmDuel( true, this );
  1135. }
  1136. virtual void Decline()
  1137. {
  1138. ConfirmDuel( false, this );
  1139. }
  1140. static bool IsDuelRequestNotification( CEconNotification *pNotification )
  1141. {
  1142. return dynamic_cast< CTFDuelRequestNotification* >( pNotification ) != NULL;
  1143. }
  1144. static void ConfirmDuel( bool bConfirmed, void *pContext )
  1145. {
  1146. CTFDuelRequestNotification *pNotification = (CTFDuelRequestNotification*)pContext;
  1147. // if this is a class restricted duel, then make sure the local player is the same class before
  1148. // they can accept
  1149. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  1150. if ( bConfirmed && pLocalPlayer && pNotification->m_iRequiredPlayerClass != TF_CLASS_UNDEFINED )
  1151. {
  1152. int iClass = pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex();
  1153. if ( pNotification->m_iRequiredPlayerClass != iClass )
  1154. {
  1155. KeyValues *pKeyValues = new KeyValues( "DuelConfirm" );
  1156. switch ( pNotification->m_iRequiredPlayerClass )
  1157. {
  1158. case TF_CLASS_SCOUT: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Scout" ) ); break;
  1159. case TF_CLASS_SNIPER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Sniper" ) ); break;
  1160. case TF_CLASS_SOLDIER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Soldier" ) ); break;
  1161. case TF_CLASS_DEMOMAN: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Demoman" ) ); break;
  1162. case TF_CLASS_MEDIC: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Medic" ) ); break;
  1163. case TF_CLASS_HEAVYWEAPONS: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_HWGuy" ) ); break;
  1164. case TF_CLASS_PYRO: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Pyro" ) ); break;
  1165. case TF_CLASS_SPY: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Spy" ) ); break;
  1166. case TF_CLASS_ENGINEER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Engineer" ) ); break;
  1167. }
  1168. ShowMessageBox( "#TF_Duel_Title", "#TF_Duel_WrongClass", pKeyValues, "#GameUI_OK" );
  1169. return;
  1170. }
  1171. }
  1172. // notify GC of our choice
  1173. GCSDK::CGCMsg< MsgGC_Duel_Response_t > msg( k_EMsgGC_Duel_Response );
  1174. msg.Body().m_ulInitiatorSteamID = pNotification->m_steamIDInitiator.ConvertToUint64();
  1175. msg.Body().m_ulTargetSteamID = pNotification->m_steamIDTarget.ConvertToUint64();
  1176. msg.Body().m_bAccepted = bConfirmed;
  1177. GCClientSystem()->BSendMessage( msg );
  1178. pNotification->SetIsInUse( false );
  1179. pNotification->MarkForDeletion();
  1180. // remove all duel notifications if we've accepted
  1181. if ( bConfirmed )
  1182. {
  1183. NotificationQueue_Remove( &RemoveRelatedDuelNotifications );
  1184. }
  1185. }
  1186. void FireGameEvent( IGameEvent *event )
  1187. {
  1188. const char *pEventName = event->GetName();
  1189. if ( FStrEq( "teamplay_round_win", pEventName ) || FStrEq( "teamplay_round_stalemate", pEventName ) )
  1190. {
  1191. Decline();
  1192. return;
  1193. }
  1194. }
  1195. CSteamID m_steamIDInitiator;
  1196. CSteamID m_steamIDTarget;
  1197. int m_iRequiredPlayerClass;
  1198. wchar_t m_wszPlayerName[MAX_PLAYER_NAME_LENGTH];
  1199. };
  1200. /**
  1201. * Listens for duel_status events and adds a notification. Ideally adds to a scoreboard or something...
  1202. */
  1203. class CDuelMiniGameEventListener : public CGameEventListener
  1204. {
  1205. public:
  1206. CDuelMiniGameEventListener()
  1207. {
  1208. ListenForGameEvent( "duel_status" );
  1209. ListenForGameEvent( "teamplay_round_win" );
  1210. ListenForGameEvent( "teamplay_round_stalemate" );
  1211. }
  1212. virtual void FireGameEvent( IGameEvent *event )
  1213. {
  1214. const char *pEventName = event->GetName();
  1215. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  1216. if ( pLocalPlayer == NULL )
  1217. {
  1218. return;
  1219. }
  1220. if ( Q_strcmp( "teamplay_round_win", pEventName ) == 0 || Q_strcmp( "teamplay_round_stalemate", pEventName ) == 0 )
  1221. {
  1222. DuelMiniGame_Reset();
  1223. return;
  1224. }
  1225. else if ( Q_strcmp( "duel_status", pEventName ) == 0 )
  1226. {
  1227. int iKillerID = engine->GetPlayerForUserID( event->GetInt( "killer" ) );
  1228. int iInitiatorID = engine->GetPlayerForUserID( event->GetInt( "initiator" ) );
  1229. int iTargetID = engine->GetPlayerForUserID( event->GetInt( "target" ) );
  1230. const char *pInitiatorName = ( iInitiatorID > 0 ? g_PR->GetPlayerName( iInitiatorID ) : "" );
  1231. wchar_t wszInitiatorName[MAX_PLAYER_NAME_LENGTH] = L"";
  1232. g_pVGuiLocalize->ConvertANSIToUnicode( pInitiatorName, wszInitiatorName, sizeof(wszInitiatorName) );
  1233. const char *pTargetName = ( iTargetID > 0 ? g_PR->GetPlayerName( iTargetID ) : "" );
  1234. wchar_t wszTargetName[MAX_PLAYER_NAME_LENGTH] = L"";
  1235. g_pVGuiLocalize->ConvertANSIToUnicode( pTargetName, wszTargetName, sizeof(wszTargetName) );
  1236. wchar_t wszInitiatorScore[16];
  1237. _snwprintf( wszInitiatorScore, ARRAYSIZE( wszInitiatorScore ), L"%i", event->GetInt( "initiator_score", 0 ) );
  1238. wchar_t wszTargetScore[16];
  1239. _snwprintf( wszTargetScore, ARRAYSIZE( wszTargetScore ), L"%i", event->GetInt( "target_score", 0 ) );
  1240. enum
  1241. {
  1242. kDuelScoreType_Kill,
  1243. kDuelScoreType_Assist,
  1244. kMaxDuelScoreTypes,
  1245. };
  1246. int iScoreType = event->GetInt( "score_type" );
  1247. KeyValues *pKeyValues = new KeyValues( "DuelStatus" );
  1248. pKeyValues->SetWString( "killer", iKillerID == iInitiatorID ? wszInitiatorName : wszTargetName );
  1249. pKeyValues->SetWString( "initiator", wszInitiatorName );
  1250. pKeyValues->SetWString( "target", wszTargetName );
  1251. pKeyValues->SetWString( "initiator_score", wszInitiatorScore );
  1252. pKeyValues->SetWString( "target_score", wszTargetScore );
  1253. // if we aren't involved in the duel, don't show the notification
  1254. if ( pLocalPlayer->entindex() == iInitiatorID || pLocalPlayer->entindex() == iTargetID )
  1255. {
  1256. // remove existing duel info notifications
  1257. NotificationQueue_Remove( &RemoveRelatedDuelNotifications );
  1258. // add new one
  1259. CTFDuelInfoNotification *pNotification = new CTFDuelInfoNotification();
  1260. pNotification->SetLifetime( 10.0f );
  1261. pNotification->SetText( iScoreType == kDuelScoreType_Kill ? "TF_Duel_StatusKill" : "TF_Duel_StatusAssist" );
  1262. pNotification->SetKeyValues( pKeyValues );
  1263. pNotification->SetSoundFilename( "ui/duel_event.wav" );
  1264. player_info_t pi;
  1265. if ( engine->GetPlayerInfo( iKillerID, &pi ) && pi.friendsID != 0 )
  1266. {
  1267. CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
  1268. pNotification->SetSteamID( steamID );
  1269. }
  1270. NotificationQueue_Add( pNotification );
  1271. if ( pLocalPlayer->entindex() == iInitiatorID )
  1272. {
  1273. gDuelMiniGameLocalData.m_unMyScore = event->GetInt( "initiator_score" );
  1274. gDuelMiniGameLocalData.m_unOpponentScore = event->GetInt( "target_score" );
  1275. }
  1276. else
  1277. {
  1278. gDuelMiniGameLocalData.m_unMyScore = event->GetInt( "target_score" );
  1279. gDuelMiniGameLocalData.m_unOpponentScore = event->GetInt( "initiator_score" );
  1280. }
  1281. }
  1282. // print to chat log
  1283. PrintTextToChat( iScoreType == kDuelScoreType_Kill ? "TF_Duel_StatusForChat_Kill" : "TF_Duel_StatusForChat_Assist", pKeyValues );
  1284. // cleanup
  1285. pKeyValues->deleteThis();
  1286. }
  1287. }
  1288. };
  1289. /**
  1290. * Duel request
  1291. */
  1292. class CGC_Duel_Request : public GCSDK::CGCClientJob
  1293. {
  1294. public:
  1295. CGC_Duel_Request( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1296. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1297. {
  1298. GCSDK::CGCMsg<MsgGC_Duel_Request_t> msg( pNetPacket );
  1299. CSteamID localSteamID;
  1300. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  1301. if ( pLocalPlayer == NULL || pLocalPlayer->GetSteamID( &localSteamID ) == false )
  1302. {
  1303. return true;
  1304. }
  1305. // get player names
  1306. CSteamID steamIDInitiator( msg.Body().m_ulInitiatorSteamID );
  1307. CSteamID steamIDTarget( msg.Body().m_ulTargetSteamID );
  1308. // ignore blocked players (we don't want to print out to the console either)
  1309. if ( IgnoreRequestFromUser( steamIDInitiator ) || IgnoreRequestFromUser( steamIDTarget ) )
  1310. {
  1311. GCSDK::CGCMsg< MsgGC_Duel_Response_t > msgGC( k_EMsgGC_Duel_Response );
  1312. msgGC.Body().m_ulInitiatorSteamID = steamIDInitiator.ConvertToUint64();
  1313. msgGC.Body().m_ulTargetSteamID = steamIDTarget.ConvertToUint64();
  1314. msgGC.Body().m_bAccepted = false;
  1315. GCClientSystem()->BSendMessage( msgGC );
  1316. return true;
  1317. }
  1318. char szPlayerName_Initiator[ MAX_PLAYER_NAME_LENGTH ];
  1319. GetPlayerNameBySteamID( steamIDInitiator, szPlayerName_Initiator, sizeof( szPlayerName_Initiator ) );
  1320. char szPlayerName_Target[ MAX_PLAYER_NAME_LENGTH ];
  1321. GetPlayerNameBySteamID( steamIDTarget, szPlayerName_Target, sizeof( szPlayerName_Target ) );
  1322. wchar_t wszPlayerName_Initiator[MAX_PLAYER_NAME_LENGTH];
  1323. wchar_t wszPlayerName_Target[MAX_PLAYER_NAME_LENGTH];
  1324. g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Initiator, wszPlayerName_Initiator, sizeof(wszPlayerName_Initiator) );
  1325. g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Target, wszPlayerName_Target, sizeof(wszPlayerName_Target) );
  1326. // add notification
  1327. KeyValues *pKeyValues = new KeyValues( "DuelText" );
  1328. pKeyValues->SetWString( "initiator", wszPlayerName_Initiator );
  1329. pKeyValues->SetWString( "target", wszPlayerName_Target );
  1330. const char *pText = localSteamID == steamIDTarget ? "TF_Duel_Request" : "TF_Duel_Challenge";
  1331. const char *pSoundFilename = "ui/duel_challenge.wav";
  1332. if ( msg.Body().m_usAsPlayerClass >= TF_FIRST_NORMAL_CLASS && msg.Body().m_usAsPlayerClass < TF_LAST_NORMAL_CLASS )
  1333. {
  1334. pText = localSteamID == steamIDTarget ? "TF_Duel_Request_Class" : "TF_Duel_Challenge_Class";
  1335. pSoundFilename = "ui/duel_challenge_with_restriction.wav";
  1336. switch ( msg.Body().m_usAsPlayerClass )
  1337. {
  1338. case TF_CLASS_SCOUT: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Scout" ) ); break;
  1339. case TF_CLASS_SNIPER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Sniper" ) ); break;
  1340. case TF_CLASS_SOLDIER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Soldier" ) ); break;
  1341. case TF_CLASS_DEMOMAN: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Demoman" ) ); break;
  1342. case TF_CLASS_MEDIC: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Medic" ) ); break;
  1343. case TF_CLASS_HEAVYWEAPONS: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_HWGuy" ) ); break;
  1344. case TF_CLASS_PYRO: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Pyro" ) ); break;
  1345. case TF_CLASS_SPY: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Spy" ) ); break;
  1346. case TF_CLASS_ENGINEER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Engineer" ) ); break;
  1347. }
  1348. }
  1349. if ( localSteamID == steamIDInitiator || localSteamID == steamIDTarget )
  1350. {
  1351. CTFDuelRequestNotification *pNotification = new CTFDuelRequestNotification( szPlayerName_Initiator, steamIDInitiator, steamIDTarget, msg.Body().m_usAsPlayerClass );
  1352. pNotification->SetLifetime( localSteamID == steamIDTarget ? 30.0f : 7.0f );
  1353. pNotification->SetText( pText );
  1354. pNotification->SetKeyValues( pKeyValues );
  1355. pNotification->SetSteamID( steamIDInitiator );
  1356. pNotification->SetSoundFilename( pSoundFilename );
  1357. NotificationQueue_Add( pNotification );
  1358. }
  1359. // print to chat log
  1360. PrintTextToChat( pText, pKeyValues );
  1361. pKeyValues->deleteThis();
  1362. return true;
  1363. }
  1364. protected:
  1365. };
  1366. GC_REG_JOB( GCSDK::CGCClient, CGC_Duel_Request, "CGC_Duel_Request", k_EMsgGC_Duel_Request, GCSDK::k_EServerTypeGCClient );
  1367. /**
  1368. * Duel response
  1369. */
  1370. class CGCClient_Duel_Response : public GCSDK::CGCClientJob
  1371. {
  1372. public:
  1373. CGCClient_Duel_Response( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1374. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1375. {
  1376. GCSDK::CGCMsg<MsgGC_Duel_Response_t> msg( pNetPacket );
  1377. CSteamID localSteamID;
  1378. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  1379. if ( pLocalPlayer == NULL || pLocalPlayer->GetSteamID( &localSteamID ) == false )
  1380. return true;
  1381. // get player names
  1382. CSteamID steamIDInitiator( msg.Body().m_ulInitiatorSteamID );
  1383. CSteamID steamIDTarget( msg.Body().m_ulTargetSteamID );
  1384. char szPlayerName_Initiator[ MAX_PLAYER_NAME_LENGTH ];
  1385. GetPlayerNameBySteamID( steamIDInitiator, szPlayerName_Initiator, sizeof( szPlayerName_Initiator ) );
  1386. char szPlayerName_Target[ MAX_PLAYER_NAME_LENGTH ];
  1387. GetPlayerNameBySteamID( steamIDTarget, szPlayerName_Target, sizeof( szPlayerName_Target ) );
  1388. wchar_t wszPlayerName_Initiator[MAX_PLAYER_NAME_LENGTH];
  1389. wchar_t wszPlayerName_Target[MAX_PLAYER_NAME_LENGTH];
  1390. g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Initiator, wszPlayerName_Initiator, sizeof(wszPlayerName_Initiator) );
  1391. g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Target, wszPlayerName_Target, sizeof(wszPlayerName_Target) );
  1392. KeyValues *pKeyValues = new KeyValues( "DuelText" );
  1393. pKeyValues->SetWString( "initiator", wszPlayerName_Initiator );
  1394. pKeyValues->SetWString( "target", wszPlayerName_Target );
  1395. bool bIsClassDuel = msg.Body().m_usAsPlayerClass >= TF_FIRST_NORMAL_CLASS && msg.Body().m_usAsPlayerClass < TF_LAST_NORMAL_CLASS;
  1396. // add notification
  1397. const char *pText = "TF_Duel_Accept";
  1398. const char *pSoundFilename = bIsClassDuel ? "ui/duel_challenge_accepted_with_restriction.wav" : "ui/duel_challenge_accepted.wav";
  1399. if ( msg.Body().m_bAccepted == false )
  1400. {
  1401. const char *kDeclineStrings[] = {
  1402. "TF_Duel_Decline",
  1403. "TF_Duel_Decline2",
  1404. "TF_Duel_Decline3"
  1405. };
  1406. pText = kDeclineStrings[ RandomInt( 0, ARRAYSIZE( kDeclineStrings ) - 1 ) ];
  1407. pSoundFilename = bIsClassDuel ? "ui/duel_challenge_rejected_with_restriction.wav" : "ui/duel_challenge_rejected.wav";
  1408. }
  1409. if ( ( TFGameRules() && TFGameRules()->CanInitiateDuels() ) || msg.Body().m_bAccepted )
  1410. {
  1411. if ( localSteamID == steamIDInitiator || localSteamID == steamIDTarget )
  1412. {
  1413. // remove existing duel info notifications
  1414. NotificationQueue_Remove( &RemoveRelatedDuelNotifications );
  1415. // add new one
  1416. CTFDuelInfoNotification *pNotification = new CTFDuelInfoNotification();
  1417. pNotification->SetLifetime( 7.0f );
  1418. pNotification->SetText( pText );
  1419. pNotification->SetKeyValues( pKeyValues );
  1420. pNotification->SetSteamID( steamIDInitiator );
  1421. pNotification->SetSoundFilename( pSoundFilename );
  1422. NotificationQueue_Add( pNotification );
  1423. }
  1424. // print to chat
  1425. PrintTextToChat( pText, pKeyValues );
  1426. }
  1427. // store away opponent id and create event listener if applicable
  1428. if ( msg.Body().m_bAccepted )
  1429. {
  1430. if ( localSteamID == steamIDInitiator )
  1431. {
  1432. gDuelMiniGameLocalData.m_steamIDOpponent = steamIDTarget;
  1433. }
  1434. else if ( localSteamID == steamIDTarget )
  1435. {
  1436. gDuelMiniGameLocalData.m_steamIDOpponent = steamIDInitiator;
  1437. }
  1438. if ( gDuelMiniGameLocalData.m_pEventListener == NULL )
  1439. {
  1440. gDuelMiniGameLocalData.m_pEventListener = new CDuelMiniGameEventListener();
  1441. }
  1442. gDuelMiniGameLocalData.m_iRequiredPlayerClass = msg.Body().m_usAsPlayerClass;
  1443. }
  1444. pKeyValues->deleteThis();
  1445. return true;
  1446. }
  1447. protected:
  1448. };
  1449. GC_REG_JOB( GCSDK::CGCClient, CGCClient_Duel_Response, "CGCClient_Duel_Response", k_EMsgGC_Duel_Response, GCSDK::k_EServerTypeGCClient );
  1450. /**
  1451. * Duel status (whether the duel is in progress or cancelled)
  1452. */
  1453. class CGC_Duel_Status : public GCSDK::CGCClientJob
  1454. {
  1455. public:
  1456. CGC_Duel_Status( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1457. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1458. {
  1459. GCSDK::CGCMsg<MsgGC_Duel_Status_t> msg( pNetPacket );
  1460. // get player names
  1461. CSteamID steamIDInitiator( msg.Body().m_ulInitiatorSteamID );
  1462. CSteamID steamIDTarget( msg.Body().m_ulTargetSteamID );
  1463. char szPlayerName_Initiator[ MAX_PLAYER_NAME_LENGTH ];
  1464. GetPlayerNameBySteamID( steamIDInitiator, szPlayerName_Initiator, sizeof( szPlayerName_Initiator ) );
  1465. char szPlayerName_Target[ MAX_PLAYER_NAME_LENGTH ];
  1466. GetPlayerNameBySteamID( steamIDTarget, szPlayerName_Target, sizeof( szPlayerName_Target ) );
  1467. wchar_t wszPlayerName_Initiator[MAX_PLAYER_NAME_LENGTH];
  1468. wchar_t wszPlayerName_Target[MAX_PLAYER_NAME_LENGTH];
  1469. g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Initiator, wszPlayerName_Initiator, sizeof(wszPlayerName_Initiator) );
  1470. g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Target, wszPlayerName_Target, sizeof(wszPlayerName_Target) );
  1471. // add notification
  1472. const char *pText = "TF_Duel_InProgress";
  1473. switch ( msg.Body().m_usStatus )
  1474. {
  1475. case kDuel_Status_AlreadyInDuel_Inititator:
  1476. pText = "TF_Duel_InADuel_Initiator";
  1477. break;
  1478. case kDuel_Status_AlreadyInDuel_Target:
  1479. pText = "TF_Duel_InADuel_Target";
  1480. break;
  1481. case kDuel_Status_DuelBanned_Initiator:
  1482. pText = "TF_Duel_TempBanned_Initiator";
  1483. break;
  1484. case kDuel_Status_DuelBanned_Target:
  1485. pText = "TF_Duel_TempBanned_Target";
  1486. break;
  1487. case kDuel_Status_Cancelled:
  1488. pText = "TF_Duel_Cancelled";
  1489. break;
  1490. case kDuel_Status_MissingSession:
  1491. pText = "TF_Duel_UserTemporarilyUnavailable";
  1492. break;
  1493. default:
  1494. AssertMsg1( false, "Unknown duel status %i in CGC_Duel_Status!", msg.Body().m_usStatus );
  1495. }
  1496. // remove existing duel info notifications
  1497. NotificationQueue_Remove( &RemoveRelatedDuelNotifications );
  1498. // add new one
  1499. CTFDuelInfoNotification *pNotification = new CTFDuelInfoNotification();
  1500. pNotification->SetLifetime( 7.0f );
  1501. pNotification->SetText( pText );
  1502. pNotification->AddStringToken( "initiator", wszPlayerName_Initiator );
  1503. pNotification->AddStringToken( "target", wszPlayerName_Target );
  1504. pNotification->SetSteamID( steamIDInitiator );
  1505. pNotification->SetSoundFilename( "ui/duel_event.wav" );
  1506. NotificationQueue_Add( pNotification );
  1507. return true;
  1508. }
  1509. protected:
  1510. };
  1511. GC_REG_JOB( GCSDK::CGCClient, CGC_Duel_Status, "CGC_Duel_Status", k_EMsgGC_Duel_Status, GCSDK::k_EServerTypeGCClient );
  1512. /**
  1513. * Duel Results--ideally this is a scoreboard as well.
  1514. */
  1515. class CGC_Duel_Results : public GCSDK::CGCClientJob
  1516. {
  1517. public:
  1518. CGC_Duel_Results( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1519. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1520. {
  1521. GCSDK::CGCMsg<MsgGC_Duel_Results_t> msg( pNetPacket );
  1522. if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL )
  1523. return true;
  1524. CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID();
  1525. // get player names
  1526. CSteamID steamIDWinner( msg.Body().m_ulWinnerSteamID );
  1527. CSteamID steamIDInitiator( msg.Body().m_ulInitiatorSteamID );
  1528. CSteamID steamIDTarget( msg.Body().m_ulTargetSteamID );
  1529. char szPlayerName_Winner[ MAX_PLAYER_NAME_LENGTH ];
  1530. GetPlayerNameBySteamID( steamIDWinner, szPlayerName_Winner, sizeof( szPlayerName_Winner ) );
  1531. char szPlayerName_Initiator[ MAX_PLAYER_NAME_LENGTH ];
  1532. GetPlayerNameBySteamID( steamIDInitiator, szPlayerName_Initiator, sizeof( szPlayerName_Initiator ) );
  1533. char szPlayerName_Target[ MAX_PLAYER_NAME_LENGTH ];
  1534. GetPlayerNameBySteamID( steamIDTarget, szPlayerName_Target, sizeof( szPlayerName_Target ) );
  1535. wchar_t wszPlayerName_Winner[MAX_PLAYER_NAME_LENGTH];
  1536. wchar_t wszPlayerName_Initiator[MAX_PLAYER_NAME_LENGTH];
  1537. wchar_t wszPlayerName_Target[MAX_PLAYER_NAME_LENGTH];
  1538. g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Winner, wszPlayerName_Winner, sizeof(wszPlayerName_Winner) );
  1539. g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Initiator, wszPlayerName_Initiator, sizeof(wszPlayerName_Initiator) );
  1540. g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Target, wszPlayerName_Target, sizeof(wszPlayerName_Target) );
  1541. // build text
  1542. bool bTie = msg.Body().m_usScoreInitiator == msg.Body().m_usScoreTarget;
  1543. const char *pText = "TF_Duel_Win";
  1544. switch ( msg.Body().m_usEndReason )
  1545. {
  1546. case kDuelEndReason_DuelOver:
  1547. break;
  1548. case kDuelEndReason_PlayerDisconnected:
  1549. bTie = false;
  1550. pText = "TF_Duel_Win_Disconnect";
  1551. break;
  1552. case kDuelEndReason_PlayerSwappedTeams:
  1553. bTie = false;
  1554. pText = "TF_Duel_Win_SwappedTeams";
  1555. break;
  1556. case kDuelEndReason_LevelShutdown:
  1557. bTie = true;
  1558. pText = "TF_Duel_Refund_LevelShutdown";
  1559. break;
  1560. case kDuelEndReason_ScoreTiedAtZero:
  1561. bTie = true;
  1562. pText = "TF_Duel_Refund_ScoreTiedAtZero";
  1563. break;
  1564. case kDuelEndReason_ScoreTied:
  1565. bTie = true;
  1566. pText = "TF_Duel_Tie";
  1567. break;
  1568. case kDuelEndReason_PlayerKicked:
  1569. bTie = true;
  1570. pText = "TF_Duel_Refund_Kicked";
  1571. break;
  1572. case kDuelEndReason_PlayerForceSwappedTeams:
  1573. bTie = true;
  1574. pText = "TF_Duel_Refund_ForceTeamSwap";
  1575. break;
  1576. case kDuelEndReason_Cancelled:
  1577. bTie = true;
  1578. pText = "TF_Duel_Cancelled";
  1579. break;
  1580. }
  1581. KeyValues *pKeyValues = new KeyValues( "DuelResults" );
  1582. if ( bTie == false )
  1583. {
  1584. pKeyValues->SetWString( "winner", wszPlayerName_Winner );
  1585. pKeyValues->SetWString( "loser", steamIDWinner == steamIDInitiator ? wszPlayerName_Target : wszPlayerName_Initiator );
  1586. wchar_t wszScoreInitiator[16];
  1587. wchar_t wszScoreTarget[16];
  1588. _snwprintf( wszScoreInitiator, ARRAYSIZE( wszScoreInitiator ), L"%u", (uint32)msg.Body().m_usScoreInitiator );
  1589. _snwprintf( wszScoreTarget, ARRAYSIZE( wszScoreTarget ), L"%u", (uint32)msg.Body().m_usScoreTarget );
  1590. pKeyValues->SetWString( "winner_score", steamIDWinner == steamIDInitiator ? wszScoreInitiator : wszScoreTarget );
  1591. pKeyValues->SetWString( "loser_score", steamIDWinner == steamIDInitiator ? wszScoreTarget : wszScoreInitiator );
  1592. }
  1593. else
  1594. {
  1595. wchar_t wszScoreInitiator[16];
  1596. _snwprintf( wszScoreInitiator, ARRAYSIZE( wszScoreInitiator ), L"%u", (uint32)msg.Body().m_usScoreInitiator );
  1597. pKeyValues->SetWString( "score", wszScoreInitiator );
  1598. pKeyValues->SetWString( "initiator", wszPlayerName_Initiator );
  1599. pKeyValues->SetWString( "target", wszPlayerName_Target );
  1600. }
  1601. // add notification
  1602. if ( localSteamID == steamIDInitiator || localSteamID == steamIDTarget )
  1603. {
  1604. // remove existing duel info notifications
  1605. NotificationQueue_Remove( &RemoveRelatedDuelNotifications );
  1606. // add new one
  1607. CTFDuelInfoNotification *pNotification = new CTFDuelInfoNotification();
  1608. pNotification->SetText( pText );
  1609. pNotification->SetKeyValues( pKeyValues );
  1610. pNotification->SetSteamID( bTie == false ? steamIDWinner : ( localSteamID == steamIDInitiator ? steamIDTarget : steamIDInitiator ) );
  1611. pNotification->SetSoundFilename( "ui/duel_event.wav" );
  1612. NotificationQueue_Add( pNotification );
  1613. gDuelMiniGameLocalData.m_steamIDOpponent = CSteamID();
  1614. }
  1615. // print out to chat
  1616. PrintTextToChat( pText, pKeyValues );
  1617. // cleanup
  1618. pKeyValues->deleteThis();
  1619. // reset the dueling minigame
  1620. DuelMiniGame_Reset();
  1621. return true;
  1622. }
  1623. protected:
  1624. };
  1625. GC_REG_JOB( GCSDK::CGCClient, CGC_Duel_Results, "CGC_Duel_Results", k_EMsgGC_Duel_Results, GCSDK::k_EServerTypeGCClient );
  1626. static bool RemoveRelatedDuelNotifications( CEconNotification *pNotification )
  1627. {
  1628. return ( CTFDuelRequestNotification::IsDuelRequestNotification( pNotification ) ||
  1629. CTFDuelInfoNotification::IsDuelInfoNotification( pNotification ) );
  1630. }
  1631. static void DuelMiniGame_Reset()
  1632. {
  1633. delete gDuelMiniGameLocalData.m_pEventListener;
  1634. gDuelMiniGameLocalData.m_pEventListener = NULL;
  1635. gDuelMiniGameLocalData.m_steamIDOpponent = CSteamID();
  1636. gDuelMiniGameLocalData.m_unMyScore = 0;
  1637. gDuelMiniGameLocalData.m_unOpponentScore = 0;
  1638. gDuelMiniGameLocalData.m_iRequiredPlayerClass = TF_CLASS_UNDEFINED;
  1639. }
  1640. bool DuelMiniGame_IsDuelingLocalPlayer( C_TFPlayer *pPlayer )
  1641. {
  1642. CSteamID steamID;
  1643. if ( pPlayer->GetSteamID( &steamID ) )
  1644. {
  1645. return ( steamID == gDuelMiniGameLocalData.m_steamIDOpponent );
  1646. }
  1647. return false;
  1648. }
  1649. bool DuelMiniGame_GetStats( C_TFPlayer **ppPlayer, uint32 &unMyScore, uint32 &unOpponentScore )
  1650. {
  1651. *ppPlayer = NULL;
  1652. int iLocalPlayerIndex = GetLocalPlayerIndex();
  1653. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  1654. {
  1655. // find all players who are on the local player's team
  1656. if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) )
  1657. {
  1658. CSteamID steamID;
  1659. C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  1660. if ( pPlayer && pPlayer->GetSteamID( &steamID ) && steamID == gDuelMiniGameLocalData.m_steamIDOpponent )
  1661. {
  1662. *ppPlayer = pPlayer;
  1663. break;
  1664. }
  1665. }
  1666. }
  1667. static bool sbTesting = false;
  1668. if ( sbTesting )
  1669. {
  1670. *ppPlayer = ToTFPlayer( C_BasePlayer::GetLocalPlayer() );
  1671. }
  1672. if ( *ppPlayer != NULL )
  1673. {
  1674. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  1675. unMyScore = gDuelMiniGameLocalData.m_unMyScore;
  1676. unOpponentScore = gDuelMiniGameLocalData.m_unOpponentScore;
  1677. return true;
  1678. }
  1679. return false;
  1680. }
  1681. /**
  1682. * Select player for duel dialog.
  1683. */
  1684. class CSelectPlayerForDuelDialog : public CSelectPlayerDialog, public CGameEventListener
  1685. {
  1686. DECLARE_CLASS_SIMPLE( CSelectPlayerForDuelDialog, CSelectPlayerDialog );
  1687. public:
  1688. CSelectPlayerForDuelDialog( uint64 iItemID );
  1689. virtual ~CSelectPlayerForDuelDialog();
  1690. virtual void OnSelectPlayer( const CSteamID &steamID );
  1691. virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
  1692. virtual void FireGameEvent( IGameEvent *event );
  1693. MESSAGE_FUNC_PARAMS( OnClassIconSelected, "ClassIconSelected", data );
  1694. MESSAGE_FUNC_PARAMS( OnShowClassIconMouseover, "ShowClassIconMouseover", data );
  1695. MESSAGE_FUNC( OnHideClassIconMouseover, "HideClassIconMouseover" );
  1696. protected:
  1697. virtual const char *GetResFile() { return "resource/ui/SelectPlayerDialog_Duel.res"; }
  1698. void SetSelectedClass( int iClass );
  1699. void SetupClassImage( const char *pImageControlName, int iClass );
  1700. uint64 m_iItemID;
  1701. uint8 m_iPlayerClass;
  1702. vgui::Label *m_pClassIconMouseoverLabel;
  1703. };
  1704. static vgui::DHANDLE< CSelectPlayerForDuelDialog > g_pSelectPlayerForDuelingDialog;
  1705. CSelectPlayerForDuelDialog::CSelectPlayerForDuelDialog( uint64 iItemID )
  1706. : CSelectPlayerDialog( NULL )
  1707. , m_iItemID( iItemID )
  1708. , m_iPlayerClass( TF_CLASS_UNDEFINED )
  1709. {
  1710. g_pSelectPlayerForDuelingDialog = this;
  1711. m_bAllowSameTeam = false;
  1712. m_bAllowOutsideServer = false;
  1713. m_pClassIconMouseoverLabel = new vgui::Label( this, "ClassUsageMouseoverLabel", "" );
  1714. ListenForGameEvent( "teamplay_round_win" );
  1715. ListenForGameEvent( "teamplay_round_stalemate" );
  1716. }
  1717. CSelectPlayerForDuelDialog::~CSelectPlayerForDuelDialog()
  1718. {
  1719. g_pSelectPlayerForDuelingDialog = NULL;
  1720. }
  1721. void CSelectPlayerForDuelDialog::OnSelectPlayer( const CSteamID &steamID )
  1722. {
  1723. GCSDK::CProtoBufMsg<CMsgUseItem> msg( k_EMsgGCUseItemRequest );
  1724. msg.Body().set_item_id( m_iItemID );
  1725. msg.Body().set_target_steam_id( steamID.ConvertToUint64() );
  1726. msg.Body().set_duel__class_lock( m_iPlayerClass );
  1727. GCClientSystem()->BSendMessage( msg );
  1728. }
  1729. void CSelectPlayerForDuelDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
  1730. {
  1731. CSelectPlayerDialog::ApplySchemeSettings( pScheme );
  1732. SetDialogVariable( "title", g_pVGuiLocalize->Find( "TF_DuelDialog_Title" ) );
  1733. SetupClassImage( "ClassUsageImage_Any", TF_CLASS_UNDEFINED );
  1734. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  1735. SetupClassImage( "ClassUsageImage_Locked", pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() );
  1736. SetSelectedClass( TF_CLASS_UNDEFINED );
  1737. }
  1738. void CSelectPlayerForDuelDialog::SetupClassImage( const char *pImageControlName, int iClass )
  1739. {
  1740. CStorePreviewClassIcon *pClassImage = dynamic_cast<CStorePreviewClassIcon*>( FindChildByName( pImageControlName ) );
  1741. if ( pClassImage )
  1742. {
  1743. pClassImage->SetClass( iClass );
  1744. }
  1745. }
  1746. void CSelectPlayerForDuelDialog::FireGameEvent( IGameEvent *event )
  1747. {
  1748. const char *pEventName = event->GetName();
  1749. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  1750. if ( pLocalPlayer == NULL )
  1751. {
  1752. OnCommand( "cancel" );
  1753. return;
  1754. }
  1755. if ( Q_strcmp( "teamplay_round_win", pEventName ) == 0 || Q_strcmp( "teamplay_round_stalemate", pEventName ) == 0 )
  1756. {
  1757. OnCommand( "cancel" );
  1758. return;
  1759. }
  1760. }
  1761. void CSelectPlayerForDuelDialog::OnClassIconSelected( KeyValues *data )
  1762. {
  1763. int iClass = data->GetInt( "class", 0 );
  1764. SetSelectedClass( iClass );
  1765. }
  1766. void CSelectPlayerForDuelDialog::OnHideClassIconMouseover( void )
  1767. {
  1768. if ( m_pClassIconMouseoverLabel )
  1769. {
  1770. m_pClassIconMouseoverLabel->SetVisible( false );
  1771. }
  1772. }
  1773. void CSelectPlayerForDuelDialog::OnShowClassIconMouseover( KeyValues *data )
  1774. {
  1775. if ( m_pClassIconMouseoverLabel )
  1776. {
  1777. // Set the text to the correct string
  1778. int iClass = data->GetInt( "class", 0 );
  1779. if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS )
  1780. {
  1781. wchar_t wzLocalized[256];
  1782. const char *pszLocString = "#TF_SelectPlayer_Duel_PlayerClass";
  1783. g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( pszLocString ), 1, g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ) );
  1784. m_pClassIconMouseoverLabel->SetText( wzLocalized );
  1785. }
  1786. else
  1787. {
  1788. const char *pszLocString = "#TF_SelectPlayer_Duel_AnyClass";
  1789. m_pClassIconMouseoverLabel->SetText( pszLocString );
  1790. }
  1791. m_pClassIconMouseoverLabel->SetVisible( true );
  1792. }
  1793. }
  1794. void CSelectPlayerForDuelDialog::SetSelectedClass( int iClass )
  1795. {
  1796. m_iPlayerClass = iClass;
  1797. const char* pClassName = "#TF_SelectPlayer_DuelClass_None";
  1798. switch ( m_iPlayerClass )
  1799. {
  1800. case TF_CLASS_SCOUT: pClassName = "#TF_Class_Name_Scout"; break;
  1801. case TF_CLASS_SNIPER: pClassName = "#TF_Class_Name_Sniper"; break;
  1802. case TF_CLASS_SOLDIER: pClassName = "#TF_Class_Name_Soldier"; break;
  1803. case TF_CLASS_DEMOMAN: pClassName = "#TF_Class_Name_Demoman"; break;
  1804. case TF_CLASS_MEDIC: pClassName = "#TF_Class_Name_Medic"; break;
  1805. case TF_CLASS_HEAVYWEAPONS: pClassName = "#TF_Class_Name_HWGuy"; break;
  1806. case TF_CLASS_PYRO: pClassName = "#TF_Class_Name_Pyro"; break;
  1807. case TF_CLASS_SPY: pClassName = "#TF_Class_Name_Spy"; break;
  1808. case TF_CLASS_ENGINEER: pClassName = "#TF_Class_Name_Engineer"; break;
  1809. }
  1810. wchar_t wszText[1024]=L"";
  1811. g_pVGuiLocalize->ConstructString_safe( wszText,
  1812. g_pVGuiLocalize->Find( "#TF_SelectPlayer_DuelClass" ),
  1813. 1,
  1814. g_pVGuiLocalize->Find( pClassName ) );
  1815. SetDialogVariable( "player_class", wszText );
  1816. }
  1817. static void ShowSelectDuelTargetDialog( uint64 iItemID )
  1818. {
  1819. CSelectPlayerForDuelDialog *pDialog = vgui::SETUP_PANEL( new CSelectPlayerForDuelDialog( iItemID ) );
  1820. pDialog->InvalidateLayout( false, true );
  1821. pDialog->Reset();
  1822. pDialog->SetVisible( true );
  1823. pDialog->MakePopup();
  1824. pDialog->MoveToFront();
  1825. pDialog->SetKeyBoardInputEnabled(true);
  1826. pDialog->SetMouseInputEnabled(true);
  1827. TFModalStack()->PushModal( pDialog );
  1828. }
  1829. //-----------------------------------------------------------------------------
  1830. void CL_Consumables_LevelShutdown()
  1831. {
  1832. DuelMiniGame_Reset();
  1833. if ( g_pSelectPlayerForDuelingDialog )
  1834. {
  1835. g_pSelectPlayerForDuelingDialog->OnCommand( "cancel" );
  1836. }
  1837. NotificationQueue_Remove( &CTFDuelRequestNotification::IsDuelRequestNotification );
  1838. }
  1839. //
  1840. // Players see this whenever a person on their server uses a name tool
  1841. //
  1842. class CTFNameItemNotification : public GCSDK::CGCClientJob
  1843. {
  1844. public:
  1845. CTFNameItemNotification( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1846. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1847. {
  1848. GCSDK::CProtoBufMsg<CMsgGCNameItemNotification> msg( pNetPacket );
  1849. if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL )
  1850. return true;
  1851. if ( !engine->IsInGame() )
  1852. return true;
  1853. CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat );
  1854. if ( pHUDChat )
  1855. {
  1856. // Player
  1857. wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH];
  1858. char szPlayerName[ MAX_PLAYER_NAME_LENGTH ];
  1859. GetPlayerNameBySteamID( msg.Body().player_steamid(), szPlayerName, sizeof( szPlayerName ) );
  1860. g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName, wszPlayerName, sizeof( wszPlayerName ) );
  1861. // Item
  1862. item_definition_index_t nDefIndex = msg.Body().item_def_index();
  1863. const GameItemDefinition_t *pItemDefinition = dynamic_cast<GameItemDefinition_t *>( GetItemSchema()->GetItemDefinition( nDefIndex ) );
  1864. if ( !pItemDefinition )
  1865. return true;
  1866. entityquality_t iItemQuality = pItemDefinition->GetQuality();
  1867. // Name
  1868. wchar_t wszCustomName[MAX_ITEM_CUSTOM_NAME_LENGTH];
  1869. g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().item_name_custom().c_str(), wszCustomName, sizeof( wszCustomName ) );
  1870. wchar_t wszItemRenamed[256];
  1871. _snwprintf( wszItemRenamed, ARRAYSIZE( wszItemRenamed ), L"%ls", g_pVGuiLocalize->Find( "#Item_Named" ) );
  1872. const char *pszQualityColorString = EconQuality_GetColorString( (EEconItemQuality)iItemQuality );
  1873. if ( pszQualityColorString )
  1874. {
  1875. pHUDChat->SetCustomColor( pszQualityColorString );
  1876. }
  1877. wchar_t wszLocalizedString[256];
  1878. g_pVGuiLocalize->ConstructString_safe( wszLocalizedString, wszItemRenamed, 3, wszPlayerName, CEconItemLocalizedFullNameGenerator( GLocalizationProvider(), pItemDefinition, iItemQuality ).GetFullName(), wszCustomName );
  1879. char szLocalized[256];
  1880. g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalizedString, szLocalized, sizeof( szLocalized ) );
  1881. pHUDChat->ChatPrintf( 0, CHAT_FILTER_SERVERMSG, "%s", szLocalized );
  1882. }
  1883. return true;
  1884. }
  1885. };
  1886. GC_REG_JOB( GCSDK::CGCClient, CTFNameItemNotification, "CTFNameItemNotification", k_EMsgGCNameItemNotification, GCSDK::k_EServerTypeGCClient );
  1887. //
  1888. // A wrapper for a generic message sent down from the GC to clients. The clients do localization
  1889. // and layout.
  1890. //
  1891. class CClientDisplayNotification : public CEconNotification
  1892. {
  1893. public:
  1894. CClientDisplayNotification( GCSDK::IMsgNetPacket *pNetPacket )
  1895. : m_msg( pNetPacket )
  1896. {
  1897. SetText( m_msg.Body().notification_body_localization_key().c_str() );
  1898. SetLifetime( 23.0f );
  1899. if ( m_msg.Body().body_substring_keys_size() == m_msg.Body().body_substring_values_size() )
  1900. {
  1901. for ( int i = 0; i < m_msg.Body().body_substring_keys_size(); i++ )
  1902. {
  1903. const char *pszSubstringKey = m_msg.Body().body_substring_keys( i ).c_str();
  1904. const char *pszSubstringValue = m_msg.Body().body_substring_values( i ).c_str();
  1905. if ( pszSubstringValue[0] == '#' )
  1906. {
  1907. AddStringToken( pszSubstringKey, GLocalizationProvider()->Find( pszSubstringValue ) );
  1908. }
  1909. else
  1910. {
  1911. CUtlConstWideString wsSubstringValue;
  1912. GLocalizationProvider()->ConvertUTF8ToLocchar( pszSubstringValue, &wsSubstringValue ) ;
  1913. AddStringToken( pszSubstringKey, wsSubstringValue.Get() );
  1914. }
  1915. }
  1916. }
  1917. }
  1918. private:
  1919. // We make a local copy of the full message because dynamic-length strings are sent down from the
  1920. // GC and we need to make sure they stay in memory until the user looks at the notification.
  1921. GCSDK::CProtoBufMsg<CMsgGCClientDisplayNotification> m_msg;
  1922. };
  1923. class CTFClientDisplayNotification : public GCSDK::CGCClientJob
  1924. {
  1925. public:
  1926. CTFClientDisplayNotification( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  1927. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  1928. {
  1929. GCSDK::CProtoBufMsg<CMsgGCClientDisplayNotification> msg( pNetPacket );
  1930. if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL )
  1931. return true;
  1932. NotificationQueue_Add( new CClientDisplayNotification( pNetPacket ) );
  1933. return true;
  1934. }
  1935. };
  1936. GC_REG_JOB( GCSDK::CGCClient, CTFClientDisplayNotification, "CTFClientDisplayNotification", k_EMsgGCClientDisplayNotification, GCSDK::k_EServerTypeGCClient );