//========= 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