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.

645 lines
20 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. #include "cbase.h"
  8. #include "econ_trading.h"
  9. // for messaging with the GC
  10. #include "econ_gcmessages.h"
  11. #include "econ_item_inventory.h"
  12. #include "econ_gcmessages.h"
  13. #include "econ_ui.h"
  14. // other
  15. #include "c_baseplayer.h"
  16. #include "c_playerresource.h"
  17. // UI
  18. #include "confirm_dialog.h"
  19. #include "econ_controls.h"
  20. #include "vgui/ILocalize.h"
  21. #include "econ_notifications.h"
  22. #ifdef TF_CLIENT_DLL
  23. #include "c_tf_gamestats.h"
  24. #endif
  25. #include "gc_clientsystem.h"
  26. // memdbgon must be the last include file in a .cpp file!!!
  27. #include <tier0/memdbgon.h>
  28. //-----------------------------------------------------------------------------
  29. const char *g_FriendRelationship[] =
  30. {
  31. "none"
  32. "blocked",
  33. "request_recipient",
  34. "friend",
  35. "request_initiator",
  36. "ignored",
  37. "ignored_friend"
  38. };
  39. const char *g_RejectedReasons[] =
  40. {
  41. "accepted",
  42. "declined",
  43. "vac_banned_initiator",
  44. "vac_banned_target",
  45. "target_already_trading",
  46. "trading_disabled",
  47. "user_not_logged_in"
  48. };
  49. const char *g_ClosedReasons[] =
  50. {
  51. "traded",
  52. "canceled",
  53. "error",
  54. "does_not_own_items",
  55. "untradable_items",
  56. "no_items",
  57. "trading_disabled"
  58. };
  59. enum
  60. {
  61. kShowTradeRequestsFrom_FriendsOnly = 1,
  62. kShowTradeRequestsFrom_FriendsAndCurrentServer = 2,
  63. kShowTradeRequestsFrom_Anyone = 3,
  64. kShowTradeRequestsFrom_NoOne = 4,
  65. };
  66. ConVar cl_trading_show_requests_from( "cl_trading_show_requests_from", "3", FCVAR_ARCHIVE, "View trade requests from a certain group only." );
  67. static bool sbTestingSelfTrade = false;
  68. static int iTradeRequests = 0;
  69. static int iTradeAttempts = 0;
  70. static int iTradeOffers = 0;
  71. static int iGiftsGiven = 0;
  72. const uint32 kTradeRequestLifetime = 30.0f;
  73. // waiting dialog
  74. class CTradingWaitDialog : public CGenericWaitingDialog
  75. {
  76. public:
  77. CTradingWaitDialog( const char *pText = "#TF_Trading_Timeout_Text", const wchar_t *pPlayerName = NULL )
  78. : CGenericWaitingDialog( NULL )
  79. , m_pText( pText )
  80. , m_pKeyValues( NULL )
  81. {
  82. if ( pPlayerName != NULL && wcsicmp( pPlayerName, L"" ) != 0 )
  83. {
  84. m_pKeyValues = new KeyValues( "CTradingWaitDialog" );
  85. m_pKeyValues->SetWString( "other_player", pPlayerName );
  86. }
  87. }
  88. virtual ~CTradingWaitDialog()
  89. {
  90. if ( m_pKeyValues != NULL )
  91. {
  92. m_pKeyValues->deleteThis();
  93. }
  94. }
  95. protected:
  96. virtual void OnTimeout()
  97. {
  98. ShowMessageBox( "#TF_Trading_Timeout_Title", m_pText, m_pKeyValues, "#GameUI_OK" );
  99. }
  100. virtual void OnUserClose()
  101. {
  102. GCSDK::CGCMsg< MsgGCTrading_CancelSession_t > msg( k_EMsgGCTrading_CancelSession );
  103. GCClientSystem()->BSendMessage( msg );
  104. // @note not sure we need to wait for the GC here, but we will just in case...
  105. ShowWaitingDialog( new CTradingWaitDialog(), "#TF_Trading_WaitingForCancel", true, false, 20.0f );
  106. }
  107. KeyValues *m_pKeyValues;
  108. const char* m_pText;
  109. };
  110. // used by the waiting dialogs
  111. static void TradeCompleteDialogClosed( bool bConfirmed, void *pContext )
  112. {
  113. if ( bConfirmed )
  114. {
  115. InventoryManager()->ShowItemsPickedUp( true );
  116. }
  117. }
  118. //-----------------------------------------------------------------------------
  119. // jobs
  120. class CTFTradeRequestNotification : public CEconNotification
  121. {
  122. public:
  123. CTFTradeRequestNotification( uint64 ulInitiatorSteamID, uint32 unTradeRequestID, const char* pPlayerName )
  124. : CEconNotification()
  125. , m_unTradeRequestID( unTradeRequestID )
  126. {
  127. SetSteamID( ulInitiatorSteamID );
  128. g_pVGuiLocalize->ConvertANSIToUnicode( pPlayerName, m_wszPlayerName, sizeof(m_wszPlayerName) );
  129. SetLifetime( kTradeRequestLifetime );
  130. SetText( "#TF_Trading_JoinText" );
  131. AddStringToken( "initiator", m_wszPlayerName );
  132. }
  133. virtual EType NotificationType() { return eType_AcceptDecline; }
  134. /// XXX(JohnS): Dead code? Was always accept/decline AFAICT
  135. virtual void Trigger()
  136. {
  137. CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_Trading_JoinTitle", "#TF_Trading_JoinText", "#GameUI_OK", "#TF_Trading_JoinCancel", &ConfirmJoinTradeSession );
  138. pDialog->SetContext( this );
  139. pDialog->AddStringToken( "initiator", m_wszPlayerName );
  140. // so we aren't deleted
  141. SetIsInUse( true );
  142. }
  143. virtual void Accept()
  144. {
  145. ConfirmJoinTradeSession( true, this );
  146. }
  147. virtual void Decline()
  148. {
  149. ConfirmJoinTradeSession( false, this );
  150. }
  151. static void ConfirmJoinTradeSession( bool bConfirmed, void *pContext )
  152. {
  153. CTFTradeRequestNotification *pNotification = (CTFTradeRequestNotification*)pContext;
  154. GCSDK::CGCMsg< MsgGCTrading_InitiateTradeResponse_t > msg( k_EMsgGCTrading_InitiateTradeResponse );
  155. msg.Body().m_eResponse = bConfirmed ? k_EGCMsgInitiateTradeResponse_Accepted : k_EGCMsgInitiateTradeResponse_Declined;
  156. msg.Body().m_unTradeRequestID = pNotification->m_unTradeRequestID;
  157. GCClientSystem()->BSendMessage( msg );
  158. if ( bConfirmed )
  159. {
  160. ShowWaitingDialog( new CTradingWaitDialog(), "#TF_Trading_WaitingForServer", true, false, kTradeRequestLifetime );
  161. }
  162. // now we can be deleted
  163. pNotification->SetIsInUse( false );
  164. pNotification->MarkForDeletion();
  165. }
  166. static bool IsTradingNotification( CEconNotification * pNotification )
  167. {
  168. return dynamic_cast< CTFTradeRequestNotification * >( pNotification ) != NULL;
  169. }
  170. public:
  171. uint32 m_unTradeRequestID;
  172. wchar_t m_wszPlayerName[MAX_PLAYER_NAME_LENGTH];
  173. };
  174. // request from party A (through GC) to start trading
  175. class CGCTrading_InitiateTradeRequest : public GCSDK::CGCClientJob
  176. {
  177. public:
  178. CGCTrading_InitiateTradeRequest( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  179. void SendDeclinedMessage( uint32 unTradeRequestID )
  180. {
  181. GCSDK::CGCMsg< MsgGCTrading_InitiateTradeResponse_t > msgResponse( k_EMsgGCTrading_InitiateTradeResponse );
  182. msgResponse.Body().m_eResponse = k_EGCMsgInitiateTradeResponse_Declined;
  183. msgResponse.Body().m_unTradeRequestID = unTradeRequestID;
  184. GCClientSystem()->BSendMessage( msgResponse );
  185. }
  186. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  187. {
  188. GCSDK::CGCMsg<MsgGCTrading_InitiateTradeRequest_t> msg( pNetPacket );
  189. CUtlString playerName;
  190. msg.BReadStr( &playerName );
  191. if ( sbTestingSelfTrade )
  192. {
  193. CloseWaitingDialog();
  194. }
  195. else if ( msg.Body().m_ulOtherSteamID == Trading_GetLocalPlayerSteamID().ConvertToUint64() )
  196. {
  197. CloseWaitingDialog();
  198. }
  199. iTradeRequests++;
  200. #ifdef TF_CLIENT_DLL
  201. C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_RECEIVED, msg.Body().m_ulOtherSteamID, iTradeRequests );
  202. #endif
  203. // auto-decline for ignored or blocked peoples
  204. if ( steamapicontext == NULL || steamapicontext->SteamFriends() == NULL )
  205. {
  206. return true;
  207. }
  208. CSteamID steamIDOther( msg.Body().m_ulOtherSteamID );
  209. EFriendRelationship eRelationship = steamapicontext->SteamFriends()->GetFriendRelationship( steamIDOther );
  210. switch ( eRelationship )
  211. {
  212. case k_EFriendRelationshipBlocked:
  213. case k_EFriendRelationshipIgnored:
  214. case k_EFriendRelationshipIgnoredFriend:
  215. {
  216. SendDeclinedMessage( msg.Body().m_unTradeRequestID );
  217. return true;
  218. }
  219. break;
  220. } // switch
  221. switch ( cl_trading_show_requests_from.GetInt() )
  222. {
  223. case kShowTradeRequestsFrom_FriendsOnly:
  224. {
  225. if ( eRelationship != k_EFriendRelationshipFriend )
  226. {
  227. SendDeclinedMessage( msg.Body().m_unTradeRequestID );
  228. return true;
  229. }
  230. }
  231. break;
  232. case kShowTradeRequestsFrom_FriendsAndCurrentServer:
  233. {
  234. if ( eRelationship == k_EFriendRelationshipFriend )
  235. break;
  236. bool bInCurrentGame = false;
  237. if ( engine->IsInGame() )
  238. {
  239. // otherwise, test if they are in the current game
  240. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  241. {
  242. if ( g_PR && g_PR->IsConnected( iPlayerIndex ) )
  243. {
  244. player_info_t pi;
  245. if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
  246. continue;
  247. if ( !pi.friendsID )
  248. continue;
  249. CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
  250. if ( steamID == steamIDOther )
  251. {
  252. bInCurrentGame = true;
  253. break;
  254. }
  255. }
  256. }
  257. }
  258. if ( bInCurrentGame == false )
  259. {
  260. SendDeclinedMessage( msg.Body().m_unTradeRequestID );
  261. return true;
  262. }
  263. }
  264. break;
  265. case kShowTradeRequestsFrom_Anyone:
  266. {
  267. // nothing to check
  268. }
  269. break;
  270. case kShowTradeRequestsFrom_NoOne:
  271. {
  272. SendDeclinedMessage( msg.Body().m_unTradeRequestID );
  273. return true;
  274. }
  275. break;
  276. }
  277. NotificationQueue_Add( new CTFTradeRequestNotification( msg.Body().m_ulOtherSteamID, msg.Body().m_unTradeRequestID, playerName.Get() ) );
  278. return true;
  279. }
  280. protected:
  281. };
  282. GC_REG_JOB( GCSDK::CGCClient, CGCTrading_InitiateTradeRequest, "CGCTrading_InitiateTradeRequest", k_EMsgGCTrading_InitiateTradeRequest, GCSDK::k_EServerTypeGCClient );
  283. #ifdef _DEBUG
  284. #ifdef CLIENT_DLL
  285. CON_COMMAND( cl_trading_test, "Tests the trade ui notification." )
  286. {
  287. if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL )
  288. return;
  289. CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
  290. NotificationQueue_Add( new CTFTradeRequestNotification( steamID.ConvertToUint64(), steamID.ConvertToUint64(), "Biff" ) );
  291. }
  292. #endif
  293. #endif // _DEBUG
  294. /**
  295. * Remove notification that matches the trade request
  296. */
  297. class CEconNotificationVisitor_RemoveTradeRequest : public CEconNotificationVisitor
  298. {
  299. public:
  300. CEconNotificationVisitor_RemoveTradeRequest( uint32 unTradeRequestID ) : m_unTradeRequestID( unTradeRequestID ) {}
  301. virtual void Visit( CEconNotification &notification )
  302. {
  303. if ( CTFTradeRequestNotification::IsTradingNotification( &notification ) )
  304. {
  305. CTFTradeRequestNotification &tradeNotification = dynamic_cast< CTFTradeRequestNotification& >( notification );
  306. if ( tradeNotification.m_unTradeRequestID == m_unTradeRequestID )
  307. {
  308. tradeNotification.MarkForDeletion();
  309. }
  310. }
  311. }
  312. private:
  313. uint32 m_unTradeRequestID;
  314. };
  315. // GC notification of trade request status
  316. static const char *g_pszTradeResponseDescLocKeys[] =
  317. {
  318. "#TF_Trading_BusyText", // k_EGCMsgInitiateTradeResponse_Accepted (should never be used!)
  319. "#TF_Trading_DeclinedText", // k_EGCMsgInitiateTradeResponse_Declined
  320. "#TF_Trading_VACBannedText", // k_EGCMsgInitiateTradeResponse_VAC_Banned_Initiator
  321. "#TF_Trading_VACBanned2Text", // k_EGCMsgInitiateTradeResponse_VAC_Banned_Target
  322. "#TF_Trading_BusyText", // k_EGCMsgInitiateTradeResponse_Target_Already_Trading
  323. "#TF_Trading_DisabledText", // k_EGCMsgInitiateTradeResponse_Disabled
  324. "#TF_Trading_NotLoggedIn", // k_EGCMsgInitiateTradeResponse_NotLoggedIn
  325. "#TF_Trading_BusyText", // k_EGCMsgInitiateTradeResponse_Cancel (should never be used!)
  326. "#TF_Trading_TooSoon", // k_EGCMsgInitiateTradeResponse_TooSoon
  327. "#TF_Trading_TooSoonPenalty", // k_EGCMsgInitiateTradeResponse_TooSoonPenalty
  328. "#TF_Trading_TradeBannedText", // (was k_EGCMsgInitiateTradeResponse_Free_Account_Initiator_DEPRECATED)
  329. "#TF_Trading_TradeBanned2Text", // k_EGCMsgInitiateTradeResponse_Trade_Banned_Target
  330. "#TF_Trading_FreeAccountInitiate", // k_EGCMsgInitiateTradeResponse_Free_Account_Initiator
  331. "#TF_Trading_SharedAccountInitiate", // k_EGCMsgInitiateTradeResponse_Shared_Account_Initiator
  332. "#TF_Trading_Service_Unavailable", // k_EGCMsgInitiateTradeResponse_Service_Unavailable
  333. "#TF_Trading_YouBlockedThem", // k_EGCMsgInitiateTradeResponse_Target_Blocked
  334. "#TF_Trading_NeedVerifiedEmail", // k_EGCMsgInitiateTradeResponse_NeedVerifiedEmail
  335. "#TF_Trading_NeedSteamGuard", // k_EGCMsgInitiateTradeResponse_NeedSteamGuard
  336. "#TF_Trading_SteamGuardDuration", // k_EGCMsgInitiateTradeResponse_SteamGuardDuration
  337. "#TF_Trading_TheyCannotTrade", // k_EGCMsgInitiateTradeResponse_TheyCannotTrade
  338. "#TF_Trading_PasswordChanged", // k_EGCMsgInitiateTradeResponse_Recent_Password_Reset = 20,
  339. "#TF_Trading_NewDevice", // k_EGCMsgInitiateTradeResponse_Using_New_Device = 21,
  340. "#TF_Trading_InvalidCookie" // k_EGCMsgInitiateTradeResponse_Sent_Invalid_Cookie = 22,
  341. };
  342. class CGCTrading_InitiateTradeResponse : public GCSDK::CGCClientJob
  343. {
  344. public:
  345. CGCTrading_InitiateTradeResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  346. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  347. {
  348. // If this assertion fails it probably means you added a new value to EGCMsgInitiateTradeResponse
  349. // but didn't add a string to the array that tracks the user-facing response strings.
  350. Assert( ARRAYSIZE( g_pszTradeResponseDescLocKeys ) == k_EGCMsgInitiateTradeResponse_Count );
  351. CloseWaitingDialog();
  352. GCSDK::CGCMsg<MsgGCTrading_InitiateTradeResponse_t> msg( pNetPacket );
  353. const uint32 eResponse = msg.Body().m_eResponse;
  354. switch ( eResponse )
  355. {
  356. case k_EGCMsgInitiateTradeResponse_Accepted:
  357. #ifdef TF_CLIENT_DLL
  358. C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_ACCEPTED, iTradeRequests, g_RejectedReasons[msg.Body().m_eResponse] );
  359. #endif
  360. ShowWaitingDialog( new CTradingWaitDialog(), "#TF_Trading_WaitingForStart", true, false, 30.0f );
  361. return true; // !
  362. case k_EGCMsgInitiateTradeResponse_Cancel:
  363. {
  364. CEconNotificationVisitor_RemoveTradeRequest visitor( msg.Body().m_unTradeRequestID );
  365. NotificationQueue_Visit( visitor );
  366. break;
  367. }
  368. case k_EGCMsgInitiateTradeResponse_Declined:
  369. case k_EGCMsgInitiateTradeResponse_VAC_Banned_Initiator:
  370. case k_EGCMsgInitiateTradeResponse_VAC_Banned_Target:
  371. case k_EGCMsgInitiateTradeResponse_Target_Already_Trading:
  372. case k_EGCMsgInitiateTradeResponse_Disabled:
  373. case k_EGCMsgInitiateTradeResponse_NotLoggedIn:
  374. case k_EGCMsgInitiateTradeResponse_TooSoon:
  375. case k_EGCMsgInitiateTradeResponse_TooSoonPenalty:
  376. case k_EGCMsgInitiateTradeResponse_Trade_Banned_Initiator:
  377. case k_EGCMsgInitiateTradeResponse_Trade_Banned_Target:
  378. case k_EGCMsgInitiateTradeResponse_Shared_Account_Initiator:
  379. case k_EGCMsgInitiateTradeResponse_Service_Unavailable:
  380. case k_EGCMsgInitiateTradeResponse_Target_Blocked:
  381. case k_EGCMsgInitiateTradeResponse_NeedVerifiedEmail:
  382. case k_EGCMsgInitiateTradeResponse_NeedSteamGuard:
  383. case k_EGCMsgInitiateTradeResponse_TheyCannotTrade:
  384. case k_EGCMsgInitiateTradeResponse_Recent_Password_Reset:
  385. case k_EGCMsgInitiateTradeResponse_Using_New_Device:
  386. case k_EGCMsgInitiateTradeResponse_Sent_Invalid_Cookie:
  387. ShowMessageBox( "#TF_Trading_StatusTitle", g_pszTradeResponseDescLocKeys[eResponse], "#GameUI_OK" );
  388. break;
  389. case k_EGCMsgInitiateTradeResponse_SteamGuardDuration:
  390. {
  391. KeyValuesAD kvTokens( "CTradingWaitDialog" );
  392. kvTokens->SetWString( "days", L"15" ); // Ideally this would come from the GC, which would get the value from Steam
  393. ShowMessageBox( "#TF_Trading_StatusTitle", g_pszTradeResponseDescLocKeys[eResponse], kvTokens, "#GameUI_OK" );
  394. break;
  395. }
  396. default:
  397. #ifdef TF_CLIENT_DLL
  398. C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_REJECTED, iTradeRequests, "unknown" );
  399. #endif
  400. ShowMessageBox( "#TF_Trading_StatusTitle", "#TF_Trading_BusyText", "#GameUI_OK" );
  401. return true;
  402. } // switch
  403. #ifdef TF_CLIENT_DLL
  404. C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_REJECTED, iTradeRequests, g_RejectedReasons[msg.Body().m_eResponse] );
  405. #endif
  406. return true;
  407. }
  408. };
  409. GC_REG_JOB( GCSDK::CGCClient, CGCTrading_InitiateTradeResponse, "CGCTrading_InitiateTradeResponse", k_EMsgGCTrading_InitiateTradeResponse, GCSDK::k_EServerTypeGCClient );
  410. // start trading session
  411. class CGCTrading_StartSession : public GCSDK::CGCClientJob
  412. {
  413. public:
  414. CGCTrading_StartSession( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  415. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  416. {
  417. GCSDK::CGCMsg<MsgGCTrading_StartSession_t> msg( pNetPacket );
  418. steamapicontext->SteamFriends()->ActivateGameOverlayToUser( "jointrade", msg.Body().m_ulSteamIDPartyB );
  419. CloseWaitingDialog();
  420. // remove all trading notifications
  421. NotificationQueue_Remove( &CTFTradeRequestNotification::IsTradingNotification );
  422. return true;
  423. }
  424. };
  425. GC_REG_JOB( GCSDK::CGCClient, CGCTrading_StartSession, "CGCTrading_StartSession", k_EMsgGCTrading_StartSession, GCSDK::k_EServerTypeGCClient );
  426. //-----------------------------------------------------------------------------
  427. // External interface
  428. CSteamID Trading_GetLocalPlayerSteamID()
  429. {
  430. if ( steamapicontext && steamapicontext->SteamUser() )
  431. {
  432. return steamapicontext->SteamUser()->GetSteamID();
  433. }
  434. return CSteamID();
  435. }
  436. void Trading_RequestTrade( int iPlayerIdx )
  437. {
  438. CSteamID steamID;
  439. C_BasePlayer *pPlayer = ToBasePlayer( UTIL_PlayerByIndex( iPlayerIdx ) );
  440. if ( pPlayer && pPlayer->GetSteamID( &steamID ) )
  441. {
  442. Trading_RequestTrade( steamID );
  443. }
  444. }
  445. void Trading_RequestTrade( const CSteamID &steamID )
  446. {
  447. sbTestingSelfTrade = false;
  448. GCSDK::CGCMsg< MsgGCTrading_InitiateTradeRequest_t > msg( k_EMsgGCTrading_InitiateTradeRequest );
  449. msg.Body().m_ulOtherSteamID = steamID.ConvertToUint64();
  450. bool bSent = GCClientSystem()->BSendMessage( msg );
  451. if ( bSent )
  452. {
  453. iTradeRequests++;
  454. #ifdef TF_CLIENT_DLL
  455. C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_SENT, msg.Body().m_ulOtherSteamID, iTradeRequests );
  456. #endif
  457. const char* pPlayerName = InventoryManager()->PersonaName_Get( steamID.GetAccountID() );
  458. if ( pPlayerName != NULL && FStrEq( pPlayerName, "" ) == false )
  459. {
  460. wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH];
  461. g_pVGuiLocalize->ConvertANSIToUnicode( pPlayerName, wszPlayerName, sizeof(wszPlayerName) );
  462. CTradingWaitDialog *pDialog = new CTradingWaitDialog( "#TF_Trading_TimeoutPartyB_Named", wszPlayerName );
  463. ShowWaitingDialog( pDialog, "#TF_Trading_WaitingForPartyB", true, true, 30.0f );
  464. wchar_t wszConstructedString[1024];
  465. g_pVGuiLocalize->ConstructString_safe( wszConstructedString, g_pVGuiLocalize->Find( "#TF_Trading_WaitingForPartyB_Named" ), 1, wszPlayerName );
  466. pDialog->SetDialogVariable( "updatetext", wszConstructedString );
  467. }
  468. else
  469. {
  470. CTradingWaitDialog *pDialog = new CTradingWaitDialog( "#TF_Trading_TimeoutPartyB" );
  471. ShowWaitingDialog( pDialog, "#TF_Trading_WaitingForPartyB", true, true, 30.0f );
  472. }
  473. }
  474. }
  475. const char* UniverseToCommunityURL( EUniverse universe )
  476. {
  477. switch( universe )
  478. {
  479. default: // return public if we don't have a better guess.
  480. case k_EUniversePublic: return "https://steamcommunity.com";
  481. case k_EUniverseBeta: return "https://beta.steamcommunity.com";
  482. case k_EUniverseDev: return "https://localhost/community";
  483. }
  484. // Should never get here.
  485. return UniverseToCommunityURL( k_EUniversePublic );
  486. }
  487. const char* GetCommunityURL()
  488. {
  489. if ( GetUniverse() == k_EUniverseInvalid )
  490. {
  491. Assert( !"calling GetCommunityURL when not connected. This is allowed, but will return public universe." );
  492. return UniverseToCommunityURL( k_EUniversePublic );
  493. }
  494. return UniverseToCommunityURL( GetUniverse() );
  495. }
  496. void Trading_SendGift( const CSteamID& steamID, const CEconItemView& giftItem )
  497. {
  498. if ( !steamapicontext || !steamapicontext->SteamFriends() )
  499. {
  500. // TODO: Error dialog.
  501. return;
  502. }
  503. #ifdef TF_CLIENT_DLL
  504. C_CTF_GameStats.Event_Trading( IE_TRADING_ITEM_GIFTED, steamID.ConvertToUint64(), iGiftsGiven );
  505. #endif
  506. // Build up the steam URL and send it over.
  507. // Should look like this: https://steamcommunity.com/trade/1/sendgift/?appid=&contextid=&assetid=&steamid_target=
  508. steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage(
  509. CFmtStrMax( "%s/trade/1/sendgift/?appid=%d&contextid=%d&assetid=%llu&steamid_target=%llu",
  510. GetCommunityURL(),
  511. engine->GetAppID(),
  512. 2, // k_EEconContextBackpack
  513. giftItem.GetItemID(),
  514. steamID.ConvertToUint64()
  515. )
  516. );
  517. }
  518. CON_COMMAND( cl_trade, "Trade with a person by player name" )
  519. {
  520. if ( args.ArgC() < 2 )
  521. return;
  522. if ( GetUniverse() == k_EUniverseInvalid )
  523. return;
  524. int iLocalPlayerIndex = GetLocalPlayerIndex();
  525. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  526. {
  527. if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) )
  528. {
  529. player_info_t pi;
  530. if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
  531. continue;
  532. if ( !pi.friendsID )
  533. continue;
  534. if ( FStrEq( pi.name, args[1] ) == false )
  535. continue;
  536. CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
  537. Trading_RequestTrade( steamID );
  538. return;
  539. }
  540. }
  541. }
  542. CON_COMMAND( cl_trade_steamid, "Trade with a person by steam id" )
  543. {
  544. if ( args.ArgC() < 2 )
  545. return;
  546. if ( GetUniverse() == k_EUniverseInvalid )
  547. return;
  548. const char *pInput = args[1];
  549. if ( pInput[0] >= '0' && pInput[0] <= '9' )
  550. {
  551. CSteamID steamID;
  552. steamID.SetFromString( pInput, GetUniverse() );
  553. if ( steamID.IsValid() )
  554. Trading_RequestTrade( steamID );
  555. }
  556. }
  557. #ifdef _DEBUG
  558. CON_COMMAND( cl_trading_test_self_trade, "Test self-trading" )
  559. {
  560. Trading_RequestTrade( Trading_GetLocalPlayerSteamID() );
  561. sbTestingSelfTrade = true;
  562. }
  563. #endif