summaryrefslogtreecommitdiff
path: root/game/client/econ/econ_trading.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/econ/econ_trading.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/client/econ/econ_trading.cpp')
-rw-r--r--game/client/econ/econ_trading.cpp645
1 files changed, 645 insertions, 0 deletions
diff --git a/game/client/econ/econ_trading.cpp b/game/client/econ/econ_trading.cpp
new file mode 100644
index 0000000..3fb532b
--- /dev/null
+++ b/game/client/econ/econ_trading.cpp
@@ -0,0 +1,645 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+#include "cbase.h"
+
+#include "econ_trading.h"
+
+// for messaging with the GC
+#include "econ_gcmessages.h"
+#include "econ_item_inventory.h"
+#include "econ_gcmessages.h"
+#include "econ_ui.h"
+
+// other
+#include "c_baseplayer.h"
+#include "c_playerresource.h"
+
+// UI
+#include "confirm_dialog.h"
+#include "econ_controls.h"
+#include "vgui/ILocalize.h"
+#include "econ_notifications.h"
+
+#ifdef TF_CLIENT_DLL
+#include "c_tf_gamestats.h"
+#endif
+
+#include "gc_clientsystem.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+
+const char *g_FriendRelationship[] =
+{
+ "none"
+ "blocked",
+ "request_recipient",
+ "friend",
+ "request_initiator",
+ "ignored",
+ "ignored_friend"
+};
+
+const char *g_RejectedReasons[] =
+{
+ "accepted",
+ "declined",
+ "vac_banned_initiator",
+ "vac_banned_target",
+ "target_already_trading",
+ "trading_disabled",
+ "user_not_logged_in"
+};
+
+const char *g_ClosedReasons[] =
+{
+ "traded",
+ "canceled",
+ "error",
+ "does_not_own_items",
+ "untradable_items",
+ "no_items",
+ "trading_disabled"
+};
+
+enum
+{
+ kShowTradeRequestsFrom_FriendsOnly = 1,
+ kShowTradeRequestsFrom_FriendsAndCurrentServer = 2,
+ kShowTradeRequestsFrom_Anyone = 3,
+ kShowTradeRequestsFrom_NoOne = 4,
+};
+
+ConVar cl_trading_show_requests_from( "cl_trading_show_requests_from", "3", FCVAR_ARCHIVE, "View trade requests from a certain group only." );
+
+static bool sbTestingSelfTrade = false;
+
+static int iTradeRequests = 0;
+static int iTradeAttempts = 0;
+static int iTradeOffers = 0;
+static int iGiftsGiven = 0;
+
+const uint32 kTradeRequestLifetime = 30.0f;
+
+// waiting dialog
+class CTradingWaitDialog : public CGenericWaitingDialog
+{
+public:
+ CTradingWaitDialog( const char *pText = "#TF_Trading_Timeout_Text", const wchar_t *pPlayerName = NULL )
+ : CGenericWaitingDialog( NULL )
+ , m_pText( pText )
+ , m_pKeyValues( NULL )
+ {
+ if ( pPlayerName != NULL && wcsicmp( pPlayerName, L"" ) != 0 )
+ {
+ m_pKeyValues = new KeyValues( "CTradingWaitDialog" );
+ m_pKeyValues->SetWString( "other_player", pPlayerName );
+ }
+ }
+
+ virtual ~CTradingWaitDialog()
+ {
+ if ( m_pKeyValues != NULL )
+ {
+ m_pKeyValues->deleteThis();
+ }
+ }
+
+protected:
+ virtual void OnTimeout()
+ {
+ ShowMessageBox( "#TF_Trading_Timeout_Title", m_pText, m_pKeyValues, "#GameUI_OK" );
+ }
+
+ virtual void OnUserClose()
+ {
+ GCSDK::CGCMsg< MsgGCTrading_CancelSession_t > msg( k_EMsgGCTrading_CancelSession );
+ GCClientSystem()->BSendMessage( msg );
+ // @note not sure we need to wait for the GC here, but we will just in case...
+ ShowWaitingDialog( new CTradingWaitDialog(), "#TF_Trading_WaitingForCancel", true, false, 20.0f );
+ }
+
+ KeyValues *m_pKeyValues;
+ const char* m_pText;
+};
+
+// used by the waiting dialogs
+static void TradeCompleteDialogClosed( bool bConfirmed, void *pContext )
+{
+ if ( bConfirmed )
+ {
+ InventoryManager()->ShowItemsPickedUp( true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// jobs
+class CTFTradeRequestNotification : public CEconNotification
+{
+public:
+ CTFTradeRequestNotification( uint64 ulInitiatorSteamID, uint32 unTradeRequestID, const char* pPlayerName )
+ : CEconNotification()
+ , m_unTradeRequestID( unTradeRequestID )
+ {
+ SetSteamID( ulInitiatorSteamID );
+ g_pVGuiLocalize->ConvertANSIToUnicode( pPlayerName, m_wszPlayerName, sizeof(m_wszPlayerName) );
+ SetLifetime( kTradeRequestLifetime );
+ SetText( "#TF_Trading_JoinText" );
+ AddStringToken( "initiator", m_wszPlayerName );
+ }
+
+ virtual EType NotificationType() { return eType_AcceptDecline; }
+
+ /// XXX(JohnS): Dead code? Was always accept/decline AFAICT
+ virtual void Trigger()
+ {
+ CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_Trading_JoinTitle", "#TF_Trading_JoinText", "#GameUI_OK", "#TF_Trading_JoinCancel", &ConfirmJoinTradeSession );
+ pDialog->SetContext( this );
+ pDialog->AddStringToken( "initiator", m_wszPlayerName );
+ // so we aren't deleted
+ SetIsInUse( true );
+ }
+
+ virtual void Accept()
+ {
+ ConfirmJoinTradeSession( true, this );
+ }
+ virtual void Decline()
+ {
+ ConfirmJoinTradeSession( false, this );
+ }
+ static void ConfirmJoinTradeSession( bool bConfirmed, void *pContext )
+ {
+ CTFTradeRequestNotification *pNotification = (CTFTradeRequestNotification*)pContext;
+ GCSDK::CGCMsg< MsgGCTrading_InitiateTradeResponse_t > msg( k_EMsgGCTrading_InitiateTradeResponse );
+ msg.Body().m_eResponse = bConfirmed ? k_EGCMsgInitiateTradeResponse_Accepted : k_EGCMsgInitiateTradeResponse_Declined;
+ msg.Body().m_unTradeRequestID = pNotification->m_unTradeRequestID;
+ GCClientSystem()->BSendMessage( msg );
+ if ( bConfirmed )
+ {
+ ShowWaitingDialog( new CTradingWaitDialog(), "#TF_Trading_WaitingForServer", true, false, kTradeRequestLifetime );
+ }
+ // now we can be deleted
+ pNotification->SetIsInUse( false );
+ pNotification->MarkForDeletion();
+ }
+
+ static bool IsTradingNotification( CEconNotification * pNotification )
+ {
+ return dynamic_cast< CTFTradeRequestNotification * >( pNotification ) != NULL;
+ }
+
+public:
+ uint32 m_unTradeRequestID;
+ wchar_t m_wszPlayerName[MAX_PLAYER_NAME_LENGTH];
+};
+
+// request from party A (through GC) to start trading
+class CGCTrading_InitiateTradeRequest : public GCSDK::CGCClientJob
+{
+public:
+ CGCTrading_InitiateTradeRequest( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+
+ void SendDeclinedMessage( uint32 unTradeRequestID )
+ {
+ GCSDK::CGCMsg< MsgGCTrading_InitiateTradeResponse_t > msgResponse( k_EMsgGCTrading_InitiateTradeResponse );
+ msgResponse.Body().m_eResponse = k_EGCMsgInitiateTradeResponse_Declined;
+ msgResponse.Body().m_unTradeRequestID = unTradeRequestID;
+ GCClientSystem()->BSendMessage( msgResponse );
+ }
+
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCTrading_InitiateTradeRequest_t> msg( pNetPacket );
+ CUtlString playerName;
+ msg.BReadStr( &playerName );
+
+ if ( sbTestingSelfTrade )
+ {
+ CloseWaitingDialog();
+ }
+ else if ( msg.Body().m_ulOtherSteamID == Trading_GetLocalPlayerSteamID().ConvertToUint64() )
+ {
+ CloseWaitingDialog();
+ }
+
+ iTradeRequests++;
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_RECEIVED, msg.Body().m_ulOtherSteamID, iTradeRequests );
+#endif
+
+ // auto-decline for ignored or blocked peoples
+ if ( steamapicontext == NULL || steamapicontext->SteamFriends() == NULL )
+ {
+ return true;
+ }
+ CSteamID steamIDOther( msg.Body().m_ulOtherSteamID );
+ EFriendRelationship eRelationship = steamapicontext->SteamFriends()->GetFriendRelationship( steamIDOther );
+ switch ( eRelationship )
+ {
+ case k_EFriendRelationshipBlocked:
+ case k_EFriendRelationshipIgnored:
+ case k_EFriendRelationshipIgnoredFriend:
+ {
+ SendDeclinedMessage( msg.Body().m_unTradeRequestID );
+ return true;
+ }
+ break;
+ } // switch
+
+ switch ( cl_trading_show_requests_from.GetInt() )
+ {
+ case kShowTradeRequestsFrom_FriendsOnly:
+ {
+ if ( eRelationship != k_EFriendRelationshipFriend )
+ {
+ SendDeclinedMessage( msg.Body().m_unTradeRequestID );
+ return true;
+ }
+ }
+ break;
+ case kShowTradeRequestsFrom_FriendsAndCurrentServer:
+ {
+ if ( eRelationship == k_EFriendRelationshipFriend )
+ break;
+ bool bInCurrentGame = false;
+ if ( engine->IsInGame() )
+ {
+ // otherwise, test if they are in the current game
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ if ( g_PR && g_PR->IsConnected( iPlayerIndex ) )
+ {
+ player_info_t pi;
+ if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
+ continue;
+ if ( !pi.friendsID )
+ continue;
+
+ CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
+ if ( steamID == steamIDOther )
+ {
+ bInCurrentGame = true;
+ break;
+ }
+ }
+ }
+ }
+ if ( bInCurrentGame == false )
+ {
+ SendDeclinedMessage( msg.Body().m_unTradeRequestID );
+ return true;
+ }
+ }
+ break;
+ case kShowTradeRequestsFrom_Anyone:
+ {
+ // nothing to check
+ }
+ break;
+ case kShowTradeRequestsFrom_NoOne:
+ {
+ SendDeclinedMessage( msg.Body().m_unTradeRequestID );
+ return true;
+ }
+ break;
+ }
+
+ NotificationQueue_Add( new CTFTradeRequestNotification( msg.Body().m_ulOtherSteamID, msg.Body().m_unTradeRequestID, playerName.Get() ) );
+ return true;
+ }
+protected:
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCTrading_InitiateTradeRequest, "CGCTrading_InitiateTradeRequest", k_EMsgGCTrading_InitiateTradeRequest, GCSDK::k_EServerTypeGCClient );
+
+#ifdef _DEBUG
+#ifdef CLIENT_DLL
+CON_COMMAND( cl_trading_test, "Tests the trade ui notification." )
+{
+ if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL )
+ return;
+
+ CSteamID steamID = steamapicontext->SteamUser()->GetSteamID();
+ NotificationQueue_Add( new CTFTradeRequestNotification( steamID.ConvertToUint64(), steamID.ConvertToUint64(), "Biff" ) );
+}
+#endif
+#endif // _DEBUG
+
+/**
+ * Remove notification that matches the trade request
+ */
+class CEconNotificationVisitor_RemoveTradeRequest : public CEconNotificationVisitor
+{
+public:
+ CEconNotificationVisitor_RemoveTradeRequest( uint32 unTradeRequestID ) : m_unTradeRequestID( unTradeRequestID ) {}
+ virtual void Visit( CEconNotification &notification )
+ {
+ if ( CTFTradeRequestNotification::IsTradingNotification( &notification ) )
+ {
+ CTFTradeRequestNotification &tradeNotification = dynamic_cast< CTFTradeRequestNotification& >( notification );
+ if ( tradeNotification.m_unTradeRequestID == m_unTradeRequestID )
+ {
+ tradeNotification.MarkForDeletion();
+ }
+ }
+ }
+private:
+ uint32 m_unTradeRequestID;
+};
+
+// GC notification of trade request status
+static const char *g_pszTradeResponseDescLocKeys[] =
+{
+ "#TF_Trading_BusyText", // k_EGCMsgInitiateTradeResponse_Accepted (should never be used!)
+ "#TF_Trading_DeclinedText", // k_EGCMsgInitiateTradeResponse_Declined
+ "#TF_Trading_VACBannedText", // k_EGCMsgInitiateTradeResponse_VAC_Banned_Initiator
+ "#TF_Trading_VACBanned2Text", // k_EGCMsgInitiateTradeResponse_VAC_Banned_Target
+ "#TF_Trading_BusyText", // k_EGCMsgInitiateTradeResponse_Target_Already_Trading
+ "#TF_Trading_DisabledText", // k_EGCMsgInitiateTradeResponse_Disabled
+ "#TF_Trading_NotLoggedIn", // k_EGCMsgInitiateTradeResponse_NotLoggedIn
+ "#TF_Trading_BusyText", // k_EGCMsgInitiateTradeResponse_Cancel (should never be used!)
+ "#TF_Trading_TooSoon", // k_EGCMsgInitiateTradeResponse_TooSoon
+ "#TF_Trading_TooSoonPenalty", // k_EGCMsgInitiateTradeResponse_TooSoonPenalty
+ "#TF_Trading_TradeBannedText", // (was k_EGCMsgInitiateTradeResponse_Free_Account_Initiator_DEPRECATED)
+ "#TF_Trading_TradeBanned2Text", // k_EGCMsgInitiateTradeResponse_Trade_Banned_Target
+ "#TF_Trading_FreeAccountInitiate", // k_EGCMsgInitiateTradeResponse_Free_Account_Initiator
+ "#TF_Trading_SharedAccountInitiate", // k_EGCMsgInitiateTradeResponse_Shared_Account_Initiator
+ "#TF_Trading_Service_Unavailable", // k_EGCMsgInitiateTradeResponse_Service_Unavailable
+ "#TF_Trading_YouBlockedThem", // k_EGCMsgInitiateTradeResponse_Target_Blocked
+ "#TF_Trading_NeedVerifiedEmail", // k_EGCMsgInitiateTradeResponse_NeedVerifiedEmail
+ "#TF_Trading_NeedSteamGuard", // k_EGCMsgInitiateTradeResponse_NeedSteamGuard
+ "#TF_Trading_SteamGuardDuration", // k_EGCMsgInitiateTradeResponse_SteamGuardDuration
+ "#TF_Trading_TheyCannotTrade", // k_EGCMsgInitiateTradeResponse_TheyCannotTrade
+ "#TF_Trading_PasswordChanged", // k_EGCMsgInitiateTradeResponse_Recent_Password_Reset = 20,
+ "#TF_Trading_NewDevice", // k_EGCMsgInitiateTradeResponse_Using_New_Device = 21,
+ "#TF_Trading_InvalidCookie" // k_EGCMsgInitiateTradeResponse_Sent_Invalid_Cookie = 22,
+};
+
+class CGCTrading_InitiateTradeResponse : public GCSDK::CGCClientJob
+{
+public:
+ CGCTrading_InitiateTradeResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ // If this assertion fails it probably means you added a new value to EGCMsgInitiateTradeResponse
+ // but didn't add a string to the array that tracks the user-facing response strings.
+ Assert( ARRAYSIZE( g_pszTradeResponseDescLocKeys ) == k_EGCMsgInitiateTradeResponse_Count );
+
+ CloseWaitingDialog();
+
+ GCSDK::CGCMsg<MsgGCTrading_InitiateTradeResponse_t> msg( pNetPacket );
+ const uint32 eResponse = msg.Body().m_eResponse;
+ switch ( eResponse )
+ {
+ case k_EGCMsgInitiateTradeResponse_Accepted:
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_ACCEPTED, iTradeRequests, g_RejectedReasons[msg.Body().m_eResponse] );
+#endif
+ ShowWaitingDialog( new CTradingWaitDialog(), "#TF_Trading_WaitingForStart", true, false, 30.0f );
+ return true; // !
+
+ case k_EGCMsgInitiateTradeResponse_Cancel:
+ {
+ CEconNotificationVisitor_RemoveTradeRequest visitor( msg.Body().m_unTradeRequestID );
+ NotificationQueue_Visit( visitor );
+ break;
+ }
+
+ case k_EGCMsgInitiateTradeResponse_Declined:
+ case k_EGCMsgInitiateTradeResponse_VAC_Banned_Initiator:
+ case k_EGCMsgInitiateTradeResponse_VAC_Banned_Target:
+ case k_EGCMsgInitiateTradeResponse_Target_Already_Trading:
+ case k_EGCMsgInitiateTradeResponse_Disabled:
+ case k_EGCMsgInitiateTradeResponse_NotLoggedIn:
+ case k_EGCMsgInitiateTradeResponse_TooSoon:
+ case k_EGCMsgInitiateTradeResponse_TooSoonPenalty:
+ case k_EGCMsgInitiateTradeResponse_Trade_Banned_Initiator:
+ case k_EGCMsgInitiateTradeResponse_Trade_Banned_Target:
+ case k_EGCMsgInitiateTradeResponse_Shared_Account_Initiator:
+ case k_EGCMsgInitiateTradeResponse_Service_Unavailable:
+ case k_EGCMsgInitiateTradeResponse_Target_Blocked:
+ case k_EGCMsgInitiateTradeResponse_NeedVerifiedEmail:
+ case k_EGCMsgInitiateTradeResponse_NeedSteamGuard:
+ case k_EGCMsgInitiateTradeResponse_TheyCannotTrade:
+ case k_EGCMsgInitiateTradeResponse_Recent_Password_Reset:
+ case k_EGCMsgInitiateTradeResponse_Using_New_Device:
+ case k_EGCMsgInitiateTradeResponse_Sent_Invalid_Cookie:
+
+ ShowMessageBox( "#TF_Trading_StatusTitle", g_pszTradeResponseDescLocKeys[eResponse], "#GameUI_OK" );
+ break;
+
+ case k_EGCMsgInitiateTradeResponse_SteamGuardDuration:
+ {
+
+ KeyValuesAD kvTokens( "CTradingWaitDialog" );
+ kvTokens->SetWString( "days", L"15" ); // Ideally this would come from the GC, which would get the value from Steam
+ ShowMessageBox( "#TF_Trading_StatusTitle", g_pszTradeResponseDescLocKeys[eResponse], kvTokens, "#GameUI_OK" );
+ break;
+ }
+
+ default:
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_REJECTED, iTradeRequests, "unknown" );
+#endif
+ ShowMessageBox( "#TF_Trading_StatusTitle", "#TF_Trading_BusyText", "#GameUI_OK" );
+ return true;
+ } // switch
+
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_REJECTED, iTradeRequests, g_RejectedReasons[msg.Body().m_eResponse] );
+#endif
+
+ return true;
+ }
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCTrading_InitiateTradeResponse, "CGCTrading_InitiateTradeResponse", k_EMsgGCTrading_InitiateTradeResponse, GCSDK::k_EServerTypeGCClient );
+
+// start trading session
+class CGCTrading_StartSession : public GCSDK::CGCClientJob
+{
+public:
+ CGCTrading_StartSession( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CGCMsg<MsgGCTrading_StartSession_t> msg( pNetPacket );
+
+ steamapicontext->SteamFriends()->ActivateGameOverlayToUser( "jointrade", msg.Body().m_ulSteamIDPartyB );
+
+ CloseWaitingDialog();
+
+ // remove all trading notifications
+ NotificationQueue_Remove( &CTFTradeRequestNotification::IsTradingNotification );
+
+ return true;
+ }
+};
+GC_REG_JOB( GCSDK::CGCClient, CGCTrading_StartSession, "CGCTrading_StartSession", k_EMsgGCTrading_StartSession, GCSDK::k_EServerTypeGCClient );
+
+
+//-----------------------------------------------------------------------------
+// External interface
+
+CSteamID Trading_GetLocalPlayerSteamID()
+{
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+ return steamapicontext->SteamUser()->GetSteamID();
+ }
+ return CSteamID();
+}
+
+void Trading_RequestTrade( int iPlayerIdx )
+{
+ CSteamID steamID;
+ C_BasePlayer *pPlayer = ToBasePlayer( UTIL_PlayerByIndex( iPlayerIdx ) );
+ if ( pPlayer && pPlayer->GetSteamID( &steamID ) )
+ {
+ Trading_RequestTrade( steamID );
+ }
+}
+
+void Trading_RequestTrade( const CSteamID &steamID )
+{
+ sbTestingSelfTrade = false;
+ GCSDK::CGCMsg< MsgGCTrading_InitiateTradeRequest_t > msg( k_EMsgGCTrading_InitiateTradeRequest );
+ msg.Body().m_ulOtherSteamID = steamID.ConvertToUint64();
+ bool bSent = GCClientSystem()->BSendMessage( msg );
+ if ( bSent )
+ {
+ iTradeRequests++;
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_REQUEST_SENT, msg.Body().m_ulOtherSteamID, iTradeRequests );
+#endif
+
+ const char* pPlayerName = InventoryManager()->PersonaName_Get( steamID.GetAccountID() );
+ if ( pPlayerName != NULL && FStrEq( pPlayerName, "" ) == false )
+ {
+ wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH];
+ g_pVGuiLocalize->ConvertANSIToUnicode( pPlayerName, wszPlayerName, sizeof(wszPlayerName) );
+ CTradingWaitDialog *pDialog = new CTradingWaitDialog( "#TF_Trading_TimeoutPartyB_Named", wszPlayerName );
+ ShowWaitingDialog( pDialog, "#TF_Trading_WaitingForPartyB", true, true, 30.0f );
+ wchar_t wszConstructedString[1024];
+ g_pVGuiLocalize->ConstructString_safe( wszConstructedString, g_pVGuiLocalize->Find( "#TF_Trading_WaitingForPartyB_Named" ), 1, wszPlayerName );
+ pDialog->SetDialogVariable( "updatetext", wszConstructedString );
+ }
+ else
+ {
+ CTradingWaitDialog *pDialog = new CTradingWaitDialog( "#TF_Trading_TimeoutPartyB" );
+ ShowWaitingDialog( pDialog, "#TF_Trading_WaitingForPartyB", true, true, 30.0f );
+ }
+ }
+}
+
+const char* UniverseToCommunityURL( EUniverse universe )
+{
+ switch( universe )
+ {
+ default: // return public if we don't have a better guess.
+ case k_EUniversePublic: return "https://steamcommunity.com";
+ case k_EUniverseBeta: return "https://beta.steamcommunity.com";
+ case k_EUniverseDev: return "https://localhost/community";
+ }
+
+ // Should never get here.
+ return UniverseToCommunityURL( k_EUniversePublic );
+}
+
+const char* GetCommunityURL()
+{
+ if ( GetUniverse() == k_EUniverseInvalid )
+ {
+ Assert( !"calling GetCommunityURL when not connected. This is allowed, but will return public universe." );
+ return UniverseToCommunityURL( k_EUniversePublic );
+ }
+
+ return UniverseToCommunityURL( GetUniverse() );
+}
+
+void Trading_SendGift( const CSteamID& steamID, const CEconItemView& giftItem )
+{
+ if ( !steamapicontext || !steamapicontext->SteamFriends() )
+ {
+ // TODO: Error dialog.
+ return;
+ }
+
+#ifdef TF_CLIENT_DLL
+ C_CTF_GameStats.Event_Trading( IE_TRADING_ITEM_GIFTED, steamID.ConvertToUint64(), iGiftsGiven );
+#endif
+
+ // Build up the steam URL and send it over.
+ // Should look like this: https://steamcommunity.com/trade/1/sendgift/?appid=&contextid=&assetid=&steamid_target=
+
+ steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage(
+ CFmtStrMax( "%s/trade/1/sendgift/?appid=%d&contextid=%d&assetid=%llu&steamid_target=%llu",
+ GetCommunityURL(),
+ engine->GetAppID(),
+ 2, // k_EEconContextBackpack
+ giftItem.GetItemID(),
+ steamID.ConvertToUint64()
+ )
+ );
+
+}
+
+CON_COMMAND( cl_trade, "Trade with a person by player name" )
+{
+ if ( args.ArgC() < 2 )
+ return;
+
+ if ( GetUniverse() == k_EUniverseInvalid )
+ return;
+
+ int iLocalPlayerIndex = GetLocalPlayerIndex();
+ for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
+ {
+ if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) )
+ {
+ player_info_t pi;
+ if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) )
+ continue;
+ if ( !pi.friendsID )
+ continue;
+
+ if ( FStrEq( pi.name, args[1] ) == false )
+ continue;
+
+ CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
+ Trading_RequestTrade( steamID );
+ return;
+ }
+ }
+}
+
+CON_COMMAND( cl_trade_steamid, "Trade with a person by steam id" )
+{
+ if ( args.ArgC() < 2 )
+ return;
+
+ if ( GetUniverse() == k_EUniverseInvalid )
+ return;
+
+ const char *pInput = args[1];
+ if ( pInput[0] >= '0' && pInput[0] <= '9' )
+ {
+ CSteamID steamID;
+ steamID.SetFromString( pInput, GetUniverse() );
+ if ( steamID.IsValid() )
+ Trading_RequestTrade( steamID );
+ }
+}
+
+#ifdef _DEBUG
+CON_COMMAND( cl_trading_test_self_trade, "Test self-trading" )
+{
+ Trading_RequestTrade( Trading_GetLocalPlayerSteamID() );
+ sbTestingSelfTrade = true;
+}
+#endif