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