auto wood

This commit is contained in:
huiyadanli
2023-11-25 22:30:18 +08:00
parent 5068f4a7f7
commit 408e372481
18 changed files with 434 additions and 50 deletions

View File

@@ -271,6 +271,18 @@
<None Update="GameTask\AutoSkip\Assets\1920x1080\stop_auto.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="GameTask\AutoWood\Assets\1920x1080\character_guide.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="GameTask\AutoWood\Assets\1920x1080\confirm.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="GameTask\AutoWood\Assets\1920x1080\enter_game.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="GameTask\AutoWood\Assets\1920x1080\TheBoonOfTheElderTree.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="User\AutoGeniusInvokation\1.莫娜砂糖琴.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>

View File

@@ -1,8 +0,0 @@
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
public class DuelEndException: System.Exception
{
public DuelEndException(string message) : base(message)
{
}
}

View File

@@ -0,0 +1,8 @@
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
public class NormalEndException: System.Exception
{
public NormalEndException(string message) : base(message)
{
}
}

View File

@@ -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
/// 角色被打败后要切换角色
/// </summary>
/// <param name="duel"></param>
/// <exception cref="DuelEndException"></exception>
/// <exception cref="NormalEndException"></exception>
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)

View File

@@ -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("对局结束");

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -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();
}
}
}

View File

@@ -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;
/// <summary>
/// 自动伐木
/// </summary>
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);
}
}

View File

@@ -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
{
/// <summary>
/// https://stackoverflow.com/questions/1563191/cleanest-way-to-write-retry-logic
/// </summary>
public static class NewRetry
{
public static void Do(
Action action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
Do<object>(() =>
{
action();
return null;
}, retryInterval, maxAttemptCount);
}
public static T Do<T>(
Func<T> action,
TimeSpan retryInterval,
int maxAttemptCount = 3)
{
var exceptions = new List<RetryException>();
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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -9,5 +9,6 @@ namespace BetterGenshinImpact.GameTask.Model;
public enum IndependentTaskEnum
{
AutoGeniusInvokation
AutoGeniusInvokation,
AutoWood,
}

View File

@@ -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();

View File

@@ -180,7 +180,7 @@
</StackPanel>
</ui:CardExpander>
<!--<ui:CardExpander Margin="0,0,0,12" ContentPadding="0" Icon="{ui:SymbolIcon LeafThree24}">
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0" Icon="{ui:SymbolIcon LeafThree24}">
<ui:CardExpander.Header>
<Grid>
<Grid.RowDefinitions>
@@ -202,9 +202,9 @@
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap">
需要装备「王树瑞佑」 -
需要装备「王树瑞佑」 -
<Hyperlink
NavigateUri="http://www.baidu.com"
Command="{Binding GoToAutoWoodUrlCommand}"
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}">
点击查看使用教程
</Hyperlink>
@@ -214,8 +214,8 @@
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,24,0"
Content="启动"
Command="{Binding GoToHotKeyPageCommand}" />
Content="{Binding SwitchAutoWoodButtonText}"
Command="{Binding SwitchAutoWoodCommand}" />
</Grid>
</ui:CardExpander.Header>
<StackPanel>
@@ -240,20 +240,18 @@
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap"
Text="循环伐木多少次,输入 0 则为无限循环直到手动终止" />
<ui:NumberBox
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,24,0"
Maximum="60"
Minimum="5"
Value="{Binding Config.AutoFishingConfig.AutoThrowRodTimeOut, Mode=TwoWay}"
ValidationMode="InvalidInputOverwritten" />
<ui:TextBox x:Name="IntervalNumberBox"
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="90"
Margin="0,0,24,0"
Text="{Binding AutoWoodRoundNum, Mode=TwoWay}" />
</Grid>
</StackPanel>
</ui:CardExpander>
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0" Icon="{ui:SymbolIcon Accessibility24}">
<!--<ui:CardExpander Margin="0,0,0,12" ContentPadding="0" Icon="{ui:SymbolIcon Accessibility24}">
<ui:CardExpander.Header>
<Grid>
<Grid.RowDefinitions>

View File

@@ -1,19 +1,17 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Documents;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.GameTask.AutoGeniusInvokation;
using BetterGenshinImpact.GameTask.Model;
using BetterGenshinImpact.Service.Interface;
using BetterGenshinImpact.View.Pages;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Windows.Input;
using System.Diagnostics;
using System.IO;
using System.Threading;
using BetterGenshinImpact.GameTask.AutoWood;
using Wpf.Ui;
using Wpf.Ui.Controls;
using System.IO;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.GameTask.AutoGeniusInvokation;
using BetterGenshinImpact.GameTask.Model;
using System.Threading;
using MessageBox = System.Windows.MessageBox;
namespace BetterGenshinImpact.ViewModel.Pages;
@@ -31,6 +29,9 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
[ObservableProperty] private string[] _strategyList;
[ObservableProperty] private string _switchAutoGeniusInvokationButtonText;
[ObservableProperty] private int _autoWoodRoundNum;
[ObservableProperty] private string _switchAutoWoodButtonText;
public TaskSettingsPageViewModel(IConfigService configService, INavigationService navigationService, TaskTriggerDispatcher taskTriggerDispatcher)
{
@@ -40,6 +41,9 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
_strategyList = LoadCustomScript();
_switchAutoGeniusInvokationButtonText = "启动";
_switchAutoWoodButtonText = "启动";
}
private string[] LoadCustomScript()
@@ -119,17 +123,47 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
Process.Start(new ProcessStartInfo("https://bgi.huiyadan.com/doc.html#%E8%87%AA%E5%8A%A8%E4%B8%83%E5%9C%A3%E5%8F%AC%E5%94%A4") { UseShellExecute = true });
}
[RelayCommand]
public void OnSwitchAutoWood()
{
try
{
if (SwitchAutoWoodButtonText == "启动")
{
_cts = new CancellationTokenSource();
var param = new WoodTaskParam(_cts, _taskDispatcher, AutoWoodRoundNum);
_taskDispatcher.StartIndependentTask(IndependentTaskEnum.AutoWood, param);
SwitchAutoWoodButtonText = "停止";
}
else
{
_cts?.Cancel();
SwitchAutoWoodButtonText = "启动";
}
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message);
}
}
[RelayCommand]
public void OnGoToAutoWoodUrl()
{
Process.Start(new ProcessStartInfo("https://bgi.huiyadan.com/doc.html#%E8%87%AA%E5%8A%A8%E4%B8%83%E5%9C%A3%E5%8F%AC%E5%94%A4") { UseShellExecute = true });
}
public static void SetSwitchAutoGeniusInvokationButtonText(bool running)
{
var instance = App.GetService<TaskSettingsPageViewModel>();
if (instance == null) { return; }
if (running)
{
instance.SwitchAutoGeniusInvokationButtonText = "停止";
}
else
{
instance.SwitchAutoGeniusInvokationButtonText = "启动";
}
instance.SwitchAutoGeniusInvokationButtonText = running ? "停止" : "启动";
}
public static void SetSwitchAutoWoodButtonText(bool running)
{
var instance = App.GetService<TaskSettingsPageViewModel>();
if (instance == null) { return; }
instance.SwitchAutoWoodButtonText = running ? "停止" : "启动";
}
}