diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj
index 8400fdb7..3452512a 100644
--- a/BetterGenshinImpact/BetterGenshinImpact.csproj
+++ b/BetterGenshinImpact/BetterGenshinImpact.csproj
@@ -44,7 +44,7 @@
-
+
diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/BgiOnnxModel.cs b/BetterGenshinImpact/Core/Recognition/ONNX/BgiOnnxModel.cs
index 0182b56c..d87369fa 100644
--- a/BetterGenshinImpact/Core/Recognition/ONNX/BgiOnnxModel.cs
+++ b/BetterGenshinImpact/Core/Recognition/ONNX/BgiOnnxModel.cs
@@ -55,6 +55,12 @@ public class BgiOnnxModel
public static readonly BgiOnnxModel BgiAvatarSide =
Register("BgiAvatarSide", @"Assets\Model\Common\avatar_side_classify_sim.onnx");
+ ///
+ /// Q技能冷却分类模型
+ ///
+ public static readonly BgiOnnxModel BgiQClassify =
+ Register("BgiQClassify", @"Assets\Model\Common\q_classify_sim.onnx");
+
///
/// paddleOCR V4 检测模型
///
@@ -144,4 +150,4 @@ public class BgiOnnxModel
RegisteredModels.Add(model);
return model;
}
-}
\ No newline at end of file
+}
diff --git a/BetterGenshinImpact/Core/Simulator/Simulation.cs b/BetterGenshinImpact/Core/Simulator/Simulation.cs
index 01ea60ec..1bcaf93b 100644
--- a/BetterGenshinImpact/Core/Simulator/Simulation.cs
+++ b/BetterGenshinImpact/Core/Simulator/Simulation.cs
@@ -28,6 +28,9 @@ public class Simulation
SendInput.Keyboard.KeyUp(key);
}
}
+ SendInput.Mouse.LeftButtonUp();
+ SendInput.Mouse.RightButtonUp();
+ SendInput.Mouse.MiddleButtonUp();
}
public static bool IsKeyDown(User32.VK key)
diff --git a/BetterGenshinImpact/GameTask/AutoFight/AutoFightConfig.cs b/BetterGenshinImpact/GameTask/AutoFight/AutoFightConfig.cs
index 70817471..2224b045 100644
--- a/BetterGenshinImpact/GameTask/AutoFight/AutoFightConfig.cs
+++ b/BetterGenshinImpact/GameTask/AutoFight/AutoFightConfig.cs
@@ -13,7 +13,7 @@ namespace BetterGenshinImpact.GameTask.AutoFight;
[Serializable]
public partial class AutoFightConfig : ObservableObject
{
- [ObservableProperty] private string _strategyName = "";
+ [ObservableProperty] private string _strategyName = "根据队伍自动选择";
///
/// 英文逗号分割 强制指定队伍角色
diff --git a/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs b/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs
index c62cb57d..625e271b 100644
--- a/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs
+++ b/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs
@@ -684,7 +684,12 @@ public class AutoFightTask : ISoloTask
picker.UseSkill(true);
await Delay(50, ct);
Simulation.SendInput.SimulateAction(GIActions.NormalAttack);
+ await Delay(100, ct);
+ Simulation.SendInput.SimulateAction(GIActions.NormalAttack);
+ await Delay(100, ct);
+ Simulation.SendInput.SimulateAction(GIActions.NormalAttack);
await Delay(1500, ct);
+ picker.AfterUseSkill();
}
}
else
diff --git a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs
index 06db3d6a..14a4bb35 100644
--- a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs
+++ b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs
@@ -1,4 +1,4 @@
-using BetterGenshinImpact.Core.Recognition.OCR;
+using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.Core.Script.Dependence;
using BetterGenshinImpact.Core.Simulator;
@@ -24,6 +24,10 @@ using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model;
using BetterGenshinImpact.GameTask.AutoPathing;
using BetterGenshinImpact.GameTask.AutoPathing.Model;
using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum;
+using BetterGenshinImpact.Core.Recognition.ONNX;
+using Compunet.YoloSharp;
+using Compunet.YoloSharp.Data;
+using Microsoft.Extensions.DependencyInjection;
namespace BetterGenshinImpact.GameTask.AutoFight.Model;
@@ -99,7 +103,10 @@ public class Avatar
};
private static readonly Random UnstuckRandom = new();
-
+
+ private static readonly Lazy QBurstClassifierLazy = new(() =>
+ App.ServiceProvider.GetRequiredService().CreateYoloPredictor(BgiOnnxModel.BgiQClassify));
+
public Avatar(CombatScenes combatScenes, string name, int index, Rect nameRect, double manualSkillCd = -1)
{
@@ -591,6 +598,13 @@ public class Avatar
///
public void UseBurst()
{
+ // CD 中立即返回,其余场景尝试释放
+ using var region1 = CaptureToRectArea();
+ if (IsBurstReadyByClassify(region1) == BurstReadyState.Cooldown)
+ {
+ return;
+ }
+
for (var i = 0; i < 10; i++)
{
if (Ct is { IsCancellationRequested: true })
@@ -610,9 +624,46 @@ public class Avatar
Sleep(1500, Ct);
return;
}
+ else
+ {
+ // 找到编号块判断是否进入了CD,四星角色没有大招动画
+ if (IsBurstReadyByClassify(region) == BurstReadyState.Cooldown)
+ {
+ Sleep(1500, Ct);
+ return;
+ }
+ }
}
}
+ private static BurstReadyState IsBurstReadyByClassify(ImageRegion imageRegion)
+ {
+ using var qRa = imageRegion.DeriveCrop(AutoFightAssets.Instance.QRect);
+ var result = QBurstClassifierLazy.Value.Predictor.Classify(qRa.CacheImage);
+ var topClass = result.GetTopClass();
+ var topClassName = topClass.Name.Name;
+
+ // 置信度不足时,直接返回未知,避免误判导致漏放/乱放
+ if (topClass.Confidence <= 0.7)
+ {
+ Logger.LogDebug("Q技能冷却分类置信度不足:{Confidence:F2},类别:{ClassName}", topClass.Confidence, topClassName);
+ return BurstReadyState.Unknown;
+ }
+
+ if (topClassName.Contains("cd_1", StringComparison.OrdinalIgnoreCase))
+ {
+ return BurstReadyState.Cooldown;
+ }
+
+ if (topClassName.Contains("cd_0", StringComparison.OrdinalIgnoreCase))
+ {
+ return BurstReadyState.Ready;
+ }
+
+ Logger.LogDebug("Q技能冷却分类出现未知类别:{ClassName},置信度:{Confidence:F2}", topClassName, topClass.Confidence);
+ return BurstReadyState.Unknown;
+ }
+
// ///
// /// 元素爆发是否正在CD中
// /// 右下 157x165
@@ -1098,4 +1149,4 @@ public class Avatar
return null;
}
-}
\ No newline at end of file
+}
diff --git a/BetterGenshinImpact/GameTask/AutoFight/Model/BurstReadyState.cs b/BetterGenshinImpact/GameTask/AutoFight/Model/BurstReadyState.cs
new file mode 100644
index 00000000..75f2d0fc
--- /dev/null
+++ b/BetterGenshinImpact/GameTask/AutoFight/Model/BurstReadyState.cs
@@ -0,0 +1,9 @@
+namespace BetterGenshinImpact.GameTask.AutoFight.Model;
+
+public enum BurstReadyState
+{
+ Ready,
+ Cooldown,
+ Unknown
+}
+
diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs
index 4e3b0578..bf2ba068 100644
--- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs
+++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs
@@ -1084,6 +1084,10 @@ public class AutoLeyLineOutcropTask : ISoloTask
kazuha.UseSkill(true);
await Delay(50, _ct);
Simulation.SendInput.SimulateAction(GIActions.NormalAttack);
+ await Delay(100, _ct);
+ Simulation.SendInput.SimulateAction(GIActions.NormalAttack);
+ await Delay(100, _ct);
+ Simulation.SendInput.SimulateAction(GIActions.NormalAttack);
await Delay(1500, _ct);
kazuha.AfterUseSkill();
_logger.LogInformation("战后聚集拾取:万叶长E动作完成,等待拾取动作结束");
diff --git a/BetterGenshinImpact/GameTask/AutoPathing/Handler/AutoFightHandler.cs b/BetterGenshinImpact/GameTask/AutoPathing/Handler/AutoFightHandler.cs
index dfcbd865..ccae440f 100644
--- a/BetterGenshinImpact/GameTask/AutoPathing/Handler/AutoFightHandler.cs
+++ b/BetterGenshinImpact/GameTask/AutoPathing/Handler/AutoFightHandler.cs
@@ -28,11 +28,11 @@ internal class AutoFightHandler : IActionHandler
TaskControl.Logger.LogInformation("执行 {Text}", "自动战斗");
// 爷们要战斗
AutoFightParam taskParams = null;
- if (config != null && config is PathingPartyConfig patyConfig && patyConfig.AutoFightEnabled)
+ if (config is PathingPartyConfig { Enabled: true, AutoFightEnabled: true } partyConfig)
{
//替换配置为地图追踪
- taskParams = GetFightAutoFightParam(patyConfig.AutoFightConfig);
+ taskParams = GetFightAutoFightParam(partyConfig.AutoFightConfig);
}
else
{
@@ -80,7 +80,7 @@ internal class AutoFightHandler : IActionHandler
private string GetFightStrategy(AutoFightConfig config)
{
var path = Global.Absolute(@"User\AutoFight\" + config.StrategyName + ".txt");
- if ("根据队伍自动选择".Equals(config.StrategyName))
+ if ("根据队伍自动选择".Equals(config.StrategyName) || string.IsNullOrEmpty(config.StrategyName))
{
path = Global.Absolute(@"User\AutoFight\");
}
diff --git a/BetterGenshinImpact/GameTask/AutoPathing/Handler/LinneaMiningHandler.cs b/BetterGenshinImpact/GameTask/AutoPathing/Handler/LinneaMiningHandler.cs
index bf0500c6..5a7a5bb1 100644
--- a/BetterGenshinImpact/GameTask/AutoPathing/Handler/LinneaMiningHandler.cs
+++ b/BetterGenshinImpact/GameTask/AutoPathing/Handler/LinneaMiningHandler.cs
@@ -44,7 +44,7 @@ public class LinneaMiningHandler : IActionHandler
private static (int mineCount, int scanRounds) ParseParams(string? actionParams)
{
- if (string.IsNullOrEmpty(actionParams)) return (1, 5);
+ if (string.IsNullOrEmpty(actionParams)) return (LinneaMiningTask.DefaultMineCount, LinneaMiningTask.DefaultScanRounds);
var parts = actionParams.Split(',');
var mineCount = -1;
@@ -72,8 +72,8 @@ public class LinneaMiningHandler : IActionHandler
}
}
- if (mineCount == -1) mineCount = 1;
- if (scanRounds == -1) scanRounds = 5;
+ if (mineCount == -1) mineCount = LinneaMiningTask.DefaultMineCount;
+ if (scanRounds == -1) scanRounds = LinneaMiningTask.DefaultScanRounds;
if (scanRounds < mineCount) scanRounds = mineCount;
return (mineCount, scanRounds);
diff --git a/BetterGenshinImpact/GameTask/AutoPathing/Handler/PickUpCollectHandler.cs b/BetterGenshinImpact/GameTask/AutoPathing/Handler/PickUpCollectHandler.cs
index 1c8c9055..5328eb7e 100644
--- a/BetterGenshinImpact/GameTask/AutoPathing/Handler/PickUpCollectHandler.cs
+++ b/BetterGenshinImpact/GameTask/AutoPathing/Handler/PickUpCollectHandler.cs
@@ -25,8 +25,8 @@ public class PickUpCollectHandler : IActionHandler
///
public static readonly string[] PickUpActions =
[
- "枫原万叶-长E keydown(E),wait(0.7),keyup(E),attack(0.2),wait(0.5)",
- "枫原万叶-短E e,attack(0.15)",
+ "枫原万叶-长E attack(0.08),keydown(E),wait(1),keyup(E),attack(0.5)",
+ "枫原万叶-短E attack(0.08),keydown(E),wait(0.47),keyup(E),attack(0.5)",
"琴-短E wait(0.1),keydown(E),wait(0.4),moveby(1000,0),wait(0.2),moveby(1000,0),wait(0.2),moveby(1000,0),wait(0.2),moveby(1000,-3500),wait(1.8),keyup(E),wait(0.3),click(middle)",
"琴-长E wait(0.1),click(middle),keydown(E),click(middle),wait(0.4),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1)," +
"moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1),moveby(500,0),wait(0.1)," +
diff --git a/BetterGenshinImpact/GameTask/Common/Job/LinneaMiningTask.cs b/BetterGenshinImpact/GameTask/Common/Job/LinneaMiningTask.cs
index 14fa307b..dfbb98cf 100644
--- a/BetterGenshinImpact/GameTask/Common/Job/LinneaMiningTask.cs
+++ b/BetterGenshinImpact/GameTask/Common/Job/LinneaMiningTask.cs
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Recognition.ONNX;
using BetterGenshinImpact.Core.Simulator;
+using BetterGenshinImpact.Core.Simulator.Extensions;
using BetterGenshinImpact.View.Drawable;
using Compunet.YoloSharp;
using Microsoft.Extensions.DependencyInjection;
@@ -15,6 +16,7 @@ using Microsoft.Extensions.Logging;
using BetterGenshinImpact.GameTask.Model.Area;
using OpenCvSharp;
using Vanara.PInvoke;
+using static BetterGenshinImpact.Core.Simulator.Extensions.SimulateKeyHelper;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
namespace BetterGenshinImpact.GameTask.Common.Job;
@@ -30,8 +32,8 @@ public class LinneaMiningTask
private const double BaseClusterDistance = 400;
// 聚类面积基准值(1920宽度下的标准矿石面积)
private const double BaseClusterArea = 1800;
- // 对准判定阈值(基于宽度缩放)
- private const double BaseArrivalThreshold = 50;
+ // 对准判定:使用目标矿物框四周扩张像素(基于宽度缩放)
+ private const double BaseAlignmentExpansion = 3;
// 屏幕边缘忽略区域宽度(基于宽度缩放)
private const double BaseEdgeIgnore = 200;
// 瞄准模式X轴灵敏度补偿系数
@@ -46,8 +48,10 @@ public class LinneaMiningTask
private const int LeftTurnStep = -250;
// 内层最大检测次数
private const int MaxInnerRetry = 7;
+ // 默认射箭次数
+ public const int DefaultMineCount = 1;
// 默认大循环次数
- private const int DefaultScanRounds = 5;
+ public const int DefaultScanRounds = 1;
// 元素视野刷新间隔
private const int ElementSightRefreshMs = 3000;
@@ -58,22 +62,22 @@ public class LinneaMiningTask
private readonly double _widthScale = TaskContext.Instance().SystemInfo.CaptureAreaRect.Width / 1920.0;
private readonly double _heightScale = TaskContext.Instance().SystemInfo.CaptureAreaRect.Height / 1080.0;
private readonly double ClusterDistanceThreshold;
- private readonly double ArrivalThreshold;
private readonly double EdgeIgnore;
+ private readonly double AlignmentExpansion;
private readonly int _scanRounds;
private readonly int _mineCount;
private int _debugIndex;
- public LinneaMiningTask(int scanRounds = DefaultScanRounds, int mineCount = 1)
+ public LinneaMiningTask(int scanRounds = DefaultScanRounds, int mineCount = DefaultMineCount)
{
_scanRounds = scanRounds;
_mineCount = mineCount;
_predictor = App.ServiceProvider.GetRequiredService()
.CreateYoloPredictor(BgiOnnxModel.BgiMine);
ClusterDistanceThreshold = BaseClusterDistance * _widthScale;
- ArrivalThreshold = BaseArrivalThreshold * _widthScale;
EdgeIgnore = BaseEdgeIgnore * _widthScale;
+ AlignmentExpansion = BaseAlignmentExpansion * _widthScale;
}
public async Task Start(CancellationToken ct)
@@ -83,7 +87,7 @@ public class LinneaMiningTask
{
// Logger.LogInformation("开始寻矿");
- Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_R);
+ Simulation.SendInput.Keyboard.KeyPress(GIActions.SwitchAimingMode.ToActionKey().ToVK());
aimingModeEntered = true;
await Delay(400, ct);
@@ -139,7 +143,7 @@ public class LinneaMiningTask
await Delay(800, ct);
}
- Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_R);
+ Simulation.SendInput.Keyboard.KeyPress(GIActions.SwitchAimingMode.ToActionKey().ToVK());
aimingModeEntered = false;
}
catch (OperationCanceledException)
@@ -154,7 +158,7 @@ public class LinneaMiningTask
{
if (aimingModeEntered)
{
- Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_R);
+ Simulation.SendInput.Keyboard.KeyPress(GIActions.SwitchAimingMode.ToActionKey().ToVK());
}
Simulation.SendInput.Mouse.MiddleButtonUp();
@@ -184,18 +188,16 @@ public class LinneaMiningTask
var offsetX = cluster.TargetX - centerX;
var offsetY = cluster.TargetY - centerY;
- var avgArea = cluster.Rects.Average(r => (double)r.Width * r.Height);
- var refArea = BaseClusterArea * _widthScale * _widthScale;
- var dynamicThreshold = ArrivalThreshold * Math.Max(1.0, Math.Sqrt(avgArea / refArea));
var isLast = retry == MaxInnerRetry - 1;
- var isAligned = Math.Abs(offsetX) <= dynamicThreshold / 2 && Math.Abs(offsetY) <= dynamicThreshold / 2;
+ var isAligned = Math.Abs(offsetX) <= (cluster.TargetWidth + AlignmentExpansion * 2) / 2
+ && Math.Abs(offsetY) <= (cluster.TargetHeight + AlignmentExpansion * 2) / 2;
// 前面所有循环都检测成功时,以不计入总次数的方式兜底射击一次
if (isAligned || (isLast && hadResult))
{
Simulation.SendInput.Mouse.MiddleButtonUp();
await Delay(300, ct);
- // Logger.LogInformation("开始挖矿");
+ Logger.LogInformation("开始挖矿");
await Mine(ct, totalDy < 0);
return (true, isAligned, 0, 0);
}
@@ -288,20 +290,17 @@ public class LinneaMiningTask
var clusters = ClusterMinerals(oreBoxes);
- // 画聚类中心标记框
- var refArea = BaseClusterArea * _widthScale * _widthScale;
- var clusterDrawList = new List();
- foreach (var cluster in clusters)
+ // 画聚类目标矿物框
+ var expansion = (int)AlignmentExpansion;
+ var clusterDrawList = clusters.Select(c =>
{
- var avgArea = cluster.Rects.Average(r => (double)r.Width * r.Height);
- var markerSize = ArrivalThreshold * Math.Max(1.0, Math.Sqrt(avgArea / refArea));
- var half = (int)markerSize / 2;
- var mark = new Rect((int)cluster.TargetX - half, (int)cluster.TargetY - half, (int)markerSize, (int)markerSize);
- clusterDrawList.Add(ra.ToRectDrawable(mark,
- $"({(int)cluster.TargetX},{(int)cluster.TargetY})",
+ var mark = new Rect((int)(c.TargetX - c.TargetWidth / 2) - expansion, (int)(c.TargetY - c.TargetHeight / 2) - expansion,
+ (int)c.TargetWidth + expansion * 2, (int)c.TargetHeight + expansion * 2);
+ return ra.ToRectDrawable(mark,
+ $"({(int)c.TargetX},{(int)c.TargetY})",
new Pen(Color.DodgerBlue, 2)
- ));
- }
+ );
+ }).ToList();
VisionContext.Instance().DrawContent.PutOrRemoveRectList("MiningCluster", clusterDrawList);
// 忽略屏幕边缘聚类,仅当中间区域存在聚类时生效
@@ -377,6 +376,8 @@ public class MineralCluster
public double CenterY { get; private set; }
public double TargetX { get; private set; }
public double TargetY { get; private set; }
+ public double TargetWidth { get; private set; }
+ public double TargetHeight { get; private set; }
public MineralCluster(Rect firstRect, double areaRatioThreshold = 5)
{
@@ -402,16 +403,19 @@ public class MineralCluster
CenterX = Rects.Average(r => r.X + r.Width / 2.0);
CenterY = Rects.Average(r => r.Y + r.Height / 2.0);
- // 按距离质心排序,取最近2个中靠左的
+ // 按距离质心排序,取最近2个中靠右的
var candidates = Rects
.Select(r => (cx: r.X + r.Width / 2.0, cy: r.Y + r.Height / 2.0,
- dist: Math.Pow(r.X + r.Width / 2.0 - CenterX, 2) + Math.Pow(r.Y + r.Height / 2.0 - CenterY, 2)))
+ dist: Math.Pow(r.X + r.Width / 2.0 - CenterX, 2) + Math.Pow(r.Y + r.Height / 2.0 - CenterY, 2),
+ w: (double)r.Width, h: (double)r.Height))
.OrderBy(t => t.dist)
.Take(2)
- .OrderBy(t => t.cx)
+ .OrderByDescending(t => t.cx)
.First();
TargetX = candidates.cx;
TargetY = candidates.cy;
+ TargetWidth = candidates.w;
+ TargetHeight = candidates.h;
}
}