mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-25 10:05:49 +08:00
Merge branch 'main' into d-v3
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>BetterGI</AssemblyName>
|
||||
<Version>0.53.1-alpha.4</Version>
|
||||
<Version>0.54.1-alpha.3</Version>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
@@ -44,8 +44,8 @@
|
||||
|
||||
|
||||
<PackageReference Include="BetterGI.VCRuntime" Version="14.44.35208" />
|
||||
<PackageReference Include="BetterGI.Assets.Map" Version="1.0.13" />
|
||||
<PackageReference Include="BetterGI.Assets.Model" Version="1.0.13" />
|
||||
<PackageReference Include="BetterGI.Assets.Map" Version="1.0.15" />
|
||||
<PackageReference Include="BetterGI.Assets.Model" Version="1.0.17" />
|
||||
<PackageReference Include="BetterGI.Assets.Other" Version="1.0.10" />
|
||||
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
|
||||
@@ -6,6 +6,7 @@ using BetterGenshinImpact.Model;
|
||||
using Gma.System.MouseKeyHook;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using Vanara.PInvoke;
|
||||
using Timer = System.Timers.Timer;
|
||||
@@ -41,21 +42,38 @@ public class MouseKeyMonitor
|
||||
/// </summary>
|
||||
private DateTime _firstSpaceKeyDownTime = DateTime.MaxValue;
|
||||
|
||||
private IKeyboardMouseEvents? _globalHook;
|
||||
private static IKeyboardMouseEvents? _globalHook;
|
||||
private static readonly object GlobalHookLock = new object();
|
||||
public static IKeyboardMouseEvents GlobalHook
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_globalHook == null)
|
||||
{
|
||||
lock (GlobalHookLock)
|
||||
{
|
||||
if (_globalHook == null)
|
||||
{
|
||||
_globalHook = Hook.GlobalEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
return _globalHook;
|
||||
}
|
||||
}
|
||||
private nint _hWnd;
|
||||
|
||||
public void Subscribe(nint gameHandle)
|
||||
{
|
||||
_hWnd = gameHandle;
|
||||
// Note: for the application hook, use the Hook.AppEvents() instead
|
||||
_globalHook = Hook.GlobalEvents();
|
||||
|
||||
_globalHook.KeyDown += GlobalHookKeyDown;
|
||||
_globalHook.KeyUp += GlobalHookKeyUp;
|
||||
_globalHook.MouseDownExt += GlobalHookMouseDownExt;
|
||||
_globalHook.MouseUpExt += GlobalHookMouseUpExt;
|
||||
_globalHook.MouseMoveExt += GlobalHookMouseMoveExt;
|
||||
_globalHook.MouseWheelExt += GlobalHookMouseWheelExt;
|
||||
GlobalHook.KeyDown += GlobalHookKeyDown;
|
||||
GlobalHook.KeyUp += GlobalHookKeyUp;
|
||||
GlobalHook.MouseDownExt += GlobalHookMouseDownExt;
|
||||
GlobalHook.MouseUpExt += GlobalHookMouseUpExt;
|
||||
GlobalHook.MouseMoveExt += GlobalHookMouseMoveExt;
|
||||
GlobalHook.MouseWheelExt += GlobalHookMouseWheelExt;
|
||||
//_globalHook.KeyPress += GlobalHookKeyPress;
|
||||
|
||||
_pickUpKey = TaskContext.Instance().Config.KeyBindingsConfig.PickUpOrInteract.ToWinFormKeys();
|
||||
@@ -200,6 +218,7 @@ public class MouseKeyMonitor
|
||||
_globalHook.MouseWheelExt -= GlobalHookMouseWheelExt;
|
||||
//_globalHook.KeyPress -= GlobalHookKeyPress;
|
||||
_globalHook.Dispose();
|
||||
_globalHook = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,8 +281,10 @@ public class PaddleOcrService : IOcrService, IDisposable
|
||||
/// </summary>
|
||||
public string OcrWithoutDetector(Mat mat)
|
||||
{
|
||||
var startTime = Stopwatch.GetTimestamp();
|
||||
var str = _localRecModel.Run(mat).Text;
|
||||
Debug.WriteLine($"PaddleOcrWithoutDetector 结果: {str}");
|
||||
var time = Stopwatch.GetElapsedTime(startTime);
|
||||
Debug.WriteLine($"PaddleOcrWithoutDetector 耗时 {time.TotalMilliseconds}ms 结果: {str}");
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
@@ -407,6 +407,11 @@ public class BgiOnnxFactory
|
||||
break;
|
||||
case ProviderType.Cpu:
|
||||
sessionOptions.AppendExecutionProvider_CPU();
|
||||
if (model.Name.Contains("PpOcr") || model.Name.Contains("Yap"))
|
||||
{
|
||||
sessionOptions.IntraOpNumThreads = 2; // 限制算子内部并行线程数
|
||||
sessionOptions.InterOpNumThreads = 1; // 限制算子间并行线程数(顺序执行)
|
||||
}
|
||||
break;
|
||||
case ProviderType.Dnnl:
|
||||
sessionOptions.AppendExecutionProvider_Dnnl();
|
||||
|
||||
@@ -6,6 +6,40 @@ using System.Linq;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Recognition.OpenCv;
|
||||
|
||||
/// <summary>
|
||||
/// 形态学操作参数类
|
||||
/// </summary>
|
||||
public class MorphologyParam
|
||||
{
|
||||
/// <summary>
|
||||
/// 运算核大小
|
||||
/// </summary>
|
||||
public Size KernelSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 运算方法
|
||||
/// </summary>
|
||||
public MorphTypes MorphType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 运算次数
|
||||
/// </summary>
|
||||
public int Iterations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 形态学操作参数构造函数
|
||||
/// </summary>
|
||||
/// <param name="kernelSize">运算核大小</param>
|
||||
/// <param name="morphType">运算方法</param>
|
||||
/// <param name="iterations">运算次数</param>
|
||||
public MorphologyParam(Size kernelSize, MorphTypes morphType, int iterations)
|
||||
{
|
||||
KernelSize = kernelSize;
|
||||
MorphType = morphType;
|
||||
Iterations = iterations;
|
||||
}
|
||||
}
|
||||
|
||||
public class ContoursHelper
|
||||
{
|
||||
/// <summary>
|
||||
@@ -16,14 +50,21 @@ public class ContoursHelper
|
||||
/// <param name="high">RGB色彩高位</param>
|
||||
/// <param name="minWidth">矩形最小宽度</param>
|
||||
/// <param name="minHeight">矩形最小高度</param>
|
||||
/// <param name="morphologyParam">形态学操作参数(可选)</param>
|
||||
/// <returns></returns>
|
||||
public static List<Rect> FindSpecifyColorRects(Mat srcMat, Scalar low, Scalar high, int minWidth = -1, int minHeight = -1)
|
||||
public static List<Rect> FindSpecifyColorRects(Mat srcMat, Scalar low, Scalar high, int minWidth = -1, int minHeight = -1, MorphologyParam? morphologyParam = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var src = srcMat.Clone();
|
||||
Cv2.CvtColor(src, src, ColorConversionCodes.BGR2RGB);
|
||||
Cv2.InRange(src, low, high, src);
|
||||
|
||||
if (morphologyParam != null)
|
||||
{
|
||||
using var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, morphologyParam.KernelSize);
|
||||
Cv2.MorphologyEx(src, src, morphologyParam.MorphType, kernel, iterations: morphologyParam.Iterations);
|
||||
}
|
||||
|
||||
Cv2.FindContours(src, out var contours, out _, RetrievalModes.External,
|
||||
ContourApproximationModes.ApproxSimple);
|
||||
@@ -56,4 +97,9 @@ public class ContoursHelper
|
||||
{
|
||||
return FindSpecifyColorRects(srcMat, color, color, minWidth, minHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Rect> FindSpecifyColorRects(Mat srcMat, Scalar color, int minWidth, int minHeight, MorphologyParam morphologyParam)
|
||||
{
|
||||
return FindSpecifyColorRects(srcMat, color, color, minWidth, minHeight, morphologyParam);
|
||||
}
|
||||
}
|
||||
@@ -14,71 +14,79 @@ public class ImageDifferenceDetector
|
||||
/// <returns>差异最大的图片索引(0-3),如果没有图片获得3票则返回-1</returns>
|
||||
public static int FindMostDifferentImage(Mat[] images)
|
||||
{
|
||||
if (images is not { Length: 4 })
|
||||
try
|
||||
{
|
||||
throw new ArgumentException("必须提供4张图片");
|
||||
}
|
||||
|
||||
// 验证所有图片尺寸相同
|
||||
Size firstSize = images[0].Size();
|
||||
for (int i = 1; i < 4; i++)
|
||||
{
|
||||
if (images[i].Size() != firstSize)
|
||||
if (images is not { Length: 4 })
|
||||
{
|
||||
throw new ArgumentException("所有图片必须具有相同的尺寸");
|
||||
throw new ArgumentException("必须提供4张图片");
|
||||
}
|
||||
}
|
||||
|
||||
// 存储每张图片获得的投票数
|
||||
int[] votes = new int[4];
|
||||
|
||||
// 每张图片投票给与它差异最大的图片
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int maxDiffImageIndex = -1;
|
||||
double maxDifference = 0;
|
||||
|
||||
// 找出与图片i差异最大的图片
|
||||
for (int j = 0; j < 4; j++)
|
||||
// 验证所有图片尺寸相同
|
||||
Size firstSize = images[0].Size();
|
||||
for (int i = 1; i < 4; i++)
|
||||
{
|
||||
if (i != j)
|
||||
if (images[i].Size() != firstSize)
|
||||
{
|
||||
double difference = CalculateDifference(images[i], images[j]);
|
||||
Debug.WriteLine($"{i} vs {j} 差异像素数量: {difference}");
|
||||
|
||||
if (difference > maxDifference)
|
||||
{
|
||||
maxDifference = difference;
|
||||
maxDiffImageIndex = j;
|
||||
}
|
||||
throw new ArgumentException("所有图片必须具有相同的尺寸");
|
||||
}
|
||||
}
|
||||
|
||||
// 图片i投票给差异最大的图片
|
||||
if (maxDiffImageIndex != -1)
|
||||
{
|
||||
votes[maxDiffImageIndex]++;
|
||||
Debug.WriteLine($"图片 {i} 投票给图片 {maxDiffImageIndex} (差异值: {maxDifference:F2})");
|
||||
}
|
||||
}
|
||||
// 存储每张图片获得的投票数
|
||||
int[] votes = new int[4];
|
||||
|
||||
// 输出投票结果
|
||||
Debug.WriteLine("\n投票结果:");
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Debug.WriteLine($"图片 {i}: {votes[i]} 票");
|
||||
}
|
||||
|
||||
// 检查是否有图片获得3票
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (votes[i] >= 3)
|
||||
// 每张图片投票给与它差异最大的图片
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
return i;
|
||||
int maxDiffImageIndex = -1;
|
||||
double maxDifference = 0;
|
||||
|
||||
// 找出与图片i差异最大的图片
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
if (i != j)
|
||||
{
|
||||
double difference = CalculateDifference(images[i], images[j]);
|
||||
Debug.WriteLine($"{i} vs {j} 差异像素数量: {difference}");
|
||||
|
||||
if (difference > maxDifference)
|
||||
{
|
||||
maxDifference = difference;
|
||||
maxDiffImageIndex = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 图片i投票给差异最大的图片
|
||||
if (maxDiffImageIndex != -1)
|
||||
{
|
||||
votes[maxDiffImageIndex]++;
|
||||
Debug.WriteLine($"图片 {i} 投票给图片 {maxDiffImageIndex} (差异值: {maxDifference:F2})");
|
||||
}
|
||||
}
|
||||
|
||||
// 输出投票结果
|
||||
Debug.WriteLine("\n投票结果:");
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Debug.WriteLine($"图片 {i}: {votes[i]} 票");
|
||||
}
|
||||
|
||||
// 检查是否有图片获得3票
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (votes[i] >= 3)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"FindMostDifferentImage 出错: {ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
386
BetterGenshinImpact/Core/Script/Dependence/KeyMouseHook.cs
Normal file
386
BetterGenshinImpact/Core/Script/Dependence/KeyMouseHook.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Forms;
|
||||
using BetterGenshinImpact.Core.Monitor;
|
||||
using Gma.System.MouseKeyHook;
|
||||
using Microsoft.ClearScript;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Script.Dependence;
|
||||
|
||||
public class KeyMouseHook: IDisposable
|
||||
{
|
||||
private IKeyboardMouseEvents? _appEvents;
|
||||
private IKeyboardMouseEvents AppHook => _appEvents ??= MouseKeyMonitor.GlobalHook;
|
||||
|
||||
private readonly List<ScriptObject> _keyDownDataCallbacks = new();
|
||||
private readonly List<ScriptObject> _keyUpDataCallbacks = new();
|
||||
private readonly List<ScriptObject> _keyDownCodeCallbacks = new();
|
||||
private readonly List<ScriptObject> _keyUpCodeCallbacks = new();
|
||||
private readonly List<ScriptObject> _mouseDownCallbacks = new();
|
||||
private readonly List<ScriptObject> _mouseUpCallbacks = new();
|
||||
private readonly List<ScriptObject> _mouseMoveCallbacks = new();
|
||||
private readonly List<ScriptObject> _mouseWheelCallbacks = new();
|
||||
|
||||
/// <summary>
|
||||
/// 存储每个鼠标移动回调的间隔时间(毫秒)
|
||||
/// </summary>
|
||||
private readonly Dictionary<ScriptObject, int> _mouseMoveCallbackIntervals = new();
|
||||
|
||||
/// <summary>
|
||||
/// 存储每个鼠标移动回调的上次调用时间
|
||||
/// </summary>
|
||||
private readonly Dictionary<ScriptObject, DateTime> _lastMouseMoveCallbackTimes = new();
|
||||
|
||||
private KeyEventHandler? _keyDownHandler;
|
||||
private KeyEventHandler? _keyUpHandler;
|
||||
private EventHandler<MouseEventExtArgs>? _mouseDownExtHandler;
|
||||
private EventHandler<MouseEventExtArgs>? _mouseUpExtHandler;
|
||||
private EventHandler<MouseEventExtArgs>? _mouseMoveExtHandler;
|
||||
private EventHandler<MouseEventExtArgs>? _mouseWheelExtHandler;
|
||||
|
||||
private readonly ILogger<KeyMouseHook> _logger = App.GetLogger<KeyMouseHook>();
|
||||
|
||||
public KeyMouseHook()
|
||||
{
|
||||
// 初始化事件处理程序
|
||||
_keyDownHandler = (_, args) =>
|
||||
{
|
||||
// 创建回调列表的副本,避免迭代期间修改集合导致异常
|
||||
var keyDownDataCallbacksCopy = new List<ScriptObject>(_keyDownDataCallbacks);
|
||||
var keyDownCodeCallbacksCopy = new List<ScriptObject>(_keyDownCodeCallbacks);
|
||||
|
||||
// 调用KeyData回调
|
||||
foreach (var callback in keyDownDataCallbacksCopy)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback.InvokeAsFunction(args.KeyData.ToString());
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.Contains("V8 object has been released"))
|
||||
{
|
||||
_logger.LogDebug("V8对象已释放,清除所有回调");
|
||||
RemoveAllListeners();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "执行键盘按下事件回调时发生错误");
|
||||
// 忽略单个回调执行异常,不影响其他回调
|
||||
}
|
||||
}
|
||||
|
||||
// 调用KeyCode回调
|
||||
foreach (var callback in keyDownCodeCallbacksCopy)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback.InvokeAsFunction(args.KeyCode.ToString());
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.Contains("V8 object has been released"))
|
||||
{
|
||||
_logger.LogDebug("V8对象已释放,清除所有回调");
|
||||
RemoveAllListeners();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "执行键盘按下事件回调时发生错误");
|
||||
// 忽略单个回调执行异常,不影响其他回调
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_keyUpHandler = (_, args) =>
|
||||
{
|
||||
// 创建回调列表的副本,避免迭代期间修改集合导致异常
|
||||
var keyUpDataCallbacksCopy = new List<ScriptObject>(_keyUpDataCallbacks);
|
||||
var keyUpCodeCallbacksCopy = new List<ScriptObject>(_keyUpCodeCallbacks);
|
||||
|
||||
// 调用KeyData回调
|
||||
foreach (var callback in keyUpDataCallbacksCopy)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback.InvokeAsFunction(args.KeyData.ToString());
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.Contains("V8 object has been released"))
|
||||
{
|
||||
_logger.LogDebug("V8对象已释放,清除所有回调");
|
||||
RemoveAllListeners();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "执行键盘释放事件回调时发生错误");
|
||||
// 忽略单个回调执行异常,不影响其他回调
|
||||
}
|
||||
}
|
||||
|
||||
// 调用KeyCode回调
|
||||
foreach (var callback in keyUpCodeCallbacksCopy)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback.InvokeAsFunction(args.KeyCode.ToString());
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.Contains("V8 object has been released"))
|
||||
{
|
||||
_logger.LogDebug("V8对象已释放,清除所有回调");
|
||||
RemoveAllListeners();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "执行键盘释放事件回调时发生错误");
|
||||
// 忽略单个回调执行异常,不影响其他回调
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_mouseDownExtHandler = (_, args) =>
|
||||
{
|
||||
// 创建回调列表的副本,避免迭代期间修改集合导致异常
|
||||
var mouseDownCallbacksCopy = new List<ScriptObject>(_mouseDownCallbacks);
|
||||
|
||||
foreach (var callback in mouseDownCallbacksCopy)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback.InvokeAsFunction(args.Button.ToString(), args.X, args.Y);
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.Contains("V8 object has been released"))
|
||||
{
|
||||
_logger.LogDebug("V8对象已释放,清除所有回调");
|
||||
RemoveAllListeners();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "执行鼠标按下事件回调时发生错误");
|
||||
// 忽略单个回调执行异常,不影响其他回调
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_mouseUpExtHandler = (_, args) =>
|
||||
{
|
||||
// 创建回调列表的副本,避免迭代期间修改集合导致异常
|
||||
var mouseUpCallbacksCopy = new List<ScriptObject>(_mouseUpCallbacks);
|
||||
|
||||
foreach (var callback in mouseUpCallbacksCopy)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback.InvokeAsFunction(args.Button.ToString(), args.X, args.Y);
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.Contains("V8 object has been released"))
|
||||
{
|
||||
_logger.LogDebug("V8对象已释放,清除所有回调");
|
||||
RemoveAllListeners();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "执行鼠标释放事件回调时发生错误");
|
||||
// 忽略单个回调执行异常,不影响其他回调
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_mouseMoveExtHandler = (_, args) =>
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
// 创建回调列表的副本,避免迭代期间修改集合导致异常
|
||||
var mouseMoveCallbacksCopy = new List<ScriptObject>(_mouseMoveCallbacks);
|
||||
|
||||
foreach (var callback in mouseMoveCallbacksCopy)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取回调的间隔时间
|
||||
if (_mouseMoveCallbackIntervals.TryGetValue(callback, out var interval))
|
||||
{
|
||||
// 获取上次调用时间
|
||||
if (_lastMouseMoveCallbackTimes.TryGetValue(callback, out var lastTime))
|
||||
{
|
||||
// 计算时间差
|
||||
var timeSpan = now - lastTime;
|
||||
// 如果时间差大于等于间隔时间,则执行回调
|
||||
if (timeSpan.TotalMilliseconds >= interval)
|
||||
{
|
||||
callback.InvokeAsFunction(args.X, args.Y);
|
||||
// 更新上次调用时间
|
||||
_lastMouseMoveCallbackTimes[callback] = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.Contains("V8 object has been released"))
|
||||
{
|
||||
_logger.LogDebug("V8对象已释放,清除所有回调");
|
||||
RemoveAllListeners();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "执行鼠标移动事件回调时发生错误");
|
||||
// 忽略单个回调执行异常,不影响其他回调
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_mouseWheelExtHandler = (_, args) =>
|
||||
{
|
||||
// 创建回调列表的副本,避免迭代期间修改集合导致异常
|
||||
var mouseWheelCallbacksCopy = new List<ScriptObject>(_mouseWheelCallbacks);
|
||||
|
||||
foreach (var callback in mouseWheelCallbacksCopy)
|
||||
{
|
||||
try
|
||||
{
|
||||
callback.InvokeAsFunction(args.Delta, args.X, args.Y);
|
||||
}
|
||||
catch (InvalidOperationException ex) when (ex.Message.Contains("V8 object has been released"))
|
||||
{
|
||||
_logger.LogDebug("V8对象已释放,清除所有回调");
|
||||
RemoveAllListeners();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "执行鼠标滚轮事件回调时发生错误");
|
||||
// 忽略单个回调执行异常,不影响其他回调
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 添加事件监听器
|
||||
AppHook.KeyDown += _keyDownHandler;
|
||||
AppHook.KeyUp += _keyUpHandler;
|
||||
AppHook.MouseDownExt += _mouseDownExtHandler;
|
||||
AppHook.MouseUpExt += _mouseUpExtHandler;
|
||||
AppHook.MouseMoveExt += _mouseMoveExtHandler;
|
||||
AppHook.MouseWheelExt += _mouseWheelExtHandler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册键盘按下事件回调
|
||||
/// </summary>
|
||||
/// <param name="callback">回调函数</param>
|
||||
/// <param name="useCodeOnly">是否仅返回KeyCode,默认为true(仅返回KeyCode)</param>
|
||||
public void OnKeyDown(ScriptObject callback, bool useCodeOnly = true)
|
||||
{
|
||||
if (useCodeOnly)
|
||||
_keyDownCodeCallbacks.Add(callback);
|
||||
else
|
||||
_keyDownDataCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册键盘释放事件回调
|
||||
/// </summary>
|
||||
/// <param name="callback">回调函数</param>
|
||||
/// <param name="useCodeOnly">是否仅返回KeyCode,默认为true(仅返回KeyCode)</param>
|
||||
public void OnKeyUp(ScriptObject callback, bool useCodeOnly = true)
|
||||
{
|
||||
if (useCodeOnly)
|
||||
_keyUpCodeCallbacks.Add(callback);
|
||||
else
|
||||
_keyUpDataCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
public void OnMouseDown(ScriptObject callback)
|
||||
{
|
||||
_mouseDownCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
public void OnMouseUp(ScriptObject callback)
|
||||
{
|
||||
_mouseUpCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册鼠标移动事件回调
|
||||
/// </summary>
|
||||
/// <param name="callback">回调函数</param>
|
||||
/// <param name="interval">回调间隔时间(毫秒),默认200ms</param>
|
||||
public void OnMouseMove(ScriptObject callback, int interval = 200)
|
||||
{
|
||||
_mouseMoveCallbacks.Add(callback);
|
||||
_mouseMoveCallbackIntervals[callback] = interval;
|
||||
_lastMouseMoveCallbackTimes[callback] = DateTime.MinValue;
|
||||
}
|
||||
|
||||
public void OnMouseWheel(ScriptObject callback)
|
||||
{
|
||||
_mouseWheelCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
public void RemoveAllListeners()
|
||||
{
|
||||
_keyDownDataCallbacks.Clear();
|
||||
_keyUpDataCallbacks.Clear();
|
||||
_keyDownCodeCallbacks.Clear();
|
||||
_keyUpCodeCallbacks.Clear();
|
||||
_mouseDownCallbacks.Clear();
|
||||
_mouseUpCallbacks.Clear();
|
||||
_mouseMoveCallbacks.Clear();
|
||||
_mouseWheelCallbacks.Clear();
|
||||
|
||||
_mouseMoveCallbackIntervals.Clear();
|
||||
_lastMouseMoveCallbackTimes.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// 移除所有事件监听器
|
||||
if (_keyDownHandler != null)
|
||||
{
|
||||
AppHook.KeyDown -= _keyDownHandler;
|
||||
_keyDownHandler = null;
|
||||
}
|
||||
|
||||
if (_keyUpHandler != null)
|
||||
{
|
||||
AppHook.KeyUp -= _keyUpHandler;
|
||||
_keyUpHandler = null;
|
||||
}
|
||||
|
||||
if (_mouseDownExtHandler != null)
|
||||
{
|
||||
AppHook.MouseDownExt -= _mouseDownExtHandler;
|
||||
_mouseDownExtHandler = null;
|
||||
}
|
||||
|
||||
if (_mouseUpExtHandler != null)
|
||||
{
|
||||
AppHook.MouseUpExt -= _mouseUpExtHandler;
|
||||
_mouseUpExtHandler = null;
|
||||
}
|
||||
|
||||
if (_mouseMoveExtHandler != null)
|
||||
{
|
||||
AppHook.MouseMoveExt -= _mouseMoveExtHandler;
|
||||
_mouseMoveExtHandler = null;
|
||||
}
|
||||
|
||||
if (_mouseWheelExtHandler != null)
|
||||
{
|
||||
AppHook.MouseWheelExt -= _mouseWheelExtHandler;
|
||||
_mouseWheelExtHandler = null;
|
||||
}
|
||||
|
||||
// 清空回调列表
|
||||
RemoveAllListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
using BetterGenshinImpact.Core.Script.Dependence;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BetterGenshinImpact.Core.Script.Dependence;
|
||||
using BetterGenshinImpact.Core.Script.Dependence.Model;
|
||||
using Microsoft.ClearScript;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenCvSharp;
|
||||
using BetterGenshinImpact.Core.Recognition;
|
||||
using BetterGenshinImpact.GameTask.Model.Area;
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.Core.Script.Utils;
|
||||
using BetterGenshinImpact.GameTask.AutoDomain;
|
||||
using BetterGenshinImpact.GameTask.AutoFight;
|
||||
using BetterGenshinImpact.GameTask.AutoFight.Model;
|
||||
@@ -34,7 +35,8 @@ public class EngineExtend
|
||||
engine.AddHostObject("file", new LimitedFile(workDir)); // 限制文件访问
|
||||
engine.AddHostObject("http", new Http()); // 限制文件访问
|
||||
engine.AddHostObject("notification", new Notification());
|
||||
|
||||
engine.AddHostObject("keyMouseHook", new KeyMouseHook());
|
||||
|
||||
// 任务调度器
|
||||
engine.AddHostObject("dispatcher", new Dispatcher(config));
|
||||
engine.AddHostType("RealtimeTimer", typeof(RealtimeTimer));
|
||||
@@ -71,17 +73,33 @@ public class EngineExtend
|
||||
engine.AddHostType("AutoFightParam", typeof(AutoFightParam));
|
||||
|
||||
|
||||
|
||||
// 添加C#的类型
|
||||
engine.AddHostType(typeof(Task));
|
||||
|
||||
// 导入 CommonJS 模块
|
||||
// 导入 JavaScript 模块
|
||||
// https://microsoft.github.io/ClearScript/2023/01/24/module-interop.html
|
||||
// https://github.com/microsoft/ClearScript/blob/master/ClearScriptTest/V8ModuleTest.cs
|
||||
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading | DocumentAccessFlags.AllowCategoryMismatch;
|
||||
if (searchPaths != null)
|
||||
{
|
||||
engine.DocumentSettings.SearchPath = string.Join(';', searchPaths);
|
||||
var normalizedPaths = new List<string>();
|
||||
foreach (var path in searchPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
var normalizedPath = ScriptUtils.NormalizePath(workDir, path);
|
||||
normalizedPaths.Add(normalizedPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"从 library 字段读取路径 '{path}' 失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (normalizedPaths.Count > 0)
|
||||
{
|
||||
engine.DocumentSettings.SearchPath = string.Join(';', normalizedPaths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,4 +140,4 @@ public class EngineExtend
|
||||
engine.AddHostObject("inputText", GlobalMethod.InputText);
|
||||
#pragma warning restore CS8974 // Converting method group to non-delegate type
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using BetterGenshinImpact.Core.Script.Dependence;
|
||||
using Microsoft.ClearScript.JavaScript;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Script.Project;
|
||||
|
||||
@@ -89,7 +90,16 @@ public class ScriptProject
|
||||
}
|
||||
try
|
||||
{
|
||||
await (Task)engine.Evaluate(code);
|
||||
if (Manifest.Library.Length != 0)
|
||||
{
|
||||
// 清除Document缓存
|
||||
DocumentLoader.Default.DiscardCachedDocuments();
|
||||
await (Task)engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, code);
|
||||
}
|
||||
else
|
||||
{
|
||||
await (Task)engine.Evaluate(code);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ using BetterGenshinImpact.View.Controls.Webview;
|
||||
using BetterGenshinImpact.ViewModel.Pages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -1150,10 +1151,20 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
|
||||
|
||||
_webWindow = null;
|
||||
};
|
||||
|
||||
_webWindow.Panel!.DownloadFolderPath = MapPathingViewModel.PathJsonPath;
|
||||
_webWindow.NavigateToFile(Global.Absolute(@"Assets\Web\ScriptRepo\index.html"));
|
||||
// _webWindow.NavigateToFile(Global.Absolute(@"Assets\Web\ScriptRepo\index.html"));
|
||||
_webWindow.Panel!.OnWebViewInitializedAction = () =>
|
||||
{
|
||||
var assetsPath = Global.Absolute(@"Assets\Web\ScriptRepo");
|
||||
_webWindow.Panel!.WebView.CoreWebView2.SetVirtualHostNameToFolderMapping(
|
||||
"bettergi.local",
|
||||
assetsPath,
|
||||
CoreWebView2HostResourceAccessKind.Allow
|
||||
);
|
||||
|
||||
// _webWindow.Panel!.WebView.CoreWebView2.Navigate("https://bettergi.local/index.html");
|
||||
|
||||
_webWindow.Panel!.WebView.CoreWebView2.AddHostObjectToScript("repoWebBridge", new RepoWebBridge());
|
||||
|
||||
// 允许内部外链使用默认浏览器打开
|
||||
@@ -1169,6 +1180,8 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
|
||||
e.Handled = true;
|
||||
};
|
||||
};
|
||||
|
||||
_webWindow.NavigateToUri(new Uri("https://bettergi.local/index.html"));
|
||||
_webWindow.Show();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -8,6 +8,7 @@ using BetterGenshinImpact.View.Windows;
|
||||
using BetterGenshinImpact.ViewModel.Message;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Net;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Script.WebView;
|
||||
|
||||
@@ -25,6 +26,11 @@ public sealed class RepoWebBridge
|
||||
".vue", ".css", ".html", ".csv", ".xml",
|
||||
".yaml", ".yml", ".ini", ".config"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> AllowedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".ico"
|
||||
};
|
||||
|
||||
public async Task<string> GetRepoJson()
|
||||
{
|
||||
@@ -75,25 +81,45 @@ public sealed class RepoWebBridge
|
||||
{
|
||||
try
|
||||
{
|
||||
// URL 解码路径(处理中文文件名)
|
||||
relPath = WebUtility.UrlDecode(relPath);
|
||||
|
||||
string filePath = Path.Combine(ScriptRepoUpdater.CenterRepoPath, "repo", relPath)
|
||||
.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
|
||||
// 验证解析后的路径在允许的目录范围内
|
||||
string normalizedBasePath = Path.GetFullPath(Path.Combine(ScriptRepoUpdater.CenterRepoPath, "repo"));
|
||||
string normalizedFilePath = Path.GetFullPath(filePath);
|
||||
if (!normalizedFilePath.StartsWith(normalizedBasePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "404";
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return "404";
|
||||
}
|
||||
|
||||
string extension = Path.GetExtension(filePath);
|
||||
return AllowedTextExtensions.Contains(extension)
|
||||
? await File.ReadAllTextAsync(filePath)
|
||||
: "404";
|
||||
string extension = Path.GetExtension(filePath).ToLower();
|
||||
|
||||
if (AllowedTextExtensions.Contains(extension))
|
||||
{
|
||||
return await File.ReadAllTextAsync(filePath);
|
||||
}
|
||||
else if (AllowedImageExtensions.Contains(extension))
|
||||
{
|
||||
byte[] bytes = await File.ReadAllBytesAsync(filePath);
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
|
||||
return "404";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "404";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> UpdateSubscribed(string path)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -533,7 +533,7 @@ public class AutoDomainTask : ISoloTask
|
||||
var ocrListLeft = ra.Find(AutoFightAssets.Instance.AbnormalIconRa);
|
||||
return (ocrList.Any(t => t.Text.Contains(leyLineDisorderLocalizedString) ||
|
||||
t.Text.Contains(clickanywheretocloseLocalizedString))) || ocrListLeft.IsExist();
|
||||
}, _ct, 20, 500);
|
||||
}, _ct, 40, 500);
|
||||
if (!domainTipFound)
|
||||
{
|
||||
Logger.LogWarning("秘境提示未出现或未能点击。");
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 607 B After Width: | Height: | Size: 1.8 KiB |
@@ -52,7 +52,7 @@
|
||||
"奇偶(男)",
|
||||
"MannequinBoy"
|
||||
],
|
||||
"id": "20000001",
|
||||
"id": "10000117",
|
||||
"name": "奇偶(男)",
|
||||
"nameEn": "MannequinBoy",
|
||||
"weapon": "1"
|
||||
@@ -62,7 +62,7 @@
|
||||
"奇偶(女)",
|
||||
"MannequinGirl"
|
||||
],
|
||||
"id": "20000002",
|
||||
"id": "10000118",
|
||||
"name": "奇偶(女)",
|
||||
"nameEn": "MannequinGirl",
|
||||
"weapon": "1"
|
||||
@@ -1981,5 +1981,45 @@
|
||||
"nameEn": "Nefer",
|
||||
"skillCD": 9,
|
||||
"weapon": "10"
|
||||
},
|
||||
{
|
||||
"alias": [
|
||||
"杜林",
|
||||
"Durin"
|
||||
],
|
||||
"burstCD": 18,
|
||||
"id": "10000123",
|
||||
"name": "杜林",
|
||||
"nameEn": "Durin",
|
||||
"skillCD": 12,
|
||||
"weapon": "10"
|
||||
},
|
||||
{
|
||||
"alias": [
|
||||
"雅珂达",
|
||||
"Jahoda"
|
||||
],
|
||||
"burstCD": 18,
|
||||
"id": "10000124",
|
||||
"name": "雅珂达",
|
||||
"nameEn": "Jahoda",
|
||||
"skillCD": 15,
|
||||
"weapon": "10"
|
||||
},
|
||||
{
|
||||
"alias": [
|
||||
"哥伦比娅",
|
||||
"Columbina",
|
||||
"少女",
|
||||
"库塔尔",
|
||||
"月神",
|
||||
"月之少女"
|
||||
],
|
||||
"burstCD": 15,
|
||||
"id": "10000125",
|
||||
"name": "哥伦比娅",
|
||||
"nameEn": "Columbina",
|
||||
"skillCD": 17,
|
||||
"weapon": "10"
|
||||
}
|
||||
]
|
||||
@@ -466,7 +466,7 @@ public class CombatScenes : IDisposable
|
||||
if (PartyAvatarSideIndexHelper.CountIndexRect(imageRegion) == Avatars.Length)
|
||||
{
|
||||
bool res = RefreshTeamAvatarIndexRectList(imageRegion);
|
||||
_logger.LogWarning("多次识别出战角色失败,尝试刷新角色编号位置(处理草露问题),刷新结果:{Result}", res ? "成功" : "失败");
|
||||
_logger.LogWarning("多次识别出战角色失败,尝试刷新角色编号位置,刷新结果:{Result}", res ? "成功" : "失败");
|
||||
imageRegion.SrcMat.SaveImage(Global.Absolute("log\\refresh_avatar_index_rect.png"));
|
||||
if (res)
|
||||
{
|
||||
|
||||
@@ -35,10 +35,12 @@ public class PartyAvatarSideIndexHelper
|
||||
{
|
||||
autoFightAssets = AutoFightAssets.Instance;
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
logger = TaskControl.Logger;
|
||||
}
|
||||
|
||||
var status = new MultiGameStatus();
|
||||
// 判断当前联机人数
|
||||
var pRaList = imageRegion.FindMulti(autoFightAssets.PRa);
|
||||
@@ -246,7 +248,7 @@ public class PartyAvatarSideIndexHelper
|
||||
|
||||
public static List<Rect> GetAvatarSideIconRectFromIndexRect(List<Rect> indexRect, ISystemInfo systemInfo)
|
||||
{
|
||||
return indexRect.Select(r=> GetAvatarSideIconRectFromIndexRect(r, systemInfo)).ToList();
|
||||
return indexRect.Select(r => GetAvatarSideIconRectFromIndexRect(r, systemInfo)).ToList();
|
||||
}
|
||||
|
||||
public static bool IsIntersecting(double y1, double h1, double y2, double h2)
|
||||
@@ -373,8 +375,7 @@ public class PartyAvatarSideIndexHelper
|
||||
Mat[] mats = new Mat[rectArray.Length];
|
||||
try
|
||||
{
|
||||
var whiteCount = 0;
|
||||
var notWhiteRectNum = 0;
|
||||
int whiteCount = 0, notWhiteRectNum = 0;
|
||||
var mat = imageRegion.CacheGreyMat;
|
||||
for (int i = 0; i < rectArray.Length; i++)
|
||||
{
|
||||
@@ -396,8 +397,22 @@ public class PartyAvatarSideIndexHelper
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用更加靠谱的差值识别(-1是未识别)
|
||||
return ImageDifferenceDetector.FindMostDifferentImage(mats);
|
||||
// 方法2:边缘像素白色比例
|
||||
int m2 = FindActiveIndexRectByEdgeColor(mats);
|
||||
if (m2 > 0)
|
||||
{
|
||||
return m2;
|
||||
}
|
||||
|
||||
// 方法3:使用更加靠谱的差值识别(-1是未识别),但是不支持非满队
|
||||
if (mats.Length == 4)
|
||||
{
|
||||
return ImageDifferenceDetector.FindMostDifferentImage(mats);
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -407,15 +422,13 @@ public class PartyAvatarSideIndexHelper
|
||||
mat?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static bool IsWhiteRect(Mat indexMat)
|
||||
{
|
||||
|
||||
var count1 = OpenCvCommonHelper.CountGrayMatColor(indexMat, 251, 255); // 白
|
||||
var count2 = OpenCvCommonHelper.CountGrayMatColor(indexMat, 50, 54); // 黑色文字
|
||||
if ((count1 + count2) * 1.0 / (indexMat.Width * indexMat.Height) > 0.4)
|
||||
if ((count1 + count2) * 1.0 / (indexMat.Width * indexMat.Height) > 0.35)
|
||||
{
|
||||
// Debug.WriteLine($"白色矩形占比{(count1 + count2) * 1.0 / (indexMat.Width * indexMat.Height)}");
|
||||
return true;
|
||||
@@ -443,7 +456,7 @@ public class PartyAvatarSideIndexHelper
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < rectArray.Length; i++)
|
||||
{
|
||||
if (IsIntersecting(curr.Y, curr.Height, rectArray[i].Y, rectArray[i].Height))
|
||||
@@ -454,4 +467,121 @@ public class PartyAvatarSideIndexHelper
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过边缘像素颜色识别出战角色编号
|
||||
/// </summary>
|
||||
/// <param name="mats"></param>
|
||||
/// <returns></returns>
|
||||
public static int FindActiveIndexRectByEdgeColor(Mat[] mats)
|
||||
{
|
||||
try
|
||||
{
|
||||
int whiteCount = 0, notWhiteRectNum = 0;
|
||||
for (int i = 0; i < mats.Length; i++)
|
||||
{
|
||||
if (CalculateWhiteEdgePixelsRatio(mats[i]) > 0.5)
|
||||
{
|
||||
whiteCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
notWhiteRectNum = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (whiteCount == mats.Length - 1)
|
||||
{
|
||||
return notWhiteRectNum;
|
||||
}
|
||||
else if (whiteCount == mats.Length)
|
||||
{
|
||||
// 如果四个都是白色,那就找内部有没有黑色
|
||||
int blackCount = 0, notBlackRectNum = -1;
|
||||
for (int i = 0; i < mats.Length; i++)
|
||||
{
|
||||
var count = OpenCvCommonHelper.CountGrayMatColorC1(mats[i], 50, 50); // 黑字
|
||||
if (count > 0)
|
||||
{
|
||||
blackCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
notBlackRectNum = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (notBlackRectNum >= 1)
|
||||
{
|
||||
TaskControl.Logger.LogDebug("当前所有编号边缘均为白色(背景过白),通过内部黑色像素识别出战编号为{Index},存在黑色数字的角色编号有{C1}个,总角色数量{C2}", notBlackRectNum, blackCount, mats.Length);
|
||||
return notBlackRectNum;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(e);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算灰度图最边缘一圈中纯白色(255)像素的占比
|
||||
/// </summary>
|
||||
/// <returns>返回纯白像素占比 (0.0 到 1.0)</returns>
|
||||
public static double CalculateWhiteEdgePixelsRatio(Mat image)
|
||||
{
|
||||
int whiteCount = 0;
|
||||
int height = image.Height;
|
||||
int width = image.Width;
|
||||
|
||||
// 如果图片太小,无法获取边缘
|
||||
if (height < 1 || width < 1)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// 计算总边缘像素数
|
||||
int totalCount = 2 * (width + height - 2);
|
||||
|
||||
// 顶边和底边
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
// 顶边
|
||||
if (image.At<byte>(0, x) == 255)
|
||||
{
|
||||
whiteCount++;
|
||||
}
|
||||
|
||||
// 底边(避免只有一行时重复计数)
|
||||
if (height > 1 && image.At<byte>(height - 1, x) == 255)
|
||||
{
|
||||
whiteCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 左边和右边(不包括四个角,因为已经在顶边和底边中计算过)
|
||||
for (int y = 1; y < height - 1; y++)
|
||||
{
|
||||
// 左边
|
||||
if (image.At<byte>(y, 0) == 255)
|
||||
{
|
||||
whiteCount++;
|
||||
}
|
||||
|
||||
// 右边(避免只有一列时重复计数)
|
||||
if (width > 1 && image.At<byte>(y, width - 1) == 255)
|
||||
{
|
||||
whiteCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算并返回占比
|
||||
return totalCount > 0 ? (double)whiteCount / totalCount : 0.0;
|
||||
}
|
||||
}
|
||||
@@ -859,10 +859,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
int hExtra = _cur.Height, vExtra = _cur.Height / 4;
|
||||
blackboard.fishBoxRect = new Rect(_cur.X - hExtra, _cur.Y - vExtra,
|
||||
(topMat.Width / 2 - _cur.X) * 2 + hExtra * 2, _cur.Height + vExtra * 2);
|
||||
// VisionContext.Instance().DrawContent.PutRect("FishBox", _fishBoxRect.ToRectDrawable(new Pen(Color.LightPink, 2)));
|
||||
using var boxRa = imageRegion.Derive(blackboard.fishBoxRect);
|
||||
using var pen = new System.Drawing.Pen(Color.LightPink, 2);
|
||||
boxRa.DrawSelf("FishBox", pen);
|
||||
boxRa.DrawSelf("FishBox", System.Drawing.Pens.LightPink);
|
||||
logger.LogInformation(" 识别到钓鱼框");
|
||||
return BehaviourStatus.Succeeded;
|
||||
}
|
||||
|
||||
@@ -30,11 +30,11 @@ public class BigFishType
|
||||
public static readonly BigFishType Rapidfish = new("rapidfish", BaitType.SpinelgrainBait, "斗士急流鱼", 9);
|
||||
public static readonly BigFishType PhonyUnihornfish = new("phony unihornfish", BaitType.EmberglowBait, "燃素独角鱼", 10);
|
||||
public static readonly BigFishType MagmaRapidfish = new("magma rapidfish", BaitType.EmberglowBait, "炽岩斗士急流鱼", 9);
|
||||
public static readonly BigFishType SecretSourceScoutSweeper = new ("secret source scout sweeper", BaitType.EmberglowBait, "秘源机关・巡戒使", 9);
|
||||
public static readonly BigFishType SecretSourceScoutSweeper = new ("secret source", BaitType.EmberglowBait, "秘源机关・巡戒使", 9);
|
||||
|
||||
public static readonly BigFishType MaulerShark = new ("mauler shark", BaitType.RefreshingLakkaBait, "凶凶鲨", 9);
|
||||
public static readonly BigFishType CrystalEye = new("crystal eye", BaitType.RefreshingLakkaBait, "明眼鱼", 9);
|
||||
public static readonly BigFishType AxeheadFish = new ("axehead fish", BaitType.BerryBait, "巨斧鱼", 9);
|
||||
public static readonly BigFishType AxeheadFish = new ("axehead", BaitType.BerryBait, "巨斧鱼", 9);
|
||||
|
||||
public static IEnumerable<BigFishType> Values
|
||||
{
|
||||
@@ -59,6 +59,7 @@ public class BigFishType
|
||||
yield return Rapidfish;
|
||||
yield return PhonyUnihornfish;
|
||||
yield return MagmaRapidfish;
|
||||
yield return SecretSourceScoutSweeper;
|
||||
yield return MaulerShark;
|
||||
yield return CrystalEye;
|
||||
yield return AxeheadFish;
|
||||
@@ -96,4 +97,4 @@ public class BigFishType
|
||||
{
|
||||
return e.NetIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -996,6 +996,83 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1115,
|
||||
"nameEn": "escoffier",
|
||||
"type": "character",
|
||||
"name": "爱可菲",
|
||||
"hp": 11,
|
||||
"energy": 2,
|
||||
"element": "冰元素",
|
||||
"weapon": "长柄武器",
|
||||
"skills": [
|
||||
{
|
||||
"nameEn": "kitchen_skills",
|
||||
"name": "后厨手艺",
|
||||
"skillTag": [
|
||||
"普通攻击"
|
||||
],
|
||||
"cost": [
|
||||
{
|
||||
"id": 1101,
|
||||
"nameEn": "cryo",
|
||||
"type": "冰元素",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"id": 1109,
|
||||
"nameEn": "unaligned_element",
|
||||
"type": "无色元素",
|
||||
"count": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nameEn": "lowtemperature_cooking",
|
||||
"name": "低温烹饪",
|
||||
"skillTag": [
|
||||
"元素战技"
|
||||
],
|
||||
"cost": [
|
||||
{
|
||||
"id": 1101,
|
||||
"nameEn": "cryo",
|
||||
"type": "冰元素",
|
||||
"count": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nameEn": "scoring_cuts",
|
||||
"name": "花刀技法",
|
||||
"skillTag": [
|
||||
"元素爆发"
|
||||
],
|
||||
"cost": [
|
||||
{
|
||||
"id": 1101,
|
||||
"nameEn": "cryo",
|
||||
"type": "冰元素",
|
||||
"count": 3
|
||||
},
|
||||
{
|
||||
"id": 1110,
|
||||
"nameEn": "energy",
|
||||
"type": "充能",
|
||||
"count": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nameEn": "constant_offthecuff_cookery",
|
||||
"name": "时时刻刻的即兴料理",
|
||||
"skillTag": [
|
||||
"被动技能"
|
||||
],
|
||||
"cost": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1201,
|
||||
"nameEn": "barbara",
|
||||
@@ -1715,7 +1792,7 @@
|
||||
"nameEn": "furina",
|
||||
"type": "character",
|
||||
"name": "芙宁娜",
|
||||
"hp": 10,
|
||||
"hp": 12,
|
||||
"energy": 2,
|
||||
"element": "水元素",
|
||||
"weapon": "单手剑",
|
||||
@@ -3001,6 +3078,75 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1316,
|
||||
"nameEn": "gaming",
|
||||
"type": "character",
|
||||
"name": "嘉明",
|
||||
"hp": 10,
|
||||
"energy": 3,
|
||||
"element": "火元素",
|
||||
"weapon": "双手剑",
|
||||
"skills": [
|
||||
{
|
||||
"nameEn": "stellar_rend",
|
||||
"name": "刃爪悬星",
|
||||
"skillTag": [
|
||||
"普通攻击"
|
||||
],
|
||||
"cost": [
|
||||
{
|
||||
"id": 1103,
|
||||
"nameEn": "pyro",
|
||||
"type": "火元素",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"id": 1109,
|
||||
"nameEn": "unaligned_element",
|
||||
"type": "无色元素",
|
||||
"count": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nameEn": "bestial_ascent",
|
||||
"name": "瑞兽登高楼",
|
||||
"skillTag": [
|
||||
"元素战技"
|
||||
],
|
||||
"cost": [
|
||||
{
|
||||
"id": 1103,
|
||||
"nameEn": "pyro",
|
||||
"type": "火元素",
|
||||
"count": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nameEn": "suannis_gilded_dance",
|
||||
"name": "璨焰金猊舞",
|
||||
"skillTag": [
|
||||
"元素爆发"
|
||||
],
|
||||
"cost": [
|
||||
{
|
||||
"id": 1103,
|
||||
"nameEn": "pyro",
|
||||
"type": "火元素",
|
||||
"count": 3
|
||||
},
|
||||
{
|
||||
"id": 1110,
|
||||
"nameEn": "energy",
|
||||
"type": "充能",
|
||||
"count": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1401,
|
||||
"nameEn": "fischl",
|
||||
@@ -3927,7 +4073,7 @@
|
||||
"nameEn": "iansan",
|
||||
"type": "character",
|
||||
"name": "伊安珊",
|
||||
"hp": 12,
|
||||
"hp": 11,
|
||||
"energy": 2,
|
||||
"element": "雷元素",
|
||||
"weapon": "长柄武器",
|
||||
@@ -8690,6 +8836,83 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2604,
|
||||
"nameEn": "black_serpent_knight_rockbreaker_ax",
|
||||
"type": "character",
|
||||
"name": "黑蛇骑士·摧岩之钺",
|
||||
"hp": 12,
|
||||
"energy": 2,
|
||||
"element": "岩元素",
|
||||
"weapon": "其他武器",
|
||||
"skills": [
|
||||
{
|
||||
"nameEn": "supreme_strike",
|
||||
"name": "顶位迅斩",
|
||||
"skillTag": [
|
||||
"普通攻击"
|
||||
],
|
||||
"cost": [
|
||||
{
|
||||
"id": 1106,
|
||||
"nameEn": "geo",
|
||||
"type": "岩元素",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"id": 1109,
|
||||
"nameEn": "unaligned_element",
|
||||
"type": "无色元素",
|
||||
"count": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nameEn": "axe_and_aegis",
|
||||
"name": "斧盾震击",
|
||||
"skillTag": [
|
||||
"元素战技"
|
||||
],
|
||||
"cost": [
|
||||
{
|
||||
"id": 1106,
|
||||
"nameEn": "geo",
|
||||
"type": "岩元素",
|
||||
"count": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nameEn": "stone_stance",
|
||||
"name": "坚岩姿态",
|
||||
"skillTag": [
|
||||
"元素爆发"
|
||||
],
|
||||
"cost": [
|
||||
{
|
||||
"id": 1106,
|
||||
"nameEn": "geo",
|
||||
"type": "岩元素",
|
||||
"count": 3
|
||||
},
|
||||
{
|
||||
"id": 1110,
|
||||
"nameEn": "energy",
|
||||
"type": "充能",
|
||||
"count": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nameEn": "attacking_momentum",
|
||||
"name": "攻阵气势",
|
||||
"skillTag": [
|
||||
"被动技能"
|
||||
],
|
||||
"cost": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2701,
|
||||
"nameEn": "jadeplume_terrorshroom",
|
||||
|
||||
@@ -18,22 +18,23 @@ public class MiningHandler : IActionHandler
|
||||
{
|
||||
private readonly string[] _miningActions =
|
||||
[
|
||||
"诺艾尔 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"娜维娅 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"玛薇卡 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"爱诺 attack(0.8)",
|
||||
"诺艾尔 attack(1.25)",
|
||||
"玛薇卡 attack(0.20),j,wait(0.5),attack(0.6)",
|
||||
"迪希雅 attack(0.22),j,wait(0.5),attack(0.22),j,wait(0.5),attack(0.45)",
|
||||
"娜维娅 attack(1.25)",
|
||||
"辛焱 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"重云 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"迪卢克 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"荒泷一斗 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"迪希雅 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"荒泷一斗 attack(0.1),charge(1.9),j,wait(0.5),attack(0.2)",
|
||||
"基尼奇 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"菲米尼 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"卡维 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"优菈 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"嘉明 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"多莉 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"多莉 attack(2.0)",
|
||||
"北斗 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"早柚 attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8),attack(0.28),jump,wait(0.8)",
|
||||
"早柚 attack(0.23),j,wait(0.6),attack(0.23),j,wait(0.6),attack(0.23),j,wait(0.6),attack(0.23),j,wait(0.6)",
|
||||
"迪卢克 charge(3.15),j",
|
||||
"坎蒂丝 e(hold,wait)",
|
||||
"雷泽 e(hold,wait)",
|
||||
"凝光 attack(4.0)",
|
||||
|
||||
@@ -35,6 +35,13 @@ public class UseGadgetHandler : IActionHandler
|
||||
}
|
||||
else
|
||||
{
|
||||
double maxWaitSeconds = 100;
|
||||
if (waypointForTrack != null
|
||||
&& !string.IsNullOrEmpty(waypointForTrack.ActionParams))
|
||||
{
|
||||
double.TryParse(waypointForTrack.ActionParams, out maxWaitSeconds); // 最大等待时间,单位秒
|
||||
}
|
||||
|
||||
var screen = CaptureToRectArea();
|
||||
var cd = GetCurrentCd(screen);
|
||||
if (cd > 100)
|
||||
@@ -46,7 +53,17 @@ public class UseGadgetHandler : IActionHandler
|
||||
{
|
||||
Logger.LogInformation("小道具正在CD中,等待CD结束 :{Cd}秒", cd);
|
||||
// 等待小道具CD结束
|
||||
var waitTime = (int)(cd * 1000) + 100; // 等待CD结束后再继续
|
||||
int waitTime; // 等待CD结束后再继续
|
||||
if (cd > maxWaitSeconds)
|
||||
{
|
||||
waitTime = (int)(maxWaitSeconds * 1000);
|
||||
Logger.LogInformation("CD过长,使用最大CD:{Max}秒", maxWaitSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
waitTime = (int)(cd * 1000) + 100; // 等待CD结束后再继续
|
||||
}
|
||||
|
||||
await Delay(waitTime, ct);
|
||||
Simulation.SendInput.SimulateAction(GIActions.QuickUseGadget);
|
||||
}
|
||||
@@ -55,6 +72,7 @@ public class UseGadgetHandler : IActionHandler
|
||||
Simulation.SendInput.SimulateAction(GIActions.QuickUseGadget);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogInformation("使用小道具");
|
||||
await Delay(300, ct);
|
||||
}
|
||||
|
||||
@@ -721,6 +721,7 @@ public class PathExecutor
|
||||
var fastMode = false;
|
||||
var prevPositions = new List<Point2f>();
|
||||
var fastModeColdTime = DateTime.MinValue;
|
||||
var prevNotTooFarPosition = position;
|
||||
int num = 0, distanceTooFarRetryCount = 0, consecutiveRotationCountBeyondAngle = 0;
|
||||
|
||||
// 按下w,一直走
|
||||
@@ -790,10 +791,22 @@ public class PathExecutor
|
||||
{
|
||||
Logger.LogWarning($"距离过远({position.X},{position.Y})->({waypoint.X},{waypoint.Y})={distance},重试");
|
||||
}
|
||||
// 取余减少判断频率
|
||||
if (distanceTooFarRetryCount % 10 == 0)
|
||||
{
|
||||
await ResolveAnomalies(screen);
|
||||
Logger.LogInformation($"重置到上次正确识别的坐标 ({prevNotTooFarPosition.X},{prevNotTooFarPosition.Y})");
|
||||
Navigation.SetPrevPosition(prevNotTooFarPosition.X, prevNotTooFarPosition.Y);
|
||||
// 淡入淡出特效
|
||||
await Delay(500, ct);
|
||||
}
|
||||
await Delay(50, ct);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
prevNotTooFarPosition = position;
|
||||
}
|
||||
|
||||
// 非攀爬状态下,检测是否卡死(脱困触发器)
|
||||
|
||||
BIN
BetterGenshinImpact/GameTask/AutoPick/Assets/1920x1080/L.png
Normal file
BIN
BetterGenshinImpact/GameTask/AutoPick/Assets/1920x1080/L.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 308 B |
@@ -16,6 +16,8 @@ public class AutoPickAssets : BaseAssets<AutoPickAssets>
|
||||
public RecognitionObject FRo;
|
||||
public RecognitionObject ChatIconRo;
|
||||
public RecognitionObject SettingsIconRo;
|
||||
public RecognitionObject LRo;
|
||||
|
||||
|
||||
public User32.VK PickVk = User32.VK.VK_F;
|
||||
public RecognitionObject PickRo;
|
||||
@@ -51,6 +53,18 @@ public class AutoPickAssets : BaseAssets<AutoPickAssets>
|
||||
DrawOnWindow = false,
|
||||
DrawOnWindowPen = new Pen(Color.Chocolate, 2)
|
||||
}.InitTemplate();
|
||||
|
||||
LRo = new RecognitionObject
|
||||
{
|
||||
Name = "L",
|
||||
RecognitionType = RecognitionTypes.TemplateMatch,
|
||||
TemplateImageMat = GameTaskManager.LoadAssetImage("AutoPick", "L.png"),
|
||||
RegionOfInterest = new Rect(CaptureRect.Width-(int)(110 * AssetScale),
|
||||
(int)(550 * AssetScale),
|
||||
(int)(70 * AssetScale),
|
||||
(int)(100 * AssetScale)),
|
||||
}.InitTemplate();
|
||||
|
||||
|
||||
PickRo = FRo;
|
||||
var keyName = TaskContext.Instance().Config.AutoPickConfig.PickKey;
|
||||
|
||||
@@ -38,7 +38,7 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
/// 拾取黑名单
|
||||
/// </summary>
|
||||
private HashSet<string> _blackList = [];
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 拾取黑名单(模糊匹配)
|
||||
/// </summary>
|
||||
@@ -78,7 +78,8 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
{
|
||||
_blackList.UnionWith(userBlackList);
|
||||
}
|
||||
_fuzzyBlackList = ReadTextList(@"User\pick_black_lists.txt");
|
||||
|
||||
_fuzzyBlackList = ReadTextList(@"User\pick_black_lists.txt");
|
||||
}
|
||||
|
||||
if (config.WhiteListEnabled)
|
||||
@@ -125,7 +126,7 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
private List<string> ReadTextList(string textFilePath)
|
||||
{
|
||||
try
|
||||
@@ -195,12 +196,19 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
var scale = TaskContext.Instance().SystemInfo.AssetScale;
|
||||
var config = TaskContext.Instance().Config.AutoPickConfig;
|
||||
|
||||
// 存在 L 键位是千星奇遇,无需拾取
|
||||
using var lKeyRa = content.CaptureRectArea.Find(_autoPickAssets.LRo);
|
||||
if (lKeyRa.IsExist())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 识别到拾取键,开始识别物品图标
|
||||
var isExcludeIcon = false;
|
||||
_autoPickAssets.ChatIconRo.RegionOfInterest = new Rect(
|
||||
foundRectArea.X + (int)(config.ItemIconLeftOffset * scale), foundRectArea.Y,
|
||||
(int)((config.ItemTextLeftOffset - config.ItemIconLeftOffset) * scale), foundRectArea.Height);
|
||||
var chatIconRa = content.CaptureRectArea.Find(_autoPickAssets.ChatIconRo);
|
||||
using var chatIconRa = content.CaptureRectArea.Find(_autoPickAssets.ChatIconRo);
|
||||
speedTimer.Record("识别聊天图标");
|
||||
if (!chatIconRa.IsEmpty())
|
||||
{
|
||||
@@ -210,7 +218,7 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
else
|
||||
{
|
||||
_autoPickAssets.SettingsIconRo.RegionOfInterest = _autoPickAssets.ChatIconRo.RegionOfInterest;
|
||||
var settingsIconRa = content.CaptureRectArea.Find(_autoPickAssets.SettingsIconRo);
|
||||
using var settingsIconRa = content.CaptureRectArea.Find(_autoPickAssets.SettingsIconRo);
|
||||
speedTimer.Record("识别设置图标");
|
||||
if (!settingsIconRa.IsEmpty())
|
||||
{
|
||||
@@ -255,8 +263,7 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
return;
|
||||
}
|
||||
|
||||
// var textMat = new Mat(content.CaptureRectArea.SrcGreyMat, textRect);
|
||||
var gradMat = new Mat(content.CaptureRectArea.CacheGreyMat,
|
||||
using var gradMat = new Mat(content.CaptureRectArea.CacheGreyMat,
|
||||
new Rect(textRect.X, textRect.Y, textRect.Width, Math.Min(textRect.Height, 3)));
|
||||
var avgGrad = gradMat.Sobel(MatType.CV_32F, 1, 0).Mean().Val0;
|
||||
if (avgGrad < -3)
|
||||
@@ -273,16 +280,17 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
}
|
||||
else
|
||||
{
|
||||
var textMat = new Mat(content.CaptureRectArea.SrcMat, textRect);
|
||||
using var textMat = new Mat(content.CaptureRectArea.SrcMat, textRect);
|
||||
var boundingRect = TextRectExtractor.GetTextBoundingRect(textMat);
|
||||
// var boundingRect = new Rect(); // 不使用自己写的文字区域提取
|
||||
// 如果找到有效区域
|
||||
if (boundingRect.X <20 && boundingRect.Width > 5 && boundingRect.Height > 5)
|
||||
if (boundingRect.X < 20 && boundingRect.Width > 5 && boundingRect.Height > 5)
|
||||
{
|
||||
// 截取只包含文字的区域
|
||||
var textOnlyMat = new Mat(textMat, new Rect(0, 0,
|
||||
using var textOnlyMat = new Mat(textMat, new Rect(0, 0,
|
||||
boundingRect.Right + 5 < textMat.Width ? boundingRect.Right + 5 : textMat.Width, textMat.Height));
|
||||
text = OcrFactory.Paddle.OcrWithoutDetector(textOnlyMat);
|
||||
|
||||
|
||||
// if (RuntimeHelper.IsDebug)
|
||||
// {
|
||||
// // 如果不等于正确文字,则保存图片
|
||||
@@ -344,7 +352,8 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_fuzzyBlackList.Count>0)
|
||||
|
||||
if (_fuzzyBlackList.Count > 0)
|
||||
{
|
||||
if (_fuzzyBlackList.Any(item => text.Contains(item)))
|
||||
{
|
||||
@@ -360,8 +369,6 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
}
|
||||
|
||||
speedTimer.DebugPrint();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private bool DoNotPick(string text)
|
||||
@@ -378,12 +385,13 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 挪德卡莱聚所中文名特殊处理,不拾取
|
||||
if (text.Contains("聚所"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (text.Contains("霜月") && text.Contains("坊"))
|
||||
{
|
||||
return true;
|
||||
@@ -394,6 +402,11 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
return true;
|
||||
}
|
||||
|
||||
if (text.Contains("西风成垒") || text.Contains("望崖营壁") || text.Contains("魔女的花园"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -468,21 +481,21 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
// 0. 首先替换相似的括号字符并删除换行符、空格,使用Span<char>进行原地替换以获得最佳性能
|
||||
Span<char> chars = stackalloc char[text.Length];
|
||||
text.AsSpan().CopyTo(chars);
|
||||
|
||||
|
||||
int writeIndex = 0;
|
||||
bool hasChanges = false;
|
||||
|
||||
|
||||
for (int i = 0; i < chars.Length; i++)
|
||||
{
|
||||
char c = chars[i];
|
||||
|
||||
|
||||
// 跳过换行符、回车符、空格、制表符等空白字符
|
||||
if (char.IsWhiteSpace(c))
|
||||
{
|
||||
hasChanges = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 替换括号字符
|
||||
if (c == '【' || c == '[')
|
||||
{
|
||||
@@ -529,11 +542,11 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
|
||||
// 获取清理后的文字
|
||||
var cleanedSpan = span.Slice(start, end - start + 1);
|
||||
|
||||
|
||||
// 3. 检查并补充引号配对
|
||||
bool hasLeftQuote = false;
|
||||
bool hasRightQuote = false;
|
||||
|
||||
|
||||
// 快速扫描是否存在引号
|
||||
for (int i = 0; i < cleanedSpan.Length; i++)
|
||||
{
|
||||
@@ -556,9 +569,7 @@ public partial class AutoPickTrigger : ITaskTrigger
|
||||
Debug.WriteLine("补充缺失的左引号");
|
||||
return string.Concat("「", cleanedSpan);
|
||||
}
|
||||
|
||||
|
||||
return cleanedSpan.ToString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -36,6 +36,12 @@ public partial class AutoSkipConfig : ObservableObject
|
||||
[ObservableProperty]
|
||||
private int _afterChooseOptionSleepDelay = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 点击对话框前的延迟(毫秒)
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private int _beforeClickConfirmDelay = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 自动领取每日委托奖励
|
||||
/// </summary>
|
||||
@@ -62,6 +68,12 @@ public partial class AutoSkipConfig : ObservableObject
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private string _clickChatOption = "优先选择第一个选项";
|
||||
|
||||
/// <summary>
|
||||
/// 自定义优先选项文本,每行一个或用分号分隔
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private string _customPriorityOptions = "";
|
||||
|
||||
/// <summary>
|
||||
/// 自动邀约启用
|
||||
@@ -96,7 +108,10 @@ public partial class AutoSkipConfig : ObservableObject
|
||||
{
|
||||
return ClickChatOption == "随机选择选项";
|
||||
}
|
||||
|
||||
public bool IsClickCustomPriorityOption()
|
||||
{
|
||||
return ClickChatOption == "自定义优先选项";
|
||||
}
|
||||
public bool IsClickNoneChatOption()
|
||||
{
|
||||
return ClickChatOption == "不选择选项";
|
||||
@@ -114,6 +129,20 @@ public partial class AutoSkipConfig : ObservableObject
|
||||
[ObservableProperty]
|
||||
private bool _submitGoodsEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏失焦时显示画中画
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private bool _pictureInPictureEnabled = false;
|
||||
|
||||
/// <summary>
|
||||
/// 画中画的源图像类型
|
||||
/// TriggerDispatcher:来自于截图器50ms一次
|
||||
/// CaptureLoop:主动获取(60帧)
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private string _pictureInPictureSourceType = nameof(PictureSourceType.CaptureLoop);
|
||||
|
||||
/// <summary>
|
||||
/// 关闭弹出层
|
||||
/// </summary>
|
||||
@@ -126,4 +155,11 @@ public partial class AutoSkipConfig : ObservableObject
|
||||
// /// </summary>
|
||||
// [ObservableProperty]
|
||||
// private string _selectChatOptionType = SelectChatOptionTypes.UseMouse;
|
||||
}
|
||||
|
||||
|
||||
public enum PictureSourceType
|
||||
{
|
||||
TriggerDispatcher,
|
||||
CaptureLoop
|
||||
}
|
||||
@@ -205,6 +205,11 @@ public partial class AutoSkipTrigger : ITaskTrigger
|
||||
_prevPlayingTime = DateTime.Now;
|
||||
if (TaskContext.Instance().Config.AutoSkipConfig.QuicklySkipConversationsEnabled)
|
||||
{
|
||||
if (_config.BeforeClickConfirmDelay > 0)
|
||||
{
|
||||
// 在触发点击动作之前延迟时间
|
||||
Thread.Sleep(_config.BeforeClickConfirmDelay);
|
||||
}
|
||||
if (IsUseInteractionKey)
|
||||
{
|
||||
_postMessageSimulator? .SimulateActionBackground(GIActions.PickUpOrInteract); // 注意这里不是交互键 NOTE By Ayu0K: 这里确实是交互键
|
||||
@@ -544,7 +549,28 @@ public partial class AutoSkipTrigger : ITaskTrigger
|
||||
|
||||
if (rs.Count > 0)
|
||||
{
|
||||
// 用户自定义关键词 匹配
|
||||
// 自定义优先选项匹配
|
||||
if (_config.IsClickCustomPriorityOption() && !string.IsNullOrEmpty(_config.CustomPriorityOptions))
|
||||
{
|
||||
var customOptions = _config.CustomPriorityOptions
|
||||
.Split(new[] { '\r', '\n', ';', ';' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToList();
|
||||
|
||||
foreach (var item in rs)
|
||||
{
|
||||
foreach (var customOption in customOptions)
|
||||
{
|
||||
if (item.Text.Contains(customOption))
|
||||
{
|
||||
ClickOcrRegion(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 内置关键词 匹配
|
||||
foreach (var item in rs)
|
||||
{
|
||||
// 选择关键词
|
||||
@@ -570,6 +596,16 @@ public partial class AutoSkipTrigger : ITaskTrigger
|
||||
if (_config.AutoGetDailyRewardsEnabled && (item.Text.Contains("每日") || item.Text.Contains("委托")))
|
||||
{
|
||||
ClickOcrRegion(item, "每日委托");
|
||||
TaskControl.Sleep(800);
|
||||
|
||||
// 6.2 每日提示确认
|
||||
var ra1 = TaskControl.CaptureToRectArea();
|
||||
if (Bv.ClickBlackConfirmButton(ra1))
|
||||
{
|
||||
_logger.LogInformation("存在提示并确认");
|
||||
}
|
||||
ra1.Dispose();
|
||||
|
||||
_prevGetDailyRewardsTime = DateTime.Now; // 记录领取时间
|
||||
}
|
||||
else if (_config.AutoReExploreEnabled && (item.Text.Contains("探索") || item.Text.Contains("派遣")))
|
||||
@@ -629,6 +665,16 @@ public partial class AutoSkipTrigger : ITaskTrigger
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有气泡的时候识别 F 选项
|
||||
using var pickRa = region.Find(AutoPickAssets.Instance.ChatPickRo);
|
||||
if (pickRa.IsExist())
|
||||
{
|
||||
_postMessageSimulator?.KeyPressBackground(AutoPickAssets.Instance.PickVk);
|
||||
AutoSkipLog("无气泡图标,但存在交互键,直接按下交互键");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -850,7 +896,8 @@ public partial class AutoSkipTrigger : ITaskTrigger
|
||||
{
|
||||
// var rects = MatchTemplateHelper.MatchOnePicForOnePic(content.CaptureRectArea.SrcMat.CvtColor(ColorConversionCodes.BGRA2BGR),
|
||||
// _autoSkipAssets.SubmitGoodsMat, TemplateMatchModes.SqDiffNormed, null, 0.9, 4);
|
||||
var rects = ContoursHelper.FindSpecifyColorRects(content.CaptureRectArea.SrcMat, new Scalar(233, 229, 220), 100, 20);
|
||||
var param = new MorphologyParam(new Size(5,5), MorphTypes.Close, 2);
|
||||
var rects = ContoursHelper.FindSpecifyColorRects(content.CaptureRectArea.SrcMat, new Scalar(233, 229, 220), 100, 20, param);
|
||||
if (rects.Count == 0)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -45,4 +45,8 @@ public partial class AutoStygianOnslaughtConfig : ObservableObject
|
||||
// 使用脆弱树脂刷取副本次数
|
||||
[ObservableProperty]
|
||||
private int _fragileResinUseCount = 0;
|
||||
|
||||
// 指定战斗队伍
|
||||
[ObservableProperty]
|
||||
private string _fightTeamName = "";
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using BetterGenshinImpact.GameTask.AutoFight.Model;
|
||||
using BetterGenshinImpact.GameTask.AutoFight.Script;
|
||||
using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
|
||||
using BetterGenshinImpact.GameTask.AutoPick.Assets;
|
||||
using BetterGenshinImpact.GameTask.AutoTrackPath;
|
||||
using BetterGenshinImpact.GameTask.Common;
|
||||
using BetterGenshinImpact.GameTask.Common.BgiVision;
|
||||
using BetterGenshinImpact.GameTask.Common.Element.Assets;
|
||||
@@ -94,6 +95,7 @@ public class AutoStygianOnslaughtTask : ISoloTask
|
||||
|
||||
// 前置进入秘境
|
||||
await TpToDomain(page);
|
||||
await SelectDifficulty(page);
|
||||
await EnterDomain(page);
|
||||
await ChooseBoss(page);
|
||||
|
||||
@@ -141,7 +143,12 @@ public class AutoStygianOnslaughtTask : ISoloTask
|
||||
|
||||
Bv.ClickWhiteCancelButton(ra); // 点击返回后是主角
|
||||
await Bv.WaitUntilFound(ElementAssets.Instance.LeylineDisorderIconRo, _ct);
|
||||
await Delay(6000, _ct); // 等待载入完成
|
||||
await Delay(2500, _ct); // 等待载入完成
|
||||
// 走一步防止在地脉花上
|
||||
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown);
|
||||
await Delay(200, _ct);
|
||||
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
|
||||
await Delay(3400, _ct); // 等待载入完成
|
||||
|
||||
// 4. 寻找地脉花
|
||||
_logger.LogInformation($"{Name}:{{Text}}", "3. 寻找地脉花");
|
||||
@@ -193,7 +200,7 @@ public class AutoStygianOnslaughtTask : ISoloTask
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TpToDomain(BvPage page)
|
||||
private async Task TpToDomain(BvPage page, bool isRetry = false)
|
||||
{
|
||||
await new ReturnMainUiTask().Start(_ct);
|
||||
await Delay(100, _ct);
|
||||
@@ -203,6 +210,7 @@ public class AutoStygianOnslaughtTask : ISoloTask
|
||||
{
|
||||
if (page.GetByText("幽境危战").WithRoi(r => r.CutRight(0.5)).IsExist())
|
||||
{
|
||||
await Delay(500, _ct);
|
||||
Simulation.SendInput.Keyboard.KeyPress(AutoPickAssets.Instance.PickVk);
|
||||
_logger.LogInformation($"{Name}:交互秘境");
|
||||
return;
|
||||
@@ -216,6 +224,7 @@ public class AutoStygianOnslaughtTask : ISoloTask
|
||||
await page.GetByText("活动一览").WithRoi(r => r.CutLeftTop(0.3, 0.2)).WaitFor();
|
||||
await Delay(500, _ct);
|
||||
|
||||
// 查找并点击幽境危战 - 前往挑战
|
||||
if (page.GetByText("幽境危战").WithRoi(r => r.CutRight(0.5)).IsExist())
|
||||
{
|
||||
await page.GetByText("前往挑战").WithRoi(r => r.CutRight(0.5)).Click();
|
||||
@@ -226,11 +235,34 @@ public class AutoStygianOnslaughtTask : ISoloTask
|
||||
await Delay(1500, _ct);
|
||||
await page.GetByText("前往挑战").WithRoi(r => r.CutRight(0.5)).Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("未找到幽境危战选项");
|
||||
}
|
||||
|
||||
_logger.LogInformation($"{Name}:点击前往挑战");
|
||||
|
||||
// 传送
|
||||
await Delay(1000, _ct);
|
||||
|
||||
try
|
||||
{
|
||||
await page.Locator(QuickTeleportAssets.Instance.TeleportButtonRo)
|
||||
.WaitFor();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!isRetry)
|
||||
{
|
||||
// 未找到传送按钮,先返回主界面再重新进入
|
||||
_logger.LogWarning($"{Name}:未找到传送按钮,返回七天神像重新开始");
|
||||
await new TpTask(_ct).TpToStatueOfTheSeven();
|
||||
// 重新执行从打开活动界面开始的流程
|
||||
await TpToDomain(page, isRetry: true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await page.Locator(QuickTeleportAssets.Instance.TeleportButtonRo).Click();
|
||||
_logger.LogInformation($"{Name}:点击传送");
|
||||
await Delay(800, _ct);
|
||||
@@ -246,7 +278,6 @@ public class AutoStygianOnslaughtTask : ISoloTask
|
||||
|
||||
private async Task EnterDomain(BvPage page)
|
||||
{
|
||||
await Delay(4000, _ct); // 等待动画完成
|
||||
await page.Locator(ElementAssets.Instance.BtnWhiteConfirm)
|
||||
.WithRoi(r => r.CutRight(0.5))
|
||||
.ClickUntilDisappears();
|
||||
@@ -285,6 +316,9 @@ public class AutoStygianOnslaughtTask : ISoloTask
|
||||
page.Click(203, 728);
|
||||
}
|
||||
|
||||
// 切换战斗队伍
|
||||
await SwitchTeam(page);
|
||||
|
||||
await Delay(120, _ct);
|
||||
|
||||
// 幽境危战确认界面
|
||||
@@ -362,10 +396,19 @@ public class AutoStygianOnslaughtTask : ISoloTask
|
||||
/// </summary>
|
||||
private Task DomainEndDetectionTask(CancellationTokenSource cts)
|
||||
{
|
||||
return new Task(async () =>
|
||||
return new Task(async void () =>
|
||||
{
|
||||
await Bv.WaitUntilFound(ElementAssets.Instance.BtnWhiteCancel, cts.Token, 150, 1000);
|
||||
await cts.CancelAsync();
|
||||
try
|
||||
{
|
||||
await Bv.WaitUntilFound(ElementAssets.Instance.BtnWhiteCancel, cts.Token, 300, 1000);
|
||||
_logger.LogInformation("检测到战斗结束,结束战斗操作线程");
|
||||
await cts.CancelAsync();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogInformation("对局结束检测线程异常结束:{Msg}", e.Message);
|
||||
_logger.LogDebug(e, "对局结束检测线程异常结束");
|
||||
}
|
||||
}, cts.Token);
|
||||
}
|
||||
|
||||
@@ -568,6 +611,113 @@ public class AutoStygianOnslaughtTask : ISoloTask
|
||||
await new AutoArtifactSalvageTask(new AutoArtifactSalvageTaskParam(star, javaScript: null, artifactSetFilter: null, maxNumToCheck: null, recognitionFailurePolicy: null)).Start(_ct);
|
||||
}
|
||||
|
||||
private async Task SelectDifficulty(BvPage page)
|
||||
{
|
||||
await Delay(4000, _ct); // 等待动画完成
|
||||
|
||||
// 检查是否需要从至危挑战切换到困难
|
||||
if (page.GetByText("至危挑战").WithRoi(r => r.CutLeftTop(0.5, 0.2)).IsExist())
|
||||
{
|
||||
_logger.LogInformation($"{Name}:找到至危挑战,尝试切换到困难模式");
|
||||
await Delay(500, _ct);
|
||||
await page.GetByText("至危挑战").WithRoi(r => r.CutLeftTop(0.5, 0.2)).Click();
|
||||
await Delay(500, _ct);
|
||||
}
|
||||
|
||||
// 检查困难模式是否已选中
|
||||
var hardMode = page.GetByText("困难").WithRoi(r => r.CutRightTop(0.5, 0.2)).IsExist();
|
||||
|
||||
if (hardMode)
|
||||
{
|
||||
_logger.LogInformation($"{Name}:确认困难模式");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("未找到困难模式,尝试切换");
|
||||
await Delay(500, _ct);
|
||||
page.Click(1096, 186);
|
||||
await Delay(500, _ct);
|
||||
page.Click(1093, 399);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SwitchTeam(BvPage page)
|
||||
{
|
||||
var fightTeamName = _taskParam.FightTeamName;
|
||||
if (string.IsNullOrEmpty(fightTeamName))
|
||||
{
|
||||
_logger.LogInformation($"{Name}:不更换战斗队伍");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"{Name}:配置战斗队伍为:{fightTeamName}");
|
||||
|
||||
// 查找预设队伍按钮并点击它打开面板
|
||||
var teamButton = page.GetByText("预设队伍").WithRoi(r => r.CutRightBottom(0.3, 0.1)).FindAll().FirstOrDefault();
|
||||
|
||||
if (teamButton == null)
|
||||
{
|
||||
// 如果按钮未找到,检查列表面板是否已打开
|
||||
var panelTitle = page.GetByText("预设队伍").WithRoi(r => r.CutLeftTop(0.15, 0.075)).FindAll().FirstOrDefault();
|
||||
if (panelTitle == null)
|
||||
{
|
||||
_logger.LogWarning("未找到预设队伍按钮,不执行切换操作");
|
||||
return;
|
||||
}
|
||||
// 列表面板已打开,跳过点击按钮步骤
|
||||
}
|
||||
else
|
||||
{
|
||||
// 点击预设队伍按钮打开队伍选择面板
|
||||
teamButton.Click();
|
||||
await Delay(100, _ct);
|
||||
}
|
||||
|
||||
// 此时面板已打开,点击滚动条准备拖动
|
||||
page.Click(936, 150);
|
||||
await Delay(100, _ct);
|
||||
|
||||
// 滚轮预操作 - 按住左键准备拖动列表
|
||||
Simulation.SendInput.Mouse.LeftButtonDown();
|
||||
await Delay(100, _ct);
|
||||
// 向上移动一点开始滚动
|
||||
GameCaptureRegion.GameRegion1080PPosMove(936, 140);
|
||||
await Delay(100, _ct);
|
||||
|
||||
int yOffset = 0;
|
||||
const int maxRetries = 30;
|
||||
|
||||
for (int retries = 0; retries < maxRetries; retries++)
|
||||
{
|
||||
// 查找队伍名称,OCR区域: 左侧竖列队伍列表
|
||||
var teamRegionList = page.GetByText(fightTeamName).WithRoi(r => r.CutLeft(0.18)).FindAll();
|
||||
var foundTeam = teamRegionList.FirstOrDefault();
|
||||
if (foundTeam != null)
|
||||
{
|
||||
Simulation.SendInput.Mouse.LeftButtonUp();
|
||||
await Delay(300, _ct);
|
||||
foundTeam.Click();
|
||||
await Delay(500, _ct);
|
||||
return;
|
||||
}
|
||||
|
||||
// 滚轮操作 - 在滚动条(936, y)位置拖动
|
||||
yOffset += 100;
|
||||
if (130 + yOffset > 1080)
|
||||
{
|
||||
Simulation.SendInput.Mouse.LeftButtonUp();
|
||||
await Delay(100, _ct);
|
||||
_logger.LogWarning("未找到预设战斗队伍名称,保持原有队伍");
|
||||
Simulation.SendInput.Keyboard.KeyPress(VK.VK_ESCAPE);
|
||||
await Delay(500, _ct);
|
||||
return;
|
||||
}
|
||||
|
||||
// 移动滚动条
|
||||
GameCaptureRegion.GameRegion1080PPosMove(936, 130 + yOffset);
|
||||
await Delay(200, _ct);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExitDomain(BvPage page)
|
||||
{
|
||||
|
||||
@@ -14477,5 +14477,392 @@
|
||||
"areaId": 600
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"mapName": "AncientSacredMountain",
|
||||
"sceneId": 101,
|
||||
"description": "远古圣山",
|
||||
"points": [
|
||||
{
|
||||
"id": "11",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
5.254,
|
||||
770.154,
|
||||
-544.8
|
||||
],
|
||||
"tranPosition": [
|
||||
8.071999,
|
||||
770.3726,
|
||||
-546.2424
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 800
|
||||
},
|
||||
{
|
||||
"id": "12",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
301.3517,
|
||||
201.98,
|
||||
-158.3329
|
||||
],
|
||||
"tranPosition": [
|
||||
308.8704,
|
||||
201.54634,
|
||||
-151.29555
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 800
|
||||
},
|
||||
{
|
||||
"id": "13",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
100.683,
|
||||
210.202,
|
||||
-143.976
|
||||
],
|
||||
"tranPosition": [
|
||||
105.01541,
|
||||
210.202,
|
||||
-152.21004
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 80005
|
||||
},
|
||||
{
|
||||
"id": "14",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
305.55,
|
||||
138.782,
|
||||
108.762
|
||||
],
|
||||
"tranPosition": [
|
||||
312.97974,
|
||||
141.13326,
|
||||
102.69159
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 80006
|
||||
},
|
||||
{
|
||||
"id": "15",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
345.413,
|
||||
259.0234,
|
||||
264.3909
|
||||
],
|
||||
"tranPosition": [
|
||||
346.47064,
|
||||
259.45032,
|
||||
270.15018
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 80006
|
||||
},
|
||||
{
|
||||
"id": "16",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
512.608,
|
||||
106.953,
|
||||
7.501
|
||||
],
|
||||
"tranPosition": [
|
||||
508.58472,
|
||||
107.43931,
|
||||
19.718029
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 800
|
||||
},
|
||||
{
|
||||
"id": "17",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
640.53,
|
||||
89.07455,
|
||||
-89.7999
|
||||
],
|
||||
"tranPosition": [
|
||||
645.846,
|
||||
88.50239,
|
||||
-85.27797
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 800
|
||||
},
|
||||
{
|
||||
"id": "18",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
261.4832,
|
||||
57.82032,
|
||||
-138.0443
|
||||
],
|
||||
"tranPosition": [
|
||||
264.99072,
|
||||
57.53672,
|
||||
-152.49945
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 80004
|
||||
},
|
||||
{
|
||||
"id": "19",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
541.595,
|
||||
77.494,
|
||||
-287.768
|
||||
],
|
||||
"tranPosition": [
|
||||
534.5397,
|
||||
74.56176,
|
||||
-293.87283
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 800
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
747.23,
|
||||
857.58,
|
||||
44.91
|
||||
],
|
||||
"tranPosition": [
|
||||
752.04047,
|
||||
857.8871,
|
||||
40.86717
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 80001
|
||||
},
|
||||
{
|
||||
"id": "20",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
474.239,
|
||||
115.454,
|
||||
-497.752
|
||||
],
|
||||
"tranPosition": [
|
||||
469.06088,
|
||||
115.62647,
|
||||
-501.97424
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 800
|
||||
},
|
||||
{
|
||||
"id": "21",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
251.6618,
|
||||
146.964,
|
||||
-424.2064
|
||||
],
|
||||
"tranPosition": [
|
||||
253.07848,
|
||||
147.8764,
|
||||
-432.8027
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 800
|
||||
},
|
||||
{
|
||||
"id": "22",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
427.1338,
|
||||
-635.1426,
|
||||
-434.3028
|
||||
],
|
||||
"tranPosition": [
|
||||
450.01736,
|
||||
-635.4016,
|
||||
-424.52426
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 80009
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
365.439,
|
||||
751.873,
|
||||
-185.486
|
||||
],
|
||||
"tranPosition": [
|
||||
363.01917,
|
||||
751.1264,
|
||||
-182.49893
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 800
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
207.5441,
|
||||
693.96,
|
||||
196.1411
|
||||
],
|
||||
"tranPosition": [
|
||||
209.92026,
|
||||
694.134,
|
||||
202.46678
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 80002
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
-42.25665,
|
||||
822.063,
|
||||
193.6675
|
||||
],
|
||||
"tranPosition": [
|
||||
-42.84648,
|
||||
821.274,
|
||||
185.37282
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 800
|
||||
},
|
||||
{
|
||||
"id": "6",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
37.49844,
|
||||
737.135,
|
||||
383.0565
|
||||
],
|
||||
"tranPosition": [
|
||||
35.7756,
|
||||
736.75525,
|
||||
387.55582
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 80002
|
||||
},
|
||||
{
|
||||
"id": "7",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
-246.441,
|
||||
831.017,
|
||||
-90.849
|
||||
],
|
||||
"tranPosition": [
|
||||
-248.21928,
|
||||
828.3109,
|
||||
-80.89331
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 800
|
||||
},
|
||||
{
|
||||
"id": "8",
|
||||
"gadgetId": 70110002,
|
||||
"gadgetType": "TransPointSecond",
|
||||
"type": "Teleport",
|
||||
"position": [
|
||||
-292.383,
|
||||
885.409,
|
||||
-249.987
|
||||
],
|
||||
"tranPosition": [
|
||||
-290.66766,
|
||||
885.0531,
|
||||
-245.20038
|
||||
],
|
||||
"country": "纳塔",
|
||||
"name": "传送锚点",
|
||||
"area": "远古圣山",
|
||||
"areaId": 800
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -894,7 +894,7 @@ public class TpTask
|
||||
if (matchRect == null)
|
||||
{
|
||||
Logger.LogWarning("切换区域失败:{Country}", areaName);
|
||||
if (areaName == MapTypes.TheChasm.GetDescription() || areaName == MapTypes.Enkanomiya.GetDescription())
|
||||
if (areaName == MapTypes.TheChasm.GetDescription() || areaName == MapTypes.Enkanomiya.GetDescription() || areaName == MapTypes.SeaOfBygoneEras.GetDescription() || areaName == MapTypes.AncientSacredMountain.GetDescription())
|
||||
{
|
||||
throw new Exception($"切换独立地图区域[{areaName}]失败");
|
||||
}
|
||||
|
||||
@@ -549,4 +549,4 @@ public partial class AutoWoodTask : ISoloTask
|
||||
throw new RetryException("未检测进入游戏界面");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Threading;
|
||||
using BetterGenshinImpact.GameTask.Model.Area;
|
||||
using Vanara.PInvoke;
|
||||
using static BetterGenshinImpact.GameTask.Common.TaskControl;
|
||||
using BetterGenshinImpact.GameTask;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoWood.Utils;
|
||||
|
||||
@@ -107,9 +108,10 @@ internal sealed class Login3rdParty
|
||||
var (loginWindow, windowType) = GetBiliLoginWindow(process);
|
||||
if (loginWindow != IntPtr.Zero)
|
||||
{
|
||||
var dpiScale = TaskContext.Instance().DpiScale;
|
||||
if (windowType.Contains("协议"))
|
||||
{
|
||||
GameCaptureRegion.GameRegion1080PPosClick(1030, 615);
|
||||
GameCaptureRegion.GameRegion1080PPosClick(960 + 70 * dpiScale, 540 + 75 * dpiScale);
|
||||
|
||||
// 检查窗口是否还存在
|
||||
var (remainingWindow, remainingType) = GetBiliLoginWindow(process);
|
||||
@@ -124,7 +126,7 @@ internal sealed class Login3rdParty
|
||||
if (windowType.Contains("登录"))
|
||||
{
|
||||
Thread.Sleep(2000);
|
||||
GameCaptureRegion.GameRegion1080PPosClick(960, 630);
|
||||
GameCaptureRegion.GameRegion1080PPosClick(960, 540 + 90 * dpiScale);
|
||||
Thread.Sleep(2000);
|
||||
|
||||
// 检查窗口是否还存在
|
||||
|
||||
@@ -6,6 +6,7 @@ using BetterGenshinImpact.GameTask.Model.Area;
|
||||
using Fischless.WindowsInput;
|
||||
using OpenCvSharp;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.Common.BgiVision;
|
||||
|
||||
@@ -17,53 +18,47 @@ namespace BetterGenshinImpact.GameTask.Common.BgiVision;
|
||||
/// </summary>
|
||||
public static partial class Bv
|
||||
{
|
||||
/// <summary>
|
||||
/// 通用方法:查找识别对象,如果存在则点击
|
||||
/// </summary>
|
||||
/// <param name="captureRa">截图区域</param>
|
||||
/// <param name="ro">识别对象</param>
|
||||
/// <returns>是否找到并点击</returns>
|
||||
public static bool FindAndClick(ImageRegion captureRa, RecognitionObject ro)
|
||||
{
|
||||
var ra = captureRa.Find(ro);
|
||||
if (ra.IsExist())
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
ra.Click();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 点击减少按钮
|
||||
/// </summary>
|
||||
/// <param name="captureRa"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClickReduceButton(ImageRegion captureRa)
|
||||
{
|
||||
var ra = captureRa.Find(ElementAssets.Instance.Keyreduce);
|
||||
if (ra.IsExist())
|
||||
{
|
||||
ra.Click();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
=> FindAndClick(captureRa, ElementAssets.Instance.Keyreduce);
|
||||
|
||||
/// <summary>
|
||||
/// 点击增加按钮
|
||||
/// </summary>
|
||||
/// <param name="captureRa"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClickAddButton(ImageRegion captureRa)
|
||||
{
|
||||
var ra = captureRa.Find(ElementAssets.Instance.Keyincrease);
|
||||
if (ra.IsExist())
|
||||
{
|
||||
ra.Click();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
=> FindAndClick(captureRa, ElementAssets.Instance.Keyincrease);
|
||||
|
||||
/// <summary>
|
||||
/// 点击白色确认按钮
|
||||
/// </summary>
|
||||
/// <param name="captureRa"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClickWhiteConfirmButton(ImageRegion captureRa)
|
||||
{
|
||||
var ra = captureRa.Find(ElementAssets.Instance.BtnWhiteConfirm);
|
||||
if (ra.IsExist())
|
||||
{
|
||||
ra.Click();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
=> FindAndClick(captureRa, ElementAssets.Instance.BtnWhiteConfirm);
|
||||
|
||||
/// <summary>
|
||||
/// 点击白色取消按钮
|
||||
@@ -71,16 +66,7 @@ public static partial class Bv
|
||||
/// <param name="captureRa"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClickWhiteCancelButton(ImageRegion captureRa)
|
||||
{
|
||||
var ra = captureRa.Find(ElementAssets.Instance.BtnWhiteCancel);
|
||||
if (ra.IsExist())
|
||||
{
|
||||
ra.Click();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
=> FindAndClick(captureRa, ElementAssets.Instance.BtnWhiteCancel);
|
||||
|
||||
/// <summary>
|
||||
/// 点击黑色确认按钮
|
||||
@@ -88,16 +74,7 @@ public static partial class Bv
|
||||
/// <param name="captureRa"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClickBlackConfirmButton(ImageRegion captureRa)
|
||||
{
|
||||
var ra = captureRa.Find(ElementAssets.Instance.BtnBlackConfirm);
|
||||
if (ra.IsExist())
|
||||
{
|
||||
ra.Click();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
=> FindAndClick(captureRa, ElementAssets.Instance.BtnBlackConfirm);
|
||||
|
||||
/// <summary>
|
||||
/// 点击黑色取消按钮
|
||||
@@ -105,16 +82,7 @@ public static partial class Bv
|
||||
/// <param name="captureRa"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClickBlackCancelButton(ImageRegion captureRa)
|
||||
{
|
||||
var ra = captureRa.Find(ElementAssets.Instance.BtnBlackCancel);
|
||||
if (ra.IsExist())
|
||||
{
|
||||
ra.Click();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
=> FindAndClick(captureRa, ElementAssets.Instance.BtnBlackCancel);
|
||||
|
||||
/// <summary>
|
||||
/// 点击联机确认按钮
|
||||
@@ -122,16 +90,7 @@ public static partial class Bv
|
||||
/// <param name="captureRa"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClickOnlineYesButton(ImageRegion captureRa)
|
||||
{
|
||||
var ra = captureRa.Find(ElementAssets.Instance.BtnOnlineYes);
|
||||
if (ra.IsExist())
|
||||
{
|
||||
ra.Click();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
=> FindAndClick(captureRa, ElementAssets.Instance.BtnOnlineYes);
|
||||
|
||||
/// <summary>
|
||||
/// 点击联机取消按钮
|
||||
@@ -139,16 +98,7 @@ public static partial class Bv
|
||||
/// <param name="captureRa"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClickOnlineNoButton(ImageRegion captureRa)
|
||||
{
|
||||
var ra = captureRa.Find(ElementAssets.Instance.BtnOnlineNo);
|
||||
if (ra.IsExist())
|
||||
{
|
||||
ra.Click();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
=> FindAndClick(captureRa, ElementAssets.Instance.BtnOnlineNo);
|
||||
|
||||
/// <summary>
|
||||
/// 点击确认按钮(优先点击白色背景的确认按钮)
|
||||
|
||||
@@ -25,7 +25,7 @@ public class BlessingOfTheWelkinMoonTask
|
||||
if (t.Hour == 4 && t.Minute < 10)
|
||||
{
|
||||
using var ra = CaptureToRectArea();
|
||||
if (Bv.IsInBlessingOfTheWelkinMoon(ra))
|
||||
if (Bv.IsInBlessingOfTheWelkinMoon(ra) || ra.Find(ElementAssets.Instance.PrimogemRo).IsExist())
|
||||
{
|
||||
Logger.LogInformation("检测到空月祝福界面,自动点击");
|
||||
GameCaptureRegion.GameRegion1080PPosMove(100, 100);
|
||||
|
||||
@@ -157,7 +157,7 @@ public partial class ChooseTalkOptionTask
|
||||
|
||||
// 通过最下面的气泡框来文字识别
|
||||
var lowest = chatOptionResultList[0];
|
||||
var ocrRect = new Rect((int)(lowest.X + lowest.Width + 8 * assetScale), region.Height / 12,
|
||||
var ocrRect = new Rect((int)(lowest.X + lowest.Width + 8 * assetScale), region.Height / 8,
|
||||
(int)(535 * assetScale), (int)(lowest.Y + lowest.Height + 30 * assetScale - region.Height / 12d));
|
||||
var ocrResList = region.FindMulti(RecognitionObject.Ocr(ocrRect));
|
||||
|
||||
|
||||
@@ -91,7 +91,16 @@ public class GoToAdventurersGuildTask
|
||||
if (res == TalkOptionRes.FoundAndClick)
|
||||
{
|
||||
Logger.LogInformation("▶ {Text}", "领取『每日委托』奖励!");
|
||||
await Delay(500, ct);
|
||||
await Delay(800, ct);
|
||||
|
||||
// 6.2 每日提示确认
|
||||
var ra1 = CaptureToRectArea();
|
||||
if (Bv.ClickBlackConfirmButton(ra1))
|
||||
{
|
||||
Logger.LogInformation("存在提示并确认");
|
||||
}
|
||||
ra1.Dispose();
|
||||
|
||||
await _chooseTalkOptionTask.SelectLastOptionUntilEnd(ct, null, 3); // 点几下
|
||||
await Bv.WaitUntilFound(ElementAssets.Instance.PaimonMenuRo, ct);
|
||||
await Delay(500, ct);
|
||||
|
||||
@@ -84,9 +84,10 @@ public class SetTimeTask
|
||||
// 取消动画函数
|
||||
private async Task CancelAnimation(CancellationToken ct)
|
||||
{
|
||||
GameCaptureRegion.GameRegion1080PPosClick(200, 200);
|
||||
await Delay(5, ct);
|
||||
GameCaptureRegion.GameRegion1080PPosClick(200, 200);
|
||||
GameCaptureRegion.GameRegion1080PPosMove(200, 200);
|
||||
Simulation.SendInput.Mouse.LeftButtonDown();
|
||||
await Delay(10, ct);
|
||||
Simulation.SendInput.Mouse.LeftButtonUp();
|
||||
}
|
||||
|
||||
double[] GetPosition(double r, double index)
|
||||
|
||||
@@ -138,7 +138,7 @@ public class SwitchPartyTask
|
||||
|
||||
// 点击到最上方
|
||||
await Task.Delay(50, ct);
|
||||
GameCaptureRegion.GameRegion1080PPosClick(700, 120);
|
||||
GameCaptureRegion.GameRegion1080PPosClick(700, 125);
|
||||
await Task.Delay(50, ct);
|
||||
Simulation.SendInput.Mouse.LeftButtonDown();
|
||||
await Task.Delay(450, ct);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BetterGenshinImpact.GameTask.Common.Map.Maps.Base;
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.GameTask.Common.Map.Maps.Base;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.Common.Map.Maps;
|
||||
@@ -11,10 +12,10 @@ public class AncientSacredMountainMap : SceneBaseMap
|
||||
{
|
||||
#region 地图参数
|
||||
|
||||
static readonly int GameMapRows = 2; // 游戏坐标下地图块的行数
|
||||
static readonly int GameMapCols = 2; // 游戏坐标下地图块的列数
|
||||
static readonly int GameMapUpRows = 0; // 游戏坐标下 左上角离地图原点的行数(注意原点在块的右下角)
|
||||
static readonly int GameMapLeftCols = 0; // 游戏坐标下 左上角离地图原点的列数(注意原点在块的右下角)
|
||||
static readonly int GameMapRows = 4; // 游戏坐标下地图块的行数
|
||||
static readonly int GameMapCols = 4; // 游戏坐标下地图块的列数
|
||||
static readonly int GameMapUpRows = 1; // 游戏坐标下 左上角离地图原点的行数(注意原点在块的右下角)
|
||||
static readonly int GameMapLeftCols = 1; // 游戏坐标下 左上角离地图原点的列数(注意原点在块的右下角)
|
||||
|
||||
#endregion 地图参数
|
||||
|
||||
@@ -27,6 +28,8 @@ public class AncientSacredMountainMap : SceneBaseMap
|
||||
splitRow: 0,
|
||||
splitCol: 0)
|
||||
{
|
||||
ExtractAndSaveFeature(Global.Absolute("Assets/Map/AncientSacredMountain/AncientSacredMountain_0_1024.png"));
|
||||
ExtractAndSaveFeature(Global.Absolute("Assets/Map/AncientSacredMountain/AncientSacredMountain_-1_1024.webp"));
|
||||
Layers = BaseMapLayer.LoadLayers(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ public class BaseMapLayer(SceneBaseMap baseMap)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a.Floor > b.Floor ? 1 : -1;
|
||||
return a.Floor < b.Floor ? 1 : -1;
|
||||
});
|
||||
return layers;
|
||||
}
|
||||
|
||||
@@ -13,4 +13,10 @@ public enum DisplayMapTypes
|
||||
|
||||
[Description("渊下宫")]
|
||||
Enkanomiya,
|
||||
|
||||
[Description("旧日之海")]
|
||||
SeaOfBygoneEras,
|
||||
|
||||
[Description("远古圣山")]
|
||||
AncientSacredMountain,
|
||||
}
|
||||
@@ -102,7 +102,7 @@ public abstract class SceneBaseMap : ISceneMap
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = SiftMatcher.KnnMatch(layer.TrainKeyPoints, layer.TrainDescriptors, greyMiniMapMat);
|
||||
var result = SiftMatcher.KnnMatch(layer.TrainKeyPoints, layer.TrainDescriptors, greyMiniMapMat, null, DescriptorMatcherType.BruteForce);
|
||||
if (result != default)
|
||||
{
|
||||
return result;
|
||||
|
||||
@@ -1,26 +1,47 @@
|
||||
using BetterGenshinImpact.GameTask.Common.Map.Maps.Base;
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.GameTask.Common.Map.Maps.Base;
|
||||
using OpenCvSharp;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using BetterGenshinImpact.Core.Recognition.OpenCv;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.Common.Map.Maps;
|
||||
|
||||
/// <summary>
|
||||
/// 旧日之海
|
||||
/// 从3x4改成了1x2
|
||||
/// 大地图都是半黑的,传送可能有问题
|
||||
/// </summary>
|
||||
public class SeaOfBygoneErasMap : SceneBaseMap
|
||||
{
|
||||
#region 地图参数
|
||||
|
||||
static readonly int GameMapRows = 1; // 游戏坐标下地图块的行数
|
||||
static readonly int GameMapCols = 2; // 游戏坐标下地图块的列数
|
||||
static readonly int GameMapUpRows = 0; // 游戏坐标下 左上角离地图原点的行数(注意原点在块的右下角) TODO 没找到
|
||||
static readonly int GameMapLeftCols = 0; // 游戏坐标下 左上角离地图原点的列数(注意原点在块的右下角) TODO 没找到
|
||||
static readonly int GameMapRows = 3; // 游戏坐标下地图块的行数
|
||||
static readonly int GameMapCols = 4; // 游戏坐标下地图块的列数
|
||||
static readonly int GameMapUpRows = 2; // 游戏坐标下 左上角离地图原点的行数(注意原点在块的右下角)
|
||||
static readonly int GameMapLeftCols = 5; // 游戏坐标下 左上角离地图原点的列数(注意原点在块的右下角)
|
||||
|
||||
#endregion 地图参数
|
||||
|
||||
static readonly int SeaOfBygoneErasMapImageBlockWidth = 1024;
|
||||
|
||||
private static Mat TeleportTemplate;
|
||||
private static Mat TeleportTemplateMask;
|
||||
private List<Point> MapTeleports;
|
||||
|
||||
static SeaOfBygoneErasMap()
|
||||
{
|
||||
Mat img = GameTaskManager.LoadAssetImage("QuickTeleport", "TeleportTransparentBackground.png", ImreadModes.Unchanged);
|
||||
|
||||
TeleportTemplate = new Mat();
|
||||
Cv2.CvtColor(img, TeleportTemplate, ColorConversionCodes.BGRA2GRAY);
|
||||
|
||||
Mat[] channels = Cv2.Split(img);
|
||||
TeleportTemplateMask = channels[3];
|
||||
}
|
||||
|
||||
public SeaOfBygoneErasMap() : base(type: MapTypes.SeaOfBygoneEras,
|
||||
mapSize: new Size(GameMapCols * SeaOfBygoneErasMapImageBlockWidth, GameMapRows * SeaOfBygoneErasMapImageBlockWidth),
|
||||
mapOriginInImageCoordinate: new Point2f((GameMapLeftCols + 1) * SeaOfBygoneErasMapImageBlockWidth, (GameMapUpRows + 1) * SeaOfBygoneErasMapImageBlockWidth),
|
||||
@@ -28,7 +49,224 @@ public class SeaOfBygoneErasMap : SceneBaseMap
|
||||
splitRow: 0,
|
||||
splitCol: 0)
|
||||
{
|
||||
ExtractAndSaveFeature(Global.Absolute("Assets/Map/SeaOfBygoneEras/SeaOfBygoneEras_0_1024.png"));
|
||||
ExtractAndSaveFeature(Global.Absolute("Assets/Map/SeaOfBygoneEras/SeaOfBygoneEras_-1_1024.webp"));
|
||||
ExtractAndSaveFeature(Global.Absolute("Assets/Map/SeaOfBygoneEras/SeaOfBygoneEras_-2_1024.webp"));
|
||||
Layers = BaseMapLayer.LoadLayers(this);
|
||||
|
||||
var mapTeleports = new List<Point>();
|
||||
var tpJson = System.IO.File.ReadAllText(Global.Absolute(@"GameTask\AutoTrackPath\Assets\tp.json"));
|
||||
|
||||
JArray j = JArray.Parse(tpJson);
|
||||
foreach (JObject i in j)
|
||||
{
|
||||
var sceneId = i["sceneId"];
|
||||
if (sceneId != null && (int)sceneId == 11)
|
||||
{
|
||||
foreach (var p in i["points"]!)
|
||||
{
|
||||
if ((string)p["type"]! != "Teleport")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var x = (float)p["position"]![2]!;
|
||||
var y = (float)p["position"]![0]!;
|
||||
var (x1, y1) = ConvertGenshinMapCoordinatesToImageCoordinates(x, y);
|
||||
mapTeleports.Add(new Point(x1, y1));
|
||||
}
|
||||
}
|
||||
}
|
||||
mapTeleports = mapTeleports.OrderBy(i => i.X).ThenBy(i => i.Y).ToList();
|
||||
MapTeleports = mapTeleports;
|
||||
}
|
||||
|
||||
public override Point2f GetBigMapPosition(Mat greyBigMapMat)
|
||||
{
|
||||
var rect = GetBigMapRectByTeleports(greyBigMapMat);
|
||||
if (rect != default)
|
||||
{
|
||||
return rect.GetCenterPoint();
|
||||
}
|
||||
|
||||
return base.GetBigMapPosition(greyBigMapMat);
|
||||
}
|
||||
|
||||
public override Rect GetBigMapRect(Mat greyBigMapMat)
|
||||
{
|
||||
var rect = GetBigMapRectByTeleports(greyBigMapMat);
|
||||
if (rect != default)
|
||||
{
|
||||
return rect;
|
||||
}
|
||||
|
||||
return base.GetBigMapRect(greyBigMapMat);
|
||||
}
|
||||
|
||||
private Rect GetBigMapRectByTeleports(Mat greyBigMapMat)
|
||||
{
|
||||
// It's fine to miss some, but definitely no false positive results.
|
||||
const double threshold = 0.99;
|
||||
using Mat result = new Mat();
|
||||
Cv2.MatchTemplate(greyBigMapMat, TeleportTemplate, result, TemplateMatchModes.CCorrNormed, TeleportTemplateMask);
|
||||
|
||||
var teleportPoints = new List<Point>();
|
||||
|
||||
// Step 1: Get all teleport positions from current screenshot
|
||||
for (int i = 1; i < result.Rows - 1; ++i)
|
||||
{
|
||||
for (int j = 1; j < result.Cols - 1; ++j)
|
||||
{
|
||||
float val = result.At<float>(i, j);
|
||||
|
||||
if (val > threshold)
|
||||
{
|
||||
if (val >= result.At<float>(i - 1, j - 1) &&
|
||||
val >= result.At<float>(i - 1, j) &&
|
||||
val >= result.At<float>(i - 1, j + 1) &&
|
||||
val >= result.At<float>(i, j - 1) &&
|
||||
val >= result.At<float>(i, j + 1) &&
|
||||
val >= result.At<float>(i + 1, j - 1) &&
|
||||
val >= result.At<float>(i + 1, j) &&
|
||||
val >= result.At<float>(i + 1, j + 1))
|
||||
{
|
||||
var newPoint = new Point(j, i);
|
||||
bool tooClose = false;
|
||||
foreach (var p in teleportPoints)
|
||||
{
|
||||
if (p.DistanceTo(newPoint) < 50)
|
||||
{
|
||||
tooClose = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!tooClose)
|
||||
{
|
||||
teleportPoints.Add(newPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (teleportPoints.Count < 2)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
teleportPoints = teleportPoints.OrderBy(i => i.X).ThenBy(i => i.Y).ToList();
|
||||
/*
|
||||
foreach (var p in teleportPoints) {
|
||||
Logger.LogInformation("Teleport point: {a}", p);
|
||||
}
|
||||
Logger.LogInformation("Total telepoints: {c}", teleportPoints.Count);
|
||||
*/
|
||||
|
||||
Func<Point, Point, double> GetAngleOfTwoPoints = (p0, p1) =>
|
||||
{
|
||||
double deltaX = p0.X - p1.X;
|
||||
int deltaY = p0.Y - p1.Y;
|
||||
if (deltaY == 0) { return 90; }
|
||||
var val = Math.Atan(deltaX / deltaY);
|
||||
return val / Math.PI * 180;
|
||||
};
|
||||
|
||||
// Step 2: find a diagonal determined by two of the teleports, let's call these two teleports reference points
|
||||
Point rp0 = new Point();
|
||||
Point rp1 = new Point();
|
||||
double refAngle = 0.0;
|
||||
{
|
||||
double minAngleDiff = 180;
|
||||
for (int i = 0; i < teleportPoints.Count; ++i)
|
||||
{
|
||||
for (int j = i + 1; j < teleportPoints.Count; ++j)
|
||||
{
|
||||
var p0 = teleportPoints[i];
|
||||
var p1 = teleportPoints[j];
|
||||
var angle = GetAngleOfTwoPoints(p0, p1);
|
||||
if (Math.Abs(Math.Abs(angle) - 45) < minAngleDiff)
|
||||
{
|
||||
rp0 = p0;
|
||||
rp1 = p1;
|
||||
minAngleDiff = Math.Abs(Math.Abs(angle) - 45);
|
||||
}
|
||||
}
|
||||
}
|
||||
refAngle = GetAngleOfTwoPoints(rp0, rp1);
|
||||
// Logger.LogInformation("Reference points {a} and {b}, Angle {c}", rp0, rp1, refAngle);
|
||||
}
|
||||
|
||||
{
|
||||
/*
|
||||
var debugMat = new Mat();
|
||||
Cv2.CvtColor(greyBigMapMat, debugMat, ColorConversionCodes.GRAY2BGR);
|
||||
foreach (var p in teleportPoints)
|
||||
{
|
||||
Cv2.DrawMarker(debugMat, p, new Scalar(255, 0, 0), MarkerTypes.Cross, 20, 2);
|
||||
}
|
||||
Cv2.DrawMarker(debugMat, rp0, new Scalar(0, 255, 0), MarkerTypes.TriangleUp, 20, 2);
|
||||
Cv2.DrawMarker(debugMat, rp1, new Scalar(0, 255, 0), MarkerTypes.TriangleUp, 20, 2);
|
||||
Cv2.ImWrite(((DateTimeOffset)DateTime.UtcNow).ToUnixTimeMilliseconds().ToString() + ".png", debugMat);
|
||||
*/
|
||||
}
|
||||
|
||||
// Step 3: For all diagonals determined by pairs of teleports on this map.
|
||||
var minDeviation = double.MaxValue;
|
||||
var transformParamScale = 0.0;
|
||||
var transformParamDeltaX = 0.0;
|
||||
var transformParamDeltaY = 0.0;
|
||||
for (int i = 0; i < MapTeleports.Count; ++i)
|
||||
{
|
||||
for (int j = i + 1; j < MapTeleports.Count; ++j)
|
||||
{
|
||||
var mp0 = MapTeleports[i];
|
||||
var mp1 = MapTeleports[j];
|
||||
var angle = GetAngleOfTwoPoints(mp0, mp1);
|
||||
if (Math.Abs(angle - refAngle) < 5)
|
||||
{
|
||||
// Step 4: Assuming this pair corresponds to the reference points
|
||||
var mpDist = mp0.DistanceTo(mp1);
|
||||
var rpDist = rp0.DistanceTo(rp1);
|
||||
var scale = mpDist / rpDist;
|
||||
var deltaX = mp0.X - rp0.X * scale;
|
||||
var deltaY = mp0.Y - rp0.Y * scale;
|
||||
|
||||
Func<Point, Point> transformPoint = i =>
|
||||
{
|
||||
return new Point(i.X * scale + deltaX, i.Y * scale + deltaY);
|
||||
};
|
||||
|
||||
var transformedPoints = teleportPoints.Select(i => transformPoint(i)).ToList();
|
||||
// Step 5: Check how close the fit is
|
||||
double totalDeviation = 0;
|
||||
foreach (var p in transformedPoints)
|
||||
{
|
||||
var minDist = MapTeleports.Select(i => i.DistanceTo(p)).Min();
|
||||
totalDeviation += minDist;
|
||||
}
|
||||
if (totalDeviation < minDeviation)
|
||||
{
|
||||
minDeviation = totalDeviation;
|
||||
transformParamScale = scale;
|
||||
transformParamDeltaX = deltaX;
|
||||
transformParamDeltaY = deltaY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Logger.LogInformation("Min deviation: {d}", minDeviation);
|
||||
if (minDeviation < 200)
|
||||
{
|
||||
Func<Point, Point> transformPoint = i =>
|
||||
{
|
||||
return new Point(i.X * transformParamScale + transformParamDeltaX, i.Y * transformParamScale + transformParamDeltaY);
|
||||
};
|
||||
var pTopLeft = transformPoint(new Point(0, 0));
|
||||
var pBottomRight = transformPoint(new Point(greyBigMapMat.Width, greyBigMapMat.Height));
|
||||
// Logger.LogInformation("Rect: {a}, {b}", pTopLeft, pBottomRight);
|
||||
return new Rect(pTopLeft.X, pTopLeft.Y, pBottomRight.X - pTopLeft.X, pBottomRight.Y - pTopLeft.Y);
|
||||
}
|
||||
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -316,15 +316,16 @@ public class GameLoadingTrigger : ITaskTrigger
|
||||
|
||||
if (process != null && loginWindow != IntPtr.Zero)
|
||||
{
|
||||
var dpiScale = TaskContext.Instance().DpiScale;
|
||||
if (windowType.Contains("协议"))
|
||||
{
|
||||
GameCaptureRegion.GameRegion1080PPosClick(1030, 615);
|
||||
GameCaptureRegion.GameRegion1080PPosClick(960 + 70 * dpiScale, 540 + 75 * dpiScale);
|
||||
}
|
||||
|
||||
if (windowType.Contains("登录"))
|
||||
{
|
||||
Thread.Sleep(2000);
|
||||
GameCaptureRegion.GameRegion1080PPosClick(960, 630);
|
||||
GameCaptureRegion.GameRegion1080PPosClick(960, 540 + 90 * dpiScale);
|
||||
Thread.Sleep(2000);
|
||||
|
||||
// 检查窗口是否还存在
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
|
||||
count++;
|
||||
}
|
||||
}
|
||||
avgColSpacing = count == 0 ? 0 : Math.Round(((double)sum) / count, MidpointRounding.AwayFromZero);
|
||||
avgColSpacing = count == 0 ? 0 : ((double)sum) / count;
|
||||
}
|
||||
{
|
||||
int count = 0;
|
||||
@@ -130,11 +130,11 @@ namespace BetterGenshinImpact.GameTask.Model.GameUI
|
||||
count++;
|
||||
}
|
||||
}
|
||||
avgRowSpace = count == 0 ? 0 : Math.Round(((double)sum) / count, MidpointRounding.AwayFromZero);
|
||||
avgRowSpace = count == 0 ? 0 : ((double)sum) / count;
|
||||
}
|
||||
|
||||
int avgLeft = (int)Math.Round(cells.Average(c => c.Rect.X - (avgWidth + avgColSpacing) * c.ColNum), MidpointRounding.AwayFromZero);
|
||||
int avgTop = (int)Math.Round(cells.Average(c => c.Rect.Y - (avgHeight + avgRowSpace) * c.RowNum), MidpointRounding.AwayFromZero);
|
||||
double avgLeft = cells.Average(c => c.Rect.X - (avgWidth + avgColSpacing) * c.ColNum);
|
||||
double avgTop = cells.Average(c => c.Rect.Y - (avgHeight + avgRowSpace) * c.RowNum);
|
||||
|
||||
for (int i = 0; i < cells.Max(r => r.ColNum) + 1; i++)
|
||||
{
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@@ -14,6 +14,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using BetterGenshinImpact.GameTask.GameLoading;
|
||||
using Fischless.GameCapture.Graphics;
|
||||
using BetterGenshinImpact.Service;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask
|
||||
@@ -39,6 +40,15 @@ namespace BetterGenshinImpact.GameTask
|
||||
|
||||
private static readonly object _triggerListLocker = new();
|
||||
|
||||
private User32.HWINEVENTHOOK _winEventHookMoveSize;
|
||||
private User32.HWINEVENTHOOK _winEventHookLocation;
|
||||
private User32.WinEventProc _winEventProc;
|
||||
private const uint EVENT_SYSTEM_MOVESIZESTART = 0x000A;
|
||||
private const uint EVENT_SYSTEM_MOVESIZEEND = 0x000B;
|
||||
private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
|
||||
private const uint WINEVENT_SKIPOWNTHREAD = 0x0001;
|
||||
private const uint WINEVENT_SKIPOWNPROCESS = 0x0002;
|
||||
|
||||
public event EventHandler? UiTaskStopTickEvent;
|
||||
|
||||
public event EventHandler? UiTaskStartTickEvent;
|
||||
@@ -132,6 +142,12 @@ namespace BetterGenshinImpact.GameTask
|
||||
}
|
||||
);
|
||||
|
||||
// 使用 SetWinEventHook 监听窗口移动和大小变化事件
|
||||
_winEventProc = WinEventCallback;
|
||||
var flags = (User32.WINEVENT)(WINEVENT_SKIPOWNPROCESS | WINEVENT_SKIPOWNTHREAD);
|
||||
_winEventHookMoveSize = User32.SetWinEventHook(EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND, default, _winEventProc, 0, 0, flags);
|
||||
_winEventHookLocation = User32.SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, default, _winEventProc, 0, 0, flags);
|
||||
|
||||
// 启动定时器
|
||||
_frameIndex = 0;
|
||||
_timer.Interval = interval;
|
||||
@@ -147,6 +163,17 @@ namespace BetterGenshinImpact.GameTask
|
||||
GameCapture?.Stop();
|
||||
_gameRect = RECT.Empty;
|
||||
_prevGameActive = false;
|
||||
PictureInPictureService.Hide(resetManual: true);
|
||||
if (_winEventHookMoveSize != default)
|
||||
{
|
||||
User32.UnhookWinEvent(_winEventHookMoveSize);
|
||||
_winEventHookMoveSize = default;
|
||||
}
|
||||
if (_winEventHookLocation != default)
|
||||
{
|
||||
User32.UnhookWinEvent(_winEventHookLocation);
|
||||
_winEventHookLocation = default;
|
||||
}
|
||||
}
|
||||
|
||||
public void StartTimer()
|
||||
@@ -195,6 +222,7 @@ namespace BetterGenshinImpact.GameTask
|
||||
_logger.LogInformation("游戏已退出,BetterGI 自动停止截图器");
|
||||
}
|
||||
|
||||
PictureInPictureService.Hide(resetManual: true);
|
||||
UiTaskStopTickEvent?.Invoke(sender, e);
|
||||
maskWindow.Invoke(maskWindow.Hide);
|
||||
return;
|
||||
@@ -202,6 +230,8 @@ namespace BetterGenshinImpact.GameTask
|
||||
|
||||
// 检查游戏是否在前台
|
||||
var hasBackgroundTriggerToRun = false;
|
||||
var autoSkipConfig = TaskContext.Instance().Config.AutoSkipConfig;
|
||||
var shouldShowPictureInPicture = autoSkipConfig.Enabled && autoSkipConfig.PictureInPictureEnabled && !PictureInPictureService.IsManuallyClosed;
|
||||
var active = SystemControl.IsGenshinImpactActive();
|
||||
if (!active)
|
||||
{
|
||||
@@ -244,15 +274,21 @@ namespace BetterGenshinImpact.GameTask
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasBackgroundTriggerToRun && shouldShowPictureInPicture)
|
||||
{
|
||||
hasBackgroundTriggerToRun = true;
|
||||
}
|
||||
|
||||
if (!hasBackgroundTriggerToRun)
|
||||
{
|
||||
// 没有后台运行的触发器,这次不再进行截图
|
||||
PictureInPictureService.Hide();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PictureInPictureService.Hide(resetManual: true);
|
||||
if (!TaskContext.Instance().Config.MaskWindowConfig.UseSubform)
|
||||
{
|
||||
// if (!_prevGameActive)
|
||||
@@ -272,11 +308,11 @@ namespace BetterGenshinImpact.GameTask
|
||||
}
|
||||
|
||||
_prevGameActive = active;
|
||||
// 移动游戏窗口的时候同步遮罩窗口的位置,此时不进行捕获
|
||||
if (SyncMaskWindowPosition())
|
||||
{
|
||||
return;
|
||||
}
|
||||
// // 移动游戏窗口的时候同步遮罩窗口的位置,此时不进行捕获
|
||||
// if (SyncMaskWindowPosition())
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
}
|
||||
|
||||
if (_triggers == null || !_triggers.Exists(t => t.IsEnabled))
|
||||
@@ -299,6 +335,15 @@ namespace BetterGenshinImpact.GameTask
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldShowPictureInPicture && !active)
|
||||
{
|
||||
PictureInPictureService.Update(bitmap);
|
||||
}
|
||||
else
|
||||
{
|
||||
PictureInPictureService.Hide();
|
||||
}
|
||||
|
||||
// 循环执行所有触发器 有独占状态的触发器的时候只执行独占触发器
|
||||
var content = new CaptureContent(bitmap, _frameIndex, _timer.Interval);
|
||||
|
||||
@@ -382,6 +427,24 @@ namespace BetterGenshinImpact.GameTask
|
||||
return rect.Width == 0 || rect.Height == 0;
|
||||
}
|
||||
|
||||
private void WinEventCallback(User32.HWINEVENTHOOK hWinEventHook, uint @event, HWND hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
|
||||
{
|
||||
var target = TaskContext.Instance().GameHandle;
|
||||
if (target == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (idObject != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var hwndPtr = hwnd.DangerousGetHandle();
|
||||
if (hwndPtr == target)
|
||||
{
|
||||
SyncMaskWindowPosition();
|
||||
}
|
||||
}
|
||||
|
||||
public void TakeScreenshot()
|
||||
{
|
||||
try
|
||||
@@ -430,4 +493,4 @@ namespace BetterGenshinImpact.GameTask
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ public class ScriptObjectConverter
|
||||
|
||||
/// <summary>
|
||||
/// <para>适用集合的重载</para>
|
||||
/// 如果<paramref name="propertyName"/>解析失败,默认返回一个空集合;
|
||||
/// 如果集合元素解析失败,将跳过该元素
|
||||
/// 如果<paramref name="propertyName"/>解析失败,返回null;
|
||||
/// 如果集合元素解析失败,将跳过该元素,因此有可能返回空集合
|
||||
/// <para>仅支持一层集合,<typeparamref name="T"/>不能再是集合</para>
|
||||
/// <para>避开反射,享受健康生活</para>
|
||||
/// </summary>
|
||||
@@ -66,7 +66,7 @@ public class ScriptObjectConverter
|
||||
/// <param name="source"></param>
|
||||
/// <param name="propertyName"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<T> GetValue<T>(ScriptObject source, string propertyName)
|
||||
public static IEnumerable<T>? GetValue<T>(ScriptObject source, string propertyName)
|
||||
{
|
||||
if (source[propertyName] is not Undefined && source[propertyName] != null)
|
||||
{
|
||||
@@ -74,7 +74,7 @@ public class ScriptObjectConverter
|
||||
|
||||
return TryMap<T>(v8Value);
|
||||
}
|
||||
return new T[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
@@ -22,12 +24,16 @@ public class SettingItem
|
||||
{
|
||||
var list = new List<UIElement>();
|
||||
|
||||
var label = new Label
|
||||
if (!String.IsNullOrEmpty(Label))
|
||||
{
|
||||
Content = Label,
|
||||
Margin = new Thickness(0, 0, 0, 5)
|
||||
};
|
||||
list.Add(label);
|
||||
var label = new TextBlock
|
||||
{
|
||||
Text = Label,
|
||||
Margin = new Thickness(0, 0, 0, 5),
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
};
|
||||
list.Add(label);
|
||||
}
|
||||
|
||||
var binding = new Binding
|
||||
{
|
||||
@@ -36,11 +42,19 @@ public class SettingItem
|
||||
};
|
||||
switch (Type)
|
||||
{
|
||||
case "separator":
|
||||
list.Add(new Separator
|
||||
{
|
||||
Margin = new Thickness(0, 0, 0, 2)
|
||||
});
|
||||
break;
|
||||
|
||||
case "input-text":
|
||||
var textBox = new TextBox
|
||||
{
|
||||
Name = Name,
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
};
|
||||
if (Default != null)
|
||||
{
|
||||
@@ -101,6 +115,62 @@ public class SettingItem
|
||||
list.Add(checkBox);
|
||||
break;
|
||||
|
||||
case "multi-checkbox":
|
||||
{
|
||||
var checkedValues = new List<string>();
|
||||
if (context is IDictionary<string, object?> ctx)
|
||||
{
|
||||
if (!ctx.ContainsKey(Name))
|
||||
{
|
||||
if (Default is JsonElement j)
|
||||
{
|
||||
ctx[Name] = j.Deserialize<List<string>>();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx[Name] = new List<string>();
|
||||
}
|
||||
}
|
||||
else if (ctx[Name] is List<object> listOfObjects)
|
||||
{
|
||||
ctx[Name] = listOfObjects.Select(i => (string)i).ToList();
|
||||
}
|
||||
checkedValues = (List<string>)ctx[Name]!;
|
||||
}
|
||||
var wrapPanel = new WrapPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal
|
||||
};
|
||||
if (Options != null)
|
||||
{
|
||||
foreach (var option in Options)
|
||||
{
|
||||
var box = new CheckBox
|
||||
{
|
||||
Content = option,
|
||||
IsChecked = checkedValues.Contains(option),
|
||||
};
|
||||
RoutedEventHandler callback = (sender, e) =>
|
||||
{
|
||||
bool isChecked = ((CheckBox)sender).IsChecked ?? false;
|
||||
if (isChecked && !checkedValues.Contains(option))
|
||||
{
|
||||
checkedValues.Add(option);
|
||||
}
|
||||
else if (!isChecked)
|
||||
{
|
||||
checkedValues.Remove(option);
|
||||
}
|
||||
};
|
||||
box.Checked += callback;
|
||||
box.Unchecked += callback;
|
||||
wrapPanel.Children.Add(box);
|
||||
}
|
||||
}
|
||||
list.Add(wrapPanel);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Exception($"Unknown setting type: {Type}");
|
||||
}
|
||||
|
||||
85
BetterGenshinImpact/Service/PictureInPictureService.cs
Normal file
85
BetterGenshinImpact/Service/PictureInPictureService.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using BetterGenshinImpact.Helpers;
|
||||
using BetterGenshinImpact.View.Windows;
|
||||
using OpenCvSharp;
|
||||
using System;
|
||||
using BetterGenshinImpact.GameTask;
|
||||
using BetterGenshinImpact.GameTask.AutoSkip;
|
||||
|
||||
namespace BetterGenshinImpact.Service;
|
||||
|
||||
public static class PictureInPictureService
|
||||
{
|
||||
private static PictureInPictureWindow? _window;
|
||||
private static bool _manualClosed;
|
||||
|
||||
public static bool IsManuallyClosed => _manualClosed;
|
||||
|
||||
public static void Update(Mat frame)
|
||||
{
|
||||
if (_manualClosed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Mat? copy = null;
|
||||
if (TaskContext.Instance().Config.AutoSkipConfig.PictureInPictureSourceType == nameof(PictureSourceType.TriggerDispatcher))
|
||||
{
|
||||
copy = frame.Clone();
|
||||
}
|
||||
UIDispatcherHelper.BeginInvoke(() =>
|
||||
{
|
||||
EnsureWindow();
|
||||
if (_window == null)
|
||||
{
|
||||
copy?.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_window.IsVisible)
|
||||
{
|
||||
_window.Show();
|
||||
}
|
||||
|
||||
_window.SetFrame(copy);
|
||||
});
|
||||
}
|
||||
|
||||
public static void Hide(bool resetManual = false)
|
||||
{
|
||||
UIDispatcherHelper.BeginInvoke(() =>
|
||||
{
|
||||
if (resetManual)
|
||||
{
|
||||
_manualClosed = false;
|
||||
}
|
||||
|
||||
if (_window != null && _window.IsVisible)
|
||||
{
|
||||
_window.Hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void ResetManualClose()
|
||||
{
|
||||
_manualClosed = false;
|
||||
}
|
||||
|
||||
private static void EnsureWindow()
|
||||
{
|
||||
if (_window != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_window = new PictureInPictureWindow();
|
||||
_window.ClosedByUser += () =>
|
||||
{
|
||||
_manualClosed = true;
|
||||
};
|
||||
_window.Closed += (_, _) =>
|
||||
{
|
||||
_window = null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -583,7 +583,7 @@ public partial class ScriptService : IScriptService
|
||||
continue;
|
||||
}
|
||||
|
||||
var content = TaskControl.CaptureToRectArea();
|
||||
using var content = TaskControl.CaptureToRectArea();
|
||||
if (Bv.IsInMainUi(content) || Bv.IsInAnyClosableUi(content) || Bv.IsInDomain(content))
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// 作者:火山
|
||||
// 描述:万能战斗策略(新手推荐)。需要在,调度器→配置组→设置→战斗配置(开启)→自动检测战斗结束(开启)→更快检查结束战斗(开启)→旋转寻找敌人位置(开启)→检查战斗结束的延时(0.1)→按键触发后检查延时(0.35)→盾奶角色优先释放技能(开启,设置你的盾位)→战斗结束后使用万叶长E手机掉落物(开启)
|
||||
|
||||
// 盾(刚需)
|
||||
茜特菈莉 attack,e,wait(0.2),keypress(q),wait(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
伊涅芙 e,attack(0.22),keypress(q),wait(0.1),keypress(q),attack(0.2),keypress(q),attack(0.2)
|
||||
钟离 s(0.2), e(hold), wait(0.2), w(0.2),keypress(q),wait(0.2),keypress(q),attack(0.1)
|
||||
莱依拉 e,wait(0.2), keypress(q),wait(0.2),keypress(q),attack(0.2),keypress(q),attack(0.2)
|
||||
绮良良 e,attack(0.2), keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
托马 e,attack(0.22),keypress(q),wait(0.1),keypress(q),attack(0.2),keypress(q),attack(0.2)
|
||||
蓝砚 e,attack(0.15), click(middle),attack(0.15),click(middle),attack(0.15),click(middle),wait(0.2).dash(0.1),attack(0.2)
|
||||
|
||||
// 后台、挂元素、副C、先手
|
||||
玛薇卡 attack(0.2),e
|
||||
迪希雅 e,attack(0.2),e
|
||||
香菱 e,wait(0.3),keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
仆人 attack,e
|
||||
那维莱特 attack(0.23),e
|
||||
纳西妲 e(hold),click(middle),keypress(q),wait(0.3),keypress(q),attack(0.3),keypress(q),attack(0.2)
|
||||
艾梅莉埃 e,attack(0.2), keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
丝柯克 attack(0.2),click(middle),keypress(q),wait(0.05),keypress(q),attack(0.05),click(middle),keydown(E),wait(0.22),attack(0.08),click(middle),keyup(E),keypress(q),wait(0.08),keypress(q)
|
||||
芙宁娜 e,attack(0.2), keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
白术 e,attack(0.2)
|
||||
芭芭拉 e,attack(0.2)
|
||||
希格雯 e(hold),wait(0.2),keypress(q),wait(0.2),keypress(q)
|
||||
爱可菲 e,attack(0.2), keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
菲谢尔 e
|
||||
欧洛伦 e,attack(0.3), keypress(q),wait(0.2),attack(0.3),keypress(q),wait(0.2),attack(0.3),keypress(q),wait(0.3)
|
||||
雷电将军 e,attack(0.22),keypress(q),wait(0.1),keypress(q),attack(0.2),keypress(q),attack(0.2)
|
||||
久岐忍 e,wait(0.2),keypress(q),attack(0.15),keypress(q),e
|
||||
瓦雷莎 e, attack(1.25),wait(0.45), s(0.4), click(middle), e, attack(1.25), wait(0.3),keypress(q), wait(0.45)
|
||||
|
||||
|
||||
// 中置位
|
||||
夏沃蕾 attack(0.08),keypress(q),wait(0.2),keypress(q),wait(0.2),attack(0.2),keydown(e),wait(0.15), moveby(0,900),wait(0.15),keyup(e),attack(0.15)
|
||||
白术 attack(0.2), keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
那维莱特 attack(0.08),keypress(q),wait(0.22),keypress(q),wait(0.2),keypress(q),e
|
||||
|
||||
|
||||
//减抗
|
||||
希诺宁 s(0.2),e,w(0.2),attack(0.35),wait(0.1),attack(0.35),keypress(x), wait(0.2), keypress(q), wait(0.3), keypress(q),keypress(x), wait(0.08), keypress(x),attack(0.2)
|
||||
枫原万叶 attack(0.08),keypress(q),wait(0.3),keypress(q),wait(0.3),attack(0.2),keydown(E),wait(0.48),keyup(E),attack(0.3), wait(0.5),attack(0.1)
|
||||
砂糖 e,attack(0.2),keypress(q),attack(0.2),keypress(q),e,attack(0.2)
|
||||
|
||||
//爆发
|
||||
玛薇卡 e,click(middle),wait(0.12), keypress(q), wait(0.3), keypress(q), wait(0.3), charge(3.8), keydown(space), wait(0.1), keyup(space), attack(0.2),wait(0.2)
|
||||
|
||||
//收尾,长轴
|
||||
那维莱特 charge(3),j,wait(0.3)
|
||||
丝柯克 attack(0.05),keypress(e),wait(0.05),keypress(e),wait(0.2),attack(2.27),keypress(Q),dash,attack(2.27),keydown(S),keypress(Q),dash,keyup(S),attack(2.27),wait(0.11),charge(0.3),attack(1)
|
||||
迪希雅 keypress(q),attack(0.1),dash(0.2),keypress(q),attack(0.3),keypress(q),attack(0.3),keypress(q),attack(0.3),keydown(S),attack(0.5),keyup(S),keydown(W),attack(0.5),keyup(W),keydown(S),attack(0.5),keyup(S),keydown(W),attack(0.5),keyup(W),keydown(S),attack(0.5),keyup(S),keydown(W),attack(0.5),keyup(W),keydown(S),attack(0.5),keyup(S)
|
||||
娜维娅 keypress(q),attack(0.1),keypress(q),attack(0.1),keypress(q),attack(0.1),keypress(q),keydown(E),wait(0.8),keyup(E),attack(1.6),keydown(E),wait(0.8),keyup(E),attack(0.1),keydown(S),attack(0.33),keyup(S),keydown(W),attack(0.3),keyup(W),keydown(S),attack(0.3),keyup(S),keydown(W),attack(0.3),keyup(W),keydown(S),attack(0.3),keyup(S),keydown(W),attack(0.3),keyup(W),attack(0.2)
|
||||
瓦雷莎 e, attack(1.25),wait(0.45), s(0.4), e, attack(1.25), wait(0.3),keypress(q), wait(0.45),s(0.4),e, attack(1.25),wait(0.45), s(0.4), e, attack(1.25), wait(0.3),keypress(q), attack(0.45)
|
||||
仆人 charge(0.35),j,attack(3.2),j,attack(0.3),attack(0.52),keypress(q),attack(0.2)
|
||||
101
BetterGenshinImpact/User/AutoFight/群友分享/万能战斗策略(萌新推荐).txt
Normal file
101
BetterGenshinImpact/User/AutoFight/群友分享/万能战斗策略(萌新推荐).txt
Normal file
@@ -0,0 +1,101 @@
|
||||
// 作者:火山
|
||||
// 描述:万能战斗策略(新手推荐)。内含详细使用说明,请务必打开本文档!!!(内含少部分满命、进阶操作,默认已注释不开启)
|
||||
// 版本:v1.6
|
||||
|
||||
// 战斗配置设置流程
|
||||
// 该设置从调度器入口开始,按以下层级路径操作,核心为开启相关功能并配置关键参数。
|
||||
// 1. 进入战斗配置页面
|
||||
// 路径:调度器 → 配置组 → 设置 → 战斗配置操作:将 “战斗配置” 功能开启
|
||||
|
||||
// 2. 战斗结束检测相关设置
|
||||
// 所有子项均需开启,用于快速识别战斗状态,提升效率。
|
||||
// 自动检测战斗结束:开启
|
||||
// 更快检查结束战斗:开启,参数设置为 1
|
||||
// 检查战斗结束的延时:设置为 0.4
|
||||
// 按键触发后检查延时:设置为 0.4
|
||||
|
||||
// 3. 敌人定位与面向设置
|
||||
// 用于自动调整角色朝向,优化战斗操作。
|
||||
// 旋转寻找敌人位置:开启
|
||||
// Q 前检测:开启
|
||||
// 尝试面向敌人:可选开启(根据需求选择)
|
||||
|
||||
// 4. 技能释放与角色优先级设置
|
||||
// 核心为盾奶角色技能优先释放,需准确配置角色位置。
|
||||
// 盾奶角色优先释放技能:开启
|
||||
// 关键设置:在队伍栏中,选择盾角色对应的序号(务必准确,不可选错)。
|
||||
|
||||
// 5. 掉落物拾取设置
|
||||
// 需根据队伍角色情况选择开启,核心注意万叶 / 琴的特殊设置。
|
||||
// 自动拾取掉落物:禁止开启(队伍中有万叶 / 琴时,一定要关闭!!!)
|
||||
// 聚集材料动作设置:开启
|
||||
// 功能:自动选择万叶或琴(优先万叶),通过长 E 动作收集掉落物。
|
||||
|
||||
// 以下是战斗策略正文,注意合理搭配队伍
|
||||
// !!!请严格遵守BGI战斗配队逻辑!!!(以 盾【刚需】+主c【可以是后台输出角色】+副c【可以是后台输出角色】+万叶/琴【如需要捡掉落物的可以带,比如精英怪,小怪、副本可酌情不带换成,奶妈 or 后台输出角色】)
|
||||
|
||||
// 盾(刚需)
|
||||
茜特菈莉 e,attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2),keypress(q),attack(0.2),keypress(e)
|
||||
伊涅芙 e,attack(0.22),keypress(q),wait(0.1),keypress(q),attack(0.2),keypress(q),attack(0.2)
|
||||
钟离 s(0.2), e(hold), wait(0.2), w(0.2),keypress(q),wait(0.2),keypress(q),attack(0.1)
|
||||
莱依拉 e,wait(0.2), keypress(q),wait(0.2),keypress(q),attack(0.2),keypress(q),attack(0.2)
|
||||
绮良良 e,attack(0.2), keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
托马 e,attack(0.22),keypress(q),wait(0.1),keypress(q),attack(0.2),keypress(q),attack(0.2)
|
||||
蓝砚 e,attack(0.15), click(middle),attack(0.15),click(middle),attack(0.15),click(middle),wait(0.2).dash(0.1),attack(0.2)
|
||||
|
||||
// 后台、挂元素、副C、先手
|
||||
玛薇卡 attack(0.2),e,wait(0.2),keypress(q),attack(0.1)
|
||||
迪希雅 e,attack(0.2),e
|
||||
香菱 e,wait(0.3),keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
仆人 attack,e
|
||||
那维莱特 attack(0.05),click(middle),e,wait(0.15),keydown(VK_LBUTTON),wait(0.27),keyup(VK_LBUTTON),wait(0.15),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),keydown(VK_LBUTTON),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-1100),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1200),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1300),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-1100),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1200),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1300),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-1100),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1200),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1300),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),click(middle),j,click(middle),wait(0.05),keyup(VK_LBUTTON)
|
||||
纳西妲 e(hold),click(middle),keypress(q),wait(0.3),keypress(q),attack(0.3),keypress(q),attack(0.2)
|
||||
艾梅莉埃 e,attack(0.2), keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
丝柯克 attack(0.2),click(middle),keypress(q),wait(0.05),keypress(q),attack(0.05),click(middle),keydown(E),wait(0.22),attack(0.08),click(middle),keyup(E),keypress(q),wait(0.08),keypress(q)
|
||||
芙宁娜 e,attack(0.2),keydown(W),attack(0.3),keypress(q),keyup(W),attack(0.3),keypress(q),attack(0.1),keydown(S),attack(0.33),keyup(S)
|
||||
爱诺 e, wait(0.3), keypress(q), attack(0.22), keypress(q), attack(0.21)
|
||||
菈乌玛 attack(0.1),keypress(q),wait(0.01),keydown(E),wait(0.4),attack(0.3),keyup(E),attack(0.15),keypress(q),keydown(E),wait(0.4),attack(0.3),keyup(E),attack(0.2),wait(0.1)
|
||||
|
||||
// (有奈芙尔就去掉角色名之前的内容,→(//+一个空格 =// ) ←按照我注释的格式给上一行的菈乌玛长e策略注释掉,后面是短e策略,不消耗草露),菈乌玛 e,attack(0.2), keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
|
||||
白术 e,attack(0.2)
|
||||
珊瑚宫心海 e
|
||||
芭芭拉 e,attack(0.2)
|
||||
希格雯 e(hold),wait(0.2),attack(0.21),keypress(q),wait(0.2),keypress(q)
|
||||
爱可菲 e,attack(0.2), keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
菲谢尔 e
|
||||
欧洛伦 e,attack(0.3), keypress(q),wait(0.2),attack(0.3),keypress(q),wait(0.2),attack(0.3),keypress(q),wait(0.3)
|
||||
雷电将军 e,attack(0.22),keypress(q),wait(0.1),keypress(q),attack(0.2),keypress(q),attack(0.2)
|
||||
久岐忍 e,wait(0.2),keypress(q),attack(0.15),keypress(q),e
|
||||
瓦雷莎 e, attack(1.25),wait(0.45), s(0.4), click(middle), e, attack(1.25), wait(0.3),keypress(q), wait(0.45)
|
||||
|
||||
|
||||
// 中置位
|
||||
夏沃蕾 attack(0.08),keypress(q),wait(0.2),keypress(q),wait(0.2),attack(0.21),keydown(e),wait(0.15), moveby(0,1300),wait(0.18),keyup(e),attack(0.15)
|
||||
白术 attack(0.2), keypress(q),attack(0.2),keypress(q),wait(0.2),keypress(q),attack(0.2)
|
||||
// 奈芙尔 attack(0.08), keydown(VK_LBUTTON), keydown(E), wait(0.015), keyup(E), wait(0.015), wait(0.95), wait(0.015), keydown(VK_RBUTTON), wait(0.015), keyup(VK_RBUTTON), wait(0.015), keydown(S), wait(0.015), keyup(S), wait(0.25), keydown(W), wait(0.015), keyup(W), wait(0.85), keydown(VK_RBUTTON), wait(0.015), keyup(VK_RBUTTON), wait(0.015), keydown(S), wait(0.015), keyup(S), wait(0.25), keydown(W), wait(0.015), wait(0.015), keyup(W), wait(0.85), keydown(VK_RBUTTON), wait(0.015), keyup(VK_RBUTTON), wait(0.015), keydown(S), wait(0.015), keyup(S), wait(0.25), keydown(W), wait(0.015), keyup(W), wait(0.015), wait(0.015), wait(0.5), keyup(VK_LBUTTON),keypress(q),attack(0.2)
|
||||
|
||||
|
||||
//减抗
|
||||
希诺宁 s(0.2),e,w(0.2),attack(0.35),wait(0.1),attack(0.35),keypress(x), wait(0.2), keypress(q), wait(0.3), keypress(q),keypress(x), wait(0.08), keypress(x),attack(0.2)
|
||||
枫原万叶 attack(0.08),keypress(q),wait(0.3),keypress(q),wait(0.3),attack(0.2),keydown(E),wait(0.48),keyup(E),attack(0.3), wait(0.5),attack(0.1)
|
||||
砂糖 e,attack(0.2),keypress(q),attack(0.2),keypress(q),e,attack(0.2)
|
||||
琴 attack(0.21),keydown(e),wait(0.14), moveby(0,1300),wait(0.75),keyup(e),attack(0.12),keypress(q),attack(0.11),keypress(q),attack
|
||||
|
||||
//爆发
|
||||
玛薇卡 attack(0.08),keydown(E),wait(0.4),attack(0.2),wait(0.01),keyup(E),click(middle),keydown(s),mousedown,wait(0.01),keypress(q), wait(0.2), keypress(q), keyup(s),mouseup,charge(3.8), keydown(space), wait(0.1), keyup(space), attack(0.2),wait(0.2)
|
||||
|
||||
|
||||
//收尾,长轴
|
||||
那维莱特 attack(0.05),keypress(q),wait(0.05),keypress(q),click(middle),e,wait(0.15),keydown(VK_LBUTTON),wait(0.27),keyup(VK_LBUTTON),wait(0.15),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),keydown(VK_LBUTTON),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-1100),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1200),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1300),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-1100),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1200),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1300),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-1100),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1200),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1300),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),click(middle),j,click(middle),keyup(VK_LBUTTON),wait(0.5),attack(0.2), click(middle),wait(0.2),keydown(VK_LBUTTON),wait(0.35),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),keydown(VK_LBUTTON),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-1100),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1200),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1300),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-1100),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1200),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1300),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-2100),wait(0.05),moveby(1800,-1100),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1200),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,1300),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),moveby(1800,0),wait(0.05),click(middle),j,click(middle),wait(0.05),keyup(VK_LBUTTON)
|
||||
芭芭拉 e,attack(0.6),keypress(q),attack(2),charge(0.5),keypress(q),attack(0.2)
|
||||
丝柯克 attack(0.05),keypress(e),wait(0.05),keypress(e),wait(0.2),attack(2.27),keypress(Q),dash,attack(2.27),keydown(S),keypress(Q),dash,keyup(S),attack(2.27),wait(0.11),charge(0.3),attack(1)
|
||||
迪希雅 keypress(q),attack(0.1),dash(0.2),keypress(q),attack(0.3),keypress(q),attack(0.3),keypress(q),attack(0.3),keydown(S),attack(0.5),keyup(S),keydown(W),attack(0.5),keyup(W),keydown(S),attack(0.5),keyup(S),keydown(W),attack(0.5),keyup(W),keydown(S),attack(0.5),keyup(S),keydown(W),attack(0.5),keyup(W),keydown(S),attack(0.5),keyup(S)
|
||||
娜维娅 keypress(q),attack(0.1),keypress(q),attack(0.1),keypress(q),attack(0.1),keypress(q),keydown(E),wait(0.8),keyup(E),attack(1.6),keydown(E),wait(0.8),keyup(E),attack(0.1),keydown(S),attack(0.33),keyup(S),keydown(W),attack(0.3),keyup(W),keydown(S),attack(0.3),keyup(S),keydown(W),attack(0.3),keyup(W),keydown(S),attack(0.3),keyup(S),keydown(W),attack(0.3),keyup(W),attack(0.2)
|
||||
// (奈芙尔3zs)奈芙尔 attack(0.08), keydown(VK_LBUTTON), keydown(E), wait(0.015), keyup(E), wait(0.015), wait(0.95), wait(0.015), keydown(VK_RBUTTON), wait(0.015), keyup(VK_RBUTTON), wait(0.015), keydown(S), wait(0.015), keyup(S), wait(0.25), keydown(W), wait(0.015), keyup(W), wait(0.85), keydown(VK_RBUTTON), wait(0.015), keyup(VK_RBUTTON), wait(0.015), keydown(S), wait(0.015), keyup(S), wait(0.25), keydown(W), wait(0.015), wait(0.015), keyup(W), wait(0.85), keydown(VK_RBUTTON), wait(0.015), keyup(VK_RBUTTON), wait(0.015), keydown(S), wait(0.015), keyup(S), wait(0.25), keydown(W), wait(0.015), keyup(W), wait(0.015), wait(0.015), wait(0.5), keyup(VK_LBUTTON)
|
||||
// (满命6刀)芙宁娜 attack(0.01),e,attack(0.1),dash(0.1),jump,wait(0.2),keypress(q),keydown(W),attack(0.3),keypress(q),keyup(W),attack(0.3),keypress(q),attack(0.1),keydown(S),attack(0.33),keyup(S),keydown(W),attack(0.3),keyup(W),keydown(S),attack(0.3),keyup(S),keydown(W),attack(0.3),keyup(W),keydown(S),attack(0.3),keyup(S),keydown(W),attack(0.3),keyup(W)
|
||||
// (火神AZS)玛薇卡 q,attack(0.1),e,charge(0.6)4,dash(0.3),moveby(500,0),moveby(2300,0),attack(0.1),charge(0.7),dash(0.3),moveby(-500,0),moveby(-2300,-0),attack(0.1),charge(0.7),dash(0.3),moveby(500,0),moveby(2600,0),attack(0.1),charge(0.8),dash(0.3),moveby(-500,0),moveby(-2300,-0),attack(0.1),charge(0.8),dash(0.3),moveby(500,0),moveby(2300,0),attack(0.1),charge(0.8),dash(0.3),moveby(-500,0),moveby(-2300,-0)
|
||||
// (火神ZZS)玛薇卡 attack(0.08), keypress(q),attack(0.03),keypress(q),keydown(E),wait(0.35),keyup(E),attack(0.03),wait(0.25), keydown(VK_LBUTTON),wait(0.155),keydown(VK_RBUTTON),wait(0.18),keyup(VK_LBUTTON),wait(0.02),keyup(VK_RBUTTON),wait(0.02),keydown(VK_LBUTTON),wait(0.16),keydown(VK_RBUTTON),wait(0.18),keyup(VK_LBUTTON),wait(0.02),keyup(VK_RBUTTON),wait(0.1),keydown(VK_LBUTTON),wait(0.05),keyup(VK_LBUTTON),wait(0.05),keydown(VK_LBUTTON),wait(0.05),keyup(VK_LBUTTON),wait(1.25), keydown(VK_LBUTTON),wait(0.155),keydown(VK_RBUTTON),wait(0.18),keyup(VK_LBUTTON),wait(0.02),keyup(VK_RBUTTON),wait(0.02),keydown(VK_LBUTTON),wait(0.16),keydown(VK_RBUTTON),wait(0.18),keyup(VK_LBUTTON),wait(0.02),keyup(VK_RBUTTON),wait(0.1),keydown(VK_LBUTTON),wait(0.05),keyup(VK_LBUTTON),wait(0.05),keydown(VK_LBUTTON),wait(0.05),keyup(VK_LBUTTON),wait(0.83)
|
||||
// (回身 QE ZZS)玛薇卡 attack(0.03),keypress(q),keypress(q),keydown(E),wait(0.45),keyup(E),wait(0.2),keydown(VK_LBUTTON),wait(0.155),wait(0.359),keyup(VK_LBUTTON),wait(0.05),wait(0.05), keydown(VK_LBUTTON),wait(0.1),keyup(VK_LBUTTON),wait(0.05),keydown(VK_LBUTTON),wait(0.1),keyup(VK_LBUTTON),click(middle),wait(0.45), moveby(5500,0),wait(0.05),keydown(VK_LBUTTON),wait(0.125),keydown(VK_RBUTTON),wait(0.15),s(0.1),wait(0.1),keyup(VK_LBUTTON),wait(0.01),keyup(VK_RBUTTON),wait(0.08),keydown(VK_LBUTTON),wait(0.125),keydown(VK_RBUTTON),wait(0.1),s(0.1),wait(0.1),keyup(VK_LBUTTON),wait(0.01),keyup(VK_RBUTTON),wait(0.1),keydown(VK_LBUTTON),wait(0.05),keyup(VK_LBUTTON),wait(0.05),keydown(VK_LBUTTON),wait(0.05),keyup(VK_LBUTTON),wait(0.3)
|
||||
瓦雷莎 e, attack(1.25),click(middle),wait(0.45), keydown(s), e, attack(1.25),keyup(s), wait(0.3),keypress(q), click(middle),wait(0.45)
|
||||
仆人 charge(0.35), j, keydown(s),attack(1.17),attack(0.45),keyup(s),keydown(w),attack(0.38),attack(0.6),keyup(w),wait(0.2),keydown(s),attack(1.17),attack(0.45),keyup(s),keydown(w),attack(0.38),attack(0.6),keyup(w),wait(0.2)
|
||||
@@ -205,6 +205,10 @@ public partial class MaskWindow : Window
|
||||
|
||||
private void LogTextBoxTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (LogTextBox.Document.Blocks.FirstBlock is Paragraph p && p.Inlines.Count > 100)
|
||||
{
|
||||
(p.Inlines as System.Collections.IList).RemoveAt(0);
|
||||
}
|
||||
var textRange = new TextRange(LogTextBox.Document.ContentStart, LogTextBox.Document.ContentEnd);
|
||||
if (textRange.Text.Length > 10000)
|
||||
{
|
||||
|
||||
@@ -399,47 +399,41 @@
|
||||
</Grid>
|
||||
<Separator Margin="-18,0" BorderThickness="0,1,0,0" />
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
TextWrapping="Wrap">
|
||||
<ui:TextBlock.Inlines>
|
||||
<Run Text="启动参数" />
|
||||
<ui:HyperlinkButton Grid.Row="1"
|
||||
Margin="0,0,0,0"
|
||||
Padding="0"
|
||||
Command="{Binding OpenGameCommandLineDocumentCommand}"
|
||||
Cursor="Hand">
|
||||
<ui:HyperlinkButton.Content>
|
||||
<TextBlock FontSize="11" Text="打开文档" />
|
||||
</ui:HyperlinkButton.Content>
|
||||
</ui:HyperlinkButton>
|
||||
</ui:TextBlock.Inlines>
|
||||
</ui:TextBlock>
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="如果你不知道什么是启动参数请不要填写。"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<StackPanel Grid.Column="0" VerticalAlignment="Top">
|
||||
|
||||
<ui:TextBlock FontTypography="Body" TextWrapping="Wrap">
|
||||
<ui:TextBlock.Inlines>
|
||||
<Run Text="启动参数" />
|
||||
<ui:HyperlinkButton Margin="4,0,0,0"
|
||||
Padding="0"
|
||||
Command="{Binding OpenGameCommandLineDocumentCommand}"
|
||||
Cursor="Hand">
|
||||
<ui:HyperlinkButton.Content>
|
||||
<TextBlock FontSize="11" Text="打开文档" />
|
||||
</ui:HyperlinkButton.Content>
|
||||
</ui:HyperlinkButton>
|
||||
</ui:TextBlock.Inlines>
|
||||
</ui:TextBlock>
|
||||
|
||||
<ui:TextBlock Margin="0,4,0,0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="如果你不知道什么是启动参数请不要填写。"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<!-- 常见启动参数:无边框 -popupwindow 指定分辨率 -screen-width 1920 -screen-height 1080 -->
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="300"
|
||||
MaxWidth="800"
|
||||
<ui:TextBox Grid.Column="1"
|
||||
Width="300"
|
||||
Margin="0,0,36,0"
|
||||
VerticalAlignment="Top"
|
||||
PlaceholderText="示例:-screen-width 1920"
|
||||
Text="{Binding Config.GenshinStartConfig.GenshinStartArgs, Mode=TwoWay}"
|
||||
TextWrapping="Wrap" />
|
||||
TextWrapping="Wrap"
|
||||
AcceptsReturn="True" />
|
||||
</Grid>
|
||||
<Separator Margin="-18,0" BorderThickness="0,1,0,0" />
|
||||
<Grid Margin="16">
|
||||
|
||||
@@ -1713,6 +1713,34 @@
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="指定战斗队伍"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="输入预设队伍的名称,留空则不更换队伍"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Width="200"
|
||||
Margin="0,0,36,0"
|
||||
Text="{Binding Config.AutoStygianOnslaughtConfig.FightTeamName, Mode=TwoWay}"
|
||||
PlaceholderText="例如:队伍1" />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
@@ -260,6 +260,40 @@
|
||||
Margin="0,0,36,0"
|
||||
IsChecked="{Binding Config.AutoSkipConfig.RunBackgroundEnabled, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="画中画窗口(建议和后台模式同时开启)"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="游戏失焦时展示小窗,滚轮缩放,左键返回游戏,右键关闭"
|
||||
TextWrapping="Wrap" />
|
||||
<ComboBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,12,0"
|
||||
DisplayMemberPath="Value"
|
||||
ItemsSource="{Binding PictureInPictureSourceTypeDict}"
|
||||
SelectedValue="{Binding Config.AutoSkipConfig.PictureInPictureSourceType, Mode=TwoWay}"
|
||||
SelectedValuePath="Key" />
|
||||
<ui:ToggleSwitch Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="2"
|
||||
Margin="0,0,36,0"
|
||||
IsChecked="{Binding Config.AutoSkipConfig.PictureInPictureEnabled, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<!--<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -328,7 +362,7 @@
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="优先选择第一个选项、选择最后一个选项、不选择选项"
|
||||
Text="进入对话后,对话选项的选择方式"
|
||||
TextWrapping="Wrap" />
|
||||
<ComboBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
@@ -337,6 +371,47 @@
|
||||
ItemsSource="{Binding ClickChatOptionNames}"
|
||||
SelectedItem="{Binding Config.AutoSkipConfig.ClickChatOption, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Margin="16" x:Name="CustomPriorityOptionsGrid">
|
||||
<Grid.Style>
|
||||
<Style TargetType="Grid">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Config.AutoSkipConfig.ClickChatOption}" Value="自定义优先选项">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Style>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="自定义优先选项(后台模式无效)"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,-10,0,0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="多个选项用分号(中英文)分隔,或者换行"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="250"
|
||||
MinHeight="60"
|
||||
Margin="0,0,24,0"
|
||||
AcceptsReturn="True"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Text="{Binding Config.AutoSkipConfig.CustomPriorityOptions, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -363,6 +438,32 @@
|
||||
Margin="0,0,24,0"
|
||||
Text="{Binding Config.AutoSkipConfig.AfterChooseOptionSleepDelay, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="点击对话框确认按钮之前的延迟(毫秒)"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="方便在自动剧情的情况下,依旧能够看完全部对话框文字"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="90"
|
||||
Margin="0,0,24,0"
|
||||
Text="{Binding Config.AutoSkipConfig.BeforeClickConfirmDelay, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -971,4 +1072,4 @@
|
||||
</StackPanel>
|
||||
</ui:CardExpander>-->
|
||||
</StackPanel>
|
||||
</Page>
|
||||
</Page>
|
||||
|
||||
@@ -5,6 +5,7 @@ using BetterGenshinImpact.GameTask.Common;
|
||||
using BetterGenshinImpact.Helpers.Extensions;
|
||||
using BetterGenshinImpact.Helpers.Ui;
|
||||
using BetterGenshinImpact.ViewModel.Pages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenCvSharp;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
@@ -23,6 +24,7 @@ public partial class ArtifactOcrDialog
|
||||
private readonly double heightRatio;
|
||||
private readonly string? javaScript;
|
||||
private readonly AutoArtifactSalvageTask autoArtifactSalvageTask;
|
||||
public static ILogger Logger { get; } = App.GetLogger<ArtifactOcrDialog>();
|
||||
|
||||
public ArtifactOcrDialog(double xRatio, double yRatio, double widthRatio, double heightRatio, string title, string? javaScript = null)
|
||||
{
|
||||
@@ -37,15 +39,17 @@ public partial class ArtifactOcrDialog
|
||||
SourceInitialized += (s, e) => WindowHelper.TryApplySystemBackdrop(this);
|
||||
|
||||
MyTitleBar.Title = title;
|
||||
|
||||
_ = CaptureAsync();
|
||||
}
|
||||
|
||||
public async Task CaptureAsync()
|
||||
public async Task<bool> CaptureAsync()
|
||||
{
|
||||
// 没启动时候,启动截图器
|
||||
var homePageViewModel = App.GetService<HomePageViewModel>();
|
||||
if (!homePageViewModel!.TaskDispatcherEnabled) { await homePageViewModel.OnStartTriggerAsync(); }
|
||||
if (!homePageViewModel!.TaskDispatcherEnabled)
|
||||
{
|
||||
_ = homePageViewModel.OnStartTriggerAsync();
|
||||
return false;
|
||||
}
|
||||
|
||||
using var ra = TaskControl.CaptureToRectArea();
|
||||
using var card = ra.DeriveCrop(new OpenCvSharp.Rect((int)(ra.Width * xRatio), (int)(ra.Height * yRatio), (int)(ra.Width * widthRatio), (int)(ra.Height * heightRatio)));
|
||||
@@ -57,27 +61,35 @@ public partial class ArtifactOcrDialog
|
||||
|
||||
try
|
||||
{
|
||||
ArtifactStat artifact = this.autoArtifactSalvageTask.GetArtifactStat(card.SrcMat, OcrFactory.Paddle, out string allText);
|
||||
// 将CPU密集的OCR操作放到后台线程执行
|
||||
var (artifact, allText) = await Task.Run(() =>
|
||||
{
|
||||
ArtifactStat art = this.autoArtifactSalvageTask.GetArtifactStat(card.SrcMat, OcrFactory.Paddle, out string text);
|
||||
return (art, text);
|
||||
});
|
||||
|
||||
// 回到UI线程更新界面
|
||||
this.TxtRecognized.Text = allText;
|
||||
this.ModelStructure.Text = artifact.ToStructuredString();
|
||||
if (this.javaScript != null)
|
||||
{
|
||||
bool isMatch = Task.Run(() => AutoArtifactSalvageTask.IsMatchJavaScript(artifact, this.javaScript)).Result;
|
||||
bool isMatch = await AutoArtifactSalvageTask.IsMatchJavaScript(artifact, this.javaScript);
|
||||
this.RegexResult.Text = isMatch ? "匹配" : "不匹配";
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await HandleOcrExceptionAsync(e, card.SrcMat);
|
||||
_ = Task.Run(() => HandleOcrExceptionAsync(e, card.SrcMat));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async Task HandleOcrExceptionAsync(Exception e, Mat srcMat)
|
||||
{
|
||||
var result = ThemedMessageBox.Error(
|
||||
$"{e.Message}\n\n是否保存该圣遗物截图?(至log/autoArtifactSalvageException/)",
|
||||
"异常处理",
|
||||
Logger.LogError(e, "自动分解圣遗物-OCR识别异常");
|
||||
var result = await ThemedMessageBox.ErrorAsync(
|
||||
$"{e.Message}\n是否保存该圣遗物截图?(至log/autoArtifactSalvageException/)",
|
||||
"识别失败",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxResult.No
|
||||
);
|
||||
@@ -89,7 +101,6 @@ public partial class ArtifactOcrDialog
|
||||
string filePath = Path.Combine(directory, $"{DateTime.Now:yyyyMMddHHmmss}_GetArtifactStat.png");
|
||||
Cv2.ImWrite(filePath, srcMat);
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async void BtnOkClick(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -21,9 +21,10 @@
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<StackPanel>
|
||||
<ScrollViewer MaxHeight="350" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="0,0,20,0">
|
||||
|
||||
<!--<TextBlock Margin="0,0,0,10" Text="执行周期" />
|
||||
<!--<TextBlock Margin="0,0,0,10" Text="执行周期" />
|
||||
<ComboBox Margin="0,0,0,20"
|
||||
DisplayMemberPath="Value"
|
||||
ItemsSource="{Binding Source={StaticResource ScheduleDescriptionsProvider}}"
|
||||
@@ -33,64 +34,65 @@
|
||||
<TextBlock Margin="0,0,0,10" Text="执行次数" />
|
||||
<TextBox Margin="0,0,0,20" Text="{Binding RunNum, Mode=TwoWay}" />-->
|
||||
|
||||
<TextBlock Margin="0,0,0,10" Text="状态" />
|
||||
<ComboBox DisplayMemberPath="Value"
|
||||
ItemsSource="{Binding Source={StaticResource StatusDescriptionsProvider}}"
|
||||
SelectedValue="{Binding Status, Mode=TwoWay}"
|
||||
SelectedValuePath="Key" />
|
||||
<TextBlock Margin="0,10,0,5" Text="JS 通知权限"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}"/>
|
||||
<ComboBox Margin="0,0,0,10"
|
||||
ItemsSource="{Binding JsNotificationOptions}"
|
||||
DisplayMemberPath="Value"
|
||||
SelectedValuePath="Key"
|
||||
IsEnabled="{Binding GlobalJsNotificationEnabled}"
|
||||
ToolTip="{Binding JsNotificationToolTip}"
|
||||
SelectedValue="{Binding AllowJsNotification, Mode=TwoWay}"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}">
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Margin="0,10,0,5" Text="JS HTTP权限"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}"/>
|
||||
<TextBlock Margin="0,0,0,5"
|
||||
Text="允许脚本发送HTTP请求,可能会带来安全风险,请谨慎开启。"
|
||||
Foreground="Orange"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}"/>
|
||||
<TextBlock Margin="0,0,0,5"
|
||||
Text="注意:脚本更新http_allowed_urls后,需手动重新启用权限。"
|
||||
Foreground="Orange"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}"/>
|
||||
<ItemsControl Margin="0,0,0,0" ItemsSource="{Binding JsHTTPInfoText}"
|
||||
<TextBlock Margin="0,0,0,10" Text="状态" />
|
||||
<ComboBox DisplayMemberPath="Value"
|
||||
ItemsSource="{Binding Source={StaticResource StatusDescriptionsProvider}}"
|
||||
SelectedValue="{Binding Status, Mode=TwoWay}"
|
||||
SelectedValuePath="Key" />
|
||||
<TextBlock Margin="0,10,0,5" Text="JS 通知权限"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}"/>
|
||||
<ComboBox Margin="0,0,0,10"
|
||||
ItemsSource="{Binding JsNotificationOptions}"
|
||||
DisplayMemberPath="Value"
|
||||
SelectedValuePath="Key"
|
||||
IsEnabled="{Binding GlobalJsNotificationEnabled}"
|
||||
ToolTip="{Binding JsNotificationToolTip}"
|
||||
SelectedValue="{Binding AllowJsNotification, Mode=TwoWay}"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Text}"
|
||||
Foreground="{Binding Color}"
|
||||
FontSize="11"
|
||||
TextWrapping="NoWrap"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<ItemsControl Margin="0,0,0,10" ItemsSource="{Binding JsHTTPInfo}"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox Text="{Binding Text}"
|
||||
IsReadOnly="True"
|
||||
Foreground="{Binding Color}"
|
||||
FontSize="11"
|
||||
FontFamily="Consolas"
|
||||
TextWrapping="NoWrap"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<ComboBox DisplayMemberPath="Value"
|
||||
ItemsSource="{Binding JsNotificationOptions}"
|
||||
SelectedValue="{Binding AllowJsHTTP, Mode=TwoWay}"
|
||||
SelectedValuePath="Key"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}">
|
||||
</ComboBox>
|
||||
</ComboBox>
|
||||
|
||||
</StackPanel>
|
||||
<TextBlock Margin="0,10,0,5" Text="JS HTTP权限"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}"/>
|
||||
<TextBlock Margin="0,0,0,5"
|
||||
Text="允许脚本发送HTTP请求,可能会带来安全风险,请谨慎开启。"
|
||||
Foreground="Orange"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}"/>
|
||||
<TextBlock Margin="0,0,0,5"
|
||||
Text="注意:脚本更新http_allowed_urls后,需手动重新启用权限。"
|
||||
Foreground="Orange"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}"/>
|
||||
<ItemsControl Margin="0,0,0,0" ItemsSource="{Binding JsHTTPInfoText}"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Text}"
|
||||
Foreground="{Binding Color}"
|
||||
FontSize="11"
|
||||
TextWrapping="NoWrap"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<ItemsControl Margin="0,0,0,10" ItemsSource="{Binding JsHTTPInfo}"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBox Text="{Binding Text}"
|
||||
IsReadOnly="True"
|
||||
Foreground="{Binding Color}"
|
||||
FontSize="11"
|
||||
FontFamily="Consolas"
|
||||
TextWrapping="NoWrap"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<ComboBox DisplayMemberPath="Value"
|
||||
ItemsSource="{Binding JsNotificationOptions}"
|
||||
SelectedValue="{Binding AllowJsHTTP, Mode=TwoWay}"
|
||||
SelectedValuePath="Key"
|
||||
Visibility="{Binding IsJsScript, Converter={StaticResource BoolToVisConverter}}">
|
||||
</ComboBox>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
32
BetterGenshinImpact/View/Windows/PictureInPictureWindow.xaml
Normal file
32
BetterGenshinImpact/View/Windows/PictureInPictureWindow.xaml
Normal file
@@ -0,0 +1,32 @@
|
||||
<Window x:Class="BetterGenshinImpact.View.Windows.PictureInPictureWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Title="原神 - 画中画"
|
||||
Width="420"
|
||||
Height="240"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
Topmost="True"
|
||||
ShowInTaskbar="False"
|
||||
ResizeMode="NoResize"
|
||||
MouseWheel="OnMouseWheel"
|
||||
MouseLeftButtonDown="OnMouseLeftButtonDown"
|
||||
MouseLeftButtonUp="OnMouseLeftButtonUp"
|
||||
MouseMove="OnMouseMove"
|
||||
MouseRightButtonUp="OnMouseRightButtonUp">
|
||||
<Border x:Name="ContainerBorder"
|
||||
BorderBrush="#40FFFFFF"
|
||||
BorderThickness="1"
|
||||
CornerRadius="16"
|
||||
SnapsToDevicePixels="True"
|
||||
SizeChanged="OnBorderSizeChanged">
|
||||
<Image x:Name="PreviewImage"
|
||||
Stretch="Uniform"
|
||||
RenderOptions.BitmapScalingMode="Fant"
|
||||
RenderOptions.EdgeMode="Aliased" />
|
||||
</Border>
|
||||
</Window>
|
||||
238
BetterGenshinImpact/View/Windows/PictureInPictureWindow.xaml.cs
Normal file
238
BetterGenshinImpact/View/Windows/PictureInPictureWindow.xaml.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using BetterGenshinImpact.GameTask;
|
||||
using BetterGenshinImpact.Helpers.DpiAwareness;
|
||||
using BetterGenshinImpact.Helpers.Extensions;
|
||||
using Mat = OpenCvSharp.Mat;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media.Animation;
|
||||
using BetterGenshinImpact.Helpers;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Interop;
|
||||
using Vanara.PInvoke;
|
||||
using Size = OpenCvSharp.Size;
|
||||
using System.Windows.Media.Imaging;
|
||||
using BetterGenshinImpact.GameTask.AutoSkip;
|
||||
using BetterGenshinImpact.Service;
|
||||
|
||||
namespace BetterGenshinImpact.View.Windows;
|
||||
|
||||
public partial class PictureInPictureWindow : Window
|
||||
{
|
||||
private const double MinWidth = 220;
|
||||
private const double MaxWidth = 1280;
|
||||
private const double MarginSize = 16;
|
||||
|
||||
private double _aspectRatio = 16d / 9d;
|
||||
private bool _initializedPosition;
|
||||
private bool _pointerDown;
|
||||
private bool _dragging;
|
||||
private Point _downPoint;
|
||||
private Size _cacheSize;
|
||||
|
||||
public event Action? ClosedByUser;
|
||||
|
||||
public PictureInPictureWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
ShowActivated = false;
|
||||
Opacity = 0;
|
||||
Loaded += OnLoaded;
|
||||
CompositionTarget.Rendering += Loop;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var fade = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(120));
|
||||
BeginAnimation(OpacityProperty, fade);
|
||||
UpdateClip();
|
||||
}
|
||||
|
||||
private void Loop(object? sender, EventArgs e)
|
||||
{
|
||||
if (PictureInPictureService.IsManuallyClosed || !IsVisible || TaskContext.Instance().Config.AutoSkipConfig.PictureInPictureSourceType != nameof(PictureSourceType.CaptureLoop))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var mat = TaskTriggerDispatcher.GlobalGameCapture?.Capture();
|
||||
if (mat != null)
|
||||
{
|
||||
if (_cacheSize != mat.Size() || PreviewImage.Source == null)
|
||||
{
|
||||
PreviewImage.Source = mat.ToWriteableBitmap();
|
||||
_cacheSize = mat.Size();
|
||||
if (!_initializedPosition)
|
||||
{
|
||||
PositionNearGame(TaskContext.Instance().SystemInfo.CaptureAreaRect);
|
||||
_initializedPosition = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mat.UpdateWriteableBitmap((WriteableBitmap)PreviewImage.Source);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("截图失败");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFrame(Mat? frame)
|
||||
{
|
||||
if (frame == null || TaskContext.Instance().Config.AutoSkipConfig.PictureInPictureSourceType != nameof(PictureSourceType.TriggerDispatcher))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Dispatcher.CheckAccess())
|
||||
{
|
||||
// 转移所有权:后台线程把 frame 交给 UI 线程处理并释放
|
||||
_ = Dispatcher.BeginInvoke(new Action(() => SetFrame(frame)));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var size = new Size(frame.Width, frame.Height);
|
||||
if (_cacheSize != size || PreviewImage.Source is not WriteableBitmap wb)
|
||||
{
|
||||
var bitmap = frame.ToWriteableBitmap();
|
||||
PreviewImage.Source = bitmap;
|
||||
_cacheSize = size;
|
||||
UpdateSizeFromFrame(frame.Width, frame.Height);
|
||||
UpdateClip();
|
||||
if (!_initializedPosition)
|
||||
{
|
||||
PositionNearGame(TaskContext.Instance().SystemInfo.CaptureAreaRect);
|
||||
_initializedPosition = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.UpdateWriteableBitmap(wb);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
frame.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSizeFromFrame(int width, int height)
|
||||
{
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_aspectRatio = width * 1d / height;
|
||||
if (double.IsNaN(Width) || Width == 0)
|
||||
{
|
||||
var targetWidth = Math.Clamp(width / 4d, MinWidth, MaxWidth / 1.5);
|
||||
Width = targetWidth;
|
||||
Height = targetWidth / _aspectRatio;
|
||||
}
|
||||
}
|
||||
|
||||
private void PositionNearGame(RECT captureRect)
|
||||
{
|
||||
var dpi = DpiHelper.ScaleY;
|
||||
var targetLeft = captureRect.Left / dpi + captureRect.Width / dpi - Width - MarginSize;
|
||||
var targetTop = captureRect.Top / dpi + MarginSize;
|
||||
|
||||
Left = Math.Max(0, targetLeft);
|
||||
Top = Math.Max(0, targetTop);
|
||||
}
|
||||
|
||||
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
var ratio = e.Delta > 0 ? 1.08 : 0.92;
|
||||
var nextWidth = Math.Clamp(Width * ratio, MinWidth, MaxWidth);
|
||||
Width = nextWidth;
|
||||
Height = nextWidth / _aspectRatio;
|
||||
}
|
||||
|
||||
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_pointerDown = true;
|
||||
_dragging = false;
|
||||
_downPoint = e.GetPosition(this);
|
||||
}
|
||||
|
||||
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_dragging)
|
||||
{
|
||||
SystemControl.ActivateWindow();
|
||||
}
|
||||
|
||||
_pointerDown = false;
|
||||
_dragging = false;
|
||||
}
|
||||
|
||||
private void OnMouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ClosedByUser?.Invoke();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!_pointerDown || _dragging)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var current = e.GetPosition(this);
|
||||
if (Math.Abs(current.X - _downPoint.X) > 4 || Math.Abs(current.Y - _downPoint.Y) > 4)
|
||||
{
|
||||
_dragging = true;
|
||||
try
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBorderSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
UpdateClip();
|
||||
}
|
||||
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
base.OnSourceInitialized(e);
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
var exStyle = User32.GetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE);
|
||||
_ = User32.SetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE, exStyle | (int)User32.WindowStylesEx.WS_EX_TOOLWINDOW);
|
||||
}
|
||||
|
||||
private void UpdateClip()
|
||||
{
|
||||
if (PreviewImage == null || ContainerBorder == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var radius = ContainerBorder.CornerRadius.TopLeft;
|
||||
if (ContainerBorder.ActualWidth <= 0 || ContainerBorder.ActualHeight <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PreviewImage.Clip = new RectangleGeometry(new Rect(0, 0, ContainerBorder.ActualWidth, ContainerBorder.ActualHeight), radius, radius);
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
PreviewImage.Source = null;
|
||||
}
|
||||
}
|
||||
@@ -510,8 +510,11 @@ public partial class HomePageViewModel : ViewModel
|
||||
{
|
||||
Title = "硬件加速设置",
|
||||
Content = new HardwareAccelerationView(new HardwareAccelerationViewModel()),
|
||||
SizeToContent = SizeToContent.WidthAndHeight,
|
||||
ResizeMode = ResizeMode.NoResize,
|
||||
Width = 800,
|
||||
Height = 600,
|
||||
MinWidth = 800,
|
||||
MaxWidth = 800,
|
||||
MinHeight = 600,
|
||||
Owner = Application.Current.MainWindow,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
ExtendsContentIntoTitleBar = true,
|
||||
@@ -585,6 +588,7 @@ public partial class HomePageViewModel : ViewModel
|
||||
bitmap.BeginInit();
|
||||
bitmap.UriSource = new Uri(Path.GetFullPath(_customBannerImagePath));
|
||||
bitmap.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmap.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
|
||||
bitmap.EndInit();
|
||||
BannerImageSource = bitmap;
|
||||
Toast.Success("背景图片更换成功!");
|
||||
@@ -611,6 +615,7 @@ public partial class HomePageViewModel : ViewModel
|
||||
defaultBitmap.BeginInit();
|
||||
defaultBitmap.UriSource = new Uri(DefaultBannerImagePath, UriKind.Absolute);
|
||||
defaultBitmap.CacheOption = BitmapCacheOption.OnLoad;
|
||||
defaultBitmap.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
|
||||
defaultBitmap.EndInit();
|
||||
BannerImageSource = defaultBitmap;
|
||||
|
||||
|
||||
@@ -218,7 +218,11 @@ public partial class HotKeyPageViewModel : ObservableObject, IViewModel
|
||||
nameof(Config.HotKeyConfig.CancelTaskHotkey),
|
||||
Config.HotKeyConfig.CancelTaskHotkey,
|
||||
Config.HotKeyConfig.CancelTaskHotkeyType,
|
||||
(_, _) => { CancellationContext.Instance.ManualCancel(); }
|
||||
(_, _) =>
|
||||
{
|
||||
_logger.LogInformation("检测到您配置的停止快捷键{Key}按下,停止当前执行任务", Config.HotKeyConfig.CancelTaskHotkey);
|
||||
CancellationContext.Instance.ManualCancel();
|
||||
}
|
||||
));
|
||||
systemDirectory.Children.Add(new HotKeySettingModel(
|
||||
"暂停当前脚本/独立任务",
|
||||
|
||||
@@ -315,6 +315,7 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
Owner = Application.Current.MainWindow,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
};
|
||||
uiMessageBox.SourceInitialized += (s, e) => WindowHelper.TryApplySystemBackdrop(uiMessageBox);
|
||||
|
||||
void OnQuestionButtonOnClick(object sender, RoutedEventArgs args)
|
||||
{
|
||||
@@ -2011,8 +2012,11 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
{
|
||||
Title = "配置组设置",
|
||||
Content = new ScriptGroupConfigView(new ScriptGroupConfigViewModel(TaskContext.Instance().Config, SelectedScriptGroup.Config)),
|
||||
SizeToContent = SizeToContent.WidthAndHeight,
|
||||
ResizeMode = ResizeMode.NoResize,
|
||||
Width = 800,
|
||||
Height = 600,
|
||||
MinWidth = 800,
|
||||
MaxWidth = 800,
|
||||
MinHeight = 600,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
ExtendsContentIntoTitleBar = true,
|
||||
WindowBackdropType = WindowBackdropType.Auto,
|
||||
|
||||
@@ -555,7 +555,7 @@ public partial class TaskSettingsPageViewModel : ViewModel
|
||||
private async Task OnOpenArtifactSalvageTestOCRWindow()
|
||||
{
|
||||
ArtifactOcrDialog ocrDialog = new ArtifactOcrDialog(0.70, 0.112, 0.275, 0.50, "圣遗物分解", this.Config.AutoArtifactSalvageConfig.JavaScript);
|
||||
ocrDialog.ShowDialog();
|
||||
if (await ocrDialog.CaptureAsync()) { ocrDialog.ShowDialog(); }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -633,7 +633,7 @@ public partial class TaskSettingsPageViewModel : ViewModel
|
||||
[RelayCommand]
|
||||
private async Task OnGoToGetGridIconsUrlAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/getGridIcons.html"));
|
||||
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/dev/getGridIcons.html"));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using BetterGenshinImpact.GameTask.AutoPick;
|
||||
using BetterGenshinImpact.GameTask.AutoSkip.Assets;
|
||||
using BetterGenshinImpact.GameTask.AutoSkip.Model;
|
||||
using BetterGenshinImpact.GameTask.AutoSkip;
|
||||
using BetterGenshinImpact.Service.Interface;
|
||||
using BetterGenshinImpact.View.Pages;
|
||||
using BetterGenshinImpact.View.Windows;
|
||||
@@ -21,7 +22,7 @@ namespace BetterGenshinImpact.ViewModel.Pages;
|
||||
|
||||
public partial class TriggerSettingsPageViewModel : ViewModel
|
||||
{
|
||||
[ObservableProperty] private string[] _clickChatOptionNames = ["优先选择第一个选项", "随机选择选项", "优先选择最后一个选项", "不选择选项"];
|
||||
[ObservableProperty] private string[] _clickChatOptionNames = ["优先选择第一个选项", "随机选择选项","自定义优先选项", "优先选择最后一个选项", "不选择选项"];
|
||||
|
||||
[ObservableProperty] private string[] _selectChatOptionTypeNames = [SelectChatOptionTypes.UseMouse, SelectChatOptionTypes.UseInteractionKey];
|
||||
|
||||
@@ -29,6 +30,13 @@ public partial class TriggerSettingsPageViewModel : ViewModel
|
||||
|
||||
[ObservableProperty] private List<string> _pickButtonNames;
|
||||
|
||||
[ObservableProperty] private Dictionary<string, string> _pictureInPictureSourceTypeDict =
|
||||
new()
|
||||
{
|
||||
{ nameof(PictureSourceType.CaptureLoop), "60帧模式" },
|
||||
{ nameof(PictureSourceType.TriggerDispatcher), "截图器供图" }
|
||||
};
|
||||
|
||||
public AllConfig Config { get; set; }
|
||||
|
||||
private readonly INavigationService _navigationService;
|
||||
@@ -234,4 +242,4 @@ public partial class TriggerSettingsPageViewModel : ViewModel
|
||||
{
|
||||
_navigationService.Navigate(typeof(HotKeyPage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,20 @@ public class ScriptGroupProjectEditorViewModel : ObservableObject
|
||||
public ScriptGroupProjectEditorViewModel(ScriptGroupProject project)
|
||||
{
|
||||
_project = project ?? throw new ArgumentNullException(nameof(project));
|
||||
|
||||
// 如果是JS脚本,每次打开配置窗口时强制重新加载项目信息,以读取最新的manifest.json
|
||||
if (_project.Type == "Javascript")
|
||||
{
|
||||
try
|
||||
{
|
||||
_project.BuildScriptProjectRelation();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略加载失败,避免无法打开窗口,界面上会显示相关错误或为空
|
||||
}
|
||||
}
|
||||
|
||||
_globalNotificationConfig = TaskContext.Instance().Config.NotificationConfig;
|
||||
// 监听全局配置变更
|
||||
_project.PropertyChanged += (s, e) =>
|
||||
|
||||
@@ -98,6 +98,14 @@ public partial class MapViewerViewModel : ObservableObject
|
||||
{
|
||||
_mapImage = new Mat(Global.Absolute(@"Assets/Map/Enkanomiya/Enkanomiya_0_1024.png"));
|
||||
}
|
||||
else if (mapName == MapTypes.SeaOfBygoneEras.ToString())
|
||||
{
|
||||
_mapImage = new Mat(Global.Absolute(@"Assets/Map/SeaOfBygoneEras/SeaOfBygoneEras_0_1024.png"));
|
||||
}
|
||||
else if (mapName == MapTypes.AncientSacredMountain.ToString())
|
||||
{
|
||||
_mapImage = new Mat(Global.Absolute(@"Assets/Map/AncientSacredMountain/AncientSacredMountain_0_1024.png"));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("暂时不支持展示路径的地图类型:" + mapName);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BetterGenshinImpact.Core.Recognition.OpenCv;
|
||||
using BetterGenshinImpact.GameTask.AutoFight.Model;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace BetterGenshinImpact.Test.Cv;
|
||||
@@ -16,4 +17,13 @@ public class ImageDifferenceDetectorTest
|
||||
|
||||
Console.WriteLine($"差异最大的图片索引是: {i}");
|
||||
}
|
||||
|
||||
public static void Test2()
|
||||
{
|
||||
Console.WriteLine($"1: {PartyAvatarSideIndexHelper.CalculateWhiteEdgePixelsRatio(Cv2.ImRead(@"E:\1.png", ImreadModes.Grayscale))}");
|
||||
Console.WriteLine($"2: {PartyAvatarSideIndexHelper.CalculateWhiteEdgePixelsRatio(Cv2.ImRead(@"E:\2.png", ImreadModes.Grayscale))}");
|
||||
Console.WriteLine($"3: {PartyAvatarSideIndexHelper.CalculateWhiteEdgePixelsRatio(Cv2.ImRead(@"E:\3.png", ImreadModes.Grayscale))}");
|
||||
Console.WriteLine($"4: {PartyAvatarSideIndexHelper.CalculateWhiteEdgePixelsRatio(Cv2.ImRead(@"E:\4.png", ImreadModes.Grayscale))}");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
[InlineData(@"20250225101300361_ChooseBait_Succeeded.png", new string[] { "medaka", "butterflyfish", "butterflyfish", "pufferfish" })]
|
||||
[InlineData(@"20250226161354285_ChooseBait_Succeeded.png", new string[] { "medaka" })] // 不稳定的测试用例,因未学习被照亮的场景
|
||||
[InlineData(@"202503160917566615@900p.png", new string[] { "pufferfish" })]
|
||||
[InlineData(@"202509141339218213_ChooseBait.png", new string[] { "axehead fish" })]
|
||||
[InlineData(@"202509141339218213_ChooseBait.png", new string[] { "axehead" })]
|
||||
[InlineData(@"202509141339218213_ChooseBait.png", new string[] { "mauler shark", "crystal eye", "medaka", "medaka", "medaka" })]
|
||||
/// <summary>
|
||||
/// 测试各种选取鱼饵,结果为成功
|
||||
|
||||
Reference in New Issue
Block a user