diff --git a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs index a2dac83a..539d24cc 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs @@ -7,14 +7,14 @@ using BetterGenshinImpact.GameTask.AutoFishing; using BetterGenshinImpact.GameTask.AutoGeniusInvokation; using BetterGenshinImpact.GameTask.AutoPathing.Handler; using BetterGenshinImpact.GameTask.AutoWood; +using BetterGenshinImpact.GameTask.Common.Job; +using BetterGenshinImpact.GameTask.Model.GameUI; using BetterGenshinImpact.Helpers; using BetterGenshinImpact.ViewModel.Pages; using Microsoft.ClearScript; using Microsoft.Extensions.Logging; using System; using System.Threading; -using Microsoft.ClearScript; -using BetterGenshinImpact.Helpers; using System.Threading.Tasks; namespace BetterGenshinImpact.Core.Script.Dependence; @@ -23,7 +23,7 @@ public class Dispatcher { private readonly ILogger _logger = App.GetLogger(); - private object _config = null; + private readonly object _config; public Dispatcher(object config) { @@ -111,7 +111,7 @@ public class Dispatcher /// 自定义取消令牌,允许从JS控制任务取消 /// /// - public async Task RunTask(SoloTask soloTask, CancellationToken? customCt = null) + public async Task RunTask(SoloTask soloTask, CancellationToken? customCt = null) { if (soloTask == null) { @@ -140,119 +140,129 @@ public class Dispatcher // 根据名称执行任务 switch (soloTask.Name) { - case "AutoGeniusInvokation": - string content; + case "AutoGeniusInvokation": + string content; // 检查是否有自定义策略内容 - if (soloTask.Config != null) - { - var jsObject = (ScriptObject)soloTask.Config; - content = ScriptObjectConverter.GetValue(jsObject, "strategy", ""); - if (string.IsNullOrEmpty(content)) - { + if (soloTask.Config != null) + { + var jsObject = (ScriptObject)soloTask.Config; + content = ScriptObjectConverter.GetValue(jsObject, "strategy", ""); + if (string.IsNullOrEmpty(content)) + { // 回退到原有逻辑 - if (taskSettingsPageViewModel.GetTcgStrategy(out content)) - { - return; - } - } - } - else - { + if (taskSettingsPageViewModel.GetTcgStrategy(out content)) + { + return null; + } + } + } + else + { // 回退到原有逻辑 - if (taskSettingsPageViewModel.GetTcgStrategy(out content)) - { - return; - } - } - - await new AutoGeniusInvokationTask(new GeniusInvokationTaskParam(content)).Start(cancellationToken); - break; + if (taskSettingsPageViewModel.GetTcgStrategy(out content)) + { + return null; + } + } + + await new AutoGeniusInvokationTask(new GeniusInvokationTaskParam(content)).Start(cancellationToken); + return null; case "AutoWood": await new AutoWoodTask(new WoodTaskParam(taskSettingsPageViewModel.AutoWoodRoundNum, taskSettingsPageViewModel.AutoWoodDailyMaxCount)).Start(cancellationToken); - break; + return null; case "AutoFight": await new AutoFightHandler().RunAsyncByScript(cancellationToken, null, _config); - break; + return null; case "AutoDomain": if (taskSettingsPageViewModel.GetFightStrategy(out var path)) { - return; + return null; } await new AutoDomainTask(new AutoDomainParam(0, path)).Start(cancellationToken); - break; + return null; case "AutoFishing": await new AutoFishingTask(AutoFishingTaskParam.BuildFromSoloTaskConfig(soloTask.Config)).Start( cancellationToken); - break; + return null; 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"); - } + 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) - { - if (foodEffectType != null) + if (foodName != null && foodEffectType != null) { - PathingPartyConfig? pathingPartyConfig = _config as PathingPartyConfig; - if (pathingPartyConfig == null) + throw new NotSupportedException("不能同时指定foodName和foodEffectType"); + } + + if (foodName == null) + { + if (foodEffectType != null) { - throw new NotSupportedException("foodEffectType参数需要调度器配置,请在调度器下使用"); - } - else - { - switch (foodEffectType) + PathingPartyConfig? pathingPartyConfig = _config as PathingPartyConfig; + if (pathingPartyConfig == null) { - 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"); + throw new NotSupportedException("foodEffectType参数需要调度器配置,请在调度器下使用"); + } + else + { + switch (foodEffectType) + { + case FoodEffectType.ATKBoostingDish: + foodName = pathingPartyConfig.AutoEatConfig.DefaultAtkBoostingDishName; + if (foodName == null) + { + _logger.LogInformation("缺少{Text}配置,跳过吃Buff", "默认的攻击类料理"); + return null; + } + break; + case FoodEffectType.AdventurersDish: + foodName = pathingPartyConfig.AutoEatConfig.DefaultAdventurersDishName; + if (foodName == null) + { + _logger.LogInformation("缺少{Text}配置,跳过吃Buff", "默认的冒险类料理"); + return null; + } + break; + case FoodEffectType.DEFBoostingDish: + foodName = pathingPartyConfig.AutoEatConfig.DefaultDefBoostingDishName; + if (foodName == null) + { + _logger.LogInformation("缺少{Text}配置,跳过吃Buff", "默认的防御类料理"); + return null; + } + break; + default: + throw new NotSupportedException("JS脚本入参错误:错误的foodEffectType"); + } } } } + + var autoEatConfig = TaskContext.Instance().Config.AutoEatConfig; + return await new AutoEatTask(new AutoEatParam() + { + CheckInterval = autoEatConfig.CheckInterval, + EatInterval = autoEatConfig.EatInterval, + ShowNotification = autoEatConfig.ShowNotification, + FoodName = foodName + }).Start(cancellationToken); } - - var autoEatConfig = TaskContext.Instance().Config.AutoEatConfig; - await new AutoEatTask(new AutoEatParam() + case "CountInventoryItem": { - CheckInterval = autoEatConfig.CheckInterval, - EatInterval = autoEatConfig.EatInterval, - ShowNotification = autoEatConfig.ShowNotification, - FoodName = foodName - }).Start(cancellationToken); - - break; + if (soloTask.Config == null) + { + throw new NullReferenceException($"{nameof(soloTask.Config)}为空"); + } + GridScreenName gridScreenName = ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "gridScreenName", null) ?? throw new Exception("gridScreenName为空或错误"); + string foodName = ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "foodName", null) ?? throw new Exception("foodName为空"); + return await new CountInventoryItem(gridScreenName, foodName).Start(cancellationToken); + } default: throw new ArgumentException($"未知的任务名称: {soloTask.Name}", nameof(soloTask.Name)); } diff --git a/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs b/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs index 92856d00..741ac366 100644 --- a/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs +++ b/BetterGenshinImpact/GameTask/AutoArtifactSalvage/AutoArtifactSalvageTask.cs @@ -82,7 +82,7 @@ public class AutoArtifactSalvageTask : ISoloTask artifactAffixStrDic = ArtifactAffix.DefaultStrDic.Select(kvp => new KeyValuePair(kvp.Key, stringLocalizer.WithCultureGet(cultureInfo, kvp.Value))).ToFrozenDictionary(); } - public static async Task OpenBag(GridScreenName gridScreenName, InputSimulator input, ILogger logger, CancellationToken ct) + public static async Task OpenInventory(GridScreenName gridScreenName, InputSimulator input, ILogger logger, CancellationToken ct) { RecognitionObject? recognitionObjectChecked; RecognitionObject? recognitionObjectUnchecked; @@ -179,7 +179,7 @@ public class AutoArtifactSalvageTask : ISoloTask await new ReturnMainUiTask().Start(ct); } - await OpenBag(GridScreenName.Artifacts, this.input, this.logger, this.ct); + await OpenInventory(GridScreenName.Artifacts, this.input, this.logger, this.ct); // 点击分解按钮打开分解界面 using var ra2 = CaptureToRectArea(); diff --git a/BetterGenshinImpact/GameTask/AutoEat/AutoEatTask.cs b/BetterGenshinImpact/GameTask/AutoEat/AutoEatTask.cs index b78b9036..09614f47 100644 --- a/BetterGenshinImpact/GameTask/AutoEat/AutoEatTask.cs +++ b/BetterGenshinImpact/GameTask/AutoEat/AutoEatTask.cs @@ -24,7 +24,7 @@ namespace BetterGenshinImpact.GameTask.AutoEat; /// 自动吃药任务 /// 检测红血自动使用便携营养袋 /// -public class AutoEatTask : BaseIndependentTask, ISoloTask +public class AutoEatTask : BaseIndependentTask, ISoloTask { public string Name => "自动吃药"; @@ -40,7 +40,12 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask _config = TaskContext.Instance().Config.AutoEatConfig; } - public async Task Start(CancellationToken ct) + async Task ISoloTask.Start(CancellationToken ct) + { + await Start(ct); + } + + public async Task Start(CancellationToken ct) { _ct = ct; @@ -52,7 +57,7 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask if (!IsTakeFood()) { _logger.LogWarning("未装备 \"{Tool}\",无法启用自动吃药功能", "便携营养袋"); - return; + return null; } try @@ -68,12 +73,14 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask { _logger.LogInformation("自动吃药任务结束"); } + + return null; } else { _logger.LogInformation("打开背包寻找{name}……", _taskParam.FoodName); await new ReturnMainUiTask().Start(ct); - await AutoArtifactSalvageTask.OpenBag(GridScreenName.Food, _input, _logger, _ct); + await AutoArtifactSalvageTask.OpenInventory(GridScreenName.Food, _input, _logger, _ct); using InferenceSession session = GridIconsAccuracyTestTask.LoadModel(out Dictionary prototypes); @@ -81,7 +88,7 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask GridScreenParams gridParams = GridScreenParams.Templates[GridScreenName.Food]; var gridRoi = gridParams.GetRect(ra0); GridScreen gridScreen = new GridScreen(gridRoi, gridParams, _logger, _ct); - bool isAte = false; + int? count = null; await foreach (ImageRegion itemRegion in gridScreen) { var result = GridIconsAccuracyTestTask.Infer(itemRegion.SrcMat, session, prototypes); @@ -90,6 +97,20 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask { // 点击item itemRegion.Click(); + + #region 识别数量 + string numStr = itemRegion.SrcMat.GetGridItemIconText(OcrFactory.Paddle); + if (int.TryParse(numStr, out int num)) + { + count = num - 1; // 算上吃掉的1个 + } + else + { + count = -2; + _logger.LogWarning("无法识别食物数量:{text},依然尝试使用", numStr); + } + #endregion + await Delay(300, ct); // 点击确定 using var ra = ra0.Find(ElementAssets.Instance.BtnWhiteConfirm); @@ -98,15 +119,17 @@ public class AutoEatTask : BaseIndependentTask, ISoloTask ra.Click(); } _logger.LogInformation("吃了一份{name},真香!", predName); - isAte = true; break; } } - if (!isAte) + if (count == null) { + count = -1; _logger.LogInformation("没有找到{name}", _taskParam.FoodName); } await new ReturnMainUiTask().Start(ct); + + return count; } } diff --git a/BetterGenshinImpact/GameTask/Common/Job/CountInventoryItem.cs b/BetterGenshinImpact/GameTask/Common/Job/CountInventoryItem.cs new file mode 100644 index 00000000..b71bdede --- /dev/null +++ b/BetterGenshinImpact/GameTask/Common/Job/CountInventoryItem.cs @@ -0,0 +1,84 @@ +using BetterGenshinImpact.Core.Recognition.OCR; +using BetterGenshinImpact.Core.Simulator; +using BetterGenshinImpact.GameTask.AutoArtifactSalvage; +using BetterGenshinImpact.GameTask.GetGridIcons; +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.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace BetterGenshinImpact.GameTask.Common.Job +{ + internal class CountInventoryItem : ISoloTask + { + public string Name => "背包数物品"; + + private readonly ILogger logger = App.GetLogger(); + private readonly InputSimulator input = Simulation.SendInput; + private CancellationToken ct; + private readonly GridScreenName gridScreenName; + private readonly string foodName; + + public CountInventoryItem(GridScreenName gridScreenName, string foodName) + { + this.gridScreenName = gridScreenName; + this.foodName = foodName; + } + + public async Task Start(CancellationToken ct) + { + this.ct = ct; + + logger.LogInformation("打开背包并在{grid}寻找{name}……", this.gridScreenName, this.foodName); + await new ReturnMainUiTask().Start(ct); + await AutoArtifactSalvageTask.OpenInventory(this.gridScreenName, input, logger, this.ct); + + using InferenceSession session = GridIconsAccuracyTestTask.LoadModel(out Dictionary prototypes); + + using var ra = TaskControl.CaptureToRectArea(); + GridScreenParams gridParams = GridScreenParams.Templates[this.gridScreenName]; + var gridRoi = gridParams.GetRect(ra); + GridScreen gridScreen = new GridScreen(gridRoi, gridParams, logger, ct); + int? count = null; + await foreach (ImageRegion itemRegion in gridScreen) + { + var result = GridIconsAccuracyTestTask.Infer(itemRegion.SrcMat, session, prototypes); + string predName = result.Item1; + if (predName == this.foodName) + { + string numStr = itemRegion.SrcMat.GetGridItemIconText(OcrFactory.Paddle); + if (int.TryParse(numStr, out int num)) + { + count = num; + } + else + { + count = -2; + logger.LogWarning("无法识别数量:{text}", numStr); + } + + break; + } + } + if (count == null) + { + count = -1; + logger.LogInformation("没有找到{name}", this.foodName); + } + await new ReturnMainUiTask().Start(ct); + + return count.Value; + } + + async Task ISoloTask.Start(CancellationToken ct) + { + await Start(ct); + } + } +} diff --git a/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsTask.cs b/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsTask.cs index f1c9cbf5..9f521107 100644 --- a/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsTask.cs +++ b/BetterGenshinImpact/GameTask/GetGridIcons/GetGridIconsTask.cs @@ -60,7 +60,7 @@ public class GetGridIconsTask : ISoloTask case GridScreenName.PreciousItems: case GridScreenName.Furnishings: await new ReturnMainUiTask().Start(ct); - await AutoArtifactSalvageTask.OpenBag(this.gridScreenName, this.input, this.logger, this.ct); + await AutoArtifactSalvageTask.OpenInventory(this.gridScreenName, this.input, this.logger, this.ct); break; default: logger.LogInformation("{name}暂不支持自动打开,请提前手动打开界面", gridScreenName.GetDescription()); diff --git a/BetterGenshinImpact/GameTask/GetGridIcons/GridIconsAccuracyTestTask.cs b/BetterGenshinImpact/GameTask/GetGridIcons/GridIconsAccuracyTestTask.cs index 689c45a3..765cf25f 100644 --- a/BetterGenshinImpact/GameTask/GetGridIcons/GridIconsAccuracyTestTask.cs +++ b/BetterGenshinImpact/GameTask/GetGridIcons/GridIconsAccuracyTestTask.cs @@ -93,7 +93,7 @@ public class GridIconsAccuracyTestTask : ISoloTask case GridScreenName.PreciousItems: case GridScreenName.Furnishings: await new ReturnMainUiTask().Start(ct); - await AutoArtifactSalvageTask.OpenBag(this.gridScreenName, this.input, this.logger, this.ct); + await AutoArtifactSalvageTask.OpenInventory(this.gridScreenName, this.input, this.logger, this.ct); break; default: logger.LogInformation("{name}暂不支持自动打开,请提前手动打开界面", gridScreenName.GetDescription()); @@ -158,7 +158,6 @@ public class GridIconsAccuracyTestTask : ISoloTask } } - // todo: 单元测试 public static (string, int) Infer(Mat mat, InferenceSession session, Dictionary prototypes) { using Mat resized = mat.Resize(new Size(125, 153)); diff --git a/BetterGenshinImpact/GameTask/ISoloTask.cs b/BetterGenshinImpact/GameTask/ISoloTask.cs index 70876229..e59ca269 100644 --- a/BetterGenshinImpact/GameTask/ISoloTask.cs +++ b/BetterGenshinImpact/GameTask/ISoloTask.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; namespace BetterGenshinImpact.GameTask; @@ -20,3 +20,13 @@ public interface ISoloTask /// Task Start(CancellationToken ct); } + +public interface ISoloTask : ISoloTask +{ + /// + /// 启动独立任务 + /// + /// 取消Token + /// + new Task Start(CancellationToken ct); +} diff --git a/BetterGenshinImpact/GameTask/Model/GameUI/GridExtensions.cs b/BetterGenshinImpact/GameTask/Model/GameUI/GridExtensions.cs new file mode 100644 index 00000000..a55917d8 --- /dev/null +++ b/BetterGenshinImpact/GameTask/Model/GameUI/GridExtensions.cs @@ -0,0 +1,23 @@ +using BetterGenshinImpact.Core.Recognition.OCR; +using OpenCvSharp; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BetterGenshinImpact.GameTask.Model.GameUI +{ + public static class GridExtensions + { + /// + /// 获取GridItem图标底部的文字 + /// + /// + /// + public static string GetGridItemIconText(this Mat mat, IOcrService ocrService) + { + Mat subMat = mat.SubMat(mat.Height * 128 / 153, mat.Height * 150 / 153, mat.Width * 5 / 125, mat.Width * 120 / 125); + using Mat resize = subMat.Resize(new Size(subMat.Width * 2, subMat.Height * 2)); + return ocrService.Ocr(resize); + } + } +} diff --git a/BetterGenshinImpact/GameTask/Model/GameUI/GridScreen.cs b/BetterGenshinImpact/GameTask/Model/GameUI/GridScreen.cs index 82f6e74a..314a8e4e 100644 --- a/BetterGenshinImpact/GameTask/Model/GameUI/GridScreen.cs +++ b/BetterGenshinImpact/GameTask/Model/GameUI/GridScreen.cs @@ -1,5 +1,4 @@ -using BetterGenshinImpact.Core.Simulator; -using BetterGenshinImpact.GameTask; +using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.GameTask.Common; using BetterGenshinImpact.GameTask.Model.Area; using Fischless.WindowsInput; @@ -153,20 +152,51 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI /// 传入的Y列表 /// /// 外层是各行从上到下,内层是一行从左到右 - static List> ClusterRows(IEnumerable regions, int threshold) + public static List> ClusterRows(IEnumerable regions, int threshold) { - // 先对Y排序,便于聚簇 - var sortedRegions = regions.OrderBy(r => r.Y).ToList(); + static int getX(T t) + { + if (t is ImageRegion imageRegion) + { + return imageRegion.X; + } + else if (t is Rect rect) + { + return rect.X; + } + else + { + throw new NotSupportedException(); + } + } + static int getY(T t) + { + if (t is ImageRegion imageRegion) + { + return imageRegion.Y; + } + else if (t is Rect rect) + { + return rect.Y; + } + else + { + throw new NotSupportedException(); + } + } - List> clusters = new List>(); + // 先对Y排序,便于聚簇 + var sortedRegions = regions.OrderBy(getY).ToList(); + + List> clusters = new List>(); if (sortedRegions.Count == 0) return clusters; // 初始化第一个聚簇 - List currentCluster = new List { }; + List currentCluster = new List { }; - foreach (ImageRegion r in sortedRegions) + foreach (T r in sortedRegions) { if (currentCluster.Count <= 0) { @@ -174,23 +204,23 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI continue; } - ImageRegion lastInCluster = currentCluster.Last(); + T lastInCluster = currentCluster.Last(); // 如果当前数字与聚簇中最后一个数字的差值小于阈值,则加入当前聚簇 - if (r.Y - lastInCluster.Y <= threshold) + if (getY(r) - getY(lastInCluster) <= threshold) { currentCluster.Add(r); } else { // 否则,创建一个新的聚簇 - clusters.Add(currentCluster.OrderBy(r => r.X).ToList()); - currentCluster = new List { r }; + clusters.Add(currentCluster.OrderBy(getX).ToList()); + currentCluster = new List { r }; } } // 添加最后一个聚簇 - clusters.Add(currentCluster.OrderBy(r => r.X).ToList()); + clusters.Add(currentCluster.OrderBy(getX).ToList()); return clusters; } @@ -294,9 +324,9 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI /// /// 背包界面的背景是把打开界面之前的画面进行了模糊+黑白渐变滤镜+左上角水印叠加处理 /// 放任五彩斑斓的输入,并且允许点击高亮的话处理起来就复杂了 - /// 所以这个Alpha版方法留在这里只是想说明: + /// 所以这个Alpha版方法留在这里只是想说明: /// 越是琢磨算法,就越会发现传统算法的能力是有极限的 - /// 既然是游戏画面,不如在输入的时候就尽量获得没有噪声的画面 + /// 既然是游戏画面,不如在输入的时候就尽量获得没有噪声的画面 /// /// /// diff --git a/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs b/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs index 976d9c2b..506ed447 100644 --- a/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs +++ b/BetterGenshinImpact/Helpers/ScriptObjectConverter.cs @@ -39,12 +39,44 @@ public class ScriptObjectConverter } } } - + public static T GetValue(ScriptObject source, string propertyName, T defaultValue) { if (source[propertyName] is not Undefined && source[propertyName] != null) { object value = source.GetProperty(propertyName); + + Type type = typeof(T); + type = Nullable.GetUnderlyingType(type) ?? type; + + if (type.IsEnum) + { + // 处理数字 + if (value is int intValue) + { + if (Enum.IsDefined(type, intValue)) + { + return (T)Enum.ToObject(type, intValue); + } + else + { + return defaultValue; + } + } + // 处理字符串 + else if (value is string strValue) + { + if (Enum.TryParse(type, strValue, ignoreCase: true, out object? parsedEnum)) + { + return (T)parsedEnum; + } + else + { + return defaultValue; + } + } + } + return (T)value; } return defaultValue; diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoArtifactSalvageTests/AutoArtifactSalvageTaskTests.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoArtifactSalvageTests/AutoArtifactSalvageTaskTests.cs index 3cf8b022..b89248b5 100644 --- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoArtifactSalvageTests/AutoArtifactSalvageTaskTests.cs +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoArtifactSalvageTests/AutoArtifactSalvageTaskTests.cs @@ -14,7 +14,7 @@ using static BetterGenshinImpact.GameTask.AutoArtifactSalvage.AutoArtifactSalvag namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoArtifactSalvageTests { [Collection("Init Collection")] - public partial class AutoArtifactSalvageTaskTests + public class AutoArtifactSalvageTaskTests { private readonly PaddleFixture paddle; private readonly IStringLocalizer stringLocalizer; diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GetGridIconsTaskTest.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GetGridIconsTaskTests.cs similarity index 88% rename from Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GetGridIconsTaskTest.cs rename to Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GetGridIconsTaskTests.cs index 3d632d16..7d6a8a1d 100644 --- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GetGridIconsTaskTest.cs +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GetGridIconsTaskTests.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.GameTask.GetGridIcons; +using BetterGenshinImpact.GameTask.GetGridIcons; using OpenCvSharp; using System; using System.Collections.Generic; @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace BetterGenshinImpact.UnitTest.GameTaskTests.GetGridIconsTests { - public class GetGridIconsTaskTest + public class GetGridIconsTaskTests { [Theory] [InlineData(@"AutoArtifactSalvage\ArtifactAffixes.png", 5)] diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GridIconModelFixture.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GridIconModelFixture.cs new file mode 100644 index 00000000..656ed088 --- /dev/null +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GridIconModelFixture.cs @@ -0,0 +1,25 @@ +using BetterGenshinImpact.GameTask.GetGridIcons; +using Microsoft.ML.OnnxRuntime; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterGenshinImpact.UnitTest.GameTaskTests.GetGridIconsTests +{ + public class GridIconModelFixture + { + internal readonly Lazy modelLoader = new Lazy(); + } + + internal class ModelLoader + { + internal readonly InferenceSession session; + internal readonly Dictionary prototypes; + public ModelLoader() + { + this.session = GridIconsAccuracyTestTask.LoadModel(out this.prototypes); + } + } +} diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GridIconsAccuracyTestTaskTests.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GridIconsAccuracyTestTaskTests.cs new file mode 100644 index 00000000..81e70dc8 --- /dev/null +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/GetGridIconsTests/GridIconsAccuracyTestTaskTests.cs @@ -0,0 +1,65 @@ +using BetterGenshinImpact.GameTask.GetGridIcons; +using BetterGenshinImpact.GameTask.Model.GameUI; +using Microsoft.ML.OnnxRuntime; +using OpenCvSharp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterGenshinImpact.UnitTest.GameTaskTests.GetGridIconsTests +{ + [Collection("Init Collection")] + public class GridIconsAccuracyTestTaskTests + { + private readonly InferenceSession session; + private readonly Dictionary prototypes; + + public GridIconsAccuracyTestTaskTests(GridIconModelFixture model) + { + this.session = model.modelLoader.Value.session; + this.prototypes = model.modelLoader.Value.prototypes; + } + + public static IEnumerable InferTestData() + { + yield return new object[] { @"GetGridIcons\FoodGrid.png", 8, new[] { ("苹果", 0), ("日落果", 0), ("星蕈", 0), ("泡泡桔", 0), ("烛伞蘑菇", 0), ("宝石闪闪", 4), ("咚咚", 4), ("枫达", 2), + ("雾凇秋分", 4), ("蒙德土豆饼", 3), ("爪爪土豆饼", 3), ("北地苹果焖肉", 3), ("四方和平", 3), ("盛世太平", 3), ("三彩团子", 3), ("夏祭游鱼", 3)} }; + // todo 爪爪土豆饼被吃掉了没进训练集。。。 + } + + [Theory] + [MemberData(nameof(InferTestData))] + /// + /// 测试推理图标的标签,结果应正确 + /// + public void Infer_ShouldBeRight(string screenshot, int columns, (string, int)[] nameAndStars) + { + // + using Mat mat = new Mat(@$"..\..\..\Assets\{screenshot}"); + + var gridItems = GridScreen.GridEnumerator.GetGridItems(mat, columns, findContoursAlpha: true); + var rows = GridScreen.GridEnumerator.ClusterRows(gridItems, 5); + + // + var result = new List<(string, int)>(); + foreach (var row in rows) + { + foreach (Rect rect in row) + { + Mat gridItemMat = mat.SubMat(rect); + var pred = GridIconsAccuracyTestTask.Infer(gridItemMat, this.session, this.prototypes); + result.Add(pred); + } + } + + // + for (int i = 0; i < nameAndStars.Length - 1; i++) + { + Assert.Equal(nameAndStars[i].Item1, result[i].Item1); + Assert.Equal(nameAndStars[i].Item2, result[i].Item2); + } + } + } +} diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/Model/GameUI/GridScreenTests.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/Model/GameUI/GridScreenTests.cs index 1f9ed583..da8c62ab 100644 --- a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/Model/GameUI/GridScreenTests.cs +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/Model/GameUI/GridScreenTests.cs @@ -1,4 +1,5 @@ -using BetterGenshinImpact.GameTask.Model.GameUI; +using BetterGenshinImpact.GameTask.Model.GameUI; +using BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests; using OpenCvSharp; using System; using System.Collections.Generic; @@ -8,8 +9,15 @@ using System.Threading.Tasks; namespace BetterGenshinImpact.UnitTest.GameTaskTests.Model.GameUI { + [Collection("Init Collection")] public class GridScreenTests { + private readonly PaddleFixture paddle; + public GridScreenTests(PaddleFixture paddle) + { + this.paddle = paddle; + } + [Theory] [InlineData(@"AutoArtifactSalvage\ArtifactGrid.png", 4, 2)] [InlineData(@"GetGridIcons\WeaponGrid3.png", 32, 8)] @@ -72,5 +80,46 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.Model.GameUI Assert.False(result2); Assert.False(result3); } + + public static IEnumerable GetGridItemIconTextTestData() + { + yield return new object[] { @"GetGridIcons\FoodGrid.png", 8, new[] { 1850, 1073, 167, 85, 69, 6, 1, 90, + 6, 5, 1, 10, 15, 3, 2, 1, + 2, 2, 2, 1, 3, 2, 2, 4, + 1, 1, 3, 2, 2, 1, 1, 1} }; + } + + [Theory] + [MemberData(nameof(GetGridItemIconTextTestData))] + /// + /// 测试获取各种界面中的物品图标文字,全部转数字,结果应正确 + /// + public void GetGridItemIconText_Number_ShouldBeRight(string screenshot, int columns, int[] numbers) + { + // + using Mat mat = new Mat(@$"..\..\..\Assets\{screenshot}"); + + // + var gridItems = GridScreen.GridEnumerator.GetGridItems(mat, columns, findContoursAlpha: true); + var rows = GridScreen.GridEnumerator.ClusterRows(gridItems, 5); + + var result = new List(); + foreach (var row in rows) + { + foreach (Rect rect in row) + { + Mat gridItemMat = mat.SubMat(rect); + string numStr = gridItemMat.GetGridItemIconText(paddle.Get()); + result.Add(numStr); + } + } + + // + for (int i = 0; i < numbers.Length - 1; i++) + { + Assert.True(int.TryParse(result[i], out int intResult), $"第{i + 1}个图标文字解析失败-->{result[i]}<--"); + Assert.Equal(numbers[i], intResult); + } + } } } diff --git a/Test/BetterGenshinImpact.UnitTest/InitCollection.cs b/Test/BetterGenshinImpact.UnitTest/InitCollection.cs index 263699aa..1e164f76 100644 --- a/Test/BetterGenshinImpact.UnitTest/InitCollection.cs +++ b/Test/BetterGenshinImpact.UnitTest/InitCollection.cs @@ -1,5 +1,6 @@ using BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests; using BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests; +using BetterGenshinImpact.UnitTest.GameTaskTests.GetGridIconsTests; using System; using System.Collections.Generic; using System.Linq; @@ -9,7 +10,7 @@ using System.Threading.Tasks; namespace BetterGenshinImpact.UnitTest { [CollectionDefinition("Init Collection")] - public class InitCollection : ICollectionFixture, ICollectionFixture, ICollectionFixture + public class InitCollection : ICollectionFixture, ICollectionFixture, ICollectionFixture, ICollectionFixture { } }