Compare commits

..

31 Commits

Author SHA1 Message Date
JustArchi
53dcd22cea Restart farming also when user registers key outside of ASF
This is now possible thanks to license callback
2016-07-30 18:28:28 +02:00
Łukasz Domeradzki
af050ae67e Merge pull request #317 from stackia/master
Revert "Make WCF interface async"
2016-07-30 06:19:28 +02:00
stackia
914936acdc Revert "Make WCF interface async"
This reverts commit fbb24506e2.
2016-07-30 12:17:01 +08:00
Łukasz Domeradzki
4bba55e8fd Merge pull request #316 from stackia/master
Make WCF interface async
2016-07-30 05:59:21 +02:00
stackia
3e1358b363 Add number of cards drop remaining in "!status" response 2016-07-30 11:52:21 +08:00
stackia
fbb24506e2 Make WCF interface async 2016-07-30 11:51:00 +08:00
JustArchi
48eae1be1a Final CodeStyle update
So it turns out that those options are under C# Text Editor, weird, at least they're exported now
2016-07-30 00:51:43 +02:00
JustArchi
6bbe543fdd Update CodeStyle
Include only options for: All languages, General and C#, I don't need to bloat anybody with other things
2016-07-30 00:47:31 +02:00
Łukasz Domeradzki
afe1d57605 Merge pull request #313 from stackia/master
Update ReSharper DotSettings file
2016-07-30 00:43:40 +02:00
stackia
5596e55c7b Update ReSharper DotSettings file
This purges all green squiggly underlines on my machine.
2016-07-30 06:39:46 +08:00
JustArchi
73cefc697e Move and regenerate CodeStyle 2016-07-30 00:32:34 +02:00
JustArchi
5680929203 Misc cleanup 2016-07-30 00:24:19 +02:00
JustArchi
c94547c68e Move FarmingOrder a bit higher and add missing example.json property
In general we should prioritize properties according to the ones user will want to modify first, but as it's unpredictable, we can only guess and do it more or less
2016-07-30 00:17:58 +02:00
Łukasz Domeradzki
11c748192f Merge pull request #312 from stackia/master
Add an option to set farming order
2016-07-30 00:09:44 +02:00
stackia
8cc3fec432 Implement Equals method on Game so it properly works with HashSet<T> 2016-07-30 05:56:31 +08:00
stackia
7baa54377a Add [JsonProperty] on Game's properties 2016-07-30 05:50:51 +08:00
stackia
4bc7fded2d Use new ConcurrentHashSet.AddRange 2016-07-30 05:49:15 +08:00
stackia
24aa1b4873 Merge remote-tracking branch 'upstream/master' 2016-07-30 05:48:02 +08:00
stackia
561f8f61df Check argument range in Game constructor 2016-07-30 05:46:21 +08:00
JustArchi
c33f575c40 Implement AddRange() + refactoring 2016-07-29 23:36:57 +02:00
stackia
2c54f6b051 Use ConcurrentHashSet<Game> for GamesToFarm and fix game didn't get sorted after first badge page 2016-07-30 05:34:57 +08:00
Stackie Jia
e0385cd343 Merge branch 'master' into master 2016-07-30 05:14:04 +08:00
stackia
80abd3ed69 Add an option to set farming order 2016-07-30 05:06:40 +08:00
JustArchi
e726558e72 Bump 2016-07-29 22:36:00 +02:00
JustArchi
07af05ad00 Revert recent confirmations rate-limiting, add debug 2016-07-29 22:34:52 +02:00
JustArchi
ef5b108b34 Correct MaxConfirmationsPerRequest 2016-07-29 14:48:34 +02:00
JustArchi
1ee9fec845 Bump 2016-07-29 03:40:41 +02:00
JustArchi
4bf1462381 Bump 2016-07-29 03:36:32 +02:00
JustArchi
e41d2cf37e Report on confirmation issues 2016-07-29 01:02:58 +02:00
JustArchi
fac5e65035 Fix accepting over 30 confirmations 2016-07-29 00:46:17 +02:00
JustArchi
b44115711b Don't stop keys forwarding if initial bot gets OnCooldown
In this case, move to the next one, try to redeem, and get the package data from it instead
2016-07-28 21:40:40 +02:00
12 changed files with 277 additions and 126 deletions

1
.gitignore vendored
View File

@@ -23,6 +23,7 @@ out/
## files generated by popular Visual Studio add-ons.
# User-specific files
.vs/
*.suo
*.user
*.sln.docstates

View File

@@ -1,8 +1,22 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_OWNER_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER_SAME_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INVOCABLE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CATCH_ON_NEW_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ELSE_ON_NEW_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FINALLY_ON_NEW_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHING_EMPTY_BRACES/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/TYPE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AES/@EntryIndexedValue">AES</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ASF/@EntryIndexedValue">ASF</s:String>

View File

@@ -377,6 +377,11 @@ namespace ArchiSteamFarm {
}
if (response != null) {
// TODO: Remove me
if (!response.Success) {
Logging.LogGenericError("HandleConfirmations() debug content: " + json, Bot.BotName);
}
return response.Success;
}

View File

@@ -253,41 +253,45 @@ namespace ArchiSteamFarm {
Trading?.Dispose();
}
internal async Task AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
internal async Task<bool> AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
if (BotDatabase.MobileAuthenticator == null) {
return;
return false;
}
HashSet<MobileAuthenticator.Confirmation> confirmations = await BotDatabase.MobileAuthenticator.GetConfirmations().ConfigureAwait(false);
if ((confirmations == null) || (confirmations.Count == 0)) {
return;
return true;
}
if (acceptedType != Steam.ConfirmationDetails.EType.Unknown) {
if (confirmations.RemoveWhere(confirmation => (confirmation.Type != acceptedType) && (confirmation.Type != Steam.ConfirmationDetails.EType.Other)) > 0) {
if (confirmations.Count == 0) {
return;
return true;
}
}
}
if ((acceptedSteamID != 0) || ((acceptedTradeIDs != null) && (acceptedTradeIDs.Count > 0))) {
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails)).ConfigureAwait(false);
HashSet<MobileAuthenticator.Confirmation> ignoredConfirmations = new HashSet<MobileAuthenticator.Confirmation>(detailsResults.Where(details => (details != null) && (
((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) ||
((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID))
)).Select(details => details.Confirmation));
if (ignoredConfirmations.Count > 0) {
confirmations.ExceptWith(ignoredConfirmations);
if (confirmations.Count == 0) {
return;
}
}
if ((acceptedSteamID == 0) && ((acceptedTradeIDs == null) || (acceptedTradeIDs.Count == 0))) {
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
}
await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails)).ConfigureAwait(false);
HashSet<MobileAuthenticator.Confirmation> ignoredConfirmations = new HashSet<MobileAuthenticator.Confirmation>(detailsResults.Where(details => (details != null) && (
((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) ||
((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID))
)).Select(details => details.Confirmation));
if (ignoredConfirmations.Count == 0) {
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
}
confirmations.ExceptWith(ignoredConfirmations);
if (confirmations.Count == 0) {
return true;
}
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
}
internal async Task<bool> RefreshSession() {
@@ -629,7 +633,10 @@ namespace ArchiSteamFarm {
}
if (CardsFarmer.CurrentGamesFarming.Count > 0) {
return "Bot " + BotName + " is farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) + " and has a total of " + CardsFarmer.GamesToFarm.Count + " games left to farm.";
return "Bot " + BotName + " is farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) +
" and has a total of " + CardsFarmer.GamesToFarm.Count + " games left, " +
CardsFarmer.GamesToFarm.Select(g => (int) g.CardsRemaining).DefaultIfEmpty(0).Sum() +
" cards drop remaining to farm.";
}
return "Bot " + BotName + " is not farming anything.";
@@ -800,8 +807,11 @@ namespace ArchiSteamFarm {
return "That bot doesn't have ASF 2FA enabled!";
}
await AcceptConfirmations(confirm).ConfigureAwait(false);
return "Done!";
if (await AcceptConfirmations(confirm).ConfigureAwait(false)) {
return "Success!";
}
return "Something went wrong!";
}
private static async Task<string> Response2FAConfirm(ulong steamID, string botName, bool confirm) {
@@ -973,7 +983,7 @@ namespace ArchiSteamFarm {
}
bool alreadyHandled = false;
foreach (Bot bot in Bots.Values.Where(bot => (bot != this) && bot.SteamClient.IsConnected && result.Items.Keys.Any(packageID => !bot.OwnedPackageIDs.Contains(packageID)))) {
foreach (Bot bot in Bots.Values.Where(bot => (bot != this) && bot.SteamClient.IsConnected && ((result.Items.Count == 0) || result.Items.Keys.Any(packageID => !bot.OwnedPackageIDs.Contains(packageID))))) {
ArchiHandler.PurchaseResponseCallback otherResult = await bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
if (otherResult == null) {
@@ -994,6 +1004,14 @@ namespace ArchiSteamFarm {
if (alreadyHandled) {
break;
}
if (result.Items.Count != 0) {
continue;
}
foreach (KeyValuePair<uint, string> item in otherResult.Items) {
result.Items[item.Key] = item.Value;
}
}
key = reader.ReadLine(); // Next key
@@ -1602,7 +1620,6 @@ namespace ArchiSteamFarm {
return;
}
bool acceptedSomething = false;
foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => (gid != 0) && !HandledGifts.Contains(gid))) {
HandledGifts.Add(gid);
@@ -1610,19 +1627,14 @@ namespace ArchiSteamFarm {
await LimitGiftsRequestsAsync().ConfigureAwait(false);
if (await ArchiWebHandler.AcceptGift(gid).ConfigureAwait(false)) {
acceptedSomething = true;
Logging.LogGenericInfo("Success!", BotName);
} else {
Logging.LogGenericInfo("Failed!", BotName);
}
}
if (acceptedSomething) {
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
}
}
private void OnLicenseList(SteamApps.LicenseListCallback callback) {
private async void OnLicenseList(SteamApps.LicenseListCallback callback) {
if (callback?.LicenseList == null) {
Logging.LogNullError(nameof(callback) + " || " + nameof(callback.LicenseList), BotName);
return;
@@ -1635,6 +1647,9 @@ namespace ArchiSteamFarm {
}
OwnedPackageIDs.TrimExcess();
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
}
private void OnChatInvite(SteamFriends.ChatInviteCallback callback) {
@@ -1838,9 +1853,6 @@ namespace ArchiSteamFarm {
}
Trading.CheckTrades().Forget();
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
CardsFarmer.StartFarming().Forget();
break;
case EResult.NoConnection:
case EResult.ServiceUnavailable:
@@ -1968,14 +1980,9 @@ namespace ArchiSteamFarm {
}
}
private async void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
private void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
if (callback == null) {
Logging.LogNullError(nameof(callback), BotName);
return;
}
if (callback.PurchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
}
}
}

View File

@@ -34,6 +34,12 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal sealed class BotConfig {
internal enum EFarmingOrder : byte {
Unordered,
MostCardDropRemainingFirst,
FewestCardDropRemainingFirst
}
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Enabled = false;
@@ -68,6 +74,9 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool DismissInventoryNotifications = true;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly EFarmingOrder FarmingOrder = EFarmingOrder.Unordered;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool FarmOffline = false;
@@ -113,7 +122,6 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly HashSet<uint> GamesPlayedWhileIdle = new HashSet<uint>();
internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
Logging.LogNullError(nameof(filePath));

View File

@@ -24,7 +24,6 @@
using HtmlAgilityPack;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -35,10 +34,47 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class CardsFarmer : IDisposable {
internal sealed class Game {
[JsonProperty]
internal readonly uint AppID;
[JsonProperty]
internal float HoursPlayed;
[JsonProperty]
internal byte CardsRemaining;
internal Game(uint appID, float hoursPlayed, byte cardsRemaining) {
if ((appID == 0) || (hoursPlayed < 0)) {
throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(hoursPlayed));
}
AppID = appID;
HoursPlayed = hoursPlayed;
CardsRemaining = cardsRemaining;
}
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
return obj is Game && Equals((Game) obj);
}
public override int GetHashCode() => (int) AppID;
private bool Equals(Game other) => AppID == other.AppID;
}
internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network
[JsonProperty]
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
internal readonly ConcurrentHashSet<Game> GamesToFarm = new ConcurrentHashSet<Game>();
[JsonProperty]
internal readonly ConcurrentHashSet<uint> CurrentGamesFarming = new ConcurrentHashSet<uint>();
@@ -143,7 +179,8 @@ namespace ArchiSteamFarm {
}
} else {
if (FarmMultiple()) {
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Keys), Bot.BotName);
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Select(g => g.AppID)),
Bot.BotName);
} else {
NowFarming = false;
return;
@@ -153,7 +190,7 @@ namespace ArchiSteamFarm {
} else { // If we have unrestricted card drops, we use simple algorithm
Logging.LogGenericInfo("Chosen farming algorithm: Simple", Bot.BotName);
while (GamesToFarm.Count > 0) {
uint appID = GamesToFarm.Keys.FirstOrDefault();
uint appID = GamesToFarm.First().AppID;
if (await FarmSolo(appID).ConfigureAwait(false)) {
continue;
}
@@ -218,7 +255,7 @@ namespace ArchiSteamFarm {
return;
}
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Values.Min() < 2)) {
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Min(g => g.HoursPlayed) < 2)) {
// If we have Complex algorithm and some games to boost, it's also worth to make a check
// That's because we would check for new games after our current round anyway
await StopFarming().ConfigureAwait(false);
@@ -226,15 +263,15 @@ namespace ArchiSteamFarm {
}
}
private static HashSet<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
private static HashSet<uint> GetGamesToFarmSolo(IEnumerable<Game> gamesToFarm) {
if (gamesToFarm == null) {
Logging.LogNullError(nameof(gamesToFarm));
return null;
}
HashSet<uint> result = new HashSet<uint>();
foreach (KeyValuePair<uint, float> keyValue in gamesToFarm.Where(keyValue => keyValue.Value >= 2)) {
result.Add(keyValue.Key);
foreach (Game game in gamesToFarm.Where(g => g.HoursPlayed >= 2)) {
result.Add(game.AppID);
}
return result;
@@ -271,6 +308,7 @@ namespace ArchiSteamFarm {
CheckPage(htmlDocument);
if (maxPages == 1) {
SortGamesToFarm();
return GamesToFarm.Count > 0;
}
@@ -283,9 +321,30 @@ namespace ArchiSteamFarm {
}
await Task.WhenAll(tasks).ConfigureAwait(false);
SortGamesToFarm();
return GamesToFarm.Count > 0;
}
private void SortGamesToFarm() {
List<Game> gamesToFarm;
switch (Bot.BotConfig.FarmingOrder) {
case BotConfig.EFarmingOrder.MostCardDropRemainingFirst:
gamesToFarm = GamesToFarm.OrderByDescending(g => g.CardsRemaining).ToList();
break;
case BotConfig.EFarmingOrder.FewestCardDropRemainingFirst:
gamesToFarm = GamesToFarm.OrderBy(g => g.CardsRemaining).ToList();
break;
default:
return;
}
GamesToFarm.Clear();
GamesToFarm.AddRange(gamesToFarm);
GamesToFarm.TrimExcess();
}
private void CheckPage(HtmlDocument htmlDocument) {
if (htmlDocument == null) {
Logging.LogNullError(nameof(htmlDocument), Bot.BotName);
@@ -303,6 +362,27 @@ namespace ArchiSteamFarm {
continue; // This game is not needed for farming
}
HtmlNode progressNode = htmlNode.SelectSingleNode(".//span[@class='progress_info_bold']");
if (progressNode == null) {
continue; // e.g. Holiday Sale 2015
}
string progress = progressNode.InnerText;
if (string.IsNullOrEmpty(progress)) {
Logging.LogNullError(nameof(progress), Bot.BotName);
return;
}
byte cardsRemaining = 0;
Match progressMatch = Regex.Match(progress, @"\d+");
if (progressMatch.Success) {
if (!byte.TryParse(progressMatch.Value, out cardsRemaining)) {
Logging.LogNullError(nameof(cardsRemaining), Bot.BotName);
return;
}
}
string steamLink = farmingNode.GetAttributeValue("href", null);
if (string.IsNullOrEmpty(steamLink)) {
Logging.LogNullError(nameof(steamLink), Bot.BotName);
@@ -347,15 +427,15 @@ namespace ArchiSteamFarm {
float hours = 0;
Match match = Regex.Match(hoursString, @"[0-9\.,]+");
if (match.Success) {
if (!float.TryParse(match.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours)) {
Match hoursMatch = Regex.Match(hoursString, @"[0-9\.,]+");
if (hoursMatch.Success) {
if (!float.TryParse(hoursMatch.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours)) {
Logging.LogNullError(nameof(hours), Bot.BotName);
return;
}
}
GamesToFarm[appID] = hours;
GamesToFarm.Add(new Game(appID, hours, cardsRemaining));
}
}
@@ -414,6 +494,9 @@ namespace ArchiSteamFarm {
}
}
Game game = GamesToFarm.First(g => g.AppID == appID);
game.CardsRemaining = cardsRemaining;
Logging.LogGenericInfo("Status for " + appID + ": " + cardsRemaining + " cards remaining", Bot.BotName);
return cardsRemaining > 0;
}
@@ -424,10 +507,10 @@ namespace ArchiSteamFarm {
}
float maxHour = 0;
foreach (KeyValuePair<uint, float> game in GamesToFarm) {
CurrentGamesFarming.Add(game.Key);
if (game.Value > maxHour) {
maxHour = game.Value;
foreach (Game game in GamesToFarm) {
CurrentGamesFarming.Add(game.AppID);
if (game.HoursPlayed > maxHour) {
maxHour = game.HoursPlayed;
}
if (CurrentGamesFarming.Count >= MaxGamesPlayedConcurrently) {
@@ -464,12 +547,14 @@ namespace ArchiSteamFarm {
return false;
}
float hours;
if (!GamesToFarm.TryRemove(appID, out hours)) {
Game game = GamesToFarm.FirstOrDefault(g => g.AppID == appID);
if (game == null) {
return false;
}
TimeSpan timeSpan = TimeSpan.FromHours(hours);
GamesToFarm.Remove(game);
TimeSpan timeSpan = TimeSpan.FromHours(game.HoursPlayed);
Logging.LogGenericInfo("Done farming: " + appID + " after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!", Bot.BotName);
return true;
}
@@ -496,7 +581,8 @@ namespace ArchiSteamFarm {
}
// Don't forget to update our GamesToFarm hours
GamesToFarm[appID] += (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
Game game = GamesToFarm.First(g => g.AppID == appID);
game.HoursPlayed += (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
if (!success) {
break;
@@ -529,8 +615,8 @@ namespace ArchiSteamFarm {
// Don't forget to update our GamesToFarm hours
float timePlayed = (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
foreach (uint appID in appIDs) {
GamesToFarm[appID] += timePlayed;
foreach (Game game in GamesToFarm.Where(game => appIDs.Contains(game.AppID))) {
game.HoursPlayed += timePlayed;
}
if (!success) {

View File

@@ -25,7 +25,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace ArchiSteamFarm {
@@ -36,6 +35,9 @@ namespace ArchiSteamFarm {
public bool IsReadOnly => false;
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(HashSet, Lock);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
void ICollection<T>.Add(T item) => Add(item);
public int Count {
get {
Lock.EnterReadLock();
@@ -48,17 +50,6 @@ namespace ArchiSteamFarm {
}
}
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
public bool Add(T item) {
Lock.EnterWriteLock();
try {
return HashSet.Add(item);
} finally {
Lock.ExitWriteLock();
}
}
public void Clear() {
Lock.EnterWriteLock();
@@ -69,17 +60,6 @@ namespace ArchiSteamFarm {
}
}
public void ClearAndTrim() {
Lock.EnterWriteLock();
try {
HashSet.Clear();
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
public bool Contains(T item) {
Lock.EnterReadLock();
@@ -100,16 +80,6 @@ namespace ArchiSteamFarm {
}
}
public void TrimExcess() {
Lock.EnterWriteLock();
try {
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
public void Dispose() => Lock.Dispose();
public void CopyTo(T[] array, int arrayIndex) {
@@ -122,8 +92,47 @@ namespace ArchiSteamFarm {
}
}
void ICollection<T>.Add(T item) => Add(item);
internal void Add(T item) {
Lock.EnterWriteLock();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
try {
HashSet.Add(item);
} finally {
Lock.ExitWriteLock();
}
}
internal void AddRange(IEnumerable<T> items) {
Lock.EnterWriteLock();
try {
foreach (T item in items) {
HashSet.Add(item);
}
} finally {
Lock.ExitWriteLock();
}
}
internal void ClearAndTrim() {
Lock.EnterWriteLock();
try {
HashSet.Clear();
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
internal void TrimExcess() {
Lock.EnterWriteLock();
try {
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
}
}

View File

@@ -24,7 +24,7 @@
namespace ArchiSteamFarm {
internal static class SharedInfo {
internal const string Version = "2.1.3.2";
internal const string Version = "2.1.3.5";
internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";

View File

@@ -10,6 +10,7 @@
"SteamMasterClanID": 0,
"CardDropsRestricted": false,
"DismissInventoryNotifications": true,
"FarmingOrder": 0,
"FarmOffline": false,
"HandleOfflineMessages": false,
"AcceptGifts": false,

38
CodeStyle.vssettings Normal file

File diff suppressed because one or more lines are too long

View File

@@ -41,6 +41,12 @@ namespace ConfigGenerator {
ProtectedDataForCurrentUser
}
internal enum EFarmingOrder : byte {
Unordered,
MostCardDropRemainingFirst,
FewestCardDropRemainingFirst
}
[JsonProperty(Required = Required.DisallowNull)]
public bool Enabled { get; set; } = false;
@@ -75,6 +81,9 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public bool DismissInventoryNotifications { get; set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
public EFarmingOrder FarmingOrder { get; set; } = EFarmingOrder.Unordered;
[JsonProperty(Required = Required.DisallowNull)]
public bool FarmOffline { get; set; } = false;

File diff suppressed because one or more lines are too long