From 28cc3fb3e3a3f2cb506f9807793fc28b8250ec6c Mon Sep 17 00:00:00 2001 From: FishmanTheMurloc Date: Sun, 19 Jan 2025 16:24:13 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=A1=8C=E4=B8=BA?= =?UTF-8?q?=E6=A0=91=E5=AF=B9=E8=87=AA=E5=8A=A8=E9=92=93=E9=B1=BC=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E4=BA=86=E6=94=B9=E5=86=99=EF=BC=8C=E4=BB=A5=E5=88=A9?= =?UTF-8?q?=E5=BE=80=E5=90=8E=E7=9A=84=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BetterGenshinImpact.csproj | 1 + .../AutoFishing/AutoFishingTrigger.cs | 963 +++++++++++------- 2 files changed, 615 insertions(+), 349 deletions(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 07ca5395..286a9169 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -41,6 +41,7 @@ + diff --git a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs index f982155e..9a5043ae 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs @@ -1,4 +1,7 @@ -using BetterGenshinImpact.Core.Config; +using BehaviourTree; +using BehaviourTree.FluentBuilder; +using BehaviourTree.Composites; +using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Recognition; using BetterGenshinImpact.Core.Recognition.OCR; using BetterGenshinImpact.Core.Recognition.ONNX; @@ -25,6 +28,7 @@ using static Vanara.PInvoke.User32; using Color = System.Drawing.Color; using Pen = System.Drawing.Pen; using Point = OpenCvSharp.Point; +using System.Linq; namespace BetterGenshinImpact.GameTask.AutoFishing { @@ -47,6 +51,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing private readonly AutoFishingAssets _autoFishingAssets; + private IBehaviour BehaviourTree { get; set; } + public AutoFishingTrigger() { _autoFishingAssets = AutoFishingAssets.Instance; @@ -64,6 +70,32 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _noFishActionContinuouslyFrameNum = 0; _isThrowRod = false; _selectedBaitName = string.Empty; + + BehaviourTree = FluentBuilder.Create() + .MySimpleParallel("root", policy: SimpleParallelPolicy.OnlyOneMustSucceed) + .Do("检查开关", ctx => TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled ? BehaviourStatus.Running : BehaviourStatus.Failed) + .UntilSuccess("钓鱼") + .Sequence("从选鱼饵开始") + .Do("抛竿前准备", ThrowRod) + .Do("打开换饵界面", OpenChooseBaitUI) + .PushLeaf(() => new ChooseBait("选择鱼饵", this)) + .UntilSuccess("重复预抛竿") + .Sequence("从预抛竿开始") + .Do("长按预抛竿", ApproachFishAndThrowRod0) + .Do("抛竿", ApproachFishAndThrowRod1) + .End() + .End() + .Do("跳转-抛竿缺鱼检查", NoTargetFishCheck) + .MySimpleParallel("下杆中", SimpleParallelPolicy.OnlyOneMustSucceed) + .PushLeaf(() => new FishBiteTimeout("下杆超时检查", 30)) + .Do("自动提竿", FishBite) + .End() + .Do("等待拉条出现", Wait4FishBoxAreaAppear) + .Do("钓鱼拉条", Fishing) + .End() + .End() + .End() + .Build(); } private Rect _fishBoxRect = Rect.Empty; @@ -90,28 +122,12 @@ namespace BetterGenshinImpact.GameTask.AutoFishing } else { - // 自动抛竿 - ThrowRod(content); - // 上钩判断 - FishBite(content); - // 进入钓鱼界面先尝试获取钓鱼框的位置 - if (_fishBoxRect.Width == 0) + BehaviourTree.Tick(content); + if (BehaviourTree.Status == BehaviourStatus.Failed) { - if ((DateTime.Now - _prevExecute).TotalMilliseconds <= 200) - { - return; - } - - _prevExecute = DateTime.Now; - - _fishBoxRect = GetFishBoxArea(content.CaptureRectArea); + _logger.LogInformation("BehaviourStatus.Failed 退出独占模式"); CheckFishingUserInterface(content); } - else - { - // 钓鱼拉条 - Fishing(content, new Mat(content.CaptureRectArea.SrcMat, _fishBoxRect)); - } } } @@ -229,375 +245,480 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// 3. /// /// - private void ThrowRod(CaptureContent content) + private BehaviourStatus ThrowRod(CaptureContent content) { // 没有拉条和提竿的时候,自动抛竿 - if (!_isFishingProcess && _biteTipsExitCount == 0 && TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled) + //if (!_isFishingProcess && _biteTipsExitCount == 0 && TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled) + //{ + var baitRectArea = content.CaptureRectArea.Find(_autoFishingAssets.BaitButtonRo); + var waitBiteArea = content.CaptureRectArea.Find(_autoFishingAssets.WaitBiteButtonRo); + if (!baitRectArea.IsEmpty() && waitBiteArea.IsEmpty()) { - var baitRectArea = content.CaptureRectArea.Find(_autoFishingAssets.BaitButtonRo); - var waitBiteArea = content.CaptureRectArea.Find(_autoFishingAssets.WaitBiteButtonRo); - if (!baitRectArea.IsEmpty() && waitBiteArea.IsEmpty()) + _switchBaitContinuouslyFrameNum++; + _waitBiteContinuouslyFrameNum = 0; + _noFishActionContinuouslyFrameNum = 0; + + if (_switchBaitContinuouslyFrameNum >= content.FrameRate) { - _switchBaitContinuouslyFrameNum++; - _waitBiteContinuouslyFrameNum = 0; - _noFishActionContinuouslyFrameNum = 0; - - if (_switchBaitContinuouslyFrameNum >= content.FrameRate) - { - _isThrowRod = false; - _switchBaitContinuouslyFrameNum = 0; - _logger.LogInformation("当前处于未抛竿状态"); - } - - if (!_isThrowRod) - { - // 1. 观察周围环境,判断鱼塘位置,视角对上鱼塘位置中心 - using var memoryStream = new MemoryStream(); - content.CaptureRectArea.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); - memoryStream.Seek(0, SeekOrigin.Begin); - var result = _predictor.Detect(memoryStream); - Debug.WriteLine($"YOLOv8识别: {result.Speed}"); - var fishpond = new Fishpond(result); - if (fishpond.FishpondRect == Rect.Empty) - { - Sleep(500); - return; - } - else - { - var centerX = content.CaptureRectArea.SrcBitmap.Width / 2; - var centerY = content.CaptureRectArea.SrcBitmap.Height / 2; - // 往左移动是正数,往右移动是负数 - if (fishpond.FishpondRect.Left > centerX) - { - Simulation.SendInput.Mouse.MoveMouseBy(100, 0); - } - - if (fishpond.FishpondRect.Right < centerX) - { - Simulation.SendInput.Mouse.MoveMouseBy(-100, 0); - } - - // 鱼塘尽量在上半屏幕 - if (fishpond.FishpondRect.Bottom > centerY) - { - Simulation.SendInput.Mouse.MoveMouseBy(0, -100); - } - - if ((fishpond.FishpondRect.Left < centerX && fishpond.FishpondRect.Right > centerX && fishpond.FishpondRect.Bottom >= centerY) || fishpond.FishpondRect.Width < content.CaptureRectArea.SrcBitmap.Width / 4) - { - // 鱼塘在中心,选择鱼饵 - if (string.IsNullOrEmpty(_selectedBaitName)) - { - _selectedBaitName = ChooseBait(content, fishpond); - } - - // 抛竿 - Sleep(2000); - ApproachFishAndThrowRod(content); - Sleep(2000); - } - } - } + _isThrowRod = false; + _switchBaitContinuouslyFrameNum = 0; + _logger.LogInformation("当前处于未抛竿状态"); } - if (baitRectArea.IsEmpty() && !waitBiteArea.IsEmpty() && _isThrowRod) + if (!_isThrowRod) { - _switchBaitContinuouslyFrameNum = 0; - _waitBiteContinuouslyFrameNum++; - _noFishActionContinuouslyFrameNum = 0; - _throwRodWaitFrameNum++; - - if (_waitBiteContinuouslyFrameNum >= content.FrameRate) + // 1. 观察周围环境,判断鱼塘位置,视角对上鱼塘位置中心 + using var memoryStream = new MemoryStream(); + content.CaptureRectArea.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); + memoryStream.Seek(0, SeekOrigin.Begin); + var result = _predictor.Detect(memoryStream); + Debug.WriteLine($"YOLOv8识别: {result.Speed}"); + var fishpond = new Fishpond(result); + if (fishpond.FishpondRect == Rect.Empty) { - _isThrowRod = true; - _waitBiteContinuouslyFrameNum = 0; + Sleep(500); + return BehaviourStatus.Running; } - - if (_isThrowRod) + else { - // 30s 没有上钩,重新抛竿 - if (_throwRodWaitFrameNum >= content.FrameRate * TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodTimeOut) + var centerX = content.CaptureRectArea.SrcBitmap.Width / 2; + var centerY = content.CaptureRectArea.SrcBitmap.Height / 2; + // 往左移动是正数,往右移动是负数 + if (fishpond.FishpondRect.Left > centerX) { - Simulation.SendInput.Mouse.LeftButtonClick(); - _throwRodWaitFrameNum = 0; - _waitBiteContinuouslyFrameNum = 0; - Debug.WriteLine("超时自动收竿"); - Sleep(2000); - _isThrowRod = false; + Simulation.SendInput.Mouse.MoveMouseBy(100, 0); } - } - } - if (baitRectArea.IsEmpty() && waitBiteArea.IsEmpty()) - { - _switchBaitContinuouslyFrameNum = 0; - _waitBiteContinuouslyFrameNum = 0; - _noFishActionContinuouslyFrameNum++; - if (_noFishActionContinuouslyFrameNum > content.FrameRate) - { - CheckFishingUserInterface(content); + if (fishpond.FishpondRect.Right < centerX) + { + Simulation.SendInput.Mouse.MoveMouseBy(-100, 0); + } + + // 鱼塘尽量在上半屏幕 + if (fishpond.FishpondRect.Bottom > centerY) + { + Simulation.SendInput.Mouse.MoveMouseBy(0, -100); + } + + if ((fishpond.FishpondRect.Left < centerX && fishpond.FishpondRect.Right > centerX && fishpond.FishpondRect.Bottom >= centerY) || fishpond.FishpondRect.Width < content.CaptureRectArea.SrcBitmap.Width / 4) + { + // 鱼塘在中心,选择鱼饵 + //if (string.IsNullOrEmpty(_selectedBaitName)) + //{ + this.fishpond = fishpond; + _logger.LogInformation("定位到鱼塘" + string.Join('、', fishpond.Fishes.GroupBy(f => f.FishType).Select(g => $"{g.Key.ChineseName}{g.Count()}条"))); + + return BehaviourStatus.Succeeded; + //} + } } } } - else + //} + //else + //{ + // _switchBaitContinuouslyFrameNum = 0; + // _waitBiteContinuouslyFrameNum = 0; + // _noFishActionContinuouslyFrameNum = 0; + // _throwRodWaitFrameNum = 0; + // _isThrowRod = false; + //} + + return BehaviourStatus.Running; + } + + private class FishBiteTimeout : BaseBehaviour + { + private readonly ILogger _logger = App.GetLogger(); + private DateTime? waitFishBiteTimeout; + private int _seconds; + + /// + /// 如果未超时返回运行中,超时返回失败 + /// + /// + /// + public FishBiteTimeout(string name, int seconds) : base(name) { - _switchBaitContinuouslyFrameNum = 0; - _waitBiteContinuouslyFrameNum = 0; - _noFishActionContinuouslyFrameNum = 0; - _throwRodWaitFrameNum = 0; - _isThrowRod = false; + _seconds = seconds; + } + protected override void OnInitialize() + { + waitFishBiteTimeout = DateTime.Now.AddSeconds(_seconds); + } + protected override BehaviourStatus Update(CaptureContent context) + { + if (DateTime.Now >= waitFishBiteTimeout) + { + _logger.LogInformation($"{_seconds}秒没有咬杆,本次收杆"); + Simulation.SendInput.Mouse.LeftButtonClick(); + Thread.Sleep(1000); + return BehaviourStatus.Failed; + } + else + { + return BehaviourStatus.Running; + } } } + private BehaviourStatus ThrowRod2(CaptureContent content) + { + //var baitRectArea = content.CaptureRectArea.Find(_autoFishingAssets.BaitButtonRo); + //var waitBiteArea = content.CaptureRectArea.Find(_autoFishingAssets.WaitBiteButtonRo); + //if (baitRectArea.IsEmpty() && !waitBiteArea.IsEmpty() && _isThrowRod) + //{ + // _switchBaitContinuouslyFrameNum = 0; + // _waitBiteContinuouslyFrameNum++; + // _noFishActionContinuouslyFrameNum = 0; + // _throwRodWaitFrameNum++; + + // if (_waitBiteContinuouslyFrameNum >= content.FrameRate) + // { + // _isThrowRod = true; + // _waitBiteContinuouslyFrameNum = 0; + // } + + // if (_isThrowRod) + // { + // // 30s 没有上钩,重新抛竿 + // if (_throwRodWaitFrameNum >= content.FrameRate * TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodTimeOut) + // { + // Simulation.SendInput.Mouse.LeftButtonClick(); + // _throwRodWaitFrameNum = 0; + // _waitBiteContinuouslyFrameNum = 0; + // Debug.WriteLine("超时自动收竿"); + // Sleep(2000); + // _isThrowRod = false; + // return BehaviourStatus.Failed; + // } + // return BehaviourStatus.Succeeded; + // } + //} + + //if (baitRectArea.IsEmpty() && waitBiteArea.IsEmpty()) + //{ + // _switchBaitContinuouslyFrameNum = 0; + // _waitBiteContinuouslyFrameNum = 0; + // _noFishActionContinuouslyFrameNum++; + // if (_noFishActionContinuouslyFrameNum > content.FrameRate) + // { + // CheckFishingUserInterface(content); + // } + // return BehaviourStatus.Failed; + //} + return BehaviourStatus.Succeeded; + } + + /// + /// 打开换饵界面 + /// + /// + /// + private BehaviourStatus OpenChooseBaitUI(CaptureContent content) + { + _logger.LogInformation("打开换饵界面"); + Simulation.SendInput.Mouse.RightButtonClick(); + Sleep(100); + Simulation.SendInput.Mouse.MoveMouseBy(0, 200); // 鼠标移走,防止干扰 + Sleep(100); + return BehaviourStatus.Succeeded; + } + private string _selectedBaitName = string.Empty; + private Fishpond fishpond; + private DateTime? ChooseBaitUIOpenWaitEndTime; // 等待选鱼饵界面出现的结束时间 /// /// 选择鱼饵 /// - /// - /// - /// - /// - private string ChooseBait(CaptureContent content, Fishpond fishpond) + private class ChooseBait : BaseBehaviour { - // 打开换饵界面 - Simulation.SendInput.Mouse.RightButtonClick(); - Sleep(100); - Simulation.SendInput.Mouse.MoveMouseBy(0, 200); // 鼠标移走,防止干扰 - Sleep(500); + private readonly ILogger _logger = App.GetLogger(); + private readonly AutoFishingTrigger _autoFishingTrigger; - _selectedBaitName = fishpond.Fishes[0].FishType.BaitName; // 选择最多鱼吃的饵料 - _logger.LogInformation("选择鱼饵 {Text}", BaitType.FromName(_selectedBaitName).ChineseName); - - // 寻找鱼饵 - var ro = new RecognitionObject + /// + /// 选择鱼饵 + /// + /// + /// + public ChooseBait(string name, AutoFishingTrigger autoFishingTrigger) : base(name) { - Name = "ChooseBait", - RecognitionType = RecognitionTypes.TemplateMatch, - TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFishing", $"bait\\{_selectedBaitName}.png"), - Threshold = 0.8, - Use3Channels = true, - DrawOnWindow = false - }.InitTemplate(); - - // 截图 - using var captureRegion = TaskControl.CaptureToRectArea(forceNew: true); - using var resRa = captureRegion.Find(ro); - if (resRa.IsEmpty()) - { - _logger.LogWarning("没有找到目标鱼饵"); - _selectedBaitName = string.Empty; - throw new Exception("没有找到目标鱼饵"); - } - else - { - resRa.Click(); - Sleep(700); - // 可能重复点击,所以固定界面点击下 - captureRegion.ClickTo((int)(captureRegion.Width * 0.675), (int)(captureRegion.Height / 3d)); - Sleep(200); - // 点击确定 - Bv.ClickWhiteConfirmButton(captureRegion); - Sleep(500); // 等待界面切换 + _autoFishingTrigger = autoFishingTrigger; } - return _selectedBaitName; + protected override void OnInitialize() + { + _autoFishingTrigger.ChooseBaitUIOpenWaitEndTime = DateTime.Now.AddSeconds(5); + } + + protected override BehaviourStatus Update(CaptureContent content) + { + if (this.Status == BehaviourStatus.Ready) + { + return BehaviourStatus.Running; + } + + _autoFishingTrigger._selectedBaitName = _autoFishingTrigger.fishpond.Fishes[0].FishType.BaitName; // 选择最多鱼吃的饵料 + _logger.LogInformation("选择鱼饵 {Text}", BaitType.FromName(_autoFishingTrigger._selectedBaitName).ChineseName); + + // 寻找鱼饵 + var ro = new RecognitionObject + { + Name = "ChooseBait", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFishing", $"bait\\{_autoFishingTrigger._selectedBaitName}.png"), + Threshold = 0.8, + Use3Channels = true, + DrawOnWindow = false + }.InitTemplate(); + + var captureRegion = content.CaptureRectArea; + using var resRa = captureRegion.Find(ro); + if (resRa.IsEmpty()) + { + if (DateTime.Now >= _autoFishingTrigger.ChooseBaitUIOpenWaitEndTime) + { + _logger.LogWarning("没有找到目标鱼饵"); + _autoFishingTrigger._selectedBaitName = string.Empty; + throw new Exception("没有找到目标鱼饵"); + } + else + { + return BehaviourStatus.Running; + } + } + else + { + resRa.Click(); + _autoFishingTrigger.Sleep(700); + // 可能重复点击,所以固定界面点击下 + captureRegion.ClickTo((int)(captureRegion.Width * 0.675), (int)(captureRegion.Height / 3d)); + _autoFishingTrigger.Sleep(200); + // 点击确定 + Bv.ClickWhiteConfirmButton(captureRegion); + _autoFishingTrigger.Sleep(500); // 等待界面切换 + } + + return BehaviourStatus.Succeeded; + } } private readonly Random _rd = new(); /// - /// 抛竿 + /// 长按预抛竿 /// /// - private void ApproachFishAndThrowRod(CaptureContent content) + private BehaviourStatus ApproachFishAndThrowRod0(CaptureContent content) { - // 预抛竿 + noPlacementTimes = 0; + noTargetFishTimes = 0; + Simulation.SendInput.Mouse.LeftButtonDown(); _logger.LogInformation("长按预抛竿"); Sleep(3000); - var noPlacementTimes = 0; // 没有落点的次数 - var noTargetFishTimes = 0; // 没有目标鱼的次数 + return BehaviourStatus.Succeeded; + } + + private bool noTargetFish; + + private int noPlacementTimes; // 没有落点的次数 + private int noTargetFishTimes; // 没有目标鱼的次数 + /// + /// 抛竿 + /// + /// + private BehaviourStatus ApproachFishAndThrowRod1(CaptureContent content) + { + noTargetFish = false; var prevTargetFishRect = Rect.Empty; // 记录上一个目标鱼的位置 - while (IsEnabled) + var ra = content.CaptureRectArea; + + // 找 鱼饵落点 + using var memoryStream = new MemoryStream(); + ra.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); + memoryStream.Seek(0, SeekOrigin.Begin); + var result = _predictor.Detect(memoryStream); + Debug.WriteLine($"YOLOv8识别: {result.Speed}"); + var fishpond = new Fishpond(result); + if (fishpond.TargetRect == Rect.Empty) { - // 截图 - var ra = TaskControl.CaptureToRectArea(forceNew: true); + noPlacementTimes++; + Sleep(50); + Debug.WriteLine("历次未找到鱼饵落点"); - // 找 鱼饵落点 - using var memoryStream = new MemoryStream(); - ra.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); - memoryStream.Seek(0, SeekOrigin.Begin); - var result = _predictor.Detect(memoryStream); - Debug.WriteLine($"YOLOv8识别: {result.Speed}"); - var fishpond = new Fishpond(result); - if (fishpond.TargetRect == Rect.Empty) + 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) { - noPlacementTimes++; - Sleep(50); - Debug.WriteLine("历次未找到鱼饵落点"); - - var cX = ra.SrcBitmap.Width / 2; - var cY = ra.SrcBitmap.Height / 2; - var rdX = _rd.Next(0, ra.SrcBitmap.Width); - var rdY = _rd.Next(0, ra.SrcBitmap.Height); - - var moveX = 100 * (cX - rdX) / ra.SrcBitmap.Width; - var moveY = 100 * (cY - rdY) / ra.SrcBitmap.Height; - - Simulation.SendInput.Mouse.MoveMouseBy(moveX, moveY); - - if (noPlacementTimes > 25) - { - _logger.LogInformation("未找到鱼饵落点,重试"); - // Simulation.SendInputEx.Mouse.LeftButtonUp(); - // // Sleep(2000); - // // Simulation.SendInputEx.Mouse.LeftButtonClick(); - // _selectedBaitName = string.Empty; - // _isThrowRod = false; - // // Sleep(2000); - // // MoveViewpointDown(); - // Sleep(300); - break; - } - - continue; + _logger.LogInformation("未找到鱼饵落点,重试"); + // Simulation.SendInputEx.Mouse.LeftButtonUp(); + // // Sleep(2000); + // // Simulation.SendInputEx.Mouse.LeftButtonClick(); + // _selectedBaitName = string.Empty; + // _isThrowRod = false; + // // Sleep(2000); + // // MoveViewpointDown(); + // Sleep(300); + return BehaviourStatus.Failed; } - // 找到落点最近的鱼 - OneFish? currentFish = null; - if (prevTargetFishRect == Rect.Empty) - { - var list = fishpond.FilterByBaitName(_selectedBaitName); - if (list.Count > 0) - { - currentFish = list[0]; - prevTargetFishRect = currentFish.Rect; - } - } - else - { - currentFish = fishpond.FilterByBaitNameAndRecently(_selectedBaitName, prevTargetFishRect); - if (currentFish != null) - { - prevTargetFishRect = currentFish.Rect; - } - } - - if (currentFish == null) - { - Debug.WriteLine("无目标鱼"); - noTargetFishTimes++; - //if (noTargetFishTimes == 30) - //{ - // Simulation.SendInputEx.Mouse.MoveMouseBy(0, 100); - //} - - if (noTargetFishTimes > 10) - { - // 没有找到目标鱼,重新选择鱼饵 - _logger.LogInformation("没有找到目标鱼,1.直接抛竿"); - Simulation.SendInput.Mouse.LeftButtonUp(); - Sleep(1500); - _logger.LogInformation("没有找到目标鱼,2.收杆"); - Simulation.SendInput.Mouse.LeftButtonClick(); - Sleep(800); - _logger.LogInformation("没有找到目标鱼,3.准备重新选择鱼饵"); - _selectedBaitName = string.Empty; - _isThrowRod = false; - MoveViewpointDown(); - Sleep(300); - break; - } - - continue; - } - else - { - noTargetFishTimes = 0; - _currContent.CaptureRectArea.DrawRect(fishpond.TargetRect, "Target"); - _currContent.CaptureRectArea.Derive(currentFish.Rect).DrawSelf("Fish"); - // VisionContext.Instance().DrawContent.PutRect("Target", fishpond.TargetRect.ToRectDrawable()); - // VisionContext.Instance().DrawContent.PutRect("Fish", currentFish.Rect.ToRectDrawable()); - - // var min = MoveMouseToFish(fishpond.TargetRect, currentFish.Rect); - // // 因为视角是斜着看向鱼的,所以Y轴抛竿距离要近一点 - // if ((_selectedBaitName != "fruit paste bait" && min is { Item1: <= 50, Item2: <= 25 }) - // || _selectedBaitName == "fruit paste bait" && min is { Item1: <= 40, Item2: <= 25 }) - // { - // Sleep(100); - // Simulation.SendInputEx.Mouse.LeftButtonUp(); - // _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); - // _isThrowRod = true; - // VisionContext.Instance().DrawContent.RemoveRect("Target"); - // VisionContext.Instance().DrawContent.RemoveRect("Fish"); - // break; - // } - - // 来自 HutaoFisher 的抛竿技术 - var rod = fishpond.TargetRect; - var fish = currentFish.Rect; - var dx = NormalizeXTo1024(fish.Left + fish.Right - rod.Left - rod.Right) / 2.0; - var dy = NormalizeYTo576(fish.Top + fish.Bottom - rod.Top - rod.Bottom) / 2.0; - var state = RodNet.GetRodState(new RodInput - { - rod_x1 = NormalizeXTo1024(rod.Left), - rod_x2 = NormalizeXTo1024(rod.Right), - rod_y1 = NormalizeYTo576(rod.Top), - rod_y2 = NormalizeYTo576(rod.Bottom), - fish_x1 = NormalizeXTo1024(fish.Left), - fish_x2 = NormalizeXTo1024(fish.Right), - fish_y1 = NormalizeYTo576(fish.Top), - fish_y2 = NormalizeYTo576(fish.Bottom), - fish_label = BigFishType.GetIndex(currentFish.FishType) - }); - if (state == -1) - { - // 失败 随机移动鼠标 - var cX = content.CaptureRectArea.SrcBitmap.Width / 2; - var cY = content.CaptureRectArea.SrcBitmap.Height / 2; - var rdX = _rd.Next(0, content.CaptureRectArea.SrcBitmap.Width); - var rdY = _rd.Next(0, content.CaptureRectArea.SrcBitmap.Height); - - var moveX = 100 * (cX - rdX) / content.CaptureRectArea.SrcBitmap.Width; - var moveY = 100 * (cY - rdY) / content.CaptureRectArea.SrcBitmap.Height; - - _logger.LogInformation("失败 随机移动 {DX}, {DY}", moveX, moveY); - Simulation.SendInput.Mouse.MoveMouseBy(moveX, moveY); - } - else if (state == 0) - { - // 成功 抛竿 - Simulation.SendInput.Mouse.LeftButtonUp(); - _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); - _isThrowRod = true; - VisionContext.Instance().DrawContent.RemoveRect("Target"); - VisionContext.Instance().DrawContent.RemoveRect("Fish"); - break; - } - else if (state == 1) - { - // 太近 - var dl = Math.Sqrt(dx * dx + dy * dy); - // set a minimum step - dx = dx / dl * 30; - dy = dy / dl * 30; - // _logger.LogInformation("太近 移动 {DX}, {DY}", dx, dy); - Simulation.SendInput.Mouse.MoveMouseBy((int)(-dx / 1.5), (int)(-dy * 1.5)); - } - else if (state == 2) - { - // 太远 - // _logger.LogInformation("太远 移动 {DX}, {DY}", dx, dy); - Simulation.SendInput.Mouse.MoveMouseBy((int)(dx / 1.5), (int)(dy * 1.5)); - } - } - - Sleep(20); + return BehaviourStatus.Running; } + + // 找到落点最近的鱼 + OneFish? currentFish = null; + if (prevTargetFishRect == Rect.Empty) + { + var list = fishpond.FilterByBaitName(_selectedBaitName); + if (list.Count > 0) + { + currentFish = list[0]; + prevTargetFishRect = currentFish.Rect; + } + } + else + { + currentFish = fishpond.FilterByBaitNameAndRecently(_selectedBaitName, prevTargetFishRect); + if (currentFish != null) + { + prevTargetFishRect = currentFish.Rect; + } + } + + if (currentFish == null) + { + Debug.WriteLine("无目标鱼"); + noTargetFishTimes++; + //if (noTargetFishTimes == 30) + //{ + // Simulation.SendInputEx.Mouse.MoveMouseBy(0, 100); + //} + + if (noTargetFishTimes > 10) + { + // 没有找到目标鱼,重新选择鱼饵 + _logger.LogInformation("没有找到目标鱼,1.直接抛竿"); + Simulation.SendInput.Mouse.LeftButtonUp(); + Sleep(1500); + _logger.LogInformation("没有找到目标鱼,2.收杆"); + Simulation.SendInput.Mouse.LeftButtonClick(); + Sleep(800); + _logger.LogInformation("没有找到目标鱼,3.准备重新选择鱼饵"); + _selectedBaitName = string.Empty; + _isThrowRod = false; + MoveViewpointDown(); + Sleep(300); + + noTargetFish = true; + return BehaviourStatus.Succeeded; + } + + return BehaviourStatus.Running; + } + else + { + noTargetFishTimes = 0; + _currContent.CaptureRectArea.DrawRect(fishpond.TargetRect, "Target"); + _currContent.CaptureRectArea.Derive(currentFish.Rect).DrawSelf("Fish"); + + // VisionContext.Instance().DrawContent.PutRect("Target", fishpond.TargetRect.ToRectDrawable()); + // VisionContext.Instance().DrawContent.PutRect("Fish", currentFish.Rect.ToRectDrawable()); + + // var min = MoveMouseToFish(fishpond.TargetRect, currentFish.Rect); + // // 因为视角是斜着看向鱼的,所以Y轴抛竿距离要近一点 + // if ((_selectedBaitName != "fruit paste bait" && min is { Item1: <= 50, Item2: <= 25 }) + // || _selectedBaitName == "fruit paste bait" && min is { Item1: <= 40, Item2: <= 25 }) + // { + // Sleep(100); + // Simulation.SendInputEx.Mouse.LeftButtonUp(); + // _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); + // _isThrowRod = true; + // VisionContext.Instance().DrawContent.RemoveRect("Target"); + // VisionContext.Instance().DrawContent.RemoveRect("Fish"); + // break; + // } + + // 来自 HutaoFisher 的抛竿技术 + var rod = fishpond.TargetRect; + var fish = currentFish.Rect; + var dx = NormalizeXTo1024(fish.Left + fish.Right - rod.Left - rod.Right) / 2.0; + var dy = NormalizeYTo576(fish.Top + fish.Bottom - rod.Top - rod.Bottom) / 2.0; + var state = RodNet.GetRodState(new RodInput + { + rod_x1 = NormalizeXTo1024(rod.Left), + rod_x2 = NormalizeXTo1024(rod.Right), + rod_y1 = NormalizeYTo576(rod.Top), + rod_y2 = NormalizeYTo576(rod.Bottom), + fish_x1 = NormalizeXTo1024(fish.Left), + fish_x2 = NormalizeXTo1024(fish.Right), + fish_y1 = NormalizeYTo576(fish.Top), + fish_y2 = NormalizeYTo576(fish.Bottom), + fish_label = BigFishType.GetIndex(currentFish.FishType) + }); + if (state == -1) + { + // 失败 随机移动鼠标 + var cX = content.CaptureRectArea.SrcBitmap.Width / 2; + var cY = content.CaptureRectArea.SrcBitmap.Height / 2; + var rdX = _rd.Next(0, content.CaptureRectArea.SrcBitmap.Width); + var rdY = _rd.Next(0, content.CaptureRectArea.SrcBitmap.Height); + + var moveX = 100 * (cX - rdX) / content.CaptureRectArea.SrcBitmap.Width; + var moveY = 100 * (cY - rdY) / content.CaptureRectArea.SrcBitmap.Height; + + _logger.LogInformation("失败 随机移动 {DX}, {DY}", moveX, moveY); + Simulation.SendInput.Mouse.MoveMouseBy(moveX, moveY); + } + else if (state == 0) + { + // 成功 抛竿 + Simulation.SendInput.Mouse.LeftButtonUp(); + _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); + _isThrowRod = true; + VisionContext.Instance().DrawContent.RemoveRect("Target"); + VisionContext.Instance().DrawContent.RemoveRect("Fish"); + return BehaviourStatus.Succeeded; + } + else if (state == 1) + { + // 太近 + var dl = Math.Sqrt(dx * dx + dy * dy); + // set a minimum step + dx = dx / dl * 30; + dy = dy / dl * 30; + // _logger.LogInformation("太近 移动 {DX}, {DY}", dx, dy); + Simulation.SendInput.Mouse.MoveMouseBy((int)(-dx / 1.5), (int)(-dy * 1.5)); + } + else if (state == 2) + { + // 太远 + // _logger.LogInformation("太远 移动 {DX}, {DY}", dx, dy); + Simulation.SendInput.Mouse.MoveMouseBy((int)(dx / 1.5), (int)(dy * 1.5)); + } + } + Sleep(50); + return BehaviourStatus.Running; + } + + /// + /// 缺鱼检查 + /// + /// + private BehaviourStatus NoTargetFishCheck(CaptureContent content) + { + return noTargetFish ? BehaviourStatus.Failed : BehaviourStatus.Succeeded; } private double NormalizeXTo1024(int x) @@ -734,7 +855,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _logger.LogWarning("当前获取焦点的窗口不是原神,暂停"); throw new RetryException("当前获取焦点的窗口不是原神"); } - + }, TimeSpan.FromSeconds(1), 100); CheckFishingUserInterface(_currContent); Thread.Sleep(millisecondsTimeout); @@ -802,13 +923,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// 自动提竿 /// /// - private void FishBite(CaptureContent content) + private BehaviourStatus FishBite(CaptureContent content) { - if (_isFishingProcess) - { - return; - } - + _logger.LogInformation("等待提竿"); // 自动识别的钓鱼框向下延伸到屏幕中间 //var liftingWordsAreaRect = new Rect(fishBoxRect.X, fishBoxRect.Y + fishBoxRect.Height * 2, // fishBoxRect.Width, content.CaptureRectArea.SrcMat.Height / 2 - fishBoxRect.Y - fishBoxRect.Height * 5); @@ -852,7 +969,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _biteTipsExitCount = 0; _baseBiteTips = Rect.Empty; VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); - return; + return BehaviourStatus.Succeeded; } // OCR 提竿判断 @@ -869,6 +986,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _biteTipsExitCount = 0; _baseBiteTips = Rect.Empty; VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); + return BehaviourStatus.Succeeded; } } } @@ -888,9 +1006,33 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _biteTipsExitCount = 0; _baseBiteTips = Rect.Empty; VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); + return BehaviourStatus.Succeeded; } } } + + return BehaviourStatus.Running; + } + + /// + /// 进入钓鱼界面先尝试获取钓鱼框的位置 + /// + /// + /// + private BehaviourStatus Wait4FishBoxAreaAppear(CaptureContent content) + { + _logger.LogInformation("寻找拉条中"); + if ((DateTime.Now - _prevExecute).TotalMilliseconds <= 200) + { + return BehaviourStatus.Running; + } + + _prevExecute = DateTime.Now; + + _fishBoxRect = GetFishBoxArea(content.CaptureRectArea); + CheckFishingUserInterface(content); + + return _fishBoxRect == Rect.Empty ? BehaviourStatus.Running : BehaviourStatus.Succeeded; } private int _noRectsCount = 0; @@ -903,8 +1045,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// /// /// - private void Fishing(CaptureContent content, Mat fishBarMat) + private BehaviourStatus Fishing(CaptureContent content) { + var fishBarMat = new Mat(content.CaptureRectArea.SrcMat, _fishBoxRect); var simulator = Simulation.SendInput; var rects = AutoFishingImageRecognition.GetFishBarRect(fishBarMat); if (rects != null && rects.Count > 0) @@ -1014,6 +1157,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing MoveViewpointDown(); Sleep(500); + + return BehaviourStatus.Succeeded; } CheckFishingUserInterface(content); @@ -1036,13 +1181,15 @@ namespace BetterGenshinImpact.GameTask.AutoFishing { _notFishingAfterBiteCount = 0; } + + return BehaviourStatus.Running; } /// /// 检查是否退出钓鱼界面 /// /// - private void CheckFishingUserInterface(CaptureContent content) + private BehaviourStatus CheckFishingUserInterface(CaptureContent content) { var prevIsExclusive = IsExclusive; IsExclusive = FindButtonForExclusive(content); @@ -1060,6 +1207,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _noFishActionContinuouslyFrameNum = 0; _isThrowRod = false; _selectedBaitName = string.Empty; + + return BehaviourStatus.Succeeded; } else if (prevIsExclusive && !IsExclusive) { @@ -1067,6 +1216,12 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _isThrowRod = false; _fishBoxRect = Rect.Empty; VisionContext.Instance().DrawContent.ClearAll(); + + return BehaviourStatus.Failed; + } + else + { + return BehaviourStatus.Running; } } @@ -1110,5 +1265,115 @@ namespace BetterGenshinImpact.GameTask.AutoFishing //{ // ClearDraw(); //} + + } + + /// + /// MySimpleParallel + /// 和SimpleParallel的区别是,任一子行为返回失败则返回失败 + /// + /// + public class MySimpleParallel : CompositeBehaviour + { + private readonly IBehaviour _first; + + private readonly IBehaviour _second; + + private BehaviourStatus _firstStatus; + + private BehaviourStatus _secondStatus; + + private readonly Func _behave; + + public readonly SimpleParallelPolicy Policy; + + public MySimpleParallel(SimpleParallelPolicy policy, IBehaviour first, IBehaviour second) + : this("SimpleParallel", policy, first, second) + { + } + + public MySimpleParallel(string name, SimpleParallelPolicy policy, IBehaviour first, IBehaviour second) + : base(name, new IBehaviour[2] { first, second }) + { + Policy = policy; + _first = first; + _second = second; + _behave = ((policy == SimpleParallelPolicy.BothMustSucceed) ? new Func(BothMustSucceedBehaviour) : new Func(OnlyOneMustSucceedBehaviour)); + } + + private BehaviourStatus OnlyOneMustSucceedBehaviour(TContext context) + { + if (_firstStatus == BehaviourStatus.Succeeded || _secondStatus == BehaviourStatus.Succeeded) + { + return BehaviourStatus.Succeeded; + } + + if (_firstStatus == BehaviourStatus.Failed && _secondStatus == BehaviourStatus.Failed) + { + return BehaviourStatus.Failed; + } + + return BehaviourStatus.Running; + } + + private BehaviourStatus BothMustSucceedBehaviour(TContext context) + { + if (_firstStatus == BehaviourStatus.Succeeded && _secondStatus == BehaviourStatus.Succeeded) + { + return BehaviourStatus.Succeeded; + } + + if (_firstStatus == BehaviourStatus.Failed || _secondStatus == BehaviourStatus.Failed) + { + return BehaviourStatus.Failed; + } + + return BehaviourStatus.Running; + } + + protected override BehaviourStatus Update(TContext context) + { + if (base.Status != BehaviourStatus.Running) + { + _firstStatus = _first.Tick(context); + _secondStatus = _second.Tick(context); + } + else + { + if (_firstStatus == BehaviourStatus.Ready || _firstStatus == BehaviourStatus.Running) + { + _firstStatus = _first.Tick(context); + } + + if (_secondStatus == BehaviourStatus.Ready || _secondStatus == BehaviourStatus.Running) + { + _secondStatus = _second.Tick(context); + } + } + + if (_firstStatus == BehaviourStatus.Failed || _secondStatus == BehaviourStatus.Failed) + { + return BehaviourStatus.Failed; + } + else + { + return _behave(context); + } + } + + protected override void DoReset(BehaviourStatus status) + { + _firstStatus = BehaviourStatus.Ready; + _secondStatus = BehaviourStatus.Ready; + base.DoReset(status); + } + } + + public static class FluentBuilderExtensions + { + public static FluentBuilder MySimpleParallel(this FluentBuilder builder, string name, SimpleParallelPolicy policy = SimpleParallelPolicy.BothMustSucceed) + { + return builder.PushComposite((IBehaviour[] children) => new MySimpleParallel(name, policy, children[0], children[1])); + } } } From 3c84f3817d9e59d29fd6afb5fd2fe302bfb05979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Fri, 24 Jan 2025 22:48:25 +0800 Subject: [PATCH 2/8] auto tcg: fix error --- .../AutoGeniusInvokationTask.cs | 18 ++++++++++++++---- .../AutoGeniusInvokation/ScriptParser.cs | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/AutoGeniusInvokationTask.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/AutoGeniusInvokationTask.cs index f2610997..731cd993 100644 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/AutoGeniusInvokationTask.cs +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/AutoGeniusInvokationTask.cs @@ -1,5 +1,7 @@ using System.Threading; using System.Threading.Tasks; +using BetterGenshinImpact.GameTask.Common; +using Microsoft.Extensions.Logging; namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation; @@ -9,9 +11,17 @@ public class AutoGeniusInvokationTask(GeniusInvokationTaskParam taskParam) : ISo public Task Start(CancellationToken ct) { - // 读取策略信息 - var duel = ScriptParser.Parse(taskParam.StrategyContent); - duel.Run(ct); + try + { + // 读取策略信息 + var duel = ScriptParser.Parse(taskParam.StrategyContent); + duel.Run(ct); + } + catch (System.Exception e) + { + TaskControl.Logger.LogDebug(e, "执行自动七圣召唤任务异常"); + TaskControl.Logger.LogError("执行自动七圣召唤任务异常:{Exception}", e.Message); + } return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/ScriptParser.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/ScriptParser.cs index 11fe0f92..fb5cdb37 100644 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/ScriptParser.cs +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/ScriptParser.cs @@ -117,7 +117,7 @@ public class ScriptParser var characterAndSkill = line.Split('{'); var parts = characterAndSkill[0].Split('='); - character.Index = int.Parse(RegexHelper.ExcludeNumberRegex().Replace(parts[0], "")); + character.Index = int.Parse(RegexHelper.ExcludeNumberRegex().Replace(parts[0].Trim(), "")); MyAssert(character.Index >= 1 && character.Index <= 3, "角色序号必须在1-3之间"); if (parts[1].Contains('|')) From 987e723b8c2d62adbd8a85f81eb6510dd019f07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 25 Jan 2025 10:31:30 +0800 Subject: [PATCH 3/8] BetterGI.exe startOneDragon --- BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs index 9bfaab2b..8054dcd5 100644 --- a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs @@ -102,6 +102,11 @@ public partial class HomePageViewModel : ObservableObject, INavigationAware, IVi { _ = OnStartTriggerAsync(); } + else if (args[1].Contains("startOneDragon")) + { + var odVm = App.GetService(); + odVm?.OneKeyExecuteCommand.Execute(null); + } } } From bb692a476e24cb4cd418c6fbff617e71b425d47b Mon Sep 17 00:00:00 2001 From: FishmanTheMurloc Date: Sat, 25 Jan 2025 13:06:04 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BA=B3=E5=A1=94?= =?UTF-8?q?=E9=B1=BC=E5=92=8C=E9=B1=BC=E9=A5=B5=EF=BC=9B=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=B1=BC=E5=A4=A7=E7=B1=BBray=E4=BB=A5=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E4=B8=A4=E7=A7=8D=E7=BB=86=E5=88=86=E7=9A=84formalo=20ray?= =?UTF-8?q?=E5=92=8Cdivda=20ray?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assets/1920x1080/bait/emberglow bait.png | Bin 0 -> 8190 bytes .../Assets/1920x1080/bait/spinelgrain bait.png | Bin 0 -> 8149 bytes .../GameTask/AutoFishing/Model/BaitType.cs | 4 ++++ .../GameTask/AutoFishing/Model/BigFishType.cs | 14 +++++++++++++- 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 BetterGenshinImpact/GameTask/AutoFishing/Assets/1920x1080/bait/emberglow bait.png create mode 100644 BetterGenshinImpact/GameTask/AutoFishing/Assets/1920x1080/bait/spinelgrain bait.png diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Assets/1920x1080/bait/emberglow bait.png b/BetterGenshinImpact/GameTask/AutoFishing/Assets/1920x1080/bait/emberglow bait.png new file mode 100644 index 0000000000000000000000000000000000000000..f7b665c6bab7c26626c29ecadeb289e9e3d84311 GIT binary patch literal 8190 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

@p#AOL0$(30Xn{}w7`%9c3W!aNz+F-XF8BmB- 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 z Values { @@ -24,6 +26,8 @@ public class BaitType yield return SugardewBait; yield return SourBait; yield return FlashingMaintenanceMekBait; + yield return SpinelgrainBait; + yield return EmberglowBait; } } public string Name { get; private set; } diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs b/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs index 6af9af13..98ff1469 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs @@ -15,12 +15,18 @@ public class BigFishType public static readonly BigFishType Koi = new("koi", "fake fly bait", "假龙"); public static readonly BigFishType Butterflyfish = new("butterflyfish", "false worm bait", "蝶鱼"); public static readonly BigFishType Pufferfish = new("pufferfish", "fake fly bait", "炮鲀"); - public static readonly BigFishType FormaloRay = new("formalo ray", "fake fly bait", "佛玛洛鳐"); + public static readonly BigFishType Ray = new("ray", "fake fly bait", "鳐"); + public static readonly BigFishType FormaloRay = new("formalo ray", "fake fly bait", "佛玛洛鳐"); // todo 等模型更新后去掉两种细分的formalo ray和divda ray,仅保留ray public static readonly BigFishType DivdaRay = new("divda ray", "fake fly bait", "迪芙妲鳐"); public static readonly BigFishType Angler = new("angler", "sugardew bait", "角鲀"); public static readonly BigFishType AxeMarlin = new("axe marlin", "sugardew bait", "斧枪鱼"); public static readonly BigFishType HeartfeatherBass = new("heartfeather bass", "sour bait", "心羽鲈"); public static readonly BigFishType MaintenanceMek = new("maintenance mek", "flashing maintenance mek bait", "维护机关"); + public static readonly BigFishType Unihornfish = new("unihornfish", "spinelgrain bait", "独角鱼"); + public static readonly BigFishType Sunfish = new("sunfish", "spinelgrain bait", "翻车鲀"); + public static readonly BigFishType Rapidfish = new("rapidfish", "spinelgrain bait", "斗士急流鱼"); + public static readonly BigFishType PhonyUnihornfish = new("phony unihornfish", "emberglow bait", "燃素独角鱼"); + public static readonly BigFishType MagmaRapidfish = new("magma rapidfish", "emberglow bait", "炽岩斗士急流鱼"); public static IEnumerable Values @@ -33,12 +39,18 @@ public class BigFishType yield return Koi; yield return Butterflyfish; yield return Pufferfish; + yield return Ray; yield return FormaloRay; yield return DivdaRay; yield return Angler; yield return AxeMarlin; yield return HeartfeatherBass; yield return MaintenanceMek; + yield return Unihornfish; + yield return Sunfish; + yield return Rapidfish; + yield return PhonyUnihornfish; + yield return MagmaRapidfish; } } From 1d5f5c772e277e8f8f072389ec2577dab896c523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 25 Jan 2025 13:55:19 +0800 Subject: [PATCH 5/8] update ui --- .../Helpers/Ui/WindowHelper.cs | 39 +++++------ .../View/Pages/OneDragonFlowPage.xaml | 66 ++++++++++++++----- .../View/Pages/TaskSettingsPage.xaml | 4 +- .../ViewModel/MainWindowViewModel.cs | 47 ++++++------- .../ViewModel/Pages/OneDragonFlowViewModel.cs | 2 + .../Pages/TaskSettingsPageViewModel.cs | 5 +- 6 files changed, 100 insertions(+), 63 deletions(-) diff --git a/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs b/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs index 36cd3278..6d63962a 100644 --- a/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs +++ b/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs @@ -9,31 +9,32 @@ public class WindowHelper { public static void TryApplySystemBackdrop(System.Windows.Window window) { - if (WindowBackdrop.IsSupported(TaskContext.Instance().Config.CommonConfig.CurrentBackdropType)) + if (OsVersionHelper.IsWindows11_OrGreater) { - if (TaskContext.Instance().Config.CommonConfig.CurrentBackdropType == WindowBackdropType.Acrylic) + if (WindowBackdrop.IsSupported(TaskContext.Instance().Config.CommonConfig.CurrentBackdropType)) { - window.Background = new SolidColorBrush(Color.FromArgb(100, 0, 0, 0)); + if (TaskContext.Instance().Config.CommonConfig.CurrentBackdropType == WindowBackdropType.Acrylic) + { + window.Background = new SolidColorBrush(Color.FromArgb(100, 0, 0, 0)); + } + else + { + window.Background = new SolidColorBrush(Colors.Transparent); + } + + WindowBackdrop.ApplyBackdrop(window, TaskContext.Instance().Config.CommonConfig.CurrentBackdropType); + return; } - else + if (WindowBackdrop.IsSupported(WindowBackdropType.Mica)) { window.Background = new SolidColorBrush(Colors.Transparent); + WindowBackdrop.ApplyBackdrop(window, WindowBackdropType.Mica); + } + else if (WindowBackdrop.IsSupported(WindowBackdropType.Acrylic)) + { + window.Background = new SolidColorBrush(Color.FromArgb(100, 0, 0, 0)); + WindowBackdrop.ApplyBackdrop(window, WindowBackdropType.Acrylic); } - - WindowBackdrop.ApplyBackdrop(window, TaskContext.Instance().Config.CommonConfig.CurrentBackdropType); - return; - } - - - if (WindowBackdrop.IsSupported(WindowBackdropType.Mica)) - { - window.Background = new SolidColorBrush(Colors.Transparent); - WindowBackdrop.ApplyBackdrop(window, WindowBackdropType.Mica); - } - else if (WindowBackdrop.IsSupported(WindowBackdropType.Acrylic)) - { - window.Background = new SolidColorBrush(Color.FromArgb(100, 0, 0, 0)); - WindowBackdrop.ApplyBackdrop(window, WindowBackdropType.Acrylic); } } } \ No newline at end of file diff --git a/BetterGenshinImpact/View/Pages/OneDragonFlowPage.xaml b/BetterGenshinImpact/View/Pages/OneDragonFlowPage.xaml index 1fa53de7..37924e5c 100644 --- a/BetterGenshinImpact/View/Pages/OneDragonFlowPage.xaml +++ b/BetterGenshinImpact/View/Pages/OneDragonFlowPage.xaml @@ -18,9 +18,9 @@ FontFamily="{StaticResource TextThemeFontFamily}" Foreground="{DynamicResource TextFillColorPrimaryBrush}" mc:Ignorable="d"> - + - + @@ -156,10 +156,9 @@ --> - + - - + @@ -199,7 +198,7 @@ - + @@ -361,7 +360,7 @@ @@ -472,6 +471,41 @@ + + + + + + + + + + + + + + + + + + + + + - + @@ -527,13 +561,13 @@ TextWrapping="Wrap" /> - + diff --git a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml index 24815d17..a3bb2eb8 100644 --- a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml @@ -1038,14 +1038,14 @@ WindowBackdropType.Acrylic, @@ -105,9 +105,15 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel // 预热OCR await OcrPreheating(); - - // 首次运行自动初始化绑定 - InitKeyBinding(); + + // 首次运行 + if (Config.CommonConfig.IsFirstRun) + { + // 自动初始化键位绑定 + InitKeyBinding(); + Config.AutoFightConfig.TeamNames = ""; // 此配置以后无用 + Config.CommonConfig.IsFirstRun = false; + } // 检查更新 await App.GetService()!.CheckUpdateAsync(new UpdateOption()); @@ -122,28 +128,24 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel ScriptRepoUpdater.Instance.AutoUpdate(); } - + private void InitKeyBinding() { - if (Config.CommonConfig.IsFirstRun) + try { - try + var kbVm = App.GetService(); + if (kbVm != null) { - var kbVm = App.GetService(); - if (kbVm != null) - { - kbVm.FetchFromRegistryCommand.Execute(null); - } + kbVm.FetchFromRegistryCommand.Execute(null); } - catch (Exception e) - { - _logger.LogError("首次运行自动初始化按键绑定异常:" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message); - - MessageBox.Error("读取原神键位并设置键位绑定数据时发生异常:" + e.Message + ",后续可以手动设置"); - } - } - } + catch (Exception e) + { + _logger.LogError("首次运行自动初始化按键绑定异常:" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message); + + MessageBox.Error("读取原神键位并设置键位绑定数据时发生异常:" + e.Message + ",后续可以手动设置"); + } + } /** * 不同的安装目录处理 @@ -151,10 +153,10 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel */ private async Task Patch1() { - if (Directory.Exists(Global.Absolute("BetterGI")) + if (Directory.Exists(Global.Absolute("BetterGI")) // && File.Exists(Global.Absolute("BetterGI/BetterGI.exe")) && Directory.Exists(Global.Absolute("BetterGI/User")) - ) + ) { var res = await MessageBox.ShowAsync("检测到旧的 BetterGI 配置,是否迁移配置并清理旧目录?", "BetterGI", System.Windows.MessageBoxButton.YesNo, MessageBoxImage.Question); if (res == System.Windows.MessageBoxResult.Yes) @@ -165,7 +167,6 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel // 删除旧目录 DirectoryHelper.DeleteDirectoryRecursively(Global.Absolute("BetterGI")); } - } } diff --git a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs index c80f597e..7900d44d 100644 --- a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs @@ -62,6 +62,8 @@ public partial class OneDragonFlowViewModel : ObservableObject, INavigationAware [ObservableProperty] private List _domainNameList = ["", ..MapLazyAssets.Instance.DomainNameList]; + + public AllConfig Config { get; set; } = TaskContext.Instance().Config; public OneDragonFlowViewModel() { diff --git a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs index 1bb570b5..361e1b6f 100644 --- a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs @@ -98,9 +98,8 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw [ObservableProperty] private List _domainNameList; - - [ObservableProperty] - private List _artifactSalvageStarList = ["4", "3", "2", "1"]; + + public static List ArtifactSalvageStarList = ["4", "3", "2", "1"]; [ObservableProperty] private List _autoMusicLevelList = ["传说", "大师", "困难", "普通", "所有"]; From 1afeb0818a6b6b86039c046a52087a0aaa9227b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 25 Jan 2025 15:55:36 +0800 Subject: [PATCH 6/8] #974 --- BetterGenshinImpact/ViewModel/MainWindowViewModel.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs index 64a1810f..dcbb3988 100644 --- a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs @@ -100,11 +100,16 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel [RelayCommand] private async Task OnLoaded() { - // 自动处理目录配置 - await Patch1(); - // 预热OCR await OcrPreheating(); + + if (Environment.GetCommandLineArgs().Length > 1) + { + return; + } + + // 自动处理目录配置 + await Patch1(); // 首次运行 if (Config.CommonConfig.IsFirstRun) From 5259b1f35114597b624e5e13214c5c340a14fad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 25 Jan 2025 16:49:05 +0800 Subject: [PATCH 7/8] startOneDragon command line #998 --- .../ViewModel/Pages/HomePageViewModel.cs | 28 +++++++++---------- .../ViewModel/Pages/OneDragonFlowViewModel.cs | 1 + 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs index 8054dcd5..bad11fd6 100644 --- a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs @@ -94,26 +94,26 @@ public partial class HomePageViewModel : ObservableObject, INavigationAware, IVi } } }); - - var args = Environment.GetCommandLineArgs(); - if (args.Length > 1) - { - if (args[1].Contains("start")) - { - _ = OnStartTriggerAsync(); - } - else if (args[1].Contains("startOneDragon")) - { - var odVm = App.GetService(); - odVm?.OneKeyExecuteCommand.Execute(null); - } - } } [RelayCommand] private void OnLoaded() { // OnTest(); + + var args = Environment.GetCommandLineArgs(); + if (args.Length > 1) + { + if (args[1].Equals("start")) + { + _ = OnStartTriggerAsync(); + } + else if (args[1].Equals("startOneDragon")) + { + var odVm = App.GetService(); + odVm?.OneKeyExecuteCommand.Execute(null); + } + } } private void OnClosed() diff --git a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs index 7900d44d..088b8b85 100644 --- a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs @@ -85,6 +85,7 @@ public partial class OneDragonFlowViewModel : ObservableObject, INavigationAware } } }; + InitConfigList(); } public void OnNavigatedTo() From 4b65370e9fc8a347dcc4311cc3ff1b95d9c6a1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 25 Jan 2025 16:54:37 +0800 Subject: [PATCH 8/8] Revert #1059 --- .../BetterGenshinImpact.csproj | 1 - .../Assets/1920x1080/bait/emberglow bait.png | Bin 8190 -> 0 bytes .../1920x1080/bait/spinelgrain bait.png | Bin 8149 -> 0 bytes .../AutoFishing/AutoFishingTrigger.cs | 983 +++++++----------- .../GameTask/AutoFishing/Model/BaitType.cs | 4 - .../GameTask/AutoFishing/Model/BigFishType.cs | 14 +- 6 files changed, 360 insertions(+), 642 deletions(-) delete mode 100644 BetterGenshinImpact/GameTask/AutoFishing/Assets/1920x1080/bait/emberglow bait.png delete mode 100644 BetterGenshinImpact/GameTask/AutoFishing/Assets/1920x1080/bait/spinelgrain bait.png diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index ec4eb95b..6edc2fa6 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -41,7 +41,6 @@ - diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Assets/1920x1080/bait/emberglow bait.png b/BetterGenshinImpact/GameTask/AutoFishing/Assets/1920x1080/bait/emberglow bait.png deleted file mode 100644 index f7b665c6bab7c26626c29ecadeb289e9e3d84311..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8190 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

@p#AOL0$(30Xn{}w7`%9c3W!aNz+F-XF8BmB- zma|fO1To&1G=TsIj`(!Q6=NuI-J*FyWdFsZ$LaNL@*j6CMRC^eOV{4o5_iD`rge)iiEB^uwqH;6Jde+Ewr`T9|2*~5ohEafb+XZi zE0tZZ=F}AZw)rr}lfBG0TN_Pdr`ndLKgUE79@N2F`noE18n3VVjzD4_*Q5FKmL3=pwTQdqY)<3HxE#f_;fRVq-MR_zeBe+Mt|!917s1=Z_*S98SA zzPy3?Cg`vhw)v$^)mu9*7_^USg*qsqL{7#N+1YgWm<`A=F?8h>%ZP9!Zj6>>|ExHF z0+hA}r=7=Wm$)|(-$gHX93v(AZF7bn4B9M6 z%DkkR{_JuKqc{3lrHHjtp3^E>kD#_@&5zwun`iQ{(VM|e@vsQ1UrE4m@bKr-cU~AD zk#0B$Y%Nr+W9Sf32{B*B0iX;^o!m6og95$?N~vwrN`!@`A-825n(@JLCBcErJTvLE z($~a~?`8GyYqq%!B=AdZe2*nt4a0V#_opo922n;?CvY*pplciTm%p(_l^c6WpCMP1 zYcU|qgSwM8-nMyeq{iH0@ilglZ>>!KmqiC^C21Yu`}C={nj}??k4iniOkbsl>X)J% zgwi~$@@G0Z%~=vCbUxJUFkGxJyvQQY71{WGsX)6{fYsxv~0e97Hecf<*sODvZE?HnoS0P9dhG05K-zeYE zvwB@8?>+j_b1JFDYj2&ChunfMEhwX$F}@17*~)a|EeR^7%2=i5z=U^zoD@r zw@f`@-KHmijb{V}n)!E2PJr{4Z=@_|zv>8_NJz(ox85&i>XzaQJZ((d*+43t{>Hdn ze|V2J`Aw|&@`%$HT*}Q1+y{39y30`>p$yL2S4rpJ@~B?aSvp~xR7cL1CKmE!)EdG? z=grVx*IvknlJh`PYUWkYwcql%L>bI9 zNE|Hw&mM1HLdSLC6+~n@NHxuzuo}foW(}w%7R%ZuD&V4a6#a$QeihH@jd@pSsB>cnKnpo;^tQDB!xrA&hn3QY_e*Msy`N{RW3F+ks>KHK(U+S+;(BJ@7Ut6t(TSkjH(N$1GgbN=<}?DFE~+V(;DK4fH0TKq;=#?W0Q?G=HWw7Hl9!fny- zjQT*rcSU+v5+1zy1W35?mtO{ltV%vU0h$rfj*v*-jH`sLTLs|w-lI)IbL4jE!TO0# zT^+icb~~k_@iHR&MORAv$-h22P!cb00@~6B0$!l#Q*lp2LGG8(B^iG?RyWi7?`y(l~G5WE%eVp zpM`eaV$|8gvzO4PHKtQhv94I9ao-HrFqa9vATffJ@~ddysnGk#6IN+V#Sd|2eIUKS z`{%nRXtEC-0c6PBk#BaBd`?;VC2*ke!RmBNzE(>D=-EL^iA0v$O*egar$Wq=)o9}& zlBGot5dTL_+;y>dNu$npudDC~!>4X|$JowJ?V)cHS>NX-vh8*cgM!fk_ z)NoKKK{fN`3vys`imBqznM|}a9V-_&OKXxj&Yr#0rXGmvFf=xsbW{zJxx4eBvp1%; z<)nr&5n*BX3+dR@P6)Lf6kjTpcanGDF$^fTq~fA8gso`)+nLhi)B`F}Cn09Dn61WQ zR7`R4hqroz>DX7O9Zf!5}%d*CP~$6V!|>2^5UVdr^ymMc);4U0L83Rx8< zg9=0`s6A+%i7Rx$Y54%r)ZOn%Is6Z_AO2=AcC;&{E1|SUaMPR(EBS7;YH4_D*D6Km zU}GGIl&-nBOOHF>igHVdTgN?*^DckRz^t1igW2>31<9Bnz;3~loK40Uv*7W`P+K~M zw?9-ST5dlXEIhN>%BJ6)`gR{EV9Bq4+KN<6SU?fLH*@_+U14j0lRes;xUnwR+LaU= z{^5)+Uvjo)km=4S&RUk*x%`dH1+^)pu32$}s40KaBD$NyBEKRcUFdSCgoL>VE(<%t z2&XT(Civ1dbm#q*!IuM)aqmVlifERelBVI+hZ8Ah#Q%w3(6`1E-Dc^yv&O#W>jfPh zkN}B5M5hBH1j^cWKOzgTwIrSMibr166p~Ihl+|Pw@BWr*lvh5ix&kM#gnci1gU50H zam%qh!EdJMz?xTXR2=iYeOZ#A02Yw>N8cffL_*Hxyv2;yE=^?Bbjy!NWnfHaPPV*x zYgV$00PenBZ|IX4g!6VPw^bmZqnW-*ujPU9}QaO?|_YgM@WOnGCD z6N=daZW@T3x0P~hGfjuPuY_%P$Q{Q?b8Yy1N%qEP&l;~JKR^#jW+$qWCVL%cSpEqS z;eZ)^QCWUV%+urv6RIE;$jmmM5Xy}6SSr9O^_8DMtZ=by){P2J9Uxsqn$V%KBBFm& zs?eixMSw`(aFx;9e6S;+wxS`vzNv}FBfBYw{WCFRSUq{As_k|4@5kVlSKf&xO`dl#?HZKn{bi4Gh+P+A326L0*r(_k7aqbTt?^?xK~Lx* zA!ODaxs4hhLXx;pFF~h3d*O#4%=Vy?GFjPn#iRGb$4I7tvntbuR?AKL~9^ zMz~={shf0f97@Tw1HO4RB%NV*s77d?GH*!eG^MkTJppdqB-Bcd+Jic)uRB=HDmaS^ z=X5s{a7(9iZ*-YNyZAs+eb)3fPk@Tyi@R9Zie%c!2BJjG5*%{lKcN~$O=);(0~{T- zaQCr8AODfOH!oEB7jl`5P4(!p!Ed}~)|&>xt!Y52C@Q+fC>o_8q?eA169jL@AtR+| zLMOVhFpe~%*y(w;;^*ved&+6mn3roVj1d|j%V3_*ge{l^WnA@bueN#uSdPv*lxSV> zR*o8W9T$o4G1Ke1aHh0q;mIydQ95c0hEwZcU+qaoC- z6|=R?)Gb~PpAOOvt}AC_yOG?_-h}Q&DR?;<5d5yl$d(3+NTiLxM*p0M-bd8bh9bkWtB#;b#b<; zwfr9bC~RoN23O||Q-vxv*0N1ldat`rQQ^C%TCnY>nJtv5a&jni9(~c`Pfs2Dgo}#D z@?rn-U@%L9NjaY1Al>S~ufX3b!irYa{%6??6|zLJrNvd(BmOqe06$_d3>qHL9s9Q0 zEj59W_#FM;SUc0Htf_z=I>HBf96$1lzs9VOYIJNroth;tTKF#sjkC-7(HVHUQ&9Zf zGGsT0wB2>WB#Ig7Hk}#|uUR`&86+SYoG$0etK1Uo`{6>)<8?AyF<-r-rJACjHu2M0 zvU6`4xFEE@lD%jbC zGt+Ka+LUM)dQ2=zF6_Pekx7Agh!Vm~3E7~56xx3KxT2=5qkr&~5}Xvr9DqN!D5t-SXB%oi|5J&O(+Ug;}cQ7*Lp~z%gd)BY`V=gN#ZZu0pK; z#DzsWrE;o75|L#}mAs~aJ3s%J<@-Vi2b~{;F(aOhO>RfP>6fK@h}O>euEl`l)BTO0y)Z-18JetB0fg?dS3NHdce-QTzgIiGa7+a?i-q{%#+}3j))&S4FwW4QsbDQSr`0*xtKxGD%NxzzpTw5X=}0BnZJ* z@WpePAg04txr52MhbvD2_s>*+k)l_k5J(1{HA{h{pm=BiSq9vHMHS6vMTfzvpBKe+=X$U<;1(;9rrI$q2%Z@#vtWMv3%X+C^ zx!2}#%1RY4LP%1GLz&6FBb5s?)0rW2=;f{W1R^Fi(djX)G1J=6DOq|=%-kLx)tw*e z@(^>?OSS_x1_qW((^>KQ-`TUjm&xOwAC-&CuL%BbdbLqw3NnUJW*a6MJu;K#ZfVwQ zw5?t4w1(Q(Z9>aFqluth7JA=wzOfYI z^91m`wcGJr$PX_uUP7#=jg2XDG$+#JYWLRZ@v{HuuHsz?AYQAKN5$ZOrtEEQl85U+q z%9byY^tlh+WgwXP;OCvwx;O0Z;enIsd%(1w()#R{kfW?d7diLr;GZ{LqDlOW8N|d; zDkv-ocCss$-S?+yA7~#C|FUVQqLa~t*@D1rc8?=X6Ujd_pKsonQl~|mDANP{V>6Y@ z#x}BZr74`7zAtncB!0Xh_`cdY)+C(q@TEu)xU!6^j-YZAJVnL2Q1u7xkQG8g#h&Yf z#4Do|8_ZFWSJE3vb(-aQ>+=ygW>O{M@N)jPM~Ex7N!wj(`AD#D{l%LC2AcycxByH= zTc8N3za1%LGMe)kY%(AAOKRh7_7R7QMl z*BcGeI!m1?@$bHCdROQ7rmFF#!piGQ9mSGsZN^-Y5&BGrV?UozNxgpnMXY_MmV)p; zWVMz|&rDdx&f;Vax56G-dh6|AV4oX>^5qkdj*ozzW6NOVLu%san!9Lx=HAU{q;Qj= zz^sSQsnak%0Ws!5?Z2U}`effHNzG+Sl7QHLBt49HiD)Mhu-3>0XgG)?vkL=nx z7G@8m;$g-_Yk5u|GW8cSnGc&*>SvhP6?34_p^XL6M(3F6J*1;Ij6plm(S$V2H(DJ-jdsXRRlo}F zsgP+NMx^q}r$!;O{`Kvx!1kJ(hz2}-q@(B{vPQD6?003p0}pM(SM^t9!pbRcV-H9$ z$F*J)l$1&_u6U^!`}s$Dm`@$8%UWpeZya<;|%mH{_hm%WNAB^24$J zD&Q6%{tXTbNS_NJNOGnZG}M>Ms@K!1;7NrFq=;2!)sL?a1d1MBh(m2l8*)r6>H<1u#9#JIqtOT*#!o&EyPJA=`6d(mC8{an52;+gOw zrI1J>4T}`AirS*?)Wzlz*#>$X;gQ<`9YTgh2;ml#p4#b$-?z@alk@z3liQGzho7yJ zG-^!~aq&J|%_rAK^x)ImTa7EL2k`b2VC!EtRNx8ynXt;N~Q9DYYT4ekC^ zMpUZb%=JA1Ud!FHkHe#YHHcvmq~!Ir|DpW8Al>BUqbA*IQ3&7Gnq&63q{kUZa=hUT zh*&-S7BaUYDf)@Od)?3)SF$f~J*1lYfh`zF*XHjxZKOC6r0&B9(bnHi7!#U3n(Qg( z*>t5!uc&umm!rR5rY)w|=i|zKB-+0%R5OgsCxh9@+Q{j&CF`P)9M!lXFvUB`07D!inVeai9v0dZI z-|(vV=d!3#U@FMTyI}aSAW|tVX}>vsaOM!DUTIKHmV$*16VF{$O@E$nkq${(0rln= zA3lf-{+K*PwaI8hePWh>Ue2rRn~`Ld_`+Zm0ZF2{&6(y=mC>p%GaJcx!|&|bIkF$AH;sJ^Rt$6 z)hr=>So$$9hxgxM!em@@@xs*Yjf$~!;4*{kBI)EV+_>YSC1;uESQ!KHH2B;{tcw6QI~th*28uswojL zw>)pnGKkp8aY@U-Lw=!$l%NqgOkJ14Z3oyyDBHi*rmT^4=Kj*6kxR!U9H4KZ#R}^I zul9fF{K}Xlnfsx)j$)Y2Cb2)udiJ}Ow3X1-`)|~|!smaA(QvUL5NO;sIPx)7VQ}Cv z BehaviourTree { get; set; } - public AutoFishingTrigger() { _autoFishingAssets = AutoFishingAssets.Instance; @@ -70,32 +64,6 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _noFishActionContinuouslyFrameNum = 0; _isThrowRod = false; _selectedBaitName = string.Empty; - - BehaviourTree = FluentBuilder.Create() - .MySimpleParallel("root", policy: SimpleParallelPolicy.OnlyOneMustSucceed) - .Do("检查开关", ctx => TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled ? BehaviourStatus.Running : BehaviourStatus.Failed) - .UntilSuccess("钓鱼") - .Sequence("从选鱼饵开始") - .Do("抛竿前准备", ThrowRod) - .Do("打开换饵界面", OpenChooseBaitUI) - .PushLeaf(() => new ChooseBait("选择鱼饵", this)) - .UntilSuccess("重复预抛竿") - .Sequence("从预抛竿开始") - .Do("长按预抛竿", ApproachFishAndThrowRod0) - .Do("抛竿", ApproachFishAndThrowRod1) - .End() - .End() - .Do("跳转-抛竿缺鱼检查", NoTargetFishCheck) - .MySimpleParallel("下杆中", SimpleParallelPolicy.OnlyOneMustSucceed) - .PushLeaf(() => new FishBiteTimeout("下杆超时检查", 30)) - .Do("自动提竿", FishBite) - .End() - .Do("等待拉条出现", Wait4FishBoxAreaAppear) - .Do("钓鱼拉条", Fishing) - .End() - .End() - .End() - .Build(); } private Rect _fishBoxRect = Rect.Empty; @@ -122,12 +90,28 @@ namespace BetterGenshinImpact.GameTask.AutoFishing } else { - BehaviourTree.Tick(content); - if (BehaviourTree.Status == BehaviourStatus.Failed) + // 自动抛竿 + ThrowRod(content); + // 上钩判断 + FishBite(content); + // 进入钓鱼界面先尝试获取钓鱼框的位置 + if (_fishBoxRect.Width == 0) { - _logger.LogInformation("BehaviourStatus.Failed 退出独占模式"); + if ((DateTime.Now - _prevExecute).TotalMilliseconds <= 200) + { + return; + } + + _prevExecute = DateTime.Now; + + _fishBoxRect = GetFishBoxArea(content.CaptureRectArea); CheckFishingUserInterface(content); } + else + { + // 钓鱼拉条 + Fishing(content, new Mat(content.CaptureRectArea.SrcMat, _fishBoxRect)); + } } } @@ -245,480 +229,375 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// 3. /// /// - private BehaviourStatus ThrowRod(CaptureContent content) + private void ThrowRod(CaptureContent content) { // 没有拉条和提竿的时候,自动抛竿 - //if (!_isFishingProcess && _biteTipsExitCount == 0 && TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled) - //{ - var baitRectArea = content.CaptureRectArea.Find(_autoFishingAssets.BaitButtonRo); - var waitBiteArea = content.CaptureRectArea.Find(_autoFishingAssets.WaitBiteButtonRo); - if (!baitRectArea.IsEmpty() && waitBiteArea.IsEmpty()) + if (!_isFishingProcess && _biteTipsExitCount == 0 && TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodEnabled) { - _switchBaitContinuouslyFrameNum++; + var baitRectArea = content.CaptureRectArea.Find(_autoFishingAssets.BaitButtonRo); + var waitBiteArea = content.CaptureRectArea.Find(_autoFishingAssets.WaitBiteButtonRo); + if (!baitRectArea.IsEmpty() && waitBiteArea.IsEmpty()) + { + _switchBaitContinuouslyFrameNum++; + _waitBiteContinuouslyFrameNum = 0; + _noFishActionContinuouslyFrameNum = 0; + + if (_switchBaitContinuouslyFrameNum >= content.FrameRate) + { + _isThrowRod = false; + _switchBaitContinuouslyFrameNum = 0; + _logger.LogInformation("当前处于未抛竿状态"); + } + + if (!_isThrowRod) + { + // 1. 观察周围环境,判断鱼塘位置,视角对上鱼塘位置中心 + using var memoryStream = new MemoryStream(); + content.CaptureRectArea.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); + memoryStream.Seek(0, SeekOrigin.Begin); + var result = _predictor.Detect(memoryStream); + Debug.WriteLine($"YOLOv8识别: {result.Speed}"); + var fishpond = new Fishpond(result); + if (fishpond.FishpondRect == Rect.Empty) + { + Sleep(500); + return; + } + else + { + var centerX = content.CaptureRectArea.SrcBitmap.Width / 2; + var centerY = content.CaptureRectArea.SrcBitmap.Height / 2; + // 往左移动是正数,往右移动是负数 + if (fishpond.FishpondRect.Left > centerX) + { + Simulation.SendInput.Mouse.MoveMouseBy(100, 0); + } + + if (fishpond.FishpondRect.Right < centerX) + { + Simulation.SendInput.Mouse.MoveMouseBy(-100, 0); + } + + // 鱼塘尽量在上半屏幕 + if (fishpond.FishpondRect.Bottom > centerY) + { + Simulation.SendInput.Mouse.MoveMouseBy(0, -100); + } + + if ((fishpond.FishpondRect.Left < centerX && fishpond.FishpondRect.Right > centerX && fishpond.FishpondRect.Bottom >= centerY) || fishpond.FishpondRect.Width < content.CaptureRectArea.SrcBitmap.Width / 4) + { + // 鱼塘在中心,选择鱼饵 + if (string.IsNullOrEmpty(_selectedBaitName)) + { + _selectedBaitName = ChooseBait(content, fishpond); + } + + // 抛竿 + Sleep(2000); + ApproachFishAndThrowRod(content); + Sleep(2000); + } + } + } + } + + if (baitRectArea.IsEmpty() && !waitBiteArea.IsEmpty() && _isThrowRod) + { + _switchBaitContinuouslyFrameNum = 0; + _waitBiteContinuouslyFrameNum++; + _noFishActionContinuouslyFrameNum = 0; + _throwRodWaitFrameNum++; + + if (_waitBiteContinuouslyFrameNum >= content.FrameRate) + { + _isThrowRod = true; + _waitBiteContinuouslyFrameNum = 0; + } + + if (_isThrowRod) + { + // 30s 没有上钩,重新抛竿 + if (_throwRodWaitFrameNum >= content.FrameRate * TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodTimeOut) + { + Simulation.SendInput.Mouse.LeftButtonClick(); + _throwRodWaitFrameNum = 0; + _waitBiteContinuouslyFrameNum = 0; + Debug.WriteLine("超时自动收竿"); + Sleep(2000); + _isThrowRod = false; + } + } + } + + if (baitRectArea.IsEmpty() && waitBiteArea.IsEmpty()) + { + _switchBaitContinuouslyFrameNum = 0; + _waitBiteContinuouslyFrameNum = 0; + _noFishActionContinuouslyFrameNum++; + if (_noFishActionContinuouslyFrameNum > content.FrameRate) + { + CheckFishingUserInterface(content); + } + } + } + else + { + _switchBaitContinuouslyFrameNum = 0; _waitBiteContinuouslyFrameNum = 0; _noFishActionContinuouslyFrameNum = 0; - - if (_switchBaitContinuouslyFrameNum >= content.FrameRate) - { - _isThrowRod = false; - _switchBaitContinuouslyFrameNum = 0; - _logger.LogInformation("当前处于未抛竿状态"); - } - - if (!_isThrowRod) - { - // 1. 观察周围环境,判断鱼塘位置,视角对上鱼塘位置中心 - using var memoryStream = new MemoryStream(); - content.CaptureRectArea.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); - memoryStream.Seek(0, SeekOrigin.Begin); - var result = _predictor.Detect(memoryStream); - Debug.WriteLine($"YOLOv8识别: {result.Speed}"); - var fishpond = new Fishpond(result); - if (fishpond.FishpondRect == Rect.Empty) - { - Sleep(500); - return BehaviourStatus.Running; - } - else - { - var centerX = content.CaptureRectArea.SrcBitmap.Width / 2; - var centerY = content.CaptureRectArea.SrcBitmap.Height / 2; - // 往左移动是正数,往右移动是负数 - if (fishpond.FishpondRect.Left > centerX) - { - Simulation.SendInput.Mouse.MoveMouseBy(100, 0); - } - - if (fishpond.FishpondRect.Right < centerX) - { - Simulation.SendInput.Mouse.MoveMouseBy(-100, 0); - } - - // 鱼塘尽量在上半屏幕 - if (fishpond.FishpondRect.Bottom > centerY) - { - Simulation.SendInput.Mouse.MoveMouseBy(0, -100); - } - - if ((fishpond.FishpondRect.Left < centerX && fishpond.FishpondRect.Right > centerX && fishpond.FishpondRect.Bottom >= centerY) || fishpond.FishpondRect.Width < content.CaptureRectArea.SrcBitmap.Width / 4) - { - // 鱼塘在中心,选择鱼饵 - //if (string.IsNullOrEmpty(_selectedBaitName)) - //{ - this.fishpond = fishpond; - _logger.LogInformation("定位到鱼塘" + string.Join('、', fishpond.Fishes.GroupBy(f => f.FishType).Select(g => $"{g.Key.ChineseName}{g.Count()}条"))); - - return BehaviourStatus.Succeeded; - //} - } - } - } + _throwRodWaitFrameNum = 0; + _isThrowRod = false; } - //} - //else - //{ - // _switchBaitContinuouslyFrameNum = 0; - // _waitBiteContinuouslyFrameNum = 0; - // _noFishActionContinuouslyFrameNum = 0; - // _throwRodWaitFrameNum = 0; - // _isThrowRod = false; - //} - - return BehaviourStatus.Running; - } - - private class FishBiteTimeout : BaseBehaviour - { - private readonly ILogger _logger = App.GetLogger(); - private DateTime? waitFishBiteTimeout; - private int _seconds; - - ///

- /// 如果未超时返回运行中,超时返回失败 - /// - /// - /// - public FishBiteTimeout(string name, int seconds) : base(name) - { - _seconds = seconds; - } - protected override void OnInitialize() - { - waitFishBiteTimeout = DateTime.Now.AddSeconds(_seconds); - } - protected override BehaviourStatus Update(CaptureContent context) - { - if (DateTime.Now >= waitFishBiteTimeout) - { - _logger.LogInformation($"{_seconds}秒没有咬杆,本次收杆"); - Simulation.SendInput.Mouse.LeftButtonClick(); - Thread.Sleep(1000); - return BehaviourStatus.Failed; - } - else - { - return BehaviourStatus.Running; - } - } - } - - private BehaviourStatus ThrowRod2(CaptureContent content) - { - //var baitRectArea = content.CaptureRectArea.Find(_autoFishingAssets.BaitButtonRo); - //var waitBiteArea = content.CaptureRectArea.Find(_autoFishingAssets.WaitBiteButtonRo); - //if (baitRectArea.IsEmpty() && !waitBiteArea.IsEmpty() && _isThrowRod) - //{ - // _switchBaitContinuouslyFrameNum = 0; - // _waitBiteContinuouslyFrameNum++; - // _noFishActionContinuouslyFrameNum = 0; - // _throwRodWaitFrameNum++; - - // if (_waitBiteContinuouslyFrameNum >= content.FrameRate) - // { - // _isThrowRod = true; - // _waitBiteContinuouslyFrameNum = 0; - // } - - // if (_isThrowRod) - // { - // // 30s 没有上钩,重新抛竿 - // if (_throwRodWaitFrameNum >= content.FrameRate * TaskContext.Instance().Config.AutoFishingConfig.AutoThrowRodTimeOut) - // { - // Simulation.SendInput.Mouse.LeftButtonClick(); - // _throwRodWaitFrameNum = 0; - // _waitBiteContinuouslyFrameNum = 0; - // Debug.WriteLine("超时自动收竿"); - // Sleep(2000); - // _isThrowRod = false; - // return BehaviourStatus.Failed; - // } - // return BehaviourStatus.Succeeded; - // } - //} - - //if (baitRectArea.IsEmpty() && waitBiteArea.IsEmpty()) - //{ - // _switchBaitContinuouslyFrameNum = 0; - // _waitBiteContinuouslyFrameNum = 0; - // _noFishActionContinuouslyFrameNum++; - // if (_noFishActionContinuouslyFrameNum > content.FrameRate) - // { - // CheckFishingUserInterface(content); - // } - // return BehaviourStatus.Failed; - //} - return BehaviourStatus.Succeeded; - } - - /// - /// 打开换饵界面 - /// - /// - /// - private BehaviourStatus OpenChooseBaitUI(CaptureContent content) - { - _logger.LogInformation("打开换饵界面"); - Simulation.SendInput.Mouse.RightButtonClick(); - Sleep(100); - Simulation.SendInput.Mouse.MoveMouseBy(0, 200); // 鼠标移走,防止干扰 - Sleep(100); - return BehaviourStatus.Succeeded; } private string _selectedBaitName = string.Empty; - private Fishpond fishpond; - private DateTime? ChooseBaitUIOpenWaitEndTime; // 等待选鱼饵界面出现的结束时间 /// /// 选择鱼饵 /// - private class ChooseBait : BaseBehaviour + /// + /// + /// + /// + private string ChooseBait(CaptureContent content, Fishpond fishpond) { - private readonly ILogger _logger = App.GetLogger(); - private readonly AutoFishingTrigger _autoFishingTrigger; + // 打开换饵界面 + Simulation.SendInput.Mouse.RightButtonClick(); + Sleep(100); + Simulation.SendInput.Mouse.MoveMouseBy(0, 200); // 鼠标移走,防止干扰 + Sleep(500); - /// - /// 选择鱼饵 - /// - /// - /// - public ChooseBait(string name, AutoFishingTrigger autoFishingTrigger) : base(name) + _selectedBaitName = fishpond.Fishes[0].FishType.BaitName; // 选择最多鱼吃的饵料 + _logger.LogInformation("选择鱼饵 {Text}", BaitType.FromName(_selectedBaitName).ChineseName); + + // 寻找鱼饵 + var ro = new RecognitionObject { - _autoFishingTrigger = autoFishingTrigger; + Name = "ChooseBait", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFishing", $"bait\\{_selectedBaitName}.png"), + Threshold = 0.8, + Use3Channels = true, + DrawOnWindow = false + }.InitTemplate(); + + // 截图 + using var captureRegion = TaskControl.CaptureToRectArea(forceNew: true); + using var resRa = captureRegion.Find(ro); + if (resRa.IsEmpty()) + { + _logger.LogWarning("没有找到目标鱼饵"); + _selectedBaitName = string.Empty; + throw new Exception("没有找到目标鱼饵"); + } + else + { + resRa.Click(); + Sleep(700); + // 可能重复点击,所以固定界面点击下 + captureRegion.ClickTo((int)(captureRegion.Width * 0.675), (int)(captureRegion.Height / 3d)); + Sleep(200); + // 点击确定 + Bv.ClickWhiteConfirmButton(captureRegion); + Sleep(500); // 等待界面切换 } - protected override void OnInitialize() - { - _autoFishingTrigger.ChooseBaitUIOpenWaitEndTime = DateTime.Now.AddSeconds(5); - } - - protected override BehaviourStatus Update(CaptureContent content) - { - if (this.Status == BehaviourStatus.Ready) - { - return BehaviourStatus.Running; - } - - _autoFishingTrigger._selectedBaitName = _autoFishingTrigger.fishpond.Fishes[0].FishType.BaitName; // 选择最多鱼吃的饵料 - _logger.LogInformation("选择鱼饵 {Text}", BaitType.FromName(_autoFishingTrigger._selectedBaitName).ChineseName); - - // 寻找鱼饵 - var ro = new RecognitionObject - { - Name = "ChooseBait", - RecognitionType = RecognitionTypes.TemplateMatch, - TemplateImageMat = GameTaskManager.LoadAssetImage("AutoFishing", $"bait\\{_autoFishingTrigger._selectedBaitName}.png"), - Threshold = 0.8, - Use3Channels = true, - DrawOnWindow = false - }.InitTemplate(); - - var captureRegion = content.CaptureRectArea; - using var resRa = captureRegion.Find(ro); - if (resRa.IsEmpty()) - { - if (DateTime.Now >= _autoFishingTrigger.ChooseBaitUIOpenWaitEndTime) - { - _logger.LogWarning("没有找到目标鱼饵"); - _autoFishingTrigger._selectedBaitName = string.Empty; - throw new Exception("没有找到目标鱼饵"); - } - else - { - return BehaviourStatus.Running; - } - } - else - { - resRa.Click(); - _autoFishingTrigger.Sleep(700); - // 可能重复点击,所以固定界面点击下 - captureRegion.ClickTo((int)(captureRegion.Width * 0.675), (int)(captureRegion.Height / 3d)); - _autoFishingTrigger.Sleep(200); - // 点击确定 - Bv.ClickWhiteConfirmButton(captureRegion); - _autoFishingTrigger.Sleep(500); // 等待界面切换 - } - - return BehaviourStatus.Succeeded; - } + return _selectedBaitName; } private readonly Random _rd = new(); /// - /// 长按预抛竿 + /// 抛竿 /// /// - private BehaviourStatus ApproachFishAndThrowRod0(CaptureContent content) + private void ApproachFishAndThrowRod(CaptureContent content) { - noPlacementTimes = 0; - noTargetFishTimes = 0; - + // 预抛竿 Simulation.SendInput.Mouse.LeftButtonDown(); _logger.LogInformation("长按预抛竿"); Sleep(3000); - return BehaviourStatus.Succeeded; - } - - private bool noTargetFish; - - private int noPlacementTimes; // 没有落点的次数 - private int noTargetFishTimes; // 没有目标鱼的次数 - /// - /// 抛竿 - /// - /// - private BehaviourStatus ApproachFishAndThrowRod1(CaptureContent content) - { - noTargetFish = false; + var noPlacementTimes = 0; // 没有落点的次数 + var noTargetFishTimes = 0; // 没有目标鱼的次数 var prevTargetFishRect = Rect.Empty; // 记录上一个目标鱼的位置 - var ra = content.CaptureRectArea; - - // 找 鱼饵落点 - using var memoryStream = new MemoryStream(); - ra.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); - memoryStream.Seek(0, SeekOrigin.Begin); - var result = _predictor.Detect(memoryStream); - Debug.WriteLine($"YOLOv8识别: {result.Speed}"); - var fishpond = new Fishpond(result); - if (fishpond.TargetRect == Rect.Empty) + while (IsEnabled) { - noPlacementTimes++; - Sleep(50); - Debug.WriteLine("历次未找到鱼饵落点"); + // 截图 + var ra = TaskControl.CaptureToRectArea(forceNew: true); - 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) + // 找 鱼饵落点 + using var memoryStream = new MemoryStream(); + ra.SrcBitmap.Save(memoryStream, ImageFormat.Bmp); + memoryStream.Seek(0, SeekOrigin.Begin); + var result = _predictor.Detect(memoryStream); + Debug.WriteLine($"YOLOv8识别: {result.Speed}"); + var fishpond = new Fishpond(result); + if (fishpond.TargetRect == Rect.Empty) { - _logger.LogInformation("未找到鱼饵落点,重试"); - // Simulation.SendInputEx.Mouse.LeftButtonUp(); - // // Sleep(2000); - // // Simulation.SendInputEx.Mouse.LeftButtonClick(); - // _selectedBaitName = string.Empty; - // _isThrowRod = false; - // // Sleep(2000); - // // MoveViewpointDown(); - // Sleep(300); - return BehaviourStatus.Failed; - } + noPlacementTimes++; + Sleep(50); + Debug.WriteLine("历次未找到鱼饵落点"); - return BehaviourStatus.Running; - } + 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); - // 找到落点最近的鱼 - OneFish? currentFish = null; - if (prevTargetFishRect == Rect.Empty) - { - var list = fishpond.FilterByBaitName(_selectedBaitName); - if (list.Count > 0) - { - currentFish = list[0]; - prevTargetFishRect = currentFish.Rect; - } - } - else - { - currentFish = fishpond.FilterByBaitNameAndRecently(_selectedBaitName, prevTargetFishRect); - if (currentFish != null) - { - prevTargetFishRect = currentFish.Rect; - } - } + var moveX = 100 * (cX - rdX) / ra.SrcBitmap.Width; + var moveY = 100 * (cY - rdY) / ra.SrcBitmap.Height; - if (currentFish == null) - { - Debug.WriteLine("无目标鱼"); - noTargetFishTimes++; - //if (noTargetFishTimes == 30) - //{ - // Simulation.SendInputEx.Mouse.MoveMouseBy(0, 100); - //} - - if (noTargetFishTimes > 10) - { - // 没有找到目标鱼,重新选择鱼饵 - _logger.LogInformation("没有找到目标鱼,1.直接抛竿"); - Simulation.SendInput.Mouse.LeftButtonUp(); - Sleep(1500); - _logger.LogInformation("没有找到目标鱼,2.收杆"); - Simulation.SendInput.Mouse.LeftButtonClick(); - Sleep(800); - _logger.LogInformation("没有找到目标鱼,3.准备重新选择鱼饵"); - _selectedBaitName = string.Empty; - _isThrowRod = false; - MoveViewpointDown(); - Sleep(300); - - noTargetFish = true; - return BehaviourStatus.Succeeded; - } - - return BehaviourStatus.Running; - } - else - { - noTargetFishTimes = 0; - _currContent.CaptureRectArea.DrawRect(fishpond.TargetRect, "Target"); - _currContent.CaptureRectArea.Derive(currentFish.Rect).DrawSelf("Fish"); - - // VisionContext.Instance().DrawContent.PutRect("Target", fishpond.TargetRect.ToRectDrawable()); - // VisionContext.Instance().DrawContent.PutRect("Fish", currentFish.Rect.ToRectDrawable()); - - // var min = MoveMouseToFish(fishpond.TargetRect, currentFish.Rect); - // // 因为视角是斜着看向鱼的,所以Y轴抛竿距离要近一点 - // if ((_selectedBaitName != "fruit paste bait" && min is { Item1: <= 50, Item2: <= 25 }) - // || _selectedBaitName == "fruit paste bait" && min is { Item1: <= 40, Item2: <= 25 }) - // { - // Sleep(100); - // Simulation.SendInputEx.Mouse.LeftButtonUp(); - // _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); - // _isThrowRod = true; - // VisionContext.Instance().DrawContent.RemoveRect("Target"); - // VisionContext.Instance().DrawContent.RemoveRect("Fish"); - // break; - // } - - // 来自 HutaoFisher 的抛竿技术 - var rod = fishpond.TargetRect; - var fish = currentFish.Rect; - var dx = NormalizeXTo1024(fish.Left + fish.Right - rod.Left - rod.Right) / 2.0; - var dy = NormalizeYTo576(fish.Top + fish.Bottom - rod.Top - rod.Bottom) / 2.0; - var state = RodNet.GetRodState(new RodInput - { - rod_x1 = NormalizeXTo1024(rod.Left), - rod_x2 = NormalizeXTo1024(rod.Right), - rod_y1 = NormalizeYTo576(rod.Top), - rod_y2 = NormalizeYTo576(rod.Bottom), - fish_x1 = NormalizeXTo1024(fish.Left), - fish_x2 = NormalizeXTo1024(fish.Right), - fish_y1 = NormalizeYTo576(fish.Top), - fish_y2 = NormalizeYTo576(fish.Bottom), - fish_label = BigFishType.GetIndex(currentFish.FishType) - }); - if (state == -1) - { - // 失败 随机移动鼠标 - var cX = content.CaptureRectArea.SrcBitmap.Width / 2; - var cY = content.CaptureRectArea.SrcBitmap.Height / 2; - var rdX = _rd.Next(0, content.CaptureRectArea.SrcBitmap.Width); - var rdY = _rd.Next(0, content.CaptureRectArea.SrcBitmap.Height); - - var moveX = 100 * (cX - rdX) / content.CaptureRectArea.SrcBitmap.Width; - var moveY = 100 * (cY - rdY) / content.CaptureRectArea.SrcBitmap.Height; - - _logger.LogInformation("失败 随机移动 {DX}, {DY}", moveX, moveY); Simulation.SendInput.Mouse.MoveMouseBy(moveX, moveY); - } - else if (state == 0) - { - // 成功 抛竿 - Simulation.SendInput.Mouse.LeftButtonUp(); - _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); - _isThrowRod = true; - VisionContext.Instance().DrawContent.RemoveRect("Target"); - VisionContext.Instance().DrawContent.RemoveRect("Fish"); - return BehaviourStatus.Succeeded; - } - else if (state == 1) - { - // 太近 - var dl = Math.Sqrt(dx * dx + dy * dy); - // set a minimum step - dx = dx / dl * 30; - dy = dy / dl * 30; - // _logger.LogInformation("太近 移动 {DX}, {DY}", dx, dy); - Simulation.SendInput.Mouse.MoveMouseBy((int)(-dx / 1.5), (int)(-dy * 1.5)); - } - else if (state == 2) - { - // 太远 - // _logger.LogInformation("太远 移动 {DX}, {DY}", dx, dy); - Simulation.SendInput.Mouse.MoveMouseBy((int)(dx / 1.5), (int)(dy * 1.5)); - } - } - Sleep(50); - return BehaviourStatus.Running; - } - /// - /// 缺鱼检查 - /// - /// - private BehaviourStatus NoTargetFishCheck(CaptureContent content) - { - return noTargetFish ? BehaviourStatus.Failed : BehaviourStatus.Succeeded; + if (noPlacementTimes > 25) + { + _logger.LogInformation("未找到鱼饵落点,重试"); + // Simulation.SendInputEx.Mouse.LeftButtonUp(); + // // Sleep(2000); + // // Simulation.SendInputEx.Mouse.LeftButtonClick(); + // _selectedBaitName = string.Empty; + // _isThrowRod = false; + // // Sleep(2000); + // // MoveViewpointDown(); + // Sleep(300); + break; + } + + continue; + } + + // 找到落点最近的鱼 + OneFish? currentFish = null; + if (prevTargetFishRect == Rect.Empty) + { + var list = fishpond.FilterByBaitName(_selectedBaitName); + if (list.Count > 0) + { + currentFish = list[0]; + prevTargetFishRect = currentFish.Rect; + } + } + else + { + currentFish = fishpond.FilterByBaitNameAndRecently(_selectedBaitName, prevTargetFishRect); + if (currentFish != null) + { + prevTargetFishRect = currentFish.Rect; + } + } + + if (currentFish == null) + { + Debug.WriteLine("无目标鱼"); + noTargetFishTimes++; + //if (noTargetFishTimes == 30) + //{ + // Simulation.SendInputEx.Mouse.MoveMouseBy(0, 100); + //} + + if (noTargetFishTimes > 10) + { + // 没有找到目标鱼,重新选择鱼饵 + _logger.LogInformation("没有找到目标鱼,1.直接抛竿"); + Simulation.SendInput.Mouse.LeftButtonUp(); + Sleep(1500); + _logger.LogInformation("没有找到目标鱼,2.收杆"); + Simulation.SendInput.Mouse.LeftButtonClick(); + Sleep(800); + _logger.LogInformation("没有找到目标鱼,3.准备重新选择鱼饵"); + _selectedBaitName = string.Empty; + _isThrowRod = false; + MoveViewpointDown(); + Sleep(300); + break; + } + + continue; + } + else + { + noTargetFishTimes = 0; + _currContent.CaptureRectArea.DrawRect(fishpond.TargetRect, "Target"); + _currContent.CaptureRectArea.Derive(currentFish.Rect).DrawSelf("Fish"); + // VisionContext.Instance().DrawContent.PutRect("Target", fishpond.TargetRect.ToRectDrawable()); + // VisionContext.Instance().DrawContent.PutRect("Fish", currentFish.Rect.ToRectDrawable()); + + // var min = MoveMouseToFish(fishpond.TargetRect, currentFish.Rect); + // // 因为视角是斜着看向鱼的,所以Y轴抛竿距离要近一点 + // if ((_selectedBaitName != "fruit paste bait" && min is { Item1: <= 50, Item2: <= 25 }) + // || _selectedBaitName == "fruit paste bait" && min is { Item1: <= 40, Item2: <= 25 }) + // { + // Sleep(100); + // Simulation.SendInputEx.Mouse.LeftButtonUp(); + // _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); + // _isThrowRod = true; + // VisionContext.Instance().DrawContent.RemoveRect("Target"); + // VisionContext.Instance().DrawContent.RemoveRect("Fish"); + // break; + // } + + // 来自 HutaoFisher 的抛竿技术 + var rod = fishpond.TargetRect; + var fish = currentFish.Rect; + var dx = NormalizeXTo1024(fish.Left + fish.Right - rod.Left - rod.Right) / 2.0; + var dy = NormalizeYTo576(fish.Top + fish.Bottom - rod.Top - rod.Bottom) / 2.0; + var state = RodNet.GetRodState(new RodInput + { + rod_x1 = NormalizeXTo1024(rod.Left), + rod_x2 = NormalizeXTo1024(rod.Right), + rod_y1 = NormalizeYTo576(rod.Top), + rod_y2 = NormalizeYTo576(rod.Bottom), + fish_x1 = NormalizeXTo1024(fish.Left), + fish_x2 = NormalizeXTo1024(fish.Right), + fish_y1 = NormalizeYTo576(fish.Top), + fish_y2 = NormalizeYTo576(fish.Bottom), + fish_label = BigFishType.GetIndex(currentFish.FishType) + }); + if (state == -1) + { + // 失败 随机移动鼠标 + var cX = content.CaptureRectArea.SrcBitmap.Width / 2; + var cY = content.CaptureRectArea.SrcBitmap.Height / 2; + var rdX = _rd.Next(0, content.CaptureRectArea.SrcBitmap.Width); + var rdY = _rd.Next(0, content.CaptureRectArea.SrcBitmap.Height); + + var moveX = 100 * (cX - rdX) / content.CaptureRectArea.SrcBitmap.Width; + var moveY = 100 * (cY - rdY) / content.CaptureRectArea.SrcBitmap.Height; + + _logger.LogInformation("失败 随机移动 {DX}, {DY}", moveX, moveY); + Simulation.SendInput.Mouse.MoveMouseBy(moveX, moveY); + } + else if (state == 0) + { + // 成功 抛竿 + Simulation.SendInput.Mouse.LeftButtonUp(); + _logger.LogInformation("尝试钓取 {Text}", currentFish.FishType.ChineseName); + _isThrowRod = true; + VisionContext.Instance().DrawContent.RemoveRect("Target"); + VisionContext.Instance().DrawContent.RemoveRect("Fish"); + break; + } + else if (state == 1) + { + // 太近 + var dl = Math.Sqrt(dx * dx + dy * dy); + // set a minimum step + dx = dx / dl * 30; + dy = dy / dl * 30; + // _logger.LogInformation("太近 移动 {DX}, {DY}", dx, dy); + Simulation.SendInput.Mouse.MoveMouseBy((int)(-dx / 1.5), (int)(-dy * 1.5)); + } + else if (state == 2) + { + // 太远 + // _logger.LogInformation("太远 移动 {DX}, {DY}", dx, dy); + Simulation.SendInput.Mouse.MoveMouseBy((int)(dx / 1.5), (int)(dy * 1.5)); + } + } + + Sleep(20); + } } private double NormalizeXTo1024(int x) @@ -855,7 +734,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _logger.LogWarning("当前获取焦点的窗口不是原神,暂停"); throw new RetryException("当前获取焦点的窗口不是原神"); } - + }, TimeSpan.FromSeconds(1), 100); CheckFishingUserInterface(_currContent); Thread.Sleep(millisecondsTimeout); @@ -923,9 +802,13 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// 自动提竿 /// /// - private BehaviourStatus FishBite(CaptureContent content) + private void FishBite(CaptureContent content) { - _logger.LogInformation("等待提竿"); + if (_isFishingProcess) + { + return; + } + // 自动识别的钓鱼框向下延伸到屏幕中间 //var liftingWordsAreaRect = new Rect(fishBoxRect.X, fishBoxRect.Y + fishBoxRect.Height * 2, // fishBoxRect.Width, content.CaptureRectArea.SrcMat.Height / 2 - fishBoxRect.Y - fishBoxRect.Height * 5); @@ -969,7 +852,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _biteTipsExitCount = 0; _baseBiteTips = Rect.Empty; VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); - return BehaviourStatus.Succeeded; + return; } // OCR 提竿判断 @@ -986,7 +869,6 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _biteTipsExitCount = 0; _baseBiteTips = Rect.Empty; VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); - return BehaviourStatus.Succeeded; } } } @@ -1006,33 +888,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _biteTipsExitCount = 0; _baseBiteTips = Rect.Empty; VisionContext.Instance().DrawContent.RemoveRect("FishBiteTips"); - return BehaviourStatus.Succeeded; } } } - - return BehaviourStatus.Running; - } - - /// - /// 进入钓鱼界面先尝试获取钓鱼框的位置 - /// - /// - /// - private BehaviourStatus Wait4FishBoxAreaAppear(CaptureContent content) - { - _logger.LogInformation("寻找拉条中"); - if ((DateTime.Now - _prevExecute).TotalMilliseconds <= 200) - { - return BehaviourStatus.Running; - } - - _prevExecute = DateTime.Now; - - _fishBoxRect = GetFishBoxArea(content.CaptureRectArea); - CheckFishingUserInterface(content); - - return _fishBoxRect == Rect.Empty ? BehaviourStatus.Running : BehaviourStatus.Succeeded; } private int _noRectsCount = 0; @@ -1045,9 +903,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing /// /// /// - private BehaviourStatus Fishing(CaptureContent content) + private void Fishing(CaptureContent content, Mat fishBarMat) { - var fishBarMat = new Mat(content.CaptureRectArea.SrcMat, _fishBoxRect); var simulator = Simulation.SendInput; var rects = AutoFishingImageRecognition.GetFishBarRect(fishBarMat); if (rects != null && rects.Count > 0) @@ -1157,8 +1014,6 @@ namespace BetterGenshinImpact.GameTask.AutoFishing MoveViewpointDown(); Sleep(500); - - return BehaviourStatus.Succeeded; } CheckFishingUserInterface(content); @@ -1181,15 +1036,13 @@ namespace BetterGenshinImpact.GameTask.AutoFishing { _notFishingAfterBiteCount = 0; } - - return BehaviourStatus.Running; } /// /// 检查是否退出钓鱼界面 /// /// - private BehaviourStatus CheckFishingUserInterface(CaptureContent content) + private void CheckFishingUserInterface(CaptureContent content) { var prevIsExclusive = IsExclusive; IsExclusive = FindButtonForExclusive(content); @@ -1207,8 +1060,6 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _noFishActionContinuouslyFrameNum = 0; _isThrowRod = false; _selectedBaitName = string.Empty; - - return BehaviourStatus.Succeeded; } else if (prevIsExclusive && !IsExclusive) { @@ -1216,12 +1067,6 @@ namespace BetterGenshinImpact.GameTask.AutoFishing _isThrowRod = false; _fishBoxRect = Rect.Empty; VisionContext.Instance().DrawContent.ClearAll(); - - return BehaviourStatus.Failed; - } - else - { - return BehaviourStatus.Running; } } @@ -1265,115 +1110,5 @@ namespace BetterGenshinImpact.GameTask.AutoFishing //{ // ClearDraw(); //} - - } - - /// - /// MySimpleParallel - /// 和SimpleParallel的区别是,任一子行为返回失败则返回失败 - /// - /// - public class MySimpleParallel : CompositeBehaviour - { - private readonly IBehaviour _first; - - private readonly IBehaviour _second; - - private BehaviourStatus _firstStatus; - - private BehaviourStatus _secondStatus; - - private readonly Func _behave; - - public readonly SimpleParallelPolicy Policy; - - public MySimpleParallel(SimpleParallelPolicy policy, IBehaviour first, IBehaviour second) - : this("SimpleParallel", policy, first, second) - { - } - - public MySimpleParallel(string name, SimpleParallelPolicy policy, IBehaviour first, IBehaviour second) - : base(name, new IBehaviour[2] { first, second }) - { - Policy = policy; - _first = first; - _second = second; - _behave = ((policy == SimpleParallelPolicy.BothMustSucceed) ? new Func(BothMustSucceedBehaviour) : new Func(OnlyOneMustSucceedBehaviour)); - } - - private BehaviourStatus OnlyOneMustSucceedBehaviour(TContext context) - { - if (_firstStatus == BehaviourStatus.Succeeded || _secondStatus == BehaviourStatus.Succeeded) - { - return BehaviourStatus.Succeeded; - } - - if (_firstStatus == BehaviourStatus.Failed && _secondStatus == BehaviourStatus.Failed) - { - return BehaviourStatus.Failed; - } - - return BehaviourStatus.Running; - } - - private BehaviourStatus BothMustSucceedBehaviour(TContext context) - { - if (_firstStatus == BehaviourStatus.Succeeded && _secondStatus == BehaviourStatus.Succeeded) - { - return BehaviourStatus.Succeeded; - } - - if (_firstStatus == BehaviourStatus.Failed || _secondStatus == BehaviourStatus.Failed) - { - return BehaviourStatus.Failed; - } - - return BehaviourStatus.Running; - } - - protected override BehaviourStatus Update(TContext context) - { - if (base.Status != BehaviourStatus.Running) - { - _firstStatus = _first.Tick(context); - _secondStatus = _second.Tick(context); - } - else - { - if (_firstStatus == BehaviourStatus.Ready || _firstStatus == BehaviourStatus.Running) - { - _firstStatus = _first.Tick(context); - } - - if (_secondStatus == BehaviourStatus.Ready || _secondStatus == BehaviourStatus.Running) - { - _secondStatus = _second.Tick(context); - } - } - - if (_firstStatus == BehaviourStatus.Failed || _secondStatus == BehaviourStatus.Failed) - { - return BehaviourStatus.Failed; - } - else - { - return _behave(context); - } - } - - protected override void DoReset(BehaviourStatus status) - { - _firstStatus = BehaviourStatus.Ready; - _secondStatus = BehaviourStatus.Ready; - base.DoReset(status); - } - } - - public static class FluentBuilderExtensions - { - public static FluentBuilder MySimpleParallel(this FluentBuilder builder, string name, SimpleParallelPolicy policy = SimpleParallelPolicy.BothMustSucceed) - { - return builder.PushComposite((IBehaviour[] children) => new MySimpleParallel(name, policy, children[0], children[1])); - } } } diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Model/BaitType.cs b/BetterGenshinImpact/GameTask/AutoFishing/Model/BaitType.cs index 16cfdc9e..d0246b85 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/Model/BaitType.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/Model/BaitType.cs @@ -12,8 +12,6 @@ public class BaitType public static readonly BaitType SugardewBait = new("sugardew bait", "甘露饵"); public static readonly BaitType SourBait = new("sour bait", "酸桔饵"); public static readonly BaitType FlashingMaintenanceMekBait = new("flashing maintenance mek bait", "维护机关频闪诱饵"); - public static readonly BaitType SpinelgrainBait = new("spinelgrain bait", "澄晶果粒饵"); - public static readonly BaitType EmberglowBait = new("emberglow bait", "温火饵"); public static IEnumerable Values { @@ -26,8 +24,6 @@ public class BaitType yield return SugardewBait; yield return SourBait; yield return FlashingMaintenanceMekBait; - yield return SpinelgrainBait; - yield return EmberglowBait; } } public string Name { get; private set; } diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs b/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs index 98ff1469..6af9af13 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/Model/BigFishType.cs @@ -15,18 +15,12 @@ public class BigFishType public static readonly BigFishType Koi = new("koi", "fake fly bait", "假龙"); public static readonly BigFishType Butterflyfish = new("butterflyfish", "false worm bait", "蝶鱼"); public static readonly BigFishType Pufferfish = new("pufferfish", "fake fly bait", "炮鲀"); - public static readonly BigFishType Ray = new("ray", "fake fly bait", "鳐"); - public static readonly BigFishType FormaloRay = new("formalo ray", "fake fly bait", "佛玛洛鳐"); // todo 等模型更新后去掉两种细分的formalo ray和divda ray,仅保留ray + public static readonly BigFishType FormaloRay = new("formalo ray", "fake fly bait", "佛玛洛鳐"); public static readonly BigFishType DivdaRay = new("divda ray", "fake fly bait", "迪芙妲鳐"); public static readonly BigFishType Angler = new("angler", "sugardew bait", "角鲀"); public static readonly BigFishType AxeMarlin = new("axe marlin", "sugardew bait", "斧枪鱼"); public static readonly BigFishType HeartfeatherBass = new("heartfeather bass", "sour bait", "心羽鲈"); public static readonly BigFishType MaintenanceMek = new("maintenance mek", "flashing maintenance mek bait", "维护机关"); - public static readonly BigFishType Unihornfish = new("unihornfish", "spinelgrain bait", "独角鱼"); - public static readonly BigFishType Sunfish = new("sunfish", "spinelgrain bait", "翻车鲀"); - public static readonly BigFishType Rapidfish = new("rapidfish", "spinelgrain bait", "斗士急流鱼"); - public static readonly BigFishType PhonyUnihornfish = new("phony unihornfish", "emberglow bait", "燃素独角鱼"); - public static readonly BigFishType MagmaRapidfish = new("magma rapidfish", "emberglow bait", "炽岩斗士急流鱼"); public static IEnumerable Values @@ -39,18 +33,12 @@ public class BigFishType yield return Koi; yield return Butterflyfish; yield return Pufferfish; - yield return Ray; yield return FormaloRay; yield return DivdaRay; yield return Angler; yield return AxeMarlin; yield return HeartfeatherBass; yield return MaintenanceMek; - yield return Unihornfish; - yield return Sunfish; - yield return Rapidfish; - yield return PhonyUnihornfish; - yield return MagmaRapidfish; } }