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