mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-04-11 15:24:02 +08:00
更新了 `BetterGenshinImpact.csproj` 文件中的程序集版本号,从 `0.36.2` 更新到 `0.36.3`。在 `ElementAssets.cs` 文件中,添加了 `CraftCondensedResin` 识别对象,并在 `ElementAssets` 类的构造函数中初始化了该对象。在 `ChooseTalkOptionTask.cs` 文件中,添加了 `BetterGenshinImpact.Core.Config` 的引用,并增加了在选项文本包含指定内容时保存截图的功能。修改了 `SelectLastOptionUntilEnd` 方法,增加了 `endAction` 参数和重试次数 `retry` 参数,并在循环中增加了对 `endAction` 的判断。修改了 `IsOrangeOption` 方法中的颜色阈值参数。在 `GoToAdventurersGuildTask.cs` 文件中,添加了 `Vanara.PInvoke` 的引用,并增加了等待对话界面的延迟时间和领取每日委托奖励后的点击操作。在 `HotKeyPageViewModel.cs` 文件中,修改了 `Test2Hotkey` 的回调函数,改为执行 `GoToCraftingBenchTask` 任务。 添加了多个命名空间引用,包括 `BetterGenshinImpact.Core.Config`、`BetterGenshinImpact.GameTask.AutoPathing`、`BetterGenshinImpact.GameTask.AutoPathing.Model`、`BetterGenshinImpact.GameTask.AutoSkip.Assets`、`BetterGenshinImpact.GameTask.AutoSkip`、`BetterGenshinImpact.GameTask.Common.BgiVision`、`Microsoft.Extensions.Logging`、`System`、`System.Threading`、`System.Threading.Tasks`、`BetterGenshinImpact.Core.Simulator`、`BetterGenshinImpact.GameTask.Common.Element.Assets` 和 `Vanara.PInvoke`。 新增了 `GoToCraftingBenchTask` 类,并定义了其命名空间 `BetterGenshinImpact.GameTask.Common.Job`。在 `GoToCraftingBenchTask` 类中,定义了 `Name` 属性,表示任务名称。定义了 `_retryTimes` 字段,表示重试次数。定义了 `_chooseTalkOptionTask` 字段,用于选择对话选项。新增了 `Start` 方法,接受 `country` 和 `CancellationToken` 参数,表示任务的开始。在 `Start` 方法中,添加了重试机制,调用 `DoOnce` 方法执行任务,并在异常情况下进行重试。新增了 `DoOnce` 方法,接受 `country` 和 `CancellationToken` 参数,表示单次任务的执行。在 `DoOnce` 方法中,定义了前往合成台、交互、等待合成界面、判断浓缩树脂是否存在等步骤。新增了 `GoToCraftingBench` 方法,接受 `country` 和 `CancellationToken` 参数,表示前往合成台的操作。在 `GoToCraftingBench` 方法中,使用 `PathingTask` 和 `PathExecutor` 执行路径任务。
225 lines
7.4 KiB
C#
225 lines
7.4 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using BetterGenshinImpact.Core.Recognition;
|
||
using BetterGenshinImpact.Core.Simulator;
|
||
using BetterGenshinImpact.GameTask.AutoSkip.Assets;
|
||
using BetterGenshinImpact.GameTask.AutoSkip;
|
||
using BetterGenshinImpact.GameTask.Common.BgiVision;
|
||
using BetterGenshinImpact.GameTask.Model.Area;
|
||
using OpenCvSharp;
|
||
using Vanara.PInvoke;
|
||
using static BetterGenshinImpact.GameTask.Common.TaskControl;
|
||
using System.Text.RegularExpressions;
|
||
using BetterGenshinImpact.Core.Config;
|
||
using Microsoft.Extensions.Logging;
|
||
using BetterGenshinImpact.Core.Recognition.OpenCv;
|
||
|
||
namespace BetterGenshinImpact.GameTask.Common.Job;
|
||
|
||
public partial class ChooseTalkOptionTask
|
||
{
|
||
private readonly ILogger<ChooseTalkOptionTask> _logger = App.GetLogger<ChooseTalkOptionTask>();
|
||
|
||
public string Name => "持续对话并选择目标选项";
|
||
|
||
// private readonly AutoSkipConfig _config = TaskContext.Instance().Config.AutoSkipConfig;
|
||
|
||
/// <summary>
|
||
/// 单个界面单个选项选择
|
||
/// </summary>
|
||
/// <param name="option"></param>
|
||
/// <param name="ct"></param>
|
||
/// <param name="skipTimes">200ms一次,点击几次空格</param>
|
||
/// <param name="isOrange"></param>
|
||
/// <returns></returns>
|
||
public async Task<TalkOptionRes> SingleSelectText(string option, CancellationToken ct, int skipTimes = 10, bool isOrange = false)
|
||
{
|
||
if (!await Bv.WaitAndSkipForTalkUi(ct, 10))
|
||
{
|
||
Logger.LogError("选项选择:{Text}", "当前界面不在对话选项界面");
|
||
return TalkOptionRes.NotFound;
|
||
}
|
||
|
||
await Task.Delay(200, ct);
|
||
|
||
for (var i = 0; i < skipTimes; i++) // 重试3次
|
||
{
|
||
var region = CaptureToRectArea();
|
||
var optionRegions = RecognizeOption(region, ct);
|
||
if (optionRegions == null)
|
||
{
|
||
TaskContext.Instance().PostMessageSimulator.KeyPressBackground(User32.VK.VK_SPACE);
|
||
await Delay(200, ct);
|
||
continue; // retry
|
||
}
|
||
|
||
foreach (var optionRa in optionRegions)
|
||
{
|
||
if (optionRa.Text.Contains(option))
|
||
{
|
||
if (isOrange)
|
||
{
|
||
region.DeriveCrop(optionRa.ToRect()).SrcMat.SaveImage(Global.Absolute($"log\\t{optionRa.Text}.png"));
|
||
if (!IsOrangeOption(region.DeriveCrop(optionRa.ToRect()).SrcMat))
|
||
{
|
||
return TalkOptionRes.FoundButNotOrange;
|
||
}
|
||
}
|
||
|
||
ClickOcrRegion(optionRa);
|
||
await Task.Delay(200, ct);
|
||
return TalkOptionRes.FoundAndClick;
|
||
}
|
||
}
|
||
}
|
||
|
||
return TalkOptionRes.NotFound;
|
||
}
|
||
|
||
public async Task SelectLastOptionOnce(CancellationToken ct)
|
||
{
|
||
var region = CaptureToRectArea();
|
||
if (Bv.IsInTalkUi(region))
|
||
{
|
||
var chatOptionResultList = region.FindMulti(AutoSkipAssets.Instance.OptionIconRo);
|
||
chatOptionResultList = [.. chatOptionResultList.OrderByDescending(r => r.Y)];
|
||
if (chatOptionResultList.Count > 0)
|
||
{
|
||
ClickOcrRegion(chatOptionResultList[0]);
|
||
await Task.Delay(200, ct);
|
||
}
|
||
}
|
||
}
|
||
|
||
public async Task SelectLastOptionUntilEnd(CancellationToken ct, Func<ImageRegion, bool>? endAction = null, int retry = 2400)
|
||
{
|
||
for (var i = 0; i < retry; i++)
|
||
{
|
||
var region = CaptureToRectArea();
|
||
if (Bv.IsInTalkUi(region))
|
||
{
|
||
var chatOptionResultList = region.FindMulti(AutoSkipAssets.Instance.OptionIconRo);
|
||
chatOptionResultList = [.. chatOptionResultList.OrderByDescending(r => r.Y)];
|
||
if (chatOptionResultList.Count > 0)
|
||
{
|
||
ClickOcrRegion(chatOptionResultList[0]);
|
||
}
|
||
else
|
||
{
|
||
TaskContext.Instance().PostMessageSimulator.KeyPressBackground(User32.VK.VK_SPACE);
|
||
}
|
||
}
|
||
else if (Bv.IsInMainUi(region))
|
||
{
|
||
break;
|
||
}
|
||
else if (endAction != null && endAction(region))
|
||
{
|
||
break;
|
||
}
|
||
await Task.Delay(200, ct);
|
||
}
|
||
}
|
||
|
||
[GeneratedRegex(@"^[a-zA-Z0-9]+$")]
|
||
private static partial Regex EnOrNumRegex();
|
||
|
||
/// <summary>
|
||
/// 识别当前对话界面的所有选项
|
||
/// </summary>
|
||
/// <param name="region"></param>
|
||
/// <param name="ct"></param>
|
||
/// <returns></returns>
|
||
public List<Region>? RecognizeOption(ImageRegion region, CancellationToken ct)
|
||
{
|
||
var assetScale = TaskContext.Instance().SystemInfo.AssetScale;
|
||
|
||
// 气泡识别
|
||
var chatOptionResultList = region.FindMulti(AutoSkipAssets.Instance.OptionIconRo);
|
||
if (chatOptionResultList.Count > 0)
|
||
{
|
||
// 第一个元素就是最下面的
|
||
chatOptionResultList = [.. chatOptionResultList.OrderByDescending(r => r.Y)];
|
||
|
||
// 通过最下面的气泡框来文字识别
|
||
var lowest = chatOptionResultList[0];
|
||
var ocrRect = new Rect((int)(lowest.X + lowest.Width + 8 * assetScale), region.Height / 12,
|
||
(int)(535 * assetScale), (int)(lowest.Y + lowest.Height + 30 * assetScale - region.Height / 12d));
|
||
var ocrResList = region.FindMulti(RecognitionObject.Ocr(ocrRect));
|
||
|
||
// 删除为空的结果 和 纯英文的结果
|
||
var rs = new List<Region>();
|
||
// 按照y坐标排序
|
||
ocrResList = [.. ocrResList.OrderBy(r => r.Y)];
|
||
for (var i = 0; i < ocrResList.Count; i++)
|
||
{
|
||
var item = ocrResList[i];
|
||
if (string.IsNullOrEmpty(item.Text) || (item.Text.Length < 5 && EnOrNumRegex().IsMatch(item.Text)))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (i != ocrResList.Count - 1)
|
||
{
|
||
if (ocrResList[i + 1].Y - ocrResList[i].Y > 150)
|
||
{
|
||
Debug.WriteLine($"存在Y轴偏差过大的结果,忽略:{item.Text}");
|
||
continue;
|
||
}
|
||
}
|
||
|
||
rs.Add(item);
|
||
}
|
||
|
||
return ocrResList;
|
||
}
|
||
|
||
return null; // 没有找到气泡
|
||
}
|
||
|
||
private void ClickOcrRegion(Region region)
|
||
{
|
||
region.Click();
|
||
AutoSkipLog(region.Text);
|
||
}
|
||
|
||
private void AutoSkipLog(string text)
|
||
{
|
||
if (!string.IsNullOrEmpty(text))
|
||
{
|
||
_logger.LogInformation("对话选项:{Text}", text);
|
||
}
|
||
}
|
||
|
||
private bool IsOrangeOption(Mat textMat)
|
||
{
|
||
// 只提取橙色
|
||
using var bMat = OpenCvCommonHelper.Threshold(textMat, new Scalar(200, 165, 45), new Scalar(255, 205, 55));
|
||
var whiteCount = OpenCvCommonHelper.CountGrayMatColor(bMat, 255);
|
||
var rate = whiteCount * 1.0 / (bMat.Width * bMat.Height);
|
||
Debug.WriteLine($"识别到橙色文字区域占比:{rate}");
|
||
if (rate > 0.06)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
public enum TalkOptionRes
|
||
{
|
||
// 未找到
|
||
NotFound,
|
||
|
||
// 找到但不是橙色
|
||
FoundButNotOrange,
|
||
|
||
// 找到并点击
|
||
FoundAndClick,
|
||
}
|