2015-10-28 19:21:27 +01:00
/ *
_ _ _ ____ _ _____
/ \ _ __ ___ | | __ ( _ ) / ___ | | | _ ___ __ _ _ __ ___ | ___ | __ _ _ __ _ __ ___
/ _ \ | ' __ | / __ | | ' _ \ | | \ ___ \ | __ | / _ \ / _ ` | | ' _ ` _ \ | | _ / _ ` | | ' __ | | ' _ ` _ \
/ ___ \ | | | ( __ | | | | | | ___ ) | | | _ | __ / | ( _ | | | | | | | | | _ | | ( _ | | | | | | | | | |
/ _ / \ _ \ | _ | \ ___ | | _ | | _ | | _ | | ____ / \ __ | \ ___ | \ __ , _ | | _ | | _ | | _ | | _ | \ __ , _ | | _ | | _ | | _ | | _ |
2016-01-16 04:21:36 +01:00
Copyright 2015 - 2016 Ł ukasz "JustArchi" Domeradzki
2015-10-28 19:21:27 +01:00
Contact : JustArchi @JustArchi . net
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2016-04-12 19:12:45 +02:00
using System ;
2015-10-28 19:21:27 +01:00
using System.Collections.Generic ;
2016-05-13 06:32:42 +02:00
using System.Linq ;
2015-10-25 06:16:50 +01:00
using System.Threading ;
using System.Threading.Tasks ;
2016-05-13 06:32:42 +02:00
using ArchiSteamFarm.JSON ;
2015-10-25 06:16:50 +01:00
namespace ArchiSteamFarm {
2016-07-08 07:41:36 +02:00
internal sealed class Trading : IDisposable {
2016-07-27 04:48:28 +02:00
private sealed class ParseTradeResult {
internal enum EResult : byte {
Unknown ,
AcceptedWithItemLose ,
AcceptedWithoutItemLose ,
RejectedTemporarily ,
RejectedPermanently
}
internal readonly ulong TradeID ;
internal readonly EResult Result ;
internal ParseTradeResult ( ulong tradeID , EResult result ) {
if ( ( tradeID = = 0 ) | | ( result = = EResult . Unknown ) ) {
throw new ArgumentNullException ( nameof ( tradeID ) + " || " + nameof ( result ) ) ;
}
TradeID = tradeID ;
Result = result ;
}
2016-06-25 03:52:02 +02:00
}
2016-01-23 23:57:46 +01:00
internal const byte MaxItemsPerTrade = 150 ; // This is due to limit on POST size in WebBrowser
internal const byte MaxTradesPerAccount = 5 ; // This is limit introduced by Valve
2016-01-22 10:13:02 +01:00
2016-01-26 23:05:53 +01:00
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim ( 1 ) ;
2015-10-31 05:27:30 +01:00
private readonly Bot Bot ;
2016-06-25 03:52:02 +02:00
private readonly ConcurrentHashSet < ulong > IgnoredTrades = new ConcurrentHashSet < ulong > ( ) ;
2016-03-24 14:18:07 +01:00
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim ( 1 ) ;
2016-03-15 04:51:51 +01:00
2016-11-20 00:03:27 +01:00
private bool ParsingScheduled ;
2015-10-25 06:16:50 +01:00
2016-01-26 23:05:53 +01:00
internal static async Task LimitInventoryRequestsAsync ( ) {
await InventorySemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
2016-03-06 22:14:02 +01:00
Task . Run ( async ( ) = > {
2016-06-24 22:27:49 +02:00
await Task . Delay ( Program . GlobalConfig . InventoryLimiterDelay * 1000 ) . ConfigureAwait ( false ) ;
2016-02-05 20:15:28 +01:00
InventorySemaphore . Release ( ) ;
2016-03-06 22:14:02 +01:00
} ) . Forget ( ) ;
2016-01-26 23:05:53 +01:00
}
2015-10-25 06:16:50 +01:00
internal Trading ( Bot bot ) {
2016-03-11 02:07:20 +01:00
if ( bot = = null ) {
2016-05-13 06:32:42 +02:00
throw new ArgumentNullException ( nameof ( bot ) ) ;
2016-03-11 02:07:20 +01:00
}
2015-10-25 06:16:50 +01:00
Bot = bot ;
}
2016-07-08 07:41:36 +02:00
public void Dispose ( ) {
IgnoredTrades . Dispose ( ) ;
TradesSemaphore . Dispose ( ) ;
}
2016-06-25 03:52:02 +02:00
internal void OnDisconnected ( ) = > IgnoredTrades . ClearAndTrim ( ) ;
2016-03-24 14:18:07 +01:00
internal async Task CheckTrades ( ) {
2016-11-20 00:03:27 +01:00
// We aim to have a maximum of 2 tasks, one already parsing, and one waiting in the queue
// This way we can call this function as many times as needed e.g. because of Steam events
2016-03-24 14:18:07 +01:00
lock ( TradesSemaphore ) {
2016-11-20 00:03:27 +01:00
if ( ParsingScheduled ) {
2016-04-26 18:01:19 +02:00
return ;
2016-03-24 14:18:07 +01:00
}
2016-11-20 00:03:27 +01:00
ParsingScheduled = true ;
2016-03-10 01:20:17 +01:00
}
2015-11-01 02:04:44 +01:00
2016-03-24 14:18:07 +01:00
await TradesSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
2015-11-01 02:04:44 +01:00
2016-11-20 00:03:27 +01:00
try {
lock ( TradesSemaphore ) {
ParsingScheduled = false ;
}
2016-03-10 01:20:17 +01:00
2016-11-20 00:03:27 +01:00
await ParseActiveTrades ( ) . ConfigureAwait ( false ) ;
} finally {
TradesSemaphore . Release ( ) ;
}
2015-10-25 06:16:50 +01:00
}
private async Task ParseActiveTrades ( ) {
2016-10-27 22:01:38 +02:00
if ( string . IsNullOrEmpty ( Bot . BotConfig . SteamApiKey ) ) {
2016-06-03 00:52:15 +02:00
return ;
}
2016-06-29 20:18:47 +02:00
HashSet < Steam . TradeOffer > tradeOffers = Bot . ArchiWebHandler . GetActiveTradeOffers ( ) ;
2016-05-13 06:32:42 +02:00
if ( ( tradeOffers = = null ) | | ( tradeOffers . Count = = 0 ) ) {
2015-11-01 02:04:44 +01:00
return ;
}
2015-10-25 06:16:50 +01:00
2016-06-29 20:18:47 +02:00
if ( tradeOffers . RemoveWhere ( tradeoffer = > IgnoredTrades . Contains ( tradeoffer . TradeOfferID ) ) > 0 ) {
2016-06-19 15:03:42 +02:00
if ( tradeOffers . Count = = 0 ) {
return ;
}
2016-05-16 03:14:57 +02:00
}
2016-07-02 22:05:29 +02:00
ParseTradeResult [ ] results = await Task . WhenAll ( tradeOffers . Select ( ParseTrade ) ) . ConfigureAwait ( false ) ;
2016-07-27 04:48:28 +02:00
2016-09-27 13:20:22 +02:00
if ( Bot . HasMobileAuthenticator ) {
HashSet < ulong > acceptedWithItemLoseTradeIDs = new HashSet < ulong > ( results . Where ( result = > ( result ! = null ) & & ( result . Result = = ParseTradeResult . EResult . AcceptedWithItemLose ) ) . Select ( result = > result . TradeID ) ) ;
if ( acceptedWithItemLoseTradeIDs . Count > 0 ) {
await Task . Delay ( 1000 ) . ConfigureAwait ( false ) ; // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
await Bot . AcceptConfirmations ( true , Steam . ConfirmationDetails . EType . Trade , 0 , acceptedWithItemLoseTradeIDs ) . ConfigureAwait ( false ) ;
}
2016-06-19 07:37:31 +02:00
}
2016-08-15 21:47:31 +02:00
if ( results . Any ( result = > ( result ! = null ) & & ( ( result . Result = = ParseTradeResult . EResult . AcceptedWithItemLose ) | | ( result . Result = = ParseTradeResult . EResult . AcceptedWithoutItemLose ) ) ) ) {
// If we finished a trade, perform a loot if user wants to do so
await Bot . LootIfNeeded ( ) . ConfigureAwait ( false ) ;
}
2015-10-25 06:16:50 +01:00
}
2016-06-25 03:52:02 +02:00
private async Task < ParseTradeResult > ParseTrade ( Steam . TradeOffer tradeOffer ) {
2016-05-30 01:57:06 +02:00
if ( tradeOffer = = null ) {
2016-11-06 12:06:02 +01:00
Bot . ArchiLogger . LogNullError ( nameof ( tradeOffer ) ) ;
2016-07-27 04:48:28 +02:00
return null ;
2016-05-30 01:57:06 +02:00
}
if ( tradeOffer . State ! = Steam . TradeOffer . ETradeOfferState . Active ) {
2016-11-06 12:06:02 +01:00
Bot . ArchiLogger . LogGenericError ( "Ignoring trade in non-active state!" ) ;
2016-07-27 04:48:28 +02:00
return null ;
2015-10-25 06:16:50 +01:00
}
2016-06-25 03:52:02 +02:00
ParseTradeResult result = await ShouldAcceptTrade ( tradeOffer ) . ConfigureAwait ( false ) ;
2016-07-27 04:48:28 +02:00
if ( result = = null ) {
2016-11-06 12:06:02 +01:00
Bot . ArchiLogger . LogNullError ( nameof ( result ) ) ;
2016-07-27 04:48:28 +02:00
return null ;
}
switch ( result . Result ) {
case ParseTradeResult . EResult . AcceptedWithItemLose :
case ParseTradeResult . EResult . AcceptedWithoutItemLose :
2016-11-06 12:06:02 +01:00
Bot . ArchiLogger . LogGenericInfo ( "Accepting trade: " + tradeOffer . TradeOfferID ) ;
2016-08-14 00:19:01 +02:00
await Bot . ArchiWebHandler . AcceptTradeOffer ( tradeOffer . TradeOfferID ) . ConfigureAwait ( false ) ;
break ;
2016-07-27 04:48:28 +02:00
case ParseTradeResult . EResult . RejectedPermanently :
case ParseTradeResult . EResult . RejectedTemporarily :
if ( result . Result = = ParseTradeResult . EResult . RejectedPermanently ) {
2016-06-26 23:01:30 +02:00
if ( Bot . BotConfig . IsBotAccount ) {
2016-11-06 12:06:02 +01:00
Bot . ArchiLogger . LogGenericInfo ( "Rejecting trade: " + tradeOffer . TradeOfferID ) ;
2016-08-14 00:19:01 +02:00
Bot . ArchiWebHandler . DeclineTradeOffer ( tradeOffer . TradeOfferID ) ;
break ;
2016-06-26 23:01:30 +02:00
}
2016-06-25 03:52:02 +02:00
IgnoredTrades . Add ( tradeOffer . TradeOfferID ) ;
}
2016-11-06 12:06:02 +01:00
Bot . ArchiLogger . LogGenericInfo ( "Ignoring trade: " + tradeOffer . TradeOfferID ) ;
2016-08-14 00:19:01 +02:00
break ;
2016-06-19 13:59:56 +02:00
}
2016-08-14 00:19:01 +02:00
return result ;
2015-10-25 06:16:50 +01:00
}
2016-04-01 20:18:21 +02:00
2016-06-25 03:52:02 +02:00
private async Task < ParseTradeResult > ShouldAcceptTrade ( Steam . TradeOffer tradeOffer ) {
2016-04-01 20:18:21 +02:00
if ( tradeOffer = = null ) {
2016-11-06 12:06:02 +01:00
Bot . ArchiLogger . LogNullError ( nameof ( tradeOffer ) ) ;
2016-07-27 04:48:28 +02:00
return null ;
2016-04-01 20:18:21 +02:00
}
2016-10-27 22:01:38 +02:00
// Always accept trades from SteamMasterID
if ( ( tradeOffer . OtherSteamID64 ! = 0 ) & & ( tradeOffer . OtherSteamID64 = = Bot . BotConfig . SteamMasterID ) ) {
return new ParseTradeResult ( tradeOffer . TradeOfferID , tradeOffer . ItemsToGive . Count > 0 ? ParseTradeResult . EResult . AcceptedWithItemLose : ParseTradeResult . EResult . AcceptedWithoutItemLose ) ;
}
2016-10-21 21:33:55 +02:00
// Check if it's donation trade
if ( tradeOffer . ItemsToGive . Count = = 0 ) {
2016-10-21 21:39:58 +02:00
ParseTradeResult . EResult donationResult ;
// If it's steam fuckup, temporarily ignore it, otherwise react accordingly, depending on our preference
2016-10-21 21:33:55 +02:00
if ( tradeOffer . ItemsToReceive . Count = = 0 ) {
2016-10-21 21:39:58 +02:00
donationResult = ParseTradeResult . EResult . RejectedTemporarily ;
2016-10-27 22:01:38 +02:00
} else if ( Bot . BotConfig . TradingPreferences . HasFlag ( BotConfig . ETradingPreferences . AcceptDonations ) | | ( ( tradeOffer . OtherSteamID64 ! = 0 ) & & Bot . Bots . Values . Any ( bot = > bot . SteamID = = tradeOffer . OtherSteamID64 ) ) ) {
2016-10-21 21:39:58 +02:00
donationResult = ParseTradeResult . EResult . AcceptedWithoutItemLose ;
} else {
donationResult = ParseTradeResult . EResult . RejectedPermanently ;
2016-10-21 21:33:55 +02:00
}
2016-10-21 21:39:58 +02:00
return new ParseTradeResult ( tradeOffer . TradeOfferID , donationResult ) ;
2016-04-01 20:18:21 +02:00
}
2016-04-20 21:27:57 +02:00
// If we don't have SteamTradeMatcher enabled, this is the end for us
2016-10-21 20:32:22 +02:00
if ( ! Bot . BotConfig . TradingPreferences . HasFlag ( BotConfig . ETradingPreferences . SteamTradeMatcher ) ) {
2016-07-27 04:48:28 +02:00
return new ParseTradeResult ( tradeOffer . TradeOfferID , ParseTradeResult . EResult . RejectedPermanently ) ;
2016-04-20 21:27:57 +02:00
}
2016-05-06 23:31:00 +02:00
// Decline trade if we're giving more count-wise
if ( tradeOffer . ItemsToGive . Count > tradeOffer . ItemsToReceive . Count ) {
2016-07-27 04:48:28 +02:00
return new ParseTradeResult ( tradeOffer . TradeOfferID , ParseTradeResult . EResult . RejectedPermanently ) ;
2016-04-20 21:27:57 +02:00
}
2016-05-06 23:31:00 +02:00
// Decline trade if we're losing anything but steam cards, or if it's non-dupes trade
2016-09-12 03:49:17 +02:00
if ( ! tradeOffer . IsSteamCardsRequest ( ) | | ! tradeOffer . IsFairTypesExchange ( ) ) {
2016-07-27 04:48:28 +02:00
return new ParseTradeResult ( tradeOffer . TradeOfferID , ParseTradeResult . EResult . RejectedPermanently ) ;
2016-04-20 21:27:57 +02:00
}
2016-04-01 20:18:21 +02:00
2016-04-21 02:32:36 +02:00
// At this point we're sure that STM trade is valid
2016-06-12 02:18:18 +02:00
2016-06-27 01:45:41 +02:00
// Fetch trade hold duration
byte? holdDuration = await Bot . ArchiWebHandler . GetTradeHoldDuration ( tradeOffer . TradeOfferID ) . ConfigureAwait ( false ) ;
if ( ! holdDuration . HasValue ) {
// If we can't get trade hold duration, reject trade temporarily
2016-07-27 04:48:28 +02:00
return new ParseTradeResult ( tradeOffer . TradeOfferID , ParseTradeResult . EResult . RejectedTemporarily ) ;
2016-06-27 01:45:41 +02:00
}
2016-06-26 23:01:30 +02:00
2016-06-27 01:45:41 +02:00
// If user has a trade hold, we add extra logic
if ( holdDuration . Value > 0 ) {
// If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade
if ( ( holdDuration . Value > Program . GlobalConfig . MaxTradeHoldDuration ) | | tradeOffer . ItemsToGive . Any ( item = > GlobalConfig . GlobalBlacklist . Contains ( item . RealAppID ) ) ) {
2016-07-27 04:48:28 +02:00
return new ParseTradeResult ( tradeOffer . TradeOfferID , ParseTradeResult . EResult . RejectedPermanently ) ;
2016-06-12 02:18:18 +02:00
}
}
2016-10-21 21:33:55 +02:00
// If we're matching everything, this is enough for us
if ( Bot . BotConfig . TradingPreferences . HasFlag ( BotConfig . ETradingPreferences . MatchEverything ) ) {
return new ParseTradeResult ( tradeOffer . TradeOfferID , ParseTradeResult . EResult . AcceptedWithItemLose ) ;
}
2016-04-21 02:32:36 +02:00
// Now check if it's worth for us to do the trade
2016-06-12 23:01:17 +02:00
await LimitInventoryRequestsAsync ( ) . ConfigureAwait ( false ) ;
2016-07-19 20:14:21 +02:00
HashSet < Steam . Item > inventory = await Bot . ArchiWebHandler . GetMySteamInventory ( false ) . ConfigureAwait ( false ) ;
2016-05-13 06:32:42 +02:00
if ( ( inventory = = null ) | | ( inventory . Count = = 0 ) ) {
2016-07-27 04:48:28 +02:00
return new ParseTradeResult ( tradeOffer . TradeOfferID , ParseTradeResult . EResult . AcceptedWithItemLose ) ; // OK, assume that this trade is valid, we can't check our EQ
2016-04-21 02:32:36 +02:00
}
// Get appIDs we're interested in
2016-05-16 21:40:20 +02:00
HashSet < uint > appIDs = new HashSet < uint > ( tradeOffer . ItemsToGive . Select ( item = > item . RealAppID ) ) ;
2016-04-21 02:32:36 +02:00
// Now remove from our inventory all items we're NOT interested in
inventory . RemoveWhere ( item = > ! appIDs . Contains ( item . RealAppID ) ) ;
2016-04-21 02:37:40 +02:00
// If for some reason Valve is talking crap and we can't find mentioned items, assume OK
if ( inventory . Count = = 0 ) {
2016-07-27 04:48:28 +02:00
return new ParseTradeResult ( tradeOffer . TradeOfferID , ParseTradeResult . EResult . AcceptedWithItemLose ) ;
2016-04-21 02:37:40 +02:00
}
2016-04-21 02:32:36 +02:00
// Now let's create a map which maps items to their amount in our EQ
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
Dictionary < ulong , uint > amountMap = new Dictionary < ulong , uint > ( ) ;
2016-04-21 02:32:36 +02:00
foreach ( Steam . Item item in inventory ) {
uint amount ;
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
if ( amountMap . TryGetValue ( item . ClassID , out amount ) ) {
amountMap [ item . ClassID ] = amount + item . Amount ;
2016-04-21 02:32:36 +02:00
} else {
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
amountMap [ item . ClassID ] = item . Amount ;
2016-04-21 02:32:36 +02:00
}
}
// Calculate our value of items to give
2016-05-13 01:35:17 +02:00
List < uint > amountsToGive = new List < uint > ( tradeOffer . ItemsToGive . Count ) ;
2016-06-24 01:44:58 +02:00
Dictionary < ulong , uint > amountMapToGive = new Dictionary < ulong , uint > ( amountMap ) ;
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
foreach ( ulong key in tradeOffer . ItemsToGive . Select ( item = > item . ClassID ) ) {
2016-04-21 02:32:36 +02:00
uint amount ;
2016-06-24 01:44:58 +02:00
if ( ! amountMapToGive . TryGetValue ( key , out amount ) ) {
2016-05-13 01:35:17 +02:00
amountsToGive . Add ( 0 ) ;
2016-04-21 02:32:36 +02:00
continue ;
}
2016-05-13 01:35:17 +02:00
amountsToGive . Add ( amount ) ;
2016-06-24 01:44:58 +02:00
amountMapToGive [ key ] = amount - 1 ; // We're giving one, so we have one less
2016-04-21 02:32:36 +02:00
}
2016-05-13 01:35:17 +02:00
// Sort it ascending
amountsToGive . Sort ( ) ;
2016-04-21 02:32:36 +02:00
// Calculate our value of items to receive
2016-05-13 01:35:17 +02:00
List < uint > amountsToReceive = new List < uint > ( tradeOffer . ItemsToReceive . Count ) ;
2016-06-24 01:44:58 +02:00
Dictionary < ulong , uint > amountMapToReceive = new Dictionary < ulong , uint > ( amountMap ) ;
EXPERIMENTAL: Base key on classID only
I've just received trade offer from Endorlight game which used different combinations of classID/instanceID for the same items, such as <1450080555, 246376127> and <1450080555, 0> for Birdy trading card.
This is the first time I've seen something like that, as up to this point all classID + instanceID matched nicely. It's not good because it results in possibly wrong results regarding numbers of the same cards.
It's not any major problem scamming-wise, as ASF does other checks (including appID checks) so in worst case we could accept a trade that is not neutral+ for us, such as 1 -> 2 dupes one.
This commit fixes wrong logic for that particular trade I've encountered, and many similar ones, but I have no idea if we can really base key on classID only.
Number seems big enough to fit all steam items, but if there ever happens item that will have same classID as other one, we'll be in deep sh*t.
I ran a test on my Steam account with 7.5k+ items and didn't find any item which could cause such conflict, so I can at least assume that the issue doesn't exist for majority of Steam users.
Sadly there doesn't seem to be any good way to solve it either, as if such conflicts happens, then we will have 3 items such as <7, 1>, <7, 4> and <7, 0>. We can't know if 0 should be 1 or 4 (thanks GabeN).
This seems to be best solution for that problem, and until we find clasID conflict, we can sleep in peace.
2016-06-02 23:48:09 +02:00
foreach ( ulong key in tradeOffer . ItemsToReceive . Select ( item = > item . ClassID ) ) {
2016-04-21 02:32:36 +02:00
uint amount ;
2016-06-24 01:44:58 +02:00
if ( ! amountMapToReceive . TryGetValue ( key , out amount ) ) {
2016-05-13 01:35:17 +02:00
amountsToReceive . Add ( 0 ) ;
2016-04-21 02:32:36 +02:00
continue ;
}
2016-05-13 01:35:17 +02:00
amountsToReceive . Add ( amount ) ;
2016-06-24 01:44:58 +02:00
amountMapToReceive [ key ] = amount + 1 ; // We're getting one, so we have one more
2016-05-13 01:35:17 +02:00
}
// Sort it ascending
amountsToReceive . Sort ( ) ;
// Check actual difference
2016-06-24 01:44:58 +02:00
// We sum only values at proper indexes of giving, because user might be overpaying
2016-05-13 06:32:42 +02:00
int difference = amountsToGive . Select ( ( t , i ) = > ( int ) ( t - amountsToReceive [ i ] ) ) . Sum ( ) ;
2016-04-21 02:32:36 +02:00
2016-05-13 01:35:17 +02:00
// Trade is worth for us if the difference is greater than 0
2016-10-27 02:19:10 +02:00
// If not, we assume that the trade might be good for us in the future, unless we're bot account where we assume that inventory doesn't change
return new ParseTradeResult ( tradeOffer . TradeOfferID , difference > 0 ? ParseTradeResult . EResult . AcceptedWithItemLose : ( Bot . BotConfig . IsBotAccount ? ParseTradeResult . EResult . RejectedPermanently : ParseTradeResult . EResult . RejectedTemporarily ) ) ;
2016-04-01 20:18:21 +02:00
}
2015-10-25 06:16:50 +01:00
}
}