AutoGeniusInvokation: migrate code
@@ -71,6 +71,108 @@
|
||||
<None Update="GameTask\AutoFishing\Assets\1920x1080\wait_bite.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\action_anemo.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\action_cryo.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\action_dendro.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\action_electro.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\action_geo.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\action_hydro.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\action_omni.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\action_pyro.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\roll_anemo.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\roll_cryo.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\roll_dendro.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\roll_electro.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\roll_geo.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\roll_hydro.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\roll_omni.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\dice\roll_pyro.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\元素调和.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\元素骰子不足.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\冻结.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\出战角色.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\回合结束.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\回合结算阶段.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\对方行动中.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\满能量.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\确定.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\确定_1600x900.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\空能量.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\角色死亡.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\角色状态_冻结.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\角色状态_冻结2.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\角色状态_水泡.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\角色血量上方.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\角色被打败.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\other\退出挑战.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoPick\Assets\1920x1080\F.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
@@ -4,6 +4,9 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Drawing;
|
||||
using OpenCvSharp;
|
||||
using Vanara.PInvoke;
|
||||
using Point = System.Drawing.Point;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Recognition.OpenCv
|
||||
{
|
||||
@@ -55,6 +58,27 @@ namespace BetterGenshinImpact.Core.Recognition.OpenCv
|
||||
}
|
||||
|
||||
|
||||
public static OpenCvSharp.Point GetCenterPoint(this RECT rectangle)
|
||||
{
|
||||
if (rectangle.IsEmpty)
|
||||
{
|
||||
throw new ArgumentException("rectangle is empty");
|
||||
}
|
||||
|
||||
return new OpenCvSharp.Point(rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2);
|
||||
}
|
||||
|
||||
public static OpenCvSharp.Point GetCenterPoint(this Rect rectangle)
|
||||
{
|
||||
if (rectangle == Rect.Empty)
|
||||
{
|
||||
throw new ArgumentException("rectangle is empty");
|
||||
}
|
||||
|
||||
return new OpenCvSharp.Point(rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2);
|
||||
}
|
||||
|
||||
|
||||
public static System.Windows.Media.Color ToWindowsColor(this Color color)
|
||||
{
|
||||
return System.Windows.Media.Color.FromArgb(color.A, color.R, color.G, color.B);
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
using OpenCvSharp;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenCvSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Point = OpenCvSharp.Point;
|
||||
using BetterGenshinImpact.GameTask.AutoSkip;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Recognition.OpenCv
|
||||
{
|
||||
@@ -17,12 +12,13 @@ namespace BetterGenshinImpact.Core.Recognition.OpenCv
|
||||
|
||||
/// <summary>
|
||||
/// 模板匹配
|
||||
/// TODO 算法不一样的的时候找点的方法也不一样
|
||||
/// </summary>
|
||||
/// <param name="srcMat"></param>
|
||||
/// <param name="dstMat"></param>
|
||||
/// <param name="matchMode"></param>
|
||||
/// <param name="maskMat"></param>
|
||||
/// <param name="threshold"></param>
|
||||
/// <param name="srcMat">原图像</param>
|
||||
/// <param name="dstMat">模板</param>
|
||||
/// <param name="matchMode">匹配方式</param>
|
||||
/// <param name="maskMat">遮罩</param>
|
||||
/// <param name="threshold">阈值</param>
|
||||
/// <returns>左上角的标点</returns>
|
||||
public static Point MatchTemplate(Mat srcMat, Mat dstMat, TemplateMatchModes matchMode, Mat? maskMat = null, double threshold = 0.8)
|
||||
{
|
||||
@@ -53,5 +49,60 @@ namespace BetterGenshinImpact.Core.Recognition.OpenCv
|
||||
return new Point();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模板匹配多个结果
|
||||
/// </summary>
|
||||
/// <param name="srcMat"></param>
|
||||
/// <param name="dstMat"></param>
|
||||
/// <param name="matchMode"></param>
|
||||
/// <param name="maskMat"></param>
|
||||
/// <param name="threshold"></param>
|
||||
/// <returns></returns>
|
||||
public static List<Point> MatchTemplateMulti(Mat srcMat, Mat dstMat, TemplateMatchModes matchMode = TemplateMatchModes.CCoeffNormed, Mat? maskMat = null, double threshold = 0.8)
|
||||
{
|
||||
var points = new List<Point>();
|
||||
try
|
||||
{
|
||||
using var result = new Mat();
|
||||
if (maskMat == null)
|
||||
{
|
||||
Cv2.MatchTemplate(srcMat, dstMat, result, matchMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Cv2.MatchTemplate(srcMat, dstMat, result, matchMode, maskMat);
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
Cv2.MinMaxLoc(result, out _, out var maxValue, out _, out var maxLoc);
|
||||
|
||||
if (maxValue >= threshold)
|
||||
{
|
||||
points.Add(new Point(maxLoc.X, maxLoc.Y));
|
||||
|
||||
//Fill in the res Mat so you don't find the same area again in the MinMaxLoc
|
||||
Cv2.FloodFill(result, maxLoc, new Scalar(0), out _, new Scalar(0.1), new Scalar(1.0));
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("{Ex}", ex);
|
||||
return points;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Point> MatchTemplateMulti(Mat srcMat, Mat dstMat, double threshold)
|
||||
{
|
||||
return MatchTemplateMulti(srcMat, dstMat, TemplateMatchModes.CCoeffNormed, null, threshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,177 +6,177 @@ using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using Point = OpenCvSharp.Point;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Recognition.OpenCv
|
||||
namespace BetterGenshinImpact.Core.Recognition.OpenCv;
|
||||
|
||||
[Obsolete]
|
||||
public class OldMatchTemplateHelper
|
||||
{
|
||||
public class OldMatchTemplateHelper
|
||||
{
|
||||
private static readonly ILogger<OldMatchTemplateHelper> _logger = App.GetLogger<OldMatchTemplateHelper>();
|
||||
private static readonly ILogger<OldMatchTemplateHelper> _logger = App.GetLogger<OldMatchTemplateHelper>();
|
||||
|
||||
public static double WidthScale = 1;
|
||||
public static double HeightScale = 1;
|
||||
public static double WidthScale = 1;
|
||||
public static double HeightScale = 1;
|
||||
|
||||
|
||||
public static Point FindSingleTarget(Bitmap imgSrc, Bitmap imgSub, double threshold = 0.8)
|
||||
{
|
||||
Mat? srcMat = null;
|
||||
Mat? dstMat = null;
|
||||
try
|
||||
{
|
||||
srcMat = imgSrc.ToMat();
|
||||
dstMat = imgSub.ToMat();
|
||||
return FindSingleTarget(srcMat, dstMat, threshold);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.ToString());
|
||||
return new Point();
|
||||
}
|
||||
finally
|
||||
{
|
||||
srcMat?.Dispose();
|
||||
dstMat?.Dispose();
|
||||
}
|
||||
}
|
||||
//public static Point FindSingleTarget(Bitmap imgSrc, Bitmap imgSub, double threshold = 0.8)
|
||||
//{
|
||||
// Mat? srcMat = null;
|
||||
// Mat? dstMat = null;
|
||||
// try
|
||||
// {
|
||||
// srcMat = imgSrc.ToMat();
|
||||
// dstMat = imgSub.ToMat();
|
||||
// return FindSingleTarget(srcMat, dstMat, threshold);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// _logger.LogError(ex.ToString());
|
||||
// return new Point();
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// srcMat?.Dispose();
|
||||
// dstMat?.Dispose();
|
||||
// }
|
||||
//}
|
||||
|
||||
public static Point FindSingleTarget(Mat srcMat, Mat dstMat, double threshold = 0.8)
|
||||
{
|
||||
Point p = new Point();
|
||||
//public static Point FindSingleTarget(Mat srcMat, Mat dstMat, double threshold = 0.8)
|
||||
//{
|
||||
// Point p = new Point();
|
||||
|
||||
OutputArray? outArray = null;
|
||||
try
|
||||
{
|
||||
dstMat = ResizeHelper.Resize(dstMat, WidthScale);
|
||||
// OutputArray? outArray = null;
|
||||
// try
|
||||
// {
|
||||
// dstMat = ResizeHelper.Resize(dstMat, WidthScale);
|
||||
|
||||
outArray = OutputArray.Create(srcMat);
|
||||
Cv2.MatchTemplate(srcMat, dstMat, outArray, TemplateMatchModes.CCoeffNormed);
|
||||
double minValue, maxValue;
|
||||
Point location, point;
|
||||
Cv2.MinMaxLoc(InputArray.Create(outArray.GetMat()), out minValue, out maxValue,
|
||||
out location, out point);
|
||||
// outArray = OutputArray.Create(srcMat);
|
||||
// Cv2.MatchTemplate(srcMat, dstMat, outArray, TemplateMatchModes.CCoeffNormed);
|
||||
// double minValue, maxValue;
|
||||
// Point location, point;
|
||||
// Cv2.MinMaxLoc(InputArray.Create(outArray.GetMat()), out minValue, out maxValue,
|
||||
// out location, out point);
|
||||
|
||||
if (maxValue >= threshold)
|
||||
{
|
||||
p = new Point(point.X + dstMat.Width / 2, point.Y + dstMat.Height / 2);
|
||||
//if (VisionContext.Instance().Drawable)
|
||||
//{
|
||||
//VisionContext.Instance().DrawContent.PutRect("", new System.Windows.Rect(point.X, point.Y, dstMat.Width, dstMat.Height));
|
||||
//VisionContext.Instance().DrawContent.TextList
|
||||
// .Add(new Tuple<System.Windows.Point, string>(new System.Windows.Point(point.X, point.Y - 10), maxValue.ToString("0.00")));
|
||||
//}
|
||||
}
|
||||
// if (maxValue >= threshold)
|
||||
// {
|
||||
// p = new Point(point.X + dstMat.Width / 2, point.Y + dstMat.Height / 2);
|
||||
// if (VisionContext.Instance().Drawable)
|
||||
// {
|
||||
// VisionContext.Instance().DrawContent.PutRect("", new System.Windows.Rect(point.X, point.Y, dstMat.Width, dstMat.Height));
|
||||
// VisionContext.Instance().DrawContent.TextList
|
||||
// .Add(new Tuple<System.Windows.Point, string>(new System.Windows.Point(point.X, point.Y - 10), maxValue.ToString("0.00")));
|
||||
// }
|
||||
// }
|
||||
|
||||
return p;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.ToString());
|
||||
return p;
|
||||
}
|
||||
finally
|
||||
{
|
||||
outArray?.Dispose();
|
||||
}
|
||||
}
|
||||
// return p;
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// _logger.LogError(ex.ToString());
|
||||
// return p;
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// outArray?.Dispose();
|
||||
// }
|
||||
//}
|
||||
|
||||
public static List<Point> FindMultiTarget(Mat srcMat, Mat dstMat, string title, out Mat resMat,
|
||||
double threshold = 0.8, int findTargetCount = 8)
|
||||
{
|
||||
List<Point> pointList = new List<Point>();
|
||||
resMat = srcMat.Clone();
|
||||
try
|
||||
{
|
||||
dstMat = ResizeHelper.Resize(dstMat, WidthScale);
|
||||
//public static List<Point> FindMultiTarget(Mat srcMat, Mat dstMat, string title, out Mat resMat,
|
||||
// double threshold = 0.8, int findTargetCount = 8)
|
||||
//{
|
||||
// List<Point> pointList = new List<Point>();
|
||||
// resMat = srcMat.Clone();
|
||||
// try
|
||||
// {
|
||||
// dstMat = ResizeHelper.Resize(dstMat, WidthScale);
|
||||
|
||||
Mat matchResult = new Mat();
|
||||
Cv2.MatchTemplate(srcMat, dstMat, matchResult, TemplateMatchModes.CCoeffNormed);
|
||||
// Mat matchResult = new Mat();
|
||||
// Cv2.MatchTemplate(srcMat, dstMat, matchResult, TemplateMatchModes.CCoeffNormed);
|
||||
|
||||
double minValue = 0;
|
||||
double maxValue = 0;
|
||||
Point minLoc = new();
|
||||
// double minValue = 0;
|
||||
// double maxValue = 0;
|
||||
// Point minLoc = new();
|
||||
|
||||
//寻找最几个最值的位置
|
||||
Mat mask = new Mat(matchResult.Height, matchResult.Width, MatType.CV_8UC1, Scalar.White);
|
||||
Mat maskSub = new Mat(matchResult.Height, matchResult.Width, MatType.CV_8UC1, Scalar.Black);
|
||||
var point = new OpenCvSharp.Point(0, 0);
|
||||
for (int i = 0; i < findTargetCount; i++)
|
||||
{
|
||||
Cv2.MinMaxLoc(matchResult, out minValue, out maxValue, out minLoc, out point, mask);
|
||||
Rect maskRect = new Rect(point.X - dstMat.Width / 2, point.Y - dstMat.Height / 2, dstMat.Width,
|
||||
dstMat.Height);
|
||||
maskSub.Rectangle(maskRect, Scalar.White, -1);
|
||||
mask -= maskSub;
|
||||
if (maxValue >= threshold)
|
||||
{
|
||||
pointList.Add(new Point(point.X + dstMat.Width / 2, point.Y + dstMat.Height / 2));
|
||||
// //寻找最几个最值的位置
|
||||
// Mat mask = new Mat(matchResult.Height, matchResult.Width, MatType.CV_8UC1, Scalar.White);
|
||||
// Mat maskSub = new Mat(matchResult.Height, matchResult.Width, MatType.CV_8UC1, Scalar.Black);
|
||||
// var point = new OpenCvSharp.Point(0, 0);
|
||||
// for (int i = 0; i < findTargetCount; i++)
|
||||
// {
|
||||
// Cv2.MinMaxLoc(matchResult, out minValue, out maxValue, out minLoc, out point, mask);
|
||||
// Rect maskRect = new Rect(point.X - dstMat.Width / 2, point.Y - dstMat.Height / 2, dstMat.Width,
|
||||
// dstMat.Height);
|
||||
// maskSub.Rectangle(maskRect, Scalar.White, -1);
|
||||
// mask -= maskSub;
|
||||
// if (maxValue >= threshold)
|
||||
// {
|
||||
// pointList.Add(new Point(point.X + dstMat.Width / 2, point.Y + dstMat.Height / 2));
|
||||
|
||||
//if (VisionContext.Instance().Drawable)
|
||||
//{
|
||||
// VisionContext.Instance().DrawContent.RectList
|
||||
// .Add(new System.Windows.Rect(point.X, point.Y, dstMat.Width, dstMat.Height));
|
||||
// VisionContext.Instance().DrawContent.TextList
|
||||
// .Add(new Tuple<System.Windows.Point, string>(new System.Windows.Point(point.X, point.Y - 10), maxValue.ToString("0.00")));
|
||||
//}
|
||||
//if (IsDebug)
|
||||
//{
|
||||
// VisionContext.Instance().Log
|
||||
// ?.LogInformation(title + " " + maxValue.ToString("0.000") + " " + point);
|
||||
// Cv2.Rectangle(resMat, point,
|
||||
// new OpenCvSharp.Point(point.X + dstMat.Width, point.Y + dstMat.Height),
|
||||
// Scalar.Red, 2);
|
||||
// Cv2.PutText(resMat, title + " " + maxValue.ToString("0.00"),
|
||||
// new OpenCvSharp.Point(point.X, point.Y - 10),
|
||||
// HersheyFonts.HersheySimplex, 0.5, Scalar.Red);
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// //if (VisionContext.Instance().Drawable)
|
||||
// //{
|
||||
// // VisionContext.Instance().DrawContent.RectList
|
||||
// // .Add(new System.Windows.Rect(point.X, point.Y, dstMat.Width, dstMat.Height));
|
||||
// // VisionContext.Instance().DrawContent.TextList
|
||||
// // .Add(new Tuple<System.Windows.Point, string>(new System.Windows.Point(point.X, point.Y - 10), maxValue.ToString("0.00")));
|
||||
// //}
|
||||
// //if (IsDebug)
|
||||
// //{
|
||||
// // VisionContext.Instance().Log
|
||||
// // ?.LogInformation(title + " " + maxValue.ToString("0.000") + " " + point);
|
||||
// // Cv2.Rectangle(resMat, point,
|
||||
// // new OpenCvSharp.Point(point.X + dstMat.Width, point.Y + dstMat.Height),
|
||||
// // Scalar.Red, 2);
|
||||
// // Cv2.PutText(resMat, title + " " + maxValue.ToString("0.00"),
|
||||
// // new OpenCvSharp.Point(point.X, point.Y - 10),
|
||||
// // HersheyFonts.HersheySimplex, 0.5, Scalar.Red);
|
||||
// //}
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
return pointList;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.ToString());
|
||||
return pointList;
|
||||
}
|
||||
finally
|
||||
{
|
||||
srcMat?.Dispose();
|
||||
dstMat?.Dispose();
|
||||
}
|
||||
}
|
||||
// return pointList;
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// _logger.LogError(ex.ToString());
|
||||
// return pointList;
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// srcMat?.Dispose();
|
||||
// dstMat?.Dispose();
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
public static Dictionary<string, List<Point>> FindMultiPicFromOneImage(Bitmap imgSrc,
|
||||
Dictionary<string, Bitmap> imgSubDictionary, double threshold = 0.8)
|
||||
{
|
||||
Dictionary<string, List<Point>> dictionary = new Dictionary<string, List<Point>>();
|
||||
Mat srcMat = imgSrc.ToMat();
|
||||
Mat resMat;
|
||||
//public static Dictionary<string, List<Point>> FindMultiPicFromOneImage(Bitmap imgSrc,
|
||||
// Dictionary<string, Bitmap> imgSubDictionary, double threshold = 0.8)
|
||||
//{
|
||||
// Dictionary<string, List<Point>> dictionary = new Dictionary<string, List<Point>>();
|
||||
// Mat srcMat = imgSrc.ToMat();
|
||||
// Mat resMat;
|
||||
|
||||
foreach (KeyValuePair<string, Bitmap> kvp in imgSubDictionary)
|
||||
{
|
||||
dictionary.Add(kvp.Key, FindMultiTarget(srcMat, kvp.Value.ToMat(), kvp.Key, out resMat, threshold));
|
||||
srcMat = resMat.Clone();
|
||||
}
|
||||
// foreach (KeyValuePair<string, Bitmap> kvp in imgSubDictionary)
|
||||
// {
|
||||
// dictionary.Add(kvp.Key, FindMultiTarget(srcMat, kvp.Value.ToMat(), kvp.Key, out resMat, threshold));
|
||||
// srcMat = resMat.Clone();
|
||||
// }
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
// return dictionary;
|
||||
//}
|
||||
|
||||
public static Dictionary<string, List<Point>> FindMultiPicFromOneImage(Mat srcMat,
|
||||
Dictionary<string, Bitmap> imgSubDictionary, double threshold = 0.8)
|
||||
{
|
||||
Dictionary<string, List<Point>> dictionary = new Dictionary<string, List<Point>>();
|
||||
Mat resMat;
|
||||
foreach (KeyValuePair<string, Bitmap> kvp in imgSubDictionary)
|
||||
{
|
||||
dictionary.Add(kvp.Key, FindMultiTarget(srcMat, kvp.Value.ToMat(), kvp.Key, out resMat, threshold));
|
||||
srcMat = resMat.Clone();
|
||||
}
|
||||
//public static Dictionary<string, List<Point>> FindMultiPicFromOneImage(Mat srcMat,
|
||||
// Dictionary<string, Bitmap> imgSubDictionary, double threshold = 0.8)
|
||||
//{
|
||||
// Dictionary<string, List<Point>> dictionary = new Dictionary<string, List<Point>>();
|
||||
// Mat resMat;
|
||||
// foreach (KeyValuePair<string, Bitmap> kvp in imgSubDictionary)
|
||||
// {
|
||||
// dictionary.Add(kvp.Key, FindMultiTarget(srcMat, kvp.Value.ToMat(), kvp.Key, out resMat, threshold));
|
||||
// srcMat = resMat.Clone();
|
||||
// }
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
}
|
||||
// return dictionary;
|
||||
//}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 914 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 835 B |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 875 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 550 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,154 @@
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using OpenCvSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.Core.Recognition;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Assets
|
||||
{
|
||||
public class AutoGeniusInvokationAssets
|
||||
{
|
||||
public RecognitionObject ConfirmButtonRo;
|
||||
public RecognitionObject RoundEndButtonRo;
|
||||
public RecognitionObject ElementalTuningConfirmButtonRo;
|
||||
public RecognitionObject ExitDuelButtonRo;
|
||||
|
||||
public RecognitionObject InOpponentActionRo;
|
||||
public RecognitionObject EndPhaseRo;
|
||||
public RecognitionObject ElementalDiceLackWarningRo;
|
||||
public RecognitionObject CharacterTakenOutRo;
|
||||
public Mat CharacterDefeatedMat;
|
||||
public RecognitionObject InCharacterPickRo;
|
||||
|
||||
// 角色区域
|
||||
public RecognitionObject CharacterHpUpperRo;
|
||||
public Mat CharacterStatusFreezeMat;
|
||||
public Mat CharacterStatusDizzinessMat;
|
||||
public Mat CharacterEnergyOnMat;
|
||||
|
||||
public Dictionary<string, Mat> RollPhaseDiceMats;
|
||||
public Dictionary<string, Mat> ActionPhaseDiceMats;
|
||||
|
||||
public AutoGeniusInvokationAssets()
|
||||
{
|
||||
var info = TaskContext.Instance().SystemInfo;
|
||||
ConfirmButtonRo = new RecognitionObject
|
||||
{
|
||||
Name = "ConfirmButton",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\确定.png"),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
RoundEndButtonRo = new RecognitionObject
|
||||
{
|
||||
Name = "RoundEndButton",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\回合结束.png"),
|
||||
RegionOfInterest = new Rect(0, 0, info.CaptureAreaRect.Width / 5, info.CaptureAreaRect.Height),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
ElementalTuningConfirmButtonRo = new RecognitionObject
|
||||
{
|
||||
Name = "ElementalTuningConfirmButton",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\元素调和.png"),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
ExitDuelButtonRo = new RecognitionObject
|
||||
{
|
||||
Name = "ExitDuelButton",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\退出挑战.png"),
|
||||
RegionOfInterest = new Rect(0, info.CaptureAreaRect.Height / 2, info.CaptureAreaRect.Width / 2, info.CaptureAreaRect.Height - info.CaptureAreaRect.Height / 2),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
InOpponentActionRo = new RecognitionObject
|
||||
{
|
||||
Name = "InOpponentAction",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\对方行动中.png"),
|
||||
RegionOfInterest = new Rect(0, 0, info.CaptureAreaRect.Width / 5, info.CaptureAreaRect.Height),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
EndPhaseRo = new RecognitionObject
|
||||
{
|
||||
Name = "EndPhase",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\回合结算阶段.png"),
|
||||
RegionOfInterest = new Rect(0, 0, info.CaptureAreaRect.Width / 5, info.CaptureAreaRect.Height),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
ElementalDiceLackWarningRo = new RecognitionObject
|
||||
{
|
||||
Name = "ElementalDiceLackWarning",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\元素骰子不足.png"),
|
||||
RegionOfInterest = new Rect(info.CaptureAreaRect.Width - info.CaptureAreaRect.Width / 2, 0,
|
||||
info.CaptureAreaRect.Width / 2, info.CaptureAreaRect.Height),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
CharacterTakenOutRo = new RecognitionObject
|
||||
{
|
||||
Name = "CharacterTakenOut",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\角色死亡.png"),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
|
||||
|
||||
CharacterDefeatedMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\角色被打败.png");
|
||||
|
||||
InCharacterPickRo = new RecognitionObject
|
||||
{
|
||||
Name = "InCharacterPick",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\出战角色.png"),
|
||||
RegionOfInterest = new Rect(info.CaptureAreaRect.Width / 2, info.CaptureAreaRect.Height / 2,
|
||||
info.CaptureAreaRect.Width - info.CaptureAreaRect.Width / 2,
|
||||
info.CaptureAreaRect.Height - info.CaptureAreaRect.Height / 2),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
CharacterHpUpperRo = new RecognitionObject
|
||||
{
|
||||
Name = "CharacterHpUpper",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\角色血量上方.png"),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
|
||||
|
||||
CharacterStatusFreezeMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\角色状态_冻结.png");
|
||||
CharacterStatusDizzinessMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\角色状态_水泡.png");
|
||||
CharacterEnergyOnMat = GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"other\满能量.png");
|
||||
|
||||
// 投掷期间的骰子
|
||||
RollPhaseDiceMats = new Dictionary<string, Mat>()
|
||||
{
|
||||
{ "anemo", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\roll_anemo.png") },
|
||||
{ "geo", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\roll_geo.png.png") },
|
||||
{ "electro", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\roll_electro.png.png") },
|
||||
{ "dendro", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\roll_dendro.png.png") },
|
||||
{ "hydro", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\roll_hydro.png.png") },
|
||||
{ "pyro", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\roll_pyro.png.png") },
|
||||
{ "cryo", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\roll_cryo.png.png") },
|
||||
{ "omni", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\roll_omni.png.png") },
|
||||
};
|
||||
|
||||
// 主界面骰子
|
||||
ActionPhaseDiceMats = new Dictionary<string, Mat>()
|
||||
{
|
||||
{ "anemo", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\action_anemo.png.png") },
|
||||
{ "geo", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\action_geo.png.png") },
|
||||
{ "electro", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\action_electro.png.png") },
|
||||
{ "dendro", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\action_dendro.png.png") },
|
||||
{ "hydro", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\action_hydro.png.png") },
|
||||
{ "pyro", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\action_pyro.png.png") },
|
||||
{ "cryo", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\action_cryo.png.png") },
|
||||
{ "omni", GameTaskManager.LoadAssertImage("AutoGeniusInvokation", @"dice\action_omni.png.png") },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
|
||||
|
||||
public class DuelEndException: System.Exception
|
||||
{
|
||||
public DuelEndException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
|
||||
|
||||
public class RetryException : System.Exception
|
||||
{
|
||||
public RetryException() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public RetryException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model
|
||||
{
|
||||
public class ActionCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public Character Character { get; set; }
|
||||
|
||||
public ActionEnum Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标编号(技能编号,从右往左)
|
||||
/// </summary>
|
||||
public int TargetIndex { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Action == ActionEnum.UseSkill)
|
||||
{
|
||||
return $"【{Character.Name}】使用【技能{TargetIndex}】";
|
||||
}
|
||||
else if (Action == ActionEnum.SwitchLater)
|
||||
{
|
||||
return $"【{Character.Name}】切换至【角色{TargetIndex}】";
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int GetSpecificElementDiceUseCount()
|
||||
{
|
||||
if (Action == ActionEnum.UseSkill)
|
||||
{
|
||||
return Character.Skills[TargetIndex].SpecificElementCost;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("未知行动");
|
||||
}
|
||||
}
|
||||
|
||||
public int GetAllDiceUseCount()
|
||||
{
|
||||
if (Action == ActionEnum.UseSkill)
|
||||
{
|
||||
return Character.Skills[TargetIndex].AllCost;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("未知行动");
|
||||
}
|
||||
}
|
||||
|
||||
public ElementalType GetDiceUseElementType()
|
||||
{
|
||||
if (Action == ActionEnum.UseSkill)
|
||||
{
|
||||
return Character.Element;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("未知行动");
|
||||
}
|
||||
}
|
||||
|
||||
public bool SwitchLater()
|
||||
{
|
||||
return Character.SwitchLater();
|
||||
}
|
||||
|
||||
public bool UseSkill(Duel duel)
|
||||
{
|
||||
return Character.UseSkill(TargetIndex, duel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model
|
||||
{
|
||||
public enum ActionEnum
|
||||
{
|
||||
ChooseFirst, SwitchLater, UseSkill
|
||||
}
|
||||
|
||||
public static class ActionEnumExtension
|
||||
{
|
||||
public static ActionEnum ChineseToActionEnum(this string type)
|
||||
{
|
||||
type = type.ToLower();
|
||||
switch (type)
|
||||
{
|
||||
case "出战":
|
||||
//return ActionEnum.ChooseFirst;
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
case "切换":
|
||||
//return ActionEnum.SwitchLater;
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
case "使用":
|
||||
return ActionEnum.UseSkill;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToChinese(this ActionEnum type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ActionEnum.ChooseFirst:
|
||||
return "出战";
|
||||
case ActionEnum.SwitchLater:
|
||||
return "切换";
|
||||
case ActionEnum.UseSkill:
|
||||
return "使用";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using BetterGenshinImpact.Core.Recognition.OpenCv;
|
||||
using BetterGenshinImpact.Helpers.Extensions;
|
||||
using OpenCvSharp;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model
|
||||
{
|
||||
public class Character
|
||||
{
|
||||
private readonly ILogger _logger = App.GetLogger<Character>();
|
||||
|
||||
/// <summary>
|
||||
/// 1-3 所在数组下标一致
|
||||
/// </summary>
|
||||
public int Index { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public ElementalType Element { get; set; }
|
||||
public Skill[] Skills { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 是否被打败
|
||||
/// </summary>
|
||||
public bool IsDefeated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 充能点
|
||||
/// </summary>
|
||||
public int Energy { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 充能点来自于图像识别
|
||||
/// </summary>
|
||||
public int EnergyByRecognition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色身上的负面状态
|
||||
/// </summary>
|
||||
public List<CharacterStatusEnum> StatusList { get; set; } = new List<CharacterStatusEnum>();
|
||||
|
||||
/// <summary>
|
||||
/// 角色区域
|
||||
/// </summary>
|
||||
public Rect Area { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 血量上方区域,用于判断是否出战
|
||||
/// </summary>
|
||||
public Rect HpUpperArea { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append($"角色{Index},");
|
||||
sb.Append($"充能={EnergyByRecognition},");
|
||||
if (StatusList?.Count > 0)
|
||||
{
|
||||
sb.Append($"状态:{string.Join(",", StatusList)}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void ChooseFirst()
|
||||
{
|
||||
ClickExtension.Move(GeniusInvokationControl.GetInstance().MakeOffset(Area.GetCenterPoint())).LeftButtonDoubleClick();
|
||||
}
|
||||
|
||||
public bool SwitchLater()
|
||||
{
|
||||
var p = GeniusInvokationControl.GetInstance().MakeOffset(Area.GetCenterPoint());
|
||||
// 选择角色
|
||||
ClickExtension.Move(p).LeftButtonClick();
|
||||
|
||||
// 点击切人按钮
|
||||
GeniusInvokationControl.GetInstance().ActionPhasePressSwitchButton();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色被打败的时候双击角色牌重新出战
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void SwitchWhenTakenOut()
|
||||
{
|
||||
_logger.LogInformation("有角色被打败,当前选择{Name}出战", Name);
|
||||
var p = GeniusInvokationControl.GetInstance().MakeOffset(Area.GetCenterPoint());
|
||||
// 选择角色
|
||||
ClickExtension.Move(p).LeftButtonClick();
|
||||
// 双击切人
|
||||
GeniusInvokationControl.GetInstance().Sleep(500);
|
||||
ClickExtension.Move(p).LeftButtonClick();
|
||||
GeniusInvokationControl.GetInstance().Sleep(300);
|
||||
}
|
||||
|
||||
public bool UseSkill(int skillIndex, Duel duel)
|
||||
{
|
||||
bool res = GeniusInvokationControl.GetInstance()
|
||||
.ActionPhaseAutoUseSkill(skillIndex, Skills[skillIndex].SpecificElementCost, Skills[skillIndex].Type, duel);
|
||||
if (res)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("没有足够的手牌或元素骰子释放技能");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model
|
||||
{
|
||||
public enum CharacterStatusEnum
|
||||
{
|
||||
Frozen,
|
||||
Dizziness
|
||||
}
|
||||
}
|
||||
382
BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs
Normal file
@@ -0,0 +1,382 @@
|
||||
using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
|
||||
using GeniusInvokationAutoToy.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenCvSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model;
|
||||
|
||||
public class Duel
|
||||
{
|
||||
private readonly ILogger<Duel> _logger = App.GetLogger<Duel>();
|
||||
|
||||
public Character CurrentCharacter { get; set; }
|
||||
public Character[] Characters { get; set; } = new Character[4];
|
||||
|
||||
/// <summary>
|
||||
/// 行动指令队列
|
||||
/// </summary>
|
||||
public List<ActionCommand> ActionCommandQueue { get; set; } = new List<ActionCommand>();
|
||||
|
||||
/// <summary>
|
||||
/// 当前回合数
|
||||
/// </summary>
|
||||
public int RoundNum { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 角色牌位置
|
||||
/// </summary>
|
||||
public List<Rect> CharacterCardRects { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 手牌数量
|
||||
/// </summary>
|
||||
public int CurrentCardCount { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 骰子数量
|
||||
/// </summary>
|
||||
public int CurrentDiceCount { get; set; } = 0;
|
||||
|
||||
|
||||
public CancellationTokenSource Cts { get; set; }
|
||||
|
||||
|
||||
public async Task CustomStrategyRunAsync(CancellationTokenSource cts1)
|
||||
{
|
||||
await Task.Run(() => { CustomStrategyRun(cts1); });
|
||||
}
|
||||
|
||||
public void CustomStrategyRun(CancellationTokenSource cts1)
|
||||
{
|
||||
Cts = cts1;
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("========================================");
|
||||
_logger.LogInformation("→ {Text}", "全自动七圣召唤,启动!");
|
||||
|
||||
GeniusInvokationControl.GetInstance().Init(Cts);
|
||||
SystemControl.ActivateWindow();
|
||||
|
||||
// 对局准备 选择初始手牌
|
||||
GeniusInvokationControl.GetInstance().CommonDuelPrepare();
|
||||
|
||||
|
||||
// 获取角色区域
|
||||
CharacterCardRects = Retry.Do(() => GeniusInvokationControl.GetInstance().GetCharacterRects(),
|
||||
TimeSpan.FromSeconds(1.5), 20);
|
||||
if (CharacterCardRects == null || CharacterCardRects.Count != 3)
|
||||
{
|
||||
throw new DuelEndException("未成功获取到角色区域");
|
||||
}
|
||||
|
||||
for (var i = 1; i < 4; i++)
|
||||
{
|
||||
Characters[i].Area = CharacterCardRects[i - 1];
|
||||
}
|
||||
|
||||
// 出战角色
|
||||
CurrentCharacter = ActionCommandQueue[0].Character;
|
||||
CurrentCharacter.ChooseFirst();
|
||||
|
||||
// 开始执行回合
|
||||
while (true)
|
||||
{
|
||||
_logger.LogInformation("--------------第{RoundNum}回合--------------", RoundNum);
|
||||
ClearCharacterStatus(); // 清空单回合的异常状态
|
||||
if (RoundNum == 1)
|
||||
{
|
||||
CurrentCardCount = 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentCardCount += 2;
|
||||
}
|
||||
|
||||
CurrentDiceCount = 8;
|
||||
|
||||
// 预计算本回合内的所有可能的元素
|
||||
var elementSet = PredictionDiceType();
|
||||
|
||||
// 0 投骰子
|
||||
GeniusInvokationControl.GetInstance().ReRollDice(elementSet.ToArray());
|
||||
|
||||
// 等待到我的回合 // 投骰子动画时间是不确定的 // 可能是对方先手
|
||||
GeniusInvokationControl.GetInstance().WaitForMyTurn(this, 1000);
|
||||
|
||||
// 开始执行行动
|
||||
while (true)
|
||||
{
|
||||
// 没骰子了就结束行动
|
||||
_logger.LogInformation("行动开始,当前骰子数[{CurrentDiceCount}],当前手牌数[{CurrentCardCount}]", CurrentDiceCount, CurrentCardCount);
|
||||
if (CurrentDiceCount <= 0)
|
||||
{
|
||||
_logger.LogInformation("骰子已经用完");
|
||||
GeniusInvokationControl.GetInstance().Sleep(2000);
|
||||
break;
|
||||
}
|
||||
|
||||
// 每次行动前都要检查当前角色
|
||||
CurrentCharacter = GeniusInvokationControl.GetInstance().WhichCharacterActiveWithRetry(this);
|
||||
|
||||
var alreadyExecutedActionIndex = new List<int>();
|
||||
var alreadyExecutedActionCommand = new List<ActionCommand>();
|
||||
var i = 0;
|
||||
for (i = 0; i < ActionCommandQueue.Count; i++)
|
||||
{
|
||||
var actionCommand = ActionCommandQueue[i];
|
||||
// 指令中的角色未被打败、角色有异常状态 跳过指令
|
||||
if (actionCommand.Character.IsDefeated || actionCommand.Character.StatusList?.Count > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 当前出战角色身上存在异常状态的情况下不执行本角色的指令
|
||||
if (CurrentCharacter.StatusList?.Count > 0 &&
|
||||
actionCommand.Character.Index == CurrentCharacter.Index)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 1. 判断切人
|
||||
if (CurrentCharacter.Index != actionCommand.Character.Index)
|
||||
{
|
||||
if (CurrentDiceCount >= 1)
|
||||
{
|
||||
actionCommand.SwitchLater();
|
||||
CurrentDiceCount--;
|
||||
alreadyExecutedActionIndex.Add(-actionCommand.Character.Index); // 标记为已执行
|
||||
var switchAction = new ActionCommand
|
||||
{
|
||||
Character = CurrentCharacter,
|
||||
Action = ActionEnum.SwitchLater,
|
||||
TargetIndex = actionCommand.Character.Index
|
||||
};
|
||||
alreadyExecutedActionCommand.Add(switchAction);
|
||||
_logger.LogInformation("→指令执行完成:{Action}", switchAction);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("骰子不足以进行下一步:切换角色 {CharacterIndex}", actionCommand.Character.Index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 判断使用技能
|
||||
if (actionCommand.GetAllDiceUseCount() > CurrentDiceCount)
|
||||
{
|
||||
_logger.LogInformation("骰子不足以进行下一步:{Action}", actionCommand);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool useSkillRes = actionCommand.UseSkill(this);
|
||||
if (useSkillRes)
|
||||
{
|
||||
CurrentDiceCount -= actionCommand.GetAllDiceUseCount();
|
||||
alreadyExecutedActionIndex.Add(i);
|
||||
alreadyExecutedActionCommand.Add(actionCommand);
|
||||
_logger.LogInformation("→指令执行完成:{Action}", actionCommand);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("→指令执行失败(可能是手牌不够):{Action}", actionCommand);
|
||||
GeniusInvokationControl.GetInstance().Sleep(1000);
|
||||
GeniusInvokationControl.GetInstance().ClickGameWindowCenter();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (alreadyExecutedActionIndex.Count != 0)
|
||||
{
|
||||
foreach (var index in alreadyExecutedActionIndex)
|
||||
{
|
||||
if (index >= 0)
|
||||
{
|
||||
ActionCommandQueue.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
alreadyExecutedActionIndex.Clear();
|
||||
// 等待对方行动完成 (开大的时候等待时间久一点)
|
||||
var sleepTime = ComputeWaitForMyTurnTime(alreadyExecutedActionCommand);
|
||||
GeniusInvokationControl.GetInstance().WaitForMyTurn(this, sleepTime);
|
||||
alreadyExecutedActionCommand.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果没有任何指令可以执行 则跳出循环
|
||||
// TODO 也有可能是角色死亡/所有角色被冻结导致没有指令可以执行
|
||||
//if (i >= ActionCommandQueue.Count)
|
||||
//{
|
||||
// throw new DuelEndException("策略中所有指令已经执行完毕,结束自动打牌");
|
||||
//}
|
||||
GeniusInvokationControl.GetInstance().Sleep(2000);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ActionCommandQueue.Count == 0)
|
||||
{
|
||||
throw new DuelEndException("策略中所有指令已经执行完毕,结束自动打牌");
|
||||
}
|
||||
}
|
||||
|
||||
// 回合结束
|
||||
GeniusInvokationControl.GetInstance().Sleep(1000);
|
||||
_logger.LogInformation("我方点击回合结束");
|
||||
GeniusInvokationControl.GetInstance().RoundEnd();
|
||||
|
||||
// 等待对方行动+回合结算
|
||||
GeniusInvokationControl.GetInstance().WaitOpponentAction(this);
|
||||
RoundNum++;
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
_logger.LogInformation(ex.Message);
|
||||
}
|
||||
catch (DuelEndException ex)
|
||||
{
|
||||
_logger.LogInformation(ex.Message);
|
||||
_logger.LogInformation("← {Text}", "退出全自动七圣召唤");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.LogInformation("========================================");
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<ElementalType> PredictionDiceType()
|
||||
{
|
||||
var actionUseDiceSum = 0;
|
||||
var elementSet = new HashSet<ElementalType>
|
||||
{
|
||||
ElementalType.Omni
|
||||
};
|
||||
for (var i = 0; i < ActionCommandQueue.Count; i++)
|
||||
{
|
||||
var actionCommand = ActionCommandQueue[i];
|
||||
|
||||
// 角色未被打败的情况下才能执行
|
||||
if (actionCommand.Character.IsDefeated)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 通过骰子数量判断是否可以执行
|
||||
|
||||
// 1. 判断切人
|
||||
if (i > 0 && actionCommand.Character.Index != ActionCommandQueue[i - 1].Character.Index)
|
||||
{
|
||||
actionUseDiceSum++;
|
||||
if (actionUseDiceSum > CurrentDiceCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
elementSet.Add(actionCommand.GetDiceUseElementType());
|
||||
//executeActionIndex.Add(-actionCommand.Character.Index);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 判断使用技能
|
||||
actionUseDiceSum += actionCommand.GetAllDiceUseCount();
|
||||
if (actionUseDiceSum > CurrentDiceCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
elementSet.Add(actionCommand.GetDiceUseElementType());
|
||||
//executeActionIndex.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
return elementSet;
|
||||
}
|
||||
|
||||
public void ClearCharacterStatus()
|
||||
{
|
||||
foreach (var character in Characters)
|
||||
{
|
||||
character?.StatusList?.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据前面执行的命令计算等待时间
|
||||
/// 大招等待15秒
|
||||
/// 快速切换等待3秒
|
||||
/// </summary>
|
||||
/// <param name="alreadyExecutedActionCommand"></param>
|
||||
/// <returns></returns>
|
||||
private int ComputeWaitForMyTurnTime(List<ActionCommand> alreadyExecutedActionCommand)
|
||||
{
|
||||
foreach (var command in alreadyExecutedActionCommand)
|
||||
{
|
||||
if (command.Action == ActionEnum.UseSkill && command.TargetIndex == 1)
|
||||
{
|
||||
return 15000;
|
||||
}
|
||||
|
||||
// 莫娜切换等待3秒
|
||||
if (command.Character.Name == "莫娜" && command.Action == ActionEnum.SwitchLater)
|
||||
{
|
||||
return 3000;
|
||||
}
|
||||
}
|
||||
|
||||
return 10000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取角色切换顺序
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<int> GetCharacterSwitchOrder()
|
||||
{
|
||||
List<int> orderList = new List<int>();
|
||||
for (var i = 0; i < ActionCommandQueue.Count; i++)
|
||||
{
|
||||
if (!orderList.Contains(ActionCommandQueue[i].Character.Index))
|
||||
{
|
||||
orderList.Add(ActionCommandQueue[i].Character.Index);
|
||||
}
|
||||
}
|
||||
|
||||
return orderList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取角色存活数量
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetCharacterAliveNum()
|
||||
{
|
||||
int num = 0;
|
||||
foreach (var character in Characters)
|
||||
{
|
||||
if (character != null && !character.IsDefeated)
|
||||
{
|
||||
num++;
|
||||
}
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model
|
||||
{
|
||||
public enum ElementalType
|
||||
{
|
||||
Omni,
|
||||
Cryo,
|
||||
Hydro,
|
||||
Pyro,
|
||||
Electro,
|
||||
Dendro,
|
||||
Anemo,
|
||||
Geo
|
||||
}
|
||||
|
||||
public static class ElementalTypeExtension
|
||||
{
|
||||
public static ElementalType ToElementalType(this string type)
|
||||
{
|
||||
type = type.ToLower();
|
||||
switch (type)
|
||||
{
|
||||
case "omni":
|
||||
return ElementalType.Omni;
|
||||
case "cryo":
|
||||
return ElementalType.Cryo;
|
||||
case "hydro":
|
||||
return ElementalType.Hydro;
|
||||
case "pyro":
|
||||
return ElementalType.Pyro;
|
||||
case "electro":
|
||||
return ElementalType.Electro;
|
||||
case "dendro":
|
||||
return ElementalType.Dendro;
|
||||
case "anemo":
|
||||
return ElementalType.Anemo;
|
||||
case "geo":
|
||||
return ElementalType.Geo;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static ElementalType ChineseToElementalType(this string type)
|
||||
{
|
||||
type = type.ToLower();
|
||||
switch (type)
|
||||
{
|
||||
case "全":
|
||||
return ElementalType.Omni;
|
||||
case "冰":
|
||||
return ElementalType.Cryo;
|
||||
case "水":
|
||||
return ElementalType.Hydro;
|
||||
case "火":
|
||||
return ElementalType.Pyro;
|
||||
case "雷":
|
||||
return ElementalType.Electro;
|
||||
case "草":
|
||||
return ElementalType.Dendro;
|
||||
case "风":
|
||||
return ElementalType.Anemo;
|
||||
case "岩":
|
||||
return ElementalType.Geo;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToChinese(this ElementalType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ElementalType.Omni:
|
||||
return "全";
|
||||
case ElementalType.Cryo:
|
||||
return "冰";
|
||||
case ElementalType.Hydro:
|
||||
return "水";
|
||||
case ElementalType.Pyro:
|
||||
return "火";
|
||||
case ElementalType.Electro:
|
||||
return "雷";
|
||||
case ElementalType.Dendro:
|
||||
return "草";
|
||||
case ElementalType.Anemo:
|
||||
return "风";
|
||||
case ElementalType.Geo:
|
||||
return "岩";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToLowerString(this ElementalType type)
|
||||
{
|
||||
return type.ToString().ToLower();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// 投掷期间骰子
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public class RollPhaseDice
|
||||
{
|
||||
/// <summary>
|
||||
/// 元素类型
|
||||
/// </summary>
|
||||
public ElementalType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 中心点位置
|
||||
/// </summary>
|
||||
public Point CenterPosition { get; set; }
|
||||
|
||||
public RollPhaseDice(ElementalType type, Point centerPosition)
|
||||
{
|
||||
Type = type;
|
||||
CenterPosition = centerPosition;
|
||||
}
|
||||
|
||||
public RollPhaseDice()
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Type:{Type},CenterPosition:{CenterPosition}";
|
||||
}
|
||||
|
||||
public void Click()
|
||||
{
|
||||
//MouseUtils.Click(CenterPosition.X, CenterPosition.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model;
|
||||
|
||||
[Obsolete]
|
||||
public class RoundStrategy
|
||||
{
|
||||
public List<string> RawCommandList { get; set; } = new List<string>();
|
||||
|
||||
public List<ActionCommand> ActionCommands { get; set; } = new List<ActionCommand>();
|
||||
|
||||
public List<ElementalType> MaybeNeedElement(Duel duel)
|
||||
{
|
||||
List<ElementalType> result = new List<ElementalType>();
|
||||
|
||||
for (int i = 0; i < ActionCommands.Count; i++)
|
||||
{
|
||||
if (ActionCommands[i].Action == ActionEnum.SwitchLater
|
||||
&& i != ActionCommands.Count-1
|
||||
&& ActionCommands[i+1].Action == ActionEnum.UseSkill)
|
||||
{
|
||||
result.Add(duel.Characters[ActionCommands[i].TargetIndex].Element);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model;
|
||||
|
||||
public class Skill
|
||||
{
|
||||
/// <summary>
|
||||
/// 1-4 和数组下标一致,游戏中是从右往左开始数的!
|
||||
/// </summary>
|
||||
public short Index { get; set; }
|
||||
public ElementalType Type { get; set; }
|
||||
/// <summary>
|
||||
/// 消耗指定元素骰子数量
|
||||
/// </summary>
|
||||
public int SpecificElementCost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消耗杂色骰子数量
|
||||
/// </summary>
|
||||
public int AnyElementCost { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 消耗指定元素骰子数量 + 消耗杂色骰子数量 = 消耗总骰子数量
|
||||
/// </summary>
|
||||
public int AllCost { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation;
|
||||
|
||||
public class ScriptParser
|
||||
{
|
||||
private static readonly ILogger<ScriptParser> MyLogger = App.GetLogger<ScriptParser>();
|
||||
public static Duel Parse(string script)
|
||||
{
|
||||
var lines = script.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
|
||||
var result = new List<string>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
string l = line.Trim();
|
||||
result.Add(l);
|
||||
}
|
||||
|
||||
return Parse(result);
|
||||
}
|
||||
|
||||
public static Duel Parse(List<string> lines)
|
||||
{
|
||||
Duel duel = new Duel();
|
||||
string stage = "";
|
||||
|
||||
int i = 0;
|
||||
try
|
||||
{
|
||||
for (i = 0; i < lines.Count; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (line.Contains(":"))
|
||||
{
|
||||
stage = line;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line == "---" || line.StartsWith("//") || string.IsNullOrEmpty(line))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stage == "角色定义:")
|
||||
{
|
||||
var character = ParseCharacter(line);
|
||||
duel.Characters[character.Index] = character;
|
||||
}
|
||||
else if (stage == "策略定义:")
|
||||
{
|
||||
MyAssert(duel.Characters[3] != null, "角色未定义");
|
||||
|
||||
string[] actionParts = line.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
MyAssert(actionParts.Length == 3, "策略中的行动命令解析错误");
|
||||
MyAssert(actionParts[1] == "使用", "策略中的行动命令解析错误");
|
||||
|
||||
var actionCommand = new ActionCommand();
|
||||
var action = actionParts[1].ChineseToActionEnum();
|
||||
actionCommand.Action = action;
|
||||
|
||||
int j = 1;
|
||||
for (j = 1; j <= 3; j++)
|
||||
{
|
||||
var character = duel.Characters[j];
|
||||
if (character != null && character.Name == actionParts[0])
|
||||
{
|
||||
actionCommand.Character = character;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MyAssert(j <= 3, "策略中的行动命令解析错误:角色名称无法从角色定义中匹配到");
|
||||
|
||||
int skillNum = int.Parse(Regex.Replace(actionParts[2], @"[^0-9]+", ""));
|
||||
MyAssert(skillNum < 5, "策略中的行动命令解析错误:技能编号错误");
|
||||
actionCommand.TargetIndex = skillNum;
|
||||
duel.ActionCommandQueue.Add(actionCommand);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.Exception($"未知的定义字段:{stage}");
|
||||
}
|
||||
}
|
||||
|
||||
MyAssert(duel.Characters[3] != null, "角色未定义,请确认策略文本格式是否为UTF-8");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
MyLogger.LogError($"解析脚本错误,行号:{i + 1},错误信息:{ex}");
|
||||
MessageBox.Show($"解析脚本错误,行号:{i + 1},错误信息:{ex}", "策略解析失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return duel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析示例
|
||||
/// 角色1=刻晴|雷{技能3消耗=1雷骰子+2任意,技能2消耗=3雷骰子,技能1消耗=4雷骰子}
|
||||
/// 角色2=雷神|雷{技能3消耗=1雷骰子+2任意,技能2消耗=3雷骰子,技能1消耗=4雷骰子}
|
||||
/// 角色3=甘雨|冰{技能4消耗=1冰骰子+2任意,技能3消耗=1冰骰子,技能2消耗=5冰骰子,技能1消耗=3冰骰子}
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public static Character ParseCharacter(string line)
|
||||
{
|
||||
var character = new Character();
|
||||
|
||||
var characterAndSkill = line.Split('{');
|
||||
|
||||
var parts = characterAndSkill[0].Split('=');
|
||||
character.Index = int.Parse(Regex.Replace(parts[0], @"[^0-9]+", ""));
|
||||
MyAssert(character.Index >= 1 && character.Index <= 3, "角色序号必须在1-3之间");
|
||||
var nameAndElement = parts[1].Split('|');
|
||||
character.Name = nameAndElement[0];
|
||||
character.Element = nameAndElement[1].Substring(0, 1).ChineseToElementalType();
|
||||
|
||||
// 技能
|
||||
string skillStr = characterAndSkill[1].Replace("}", "");
|
||||
var skillParts = skillStr.Split(',');
|
||||
var skills = new Skill[skillParts.Length + 1];
|
||||
for (int i = 0; i < skillParts.Length; i++)
|
||||
{
|
||||
var skill = ParseSkill(skillParts[i]);
|
||||
skills[skill.Index] = skill;
|
||||
}
|
||||
|
||||
character.Skills = skills.ToArray();
|
||||
return character;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 技能3消耗=1雷骰子+2任意
|
||||
/// 技能2消耗=3雷骰子
|
||||
/// 技能1消耗=4雷骰子
|
||||
/// </summary>
|
||||
/// <param name="oneSkillStr"></param>
|
||||
/// <returns></returns>
|
||||
public static Skill ParseSkill(string oneSkillStr)
|
||||
{
|
||||
var skill = new Skill();
|
||||
var parts = oneSkillStr.Split('=');
|
||||
skill.Index = short.Parse(Regex.Replace(parts[0], @"[^0-9]+", ""));
|
||||
MyAssert(skill.Index >= 1 && skill.Index <= 5, "技能序号必须在1-5之间");
|
||||
var costStr = parts[1];
|
||||
var costParts = costStr.Split('+');
|
||||
skill.SpecificElementCost = int.Parse(costParts[0].Substring(0, 1));
|
||||
skill.Type = costParts[0].Substring(1, 1).ChineseToElementalType();
|
||||
// 杂色骰子在+号右边
|
||||
if (costParts.Length > 1)
|
||||
{
|
||||
skill.AnyElementCost = int.Parse(costParts[1].Substring(0, 1));
|
||||
}
|
||||
|
||||
skill.AllCost = skill.SpecificElementCost + skill.AnyElementCost;
|
||||
return skill;
|
||||
}
|
||||
|
||||
private static void MyAssert(bool b, string msg)
|
||||
{
|
||||
if (!b)
|
||||
{
|
||||
throw new System.Exception(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GeniusInvokationAutoToy.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// https://stackoverflow.com/questions/1563191/cleanest-way-to-write-retry-logic
|
||||
/// </summary>
|
||||
public static class Retry
|
||||
{
|
||||
public static void Do(
|
||||
Action action,
|
||||
TimeSpan retryInterval,
|
||||
int maxAttemptCount = 3)
|
||||
{
|
||||
Do<object>(() =>
|
||||
{
|
||||
action();
|
||||
return null;
|
||||
}, retryInterval, maxAttemptCount);
|
||||
}
|
||||
|
||||
public static T Do<T>(
|
||||
Func<T> action,
|
||||
TimeSpan retryInterval,
|
||||
int maxAttemptCount = 3)
|
||||
{
|
||||
var exceptions = new List<Exception>();
|
||||
|
||||
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (attempted > 0)
|
||||
{
|
||||
Thread.Sleep(retryInterval);
|
||||
}
|
||||
|
||||
return action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,22 +193,22 @@ public class RectArea : IDisposable
|
||||
return _srcBitmap != null || _srcMat != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在本区域内查找目标图像
|
||||
/// </summary>
|
||||
/// <param name="targetImageMat"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete]
|
||||
public RectArea Find(Mat targetImageMat)
|
||||
{
|
||||
if (!HasImage())
|
||||
{
|
||||
throw new Exception("当前对象内没有图像内容,无法完成 Find 操作");
|
||||
}
|
||||
///// <summary>
|
||||
///// 在本区域内查找目标图像
|
||||
///// </summary>
|
||||
///// <param name="targetImageMat"></param>
|
||||
///// <returns></returns>
|
||||
//[Obsolete]
|
||||
//public RectArea Find(Mat targetImageMat)
|
||||
//{
|
||||
// if (!HasImage())
|
||||
// {
|
||||
// throw new Exception("当前对象内没有图像内容,无法完成 Find 操作");
|
||||
// }
|
||||
|
||||
var p = OldMatchTemplateHelper.FindSingleTarget(SrcGreyMat, targetImageMat);
|
||||
return p is { X: > 0, Y: > 0 } ? new RectArea(targetImageMat, p.X - targetImageMat.Width / 2, p.Y - targetImageMat.Height / 2, this) : new RectArea();
|
||||
}
|
||||
// var p = OldMatchTemplateHelper.FindSingleTarget(SrcGreyMat, targetImageMat);
|
||||
// return p is { X: > 0, Y: > 0 } ? new RectArea(targetImageMat, p.X - targetImageMat.Width / 2, p.Y - targetImageMat.Height / 2, this) : new RectArea();
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 在本区域内查找识别对象
|
||||
@@ -343,22 +343,22 @@ public class RectArea : IDisposable
|
||||
return ra;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 找到图像并点击中心
|
||||
/// </summary>
|
||||
/// <param name="targetImageMat"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete]
|
||||
public RectArea ClickCenter(Mat targetImageMat)
|
||||
{
|
||||
var ra = Find(targetImageMat);
|
||||
if (!ra.IsEmpty())
|
||||
{
|
||||
ra.ClickCenter();
|
||||
}
|
||||
///// <summary>
|
||||
///// 找到图像并点击中心
|
||||
///// </summary>
|
||||
///// <param name="targetImageMat"></param>
|
||||
///// <returns></returns>
|
||||
//[Obsolete]
|
||||
//public RectArea ClickCenter(Mat targetImageMat)
|
||||
//{
|
||||
// var ra = Find(targetImageMat);
|
||||
// if (!ra.IsEmpty())
|
||||
// {
|
||||
// ra.ClickCenter();
|
||||
// }
|
||||
|
||||
return ra;
|
||||
}
|
||||
// return ra;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 当前对象点击中心
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace BetterGenshinImpact.GameTask.Model
|
||||
/// 捕获窗口区域 现在已经和实际游戏画面一致
|
||||
/// CaptureAreaRect = GameScreenSize or GameWindowRect
|
||||
/// </summary>
|
||||
public RECT CaptureAreaRect { get; }
|
||||
public RECT CaptureAreaRect { get; set; }
|
||||
|
||||
public Process GameProcess { get; }
|
||||
|
||||
|
||||
@@ -14,21 +14,9 @@ public class TestTrigger : ITaskTrigger
|
||||
public int Priority => 9999;
|
||||
public bool IsExclusive { get; private set; }
|
||||
|
||||
private readonly RecognitionObject _optionButtonRo;
|
||||
|
||||
private readonly AutoFishingAssets _autoFishingAssets;
|
||||
|
||||
public TestTrigger()
|
||||
{
|
||||
var info = TaskContext.Instance().SystemInfo;
|
||||
_optionButtonRo = new RecognitionObject
|
||||
{
|
||||
Name = "OptionButton",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssertImage("AutoSkip", "option.png"),
|
||||
DrawOnWindow = true
|
||||
}.InitTemplate();
|
||||
_autoFishingAssets = new AutoFishingAssets();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
|
||||
@@ -12,8 +12,7 @@ public class SystemControl
|
||||
return FindHandleByProcessName("YuanShen", "GenshinImpact", "Genshin Impact Cloud Game");
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static bool IsGenshinImpactActiveOld()
|
||||
public static bool IsGenshinImpactActiveByProcess()
|
||||
{
|
||||
var name = GetActiveProcessName();
|
||||
return name is "YuanShen" or "GenshinImpact" or "Genshin Impact Cloud Game";
|
||||
@@ -119,6 +118,15 @@ public class SystemControl
|
||||
User32.SetForegroundWindow(hWnd);
|
||||
}
|
||||
|
||||
public static void ActivateWindow()
|
||||
{
|
||||
if (!TaskContext.Instance().IsInitialized)
|
||||
{
|
||||
throw new Exception("请先启动BetterGI");
|
||||
}
|
||||
ActivateWindow(TaskContext.Instance().GameHandle);
|
||||
}
|
||||
|
||||
public static bool IsFullScreenMode(IntPtr hWnd)
|
||||
{
|
||||
if (hWnd == IntPtr.Zero)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using BetterGenshinImpact.Core.Simulator;
|
||||
using OpenCvSharp;
|
||||
using WindowsInput;
|
||||
|
||||
namespace BetterGenshinImpact.Helpers.Extensions;
|
||||
|
||||
@@ -17,9 +18,20 @@ public static class ClickExtension
|
||||
(rect.Y + rect.Height * 1d / 2) * 65535 / PrimaryScreen.WorkingArea.Height).LeftButtonClick();
|
||||
}
|
||||
|
||||
public static void Click(int x, int y)
|
||||
public static IMouseSimulator Click(int x, int y)
|
||||
{
|
||||
Simulation.SendInput.Mouse.MoveMouseTo(x * 65535 * 1d / PrimaryScreen.WorkingArea.Width,
|
||||
return Simulation.SendInput.Mouse.MoveMouseTo(x * 65535 * 1d / PrimaryScreen.WorkingArea.Width,
|
||||
y * 65535 * 1d / PrimaryScreen.WorkingArea.Height).LeftButtonClick();
|
||||
}
|
||||
|
||||
public static IMouseSimulator Move(double x, double y)
|
||||
{
|
||||
return Simulation.SendInput.Mouse.MoveMouseTo(x * 65535 * 1d / PrimaryScreen.WorkingArea.Width,
|
||||
y * 65535 * 1d / PrimaryScreen.WorkingArea.Height).LeftButtonClick();
|
||||
}
|
||||
|
||||
public static IMouseSimulator Move(Point p)
|
||||
{
|
||||
return Move(p.X, p.Y);
|
||||
}
|
||||
}
|
||||