From e61212a00fa9ddb42b060ff8ae6a1cd1e395013e Mon Sep 17 00:00:00 2001 From: JustArchi Date: Wed, 5 Dec 2018 23:13:55 +0100 Subject: [PATCH] Rewrite active matching code for more fairness and robustness --- ArchiSteamFarm/Statistics.cs | 41 ++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/ArchiSteamFarm/Statistics.cs b/ArchiSteamFarm/Statistics.cs index 6c45cc629..d4c447a91 100644 --- a/ArchiSteamFarm/Statistics.cs +++ b/ArchiSteamFarm/Statistics.cs @@ -226,7 +226,7 @@ namespace ArchiSteamFarm { try { Bot.ArchiLogger.LogGenericTrace(Strings.Starting); - Dictionary triedSteamIDs = new Dictionary(); + Dictionary GivenAssetIDs, ISet ReceivedAssetIDs)> triedSteamIDs = new Dictionary GivenAssetIDs, ISet ReceivedAssetIDs)>(); bool match = true; @@ -255,7 +255,7 @@ namespace ArchiSteamFarm { } [SuppressMessage("ReSharper", "FunctionComplexityOverflow")] - private async Task MatchActivelyRound(IReadOnlyCollection acceptedMatchableTypes, IDictionary triedSteamIDs) { + private async Task MatchActivelyRound(IReadOnlyCollection acceptedMatchableTypes, IDictionary GivenAssetIDs, ISet ReceivedAssetIDs)> triedSteamIDs) { if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0) || (triedSteamIDs == null)) { Bot.ArchiLogger.LogNullError(nameof(acceptedMatchableTypes) + " || " + nameof(triedSteamIDs)); return false; @@ -284,7 +284,7 @@ namespace ArchiSteamFarm { byte emptyMatches = 0; HashSet<(uint AppID, Steam.Asset.EType Type)> skippedSetsThisRound = new HashSet<(uint AppID, Steam.Asset.EType Type)>(); - foreach (ListedUser listedUser in listedUsers.Where(listedUser => listedUser.MatchEverything && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && (!triedSteamIDs.TryGetValue(listedUser.SteamID, out byte tries) || (tries < byte.MaxValue)) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => triedSteamIDs.TryGetValue(listedUser.SteamID, out byte tries) ? tries : 0).ThenByDescending(listedUser => listedUser.Score).Take(MaxMatchedBotsHard)) { + foreach (ListedUser listedUser in listedUsers.Where(listedUser => listedUser.MatchEverything && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && (!triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet GivenAssetIDs, ISet ReceivedAssetIDs) attempt) || (attempt.Tries < byte.MaxValue)) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet GivenAssetIDs, ISet ReceivedAssetIDs) attempt) ? attempt.Tries : 0).ThenByDescending(listedUser => listedUser.Score).Take(MaxMatchedBotsHard)) { Bot.ArchiLogger.LogGenericTrace(listedUser.SteamID + "..."); HashSet theirInventory = await Bot.ArchiWebHandler.GetInventory(listedUser.SteamID, tradable: true, wantedSets: ourInventoryState.Keys, skippedSets: skippedSetsThisRound).ConfigureAwait(false); @@ -298,6 +298,7 @@ namespace ArchiSteamFarm { for (byte i = 0; i < Trading.MaxTradesPerAccount; i++) { byte itemsInTrade = 0; + HashSet<(uint AppID, Steam.Asset.EType Type)> skippedSetsThisTrade = new HashSet<(uint AppID, Steam.Asset.EType Type)>(); Dictionary classIDsToGive = new Dictionary(); Dictionary classIDsToReceive = new Dictionary(); @@ -319,7 +320,7 @@ namespace ArchiSteamFarm { } // Skip this set from the remaining of this round - skippedSetsThisUser.Add(ourInventoryStateSet.Key); + skippedSetsThisTrade.Add(ourInventoryStateSet.Key); // Update our state based on given items classIDsToGive[ourItem.Key] = classIDsToGive.TryGetValue(ourItem.Key, out uint givenAmount) ? givenAmount + 1 : 1; @@ -353,16 +354,35 @@ namespace ArchiSteamFarm { } } - if (skippedSetsThisUser.Count == 0) { - Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(skippedSetsThisUser))); + if (skippedSetsThisTrade.Count == 0) { + Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(skippedSetsThisTrade))); break; } - emptyMatches = 0; - HashSet itemsToGive = Trading.GetItemsFromInventory(ourInventory, classIDsToGive); HashSet itemsToReceive = Trading.GetItemsFromInventory(theirInventory, classIDsToReceive); + if (triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet GivenAssetIDs, ISet ReceivedAssetIDs) previousAttempt)) { + Bot.ArchiLogger.LogGenericDebug("Previous: " + listedUser.SteamID + ": " + previousAttempt.Tries + " | " + string.Join(", ", previousAttempt.GivenAssetIDs) + " | " + string.Join(", ", previousAttempt.ReceivedAssetIDs)); // TODO: remove debug + if (itemsToGive.Select(item => item.AssetID).All(previousAttempt.GivenAssetIDs.Contains) && itemsToReceive.Select(item => item.AssetID).All(previousAttempt.ReceivedAssetIDs.Contains)) { + // This user didn't respond in our previous round, avoid him for remaining ones + triedSteamIDs[listedUser.SteamID] = (byte.MaxValue, previousAttempt.GivenAssetIDs, previousAttempt.ReceivedAssetIDs); + Bot.ArchiLogger.LogGenericDebug("Banned: " + listedUser.SteamID); // TODO: remove debug + break; + } + + previousAttempt.GivenAssetIDs.UnionWith(itemsToGive.Select(item => item.AssetID)); + previousAttempt.ReceivedAssetIDs.UnionWith(itemsToReceive.Select(item => item.AssetID)); + } else { + previousAttempt.GivenAssetIDs = new HashSet(itemsToGive.Select(item => item.AssetID)); + previousAttempt.ReceivedAssetIDs = new HashSet(itemsToReceive.Select(item => item.AssetID)); + Bot.ArchiLogger.LogGenericDebug("New: " + listedUser.SteamID + ": " + string.Join(", ", previousAttempt.GivenAssetIDs) + " | " + string.Join(", ", previousAttempt.ReceivedAssetIDs)); // TODO: remove debug + } + + triedSteamIDs[listedUser.SteamID] = (++previousAttempt.Tries, previousAttempt.GivenAssetIDs, previousAttempt.ReceivedAssetIDs); + + emptyMatches = 0; + Bot.ArchiLogger.LogGenericTrace(Bot.SteamID + " <- " + string.Join(", ", itemsToReceive.Select(item => item.RealAppID + "/" + item.Type + "-" + item.ClassID + " #" + item.Amount)) + " | " + string.Join(", ", itemsToGive.Select(item => item.RealAppID + "/" + item.Type + "-" + item.ClassID + " #" + item.Amount)) + " -> " + listedUser.SteamID); (bool success, HashSet mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(listedUser.SteamID, itemsToGive, itemsToReceive, listedUser.TradeToken, true).ConfigureAwait(false); @@ -379,15 +399,14 @@ namespace ArchiSteamFarm { break; } + skippedSetsThisUser.UnionWith(skippedSetsThisTrade); Bot.ArchiLogger.LogGenericTrace(Strings.Success); } - triedSteamIDs[listedUser.SteamID] = triedSteamIDs.TryGetValue(listedUser.SteamID, out byte tries) ? ++tries : (byte) 1; - if (skippedSetsThisUser.Count == 0) { if (skippedSetsThisRound.Count == 0) { // If we didn't find any match on clean round, this user isn't going to have anything interesting for us anytime soon - triedSteamIDs[listedUser.SteamID] = byte.MaxValue; + triedSteamIDs[listedUser.SteamID] = (byte.MaxValue, null, null); } if (++emptyMatches >= MaxMatchesBotsSoft) {