Files
better-genshin-impact/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTask.cs
FishmanTheMurloc a268c1d9a2 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>
2025-02-17 11:34:18 +08:00

288 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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使2F交互键被吞
// 加入昼夜切换后使用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;
}
}
}
}
}