@@ -23,27 +23,102 @@ limitations under the License.
23
23
*/
24
24
25
25
using HtmlAgilityPack ;
26
+ using System . Collections . Concurrent ;
26
27
using System . Collections . Generic ;
28
+ using System . Globalization ;
29
+ using System . Text . RegularExpressions ;
27
30
using System . Threading ;
28
31
using System . Threading . Tasks ;
29
32
30
33
namespace ArchiSteamFarm {
31
34
internal class CardsFarmer {
32
35
private const byte StatusCheckSleep = 5 ; // In minutes, how long to wait before checking the appID again
33
36
37
+ private readonly ConcurrentDictionary < uint , double > GamesToFarm = new ConcurrentDictionary < uint , double > ( ) ;
34
38
private readonly ManualResetEvent FarmResetEvent = new ManualResetEvent ( false ) ;
35
39
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim ( 1 ) ;
36
40
private readonly Bot Bot ;
37
41
38
42
internal uint CurrentGame { get ; private set ; } = 0 ;
39
- internal int GamesLeft { get ; private set ; } = 0 ;
43
+ internal int GamesLeftCount { get ; private set ; } = 0 ;
40
44
41
45
private volatile bool NowFarming = false ;
42
46
43
47
internal CardsFarmer ( Bot bot ) {
44
48
Bot = bot ;
45
49
}
46
50
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
+
47
122
internal async Task StartFarming ( ) {
48
123
await StopFarming ( ) . ConfigureAwait ( false ) ;
49
124
@@ -71,7 +146,6 @@ internal async Task StartFarming() {
71
146
}
72
147
73
148
// Find APPIDs we need to farm
74
- List < uint > appIDs = new List < uint > ( ) ;
75
149
for ( var page = 1 ; page <= maxPages ; page ++ ) {
76
150
Logging . LogGenericInfo ( Bot . BotName , "Checking page: " + page + "/" + maxPages ) ;
77
151
@@ -90,43 +164,98 @@ internal async Task StartFarming() {
90
164
foreach ( HtmlNode badgesPageNode in badgesPageNodes ) {
91
165
string steamLink = badgesPageNode . GetAttributeValue ( "href" , null ) ;
92
166
if ( steamLink == null ) {
93
- Logging . LogGenericError ( Bot . BotName , "Couldn't get steamLink for one of the games: " + badgesPageNode . OuterHtml ) ;
94
167
continue ;
95
168
}
96
169
97
170
uint appID = ( uint ) Utilities . OnlyNumbers ( steamLink ) ;
98
171
if ( appID == 0 ) {
99
- Logging . LogGenericError ( Bot . BotName , "Couldn't get appID for one of the games: " + badgesPageNode . OuterHtml ) ;
100
172
continue ;
101
173
}
102
174
103
175
if ( Bot . Blacklist . Contains ( appID ) ) {
104
176
continue ;
105
177
}
106
178
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 ;
108
213
}
109
214
}
110
215
111
216
Logging . LogGenericInfo ( Bot . BotName , "Farming in progress..." ) ;
112
- NowFarming = appIDs . Count > 0 ;
217
+
218
+ GamesLeftCount = GamesToFarm . Count ;
219
+ NowFarming = GamesLeftCount > 0 ;
113
220
Semaphore . Release ( ) ;
114
221
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
+ }
130
259
}
131
260
}
132
261
@@ -167,8 +296,8 @@ internal async Task StopFarming() {
167
296
return result ;
168
297
}
169
298
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 ) ;
172
301
173
302
bool success = true ;
174
303
bool ? keepFarming = await ShouldFarm ( appID ) . ConfigureAwait ( false ) ;
@@ -181,9 +310,42 @@ private async Task<bool> Farm(ulong appID) {
181
310
keepFarming = await ShouldFarm ( appID ) . ConfigureAwait ( false ) ;
182
311
}
183
312
184
- Bot . PlayGame ( 0 ) ;
313
+ Bot . ArchiHandler . PlayGames ( 0 ) ;
185
314
Logging . LogGenericInfo ( Bot . BotName , "Stopped farming: " + appID ) ;
186
315
return success ;
187
316
}
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
+ }
188
350
}
189
351
}
0 commit comments