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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全自动钓鱼任务 -
+
+ 点击查看使用教程
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+