AutoGeniusInvokation: migrate code

This commit is contained in:
huiyadanli
2023-10-21 14:08:16 +08:00
parent a65ab0adbd
commit feb09bb6bc
58 changed files with 2892 additions and 211 deletions

View File

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

View File

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

View File

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

View File

@@ -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;
//}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -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") },
};
}
}
}

View File

@@ -0,0 +1,8 @@
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
public class DuelEndException: System.Exception
{
public DuelEndException(string message) : base(message)
{
}
}

View File

@@ -0,0 +1,12 @@
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
public class RetryException : System.Exception
{
public RetryException() : base()
{
}
public RetryException(string message) : base(message)
{
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model
{
public enum CharacterStatusEnum
{
Frozen,
Dizziness
}
}

View 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;
}
}

View File

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

View File

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

View File

@@ -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;
}
}

View File

@@ -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; }
}

View File

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

View File

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

View File

@@ -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>
/// 当前对象点击中心

View File

@@ -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; }

View File

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

View File

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

View File

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