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