mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
* 没有找到鱼饵时不再抛异常而是返回行为失败;细小优化;测试分支是否配置正确 * 恢复半自动钓鱼功能(仅自动拉条);将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>
288 lines
12 KiB
C#
288 lines
12 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|