mirror of
https://github.com/JustArchiNET/ArchiSteamFarm
synced 2024-11-10 07:04:27 +00:00
Limit full set trades (#2133)
* Limit items to send when looting full sets * Add test for item limit when looting full sets * Fix small mistakes * Change another type to byte * And another one * Add test to verify that the amount of appIDs does not matter in limiting the amount of items * Add maxItems parameter to GetFullItemSets * Test for exception with too small parameter value * Let's get the finishing touches on that PR :'<,'>s/byte/ushort/g * Use named const instead of hard-coded magic number * Fix the last remaining typecast (hopefully) * Send as many sets as possible and Add test for this and Check values of amountsToExtract as it is part of the public API * Remove check for minimum items per set. Plugins could still use this with smaller sets on non-steam items * Correct force of habit to match the projects code-style
This commit is contained in:
parent
434ea8096b
commit
b8fe02e2b6
2 changed files with 104 additions and 10 deletions
|
@ -50,6 +50,87 @@ namespace ArchiSteamFarm.Tests {
|
|||
AssertResultMatchesExpectation(expectedResult, itemsToSend);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MaxItemsBarelyEnoughForOneSet() {
|
||||
const uint relevantAppID = 42;
|
||||
|
||||
Dictionary<uint, byte> itemsPerSet = new() {
|
||||
{ relevantAppID, ArchiSteamFarm.Bot.MinCardsPerBadge },
|
||||
{ 43, ArchiSteamFarm.Bot.MinCardsPerBadge + 1 }
|
||||
};
|
||||
|
||||
HashSet<Steam.Asset> items = new();
|
||||
|
||||
foreach ((uint appID, byte cards) in itemsPerSet) {
|
||||
for (byte i = 1; i <= cards; i++) {
|
||||
items.Add(CreateCard(i, appID));
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> itemsToSend = GetItemsForFullBadge(items, itemsPerSet, ArchiSteamFarm.Bot.MinCardsPerBadge);
|
||||
|
||||
Dictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult = items.Where(item => item.RealAppID == relevantAppID)
|
||||
.GroupBy(item => (item.RealAppID, item.ContextID, item.ClassID))
|
||||
.ToDictionary(grouping => grouping.Key, grouping => (uint) grouping.Sum(item => item.Amount));
|
||||
|
||||
AssertResultMatchesExpectation(expectedResult, itemsToSend);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void MaxItemsTooSmall() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Steam.Asset> items = new() {
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID)
|
||||
};
|
||||
|
||||
GetItemsForFullBadge(items, 2, appID, ArchiSteamFarm.Bot.MinCardsPerBadge - 1);
|
||||
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TooManyCardsForSingleTrade() {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Steam.Asset> items = new();
|
||||
|
||||
for (byte i = 0; i < ArchiSteamFarm.Trading.MaxItemsPerTrade; i++) {
|
||||
items.Add(CreateCard(1, appID));
|
||||
items.Add(CreateCard(2, appID));
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
|
||||
Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TooManyCardsForSingleTradeMultipleAppIDs() {
|
||||
const uint appID0 = 42;
|
||||
const uint appID1 = 43;
|
||||
|
||||
HashSet<Steam.Asset> items = new();
|
||||
|
||||
for (byte i = 0; i < 100; i++) {
|
||||
items.Add(CreateCard(1, appID0));
|
||||
items.Add(CreateCard(2, appID0));
|
||||
items.Add(CreateCard(1, appID1));
|
||||
items.Add(CreateCard(2, appID1));
|
||||
}
|
||||
|
||||
Dictionary<uint, byte> itemsPerSet = new() {
|
||||
{ appID0, 2 },
|
||||
{ appID1, 2 }
|
||||
};
|
||||
|
||||
HashSet<Steam.Asset> itemsToSend = GetItemsForFullBadge(items, itemsPerSet);
|
||||
|
||||
Assert.IsTrue(itemsToSend.Count <= ArchiSteamFarm.Trading.MaxItemsPerTrade);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MultipleSets() {
|
||||
const uint appID = 42;
|
||||
|
@ -419,12 +500,12 @@ namespace ArchiSteamFarm.Tests {
|
|||
|
||||
private static Steam.Asset CreateCard(ulong classID, uint realAppID, uint amount = 1, Steam.Asset.EType type = Steam.Asset.EType.TradingCard, Steam.Asset.ERarity rarity = Steam.Asset.ERarity.Common) => new(Steam.Asset.SteamAppID, Steam.Asset.SteamCommunityContextID, classID, amount, realAppID: realAppID, type: type, rarity: rarity);
|
||||
|
||||
private static HashSet<Steam.Asset> GetItemsForFullBadge(IReadOnlyCollection<Steam.Asset> inventory, byte cardsPerSet, uint appID) => GetItemsForFullBadge(inventory, new Dictionary<uint, byte> { { appID, cardsPerSet } });
|
||||
private static HashSet<Steam.Asset> GetItemsForFullBadge(IReadOnlyCollection<Steam.Asset> inventory, byte cardsPerSet, uint appID, ushort maxItems = ArchiSteamFarm.Trading.MaxItemsPerTrade) => GetItemsForFullBadge(inventory, new Dictionary<uint, byte> { { appID, cardsPerSet } }, maxItems);
|
||||
|
||||
private static HashSet<Steam.Asset> GetItemsForFullBadge(IReadOnlyCollection<Steam.Asset> inventory, IDictionary<uint, byte> cardsPerSet) {
|
||||
private static HashSet<Steam.Asset> GetItemsForFullBadge(IReadOnlyCollection<Steam.Asset> inventory, IDictionary<uint, byte> cardsPerSet, ushort maxItems = ArchiSteamFarm.Trading.MaxItemsPerTrade) {
|
||||
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List<uint>> inventorySets = ArchiSteamFarm.Trading.GetInventorySets(inventory);
|
||||
|
||||
return ArchiSteamFarm.Bot.GetItemsForFullSets(inventory, inventorySets.ToDictionary(kv => kv.Key, kv => (SetsToExtract: inventorySets[kv.Key][0], cardsPerSet[kv.Key.RealAppID]))).ToHashSet();
|
||||
return ArchiSteamFarm.Bot.GetItemsForFullSets(inventory, inventorySets.ToDictionary(kv => kv.Key, kv => (SetsToExtract: inventorySets[kv.Key][0], cardsPerSet[kv.Key.RealAppID])), maxItems).ToHashSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ namespace ArchiSteamFarm {
|
|||
public sealed class Bot : IAsyncDisposable {
|
||||
internal const ushort CallbackSleep = 500; // In milliseconds
|
||||
internal const ushort MaxMessagePrefixLength = MaxMessageLength - ReservedMessageLength - 2; // 2 for a minimum of 2 characters (escape one and real one)
|
||||
internal const byte MinCardsPerBadge = 5;
|
||||
internal const byte MinPlayingBlockedTTL = 60; // Delay in seconds added when account was occupied during our disconnect, to not disconnect other Steam client session too soon
|
||||
|
||||
private const char DefaultBackgroundKeysRedeemerSeparator = '\t';
|
||||
|
@ -57,7 +58,6 @@ namespace ArchiSteamFarm {
|
|||
private const byte MaxInvalidPasswordFailures = WebBrowser.MaxTries; // Max InvalidPassword failures in a row before we determine that our password is invalid (because Steam wrongly returns those, of course)
|
||||
private const ushort MaxMessageLength = 5000; // This is a limitation enforced by Steam
|
||||
private const byte MaxTwoFactorCodeFailures = WebBrowser.MaxTries; // Max TwoFactorCodeMismatch failures in a row before we determine that our 2FA credentials are invalid (because Steam wrongly returns those, of course)
|
||||
private const byte MinimumCardsPerBadge = 5;
|
||||
private const byte RedeemCooldownInHours = 1; // 1 hour since first redeem attempt, this is a limitation enforced by Steam
|
||||
private const byte ReservedMessageLength = 2; // 2 for 2x optional …
|
||||
|
||||
|
@ -509,19 +509,23 @@ namespace ArchiSteamFarm {
|
|||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static HashSet<Steam.Asset> GetItemsForFullSets(IReadOnlyCollection<Steam.Asset> inventory, IReadOnlyDictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), (uint SetsToExtract, byte ItemsPerSet)> amountsToExtract) {
|
||||
public static HashSet<Steam.Asset> GetItemsForFullSets(IReadOnlyCollection<Steam.Asset> inventory, IReadOnlyDictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), (uint SetsToExtract, byte ItemsPerSet)> amountsToExtract, ushort maxItems = Trading.MaxItemsPerTrade) {
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
if ((amountsToExtract == null) || (amountsToExtract.Count == 0)) {
|
||||
if ((amountsToExtract == null) || (amountsToExtract.Count == 0) || amountsToExtract.Any(kv => kv.Value.SetsToExtract == 0)) {
|
||||
throw new ArgumentNullException(nameof(amountsToExtract));
|
||||
}
|
||||
|
||||
if (maxItems < MinCardsPerBadge) {
|
||||
throw new ArgumentOutOfRangeException(nameof(maxItems));
|
||||
}
|
||||
|
||||
HashSet<Steam.Asset> result = new();
|
||||
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, HashSet<Steam.Asset>>> itemsPerClassIDPerSet = inventory.GroupBy(item => (item.RealAppID, item.Type, item.Rarity)).ToDictionary(grouping => grouping.Key, grouping => grouping.GroupBy(item => item.ClassID).ToDictionary(group => group.Key, group => group.ToHashSet()));
|
||||
|
||||
foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, (uint setsToExtract, byte itemsPerSet)) in amountsToExtract) {
|
||||
foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, (uint setsToExtract, byte itemsPerSet)) in amountsToExtract.OrderBy(kv => kv.Value.ItemsPerSet)) {
|
||||
if (!itemsPerClassIDPerSet.TryGetValue(set, out Dictionary<ulong, HashSet<Steam.Asset>>? itemsPerClassID)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -534,8 +538,17 @@ namespace ArchiSteamFarm {
|
|||
continue;
|
||||
}
|
||||
|
||||
ushort maxSetsAllowed = (ushort) (maxItems - result.Count);
|
||||
maxSetsAllowed -= (ushort) (maxSetsAllowed % itemsPerSet);
|
||||
maxSetsAllowed /= itemsPerSet;
|
||||
ushort realSetsToExtract = (ushort) Math.Min(setsToExtract, maxSetsAllowed);
|
||||
|
||||
if (realSetsToExtract == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (HashSet<Steam.Asset> itemsOfClass in itemsPerClassID.Values) {
|
||||
uint classRemaining = setsToExtract;
|
||||
ushort classRemaining = realSetsToExtract;
|
||||
|
||||
foreach (Steam.Asset item in itemsOfClass.TakeWhile(_ => classRemaining > 0)) {
|
||||
if (item.Amount > classRemaining) {
|
||||
|
@ -547,7 +560,7 @@ namespace ArchiSteamFarm {
|
|||
} else {
|
||||
result.Add(item);
|
||||
|
||||
classRemaining -= item.Amount;
|
||||
classRemaining -= (ushort) item.Amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3234,7 +3247,7 @@ namespace ArchiSteamFarm {
|
|||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List<uint>> inventorySets = Trading.GetInventorySets(inventory);
|
||||
appIDs.IntersectWith(inventorySets.Where(kv => kv.Value.Count >= MinimumCardsPerBadge).Select(kv => kv.Key.RealAppID));
|
||||
appIDs.IntersectWith(inventorySets.Where(kv => kv.Value.Count >= MinCardsPerBadge).Select(kv => kv.Key.RealAppID));
|
||||
|
||||
if (appIDs.Count == 0) {
|
||||
return;
|
||||
|
|
Loading…
Reference in a new issue