diff --git a/ArchiSteamFarm.sln.DotSettings b/ArchiSteamFarm.sln.DotSettings index a0608cbd4..b7229d668 100644 --- a/ArchiSteamFarm.sln.DotSettings +++ b/ArchiSteamFarm.sln.DotSettings @@ -375,4 +375,5 @@ True True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 10f8e41c3..33f1810a4 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -477,7 +477,7 @@ namespace ArchiSteamFarm { return result; } - internal async Task> GetMySteamInventory(bool tradable, HashSet wantedTypes) { + internal async Task> GetMySteamInventory(bool tradable, HashSet wantedTypes, HashSet wantedRealAppIDs = null) { if ((wantedTypes == null) || (wantedTypes.Count == 0)) { Bot.ArchiLogger.LogNullError(nameof(wantedTypes)); return null; @@ -576,7 +576,7 @@ namespace ArchiSteamFarm { steamItem.Type = description.Item2; } - if (!wantedTypes.Contains(steamItem.Type)) { + if (!wantedTypes.Contains(steamItem.Type) || (wantedRealAppIDs?.Contains(steamItem.RealAppID) == false)) { continue; } diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs index 91e84269c..94eeeee80 100644 --- a/ArchiSteamFarm/Trading.cs +++ b/ArchiSteamFarm/Trading.cs @@ -245,75 +245,82 @@ namespace ArchiSteamFarm { return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose); } + // Get appIDs we're interested in + HashSet appIDs = new HashSet(tradeOffer.ItemsToGive.Select(item => item.RealAppID)); + // Now check if it's worth for us to do the trade await LimitInventoryRequestsAsync().ConfigureAwait(false); - HashSet inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false, new HashSet { Steam.Item.EType.TradingCard }).ConfigureAwait(false); + HashSet inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false, new HashSet { Steam.Item.EType.TradingCard }, appIDs).ConfigureAwait(false); if ((inventory == null) || (inventory.Count == 0)) { // If we can't check our inventory when not using MatchEverything, this is a temporary failure Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily); } - // Get appIDs we're interested in - HashSet appIDs = new HashSet(tradeOffer.ItemsToGive.Select(item => item.RealAppID)); - - // Now remove from our inventory all items we're NOT interested in - inventory.RemoveWhere(item => !appIDs.Contains(item.RealAppID)); - - // If for some reason Valve is talking crap and we can't find mentioned items, this is a temporary failure - if (inventory.Count == 0) { - Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); - return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily); - } - // Now let's create a map which maps items to their amount in our EQ - Dictionary amountMap = new Dictionary(); + // This has to be done as we might have multiple items of given ClassID with multiple amounts + Dictionary itemAmounts = new Dictionary(); foreach (Steam.Item item in inventory) { - if (amountMap.TryGetValue(item.ClassID, out uint amount)) { - amountMap[item.ClassID] = amount + item.Amount; + if (itemAmounts.TryGetValue(item.ClassID, out uint amount)) { + itemAmounts[item.ClassID] = amount + item.Amount; } else { - amountMap[item.ClassID] = item.Amount; + itemAmounts[item.ClassID] = item.Amount; } } - // Calculate our value of items to give - List amountsToGive = new List(tradeOffer.ItemsToGive.Count); - Dictionary amountMapToGive = new Dictionary(amountMap); - foreach (ulong key in tradeOffer.ItemsToGive.Select(item => item.ClassID)) { - if (!amountMapToGive.TryGetValue(key, out uint amount)) { + // Calculate our value of items to give on per-game basis + Dictionary> itemAmountToGivePerGame = new Dictionary>(appIDs.Count); + Dictionary itemAmountsToGive = new Dictionary(itemAmounts); + foreach (Steam.Item item in tradeOffer.ItemsToGive) { + if (!itemAmountToGivePerGame.TryGetValue(item.RealAppID, out List amountsToGive)) { + amountsToGive = new List(); + itemAmountToGivePerGame[item.RealAppID] = amountsToGive; + } + + if (!itemAmountsToGive.TryGetValue(item.ClassID, out uint amount)) { amountsToGive.Add(0); continue; } amountsToGive.Add(amount); - amountMapToGive[key] = amount - 1; // We're giving one, so we have one less + itemAmountsToGive[item.ClassID] = amount - 1; // We're giving one, so we have one less } - // Sort it ascending - amountsToGive.Sort(); + // Sort all the lists of amounts to give on per-game basis ascending + foreach (List amountsToGive in itemAmountToGivePerGame.Values) { + amountsToGive.Sort(); + } - // Calculate our value of items to receive - List amountsToReceive = new List(tradeOffer.ItemsToReceive.Count); - Dictionary amountMapToReceive = new Dictionary(amountMap); - foreach (ulong key in tradeOffer.ItemsToReceive.Select(item => item.ClassID)) { - if (!amountMapToReceive.TryGetValue(key, out uint amount)) { + // Calculate our value of items to receive on per-game basis + Dictionary> itemAmountToReceivePerGame = new Dictionary>(appIDs.Count); + Dictionary itemAmountsToReceive = new Dictionary(itemAmounts); + foreach (Steam.Item item in tradeOffer.ItemsToReceive) { + if (!itemAmountToReceivePerGame.TryGetValue(item.RealAppID, out List amountsToReceive)) { + amountsToReceive = new List(); + itemAmountToReceivePerGame[item.RealAppID] = amountsToReceive; + } + + if (!itemAmountsToReceive.TryGetValue(item.ClassID, out uint amount)) { amountsToReceive.Add(0); continue; } amountsToReceive.Add(amount); - amountMapToReceive[key] = amount + 1; // We're getting one, so we have one more + itemAmountsToReceive[item.ClassID] = amount + 1; // We're getting one, so we have one more } - // Sort it ascending - amountsToReceive.Sort(); + // Sort all the lists of amounts to receive on per-game basis ascending + foreach (List amountsToReceive in itemAmountToReceivePerGame.Values) { + amountsToReceive.Sort(); + } - // Check actual difference - // We sum only values at proper indexes of giving, because user might be overpaying - int difference = amountsToGive.Select((t, i) => (int) (t - amountsToReceive[i])).Sum(); + // Calculate final neutrality result + // This is quite complex operation of taking minimum difference from all differences on per-game basis + // When calculating per-game difference, we sum only amounts at proper indexes, because user might be overpaying + int difference = itemAmountToGivePerGame.Min(kv => kv.Value.Select((t, i) => (int) (t - itemAmountToReceivePerGame[kv.Key][i])).Sum()); - // Trade is worth for us if the difference is greater than 0 + // Trade is neutral+ for us if the difference is greater than 0 // 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)); }