Skip to content

Commit bfaa99a

Browse files
committed
Revolution is here
1 parent 1ff4ed0 commit bfaa99a

File tree

4 files changed

+204
-32
lines changed

4 files changed

+204
-32
lines changed

ArchiSteamFarm/ArchiHandler.cs

+16-2
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,23 @@ internal void DeclineClanInvite(ulong clanID) {
9090
Client.Send(request);
9191
}
9292

93-
internal void PlayGames(params ulong[] gameIDs) {
93+
internal void PlayGames(params uint[] gameIDs) {
9494
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
95-
foreach (ulong gameID in gameIDs) {
95+
foreach (uint gameID in gameIDs) {
96+
if (gameID == 0) {
97+
continue;
98+
}
99+
100+
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
101+
game_id = new GameID(gameID),
102+
});
103+
}
104+
Client.Send(request);
105+
}
106+
107+
internal void PlayGames(ICollection<uint> gameIDs) {
108+
var request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
109+
foreach (uint gameID in gameIDs) {
96110
if (gameID == 0) {
97111
continue;
98112
}

ArchiSteamFarm/Bot.cs

+1-5
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,6 @@ internal async Task OnFarmingFinished() {
254254
}
255255
}
256256

257-
internal void PlayGame(params ulong[] gameIDs) {
258-
ArchiHandler.PlayGames(gameIDs);
259-
}
260-
261257
private void HandleCallbacks() {
262258
TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep);
263259
while (IsRunning) {
@@ -291,7 +287,7 @@ private void ResponseStatus(ulong steamID, string botName = null) {
291287
}
292288

293289
if (bot.CardsFarmer.CurrentGame > 0) {
294-
SendMessageToUser(steamID, "Bot " + bot.BotName + " is currently farming appID " + bot.CardsFarmer.CurrentGame + " and has total of " + bot.CardsFarmer.GamesLeft + " games left to farm");
290+
SendMessageToUser(steamID, "Bot " + bot.BotName + " is currently farming appID " + bot.CardsFarmer.CurrentGame + " and has total of " + bot.CardsFarmer.GamesLeftCount + " games left to farm");
295291
}
296292
SendMessageToUser(steamID, "Currently " + Bots.Count + " bots are running");
297293
}

ArchiSteamFarm/CardsFarmer.cs

+186-24
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,102 @@ limitations under the License.
2323
*/
2424

2525
using HtmlAgilityPack;
26+
using System.Collections.Concurrent;
2627
using System.Collections.Generic;
28+
using System.Globalization;
29+
using System.Text.RegularExpressions;
2730
using System.Threading;
2831
using System.Threading.Tasks;
2932

3033
namespace ArchiSteamFarm {
3134
internal class CardsFarmer {
3235
private const byte StatusCheckSleep = 5; // In minutes, how long to wait before checking the appID again
3336

37+
private readonly ConcurrentDictionary<uint, double> GamesToFarm = new ConcurrentDictionary<uint, double>();
3438
private readonly ManualResetEvent FarmResetEvent = new ManualResetEvent(false);
3539
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
3640
private readonly Bot Bot;
3741

3842
internal uint CurrentGame { get; private set; } = 0;
39-
internal int GamesLeft { get; private set; } = 0;
43+
internal int GamesLeftCount { get; private set; } = 0;
4044

4145
private volatile bool NowFarming = false;
4246

4347
internal CardsFarmer(Bot bot) {
4448
Bot = bot;
4549
}
4650

51+
internal static List<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, double> gamesToFarm) {
52+
if (gamesToFarm == null) {
53+
return null;
54+
}
55+
56+
List<uint> result = new List<uint>();
57+
foreach (KeyValuePair<uint, double> keyValue in gamesToFarm) {
58+
if (keyValue.Value >= 2) {
59+
result.Add(keyValue.Key);
60+
}
61+
}
62+
63+
return result;
64+
}
65+
66+
internal static uint GetAnyGameToFarm(ConcurrentDictionary<uint, double> gamesToFarm) {
67+
if (gamesToFarm == null) {
68+
return 0;
69+
}
70+
71+
foreach (uint appID in gamesToFarm.Keys) {
72+
return appID;
73+
}
74+
75+
return 0;
76+
}
77+
78+
internal bool FarmMultiple() {
79+
if (GamesToFarm == null || GamesToFarm.Count == 0) {
80+
return true;
81+
}
82+
83+
double maxHour = -1;
84+
85+
foreach (KeyValuePair<uint, double> keyValue in GamesToFarm) {
86+
if (keyValue.Value > maxHour) {
87+
maxHour = keyValue.Value;
88+
}
89+
}
90+
91+
Logging.LogGenericInfo(Bot.BotName, "Now farming: " + string.Join(", ", GamesToFarm.Keys));
92+
if (Farm(maxHour, GamesToFarm.Keys)) {
93+
return true;
94+
} else {
95+
GamesLeftCount = 0;
96+
CurrentGame = 0;
97+
NowFarming = false;
98+
return false;
99+
}
100+
}
101+
102+
internal async Task<bool> FarmSolo(uint appID) {
103+
if (appID == 0) {
104+
return false;
105+
}
106+
107+
CurrentGame = appID;
108+
Logging.LogGenericInfo(Bot.BotName, "Now farming: " + appID);
109+
if (await Farm(appID).ConfigureAwait(false)) {
110+
double hours;
111+
GamesToFarm.TryRemove(appID, out hours);
112+
GamesLeftCount--;
113+
return true;
114+
} else {
115+
GamesLeftCount = 0;
116+
CurrentGame = 0;
117+
NowFarming = false;
118+
return false;
119+
}
120+
}
121+
47122
internal async Task StartFarming() {
48123
await StopFarming().ConfigureAwait(false);
49124

@@ -71,7 +146,6 @@ internal async Task StartFarming() {
71146
}
72147

73148
// Find APPIDs we need to farm
74-
List<uint> appIDs = new List<uint>();
75149
for (var page = 1; page <= maxPages; page++) {
76150
Logging.LogGenericInfo(Bot.BotName, "Checking page: " + page + "/" + maxPages);
77151

@@ -90,43 +164,98 @@ internal async Task StartFarming() {
90164
foreach (HtmlNode badgesPageNode in badgesPageNodes) {
91165
string steamLink = badgesPageNode.GetAttributeValue("href", null);
92166
if (steamLink == null) {
93-
Logging.LogGenericError(Bot.BotName, "Couldn't get steamLink for one of the games: " + badgesPageNode.OuterHtml);
94167
continue;
95168
}
96169

97170
uint appID = (uint) Utilities.OnlyNumbers(steamLink);
98171
if (appID == 0) {
99-
Logging.LogGenericError(Bot.BotName, "Couldn't get appID for one of the games: " + badgesPageNode.OuterHtml);
100172
continue;
101173
}
102174

103175
if (Bot.Blacklist.Contains(appID)) {
104176
continue;
105177
}
106178

107-
appIDs.Add(appID);
179+
// We assume that every game has at least 2 hours played, until we actually check them
180+
GamesToFarm.AddOrUpdate(appID, 2, (key, value) => 2);
181+
}
182+
}
183+
184+
// If we have restricted card drops, actually do check all games that are left to farm
185+
if (Bot.CardDropsRestricted) {
186+
foreach (uint appID in GamesToFarm.Keys) {
187+
Logging.LogGenericInfo(Bot.BotName, "Checking hours of appID: " + appID);
188+
HtmlDocument appPage = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
189+
if (appPage == null) {
190+
continue;
191+
}
192+
193+
HtmlNode appNode = appPage.DocumentNode.SelectSingleNode("//div[@class='badge_title_stats_playtime']");
194+
if (appNode == null) {
195+
continue;
196+
}
197+
198+
string hoursString = appNode.InnerText;
199+
if (string.IsNullOrEmpty(hoursString)) {
200+
continue;
201+
}
202+
203+
hoursString = Regex.Match(hoursString, @"[0-9\.,]+").Value;
204+
double hours;
205+
206+
if (string.IsNullOrEmpty(hoursString)) {
207+
hours = 0;
208+
} else {
209+
hours = double.Parse(hoursString, CultureInfo.InvariantCulture);
210+
}
211+
212+
GamesToFarm[appID] = hours;
108213
}
109214
}
110215

111216
Logging.LogGenericInfo(Bot.BotName, "Farming in progress...");
112-
NowFarming = appIDs.Count > 0;
217+
218+
GamesLeftCount = GamesToFarm.Count;
219+
NowFarming = GamesLeftCount > 0;
113220
Semaphore.Release();
114221

115-
GamesLeft = appIDs.Count;
116-
117-
// Start farming
118-
while (appIDs.Count > 0) {
119-
uint appID = appIDs[0];
120-
CurrentGame = appID;
121-
Logging.LogGenericInfo(Bot.BotName, "Now farming: " + appID);
122-
if (await Farm(appID).ConfigureAwait(false)) {
123-
appIDs.Remove(appID);
124-
GamesLeft--;
125-
} else {
126-
GamesLeft = 0;
127-
CurrentGame = 0;
128-
NowFarming = false;
129-
return;
222+
// Now the algorithm used for farming depends on whether account is restricted or not
223+
if (Bot.CardDropsRestricted) {
224+
// If we have restricted card drops, we use complex algorithm, which prioritizes farming solo titles >= 2 hours, then all at once, until any game hits mentioned 2 hours
225+
Logging.LogGenericInfo(Bot.BotName, "Chosen farming algorithm: Complex");
226+
while (GamesLeftCount > 0) {
227+
List<uint> gamesToFarmSolo = GetGamesToFarmSolo(GamesToFarm);
228+
if (gamesToFarmSolo.Count > 0) {
229+
while (gamesToFarmSolo.Count > 0) {
230+
uint appID = gamesToFarmSolo[0];
231+
bool success = await FarmSolo(appID).ConfigureAwait(false);
232+
if (success) {
233+
Logging.LogGenericInfo(Bot.BotName, "Done farming: " + appID);
234+
gamesToFarmSolo.Remove(appID);
235+
} else {
236+
return;
237+
}
238+
}
239+
} else {
240+
bool success = FarmMultiple();
241+
if (success) {
242+
Logging.LogGenericInfo(Bot.BotName, "Done farming: " + string.Join(", ", GamesToFarm.Keys));
243+
} else {
244+
return;
245+
}
246+
}
247+
}
248+
} else {
249+
// If we have unrestricted card drops, we use simple algorithm and farm cards one-by-one
250+
Logging.LogGenericInfo(Bot.BotName, "Chosen farming algorithm: Simple");
251+
while (GamesLeftCount > 0) {
252+
uint appID = GetAnyGameToFarm(GamesToFarm);
253+
bool success = await FarmSolo(appID).ConfigureAwait(false);
254+
if (success) {
255+
Logging.LogGenericInfo(Bot.BotName, "Done farming: " + appID);
256+
} else {
257+
return;
258+
}
130259
}
131260
}
132261

@@ -167,8 +296,8 @@ internal async Task StopFarming() {
167296
return result;
168297
}
169298

170-
private async Task<bool> Farm(ulong appID) {
171-
Bot.PlayGame(appID);
299+
private async Task<bool> Farm(uint appID) {
300+
Bot.ArchiHandler.PlayGames(appID);
172301

173302
bool success = true;
174303
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
@@ -181,9 +310,42 @@ private async Task<bool> Farm(ulong appID) {
181310
keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
182311
}
183312

184-
Bot.PlayGame(0);
313+
Bot.ArchiHandler.PlayGames(0);
185314
Logging.LogGenericInfo(Bot.BotName, "Stopped farming: " + appID);
186315
return success;
187316
}
317+
318+
private bool Farm(double maxHour, ICollection<uint> appIDs) {
319+
if (maxHour >= 2) {
320+
return true;
321+
}
322+
323+
Bot.ArchiHandler.PlayGames(appIDs);
324+
325+
bool success = true;
326+
while (maxHour < 2) {
327+
Logging.LogGenericInfo(Bot.BotName, "Still farming: " + string.Join(", ", appIDs));
328+
if (FarmResetEvent.WaitOne(1000 * 60 * StatusCheckSleep)) {
329+
success = false;
330+
break;
331+
}
332+
333+
// Don't forget to update our GamesToFarm hours
334+
double timePlayed = StatusCheckSleep / 60.0;
335+
foreach (KeyValuePair<uint, double> keyValue in GamesToFarm) {
336+
if (!appIDs.Contains(keyValue.Key)) {
337+
continue;
338+
}
339+
340+
GamesToFarm[keyValue.Key] = keyValue.Value + timePlayed;
341+
}
342+
343+
maxHour += timePlayed;
344+
}
345+
346+
Bot.ArchiHandler.PlayGames(0);
347+
Logging.LogGenericInfo(Bot.BotName, "Stopped farming: " + string.Join(", ", appIDs));
348+
return success;
349+
}
188350
}
189351
}

ArchiSteamFarm/config/example.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
<!-- This switch defines if the account has card drops restricted -->
4747
<!-- Restricted card drops means that the account doesn't receive any steam cards until it plays the game for at least 2 hours -->
4848
<!-- As there is no magical way to detect it by ASF, I made this option config-based switch -->
49-
<!-- TIP: At the moment this option changes nothing, but in future it may affect cards farming algorithm -->
49+
<!-- TIP: Based on this parameter, ASF will try to choose the most optimized cards farming algorithm for this account -->
5050
<CardDropsRestricted type="bool" value="false"/>
5151

5252
<!-- This switch defines if bot should disconnect once farming is finished -->

0 commit comments

Comments
 (0)