mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
Feat/new fishing (#1060)
* 没有找到鱼饵时不再抛异常而是返回行为失败;细小优化;测试分支是否配置正确 * 恢复半自动钓鱼功能(仅自动拉条);将CheckFishingUserInterface方法添加到行为树,使其直接控制启停;PutRects方法增加筛选,避免画出没有高度的框框导致残留红点在画布上;去掉局部变量_currContent;钓鱼结束不再依据_noRectsCount判断 * 添加一步抛竿后检查,避免往红色靶点抛竿导致失败 * 大家终于炼出了好用的适用纳塔版本的鱼模型;实现注释描述的“选择最多鱼吃的饵料”;添加koihead鱼类,进入抛竿时忽略koi,只看koihead;Fishpond.TargetRect补上空值处理;去掉_switchBaitContinuouslyFrameNum,目前该段代码有时候会导致发呆;钓鱼结束时多等5秒,避免“获得鱼”的提示图被错误地计入下一次抛竿找鱼的预测 * 注释了AutoFishingTrigger中,FishBite和Fishing方法中的一些代码,解除了对CaptureContent.FrameRate的引用以方便开新坑;开了个新坑AutoFishingTask * 新增全自动钓鱼独立任务的ui界面 * 封装了所有钓鱼行为,消灭了AutoFishingTrigger中大部分私有变量,剩余一些用来在行为之间传递信息的变量被丢到Blackboard中 * 代码清理:删除AutoFishingTrigger中被注释的私有变量;行为树扩展方法移动到单独的文件中 * 封装好的行为都搬家到Behaviours.cs去了;钓鱼独立任务基本完成;Blackboard添加chooseBaitUIOpening字段以避免在选择鱼饵界面时因图标被灰色遮罩而影响图像匹配;抛竿行为添加OnTerminate方法修复合并预抛竿和抛竿行为时产生的bug * 优化VisionContext框框的代码 * AutoFishingTask加了个转圈圈找鱼的动作 * 钓鱼任务时如果有F键以及确认键,就交互一下进入钓鱼模式 * 添加供js调用的钓鱼任务方法 * 调整视角时也调整游戏角色的朝向;因为错误率较高,抛竿前找鱼时不再对右下角图标进行模板匹配检查 * 把MoveViewpointDown封装成行为了,黑板新增字段pitchReset,改进了流程中调整视角俯仰的部分;钓鱼任务中为了避免人物待机动作吃掉钓鱼F键,ChangeView方法改成始终都按S和W键 * ThrowRod行为删去对鱼群位置的校验,该段校验经常导致发呆;并将该行为更名为GetFishpond * 对EnterFishingMode行为进行优化并修复bug;钓鱼循环修正 * 将螺旋视角找鱼的行为简化为低头转圈找,以适应路径任务完成时经常无法朝向鱼的情况;按下钓鱼键后等待界面出现时间延长至2秒 * 添加js独立任务调用自动钓鱼 * 新增`fishing`的Action用于触发钓鱼 * AutoFishingTask删去右下角ExitFishingButtonRo的模板匹配校验,因为错误的未识别有点多;添加当前焦点窗口校验 * AutoFishingTask增加设置昼夜功能,在7点和19点各钓一轮 --------- Co-authored-by: 辉鸭蛋 <huiyadanli@gmail.com>
This commit is contained in:
Binary file not shown.
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 钓鱼
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task AutoFishing()
|
||||
{
|
||||
await new AutoFishingTask().Start(CancellationContext.Instance.Cts.Token);
|
||||
}
|
||||
}
|
||||
287
BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs
Normal file
287
BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs
Normal file
@@ -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<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
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<CaptureContent>()
|
||||
.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<CaptureContent>
|
||||
{
|
||||
private readonly ILogger<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
private readonly Blackboard blackboard;
|
||||
private DateTime? timeout;
|
||||
private int seconds;
|
||||
|
||||
/// <summary>
|
||||
/// 如果未超时返回运行中,超时返回失败
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="seconds"></param>
|
||||
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<CaptureContent>
|
||||
{
|
||||
private readonly ILogger<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<TContext> MySimpleParallel<TContext>(this FluentBuilder<TContext> builder, string name, SimpleParallelPolicy policy = SimpleParallelPolicy.BothMustSucceed)
|
||||
{
|
||||
return builder.PushComposite((IBehaviour<TContext>[] children) => new MySimpleParallel<TContext>(name, policy, children[0], children[1]));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MySimpleParallel
|
||||
/// 和SimpleParallel的区别是,任一子行为返回失败则返回失败
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext"></typeparam>
|
||||
public class MySimpleParallel<TContext> : CompositeBehaviour<TContext>
|
||||
{
|
||||
private readonly IBehaviour<TContext> _first;
|
||||
|
||||
private readonly IBehaviour<TContext> _second;
|
||||
|
||||
private BehaviourStatus _firstStatus;
|
||||
|
||||
private BehaviourStatus _secondStatus;
|
||||
|
||||
private readonly Func<TContext, BehaviourStatus> _behave;
|
||||
|
||||
public readonly SimpleParallelPolicy Policy;
|
||||
|
||||
public MySimpleParallel(SimpleParallelPolicy policy, IBehaviour<TContext> first, IBehaviour<TContext> second)
|
||||
: this("SimpleParallel", policy, first, second)
|
||||
{
|
||||
}
|
||||
|
||||
public MySimpleParallel(string name, SimpleParallelPolicy policy, IBehaviour<TContext> first, IBehaviour<TContext> second)
|
||||
: base(name, new IBehaviour<TContext>[2] { first, second })
|
||||
{
|
||||
Policy = policy;
|
||||
_first = first;
|
||||
_second = second;
|
||||
_behave = ((policy == SimpleParallelPolicy.BothMustSucceed) ? new Func<TContext, BehaviourStatus>(BothMustSucceedBehaviour) : new Func<TContext, BehaviourStatus>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
762
BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs
Normal file
762
BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测鱼群
|
||||
/// </summary>
|
||||
public class GetFishpond : BaseBehaviour<CaptureContent>
|
||||
{
|
||||
private readonly ILogger<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选择鱼饵
|
||||
/// </summary>
|
||||
public class ChooseBait : BaseBehaviour<CaptureContent>
|
||||
{
|
||||
private readonly ILogger<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
private readonly Blackboard blackboard;
|
||||
private DateTime? chooseBaitUIOpenWaitEndTime; // 等待选鱼饵界面出现并尝试找鱼饵的结束时间
|
||||
|
||||
/// <summary>
|
||||
/// 选择鱼饵
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="autoFishingTrigger"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 抛竿
|
||||
/// </summary>
|
||||
public class ApproachFishAndThrowRod : BaseBehaviour<CaptureContent>
|
||||
{
|
||||
private readonly ILogger<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 检查抛竿结果
|
||||
/// 避免往红色靶点抛竿导致失败
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
public class CheckThrowRod : BaseBehaviour<CaptureContent>
|
||||
{
|
||||
private readonly ILogger<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
private DateTime? timeDelay;
|
||||
|
||||
/// <summary>
|
||||
/// 检查抛竿结果
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
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<CaptureContent>
|
||||
{
|
||||
private readonly ILogger<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
private DateTime? waitFishBiteTimeout;
|
||||
private int seconds;
|
||||
|
||||
/// <summary>
|
||||
/// 如果未超时返回运行中,超时返回失败
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="seconds"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动提竿
|
||||
/// </summary>
|
||||
public class FishBite : BaseBehaviour<CaptureContent>
|
||||
{
|
||||
private readonly ILogger<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入钓鱼界面先尝试获取钓鱼框的位置
|
||||
/// </summary>
|
||||
public class GetFishBoxArea : BaseBehaviour<CaptureContent>
|
||||
{
|
||||
private readonly ILogger<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拉条
|
||||
/// </summary>
|
||||
public class Fishing : BaseBehaviour<CaptureContent>
|
||||
{
|
||||
private readonly ILogger<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
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<RectDrawable>
|
||||
//{
|
||||
// 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<RectDrawable>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果视角被其他行为重置过,则调整视角至俯视
|
||||
/// </summary>
|
||||
public class MoveViewpointDown : BaseBehaviour<CaptureContent>
|
||||
{
|
||||
private readonly ILogger<AutoFishingTrigger> _logger = App.GetLogger<AutoFishingTrigger>();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
62
BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs
Normal file
62
BetterGenshinImpact/GameTask/AutoFishing/Blackboard.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于在钓鱼行为之间传递数据
|
||||
/// </summary>
|
||||
public class Blackboard
|
||||
{
|
||||
/// <summary>
|
||||
/// 已选择的鱼饵名
|
||||
/// </summary>
|
||||
internal string selectedBaitName = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 鱼塘
|
||||
/// </summary>
|
||||
internal Fishpond fishpond;
|
||||
|
||||
/// <summary>
|
||||
/// 是否没有目标鱼
|
||||
/// </summary>
|
||||
internal bool noTargetFish;
|
||||
|
||||
/// <summary>
|
||||
/// 拉条位置的识别框
|
||||
/// </summary>
|
||||
internal Rect fishBoxRect = Rect.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在选鱼饵界面
|
||||
/// 此时有阴影遮罩,OpenCv的图像匹配会受干扰
|
||||
/// </summary>
|
||||
internal bool chooseBaitUIOpening = false;
|
||||
|
||||
/// <summary>
|
||||
/// 镜头俯仰是否被行为重置
|
||||
/// 进入钓鱼模式后、以及提竿后,镜头的俯仰会被重置。进行相关动作前须优化俯仰角,避免鱼塘被脚下的悬崖遮挡。
|
||||
/// </summary>
|
||||
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<int> Sleep { get; set; }
|
||||
#endregion
|
||||
|
||||
internal virtual void Reset()
|
||||
{
|
||||
noTargetFish = false;
|
||||
fishBoxRect = Rect.Empty;
|
||||
chooseBaitUIOpening = false;
|
||||
pitchReset = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -17,26 +17,32 @@ public class Fishpond
|
||||
/// <summary>
|
||||
/// 抛竿落点位置
|
||||
/// </summary>
|
||||
public Rect TargetRect { get; set; }
|
||||
public Rect? TargetRect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 鱼池中的鱼
|
||||
/// </summary>
|
||||
public List<OneFish> Fishes { get; set; } = [];
|
||||
|
||||
public Fishpond(DetectionResult result)
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="includeTarget">是否包含抛竿落点</param>
|
||||
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);
|
||||
|
||||
@@ -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 类型")
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 挖矿并拾取
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1393,6 +1393,79 @@
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
|
||||
<!-- 自动钓鱼 -->
|
||||
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0">
|
||||
<ui:CardExpander.Icon>
|
||||
<ui:FontIcon Glyph="" Style="{StaticResource FaFontIconStyle}" />
|
||||
</ui:CardExpander.Icon>
|
||||
<ui:CardExpander.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="自动钓鱼"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
TextWrapping="Wrap">
|
||||
全自动钓鱼任务 -
|
||||
<Hyperlink Command="{Binding GoToAutoFishingUrlCommand}"
|
||||
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}">
|
||||
点击查看使用教程
|
||||
</Hyperlink>
|
||||
</ui:TextBlock>
|
||||
<controls:TwoStateButton Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,24,0"
|
||||
DisableCommand="{Binding StopSoloTaskCommand}"
|
||||
DisableContent="停止"
|
||||
EnableCommand="{Binding SwitchAutoFishingCommand}"
|
||||
EnableContent="{Binding SwitchAutoFishingButtonText}"
|
||||
IsChecked="{Binding SwitchAutoFishingEnabled}" />
|
||||
</Grid>
|
||||
</ui:CardExpander.Header>
|
||||
<StackPanel>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="选择需要钓的鱼"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="选择需要钓的鱼,如果不选择则默认为所有鱼"
|
||||
TextWrapping="Wrap" />
|
||||
<ComboBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Width="80"
|
||||
Margin="0,0,36,0"
|
||||
ItemsSource="{Binding AutoMusicLevelList}"
|
||||
SelectedItem="{Binding Config.AutoFishingConfig.XXXXX, Mode=TwoWay}"
|
||||
SelectedIndex="0" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
|
||||
|
||||
<!--<ui:CardExpander Margin="0,0,0,12" ContentPadding="0" Icon="{ui:SymbolIcon Accessibility24}">
|
||||
|
||||
@@ -20,6 +20,7 @@ using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.System;
|
||||
using BetterGenshinImpact.GameTask.AutoFishing;
|
||||
using BetterGenshinImpact.GameTask.Common.Element.Assets;
|
||||
using BetterGenshinImpact.GameTask.Model.Enum;
|
||||
using BetterGenshinImpact.Helpers;
|
||||
@@ -107,6 +108,13 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
|
||||
|
||||
[ObservableProperty]
|
||||
private AutoFightViewModel? _autoFightViewModel;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _switchAutoFishingEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _switchAutoFishingButtonText = "启动";
|
||||
|
||||
|
||||
public TaskSettingsPageViewModel(IConfigService configService, INavigationService navigationService, TaskTriggerDispatcher taskTriggerDispatcher)
|
||||
{
|
||||
@@ -347,13 +355,13 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OnGoToAutoTrackPathUrlAsync()
|
||||
private async Task OnGoToAutoTrackPathUrlAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/track.html"));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OnSwitchAutoMusicGame()
|
||||
private async Task OnSwitchAutoMusicGame()
|
||||
{
|
||||
SwitchAutoMusicGameEnabled = true;
|
||||
await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage)
|
||||
@@ -362,22 +370,37 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OnGoToAutoMusicGameUrlAsync()
|
||||
private async Task OnGoToAutoMusicGameUrlAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/music.html"));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OnSwitchAutoAlbum()
|
||||
private async Task OnSwitchAutoAlbum()
|
||||
{
|
||||
SwitchAutoAlbumEnabled = true;
|
||||
await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage)
|
||||
.RunSoloTaskAsync(new AutoAlbumTask(new AutoMusicGameParam()));
|
||||
SwitchAutoAlbumEnabled = false;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OnSwitchAutoFishing()
|
||||
{
|
||||
SwitchAutoFishingEnabled = true;
|
||||
await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage)
|
||||
.RunSoloTaskAsync(new AutoFishingTask());
|
||||
SwitchAutoFishingEnabled = false;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void OnOpenLocalScriptRepo()
|
||||
private async Task OnGoToAutoFishingUrlAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/timer/fish.html"));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OnOpenLocalScriptRepo()
|
||||
{
|
||||
_autoFightViewModel.OnOpenLocalScriptRepo();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user