diff --git a/BetterGenshinImpact/Assets/Model/Fish/bgi_fish.onnx b/BetterGenshinImpact/Assets/Model/Fish/bgi_fish.onnx index cd170cdd..24eab6a6 100644 Binary files a/BetterGenshinImpact/Assets/Model/Fish/bgi_fish.onnx and b/BetterGenshinImpact/Assets/Model/Fish/bgi_fish.onnx differ diff --git a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs index e19e370a..98ed81f6 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs @@ -5,6 +5,7 @@ using System; using System.Threading.Tasks; using BetterGenshinImpact.GameTask.AutoDomain; using BetterGenshinImpact.GameTask.AutoFight; +using BetterGenshinImpact.GameTask.AutoFishing; using BetterGenshinImpact.GameTask.AutoWood; using BetterGenshinImpact.GameTask.Model.Enum; using BetterGenshinImpact.GameTask.AutoGeniusInvokation; @@ -105,6 +106,10 @@ public class Dispatcher // case "AutoMusicGame": // taskSettingsPageViewModel.SwitchAutoMusicGameCommand.Execute(null); // break; + + case "AutoFishing": + await new AutoFishingTask().Start(CancellationContext.Instance.Cts.Token); + break; default: throw new ArgumentException($"未知的任务名称: {soloTask.Name}", nameof(soloTask.Name)); diff --git a/BetterGenshinImpact/Core/Script/Dependence/Genshin.cs b/BetterGenshinImpact/Core/Script/Dependence/Genshin.cs index 6df1bf10..893d4a4d 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/Genshin.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/Genshin.cs @@ -3,6 +3,7 @@ using BetterGenshinImpact.GameTask.AutoTrackPath; using System.Threading.Tasks; using BetterGenshinImpact.GameTask.Common.Job; using Vanara.PInvoke; +using BetterGenshinImpact.GameTask.AutoFishing; namespace BetterGenshinImpact.Core.Script.Dependence; @@ -144,4 +145,13 @@ public class Genshin { await new ReturnMainUiTask().Start(CancellationContext.Instance.Cts.Token); } + + /// + /// 钓鱼 + /// + /// + public async Task AutoFishing() + { + await new AutoFishingTask().Start(CancellationContext.Instance.Cts.Token); + } } \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs new file mode 100644 index 00000000..24b92309 --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs @@ -0,0 +1,287 @@ +using BetterGenshinImpact.GameTask.Common; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using BetterGenshinImpact.GameTask.Common.BgiVision; +using BehaviourTree.Composites; +using BehaviourTree.FluentBuilder; +using BehaviourTree; +using BetterGenshinImpact.Core.Simulator; +using BetterGenshinImpact.View.Drawable; +using BetterGenshinImpact.GameTask.AutoFishing.Assets; +using Vanara.PInvoke; +using Compunet.YoloV8; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using BetterGenshinImpact.GameTask.AutoFishing.Model; +using BetterGenshinImpact.GameTask.Common.Job; + +namespace BetterGenshinImpact.GameTask.AutoFishing +{ + public class AutoFishingTask : ISoloTask + { + private readonly ILogger _logger = App.GetLogger(); + public string Name => "钓鱼独立任务"; + + private CancellationToken ct; + + private Blackboard blackboard; + + public class Blackboard : AutoFishing.Blackboard + { + public bool noFish = false; + + internal override void Reset() + { + base.Reset(); + noFish = false; + } + } + + public Task Start(CancellationToken ct) + { + this.ct = ct; + + this.blackboard = new Blackboard() + { + Sleep = this.Sleep + }; + + var BehaviourTree = FluentBuilder.Create() + .Sequence("调整视角并钓鱼") + .Do($"设置变量{nameof(blackboard.pitchReset)}", _ => + { + blackboard.pitchReset = true; + return BehaviourStatus.Succeeded; + }) + .PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard)) + .MySimpleParallel("找鱼20秒", policy: SimpleParallelPolicy.OnlyOneMustSucceed) + .Do("转圈圈调整视角", TurnAround) + .PushLeaf(() => new FindFishTimeout("等20秒", 20, blackboard)) + .End() + .PushLeaf(() => new EnterFishingMode("进入钓鱼模式", blackboard)) + .UntilFailed(@"\") + .Sequence("一直钓鱼直到没鱼") + .AlwaysSucceed(@"\") + .Sequence("从找鱼开始") + .PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard)) + .MySimpleParallel("找鱼10秒", policy: SimpleParallelPolicy.OnlyOneMustSucceed) + .PushLeaf(() => new GetFishpond("检测鱼群", blackboard)) + .PushLeaf(() => new FindFishTimeout("等10秒", 10, blackboard)) + .End() + .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() + .Do("冒泡-找鱼-没鱼检查", _ => blackboard.noFish ? BehaviourStatus.Failed : BehaviourStatus.Succeeded) + .End() + .End() + .End() + .Build(); + + _logger.LogInformation("→ {Text}", "自动钓鱼,启动!"); + + SetTimeTask setTimeTask = new SetTimeTask(); + foreach (int hour in new int[] { 7, 19 }) + { + setTimeTask.Start(hour, 0, ct).Wait(); + + this.blackboard.Reset(); + + while (!ct.IsCancellationRequested) + { + if (!SystemControl.IsGenshinImpactActiveByProcess()) + { + _logger.LogInformation("当前获取焦点的窗口不是原神,停止执行"); + break; + } + + var ra = TaskControl.CaptureToRectArea(forceNew: true); + BehaviourTree.Tick(new CaptureContent(ra.SrcBitmap, 0, 0)); + + if (BehaviourTree.Status != BehaviourStatus.Running) + { + _logger.LogInformation("钓鱼结束"); + + if (!ra.Find(AutoFishingAssets.Instance.ExitFishingButtonRo).IsEmpty()) + { + _logger.LogInformation("← {Text}", "退出钓鱼界面"); + Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_ESCAPE); + Sleep(1000); + ra = TaskControl.CaptureToRectArea(forceNew: true); + Bv.ClickBlackConfirmButton(ra); + Sleep(1000); + } + break; + } + } + } + + _logger.LogInformation("→ 钓鱼任务结束"); + + return Task.CompletedTask; // todo 这个行为树库不支持异步编程。。。 + } + + public void Sleep(int millisecondsTimeout) + { + TaskControl.Sleep(millisecondsTimeout, ct); + } + + public class FindFishTimeout : BaseBehaviour + { + private readonly ILogger _logger = App.GetLogger(); + private readonly Blackboard blackboard; + private DateTime? timeout; + private int seconds; + + /// + /// 如果未超时返回运行中,超时返回失败 + /// + /// + /// + public FindFishTimeout(string name, int seconds, Blackboard blackboard) : base(name) + { + this.blackboard = blackboard; + this.seconds = seconds; + } + protected override void OnInitialize() + { + timeout = DateTime.Now.AddSeconds(seconds); + } + protected override BehaviourStatus Update(CaptureContent content) + { + if (DateTime.Now >= timeout) + { + blackboard.noFish = true; + _logger.LogInformation($"{seconds}秒没有找到鱼,退出钓鱼界面"); + return BehaviourStatus.Failed; + } + else + { + return BehaviourStatus.Running; + } + } + } + + private BehaviourStatus TurnAround(CaptureContent content) + { + 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 Blackboard blackboard; + public EnterFishingMode(string name, Blackboard blackboard) : base(name) + { + this.blackboard = blackboard; + } + + protected override BehaviourStatus Update(CaptureContent content) + { + if (Status == BehaviourStatus.Ready) + { + return BehaviourStatus.Running; + } + + if (Bv.FindFAndPress(content.CaptureRectArea, "钓鱼")) + { + _logger.LogInformation("按下钓鱼键"); + blackboard.Sleep(2000); + return BehaviourStatus.Running; + } + else if (Bv.ClickWhiteConfirmButton(content.CaptureRectArea)) + { + _logger.LogInformation("点击开始钓鱼"); + + this.blackboard.pitchReset = true; + + blackboard.Sleep(2000); // 这里要多等一会儿界面遮罩消退 + + return BehaviourStatus.Running; + } + + if (content.CaptureRectArea.Find(AutoFishingAssets.Instance.ExitFishingButtonRo).IsEmpty()) + { + _logger.LogInformation("进入钓鱼模式失败"); + return BehaviourStatus.Failed; + } + else + { + _logger.LogInformation("进入钓鱼模式"); + return BehaviourStatus.Succeeded; + } + } + } + } +} diff --git a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs index 9a5043ae..806155c8 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs @@ -1,42 +1,26 @@ using BehaviourTree; using BehaviourTree.FluentBuilder; using BehaviourTree.Composites; -using BetterGenshinImpact.Core.Config; -using BetterGenshinImpact.Core.Recognition; -using BetterGenshinImpact.Core.Recognition.OCR; -using BetterGenshinImpact.Core.Recognition.ONNX; using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.GameTask.AutoFishing.Assets; -using BetterGenshinImpact.GameTask.AutoFishing.Model; using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; using BetterGenshinImpact.GameTask.Common; -using BetterGenshinImpact.GameTask.Common.BgiVision; -using BetterGenshinImpact.GameTask.Model.Area; -using BetterGenshinImpact.Helpers; using BetterGenshinImpact.Helpers.Extensions; using BetterGenshinImpact.View.Drawable; -using Compunet.YoloV8; using Microsoft.Extensions.Logging; using OpenCvSharp; using System; using System.Collections.Generic; using System.Diagnostics; -using System.Drawing.Imaging; using System.IO; using System.Threading; -using static Vanara.PInvoke.User32; -using Color = System.Drawing.Color; -using Pen = System.Drawing.Pen; using Point = OpenCvSharp.Point; -using System.Linq; namespace BetterGenshinImpact.GameTask.AutoFishing { public class AutoFishingTrigger : ITaskTrigger { private readonly ILogger _logger = App.GetLogger(); - private readonly IOcrService _ocrService = OcrFactory.Paddle; - private readonly YoloV8Predictor _predictor = YoloV8Builder.CreateDefaultBuilder().UseOnnxModel(Global.Absolute(@"Assets\Model\Fish\bgi_fish.onnx")).WithSessionOptions(BgiSessionOption.Instance.Options).Build(); public string Name => "自动钓鱼"; public bool IsEnabled { get; set; } @@ -49,13 +33,20 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// public bool IsExclusive { get; set; } - private readonly AutoFishingAssets _autoFishingAssets; + private Blackboard blackboard; + internal IBehaviour BehaviourTree { get; set; } - private IBehaviour BehaviourTree { get; set; } + /// + /// 辣条(误) + /// + private IBehaviour BehaviourTreeLaTiao { get; set; } public AutoFishingTrigger() { - _autoFishingAssets = AutoFishingAssets.Instance; + this.blackboard = new Blackboard() + { + Sleep = this.Sleep + }; } public void Init() @@ -63,70 +54,72 @@ namespace BetterGenshinImpact.GameTask.AutoFishing IsEnabled = TaskContext.Instance().Config.AutoFishingConfig.Enabled; IsExclusive = false; - // 钓鱼变量初始化 - _findFishBoxTips = false; - _switchBaitContinuouslyFrameNum = 0; - _waitBiteContinuouslyFrameNum = 0; - _noFishActionContinuouslyFrameNum = 0; - _isThrowRod = false; - _selectedBaitName = string.Empty; - BehaviourTree = FluentBuilder.Create() .MySimpleParallel("root", policy: SimpleParallelPolicy.OnlyOneMustSucceed) - .Do("检查开关", ctx => TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled ? BehaviourStatus.Running : BehaviourStatus.Failed) - .UntilSuccess("钓鱼") - .Sequence("从选鱼饵开始") - .Do("抛竿前准备", ThrowRod) - .Do("打开换饵界面", OpenChooseBaitUI) - .PushLeaf(() => new ChooseBait("选择鱼饵", this)) - .UntilSuccess("重复预抛竿") - .Sequence("从预抛竿开始") - .Do("长按预抛竿", ApproachFishAndThrowRod0) - .Do("抛竿", ApproachFishAndThrowRod1) + .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("跳转-抛竿缺鱼检查", NoTargetFishCheck) + .Do("冒泡-抛竿-缺鱼检查", _ => blackboard.noTargetFish ? BehaviourStatus.Failed : BehaviourStatus.Succeeded) + .PushLeaf(() => new CheckThrowRod("检查抛竿结果")) .MySimpleParallel("下杆中", SimpleParallelPolicy.OnlyOneMustSucceed) + .PushLeaf(() => new FishBite("自动提竿")) .PushLeaf(() => new FishBiteTimeout("下杆超时检查", 30)) - .Do("自动提竿", FishBite) .End() - .Do("等待拉条出现", Wait4FishBoxAreaAppear) - .Do("钓鱼拉条", Fishing) + .PushLeaf(() => new GetFishBoxArea("等待拉条出现", blackboard)) + .PushLeaf(() => new Fishing("钓鱼拉条", blackboard)) + .End() + .End() + .End() + .Build(); + + BehaviourTreeLaTiao = FluentBuilder.Create() + .MySimpleParallel("root", policy: SimpleParallelPolicy.OnlyOneMustSucceed) + .Do("检查是否在钓鱼界面", CheckFishingUserInterface) + .UntilSuccess("拉条循环") + .Sequence("拉条") + .PushLeaf(() => new GetFishBoxArea("等待拉条出现", blackboard)) + .PushLeaf(() => new Fishing("钓鱼拉条", blackboard)) .End() .End() .End() .Build(); } - private Rect _fishBoxRect = Rect.Empty; - private DateTime _prevExecute = DateTime.MinValue; - private CaptureContent _currContent; - public void OnCapture(CaptureContent content) { - this._currContent = content; + if ((DateTime.Now - _prevExecute).TotalMilliseconds <= 67) + { + return; + } + + _prevExecute = DateTime.Now; + // 进入独占的判定 if (!IsExclusive) { - if ((DateTime.Now - _prevExecute).TotalMilliseconds <= 200) - { - return; - } - - _prevExecute = DateTime.Now; - // 进入独占模式判断 CheckFishingUserInterface(content); } else { - BehaviourTree.Tick(content); - if (BehaviourTree.Status == BehaviourStatus.Failed) + if (TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled) { - _logger.LogInformation("BehaviourStatus.Failed 退出独占模式"); - CheckFishingUserInterface(content); + BehaviourTree.Tick(content); + } + else + { + BehaviourTreeLaTiao.Tick(content); } } } @@ -214,537 +207,17 @@ namespace BetterGenshinImpact.GameTask.AutoFishing // return false; //} - /// - /// 找右下角的退出钓鱼按钮 - /// 用于判断是否进入钓鱼界面 - /// 进入钓鱼界面时该触发器进入独占模式 - /// - /// - /// - private bool FindButtonForExclusive(CaptureContent content) - { - return !content.CaptureRectArea.Find(_autoFishingAssets.ExitFishingButtonRo).IsEmpty(); - } - - private int _throwRodWaitFrameNum = 0; // 抛竿等待的时间(帧数) - private int _switchBaitContinuouslyFrameNum = 0; // 切换鱼饵按钮图标的持续时间(帧数) - private int _waitBiteContinuouslyFrameNum = 0; // 等待上钩的持续时间(帧数) - private int _noFishActionContinuouslyFrameNum = 0; // 无钓鱼三种场景的持续时间(帧数) - private bool _isThrowRod = false; // 是否已经抛竿 - /// /// 钓鱼有3种场景 /// 1. 未抛竿 BaitButtonRo存在 && WaitBiteButtonRo不存在 /// 2. 抛竿后未拉条 WaitBiteButtonRo存在 && BaitButtonRo不存在 - /// 3. 上钩拉条 _isFishingProcess && _biteTipsExitCount > 0 + /// 3. 上钩拉条 /// /// 新AI钓鱼 /// 前提:必须要正面面对鱼塘,没有识别到鱼的时候不会自动抛竿 /// 1. 观察周围环境,判断鱼塘位置,视角对上鱼塘位置中心 /// 2. 根据第一步的观察结果,提前选择鱼饵 - /// 3. /// - /// - private BehaviourStatus ThrowRod(CaptureContent content) - { - // 没有拉条和提竿的时候,自动抛竿 - //if (!_isFishingProcess && _biteTipsExitCount == 0 && TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled) - //{ - var baitRectArea = content.CaptureRectArea.Find(_autoFishingAssets.BaitButtonRo); - var waitBiteArea = content.CaptureRectArea.Find(_autoFishingAssets.WaitBiteButtonRo); - if (!baitRectArea.IsEmpty() && waitBiteArea.IsEmpty()) - { - _switchBaitContinuouslyFrameNum++; - _waitBiteContinuouslyFrameNum = 0; - _noFishActionContinuouslyFrameNum = 0; - - if (_switchBaitContinuouslyFrameNum >= content.FrameRate) - { - _isThrowRod = false; - _switchBaitContinuouslyFrameNum = 0; - _logger.LogInformation("当前处于未抛竿状态"); - } - - if (!_isThrowRod) - { - // 1. 观察周围环境,判断鱼塘位置,视角对上鱼塘位置中心 - using var memoryStream = new MemoryStream(); - content.CaptureRectArea.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); - memoryStream.Seek(0, SeekOrigin.Begin); - var result = _predictor.Detect(memoryStream); - Debug.WriteLine($"YOLOv8识别: {result.Speed}"); - var fishpond = new Fishpond(result); - if (fishpond.FishpondRect == Rect.Empty) - { - Sleep(500); - return BehaviourStatus.Running; - } - else - { - var centerX = content.CaptureRectArea.SrcBitmap.Width / 2; - var centerY = content.CaptureRectArea.SrcBitmap.Height / 2; - // 往左移动是正数,往右移动是负数 - if (fishpond.FishpondRect.Left > centerX) - { - Simulation.SendInput.Mouse.MoveMouseBy(100, 0); - } - - if (fishpond.FishpondRect.Right < centerX) - { - Simulation.SendInput.Mouse.MoveMouseBy(-100, 0); - } - - // 鱼塘尽量在上半屏幕 - if (fishpond.FishpondRect.Bottom > centerY) - { - Simulation.SendInput.Mouse.MoveMouseBy(0, -100); - } - - if ((fishpond.FishpondRect.Left < centerX && fishpond.FishpondRect.Right > centerX && fishpond.FishpondRect.Bottom >= centerY) || fishpond.FishpondRect.Width < content.CaptureRectArea.SrcBitmap.Width / 4) - { - // 鱼塘在中心,选择鱼饵 - //if (string.IsNullOrEmpty(_selectedBaitName)) - //{ - this.fishpond = fishpond; - _logger.LogInformation("定位到鱼塘" + string.Join('、', fishpond.Fishes.GroupBy(f => f.FishType).Select(g => $"{g.Key.ChineseName}{g.Count()}条"))); - - return BehaviourStatus.Succeeded; - //} - } - } - } - } - //} - //else - //{ - // _switchBaitContinuouslyFrameNum = 0; - // _waitBiteContinuouslyFrameNum = 0; - // _noFishActionContinuouslyFrameNum = 0; - // _throwRodWaitFrameNum = 0; - // _isThrowRod = false; - //} - - return BehaviourStatus.Running; - } - - private class FishBiteTimeout : BaseBehaviour - { - private readonly ILogger _logger = App.GetLogger(); - private DateTime? waitFishBiteTimeout; - private int _seconds; - - /// - /// 如果未超时返回运行中,超时返回失败 - /// - /// - /// - public FishBiteTimeout(string name, int seconds) : base(name) - { - _seconds = seconds; - } - protected override void OnInitialize() - { - waitFishBiteTimeout = DateTime.Now.AddSeconds(_seconds); - } - protected override BehaviourStatus Update(CaptureContent context) - { - if (DateTime.Now >= waitFishBiteTimeout) - { - _logger.LogInformation($"{_seconds}秒没有咬杆,本次收杆"); - Simulation.SendInput.Mouse.LeftButtonClick(); - Thread.Sleep(1000); - return BehaviourStatus.Failed; - } - else - { - return BehaviourStatus.Running; - } - } - } - - private BehaviourStatus ThrowRod2(CaptureContent content) - { - //var baitRectArea = content.CaptureRectArea.Find(_autoFishingAssets.BaitButtonRo); - //var waitBiteArea = content.CaptureRectArea.Find(_autoFishingAssets.WaitBiteButtonRo); - //if (baitRectArea.IsEmpty() && !waitBiteArea.IsEmpty() && _isThrowRod) - //{ - // _switchBaitContinuouslyFrameNum = 0; - // _waitBiteContinuouslyFrameNum++; - // _noFishActionContinuouslyFrameNum = 0; - // _throwRodWaitFrameNum++; - - // if (_waitBiteContinuouslyFrameNum >= content.FrameRate) - // { - // _isThrowRod = true; - // _waitBiteContinuouslyFrameNum = 0; - // } - - // if (_isThrowRod) - // { - // // 30s 没有上钩,重新抛竿 - // if (_throwRodWaitFrameNum >= content.FrameRate * TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodTimeOut) - // { - // Simulation.SendInput.Mouse.LeftButtonClick(); - // _throwRodWaitFrameNum = 0; - // _waitBiteContinuouslyFrameNum = 0; - // Debug.WriteLine("超时自动收竿"); - // Sleep(2000); - // _isThrowRod = false; - // return BehaviourStatus.Failed; - // } - // return BehaviourStatus.Succeeded; - // } - //} - - //if (baitRectArea.IsEmpty() && waitBiteArea.IsEmpty()) - //{ - // _switchBaitContinuouslyFrameNum = 0; - // _waitBiteContinuouslyFrameNum = 0; - // _noFishActionContinuouslyFrameNum++; - // if (_noFishActionContinuouslyFrameNum > content.FrameRate) - // { - // CheckFishingUserInterface(content); - // } - // return BehaviourStatus.Failed; - //} - return BehaviourStatus.Succeeded; - } - - /// - /// 打开换饵界面 - /// - /// - /// - private BehaviourStatus OpenChooseBaitUI(CaptureContent content) - { - _logger.LogInformation("打开换饵界面"); - Simulation.SendInput.Mouse.RightButtonClick(); - Sleep(100); - Simulation.SendInput.Mouse.MoveMouseBy(0, 200); // 鼠标移走,防止干扰 - Sleep(100); - return BehaviourStatus.Succeeded; - } - - private string _selectedBaitName = string.Empty; - private Fishpond fishpond; - private DateTime? ChooseBaitUIOpenWaitEndTime; // 等待选鱼饵界面出现的结束时间 - - /// - /// 选择鱼饵 - /// - private class ChooseBait : BaseBehaviour - { - private readonly ILogger _logger = App.GetLogger(); - private readonly AutoFishingTrigger _autoFishingTrigger; - - /// - /// 选择鱼饵 - /// - /// - /// - public ChooseBait(string name, AutoFishingTrigger autoFishingTrigger) : base(name) - { - _autoFishingTrigger = autoFishingTrigger; - } - - protected override void OnInitialize() - { - _autoFishingTrigger.ChooseBaitUIOpenWaitEndTime = DateTime.Now.AddSeconds(5); - } - - protected override BehaviourStatus Update(CaptureContent content) - { - if (this.Status == BehaviourStatus.Ready) - { - return BehaviourStatus.Running; - } - - _autoFishingTrigger._selectedBaitName = _autoFishingTrigger.fishpond.Fishes[0].FishType.BaitName; // 选择最多鱼吃的饵料 - _logger.LogInformation("选择鱼饵 {Text}", BaitType.FromName(_autoFishingTrigger._selectedBaitName).ChineseName); - - // 寻找鱼饵 - var ro = new RecognitionObject - { - Name = "ChooseBait", - RecognitionType = RecognitionTypes.TemplateMatch, - TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFishing", $"bait\\{_autoFishingTrigger._selectedBaitName}.png"), - Threshold = 0.8, - Use3Channels = true, - DrawOnWindow = false - }.InitTemplate(); - - var captureRegion = content.CaptureRectArea; - using var resRa = captureRegion.Find(ro); - if (resRa.IsEmpty()) - { - if (DateTime.Now >= _autoFishingTrigger.ChooseBaitUIOpenWaitEndTime) - { - _logger.LogWarning("没有找到目标鱼饵"); - _autoFishingTrigger._selectedBaitName = string.Empty; - throw new Exception("没有找到目标鱼饵"); - } - else - { - return BehaviourStatus.Running; - } - } - else - { - resRa.Click(); - _autoFishingTrigger.Sleep(700); - // 可能重复点击,所以固定界面点击下 - captureRegion.ClickTo((int)(captureRegion.Width * 0.675), (int)(captureRegion.Height / 3d)); - _autoFishingTrigger.Sleep(200); - // 点击确定 - Bv.ClickWhiteConfirmButton(captureRegion); - _autoFishingTrigger.Sleep(500); // 等待界面切换 - } - - return BehaviourStatus.Succeeded; - } - } - - private readonly Random _rd = new(); - - /// - /// 长按预抛竿 - /// - /// - private BehaviourStatus ApproachFishAndThrowRod0(CaptureContent content) - { - noPlacementTimes = 0; - noTargetFishTimes = 0; - - Simulation.SendInput.Mouse.LeftButtonDown(); - _logger.LogInformation("长按预抛竿"); - Sleep(3000); - - return BehaviourStatus.Succeeded; - } - - private bool noTargetFish; - - private int noPlacementTimes; // 没有落点的次数 - private int noTargetFishTimes; // 没有目标鱼的次数 - /// - /// 抛竿 - /// - /// - private BehaviourStatus ApproachFishAndThrowRod1(CaptureContent content) - { - noTargetFish = false; - var prevTargetFishRect = Rect.Empty; // 记录上一个目标鱼的位置 - - var ra = content.CaptureRectArea; - - // 找 鱼饵落点 - using var memoryStream = new MemoryStream(); - ra.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); - memoryStream.Seek(0, SeekOrigin.Begin); - var result = _predictor.Detect(memoryStream); - Debug.WriteLine($"YOLOv8识别: {result.Speed}"); - var fishpond = new Fishpond(result); - if (fishpond.TargetRect == Rect.Empty) - { - noPlacementTimes++; - Sleep(50); - Debug.WriteLine("历次未找到鱼饵落点"); - - var cX = ra.SrcBitmap.Width / 2; - var cY = ra.SrcBitmap.Height / 2; - var rdX = _rd.Next(0, ra.SrcBitmap.Width); - var rdY = _rd.Next(0, ra.SrcBitmap.Height); - - var moveX = 100 * (cX - rdX) / ra.SrcBitmap.Width; - var moveY = 100 * (cY - rdY) / ra.SrcBitmap.Height; - - Simulation.SendInput.Mouse.MoveMouseBy(moveX, moveY); - - if (noPlacementTimes > 25) - { - _logger.LogInformation("未找到鱼饵落点,重试"); - // Simulation.SendInputEx.Mouse.LeftButtonUp(); - // // Sleep(2000); - // // Simulation.SendInputEx.Mouse.LeftButtonClick(); - // _selectedBaitName = string.Empty; - // _isThrowRod = false; - // // Sleep(2000); - // // MoveViewpointDown(); - // Sleep(300); - return BehaviourStatus.Failed; - } - - return BehaviourStatus.Running; - } - - // 找到落点最近的鱼 - OneFish? currentFish = null; - if (prevTargetFishRect == Rect.Empty) - { - var list = fishpond.FilterByBaitName(_selectedBaitName); - if (list.Count > 0) - { - currentFish = list[0]; - prevTargetFishRect = currentFish.Rect; - } - } - else - { - currentFish = fishpond.FilterByBaitNameAndRecently(_selectedBaitName, prevTargetFishRect); - if (currentFish != null) - { - prevTargetFishRect = currentFish.Rect; - } - } - - if (currentFish == null) - { - Debug.WriteLine("无目标鱼"); - noTargetFishTimes++; - //if (noTargetFishTimes == 30) - //{ - // Simulation.SendInputEx.Mouse.MoveMouseBy(0, 100); - //} - - if (noTargetFishTimes > 10) - { - // 没有找到目标鱼,重新选择鱼饵 - _logger.LogInformation("没有找到目标鱼,1.直接抛竿"); - Simulation.SendInput.Mouse.LeftButtonUp(); - Sleep(1500); - _logger.LogInformation("没有找到目标鱼,2.收杆"); - Simulation.SendInput.Mouse.LeftButtonClick(); - Sleep(800); - _logger.LogInformation("没有找到目标鱼,3.准备重新选择鱼饵"); - _selectedBaitName = string.Empty; - _isThrowRod = false; - MoveViewpointDown(); - Sleep(300); - - noTargetFish = true; - return BehaviourStatus.Succeeded; - } - - return BehaviourStatus.Running; - } - else - { - noTargetFishTimes = 0; - _currContent.CaptureRectArea.DrawRect(fishpond.TargetRect, "Target"); - _currContent.CaptureRectArea.Derive(currentFish.Rect).DrawSelf("Fish"); - - // VisionContext.Instance().DrawContent.PutRect("Target", fishpond.TargetRect.ToRectDrawable()); - // VisionContext.Instance().DrawContent.PutRect("Fish", currentFish.Rect.ToRectDrawable()); - - // var min = MoveMouseToFish(fishpond.TargetRect, currentFish.Rect); - // // 因为视角是斜着看向鱼的,所以Y轴抛竿距离要近一点 - // if ((_selectedBaitName != "fruit paste bait" && min is { Item1: <= 50, Item2: <= 25 }) - // || _selectedBaitName == "fruit paste bait" && min is { Item1: <= 40, Item2: <= 25 }) - // { - // Sleep(100); - // Simulation.SendInputEx.Mouse.LeftButtonUp(); - // _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); - // _isThrowRod = true; - // VisionContext.Instance().DrawContent.RemoveRect("Target"); - // VisionContext.Instance().DrawContent.RemoveRect("Fish"); - // break; - // } - - // 来自 HutaoFisher 的抛竿技术 - var rod = fishpond.TargetRect; - var fish = currentFish.Rect; - 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 - { - rod_x1 = NormalizeXTo1024(rod.Left), - rod_x2 = NormalizeXTo1024(rod.Right), - rod_y1 = NormalizeYTo576(rod.Top), - rod_y2 = NormalizeYTo576(rod.Bottom), - fish_x1 = NormalizeXTo1024(fish.Left), - fish_x2 = NormalizeXTo1024(fish.Right), - fish_y1 = NormalizeYTo576(fish.Top), - fish_y2 = NormalizeYTo576(fish.Bottom), - fish_label = BigFishType.GetIndex(currentFish.FishType) - }); - 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 moveX = 100 * (cX - rdX) / content.CaptureRectArea.SrcBitmap.Width; - var moveY = 100 * (cY - rdY) / content.CaptureRectArea.SrcBitmap.Height; - - _logger.LogInformation("失败 随机移动 {DX}, {DY}", moveX, moveY); - Simulation.SendInput.Mouse.MoveMouseBy(moveX, moveY); - } - else if (state == 0) - { - // 成功 抛竿 - Simulation.SendInput.Mouse.LeftButtonUp(); - _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); - _isThrowRod = true; - VisionContext.Instance().DrawContent.RemoveRect("Target"); - VisionContext.Instance().DrawContent.RemoveRect("Fish"); - return BehaviourStatus.Succeeded; - } - else if (state == 1) - { - // 太近 - var dl = Math.Sqrt(dx * dx + dy * dy); - // set a minimum step - 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)); - } - else if (state == 2) - { - // 太远 - // _logger.LogInformation("太远 移动 {DX}, {DY}", dx, dy); - Simulation.SendInput.Mouse.MoveMouseBy((int)(dx / 1.5), (int)(dy * 1.5)); - } - } - Sleep(50); - return BehaviourStatus.Running; - } - - /// - /// 缺鱼检查 - /// - /// - private BehaviourStatus NoTargetFishCheck(CaptureContent content) - { - return noTargetFish ? BehaviourStatus.Failed : BehaviourStatus.Succeeded; - } - - private double NormalizeXTo1024(int x) - { - return x * 1.0 / TaskContext.Instance().SystemInfo.ScaleMax1080PCaptureRect.Width * 1024; - } - - private double NormalizeYTo576(int y) - { - return y * 1.0 / TaskContext.Instance().SystemInfo.ScaleMax1080PCaptureRect.Height * 576; - } - - /// - /// 向下移动视角 - /// - private void MoveViewpointDown() - { - if (TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled) - { - // 下移视角方便看鱼 - Simulation.SendInput.Mouse.MoveMouseBy(0, 400); - Sleep(500); - Simulation.SendInput.Mouse.MoveMouseBy(0, 500); - Sleep(500); - } - } [Obsolete] private (int, int) MoveMouseToFish(Rect rect1, Rect rect2) @@ -847,402 +320,43 @@ namespace BetterGenshinImpact.GameTask.AutoFishing public void Sleep(int millisecondsTimeout) { - NewRetry.Do(() => - { - TaskControl.TrySuspend(); - if (IsEnabled && !SystemControl.IsGenshinImpactActiveByProcess()) - { - _logger.LogWarning("当前获取焦点的窗口不是原神,暂停"); - throw new RetryException("当前获取焦点的窗口不是原神"); - } - - }, TimeSpan.FromSeconds(1), 100); - CheckFishingUserInterface(_currContent); - Thread.Sleep(millisecondsTimeout); + TaskControl.Sleep(millisecondsTimeout); } /// - /// 获取钓鱼框的位置 - /// - private Rect GetFishBoxArea(ImageRegion captureRegion) - { - using var topMat = new Mat(captureRegion.SrcMat, new Rect(0, 0, captureRegion.Width, captureRegion.Height / 2)); - ; - var rects = AutoFishingImageRecognition.GetFishBarRect(topMat); - if (rects != null && rects.Count == 2) - { - if (Math.Abs(rects[0].Height - rects[1].Height) > 10) - { - TaskControl.Logger.LogError("两个矩形高度差距过大,未识别到钓鱼框"); - VisionContext.Instance().DrawContent.RemoveRect("FishBox"); - return Rect.Empty; - } - - if (rects[0].Width < rects[1].Width) - { - _cur = rects[0]; - _left = rects[1]; - } - else - { - _cur = rects[1]; - _left = rects[0]; - } - - if (_left.X < _cur.X // cur 是游标位置, 在初始状态下,cur 一定在left左边 - || _cur.Width > _left.Width // left一定比cur宽 - || _cur.X + _cur.Width > topMat.Width / 2 // cur 一定在屏幕左侧 - || _cur.X + _cur.Width > _left.X - _left.Width / 2 // cur 一定在left左侧+left的一半宽度 - || _cur.X + _cur.Width > topMat.Width / 2 - _left.Width // cur 一定在屏幕中轴线减去整个left的宽度的位置左侧 - || !(_left.X < topMat.Width / 2 && _left.X + _left.Width > topMat.Width / 2) // left肯定穿过游戏中轴线 - ) - { - VisionContext.Instance().DrawContent.RemoveRect("FishBox"); - return Rect.Empty; - } - - int hExtra = _cur.Height, vExtra = _cur.Height / 4; - _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 = captureRegion.Derive(_fishBoxRect); - boxRa.DrawSelf("FishBox", new Pen(Color.LightPink, 2)); - return _fishBoxRect; - } - - VisionContext.Instance().DrawContent.RemoveRect("FishBox"); - return Rect.Empty; - } - - private bool _isFishingProcess = false; // 提杆后会设置为true - private int _biteTipsExitCount = 0; // 钓鱼提示持续时间 - private int _notFishingAfterBiteCount = 0; // 提竿后没有钓鱼的时间 - private Rect _baseBiteTips = Rect.Empty; - - /// - /// 自动提竿 - /// - /// - private BehaviourStatus FishBite(CaptureContent content) - { - _logger.LogInformation("等待提竿"); - // 自动识别的钓鱼框向下延伸到屏幕中间 - //var liftingWordsAreaRect = new Rect(fishBoxRect.X, fishBoxRect.Y + fishBoxRect.Height * 2, - // fishBoxRect.Width, content.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); - //VisionContext.Instance().DrawContent.PutRect("liftingWordsAreaRect", liftingWordsAreaRect.ToRectDrawable(new Pen(Color.Cyan, 2))); - var wordCaptureMat = new Mat(content.CaptureRectArea.SrcMat, liftingWordsAreaRect); - var currentBiteWordsTips = AutoFishingImageRecognition.MatchFishBiteWords(wordCaptureMat, liftingWordsAreaRect); - if (currentBiteWordsTips != Rect.Empty) - { - if (_baseBiteTips == Rect.Empty) - { - _baseBiteTips = currentBiteWordsTips; - } - else - { - if (Math.Abs(_baseBiteTips.X - currentBiteWordsTips.X) < 10 - && Math.Abs(_baseBiteTips.Y - currentBiteWordsTips.Y) < 10 - && Math.Abs(_baseBiteTips.Width - currentBiteWordsTips.Width) < 10 - && Math.Abs(_baseBiteTips.Height - currentBiteWordsTips.Height) < 10) - { - _biteTipsExitCount++; - // VisionContext.Instance().DrawContent.PutRect("FishBiteTips", - // currentBiteWordsTips - // .ToWindowsRectangleOffset(liftingWordsAreaRect.X, liftingWordsAreaRect.Y) - // .ToRectDrawable()); - using var tipsRa = content.CaptureRectArea.Derive(currentBiteWordsTips + liftingWordsAreaRect.Location); - tipsRa.DrawSelf("FishBiteTips"); - - if (_biteTipsExitCount >= content.FrameRate / 4d) - { - // 图像提竿判断 - using var liftRodButtonRa = content.CaptureRectArea.Find(_autoFishingAssets.LiftRodButtonRo); - if (!liftRodButtonRa.IsEmpty()) - { - Simulation.SendInput.Mouse.LeftButtonClick(); - _logger.LogInformation(@"┌------------------------┐"); - _logger.LogInformation(" 自动提竿(图像识别)"); - _isFishingProcess = true; - _biteTipsExitCount = 0; - _baseBiteTips = Rect.Empty; - VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); - return BehaviourStatus.Succeeded; - } - - // OCR 提竿判断 - var text = _ocrService.Ocr(new Mat(content.CaptureRectArea.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)"); - _isFishingProcess = true; - _biteTipsExitCount = 0; - _baseBiteTips = Rect.Empty; - VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); - return BehaviourStatus.Succeeded; - } - } - } - else - { - _biteTipsExitCount = 0; - _baseBiteTips = currentBiteWordsTips; - VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); - } - - if (_biteTipsExitCount >= content.FrameRate / 2d) - { - Simulation.SendInput.Mouse.LeftButtonClick(); - _logger.LogInformation(@"┌------------------------┐"); - _logger.LogInformation(" 自动提竿(文字块)"); - _isFishingProcess = true; - _biteTipsExitCount = 0; - _baseBiteTips = Rect.Empty; - VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); - return BehaviourStatus.Succeeded; - } - } - } - - return BehaviourStatus.Running; - } - - /// - /// 进入钓鱼界面先尝试获取钓鱼框的位置 - /// - /// - /// - private BehaviourStatus Wait4FishBoxAreaAppear(CaptureContent content) - { - _logger.LogInformation("寻找拉条中"); - if ((DateTime.Now - _prevExecute).TotalMilliseconds <= 200) - { - return BehaviourStatus.Running; - } - - _prevExecute = DateTime.Now; - - _fishBoxRect = GetFishBoxArea(content.CaptureRectArea); - CheckFishingUserInterface(content); - - return _fishBoxRect == Rect.Empty ? BehaviourStatus.Running : BehaviourStatus.Succeeded; - } - - private int _noRectsCount = 0; - private Rect _cur, _left, _right; - private MOUSEEVENTF _prevMouseEvent = 0x0; - private bool _findFishBoxTips; - - /// - /// 钓鱼拉条 - /// - /// - /// - private BehaviourStatus Fishing(CaptureContent content) - { - var fishBarMat = new Mat(content.CaptureRectArea.SrcMat, _fishBoxRect); - var simulator = Simulation.SendInput; - var rects = AutoFishingImageRecognition.GetFishBarRect(fishBarMat); - if (rects != null && rects.Count > 0) - { - if (rects.Count >= 2 && _prevMouseEvent == 0x0 && !_findFishBoxTips) - { - _findFishBoxTips = true; - _logger.LogInformation(" 识别到钓鱼框,自动拉扯中..."); - } - - // 超过3个矩形是异常情况,取高度最高的三个矩形进行识别 - if (rects.Count > 3) - { - rects.Sort((a, b) => b.Height.CompareTo(a.Height)); - rects.RemoveRange(3, rects.Count - 3); - } - - //Debug.WriteLine($"识别到{rects.Count} 个矩形"); - if (rects.Count == 2) - { - if (rects[0].Width < rects[1].Width) - { - _cur = rects[0]; - _left = rects[1]; - } - else - { - _cur = rects[1]; - _left = rects[0]; - } - - PutRects(_left, _cur, new Rect()); - - if (_cur.X < _left.X) - { - if (_prevMouseEvent != MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN) - { - simulator.Mouse.LeftButtonDown(); - //Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown(); - _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN; - //Debug.WriteLine("进度不到 左键按下"); - } - } - else - { - if (_prevMouseEvent == MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN) - { - simulator.Mouse.LeftButtonUp(); - //Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonUp(); - _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP; - //Debug.WriteLine("进度超出 左键松开"); - } - } - } - else if (rects.Count == 3) - { - rects.Sort((a, b) => a.X.CompareTo(b.X)); - _left = rects[0]; - _cur = rects[1]; - _right = rects[2]; - PutRects(_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(); - _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP; - //Debug.WriteLine("进入框内中间 左键松开"); - } - } - else - { - if (_prevMouseEvent != MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN) - { - simulator.Mouse.LeftButtonDown(); - //Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown(); - _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN; - //Debug.WriteLine("未到框内中间 左键按下"); - } - } - } - else - { - PutRects(new Rect(), new Rect(), new Rect()); - } - } - else - { - PutRects(new Rect(), new Rect(), new Rect()); - _noRectsCount++; - // 2s 没有矩形视为已经完成钓鱼 - if (_noRectsCount >= content.FrameRate * 2 && _prevMouseEvent != 0x0) - { - _findFishBoxTips = false; - _isFishingProcess = false; - _isThrowRod = false; - _prevMouseEvent = 0x0; - _logger.LogInformation(" 钓鱼结束"); - _logger.LogInformation(@"└------------------------┘"); - - // 保证鼠标松开 - simulator.Mouse.LeftButtonUp(); - - Sleep(1000); - - MoveViewpointDown(); - Sleep(500); - - return BehaviourStatus.Succeeded; - } - - CheckFishingUserInterface(content); - } - - // 提竿后没有钓鱼的情况 - if (_isFishingProcess && !_findFishBoxTips) - { - _notFishingAfterBiteCount++; - if (_notFishingAfterBiteCount >= decimal.ToDouble(content.FrameRate) * 2) - { - _isFishingProcess = false; - _isThrowRod = false; - _notFishingAfterBiteCount = 0; - _logger.LogInformation(" X 提竿后没有钓鱼,重置!"); - _logger.LogInformation(@"└------------------------┘"); - } - } - else - { - _notFishingAfterBiteCount = 0; - } - - return BehaviourStatus.Running; - } - - /// - /// 检查是否退出钓鱼界面 + /// 检查是否在钓鱼界面 + /// 方法是找右下角的退出钓鱼按钮 + /// 进入钓鱼界面时该触发器进入独占模式 /// /// private BehaviourStatus CheckFishingUserInterface(CaptureContent content) { - var prevIsExclusive = IsExclusive; - IsExclusive = FindButtonForExclusive(content); - if (IsEnabled && !prevIsExclusive && IsExclusive) - { - _logger.LogInformation("→ {Text}", "自动钓鱼,启动!"); - var autoThrowRodEnabled = TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled; - _logger.LogInformation("当前自动选饵抛竿状态[{Enabled}]", autoThrowRodEnabled.ToChinese()); - // if (autoThrowRodEnabled) - // { - // _logger.LogInformation("枫丹、须弥地区暂不支持自动抛竿,如果在这两个地区钓鱼请关闭自动抛竿功能"); - // } - _switchBaitContinuouslyFrameNum = 0; - _waitBiteContinuouslyFrameNum = 0; - _noFishActionContinuouslyFrameNum = 0; - _isThrowRod = false; - _selectedBaitName = string.Empty; - - return BehaviourStatus.Succeeded; - } - else if (prevIsExclusive && !IsExclusive) - { - _logger.LogInformation("← {Text}", "退出钓鱼界面"); - _isThrowRod = false; - _fishBoxRect = Rect.Empty; - VisionContext.Instance().DrawContent.ClearAll(); - - return BehaviourStatus.Failed; - } - else + if (blackboard.chooseBaitUIOpening) { return BehaviourStatus.Running; } - } - private readonly Pen _pen = new(Color.Red, 1); - - private void PutRects(Rect left, Rect cur, Rect right) - { - //var list = new List - //{ - // left.ToWindowsRectangleOffset(_fishBoxRect.X, _fishBoxRect.Y).ToRectDrawable(_pen), - // cur.ToWindowsRectangleOffset(_fishBoxRect.X, _fishBoxRect.Y).ToRectDrawable(_pen), - // right.ToWindowsRectangleOffset(_fishBoxRect.X, _fishBoxRect.Y).ToRectDrawable(_pen) - //}; - using var fishBoxRa = _currContent.CaptureRectArea.Derive(_fishBoxRect); - var list = new List + var prevIsExclusive = IsExclusive; + IsExclusive = !content.CaptureRectArea.Find(AutoFishingAssets.Instance.ExitFishingButtonRo).IsEmpty(); + if (IsExclusive) { - fishBoxRa.ToRectDrawable(left, "left", _pen), - fishBoxRa.ToRectDrawable(cur, "cur", _pen), - fishBoxRa.ToRectDrawable(right, "right", _pen), - }; - VisionContext.Instance().DrawContent.PutOrRemoveRectList("FishingBarAll", list); + if (IsEnabled && !prevIsExclusive) + { + _logger.LogInformation("→ {Text}", "自动钓鱼,启动!"); + _logger.LogInformation("当前自动选饵抛竿状态[{Enabled}]", TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled.ToChinese()); + } + + return BehaviourStatus.Running; + } + else + { + if (prevIsExclusive) + { + _logger.LogInformation("← {Text}", "退出钓鱼界面"); + } + + return BehaviourStatus.Failed; + } } ///// @@ -1267,113 +381,4 @@ namespace BetterGenshinImpact.GameTask.AutoFishing //} } - - /// - /// MySimpleParallel - /// 和SimpleParallel的区别是,任一子行为返回失败则返回失败 - /// - /// - public class MySimpleParallel : CompositeBehaviour - { - private readonly IBehaviour _first; - - private readonly IBehaviour _second; - - private BehaviourStatus _firstStatus; - - private BehaviourStatus _secondStatus; - - private readonly Func _behave; - - public readonly SimpleParallelPolicy Policy; - - public MySimpleParallel(SimpleParallelPolicy policy, IBehaviour first, IBehaviour second) - : this("SimpleParallel", policy, first, second) - { - } - - public MySimpleParallel(string name, SimpleParallelPolicy policy, IBehaviour first, IBehaviour second) - : base(name, new IBehaviour[2] { first, second }) - { - Policy = policy; - _first = first; - _second = second; - _behave = ((policy == SimpleParallelPolicy.BothMustSucceed) ? new Func(BothMustSucceedBehaviour) : new Func(OnlyOneMustSucceedBehaviour)); - } - - private BehaviourStatus OnlyOneMustSucceedBehaviour(TContext context) - { - if (_firstStatus == BehaviourStatus.Succeeded || _secondStatus == BehaviourStatus.Succeeded) - { - return BehaviourStatus.Succeeded; - } - - if (_firstStatus == BehaviourStatus.Failed && _secondStatus == BehaviourStatus.Failed) - { - return BehaviourStatus.Failed; - } - - return BehaviourStatus.Running; - } - - private BehaviourStatus BothMustSucceedBehaviour(TContext context) - { - if (_firstStatus == BehaviourStatus.Succeeded && _secondStatus == BehaviourStatus.Succeeded) - { - return BehaviourStatus.Succeeded; - } - - if (_firstStatus == BehaviourStatus.Failed || _secondStatus == BehaviourStatus.Failed) - { - return BehaviourStatus.Failed; - } - - return BehaviourStatus.Running; - } - - protected override BehaviourStatus Update(TContext context) - { - if (base.Status != BehaviourStatus.Running) - { - _firstStatus = _first.Tick(context); - _secondStatus = _second.Tick(context); - } - else - { - if (_firstStatus == BehaviourStatus.Ready || _firstStatus == BehaviourStatus.Running) - { - _firstStatus = _first.Tick(context); - } - - if (_secondStatus == BehaviourStatus.Ready || _secondStatus == BehaviourStatus.Running) - { - _secondStatus = _second.Tick(context); - } - } - - if (_firstStatus == BehaviourStatus.Failed || _secondStatus == BehaviourStatus.Failed) - { - return BehaviourStatus.Failed; - } - else - { - return _behave(context); - } - } - - protected override void DoReset(BehaviourStatus status) - { - _firstStatus = BehaviourStatus.Ready; - _secondStatus = BehaviourStatus.Ready; - base.DoReset(status); - } - } - - public static class FluentBuilderExtensions - { - public static FluentBuilder MySimpleParallel(this FluentBuilder builder, string name, SimpleParallelPolicy policy = SimpleParallelPolicy.BothMustSucceed) - { - return builder.PushComposite((IBehaviour[] children) => new MySimpleParallel(name, policy, children[0], children[1])); - } - } } diff --git a/BetterGenshinImpact/GameTask/AutoFishing/BehaviourTreeExtensions.cs b/BetterGenshinImpact/GameTask/AutoFishing/BehaviourTreeExtensions.cs new file mode 100644 index 00000000..dbd4518d --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoFishing/BehaviourTreeExtensions.cs @@ -0,0 +1,118 @@ +using BehaviourTree.Composites; +using BehaviourTree.FluentBuilder; +using BehaviourTree; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BetterGenshinImpact.GameTask.AutoFishing +{ + public static class BehaviourTreeExtensions + { + public static FluentBuilder MySimpleParallel(this FluentBuilder builder, string name, SimpleParallelPolicy policy = SimpleParallelPolicy.BothMustSucceed) + { + return builder.PushComposite((IBehaviour[] children) => new MySimpleParallel(name, policy, children[0], children[1])); + } + } + + /// + /// MySimpleParallel + /// 和SimpleParallel的区别是,任一子行为返回失败则返回失败 + /// + /// + public class MySimpleParallel : CompositeBehaviour + { + private readonly IBehaviour _first; + + private readonly IBehaviour _second; + + private BehaviourStatus _firstStatus; + + private BehaviourStatus _secondStatus; + + private readonly Func _behave; + + public readonly SimpleParallelPolicy Policy; + + public MySimpleParallel(SimpleParallelPolicy policy, IBehaviour first, IBehaviour second) + : this("SimpleParallel", policy, first, second) + { + } + + public MySimpleParallel(string name, SimpleParallelPolicy policy, IBehaviour first, IBehaviour second) + : base(name, new IBehaviour[2] { first, second }) + { + Policy = policy; + _first = first; + _second = second; + _behave = ((policy == SimpleParallelPolicy.BothMustSucceed) ? new Func(BothMustSucceedBehaviour) : new Func(OnlyOneMustSucceedBehaviour)); + } + + private BehaviourStatus OnlyOneMustSucceedBehaviour(TContext context) + { + if (_firstStatus == BehaviourStatus.Succeeded || _secondStatus == BehaviourStatus.Succeeded) + { + return BehaviourStatus.Succeeded; + } + + if (_firstStatus == BehaviourStatus.Failed && _secondStatus == BehaviourStatus.Failed) + { + return BehaviourStatus.Failed; + } + + return BehaviourStatus.Running; + } + + private BehaviourStatus BothMustSucceedBehaviour(TContext context) + { + if (_firstStatus == BehaviourStatus.Succeeded && _secondStatus == BehaviourStatus.Succeeded) + { + return BehaviourStatus.Succeeded; + } + + if (_firstStatus == BehaviourStatus.Failed || _secondStatus == BehaviourStatus.Failed) + { + return BehaviourStatus.Failed; + } + + return BehaviourStatus.Running; + } + + protected override BehaviourStatus Update(TContext context) + { + if (base.Status != BehaviourStatus.Running) + { + _firstStatus = _first.Tick(context); + _secondStatus = _second.Tick(context); + } + else + { + if (_firstStatus == BehaviourStatus.Ready || _firstStatus == BehaviourStatus.Running) + { + _firstStatus = _first.Tick(context); + } + + if (_secondStatus == BehaviourStatus.Ready || _secondStatus == BehaviourStatus.Running) + { + _secondStatus = _second.Tick(context); + } + } + + if (_firstStatus == BehaviourStatus.Failed || _secondStatus == BehaviourStatus.Failed) + { + return BehaviourStatus.Failed; + } + else + { + return _behave(context); + } + } + + protected override void DoReset(BehaviourStatus status) + { + _firstStatus = BehaviourStatus.Ready; + _secondStatus = BehaviourStatus.Ready; + base.DoReset(status); + } + } +} diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs b/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs new file mode 100644 index 00000000..321220ec --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs @@ -0,0 +1,762 @@ +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; +using Compunet.YoloV8; +using Microsoft.Extensions.Logging; +using OpenCvSharp; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing.Imaging; +using System.IO; +using static Vanara.PInvoke.User32; +using Color = System.Drawing.Color; +using Pen = System.Drawing.Pen; +using System.Linq; + +namespace BetterGenshinImpact.GameTask.AutoFishing +{ + /// + /// 检测鱼群 + /// + public class GetFishpond : BaseBehaviour + { + private readonly ILogger _logger = App.GetLogger(); + private readonly Blackboard blackboard; + public GetFishpond(string name, Blackboard blackboard) : base(name) + { + this.blackboard = blackboard; + } + + protected override BehaviourStatus Update(CaptureContent content) + { + _logger.LogDebug("GetFishpond"); + using var memoryStream = new MemoryStream(); + content.CaptureRectArea.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); + memoryStream.Seek(0, SeekOrigin.Begin); + var result = Blackboard.predictor.Detect(memoryStream); + Debug.WriteLine($"YOLOv8识别: {result.Speed}"); + var fishpond = new Fishpond(result); + if (fishpond.FishpondRect == Rect.Empty) + { + blackboard.Sleep(500); + return BehaviourStatus.Running; + } + else + { + blackboard.fishpond = fishpond; + _logger.LogInformation("定位到鱼塘:" + string.Join('、', fishpond.Fishes.GroupBy(f => f.FishType).Select(g => $"{g.Key.ChineseName}{g.Count()}条"))); + + return BehaviourStatus.Succeeded; + } + } + } + + /// + /// 选择鱼饵 + /// + public class ChooseBait : BaseBehaviour + { + private readonly ILogger _logger = App.GetLogger(); + private readonly Blackboard blackboard; + private DateTime? chooseBaitUIOpenWaitEndTime; // 等待选鱼饵界面出现并尝试找鱼饵的结束时间 + + /// + /// 选择鱼饵 + /// + /// + /// + public ChooseBait(string name, Blackboard blackboard) : base(name) + { + this.blackboard = blackboard; + } + + protected override BehaviourStatus Update(CaptureContent content) + { + if (this.Status == BehaviourStatus.Ready) // 第一次进来直接返回,更新截图 + { + chooseBaitUIOpenWaitEndTime = DateTime.Now.AddSeconds(3); + _logger.LogInformation("打开换饵界面"); + blackboard.chooseBaitUIOpening = true; + Simulation.SendInput.Mouse.RightButtonClick(); + blackboard.Sleep(100); + Simulation.SendInput.Mouse.MoveMouseBy(0, 200); // 鼠标移走,防止干扰 + blackboard.Sleep(100); + 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); + + // 寻找鱼饵 + var ro = new RecognitionObject + { + Name = "ChooseBait", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFishing", $"bait\\{blackboard.selectedBaitName}.png"), + Threshold = 0.8, + Use3Channels = true, + DrawOnWindow = false + }.InitTemplate(); + + var captureRegion = content.CaptureRectArea; + using var resRa = captureRegion.Find(ro); + if (resRa.IsEmpty()) + { + if (DateTime.Now >= chooseBaitUIOpenWaitEndTime) + { + _logger.LogWarning("没有找到目标鱼饵"); + Simulation.SendInput.Keyboard.KeyPress(VK.VK_ESCAPE); + blackboard.chooseBaitUIOpening = false; + _logger.LogInformation("退出换饵界面"); + blackboard.selectedBaitName = string.Empty; + return BehaviourStatus.Failed; + } + else + { + return BehaviourStatus.Running; + } + } + else + { + resRa.Click(); + blackboard.Sleep(700); + // 可能重复点击,所以固定界面点击下 + captureRegion.ClickTo((int)(captureRegion.Width * 0.675), (int)(captureRegion.Height / 3d)); + blackboard.Sleep(200); + // 点击确定 + Bv.ClickWhiteConfirmButton(captureRegion); + blackboard.chooseBaitUIOpening = false; + _logger.LogInformation("退出换饵界面"); + blackboard.Sleep(500); // 等待界面切换 + } + + return BehaviourStatus.Succeeded; + } + } + + /// + /// 抛竿 + /// + public class ApproachFishAndThrowRod : BaseBehaviour + { + private readonly ILogger _logger = App.GetLogger(); + private readonly Blackboard blackboard; + + private int noPlacementTimes; // 没有落点的次数 + private int noTargetFishTimes; // 没有目标鱼的次数 + public ApproachFishAndThrowRod(string name, Blackboard blackboard) : base(name) + { + this.blackboard = blackboard; + } + + protected override void OnInitialize() + { + noPlacementTimes = 0; + noTargetFishTimes = 0; + + Simulation.SendInput.Mouse.LeftButtonDown(); + blackboard.pitchReset = true; + _logger.LogInformation("长按预抛竿"); + blackboard.Sleep(3000); + } + + protected override void OnTerminate(BehaviourStatus status) + { + if (status != BehaviourStatus.Running) + { + VisionContext.Instance().DrawContent.RemoveRect("Target"); + VisionContext.Instance().DrawContent.RemoveRect("Fish"); + } + } + + protected override BehaviourStatus Update(CaptureContent content) + { + _logger.LogDebug("ApproachFishAndThrowRod"); + blackboard.noTargetFish = false; + var prevTargetFishRect = Rect.Empty; // 记录上一个目标鱼的位置 + + var ra = content.CaptureRectArea; + + // 找 鱼饵落点 + using var memoryStream = new MemoryStream(); + ra.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); + memoryStream.Seek(0, SeekOrigin.Begin); + var result = Blackboard.predictor.Detect(memoryStream); + Debug.WriteLine($"YOLOv8识别: {result.Speed}"); + var fishpond = new Fishpond(result, includeTarget: true); + Random _rd = new(); + if (fishpond.TargetRect == null || fishpond.TargetRect == Rect.Empty) + { + noPlacementTimes++; + blackboard.Sleep(50); + Debug.WriteLine($"{noPlacementTimes}次未找到鱼饵落点"); + + var cX = ra.SrcBitmap.Width / 2; + var cY = ra.SrcBitmap.Height / 2; + var rdX = _rd.Next(0, ra.SrcBitmap.Width); + var rdY = _rd.Next(0, ra.SrcBitmap.Height); + + var moveX = 100 * (cX - rdX) / ra.SrcBitmap.Width; + var moveY = 100 * (cY - rdY) / ra.SrcBitmap.Height; + + Simulation.SendInput.Mouse.MoveMouseBy(moveX, moveY); + + if (noPlacementTimes > 25) + { + _logger.LogInformation("未找到鱼饵落点,重试"); + Simulation.SendInput.Mouse.LeftButtonUp(); + blackboard.Sleep(2000); + Simulation.SendInput.Mouse.LeftButtonClick(); + blackboard.Sleep(2000); //此处需要久一点 + return BehaviourStatus.Failed; + } + + return BehaviourStatus.Running; + } + + Rect fishpondTargetRect = (Rect)fishpond.TargetRect; + + // 找到落点最近的鱼 + OneFish? currentFish = null; + if (prevTargetFishRect == Rect.Empty) + { + var list = fishpond.FilterByBaitName(blackboard.selectedBaitName); + if (list.Count > 0) + { + currentFish = list[0]; + prevTargetFishRect = currentFish.Rect; + } + } + else + { + currentFish = fishpond.FilterByBaitNameAndRecently(blackboard.selectedBaitName, prevTargetFishRect); + if (currentFish != null) + { + prevTargetFishRect = currentFish.Rect; + } + } + + if (currentFish == null) + { + Debug.WriteLine("无目标鱼"); + noTargetFishTimes++; + //if (noTargetFishTimes == 30) + //{ + // Simulation.SendInputEx.Mouse.MoveMouseBy(0, 100); + //} + + if (noTargetFishTimes > 10) + { + // 没有找到目标鱼,重新选择鱼饵 + blackboard.selectedBaitName = string.Empty; + _logger.LogInformation("没有找到目标鱼,1.直接抛竿"); + Simulation.SendInput.Mouse.LeftButtonUp(); + blackboard.Sleep(2000); + _logger.LogInformation("没有找到目标鱼,2.收杆"); + Simulation.SendInput.Mouse.LeftButtonClick(); + blackboard.Sleep(800); + + blackboard.noTargetFish = true; + return BehaviourStatus.Succeeded; + } + + return BehaviourStatus.Running; + } + else + { + noTargetFishTimes = 0; + content.CaptureRectArea.DrawRect(fishpondTargetRect, "Target"); + content.CaptureRectArea.Derive(currentFish.Rect).DrawSelf("Fish"); + + // VisionContext.Instance().DrawContent.PutRect("Target", fishpond.TargetRect.ToRectDrawable()); + // VisionContext.Instance().DrawContent.PutRect("Fish", currentFish.Rect.ToRectDrawable()); + + // 来自 HutaoFisher 的抛竿技术 + var rod = fishpondTargetRect; + var fish = currentFish.Rect; + 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 + { + rod_x1 = NormalizeXTo1024(rod.Left), + rod_x2 = NormalizeXTo1024(rod.Right), + rod_y1 = NormalizeYTo576(rod.Top), + rod_y2 = NormalizeYTo576(rod.Bottom), + fish_x1 = NormalizeXTo1024(fish.Left), + fish_x2 = NormalizeXTo1024(fish.Right), + fish_y1 = NormalizeYTo576(fish.Top), + fish_y2 = NormalizeYTo576(fish.Bottom), + fish_label = BigFishType.GetIndex(currentFish.FishType) + }); + + // 如果hutao钓鱼暂时没有更新导致报错,可以先用这段凑合 + //int state; + //System.Drawing.Rectangle rod3XRectangle = new System.Drawing.Rectangle(rod.Left - rod.Width, rod.Top - rod.Height, rod.Width * 3, rod.Height * 3); + //System.Drawing.Rectangle rod5XRectangle = new System.Drawing.Rectangle(rod.Left - rod.Width * 2, rod.Top - rod.Height * 2, rod.Width * 5, rod.Height * 5); + //System.Drawing.Rectangle fishRectangle = new System.Drawing.Rectangle(fish.Left, fish.Top, fish.Width, fish.Height); + //if (rod3XRectangle.IntersectsWith(fishRectangle)) + //{ + // state = 1; + //} + //else if (rod5XRectangle.IntersectsWith(fishRectangle)) + //{ + // state = 0; + //} + //else + //{ + // state = 2; + //} + + 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 moveX = 100 * (cX - rdX) / content.CaptureRectArea.SrcBitmap.Width; + var moveY = 100 * (cY - rdY) / content.CaptureRectArea.SrcBitmap.Height; + + _logger.LogInformation("失败 随机移动 {DX}, {DY}", moveX, moveY); + Simulation.SendInput.Mouse.MoveMouseBy(moveX, moveY); + } + else if (state == 0) + { + // 成功 抛竿 + Simulation.SendInput.Mouse.LeftButtonUp(); + _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); + return BehaviourStatus.Succeeded; + } + else if (state == 1) + { + // 太近 + var dl = Math.Sqrt(dx * dx + dy * dy); + // set a minimum step + 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)); + } + else if (state == 2) + { + // 太远 + // _logger.LogInformation("太远 移动 {DX}, {DY}", dx, dy); + Simulation.SendInput.Mouse.MoveMouseBy((int)(dx / 1.5), (int)(dy * 1.5)); + } + } + blackboard.Sleep(50); + return BehaviourStatus.Running; + } + + private double NormalizeXTo1024(int x) + { + return x * 1.0 / TaskContext.Instance().SystemInfo.ScaleMax1080PCaptureRect.Width * 1024; + } + + private double NormalizeYTo576(int y) + { + return y * 1.0 / TaskContext.Instance().SystemInfo.ScaleMax1080PCaptureRect.Height * 576; + } + + } + + + /// + /// 检查抛竿结果 + /// 避免往红色靶点抛竿导致失败 + /// + /// + public class CheckThrowRod : BaseBehaviour + { + private readonly ILogger _logger = App.GetLogger(); + private DateTime? timeDelay; + + /// + /// 检查抛竿结果 + /// + /// + public CheckThrowRod(string name) : base(name) + { + } + + protected override void OnInitialize() + { + timeDelay = DateTime.Now.AddSeconds(3); + } + + protected override BehaviourStatus Update(CaptureContent content) + { + if (DateTime.Now < timeDelay) + { + return BehaviourStatus.Running; + } + + Region baitRectArea = content.CaptureRectArea.Find(AutoFishingAssets.Instance.BaitButtonRo); + if (baitRectArea.IsEmpty()) + { + return BehaviourStatus.Succeeded; + } + else + { + _logger.LogInformation("抛竿失败"); + return BehaviourStatus.Failed; + } + } + } + + public class FishBiteTimeout : BaseBehaviour + { + private readonly ILogger _logger = App.GetLogger(); + private DateTime? waitFishBiteTimeout; + private int seconds; + + /// + /// 如果未超时返回运行中,超时返回失败 + /// + /// + /// + public FishBiteTimeout(string name, int seconds) : base(name) + { + this.seconds = seconds; + } + protected override void OnInitialize() + { + waitFishBiteTimeout = DateTime.Now.AddSeconds(seconds); + } + protected override BehaviourStatus Update(CaptureContent context) + { + if (DateTime.Now >= waitFishBiteTimeout) + { + _logger.LogInformation($"{seconds}秒没有咬杆,本次收杆"); + Simulation.SendInput.Mouse.LeftButtonClick(); + TaskControl.Sleep(1000); + return BehaviourStatus.Failed; + } + else + { + return BehaviourStatus.Running; + } + } + } + + /// + /// 自动提竿 + /// + public class FishBite : BaseBehaviour + { + private readonly ILogger _logger = App.GetLogger(); + private readonly IOcrService ocrService = OcrFactory.Paddle; + public FishBite(string name) : base(name) + { + } + protected override BehaviourStatus Update(CaptureContent content) + { + _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); + // 上半屏幕和中间1/3的区域 + var liftingWordsAreaRect = new Rect(content.CaptureRectArea.SrcMat.Width / 3, 0, content.CaptureRectArea.SrcMat.Width / 3, + content.CaptureRectArea.SrcMat.Height / 2); + //VisionContext.Instance().DrawContent.PutRect("liftingWordsAreaRect", liftingWordsAreaRect.ToRectDrawable(new Pen(Color.Cyan, 2))); + var wordCaptureMat = new Mat(content.CaptureRectArea.SrcMat, liftingWordsAreaRect); + var currentBiteWordsTips = AutoFishingImageRecognition.MatchFishBiteWords(wordCaptureMat, liftingWordsAreaRect); + if (currentBiteWordsTips != Rect.Empty) + { + // VisionContext.Instance().DrawContent.PutRect("FishBiteTips", + // currentBiteWordsTips + // .ToWindowsRectangleOffset(liftingWordsAreaRect.X, liftingWordsAreaRect.Y) + // .ToRectDrawable()); + using var tipsRa = content.CaptureRectArea.Derive(currentBiteWordsTips + liftingWordsAreaRect.Location); + tipsRa.DrawSelf("FishBiteTips"); + + // 图像提竿判断 + using var liftRodButtonRa = content.CaptureRectArea.Find(AutoFishingAssets.Instance.LiftRodButtonRo); + if (!liftRodButtonRa.IsEmpty()) + { + Simulation.SendInput.Mouse.LeftButtonClick(); + _logger.LogInformation(@"┌------------------------┐"); + _logger.LogInformation(" 自动提竿(图像识别)"); + VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); + return BehaviourStatus.Succeeded; + } + + // OCR 提竿判断 + var text = ocrService.Ocr(new Mat(content.CaptureRectArea.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)"); + VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); + return BehaviourStatus.Succeeded; + } + + Simulation.SendInput.Mouse.LeftButtonClick(); + _logger.LogInformation(@"┌------------------------┐"); + _logger.LogInformation(" 自动提竿(文字块)"); + VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); + return BehaviourStatus.Succeeded; + } + + return BehaviourStatus.Running; + } + } + + /// + /// 进入钓鱼界面先尝试获取钓鱼框的位置 + /// + public class GetFishBoxArea : BaseBehaviour + { + private readonly ILogger _logger = App.GetLogger(); + private readonly Blackboard blackboard; + public GetFishBoxArea(string name, Blackboard blackboard) : base(name) + { + this.blackboard = blackboard; + } + + protected override BehaviourStatus Update(CaptureContent content) + { + _logger.LogDebug("GetFishBoxArea"); + + using var topMat = new Mat(content.CaptureRectArea.SrcMat, new Rect(0, 0, content.CaptureRectArea.Width, content.CaptureRectArea.Height / 2)); + + var rects = AutoFishingImageRecognition.GetFishBarRect(topMat); + if (rects != null && rects.Count == 2) + { + Rect _cur, _left; + if (Math.Abs(rects[0].Height - rects[1].Height) > 10) + { + TaskControl.Logger.LogError("两个矩形高度差距过大,未识别到钓鱼框"); + return BehaviourStatus.Running; + } + + if (rects[0].Width < rects[1].Width) + { + _cur = rects[0]; + _left = rects[1]; + } + else + { + _cur = rects[1]; + _left = rects[0]; + } + + if (_left.X < _cur.X // cur 是游标位置, 在初始状态下,cur 一定在left左边 + || _cur.Width > _left.Width // left一定比cur宽 + || _cur.X + _cur.Width > topMat.Width / 2 // cur 一定在屏幕左侧 + || _cur.X + _cur.Width > _left.X - _left.Width / 2 // cur 一定在left左侧+left的一半宽度 + || _cur.X + _cur.Width > topMat.Width / 2 - _left.Width // cur 一定在屏幕中轴线减去整个left的宽度的位置左侧 + || !(_left.X < topMat.Width / 2 && _left.X + _left.Width > topMat.Width / 2) // left肯定穿过游戏中轴线 + ) + { + return BehaviourStatus.Running; + } + + int hExtra = _cur.Height, vExtra = _cur.Height / 4; + 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); + boxRa.DrawSelf("FishBox", new Pen(Color.LightPink, 2)); + return BehaviourStatus.Succeeded; + } + + return BehaviourStatus.Running; + + //CheckFishingUserInterface(content); + } + } + + /// + /// 拉条 + /// + public class Fishing : BaseBehaviour + { + private readonly ILogger _logger = App.GetLogger(); + private readonly Blackboard blackboard; + public Fishing(string name, Blackboard blackboard) : base(name) + { + this.blackboard = blackboard; + } + + private MOUSEEVENTF _prevMouseEvent = 0x0; + private bool _findFishBoxTips; + + protected override BehaviourStatus Update(CaptureContent content) + { + _logger.LogDebug("Fishing"); + var fishBarMat = new Mat(content.CaptureRectArea.SrcMat, blackboard.fishBoxRect); + var simulator = Simulation.SendInput; + var rects = AutoFishingImageRecognition.GetFishBarRect(fishBarMat); + if (rects != null && rects.Count > 0) + { + if (rects.Count >= 2 && _prevMouseEvent == 0x0 && !_findFishBoxTips) + { + _findFishBoxTips = true; + _logger.LogInformation(" 识别到钓鱼框,自动拉扯中..."); + } + + // 超过3个矩形是异常情况,取高度最高的三个矩形进行识别 + if (rects.Count > 3) + { + rects.Sort((a, b) => b.Height.CompareTo(a.Height)); + rects.RemoveRange(3, rects.Count - 3); + } + + Rect _cur, _left, _right; + //Debug.WriteLine($"识别到{rects.Count} 个矩形"); + if (rects.Count == 2) + { + if (rects[0].Width < rects[1].Width) + { + _cur = rects[0]; + _left = rects[1]; + } + else + { + _cur = rects[1]; + _left = rects[0]; + } + + PutRects(content, _left, _cur, new Rect()); + + if (_cur.X < _left.X) + { + if (_prevMouseEvent != MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN) + { + simulator.Mouse.LeftButtonDown(); + //Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown(); + _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN; + //Debug.WriteLine("进度不到 左键按下"); + } + } + else + { + if (_prevMouseEvent == MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN) + { + simulator.Mouse.LeftButtonUp(); + //Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonUp(); + _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP; + //Debug.WriteLine("进度超出 左键松开"); + } + } + } + else if (rects.Count == 3) + { + rects.Sort((a, b) => a.X.CompareTo(b.X)); + _left = rects[0]; + _cur = rects[1]; + _right = rects[2]; + PutRects(content, _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(); + _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP; + //Debug.WriteLine("进入框内中间 左键松开"); + } + } + else + { + if (_prevMouseEvent != MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN) + { + simulator.Mouse.LeftButtonDown(); + //Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown(); + _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN; + //Debug.WriteLine("未到框内中间 左键按下"); + } + } + } + else + { + PutRects(content, new Rect(), new Rect(), new Rect()); + } + } + else + { + PutRects(content, new Rect(), new Rect(), new Rect()); + // 没有矩形视为已经完成钓鱼 + VisionContext.Instance().DrawContent.RemoveRect("FishBox"); + _findFishBoxTips = false; + _prevMouseEvent = 0x0; + _logger.LogInformation(" 拉扯结束"); + _logger.LogInformation(@"└------------------------┘"); + + // 保证鼠标松开 + simulator.Mouse.LeftButtonUp(); + + blackboard.Sleep(7000); + + return BehaviourStatus.Succeeded; + + //CheckFishingUserInterface(content); + } + + return BehaviourStatus.Running; + } + + private readonly Pen _pen = new(Color.Red, 1); + private void PutRects(CaptureContent content, Rect left, Rect cur, Rect right) + { + //var list = new List + //{ + // left.ToWindowsRectangleOffset(_fishBoxRect.X, _fishBoxRect.Y).ToRectDrawable(_pen), + // cur.ToWindowsRectangleOffset(_fishBoxRect.X, _fishBoxRect.Y).ToRectDrawable(_pen), + // right.ToWindowsRectangleOffset(_fishBoxRect.X, _fishBoxRect.Y).ToRectDrawable(_pen) + //}; + using var fishBoxRa = content.CaptureRectArea.Derive(blackboard.fishBoxRect); + var list = new List + { + fishBoxRa.ToRectDrawable(left, "left", _pen), + fishBoxRa.ToRectDrawable(cur, "cur", _pen), + fishBoxRa.ToRectDrawable(right, "right", _pen), + }.Where(r => r.Rect.Height != 0).ToList(); + VisionContext.Instance().DrawContent.PutOrRemoveRectList("FishingBarAll", list); + } + } + + /// + /// 如果视角被其他行为重置过,则调整视角至俯视 + /// + public class MoveViewpointDown : BaseBehaviour + { + private readonly ILogger _logger = App.GetLogger(); + private readonly Blackboard blackboard; + public MoveViewpointDown(string name, Blackboard blackboard) : base(name) + { + this.blackboard = blackboard; + } + + protected override BehaviourStatus Update(CaptureContent context) + { + if (blackboard.pitchReset) + { + _logger.LogInformation("调整视角至俯视"); + blackboard.pitchReset = false; + // 下移视角方便看鱼 + Simulation.SendInput.Mouse.MoveMouseBy(0, 400); + blackboard.Sleep(100); + return BehaviourStatus.Running; + } + return BehaviourStatus.Succeeded; + } + + } +} diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs b/BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs new file mode 100644 index 00000000..c9952cc9 --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Recognition.ONNX; +using BetterGenshinImpact.GameTask.AutoFishing.Model; +using Compunet.YoloV8; +using OpenCvSharp; + +namespace BetterGenshinImpact.GameTask.AutoFishing +{ + /// + /// 用于在钓鱼行为之间传递数据 + /// + public class Blackboard + { + /// + /// 已选择的鱼饵名 + /// + internal string selectedBaitName = string.Empty; + + /// + /// 鱼塘 + /// + internal Fishpond fishpond; + + /// + /// 是否没有目标鱼 + /// + internal bool noTargetFish; + + /// + /// 拉条位置的识别框 + /// + internal Rect fishBoxRect = Rect.Empty; + + /// + /// 是否正在选鱼饵界面 + /// 此时有阴影遮罩,OpenCv的图像匹配会受干扰 + /// + internal bool chooseBaitUIOpening = false; + + /// + /// 镜头俯仰是否被行为重置 + /// 进入钓鱼模式后、以及提竿后,镜头的俯仰会被重置。进行相关动作前须优化俯仰角,避免鱼塘被脚下的悬崖遮挡。 + /// + 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; } + #endregion + + internal virtual void Reset() + { + noTargetFish = false; + fishBoxRect = Rect.Empty; + chooseBaitUIOpening = false; + pitchReset = false; + } + } +} diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs b/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs index 09697b7b..6408cae3 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs @@ -13,6 +13,7 @@ public class BigFishType public static readonly BigFishType LargeMedaka = new("large medaka", "fruit paste bait", "大花鳉", 1); public static readonly BigFishType Stickleback = new("stickleback", "redrot bait", "棘鱼", 2); public static readonly BigFishType Koi = new("koi", "fake fly bait", "假龙", 3); + public static readonly BigFishType KoiHead = new("koi head", "fake fly bait", "假龙头", 3); public static readonly BigFishType Butterflyfish = new("butterflyfish", "false worm bait", "蝶鱼", 4); public static readonly BigFishType Pufferfish = new("pufferfish", "fake fly bait", "炮鲀", 5); @@ -39,6 +40,7 @@ public class BigFishType yield return LargeMedaka; yield return Stickleback; yield return Koi; + yield return KoiHead; yield return Butterflyfish; yield return Pufferfish; yield return Ray; diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs b/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs index 9891761e..28f5b4a1 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/Model/Fishpond.cs @@ -17,26 +17,32 @@ public class Fishpond /// /// 抛竿落点位置 /// - public Rect TargetRect { get; set; } + public Rect? TargetRect { get; set; } /// /// 鱼池中的鱼 /// public List Fishes { get; set; } = []; - public Fishpond(DetectionResult result) + /// + /// + /// + /// 是否包含抛竿落点 + public Fishpond(DetectionResult result, bool includeTarget = false) { foreach (var box in result.Boxes) { - if (box.Class.Name == "rod") + 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); continue; } - else if (box.Class.Name == "err rod") + if (includeTarget) { - TargetRect = new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height); - continue; + if (box.Class.Name == "koi") //进入抛竿的时候只看koihead + { + continue; + } } var fish = new OneFish(box.Class.Name, new Rect(box.Bounds.X, box.Bounds.Y, box.Bounds.Width, box.Bounds.Height), box.Confidence); diff --git a/BetterGenshinImpact/GameTask/AutoPathing/Handler/ActionFactory.cs b/BetterGenshinImpact/GameTask/AutoPathing/Handler/ActionFactory.cs index 66058aac..2d7115de 100644 --- a/BetterGenshinImpact/GameTask/AutoPathing/Handler/ActionFactory.cs +++ b/BetterGenshinImpact/GameTask/AutoPathing/Handler/ActionFactory.cs @@ -24,6 +24,7 @@ public class ActionFactory "anemo_collect" => new ElementalCollectHandler(ElementalType.Anemo), "combat_script" => new CombatScriptHandler(), "mining" => new MiningHandler(), + "fishing" => new FishingHandler(), _ => throw new ArgumentException("未知的后置 action 类型") }; }); diff --git a/BetterGenshinImpact/GameTask/AutoPathing/Handler/FishingHandler.cs b/BetterGenshinImpact/GameTask/AutoPathing/Handler/FishingHandler.cs new file mode 100644 index 00000000..1b6e4e6c --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoPathing/Handler/FishingHandler.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.GameTask.AutoFight.Model; +using BetterGenshinImpact.GameTask.AutoFight.Script; +using BetterGenshinImpact.GameTask.AutoFishing; +using BetterGenshinImpact.GameTask.AutoPathing.Model; +using BetterGenshinImpact.GameTask.Common.Job; +using Microsoft.Extensions.Logging; +using static BetterGenshinImpact.GameTask.Common.TaskControl; + +namespace BetterGenshinImpact.GameTask.AutoPathing.Handler; + +/// +/// 挖矿并拾取 +/// +public class FishingHandler : IActionHandler +{ + private AutoFishingTask _autoFishingTask = new(); + + public async Task RunAsync(CancellationToken ct, WaypointForTrack? waypointForTrack = null, object? config = null) + { + // 钓鱼 + await _autoFishingTask.Start(ct); + + await Delay(1000, ct); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/ActionEnum.cs b/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/ActionEnum.cs index 1442720b..47560d4e 100644 --- a/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/ActionEnum.cs +++ b/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/ActionEnum.cs @@ -19,6 +19,9 @@ public class ActionEnum(string code, string msg, ActionUseWaypointTypeEnum useWa public static readonly ActionEnum Mining = new("mining", "挖矿", ActionUseWaypointTypeEnum.Custom); public static readonly ActionEnum LogOutput = new("log_output", "输出日志", ActionUseWaypointTypeEnum.Custom); + + public static readonly ActionEnum Fishing = new("fishing", "钓鱼", ActionUseWaypointTypeEnum.Custom); + // 还有要加入的其他动作 // 滚轮F diff --git a/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs b/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs index bd51cb40..d4d32a0c 100644 --- a/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs +++ b/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs @@ -165,10 +165,6 @@ public class PathExecutor CurWaypoint = (waypoints.FindIndex(wps => wps == waypoint), waypoint); TryCloseSkipOtherOperations(); await RecoverWhenLowHp(waypoint); // 低血量恢复 - if (waypoint.Action == ActionEnum.LogOutput.Code) - { - Logger.LogInformation(waypoint.LogInfo); - } if (waypoint.Type == WaypointType.Teleport.Code) { @@ -260,7 +256,7 @@ public class PathExecutor { return false; } - + var action = ActionEnum.GetEnumByCode(waypoint.Action); if (action is not null && action.UseWaypointTypeEnum != ActionUseWaypointTypeEnum.Custom) { @@ -895,9 +891,13 @@ public class PathExecutor if (waypoint.Action == ActionEnum.UpDownGrabLeaf.Code) { var handler = ActionFactory.GetBeforeHandler(waypoint.Action); - await handler.RunAsync(ct); + await handler.RunAsync(ct, waypoint); await Delay(800, ct); } + else if (waypoint.Action == ActionEnum.LogOutput.Code) + { + Logger.LogInformation(waypoint.LogInfo); + } } private async Task AfterMoveToTarget(WaypointForTrack waypoint) @@ -908,7 +908,9 @@ public class PathExecutor || waypoint.Action == ActionEnum.HydroCollect.Code || waypoint.Action == ActionEnum.ElectroCollect.Code || waypoint.Action == ActionEnum.AnemoCollect.Code - || waypoint.Action == ActionEnum.CombatScript.Code) + || waypoint.Action == ActionEnum.CombatScript.Code + || waypoint.Action == ActionEnum.Mining.Code + || waypoint.Action == ActionEnum.Fishing.Code) { var handler = ActionFactory.GetAfterHandler(waypoint.Action); //,PartyConfig diff --git a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml index 3644a2b3..9cd2b48d 100644 --- a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml @@ -1393,6 +1393,79 @@ + + + + + + + + + + + + + + + + + + + 全自动钓鱼任务 - + + 点击查看使用教程 + + + + + + + + + + + + + + + + + + + + +