feat: auto lifting rod

This commit is contained in:
huiyadanli 2023-09-25 00:16:08 +08:00
parent c0704c58e2
commit 6fd51f1635
9 changed files with 280 additions and 33 deletions

View File

@ -32,6 +32,9 @@
</ItemGroup>
<ItemGroup>
<None Update="GameTask\AutoFishing\Assets\1920x1080\Space.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="GameTask\AutoSkip\Assets\1920x1080\menu.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,15 @@
using BetterGenshinImpact.Core.Config;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterGenshinImpact.GameTask.AutoFishing.Assets
{
public class AutoFishingAssets
{
public static Mat SpaceButtonMat = new(Global.Absolute(@"GameTask\AutoFishing\Assets\1920x1080\Space.png"), ImreadModes.Grayscale);
}
}

View File

@ -1,10 +1,12 @@
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Vision.Recognition;
using Vision.Recognition.Helper.OpenCv;
namespace BetterGenshinImpact.GameTask.AutoFishing
@ -18,22 +20,83 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
/// <returns></returns>
public static List<Rect>? GetFishBarRect(Mat src)
{
using var mask = new Mat();
using var rgbMat = new Mat();
Cv2.CvtColor(src, rgbMat, ColorConversionCodes.BGR2RGB);
var lowPurple = new Scalar(255, 255, 192);
var highPurple = new Scalar(255, 255, 192);
Cv2.InRange(rgbMat, lowPurple, highPurple, mask);
Cv2.Threshold(mask, mask, 0, 255, ThresholdTypes.Binary); //二值化
Cv2.FindContours(mask, out var contours, out _, RetrievalModes.External, ContourApproximationModes.ApproxSimple, null);
if (contours.Length > 0)
try
{
var boxes = contours.Select(Cv2.BoundingRect).Where(w => w.Height >= 10);
return boxes.ToList();
using var mask = new Mat();
using var rgbMat = new Mat();
Cv2.CvtColor(src, rgbMat, ColorConversionCodes.BGR2RGB);
var lowPurple = new Scalar(255, 255, 192);
var highPurple = new Scalar(255, 255, 192);
Cv2.InRange(rgbMat, lowPurple, highPurple, mask);
Cv2.Threshold(mask, mask, 0, 255, ThresholdTypes.Binary); //二值化
Cv2.FindContours(mask, out var contours, out _, RetrievalModes.External,
ContourApproximationModes.ApproxSimple, null);
if (contours.Length > 0)
{
var boxes = contours.Select(Cv2.BoundingRect).Where(w => w.Height >= 10);
return boxes.ToList();
}
}
catch (Exception e)
{
Debug.WriteLine(e);
}
return null;
}
/// <summary>
/// 匹配 “鱼儿上钩拉!”文字区域
/// </summary>
/// <param name="src"></param>
/// <param name="liftingWordsAreaRect"></param>
/// <returns></returns>
public static Rect MatchFishBiteWords(Mat src, Rect liftingWordsAreaRect)
{
try
{
Cv2.CvtColor(src, src, ColorConversionCodes.BGR2RGB);
var lowPurple = new Scalar(253, 253, 253);
var highPurple = new Scalar(255, 255, 255);
Cv2.InRange(src, lowPurple, highPurple, src);
Cv2.Threshold(src, src, 0, 255, ThresholdTypes.Binary);
var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(20, 20),
new OpenCvSharp.Point(-1, -1));
Cv2.Dilate(src, src, kernel); //膨胀
var color = new Scalar(0, 0, 255);
Cv2.FindContours(src, out var contours, out _, RetrievalModes.External,
ContourApproximationModes.ApproxSimple, null);
if (contours.Length > 0)
{
var boxes = contours.Select(Cv2.BoundingRect);
var rects = boxes.ToList();
if (rects.Count > 1)
{
rects.Sort((a, b) => b.Height.CompareTo(a.Height));
}
//VisionContext.Instance().DrawContent.PutRect("FishBiteTipsDebug",
// rects[0].ToWindowsRectangleOffset(liftingWordsAreaRect.X, liftingWordsAreaRect.Y)
// .ToRectDrawable());
if (rects[0].Height < src.Height
&& rects[0].Width * 1.0 / rects[0].Height >= 3 // 长宽比判断
&& liftingWordsAreaRect.Width > rects[0].Width * 3 // 文字范围3倍小于钓鱼条范围的
&& liftingWordsAreaRect.Width * 1.0 / 2 > rects[0].X // 中轴线判断左
&& liftingWordsAreaRect.Width * 1.0 / 2 < rects[0].X + rects[0].Width) // 中轴线判断右
{
return rects[0];
}
}
}
catch (Exception e)
{
Debug.WriteLine(e);
}
return Rect.Empty;
}
}
}
}

View File

@ -1,14 +1,19 @@
using Microsoft.Extensions.Logging;
using BetterGenshinImpact.GameTask.AutoSkip.Assets;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using BetterGenshinImpact.GameTask.AutoFishing.Assets;
using Vision.Recognition;
using Vision.Recognition.Helper.OpenCv;
using Vision.Recognition.Helper.Simulator;
using Vision.Recognition.Task;
using WindowsInput;
using static Vanara.PInvoke.User32;
using Point = OpenCvSharp.Point;
using Serilog.Core;
namespace BetterGenshinImpact.GameTask.AutoFishing
{
@ -23,7 +28,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
/// <summary>
/// 钓鱼是要独占模式的
/// 在钓鱼的时候,不应该有其他任务在执行
/// 在触发器发现正在钓鱼的时候,启用独占模式
/// 在触发器发现正在钓鱼的时候,启用独占模式(通过右下角的 Space 判断)
/// </summary>
public bool IsExclusive { get; set; }
@ -40,31 +45,73 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
public void OnCapture(CaptureContent content)
{
// TODO 进入独占的判定
if (_fishBoxRect.Width == 0)
// 进入独占的判定 通过右下角的 Space 判断
if (!IsExclusive)
{
GetFishBoxArea(content.SrcMat);
if (!content.IsReachInterval(TimeSpan.FromSeconds(1)))
{
return;
}
// 找右下角的 Space 按钮
IsExclusive = FindSpaceButtonForExclusive(content);
if (IsExclusive)
{
_logger.LogInformation("进入钓鱼界面");
_fishBoxRect = new(0, 0, 0, 0);
}
}
else
{
Fishing(content, new Mat(content.SrcMat, _fishBoxRect));
// 进入钓鱼界面先尝试获取钓鱼框的位置
if (_fishBoxRect.Width == 0)
{
if (!content.IsReachInterval(TimeSpan.FromMilliseconds(200)))
{
return;
}
_fishBoxRect = GetFishBoxArea(content.SrcMat);
}
else
{
// 上钩判断
FishBite(content, _fishBoxRect);
// 钓鱼拉条
Fishing(content, new Mat(content.SrcMat, _fishBoxRect));
}
}
}
/// <summary>
/// 找右下角的 Space 按钮
/// 用于判断是否进入钓鱼界面
/// 进入钓鱼界面时该触发器进入独占模式
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
private bool FindSpaceButtonForExclusive(CaptureContent content)
{
var grayMat = content.SrcGreyMat;
var grayRightBottomMat = CutHelper.CutRightBottom(grayMat, grayMat.Width / 3, grayMat.Height / 5);
var p = MatchTemplateHelper.FindSingleTarget(grayRightBottomMat, AutoFishingAssets.SpaceButtonMat);
return p is { X: > 0, Y: > 0 };
}
/// <summary>
/// 获取钓鱼框的位置
/// </summary>
private void GetFishBoxArea(Mat srcMat)
private Rect GetFishBoxArea(Mat srcMat)
{
srcMat = CutHelper.CutTop(srcMat, srcMat.Height / 2);
var rects = AutoFishingImageRecognition.GetFishBarRect(srcMat);
if (rects != null && rects.Count == 2)
{
if (Math.Abs(rects[0].Height - rects[1].Height) > 10)
{
Debug.WriteLine("两个矩形高度差距过大,未识别到钓鱼框");
return;
return Rect.Empty;
}
if (rects[0].Width < rects[1].Width)
@ -78,10 +125,14 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
_left = rects[0];
}
// cur 是游标位置, 在初始状态下cur 一定在left左边
if (_left.X < _cur.X)
if (_left.X < _cur.X // cur 是游标位置, 在初始状态下cur 一定在left左边
|| _cur.Width > _left.Width // left一定比cur宽
|| _cur.X + _cur.Width > srcMat.Width / 2 // cur 一定在屏幕左侧
|| !(_left.X < srcMat.Width / 2 && _left.X + _left.Width > srcMat.Width / 2) // left肯定穿过游戏中轴线
)
{
return;
return Rect.Empty;
}
int hExtra = _cur.Height, vExtra = _cur.Height / 4;
@ -89,12 +140,77 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
(_left.X + _left.Width / 2 - _cur.X) * 2 + hExtra * 2, _cur.Height + vExtra * 2);
VisionContext.Instance().DrawContent
.PutRect("FishBox", _fishBoxRect.ToRectDrawable(new Pen(Color.LightPink, 2)));
return _fishBoxRect;
}
VisionContext.Instance().DrawContent.RemoveRect("FishBox");
return Rect.Empty;
}
private bool _isFishingProcess = false; // 提杆后会设置为true
int _biteTipsExitCount = 0; // 钓鱼提示持续时间
int _notFishingAfterBiteCount = 0; // 提竿后没有钓鱼的时间
Rect _baseBiteTips = Rect.Empty;
/// <summary>
/// 自动提竿
/// </summary>
/// <param name="content"></param>
/// <param name="fishBoxRect"></param>
private void FishBite(CaptureContent content, Rect fishBoxRect)
{
if (_isFishingProcess || fishBoxRect == Rect.Empty)
{
return;
}
// 自动识别的钓鱼框向下延伸到屏幕中间
var liftingWordsAreaRect = new Rect(fishBoxRect.X, fishBoxRect.Y + fishBoxRect.Height * 2,
fishBoxRect.Width, content.SrcMat.Height / 2 - fishBoxRect.Y - fishBoxRect.Height * 5);
//VisionContext.Instance().DrawContent.PutRect("liftingWordsAreaRect", liftingWordsAreaRect.ToRectDrawable(new Pen(Color.Cyan, 2)));
var currentBiteWordsTips =
AutoFishingImageRecognition.MatchFishBiteWords(new Mat(content.SrcMat, liftingWordsAreaRect),
liftingWordsAreaRect);
if (currentBiteWordsTips != Rect.Empty)
{
if (_baseBiteTips == Rect.Empty)
{
_baseBiteTips = currentBiteWordsTips;
}
else
{
if (Math.Abs(_baseBiteTips.X - currentBiteWordsTips.X) < 10
&& Math.Abs(_baseBiteTips.Y - currentBiteWordsTips.Y) < 10
&& Math.Abs(_baseBiteTips.Width - currentBiteWordsTips.Width) < 10
&& Math.Abs(_baseBiteTips.Height - currentBiteWordsTips.Height) < 10)
{
_biteTipsExitCount++;
VisionContext.Instance().DrawContent.PutRect("FishBiteTips",
currentBiteWordsTips
.ToWindowsRectangleOffset(liftingWordsAreaRect.X, liftingWordsAreaRect.Y)
.ToRectDrawable());
}
else
{
_biteTipsExitCount = 0;
_baseBiteTips = currentBiteWordsTips;
}
if (_biteTipsExitCount >= content.FrameRate / 2d)
{
new InputSimulator().Mouse.LeftButtonClick();
_logger.LogInformation(@"┌------------------------┐");
_logger.LogInformation(" 自动提竿");
_isFishingProcess = true;
_biteTipsExitCount = 0;
_baseBiteTips = Rect.Empty;
VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips");
}
}
}
}
private int _noRectsCount = 0;
private bool _isFishingProcess = false; // 提杆后会设置为true
private Rect _cur, _left, _right;
private MOUSEEVENTF _prevMouseEvent = 0x0;
private bool _findFishBoxTips;
@ -145,6 +261,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
if (_prevMouseEvent != MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN)
{
simulator.Mouse.LeftButtonDown();
//Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown();
_prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN;
//Debug.WriteLine("进度不到 左键按下");
}
@ -154,6 +271,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
if (_prevMouseEvent == MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN)
{
simulator.Mouse.LeftButtonUp();
//Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonUp();
_prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP;
//Debug.WriteLine("进度超出 左键松开");
}
@ -172,6 +290,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
if (_prevMouseEvent == MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN)
{
simulator.Mouse.LeftButtonUp();
//Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonUp();
_prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP;
//Debug.WriteLine("进入框内中间 左键松开");
}
@ -181,6 +300,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
if (_prevMouseEvent != MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN)
{
simulator.Mouse.LeftButtonDown();
//Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown();
_prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN;
//Debug.WriteLine("未到框内中间 左键按下");
}
@ -202,8 +322,32 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
_isFishingProcess = false;
_prevMouseEvent = 0x0;
_logger.LogInformation(" 钓鱼结束");
//_logger.LogInformation(@"└------------------------┘");
_logger.LogInformation(@"└------------------------┘");
}
IsExclusive = FindSpaceButtonForExclusive(content);
if (!IsExclusive)
{
_logger.LogInformation("退出钓鱼界面");
//_fishBoxRect = new(0, 0, 0, 0);
}
}
// 提竿后没有钓鱼的情况
if (_isFishingProcess && !_findFishBoxTips)
{
_notFishingAfterBiteCount++;
if (_notFishingAfterBiteCount >= decimal.ToDouble(content.FrameRate) * 2)
{
_isFishingProcess = false;
_notFishingAfterBiteCount = 0;
_logger.LogInformation(" X 提竿后没有钓鱼,重置!");
_logger.LogInformation(@"└------------------------┘");
}
}
else
{
_notFishingAfterBiteCount = 0;
}
}

View File

@ -32,7 +32,7 @@ namespace BetterGenshinImpact.GameTask.AutoSkip
public void OnCapture(CaptureContent content)
{
if (content.FrameIndex % 2 == 0)
if (content.IsReachInterval(TimeSpan.FromMilliseconds(200)))
{
return;
}

View File

@ -32,14 +32,16 @@ namespace BetterGenshinImpact.GameTask
private int _frameRate = 30;
public TaskDispatcher()
{
_triggers = GameTaskManager.LoadTriggers();
_timer.Elapsed += Tick;
//_timer.Tick += Tick;
}
public void Start(CaptureMode mode, int frameRate = 60)
public void Start(CaptureMode mode, int frameRate = 30)
{
IntPtr hWnd = SystemControl.FindGenshinImpactHandle();
if (hWnd == IntPtr.Zero)
@ -81,8 +83,8 @@ namespace BetterGenshinImpact.GameTask
return;
}
// 帧序号自增 1分钟后归零
_frameIndex = (_frameIndex + 1) % (_frameRate * 60);
// 帧序号自增 1分钟后归零(MaxFrameIndexSecond)
_frameIndex = (_frameIndex + 1) % (_frameRate * CaptureContent.MaxFrameIndexSecond);
// 捕获游戏画面
//var sw = new Stopwatch();

View File

@ -68,7 +68,7 @@ namespace Vision.Recognition
protected override void OnRender(DrawingContext drawingContext)
{
Logger?.LogInformation("绘制识别结果");
//Logger?.LogInformation("绘制识别结果");
try
{
foreach (var kv in VisionContext.Instance().DrawContent.RectList)

View File

@ -15,6 +15,8 @@ namespace Vision.Recognition.Task
/// </summary>
public class CaptureContent
{
public static readonly int MaxFrameIndexSecond = 60;
public Bitmap SrcBitmap { get; }
public int FrameIndex { get; private set; }
public int FrameRate { get; private set; }
@ -27,6 +29,7 @@ namespace Vision.Recognition.Task
}
private Mat? _srcMat;
public Mat SrcMat
{
get
@ -37,6 +40,7 @@ namespace Vision.Recognition.Task
}
private Mat? _srcGreyMat;
public Mat SrcGreyMat
{
get
@ -46,5 +50,21 @@ namespace Vision.Recognition.Task
return _srcGreyMat;
}
}
/// <summary>
/// 达到了什么时间间隔
/// 最大MaxFrameIndexSecond秒
/// </summary>
/// <param name="interval"></param>
/// <returns></returns>
public bool IsReachInterval(TimeSpan interval)
{
if (interval.TotalSeconds > MaxFrameIndexSecond)
{
throw new ArgumentException($"时间间隔不能超过{MaxFrameIndexSecond}s");
}
return FrameIndex % (FrameRate * interval.TotalSeconds) == 0;
}
}
}