From ba07405d9aa76b39e1f7832cbfdb73cfb71970cf Mon Sep 17 00:00:00 2001
From: Archi <JustArchi@JustArchi.net>
Date: Tue, 23 Jan 2024 22:49:33 +0100
Subject: [PATCH] Refactor selected boolean bot config properties

All of them are common enough to be contained into a single flags property, this will vastly improve readability of the bot config, among being ready to add more properties in the future without polluting it.

Also hooray for 6 bytes less of memory usage of each bot, glorious.
---
 ArchiSteamFarm/Steam/Bot.cs                  |  10 +-
 ArchiSteamFarm/Steam/Cards/CardsFarmer.cs    |  12 +-
 ArchiSteamFarm/Steam/Exchange/Trading.cs     |   2 +-
 ArchiSteamFarm/Steam/Interaction/Commands.cs |   2 +-
 ArchiSteamFarm/Steam/Storage/BotConfig.cs    | 172 +++++++++++++------
 5 files changed, 128 insertions(+), 70 deletions(-)

diff --git a/ArchiSteamFarm/Steam/Bot.cs b/ArchiSteamFarm/Steam/Bot.cs
index e7f3aa9ce3a2f..2c34f9e23f2c7 100644
--- a/ArchiSteamFarm/Steam/Bot.cs
+++ b/ArchiSteamFarm/Steam/Bot.cs
@@ -1070,7 +1070,7 @@ internal static string FormatBotResponse(string response, string botName) {
 			return (0, DateTime.MaxValue, true);
 		}
 
-		if ((hoursPlayed < CardsFarmer.HoursForRefund) && BotConfig.SkipRefundableGames) {
+		if ((hoursPlayed < CardsFarmer.HoursForRefund) && BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.SkipRefundableGames)) {
 			DateTime mostRecent = DateTime.MinValue;
 
 			foreach (uint packageID in packageIDs) {
@@ -1531,13 +1531,13 @@ internal async Task OnConfigChanged(bool deleted) {
 	internal async Task OnFarmingFinished(bool farmedSomething) {
 		await OnFarmingStopped().ConfigureAwait(false);
 
-		if (BotConfig is { SendOnFarmingFinished: true, LootableTypes.Count: > 0 } && (farmedSomething || !FirstTradeSent)) {
+		if (BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.SendOnFarmingFinished) && (BotConfig.LootableTypes.Count > 0) && (farmedSomething || !FirstTradeSent)) {
 			FirstTradeSent = true;
 
 			await Actions.SendInventory(filterFunction: item => BotConfig.LootableTypes.Contains(item.Type)).ConfigureAwait(false);
 		}
 
-		if (BotConfig.ShutdownOnFarmingFinished) {
+		if (BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.ShutdownOnFarmingFinished)) {
 			Stop();
 		}
 
@@ -2378,7 +2378,7 @@ private async Task InitModules() {
 		AccessToken = accessToken;
 		RefreshToken = refreshToken;
 
-		CardsFarmer.SetInitialState(BotConfig.Paused);
+		CardsFarmer.SetInitialState(BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.FarmingPausedByDefault));
 
 		if (SendItemsTimer != null) {
 			await SendItemsTimer.DisposeAsync().ConfigureAwait(false);
@@ -2407,7 +2407,7 @@ private async Task InitModules() {
 			);
 		}
 
-		if (BotConfig.AutoSteamSaleEvent) {
+		if (BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.AutoSteamSaleEvent)) {
 			SteamSaleEvent = new SteamSaleEvent(this);
 		}
 
diff --git a/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs b/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs
index 29695474271c9..1393b13610ac3 100644
--- a/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs
+++ b/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs
@@ -258,7 +258,7 @@ internal async Task OnNewItemsNotification() {
 
 		// If we're not farming, and we got new items, it's likely to be a booster pack or likewise
 		// In this case, perform a loot if user wants to do so
-		if (Bot.BotConfig is { SendOnFarmingFinished: true, LootableTypes.Count: > 0 }) {
+		if (Bot.BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.SendOnFarmingFinished) && (Bot.BotConfig.LootableTypes.Count > 0)) {
 			await Bot.Actions.SendInventory(filterFunction: item => Bot.BotConfig.LootableTypes.Contains(item.Type)).ConfigureAwait(false);
 		}
 	}
@@ -313,7 +313,7 @@ internal async Task StartFarming() {
 			return;
 		}
 
-		if (!Bot.CanReceiveSteamCards || (Bot.BotConfig.FarmPriorityQueueOnly && (Bot.BotDatabase.FarmingPriorityQueueAppIDs.Count == 0))) {
+		if (!Bot.CanReceiveSteamCards || (Bot.BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.FarmPriorityQueueOnly) && (Bot.BotDatabase.FarmingPriorityQueueAppIDs.Count == 0))) {
 			Bot.ArchiLogger.LogGenericInfo(Strings.NothingToIdle);
 			await Bot.OnFarmingFinished(false).ConfigureAwait(false);
 
@@ -1106,12 +1106,12 @@ private async Task<bool> FarmSolo(Game game) {
 		// Find the number of badge pages
 		Bot.ArchiLogger.LogGenericInfo(Strings.CheckingFirstBadgePage);
 
-		using IDocument? htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(1, Bot.BotConfig.EnableRiskyCardsDiscovery ? (byte) 2 : WebBrowser.MaxTries).ConfigureAwait(false);
+		using IDocument? htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(1, Bot.BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.EnableRiskyCardsDiscovery) ? (byte) 2 : WebBrowser.MaxTries).ConfigureAwait(false);
 
 		if (htmlDocument == null) {
 			Bot.ArchiLogger.LogGenericWarning(Strings.WarningCouldNotCheckBadges);
 
-			if (!Bot.BotConfig.EnableRiskyCardsDiscovery) {
+			if (!Bot.BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.EnableRiskyCardsDiscovery)) {
 				return null;
 			}
 
@@ -1195,7 +1195,7 @@ private async Task<bool> FarmSolo(Game game) {
 			ShouldResumeFarming = false;
 
 			// Allow changing to risky algorithm only if we failed at least some badge pages and we have the prop enabled
-			if (allTasksSucceeded || !Bot.BotConfig.EnableRiskyCardsDiscovery) {
+			if (allTasksSucceeded || !Bot.BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.EnableRiskyCardsDiscovery)) {
 				return false;
 			}
 
@@ -1341,7 +1341,7 @@ private async Task<bool> IsPlayableGame(Game game) {
 	private bool ShouldIdle(uint appID) {
 		ArgumentOutOfRangeException.ThrowIfZero(appID);
 
-		if (SalesBlacklist.Contains(appID) || (ASF.GlobalConfig?.Blacklist.Contains(appID) == true) || Bot.IsBlacklistedFromIdling(appID) || (Bot.BotConfig.FarmPriorityQueueOnly && !Bot.IsPriorityIdling(appID))) {
+		if (SalesBlacklist.Contains(appID) || (ASF.GlobalConfig?.Blacklist.Contains(appID) == true) || Bot.IsBlacklistedFromIdling(appID) || (Bot.BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.FarmPriorityQueueOnly) && !Bot.IsPriorityIdling(appID))) {
 			// We're configured to ignore this appID, so skip it
 			return false;
 		}
diff --git a/ArchiSteamFarm/Steam/Exchange/Trading.cs b/ArchiSteamFarm/Steam/Exchange/Trading.cs
index 90aa72390ae1e..e5c133c1de276 100644
--- a/ArchiSteamFarm/Steam/Exchange/Trading.cs
+++ b/ArchiSteamFarm/Steam/Exchange/Trading.cs
@@ -372,7 +372,7 @@ internal async Task OnNewTrade() {
 				lootableTypesReceived = await ParseActiveTrades().ConfigureAwait(false);
 			}
 
-			if (lootableTypesReceived && Bot.BotConfig is { SendOnFarmingFinished: true, LootableTypes.Count: > 0 }) {
+			if (lootableTypesReceived && Bot.BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.SendOnFarmingFinished) && (Bot.BotConfig.LootableTypes.Count > 0)) {
 				await Bot.Actions.SendInventory(filterFunction: item => Bot.BotConfig.LootableTypes.Contains(item.Type)).ConfigureAwait(false);
 			}
 		} finally {
diff --git a/ArchiSteamFarm/Steam/Interaction/Commands.cs b/ArchiSteamFarm/Steam/Interaction/Commands.cs
index f379cfdb4f106..4cca8be2cadc0 100644
--- a/ArchiSteamFarm/Steam/Interaction/Commands.cs
+++ b/ArchiSteamFarm/Steam/Interaction/Commands.cs
@@ -1239,7 +1239,7 @@ internal void OnNewLicenseList() {
 		}
 
 		switch (Bot.CardsFarmer.NowFarming) {
-			case false when Bot.BotConfig.FarmPriorityQueueOnly:
+			case false when Bot.BotConfig.FarmingPreferences.HasFlag(BotConfig.EFarmingPreferences.FarmPriorityQueueOnly):
 				Utilities.InBackground(Bot.CardsFarmer.StartFarming);
 
 				break;
diff --git a/ArchiSteamFarm/Steam/Storage/BotConfig.cs b/ArchiSteamFarm/Steam/Storage/BotConfig.cs
index 8c721aa07cf49..59a89bca4a650 100644
--- a/ArchiSteamFarm/Steam/Storage/BotConfig.cs
+++ b/ArchiSteamFarm/Steam/Storage/BotConfig.cs
@@ -47,9 +47,6 @@ public sealed class BotConfig {
 	[PublicAPI]
 	public const bool DefaultAcceptGifts = false;
 
-	[PublicAPI]
-	public const bool DefaultAutoSteamSaleEvent = false;
-
 	[PublicAPI]
 	public const EBotBehaviour DefaultBotBehaviour = EBotBehaviour.None;
 
@@ -63,10 +60,7 @@ public sealed class BotConfig {
 	public const bool DefaultEnabled = false;
 
 	[PublicAPI]
-	public const bool DefaultEnableRiskyCardsDiscovery = false;
-
-	[PublicAPI]
-	public const bool DefaultFarmPriorityQueueOnly = false;
+	public const EFarmingPreferences DefaultFarmingPreferences = EFarmingPreferences.None;
 
 	[PublicAPI]
 	public const byte DefaultHoursUntilCardDrops = 3;
@@ -80,27 +74,15 @@ public sealed class BotConfig {
 	[PublicAPI]
 	public const ArchiCryptoHelper.ECryptoMethod DefaultPasswordFormat = ArchiCryptoHelper.ECryptoMethod.PlainText;
 
-	[PublicAPI]
-	public const bool DefaultPaused = false;
-
 	[PublicAPI]
 	public const ERedeemingPreferences DefaultRedeemingPreferences = ERedeemingPreferences.None;
 
 	[PublicAPI]
 	public const ERemoteCommunication DefaultRemoteCommunication = ERemoteCommunication.All;
 
-	[PublicAPI]
-	public const bool DefaultSendOnFarmingFinished = false;
-
 	[PublicAPI]
 	public const byte DefaultSendTradePeriod = 0;
 
-	[PublicAPI]
-	public const bool DefaultShutdownOnFarmingFinished = false;
-
-	[PublicAPI]
-	public const bool DefaultSkipRefundableGames = false;
-
 	[PublicAPI]
 	public const string? DefaultSteamLogin = null;
 
@@ -155,9 +137,6 @@ public sealed class BotConfig {
 	[JsonProperty(Required = Required.DisallowNull)]
 	public bool AcceptGifts { get; private set; } = DefaultAcceptGifts;
 
-	[JsonProperty(Required = Required.DisallowNull)]
-	public bool AutoSteamSaleEvent { get; private set; } = DefaultAutoSteamSaleEvent;
-
 	[JsonProperty(Required = Required.DisallowNull)]
 	public EBotBehaviour BotBehaviour { get; private set; } = DefaultBotBehaviour;
 
@@ -174,14 +153,11 @@ public sealed class BotConfig {
 	[JsonProperty(Required = Required.DisallowNull)]
 	public bool Enabled { get; private set; } = DefaultEnabled;
 
-	[JsonProperty]
-	public bool EnableRiskyCardsDiscovery { get; private set; } = DefaultEnableRiskyCardsDiscovery;
-
 	[JsonProperty(Required = Required.DisallowNull)]
 	public ImmutableList<EFarmingOrder> FarmingOrders { get; private set; } = DefaultFarmingOrders;
 
 	[JsonProperty(Required = Required.DisallowNull)]
-	public bool FarmPriorityQueueOnly { get; private set; } = DefaultFarmPriorityQueueOnly;
+	public EFarmingPreferences FarmingPreferences { get; private set; } = DefaultFarmingPreferences;
 
 	[JsonProperty(Required = Required.DisallowNull)]
 	[MaxLength(ArchiHandler.MaxGamesPlayedConcurrently)]
@@ -208,28 +184,16 @@ public sealed class BotConfig {
 	[JsonProperty(Required = Required.DisallowNull)]
 	public ArchiCryptoHelper.ECryptoMethod PasswordFormat { get; internal set; } = DefaultPasswordFormat;
 
-	[JsonProperty(Required = Required.DisallowNull)]
-	public bool Paused { get; private set; } = DefaultPaused;
-
 	[JsonProperty(Required = Required.DisallowNull)]
 	public ERedeemingPreferences RedeemingPreferences { get; private set; } = DefaultRedeemingPreferences;
 
 	[JsonProperty(Required = Required.DisallowNull)]
 	public ERemoteCommunication RemoteCommunication { get; private set; } = DefaultRemoteCommunication;
 
-	[JsonProperty(Required = Required.DisallowNull)]
-	public bool SendOnFarmingFinished { get; private set; } = DefaultSendOnFarmingFinished;
-
 	[JsonProperty(Required = Required.DisallowNull)]
 	[Range(byte.MinValue, byte.MaxValue)]
 	public byte SendTradePeriod { get; private set; } = DefaultSendTradePeriod;
 
-	[JsonProperty(Required = Required.DisallowNull)]
-	public bool ShutdownOnFarmingFinished { get; private set; } = DefaultShutdownOnFarmingFinished;
-
-	[JsonProperty(Required = Required.DisallowNull)]
-	public bool SkipRefundableGames { get; private set; } = DefaultSkipRefundableGames;
-
 	[JsonProperty]
 	public string? SteamLogin {
 		get => BackingSteamLogin;
@@ -310,6 +274,104 @@ internal Dictionary<string, JToken>? AdditionalProperties {
 	private string? BackingSteamParentalCode = DefaultSteamParentalCode;
 	private string? BackingSteamPassword = DefaultSteamPassword;
 
+	[JsonProperty(Required = Required.DisallowNull)]
+	[Obsolete]
+	private bool AutoSteamSaleEvent {
+		set {
+			ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningDeprecated, nameof(AutoSteamSaleEvent), nameof(FarmingPreferences)));
+
+			if (value) {
+				FarmingPreferences |= EFarmingPreferences.AutoSteamSaleEvent;
+			} else {
+				FarmingPreferences &= ~EFarmingPreferences.AutoSteamSaleEvent;
+			}
+		}
+	}
+
+	[JsonProperty(Required = Required.DisallowNull)]
+	[Obsolete]
+	private bool EnableRiskyCardsDiscovery {
+		set {
+			ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningDeprecated, nameof(EnableRiskyCardsDiscovery), nameof(FarmingPreferences)));
+
+			if (value) {
+				FarmingPreferences |= EFarmingPreferences.EnableRiskyCardsDiscovery;
+			} else {
+				FarmingPreferences &= ~EFarmingPreferences.EnableRiskyCardsDiscovery;
+			}
+		}
+	}
+
+	[JsonProperty(Required = Required.DisallowNull)]
+	[Obsolete]
+	private bool FarmPriorityQueueOnly {
+		set {
+			ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningDeprecated, nameof(FarmPriorityQueueOnly), nameof(FarmingPreferences)));
+
+			if (value) {
+				FarmingPreferences |= EFarmingPreferences.FarmPriorityQueueOnly;
+			} else {
+				FarmingPreferences &= ~EFarmingPreferences.FarmPriorityQueueOnly;
+			}
+		}
+	}
+
+	[JsonProperty(Required = Required.DisallowNull)]
+	[Obsolete]
+	private bool Paused {
+		set {
+			ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningDeprecated, nameof(Paused), nameof(FarmingPreferences)));
+
+			if (value) {
+				FarmingPreferences |= EFarmingPreferences.FarmingPausedByDefault;
+			} else {
+				FarmingPreferences &= ~EFarmingPreferences.FarmingPausedByDefault;
+			}
+		}
+	}
+
+	[JsonProperty(Required = Required.DisallowNull)]
+	[Obsolete]
+	private bool SendOnFarmingFinished {
+		set {
+			ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningDeprecated, nameof(SendOnFarmingFinished), nameof(FarmingPreferences)));
+
+			if (value) {
+				FarmingPreferences |= EFarmingPreferences.SendOnFarmingFinished;
+			} else {
+				FarmingPreferences &= ~EFarmingPreferences.SendOnFarmingFinished;
+			}
+		}
+	}
+
+	[JsonProperty(Required = Required.DisallowNull)]
+	[Obsolete]
+	private bool ShutdownOnFarmingFinished {
+		set {
+			ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningDeprecated, nameof(ShutdownOnFarmingFinished), nameof(FarmingPreferences)));
+
+			if (value) {
+				FarmingPreferences |= EFarmingPreferences.ShutdownOnFarmingFinished;
+			} else {
+				FarmingPreferences &= ~EFarmingPreferences.ShutdownOnFarmingFinished;
+			}
+		}
+	}
+
+	[JsonProperty(Required = Required.DisallowNull)]
+	[Obsolete]
+	private bool SkipRefundableGames {
+		set {
+			ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningDeprecated, nameof(SkipRefundableGames), nameof(FarmingPreferences)));
+
+			if (value) {
+				FarmingPreferences |= EFarmingPreferences.SkipRefundableGames;
+			} else {
+				FarmingPreferences &= ~EFarmingPreferences.SkipRefundableGames;
+			}
+		}
+	}
+
 	[JsonProperty($"{SharedInfo.UlongCompatibilityStringPrefix}{nameof(SteamMasterClanID)}", Required = Required.DisallowNull)]
 	private string SSteamMasterClanID {
 		get => SteamMasterClanID.ToString(CultureInfo.InvariantCulture);
@@ -331,9 +393,6 @@ internal BotConfig() { }
 	[UsedImplicitly]
 	public bool ShouldSerializeAcceptGifts() => !Saving || (AcceptGifts != DefaultAcceptGifts);
 
-	[UsedImplicitly]
-	public bool ShouldSerializeAutoSteamSaleEvent() => !Saving || (AutoSteamSaleEvent != DefaultAutoSteamSaleEvent);
-
 	[UsedImplicitly]
 	public bool ShouldSerializeBotBehaviour() => !Saving || (BotBehaviour != DefaultBotBehaviour);
 
@@ -349,14 +408,11 @@ internal BotConfig() { }
 	[UsedImplicitly]
 	public bool ShouldSerializeEnabled() => !Saving || (Enabled != DefaultEnabled);
 
-	[UsedImplicitly]
-	public bool ShouldSerializeEnableRiskyCardsDiscovery() => !Saving || (EnableRiskyCardsDiscovery != DefaultEnableRiskyCardsDiscovery);
-
 	[UsedImplicitly]
 	public bool ShouldSerializeFarmingOrders() => !Saving || ((FarmingOrders != DefaultFarmingOrders) && !FarmingOrders.SequenceEqual(DefaultFarmingOrders));
 
 	[UsedImplicitly]
-	public bool ShouldSerializeFarmPriorityQueueOnly() => !Saving || (FarmPriorityQueueOnly != DefaultFarmPriorityQueueOnly);
+	public bool ShouldSerializeFarmingPreferences() => !Saving || (FarmingPreferences != DefaultFarmingPreferences);
 
 	[UsedImplicitly]
 	public bool ShouldSerializeGamesPlayedWhileIdle() => !Saving || ((GamesPlayedWhileIdle != DefaultGamesPlayedWhileIdle) && !GamesPlayedWhileIdle.SequenceEqual(DefaultGamesPlayedWhileIdle));
@@ -379,27 +435,15 @@ internal BotConfig() { }
 	[UsedImplicitly]
 	public bool ShouldSerializePasswordFormat() => !Saving || (PasswordFormat != DefaultPasswordFormat);
 
-	[UsedImplicitly]
-	public bool ShouldSerializePaused() => !Saving || (Paused != DefaultPaused);
-
 	[UsedImplicitly]
 	public bool ShouldSerializeRedeemingPreferences() => !Saving || (RedeemingPreferences != DefaultRedeemingPreferences);
 
 	[UsedImplicitly]
 	public bool ShouldSerializeRemoteCommunication() => !Saving || (RemoteCommunication != DefaultRemoteCommunication);
 
-	[UsedImplicitly]
-	public bool ShouldSerializeSendOnFarmingFinished() => !Saving || (SendOnFarmingFinished != DefaultSendOnFarmingFinished);
-
 	[UsedImplicitly]
 	public bool ShouldSerializeSendTradePeriod() => !Saving || (SendTradePeriod != DefaultSendTradePeriod);
 
-	[UsedImplicitly]
-	public bool ShouldSerializeShutdownOnFarmingFinished() => !Saving || (ShutdownOnFarmingFinished != DefaultShutdownOnFarmingFinished);
-
-	[UsedImplicitly]
-	public bool ShouldSerializeSkipRefundableGames() => !Saving || (SkipRefundableGames != DefaultSkipRefundableGames);
-
 	[UsedImplicitly]
 	public bool ShouldSerializeSSteamMasterClanID() => !Saving;
 
@@ -697,6 +741,20 @@ public enum EFarmingOrder : byte {
 		MarketableDescending
 	}
 
+	[Flags]
+	[PublicAPI]
+	public enum EFarmingPreferences : byte {
+		None = 0,
+		FarmingPausedByDefault = 1,
+		ShutdownOnFarmingFinished = 2,
+		SendOnFarmingFinished = 4,
+		FarmPriorityQueueOnly = 8,
+		SkipRefundableGames = 16,
+		EnableRiskyCardsDiscovery = 32,
+		AutoSteamSaleEvent = 64,
+		All = FarmingPausedByDefault | ShutdownOnFarmingFinished | SendOnFarmingFinished | FarmPriorityQueueOnly | SkipRefundableGames | EnableRiskyCardsDiscovery | AutoSteamSaleEvent
+	}
+
 	[Flags]
 	[PublicAPI]
 	public enum ERedeemingPreferences : byte {