@@ -23,27 +23,102 @@ limitations under the License.
2323*/
2424
2525using HtmlAgilityPack ;
26+ using System . Collections . Concurrent ;
2627using System . Collections . Generic ;
28+ using System . Globalization ;
29+ using System . Text . RegularExpressions ;
2730using System . Threading ;
2831using System . Threading . Tasks ;
2932
3033namespace 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}
0 commit comments