diff --git a/BetterGenshinImpact.sln b/BetterGenshinImpact.sln index f6624165..e0be402c 100644 --- a/BetterGenshinImpact.sln +++ b/BetterGenshinImpact.sln @@ -71,8 +71,8 @@ Global {673344BC-B860-44AE-AD88-D33465BDE25B}.Release|Any CPU.Build.0 = Release|Any CPU {673344BC-B860-44AE-AD88-D33465BDE25B}.Release|x64.ActiveCfg = Release|x64 {673344BC-B860-44AE-AD88-D33465BDE25B}.Release|x64.Build.0 = Release|x64 - {27AF227E-BE44-450F-8E95-C1AD4FDE14BE}.Debug|Any CPU.ActiveCfg = Debug|x64 - {27AF227E-BE44-450F-8E95-C1AD4FDE14BE}.Debug|Any CPU.Build.0 = Debug|x64 + {27AF227E-BE44-450F-8E95-C1AD4FDE14BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27AF227E-BE44-450F-8E95-C1AD4FDE14BE}.Debug|Any CPU.Build.0 = Debug|Any CPU {27AF227E-BE44-450F-8E95-C1AD4FDE14BE}.Debug|x64.ActiveCfg = Debug|x64 {27AF227E-BE44-450F-8E95-C1AD4FDE14BE}.Debug|x64.Build.0 = Debug|x64 {27AF227E-BE44-450F-8E95-C1AD4FDE14BE}.Release|Any CPU.ActiveCfg = Release|x64 @@ -87,8 +87,8 @@ Global {9D00BC7A-9280-4AC9-8951-4502EDB71B76}.Release|Any CPU.Build.0 = Release|x64 {9D00BC7A-9280-4AC9-8951-4502EDB71B76}.Release|x64.ActiveCfg = Release|x64 {9D00BC7A-9280-4AC9-8951-4502EDB71B76}.Release|x64.Build.0 = Release|x64 - {7DA575C0-8D1E-4BD2-AEDB-5471D8906B98}.Debug|Any CPU.ActiveCfg = Debug|x64 - {7DA575C0-8D1E-4BD2-AEDB-5471D8906B98}.Debug|Any CPU.Build.0 = Debug|x64 + {7DA575C0-8D1E-4BD2-AEDB-5471D8906B98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DA575C0-8D1E-4BD2-AEDB-5471D8906B98}.Debug|Any CPU.Build.0 = Debug|Any CPU {7DA575C0-8D1E-4BD2-AEDB-5471D8906B98}.Debug|x64.ActiveCfg = Debug|x64 {7DA575C0-8D1E-4BD2-AEDB-5471D8906B98}.Debug|x64.Build.0 = Debug|x64 {7DA575C0-8D1E-4BD2-AEDB-5471D8906B98}.Release|Any CPU.ActiveCfg = Release|x64 @@ -102,8 +102,8 @@ Global GlobalSection(NestedProjects) = preSolution {AB85DA23-EB8F-4FBF-A7FA-35CE05B23C15} = {458E1106-43A4-47E6-B11B-D243035D4C76} {673344BC-B860-44AE-AD88-D33465BDE25B} = {458E1106-43A4-47E6-B11B-D243035D4C76} - {7DA575C0-8D1E-4BD2-AEDB-5471D8906B98} = {02CEA57F-C24B-40F9-AF31-F1D4F3BDB4BF} {27AF227E-BE44-450F-8E95-C1AD4FDE14BE} = {02CEA57F-C24B-40F9-AF31-F1D4F3BDB4BF} + {7DA575C0-8D1E-4BD2-AEDB-5471D8906B98} = {02CEA57F-C24B-40F9-AF31-F1D4F3BDB4BF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {352D8B78-9DE3-4E58-985F-FADD22594DB4} diff --git a/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs b/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs index f30a3e86..c62e84fd 100644 --- a/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs +++ b/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs @@ -13,7 +13,10 @@ public partial class PathingPartyConfig : ObservableObject // 配置是否启用,不启用会使用路径追踪内的条件配置 [ObservableProperty] private bool _enabled = false; - + + // 是否启用自动拾取 + [ObservableProperty] + private bool _autoPickEnabled = true; // 切换到队伍的名称 [ObservableProperty] private string _partyName = string.Empty; diff --git a/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs b/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs index 9fd0d7ad..a9a3168e 100644 --- a/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs +++ b/BetterGenshinImpact/Core/Script/Group/ScriptGroupProject.cs @@ -157,15 +157,14 @@ public partial class ScriptGroupProject : ObservableObject { // 加载并执行 var task = PathingTask.BuildFromFilePath(Path.Combine(MapPathingViewModel.PathJsonPath, FolderName, Name)); - TaskTriggerDispatcher.Instance().AddTrigger("AutoPick", null); var pathingTask = new PathExecutor(CancellationContext.Instance.Cts.Token); pathingTask.PartyConfig = GroupInfo?.Config.PathingConfig; + if (pathingTask.PartyConfig is null || pathingTask.PartyConfig.AutoPickEnabled) + { + TaskTriggerDispatcher.Instance().AddTrigger("AutoPick", null); + } await pathingTask.Pathing(task); } - else - { - //throw new Exception("不支持的脚本类型"); - } } partial void OnTypeChanged(string value) diff --git a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingConfig.cs b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingConfig.cs index b10374e2..b021769e 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingConfig.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingConfig.cs @@ -32,5 +32,5 @@ public partial class AutoFishingConfig : ObservableObject /// /// 自动抛竿未上钩超时时间(秒) /// - [ObservableProperty] private int _autoThrowRodTimeOut = 10; + [ObservableProperty] private int _autoThrowRodTimeOut = 15; } \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs index a90cf94f..5456d696 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs @@ -19,22 +19,37 @@ using System.IO; using System.Linq; using BetterGenshinImpact.GameTask.AutoFishing.Model; using BetterGenshinImpact.GameTask.Common.Job; +using Fischless.WindowsInput; +using BetterGenshinImpact.GameTask.Model.Area; +using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Recognition.ONNX; namespace BetterGenshinImpact.GameTask.AutoFishing { public class AutoFishingTask : ISoloTask { - private readonly ILogger _logger = App.GetLogger(); + private readonly ILogger _logger = App.GetLogger(); + private readonly InputSimulator input = Simulation.SendInput; public string Name => "钓鱼独立任务"; private CancellationToken _ct; - private Blackboard blackboard; + private readonly Blackboard blackboard; + + public AutoFishingTask() + { + var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).WithSessionOptions(BgiSessionOption.Instance.Options).Build(); + this.blackboard = new Blackboard(predictor, this.Sleep); + } public class Blackboard : AutoFishing.Blackboard { public bool noFish = false; + public Blackboard(YoloV8Predictor predictor, Action sleep) : base(predictor, sleep) + { + } + internal override void Reset() { base.Reset(); @@ -46,50 +61,45 @@ namespace BetterGenshinImpact.GameTask.AutoFishing { this._ct = ct; - this.blackboard = new Blackboard() - { - Sleep = this.Sleep - }; - var autoThrowRodTimeOut = TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodTimeOut; - var behaviourTree = FluentBuilder.Create() + var behaviourTree = FluentBuilder.Create() .Sequence("调整视角并钓鱼") .Do($"设置变量{nameof(blackboard.pitchReset)}", _ => { blackboard.pitchReset = true; return BehaviourStatus.Succeeded; }) - .PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard)) + .PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard, _logger, input)) .MySimpleParallel("找鱼20秒", policy: SimpleParallelPolicy.OnlyOneMustSucceed) - .Do("转圈圈调整视角", TurnAround) - .PushLeaf(() => new FindFishTimeout("等20秒", 20, blackboard)) + .PushLeaf(() => new TurnAround("转圈圈调整视角", blackboard, _logger, input)) + .PushLeaf(() => new FindFishTimeout("等20秒", 20, blackboard, _logger)) .End() - .PushLeaf(() => new EnterFishingMode("进入钓鱼模式", blackboard)) + .PushLeaf(() => new EnterFishingMode("进入钓鱼模式", blackboard, _logger, input)) .UntilFailed(@"\") .Sequence("一直钓鱼直到没鱼") .AlwaysSucceed(@"\") .Sequence("从找鱼开始") - .PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard)) + .PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard, _logger, input)) .MySimpleParallel("找鱼10秒", policy: SimpleParallelPolicy.OnlyOneMustSucceed) - .PushLeaf(() => new GetFishpond("检测鱼群", blackboard)) - .PushLeaf(() => new FindFishTimeout("等10秒", 10, blackboard)) + .PushLeaf(() => new GetFishpond("检测鱼群", blackboard, _logger)) + .PushLeaf(() => new FindFishTimeout("等10秒", 10, blackboard, _logger)) .End() - .PushLeaf(() => new ChooseBait("选择鱼饵", blackboard)) + .PushLeaf(() => new ChooseBait("选择鱼饵", blackboard, _logger, input)) .UntilSuccess("重复抛竿") .Sequence("重复抛竿序列") - .PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard)) - .PushLeaf(() => new ApproachFishAndThrowRod("抛竿", blackboard)) + .PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard, _logger, input)) + .PushLeaf(() => new ThrowRod("抛竿", blackboard, _logger, input)) .End() .End() .Do("冒泡-抛竿-缺鱼检查", _ => blackboard.noTargetFish ? BehaviourStatus.Failed : BehaviourStatus.Succeeded) - .PushLeaf(() => new CheckThrowRod("检查抛竿结果")) + .PushLeaf(() => new CheckThrowRod("检查抛竿结果", _logger)) .MySimpleParallel("下杆中", SimpleParallelPolicy.OnlyOneMustSucceed) - .PushLeaf(() => new FishBite("自动提竿")) - .PushLeaf(() => new FishBiteTimeout("下杆超时检查", autoThrowRodTimeOut)) + .PushLeaf(() => new FishBite("自动提竿", _logger, input)) + .PushLeaf(() => new FishBiteTimeout("下杆超时检查", autoThrowRodTimeOut, blackboard, _logger, input)) .End() - .PushLeaf(() => new GetFishBoxArea("等待拉条出现", blackboard)) - .PushLeaf(() => new Fishing("钓鱼拉条", blackboard)) + .PushLeaf(() => new GetFishBoxArea("等待拉条出现", blackboard, _logger)) + .PushLeaf(() => new Fishing("钓鱼拉条", blackboard, _logger, input)) .End() .End() .Do("冒泡-找鱼-没鱼检查", _ => blackboard.noFish ? BehaviourStatus.Failed : BehaviourStatus.Succeeded) @@ -125,13 +135,13 @@ namespace BetterGenshinImpact.GameTask.AutoFishing continue; } var content = new CaptureContent(bitmap, 0, 0); - behaviourTree.Tick(content); + behaviourTree.Tick(content.CaptureRectArea); if (behaviourTree.Status != BehaviourStatus.Running) { _logger.LogInformation("钓鱼结束"); - + var ra = content.CaptureRectArea; if (!ra.Find(AutoFishingAssets.Instance.ExitFishingButtonRo).IsEmpty()) { @@ -157,9 +167,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing TaskControl.Sleep(millisecondsTimeout, _ct); } - public class FindFishTimeout : BaseBehaviour + public class FindFishTimeout : BaseBehaviour { - private readonly ILogger _logger = App.GetLogger(); + private readonly ILogger logger; private readonly Blackboard blackboard; private DateTime? timeout; private int seconds; @@ -169,21 +179,22 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// /// /// - public FindFishTimeout(string name, int seconds, Blackboard blackboard) : base(name) + public FindFishTimeout(string name, int seconds, Blackboard blackboard, ILogger logger) : base(name) { this.blackboard = blackboard; + this.logger = logger; this.seconds = seconds; } protected override void OnInitialize() { timeout = DateTime.Now.AddSeconds(seconds); } - protected override BehaviourStatus Update(CaptureContent content) + protected override BehaviourStatus Update(ImageRegion _) { if (DateTime.Now >= timeout) { blackboard.noFish = true; - _logger.LogInformation($"{seconds}秒没有找到鱼,退出钓鱼界面"); + logger.LogInformation($"{seconds}秒没有找到鱼,退出钓鱼界面"); return BehaviourStatus.Failed; } else @@ -193,89 +204,106 @@ namespace BetterGenshinImpact.GameTask.AutoFishing } } - private BehaviourStatus TurnAround(CaptureContent content) + public class TurnAround : BaseBehaviour { - using var memoryStream = new MemoryStream(); - content.CaptureRectArea.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); - memoryStream.Seek(0, SeekOrigin.Begin); - var result = Blackboard.predictor.Detect(memoryStream); - if (result.Boxes.Any()) - { - Fishpond fishpond = new Fishpond(result); - int i = 0; - foreach (var fish in fishpond.Fishes) - { - content.CaptureRectArea.Derive(fish.Rect).DrawSelf($"{fish.FishType.ChineseName}.{i}"); - } - Sleep(1000); - VisionContext.Instance().DrawContent.ClearAll(); - - var oneFourthX = content.CaptureRectArea.SrcBitmap.Width / 4; - var threeFourthX = content.CaptureRectArea.SrcBitmap.Width * 3 / 4; - var centerY = content.CaptureRectArea.SrcBitmap.Height / 2; - if (fishpond.FishpondRect.Left > threeFourthX) - { - Simulation.SendInput.Mouse.MoveMouseBy(100, 0); - Sleep(100); - return BehaviourStatus.Running; - } - else if (fishpond.FishpondRect.Right < oneFourthX) - { - Simulation.SendInput.Mouse.MoveMouseBy(-100, 0); - Sleep(100); - return BehaviourStatus.Running; - } - - #region 1、使人物朝向和镜头方向一致;2、打断角色待机动作,避免钓鱼F交互键被吞 - // 加入昼夜切换后,使用KeyPress按S键被莫名吞掉了 - // 并且发现如果原地空格跳跃后紧跟按一下S键,角色会向侧后方走去 - // 于是使用“按一段时间”来代替KeyPress的“按一瞬间”,以求稳定的表现 - Simulation.SendInput.Keyboard.KeyDown(User32.VK.VK_S); - Sleep(100); - Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_S); - Sleep(400); - Simulation.SendInput.Keyboard.KeyDown(User32.VK.VK_W); - Sleep(100); - Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W); - Sleep(400); - Sleep(300); - #endregion - - _logger.LogInformation("视角调整完毕"); - return BehaviourStatus.Succeeded; - } - - Simulation.SendInput.Mouse.MoveMouseBy(100, 0); - Sleep(100); - - return BehaviourStatus.Running; - } - - private class EnterFishingMode : BaseBehaviour - { - private readonly ILogger _logger = App.GetLogger(); + private readonly ILogger logger; + private readonly IInputSimulator input; private readonly Blackboard blackboard; - public EnterFishingMode(string name, Blackboard blackboard) : base(name) + public TurnAround(string name, Blackboard blackboard, ILogger logger, IInputSimulator input) : base(name) { this.blackboard = blackboard; + this.logger = logger; + this.input = input; } - protected override BehaviourStatus Update(CaptureContent content) + protected override BehaviourStatus Update(ImageRegion imageRegion) + { + using var memoryStream = new MemoryStream(); + imageRegion.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); + memoryStream.Seek(0, SeekOrigin.Begin); + var result = blackboard.predictor.Detect(memoryStream); + if (result.Boxes.Any()) + { + Fishpond fishpond = new Fishpond(result); + logger.LogInformation("定位到鱼塘:" + string.Join('、', fishpond.Fishes.GroupBy(f => f.FishType).Select(g => $"{g.Key.ChineseName}{g.Count()}条"))); + int i = 0; + foreach (var fish in fishpond.Fishes) + { + imageRegion.Derive(fish.Rect).DrawSelf($"{fish.FishType.ChineseName}.{i++}"); + } + blackboard.Sleep(1000); + VisionContext.Instance().DrawContent.ClearAll(); + + var oneFourthX = imageRegion.SrcBitmap.Width / 4; + var threeFourthX = imageRegion.SrcBitmap.Width * 3 / 4; + var centerY = imageRegion.SrcBitmap.Height / 2; + if (fishpond.FishpondRect.Left > threeFourthX) + { + Simulation.SendInput.Mouse.MoveMouseBy(100, 0); + blackboard.Sleep(100); + return BehaviourStatus.Running; + } + else if (fishpond.FishpondRect.Right < oneFourthX) + { + Simulation.SendInput.Mouse.MoveMouseBy(-100, 0); + blackboard.Sleep(100); + return BehaviourStatus.Running; + } + + #region 1、使人物朝向和镜头方向一致;2、打断角色待机动作,避免钓鱼F交互键被吞 + // 加入昼夜切换后,使用KeyPress按S键被莫名吞掉了 + // 并且发现如果原地空格跳跃后紧跟按一下S键,角色会向侧后方走去 + // 于是使用“按一段时间”来代替KeyPress的“按一瞬间”,以求稳定的表现 + Simulation.SendInput.Keyboard.KeyDown(User32.VK.VK_S); + blackboard.Sleep(100); + Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_S); + blackboard.Sleep(400); + Simulation.SendInput.Keyboard.KeyDown(User32.VK.VK_W); + blackboard.Sleep(100); + Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W); + blackboard.Sleep(400); + blackboard.Sleep(300); + #endregion + + logger.LogInformation("视角调整完毕"); + return BehaviourStatus.Succeeded; + } + + input.Mouse.MoveMouseBy(100, 0); + blackboard.Sleep(100); + + return BehaviourStatus.Running; + } + } + + private class EnterFishingMode : BaseBehaviour + { + private readonly ILogger logger; + private readonly IInputSimulator input; + private readonly Blackboard blackboard; + public EnterFishingMode(string name, Blackboard blackboard, ILogger logger, IInputSimulator input) : base(name) + { + this.blackboard = blackboard; + this.logger = logger; + this.input = input; + } + + protected override BehaviourStatus Update(ImageRegion imageRegion) { if (Status == BehaviourStatus.Ready) { return BehaviourStatus.Running; } - if (Bv.FindFAndPress(content.CaptureRectArea, "钓鱼")) + if (Bv.FindFAndPress(imageRegion, input.Keyboard, "钓鱼")) { - _logger.LogInformation("按下钓鱼键"); + logger.LogInformation("按下钓鱼键"); blackboard.Sleep(2000); return BehaviourStatus.Running; } - else if (Bv.ClickWhiteConfirmButton(content.CaptureRectArea)) + else if (Bv.ClickWhiteConfirmButton(imageRegion)) { - _logger.LogInformation("点击开始钓鱼"); + logger.LogInformation("点击开始钓鱼"); this.blackboard.pitchReset = true; @@ -284,14 +312,14 @@ namespace BetterGenshinImpact.GameTask.AutoFishing return BehaviourStatus.Running; } - if (content.CaptureRectArea.Find(AutoFishingAssets.Instance.ExitFishingButtonRo).IsEmpty()) + if (imageRegion.Find(AutoFishingAssets.Instance.ExitFishingButtonRo).IsEmpty()) { - _logger.LogInformation("进入钓鱼模式失败"); + logger.LogInformation("进入钓鱼模式失败"); return BehaviourStatus.Failed; } else { - _logger.LogInformation("进入钓鱼模式"); + logger.LogInformation("进入钓鱼模式"); return BehaviourStatus.Succeeded; } } diff --git a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs index 81945fae..b1d7c87c 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs @@ -3,10 +3,7 @@ using BehaviourTree.FluentBuilder; using BehaviourTree.Composites; using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.GameTask.AutoFishing.Assets; -using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; using BetterGenshinImpact.GameTask.Common; -using BetterGenshinImpact.Helpers.Extensions; -using BetterGenshinImpact.View.Drawable; using Microsoft.Extensions.Logging; using OpenCvSharp; using System; @@ -15,12 +12,18 @@ using System.Diagnostics; using System.IO; using System.Threading; using Point = OpenCvSharp.Point; +using Fischless.WindowsInput; +using BetterGenshinImpact.GameTask.Model.Area; +using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Recognition.ONNX; +using Compunet.YoloV8; namespace BetterGenshinImpact.GameTask.AutoFishing { public class AutoFishingTrigger : ITaskTrigger { private readonly ILogger _logger = App.GetLogger(); + private readonly InputSimulator input = Simulation.SendInput; public string Name => "自动钓鱼"; public bool IsEnabled { get; set; } @@ -34,19 +37,16 @@ namespace BetterGenshinImpact.GameTask.AutoFishing public bool IsExclusive { get; set; } private Blackboard blackboard; - // internal IBehaviour BehaviourTree { get; set; } /// /// 辣条(误) /// - private IBehaviour BehaviourTreeLaTiao { get; set; } + private IBehaviour BehaviourTreeLaTiao { get; set; } public AutoFishingTrigger() { - this.blackboard = new Blackboard() - { - Sleep = this.Sleep - }; + var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).WithSessionOptions(BgiSessionOption.Instance.Options).Build(); + this.blackboard = new Blackboard(predictor, this.Sleep); } public void Init() @@ -54,41 +54,14 @@ namespace BetterGenshinImpact.GameTask.AutoFishing IsEnabled = TaskContext.Instance().Config.AutoFishingConfig.Enabled; IsExclusive = false; - /*BehaviourTree = FluentBuilder.Create() - .MySimpleParallel("root", policy: SimpleParallelPolicy.OnlyOneMustSucceed) - .Do("检查是否在钓鱼界面", CheckFishingUserInterface) - .UntilSuccess("钓鱼循环") - .Sequence("从找鱼开始") - .PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard)) - .PushLeaf(() => new GetFishpond("检测鱼群", blackboard)) - .PushLeaf(() => new ChooseBait("选择鱼饵", blackboard)) - .UntilSuccess("重复抛竿") - .Sequence("重复抛竿序列") - .PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard)) - .PushLeaf(() => new ApproachFishAndThrowRod("抛竿", blackboard)) - .End() - .End() - .Do("冒泡-抛竿-缺鱼检查", _ => blackboard.noTargetFish ? BehaviourStatus.Failed : BehaviourStatus.Succeeded) - .PushLeaf(() => new CheckThrowRod("检查抛竿结果")) - .MySimpleParallel("下杆中", SimpleParallelPolicy.OnlyOneMustSucceed) - .PushLeaf(() => new FishBite("自动提竿")) - .PushLeaf(() => new FishBiteTimeout("下杆超时检查", 30)) - .End() - .PushLeaf(() => new GetFishBoxArea("等待拉条出现", blackboard)) - .PushLeaf(() => new Fishing("钓鱼拉条", blackboard)) - .End() - .End() - .End() - .Build();*/ - - BehaviourTreeLaTiao = FluentBuilder.Create() + BehaviourTreeLaTiao = FluentBuilder.Create() .MySimpleParallel("root", policy: SimpleParallelPolicy.OnlyOneMustSucceed) .Do("检查是否在钓鱼界面", CheckFishingUserInterface) .UntilSuccess("拉条循环") .Sequence("拉条") - .PushLeaf(() => new FishBite("自动提竿")) - .PushLeaf(() => new GetFishBoxArea("等待拉条出现", blackboard)) - .PushLeaf(() => new Fishing("钓鱼拉条", blackboard)) + .PushLeaf(() => new FishBite("自动提竿", _logger, input)) + .PushLeaf(() => new GetFishBoxArea("等待拉条出现", blackboard, _logger)) + .PushLeaf(() => new Fishing("钓鱼拉条", blackboard, _logger, input)) .End() .End() .End() @@ -110,7 +83,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing if (!IsExclusive) { // 进入独占模式判断 - CheckFishingUserInterface(content); + CheckFishingUserInterface(content.CaptureRectArea); } else { @@ -122,7 +95,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing // { // BehaviourTreeLaTiao.Tick(content); // } - BehaviourTreeLaTiao.Tick(content); + BehaviourTreeLaTiao.Tick(content.CaptureRectArea); } } @@ -330,8 +303,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// 方法是找右下角的退出钓鱼按钮 /// 进入钓鱼界面时该触发器进入独占模式 /// - /// - private BehaviourStatus CheckFishingUserInterface(CaptureContent content) + /// + private BehaviourStatus CheckFishingUserInterface(ImageRegion imageRegion) { if (blackboard.chooseBaitUIOpening) { @@ -339,7 +312,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing } var prevIsExclusive = IsExclusive; - IsExclusive = !content.CaptureRectArea.Find(AutoFishingAssets.Instance.ExitFishingButtonRo).IsEmpty(); + IsExclusive = !imageRegion.Find(AutoFishingAssets.Instance.ExitFishingButtonRo).IsEmpty(); if (IsExclusive) { if (IsEnabled && !prevIsExclusive) diff --git a/BetterGenshinImpact/GameTask/AutoFishing/BehaviourTreeExtensions.cs b/BetterGenshinImpact/GameTask/AutoFishing/BehaviourTreeExtensions.cs index dbd4518d..b8321e60 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/BehaviourTreeExtensions.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/BehaviourTreeExtensions.cs @@ -4,6 +4,14 @@ using BehaviourTree; using System; using System.Collections.Generic; using System.Text; +using BetterGenshinImpact.Core.Config; +using OpenCvSharp; +using System.Drawing.Imaging; +using System.IO; +using OpenCvSharp.Extensions; +using Microsoft.Extensions.Logging; +using BetterGenshinImpact.GameTask.Model.Area; +using System.Threading.Tasks; namespace BetterGenshinImpact.GameTask.AutoFishing { @@ -115,4 +123,114 @@ namespace BetterGenshinImpact.GameTask.AutoFishing base.DoReset(status); } } + + [Obsolete] + /// + /// 方便生产截图的类,用于覆盖行为树原本的BaseBehaviour类 + /// 不用的时候记得注释或者改名 + /// + /// + public abstract class XBaseBehaviour : IBehaviour, IDisposable where TContext : ImageRegion + { + private readonly ILogger logger = App.GetLogger>(); + public string Name { get; } + + public BehaviourStatus Status { get; private set; } + + protected XBaseBehaviour(string name) + { + Name = name; + } + + public BehaviourStatus Tick(TContext context) + { + if (Status == BehaviourStatus.Ready) + { + OnInitialize(); + } + + Status = Update(context); + if (Status == BehaviourStatus.Ready) + { + throw new InvalidOperationException("Ready status should not be returned by Behaviour Update Method"); + } + + if (Status != BehaviourStatus.Running) + { + TakeScreenshot(context, $"{DateTime.Now:yyyyMMddHHmmssfff}_{this.GetType().Name}_{Status}.png"); + OnTerminate(Status); + } + + return Status; + } + + public void TakeScreenshot(ImageRegion imageRegion, string? name) + { + var path = Global.Absolute($@"log\screenshot\"); + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + var bitmap = imageRegion.SrcBitmap; + if (String.IsNullOrWhiteSpace(name)) + { + name = $@"{DateTime.Now:yyyyMMddHHmmssffff}.png"; + } + var savePath = Global.Absolute($@"log\screenshot\{name}"); + + if (TaskContext.Instance().Config.CommonConfig.ScreenshotUidCoverEnabled) + { + var mat = bitmap.ToMat(); + var rect = TaskContext.Instance().Config.MaskWindowConfig.UidCoverRect; + mat.Rectangle(rect, Scalar.White, -1); + new Task(() => + { + Cv2.ImWrite(savePath, mat); + }).Start(); + } + else + { + new Task(() => + { + bitmap.Save(savePath, ImageFormat.Png); + }).Start(); + } + + logger.LogInformation("截图已保存: {Name}", name); + } + + public void Reset() + { + if (Status != 0) + { + DoReset(Status); + Status = BehaviourStatus.Ready; + } + } + + protected abstract BehaviourStatus Update(TContext context); + + protected virtual void OnTerminate(BehaviourStatus status) + { + } + + protected virtual void OnInitialize() + { + } + + protected virtual void DoReset(BehaviourStatus status) + { + } + + protected virtual void Dispose(bool disposing) + { + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } } diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs b/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs index f54d33d7..42158055 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs @@ -1,11 +1,9 @@ using BehaviourTree; using BetterGenshinImpact.Core.Recognition; using BetterGenshinImpact.Core.Recognition.OCR; -using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.GameTask.AutoFishing.Assets; using BetterGenshinImpact.GameTask.AutoFishing.Model; using BetterGenshinImpact.GameTask.Common; -using BetterGenshinImpact.GameTask.Common.BgiVision; using BetterGenshinImpact.GameTask.Model.Area; using BetterGenshinImpact.Helpers; using BetterGenshinImpact.View.Drawable; @@ -21,30 +19,34 @@ using static Vanara.PInvoke.User32; using Color = System.Drawing.Color; using Pen = System.Drawing.Pen; using System.Linq; +using Fischless.WindowsInput; namespace BetterGenshinImpact.GameTask.AutoFishing { /// /// 检测鱼群 /// - public class GetFishpond : BaseBehaviour + public class GetFishpond : BaseBehaviour { - private readonly ILogger _logger = App.GetLogger(); + private readonly ILogger logger; private readonly Blackboard blackboard; - public GetFishpond(string name, Blackboard blackboard) : base(name) + private readonly DrawContent drawContent; + public GetFishpond(string name, Blackboard blackboard, ILogger logger, DrawContent? drawContent = null) : base(name) { this.blackboard = blackboard; + this.logger = logger; + this.drawContent = drawContent ?? VisionContext.Instance().DrawContent; } - protected override BehaviourStatus Update(CaptureContent content) + protected override BehaviourStatus Update(ImageRegion imageRegion) { - _logger.LogDebug("GetFishpond"); + logger.LogDebug("GetFishpond"); using var memoryStream = new MemoryStream(); - content.CaptureRectArea.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); + imageRegion.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); memoryStream.Seek(0, SeekOrigin.Begin); - var result = Blackboard.predictor.Detect(memoryStream); + var result = blackboard.predictor.Detect(memoryStream); Debug.WriteLine($"YOLOv8识别: {result.Speed}"); - var fishpond = new Fishpond(result); + var fishpond = new Fishpond(result, ignoreObtained: true); if (fishpond.FishpondRect == Rect.Empty) { blackboard.Sleep(500); @@ -53,7 +55,14 @@ namespace BetterGenshinImpact.GameTask.AutoFishing else { blackboard.fishpond = fishpond; - _logger.LogInformation("定位到鱼塘:" + string.Join('、', fishpond.Fishes.GroupBy(f => f.FishType).Select(g => $"{g.Key.ChineseName}{g.Count()}条"))); + logger.LogInformation("定位到鱼塘:" + string.Join('、', fishpond.Fishes.GroupBy(f => f.FishType).Select(g => $"{g.Key.ChineseName}{g.Count()}条"))); + int i = 0; + foreach (var fish in fishpond.Fishes) + { + imageRegion.Derive(fish.Rect).DrawSelf($"{fish.FishType.ChineseName}.{i++}"); + } + blackboard.Sleep(1000); + drawContent.ClearAll(); return BehaviourStatus.Succeeded; } @@ -63,60 +72,69 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// /// 选择鱼饵 /// - public class ChooseBait : BaseBehaviour + public class ChooseBait : BaseBehaviour { - private readonly ILogger _logger = App.GetLogger(); + private readonly ILogger logger; + private readonly IInputSimulator input; private readonly Blackboard blackboard; - private DateTime? chooseBaitUIOpenWaitEndTime; // 等待选鱼饵界面出现并尝试找鱼饵的结束时间 + private readonly TimeProvider timeProvider; + private DateTimeOffset? chooseBaitUIOpenWaitEndTime; // 等待选鱼饵界面出现并尝试找鱼饵的结束时间 /// /// 选择鱼饵 /// /// /// - public ChooseBait(string name, Blackboard blackboard) : base(name) + public ChooseBait(string name, Blackboard blackboard, ILogger logger, IInputSimulator input, TimeProvider? timeProvider = null) : base(name) { this.blackboard = blackboard; + this.logger = logger; + this.input = input; + this.timeProvider = timeProvider ?? TimeProvider.System; } - protected override BehaviourStatus Update(CaptureContent content) + protected override BehaviourStatus Update(ImageRegion imageRegion) { - if (this.Status == BehaviourStatus.Ready) // 第一次进来直接返回,更新截图 + if (this.Status == BehaviourStatus.Ready) { - chooseBaitUIOpenWaitEndTime = DateTime.Now.AddSeconds(3); - _logger.LogInformation("打开换饵界面"); + if (blackboard.fishpond.Fishes.Any(f => f.FishType.BaitName == blackboard.selectedBaitName)) // 如果该种鱼没钓完就不用换饵 + { + return BehaviourStatus.Succeeded; + } + chooseBaitUIOpenWaitEndTime = timeProvider.GetLocalNow().AddSeconds(3); + logger.LogInformation("打开换饵界面"); blackboard.chooseBaitUIOpening = true; - Simulation.SendInput.Mouse.RightButtonClick(); - blackboard.Sleep(100); - Simulation.SendInput.Mouse.MoveMouseBy(0, 200); // 鼠标移走,防止干扰 + input.Mouse.RightButtonClick(); blackboard.Sleep(100); + input.Mouse.MoveMouseBy(0, 200); // 鼠标移走,防止干扰 + blackboard.Sleep(500); return BehaviourStatus.Running; } blackboard.selectedBaitName = blackboard.fishpond.Fishes.GroupBy(f => f.FishType).OrderByDescending(g => g.Count()).First().First().FishType.BaitName; // 选择最多鱼吃的饵料 - _logger.LogInformation("选择鱼饵 {Text}", BaitType.FromName(blackboard.selectedBaitName).ChineseName); + logger.LogInformation("选择鱼饵 {Text}", BaitType.FromName(blackboard.selectedBaitName).ChineseName); // 寻找鱼饵 var ro = new RecognitionObject { Name = "ChooseBait", RecognitionType = RecognitionTypes.TemplateMatch, - TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFishing", $"bait\\{blackboard.selectedBaitName}.png"), + TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFishing", $"bait\\{blackboard.selectedBaitName}.png", 1920, 1080, 1d), // todo 改成注入配置的形式,直接用魔法值不好 Threshold = 0.8, Use3Channels = true, DrawOnWindow = false }.InitTemplate(); - var captureRegion = content.CaptureRectArea; + var captureRegion = imageRegion; using var resRa = captureRegion.Find(ro); if (resRa.IsEmpty()) { - if (DateTime.Now >= chooseBaitUIOpenWaitEndTime) + if (timeProvider.GetLocalNow() >= chooseBaitUIOpenWaitEndTime) { - _logger.LogWarning("没有找到目标鱼饵"); - Simulation.SendInput.Keyboard.KeyPress(VK.VK_ESCAPE); + logger.LogWarning("没有找到目标鱼饵"); + input.Keyboard.KeyPress(VK.VK_ESCAPE); blackboard.chooseBaitUIOpening = false; - _logger.LogInformation("退出换饵界面"); + logger.LogInformation("退出换饵界面"); blackboard.selectedBaitName = string.Empty; return BehaviourStatus.Failed; } @@ -133,9 +151,19 @@ namespace BetterGenshinImpact.GameTask.AutoFishing captureRegion.ClickTo((int)(captureRegion.Width * 0.675), (int)(captureRegion.Height / 3d)); blackboard.Sleep(200); // 点击确定 - Bv.ClickWhiteConfirmButton(captureRegion); + var ra = captureRegion.Find(new RecognitionObject + { + Name = "BtnWhiteConfirm", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"Common\Element", "btn_white_confirm.png", 1920, 1080, 1d), // todo 改成注入配置的形式,直接用魔法值不好 + Use3Channels = true + }.InitTemplate()); + if (ra.IsExist()) + { + ra.Click(); + } blackboard.chooseBaitUIOpening = false; - _logger.LogInformation("退出换饵界面"); + logger.LogInformation("退出换饵界面"); blackboard.Sleep(500); // 等待界面切换 } @@ -146,16 +174,21 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// /// 抛竿 /// - public class ApproachFishAndThrowRod : BaseBehaviour + public class ThrowRod : BaseBehaviour { - private readonly ILogger _logger = App.GetLogger(); + private readonly ILogger logger; + private readonly IInputSimulator input; private readonly Blackboard blackboard; + private readonly DrawContent drawContent; private int noPlacementTimes; // 没有落点的次数 private int noTargetFishTimes; // 没有目标鱼的次数 - public ApproachFishAndThrowRod(string name, Blackboard blackboard) : base(name) + public ThrowRod(string name, Blackboard blackboard, ILogger logger, IInputSimulator input, DrawContent? drawContent = null) : base(name) { this.blackboard = blackboard; + this.logger = logger; + this.input = input; + this.drawContent = drawContent ?? VisionContext.Instance().DrawContent; } protected override void OnInitialize() @@ -163,9 +196,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing noPlacementTimes = 0; noTargetFishTimes = 0; - Simulation.SendInput.Mouse.LeftButtonDown(); + input.Mouse.LeftButtonDown(); blackboard.pitchReset = true; - _logger.LogInformation("长按预抛竿"); + logger.LogInformation("长按预抛竿"); blackboard.Sleep(3000); } @@ -173,24 +206,24 @@ namespace BetterGenshinImpact.GameTask.AutoFishing { if (status != BehaviourStatus.Running) { - VisionContext.Instance().DrawContent.RemoveRect("Target"); - VisionContext.Instance().DrawContent.RemoveRect("Fish"); + drawContent.RemoveRect("Target"); + drawContent.RemoveRect("Fish"); } } - protected override BehaviourStatus Update(CaptureContent content) + protected override BehaviourStatus Update(ImageRegion imageRegion) { - _logger.LogDebug("ApproachFishAndThrowRod"); + logger.LogDebug("ThrowRod"); blackboard.noTargetFish = false; var prevTargetFishRect = Rect.Empty; // 记录上一个目标鱼的位置 - var ra = content.CaptureRectArea; + var ra = imageRegion; // 找 鱼饵落点 using var memoryStream = new MemoryStream(); ra.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); memoryStream.Seek(0, SeekOrigin.Begin); - var result = Blackboard.predictor.Detect(memoryStream); + var result = blackboard.predictor.Detect(memoryStream); Debug.WriteLine($"YOLOv8识别: {result.Speed}"); var fishpond = new Fishpond(result, includeTarget: true); Random _rd = new(); @@ -208,14 +241,14 @@ namespace BetterGenshinImpact.GameTask.AutoFishing var moveX = 100 * (cX - rdX) / ra.SrcBitmap.Width; var moveY = 100 * (cY - rdY) / ra.SrcBitmap.Height; - Simulation.SendInput.Mouse.MoveMouseBy(moveX, moveY); + input.Mouse.MoveMouseBy(moveX, moveY); if (noPlacementTimes > 25) { - _logger.LogInformation("未找到鱼饵落点,重试"); - Simulation.SendInput.Mouse.LeftButtonUp(); + logger.LogInformation("未找到鱼饵落点,重试"); + input.Mouse.LeftButtonUp(); blackboard.Sleep(2000); - Simulation.SendInput.Mouse.LeftButtonClick(); + input.Mouse.LeftButtonClick(); blackboard.Sleep(2000); //此处需要久一点 return BehaviourStatus.Failed; } @@ -251,18 +284,18 @@ namespace BetterGenshinImpact.GameTask.AutoFishing noTargetFishTimes++; //if (noTargetFishTimes == 30) //{ - // Simulation.SendInputEx.Mouse.MoveMouseBy(0, 100); + // inputEx.Mouse.MoveMouseBy(0, 100); //} if (noTargetFishTimes > 10) { // 没有找到目标鱼,重新选择鱼饵 blackboard.selectedBaitName = string.Empty; - _logger.LogInformation("没有找到目标鱼,1.直接抛竿"); - Simulation.SendInput.Mouse.LeftButtonUp(); + logger.LogInformation("没有找到目标鱼,1.直接抛竿"); + input.Mouse.LeftButtonUp(); blackboard.Sleep(2000); - _logger.LogInformation("没有找到目标鱼,2.收杆"); - Simulation.SendInput.Mouse.LeftButtonClick(); + logger.LogInformation("没有找到目标鱼,2.收杆"); + input.Mouse.LeftButtonClick(); blackboard.Sleep(800); blackboard.noTargetFish = true; @@ -274,15 +307,27 @@ namespace BetterGenshinImpact.GameTask.AutoFishing else { noTargetFishTimes = 0; - content.CaptureRectArea.DrawRect(fishpondTargetRect, "Target"); - content.CaptureRectArea.Derive(currentFish.Rect).DrawSelf("Fish"); + imageRegion.DrawRect(fishpondTargetRect, "Target"); + imageRegion.Derive(currentFish.Rect).DrawSelf("Fish"); - // VisionContext.Instance().DrawContent.PutRect("Target", fishpond.TargetRect.ToRectDrawable()); - // VisionContext.Instance().DrawContent.PutRect("Fish", currentFish.Rect.ToRectDrawable()); + // drawContent.PutRect("Target", fishpond.TargetRect.ToRectDrawable()); + // drawContent.PutRect("Fish", currentFish.Rect.ToRectDrawable()); // 来自 HutaoFisher 的抛竿技术 var rod = fishpondTargetRect; var fish = currentFish.Rect; + if (ScaleMax1080PCaptureRect == default) // todo 等配置能注入后和SystemInfo.ScaleMax1080PCaptureRect放到一起 + { + if (imageRegion.Width > 1920) + { + var scale = imageRegion.Width / 1920d; + ScaleMax1080PCaptureRect = new Rect(imageRegion.X, imageRegion.Y, 1920, (int)(imageRegion.Height / scale)); + } + else + { + ScaleMax1080PCaptureRect = new Rect(imageRegion.X, imageRegion.Y, imageRegion.Width, imageRegion.Height); + } + } var dx = NormalizeXTo1024(fish.Left + fish.Right - rod.Left - rod.Right) / 2.0; var dy = NormalizeYTo576(fish.Top + fish.Bottom - rod.Top - rod.Bottom) / 2.0; var state = RodNet.GetRodState(new RodInput @@ -319,22 +364,22 @@ namespace BetterGenshinImpact.GameTask.AutoFishing if (state == -1) { // 失败 随机移动鼠标 - var cX = content.CaptureRectArea.SrcBitmap.Width / 2; - var cY = content.CaptureRectArea.SrcBitmap.Height / 2; - var rdX = _rd.Next(0, content.CaptureRectArea.SrcBitmap.Width); - var rdY = _rd.Next(0, content.CaptureRectArea.SrcBitmap.Height); + var cX = imageRegion.SrcBitmap.Width / 2; + var cY = imageRegion.SrcBitmap.Height / 2; + var rdX = _rd.Next(0, imageRegion.SrcBitmap.Width); + var rdY = _rd.Next(0, imageRegion.SrcBitmap.Height); - var moveX = 100 * (cX - rdX) / content.CaptureRectArea.SrcBitmap.Width; - var moveY = 100 * (cY - rdY) / content.CaptureRectArea.SrcBitmap.Height; + var moveX = 100 * (cX - rdX) / imageRegion.SrcBitmap.Width; + var moveY = 100 * (cY - rdY) / imageRegion.SrcBitmap.Height; - _logger.LogInformation("失败 随机移动 {DX}, {DY}", moveX, moveY); - Simulation.SendInput.Mouse.MoveMouseBy(moveX, moveY); + logger.LogInformation("失败 随机移动 {DX}, {DY}", moveX, moveY); + input.Mouse.MoveMouseBy(moveX, moveY); } else if (state == 0) { // 成功 抛竿 - Simulation.SendInput.Mouse.LeftButtonUp(); - _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); + input.Mouse.LeftButtonUp(); + logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); return BehaviourStatus.Succeeded; } else if (state == 1) @@ -345,27 +390,29 @@ namespace BetterGenshinImpact.GameTask.AutoFishing dx = dx / dl * 30; dy = dy / dl * 30; // _logger.LogInformation("太近 移动 {DX}, {DY}", dx, dy); - Simulation.SendInput.Mouse.MoveMouseBy((int)(-dx / 1.5), (int)(-dy * 1.5)); + input.Mouse.MoveMouseBy((int)(-dx / 1.5), (int)(-dy * 1.5)); } else if (state == 2) { // 太远 // _logger.LogInformation("太远 移动 {DX}, {DY}", dx, dy); - Simulation.SendInput.Mouse.MoveMouseBy((int)(dx / 1.5), (int)(dy * 1.5)); + input.Mouse.MoveMouseBy((int)(dx / 1.5), (int)(dy * 1.5)); } } blackboard.Sleep(50); return BehaviourStatus.Running; } + private Rect ScaleMax1080PCaptureRect { get; set; } + private double NormalizeXTo1024(int x) { - return x * 1.0 / TaskContext.Instance().SystemInfo.ScaleMax1080PCaptureRect.Width * 1024; + return x * 1.0 / ScaleMax1080PCaptureRect.Width * 1024; } private double NormalizeYTo576(int y) { - return y * 1.0 / TaskContext.Instance().SystemInfo.ScaleMax1080PCaptureRect.Height * 576; + return y * 1.0 / ScaleMax1080PCaptureRect.Height * 576; } } @@ -375,18 +422,19 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// 检查抛竿结果 /// 避免往红色靶点抛竿导致失败 /// - /// - public class CheckThrowRod : BaseBehaviour + /// + public class CheckThrowRod : BaseBehaviour { - private readonly ILogger _logger = App.GetLogger(); + private readonly ILogger logger; private DateTime? timeDelay; /// /// 检查抛竿结果 /// /// - public CheckThrowRod(string name) : base(name) + public CheckThrowRod(string name, ILogger logger) : base(name) { + this.logger = logger; } protected override void OnInitialize() @@ -394,29 +442,31 @@ namespace BetterGenshinImpact.GameTask.AutoFishing timeDelay = DateTime.Now.AddSeconds(3); } - protected override BehaviourStatus Update(CaptureContent content) + protected override BehaviourStatus Update(ImageRegion imageRegion) { if (DateTime.Now < timeDelay) { return BehaviourStatus.Running; } - Region baitRectArea = content.CaptureRectArea.Find(AutoFishingAssets.Instance.BaitButtonRo); + Region baitRectArea = imageRegion.Find(AutoFishingAssets.Instance.BaitButtonRo); if (baitRectArea.IsEmpty()) { return BehaviourStatus.Succeeded; } else { - _logger.LogInformation("抛竿失败"); + logger.LogInformation("抛竿失败"); return BehaviourStatus.Failed; } } } - public class FishBiteTimeout : BaseBehaviour + public class FishBiteTimeout : BaseBehaviour { - private readonly ILogger _logger = App.GetLogger(); + private readonly Blackboard blackboard; + private readonly ILogger logger; + private readonly IInputSimulator input; private DateTime? waitFishBiteTimeout; private int seconds; @@ -425,21 +475,24 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// /// /// - public FishBiteTimeout(string name, int seconds) : base(name) + public FishBiteTimeout(string name, int seconds, Blackboard blackboard, ILogger logger, IInputSimulator input) : base(name) { this.seconds = seconds; + this.blackboard = blackboard; + this.logger = logger; + this.input = input; } protected override void OnInitialize() { waitFishBiteTimeout = DateTime.Now.AddSeconds(seconds); } - protected override BehaviourStatus Update(CaptureContent context) + protected override BehaviourStatus Update(ImageRegion context) { if (DateTime.Now >= waitFishBiteTimeout) { - _logger.LogInformation($"{seconds}秒没有咬杆,本次收杆"); - Simulation.SendInput.Mouse.LeftButtonClick(); - TaskControl.Sleep(1000); + logger.LogInformation($"{seconds}秒没有咬杆,本次收杆"); + input.Mouse.LeftButtonClick(); + blackboard.Sleep(2000); return BehaviourStatus.Failed; } else @@ -452,24 +505,27 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// /// 自动提竿 /// - public class FishBite : BaseBehaviour + public class FishBite : BaseBehaviour { - private readonly ILogger _logger = App.GetLogger(); + private readonly ILogger logger; + private readonly IInputSimulator input; private readonly IOcrService ocrService = OcrFactory.Paddle; - public FishBite(string name) : base(name) + public FishBite(string name, ILogger logger, IInputSimulator input) : base(name) { + this.logger = logger; + this.input = input; } - protected override BehaviourStatus Update(CaptureContent content) + protected override BehaviourStatus Update(ImageRegion imageRegion) { - _logger.LogDebug("FishBite"); + logger.LogDebug("FishBite"); // 自动识别的钓鱼框向下延伸到屏幕中间 //var liftingWordsAreaRect = new Rect(fishBoxRect.X, fishBoxRect.Y + fishBoxRect.Height * 2, - // fishBoxRect.Width, content.CaptureRectArea.SrcMat.Height / 2 - fishBoxRect.Y - fishBoxRect.Height * 5); + // fishBoxRect.Width, imageRegion.CaptureRectArea.SrcMat.Height / 2 - fishBoxRect.Y - fishBoxRect.Height * 5); // 上半屏幕和中间1/3的区域 - var liftingWordsAreaRect = new Rect(content.CaptureRectArea.SrcMat.Width / 3, 0, content.CaptureRectArea.SrcMat.Width / 3, - content.CaptureRectArea.SrcMat.Height / 2); + var liftingWordsAreaRect = new Rect(imageRegion.SrcMat.Width / 3, 0, imageRegion.SrcMat.Width / 3, + imageRegion.SrcMat.Height / 2); //VisionContext.Instance().DrawContent.PutRect("liftingWordsAreaRect", liftingWordsAreaRect.ToRectDrawable(new Pen(Color.Cyan, 2))); - var wordCaptureMat = new Mat(content.CaptureRectArea.SrcMat, liftingWordsAreaRect); + var wordCaptureMat = new Mat(imageRegion.SrcMat, liftingWordsAreaRect); var currentBiteWordsTips = AutoFishingImageRecognition.MatchFishBiteWords(wordCaptureMat, liftingWordsAreaRect); if (currentBiteWordsTips != Rect.Empty) { @@ -477,37 +533,37 @@ namespace BetterGenshinImpact.GameTask.AutoFishing // currentBiteWordsTips // .ToWindowsRectangleOffset(liftingWordsAreaRect.X, liftingWordsAreaRect.Y) // .ToRectDrawable()); - using var tipsRa = content.CaptureRectArea.Derive(currentBiteWordsTips + liftingWordsAreaRect.Location); + using var tipsRa = imageRegion.Derive(currentBiteWordsTips + liftingWordsAreaRect.Location); tipsRa.DrawSelf("FishBiteTips"); // 图像提竿判断 - using var liftRodButtonRa = content.CaptureRectArea.Find(AutoFishingAssets.Instance.LiftRodButtonRo); + using var liftRodButtonRa = imageRegion.Find(AutoFishingAssets.Instance.LiftRodButtonRo); if (!liftRodButtonRa.IsEmpty()) { - Simulation.SendInput.Mouse.LeftButtonClick(); - _logger.LogInformation(@"┌------------------------┐"); - _logger.LogInformation(" 自动提竿(图像识别)"); + input.Mouse.LeftButtonClick(); + logger.LogInformation(@"┌------------------------┐"); + logger.LogInformation(" 自动提竿(图像识别)"); VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); return BehaviourStatus.Succeeded; } // OCR 提竿判断 - var text = ocrService.Ocr(new Mat(content.CaptureRectArea.SrcGreyMat, + var text = ocrService.Ocr(new Mat(imageRegion.SrcGreyMat, new Rect(currentBiteWordsTips.X + liftingWordsAreaRect.X, currentBiteWordsTips.Y + liftingWordsAreaRect.Y, currentBiteWordsTips.Width, currentBiteWordsTips.Height))); if (!string.IsNullOrEmpty(text) && StringUtils.RemoveAllSpace(text).Contains("上钩")) { - Simulation.SendInput.Mouse.LeftButtonClick(); - _logger.LogInformation(@"┌------------------------┐"); - _logger.LogInformation(" 自动提竿(OCR)"); + input.Mouse.LeftButtonClick(); + logger.LogInformation(@"┌------------------------┐"); + logger.LogInformation(" 自动提竿(OCR)"); VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); return BehaviourStatus.Succeeded; } - Simulation.SendInput.Mouse.LeftButtonClick(); - _logger.LogInformation(@"┌------------------------┐"); - _logger.LogInformation(" 自动提竿(文字块)"); + input.Mouse.LeftButtonClick(); + logger.LogInformation(@"┌------------------------┐"); + logger.LogInformation(" 自动提竿(文字块)"); VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); return BehaviourStatus.Succeeded; } @@ -519,20 +575,21 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// /// 进入钓鱼界面先尝试获取钓鱼框的位置 /// - public class GetFishBoxArea : BaseBehaviour + public class GetFishBoxArea : BaseBehaviour { - private readonly ILogger _logger = App.GetLogger(); + private readonly ILogger logger; private readonly Blackboard blackboard; - public GetFishBoxArea(string name, Blackboard blackboard) : base(name) + public GetFishBoxArea(string name, Blackboard blackboard, ILogger logger) : base(name) { this.blackboard = blackboard; + this.logger = logger; } - protected override BehaviourStatus Update(CaptureContent content) + protected override BehaviourStatus Update(ImageRegion imageRegion) { - _logger.LogDebug("GetFishBoxArea"); + logger.LogDebug("GetFishBoxArea"); - using var topMat = new Mat(content.CaptureRectArea.SrcMat, new Rect(0, 0, content.CaptureRectArea.Width, content.CaptureRectArea.Height / 2)); + using var topMat = new Mat(imageRegion.SrcMat, new Rect(0, 0, imageRegion.Width, imageRegion.Height / 2)); var rects = AutoFishingImageRecognition.GetFishBarRect(topMat); if (rects != null && rects.Count == 2) @@ -570,44 +627,46 @@ namespace BetterGenshinImpact.GameTask.AutoFishing blackboard.fishBoxRect = new Rect(_cur.X - hExtra, _cur.Y - vExtra, (_left.X + _left.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 = content.CaptureRectArea.Derive(blackboard.fishBoxRect); + using var boxRa = imageRegion.Derive(blackboard.fishBoxRect); boxRa.DrawSelf("FishBox", new Pen(Color.LightPink, 2)); return BehaviourStatus.Succeeded; } return BehaviourStatus.Running; - //CheckFishingUserInterface(content); + //CheckFishingUserInterface(imageRegion); } } /// /// 拉条 /// - public class Fishing : BaseBehaviour + public class Fishing : BaseBehaviour { - private readonly ILogger _logger = App.GetLogger(); + private readonly ILogger logger; + private readonly IInputSimulator input; private readonly Blackboard blackboard; - public Fishing(string name, Blackboard blackboard) : base(name) + public Fishing(string name, Blackboard blackboard, ILogger logger, IInputSimulator input) : base(name) { this.blackboard = blackboard; + this.logger = logger; + this.input = input; } private MOUSEEVENTF _prevMouseEvent = 0x0; private bool _findFishBoxTips; - protected override BehaviourStatus Update(CaptureContent content) + protected override BehaviourStatus Update(ImageRegion imageRegion) { - _logger.LogDebug("Fishing"); - var fishBarMat = new Mat(content.CaptureRectArea.SrcMat, blackboard.fishBoxRect); - var simulator = Simulation.SendInput; + logger.LogDebug("Fishing"); + var fishBarMat = new Mat(imageRegion.SrcMat, blackboard.fishBoxRect); var rects = AutoFishingImageRecognition.GetFishBarRect(fishBarMat); if (rects != null && rects.Count > 0) { if (rects.Count >= 2 && _prevMouseEvent == 0x0 && !_findFishBoxTips) { _findFishBoxTips = true; - _logger.LogInformation(" 识别到钓鱼框,自动拉扯中..."); + logger.LogInformation(" 识别到钓鱼框,自动拉扯中..."); } // 超过3个矩形是异常情况,取高度最高的三个矩形进行识别 @@ -632,14 +691,14 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _left = rects[0]; } - PutRects(content, _left, _cur, new Rect()); + PutRects(imageRegion, _left, _cur, new Rect()); if (_cur.X < _left.X) { if (_prevMouseEvent != MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN) { - simulator.Mouse.LeftButtonDown(); - //Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown(); + input.Mouse.LeftButtonDown(); + //input.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown(); _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN; //Debug.WriteLine("进度不到 左键按下"); } @@ -648,8 +707,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing { if (_prevMouseEvent == MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN) { - simulator.Mouse.LeftButtonUp(); - //Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonUp(); + input.Mouse.LeftButtonUp(); + //input.PostMessage(TaskContext.Instance().GameHandle).LeftButtonUp(); _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP; //Debug.WriteLine("进度超出 左键松开"); } @@ -661,14 +720,14 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _left = rects[0]; _cur = rects[1]; _right = rects[2]; - PutRects(content, _left, _cur, _right); + PutRects(imageRegion, _left, _cur, _right); if (_right.X + _right.Width - (_cur.X + _cur.Width) <= _cur.X - _left.X) { if (_prevMouseEvent == MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN) { - simulator.Mouse.LeftButtonUp(); - //Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonUp(); + input.Mouse.LeftButtonUp(); + //input.PostMessage(TaskContext.Instance().GameHandle).LeftButtonUp(); _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP; //Debug.WriteLine("进入框内中间 左键松开"); } @@ -677,8 +736,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing { if (_prevMouseEvent != MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN) { - simulator.Mouse.LeftButtonDown(); - //Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown(); + input.Mouse.LeftButtonDown(); + //input.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown(); _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN; //Debug.WriteLine("未到框内中间 左键按下"); } @@ -686,34 +745,34 @@ namespace BetterGenshinImpact.GameTask.AutoFishing } else { - PutRects(content, new Rect(), new Rect(), new Rect()); + PutRects(imageRegion, new Rect(), new Rect(), new Rect()); } } else { - PutRects(content, new Rect(), new Rect(), new Rect()); + PutRects(imageRegion, new Rect(), new Rect(), new Rect()); // 没有矩形视为已经完成钓鱼 VisionContext.Instance().DrawContent.RemoveRect("FishBox"); _findFishBoxTips = false; _prevMouseEvent = 0x0; - _logger.LogInformation(" 拉扯结束"); - _logger.LogInformation(@"└------------------------┘"); + logger.LogInformation(" 拉扯结束"); + logger.LogInformation(@"└------------------------┘"); // 保证鼠标松开 - simulator.Mouse.LeftButtonUp(); + input.Mouse.LeftButtonUp(); - blackboard.Sleep(7000); + blackboard.Sleep(2000); return BehaviourStatus.Succeeded; - //CheckFishingUserInterface(content); + //CheckFishingUserInterface(imageRegion); } return BehaviourStatus.Running; } private readonly Pen _pen = new(Color.Red, 1); - private void PutRects(CaptureContent content, Rect left, Rect cur, Rect right) + private void PutRects(ImageRegion imageRegion, Rect left, Rect cur, Rect right) { //var list = new List //{ @@ -721,7 +780,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing // cur.ToWindowsRectangleOffset(_fishBoxRect.X, _fishBoxRect.Y).ToRectDrawable(_pen), // right.ToWindowsRectangleOffset(_fishBoxRect.X, _fishBoxRect.Y).ToRectDrawable(_pen) //}; - using var fishBoxRa = content.CaptureRectArea.Derive(blackboard.fishBoxRect); + using var fishBoxRa = imageRegion.Derive(blackboard.fishBoxRect); var list = new List { fishBoxRa.ToRectDrawable(left, "left", _pen), @@ -735,23 +794,26 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// /// 如果视角被其他行为重置过,则调整视角至俯视 /// - public class MoveViewpointDown : BaseBehaviour + public class MoveViewpointDown : BaseBehaviour { - private readonly ILogger _logger = App.GetLogger(); + private readonly ILogger logger; + private readonly IInputSimulator input; private readonly Blackboard blackboard; - public MoveViewpointDown(string name, Blackboard blackboard) : base(name) + public MoveViewpointDown(string name, Blackboard blackboard, ILogger logger, IInputSimulator input) : base(name) { this.blackboard = blackboard; + this.logger = logger; + this.input = input; } - protected override BehaviourStatus Update(CaptureContent context) + protected override BehaviourStatus Update(ImageRegion context) { if (blackboard.pitchReset) { - _logger.LogInformation("调整视角至俯视"); + logger.LogInformation("调整视角至俯视"); blackboard.pitchReset = false; // 下移视角方便看鱼 - Simulation.SendInput.Mouse.MoveMouseBy(0, 400); + input.Mouse.MoveMouseBy(0, 500); blackboard.Sleep(100); return BehaviourStatus.Running; } diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs b/BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs index c9952cc9..6d06c374 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs @@ -17,17 +17,17 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// /// 已选择的鱼饵名 /// - internal string selectedBaitName = string.Empty; + public string selectedBaitName = string.Empty; /// /// 鱼塘 /// - internal Fishpond fishpond; + public Fishpond fishpond; /// /// 是否没有目标鱼 /// - internal bool noTargetFish; + public bool noTargetFish; /// /// 拉条位置的识别框 @@ -38,7 +38,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// 是否正在选鱼饵界面 /// 此时有阴影遮罩,OpenCv的图像匹配会受干扰 /// - internal bool chooseBaitUIOpening = false; + public bool chooseBaitUIOpening = false; /// /// 镜头俯仰是否被行为重置 @@ -47,8 +47,14 @@ namespace BetterGenshinImpact.GameTask.AutoFishing internal bool pitchReset = false; #region 分层暂放 - internal static readonly YoloV8Predictor predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).WithSessionOptions(BgiSessionOption.Instance.Options).Build(); - internal Action Sleep { get; set; } + public YoloV8Predictor predictor; + public Action Sleep; + + public Blackboard(YoloV8Predictor predictor, Action sleep) + { + this.predictor = predictor; + Sleep = sleep; + } #endregion internal virtual void Reset() @@ -57,6 +63,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing fishBoxRect = Rect.Empty; chooseBaitUIOpening = false; pitchReset = false; + selectedBaitName = string.Empty; } } } diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs b/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs index 28f5b4a1..90d969b7 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs @@ -24,19 +24,53 @@ public class Fishpond /// public List Fishes { get; set; } = []; + public Fishpond(List fishes) + { + Fishes = fishes; + + FishpondRect = CalculateFishpondRect(); + } + /// /// /// /// 是否包含抛竿落点 - public Fishpond(DetectionResult result, bool includeTarget = false) + /// 是否忽略“获得”物品的图标 + public Fishpond(DetectionResult result, bool includeTarget = false, bool ignoreObtained = false) { foreach (var box in result.Boxes) { + Rect rect = new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height); if (box.Class.Name == "rod" || box.Class.Name == "err rod") { - TargetRect = new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height); + TargetRect = rect; continue; } + else if (ignoreObtained) + { + // todo:特殊鱼的图标以及新获得的图标是不一样的,要特殊处理 + // todo:不是很重要但有机会可以从构造函数里分离逻辑 + // 忽略界面左侧提示的“获得”物品的图标,当上一竿获得鱼时,会对当前竿产生干扰 + // 使用估算大小和位置的方式来判断并剔除 + if (box.Bounds.Width < result.Image.Width * 0.024 && box.Bounds.Height < result.Image.Width * 0.024) + { + Rect huode = new Rect((int)(0.04375 * result.Image.Width), (int)(0.4666 * result.Image.Height), (int)(0.1 * result.Image.Width), (int)(0.1 * result.Image.Width)); + if (huode.Contains(rect)) + { + continue; + } + } + // 忽略界面中央提示的“获得”物品的图标 + if (box.Bounds.Width > result.Image.Width * 0.03 && box.Bounds.Width < result.Image.Width * 0.05 && + box.Bounds.Height > result.Image.Width * 0.03 && box.Bounds.Height < result.Image.Width * 0.05) + { + Rect huode = new Rect((int)(0.4 * result.Image.Width), (int)(0.445 * result.Image.Height), (int)(0.2 * result.Image.Width), (int)(0.06125 * result.Image.Width)); + if (huode.Contains(rect)) + { + continue; + } + } + } if (includeTarget) { if (box.Class.Name == "koi") //进入抛竿的时候只看koihead @@ -45,7 +79,7 @@ public class Fishpond } } - var fish = new OneFish(box.Class.Name, new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height), box.Confidence); + var fish = new OneFish(box.Class.Name, rect, box.Confidence); Fishes.Add(fish); } @@ -130,37 +164,4 @@ public class Fishpond return result; } - - /// - /// 最多的鱼吃的鱼饵名称 - /// - /// - public string MostMatchBait() - { - Dictionary dict = []; - foreach (var fish in Fishes) - { - if (dict.TryGetValue(fish.FishType.BaitName, out _)) - { - dict[fish.FishType.BaitName]++; - } - else - { - dict[fish.FishType.BaitName] = 1; - } - } - - var max = 0; - var result = ""; - foreach (var (key, value) in dict) - { - if (value > max) - { - max = value; - result = key; - } - } - - return result; - } } diff --git a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs index 225dbd09..ae33e2f1 100644 --- a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs +++ b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs @@ -479,11 +479,13 @@ public class TpTask(CancellationToken ct) /// 鼠标移动后位置y public async Task MouseClickAndMove(int x1, int y1, int x2, int y2) { - GlobalMethod.MoveMouseTo(x1, y1); + // GlobalMethod.MoveMouseTo(x1, y1); + GameCaptureRegion.GameRegionMove((rect, scale) => (x1 * scale, y1 * scale)); await Delay(50, ct); GlobalMethod.LeftButtonDown(); await Delay(50, ct); - GlobalMethod.MoveMouseTo(x2, y2); + // GlobalMethod.MoveMouseTo(x2, y2); + GameCaptureRegion.GameRegionMove((rect, scale) => (x2 * scale, y2 * scale)); await Delay(50, ct); GlobalMethod.LeftButtonUp(); await Delay(50, ct); @@ -544,31 +546,9 @@ public class TpTask(CancellationToken ct) private async Task MouseMoveMap(int pixelDeltaX, int pixelDeltaY, int steps = 10) { - // 确保不影响总移动距离 - int totalX = 0; - int totalY = 0; - // 梯形缩放因子 - double scaleFactor = 0.75; - // 计算每一步的位移,从steps/2逐渐减小到0 - int[] stepX = new int[steps]; - int[] stepY = new int[steps]; - for (int i = 0; i < steps; i++) - { - double factor = ((double)(steps - Math.Max(i, steps / 2)) / (steps / 2)) / scaleFactor; - stepX[i] = (int)(pixelDeltaX * factor / steps); - stepY[i] = (int)(pixelDeltaY * factor / steps); - totalX += stepX[i]; - totalY += stepY[i]; - } - // 均匀分配多余的部分到前半段 - int remainingX = (pixelDeltaX - totalX); - int remainingY = (pixelDeltaY - totalY); - for (int i = 0; i < steps / 2 + 1; i++) - { - stepX[i] += remainingX / (steps / 2 + 1) + ((remainingX % (steps / 2 + 1) > i) ? 0 : 1); - stepY[i] += remainingY / (steps / 2 + 1) + ((remainingX % (steps / 2 + 1) > i) ? 0 : 1); - } + int[] stepX = GenerateSteps(pixelDeltaX, steps); + int[] stepY = GenerateSteps(pixelDeltaY, steps); // 随机起点以避免地图移动无效 GameCaptureRegion.GameRegionMove((rect, _) => @@ -578,13 +558,42 @@ public class TpTask(CancellationToken ct) Simulation.SendInput.Mouse.LeftButtonDown(); for (var i = 0; i < steps; i++) { - Simulation.SendInput.Mouse.MoveMouseBy(stepX[i], stepY[i]); + var i1 = i; await Delay(_tpConfig.StepIntervalMilliseconds, ct); + // Simulation.SendInput.Mouse.MoveMouseBy(stepX[i], stepY[i]); + GameCaptureRegion.GameRegionMoveBy((_, scale) => (stepX[i1] * scale, stepY[i1] * scale)); } Simulation.SendInput.Mouse.LeftButtonUp(); } + private int[] GenerateSteps(int delta, int steps) { + double[] factors = new double[steps]; + double sum = 0; + for (int i = 0; i < steps; i++) { + factors[i] = Math.Cos(i * Math.PI / (2 * steps)); + sum += factors[i]; + } + + int[] stepsArr = new int[steps]; + int remaining = delta; + + // 两阶段分配:基础值 + 余数补偿 + for (int i = 0; i < steps; i++) { + double ratio = factors[i] / sum; + stepsArr[i] = (int)(delta * ratio); // 基础值 + remaining -= stepsArr[i]; + } + + int center = steps / 2; + for (int r = 0; r < Math.Abs(remaining); r++) { + int target = (center + r) % steps; // 从中点开始螺旋分配 + stepsArr[target] += remaining > 0 ? 1 : -1; + } + + return stepsArr; + } + public Point2f GetPositionFromBigMap() { return GetBigMapCenterPoint(); diff --git a/BetterGenshinImpact/GameTask/Common/BgiVision/BvSimpleOperation.cs b/BetterGenshinImpact/GameTask/Common/BgiVision/BvSimpleOperation.cs index f7dc8eeb..419fe8fa 100644 --- a/BetterGenshinImpact/GameTask/Common/BgiVision/BvSimpleOperation.cs +++ b/BetterGenshinImpact/GameTask/Common/BgiVision/BvSimpleOperation.cs @@ -5,8 +5,8 @@ using BetterGenshinImpact.GameTask.AutoPick.Assets; using BetterGenshinImpact.GameTask.AutoSkip.Assets; using BetterGenshinImpact.GameTask.Common.Element.Assets; using BetterGenshinImpact.GameTask.Model.Area; +using Fischless.WindowsInput; using OpenCvSharp; -using Vanara.PInvoke; namespace BetterGenshinImpact.GameTask.Common.BgiVision; @@ -198,4 +198,15 @@ public static partial class Bv return false; } + + public static bool FindFAndPress(ImageRegion captureRa, IKeyboardSimulator keyboard, params string[] text) + { + if (FindF(captureRa, text)) + { + keyboard.KeyPress(AutoPickAssets.Instance.PickVk); + return true; + } + + return false; + } } diff --git a/BetterGenshinImpact/GameTask/GameTaskManager.cs b/BetterGenshinImpact/GameTask/GameTaskManager.cs index 7b762f6d..46de5967 100644 --- a/BetterGenshinImpact/GameTask/GameTaskManager.cs +++ b/BetterGenshinImpact/GameTask/GameTaskManager.cs @@ -140,7 +140,18 @@ internal class GameTaskManager public static Mat LoadAssetImage(string featName, string assertName, ImreadModes flags = ImreadModes.Color) { var info = TaskContext.Instance().SystemInfo; - var assetsFolder = Global.Absolute($@"GameTask\{featName}\Assets\{info.GameScreenSize.Width}x{info.GameScreenSize.Height}"); + return LoadAssetImage(featName, assertName, info.GameScreenSize.Width, info.GameScreenSize.Height, info.AssetScale, flags); + } + + /// + /// 这个重载是为了和TaskContext.Instance().SystemInfo解耦 + /// todo: 更系统的分层 + /// + /// + /// + public static Mat LoadAssetImage(string featName, string assertName, int width, int height, double assetScale, ImreadModes flags = ImreadModes.Color) + { + var assetsFolder = Global.Absolute($@"GameTask\{featName}\Assets\{width}x{height}"); if (!Directory.Exists(assetsFolder)) { assetsFolder = Global.Absolute($@"GameTask\{featName}\Assets\1920x1080"); @@ -158,9 +169,9 @@ internal class GameTaskManager } var mat = Mat.FromStream(File.OpenRead(filePath), flags); - if (info.GameScreenSize.Width != 1920) + if (width != 1920) { - mat = ResizeHelper.Resize(mat, info.AssetScale); + mat = ResizeHelper.Resize(mat, assetScale); } return mat; diff --git a/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs index 75521d17..1c4607cc 100644 --- a/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs +++ b/BetterGenshinImpact/GameTask/Model/Area/DesktopRegion.cs @@ -1,6 +1,7 @@ using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.GameTask.Model.Area.Converter; using BetterGenshinImpact.Helpers; +using Fischless.WindowsInput; using System.Drawing; namespace BetterGenshinImpact.GameTask.Model.Area; @@ -10,17 +11,37 @@ namespace BetterGenshinImpact.GameTask.Model.Area; /// 无缩放的桌面屏幕大小 /// 主要用于点击操作 /// -public class DesktopRegion() : Region(0, 0, PrimaryScreen.WorkingArea.Width, PrimaryScreen.WorkingArea.Height) +public class DesktopRegion : Region { + private readonly IMouseSimulator mouse; + public DesktopRegion() : base(0, 0, PrimaryScreen.WorkingArea.Width, PrimaryScreen.WorkingArea.Height) + { + mouse = Simulation.SendInput.Mouse; + } + + public DesktopRegion(IMouseSimulator mouse) : base(0, 0, PrimaryScreen.WorkingArea.Width, PrimaryScreen.WorkingArea.Height) + { + this.mouse = mouse; + } + + public void DesktopRegionClick(int x, int y, int w, int h) { - Simulation.SendInput.Mouse.MoveMouseTo((x + (w * 1d / 2)) * 65535 / Width, + if (mouse == null) + { + throw new System.NullReferenceException(); + } + mouse.MoveMouseTo((x + (w * 1d / 2)) * 65535 / Width, (y + (h * 1d / 2)) * 65535 / Height).LeftButtonClick().Sleep(50).LeftButtonUp(); } public void DesktopRegionMove(int x, int y, int w, int h) { - Simulation.SendInput.Mouse.MoveMouseTo((x + (w * 1d / 2)) * 65535 / Width, + if (mouse == null) + { + throw new System.NullReferenceException(); + } + mouse.MoveMouseTo((x + (w * 1d / 2)) * 65535 / Width, (y + (h * 1d / 2)) * 65535 / Height); } @@ -40,6 +61,11 @@ public class DesktopRegion() : Region(0, 0, PrimaryScreen.WorkingArea.Width, Pri Simulation.SendInput.Mouse.MoveMouseTo(cx * 65535 * 1d / PrimaryScreen.WorkingArea.Width, cy * 65535 * 1d / PrimaryScreen.WorkingArea.Height); } + + public static void DesktopRegionMoveBy(double dx, double dy) + { + Simulation.SendInput.Mouse.MoveMouseBy((int)dx, (int)dy); + } public GameCaptureRegion Derive(Bitmap captureBitmap, int x, int y) { diff --git a/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs index ef040b1e..4e497304 100644 --- a/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs +++ b/BetterGenshinImpact/GameTask/Model/Area/GameCaptureRegion.cs @@ -11,7 +11,7 @@ namespace BetterGenshinImpact.GameTask.Model.Area; /// 游戏捕获区域类 /// 主要用于转换到遮罩窗口的坐标 /// -public class GameCaptureRegion(Bitmap bitmap, int initX, int initY, Region? owner = null, INodeConverter? converter = null) : ImageRegion(bitmap, initX, initY, owner, converter) +public class GameCaptureRegion(Bitmap bitmap, int initX, int initY, Region? owner = null, INodeConverter? converter = null, DrawContent? drawContent = null) : ImageRegion(bitmap, initX, initY, owner, converter, drawContent) { /// /// 在游戏捕获图像的坐标维度进行转换到遮罩窗口的坐标维度 @@ -102,6 +102,14 @@ public class GameCaptureRegion(Bitmap bitmap, int initX, int initY, Region? owne DesktopRegion.DesktopRegionMove(captureAreaRect.X + cx, captureAreaRect.Y + cy); } + public static void GameRegionMoveBy(Func deltaFunc) + { + var captureAreaRect = TaskContext.Instance().SystemInfo.CaptureAreaRect; + var assetScale = TaskContext.Instance().SystemInfo.ScaleTo1080PRatio; + var (dx, dy) = deltaFunc(new Size(captureAreaRect.Width, captureAreaRect.Height), assetScale); + DesktopRegion.DesktopRegionMoveBy(dx, dy); + } + /// /// 静态方法,输入1080P下的坐标,方法会自动转换到当前游戏捕获区域大小下的坐标并点击 /// diff --git a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs index bca5ff10..457a8c73 100644 --- a/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs +++ b/BetterGenshinImpact/GameTask/Model/Area/ImageRegion.cs @@ -71,7 +71,7 @@ public class ImageRegion : Region } } - public ImageRegion(Bitmap bitmap, int x, int y, Region? owner = null, INodeConverter? converter = null) : base(x, y, bitmap.Width, bitmap.Height, owner, converter) + public ImageRegion(Bitmap bitmap, int x, int y, Region? owner = null, INodeConverter? converter = null, DrawContent? drawContent = null) : base(x, y, bitmap.Width, bitmap.Height, owner, converter, drawContent) { _srcBitmap = bitmap; } diff --git a/BetterGenshinImpact/GameTask/Model/Area/Region.cs b/BetterGenshinImpact/GameTask/Model/Area/Region.cs index bde452c8..b324162b 100644 --- a/BetterGenshinImpact/GameTask/Model/Area/Region.cs +++ b/BetterGenshinImpact/GameTask/Model/Area/Region.cs @@ -1,5 +1,6 @@ using BetterGenshinImpact.GameTask.Model.Area.Converter; using BetterGenshinImpact.View.Drawable; +using Fischless.WindowsInput; using OpenCvSharp; using System; using System.Diagnostics; @@ -54,7 +55,7 @@ public class Region : IDisposable { } - public Region(int x, int y, int width, int height, Region? owner = null, INodeConverter? converter = null) + public Region(int x, int y, int width, int height, Region? owner = null, INodeConverter? converter = null, DrawContent? drawContent = null) { X = x; Y = y; @@ -62,6 +63,7 @@ public class Region : IDisposable Height = height; Prev = owner; PrevConverter = converter; + this.drawContent = drawContent ?? VisionContext.Instance().DrawContent; } public Region(Rect rect, Region? owner = null, INodeConverter? converter = null) : this(rect.X, rect.Y, rect.Width, rect.Height, owner, converter) @@ -75,6 +77,11 @@ public class Region : IDisposable /// public INodeConverter? PrevConverter { get; } + /// + /// 绘图上下文 + /// + private readonly DrawContent drawContent; + // public List? NextChildren { get; protected set; } /// @@ -189,13 +196,13 @@ public class Region : IDisposable public void DrawRect(int x, int y, int w, int h, string name, Pen? pen = null) { var drawable = ToRectDrawable(x, y, w, h, name, pen); - VisionContext.Instance().DrawContent.PutRect(name, drawable); + drawContent.PutRect(name, drawable); } public void DrawRect(Rect rect, string name, Pen? pen = null) { var drawable = ToRectDrawable(rect.X, rect.Y, rect.Width, rect.Height, name, pen); - VisionContext.Instance().DrawContent.PutRect(name, drawable); + drawContent.PutRect(name, drawable); } /// @@ -259,7 +266,7 @@ public class Region : IDisposable public void DrawLine(int x1, int y1, int x2, int y2, string name, Pen? pen = null) { var drawable = ToLineDrawable(x1, y1, x2, y2, name, pen); - VisionContext.Instance().DrawContent.PutLine(name, drawable); + drawContent.PutLine(name, drawable); } public Rect ConvertSelfPositionToGameCaptureRegion() @@ -349,7 +356,7 @@ public class Region : IDisposable /// public Region Derive(int x, int y, int w, int h) { - return new Region(x, y, w, h, this, new TranslationConverter(x, y)); + return new Region(x, y, w, h, this, new TranslationConverter(x, y), this.drawContent); } public Region Derive(Rect rect) diff --git a/BetterGenshinImpact/View/Drawable/DrawContent.cs b/BetterGenshinImpact/View/Drawable/DrawContent.cs index 8769f9f2..f23c6d39 100644 --- a/BetterGenshinImpact/View/Drawable/DrawContent.cs +++ b/BetterGenshinImpact/View/Drawable/DrawContent.cs @@ -20,7 +20,7 @@ public class DrawContent /// public ConcurrentDictionary> LineList { get; set; } = new(); - public void PutRect(string key, RectDrawable newRect) + public virtual void PutRect(string key, RectDrawable newRect) { if (RectList.TryGetValue(key, out var prevRect)) { @@ -34,7 +34,7 @@ public class DrawContent MaskWindow.Instance().Refresh(); } - public void PutOrRemoveRectList(string key, List? list) + public virtual void PutOrRemoveRectList(string key, List? list) { bool changed = false; @@ -72,7 +72,7 @@ public class DrawContent } } - public void RemoveRect(string key) + public virtual void RemoveRect(string key) { if (RectList.TryGetValue(key, out _)) { @@ -81,7 +81,7 @@ public class DrawContent } } - public void PutLine(string key, LineDrawable newLine) + public virtual void PutLine(string key, LineDrawable newLine) { if (LineList.TryGetValue(key, out var prev)) { @@ -96,7 +96,7 @@ public class DrawContent } - public void RemoveLine(string key) + public virtual void RemoveLine(string key) { if (LineList.TryGetValue(key, out _)) { @@ -110,7 +110,7 @@ public class DrawContent /// /// 清理所有绘制内容 /// - public void ClearAll() + public virtual void ClearAll() { if (RectList.IsEmpty && TextList.IsEmpty && LineList.IsEmpty) { diff --git a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml index 97fceb7a..52b718a9 100644 --- a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml +++ b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml @@ -49,7 +49,30 @@ - + + + + + + + + + + + + + diff --git a/Test/BetterGenshinImpact.UnitTest/BetterGenshinImpact.UnitTest.csproj b/Test/BetterGenshinImpact.UnitTest/BetterGenshinImpact.UnitTest.csproj index f0f899fa..3fef4dee 100644 --- a/Test/BetterGenshinImpact.UnitTest/BetterGenshinImpact.UnitTest.csproj +++ b/Test/BetterGenshinImpact.UnitTest/BetterGenshinImpact.UnitTest.csproj @@ -1,7 +1,7 @@ - net8.0 + net8.0-windows10.0.22621.0 enable enable @@ -10,18 +10,19 @@ - - - - + + + + + - + - + diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ChooseBait.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ChooseBait.cs new file mode 100644 index 00000000..acd65a6d --- /dev/null +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ChooseBait.cs @@ -0,0 +1,100 @@ +using BehaviourTree; +using BetterGenshinImpact.GameTask.AutoFishing; +using BetterGenshinImpact.GameTask.AutoFishing.Model; +using BetterGenshinImpact.GameTask.Model.Area; +using BetterGenshinImpact.GameTask.Model.Area.Converter; +using Microsoft.Extensions.Time.Testing; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests +{ + public partial class BehavioursTests + { + [Theory] + [InlineData(@"20250225101300361_ChooseBait_Succeeded.png", new string[] { "medaka", "butterflyfish", "butterflyfish", "pufferfish" })] + [InlineData(@"20250226161354285_ChooseBait_Succeeded.png", new string[] { "medaka", "medaka" })] + /// + /// 测试各种选取鱼饵,结果为成功 + /// + public void ChooseBaitTest_VariousBait_ShouldSuccess(string screenshot1080p, IEnumerable fishNames) + { + // + Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + var imageRegion = new ImageRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d)); + + var blackboard = new Blackboard(null, sleep: i => { }) + { + fishpond = new Fishpond(fishNames.Select(n => new OneFish(n, OpenCvSharp.Rect.Empty, 0)).ToList()) + }; + + // + ChooseBait sut = new ChooseBait("-", blackboard, new FakeLogger(), new FakeInputSimulator()); + BehaviourStatus actual = sut.Tick(imageRegion); + + // + Assert.Equal(BehaviourStatus.Running, actual); + + // + actual = sut.Tick(imageRegion); + + // + Assert.Equal(BehaviourStatus.Succeeded, actual); + } + + [Theory] + [InlineData(@"20250226161354285_ChooseBait_Succeeded.png", new string[] { "koi" })] + /// + /// 测试各种选取鱼饵,结果为失败 + /// + public void ChooseBaitTest_VariousBait_ShouldFail(string screenshot1080p, IEnumerable fishNames) + { + // + Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + var imageRegion = new ImageRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d)); + + var blackboard = new Blackboard(null, sleep: i => { }) + { + fishpond = new Fishpond(fishNames.Select(n => new OneFish(n, OpenCvSharp.Rect.Empty, 0)).ToList()) + }; + + DateTimeOffset dateTime = new DateTimeOffset(2025, 2, 26, 16, 13, 54, 285, TimeSpan.FromHours(8)); + FakeTimeProvider fakeTimeProvider = new FakeTimeProvider(dateTime); + + // + ChooseBait sut = new ChooseBait("-", blackboard, new FakeLogger(), new FakeInputSimulator(), fakeTimeProvider); + BehaviourStatus actual = sut.Tick(imageRegion); + + // + Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName)); + Assert.True(blackboard.chooseBaitUIOpening); + Assert.Equal(BehaviourStatus.Running, actual); + + // + fakeTimeProvider.SetUtcNow(dateTime.AddSeconds(1)); + + // + actual = sut.Tick(imageRegion); + + // + Assert.False(String.IsNullOrEmpty(blackboard.selectedBaitName)); + Assert.True(blackboard.chooseBaitUIOpening); + Assert.Equal(BehaviourStatus.Running, actual); + + // + fakeTimeProvider.SetUtcNow(dateTime.AddSeconds(3)); + + // + actual = sut.Tick(imageRegion); + + // + Assert.True(String.IsNullOrEmpty(blackboard.selectedBaitName)); + Assert.False(blackboard.chooseBaitUIOpening); + Assert.Equal(BehaviourStatus.Failed, actual); + } + } +} diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishpond.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishpond.cs new file mode 100644 index 00000000..e3735f3d --- /dev/null +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.GetFishpond.cs @@ -0,0 +1,80 @@ +using BetterGenshinImpact.GameTask.AutoFishing; +using BehaviourTree; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BetterGenshinImpact.GameTask.Model.Area; +using BetterGenshinImpact.Core.Config; +using Compunet.YoloV8; +using BetterGenshinImpact.GameTask.AutoFishing.Model; + +namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests +{ + public partial class BehavioursTests + { + [Theory] + [InlineData("20250225101257889_GetFishpond_Succeeded.png", new string[] { "medaka", "butterflyfish", "pufferfish", "stickleback" })] + [InlineData("202502252347412417.png", new string[] { "medaka", "koi", "koi head" })] + [InlineData("202502252350206390.png", new string[] { "phony unihornfish", "magma rapidfish" })] + /// + /// 测试各种鱼的获取,结果为成功 + /// + public void GetFishpondTest_VariousFishExist_ShouldSuccess(string screenshot1080p, IEnumerable fishNames) + { + // + Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + var imageRegion = new GameCaptureRegion(bitmap, 0, 0, drawContent: new FakeDrawContent()); + + var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build(); + + var blackboard = new Blackboard(predictor, sleep: i => { }); + + // + GetFishpond sut = new GetFishpond("-", blackboard, new FakeLogger(), drawContent: new FakeDrawContent()); + BehaviourStatus actualStatus = sut.Tick(imageRegion); + + // + Assert.Equal(BehaviourStatus.Succeeded, actualStatus); + foreach (var g in fishNames.GroupBy(n => n)) + { + string fishName = g.Key; + var fish = blackboard.fishpond.Fishes.Where(f => f.FishType.Name == fishName); + Assert.NotEmpty(fish); + } + } + + [Theory] + [InlineData("20250225101257889_GetFishpond_Succeeded.png", "medaka", 1)] + [InlineData("20250301192848793_GetFishpond_Succeeded.png", "medaka", 2)] + [InlineData("20250226161354285_ChooseBait_Succeeded.png", "medaka", 0)] + [InlineData("202503012143011486@900p.png", "medaka", 0)] + [InlineData("20250301231059172_GetFishpond_Succeeded.png", "medaka", 0)] + [InlineData("20250301234659009_GetFishpond_Succeeded.png", "axe marlin", 2)] + [InlineData("20250301235638915_GetFishpond_Succeeded.png", "butterflyfish", 1)] + [InlineData("20250302001049589_GetFishpond_Succeeded.png", "axe marlin", 0)] + /// + /// 测试各种鱼的获取数量,数量应相符 + /// + public void GetFishpondTest_FishCount_ShouldSuccess(string screenshot1080p, string fishName, int count) + { + // + Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + var imageRegion = new GameCaptureRegion(bitmap, 0, 0, drawContent: new FakeDrawContent()); + + var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build(); + + var blackboard = new Blackboard(predictor, sleep: i => { }); + + // + GetFishpond sut = new GetFishpond("-", blackboard, new FakeLogger(), drawContent: new FakeDrawContent()); + sut.Tick(imageRegion); + int actual = blackboard.fishpond?.Fishes?.Count(f => f.FishType.Name == fishName) ?? 0; + + // + Assert.Equal(count, actual); + } + } +} diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ThrowRod.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ThrowRod.cs new file mode 100644 index 00000000..92ef4d3c --- /dev/null +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/BehavioursTests.ThrowRod.cs @@ -0,0 +1,76 @@ +using BehaviourTree; +using BetterGenshinImpact.GameTask.AutoFishing.Model; +using BetterGenshinImpact.GameTask.AutoFishing; +using BetterGenshinImpact.GameTask.Model.Area.Converter; +using BetterGenshinImpact.GameTask.Model.Area; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; +using BetterGenshinImpact.Core.Config; +using Compunet.YoloV8; + +namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests +{ + public partial class BehavioursTests + { + [Theory] + [InlineData(@"20250225101304534_ThrowRod_Succeeded.png", "false worm bait")] + [InlineData(@"20250226162217468_ThrowRod_Succeeded.png", "fruit paste bait")] + /// + /// 测试各种抛竿,结果为成功 + /// + public void ThrowRodTest_VariousFish_ShouldSuccess(string screenshot1080p, string selectedBaitName) + { + // + Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + var imageRegion = new GameCaptureRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent()); + + var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build(); + + var blackboard = new Blackboard(predictor, sleep: i => { }) + { + selectedBaitName = selectedBaitName + }; + + // + ThrowRod sut = new ThrowRod("-", blackboard, new FakeLogger(), new FakeInputSimulator(), drawContent: new FakeDrawContent()); + BehaviourStatus actual = sut.Tick(imageRegion); + + // + Assert.False(blackboard.noTargetFish); + Assert.Equal(BehaviourStatus.Succeeded, actual); + } + + [Theory] + [InlineData(@"20250225101304534_ThrowRod_Succeeded.png", "fruit paste bait")] + [InlineData(@"20250225101304534_ThrowRod_Succeeded.png", "redrot bait")] + [InlineData(@"20250225101304534_ThrowRod_Succeeded.png", "fake fly bait")] + /// + /// 测试各种抛竿,结果为运行中 + /// + public void ThrowRodTest_VariousFish_ShouldFail(string screenshot1080p, string selectedBaitName) + { + // + Bitmap bitmap = new Bitmap(@$"..\..\..\Assets\AutoFishing\{screenshot1080p}"); + var imageRegion = new GameCaptureRegion(bitmap, 0, 0, new DesktopRegion(new FakeMouseSimulator()), converter: new ScaleConverter(1d), drawContent: new FakeDrawContent()); + + var predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).Build(); + + var blackboard = new Blackboard(predictor, sleep: i => { }) + { + selectedBaitName = selectedBaitName + }; + + // + ThrowRod sut = new ThrowRod("-", blackboard, new FakeLogger(), new FakeInputSimulator(), drawContent: new FakeDrawContent()); + BehaviourStatus actual = sut.Tick(imageRegion); + + // + Assert.False(blackboard.noTargetFish); + Assert.Equal(BehaviourStatus.Running, actual); + } + } +} diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/FakeDrawContent.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/FakeDrawContent.cs new file mode 100644 index 00000000..a61a25c0 --- /dev/null +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/FakeDrawContent.cs @@ -0,0 +1,36 @@ +using BetterGenshinImpact.View.Drawable; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests +{ + internal class FakeDrawContent : DrawContent + { + public override void ClearAll() + { + } + + public override void PutLine(string key, LineDrawable newLine) + { + } + + public override void PutOrRemoveRectList(string key, List? list) + { + } + + public override void PutRect(string key, RectDrawable newRect) + { + } + + public override void RemoveLine(string key) + { + } + + public override void RemoveRect(string key) + { + } + } +} diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/FakeInputSimulator.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/FakeInputSimulator.cs new file mode 100644 index 00000000..2270a987 --- /dev/null +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/FakeInputSimulator.cs @@ -0,0 +1,107 @@ +using Fischless.WindowsInput; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Vanara.PInvoke; + +namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests +{ + internal class FakeInputSimulator : IInputSimulator + { + public IKeyboardSimulator Keyboard => new FakeKeyboardSimulator(); + + public IMouseSimulator Mouse => new FakeMouseSimulator(); + + public IInputDeviceStateAdaptor InputDeviceState => throw new NotImplementedException(); + } + + internal class FakeKeyboardSimulator : IKeyboardSimulator + { + public IMouseSimulator Mouse => throw new NotImplementedException(); + + public IKeyboardSimulator KeyDown(User32.VK keyCode) => this; + + public IKeyboardSimulator KeyDown(bool? isExtendedKey, User32.VK keyCode) => this; + + public IKeyboardSimulator KeyPress(User32.VK keyCode) => this; + + public IKeyboardSimulator KeyPress(bool? isExtendedKey, User32.VK keyCode) => this; + + public IKeyboardSimulator KeyPress(params User32.VK[] keyCodes) => this; + + public IKeyboardSimulator KeyPress(bool? isExtendedKey, params User32.VK[] keyCodes) => this; + + public IKeyboardSimulator KeyUp(User32.VK keyCode) => this; + + public IKeyboardSimulator KeyUp(bool? isExtendedKey, User32.VK keyCode) => this; + + public IKeyboardSimulator ModifiedKeyStroke(IEnumerable modifierKeyCodes, IEnumerable keyCodes) => this; + + public IKeyboardSimulator ModifiedKeyStroke(IEnumerable modifierKeyCodes, User32.VK keyCode) => this; + + public IKeyboardSimulator ModifiedKeyStroke(User32.VK modifierKey, IEnumerable keyCodes) => this; + + public IKeyboardSimulator ModifiedKeyStroke(User32.VK modifierKeyCode, User32.VK keyCode) => this; + + public IKeyboardSimulator Sleep(int millsecondsTimeout) => this; + + public IKeyboardSimulator Sleep(TimeSpan timeout) => this; + + public IKeyboardSimulator TextEntry(string text) => this; + + public IKeyboardSimulator TextEntry(char character) => this; + } + + internal class FakeMouseSimulator : IMouseSimulator + { + public IKeyboardSimulator Keyboard => throw new NotImplementedException(); + + public IMouseSimulator HorizontalScroll(int scrollAmountInClicks) => this; + + public IMouseSimulator LeftButtonClick() => this; + + public IMouseSimulator LeftButtonDoubleClick() => this; + + public IMouseSimulator LeftButtonDown() => this; + + public IMouseSimulator LeftButtonUp() => this; + + public IMouseSimulator MiddleButtonClick() => this; + + public IMouseSimulator MiddleButtonDoubleClick() => this; + + public IMouseSimulator MiddleButtonDown() => this; + + public IMouseSimulator MiddleButtonUp() => this; + + public IMouseSimulator MoveMouseBy(int pixelDeltaX, int pixelDeltaY) => this; + + public IMouseSimulator MoveMouseTo(double absoluteX, double absoluteY) => this; + + public IMouseSimulator MoveMouseToPositionOnVirtualDesktop(double absoluteX, double absoluteY) => this; + + public IMouseSimulator RightButtonClick() => this; + + public IMouseSimulator RightButtonDoubleClick() => this; + + public IMouseSimulator RightButtonDown() => this; + + public IMouseSimulator RightButtonUp() => this; + + public IMouseSimulator Sleep(int millsecondsTimeout) => this; + + public IMouseSimulator Sleep(TimeSpan timeout) => this; + + public IMouseSimulator VerticalScroll(int scrollAmountInClicks) => this; + + public IMouseSimulator XButtonClick(int buttonId) => this; + + public IMouseSimulator XButtonDoubleClick(int buttonId) => this; + + public IMouseSimulator XButtonDown(int buttonId) => this; + + public IMouseSimulator XButtonUp(int buttonId) => this; + } +} diff --git a/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/FakeLogger.cs b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/FakeLogger.cs new file mode 100644 index 00000000..2be6b2f4 --- /dev/null +++ b/Test/BetterGenshinImpact.UnitTest/GameTaskTests/AutoFishingTests/FakeLogger.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests +{ + internal class FakeLogger : ILogger + { + public IDisposable? BeginScope(TState state) where TState : notnull + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + } + } +} diff --git a/Test/BetterGenshinImpact.UnitTest/UnitTest1.cs b/Test/BetterGenshinImpact.UnitTest/UnitTest1.cs index 89947f9d..bc72e1e5 100644 --- a/Test/BetterGenshinImpact.UnitTest/UnitTest1.cs +++ b/Test/BetterGenshinImpact.UnitTest/UnitTest1.cs @@ -2,8 +2,17 @@ namespace BetterGenshinImpact.UnitTest; public class UnitTest1 { - [Fact] - public void Test1() + /// + /// 测试从Assets目录获取图片,结果应为成功。 + /// Assets是BGI项目的一个submodule,须要单独获取。 + /// + /// + [Theory] + [InlineData("UI_Icon_Intee_AlchemySim_Dealer.png")] + public void TestGetDataFromAssets(string filename) { + bool actual = File.Exists(@$"..\..\..\Assets\{filename}"); + + Assert.True(actual); } } \ No newline at end of file