diff --git a/ArchiSteamFarm/Steam/Data/EAssetRarity.cs b/ArchiSteamFarm/Steam/Data/EAssetRarity.cs index 3b97ebf43d1e1..aae03dcfe1b97 100644 --- a/ArchiSteamFarm/Steam/Data/EAssetRarity.cs +++ b/ArchiSteamFarm/Steam/Data/EAssetRarity.cs @@ -23,9 +23,21 @@ namespace ArchiSteamFarm.Steam.Data; +#pragma warning disable CA1027 // Aliases are intentional, we don't plan to combine fields public enum EAssetRarity : byte { Unknown, Common, Uncommon, - Rare + Rare, + Mythical, + Epic = Mythical, + Legendary, + Exotic = Legendary, + Ancient, + Extraordinary = Ancient, + Immortal, + Contraband = Immortal, + Arcana, + Unusual } +#pragma warning restore CA1027 // Aliases are intentional, we don't plan to combine fields diff --git a/ArchiSteamFarm/Steam/Data/InventoryDescription.cs b/ArchiSteamFarm/Steam/Data/InventoryDescription.cs index 243f75220dad2..61661dbd9b40b 100644 --- a/ArchiSteamFarm/Steam/Data/InventoryDescription.cs +++ b/ArchiSteamFarm/Steam/Data/InventoryDescription.cs @@ -191,8 +191,8 @@ public EAssetRarity Rarity { } foreach (CEconItem_Tag? tag in Body.tags) { - switch (tag.category) { - case "droprate": + switch (tag.category.ToUpperInvariant()) { + case "DROPRATE": switch (tag.internal_name) { case "droprate_0": CachedRarity = EAssetRarity.Common; @@ -213,6 +213,20 @@ public EAssetRarity Rarity { return CachedRarity.Value; } + case "RARITY": + string internalName = tag.internal_name.Split('_', StringSplitOptions.RemoveEmptyEntries).Skip(1).FirstOrDefault() ?? tag.internal_name; + + if (Enum.TryParse(internalName, true, out EAssetRarity assetRarity)) { + CachedRarity = assetRarity; + + return CachedRarity.Value; + } + + ASF.ArchiLogger.LogGenericError(Strings.FormatWarningUnknownValuePleaseReport(nameof(tag.internal_name), tag.internal_name)); + + CachedRarity = EAssetRarity.Unknown; + + return CachedRarity.Value; } } diff --git a/ArchiSteamFarm/Steam/Interaction/Commands.cs b/ArchiSteamFarm/Steam/Interaction/Commands.cs index dca4418a36759..052e0038c0024 100644 --- a/ArchiSteamFarm/Steam/Interaction/Commands.cs +++ b/ArchiSteamFarm/Steam/Interaction/Commands.cs @@ -236,6 +236,10 @@ public static EAccess GetProxyAccess(Bot bot, EAccess access, ulong steamID = 0) return await ResponseAdvancedLoot(access, args[1], args[2], Utilities.GetArgsAsText(message, 3), steamID).ConfigureAwait(false); case "LOOT^" when args.Length > 2: return await ResponseAdvancedLoot(access, args[1], args[2]).ConfigureAwait(false); + case "LOOT&" when args.Length > 4: + return await ResponseAdvancedLootByAssetRarity(access, args[1], args[2], args[3], Utilities.GetArgsAsText(args, 4, ",")).ConfigureAwait(false); + case "LOOT&" when args.Length > 3: + return await ResponseAdvancedLootByAssetRarity(access, args[1], args[2], args[3]).ConfigureAwait(false); case "LOOT@" when args.Length > 2: return await ResponseLootByRealAppIDs(access, args[1], Utilities.GetArgsAsText(args, 2, ","), false, steamID).ConfigureAwait(false); case "LOOT@": @@ -318,6 +322,10 @@ public static EAccess GetProxyAccess(Bot bot, EAccess access, ulong steamID = 0) return await ResponseAdvancedTransfer(access, args[1], args[2], args[3], Utilities.GetArgsAsText(message, 4), steamID).ConfigureAwait(false); case "TRANSFER^" when args.Length > 3: return await ResponseAdvancedTransfer(access, args[1], args[2], args[3]).ConfigureAwait(false); + case "TRANSFER&" when args.Length > 5: + return await ResponseAdvancedTransferByAssetRarity(access, args[1], args[2], args[3], args[4], Utilities.GetArgsAsText(args, 5, ","), steamID).ConfigureAwait(false); + case "TRANSFER&" when args.Length > 4: + return await ResponseAdvancedTransferByAssetRarity(access, args[1], args[2], args[3], args[4]).ConfigureAwait(false); case "TRANSFER@" when args.Length > 3: return await ResponseTransferByRealAppIDs(access, args[1], args[2], Utilities.GetArgsAsText(message, 3), false, steamID).ConfigureAwait(false); case "TRANSFER@" when args.Length > 2: @@ -745,6 +753,65 @@ internal void OnNewLicenseList() { return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; } + private async Task ResponseAdvancedLootByAssetRarity(EAccess access, string targetAppID, string targetContextID, string assetRaritiesText) { + if (!Enum.IsDefined(access)) { + throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); + } + + ArgumentException.ThrowIfNullOrEmpty(targetAppID); + ArgumentException.ThrowIfNullOrEmpty(targetContextID); + ArgumentException.ThrowIfNullOrEmpty(assetRaritiesText); + + if (access < EAccess.Master) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (!uint.TryParse(targetAppID, out uint appID) || (appID == 0)) { + return FormatBotResponse(Strings.FormatErrorIsInvalid(nameof(appID))); + } + + if (!ulong.TryParse(targetContextID, out ulong contextID) || (contextID == 0)) { + return FormatBotResponse(Strings.FormatErrorIsInvalid(nameof(contextID))); + } + + HashSet? assetRarities = ParseAssetRarities(assetRaritiesText); + + if (assetRarities == null) { + return FormatBotResponse(Strings.FormatErrorIsInvalid(nameof(assetRarities))); + } + + (bool success, string message) = await Bot.Actions.SendInventory(appID, contextID, filterFunction: item => assetRarities.Contains(item.Rarity)).ConfigureAwait(false); + + return FormatBotResponse(success ? message : Strings.FormatWarningFailedWithError(message)); + } + + private static async Task ResponseAdvancedLootByAssetRarity(EAccess access, string botNames, string appID, string contextID, string assetRaritiesText, ulong steamID = 0) { + if (!Enum.IsDefined(access)) { + throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); + } + + ArgumentException.ThrowIfNullOrEmpty(botNames); + ArgumentException.ThrowIfNullOrEmpty(appID); + ArgumentException.ThrowIfNullOrEmpty(contextID); + ArgumentException.ThrowIfNullOrEmpty(assetRaritiesText); + + HashSet? bots = Bot.GetBots(botNames); + + if ((bots == null) || (bots.Count == 0)) { + return access >= EAccess.Owner ? FormatStaticResponse(Strings.FormatBotNotFound(botNames)) : null; + } + + IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAdvancedLootByAssetRarity(GetProxyAccess(bot, access, steamID), appID, contextID, assetRaritiesText))).ConfigureAwait(false); + + List responses = [..results.Where(static result => !string.IsNullOrEmpty(result))!]; + + return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; + } + private async Task ResponseAdvancedRedeem(EAccess access, string options, string keys, ulong steamID = 0) { if (!Enum.IsDefined(access)) { throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); @@ -926,6 +993,109 @@ internal void OnNewLicenseList() { return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; } + private async Task ResponseAdvancedTransferByAssetRarity(EAccess access, uint appID, ulong contextID, Bot targetBot, HashSet assetRarities) { + if (!Enum.IsDefined(access)) { + throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); + } + + ArgumentOutOfRangeException.ThrowIfZero(appID); + ArgumentOutOfRangeException.ThrowIfZero(contextID); + ArgumentNullException.ThrowIfNull(targetBot); + + if (access < EAccess.Master) { + return null; + } + + if (!Bot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.BotNotConnected); + } + + if (!targetBot.IsConnectedAndLoggedOn) { + return FormatBotResponse(Strings.TargetBotNotConnected); + } + + (bool success, string message) = await Bot.Actions.SendInventory(appID, contextID, targetBot.SteamID, filterFunction: item => assetRarities.Contains(item.Rarity)).ConfigureAwait(false); + + return FormatBotResponse(success ? message : Strings.FormatWarningFailedWithError(message)); + } + + private async Task ResponseAdvancedTransferByAssetRarity(EAccess access, string targetAppID, string targetContextID, string botNameTo, string assetRaritiesText) { + if (!Enum.IsDefined(access)) { + throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); + } + + ArgumentException.ThrowIfNullOrEmpty(targetAppID); + ArgumentException.ThrowIfNullOrEmpty(targetContextID); + ArgumentException.ThrowIfNullOrEmpty(botNameTo); + ArgumentException.ThrowIfNullOrEmpty(assetRaritiesText); + + Bot? targetBot = Bot.GetBot(botNameTo); + + if (targetBot == null) { + return access >= EAccess.Owner ? FormatBotResponse(Strings.FormatBotNotFound(botNameTo)) : null; + } + + if (!uint.TryParse(targetAppID, out uint appID) || (appID == 0)) { + return FormatBotResponse(Strings.FormatErrorIsInvalid(nameof(appID))); + } + + if (!ulong.TryParse(targetContextID, out ulong contextID) || (contextID == 0)) { + return FormatBotResponse(Strings.FormatErrorIsInvalid(nameof(contextID))); + } + + HashSet? assetRarities = ParseAssetRarities(assetRaritiesText); + + if (assetRarities == null) { + return FormatBotResponse(Strings.FormatErrorIsInvalid(nameof(assetRarities))); + } + + return await ResponseAdvancedTransferByAssetRarity(access, appID, contextID, targetBot, assetRarities).ConfigureAwait(false); + } + + private static async Task ResponseAdvancedTransferByAssetRarity(EAccess access, string botNames, string targetAppID, string targetContextID, string botNameTo, string assetRaritiesText, ulong steamID = 0) { + if (!Enum.IsDefined(access)) { + throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); + } + + ArgumentException.ThrowIfNullOrEmpty(botNames); + ArgumentException.ThrowIfNullOrEmpty(targetAppID); + ArgumentException.ThrowIfNullOrEmpty(targetContextID); + ArgumentException.ThrowIfNullOrEmpty(botNameTo); + ArgumentException.ThrowIfNullOrEmpty(assetRaritiesText); + + HashSet? bots = Bot.GetBots(botNames); + + if ((bots == null) || (bots.Count == 0)) { + return access >= EAccess.Owner ? FormatStaticResponse(Strings.FormatBotNotFound(botNames)) : null; + } + + if (!uint.TryParse(targetAppID, out uint appID) || (appID == 0)) { + return FormatStaticResponse(Strings.FormatErrorIsInvalid(nameof(appID))); + } + + if (!ulong.TryParse(targetContextID, out ulong contextID) || (contextID == 0)) { + return FormatStaticResponse(Strings.FormatErrorIsInvalid(nameof(contextID))); + } + + Bot? targetBot = Bot.GetBot(botNameTo); + + if (targetBot == null) { + return access >= EAccess.Owner ? FormatStaticResponse(Strings.FormatBotNotFound(botNameTo)) : null; + } + + HashSet? assetRarities = ParseAssetRarities(assetRaritiesText); + + if (assetRarities == null) { + return FormatStaticResponse(Strings.FormatErrorIsInvalid(nameof(assetRarities))); + } + + IList results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAdvancedTransferByAssetRarity(GetProxyAccess(bot, access, steamID), appID, contextID, targetBot, assetRarities))).ConfigureAwait(false); + + List responses = [..results.Where(static result => !string.IsNullOrEmpty(result))!]; + + return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; + } + private string? ResponseBackgroundGamesRedeemer(EAccess access) { if (!Enum.IsDefined(access)) { throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess)); @@ -3257,6 +3427,22 @@ internal void OnNewLicenseList() { return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; } + private static HashSet? ParseAssetRarities(string assetRaritiesText) { + string[] assetRaritiesArgs = assetRaritiesText.Split(SharedInfo.ListElementSeparators, StringSplitOptions.RemoveEmptyEntries); + + HashSet assetRarities = []; + + foreach (string assetRarityArg in assetRaritiesArgs) { + if (!Enum.TryParse(assetRarityArg, true, out EAssetRarity assetRarity) || !Enum.IsDefined(assetRarity)) { + return null; + } + + assetRarities.Add(assetRarity); + } + + return assetRarities; + } + [Flags] private enum ERedeemFlags : ushort { None = 0,