diff --git a/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs b/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs index 1a69d149..59356af3 100644 --- a/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs +++ b/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs @@ -1,9 +1,10 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using BetterGenshinImpact.GameTask; +using BetterGenshinImpact.GameTask.AutoEat; +using BetterGenshinImpact.GameTask.AutoFight; +using CommunityToolkit.Mvvm.ComponentModel; using System; using System.Collections.Generic; using System.Text.Json.Serialization; -using BetterGenshinImpact.GameTask; -using BetterGenshinImpact.GameTask.AutoFight; namespace BetterGenshinImpact.Core.Config; @@ -96,6 +97,13 @@ public partial class PathingPartyConfig : ObservableObject [ObservableProperty] private bool _autoEatEnabled = false; + /// + /// 自动吃食物配置 + /// 供JS脚本使用 + /// + [ObservableProperty] + private AutoEatConfig _autoEatConfig = new(); + //在连续执行时是否隐藏 [ObservableProperty] private bool _hideOnRepeat = false; diff --git a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs index 9a281126..3e7b89d8 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs @@ -1,19 +1,26 @@ -using BetterGenshinImpact.Core.Script.Dependence.Model; +using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Script.Dependence.Model; using BetterGenshinImpact.GameTask; -using BetterGenshinImpact.ViewModel.Pages; -using System; -using System.Threading.Tasks; using BetterGenshinImpact.GameTask.AutoDomain; +using BetterGenshinImpact.GameTask.AutoEat; using BetterGenshinImpact.GameTask.AutoFishing; -using BetterGenshinImpact.GameTask.AutoWood; using BetterGenshinImpact.GameTask.AutoGeniusInvokation; using BetterGenshinImpact.GameTask.AutoPathing.Handler; +using BetterGenshinImpact.GameTask.AutoWood; +using BetterGenshinImpact.Helpers; +using BetterGenshinImpact.ViewModel.Pages; +using Microsoft.ClearScript; +using Microsoft.Extensions.Logging; +using System; using System.Threading; +using System.Threading.Tasks; namespace BetterGenshinImpact.Core.Script.Dependence; public class Dispatcher { + private readonly ILogger _logger = App.GetLogger(); + private object _config = null; public Dispatcher(object config) @@ -127,7 +134,7 @@ public class Dispatcher // 如果没有自定义令牌,就使用全局令牌 cancellationToken = CancellationContext.Instance.Cts.Token; } - + // 根据名称执行任务 switch (soloTask.Name) { @@ -162,7 +169,69 @@ public class Dispatcher await new AutoFishingTask(AutoFishingTaskParam.BuildFromSoloTaskConfig(soloTask.Config)).Start( cancellationToken); break; + case "AutoEat": + string? foodName = soloTask.Config == null ? null : ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "foodName", null); + FoodEffectType? foodEffectType = soloTask.Config == null ? null : (FoodEffectType?)ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "foodEffectType", null); + if (foodName != null && foodEffectType != null) + { + throw new NotSupportedException("不能同时指定foodName和foodEffectType"); + } + + if (foodName == null) + { + if (foodEffectType != null) + { + PathingPartyConfig? pathingPartyConfig = _config as PathingPartyConfig; + if (pathingPartyConfig == null) + { + throw new NotSupportedException("foodEffectType参数需要调度器配置,请在调度器下使用"); + } + else + { + switch (foodEffectType) + { + case FoodEffectType.ATKBoostingDish: + foodName = pathingPartyConfig.AutoEatConfig.DefaultAtkBoostingDishName; + if (foodName == null) + { + _logger.LogInformation("缺少{Text}配置,跳过吃Buff", "默认的攻击类料理"); + return; + } + break; + case FoodEffectType.AdventurersDish: + foodName = pathingPartyConfig.AutoEatConfig.DefaultAdventurersDishName; + if (foodName == null) + { + _logger.LogInformation("缺少{Text}配置,跳过吃Buff", "默认的冒险类料理"); + return; + } + break; + case FoodEffectType.DEFBoostingDish: + foodName = pathingPartyConfig.AutoEatConfig.DefaultDefBoostingDishName; + if (foodName == null) + { + _logger.LogInformation("缺少{Text}配置,跳过吃Buff", "默认的防御类料理"); + return; + } + break; + default: + throw new NotSupportedException("JS脚本入参错误:错误的foodEffectType"); + } + } + } + } + + var autoEatConfig = TaskContext.Instance().Config.AutoEatConfig; + await new AutoEatTask(new AutoEatParam() + { + CheckInterval = autoEatConfig.CheckInterval, + EatInterval = autoEatConfig.EatInterval, + ShowNotification = autoEatConfig.ShowNotification, + FoodName = foodName + }).Start(cancellationToken); + + break; default: throw new ArgumentException($"未知的任务名称: {soloTask.Name}", nameof(soloTask.Name)); } diff --git a/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs b/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs index 83728f3a..ca5f1737 100644 --- a/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs +++ b/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.GameTask.Common.BgiVision; +using BetterGenshinImpact.GameTask.Common.BgiVision; using System; using System.Diagnostics; using System.Threading; @@ -35,7 +35,6 @@ public class AutoArtifactSalvageTask : ISoloTask { private readonly ILogger logger = App.GetLogger(); private readonly InputSimulator input = Simulation.SendInput; - private readonly ReturnMainUiTask _returnMainUiTask = new(); private CancellationToken ct; @@ -51,7 +50,7 @@ public class AutoArtifactSalvageTask : ISoloTask private readonly int? maxNumToCheck; - private bool returnToMainUi = true; + private readonly bool returnToMainUi = true; public AutoArtifactSalvageTask(int star, string? regularExpression = null, int? maxNumToCheck = null) { @@ -75,12 +74,51 @@ public class AutoArtifactSalvageTask : ISoloTask this.returnToMainUi = returnToMainUi; } - public async Task Start(CancellationToken ct) + public static async Task OpenBag(GridScreenName gridScreenName, InputSimulator input, ILogger logger, CancellationToken ct) { - this.ct = ct; - if (returnToMainUi) + RecognitionObject? recognitionObjectChecked; + RecognitionObject? recognitionObjectUnchecked; + + switch (gridScreenName) { - await _returnMainUiTask.Start(ct); + case GridScreenName.Weapons: + recognitionObjectChecked = ElementAssets.Instance.BagWeaponChecked; + recognitionObjectUnchecked = ElementAssets.Instance.BagWeaponUnchecked; + break; + case GridScreenName.Artifacts: + recognitionObjectChecked = ElementAssets.Instance.BagArtifactChecked; + recognitionObjectUnchecked = ElementAssets.Instance.BagArtifactUnchecked; + break; + case GridScreenName.CharacterDevelopmentItems: + recognitionObjectChecked = ElementAssets.Instance.BagCharacterDevelopmentItemChecked; + recognitionObjectUnchecked = ElementAssets.Instance.BagCharacterDevelopmentItemUnchecked; + break; + case GridScreenName.Food: + recognitionObjectChecked = ElementAssets.Instance.BagFoodChecked; + recognitionObjectUnchecked = ElementAssets.Instance.BagFoodUnchecked; + break; + case GridScreenName.Materials: + recognitionObjectChecked = ElementAssets.Instance.BagMaterialChecked; + recognitionObjectUnchecked = ElementAssets.Instance.BagMaterialUnchecked; + break; + case GridScreenName.Gadget: + recognitionObjectChecked = ElementAssets.Instance.BagGadgetChecked; + recognitionObjectUnchecked = ElementAssets.Instance.BagGadgetUnchecked; + break; + case GridScreenName.Quest: + recognitionObjectChecked = ElementAssets.Instance.BagQuestChecked; + recognitionObjectUnchecked = ElementAssets.Instance.BagQuestUnchecked; + break; + case GridScreenName.PreciousItems: + recognitionObjectChecked = ElementAssets.Instance.BagPreciousItemChecked; + recognitionObjectUnchecked = ElementAssets.Instance.BagPreciousItemUnchecked; + break; + case GridScreenName.Furnishings: + recognitionObjectChecked = ElementAssets.Instance.BagFurnishingChecked; + recognitionObjectUnchecked = ElementAssets.Instance.BagFurnishingUnchecked; + break; + default: + throw new NotSupportedException($"背包不支持的界面:{gridScreenName.GetDescription()}"); } // B键打开背包 @@ -89,12 +127,11 @@ public class AutoArtifactSalvageTask : ISoloTask var openBagSuccess = await NewRetry.WaitForAction(() => { - // 选择圣遗物 using var ra = CaptureToRectArea(); - using var artifactBtn = ra.Find(ElementAssets.Instance.BagArtifactChecked); + using var artifactBtn = ra.Find(recognitionObjectChecked); if (artifactBtn.IsEmpty()) { - using var artifactBtn2 = ra.Find(ElementAssets.Instance.BagArtifactUnchecked); + using var artifactBtn2 = ra.Find(recognitionObjectUnchecked); if (artifactBtn2.IsExist()) { artifactBtn2.Click(); @@ -118,12 +155,23 @@ public class AutoArtifactSalvageTask : ISoloTask if (!openBagSuccess) { - logger.LogError("未找到背包中圣遗物菜单按钮,打开背包失败"); + logger.LogError("未找到背包中{name}菜单按钮,打开背包失败", gridScreenName.GetDescription()); return; } await Delay(800, ct); + } + + public async Task Start(CancellationToken ct) + { + this.ct = ct; + if (returnToMainUi) + { + await new ReturnMainUiTask().Start(ct); + } + + await OpenBag(GridScreenName.Artifacts, this.input, this.logger, this.ct); // 点击分解按钮打开分解界面 using var ra2 = CaptureToRectArea(); @@ -217,7 +265,7 @@ public class AutoArtifactSalvageTask : ISoloTask if (returnToMainUi) { - await _returnMainUiTask.Start(ct); + await new ReturnMainUiTask().Start(ct); } } } diff --git a/BetterGenshinImpact/GameTask/AutoEat/AutoEatConfig.cs b/BetterGenshinImpact/GameTask/AutoEat/AutoEatConfig.cs index 1e0c1292..5fab8ad9 100644 --- a/BetterGenshinImpact/GameTask/AutoEat/AutoEatConfig.cs +++ b/BetterGenshinImpact/GameTask/AutoEat/AutoEatConfig.cs @@ -33,4 +33,28 @@ public partial class AutoEatConfig : ObservableObject /// [ObservableProperty] private int _eatInterval = 2000; + + /// + /// 测试食物名称 + /// + [ObservableProperty] + private string? _testFoodName; + + /// + /// 默认的攻击类料理名称 + /// + [ObservableProperty] + private string? _defaultAtkBoostingDishName; + + /// + /// 默认的冒险类料理名称 + /// + [ObservableProperty] + private string? _defaultAdventurersDishName; + + /// + /// 默认的防御类料理名称 + /// + [ObservableProperty] + private string? _defaultDefBoostingDishName; } \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoEat/AutoEatParam.cs b/BetterGenshinImpact/GameTask/AutoEat/AutoEatParam.cs index 1777886d..4a0ec76d 100644 --- a/BetterGenshinImpact/GameTask/AutoEat/AutoEatParam.cs +++ b/BetterGenshinImpact/GameTask/AutoEat/AutoEatParam.cs @@ -22,16 +22,9 @@ public class AutoEatParam : BaseTaskParam /// public int EatInterval { get; set; } - public AutoEatParam() - { - SetDefault(); - } - - public void SetDefault() - { - var config = TaskContext.Instance().Config.AutoEatConfig; - ShowNotification = config.ShowNotification; - CheckInterval = config.CheckInterval; - EatInterval = config.EatInterval; - } + /// + /// 食物名称 + /// 如果传空就使用便携营养袋,否则进入背包查找对应食物并使用 + /// + public string? FoodName { get; set; } } \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoEat/AutoEatTask.cs b/BetterGenshinImpact/GameTask/AutoEat/AutoEatTask.cs index 90b151ba..b78b9036 100644 --- a/BetterGenshinImpact/GameTask/AutoEat/AutoEatTask.cs +++ b/BetterGenshinImpact/GameTask/AutoEat/AutoEatTask.cs @@ -1,13 +1,19 @@ using BetterGenshinImpact.Core.Recognition.OCR; using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.Core.Simulator.Extensions; +using BetterGenshinImpact.GameTask.AutoArtifactSalvage; using BetterGenshinImpact.GameTask.Common.BgiVision; using BetterGenshinImpact.GameTask.Common.Element.Assets; +using BetterGenshinImpact.GameTask.Common.Job; +using BetterGenshinImpact.GameTask.GetGridIcons; using BetterGenshinImpact.GameTask.Model; -using BetterGenshinImpact.Service.Notification; -using BetterGenshinImpact.Service.Notification.Model.Enum; +using BetterGenshinImpact.GameTask.Model.Area; +using BetterGenshinImpact.GameTask.Model.GameUI; +using Fischless.WindowsInput; using Microsoft.Extensions.Logging; +using Microsoft.ML.OnnxRuntime; using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using static BetterGenshinImpact.GameTask.Common.TaskControl; @@ -24,6 +30,8 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask private readonly AutoEatParam _taskParam; private readonly AutoEatConfig _config; + private readonly ILogger _logger = App.GetLogger(); + private readonly InputSimulator _input = Simulation.SendInput; private CancellationToken _ct; public AutoEatTask(AutoEatParam taskParam) @@ -37,33 +45,75 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask _ct = ct; Init(); - Logger.LogInformation("自动吃药任务启动"); + _logger.LogInformation("自动吃药任务启动"); - if (!IsTakeFood()) + if (String.IsNullOrWhiteSpace(_taskParam.FoodName)) { - Logger.LogWarning("未装备 \"{Tool}\",无法启用自动吃药功能", "便携营养袋"); - return; - } + if (!IsTakeFood()) + { + _logger.LogWarning("未装备 \"{Tool}\",无法启用自动吃药功能", "便携营养袋"); + return; + } - try - { - await AutoEatLoop(); + try + { + await AutoEatLoop(); + } + catch (Exception e) + { + _logger.LogError(e, "自动吃药任务发生异常"); + throw; + } + finally + { + _logger.LogInformation("自动吃药任务结束"); + } } - catch (Exception e) + else { - Logger.LogError(e, "自动吃药任务发生异常"); - throw; - } - finally - { - Logger.LogInformation("自动吃药任务结束"); + _logger.LogInformation("打开背包寻找{name}……", _taskParam.FoodName); + await new ReturnMainUiTask().Start(ct); + await AutoArtifactSalvageTask.OpenBag(GridScreenName.Food, _input, _logger, _ct); + + using InferenceSession session = GridIconsAccuracyTestTask.LoadModel(out Dictionary prototypes); + + using var ra0 = CaptureToRectArea(); + GridScreenParams gridParams = GridScreenParams.Templates[GridScreenName.Food]; + var gridRoi = gridParams.GetRect(ra0); + GridScreen gridScreen = new GridScreen(gridRoi, gridParams, _logger, _ct); + bool isAte = false; + await foreach (ImageRegion itemRegion in gridScreen) + { + var result = GridIconsAccuracyTestTask.Infer(itemRegion.SrcMat, session, prototypes); + string predName = result.Item1; + if (predName == _taskParam.FoodName) + { + // 点击item + itemRegion.Click(); + await Delay(300, ct); + // 点击确定 + using var ra = ra0.Find(ElementAssets.Instance.BtnWhiteConfirm); + if (ra.IsExist()) + { + ra.Click(); + } + _logger.LogInformation("吃了一份{name},真香!", predName); + isAte = true; + break; + } + } + if (!isAte) + { + _logger.LogInformation("没有找到{name}", _taskParam.FoodName); + } + await new ReturnMainUiTask().Start(ct); } } private void Init() { - Logger.LogInformation("→ {Text} 检测间隔: {Interval}ms", "自动吃药,", _config.CheckInterval); - Logger.LogInformation("→ {Text} 吃药间隔: {Interval}ms", "自动吃药,", _config.EatInterval); + _logger.LogInformation("→ {Text} 检测间隔: {Interval}ms", "自动吃药,", _config.CheckInterval); + _logger.LogInformation("→ {Text} 吃药间隔: {Interval}ms", "自动吃药,", _config.EatInterval); } /// @@ -87,8 +137,8 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask // 模拟按键 "Z" 使用便携营养袋 Simulation.SendInput.SimulateAction(GIActions.QuickUseGadget); lastEatTime = now; - - Logger.LogInformation("检测到红血,自动吃药"); + + _logger.LogInformation("检测到红血,自动吃药"); } } @@ -101,7 +151,7 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask } catch (Exception e) { - Logger.LogDebug(e, "自动吃药检测时发生异常"); + _logger.LogDebug(e, "自动吃药检测时发生异常"); await Delay(1000, _ct); // 异常时稍作等待 } } @@ -124,7 +174,7 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask } catch (Exception e) { - Logger.LogDebug(e, "检测便携营养袋时发生异常"); + _logger.LogDebug(e, "检测便携营养袋时发生异常"); return false; } } diff --git a/BetterGenshinImpact/GameTask/AutoEat/FoodEffectType.cs b/BetterGenshinImpact/GameTask/AutoEat/FoodEffectType.cs new file mode 100644 index 00000000..8b774ffe --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoEat/FoodEffectType.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; + +namespace BetterGenshinImpact.GameTask.AutoEat +{ + public enum FoodEffectType + { + [Description("恢复类料理")] + RecoveryDish, + [Description("攻击类料理")] + ATKBoostingDish, + [Description("冒险类料理")] + AdventurersDish, + [Description("防御类料理")] + DEFBoostingDish, + [Description("药剂")] + Potion, + [Description("其他")] + Other + } +} diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_artifact_unchecked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_artifact_unchecked.png index 7d15f536..9f5f2d20 100644 Binary files a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_artifact_unchecked.png and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_artifact_unchecked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_characterdevelopmentitem_checked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_characterdevelopmentitem_checked.png new file mode 100644 index 00000000..a5a8ab48 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_characterdevelopmentitem_checked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_characterdevelopmentitem_unchecked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_characterdevelopmentitem_unchecked.png new file mode 100644 index 00000000..f8158cae Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_characterdevelopmentitem_unchecked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_food_checked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_food_checked.png new file mode 100644 index 00000000..06a298c9 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_food_checked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_food_unchecked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_food_unchecked.png new file mode 100644 index 00000000..a26aa3e8 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_food_unchecked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_furnishing_checked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_furnishing_checked.png new file mode 100644 index 00000000..7452c1d6 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_furnishing_checked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_furnishing_unchecked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_furnishing_unchecked.png new file mode 100644 index 00000000..3c32b492 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_furnishing_unchecked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_gadget_checked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_gadget_checked.png new file mode 100644 index 00000000..84445a35 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_gadget_checked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_gadget_unchecked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_gadget_unchecked.png new file mode 100644 index 00000000..96de9da3 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_gadget_unchecked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_material_checked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_material_checked.png new file mode 100644 index 00000000..c0cdf23d Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_material_checked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_material_unchecked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_material_unchecked.png new file mode 100644 index 00000000..67424257 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_material_unchecked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_preciousitem_checked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_preciousitem_checked.png new file mode 100644 index 00000000..275f4bb3 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_preciousitem_checked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_preciousitem_unchecked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_preciousitem_unchecked.png new file mode 100644 index 00000000..503e0e6b Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_preciousitem_unchecked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_quest_checked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_quest_checked.png new file mode 100644 index 00000000..3c85a867 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_quest_checked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_quest_unchecked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_quest_unchecked.png new file mode 100644 index 00000000..ba2138e5 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_quest_unchecked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_weapon_checked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_weapon_checked.png new file mode 100644 index 00000000..c92fecc7 Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_weapon_checked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_weapon_unchecked.png b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_weapon_unchecked.png new file mode 100644 index 00000000..2189cd3b Binary files /dev/null and b/BetterGenshinImpact/GameTask/Common/Element/Assets/1920x1080/bag_weapon_unchecked.png differ diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/ElementAssets.cs b/BetterGenshinImpact/GameTask/Common/Element/Assets/ElementAssets.cs index 25eac7f9..52f9218b 100644 --- a/BetterGenshinImpact/GameTask/Common/Element/Assets/ElementAssets.cs +++ b/BetterGenshinImpact/GameTask/Common/Element/Assets/ElementAssets.cs @@ -1,4 +1,4 @@ -using System; +using System; using BetterGenshinImpact.Core.Recognition; using BetterGenshinImpact.GameTask.Model; using BetterGenshinImpact.Helpers.Extensions; @@ -35,8 +35,24 @@ public class ElementAssets : BaseAssets public RecognitionObject Keyreduce; public RecognitionObject Keyincrease; + public RecognitionObject BagWeaponUnchecked; + public RecognitionObject BagWeaponChecked; public RecognitionObject BagArtifactUnchecked; public RecognitionObject BagArtifactChecked; + public RecognitionObject BagCharacterDevelopmentItemUnchecked; + public RecognitionObject BagCharacterDevelopmentItemChecked; + public RecognitionObject BagFoodUnchecked; + public RecognitionObject BagFoodChecked; + public RecognitionObject BagMaterialUnchecked; + public RecognitionObject BagMaterialChecked; + public RecognitionObject BagGadgetUnchecked; + public RecognitionObject BagGadgetChecked; + public RecognitionObject BagQuestUnchecked; + public RecognitionObject BagQuestChecked; + public RecognitionObject BagPreciousItemUnchecked; + public RecognitionObject BagPreciousItemChecked; + public RecognitionObject BagFurnishingUnchecked; + public RecognitionObject BagFurnishingChecked; public RecognitionObject BtnArtifactSalvage; public RecognitionObject BtnArtifactSalvageConfirm; @@ -245,7 +261,26 @@ public class ElementAssets : BaseAssets DrawOnWindow = false }.InitTemplate(); - // 分解圣遗物 + // 背包武器 + BagWeaponUnchecked = new RecognitionObject + { + Name = "BagWeaponUnchecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_weapon_unchecked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.87, + DrawOnWindow = false + }.InitTemplate(); + BagWeaponChecked = new RecognitionObject + { + Name = "BagWeaponChecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_weapon_checked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.8, + DrawOnWindow = false + }.InitTemplate(); + // 背包圣遗物 BagArtifactUnchecked = new RecognitionObject { Name = "BagArtifactUnchecked", @@ -264,6 +299,141 @@ public class ElementAssets : BaseAssets Threshold = 0.8, DrawOnWindow = false }.InitTemplate(); + // 背包养成道具 + BagCharacterDevelopmentItemUnchecked = new RecognitionObject + { + Name = "BagCharacterDevelopmentItemUnchecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_characterdevelopmentitem_unchecked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.87, + DrawOnWindow = false + }.InitTemplate(); + BagCharacterDevelopmentItemChecked = new RecognitionObject + { + Name = "BagCharacterDevelopmentItemChecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_characterdevelopmentitem_checked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.8, + DrawOnWindow = false + }.InitTemplate(); + // 背包食物 + BagFoodUnchecked = new RecognitionObject + { + Name = "BagFoodUnchecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_food_unchecked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.87, + DrawOnWindow = false + }.InitTemplate(); + BagFoodChecked = new RecognitionObject + { + Name = "BagFoodChecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_food_checked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.8, + DrawOnWindow = false + }.InitTemplate(); + // 背包材料 + BagMaterialUnchecked = new RecognitionObject + { + Name = "BagMaterialUnchecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_material_unchecked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.87, + DrawOnWindow = false + }.InitTemplate(); + BagMaterialChecked = new RecognitionObject + { + Name = "BagMaterialChecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_material_checked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.8, + DrawOnWindow = false + }.InitTemplate(); + // 背包小道具 + BagGadgetUnchecked = new RecognitionObject + { + Name = "BagGadgetUnchecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_gadget_unchecked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.87, + DrawOnWindow = false + }.InitTemplate(); + BagGadgetChecked = new RecognitionObject + { + Name = "BagGadgetChecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_gadget_checked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.8, + DrawOnWindow = false + }.InitTemplate(); + // 背包任务 + BagQuestUnchecked = new RecognitionObject + { + Name = "BagQuestUnchecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_quest_unchecked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.87, + DrawOnWindow = false + }.InitTemplate(); + BagQuestChecked = new RecognitionObject + { + Name = "BagQuestChecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_quest_checked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.8, + DrawOnWindow = false + }.InitTemplate(); + // 背包贵重道具 + BagPreciousItemUnchecked = new RecognitionObject + { + Name = "BagPreciousItemUnchecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_preciousitem_unchecked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.87, + DrawOnWindow = false + }.InitTemplate(); + BagPreciousItemChecked = new RecognitionObject + { + Name = "BagPreciousItemChecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_preciousitem_checked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.8, + DrawOnWindow = false + }.InitTemplate(); + // 背包摆设 + BagFurnishingUnchecked = new RecognitionObject + { + Name = "BagFurnishingUnchecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_furnishing_unchecked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.87, + DrawOnWindow = false + }.InitTemplate(); + BagFurnishingChecked = new RecognitionObject + { + Name = "BagFurnishingChecked", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "bag_furnishing_checked.png"), + RegionOfInterest = CaptureRect.CutTop(0.1), + Threshold = 0.8, + DrawOnWindow = false + }.InitTemplate(); + + // 分解圣遗物 BtnArtifactSalvage = new RecognitionObject { Name = "BtnArtifactSalvage", diff --git a/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsConfig.cs b/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsConfig.cs index feadb5a2..5a5fa1d4 100644 --- a/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsConfig.cs +++ b/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsConfig.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.GameTask.Model.GameUI; +using BetterGenshinImpact.GameTask.Model.GameUI; using CommunityToolkit.Mvvm.ComponentModel; using System; @@ -8,8 +8,7 @@ namespace BetterGenshinImpact.GameTask.GetGridIcons; public partial class GetGridIconsConfig : ObservableObject { /// - /// 昼夜策略 - /// 钓全天的鱼、还是只钓白天或夜晚的鱼 + /// Grid界面名称 /// [ObservableProperty] private GridScreenName _gridName = GridScreenName.Weapons; diff --git a/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsTask.cs b/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsTask.cs index 1028e9de..f1c9cbf5 100644 --- a/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsTask.cs +++ b/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsTask.cs @@ -1,17 +1,21 @@ +using BetterGenshinImpact.Core.Recognition.OCR; +using BetterGenshinImpact.Core.Simulator; +using BetterGenshinImpact.GameTask.AutoArtifactSalvage; +using BetterGenshinImpact.GameTask.Common.Job; +using BetterGenshinImpact.GameTask.Model.Area; +using BetterGenshinImpact.GameTask.Model.GameUI; +using BetterGenshinImpact.Helpers.Extensions; +using Fischless.WindowsInput; +using Microsoft.Extensions.Logging; +using OpenCvSharp; +using OpenCvSharp.Extensions; using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; using static BetterGenshinImpact.GameTask.Common.TaskControl; -using Microsoft.Extensions.Localization; -using BetterGenshinImpact.GameTask.Model.Area; -using System.Collections.Generic; -using OpenCvSharp; -using System.Linq; -using BetterGenshinImpact.Core.Recognition.OCR; -using System.IO; -using OpenCvSharp.Extensions; -using BetterGenshinImpact.GameTask.Model.GameUI; namespace BetterGenshinImpact.GameTask.GetGridIcons; @@ -21,6 +25,7 @@ namespace BetterGenshinImpact.GameTask.GetGridIcons; public class GetGridIconsTask : ISoloTask { private readonly ILogger logger = App.GetLogger(); + private readonly InputSimulator input = Simulation.SendInput; private CancellationToken ct; @@ -37,13 +42,31 @@ public class GetGridIconsTask : ISoloTask this.gridScreenName = gridScreenName; this.starAsSuffix = starAsSuffix; this.maxNumToGet = maxNumToGet; - IStringLocalizer stringLocalizer = App.GetService>() ?? throw new NullReferenceException(); } public async Task Start(CancellationToken ct) { this.ct = ct; + switch (this.gridScreenName) + { + case GridScreenName.Weapons: + case GridScreenName.Artifacts: + case GridScreenName.CharacterDevelopmentItems: + case GridScreenName.Food: + case GridScreenName.Materials: + case GridScreenName.Gadget: + case GridScreenName.Quest: + case GridScreenName.PreciousItems: + case GridScreenName.Furnishings: + await new ReturnMainUiTask().Start(ct); + await AutoArtifactSalvageTask.OpenBag(this.gridScreenName, this.input, this.logger, this.ct); + break; + default: + logger.LogInformation("{name}暂不支持自动打开,请提前手动打开界面", gridScreenName.GetDescription()); + break; + } + using var ra0 = CaptureToRectArea(); GridScreenParams gridParams = GridScreenParams.Templates[this.gridScreenName]; Rect gridRoi = gridParams.GetRect(ra0); diff --git a/BetterGenshinImpact/GameTask/GetGridIcons/GridIconsAccuracyTestTask.cs b/BetterGenshinImpact/GameTask/GetGridIcons/GridIconsAccuracyTestTask.cs index aa43c96f..689c45a3 100644 --- a/BetterGenshinImpact/GameTask/GetGridIcons/GridIconsAccuracyTestTask.cs +++ b/BetterGenshinImpact/GameTask/GetGridIcons/GridIconsAccuracyTestTask.cs @@ -1,6 +1,11 @@ using BetterGenshinImpact.Core.Recognition.OCR; +using BetterGenshinImpact.Core.Simulator; +using BetterGenshinImpact.GameTask.AutoArtifactSalvage; +using BetterGenshinImpact.GameTask.Common.Job; using BetterGenshinImpact.GameTask.Model.Area; using BetterGenshinImpact.GameTask.Model.GameUI; +using BetterGenshinImpact.Helpers.Extensions; +using Fischless.WindowsInput; using Microsoft.Extensions.Logging; using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; @@ -21,6 +26,7 @@ namespace BetterGenshinImpact.GameTask.GetGridIcons; public class GridIconsAccuracyTestTask : ISoloTask { private readonly ILogger logger = App.GetLogger(); + private readonly InputSimulator input = Simulation.SendInput; private CancellationToken ct; @@ -36,25 +42,28 @@ public class GridIconsAccuracyTestTask : ISoloTask this.maxNumToTest = maxNumToTest; } - public async Task Start(CancellationToken ct) + /// + /// 加载图标识别模型 + /// + /// 原型向量 + /// 推理会话 + /// + public static InferenceSession LoadModel(out Dictionary prototypes) { - this.ct = ct; - #region 加载model - using var session = new InferenceSession(@".\GameTask\GetGridIcons\gridIcon.onnx"); // todo 所有数据炼好后放到onnx统一存放的位置去 + var session = new InferenceSession(@".\GameTask\GetGridIcons\gridIcon.onnx"); // todo 所有数据炼好后放到onnx统一存放的位置去 var metadata = session.ModelMetadata; if (!metadata.CustomMetadataMap.TryGetValue("prefix_list", out string? prefixListJson)) { - logger.LogError("模型文件缺少prefix_list"); - return; + throw new Exception("模型文件缺少prefix_list"); } List prefixList = System.Text.Json.JsonSerializer.Deserialize>(prefixListJson) ?? throw new Exception(); // 不预测前缀 #endregion #region 加载原型向量 var allLines = File.ReadLines(@".\GameTask\GetGridIcons\训练集原型特征.csv").Skip(1); // 跳过首行列名 - Dictionary prototypes = new Dictionary(); + prototypes = new Dictionary(); foreach (string line in allLines) { var columns = line.Split(",").ToArray(); @@ -65,6 +74,33 @@ public class GridIconsAccuracyTestTask : ISoloTask prototypes.Add(columns[0], flatData); } #endregion + return session; + } + + public async Task Start(CancellationToken ct) + { + this.ct = ct; + + switch (this.gridScreenName) + { + case GridScreenName.Weapons: + case GridScreenName.Artifacts: + case GridScreenName.CharacterDevelopmentItems: + case GridScreenName.Food: + case GridScreenName.Materials: + case GridScreenName.Gadget: + case GridScreenName.Quest: + case GridScreenName.PreciousItems: + case GridScreenName.Furnishings: + await new ReturnMainUiTask().Start(ct); + await AutoArtifactSalvageTask.OpenBag(this.gridScreenName, this.input, this.logger, this.ct); + break; + default: + logger.LogInformation("{name}暂不支持自动打开,请提前手动打开界面", gridScreenName.GetDescription()); + break; + } + + using InferenceSession session = LoadModel(out Dictionary prototypes); using var ra0 = CaptureToRectArea(); GridScreenParams gridParams = GridScreenParams.Templates[this.gridScreenName]; @@ -89,6 +125,8 @@ public class GridIconsAccuracyTestTask : ISoloTask await Task.WhenAll(task1, task2); (string, int) result = task2.Result; + string predName = result.Item1; + int predStarNum = result.Item2; // 用CV方法得到的结果 using var ra1 = CaptureToRectArea(); @@ -101,14 +139,14 @@ public class GridIconsAccuracyTestTask : ISoloTask // 统计结果 total_count++; - if (itemName.Contains(result.Item1) && result.Item2 == itemStarNum) + if (itemName.Contains(predName) && predStarNum == itemStarNum) { total_acc++; - logger.LogInformation($"{result.Item1}|{result.Item2}星,✔,正确率{total_acc / total_count:0.00}"); + logger.LogInformation($"{predName}|{predStarNum}星,✔,正确率{total_acc / total_count:0.00}"); } else { - logger.LogInformation($"{result.Item1}|{result.Item2}星,应为:{itemName}|{itemStarNum}星,❌,正确率{total_acc / total_count:0.00}"); + logger.LogInformation($"{predName}|{predStarNum}星,应为:{itemName}|{itemStarNum}星,❌,正确率{total_acc / total_count:0.00}"); } count--; diff --git a/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs b/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs index 663c04fb..976d9c2b 100644 --- a/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs +++ b/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs @@ -1,4 +1,4 @@ -using Microsoft.ClearScript; +using Microsoft.ClearScript; using System; using System.Reflection; @@ -45,7 +45,7 @@ public class ScriptObjectConverter if (source[propertyName] is not Undefined && source[propertyName] != null) { object value = source.GetProperty(propertyName); - return (T)Convert.ChangeType(value, typeof(T)); + return (T)value; } return defaultValue; } diff --git a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml index 9ef5895b..1cecc075 100644 --- a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml @@ -782,7 +782,8 @@ ItemsSource=' 1234' SelectedIndex="{Binding Config.AutoFightConfig.GuardianAvatar, Mode=TwoWay}" SelectedItem="{Binding Config.AutoFightConfig.GuardianAvatar, Mode=TwoWay}" /> - + + - + @@ -2580,7 +2581,7 @@ - + + + + + + + + + + + + + + + + + + + + + 供JS脚本调用Buff类食物配置;食物名称会忽略“美味的”等前缀,请填不带前缀的名称 + + + + + + + + + + + + + + + + 入参"foodEffectType":1时使用 + + + + + + + + + + + + + + + 入参"foodEffectType":2时使用 + + + + + + + + + + + + + + + 入参"foodEffectType":3时使用 + + + + + + Icon="{ui:SymbolIcon FlashSettings24}"> diff --git a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs index d66ceba8..82a42a8b 100644 --- a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs @@ -123,7 +123,7 @@ public partial class TaskSettingsPageViewModel : ViewModel private List _domainNameList; public static List ArtifactSalvageStarList = ["4", "3", "2", "1"]; - + public static List BossNumList = [1, 2, 3]; @@ -225,6 +225,12 @@ public partial class TaskSettingsPageViewModel : ViewModel SwitchAutoDomainEnabled = false; SwitchAutoFightEnabled = false; SwitchAutoMusicGameEnabled = false; + SwitchAutoAlbumEnabled = false; + SwitchAutoFishingEnabled = false; + SwitchArtifactSalvageEnabled = false; + SwitchAutoRedeemCodeEnabled = false; + SwitchAutoStygianOnslaughtEnabled = false; + SwitchGetGridIconsEnabled = false; await Task.Delay(800); } @@ -597,20 +603,20 @@ public partial class TaskSettingsPageViewModel : ViewModel p.Height = 500; p.ShowDialog(); if (p.DialogResult == true && !string.IsNullOrWhiteSpace(multilineTextBox.Text)) - { + { char[] separators = ['\r', '\n']; - var codes = multilineTextBox.Text.Split(separators, StringSplitOptions.RemoveEmptyEntries) + var codes = multilineTextBox.Text.Split(separators, StringSplitOptions.RemoveEmptyEntries) - .Select(code => code.Trim()) - .Where(code => !string.IsNullOrEmpty(code)) - .ToList(); + .Select(code => code.Trim()) + .Where(code => !string.IsNullOrEmpty(code)) + .ToList(); if (codes.Count == 0) { Toast.Warning("没有有效的兑换码"); return; } - + SwitchAutoRedeemCodeEnabled = true; await new TaskRunner() .RunSoloTaskAsync(new UseRedemptionCodeTask(codes));