Merge branch 'main' into d-v3

This commit is contained in:
辉鸭蛋
2025-12-23 00:30:26 +08:00
71 changed files with 2985 additions and 441 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"
}
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}
// 非攀爬状态下,检测是否卡死(脱困触发器)

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View File

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

View File

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

View File

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

View File

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

View File

@@ -45,4 +45,8 @@ public partial class AutoStygianOnslaughtConfig : ObservableObject
// 使用脆弱树脂刷取副本次数
[ObservableProperty]
private int _fragileResinUseCount = 0;
// 指定战斗队伍
[ObservableProperty]
private string _fightTeamName = "";
}

View File

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

View File

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

View File

@@ -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}]失败");
}

View File

@@ -549,4 +549,4 @@ public partial class AutoWoodTask : ISoloTask
throw new RetryException("未检测进入游戏界面");
}
}
}
}

View File

@@ -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);
// 检查窗口是否还存在

View File

@@ -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>
/// 点击确认按钮(优先点击白色背景的确认按钮)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,4 +13,10 @@ public enum DisplayMapTypes
[Description("渊下宫")]
Enkanomiya,
[Description("旧日之海")]
SeaOfBygoneEras,
[Description("远古圣山")]
AncientSacredMountain,
}

View File

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

View File

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

View File

@@ -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);
// 检查窗口是否还存在

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

@@ -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(
"暂停当前脚本/独立任务",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>
/// 测试各种选取鱼饵,结果为成功