mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
23
BetterGenshinImpact/GameTask/Model/GameUI/GridExtensions.cs
Normal file
23
BetterGenshinImpact/GameTask/Model/GameUI/GridExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)]
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user