diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 877631a1..bc058648 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -271,6 +271,18 @@ Always + + Always + + + Always + + + Always + + + Always + Always diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Exception/DuelEndException.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Exception/DuelEndException.cs deleted file mode 100644 index 67357681..00000000 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Exception/DuelEndException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; - -public class DuelEndException: System.Exception -{ - public DuelEndException(string message) : base(message) - { - } -} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Exception/NormalEndException.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Exception/NormalEndException.cs new file mode 100644 index 00000000..f20f55bb --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Exception/NormalEndException.cs @@ -0,0 +1,8 @@ +namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; + +public class NormalEndException: System.Exception +{ + public NormalEndException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs index 582aed90..66435630 100644 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs @@ -494,7 +494,7 @@ public class GeniusInvokationControl if (IsDuelEnd()) { - throw new DuelEndException("对战已结束,停止自动打牌!"); + throw new NormalEndException("对战已结束,停止自动打牌!"); } //MyLogger.Debug("识别骰子数量不正确,第{}次重试中...", retryCount); @@ -903,7 +903,7 @@ public class GeniusInvokationControl } else if (IsDuelEnd()) { - throw new DuelEndException("对战已结束,停止自动打牌!"); + throw new NormalEndException("对战已结束,停止自动打牌!"); } retryCount++; @@ -946,7 +946,7 @@ public class GeniusInvokationControl } else if (IsDuelEnd()) { - throw new DuelEndException("对战已结束,停止自动打牌!"); + throw new NormalEndException("对战已结束,停止自动打牌!"); } else { @@ -976,7 +976,7 @@ public class GeniusInvokationControl /// 角色被打败后要切换角色 /// /// - /// + /// public void DoWhenCharacterDefeated(Duel duel) { _logger.LogInformation("当前出战角色被打败,需要选择新的出战角色"); @@ -990,7 +990,7 @@ public class GeniusInvokationControl var orderList = duel.GetCharacterSwitchOrder(); if (orderList.Count == 0) { - throw new DuelEndException("后续行动策略中,已经没有可切换且存活的角色了,结束自动打牌(建议添加更多行动)"); + throw new NormalEndException("后续行动策略中,已经没有可切换且存活的角色了,结束自动打牌(建议添加更多行动)"); } foreach (var j in orderList) diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs index b538b651..6261392f 100644 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs @@ -265,7 +265,7 @@ public class Duel if (ActionCommandQueue.Count == 0) { - throw new DuelEndException("策略中所有指令已经执行完毕,结束自动打牌"); + throw new NormalEndException("策略中所有指令已经执行完毕,结束自动打牌"); } } @@ -283,7 +283,7 @@ public class Duel { _logger.LogInformation(ex.Message); } - catch (DuelEndException ex) + catch (NormalEndException ex) { _logger.LogInformation(ex.Message); _logger.LogInformation("对局结束"); diff --git a/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/TheBoonOfTheElderTree.png b/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/TheBoonOfTheElderTree.png new file mode 100644 index 00000000..893c154d Binary files /dev/null and b/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/TheBoonOfTheElderTree.png differ diff --git a/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/character_guide.png b/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/character_guide.png new file mode 100644 index 00000000..78220fdc Binary files /dev/null and b/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/character_guide.png differ diff --git a/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/confirm.png b/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/confirm.png new file mode 100644 index 00000000..0285f140 Binary files /dev/null and b/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/confirm.png differ diff --git a/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/enter_game.png b/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/enter_game.png new file mode 100644 index 00000000..7bd63f86 Binary files /dev/null and b/BetterGenshinImpact/GameTask/AutoWood/Assets/1920x1080/enter_game.png differ diff --git a/BetterGenshinImpact/GameTask/AutoWood/Assets/AutoWoodAssets.cs b/BetterGenshinImpact/GameTask/AutoWood/Assets/AutoWoodAssets.cs new file mode 100644 index 00000000..0018bc2e --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoWood/Assets/AutoWoodAssets.cs @@ -0,0 +1,55 @@ +using System.Drawing; +using BetterGenshinImpact.Core.Recognition; +using OpenCvSharp; + +namespace BetterGenshinImpact.GameTask.AutoWood.Assets +{ + public class AutoWoodAssets + { + public RecognitionObject TheBoonOfTheElderTreeRo; + public RecognitionObject CharacterGuideRo; + public RecognitionObject ConfirmRo; + public RecognitionObject EnterGameRo; + + public AutoWoodAssets() + { + var info = TaskContext.Instance().SystemInfo; + //「王树瑞佑」 + TheBoonOfTheElderTreeRo = new RecognitionObject + { + Name = "TheBoonOfTheElderTree", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage("AutoWood", "TheBoonOfTheElderTree.png"), + RegionOfInterest = new Rect(info.CaptureAreaRect.Width - info.CaptureAreaRect.Width / 4, info.CaptureAreaRect.Height / 2, + info.CaptureAreaRect.Width / 4, info.CaptureAreaRect.Height - info.CaptureAreaRect.Height / 2), + DrawOnWindow = false + }.InitTemplate(); + + CharacterGuideRo = new RecognitionObject + { + Name = "CharacterGuide", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage("AutoWood", "character_guide.png"), + RegionOfInterest = new Rect(0, 0, info.CaptureAreaRect.Width / 2, info.CaptureAreaRect.Height), + DrawOnWindow = false + }.InitTemplate(); + + ConfirmRo = new RecognitionObject + { + Name = "AutoWoodConfirm", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage("AutoWood", "confirm.png"), + DrawOnWindow = false + }.InitTemplate(); + + EnterGameRo = new RecognitionObject + { + Name = "EnterGame", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage("AutoWood", "enter_game.png"), + RegionOfInterest = new Rect(0, info.CaptureAreaRect.Height / 2, info.CaptureAreaRect.Width, info.CaptureAreaRect.Height - info.CaptureAreaRect.Height / 2), + DrawOnWindow = false + }.InitTemplate(); + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoWood/AutoWoodTask.cs b/BetterGenshinImpact/GameTask/AutoWood/AutoWoodTask.cs new file mode 100644 index 00000000..e0c77507 --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoWood/AutoWoodTask.cs @@ -0,0 +1,166 @@ +using BetterGenshinImpact.Core.Simulator; +using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; +using BetterGenshinImpact.GameTask.AutoWood.Assets; +using BetterGenshinImpact.GameTask.AutoWood.Utils; +using BetterGenshinImpact.Helpers; +using BetterGenshinImpact.View.Drawable; +using BetterGenshinImpact.ViewModel.Pages; +using Microsoft.Extensions.Logging; +using System; +using System.Windows; +using WindowsInput; +using static BetterGenshinImpact.GameTask.Common.TaskControl; + +namespace BetterGenshinImpact.GameTask.AutoWood; + +/// +/// 自动伐木 +/// +public class AutoWoodTask +{ + private readonly AutoWoodAssets _assets = new(); + + private bool _first = true; + + private readonly ClickOffset _clickOffset; + + public AutoWoodTask() + { + var captureArea = TaskContext.Instance().SystemInfo.CaptureAreaRect; + var assetScale = TaskContext.Instance().SystemInfo.AssetScale; + _clickOffset = new ClickOffset(captureArea.X, captureArea.Y, assetScale); + } + + public void Start(WoodTaskParam taskParam) + { + try + { + Logger.LogInformation("→ {Text} 设置伐木总次数:{Cnt}", "自动伐木,启动!", taskParam.WoodRoundNum); + SystemControl.ActivateWindow(); + for (var i = 0; i < taskParam.WoodRoundNum; i++) + { + Logger.LogInformation("第{Cnt}次伐木", i + 1); + if (taskParam.Cts.IsCancellationRequested) + { + break; + } + + Felling(taskParam); + VisionContext.Instance().DrawContent.ClearAll(); + Sleep(500, taskParam.Cts); + } + } + catch (NormalEndException e) + { + Logger.LogInformation(e.Message); + } + catch (Exception e) + { + Logger.LogInformation(e.Message); + MessageBox.Show("自动伐木时异常:" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message); + } + finally + { + VisionContext.Instance().DrawContent.ClearAll(); + TaskSettingsPageViewModel.SetSwitchAutoWoodButtonText(false); + Logger.LogInformation("← {Text}", "退出自动伐木"); + taskParam.Dispatcher.StartTimer(); + } + } + + private void Felling(WoodTaskParam taskParam) + { + // 1. 按 z 触发「王树瑞佑」 + PressZ(taskParam); + + // 2. 按下 ESC 打开菜单 并退出游戏 + PressEsc(taskParam); + + // 3. 等待进入游戏 + EnterGame(taskParam); + } + + + private void PressZ(WoodTaskParam taskParam) + { + if (_first) + { + var content = CaptureToContent(taskParam.Dispatcher.GameCapture); + var ra = content.CaptureRectArea.Find(_assets.TheBoonOfTheElderTreeRo); + if (ra.IsEmpty()) + { + throw new NormalEndException("请先装备小道具「王树瑞佑」!"); + } + else + { + Simulation.SendInput.Keyboard.KeyPress(VirtualKeyCode.VK_Z); + _first = false; + } + } + else + { + NewRetry.Do(() => + { + Sleep(1, taskParam.Cts); + var content = CaptureToContent(taskParam.Dispatcher.GameCapture); + var ra = content.CaptureRectArea.Find(_assets.TheBoonOfTheElderTreeRo); + if (ra.IsEmpty()) + { + throw new RetryException("未找到「王树瑞佑」"); + } + + Simulation.SendInput.Keyboard.KeyPress(VirtualKeyCode.VK_Z); + }, TimeSpan.FromSeconds(1), 120); + } + + Sleep(300, taskParam.Cts); + } + + private void PressEsc(WoodTaskParam taskParam) + { + Simulation.SendInput.Keyboard.KeyPress(VirtualKeyCode.ESCAPE); + Sleep(800, taskParam.Cts); + // 确认在菜单界面 + NewRetry.Do(() => + { + Sleep(1, taskParam.Cts); + var content = CaptureToContent(taskParam.Dispatcher.GameCapture); + var ra = content.CaptureRectArea.Find(_assets.CharacterGuideRo); + if (ra.IsEmpty()) + { + throw new RetryException("未检测到弹出菜单"); + } + + Simulation.SendInput.Keyboard.KeyPress(VirtualKeyCode.VK_Z); + }, TimeSpan.FromSeconds(1), 3); + + // 点击退出 + var captureArea = TaskContext.Instance().SystemInfo.CaptureAreaRect; + _clickOffset.Click(50, captureArea.Height - 50); + + Sleep(500, taskParam.Cts); + + // 点击确认 + var content = CaptureToContent(taskParam.Dispatcher.GameCapture); + content.CaptureRectArea.Find(_assets.ConfirmRo, ra => { ra.ClickCenter(); }); + } + + private void EnterGame(WoodTaskParam taskParam) + { + NewRetry.Do(() => + { + Sleep(1, taskParam.Cts); + var content = CaptureToContent(taskParam.Dispatcher.GameCapture); + var ra = content.CaptureRectArea.Find(_assets.EnterGameRo); + if (ra.IsEmpty()) + { + throw new RetryException("未检测进入游戏字样"); + } + else + { + Simulation.SendInput.Mouse.LeftButtonClick(); + Sleep(5000, taskParam.Cts); + } + }, TimeSpan.FromSeconds(1), 50); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoWood/Utils/NewRetry.cs b/BetterGenshinImpact/GameTask/AutoWood/Utils/NewRetry.cs new file mode 100644 index 00000000..1f78a713 --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoWood/Utils/NewRetry.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; + +namespace BetterGenshinImpact.GameTask.AutoWood.Utils +{ + /// + /// https://stackoverflow.com/questions/1563191/cleanest-way-to-write-retry-logic + /// + public static class NewRetry + { + public static void Do( + Action action, + TimeSpan retryInterval, + int maxAttemptCount = 3) + { + Do(() => + { + action(); + return null; + }, retryInterval, maxAttemptCount); + } + + public static T Do( + Func action, + TimeSpan retryInterval, + int maxAttemptCount = 3) + { + var exceptions = new List(); + + for (int attempted = 0; attempted < maxAttemptCount; attempted++) + { + try + { + if (attempted > 0) + { + Thread.Sleep(retryInterval); + } + + return action(); + } + catch (RetryException ex) + { + exceptions.Add(ex); + } + } + + if (exceptions.Count > 0) + { + throw exceptions.Last(); + } + throw new AggregateException(exceptions); + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoWood/WoodTaskParam.cs b/BetterGenshinImpact/GameTask/AutoWood/WoodTaskParam.cs new file mode 100644 index 00000000..277acb14 --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoWood/WoodTaskParam.cs @@ -0,0 +1,21 @@ +using System; +using System.Threading; +using BetterGenshinImpact.GameTask.Model; + +namespace BetterGenshinImpact.GameTask.AutoWood; + +public class WoodTaskParam : BaseTaskParam +{ + public int WoodRoundNum { get; set; } + public TaskTriggerDispatcher Dispatcher { get; set; } + + public WoodTaskParam(CancellationTokenSource cts, TaskTriggerDispatcher dispatcher, int woodRoundNum) : base(cts) + { + Dispatcher = dispatcher; + WoodRoundNum = woodRoundNum; + if (woodRoundNum == 0) + { + WoodRoundNum = 9999; + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/Common/TaskControl.cs b/BetterGenshinImpact/GameTask/Common/TaskControl.cs index fa4c959b..d6425706 100644 --- a/BetterGenshinImpact/GameTask/Common/TaskControl.cs +++ b/BetterGenshinImpact/GameTask/Common/TaskControl.cs @@ -26,6 +26,31 @@ public class TaskControl Thread.Sleep(millisecondsTimeout); } + public static void Sleep(int millisecondsTimeout, CancellationTokenSource cts) + { + if (cts.IsCancellationRequested) + { + throw new NormalEndException("取消自动伐木任务"); + } + Retry.Do(() => + { + if (cts.IsCancellationRequested) + { + throw new NormalEndException("取消自动伐木任务"); + } + if (!SystemControl.IsGenshinImpactActiveByProcess()) + { + Logger.LogInformation("当前获取焦点的窗口不是原神,暂停"); + throw new RetryException("当前获取焦点的窗口不是原神"); + } + }, TimeSpan.FromSeconds(1), 100); + Thread.Sleep(millisecondsTimeout); + if (cts.IsCancellationRequested) + { + throw new NormalEndException("取消自动伐木任务"); + } + } + public static Bitmap CaptureGameBitmap(IGameCapture? gameCapture) { var bitmap = gameCapture?.Capture(); @@ -47,4 +72,10 @@ public class TaskControl return bitmap; } + + public static CaptureContent CaptureToContent(IGameCapture? gameCapture) + { + var bitmap = CaptureGameBitmap(gameCapture); + return new CaptureContent(bitmap, 0, 0, null); + } } \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/Model/IndependentTaskEnum.cs b/BetterGenshinImpact/GameTask/Model/IndependentTaskEnum.cs index 60494dc5..0e4ba081 100644 --- a/BetterGenshinImpact/GameTask/Model/IndependentTaskEnum.cs +++ b/BetterGenshinImpact/GameTask/Model/IndependentTaskEnum.cs @@ -9,5 +9,6 @@ namespace BetterGenshinImpact.GameTask.Model; public enum IndependentTaskEnum { - AutoGeniusInvokation + AutoGeniusInvokation, + AutoWood, } \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs index 442e598c..e5e3e0bd 100644 --- a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs +++ b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs @@ -10,7 +10,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Windows; +using BetterGenshinImpact.GameTask.AutoWood; using Vanara.PInvoke; namespace BetterGenshinImpact.GameTask @@ -130,7 +132,7 @@ namespace BetterGenshinImpact.GameTask { if (!_timer.Enabled) { - throw new Exception("请先启动BetterGI"); + throw new Exception("请先在启动页启动BetterGI,如果已经启动请重启"); } StopTimer(); @@ -141,6 +143,13 @@ namespace BetterGenshinImpact.GameTask { AutoGeniusInvokationTask.Start((GeniusInvokationTaskParam)param); } + else if (taskType == IndependentTaskEnum.AutoWood) + { + Task.Run(() => + { + new AutoWoodTask().Start((WoodTaskParam)param); + }); + } } public void Dispose() => Stop(); diff --git a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml index c8a77194..c92d6a37 100644 --- a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml @@ -180,7 +180,7 @@ -