mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2026-01-06 17:10:13 +00:00
Make MatchActively take into account non-tradable items
This commit is contained in:
@@ -630,6 +630,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
asset.RealAppID = description.RealAppID;
|
||||
asset.Tradable = description.Tradable;
|
||||
asset.Type = description.Type;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace ArchiSteamFarm.Json {
|
||||
internal ulong ClassID { get; private set; }
|
||||
internal ulong ContextID { get; private set; }
|
||||
internal uint RealAppID { get; set; }
|
||||
internal bool Tradable { get; set; }
|
||||
internal EType Type { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
|
||||
@@ -430,47 +431,6 @@ namespace ArchiSteamFarm.Json {
|
||||
State = state;
|
||||
}
|
||||
|
||||
internal bool IsFairTypesExchange() {
|
||||
Dictionary<uint, Dictionary<Asset.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Asset.EType, uint>>();
|
||||
foreach (Asset item in ItemsToGive) {
|
||||
if (itemsToGivePerGame.TryGetValue(item.RealAppID, out Dictionary<Asset.EType, uint> itemsPerType)) {
|
||||
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount;
|
||||
} else {
|
||||
itemsPerType = new Dictionary<Asset.EType, uint> { [item.Type] = item.Amount };
|
||||
itemsToGivePerGame[item.RealAppID] = itemsPerType;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<uint, Dictionary<Asset.EType, uint>> itemsToReceivePerGame = new Dictionary<uint, Dictionary<Asset.EType, uint>>();
|
||||
foreach (Asset item in ItemsToReceive) {
|
||||
if (itemsToReceivePerGame.TryGetValue(item.RealAppID, out Dictionary<Asset.EType, uint> itemsPerType)) {
|
||||
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount;
|
||||
} else {
|
||||
itemsPerType = new Dictionary<Asset.EType, uint> { [item.Type] = item.Amount };
|
||||
itemsToReceivePerGame[item.RealAppID] = itemsPerType;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that amount of items to give is at least amount of items to receive (per game and per type)
|
||||
foreach (KeyValuePair<uint, Dictionary<Asset.EType, uint>> itemsPerGame in itemsToGivePerGame) {
|
||||
if (!itemsToReceivePerGame.TryGetValue(itemsPerGame.Key, out Dictionary<Asset.EType, uint> otherItemsPerType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Asset.EType, uint> itemsPerType in itemsPerGame.Value) {
|
||||
if (!otherItemsPerType.TryGetValue(itemsPerType.Key, out uint otherAmount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemsPerType.Value > otherAmount) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool IsValidSteamItemsRequest(IReadOnlyCollection<Asset.EType> acceptedTypes) {
|
||||
if ((acceptedTypes == null) || (acceptedTypes.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(acceptedTypes));
|
||||
|
||||
@@ -100,14 +100,19 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
#if NETFRAMEWORK
|
||||
internal static void TrimExcess<T1, T2>(this Dictionary<T1, T2> _) { } // no-op
|
||||
#endif
|
||||
|
||||
#if NETFRAMEWORK
|
||||
internal static async Task<WebSocketReceiveResult> ReceiveAsync(this WebSocket webSocket, byte[] buffer, CancellationToken cancellationToken) => await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken).ConfigureAwait(false);
|
||||
internal static async Task SendAsync(this WebSocket webSocket, byte[] buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) => await webSocket.SendAsync(new ArraySegment<byte>(buffer), messageType, endOfMessage, cancellationToken).ConfigureAwait(false);
|
||||
internal static string[] Split(this string text, char separator, StringSplitOptions options = StringSplitOptions.None) => text.Split(new[] { separator }, options);
|
||||
internal static void TrimExcess<T1, T2>(this Dictionary<T1, T2> _) { } // no-op
|
||||
|
||||
internal static void TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue value) {
|
||||
if (dictionary.ContainsKey(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dictionary.Add(key, value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,17 +270,17 @@ namespace ArchiSteamFarm {
|
||||
return false;
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> ourInventory = await Bot.ArchiWebHandler.GetInventory(Bot.SteamID, tradable: true, wantedTypes: acceptedMatchableTypes).ConfigureAwait(false);
|
||||
HashSet<Steam.Asset> ourInventory = await Bot.ArchiWebHandler.GetInventory(Bot.SteamID, wantedTypes: acceptedMatchableTypes).ConfigureAwait(false);
|
||||
if ((ourInventory == null) || (ourInventory.Count == 0)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(ourInventory)));
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> ourInventoryState = Trading.GetInventoryState(ourInventory);
|
||||
(Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> fullState, Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> tradableState) = Trading.GetDividedInventoryState(ourInventory);
|
||||
|
||||
if (ourInventoryState.Values.All(set => set.Values.All(amount => amount <= 1))) {
|
||||
if (Trading.IsEmptyForMatching(fullState, tradableState)) {
|
||||
// User doesn't have any more dupes in the inventory
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(ourInventoryState)));
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(fullState) + " || " + nameof(tradableState)));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -296,14 +296,18 @@ namespace ArchiSteamFarm {
|
||||
foreach (ListedUser listedUser in listedUsers.Where(listedUser => listedUser.MatchEverything && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && (!triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) || (attempt.Tries < byte.MaxValue)) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) ? attempt.Tries : 0).ThenByDescending(listedUser => listedUser.Score).Take(MaxMatchedBotsHard)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(listedUser.SteamID + "...");
|
||||
|
||||
HashSet<Steam.Asset> theirInventory = await Bot.ArchiWebHandler.GetInventory(listedUser.SteamID, tradable: true, wantedSets: ourInventoryState.Keys, skippedSets: skippedSetsThisRound).ConfigureAwait(false);
|
||||
HashSet<Steam.Asset> theirInventory = await Bot.ArchiWebHandler.GetInventory(listedUser.SteamID, tradable: true, wantedSets: fullState.Keys, skippedSets: skippedSetsThisRound).ConfigureAwait(false);
|
||||
if ((theirInventory == null) || (theirInventory.Count == 0)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(theirInventory)));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Those 3 collections are on user-basis since we can't be sure that the trade passes through (and therefore we need to keep original state in case of failure)
|
||||
HashSet<(uint AppID, Steam.Asset.EType Type)> skippedSetsThisUser = new HashSet<(uint AppID, Steam.Asset.EType Type)>();
|
||||
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> theirInventoryState = Trading.GetInventoryState(theirInventory);
|
||||
Dictionary<ulong, uint> ourFullSet = new Dictionary<ulong, uint>();
|
||||
Dictionary<ulong, uint> ourTradableSet = new Dictionary<ulong, uint>();
|
||||
|
||||
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> theirTradableState = Trading.GetInventoryState(theirInventory);
|
||||
|
||||
for (byte i = 0; i < Trading.MaxTradesPerAccount; i++) {
|
||||
byte itemsInTrade = 0;
|
||||
@@ -312,19 +316,30 @@ namespace ArchiSteamFarm {
|
||||
Dictionary<ulong, uint> classIDsToGive = new Dictionary<ulong, uint>();
|
||||
Dictionary<ulong, uint> classIDsToReceive = new Dictionary<ulong, uint>();
|
||||
|
||||
foreach (KeyValuePair<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> ourInventoryStateSet in ourInventoryState.Where(set => listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1))) {
|
||||
if (!theirInventoryState.TryGetValue(ourInventoryStateSet.Key, out Dictionary<ulong, uint> theirItems)) {
|
||||
foreach (KeyValuePair<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> ourInventoryStateSet in fullState.Where(set => listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1))) {
|
||||
if (!tradableState.TryGetValue(ourInventoryStateSet.Key, out Dictionary<ulong, uint> ourTradableItems)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!theirTradableState.TryGetValue(ourInventoryStateSet.Key, out Dictionary<ulong, uint> theirItems)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ourFullSet.AddRange(ourInventoryStateSet.Value);
|
||||
ourTradableSet.AddRange(ourTradableItems);
|
||||
|
||||
bool match;
|
||||
|
||||
do {
|
||||
match = false;
|
||||
|
||||
foreach (KeyValuePair<ulong, uint> ourItem in ourInventoryStateSet.Value.Where(item => item.Value > 1).OrderByDescending(item => item.Value)) {
|
||||
foreach (KeyValuePair<ulong, uint> theirItem in theirItems.OrderBy(item => ourInventoryStateSet.Value.TryGetValue(item.Key, out uint ourAmount) ? ourAmount : 0)) {
|
||||
if (ourInventoryStateSet.Value.TryGetValue(theirItem.Key, out uint ourAmountOfTheirItem) && (ourItem.Value <= ourAmountOfTheirItem + 1)) {
|
||||
foreach (KeyValuePair<ulong, uint> ourItem in ourFullSet.Where(item => item.Value > 1).OrderByDescending(item => item.Value)) {
|
||||
if (!ourTradableSet.TryGetValue(ourItem.Key, out uint tradableAmount)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<ulong, uint> theirItem in theirItems.OrderBy(item => ourFullSet.TryGetValue(item.Key, out uint ourAmount) ? ourAmount : 0)) {
|
||||
if (ourFullSet.TryGetValue(theirItem.Key, out uint ourAmountOfTheirItem) && (ourItem.Value <= ourAmountOfTheirItem + 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -333,11 +348,11 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// Update our state based on given items
|
||||
classIDsToGive[ourItem.Key] = classIDsToGive.TryGetValue(ourItem.Key, out uint givenAmount) ? givenAmount + 1 : 1;
|
||||
ourInventoryStateSet.Value[ourItem.Key] = ourItem.Value - 1;
|
||||
ourFullSet[ourItem.Key] = ourItem.Value - 1;
|
||||
|
||||
// Update our state based on received items
|
||||
classIDsToReceive[theirItem.Key] = classIDsToReceive.TryGetValue(theirItem.Key, out uint receivedAmount) ? receivedAmount + 1 : 1;
|
||||
ourInventoryStateSet.Value[theirItem.Key] = ourAmountOfTheirItem + 1;
|
||||
ourFullSet[theirItem.Key] = ourAmountOfTheirItem + 1;
|
||||
|
||||
// Update their state based on taken items
|
||||
if (theirItems.TryGetValue(theirItem.Key, out uint theirAmount) && (theirAmount > 1)) {
|
||||
@@ -346,6 +361,12 @@ namespace ArchiSteamFarm {
|
||||
theirItems.Remove(theirItem.Key);
|
||||
}
|
||||
|
||||
if (tradableAmount > 1) {
|
||||
ourTradableSet[ourItem.Key] = tradableAmount - 1;
|
||||
} else {
|
||||
ourTradableSet.Remove(ourItem.Key);
|
||||
}
|
||||
|
||||
itemsInTrade += 2;
|
||||
|
||||
match = true;
|
||||
@@ -368,8 +389,14 @@ namespace ArchiSteamFarm {
|
||||
break;
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> itemsToGive = Trading.GetItemsFromInventory(ourInventory, classIDsToGive);
|
||||
HashSet<Steam.Asset> itemsToReceive = Trading.GetItemsFromInventory(theirInventory, classIDsToReceive);
|
||||
HashSet<Steam.Asset> itemsToGive = Trading.GetTradableItemsFromInventory(ourInventory, classIDsToGive);
|
||||
HashSet<Steam.Asset> itemsToReceive = Trading.GetTradableItemsFromInventory(theirInventory, classIDsToReceive);
|
||||
|
||||
if ((itemsToGive.Count != itemsToReceive.Count) || !Trading.IsFairTypesExchange(itemsToGive, itemsToReceive)) {
|
||||
// Failsafe
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, Strings.ErrorAborted));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) previousAttempt)) {
|
||||
if (itemsToGive.Select(item => item.AssetID).All(previousAttempt.GivenAssetIDs.Contains) && itemsToReceive.Select(item => item.AssetID).All(previousAttempt.ReceivedAssetIDs.Contains)) {
|
||||
@@ -425,14 +452,17 @@ namespace ArchiSteamFarm {
|
||||
skippedSetsThisRound.UnionWith(skippedSetsThisUser);
|
||||
|
||||
foreach ((uint AppID, Steam.Asset.EType Type) skippedSet in skippedSetsThisUser) {
|
||||
ourInventoryState.Remove(skippedSet);
|
||||
fullState.Remove(skippedSet);
|
||||
tradableState.Remove(skippedSet);
|
||||
}
|
||||
|
||||
if (ourInventoryState.Values.All(set => set.Values.All(amount => amount <= 1))) {
|
||||
if (Trading.IsEmptyForMatching(fullState, tradableState)) {
|
||||
// User doesn't have any more dupes in the inventory
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(ourInventoryState)));
|
||||
break;
|
||||
}
|
||||
|
||||
fullState.TrimExcess();
|
||||
tradableState.TrimExcess();
|
||||
}
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ActivelyMatchingItemsRound, skippedSetsThisRound.Count));
|
||||
|
||||
@@ -43,32 +43,72 @@ namespace ArchiSteamFarm {
|
||||
|
||||
public void Dispose() => TradesSemaphore.Dispose();
|
||||
|
||||
internal static (Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> FullState, Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> TradableState) GetDividedInventoryState(IReadOnlyCollection<Steam.Asset> inventory) {
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(inventory));
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> fullState = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
|
||||
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> tradableState = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
|
||||
|
||||
foreach (Steam.Asset item in inventory) {
|
||||
(uint RealAppID, Steam.Asset.EType Type) key = (item.RealAppID, item.Type);
|
||||
|
||||
if (fullState.TryGetValue(key, out Dictionary<ulong, uint> fullSet)) {
|
||||
if (fullSet.TryGetValue(item.ClassID, out uint amount)) {
|
||||
fullSet[item.ClassID] = amount + item.Amount;
|
||||
} else {
|
||||
fullSet[item.ClassID] = item.Amount;
|
||||
}
|
||||
} else {
|
||||
fullState[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
|
||||
}
|
||||
|
||||
if (!item.Tradable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tradableState.TryGetValue(key, out Dictionary<ulong, uint> tradableSet)) {
|
||||
if (tradableSet.TryGetValue(item.ClassID, out uint amount)) {
|
||||
tradableSet[item.ClassID] = amount + item.Amount;
|
||||
} else {
|
||||
tradableSet[item.ClassID] = item.Amount;
|
||||
}
|
||||
} else {
|
||||
tradableState[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
|
||||
}
|
||||
}
|
||||
|
||||
return (fullState, tradableState);
|
||||
}
|
||||
|
||||
internal static Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> GetInventoryState(IReadOnlyCollection<Steam.Asset> inventory) {
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(inventory));
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> sets = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
|
||||
Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> state = new Dictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>>();
|
||||
|
||||
foreach (Steam.Asset item in inventory) {
|
||||
(uint RealAppID, Steam.Asset.EType Type) key = (item.RealAppID, item.Type);
|
||||
|
||||
if (sets.TryGetValue(key, out Dictionary<ulong, uint> set)) {
|
||||
if (state.TryGetValue(key, out Dictionary<ulong, uint> set)) {
|
||||
if (set.TryGetValue(item.ClassID, out uint amount)) {
|
||||
set[item.ClassID] = amount + item.Amount;
|
||||
} else {
|
||||
set[item.ClassID] = item.Amount;
|
||||
}
|
||||
} else {
|
||||
sets[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
|
||||
state[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
|
||||
}
|
||||
}
|
||||
|
||||
return sets;
|
||||
return state;
|
||||
}
|
||||
|
||||
internal static HashSet<Steam.Asset> GetItemsFromInventory(IReadOnlyCollection<Steam.Asset> inventory, IDictionary<ulong, uint> classIDs) {
|
||||
internal static HashSet<Steam.Asset> GetTradableItemsFromInventory(IReadOnlyCollection<Steam.Asset> inventory, IDictionary<ulong, uint> classIDs) {
|
||||
if ((inventory == null) || (inventory.Count == 0) || (classIDs == null) || (classIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(inventory) + " || " + nameof(classIDs));
|
||||
return null;
|
||||
@@ -76,7 +116,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
HashSet<Steam.Asset> result = new HashSet<Steam.Asset>();
|
||||
|
||||
foreach (Steam.Asset item in inventory) {
|
||||
foreach (Steam.Asset item in inventory.Where(item => item.Tradable)) {
|
||||
if (!classIDs.TryGetValue(item.ClassID, out uint amount)) {
|
||||
continue;
|
||||
}
|
||||
@@ -97,6 +137,95 @@ namespace ArchiSteamFarm {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> fullState, IReadOnlyDictionary<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> tradableState) {
|
||||
if ((fullState == null) || (tradableState == null)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(fullState) + " || " + nameof(tradableState));
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<(uint AppID, Steam.Asset.EType Type), Dictionary<ulong, uint>> tradableSet in tradableState) {
|
||||
foreach (KeyValuePair<ulong, uint> tradableItem in tradableSet.Value) {
|
||||
switch (tradableItem.Value) {
|
||||
case 0:
|
||||
// No tradable items, this should never happen, dictionary should not have this key to begin with
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(tradableItem.Value), tradableItem.Value));
|
||||
return false;
|
||||
case 1:
|
||||
// Single tradable item, can be matchable or not depending on the rest of the inventory
|
||||
if (!fullState.TryGetValue(tradableSet.Key, out Dictionary<ulong, uint> fullItem) || (fullItem == null) || (fullItem.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(fullItem));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fullItem.TryGetValue(tradableItem.Key, out uint fullAmount) || (fullAmount == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(fullAmount));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fullAmount > 1) {
|
||||
// If we have a single tradable item but more than 1 in total, this is matchable
|
||||
return false;
|
||||
}
|
||||
|
||||
// A single exclusive tradable item is not matchable, continue
|
||||
continue;
|
||||
default:
|
||||
// Any other combination of tradable items is always matchable
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find any matchable combinations, so this inventory is empty
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool IsFairTypesExchange(IReadOnlyCollection<Steam.Asset> itemsToGive, IReadOnlyCollection<Steam.Asset> itemsToReceive) {
|
||||
if ((itemsToGive == null) || (itemsToGive.Count == 0) || (itemsToReceive == null) || (itemsToReceive.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(itemsToGive) + " || " + nameof(itemsToReceive));
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary<uint, Dictionary<Steam.Asset.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Steam.Asset.EType, uint>>();
|
||||
foreach (Steam.Asset item in itemsToGive) {
|
||||
if (itemsToGivePerGame.TryGetValue(item.RealAppID, out Dictionary<Steam.Asset.EType, uint> itemsPerType)) {
|
||||
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount;
|
||||
} else {
|
||||
itemsPerType = new Dictionary<Steam.Asset.EType, uint> { [item.Type] = item.Amount };
|
||||
itemsToGivePerGame[item.RealAppID] = itemsPerType;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<uint, Dictionary<Steam.Asset.EType, uint>> itemsToReceivePerGame = new Dictionary<uint, Dictionary<Steam.Asset.EType, uint>>();
|
||||
foreach (Steam.Asset item in itemsToReceive) {
|
||||
if (itemsToReceivePerGame.TryGetValue(item.RealAppID, out Dictionary<Steam.Asset.EType, uint> itemsPerType)) {
|
||||
itemsPerType[item.Type] = itemsPerType.TryGetValue(item.Type, out uint amount) ? amount + item.Amount : item.Amount;
|
||||
} else {
|
||||
itemsPerType = new Dictionary<Steam.Asset.EType, uint> { [item.Type] = item.Amount };
|
||||
itemsToReceivePerGame[item.RealAppID] = itemsPerType;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that amount of items to give is at least amount of items to receive (per game and per type)
|
||||
foreach (KeyValuePair<uint, Dictionary<Steam.Asset.EType, uint>> itemsPerGame in itemsToGivePerGame) {
|
||||
if (!itemsToReceivePerGame.TryGetValue(itemsPerGame.Key, out Dictionary<Steam.Asset.EType, uint> otherItemsPerType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Steam.Asset.EType, uint> itemsPerType in itemsPerGame.Value) {
|
||||
if (!otherItemsPerType.TryGetValue(itemsPerType.Key, out uint otherAmount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemsPerType.Value > otherAmount) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void OnDisconnected() => IgnoredTrades.Clear();
|
||||
|
||||
internal async Task OnNewTrade() {
|
||||
@@ -365,7 +494,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// Decline trade if we're requested to handle any not-accepted item type or if it's not fair games/types exchange
|
||||
if (!tradeOffer.IsValidSteamItemsRequest(Bot.BotConfig.MatchableTypes) || !tradeOffer.IsFairTypesExchange()) {
|
||||
if (!tradeOffer.IsValidSteamItemsRequest(Bot.BotConfig.MatchableTypes) || !IsFairTypesExchange(tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive)) {
|
||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently, tradeOffer.ItemsToReceive);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,17 @@ namespace ArchiSteamFarm {
|
||||
// Normally we wouldn't need to use this singleton, but we want to ensure decent randomness across entire program's lifetime
|
||||
private static readonly Random Random = new Random();
|
||||
|
||||
internal static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, IEnumerable<KeyValuePair<TKey, TValue>> other) {
|
||||
if ((dictionary == null) || (other == null)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(dictionary) + " || " + nameof(other));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<TKey, TValue> item in other) {
|
||||
dictionary.TryAdd(item.Key, item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetArgsAsText(string[] args, byte argsToSkip, string delimiter) {
|
||||
if ((args == null) || (args.Length <= argsToSkip) || string.IsNullOrEmpty(delimiter)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(argsToSkip) + " || " + nameof(delimiter));
|
||||
|
||||
Reference in New Issue
Block a user