Merge remote-tracking branch 'origin/main'

This commit is contained in:
辉鸭蛋
2025-09-04 00:31:29 +08:00
16 changed files with 470 additions and 119 deletions

View File

@@ -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<Dispatcher> _logger = App.GetLogger<Dispatcher>();
private object _config = null;
private readonly object _config;
public Dispatcher(object config)
{
@@ -111,7 +111,7 @@ public class Dispatcher
/// <param name="customCt">自定义取消令牌允许从JS控制任务取消</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public async Task RunTask(SoloTask soloTask, CancellationToken? customCt = null)
public async Task<object?> 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<string?>((ScriptObject)soloTask.Config, "foodName", null);
FoodEffectType? foodEffectType = soloTask.Config == null ? null : (FoodEffectType?)ScriptObjectConverter.GetValue<int?>((ScriptObject)soloTask.Config, "foodEffectType", null);
if (foodName != null && foodEffectType != null)
{
throw new NotSupportedException("不能同时指定foodName和foodEffectType");
}
string? foodName = soloTask.Config == null ? null : ScriptObjectConverter.GetValue<string?>((ScriptObject)soloTask.Config, "foodName", null);
FoodEffectType? foodEffectType = soloTask.Config == null ? null : (FoodEffectType?)ScriptObjectConverter.GetValue<int?>((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<GridScreenName?>((ScriptObject)soloTask.Config, "gridScreenName", null) ?? throw new Exception("gridScreenName为空或错误");
string foodName = ScriptObjectConverter.GetValue<string?>((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));
}

View File

@@ -82,7 +82,7 @@ public class AutoArtifactSalvageTask : ISoloTask
artifactAffixStrDic = ArtifactAffix.DefaultStrDic.Select(kvp => new KeyValuePair<ArtifactAffixType, string>(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();

View File

@@ -24,7 +24,7 @@ namespace BetterGenshinImpact.GameTask.AutoEat;
/// 自动吃药任务
/// 检测红血自动使用便携营养袋
/// </summary>
public class AutoEatTask : BaseIndependentTask, ISoloTask
public class AutoEatTask : BaseIndependentTask, ISoloTask<int?>
{
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<int?> 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<string, float[]> 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;
}
}

View File

@@ -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<int>
{
public string Name => "背包数物品";
private readonly ILogger logger = App.GetLogger<CountInventoryItem>();
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<int> 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<string, float[]> 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);
}
}
}

View File

@@ -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());

View File

@@ -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<string, float[]> prototypes)
{
using Mat resized = mat.Resize(new Size(125, 153));

View File

@@ -1,4 +1,4 @@
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
namespace BetterGenshinImpact.GameTask;
@@ -20,3 +20,13 @@ public interface ISoloTask
/// <returns></returns>
Task Start(CancellationToken ct);
}
public interface ISoloTask<T> : ISoloTask
{
/// <summary>
/// 启动独立任务
/// </summary>
/// <param name="ct">取消Token</param>
/// <returns></returns>
new Task<T> Start(CancellationToken ct);
}

View File

@@ -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
{
/// <summary>
/// 获取GridItem图标底部的文字
/// </summary>
/// <param name="mat"></param>
/// <returns></returns>
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);
}
}
}

View File

@@ -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
/// <param name="numbers">传入的Y列表</param>
/// <param name="threshold"></param>
/// <returns>外层是各行从上到下,内层是一行从左到右</returns>
static List<List<ImageRegion>> ClusterRows(IEnumerable<ImageRegion> regions, int threshold)
public static List<List<T>> ClusterRows<T>(IEnumerable<T> 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<List<ImageRegion>> clusters = new List<List<ImageRegion>>();
// 先对Y排序便于聚簇
var sortedRegions = regions.OrderBy(getY).ToList();
List<List<T>> clusters = new List<List<T>>();
if (sortedRegions.Count == 0)
return clusters;
// 初始化第一个聚簇
List<ImageRegion> currentCluster = new List<ImageRegion> { };
List<T> currentCluster = new List<T> { };
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<ImageRegion> { r };
clusters.Add(currentCluster.OrderBy(getX).ToList());
currentCluster = new List<T> { 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
/// <summary>
/// 背包界面的背景是把打开界面之前的画面进行了模糊+黑白渐变滤镜+左上角水印叠加处理
/// 放任五彩斑斓的输入,并且允许点击高亮的话处理起来就复杂了
/// 所以这个Alpha版方法留在这里只是想说明
/// <para>所以这个Alpha版方法留在这里只是想说明
/// 越是琢磨算法,就越会发现传统算法的能力是有极限的
/// 既然是游戏画面,不如在输入的时候就尽量获得没有噪声的画面
/// 既然是游戏画面,不如在输入的时候就尽量获得没有噪声的画面</para>
/// </summary>
/// <param name="src"></param>
/// <returns></returns>

View File

@@ -39,12 +39,44 @@ public class ScriptObjectConverter
}
}
}
public static T GetValue<T>(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;

View File

@@ -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<AutoArtifactSalvageTask> stringLocalizer;

View File

@@ -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)]

View File

@@ -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> modelLoader = new Lazy<ModelLoader>();
}
internal class ModelLoader
{
internal readonly InferenceSession session;
internal readonly Dictionary<string, float[]> prototypes;
public ModelLoader()
{
this.session = GridIconsAccuracyTestTask.LoadModel(out this.prototypes);
}
}
}

View File

@@ -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<string, float[]> prototypes;
public GridIconsAccuracyTestTaskTests(GridIconModelFixture model)
{
this.session = model.modelLoader.Value.session;
this.prototypes = model.modelLoader.Value.prototypes;
}
public static IEnumerable<object[]> 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))]
/// <summary>
/// 测试推理图标的标签,结果应正确
/// </summary>
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);
}
}
}
}

View File

@@ -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<object[]> 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))]
/// <summary>
/// 测试获取各种界面中的物品图标文字,全部转数字,结果应正确
/// </summary>
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<string>();
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);
}
}
}
}

View File

@@ -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<PaddleFixture>, ICollectionFixture<TorchFixture>, ICollectionFixture<LocalizationFixture>
public class InitCollection : ICollectionFixture<PaddleFixture>, ICollectionFixture<TorchFixture>, ICollectionFixture<LocalizationFixture>, ICollectionFixture<GridIconModelFixture>
{
}
}