mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-25 10:05:49 +08:00
Merge branch 'main' into d-v3
This commit is contained in:
@@ -1,2 +1,5 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=enkanomiya/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=chyan/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=enkanomiya/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mirrorchan/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=steambird/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using BetterGenshinImpact.Core.Recognition.ONNX;
|
||||
using BetterGenshinImpact.GameTask;
|
||||
using BetterGenshinImpact.Helpers;
|
||||
using BetterGenshinImpact.Helpers.Extensions;
|
||||
@@ -125,6 +126,8 @@ public partial class App : Application
|
||||
services.AddSingleton<NotifierManager>();
|
||||
services.AddSingleton<IScriptService, ScriptService>();
|
||||
services.AddSingleton<HutaoNamedPipe>();
|
||||
services.AddSingleton(sp=> sp.GetRequiredService<HomePageViewModel>().Config.HardwareAccelerationConfig);
|
||||
services.AddSingleton<BgiOnnxFactory>();
|
||||
|
||||
// Configuration
|
||||
//services.Configure<AppConfig>(context.Configuration.GetSection(nameof(AppConfig)));
|
||||
@@ -132,6 +135,8 @@ public partial class App : Application
|
||||
)
|
||||
.Build();
|
||||
|
||||
public static IServiceProvider ServiceProvider => _host.Services;
|
||||
|
||||
public static ILogger<T> GetLogger<T>()
|
||||
{
|
||||
return _host.Services.GetService<ILogger<T>>()!;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,148 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ApplicationIcon>Assets\Images\logo.ico</ApplicationIcon>
|
||||
<AssemblyName>BetterGI</AssemblyName>
|
||||
<AssemblyVersion>0.36.2</AssemblyVersion>
|
||||
<Platforms>x64</Platforms>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Images\*.jpg" />
|
||||
<None Remove="Assets\Images\*" />
|
||||
<None Remove="Assets\Images\*.png" />
|
||||
<None Remove="Assets\Images\*.ico" />
|
||||
<None Remove="Assets\Fonts\*.ttf" />
|
||||
<None Remove="Assets\Highlighting\*.xshd" />
|
||||
<None Remove="Assets\Strings\*.html" />
|
||||
<None Remove="Assets\Strings\*.md" />
|
||||
<None Remove="Assets\Audios\*.mp3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Assets\Images\*.jpg" />
|
||||
<Resource Include="Assets\Images\Anniversary\*" />
|
||||
<Resource Include="Assets\Images\*.png" />
|
||||
<Resource Include="Assets\Images\*.ico" />
|
||||
<Resource Include="Assets\Fonts\*.ttf" />
|
||||
<Resource Include="Assets\Highlighting\*.xshd" />
|
||||
<Resource Include="Assets\Strings\*.html" />
|
||||
<Resource Include="Assets\Strings\*.md" />
|
||||
<Resource Include="Assets\Audios\*.mp3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.DirectML" Version="1.18.1" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.Managed" Version="1.18.1" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2592.51" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||
<PackageReference Include="OpenCvSharp4.WpfExtensions" Version="4.8.0.20230708" />
|
||||
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.8.0.20230708" />
|
||||
<PackageReference Include="OpenCvSharp4.Windows" Version="4.8.0.20230708" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.122" />
|
||||
<PackageReference Include="Microsoft.ClearScript.V8" Version="7.4.5" />
|
||||
<PackageReference Include="Microsoft.ClearScript.V8.Native.win-x64" Version="7.4.5" />
|
||||
<PackageReference Include="MouseKeyHook" Version="5.7.1" />
|
||||
<PackageReference Include="PresentMonFps" Version="2.0.5" />
|
||||
<PackageReference Include="Sdcb.PaddleInference" Version="2.5.0.1" />
|
||||
<PackageReference Include="Sdcb.PaddleInference.runtime.win64.openblas" Version="2.5.1" />
|
||||
<PackageReference Include="Sdcb.PaddleOCR" Version="2.7.0" />
|
||||
<PackageReference Include="Sdcb.PaddleOCR.Models.Online" Version="2.7.0.1" />
|
||||
<PackageReference Include="Sdl.MultiSelectComboBox" Version="1.0.103" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.RichTextBoxEx.Wpf" Version="1.1.0.1" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.NtDll" Version="4.0.2" />
|
||||
<PackageReference Include="Vanara.PInvoke.SHCore" Version="4.0.2" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.0.2" />
|
||||
<PackageReference Include="WPF-UI" Version="3.0.5" />
|
||||
<PackageReference Include="WPF-UI.Tray" Version="3.0.5" />
|
||||
<PackageReference Include="WPF-UI.Violeta" Version="3.0.5.23" />
|
||||
<PackageReference Include="YoloV8" Version="4.1.7" />
|
||||
<PackageReference Include="gong-wpf-dragdrop" Version="3.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug'">
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Fischless.GameCapture\Fischless.GameCapture.csproj" />
|
||||
<ProjectReference Include="..\Fischless.HotkeyCapture\Fischless.HotkeyCapture.csproj" />
|
||||
<ProjectReference Include="..\Fischless.WindowsInput\Fischless.WindowsInput.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Assets\Map\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Assets\Model\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoFight\Assets\1920x1080\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoFight\Assets\combat_avatar.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoFishing\Assets\1920x1080\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\1920x1080\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoGeniusInvokation\Assets\tcg_character_card.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoPick\Assets\1920x1080\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoSkip\Assets\1920x1080\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoSkip\Assets\hangout.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoTrackPath\Assets\tp.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\Common\Element\Assets\1920x1080\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\AutoWood\Assets\1920x1080\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\GameLoading\Assets\1920x1080\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\QuickTeleport\Assets\1920x1080\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="GameTask\QuickSereniteaPot\Assets\1920x1080\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="User\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="GameTask\Common\Element\Assets\新文件夹\" />
|
||||
<Folder Include="GameTask\OneDragon\" />
|
||||
<Folder Include="User\AutoPathing\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>BetterGI</AssemblyName>
|
||||
<Version>0.45.2-alpha.1</Version>
|
||||
<Version>0.45.3-alpha.1</Version>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
@@ -48,13 +48,14 @@
|
||||
<PackageReference Include="DeviceId.Windows" Version="6.9.0" />
|
||||
<PackageReference Include="DeviceId.Windows.Wmi" Version="6.9.0" />
|
||||
<PackageReference Include="Emoji.Wpf" Version="0.3.4" />
|
||||
<PackageReference Include="Meziantou.Framework.Win32.CredentialManager" Version="1.7.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.DirectML" Version="1.21.0" />
|
||||
<!--排除掉cpu的runtime dll-->
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.21.0" IncludeAssets="none"/>
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.21.0" IncludeAssets="none" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.Managed" Version="1.21.0" />
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2592.51" />
|
||||
@@ -72,8 +73,9 @@
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.RichTextBoxEx.Wpf" Version="1.1.0.1" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.0-preview.4.25258.110" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.5" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="9.0.4" />
|
||||
<PackageReference Include="TorchSharp" Version="0.105.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.NtDll" Version="4.1.3" />
|
||||
<PackageReference Include="Vanara.PInvoke.SHCore" Version="4.1.3" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.3" />
|
||||
|
||||
@@ -74,7 +74,13 @@ public partial class AllConfig : ObservableObject
|
||||
|
||||
[ObservableProperty]
|
||||
private List<ValueTuple<string, int, string, string>> _nextScheduledTask = [];
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 连续执行任务时,从此任务开始执行
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string NextScriptGroupName { get; set; }= string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 一条龙选中使用的配置
|
||||
/// </summary>
|
||||
|
||||
@@ -111,12 +111,12 @@ public partial class OneDragonFlowConfig : ObservableObject
|
||||
[ObservableProperty]
|
||||
private string _completionAction = string.Empty;
|
||||
|
||||
// 通过当天是哪一天来返回配置
|
||||
// 通过当天(4点起始)是哪一天来返回配置
|
||||
public (string partyName, string domainName, string sundaySelectedValue) GetDomainConfig()
|
||||
{
|
||||
if (WeeklyDomainEnabled)
|
||||
{
|
||||
var dayOfWeek = DateTime.Now.DayOfWeek;
|
||||
var dayOfWeek = (DateTime.Now.Hour >= 4 ? DateTime.Today : DateTime.Today.AddDays(-1)).DayOfWeek;
|
||||
return dayOfWeek switch
|
||||
{
|
||||
DayOfWeek.Monday => (MondayPartyName, MondayDomainName,SundaySelectedValue),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.DirectoryServices.ActiveDirectory;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Config;
|
||||
@@ -13,7 +14,25 @@ public partial class OtherConfig : ObservableObject
|
||||
//自动领取派遣任务城市
|
||||
[ObservableProperty]
|
||||
private string _autoFetchDispatchAdventurersGuildCountry = "无";
|
||||
[ObservableProperty]
|
||||
private AutoRestart _autoRestartConfig = new();
|
||||
public partial class AutoRestart : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private bool _enabled = false;
|
||||
|
||||
//调度器任务连续异常退出几次任务自动重启
|
||||
[ObservableProperty]
|
||||
private int _failureCount = 5;
|
||||
|
||||
//是否同时重启游戏,需开启首页启动配置:同时启动原神、自动进入游戏,此配置才会生效
|
||||
[ObservableProperty]
|
||||
private bool _restartGameTogether = false;
|
||||
|
||||
}
|
||||
|
||||
//public partial class OtherConfig : ObservableObject
|
||||
|
||||
/// <summary>
|
||||
/// 游戏语言名称
|
||||
/// </summary>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.Core.Recognition.ONNX;
|
||||
using BetterGenshinImpact.GameTask;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Recognition.OCR;
|
||||
@@ -34,7 +36,7 @@ public class OcrFactory
|
||||
var result = type switch
|
||||
{
|
||||
OcrEngineTypes.Paddle => new KeyValuePair<string, IOcrService>(cultureInfoName,
|
||||
new PaddleOcrService(cultureInfoName)),
|
||||
new PaddleOcrService(cultureInfoName, App.ServiceProvider.GetRequiredService<BgiOnnxFactory>())),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
|
||||
};
|
||||
Logger.LogDebug("为 {CultureInfoName} 创建了类型为 {Type} 的 OCR服务", result.Key, result.Value);
|
||||
|
||||
@@ -13,10 +13,10 @@ public class Det
|
||||
private readonly OcrVersionConfig _config;
|
||||
private readonly InferenceSession _session;
|
||||
|
||||
public Det(BgiOnnxModel model, OcrVersionConfig config)
|
||||
public Det(BgiOnnxModel model, OcrVersionConfig config, BgiOnnxFactory bgiOnnxFactory)
|
||||
{
|
||||
_config = config;
|
||||
_session = BgiOnnxFactory.Instance.CreateInferenceSession(model, true);
|
||||
_session = bgiOnnxFactory.CreateInferenceSession(model, true);
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the maximum size for resizing the input image.</summary>
|
||||
|
||||
@@ -22,26 +22,26 @@ public class PaddleOcrService : IOcrService
|
||||
|
||||
private readonly Rec _localRecModel;
|
||||
|
||||
public PaddleOcrService(string cultureInfoName)
|
||||
public PaddleOcrService(string cultureInfoName, BgiOnnxFactory bgiOnnxFactory)
|
||||
{
|
||||
var path = Global.Absolute(@"Assets\Model\PaddleOcr");
|
||||
|
||||
switch (cultureInfoName)
|
||||
{
|
||||
case "zh-Hant":
|
||||
_localDetModel = new Det(BgiOnnxModel.PaddleOcrChDet, OcrVersionConfig.PpOcrV4);
|
||||
_localDetModel = new Det(BgiOnnxModel.PaddleOcrChDet, OcrVersionConfig.PpOcrV4, bgiOnnxFactory);
|
||||
_localRecModel = new Rec(BgiOnnxModel.PaddleOcrChtRec, Path.Combine(path, "chinese_cht_dict.txt"),
|
||||
OcrVersionConfig.PpOcrV3);
|
||||
OcrVersionConfig.PpOcrV3, bgiOnnxFactory);
|
||||
break;
|
||||
case "fr":
|
||||
_localDetModel = new Det(BgiOnnxModel.PaddleOcrEnDet, OcrVersionConfig.PpOcrV3);
|
||||
_localDetModel = new Det(BgiOnnxModel.PaddleOcrEnDet, OcrVersionConfig.PpOcrV3, bgiOnnxFactory);
|
||||
_localRecModel = new Rec(BgiOnnxModel.PaddleOcrLatinRec, Path.Combine(path, "latin_dict.txt"),
|
||||
OcrVersionConfig.PpOcrV3);
|
||||
OcrVersionConfig.PpOcrV3, bgiOnnxFactory);
|
||||
break;
|
||||
default:
|
||||
_localDetModel = new Det(BgiOnnxModel.PaddleOcrChDet, OcrVersionConfig.PpOcrV4);
|
||||
_localDetModel = new Det(BgiOnnxModel.PaddleOcrChDet, OcrVersionConfig.PpOcrV4, bgiOnnxFactory);
|
||||
_localRecModel = new Rec(BgiOnnxModel.PaddleOcrChRec, Path.Combine(path, "ppocr_keys_v1.txt"),
|
||||
OcrVersionConfig.PpOcrV4);
|
||||
OcrVersionConfig.PpOcrV4, bgiOnnxFactory);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ public class Rec
|
||||
private readonly IReadOnlyList<string> _labels;
|
||||
private readonly InferenceSession _session;
|
||||
|
||||
public Rec(BgiOnnxModel model, string labelFilePath, OcrVersionConfig config)
|
||||
public Rec(BgiOnnxModel model, string labelFilePath, OcrVersionConfig config, BgiOnnxFactory bgiOnnxFactory)
|
||||
{
|
||||
_config = config;
|
||||
_session = BgiOnnxFactory.Instance.CreateInferenceSession(model, true);
|
||||
_session = bgiOnnxFactory.CreateInferenceSession(model, true);
|
||||
|
||||
|
||||
_labels = File.ReadAllLines(labelFilePath);
|
||||
|
||||
@@ -15,9 +15,9 @@ using Vanara;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Recognition.ONNX;
|
||||
|
||||
public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
public class BgiOnnxFactory
|
||||
{
|
||||
private static readonly ILogger<BgiOnnxFactory> Logger = App.GetLogger<BgiOnnxFactory>();
|
||||
private readonly ILogger logger;
|
||||
|
||||
/// <summary>
|
||||
/// 缓存模型路径。如果一开始使用缓存就一直使用缓存文件,如果没有使用缓存就一直使用原始模型路径。
|
||||
@@ -26,9 +26,15 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<BgiOnnxModel, string?> _cachedModelPaths = new();
|
||||
|
||||
public BgiOnnxFactory()
|
||||
/// <summary>
|
||||
/// 请勿直接实例化此类
|
||||
/// </summary>
|
||||
/// <param name="hardwareAccelerationConfig"></param>
|
||||
/// <param name="logger"></param>
|
||||
public BgiOnnxFactory(HardwareAccelerationConfig hardwareAccelerationConfig, ILogger<BgiOnnxFactory> logger)
|
||||
{
|
||||
var config = TaskContext.Instance().Config.HardwareAccelerationConfig;
|
||||
var config = hardwareAccelerationConfig;
|
||||
this.logger = logger;
|
||||
if (config.AutoAppendCudaPath) AppendCudaPath();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(config.AdditionalPath))
|
||||
@@ -41,9 +47,9 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
TrtUseEmbedMode = config.EmbedTensorRtCache;
|
||||
EnableCache = config.EnableTensorRtCache;
|
||||
CpuOcr = config.CpuOcr;
|
||||
Logger.LogDebug(
|
||||
this.logger.LogDebug(
|
||||
"[ONNX]启用的provider:{Device},初始化参数: InferenceDevice={InferenceDevice}, OptimizedModel={OptimizedModel}, CudaDeviceId={CudaDeviceId}, DmlDeviceId={DmlDeviceId}, EmbedTensorRtCache={EmbedTensorRtCache}, EnableTensorRtCache={EnableTensorRtCache}, CpuOcr={CpuOcr}",
|
||||
string.Join(",", ProviderTypes.Select(Enum.GetName)),
|
||||
string.Join(",", ProviderTypes.Select<ProviderType, string>(Enum.GetName)),
|
||||
config.InferenceDevice,
|
||||
OptimizedModel,
|
||||
CudaDeviceId,
|
||||
@@ -70,7 +76,7 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
/// <param name="dmlDeviceId">dml设备id</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidEnumArgumentException"></exception>
|
||||
private static ProviderType[] GetProviderType(InferenceDeviceType inferenceDeviceType, int cudaDeviceId,
|
||||
private ProviderType[] GetProviderType(InferenceDeviceType inferenceDeviceType, int cudaDeviceId,
|
||||
int dmlDeviceId)
|
||||
{
|
||||
switch (inferenceDeviceType)
|
||||
@@ -94,7 +100,7 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogDebug("[init]无法加载TensorRt。可能不支持,跳过。({Err})", e.Message);
|
||||
logger.LogDebug("[init]无法加载TensorRt。可能不支持,跳过。({Err})", e.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -112,7 +118,7 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogDebug("[init]无法加载DML。可能不支持,跳过。({Err})", e.Message);
|
||||
logger.LogDebug("[init]无法加载DML。可能不支持,跳过。({Err})", e.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -129,14 +135,14 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogDebug("[init]无法加载Cuda。可能不支持,跳过。({Err})", e.Message);
|
||||
logger.LogDebug("[init]无法加载Cuda。可能不支持,跳过。({Err})", e.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
testSession?.Dispose();
|
||||
}
|
||||
|
||||
if (!hasGpu) Logger.LogWarning("[init]GPU自动选择失败,回退到CPU处理");
|
||||
if (!hasGpu) logger.LogWarning("[init]GPU自动选择失败,回退到CPU处理");
|
||||
|
||||
//无论如何都要加入cpu,一些计算在纯gpu上不被支持或性能很烂
|
||||
list.Add(ProviderType.Cpu);
|
||||
@@ -149,7 +155,7 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
/// <summary>
|
||||
/// 自动嗅探并修改path以加载cuda
|
||||
/// </summary>
|
||||
private static void AppendCudaPath()
|
||||
private void AppendCudaPath()
|
||||
{
|
||||
var cudaVersion =
|
||||
Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\NVIDIA Corporation\GPU Computing Toolkit\CUDA",
|
||||
@@ -203,7 +209,7 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
/// 将附加的path应用进来
|
||||
/// </summary>
|
||||
/// <param name="extraPath">附加的path字符串</param>
|
||||
private static void AppendPath(string[] extraPath)
|
||||
private void AppendPath(string[] extraPath)
|
||||
{
|
||||
if (extraPath.Length <= 0) return;
|
||||
|
||||
@@ -212,12 +218,12 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
pathVariables.AddRange(extraPath);
|
||||
if (pathVariables.Count <= 0)
|
||||
{
|
||||
Logger.LogWarning("[GpuAuto]SetCudaPath:No valid paths found.");
|
||||
logger.LogWarning("[GpuAuto]SetCudaPath:No valid paths found.");
|
||||
return;
|
||||
}
|
||||
|
||||
var updatedPath = string.Join(Path.PathSeparator, pathVariables.Distinct());
|
||||
Logger.LogDebug("[GpuAuto]修改进程PATH为:{UpdatedPath}", updatedPath);
|
||||
logger.LogDebug("[GpuAuto]修改进程PATH为:{UpdatedPath}", updatedPath);
|
||||
Environment.SetEnvironmentVariable("PATH", updatedPath, EnvironmentVariableTarget.Process);
|
||||
}
|
||||
|
||||
@@ -228,7 +234,7 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
/// <returns>BgiYoloPredictor</returns>
|
||||
public BgiYoloPredictor CreateYoloPredictor(BgiOnnxModel model)
|
||||
{
|
||||
Logger.LogDebug("[Yolo]创建yolo预测器,模型: {ModelName}", model.Name);
|
||||
logger.LogDebug("[Yolo]创建yolo预测器,模型: {ModelName}", model.Name);
|
||||
if (!EnableCache) return new BgiYoloPredictor(model, model.ModalPath, CreateSessionOptions(model, false));
|
||||
|
||||
var cached = GetCached(model);
|
||||
@@ -245,7 +251,7 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
/// <returns>InferenceSession</returns>
|
||||
public InferenceSession CreateInferenceSession(BgiOnnxModel model, bool ocr = false)
|
||||
{
|
||||
Logger.LogDebug("[ONNX]创建推理会话,模型: {ModelName}", model.Name);
|
||||
logger.LogDebug("[ONNX]创建推理会话,模型: {ModelName}", model.Name);
|
||||
ProviderType[]? providerTypes = null;
|
||||
if (CpuOcr && ocr) providerTypes = [ProviderType.Cpu];
|
||||
|
||||
@@ -275,7 +281,7 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
// 判断文件是否存在
|
||||
if (File.Exists(result)) return result;
|
||||
|
||||
Logger.LogWarning("[ONNX]模型 {Model} 的缓存文件可能已被删除,使用原始模型文件。", model.Name);
|
||||
logger.LogWarning("[ONNX]模型 {Model} 的缓存文件可能已被删除,使用原始模型文件。", model.Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -289,7 +295,7 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
var ctxA = Path.Combine(model.CachePath, "trt", "_ctx.onnx");
|
||||
if (File.Exists(ctxA))
|
||||
{
|
||||
Logger.LogDebug("[ONNX]模型 {Model} 命中TRT匿名缓存文件: {Path}", model.Name, ctxA);
|
||||
logger.LogDebug("[ONNX]模型 {Model} 命中TRT匿名缓存文件: {Path}", model.Name, ctxA);
|
||||
return ctxA;
|
||||
}
|
||||
|
||||
@@ -297,11 +303,11 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
Path.GetFileNameWithoutExtension(model.ModalPath) + "_ctx.onnx");
|
||||
if (File.Exists(ctxB))
|
||||
{
|
||||
Logger.LogDebug("[ONNX]模型 {Model} 命中TRT命名缓存文件: {Path}", model.Name, ctxB);
|
||||
logger.LogDebug("[ONNX]模型 {Model} 命中TRT命名缓存文件: {Path}", model.Name, ctxB);
|
||||
return ctxB;
|
||||
}
|
||||
|
||||
Logger.LogDebug("[ONNX]没有找到模型 {Model} 的模型缓存文件。", model.Name);
|
||||
logger.LogDebug("[ONNX]没有找到模型 {Model} 的模型缓存文件。", model.Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -315,7 +321,7 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
/// <param name="forcedProvider">强制使用的Provider,为空或null则不强制</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidEnumArgumentException"></exception>
|
||||
private SessionOptions CreateSessionOptions(BgiOnnxModel path, bool genCache, ProviderType[]? forcedProvider = null)
|
||||
protected SessionOptions CreateSessionOptions(BgiOnnxModel path, bool genCache, ProviderType[]? forcedProvider = null)
|
||||
{
|
||||
var sessionOptions = new SessionOptions();
|
||||
foreach (var type in
|
||||
@@ -355,7 +361,7 @@ public class BgiOnnxFactory : Singleton<BgiOnnxFactory>
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError("无法加载指定的 ONNX provider {Provider},跳过。请检查推理设备配置是否正确。({Err})", Enum.GetName(type),
|
||||
logger.LogError("无法加载指定的 ONNX provider {Provider},跳过。请检查推理设备配置是否正确。({Err})", Enum.GetName(type),
|
||||
e.Message);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using BetterGenshinImpact.Core.Recognition.OCR.engine;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BetterGenshinImpact.Core.Recognition.ONNX.SVTR;
|
||||
|
||||
@@ -24,7 +25,7 @@ public class PickTextInference : ITextInference
|
||||
|
||||
public PickTextInference()
|
||||
{
|
||||
_session = BgiOnnxFactory.Instance.CreateInferenceSession(BgiOnnxModel.YapModelTraining,true);
|
||||
_session = App.ServiceProvider.GetRequiredService<BgiOnnxFactory>().CreateInferenceSession(BgiOnnxModel.YapModelTraining,true);
|
||||
|
||||
var wordJsonPath = Global.Absolute(@"Assets\Model\Yap\index_2_word.json");
|
||||
if (!File.Exists(wordJsonPath)) throw new FileNotFoundException("Yap字典文件不存在", wordJsonPath);
|
||||
|
||||
@@ -79,6 +79,15 @@ public class Dispatcher
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RunTask(SoloTask soloTask, CancellationTokenSource customCts)
|
||||
{
|
||||
// 创建链接的取消令牌源,任何一个取消都会触发
|
||||
CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
customCts.Token,
|
||||
CancellationContext.Instance.Cts.Token);
|
||||
await RunTask(soloTask, linkedCts.Token);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 运行独立任务
|
||||
@@ -90,10 +99,10 @@ public class Dispatcher
|
||||
/// - AutoFight: 启动自动战斗任务
|
||||
/// - AutoDomain: 启动自动秘境任务
|
||||
/// </param>
|
||||
/// <param name="customCts">自定义取消令牌源,允许从JS控制任务取消</param>
|
||||
/// <param name="customCt">自定义取消令牌,允许从JS控制任务取消</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public async Task RunTask(SoloTask soloTask, System.Threading.CancellationTokenSource customCts = null)
|
||||
public async Task RunTask(SoloTask soloTask, CancellationToken? customCt = null)
|
||||
{
|
||||
if (soloTask == null)
|
||||
{
|
||||
@@ -106,69 +115,68 @@ public class Dispatcher
|
||||
throw new ArgumentNullException(nameof(taskSettingsPageViewModel), "内部视图模型对象为空");
|
||||
}
|
||||
|
||||
// 创建一个链接的取消令牌源,同时监听自定义令牌和全局令牌
|
||||
CancellationTokenSource linkedCts = null;
|
||||
|
||||
CancellationToken cancellationToken;
|
||||
|
||||
if (customCts != null)
|
||||
if (customCt != null)
|
||||
{
|
||||
// 创建链接的取消令牌源,任何一个取消都会触发
|
||||
linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
customCts.Token,
|
||||
CancellationContext.Instance.Cts.Token);
|
||||
cancellationToken = linkedCts.Token;
|
||||
cancellationToken = customCt.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果没有自定义令牌,就使用全局令牌
|
||||
cancellationToken = CancellationContext.Instance.Cts.Token;
|
||||
}
|
||||
|
||||
try
|
||||
|
||||
// 根据名称执行任务
|
||||
switch (soloTask.Name)
|
||||
{
|
||||
// 根据名称执行任务
|
||||
switch (soloTask.Name)
|
||||
{
|
||||
case "AutoGeniusInvokation":
|
||||
if (taskSettingsPageViewModel.GetTcgStrategy(out var content))
|
||||
{
|
||||
return;
|
||||
}
|
||||
case "AutoGeniusInvokation":
|
||||
if (taskSettingsPageViewModel.GetTcgStrategy(out var content))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await new AutoGeniusInvokationTask(new GeniusInvokationTaskParam(content)).Start(cancellationToken);
|
||||
break;
|
||||
await new AutoGeniusInvokationTask(new GeniusInvokationTaskParam(content)).Start(cancellationToken);
|
||||
break;
|
||||
|
||||
case "AutoWood":
|
||||
await new AutoWoodTask(new WoodTaskParam(taskSettingsPageViewModel.AutoWoodRoundNum,
|
||||
taskSettingsPageViewModel.AutoWoodDailyMaxCount)).Start(cancellationToken);
|
||||
break;
|
||||
case "AutoWood":
|
||||
await new AutoWoodTask(new WoodTaskParam(taskSettingsPageViewModel.AutoWoodRoundNum,
|
||||
taskSettingsPageViewModel.AutoWoodDailyMaxCount)).Start(cancellationToken);
|
||||
break;
|
||||
|
||||
case "AutoFight":
|
||||
await new AutoFightHandler().RunAsyncByScript(cancellationToken, null, _config);
|
||||
break;
|
||||
case "AutoFight":
|
||||
await new AutoFightHandler().RunAsyncByScript(cancellationToken, null, _config);
|
||||
break;
|
||||
|
||||
case "AutoDomain":
|
||||
if (taskSettingsPageViewModel.GetFightStrategy(out var path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
case "AutoDomain":
|
||||
if (taskSettingsPageViewModel.GetFightStrategy(out var path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await new AutoDomainTask(new AutoDomainParam(0, path)).Start(cancellationToken);
|
||||
break;
|
||||
await new AutoDomainTask(new AutoDomainParam(0, path)).Start(cancellationToken);
|
||||
break;
|
||||
|
||||
case "AutoFishing":
|
||||
await new AutoFishingTask(AutoFishingTaskParam.BuildFromSoloTaskConfig(soloTask.Config)).Start(
|
||||
cancellationToken);
|
||||
break;
|
||||
case "AutoFishing":
|
||||
await new AutoFishingTask(AutoFishingTaskParam.BuildFromSoloTaskConfig(soloTask.Config)).Start(
|
||||
cancellationToken);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"未知的任务名称: {soloTask.Name}", nameof(soloTask.Name));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 释放链接的取消令牌源
|
||||
linkedCts?.Dispose();
|
||||
default:
|
||||
throw new ArgumentException($"未知的任务名称: {soloTask.Name}", nameof(soloTask.Name));
|
||||
}
|
||||
}
|
||||
|
||||
public CancellationTokenSource GetLinkedCancellationTokenSource()
|
||||
{
|
||||
// 创建一个新的链接令牌源,链接到全局令牌
|
||||
return CancellationTokenSource.CreateLinkedTokenSource(CancellationContext.Instance.Cts.Token);
|
||||
}
|
||||
|
||||
|
||||
public CancellationToken GetLinkedCancellationToken()
|
||||
{
|
||||
return GetLinkedCancellationTokenSource().Token;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
using BetterGenshinImpact.Service;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text.Json;
|
||||
|
||||
using BetterGenshinImpact.Service;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
namespace BetterGenshinImpact.Core.Script.Group;
|
||||
|
||||
/// <summary>
|
||||
@@ -24,6 +23,14 @@ public partial class ScriptGroup : ObservableObject
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<ScriptGroupProject> _projects = [];
|
||||
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public bool NextFlag
|
||||
{
|
||||
get => _nextFlag;
|
||||
set => SetProperty(ref _nextFlag, value);
|
||||
}
|
||||
private bool _nextFlag;
|
||||
|
||||
public ScriptGroup()
|
||||
{
|
||||
Projects.CollectionChanged += ProjectsCollectionChanged;
|
||||
@@ -41,7 +48,7 @@ public partial class ScriptGroup : ObservableObject
|
||||
|
||||
public static ScriptGroup FromJson(string json)
|
||||
{
|
||||
var group = Newtonsoft.Json.JsonConvert.DeserializeObject<ScriptGroup>(json) ?? throw new Exception("解析配置组JSON配置失败");
|
||||
var group = JsonConvert.DeserializeObject<ScriptGroup>(json) ?? throw new Exception("解析配置组JSON配置失败");
|
||||
ResetGroupInfo(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
@@ -72,12 +72,29 @@ public partial class ScriptGroupProject : ObservableObject
|
||||
[JsonIgnore]
|
||||
public ScriptGroup? GroupInfo { get; set; }
|
||||
|
||||
private bool? _nextFlag = false;
|
||||
private bool? _skipFlag = false;
|
||||
|
||||
/// <summary>
|
||||
/// 下一个从此执行标志
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
[ObservableProperty]
|
||||
public bool? _nextFlag = false;
|
||||
public bool? NextFlag
|
||||
{
|
||||
get => _nextFlag;
|
||||
set => SetProperty(ref _nextFlag, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接跳过标志
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool? SkipFlag
|
||||
{
|
||||
get => _skipFlag;
|
||||
set => SetProperty(ref _skipFlag, value);
|
||||
}
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
private bool? _allowJsNotification = true;
|
||||
|
||||
@@ -40,6 +40,7 @@ using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
|
||||
using System.Collections.ObjectModel;
|
||||
using BetterGenshinImpact.Core.Script.Dependence;
|
||||
using Compunet.YoloSharp;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoDomain;
|
||||
|
||||
@@ -72,7 +73,7 @@ public class AutoDomainTask : ISoloTask
|
||||
{
|
||||
AutoFightAssets.DestroyInstance();
|
||||
_taskParam = taskParam;
|
||||
_predictor = BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiTree);
|
||||
_predictor = App.ServiceProvider.GetRequiredService<BgiOnnxFactory>().CreateYoloPredictor(BgiOnnxModel.BgiTree);
|
||||
|
||||
_config = TaskContext.Instance().Config.AutoDomainConfig;
|
||||
|
||||
@@ -279,7 +280,7 @@ public class AutoDomainTask : ISoloTask
|
||||
else if ("塞西莉亚苗圃".Equals(_taskParam.DomainName))
|
||||
{
|
||||
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown);
|
||||
Thread.Sleep(2300);
|
||||
Thread.Sleep(2500);
|
||||
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
|
||||
}
|
||||
else if ("太山府".Equals(_taskParam.DomainName))
|
||||
|
||||
@@ -31,7 +31,13 @@ public partial class AutoFightConfig : ObservableObject
|
||||
/// 多种用分号分隔,例如:白术;钟离,12;,如果人名,则用内置cd检查,如果是人名和数字,则把数字当做出招cd(秒)。
|
||||
/// </summary>
|
||||
[ObservableProperty] private string _actionSchedulerByCd = "";
|
||||
|
||||
/// <summary>
|
||||
/// 只拾取精英掉落
|
||||
/// Closed :关闭功能
|
||||
/// AllowAutoPickupForNonElite: 非精英允许自动拾取:战斗过程中掉落脚下的可以自动拾取,但不会执行万叶拾取和拾取配置逻辑。
|
||||
/// DisableAutoPickupForNonElite: 非精英关闭拾取:战斗过程中掉落到脚下的也不会自动拾取。
|
||||
/// </summary>
|
||||
[ObservableProperty] private string _onlyPickEliteDropsMode = "Closed";
|
||||
[Serializable]
|
||||
public partial class FightFinishDetectConfig : ObservableObject
|
||||
{
|
||||
|
||||
@@ -35,6 +35,7 @@ public class AutoFightParam : BaseTaskParam
|
||||
FinishDetectConfig.CheckEndDelay = autoFightConfig.FinishDetectConfig.CheckEndDelay;
|
||||
FinishDetectConfig.BeforeDetectDelay = autoFightConfig.FinishDetectConfig.BeforeDetectDelay;
|
||||
KazuhaPartyName = autoFightConfig.KazuhaPartyName;
|
||||
OnlyPickEliteDropsMode = autoFightConfig.OnlyPickEliteDropsMode;
|
||||
//下面参数固定,只取自动战斗里面的
|
||||
FinishDetectConfig.BattleEndProgressBarColor = TaskContext.Instance().Config.AutoFightConfig.FinishDetectConfig.BattleEndProgressBarColor;
|
||||
FinishDetectConfig.BattleEndProgressBarColorTolerance = TaskContext.Instance().Config.AutoFightConfig.FinishDetectConfig.BattleEndProgressBarColorTolerance;
|
||||
@@ -53,6 +54,7 @@ public class AutoFightParam : BaseTaskParam
|
||||
public bool KazuhaPickupEnabled = true;
|
||||
public string ActionSchedulerByCd = "";
|
||||
public string KazuhaPartyName;
|
||||
public string OnlyPickEliteDropsMode="";
|
||||
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ using OpenCvSharp;
|
||||
using BetterGenshinImpact.Helpers;
|
||||
using Vanara;
|
||||
using Vanara.PInvoke;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoFight;
|
||||
@@ -185,7 +186,7 @@ public class AutoFightTask : ISoloTask
|
||||
|
||||
if (_taskParam.FightFinishDetectEnabled)
|
||||
{
|
||||
_predictor = BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiWorld);
|
||||
_predictor = App.ServiceProvider.GetRequiredService<BgiOnnxFactory>().CreateYoloPredictor(BgiOnnxModel.BgiWorld);
|
||||
}
|
||||
|
||||
_finishDetectConfig = new TaskFightFinishDetectConfig(_taskParam.FinishDetectConfig);
|
||||
|
||||
@@ -720,7 +720,7 @@ public class Avatar
|
||||
{
|
||||
var dpi = TaskContext.Instance().DpiScale;
|
||||
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyDown);
|
||||
int cnt = 0;
|
||||
int tick = -4; // 起飞那一刻需要多一点点时间用来矫正视角高度
|
||||
while (ms >= 0)
|
||||
{
|
||||
if (Ct is { IsCancellationRequested: true })
|
||||
@@ -728,12 +728,47 @@ public class Avatar
|
||||
return;
|
||||
}
|
||||
|
||||
// 恰在蓄力时快速转动会把视角趋向于水平,所以在回正的时候不做额外Y轴移动
|
||||
double rate = cnt % 10 < 5 ? 0 : 4.5; //每500ms做一轮上下移动。
|
||||
cnt++;
|
||||
Simulation.SendInput.Mouse.MoveMouseBy((int)(500 * dpi), (int)(rate * 100 * dpi));
|
||||
ms -= 50;
|
||||
Sleep(50);
|
||||
// 恰在蓄力时转得越快越容易把视角趋向于水平
|
||||
// 基于上面这个特性,如果我们用同一个鼠标方向向量,大致能在所有设备上控制视角高低(只要帧率不太低)
|
||||
|
||||
// 恰的子弹上膛机制:怪物要在HUD准星框内超过一定时长(体感0.2-0.3秒)才能让子弹上膛。所以搜索敌人要低速。不然敌人体型小或者远就很容易锁不上。
|
||||
const double lowspeed = 0.7, highspeed = 50;
|
||||
double rateX, rateY;
|
||||
if (tick < 3)
|
||||
{
|
||||
rateX = highspeed;
|
||||
rateY = highspeed * 0.23;
|
||||
}
|
||||
else if (tick < 40)
|
||||
{
|
||||
rateX = lowspeed * 0.7;
|
||||
rateY = 0;
|
||||
}
|
||||
else if (tick < 43)
|
||||
{
|
||||
rateX = highspeed;
|
||||
rateY = highspeed * 0.4;
|
||||
}
|
||||
else if (tick < 70)
|
||||
{
|
||||
rateX = lowspeed * 0.9;
|
||||
rateY = 0;
|
||||
}
|
||||
else if (tick < 73)
|
||||
{
|
||||
rateX = highspeed;
|
||||
rateY = highspeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
rateX = lowspeed;
|
||||
rateY = 0;
|
||||
}
|
||||
Simulation.SendInput.Mouse.MoveMouseBy((int)(rateX * 50 * dpi), (int)(rateY * 50 * dpi));
|
||||
|
||||
tick = (tick + 1) % 100;
|
||||
Sleep(25);
|
||||
ms -= 25;
|
||||
}
|
||||
|
||||
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyUp);
|
||||
|
||||
@@ -12,6 +12,11 @@ public class AvatarMacro
|
||||
public string ScriptContent4 { get; set; } = string.Empty;
|
||||
public string ScriptContent5 { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色当前使用的战斗宏编号 (1-5),如果为0则使用默认宏1
|
||||
/// </summary>
|
||||
public int MacroPriority { get; set; } = 0;
|
||||
|
||||
public string GetScriptContent(int index)
|
||||
{
|
||||
return index switch
|
||||
@@ -25,9 +30,34 @@ public class AvatarMacro
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证宏优先级是否有效
|
||||
/// </summary>
|
||||
/// <returns>如果优先级有效返回true,否则返回false</returns>
|
||||
public bool IsValidMacroPriority()
|
||||
{
|
||||
return MacroPriority >= 0 && MacroPriority <= 5;
|
||||
}
|
||||
|
||||
public string GetScriptContent()
|
||||
{
|
||||
return GetScriptContent(TaskContext.Instance().Config.MacroConfig.CombatMacroPriority);
|
||||
// 验证宏优先级的有效性
|
||||
if (!IsValidMacroPriority())
|
||||
{
|
||||
MacroPriority = 0; // 重置为默认值
|
||||
}
|
||||
|
||||
// 如果角色设置了自己的宏优先级,使用角色的;否则使用全局配置
|
||||
var priority = MacroPriority > 0 ? MacroPriority :
|
||||
TaskContext.Instance().Config.MacroConfig.CombatMacroPriority;
|
||||
|
||||
// 确保最终优先级在有效范围内
|
||||
if (priority < 1 || priority > 5)
|
||||
{
|
||||
priority = 1; // 默认使用宏1
|
||||
}
|
||||
|
||||
return GetScriptContent(priority);
|
||||
}
|
||||
|
||||
public List<CombatCommand>? LoadCommands()
|
||||
|
||||
@@ -19,6 +19,7 @@ using Compunet.YoloSharp.Data;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using static BetterGenshinImpact.GameTask.Common.TaskControl;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoFight.Model;
|
||||
|
||||
@@ -36,7 +37,7 @@ public class CombatScenes : IDisposable
|
||||
|
||||
|
||||
private readonly BgiYoloPredictor _predictor =
|
||||
BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiAvatarSide);
|
||||
App.ServiceProvider.GetRequiredService<BgiOnnxFactory>().CreateYoloPredictor(BgiOnnxModel.BgiAvatarSide);
|
||||
|
||||
public int ExpectedTeamAvatarNum { get; private set; } = 4;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public class OneKeyFightTask : Singleton<OneKeyFightTask>
|
||||
private Task? _fightTask;
|
||||
|
||||
private bool _isKeyDown = false;
|
||||
private int activeMacroPriority = -1;
|
||||
private int _activeMacroPriority = -1;
|
||||
private DateTime _lastUpdateTime = DateTime.MinValue;
|
||||
|
||||
private CombatScenes? _currentCombatScenes;
|
||||
@@ -40,10 +40,10 @@ public class OneKeyFightTask : Singleton<OneKeyFightTask>
|
||||
}
|
||||
|
||||
_isKeyDown = true;
|
||||
if (activeMacroPriority != TaskContext.Instance().Config.MacroConfig.CombatMacroPriority ||
|
||||
if (_activeMacroPriority != TaskContext.Instance().Config.MacroConfig.CombatMacroPriority ||
|
||||
IsAvatarMacrosEdited())
|
||||
{
|
||||
activeMacroPriority = TaskContext.Instance().Config.MacroConfig.CombatMacroPriority;
|
||||
_activeMacroPriority = TaskContext.Instance().Config.MacroConfig.CombatMacroPriority;
|
||||
_avatarMacros = LoadAvatarMacros();
|
||||
Logger.LogInformation("加载一键宏配置完成");
|
||||
}
|
||||
@@ -187,7 +187,7 @@ public class OneKeyFightTask : Singleton<OneKeyFightTask>
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("→ {Name}配置[{Priority}]为空,请先配置一键宏", activeAvatar.Name, activeMacroPriority);
|
||||
Logger.LogWarning("→ {Name}配置[{Priority}]为空,请先配置一键宏", activeAvatar.Name, _activeMacroPriority);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,4 +46,10 @@ public partial class AutoFishingConfig : ObservableObject
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private FishingTimePolicy _fishingTimePolicy = FishingTimePolicy.All;
|
||||
|
||||
/// <summary>
|
||||
/// torch库文件地址
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private string _torchDllFullPath = @"C:\torch\lib\torch_cpu.dll";
|
||||
}
|
||||
@@ -59,49 +59,42 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
/// <param name="src"></param>
|
||||
/// <param name="liftingWordsAreaRect"></param>
|
||||
/// <returns></returns>
|
||||
public static Rect MatchFishBiteWords(Mat src, Rect liftingWordsAreaRect)
|
||||
public static Rect? MatchFishBiteWords(Mat src, Rect liftingWordsAreaRect)
|
||||
{
|
||||
try
|
||||
using Mat rgb = src.CvtColor(ColorConversionCodes.BGR2RGB);
|
||||
var lowPurple = new Scalar(253, 253, 253);
|
||||
var highPurple = new Scalar(255, 255, 255);
|
||||
using Mat purple = rgb.InRange(lowPurple, highPurple);
|
||||
using Mat threshold = purple.Threshold(0, 255, ThresholdTypes.Binary);
|
||||
var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(20, 20),
|
||||
new Point(-1, -1));
|
||||
using Mat dilate = threshold.Dilate(kernel); //膨胀
|
||||
|
||||
Cv2.FindContours(dilate, out var contours, out _, RetrievalModes.External,
|
||||
ContourApproximationModes.ApproxSimple, null);
|
||||
if (contours.Length > 0)
|
||||
{
|
||||
Cv2.CvtColor(src, src, ColorConversionCodes.BGR2RGB);
|
||||
var lowPurple = new Scalar(253, 253, 253);
|
||||
var highPurple = new Scalar(255, 255, 255);
|
||||
Cv2.InRange(src, lowPurple, highPurple, src);
|
||||
Cv2.Threshold(src, src, 0, 255, ThresholdTypes.Binary);
|
||||
var kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(20, 20),
|
||||
new OpenCvSharp.Point(-1, -1));
|
||||
Cv2.Dilate(src, src, kernel); //膨胀
|
||||
|
||||
Cv2.FindContours(src, out var contours, out _, RetrievalModes.External,
|
||||
ContourApproximationModes.ApproxSimple, null);
|
||||
if (contours.Length > 0)
|
||||
var boxes = contours.Select(Cv2.BoundingRect);
|
||||
var rects = boxes.ToList();
|
||||
if (rects.Count > 1)
|
||||
{
|
||||
var boxes = contours.Select(Cv2.BoundingRect);
|
||||
var rects = boxes.ToList();
|
||||
if (rects.Count > 1)
|
||||
{
|
||||
rects.Sort((a, b) => b.Height.CompareTo(a.Height));
|
||||
}
|
||||
rects.Sort((a, b) => b.Height.CompareTo(a.Height));
|
||||
}
|
||||
|
||||
//VisionContext.Instance().DrawContent.PutRect("FishBiteTipsDebug",
|
||||
// rects[0].ToWindowsRectangleOffset(liftingWordsAreaRect.X, liftingWordsAreaRect.Y)
|
||||
// .ToRectDrawable());
|
||||
if (rects[0].Height < src.Height
|
||||
&& rects[0].Width * 1.0 / rects[0].Height >= 3 // 长宽比判断
|
||||
&& liftingWordsAreaRect.Width > rects[0].Width * 3 // 文字范围3倍小于钓鱼条范围的
|
||||
&& liftingWordsAreaRect.Width * 1.0 / 2 > rects[0].X // 中轴线判断左
|
||||
&& liftingWordsAreaRect.Width * 1.0 / 2 < rects[0].X + rects[0].Width) // 中轴线判断右
|
||||
{
|
||||
return rects[0];
|
||||
}
|
||||
//VisionContext.Instance().DrawContent.PutRect("FishBiteTipsDebug",
|
||||
// rects[0].ToWindowsRectangleOffset(liftingWordsAreaRect.X, liftingWordsAreaRect.Y)
|
||||
// .ToRectDrawable());
|
||||
if (rects[0].Height < src.Height
|
||||
&& rects[0].Width * 1.0 / rects[0].Height >= 3 // 长宽比判断
|
||||
&& liftingWordsAreaRect.Width > rects[0].Width * 3 // 文字范围3倍小于钓鱼条范围的
|
||||
&& liftingWordsAreaRect.Width * 1.0 / 2 > rects[0].X // 中轴线判断左
|
||||
&& liftingWordsAreaRect.Width * 1.0 / 2 < rects[0].X + rects[0].Width) // 中轴线判断右
|
||||
{
|
||||
return rects[0];
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(e);
|
||||
}
|
||||
|
||||
return default;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ using Microsoft.Extensions.Localization;
|
||||
using BetterGenshinImpact.Helpers;
|
||||
using BetterGenshinImpact.Core.Recognition.OCR;
|
||||
using Compunet.YoloSharp;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
{
|
||||
@@ -38,7 +39,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
private readonly AutoFishingTaskParam param;
|
||||
|
||||
private readonly BgiYoloPredictor _predictor =
|
||||
BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiFish);
|
||||
App.ServiceProvider.GetRequiredService<BgiOnnxFactory>().CreateYoloPredictor(BgiOnnxModel.BgiFish);
|
||||
|
||||
public AutoFishingTask(AutoFishingTaskParam param)
|
||||
{
|
||||
@@ -89,7 +90,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
.PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard, _logger, param.SaveScreenshotOnKeyTick, input))
|
||||
//.MySimpleParallel("举起鱼竿并抛竿", policy: SimpleParallelPolicy.OnlyOneMustSucceed)
|
||||
// .PushLeaf(() => new LiftAndHold("举起鱼竿", blackboard, _logger, param.SaveScreenshotOnKeyTick, input))
|
||||
.PushLeaf(() => new ThrowRod("抛竿", blackboard, _logger, param.SaveScreenshotOnKeyTick, input))
|
||||
.PushLeaf(() => new ThrowRod("抛竿", blackboard, param.UseTorch, _logger, param.SaveScreenshotOnKeyTick, input))
|
||||
//.End()
|
||||
.End()
|
||||
.End()
|
||||
@@ -120,9 +121,9 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
.Build();
|
||||
// @formatter:on
|
||||
_logger.LogInformation("→ {Text}", "自动钓鱼,启动!");
|
||||
_logger.LogWarning("请不要携带任何{Msg},极有可能会误识别导致无法结束自动钓鱼!", "跟宠");
|
||||
_logger.LogWarning("请不要携带任何{Msg},极有可能会误识别导致拖慢速度!", "跟宠");
|
||||
_logger.LogInformation(
|
||||
$"当前参数:{param.WholeProcessTimeoutSeconds},{param.ThrowRodTimeOutTimeoutSeconds},{param.FishingTimePolicy}, {param.SaveScreenshotOnKeyTick}, {param.GameCultureInfo}");
|
||||
$"当前参数:{param.WholeProcessTimeoutSeconds},{param.ThrowRodTimeOutTimeoutSeconds},{param.FishingTimePolicy}, {param.SaveScreenshotOnKeyTick}, {param.GameCultureInfo}, {param.UseTorch}");
|
||||
TaskContext.Instance().Config.AutoFishingConfig.Enabled = false;
|
||||
_logger.LogInformation("全自动运行时,自动切换实时任务中的半自动钓鱼功能为关闭状态");
|
||||
|
||||
|
||||
@@ -5,17 +5,20 @@ using System.Text;
|
||||
using BetterGenshinImpact.Helpers;
|
||||
using Microsoft.ClearScript;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using TorchSharp;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
{
|
||||
public class AutoFishingTaskParam : BaseTaskParam
|
||||
{
|
||||
public AutoFishingTaskParam(int wholeProcessTimeoutSeconds, int throwRodTimeOutTimeoutSeconds, FishingTimePolicy fishingTimePolicy, bool saveScreenshotOnKeyTick, CultureInfo? cultureInfo) : base(cultureInfo)
|
||||
public AutoFishingTaskParam(int wholeProcessTimeoutSeconds, int throwRodTimeOutTimeoutSeconds, FishingTimePolicy fishingTimePolicy, bool saveScreenshotOnKeyTick, CultureInfo? cultureInfo, bool useTorch) : base(cultureInfo)
|
||||
{
|
||||
WholeProcessTimeoutSeconds = wholeProcessTimeoutSeconds;
|
||||
ThrowRodTimeOutTimeoutSeconds = throwRodTimeOutTimeoutSeconds;
|
||||
FishingTimePolicy = fishingTimePolicy;
|
||||
SaveScreenshotOnKeyTick = saveScreenshotOnKeyTick;
|
||||
UseTorch = useTorch;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +26,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
public int ThrowRodTimeOutTimeoutSeconds { get; set; }
|
||||
public FishingTimePolicy FishingTimePolicy { get; set; }
|
||||
public bool SaveScreenshotOnKeyTick { get; set; }
|
||||
public bool UseTorch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 从JS请求参数构建任务参数
|
||||
@@ -44,7 +48,22 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
var fishingTimePolicy = (FishingTimePolicy)ScriptObjectConverter.GetValue(jsObject, "fishingTimePolicy", (int)autoFishingConfig.FishingTimePolicy);
|
||||
var saveScreenshotOnKeyTick = ScriptObjectConverter.GetValue(jsObject, "saveScreenshotOnKeyTick", false);
|
||||
|
||||
return new AutoFishingTaskParam(wholeProcessTimeoutSeconds, throwRodTimeOutTimeoutSeconds, fishingTimePolicy, saveScreenshotOnKeyTick, null);
|
||||
bool useTorch;
|
||||
try
|
||||
{
|
||||
NativeLibrary.Load(autoFishingConfig.TorchDllFullPath);
|
||||
if (torch.TryInitializeDeviceType(DeviceType.CUDA))
|
||||
{
|
||||
torch.set_default_device(new torch.Device(DeviceType.CUDA));
|
||||
}
|
||||
useTorch = true;
|
||||
}
|
||||
catch (Exception e) when (e is DllNotFoundException || e is NotSupportedException)
|
||||
{
|
||||
useTorch = false;
|
||||
}
|
||||
|
||||
return new AutoFishingTaskParam(wholeProcessTimeoutSeconds, throwRodTimeOutTimeoutSeconds, fishingTimePolicy, saveScreenshotOnKeyTick, null, useTorch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -56,7 +75,21 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
public static AutoFishingTaskParam BuildFromConfig(AutoFishingConfig config, bool saveScreenshotOnKeyTick = false)
|
||||
{
|
||||
CultureInfo cultureInfo = new CultureInfo(TaskContext.Instance().Config.OtherConfig.GameCultureInfoName);
|
||||
return new AutoFishingTaskParam(config.WholeProcessTimeoutSeconds, config.AutoThrowRodTimeOut, config.FishingTimePolicy, saveScreenshotOnKeyTick, cultureInfo);
|
||||
bool useTorch;
|
||||
try
|
||||
{
|
||||
NativeLibrary.Load(config.TorchDllFullPath);
|
||||
if (torch.TryInitializeDeviceType(DeviceType.CUDA))
|
||||
{
|
||||
torch.set_default_device(new torch.Device(DeviceType.CUDA));
|
||||
}
|
||||
useTorch = true;
|
||||
}
|
||||
catch (Exception e) when (e is DllNotFoundException || e is NotSupportedException)
|
||||
{
|
||||
useTorch = false;
|
||||
}
|
||||
return new AutoFishingTaskParam(config.WholeProcessTimeoutSeconds, config.AutoThrowRodTimeOut, config.FishingTimePolicy, saveScreenshotOnKeyTick, cultureInfo, useTorch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.Core.Recognition.ONNX;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using BetterGenshinImpact.Core.Recognition.OCR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
{
|
||||
@@ -39,7 +40,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
|
||||
private Blackboard blackboard;
|
||||
|
||||
private readonly BgiYoloPredictor _predictor = BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiFish);
|
||||
private readonly BgiYoloPredictor _predictor = App.ServiceProvider.GetRequiredService<BgiOnnxFactory>().CreateYoloPredictor(BgiOnnxModel.BgiFish);
|
||||
|
||||
/// <summary>
|
||||
/// 辣条(误)
|
||||
|
||||
@@ -255,12 +255,14 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
public const int MAX_NO_BAIT_FISH_TIMES = 2;
|
||||
private DateTimeOffset? findTargetEndTime;
|
||||
private bool foundTarget;
|
||||
private bool useTorch;
|
||||
|
||||
private int noPlacementTimes; // 没有落点的次数
|
||||
private int noTargetFishTimes; // 没有目标鱼的次数
|
||||
public ThrowRod(string name, Blackboard blackboard, ILogger logger, bool saveScreenshotOnTerminat, IInputSimulator input, TimeProvider? timeProvider = null, DrawContent? drawContent = null) : base(name, logger, saveScreenshotOnTerminat)
|
||||
public ThrowRod(string name, Blackboard blackboard, bool useTorch, ILogger logger, bool saveScreenshotOnTerminat, IInputSimulator input, TimeProvider? timeProvider = null, DrawContent? drawContent = null) : base(name, logger, saveScreenshotOnTerminat)
|
||||
{
|
||||
this.blackboard = blackboard;
|
||||
this.useTorch = useTorch;
|
||||
this.input = input;
|
||||
this.timeProvider = timeProvider ?? TimeProvider.System;
|
||||
this.drawContent = drawContent ?? VisionContext.Instance().DrawContent;
|
||||
@@ -437,7 +439,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
var dy = NormalizeYTo576(fish.Top + fish.Bottom - rod.Top - rod.Bottom) / 2.0;
|
||||
var dl = Math.Sqrt(dx * dx + dy * dy);
|
||||
//logger.LogInformation("dl = {dl}", dl);
|
||||
var state = RodNet.GetRodState(new RodInput
|
||||
|
||||
RodInput rodInput = new RodInput
|
||||
{
|
||||
rod_x1 = NormalizeXTo1024(rod.Left),
|
||||
rod_x2 = NormalizeXTo1024(rod.Right),
|
||||
@@ -448,7 +451,8 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
fish_y1 = NormalizeYTo576(fish.Top),
|
||||
fish_y2 = NormalizeYTo576(fish.Bottom),
|
||||
fish_label = BigFishType.GetIndex(currentFish.FishType)
|
||||
});
|
||||
};
|
||||
int state = this.useTorch ? new RodNet().GetRodState_Torch(rodInput) : RodNet.GetRodState(rodInput);
|
||||
|
||||
// 如果hutao钓鱼暂时没有更新导致报错,可以先用这段凑合
|
||||
//int state;
|
||||
@@ -705,13 +709,13 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
//VisionContext.Instance().DrawContent.PutRect("liftingWordsAreaRect", liftingWordsAreaRect.ToRectDrawable(new Pen(Color.Cyan, 2)));
|
||||
using var wordCaptureMat = new Mat(imageRegion.SrcMat, liftingWordsAreaRect);
|
||||
var currentBiteWordsTips = AutoFishingImageRecognition.MatchFishBiteWords(wordCaptureMat, liftingWordsAreaRect);
|
||||
if (currentBiteWordsTips != default)
|
||||
if (currentBiteWordsTips != null)
|
||||
{
|
||||
// VisionContext.Instance().DrawContent.PutRect("FishBiteTips",
|
||||
// currentBiteWordsTips
|
||||
// .ToWindowsRectangleOffset(liftingWordsAreaRect.X, liftingWordsAreaRect.Y)
|
||||
// .ToRectDrawable());
|
||||
using var tipsRa = imageRegion.Derive(currentBiteWordsTips + liftingWordsAreaRect.Location);
|
||||
using var tipsRa = imageRegion.Derive((Rect)currentBiteWordsTips + liftingWordsAreaRect.Location);
|
||||
tipsRa.DrawSelf("FishBiteTips");
|
||||
|
||||
return RaiseRod("文字块");
|
||||
@@ -847,7 +851,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
logger.LogInformation("拉扯开始");
|
||||
}
|
||||
|
||||
private MOUSEEVENTF _prevMouseEvent = 0x0;
|
||||
private MOUSEEVENTF _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP;
|
||||
|
||||
protected override BehaviourStatus Update(ImageRegion imageRegion)
|
||||
{
|
||||
@@ -867,24 +871,29 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
rects.RemoveRange(3, rects.Count - 3);
|
||||
}
|
||||
|
||||
Rect _cur, _left, _right;
|
||||
//Debug.WriteLine($"识别到{rects.Count} 个矩形");
|
||||
if (rects.Count == 2)
|
||||
{
|
||||
// 游标矩形不在区间内或恰在区间两端时只会检测到两个矩形
|
||||
Rect _cursor, _target;
|
||||
if (rects[0].Width < rects[1].Width)
|
||||
{
|
||||
_cur = rects[0];
|
||||
_left = rects[1];
|
||||
_cursor = rects[0];
|
||||
_target = rects[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
_cur = rects[1];
|
||||
_left = rects[0];
|
||||
_cursor = rects[1];
|
||||
_target = rects[0];
|
||||
}
|
||||
if (_target.Width < _cursor.Width * 10) // 异常:当目标矩形明显不够长时视为无效检测,不作为
|
||||
{
|
||||
return BehaviourStatus.Running;
|
||||
}
|
||||
|
||||
PutRects(imageRegion, _left, _cur, new Rect());
|
||||
PutRects(imageRegion, _target, _cursor, new Rect());
|
||||
|
||||
if (_cur.X < _left.X)
|
||||
if (_cursor.X < _target.X)
|
||||
{
|
||||
if (_prevMouseEvent != MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN)
|
||||
{
|
||||
@@ -907,13 +916,15 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
}
|
||||
else if (rects.Count == 3)
|
||||
{
|
||||
// 游标矩形在区间内会检测到三个矩形,即目标区间被游标分割成左半和右半
|
||||
Rect _cursor, _left, _right;
|
||||
rects.Sort((a, b) => a.X.CompareTo(b.X));
|
||||
_left = rects[0];
|
||||
_cur = rects[1];
|
||||
_cursor = rects[1];
|
||||
_right = rects[2];
|
||||
PutRects(imageRegion, _left, _cur, _right);
|
||||
PutRects(imageRegion, _left, _cursor, _right);
|
||||
|
||||
if (_right.X + _right.Width - (_cur.X + _cur.Width) <= _cur.X - _left.X)
|
||||
if (_right.X + _right.Width - (_cursor.X + _cursor.Width) <= _cursor.X - _left.X)
|
||||
{
|
||||
if (_prevMouseEvent == MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN)
|
||||
{
|
||||
@@ -955,7 +966,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
|
||||
|
||||
// 没有矩形视为已经完成钓鱼
|
||||
drawContent.RemoveRect("FishBox");
|
||||
_prevMouseEvent = 0x0;
|
||||
_prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP;
|
||||
logger.LogInformation(" 拉扯结束");
|
||||
logger.LogInformation(@"└------------------------┘");
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using OpenCvSharp;
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using static TorchSharp.torch.nn;
|
||||
using static TorchSharp.torch;
|
||||
using TorchSharp.Modules;
|
||||
using TorchSharp;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.AutoFishing;
|
||||
|
||||
@@ -34,98 +38,62 @@ namespace BetterGenshinImpact.GameTask.AutoFishing;
|
||||
///
|
||||
/// 哦 到这一步以后剩下的就很弱智了 远了挪近一点 近了挪远一点 调调参差不多得了
|
||||
/// </summary>
|
||||
public class RodNet
|
||||
public class RodNet : Module<Tensor, Tensor>
|
||||
{
|
||||
const double alpha = 1734.34 / 2.5;
|
||||
// fitted parameters
|
||||
static readonly double[] dz = {1.0307939, 1.5887239, 1.4377865, 0.8548809,
|
||||
1.8640924, -0.1687729, 1.8621461, 0.7167622,
|
||||
1.7071064, 1.8727832, 0.5531539};
|
||||
static readonly double[] h_coeff = {0.5840698, 0.8029298, 0.6090596,
|
||||
-0.1390072, 0.7214464, -0.6076725,
|
||||
0.3286690, -0.2991239, 0.6072225,
|
||||
0.7662407, -0.3689651};
|
||||
static readonly double[,] weight = {{0.7779633, -1.7124480, 2.7366412},
|
||||
{-0.0381155, -1.6536976, 3.5904298},
|
||||
{0.1947731, -0.0445049, 0.8416666},
|
||||
{-0.0331017, -1.3641578, 1.2834741},
|
||||
{1.0268835, -1.6553984, 2.9930501},
|
||||
{0.0108103, -0.8515291, 1.0032536},
|
||||
{-0.0746362, -0.9677668, 0.7450780},
|
||||
{0.7382144, -9.5275803, 2.6134675},
|
||||
{-0.3597502, -1.7422760, 1.4354013},
|
||||
{-0.0578425, -2.0274212, 1.7173727},
|
||||
{-0.1225260, -1.0630554, 1.2958838}};
|
||||
static readonly double[,] bias = {{3.1733532, 9.3601589, -11.0612173},
|
||||
{6.4961057, 11.2683334, -13.7752209},
|
||||
{2.3662698, 2.4709859, -2.5402584},
|
||||
{2.4701204, 8.5112562, -7.6070199},
|
||||
{0.9597272, 8.9189463, -11.9037018},
|
||||
{2.1239815, 5.8446727, -5.7748013},
|
||||
{2.1403685, 5.5432696, -4.0048418},
|
||||
{-9.0128260, 28.4402637, -24.2205143},
|
||||
{5.2072763, 8.6428480, -9.2946615},
|
||||
{4.9253063, 11.4634714, -9.4336052},
|
||||
{5.2460732, 7.7711511, -7.5998945}};
|
||||
|
||||
static readonly double[] dz = [ 0.561117562965, 0.637026851288, 0.705579317577,
|
||||
1.062734463845, 0.949307580751, 1.015620474332,
|
||||
1.797904203405, 1.513476738412, 1.013873007495,
|
||||
1.159949954831, 1.353650974146, 1.302893195071 ];
|
||||
|
||||
static readonly double[,] theta = {
|
||||
{-0.262674397633, 0.317025388945, -0.457150765450, 0.174522158281,
|
||||
-0.957110676932, -0.095339800558, -0.119519564026, -0.139914755291,
|
||||
-0.580893838475, 0.702302245305, 0.271575851220, 0.708473199472,
|
||||
0.699108382380},
|
||||
{-1.062702043060, -0.280779165943, -0.289891597384, 0.220173840594,
|
||||
0.493463877037, -0.326492366566, 1.215859141832, 1.607133159643,
|
||||
1.619199133672, 0.356402262447, 0.365385941958, 0.411869019381,
|
||||
0.224962055122},
|
||||
{0.460481782256, 0.048180392806, 0.475529271293, -0.150186412126,
|
||||
0.135512307120, 0.087365984352, -1.317661146364, -1.882438208662,
|
||||
-1.502483859283, -0.580228373556, -1.005821958682, -1.184199131739,
|
||||
-1.285988918494}
|
||||
};
|
||||
static readonly double[] offset = { 0.8, 0.4, 0.35, 0.35, 0.6, 0.3, 0.3, 0.8, 0.8, 0.8, 0.8 };
|
||||
|
||||
static readonly double[] B = [1.241950004386, 3.051113640564, -3.848898190087];
|
||||
private readonly Module<Tensor, Tensor> layers;
|
||||
|
||||
static readonly double[] offset = [ 0.4, 0.2, 0.4, 0, 0.3, 0.3,
|
||||
0.3, 0.15, 0.5, 0.5, 0.5, 0.5 ];
|
||||
|
||||
static void F(double[] dst, double[] x, double[] y)
|
||||
public RodNet() : base("RodNet")
|
||||
{
|
||||
double y0 = x[0], z0 = x[1], t = x[2];
|
||||
double tmp = (y0 + t * z0) * (y0 + t * z0) - 1;
|
||||
dst[0] = Math.Sqrt((1 + t * t) / tmp) - y[0];
|
||||
dst[1] = (1 + t * t) * z0 / tmp - y[1];
|
||||
dst[2] = ((t * t - 1) * y0 * z0 + t * (y0 * y0 - z0 * z0 - 1)) / tmp - y[2];
|
||||
}
|
||||
var weight = tensor(RodNet.weight, ScalarType.Float64);
|
||||
var bias = tensor(RodNet.bias, ScalarType.Float64);
|
||||
|
||||
static void DfInv(double[] dst, double[] x)
|
||||
{
|
||||
double y0 = x[0], z0 = x[1], t = x[2];
|
||||
double tmp1 = (y0 + t * z0) * (y0 + t * z0) - 1;
|
||||
double tmp2 = 1 + t * t;
|
||||
dst[0] = (1 - y0 * y0 + z0 * z0) / y0 * Math.Sqrt(tmp1 / tmp2);
|
||||
dst[1] = -z0 * (y0 * y0 + t * (t * (1 + z0 * z0) + 2 * y0 * z0)) / tmp2 / y0;
|
||||
dst[2] = ((t * t - 1) * y0 * z0 + t * (y0 * y0 - z0 * z0 - 1)) / y0 / tmp2;
|
||||
dst[3] = -2 * z0 * Math.Sqrt(tmp1 / tmp2);
|
||||
dst[4] = tmp1 / tmp2;
|
||||
dst[5] = 0;
|
||||
dst[6] = -z0 / y0 * Math.Sqrt(tmp1 / tmp2);
|
||||
dst[7] = (y0 + t * z0) * (y0 + t * z0) / y0;
|
||||
dst[8] = 1 + t * z0 / y0;
|
||||
}
|
||||
RodLayer1 rodLayer1 = new RodLayer1(num_embeddings: weight.shape[0], embedding_dim: weight.shape[1], input_dim: 3, output_dim: 3);
|
||||
rodLayer1.SetWeightsManually(weight, bias);
|
||||
|
||||
static bool NewtonRaphson(Action<double[], double[], double[]> f, Action<double[], double[]> dfInv, double[] dst, double[] y,
|
||||
double[] init, int n, int maxIter, double eps)
|
||||
{
|
||||
double[] fEst = new double[n];
|
||||
double[] dfInvMat = new double[n * n];
|
||||
double[] x = new double[n];
|
||||
double err;
|
||||
|
||||
Array.Copy(init, x, n);
|
||||
|
||||
for (int iter = 0; iter < maxIter; iter++)
|
||||
var modules = new List<(string, Module<Tensor, Tensor>)>
|
||||
{
|
||||
err = 0;
|
||||
f(fEst, x, y);
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
err += Math.Abs(fEst[i]);
|
||||
}
|
||||
($"rodLayer1", rodLayer1),
|
||||
($"softmax", nn.Softmax(1))
|
||||
};
|
||||
|
||||
if (err < eps)
|
||||
{
|
||||
Array.Copy(x, dst, n);
|
||||
// printf("Newton-Raphson solver converge after %d steps, err: %lf !\n",
|
||||
// iter, err);
|
||||
return true;
|
||||
}
|
||||
layers = Sequential(modules);
|
||||
|
||||
dfInv(dfInvMat, x);
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
x[i] -= dfInvMat[n * i + j] * fEst[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
RegisterComponents();
|
||||
}
|
||||
|
||||
static void Softmax(double[] dst, double[] x, int n)
|
||||
@@ -141,68 +109,141 @@ public class RodNet
|
||||
dst[i] /= sum;
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetRodState(RodInput input)
|
||||
public record NetInput(double dist, int fish_label);
|
||||
public static NetInput? GeometryProcessing(RodInput input)
|
||||
{
|
||||
double a, b, v0, u, v;
|
||||
double a, b, v0, u, v, h;
|
||||
|
||||
a = (input.rod_x2 - input.rod_x1) / 2 / alpha;
|
||||
b = (input.rod_y2 - input.rod_y1) / 2 / alpha;
|
||||
h = (input.fish_y2 - input.fish_y1) / 2 / alpha;
|
||||
|
||||
if (a < b)
|
||||
{
|
||||
(b, a) = (a, b);
|
||||
b = Math.Sqrt(a * b);
|
||||
a = b + 1e-6;
|
||||
}
|
||||
|
||||
v0 = (288 - (input.rod_y1 + input.rod_y2) / 2) / alpha;
|
||||
|
||||
u = (input.fish_x1 + input.fish_x2 - input.rod_x1 - input.rod_x2) / 2 / alpha;
|
||||
v = (288 - (input.fish_y1 + input.fish_y2) / 2) / alpha;
|
||||
v -= h * h_coeff[input.fish_label];
|
||||
|
||||
double[] y0z0t = new double[3];
|
||||
double[] abv0 = [a, b, v0];
|
||||
double[] init = [30, 15, 1];
|
||||
|
||||
bool solveSuccess = NewtonRaphson(F, DfInv, y0z0t, abv0, init, 3, 1000, 1e-6);
|
||||
|
||||
if (!solveSuccess)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
double y0 = y0z0t[0], z0 = y0z0t[1], t = y0z0t[2];
|
||||
double y0, z0, t;
|
||||
double x, y, dist;
|
||||
|
||||
y0 = Math.Sqrt(Math.Pow(a, 4) - b * b + a * a * (1 - b * b + v0 * v0)) / (a * a);
|
||||
z0 = b / (a * a);
|
||||
t = a * a * (y0 * b + v0) / (a * a - b * b);
|
||||
|
||||
x = u * (z0 + dz[input.fish_label]) * Math.Sqrt(1 + t * t) / (t - v);
|
||||
y = (z0 + dz[input.fish_label]) * (1 + t * v) / (t - v);
|
||||
dist = Math.Sqrt(x * x + (y - y0) * (y - y0));
|
||||
|
||||
double[] logits = new double[3];
|
||||
for (int i = 0; i < 3; i++)
|
||||
return new NetInput(dist, input.fish_label);
|
||||
}
|
||||
|
||||
internal static int GetRodState(RodInput input)
|
||||
{
|
||||
NetInput? netInput = GeometryProcessing(input);
|
||||
if (netInput is null)
|
||||
{
|
||||
logits[i] = theta[i, 0] * dist + theta[i, 1 + input.fish_label] + B[i];
|
||||
return -1;
|
||||
}
|
||||
|
||||
double[] pred = new double[3];
|
||||
Softmax(pred, logits, 3);
|
||||
pred[0] -= offset[input.fish_label];
|
||||
double[] pred = ComputeScores(netInput);
|
||||
|
||||
return Array.IndexOf(pred, pred.Max());
|
||||
}
|
||||
|
||||
public static int GetRodState(Rect rod, Rect fish, int fishTypeIndex)
|
||||
public static double[] ComputeScores(NetInput netInput)
|
||||
{
|
||||
RodInput input = new()
|
||||
double dist = netInput.dist;
|
||||
int fish_label = netInput.fish_label;
|
||||
|
||||
double[] logits = new double[3];
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
rod_x1 = rod.Left,
|
||||
rod_x2 = rod.Right,
|
||||
rod_y1 = rod.Top,
|
||||
rod_y2 = rod.Bottom,
|
||||
fish_x1 = fish.Left,
|
||||
fish_x2 = fish.Right,
|
||||
fish_y1 = fish.Top,
|
||||
fish_y2 = fish.Bottom,
|
||||
fish_label = fishTypeIndex
|
||||
};
|
||||
return GetRodState(input);
|
||||
logits[i] = weight[fish_label, i] * dist + bias[fish_label, i];
|
||||
}
|
||||
|
||||
double[] pred = new double[3];
|
||||
Softmax(pred, logits, 3);
|
||||
pred[0] -= offset[fish_label]; // to make the prediction more precise when deployed
|
||||
|
||||
return pred;
|
||||
}
|
||||
|
||||
internal int GetRodState_Torch(RodInput input)
|
||||
{
|
||||
NetInput? netInput = GeometryProcessing(input);
|
||||
if (netInput is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
Tensor outputTensor = ComputeScores_Torch(netInput);
|
||||
|
||||
var max = argmax(outputTensor);
|
||||
return (int)max.item<long>();
|
||||
}
|
||||
|
||||
public Tensor ComputeScores_Torch(NetInput netInput)
|
||||
{
|
||||
double dist = netInput.dist;
|
||||
int fish_label = netInput.fish_label;
|
||||
|
||||
Tensor inputTensor = cat([tensor(new double[,] { { dist } }, dtype: ScalarType.Float64),
|
||||
tensor(new int[,] { {fish_label } }, dtype: ScalarType.Int32)]).T;
|
||||
var outputTensor = forward(inputTensor);
|
||||
|
||||
outputTensor[0][0] = outputTensor[0][0] - RodNet.offset[fish_label];
|
||||
|
||||
return outputTensor;
|
||||
}
|
||||
|
||||
public override Tensor forward(Tensor input)
|
||||
{
|
||||
return layers.forward(input);
|
||||
}
|
||||
}
|
||||
|
||||
public class RodLayer1 : Module<Tensor, Tensor>
|
||||
{
|
||||
private readonly Embedding embedding1;
|
||||
private readonly Embedding embedding2;
|
||||
private readonly Linear linear;
|
||||
public RodLayer1(long num_embeddings, long embedding_dim, long input_dim, long output_dim)
|
||||
: base("RodLinear")
|
||||
{
|
||||
embedding1 = torch.nn.Embedding(num_embeddings, embedding_dim);
|
||||
embedding2 = torch.nn.Embedding(num_embeddings, embedding_dim);
|
||||
linear = torch.nn.Linear(input_dim, output_dim);
|
||||
|
||||
RegisterComponents();
|
||||
}
|
||||
|
||||
public void SetWeightsManually(Tensor weight, Tensor bias)
|
||||
{
|
||||
embedding1.weight = new Parameter(weight);
|
||||
embedding2.weight = new Parameter(bias);
|
||||
}
|
||||
|
||||
public override Tensor forward(Tensor input)
|
||||
{
|
||||
var splitInput = input.split([1, 1], dim: 1);
|
||||
var dist = splitInput[0];
|
||||
var fish_label = splitInput[1].to(ScalarType.Int32).flatten();
|
||||
|
||||
var embed1 = embedding1.forward(fish_label);
|
||||
//Console.WriteLine(String.Join(",", embed1.data<double>()));
|
||||
var embed2 = embedding2.forward(fish_label);
|
||||
//Console.WriteLine(String.Join(",", embed2.data<double>()));
|
||||
|
||||
linear.weight = new Parameter(embed1.T);
|
||||
linear.bias = new Parameter(embed2);
|
||||
|
||||
return linear.forward(dist);
|
||||
}
|
||||
}
|
||||
@@ -12,21 +12,22 @@ namespace BetterGenshinImpact.GameTask.AutoPathing.Handler;
|
||||
|
||||
internal class AutoFightHandler : IActionHandler
|
||||
{
|
||||
private readonly ILogger<AutoFightHandler> _logger = App.GetLogger<AutoFightHandler>();
|
||||
public async Task RunAsyncByScript(CancellationToken ct, WaypointForTrack? waypointForTrack = null, object? config = null)
|
||||
{
|
||||
if (!(config != null && config is PathingPartyConfig patyConfig && patyConfig is {AutoFightEnabled:true,JsScriptUseEnabled:true,SoloTaskUseFightEnabled:true} ))
|
||||
{
|
||||
config = null;
|
||||
}
|
||||
await StartFight(ct, config);
|
||||
await StartFight(ct, config,waypointForTrack);
|
||||
}
|
||||
|
||||
public async Task RunAsync(CancellationToken ct, WaypointForTrack? waypointForTrack = null, object? config = null)
|
||||
{
|
||||
await StartFight(ct, config);
|
||||
await StartFight(ct, config,waypointForTrack);
|
||||
}
|
||||
|
||||
private async Task StartFight(CancellationToken ct, object? config = null)
|
||||
private async Task StartFight(CancellationToken ct, object? config = null , WaypointForTrack? waypointForTrack = null)
|
||||
{
|
||||
TaskControl.Logger.LogInformation("执行 {Text}", "自动战斗");
|
||||
// 爷们要战斗
|
||||
@@ -42,6 +43,34 @@ internal class AutoFightHandler : IActionHandler
|
||||
taskParams = new AutoFightParam(GetFightStrategy(), TaskContext.Instance().Config.AutoFightConfig);
|
||||
}
|
||||
|
||||
//根据怪物标签,调整拾取配置
|
||||
if (waypointForTrack!=null)
|
||||
{
|
||||
// normal 小怪,elite 精英,legendary 传奇
|
||||
//不为精英或者小怪
|
||||
if (!(waypointForTrack.MonsterTag == "elite" || waypointForTrack.MonsterTag == "legendary"))
|
||||
{
|
||||
|
||||
if (taskParams.OnlyPickEliteDropsMode == "AllowAutoPickupForNonElite" || taskParams.OnlyPickEliteDropsMode == "DisableAutoPickupForNonElite")
|
||||
{
|
||||
//允许自动拾取,即只关闭配置上的拾取即刻
|
||||
taskParams.KazuhaPickupEnabled = false;
|
||||
taskParams.PickDropsAfterFightEnabled = false;
|
||||
_logger.LogInformation("当前非精英或传奇点位,关闭战斗拾取配置!");
|
||||
//禁止自动拾取,除了关闭配置拾取外,连自动拾取都关掉
|
||||
if (taskParams.OnlyPickEliteDropsMode == "DisableAutoPickupForNonElite")
|
||||
{
|
||||
await RunnerContext.Instance.StopAutoPickRunTask(
|
||||
async () => await new AutoFightTask(taskParams).Start(ct),
|
||||
5);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var fightSoloTask = new AutoFightTask(taskParams);
|
||||
await fightSoloTask.Start(ct);
|
||||
}
|
||||
|
||||
@@ -17,28 +17,27 @@ namespace BetterGenshinImpact.GameTask.AutoPathing.Handler;
|
||||
public class MiningHandler : IActionHandler
|
||||
{
|
||||
private readonly CombatScript _miningCombatScript = CombatScriptParser.ParseContext("""
|
||||
卡齐娜 e(hold),keydown(s),wait(0.4),keyup(s),attack(0.2),attack(0.2),attack(0.2),attack(0.2),attack(0.2),attack(0.2)
|
||||
坎蒂丝 e(hold)
|
||||
雷泽 e(hold)
|
||||
钟离 e(hold)
|
||||
凝光 attack(0.4),attack(0.4),attack(0.4),attack(0.4)
|
||||
荒泷一斗 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
迪希雅 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
玛薇卡 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
基尼奇 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
娜维娅 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
菲米尼 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
迪卢克 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
诺艾尔 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
多莉 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
卡维 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
早柚 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
雷泽 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
优菈 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
嘉明 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
辛焱 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
重云 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
北斗 attack(0.4),attack(0.4),attack(0.4),attack(0.25)
|
||||
荒泷一斗 attack(2.0)
|
||||
迪希雅 attack(2.0)
|
||||
玛薇卡 attack(2.0)
|
||||
基尼奇 attack(2.0)
|
||||
娜维娅 attack(2.0)
|
||||
菲米尼 attack(2.0)
|
||||
迪卢克 attack(2.0)
|
||||
诺艾尔 attack(2.0)
|
||||
卡维 attack(2.0)
|
||||
雷泽 attack(2.0)
|
||||
优菈 attack(2.0)
|
||||
嘉明 attack(2.0)
|
||||
辛焱 attack(2.0)
|
||||
重云 attack(2.0)
|
||||
多莉 attack(2.0)
|
||||
北斗 attack(2.5)
|
||||
早柚 attack(2.5)
|
||||
坎蒂丝 e(hold,wait)
|
||||
雷泽 e(hold,wait)
|
||||
钟离 e(hold,wait)
|
||||
凝光 attack(4.0)
|
||||
""");
|
||||
|
||||
private readonly ScanPickTask _scanPickTask = new();
|
||||
|
||||
@@ -22,6 +22,9 @@ public class WaypointForTrack : Waypoint
|
||||
public string MapName { get; set; }
|
||||
//异常识别
|
||||
public Misidentification Misidentification { get; set; } = new();
|
||||
|
||||
//怪物标签
|
||||
public string MonsterTag { get; set; } ="";
|
||||
|
||||
/// <summary>
|
||||
/// 存在 combat_script 的 action 的话,这个值会存在
|
||||
|
||||
@@ -496,6 +496,7 @@ public class PathExecutor
|
||||
{
|
||||
WaypointForTrack wft=new WaypointForTrack(waypoint, task.Info.MapName);
|
||||
wft.Misidentification=waypoint.PointExtParams.Misidentification;
|
||||
wft.MonsterTag = waypoint.PointExtParams.MonsterTag;
|
||||
return wft;
|
||||
}).ToList();
|
||||
|
||||
@@ -683,7 +684,7 @@ public class PathExecutor
|
||||
var screen = CaptureToRectArea();
|
||||
var position = await GetPosition(screen, waypoint);
|
||||
var targetOrientation = Navigation.GetTargetOrientation(waypoint, position);
|
||||
Logger.LogInformation("朝向点,位置({x2},{y2})", $"{waypoint.GameX:F1}", $"{waypoint.GameY:F1}");
|
||||
Logger.LogDebug("朝向点,位置({x2},{y2})", $"{waypoint.GameX:F1}", $"{waypoint.GameY:F1}");
|
||||
await _rotateTask.WaitUntilRotatedTo(targetOrientation, 2);
|
||||
await Delay(500, ct);
|
||||
}
|
||||
@@ -698,7 +699,7 @@ public class PathExecutor
|
||||
var screen = CaptureToRectArea();
|
||||
var (position, additionalTimeInMs) = await GetPositionAndTime(screen, waypoint);
|
||||
var targetOrientation = Navigation.GetTargetOrientation(waypoint, position);
|
||||
Logger.LogInformation("粗略接近途经点,位置({x2},{y2})", $"{waypoint.GameX:F1}", $"{waypoint.GameY:F1}");
|
||||
Logger.LogDebug("粗略接近途经点,位置({x2},{y2})", $"{waypoint.GameX:F1}", $"{waypoint.GameY:F1}");
|
||||
await _rotateTask.WaitUntilRotatedTo(targetOrientation, 5);
|
||||
moveToStartTime = DateTime.UtcNow;
|
||||
var lastPositionRecord = DateTime.UtcNow;
|
||||
@@ -742,7 +743,7 @@ public class PathExecutor
|
||||
Debug.WriteLine($"接近目标点中,距离为{distance}");
|
||||
if (distance < 4)
|
||||
{
|
||||
Logger.LogInformation("到达路径点附近");
|
||||
Logger.LogDebug("到达路径点附近");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -942,7 +943,7 @@ public class PathExecutor
|
||||
ImageRegion screen;
|
||||
Point2f position;
|
||||
int targetOrientation;
|
||||
Logger.LogInformation("精确接近目标点,位置({x2},{y2})", $"{waypoint.GameX:F1}", $"{waypoint.GameY:F1}");
|
||||
Logger.LogDebug("精确接近目标点,位置({x2},{y2})", $"{waypoint.GameX:F1}", $"{waypoint.GameY:F1}");
|
||||
|
||||
var stepsTaken = 0;
|
||||
while (!ct.IsCancellationRequested)
|
||||
@@ -961,7 +962,7 @@ public class PathExecutor
|
||||
position = await GetPosition(screen, waypoint);
|
||||
if (Navigation.GetDistance(waypoint, position) < 2)
|
||||
{
|
||||
Logger.LogInformation("已到达路径点");
|
||||
Logger.LogDebug("已到达路径点");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1188,7 +1189,7 @@ public class PathExecutor
|
||||
preTime = DateTime.Now;
|
||||
}
|
||||
|
||||
//Logger.LogInformation("识别到路径:"+position.X+","+position.Y);
|
||||
//Logger.LogDebug("识别到路径:"+position.X+","+position.Y);
|
||||
return (position,time);
|
||||
}
|
||||
|
||||
|
||||
@@ -527,7 +527,7 @@ public class TpTask(CancellationToken ct)
|
||||
// 非常接近目标点,不再进一步调整
|
||||
if (mouseDistance < _tpConfig.Tolerance)
|
||||
{
|
||||
Logger.LogInformation("移动 {I} 次鼠标后,已经接近目标点,不再移动地图。", iteration + 1);
|
||||
Logger.LogDebug("移动 {I} 次鼠标后,已经接近目标点,不再移动地图。", iteration + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.Core.Recognition;
|
||||
using System.Threading;
|
||||
|
||||
using BetterGenshinImpact.GameTask.AutoFight.Assets;
|
||||
using BetterGenshinImpact.GameTask.AutoSkip.Assets;
|
||||
using BetterGenshinImpact.GameTask.GameLoading.Assets;
|
||||
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.Common.BgiVision;
|
||||
|
||||
/// <summary>
|
||||
@@ -21,6 +23,7 @@ namespace BetterGenshinImpact.GameTask.Common.BgiVision;
|
||||
/// </summary>
|
||||
public static partial class Bv
|
||||
{
|
||||
|
||||
public static string WhichGameUi()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@@ -33,7 +36,7 @@ public static partial class Bv
|
||||
/// <returns></returns>
|
||||
public static bool IsInMainUi(ImageRegion captureRa)
|
||||
{
|
||||
return captureRa.Find(ElementAssets.Instance.PaimonMenuRo).IsExist();
|
||||
return captureRa.Find(ElementAssets.Instance.PaimonMenuRo).IsExist() && !IsInRevivePrompt(captureRa);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -83,7 +83,7 @@ public class GoToCraftingBenchTask
|
||||
await _chooseTalkOptionTask.SelectLastOptionUntilEnd(ct,
|
||||
region => region.Find(ElementAssets.Instance.BtnWhiteConfirm).IsExist()
|
||||
);
|
||||
await Delay(200, ct);
|
||||
await Delay(800, ct);
|
||||
|
||||
// 判断浓缩树脂是否存在
|
||||
// TODO 满的情况是怎么样子的
|
||||
|
||||
@@ -9,6 +9,7 @@ using BetterGenshinImpact.Core.Simulator;
|
||||
using BetterGenshinImpact.Core.Simulator.Extensions;
|
||||
using BetterGenshinImpact.GameTask.Model.Area;
|
||||
using BetterGenshinImpact.View.Drawable;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenCvSharp;
|
||||
using Vanara.PInvoke;
|
||||
@@ -22,7 +23,7 @@ namespace BetterGenshinImpact.GameTask.Common.Job;
|
||||
/// </summary>
|
||||
public class ScanPickTask
|
||||
{
|
||||
private readonly BgiYoloPredictor _predictor = BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiWorld);
|
||||
private readonly BgiYoloPredictor _predictor = App.ServiceProvider.GetRequiredService<BgiOnnxFactory>().CreateYoloPredictor(BgiOnnxModel.BgiWorld);
|
||||
private readonly double _dpi = TaskContext.Instance().DpiScale;
|
||||
private readonly RECT _realCaptureRect = TaskContext.Instance().SystemInfo.CaptureAreaRect;
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BetterGenshinImpact.GameTask.AutoFight.Model;
|
||||
using BetterGenshinImpact.Model;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.GameTask.AutoFight.Model;
|
||||
using BetterGenshinImpact.GameTask.AutoPathing.Suspend;
|
||||
using BetterGenshinImpact.GameTask.Common.Job;
|
||||
using OpenCvSharp;
|
||||
using Wpf.Ui.Controls;
|
||||
using BetterGenshinImpact.Model;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static BetterGenshinImpact.GameTask.Common.TaskControl;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask;
|
||||
@@ -23,6 +21,8 @@ public class RunnerContext : Singleton<RunnerContext>
|
||||
/// </summary>
|
||||
public bool IsContinuousRunGroup { get; set; }
|
||||
|
||||
public TaskProgress.TaskProgress? taskProgress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 暂停逻辑
|
||||
/// </summary>
|
||||
@@ -112,6 +112,7 @@ public class RunnerContext : Singleton<RunnerContext>
|
||||
isAutoFetchDispatch = false;
|
||||
SuspendableDictionary.Clear();
|
||||
AutoPickTriggerStopCount = 0;
|
||||
taskProgress = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -185,7 +185,25 @@ public class SystemControl
|
||||
|
||||
ActivateWindow(TaskContext.Instance().GameHandle);
|
||||
}
|
||||
public static void RestartApplication(string[] newArgs)
|
||||
{
|
||||
// 获取当前程序路径
|
||||
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
|
||||
// 构建参数字符串
|
||||
string arguments = string.Join(" ", [..newArgs,"--no-single"]);
|
||||
|
||||
// 启动新进程
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = exePath,
|
||||
Arguments = arguments,
|
||||
UseShellExecute = false
|
||||
});
|
||||
|
||||
// 关闭当前程序
|
||||
Environment.Exit(0);
|
||||
}
|
||||
public static void FocusWindow(nint hWnd)
|
||||
{
|
||||
if (User32.IsWindow(hWnd))
|
||||
|
||||
71
BetterGenshinImpact/GameTask/TaskProgress/TaskProgress.cs
Normal file
71
BetterGenshinImpact/GameTask/TaskProgress/TaskProgress.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using BetterGenshinImpact.Service;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.TaskProgress;
|
||||
|
||||
public partial class TaskProgress : ObservableObject
|
||||
{
|
||||
|
||||
[ObservableProperty] private List<string> _scriptGroupNames = new();
|
||||
|
||||
[ObservableProperty] private string? _lastScriptGroupName;
|
||||
[ObservableProperty] private ScriptGroupProjectInfo? _lastSuccessScriptGroupProjectInfo;
|
||||
[ObservableProperty] private string? _currentScriptGroupName;
|
||||
[ObservableProperty] private ScriptGroupProjectInfo? _currentScriptGroupProjectInfo;
|
||||
[ObservableProperty] private string _name = DateTime.Now.ToString("yyyyMMddHHmmss");
|
||||
[ObservableProperty] private DateTime _startTime = DateTime.Now;
|
||||
[ObservableProperty] private DateTime? _endTime = null;
|
||||
[ObservableProperty] private List<ScriptGroupProjectInfo>? _history = new();
|
||||
[ObservableProperty] private bool _loop = false;
|
||||
//记录完成了几圈
|
||||
[ObservableProperty] private int _loopCount = 0;
|
||||
private int _consecutiveFailureCount = 0;
|
||||
|
||||
private Progress? _next;
|
||||
/// <summary>
|
||||
/// 连续失败次数
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public int ConsecutiveFailureCount
|
||||
{
|
||||
get => _consecutiveFailureCount;
|
||||
set => SetProperty(ref _consecutiveFailureCount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进度信息,如果next不为空,则从next执行
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Progress? Next
|
||||
{
|
||||
get => _next;
|
||||
set => SetProperty(ref _next, value);
|
||||
}
|
||||
public partial class Progress : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string _groupName = string.Empty;
|
||||
[ObservableProperty] private int _index = 0;
|
||||
[ObservableProperty] private string _projectName = string.Empty;
|
||||
[ObservableProperty] private string _folderName = string.Empty;
|
||||
}
|
||||
public partial class ScriptGroupProjectInfo : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string _groupName = string.Empty;
|
||||
[ObservableProperty] private bool _taskEnd = false;
|
||||
[ObservableProperty] private int _index = 0;
|
||||
[ObservableProperty] private string _name = string.Empty;
|
||||
[ObservableProperty] private string _folderName = string.Empty;
|
||||
[ObservableProperty] private DateTime _startTime = DateTime.Now;
|
||||
[ObservableProperty] private DateTime? _endTime = null;
|
||||
//状态 1 成功 2 失败
|
||||
[ObservableProperty] private int _status = 1;
|
||||
}
|
||||
public string ToJson()
|
||||
{
|
||||
return JsonSerializer.Serialize(this, ConfigService.JsonOptions);
|
||||
}
|
||||
}
|
||||
184
BetterGenshinImpact/GameTask/TaskProgress/TaskProgressManager.cs
Normal file
184
BetterGenshinImpact/GameTask/TaskProgress/TaskProgressManager.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BetterGenshinImpact.GameTask.TaskProgress;
|
||||
|
||||
public class TaskProgressManager
|
||||
{
|
||||
private static readonly string _configDir = Global.Absolute(@"log\task_progress");
|
||||
public static ILogger Logger { get; } = App.GetLogger<TaskProgressManager>();
|
||||
public static void SaveTaskProgress(TaskProgress taskProgress)
|
||||
{
|
||||
// 如果目录不存在,则创建
|
||||
if (!Directory.Exists(_configDir))
|
||||
{
|
||||
Directory.CreateDirectory(_configDir);
|
||||
}
|
||||
|
||||
var file = Path.Combine(_configDir, $"{taskProgress.Name}.json");
|
||||
File.WriteAllText(file, taskProgress.ToJson());
|
||||
}
|
||||
|
||||
public static List<TaskProgress> LoadAllTaskProgress()
|
||||
{
|
||||
// 确保目录存在
|
||||
if (!Directory.Exists(_configDir))
|
||||
{
|
||||
Directory.CreateDirectory(_configDir);
|
||||
}
|
||||
|
||||
var result = new List<TaskProgress>();
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 匹配全数字文件名,形如:20250531081114.json
|
||||
var regex = new Regex(@"^\d{14}\.json$");
|
||||
var fileList = Directory.GetFiles(_configDir, "*.json")
|
||||
.Where(file => regex.IsMatch(Path.GetFileName(file))) // 筛选纯数字 JSON
|
||||
.Select(file => new FileInfo(file))
|
||||
.OrderByDescending(fi => fi.LastWriteTime) // 最后修改时间倒序
|
||||
.ToList();
|
||||
|
||||
foreach (var file in fileList.ToArray())
|
||||
{
|
||||
var fileName = file.Name;
|
||||
|
||||
// 跳过非纯数字文件名
|
||||
// if (!regex.IsMatch(fileName)) continue;
|
||||
|
||||
var lastWrite = file.LastWriteTime;
|
||||
|
||||
// 删除3天前未修改的文件
|
||||
if ((now - lastWrite).TotalDays > 3)
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogInformation($"删除文件失败:{file} - {ex.Message}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(file.FullName);
|
||||
var progress = JsonConvert.DeserializeObject<TaskProgress>(json);
|
||||
if (progress != null && progress.EndTime == null)
|
||||
result.Add(progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogInformation($"读取文件失败:{file} - {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void GenerNextProjectInfo(
|
||||
TaskProgress taskProgress,
|
||||
List<ScriptGroup> scriptGroups)
|
||||
{
|
||||
var currentGroupIndex = 0;
|
||||
var currentProjectIndex = -1;
|
||||
/*if (taskProgress.LastSuccessScriptGroupProjectInfo == null)
|
||||
return ;*/
|
||||
|
||||
if (taskProgress.LastScriptGroupName!=null)
|
||||
{
|
||||
currentGroupIndex = scriptGroups.FindIndex(g => g.Name == taskProgress.LastScriptGroupName);
|
||||
if (currentGroupIndex == -1)
|
||||
return ;
|
||||
}
|
||||
|
||||
var currentGroup = scriptGroups[currentGroupIndex];
|
||||
var isLastInGroup = false;
|
||||
if (taskProgress.LastSuccessScriptGroupProjectInfo!=null)
|
||||
{
|
||||
|
||||
var currentProjectInfo = taskProgress.LastSuccessScriptGroupProjectInfo;
|
||||
|
||||
currentProjectIndex = currentGroup.Projects.ToList().FindIndex(p =>
|
||||
p.Name == currentProjectInfo.Name &&
|
||||
p.FolderName == currentProjectInfo.FolderName);
|
||||
|
||||
if (currentProjectIndex == -1)
|
||||
return ;
|
||||
|
||||
isLastInGroup = currentProjectIndex == currentGroup.Projects.Count - 1;
|
||||
}
|
||||
|
||||
//bool isIncomplete = currentProjectInfo.EndTime == null;
|
||||
|
||||
if (isLastInGroup)
|
||||
{
|
||||
// 向后查找下一个非空组
|
||||
for (int i = currentGroupIndex + 1; i < scriptGroups.Count; i++)
|
||||
{
|
||||
var group = scriptGroups[i];
|
||||
if (group.Projects != null && group.Projects.Any())
|
||||
{
|
||||
var project = group.Projects.First();
|
||||
|
||||
taskProgress.Next=new TaskProgress.Progress
|
||||
{
|
||||
GroupName = group.Name,
|
||||
Index = 0,
|
||||
ProjectName = project.Name,
|
||||
FolderName = project.FolderName
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 循环从开头查找直到当前组之前
|
||||
if (taskProgress.Loop)
|
||||
{
|
||||
for (int i = 0; i < currentGroupIndex; i++)
|
||||
{
|
||||
var group = scriptGroups[i];
|
||||
if (group.Projects != null && group.Projects.Any())
|
||||
{
|
||||
var project = group.Projects.First();
|
||||
taskProgress.Next = new TaskProgress.Progress
|
||||
{
|
||||
GroupName = group.Name,
|
||||
Index = 0,
|
||||
ProjectName = project.Name,
|
||||
FolderName = project.FolderName
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ;
|
||||
}
|
||||
else
|
||||
{
|
||||
//取成功执行的下一个任务
|
||||
currentProjectIndex++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 返回当前项目
|
||||
var currentProject = currentGroup.Projects[currentProjectIndex];
|
||||
taskProgress.Next = new TaskProgress.Progress
|
||||
{
|
||||
GroupName = currentGroup.Name,
|
||||
Index = currentProjectIndex,
|
||||
ProjectName = currentProject.Name,
|
||||
FolderName = currentProject.FolderName
|
||||
};
|
||||
}
|
||||
}
|
||||
33
BetterGenshinImpact/Helpers/DeviceIdHelper.cs
Normal file
33
BetterGenshinImpact/Helpers/DeviceIdHelper.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using DeviceId;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterGenshinImpact.Helpers;
|
||||
|
||||
public class DeviceIdHelper
|
||||
{
|
||||
private static readonly ILogger _logger = App.GetLogger<DeviceIdHelper>();
|
||||
private static readonly Lazy<string> _lazyDeviceId = new(InitializeDeviceId);
|
||||
|
||||
public static string DeviceId => _lazyDeviceId.Value;
|
||||
|
||||
private static string InitializeDeviceId()
|
||||
{
|
||||
try
|
||||
{
|
||||
return new DeviceIdBuilder()
|
||||
.OnWindows(windows => windows
|
||||
.AddMacAddressFromWmi(excludeWireless: true, excludeNonPhysical: true)
|
||||
.AddProcessorId()
|
||||
.AddMotherboardSerialNumber()
|
||||
)
|
||||
.ToString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogDebug("获取设备ID异常:" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" +
|
||||
Environment.NewLine + e.Message);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,10 @@ internal static class RuntimeExtension
|
||||
|
||||
public static IHostBuilder UseSingleInstance(this IHostBuilder self, string instanceName, Action<bool> callback = null!)
|
||||
{
|
||||
RuntimeHelper.CheckSingleInstance(instanceName, callback);
|
||||
if (!Environment.GetCommandLineArgs().Contains("--no-single"))
|
||||
{
|
||||
RuntimeHelper.CheckSingleInstance(instanceName, callback);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
52
BetterGenshinImpact/Helpers/Win32/CredentialManagerHelper.cs
Normal file
52
BetterGenshinImpact/Helpers/Win32/CredentialManagerHelper.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using Meziantou.Framework.Win32;
|
||||
|
||||
namespace BetterGenshinImpact.Helpers.Win32;
|
||||
|
||||
public static class CredentialManagerHelper
|
||||
{
|
||||
public static void SaveCredential(string applicationName, string userName, string secret, string comment,
|
||||
CredentialPersistence persistence)
|
||||
{
|
||||
CredentialManager.WriteCredential(
|
||||
applicationName: applicationName,
|
||||
userName: userName,
|
||||
secret: secret,
|
||||
comment: comment,
|
||||
persistence: persistence);
|
||||
}
|
||||
|
||||
public static Credential? ReadCredential(string applicationName)
|
||||
{
|
||||
var credential = CredentialManager.ReadCredential(applicationName);
|
||||
if (credential == null)
|
||||
{
|
||||
Console.WriteLine("No credential found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
Console.WriteLine($"UserName: {credential.UserName}");
|
||||
Console.WriteLine($"Secret: {credential.Password}");
|
||||
Console.WriteLine($"Comment: {credential.Comment}");
|
||||
|
||||
return credential;
|
||||
}
|
||||
|
||||
public static void UpdateCredential(string applicationName, string newUserName, string newSecret, string newComment)
|
||||
{
|
||||
SaveCredential(applicationName, newUserName, newSecret, newComment, CredentialPersistence.LocalMachine);
|
||||
}
|
||||
|
||||
public static void DeleteCredential(string applicationName)
|
||||
{
|
||||
try
|
||||
{
|
||||
CredentialManager.DeleteCredential(applicationName);
|
||||
Console.WriteLine("Credential deleted successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error deleting credential: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
93
BetterGenshinImpact/Helpers/Win32/MirrorChyanHelper.cs
Normal file
93
BetterGenshinImpact/Helpers/Win32/MirrorChyanHelper.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using Windows.System;
|
||||
using BetterGenshinImpact.View.Windows;
|
||||
using Meziantou.Framework.Win32;
|
||||
using Wpf.Ui.Violeta.Controls;
|
||||
|
||||
namespace BetterGenshinImpact.Helpers.Win32;
|
||||
|
||||
public static class MirrorChyanHelper
|
||||
{
|
||||
public static readonly string MirrorChyanCdkAppName = "KachinaInstaller_MirrorChyanCDK_BetterGI";
|
||||
|
||||
|
||||
public static string? GetCdk()
|
||||
{
|
||||
var credential = CredentialManagerHelper.ReadCredential(MirrorChyanCdkAppName);
|
||||
return credential?.Password;
|
||||
}
|
||||
|
||||
public static string? GetAndPromptCdk()
|
||||
{
|
||||
var credential = CredentialManagerHelper.ReadCredential(MirrorChyanCdkAppName);
|
||||
if (credential == null || credential.Password == null)
|
||||
{
|
||||
var cdk = PromptDialog.Prompt("Mirror酱是独立的第三方软件下载平台,提供付费的软件下载加速服务。\n如果你有 Mirror酱的 CDK,可以在这里输入。",
|
||||
"请输入Mirror酱CDK",
|
||||
string.Empty,
|
||||
new PromptDialogConfig
|
||||
{
|
||||
ShowLeftButton = true,
|
||||
LeftButtonText = "获取CDK",
|
||||
LeftButtonClick = (sender, args) =>
|
||||
{
|
||||
Launcher.LaunchUriAsync(new Uri("https://mirrorchyan.com/zh/get-start"));
|
||||
}
|
||||
}
|
||||
);
|
||||
if (string.IsNullOrEmpty(cdk))
|
||||
{
|
||||
Toast.Warning("输入CDK为空,无法继续操作");
|
||||
return null;
|
||||
}
|
||||
|
||||
CredentialManagerHelper.SaveCredential(
|
||||
MirrorChyanCdkAppName,
|
||||
string.Empty,
|
||||
cdk,
|
||||
string.Empty,
|
||||
CredentialPersistence.LocalMachine);
|
||||
return cdk;
|
||||
}
|
||||
else
|
||||
{
|
||||
return credential.Password;
|
||||
}
|
||||
}
|
||||
|
||||
public static void EditCdk()
|
||||
{
|
||||
var credential = CredentialManagerHelper.ReadCredential(MirrorChyanCdkAppName);
|
||||
var cdk = PromptDialog.Prompt("Mirror酱是独立的第三方软件下载平台,提供付费的软件下载加速服务。\n如果你有 Mirror酱的 CDK,可以在这里输入。",
|
||||
"修改Mirror酱CDK",
|
||||
credential?.Password!,
|
||||
new PromptDialogConfig
|
||||
{
|
||||
ShowLeftButton = true,
|
||||
LeftButtonText = "获取CDK",
|
||||
LeftButtonClick = (sender, args) =>
|
||||
{
|
||||
Launcher.LaunchUriAsync(new Uri("https://mirrorchyan.com/zh/get-start"));
|
||||
}
|
||||
}
|
||||
);
|
||||
if (string.IsNullOrEmpty(cdk))
|
||||
{
|
||||
DeleteCdk();
|
||||
}
|
||||
else
|
||||
{
|
||||
CredentialManagerHelper.SaveCredential(
|
||||
MirrorChyanCdkAppName,
|
||||
string.Empty,
|
||||
cdk,
|
||||
string.Empty,
|
||||
CredentialPersistence.LocalMachine);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteCdk()
|
||||
{
|
||||
CredentialManagerHelper.DeleteCredential(MirrorChyanCdkAppName);
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,6 @@
|
||||
public class Notice
|
||||
{
|
||||
public string Version { get; set; } = string.Empty;
|
||||
}
|
||||
public int Gray { get; set; } = 10;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
public sealed class UpdateOption
|
||||
{
|
||||
public UpdateTrigger Trigger { get; set; } = default;
|
||||
|
||||
public UpdateChannel Channel { get; set; } = UpdateChannel.Stable;
|
||||
}
|
||||
|
||||
public enum UpdateTrigger
|
||||
@@ -10,3 +12,10 @@ public enum UpdateTrigger
|
||||
Auto,
|
||||
Manual,
|
||||
}
|
||||
|
||||
|
||||
public enum UpdateChannel
|
||||
{
|
||||
Stable,
|
||||
Alpha,
|
||||
}
|
||||
@@ -7,6 +7,8 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using BetterGenshinImpact.GameTask.Common;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Wpf.Ui;
|
||||
|
||||
namespace BetterGenshinImpact.Service;
|
||||
@@ -49,8 +51,13 @@ public class ApplicationHostService(IServiceProvider serviceProvider) : IHostedS
|
||||
_navigationWindow!.ShowWindow();
|
||||
//
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
|
||||
if (args.Length > 1)
|
||||
{
|
||||
|
||||
//无论如何,先跳到主页,否则在通过参数的任务在执行完之前,不会加载快捷键
|
||||
_ = _navigationWindow.Navigate(typeof(HomePage));
|
||||
|
||||
if (args[1].Contains("startOneDragon"))
|
||||
{
|
||||
|
||||
@@ -70,6 +77,19 @@ public class ApplicationHostService(IServiceProvider serviceProvider) : IHostedS
|
||||
var scheduler = App.GetService<ScriptControlViewModel>();
|
||||
scheduler?.OnStartMultiScriptGroupWithNamesAsync(names);
|
||||
}
|
||||
}else if (args[1].Trim().Equals("--TaskProgress", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
|
||||
// 通过命令行参数启动「调度组」 => 跳转到调度器配置页。
|
||||
_ = _navigationWindow.Navigate(typeof(ScriptControlPage));
|
||||
if (args.Length > 1)
|
||||
{
|
||||
// 获取调度组
|
||||
var names = args.Skip(2).ToArray().Select(x => x.Trim()).ToArray();
|
||||
// 启动调度器
|
||||
var scheduler = App.GetService<ScriptControlViewModel>();
|
||||
scheduler?.OnStartMultiScriptTaskProgressAsync(names);
|
||||
}
|
||||
}
|
||||
else if (args[1].Contains("start"))
|
||||
{
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
using BetterGenshinImpact.GameTask.TaskProgress;
|
||||
|
||||
namespace BetterGenshinImpact.Service.Interface;
|
||||
|
||||
public interface IScriptService
|
||||
{
|
||||
Task RunMulti(IEnumerable<ScriptGroupProject> projectList, string? groupName = null);
|
||||
Task RunMulti(IEnumerable<ScriptGroupProject> projectList, string? groupName = null,TaskProgress? taskProgress = null);
|
||||
}
|
||||
|
||||
117
BetterGenshinImpact/Service/Model/MirrorChyan/LatestResponse.cs
Normal file
117
BetterGenshinImpact/Service/Model/MirrorChyan/LatestResponse.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace BetterGenshinImpact.Service.Model.MirrorChyan;
|
||||
|
||||
#nullable enable
|
||||
#pragma warning disable CS8618
|
||||
#pragma warning disable CS8601
|
||||
#pragma warning disable CS8603
|
||||
public partial class LatestResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 响应代码,https://github.com/MirrorChyan/docs/blob/main/ErrorCode.md
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public long Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 响应数据
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("data")]
|
||||
public Data Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 响应信息
|
||||
/// </summary>
|
||||
[JsonPropertyName("msg")]
|
||||
public string Msg { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 响应数据
|
||||
/// </summary>
|
||||
public partial class Data
|
||||
{
|
||||
/// <summary>
|
||||
/// 更新包架构
|
||||
/// </summary>
|
||||
[JsonPropertyName("arch")]
|
||||
public string Arch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// CDK过期时间戳
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("cdk_expired_time")]
|
||||
public double? CdkExpiredTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新频道,stable | beta | alpha
|
||||
/// </summary>
|
||||
[JsonPropertyName("channel")]
|
||||
public string Channel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 自定义数据
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("custom_data")]
|
||||
public string CustomData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件大小
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("filesize")]
|
||||
public long? Filesize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新包系统
|
||||
/// </summary>
|
||||
[JsonPropertyName("os")]
|
||||
public string Os { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发版日志
|
||||
/// </summary>
|
||||
[JsonPropertyName("release_note")]
|
||||
public string ReleaseNote { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// sha256
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("sha256")]
|
||||
public string Sha256 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新包类型,incremental | full
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("update_type")]
|
||||
public string UpdateType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下载地址
|
||||
/// </summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 资源版本名称
|
||||
/// </summary>
|
||||
[JsonPropertyName("version_name")]
|
||||
public string VersionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 资源版本号仅内部使用
|
||||
/// </summary>
|
||||
[JsonPropertyName("version_number")]
|
||||
public long VersionNumber { get; set; }
|
||||
}
|
||||
|
||||
#pragma warning restore CS8618
|
||||
#pragma warning restore CS8601
|
||||
#pragma warning restore CS8603
|
||||
@@ -1,23 +1,25 @@
|
||||
using BetterGenshinImpact.Core.Script;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
using BetterGenshinImpact.Core.Script.Project;
|
||||
using BetterGenshinImpact.GameTask;
|
||||
|
||||
using BetterGenshinImpact.Service.Interface;
|
||||
using BetterGenshinImpact.ViewModel.Pages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.Core.Script;
|
||||
using BetterGenshinImpact.Core.Script.Dependence;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
using BetterGenshinImpact.Core.Script.Project;
|
||||
using BetterGenshinImpact.GameTask;
|
||||
using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
|
||||
using BetterGenshinImpact.GameTask.Common;
|
||||
using BetterGenshinImpact.GameTask.Common.BgiVision;
|
||||
using BetterGenshinImpact.GameTask.Common.Job;
|
||||
using BetterGenshinImpact.GameTask.TaskProgress;
|
||||
using BetterGenshinImpact.Service.Interface;
|
||||
using BetterGenshinImpact.Service.Notification;
|
||||
using BetterGenshinImpact.Service.Notification.Model.Enum;
|
||||
using BetterGenshinImpact.ViewModel.Pages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterGenshinImpact.Service;
|
||||
|
||||
@@ -45,6 +47,7 @@ public partial class ScriptService : IScriptService
|
||||
}
|
||||
public bool ShouldSkipTask(ScriptGroupProject project)
|
||||
{
|
||||
|
||||
if (project.GroupInfo is { Config.PathingConfig.Enabled: true } )
|
||||
{
|
||||
if (IsCurrentHourEqual(project.GroupInfo.Config.PathingConfig.SkipDuring))
|
||||
@@ -72,11 +75,18 @@ public partial class ScriptService : IScriptService
|
||||
}
|
||||
return false; // 不跳过
|
||||
}
|
||||
public async Task RunMulti(IEnumerable<ScriptGroupProject> projectList, string? groupName = null)
|
||||
public async Task RunMulti(IEnumerable<ScriptGroupProject> projectList, string? groupName = null,TaskProgress? taskProgress = null)
|
||||
{
|
||||
groupName ??= "默认";
|
||||
|
||||
var list = ReloadScriptProjects(projectList);
|
||||
|
||||
//恢复临时的跳过标志
|
||||
foreach (var scriptGroupProject in projectList)
|
||||
{
|
||||
scriptGroupProject.SkipFlag = false;
|
||||
}
|
||||
|
||||
|
||||
// // 针对JS 脚本,检查是否包含定时器操作
|
||||
// var jsProjects = ExtractJsProjects(list);
|
||||
@@ -101,15 +111,29 @@ public partial class ScriptService : IScriptService
|
||||
|
||||
// var timerOperation = hasTimer ? DispatcherTimerOperationEnum.UseCacheImageWithTriggerEmpty : DispatcherTimerOperationEnum.UseSelfCaptureImage;
|
||||
|
||||
Notify.Event(NotificationEvent.GroupStart).Success($"配置组{groupName}启动");
|
||||
|
||||
|
||||
bool fisrt = true;
|
||||
await new TaskRunner()
|
||||
.RunThreadAsync(async () =>
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
|
||||
int projectIndex = -1;
|
||||
foreach (var project in list)
|
||||
{
|
||||
projectIndex++;
|
||||
if (taskProgress != null && taskProgress.Next != null)
|
||||
{
|
||||
if (taskProgress.Next.Index>projectIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
taskProgress.Next = null;
|
||||
}
|
||||
|
||||
if (project is {SkipFlag:true})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (ShouldSkipTask(project))
|
||||
{
|
||||
continue;
|
||||
@@ -128,6 +152,24 @@ public partial class ScriptService : IScriptService
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (fisrt)
|
||||
{
|
||||
fisrt = false;
|
||||
Notify.Event(NotificationEvent.GroupStart).Success($"配置组{groupName}启动");
|
||||
}
|
||||
|
||||
if (taskProgress!=null)
|
||||
{
|
||||
taskProgress.CurrentScriptGroupProjectInfo = new TaskProgress.ScriptGroupProjectInfo
|
||||
{
|
||||
Name = project.Name,
|
||||
FolderName = project.FolderName
|
||||
,Index = projectIndex
|
||||
,GroupName = taskProgress?.CurrentScriptGroupName ?? ""
|
||||
};
|
||||
TaskProgressManager.SaveTaskProgress(taskProgress);
|
||||
}
|
||||
for (var i = 0; i < project.RunNum; i++)
|
||||
{
|
||||
try
|
||||
@@ -139,6 +181,7 @@ public partial class ScriptService : IScriptService
|
||||
|
||||
stopwatch.Reset();
|
||||
stopwatch.Start();
|
||||
|
||||
await ExecuteProject(project);
|
||||
|
||||
//多次执行时及时中断
|
||||
@@ -160,6 +203,10 @@ public partial class ScriptService : IScriptService
|
||||
{
|
||||
_logger.LogDebug(e, "执行脚本时发生异常");
|
||||
_logger.LogError("执行脚本时发生异常: {Msg}", e.Message);
|
||||
if (taskProgress!=null && taskProgress.CurrentScriptGroupProjectInfo!=null )
|
||||
{
|
||||
taskProgress.CurrentScriptGroupProjectInfo.Status = 2;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -173,6 +220,48 @@ public partial class ScriptService : IScriptService
|
||||
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
|
||||
if (taskProgress != null)
|
||||
{
|
||||
if (taskProgress.CurrentScriptGroupProjectInfo!=null )
|
||||
{
|
||||
taskProgress.CurrentScriptGroupProjectInfo.TaskEnd = true;
|
||||
taskProgress.CurrentScriptGroupProjectInfo.EndTime = DateTime.Now;
|
||||
if (taskProgress.CurrentScriptGroupProjectInfo.Status == 1)
|
||||
{
|
||||
taskProgress.ConsecutiveFailureCount = 0;
|
||||
taskProgress.LastSuccessScriptGroupProjectInfo =
|
||||
taskProgress.CurrentScriptGroupProjectInfo;
|
||||
taskProgress.LastScriptGroupName =taskProgress.CurrentScriptGroupName;
|
||||
}
|
||||
//累计连续失败次数
|
||||
if (taskProgress.CurrentScriptGroupProjectInfo.Status == 2)
|
||||
{
|
||||
taskProgress.ConsecutiveFailureCount++;
|
||||
}
|
||||
|
||||
taskProgress?.History?.Add(taskProgress.CurrentScriptGroupProjectInfo);
|
||||
TaskProgressManager.SaveTaskProgress(taskProgress);
|
||||
}
|
||||
|
||||
//异常达到一次次数,重启bgi
|
||||
var autoconfig = TaskContext.Instance().Config.OtherConfig.AutoRestartConfig;
|
||||
if (autoconfig.Enabled && taskProgress.ConsecutiveFailureCount >= autoconfig.FailureCount)
|
||||
{
|
||||
_logger.LogInformation("调度器任务出现未预期的异常,自动重启bgi");
|
||||
Notify.Event(NotificationEvent.GroupEnd).Error("调度器任务出现未预期的异常,自动重启bgi");
|
||||
if (autoconfig.RestartGameTogether
|
||||
&& TaskContext.Instance().Config.GenshinStartConfig.LinkedStartEnabled
|
||||
&& TaskContext.Instance().Config.GenshinStartConfig.AutoEnterGameEnabled)
|
||||
{
|
||||
SystemControl.CloseGame();
|
||||
Thread.Sleep(2000);
|
||||
}
|
||||
|
||||
SystemControl.RestartApplication(["--TaskProgress",taskProgress.Name]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -184,7 +273,16 @@ public partial class ScriptService : IScriptService
|
||||
_logger.LogInformation("配置组 {Name} 执行结束", groupName);
|
||||
}
|
||||
|
||||
Notify.Event(NotificationEvent.GroupEnd).Success($"配置组{groupName}结束");
|
||||
if (!fisrt)
|
||||
{
|
||||
Notify.Event(NotificationEvent.GroupEnd).Success($"配置组{groupName}结束");
|
||||
}
|
||||
|
||||
if (taskProgress != null)
|
||||
{
|
||||
taskProgress.Next = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private List<ScriptGroupProject> ReloadScriptProjects(IEnumerable<ScriptGroupProject> projectList)
|
||||
@@ -231,6 +329,7 @@ public partial class ScriptService : IScriptService
|
||||
target.JsScriptSettingsObject = source.JsScriptSettingsObject;
|
||||
target.GroupInfo = source.GroupInfo;
|
||||
target.AllowJsNotification = source.AllowJsNotification;
|
||||
target.SkipFlag = source.SkipFlag;
|
||||
}
|
||||
|
||||
// private List<ScriptProject> ExtractJsProjects(List<ScriptGroupProject> list)
|
||||
@@ -312,6 +411,8 @@ public partial class ScriptService : IScriptService
|
||||
{
|
||||
await Task.Delay(200);
|
||||
var first = true;
|
||||
var sw = Stopwatch.StartNew();
|
||||
var loseFocusCount = 0;
|
||||
while (true)
|
||||
{
|
||||
if (!homePageViewModel.TaskDispatcherEnabled || !TaskContext.Instance().IsInitialized)
|
||||
@@ -329,10 +430,31 @@ public partial class ScriptService : IScriptService
|
||||
{
|
||||
first = false;
|
||||
TaskControl.Logger.LogInformation("当前不在游戏主界面,等待进入主界面后执行任务...");
|
||||
TaskControl.Logger.LogInformation("如果你已经在游戏内的其他界面,请自行退出当前界面(ESC),使当前任务能够继续运行!");
|
||||
TaskControl.Logger.LogInformation("如果你已经在游戏内的其他界面,请自行退出当前界面(ESC),或是30秒后将程序将自动尝试到入主界面,使当前任务能够继续运行!");
|
||||
}
|
||||
|
||||
await Task.Delay(500);
|
||||
if (sw.Elapsed.TotalSeconds >= 30)
|
||||
{
|
||||
//防止自启动游戏后因为一些原因失焦,导致一直卡住
|
||||
if (!SystemControl.IsGenshinImpactActiveByProcess())
|
||||
{
|
||||
loseFocusCount++;
|
||||
if (loseFocusCount>50 && loseFocusCount<100)
|
||||
{
|
||||
SystemControl.MinimizeAndActivateWindow(TaskContext.Instance().GameHandle);
|
||||
}
|
||||
SystemControl.ActivateWindow();
|
||||
}
|
||||
|
||||
//自启动游戏,如果鼠标在游戏外面,将无法自动开门,这里尝试移动到游戏界面
|
||||
if (sw.Elapsed.TotalSeconds < 200)
|
||||
{
|
||||
GlobalMethod.MoveMouseTo(300, 300);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
@@ -18,6 +19,8 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using BetterGenshinImpact.Service.Model.MirrorChyan;
|
||||
using Wpf.Ui.Violeta.Controls;
|
||||
|
||||
namespace BetterGenshinImpact.Service;
|
||||
|
||||
@@ -37,7 +40,7 @@ public class UpdateService : IUpdateService
|
||||
_configService = configService;
|
||||
Config = _configService.Get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Please call me in main thread
|
||||
@@ -47,25 +50,32 @@ public class UpdateService : IUpdateService
|
||||
{
|
||||
try
|
||||
{
|
||||
#if DEBUG && true
|
||||
#if DEBUG && false
|
||||
return;
|
||||
#endif
|
||||
string newVersion = await GetLatestVersionAsync();
|
||||
|
||||
|
||||
string newVersion = await GetLatestVersionAsync(option);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newVersion))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- 如果是调试模式且手动的检查更新的情况下,强制打开更新窗口 -----
|
||||
// 方便调试窗口
|
||||
if (RuntimeHelper.IsDebuggerAttached && option.Trigger == UpdateTrigger.Manual)
|
||||
{
|
||||
await OpenCheckUpdateWindow(option, newVersion);
|
||||
return;
|
||||
}
|
||||
// ---- 如果是调试模式且手动的检查更新的情况下,强制打开更新窗口 -----
|
||||
|
||||
if (!Global.IsNewVersion(newVersion))
|
||||
{
|
||||
if (option.Trigger == UpdateTrigger.Manual)
|
||||
{
|
||||
await MessageBox.InformationAsync("当前已是最新版本!");
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,76 +86,203 @@ public class UpdateService : IUpdateService
|
||||
return;
|
||||
}
|
||||
|
||||
CheckUpdateWindow win = new(option)
|
||||
{
|
||||
Owner = Application.Current.MainWindow,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
Title = $"发现新版本 {newVersion}",
|
||||
UserInteraction = async (sender, button) =>
|
||||
{
|
||||
CheckUpdateWindow win = (CheckUpdateWindow)sender;
|
||||
|
||||
switch (button)
|
||||
{
|
||||
case CheckUpdateWindow.CheckUpdateWindowButton.BackgroundUpdate:
|
||||
// TBD
|
||||
break;
|
||||
|
||||
case CheckUpdateWindow.CheckUpdateWindowButton.OtherUpdate:
|
||||
Process.Start(new ProcessStartInfo(DownloadPageUrl) { UseShellExecute = true });
|
||||
break;
|
||||
|
||||
case CheckUpdateWindow.CheckUpdateWindowButton.Update:
|
||||
{
|
||||
// 唤起更新程序
|
||||
string updaterExePath = Global.Absolute("BetterGI.update.exe");
|
||||
if (!File.Exists(updaterExePath))
|
||||
{
|
||||
await MessageBox.ErrorAsync("更新程序不存在,请选择其他更新方式!");
|
||||
return;
|
||||
}
|
||||
// 启动
|
||||
Process.Start(updaterExePath, "-I");
|
||||
|
||||
// 退出程序
|
||||
Application.Current.Shutdown();
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case CheckUpdateWindow.CheckUpdateWindowButton.Ignore:
|
||||
Config.NotShowNewVersionNoticeEndVersion = newVersion;
|
||||
win.Close();
|
||||
break;
|
||||
|
||||
case CheckUpdateWindow.CheckUpdateWindowButton.Cancel:
|
||||
win.ShowUpdateStatus = false;
|
||||
win.Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
win.NavigateToHtml(await GetReleaseMarkdownHtmlAsync());
|
||||
win.ShowDialog();
|
||||
await OpenCheckUpdateWindow(option, newVersion);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine("获取最新版本信息失败:" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message);
|
||||
Debug.WriteLine("获取最新版本信息失败:" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" +
|
||||
Environment.NewLine + e.Message);
|
||||
_logger.LogWarning("获取 BetterGI 最新版本信息失败");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GetLatestVersionAsync()
|
||||
private async Task OpenCheckUpdateWindow(UpdateOption option, string newVersion)
|
||||
{
|
||||
CheckUpdateWindow win = new(option)
|
||||
{
|
||||
Owner = Application.Current.MainWindow,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
Title = $"发现新版本 {newVersion}",
|
||||
UserInteraction = async (sender, button) =>
|
||||
{
|
||||
CheckUpdateWindow win = (CheckUpdateWindow)sender;
|
||||
|
||||
switch (button)
|
||||
{
|
||||
case CheckUpdateWindow.CheckUpdateWindowButton.BackgroundUpdate:
|
||||
// TBD
|
||||
break;
|
||||
|
||||
case CheckUpdateWindow.CheckUpdateWindowButton.OtherUpdate:
|
||||
if (option.Channel == UpdateChannel.Stable)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(DownloadPageUrl) { UseShellExecute = true });
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("https://github.com/babalae/better-genshin-impact/actions/workflows/publish.yml") { UseShellExecute = true });
|
||||
}
|
||||
break;
|
||||
|
||||
case CheckUpdateWindow.CheckUpdateWindowButton.Update:
|
||||
{
|
||||
// 唤起更新程序
|
||||
string updaterExePath = Global.Absolute("BetterGI.update.exe");
|
||||
if (!File.Exists(updaterExePath))
|
||||
{
|
||||
await MessageBox.ErrorAsync("更新程序不存在,请选择其他更新方式!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动
|
||||
Process.Start(updaterExePath, "-I");
|
||||
|
||||
// 退出程序
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
break;
|
||||
|
||||
case CheckUpdateWindow.CheckUpdateWindowButton.Ignore:
|
||||
Config.NotShowNewVersionNoticeEndVersion = newVersion;
|
||||
win.Close();
|
||||
break;
|
||||
|
||||
case CheckUpdateWindow.CheckUpdateWindowButton.Cancel:
|
||||
win.ShowUpdateStatus = false;
|
||||
win.Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (option.Channel == UpdateChannel.Stable)
|
||||
{
|
||||
win.NavigateToHtml(await GetReleaseMarkdownHtmlAsync());
|
||||
}
|
||||
|
||||
win.ShowDialog();
|
||||
}
|
||||
|
||||
private async Task<string> GetLatestVersionAsync(UpdateOption option)
|
||||
{
|
||||
if (option.Channel == UpdateChannel.Stable)
|
||||
{
|
||||
return await UpdateFromOss();
|
||||
}
|
||||
else
|
||||
{
|
||||
return await UpdateFromMirrorChyan();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文档
|
||||
/// https://apifox.com/apidoc/shared/ffdc8453-597d-4ba6-bd3c-5e375c10c789
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task<string> UpdateFromMirrorChyan()
|
||||
{
|
||||
try
|
||||
{
|
||||
const string url = "https://mirrorchyan.com/api/resources/BGI/latest";
|
||||
var queryParams = new Dictionary<string, string>
|
||||
{
|
||||
{ "user_agent", "BetterGI" },
|
||||
{ "os", "win" },
|
||||
{ "arch", "x64" },
|
||||
{ "channel", "alpha" }
|
||||
};
|
||||
|
||||
using var httpClient = new HttpClient();
|
||||
|
||||
var finalUrl = $"{url}?{string.Join("&", queryParams.Select(x => $"{x.Key}={x.Value}"))}";
|
||||
var response = await httpClient.GetAsync(finalUrl);
|
||||
LatestResponse? result = null;
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
result = await response.Content.ReadFromJsonAsync<LatestResponse>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 即使是403、400也尝试读取响应体
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
result = JsonConvert.DeserializeObject<LatestResponse>(content);
|
||||
}
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
if (result.Code == 0)
|
||||
{
|
||||
return result.Data.VersionName;
|
||||
}
|
||||
else if (result.Code < 0)
|
||||
{
|
||||
Toast.Error(
|
||||
$"Mirror酱源更新检查失败,意料之外的严重错误,请及时联系 Mirror 酱的技术支持处理\n,错误代码:{result.Code},错误信息:{result.Msg}");
|
||||
return string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
ToastError(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogDebug(e, "Mirror源更新检查失败");
|
||||
Toast.Warning($"Mirror源更新检查失败,{e.Message}");
|
||||
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static void ToastError(LatestResponse response)
|
||||
{
|
||||
if (response.Code == 7001)
|
||||
{
|
||||
Toast.Warning("Mirror酱 CDK 已过期,请重新获取CDK");
|
||||
}
|
||||
else if (response.Code == 7002)
|
||||
{
|
||||
Toast.Warning("Mirror酱 CDK 错误!");
|
||||
}
|
||||
else if (response.Code == 7003)
|
||||
{
|
||||
Toast.Warning("Mirror酱 CDK 今日下载次数已达上限");
|
||||
}
|
||||
else if (response.Code == 7004)
|
||||
{
|
||||
Toast.Warning("Mirror酱 CDK 类型和待下载的资源不匹配");
|
||||
}
|
||||
else if (response.Code == 7005)
|
||||
{
|
||||
Toast.Warning("Mirror酱 CDK 已被封禁");
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.Warning($"Mirror酱源更新检查失败,错误信息:{response.Msg}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> UpdateFromOss()
|
||||
{
|
||||
try
|
||||
{
|
||||
using HttpClient httpClient = new();
|
||||
Notice? notice = await httpClient.GetFromJsonAsync<Notice>(NoticeUrl);
|
||||
string deviceId = DeviceIdHelper.DeviceId;
|
||||
|
||||
if (notice != null)
|
||||
{
|
||||
return notice.Version;
|
||||
// 灰度发布逻辑:deviceId做hash取余
|
||||
int hash = deviceId.GetHashCode();
|
||||
int mod = Math.Abs(hash % 10);
|
||||
if (mod < notice.Gray)
|
||||
{
|
||||
return notice.Version;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -162,7 +299,9 @@ public class UpdateService : IUpdateService
|
||||
{
|
||||
using HttpClient httpClient = new();
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
|
||||
string jsonString = await httpClient.GetStringAsync("https://api.github.com/repos/babalae/better-genshin-impact/releases/latest");
|
||||
string jsonString =
|
||||
await httpClient.GetStringAsync(
|
||||
"https://api.github.com/repos/babalae/better-genshin-impact/releases/latest");
|
||||
var jsonDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonString);
|
||||
|
||||
if (jsonDict != null)
|
||||
@@ -172,7 +311,8 @@ public class UpdateService : IUpdateService
|
||||
string md = $"# {name}{new string('\n', 2)}{body}";
|
||||
|
||||
md = WebUtility.HtmlEncode(md);
|
||||
string md2html = ResourceHelper.GetString($"pack://application:,,,/Assets/Strings/md2html.html", Encoding.UTF8);
|
||||
string md2html = ResourceHelper.GetString($"pack://application:,,,/Assets/Strings/md2html.html",
|
||||
Encoding.UTF8);
|
||||
var html = md2html.Replace("{{content}}", md);
|
||||
|
||||
return html;
|
||||
@@ -221,4 +361,4 @@ public class UpdateService : IUpdateService
|
||||
</html>
|
||||
""";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,389 +1,516 @@
|
||||
[
|
||||
{
|
||||
"name": "荧",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "空",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "神里绫华",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "琴",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "丽莎",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "芭芭拉",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "凯亚",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "迪卢克",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "雷泽",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "安柏",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "温迪",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "香菱",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "北斗",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "行秋",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "魈",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "凝光",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "可莉",
|
||||
"scriptContent1": "keydown(w),wait(0.08),attack(0.05),keyup(w),wait(0.2)",
|
||||
"scriptContent2": "mousedown(left),mouseup(left),wait(0.09),charge(0.519),wait(0.09),charge(0.519),wait(0.09)"
|
||||
"scriptContent2": "mousedown(left),mouseup(left),wait(0.09),charge(0.519),wait(0.09),charge(0.519),wait(0.09)",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "钟离",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "菲谢尔",
|
||||
"scriptContent1": "mousedown(left),wait(0.001),mouseup(left),wait(0.3),mousedown(left),wait(0.001),mouseup(left),wait(0.2),keydown(r),wait(0.02),keyup(r),wait(0.1),keydown(r),wait(0.02),keyup(r),wait(0.04)"
|
||||
"scriptContent1": "mousedown(left),wait(0.001),mouseup(left),wait(0.3),mousedown(left),wait(0.001),mouseup(left),wait(0.2),keydown(r),wait(0.02),keyup(r),wait(0.1),keydown(r),wait(0.02),keyup(r),wait(0.04)",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "班尼特",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "达达利亚",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "诺艾尔",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "七七",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "重云",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "甘雨",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "阿贝多",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "迪奥娜",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "莫娜",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "刻晴",
|
||||
"scriptContent1": "mousedown(left),wait(0.350),mouseup(left),wait(0.100)",
|
||||
"scriptContent2": "keydown(e),wait(0.085),keyup(e),wait(0.667),keydown(q),wait(0.081),keyup(q),wait(2.388),keydown(e),wait(0.085),keyup(e)"
|
||||
"scriptContent2": "keydown(e),wait(0.085),keyup(e),wait(0.667),keydown(q),wait(0.081),keyup(q),wait(2.388),keydown(e),wait(0.085),keyup(e)",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "砂糖",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "辛焱",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "罗莎莉亚",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "胡桃",
|
||||
"scriptContent1": "charge(0.3),wait(0.1),dash,wait(0.1),charge(0.3),wait(0.1),dash,wait(0.1),charge(0.3),wait(0.1),j,wait(0.52)"
|
||||
"scriptContent1": "charge(0.3),wait(0.1),dash,wait(0.1),charge(0.3),wait(0.1),dash,wait(0.1),charge(0.3),wait(0.1),j,wait(0.52)",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "枫原万叶",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "烟绯",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "宵宫",
|
||||
"scriptContent1": "e,wait(0.8),attack(1)",
|
||||
"scriptContent2": "mousedown(left),wait(0.001),mouseup(left),wait(0.218),mousedown(left),wait(0.001),mouseup(left),wait(0.219),mousedown(left),wait(0.25),mouseup(left),wait(0.206)"
|
||||
"scriptContent2": "mousedown(left),wait(0.001),mouseup(left),wait(0.218),mousedown(left),wait(0.001),mouseup(left),wait(0.219),mousedown(left),wait(0.25),mouseup(left),wait(0.206)",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "托马",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "优菈",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "雷电将军",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "早柚",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "珊瑚宫心海",
|
||||
"scriptContent1": "mousedown(left),wait(0.005),mouseup(left),wait(0.005),mousedown(left),wait(0.005),mouseup(left),wait(0.425),keydown(w),wait(0.225),keyup(w),wait(0.05)"
|
||||
"scriptContent1": "mousedown(left),wait(0.005),mouseup(left),wait(0.005),mousedown(left),wait(0.005),mouseup(left),wait(0.425),keydown(w),wait(0.225),keyup(w),wait(0.05)",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "五郎",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "九条裟罗",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "荒泷一斗",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "八重神子",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "鹿野院平藏",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "夜兰",
|
||||
"scriptContent1": "mousedown(left),wait(0.550),mouseup(left)"
|
||||
"scriptContent1": "mousedown(left),wait(0.550),mouseup(left)",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "绮良良",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "埃洛伊",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "申鹤",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "云堇",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "久岐忍",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "神里绫人",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "柯莱",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "多莉",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "提纳里",
|
||||
"scriptContent1": "keydown(r),wait(0.005),keyup(r),wait(0.25),mousedown(left),wait(0.05),mouseup(left),wait(0.8),mousedown(left),wait(0.05),mouseup(left),wait(0.8),mousedown(left),wait(0.05),mouseup(left),wait(0.05),keydown(r),wait(0.005),keyup(r)"
|
||||
"scriptContent1": "keydown(r),wait(0.005),keyup(r),wait(0.25),mousedown(left),wait(0.05),mouseup(left),wait(0.8),mousedown(left),wait(0.05),mouseup(left),wait(0.8),mousedown(left),wait(0.05),mouseup(left),wait(0.05),keydown(r),wait(0.005),keyup(r)",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "妮露",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "赛诺",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "坎蒂丝",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "纳西妲",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "莱依拉",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "流浪者",
|
||||
"scriptContent1": "e,wait(0.3),charge(0.71),wait(0.101),charge(0.69),wait(0.09),charge(0.702),wait(0.101),charge(0.703),wait(0.102),charge(0.698),wait(0.099),charge(0.703),wait(0.098),charge(0.703),wait(0.101),charge(0.704),wait(0.099),charge(0.701),wait(0.101),charge(0.701),wait(0.101),charge(0.702),wait(0.103),charge(0.704),wait(0.103),charge(0.704)"
|
||||
"scriptContent1": "e,wait(0.3),charge(0.71),wait(0.101),charge(0.69),wait(0.09),charge(0.702),wait(0.101),charge(0.703),wait(0.102),charge(0.698),wait(0.099),charge(0.703),wait(0.098),charge(0.703),wait(0.101),charge(0.704),wait(0.099),charge(0.701),wait(0.101),charge(0.701),wait(0.101),charge(0.702),wait(0.103),charge(0.704),wait(0.103),charge(0.704)",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "珐露珊",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "瑶瑶",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "艾尔海森",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "迪希雅",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "米卡",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "卡维",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "白术",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "琳妮特",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "林尼",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "菲米尼",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "莱欧斯利",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "那维莱特",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "夏洛蒂",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "芙宁娜",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "夏沃蕾",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "娜维娅",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "嘉明",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "闲云",
|
||||
"scriptContent1": ""
|
||||
},
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "千织",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "希格雯",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "阿蕾奇诺",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "赛索斯",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "克洛琳德",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "艾梅莉埃",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "卡齐娜",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "基尼奇",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "玛拉妮",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "希诺宁",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "恰斯卡",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "欧洛伦",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "玛薇卡",
|
||||
"scriptContent1": "keydown(space),keyup(space),wait(0.08),keydown(space),keyup(space),wait(0.08),keydown(space),keyup(space),wait(1.3)"
|
||||
"scriptContent1": "keydown(space),keyup(space),wait(0.08),keydown(space),keyup(space),wait(0.08),keydown(space),keyup(space),wait(1.3)",
|
||||
"scriptContent2": "mousedown(left),wait(0.3),mouseup(left),wait(0.6),mousedown(right),wait(0.08),mouseup(right),wait(0.02),mousedown(left),wait(0.15),mouseup(left),wait(0.05)",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "茜特菈莉",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "蓝砚",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "梦见月瑞希",
|
||||
"scriptContent1": ""
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "伊安珊",
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "瓦雷莎",
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "爱可菲",
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "伊法",
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "丝柯克",
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
},
|
||||
{
|
||||
"name": "塔利雅",
|
||||
"scriptContent1": "",
|
||||
"macroPriority": 0
|
||||
}
|
||||
]
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:pages="clr-namespace:BetterGenshinImpact.ViewModel.Pages"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
||||
Title="CommonSettingsPage"
|
||||
d:DataContext="{d:DesignInstance Type=pages:CommonSettingsPageViewModel}"
|
||||
d:DesignHeight="2000"
|
||||
@@ -989,6 +990,104 @@
|
||||
Margin="0,0,36,0"
|
||||
IsChecked="{Binding Config.OtherConfig.RestoreFocusOnLostEnabled, Mode=TwoWay}" />-->
|
||||
</Grid>
|
||||
<ui:CardExpander Margin="0,0,0,12"
|
||||
ContentPadding="0"
|
||||
Icon="{ui:SymbolIcon SquareHintSparkles24}">
|
||||
<ui:CardExpander.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="调度器异常重启配置"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="当调度器任务异常抛出未预期错误时,累计一定次数后,自动重启bgi,以恢复功能。"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:ToggleSwitch Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,24,0"
|
||||
IsChecked="{Binding Config.OtherConfig.AutoRestartConfig.Enabled, Mode=TwoWay}" />
|
||||
<b:Interaction.Triggers>
|
||||
<b:EventTrigger EventName="Unchecked">
|
||||
<b:InvokeCommandAction Command="{Binding SwitchMaskEnabledCommand}" />
|
||||
</b:EventTrigger>
|
||||
<b:EventTrigger EventName="Checked">
|
||||
<b:InvokeCommandAction Command="{Binding SwitchMaskEnabledCommand}" />
|
||||
</b:EventTrigger>
|
||||
</b:Interaction.Triggers>
|
||||
</Grid>
|
||||
</ui:CardExpander.Header>
|
||||
<StackPanel>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="异常次数"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="当运行调度器任务时,异常导致任务失败的计数,当达到计数时会重启bgi。"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="90"
|
||||
Margin="0,0,36,0"
|
||||
Text="{Binding Config.OtherConfig.AutoRestartConfig.FailureCount, Mode=TwoWay,
|
||||
ValidatesOnNotifyDataErrors=True}" />
|
||||
</Grid>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="是否同时重启游戏"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
TextWrapping="Wrap">
|
||||
重启bgi时,同时重启游戏,需开启首页启动配置:同时启动原神、自动进入游戏,此配置才会生效。
|
||||
</ui:TextBlock>
|
||||
|
||||
<ui:ToggleSwitch Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,36,0"
|
||||
IsChecked="{Binding Config.OtherConfig.AutoRestartConfig.RestartGameTogether, Mode=TwoWay}">
|
||||
</ui:ToggleSwitch>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
<!-- 地图 -->
|
||||
@@ -1049,6 +1148,98 @@
|
||||
<ui:TextBlock Margin="0,0,0,8"
|
||||
FontTypography="BodyStrong"
|
||||
Text="帮助" />
|
||||
|
||||
<!-- Update Check Card -->
|
||||
|
||||
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0" Icon="{ui:SymbolIcon ArrowDownload24}">
|
||||
<ui:CardExpander.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="版本更新"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="检查软件是否有新版本可用"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:Button Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,20,0"
|
||||
Command="{Binding CheckUpdateCommand}"
|
||||
Content="检查更新" />
|
||||
</Grid>
|
||||
</ui:CardExpander.Header>
|
||||
<StackPanel>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<emoji:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="检查是否存在最新测试版"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="【测试版】非常不稳定,请谨慎选择更新!"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal">
|
||||
<ui:Button
|
||||
Margin="0,0,36,0"
|
||||
Command="{Binding CheckUpdateAlphaCommand}"
|
||||
Content="检查更新" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!--<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<emoji:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="直接从 Github 获取最新测试版"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="【测试版】非常不稳定,请谨慎选择更新!"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:Button Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,36,0"
|
||||
Command="{Binding GotoGithubActionCommand}"
|
||||
Content="访问 Github" />
|
||||
</Grid>-->
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
|
||||
<!-- About Option -->
|
||||
<ui:CardControl Margin="0,0,0,12" Icon="{ui:SymbolIcon Info24}">
|
||||
<ui:CardControl.Header>
|
||||
<Grid>
|
||||
@@ -1073,4 +1264,5 @@
|
||||
Content="查看" />
|
||||
</ui:CardControl>
|
||||
</StackPanel>
|
||||
</Page>
|
||||
</Page>
|
||||
|
||||
|
||||
@@ -120,21 +120,37 @@
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="角色选择使用的战斗宏编号"
|
||||
Text="默认战斗宏编号"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="用于修改当前配置文件中哪个战斗宏是优先使用的(1~5)"
|
||||
Text="当角色的 macroPriority 设置为0时,使用此默认宏编号(1~5)"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:NumberBox Grid.Row="0"
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="90"
|
||||
Margin="0,0,36,0"
|
||||
Maximum="5"
|
||||
Minimum="1"
|
||||
ValidationMode="InvalidInputOverwritten"
|
||||
Value="{Binding Config.MacroConfig.CombatMacroPriority, Mode=TwoWay}" />
|
||||
Text="{Binding Config.MacroConfig.CombatMacroPriority, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
FontTypography="Body"
|
||||
Text="角色个性化宏编号设置"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="上方宏配置支持为每个角色单独设置宏编号。在角色宏配置中设置 macroPriority 字段(1-5),设置为0则使用上面的默认战斗宏编号。"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
|
||||
@@ -341,7 +341,7 @@
|
||||
Text="自动秘境" />
|
||||
|
||||
|
||||
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0">
|
||||
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0" IsExpanded="{Binding SelectedConfig.WeeklyDomainEnabled, Converter={StaticResource InverseBooleanConverter}, Mode=TwoWay}">
|
||||
<ui:CardExpander.Icon>
|
||||
<ui:FontIcon Glyph="" Style="{StaticResource FaFontIconStyle}" />
|
||||
</ui:CardExpander.Icon>
|
||||
@@ -470,8 +470,7 @@
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
|
||||
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0">
|
||||
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0" IsExpanded="{Binding SelectedConfig.WeeklyDomainEnabled, Mode=TwoWay}">
|
||||
<ui:CardExpander.Icon>
|
||||
<ui:FontIcon Glyph="" Style="{StaticResource FaFontIconStyle}" />
|
||||
</ui:CardExpander.Icon>
|
||||
@@ -493,10 +492,8 @@
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="启用后,每日刷取配置将会失效"
|
||||
TextWrapping="Wrap">
|
||||
</ui:TextBlock>
|
||||
|
||||
Text="启用后,每日刷取配置将会失效。"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:ToggleSwitch Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
@@ -505,6 +502,25 @@
|
||||
</Grid>
|
||||
</ui:CardExpander.Header>
|
||||
<StackPanel>
|
||||
<Grid Margin="52,16,16,8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text=""
|
||||
TextWrapping="Wrap">
|
||||
新的一天开始于 4:00
|
||||
</ui:TextBlock>
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
TextWrapping="Wrap">
|
||||
周一 4:00 至周二 3:59 执行周一配置,以此类推。
|
||||
</ui:TextBlock>
|
||||
</Grid>
|
||||
<Grid Margin="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -513,7 +529,7 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="35,0,0,0"
|
||||
Margin="38,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="周一"
|
||||
VerticalAlignment="Center" />
|
||||
@@ -542,7 +558,7 @@
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Margin="35,0,0,0"
|
||||
Margin="38,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="周二"
|
||||
VerticalAlignment="Center" />
|
||||
@@ -570,7 +586,7 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="35,0,0,0"
|
||||
Margin="38,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="周三"
|
||||
VerticalAlignment="Center" />
|
||||
@@ -599,7 +615,7 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="35,0,0,0"
|
||||
Margin="38,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="周四"
|
||||
VerticalAlignment="Center" />
|
||||
@@ -628,7 +644,7 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="35,0,0,0"
|
||||
Margin="38,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="周五"
|
||||
VerticalAlignment="Center" />
|
||||
@@ -658,7 +674,7 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="35,0,0,0"
|
||||
Margin="38,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="周六"
|
||||
VerticalAlignment="Center" />
|
||||
@@ -692,7 +708,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="35,0,0,0"
|
||||
Margin="38,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="周日"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
@@ -55,7 +55,18 @@
|
||||
SelectionMode="Single">
|
||||
<ui:ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Margin="8,4" Text="{Binding Name, Mode=OneWay}" />
|
||||
<TextBlock Margin="8,4" Text="{Binding Name, Mode=OneWay}" >
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding NextFlag}" Value="True">
|
||||
<Setter Property="Foreground" Value="Green" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</ui:ListView.ItemTemplate>
|
||||
<ui:ListView.ContextMenu>
|
||||
@@ -70,14 +81,30 @@
|
||||
<MenuItem Command="{Binding CopyScriptGroupCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}"
|
||||
Header="复制组" />
|
||||
<MenuItem Command="{Binding AddScriptGroupNextFlagCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}"
|
||||
Header="连续任务从此开始执行" />
|
||||
</ContextMenu>
|
||||
</ui:ListView.ContextMenu>
|
||||
</ui:ListView>
|
||||
<ui:Button Grid.Row="2"
|
||||
Margin="0,2,0,2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding StartMultiScriptGroupCommand}"
|
||||
Content="连续执行" />
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ui:Button Grid.Row="0"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding StartMultiScriptGroupCommand}"
|
||||
Content="连续执行" />
|
||||
|
||||
<ui:Button Grid.Row="1"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding ContinueMultiScriptGroupCommand}"
|
||||
Content="继续执行" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -1526,6 +1526,40 @@
|
||||
IsEnabled="{Binding Config.CommonConfig.ScreenshotEnabled}"
|
||||
IsChecked="{Binding SaveScreenshotOnKeyTick, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="torch库文件地址(仅限2.5.1版本)"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
TextWrapping="Wrap" >
|
||||
请
|
||||
<Hyperlink Command="{Binding GoToTorchPreviousVersionsCommand}"
|
||||
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}">
|
||||
下载
|
||||
</Hyperlink> 到本地后填入torch_cpu.dll或torch_cuda.dll的完整地址。如未生效可尝试重启BGI。
|
||||
|
||||
</ui:TextBlock>
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="180"
|
||||
MaxWidth="800"
|
||||
Margin="0,0,36,0"
|
||||
Text="{Binding Config.AutoFishingConfig.TorchDllFullPath, Mode=TwoWay}"
|
||||
TextWrapping="Wrap" Cursor="IBeam" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
|
||||
|
||||
@@ -459,7 +459,7 @@
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="执行周期序号,按时间戳对应的天数(会根据分界时间修正),对周期求余值加1,得出的值和配置执一致就会执行,否则跳过任务。"
|
||||
Text="执行周期序号,按时间戳对应的天数(会根据分界时间修正),对周期求余值加1,得出的值和配置执一致就会执行,否则跳过任务。可以理解为周期内第几天执行。"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
@@ -486,7 +486,7 @@
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="点击计算按钮,可计算出当天执行序号。"
|
||||
Text="点击计算按钮,可计算出当天执行序号(今天是周期内的第几天)。"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:Button Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
@@ -878,6 +878,35 @@
|
||||
Margin="0,0,36,0"
|
||||
IsChecked="{Binding PathingConfig.AutoFightConfig.KazuhaPickupEnabled, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="只拾取精英掉落模式"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="需要路径文件支持,在点位的更多》扩展配置》怪物标签中,可标记。只拾取标记为精英或传奇的点位。非精英允许自动拾取:战斗过程中掉落脚下的可以自动拾取,但不会执行万叶拾取和自动拾取配置逻辑。非精英关闭拾取:战斗过程中掉落到脚下的也不会自动拾取。"
|
||||
TextWrapping="Wrap" />
|
||||
<ComboBox Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
MinWidth="100"
|
||||
SelectedValuePath="Key"
|
||||
DisplayMemberPath="Value"
|
||||
ItemsSource="{Binding OnlyPickEliteDropsSource}"
|
||||
SelectedValue="{Binding PathingConfig.AutoFightConfig.OnlyPickEliteDropsMode}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:vio="http://schemas.lepo.co/wpfui/2022/xaml/violeta"
|
||||
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
|
||||
xmlns:webview="clr-namespace:BetterGenshinImpact.View.Controls.Webview"
|
||||
x:Name="app"
|
||||
Title="发现新版本"
|
||||
Width="680"
|
||||
Height="800"
|
||||
MinHeight="10"
|
||||
SizeToContent="Height"
|
||||
Background="#202020"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
FontFamily="{DynamicResource TextThemeFontFamily}"
|
||||
@@ -18,11 +20,13 @@
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<ui:Grid Margin="0,48,0,0" RowDefinitions="Auto,*,Auto">
|
||||
<ui:Grid Name="MyGrid" Margin="0,48,0,0" RowDefinitions="Auto,*,Auto,Auto">
|
||||
<webview:WebpagePanel x:Name="WebpagePanel"
|
||||
Grid.Row="1"
|
||||
Height="400"
|
||||
Margin="12,0,12,0" />
|
||||
<ui:Grid Grid.Row="0"
|
||||
<ui:Grid Name="UpdateStatusMessageGrid"
|
||||
Grid.Row="0"
|
||||
Margin="16,0,16,0"
|
||||
ColumnDefinitions="Auto,*"
|
||||
Visibility="{Binding ShowUpdateStatus, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
@@ -31,7 +35,106 @@
|
||||
<TextBlock Text="{Binding UpdateStatusMessage}" />
|
||||
</ui:StackPanel>
|
||||
</ui:Grid>
|
||||
<ui:Grid Grid.Row="2"
|
||||
<!-- 新增:多渠道更新方式卡片 -->
|
||||
<StackPanel Name="ServerPanel" Grid.Row="2" Margin="12,0,12,0">
|
||||
<ui:CardControl Name="DefaultCard" Margin="0,0,0,8">
|
||||
<ui:CardControl.Icon>
|
||||
<ui:FontIcon Glyph="" Style="{StaticResource FaFontIconStyle}" />
|
||||
</ui:CardControl.Icon>
|
||||
<ui:CardControl.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ui:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
FontTypography="Body"
|
||||
Text="默认更新服务器"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="普通用户可点此直接更新"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</ui:CardControl.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ui:Button
|
||||
Appearance="Success"
|
||||
Icon="{ui:SymbolIcon ArrowDownload24}"
|
||||
Content="立即更新"
|
||||
Command="{Binding UpdateFromSteambirdCommand}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl>
|
||||
<ui:CardControl Margin="0,0,0,8">
|
||||
<ui:CardControl.Icon>
|
||||
<ui:FontIcon Glyph="" Style="{StaticResource FaFontIconStyle}" />
|
||||
</ui:CardControl.Icon>
|
||||
<ui:CardControl.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<emoji:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="Mirror酱⚡"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="Mirror酱用户可以输入CDK高速更新"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</ui:CardControl.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ui:Button
|
||||
Name="EditCdkButton"
|
||||
Margin="0,0,8,0"
|
||||
Icon="{ui:SymbolIcon TicketDiagonal24}"
|
||||
Content="修改CDK"
|
||||
Command="{Binding EditCdkCommand}" />
|
||||
<ui:Button
|
||||
Appearance="Success"
|
||||
Icon="{ui:SymbolIcon ArrowDownload24}"
|
||||
Content="立即更新"
|
||||
Command="{Binding UpdateFromMirrorChyanCommand}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl>
|
||||
|
||||
<!--<ui:CardControl Margin="0,0,0,8">
|
||||
<ui:CardControl.Icon>
|
||||
<ui:FontIcon Glyph="" Style="{StaticResource FaFontIconStyle}" />
|
||||
</ui:CardControl.Icon>
|
||||
<ui:CardControl.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<emoji:TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="Hutao Cloud 胡桃云⚡"
|
||||
TextWrapping="Wrap" />
|
||||
<ui:TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="胡桃云CDN用户,可使用 Hutao Cloud CDN 高速更新"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</ui:CardControl.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ui:Button
|
||||
Appearance="Primary"
|
||||
Icon="{ui:SymbolIcon ArrowBounce24}"
|
||||
Content="唤起胡桃"
|
||||
Command="{Binding OneKeyExecuteCommand}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl>-->
|
||||
</StackPanel>
|
||||
<!-- 原有按钮区域 -->
|
||||
<ui:Grid Grid.Row="3"
|
||||
Margin="8"
|
||||
ColumnDefinitions="*,Auto,Auto,Auto,Auto">
|
||||
<ui:Button Grid.Column="0"
|
||||
@@ -45,14 +148,14 @@
|
||||
Margin="8,0,8,0"
|
||||
Command="{Binding OtherUpdateCommand}"
|
||||
Content="其他更新方式" />
|
||||
<ui:Button Grid.Column="1"
|
||||
<!--<ui:Button Grid.Column="1"
|
||||
MinWidth="90"
|
||||
Margin="8,0,8,0"
|
||||
Appearance="Success"
|
||||
Command="{Binding UpdateCommand}"
|
||||
Content="立即更新" />
|
||||
Content="立即更新" />-->
|
||||
<ui:Button Name="IgnoreButton"
|
||||
Grid.Column="2"
|
||||
Grid.Column="2"
|
||||
MinWidth="90"
|
||||
Margin="8,0,8,0"
|
||||
Command="{Binding IgnoreCommand}"
|
||||
@@ -72,4 +175,4 @@
|
||||
</ui:TitleBar.Icon>
|
||||
</ui:TitleBar>
|
||||
</Grid>
|
||||
</ui:FluentWindow>
|
||||
</ui:FluentWindow>
|
||||
@@ -2,10 +2,17 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Windows.System;
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.Helpers.Win32;
|
||||
using BetterGenshinImpact.Model;
|
||||
using Meziantou.Framework.Win32;
|
||||
using Wpf.Ui.Controls;
|
||||
using Wpf.Ui.Violeta.Controls;
|
||||
|
||||
namespace BetterGenshinImpact.View.Windows;
|
||||
|
||||
@@ -14,22 +21,59 @@ public partial class CheckUpdateWindow : FluentWindow
|
||||
{
|
||||
public Func<object, CheckUpdateWindowButton, Task>? UserInteraction = null!;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool showUpdateStatus = false;
|
||||
[ObservableProperty] private bool showUpdateStatus = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private string updateStatusMessage = string.Empty;
|
||||
[ObservableProperty] private string updateStatusMessage = string.Empty;
|
||||
|
||||
private UpdateOption _option;
|
||||
|
||||
public CheckUpdateWindow(UpdateOption option)
|
||||
{
|
||||
_option = option ?? throw new ArgumentNullException(nameof(option));
|
||||
DataContext = this;
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
// 存在CDK则显示修改按钮
|
||||
if (string.IsNullOrEmpty(MirrorChyanHelper.GetCdk()))
|
||||
{
|
||||
EditCdkButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
if (option.Trigger == UpdateTrigger.Manual)
|
||||
{
|
||||
IgnoreButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
if (option.Channel == UpdateChannel.Alpha)
|
||||
{
|
||||
WebpagePanel.Height = 0;
|
||||
WebpagePanel.Visibility = Visibility.Collapsed;
|
||||
UpdateStatusMessageGrid.Height = 0;
|
||||
ShowUpdateStatus = false;
|
||||
|
||||
// 删除前两行
|
||||
MyGrid.RowDefinitions.RemoveAt(0);
|
||||
MyGrid.RowDefinitions.RemoveAt(0);
|
||||
|
||||
// 注意:删除行定义后,需要调整剩余元素的 Grid.Row 属性
|
||||
foreach (FrameworkElement child in MyGrid.Children)
|
||||
{
|
||||
int currentRow = System.Windows.Controls.Grid.GetRow(child);
|
||||
if (currentRow > 1) // 如果元素在第三行或之后
|
||||
{
|
||||
Grid.SetRow(child, currentRow - 2); // 行号减2
|
||||
}
|
||||
}
|
||||
|
||||
if (ServerPanel.Children.Count > 0)
|
||||
{
|
||||
ServerPanel.Children.RemoveAt(0);
|
||||
}
|
||||
SizeToContent = SizeToContent.Height; // 设置高度为自动
|
||||
UpdateLayout();
|
||||
}
|
||||
|
||||
|
||||
Closing += OnClosing;
|
||||
}
|
||||
|
||||
@@ -73,6 +117,55 @@ public partial class CheckUpdateWindow : FluentWindow
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
private async Task UpdateFromSteambirdAsync()
|
||||
{
|
||||
await RunUpdaterAsync("-I");
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task UpdateFromMirrorChyanAsync()
|
||||
{
|
||||
var cdk = MirrorChyanHelper.GetAndPromptCdk();
|
||||
if (string.IsNullOrEmpty(cdk))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_option.Channel == UpdateChannel.Stable)
|
||||
{
|
||||
await RunUpdaterAsync("--source mirrorc");
|
||||
}
|
||||
else
|
||||
{
|
||||
await RunUpdaterAsync("--source mirrorc-alpha");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// --source mirrorc
|
||||
/// --source mirrorc-alpha
|
||||
/// --source github
|
||||
/// --dfs-extras {"hutao-token": "...."}
|
||||
/// </summary>
|
||||
private async Task RunUpdaterAsync(string parameters)
|
||||
{
|
||||
// 唤起更新程序
|
||||
string updaterExePath = Global.Absolute("BetterGI.update.exe");
|
||||
if (!File.Exists(updaterExePath))
|
||||
{
|
||||
await MessageBox.ErrorAsync("更新程序不存在,请选择其他更新方式!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动
|
||||
Process.Start(updaterExePath, parameters);
|
||||
|
||||
// 退出程序
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task IgnoreAsync()
|
||||
{
|
||||
@@ -90,6 +183,12 @@ public partial class CheckUpdateWindow : FluentWindow
|
||||
await UserInteraction.Invoke(this, CheckUpdateWindowButton.Cancel);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void EditCdk()
|
||||
{
|
||||
MirrorChyanHelper.EditCdk();
|
||||
}
|
||||
|
||||
public enum CheckUpdateWindowButton
|
||||
{
|
||||
|
||||
@@ -28,21 +28,36 @@
|
||||
<ui:TextBlock Name="TxtQuestion" Margin="5" />
|
||||
<!-- <ui:TextBox Name="TxtResponse" Margin="5" /> -->
|
||||
<ContentControl Name="DynamicContent" Margin="5" />
|
||||
<StackPanel Margin="5"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<ui:Button Name="BtnOk"
|
||||
<Grid Margin="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 左下角按钮 -->
|
||||
<ui:Button Name="BtnLeftBottom"
|
||||
Margin="5"
|
||||
Appearance="Primary"
|
||||
Click="BtnOkClick"
|
||||
Content="确定"
|
||||
IsDefault="True" />
|
||||
<ui:Button Name="BtnCancel"
|
||||
Margin="5"
|
||||
Click="BtnCancelClick"
|
||||
Content="取消"
|
||||
IsCancel="True" />
|
||||
</StackPanel>
|
||||
HorizontalAlignment="Left"
|
||||
Appearance="Success"
|
||||
Content="左下角按钮"
|
||||
Grid.Column="0" />
|
||||
|
||||
<!-- 原有的右侧按钮 -->
|
||||
<StackPanel Grid.Column="1"
|
||||
Orientation="Horizontal">
|
||||
<ui:Button Name="BtnOk"
|
||||
Margin="5"
|
||||
Appearance="Primary"
|
||||
Click="BtnOkClick"
|
||||
Content="确定"
|
||||
IsDefault="True" />
|
||||
<ui:Button Name="BtnCancel"
|
||||
Margin="5"
|
||||
Click="BtnCancelClick"
|
||||
Content="取消"
|
||||
IsCancel="True" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<ui:TitleBar Name="MyTitleBar" Grid.Row="0">
|
||||
|
||||
@@ -3,13 +3,37 @@ using System.Windows.Controls;
|
||||
|
||||
namespace BetterGenshinImpact.View.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// 对话框配置类,用于控制对话框中的元素显示
|
||||
/// </summary>
|
||||
public class PromptDialogConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否显示左下角按钮
|
||||
/// </summary>
|
||||
public bool ShowLeftButton { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 左下角按钮的文本
|
||||
/// </summary>
|
||||
public string LeftButtonText { get; set; } = "左下角按钮";
|
||||
|
||||
/// <summary>
|
||||
/// 左下角按钮的点击事件
|
||||
/// </summary>
|
||||
public RoutedEventHandler? LeftButtonClick { get; set; }
|
||||
}
|
||||
|
||||
public partial class PromptDialog
|
||||
{
|
||||
public PromptDialog(string question, string title, UIElement uiElement, string defaultValue)
|
||||
private readonly PromptDialogConfig _config;
|
||||
|
||||
public PromptDialog(string question, string title, UIElement uiElement, string defaultValue, PromptDialogConfig? config = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
MyTitleBar.Title = title;
|
||||
TxtQuestion.Text = question;
|
||||
_config = config ?? new PromptDialogConfig();
|
||||
|
||||
DynamicContent.Content = uiElement;
|
||||
if (DynamicContent.Content is TextBox textBox)
|
||||
@@ -21,31 +45,50 @@ public partial class PromptDialog
|
||||
comboBox.Text = defaultValue;
|
||||
}
|
||||
|
||||
// 配置左下角按钮
|
||||
ConfigureLeftButton();
|
||||
|
||||
this.Loaded += PromptDialogLoaded;
|
||||
}
|
||||
|
||||
private void ConfigureLeftButton()
|
||||
{
|
||||
if (_config.ShowLeftButton)
|
||||
{
|
||||
BtnLeftBottom.Content = _config.LeftButtonText;
|
||||
if (_config.LeftButtonClick != null)
|
||||
{
|
||||
BtnLeftBottom.Click += _config.LeftButtonClick;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnLeftBottom.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void PromptDialogLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DynamicContent.Focus();
|
||||
}
|
||||
|
||||
public static string Prompt(string question, string title, string defaultValue = "")
|
||||
public static string Prompt(string question, string title, string defaultValue = "", PromptDialogConfig? config = null)
|
||||
{
|
||||
var inst = new PromptDialog(question, title, new TextBox(), defaultValue);
|
||||
var inst = new PromptDialog(question, title, new TextBox(), defaultValue, config);
|
||||
inst.ShowDialog();
|
||||
return inst.DialogResult == true ? inst.ResponseText : defaultValue;
|
||||
}
|
||||
|
||||
public static string Prompt(string question, string title, UIElement uiElement, string defaultValue = "")
|
||||
public static string Prompt(string question, string title, UIElement uiElement, string defaultValue = "", PromptDialogConfig? config = null)
|
||||
{
|
||||
var inst = new PromptDialog(question, title, uiElement, defaultValue);
|
||||
var inst = new PromptDialog(question, title, uiElement, defaultValue, config);
|
||||
inst.ShowDialog();
|
||||
return inst.DialogResult == true ? inst.ResponseText : defaultValue;
|
||||
}
|
||||
|
||||
public static string Prompt(string question, string title, UIElement uiElement, Size size)
|
||||
public static string Prompt(string question, string title, UIElement uiElement, Size size, PromptDialogConfig? config = null)
|
||||
{
|
||||
var inst = new PromptDialog(question, title, uiElement, "")
|
||||
var inst = new PromptDialog(question, title, uiElement, "", config)
|
||||
{
|
||||
Width = size.Width,
|
||||
Height = size.Height
|
||||
|
||||
@@ -288,21 +288,10 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel
|
||||
|
||||
private void OnceRun()
|
||||
{
|
||||
string deviceId = "default";
|
||||
try
|
||||
string deviceId = DeviceIdHelper.DeviceId;
|
||||
if (string.IsNullOrWhiteSpace(deviceId))
|
||||
{
|
||||
deviceId = new DeviceIdBuilder()
|
||||
.OnWindows(windows => windows
|
||||
.AddMacAddressFromWmi(excludeWireless: true, excludeNonPhysical: true)
|
||||
.AddProcessorId()
|
||||
.AddMotherboardSerialNumber()
|
||||
)
|
||||
.ToString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogDebug("获取设备ID异常:" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" +
|
||||
Environment.NewLine + e.Message);
|
||||
deviceId = "default"; // 如果获取设备ID失败,使用默认值
|
||||
}
|
||||
|
||||
// 每个设备只运行一次
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Windows.System;
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.Core.Recognition.OCR;
|
||||
using BetterGenshinImpact.Core.Script;
|
||||
@@ -16,6 +17,8 @@ using BetterGenshinImpact.GameTask;
|
||||
using BetterGenshinImpact.GameTask.AutoTrackPath;
|
||||
using BetterGenshinImpact.GameTask.Common.Element.Assets;
|
||||
using BetterGenshinImpact.Helpers;
|
||||
using BetterGenshinImpact.Helpers.Win32;
|
||||
using BetterGenshinImpact.Model;
|
||||
using BetterGenshinImpact.Service.Interface;
|
||||
using BetterGenshinImpact.Service.Notification;
|
||||
using BetterGenshinImpact.View.Converters;
|
||||
@@ -25,9 +28,11 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Meziantou.Framework.Win32;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Win32;
|
||||
using Wpf.Ui;
|
||||
using Wpf.Ui.Violeta.Controls;
|
||||
|
||||
namespace BetterGenshinImpact.ViewModel.Pages;
|
||||
|
||||
@@ -42,8 +47,8 @@ public partial class CommonSettingsPageViewModel : ViewModel
|
||||
|
||||
|
||||
private string _selectedCountry = string.Empty;
|
||||
[ObservableProperty]
|
||||
private List<string> _adventurersGuildCountry = ["无","枫丹", "稻妻", "璃月", "蒙德"];
|
||||
[ObservableProperty] private List<string> _adventurersGuildCountry = ["无", "枫丹", "稻妻", "璃月", "蒙德"];
|
||||
|
||||
public CommonSettingsPageViewModel(IConfigService configService, INavigationService navigationService,
|
||||
NotificationService notificationService)
|
||||
{
|
||||
@@ -57,17 +62,18 @@ public partial class CommonSettingsPageViewModel : ViewModel
|
||||
public ObservableCollection<string> CountryList { get; } = new();
|
||||
public ObservableCollection<string> Areas { get; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private FrozenDictionary<string, string> _languageDict = new string[] { "zh-Hans", "zh-Hant", "en", "fr" }
|
||||
.ToFrozenDictionary(
|
||||
c => c,
|
||||
c =>
|
||||
{
|
||||
CultureInfo.CurrentUICulture = new CultureInfo(c);
|
||||
var stringLocalizer = App.GetService<IStringLocalizer<CultureInfoNameToKVPConverter>>() ?? throw new NullReferenceException();
|
||||
return stringLocalizer["简体中文"].ToString();
|
||||
}
|
||||
);
|
||||
[ObservableProperty] private FrozenDictionary<string, string> _languageDict =
|
||||
new string[] { "zh-Hans", "zh-Hant", "en", "fr" }
|
||||
.ToFrozenDictionary(
|
||||
c => c,
|
||||
c =>
|
||||
{
|
||||
CultureInfo.CurrentUICulture = new CultureInfo(c);
|
||||
var stringLocalizer = App.GetService<IStringLocalizer<CultureInfoNameToKVPConverter>>() ??
|
||||
throw new NullReferenceException();
|
||||
return stringLocalizer["简体中文"].ToString();
|
||||
}
|
||||
);
|
||||
|
||||
public string SelectedCountry
|
||||
{
|
||||
@@ -238,7 +244,7 @@ public partial class CommonSettingsPageViewModel : ViewModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenAboutWindow()
|
||||
{
|
||||
@@ -246,7 +252,7 @@ public partial class CommonSettingsPageViewModel : ViewModel
|
||||
aboutWindow.Owner = Application.Current.MainWindow;
|
||||
aboutWindow.ShowDialog();
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenKeyBindingsWindow()
|
||||
{
|
||||
@@ -260,4 +266,31 @@ public partial class CommonSettingsPageViewModel : ViewModel
|
||||
{
|
||||
await OcrFactory.ChangeCulture(type.Key);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CheckUpdateAsync()
|
||||
{
|
||||
await App.GetService<IUpdateService>()!.CheckUpdateAsync(new UpdateOption
|
||||
{
|
||||
Trigger = UpdateTrigger.Manual,
|
||||
Channel = UpdateChannel.Stable
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CheckUpdateAlphaAsync()
|
||||
{
|
||||
await App.GetService<IUpdateService>()!.CheckUpdateAsync(new UpdateOption
|
||||
{
|
||||
Trigger = UpdateTrigger.Manual,
|
||||
Channel = UpdateChannel.Alpha,
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task GotoGithubActionAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(
|
||||
new Uri("https://github.com/babalae/better-genshin-impact/actions/workflows/publish.yml"));
|
||||
}
|
||||
}
|
||||
@@ -57,20 +57,28 @@ public partial class MapPathingViewModel : ViewModel
|
||||
// 循环写入 root.Children
|
||||
foreach (var item in root.Children)
|
||||
{
|
||||
// 补充图标
|
||||
if (!string.IsNullOrEmpty(item.FilePath) && File.Exists(Path.Combine(item.FilePath, "icon.ico")))
|
||||
{
|
||||
item.IconFilePath = Path.Combine(item.FilePath, "icon.ico");
|
||||
}
|
||||
else
|
||||
{
|
||||
item.IconFilePath = item.FilePath;
|
||||
}
|
||||
|
||||
SetIconForNodeAndChildren(item);
|
||||
TreeList.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetIconForNodeAndChildren(FileTreeNode<PathingTask> node)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(node.FilePath) && File.Exists(Path.Combine(node.FilePath, "icon.ico")))
|
||||
{
|
||||
node.IconFilePath = Path.Combine(node.FilePath, "icon.ico");
|
||||
}
|
||||
else
|
||||
{
|
||||
node.IconFilePath = node.FilePath;
|
||||
}
|
||||
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
SetIconForNodeAndChildren(child);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo()
|
||||
{
|
||||
InitScriptListViewData();
|
||||
|
||||
@@ -18,6 +18,7 @@ using BetterGenshinImpact.Core.Script.Project;
|
||||
using BetterGenshinImpact.GameTask;
|
||||
using BetterGenshinImpact.GameTask.AutoPathing.Model;
|
||||
using BetterGenshinImpact.GameTask.LogParse;
|
||||
using BetterGenshinImpact.GameTask.TaskProgress;
|
||||
using BetterGenshinImpact.Helpers.Ui;
|
||||
using BetterGenshinImpact.Model;
|
||||
using BetterGenshinImpact.Service.Interface;
|
||||
@@ -36,6 +37,7 @@ using Wpf.Ui.Violeta.Controls;
|
||||
using StackPanel = Wpf.Ui.Controls.StackPanel;
|
||||
using TextBox = Wpf.Ui.Controls.TextBox;
|
||||
using Button = Wpf.Ui.Controls.Button;
|
||||
using MessageBoxButton = System.Windows.MessageBoxButton;
|
||||
using MessageBoxResult = Wpf.Ui.Controls.MessageBoxResult;
|
||||
using TextBlock = Wpf.Ui.Controls.TextBlock;
|
||||
|
||||
@@ -105,7 +107,7 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
private void ClearTasks()
|
||||
{
|
||||
// 确认?
|
||||
var result = MessageBox.Show("是否清空所有任务?", "清空任务", System.Windows.MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
var result = MessageBox.Show("是否清空所有任务?", "清空任务", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (result != System.Windows.MessageBoxResult.Yes)
|
||||
{
|
||||
return;
|
||||
@@ -510,6 +512,21 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
if (SelectedScriptGroup != null) WriteScriptGroup(SelectedScriptGroup);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void AddScriptGroupNextFlag(ScriptGroup? item)
|
||||
{
|
||||
foreach (var scriptGroup in ScriptGroups)
|
||||
{
|
||||
scriptGroup.NextFlag = false;
|
||||
}
|
||||
|
||||
if (item!=null)
|
||||
{
|
||||
item.NextFlag = true;
|
||||
TaskContext.Instance().Config.NextScriptGroupName = item.Name;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void OnCopyScriptGroup(ScriptGroup? item)
|
||||
{
|
||||
@@ -577,6 +594,10 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
{
|
||||
File.Move(Path.Combine(ScriptGroupPath, $"{item.Name}.json"), Path.Combine(ScriptGroupPath, $"{str}.json"));
|
||||
item.Name = str;
|
||||
if (item.NextFlag)
|
||||
{
|
||||
TaskContext.Instance().Config.NextScriptGroupName = item.Name;
|
||||
}
|
||||
WriteScriptGroup(item);
|
||||
}
|
||||
}
|
||||
@@ -1124,6 +1145,22 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetTaskContextNextFlag(ScriptGroup group)
|
||||
{
|
||||
var nst = TaskContext.Instance().Config.NextScheduledTask.Find(item => item.Item1 == group.Name);
|
||||
foreach (var item in group.Projects)
|
||||
{
|
||||
item.NextFlag = false;
|
||||
if (nst != default)
|
||||
{
|
||||
if (nst.Item2 == item.Index && nst.Item3 == item.FolderName && nst.Item4 == item.Name)
|
||||
{
|
||||
item.NextFlag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadScriptGroup()
|
||||
{
|
||||
try
|
||||
@@ -1142,21 +1179,11 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
{
|
||||
var json = File.ReadAllText(file);
|
||||
var group = ScriptGroup.FromJson(json);
|
||||
|
||||
|
||||
var nst = TaskContext.Instance().Config.NextScheduledTask.Find(item => item.Item1 == group.Name);
|
||||
foreach (var item in group.Projects)
|
||||
SetTaskContextNextFlag(group);
|
||||
if (group.Name == TaskContext.Instance().Config.NextScriptGroupName)
|
||||
{
|
||||
item.NextFlag = false;
|
||||
if (nst != default)
|
||||
{
|
||||
if (nst.Item2 == item.Index && nst.Item3 == item.FolderName && nst.Item4 == item.Name)
|
||||
{
|
||||
item.NextFlag = true;
|
||||
}
|
||||
}
|
||||
group.NextFlag = true;
|
||||
}
|
||||
|
||||
groups.Add(group);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -1239,7 +1266,15 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
}
|
||||
|
||||
RunnerContext.Instance.Reset();
|
||||
await _scriptService.RunMulti(GetNextProjects(SelectedScriptGroup), SelectedScriptGroup.Name);
|
||||
|
||||
TaskProgress taskProgress = new()
|
||||
{
|
||||
ScriptGroupNames = [SelectedScriptGroup.Name]
|
||||
};
|
||||
RunnerContext.Instance.taskProgress = taskProgress;
|
||||
taskProgress.CurrentScriptGroupName = SelectedScriptGroup.Name;
|
||||
TaskProgressManager.SaveTaskProgress(taskProgress);
|
||||
await _scriptService.RunMulti(GetNextProjects(SelectedScriptGroup), SelectedScriptGroup.Name,taskProgress);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -1282,49 +1317,219 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
WriteScriptGroup(SelectedScriptGroup);
|
||||
}
|
||||
|
||||
public static List<ScriptGroupProject> GetNextProjects(ScriptGroup group)
|
||||
public static List<ScriptGroup> GetNextScriptGroups(List<ScriptGroup> groups)
|
||||
{
|
||||
List<ScriptGroupProject> ls = new List<ScriptGroupProject>();
|
||||
bool start = false;
|
||||
foreach (var item in group.Projects)
|
||||
if (groups.Where(g => g.NextFlag).Count() > 0)
|
||||
{
|
||||
if (item.NextFlag ?? false)
|
||||
List<ScriptGroup> ng = new();
|
||||
bool start = false;
|
||||
foreach (var group in groups)
|
||||
{
|
||||
start = true;
|
||||
}
|
||||
|
||||
if (start)
|
||||
{
|
||||
ls.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!start)
|
||||
{
|
||||
ls.AddRange(group.Projects);
|
||||
}
|
||||
|
||||
//拿出来后清空,和置状态
|
||||
if (start)
|
||||
{
|
||||
List<ValueTuple<string, int, string, string>> nextScheduledTask = TaskContext.Instance().Config.NextScheduledTask;
|
||||
foreach (var item in nextScheduledTask)
|
||||
{
|
||||
if (item.Item1 == group.Name)
|
||||
if (group.NextFlag)
|
||||
{
|
||||
nextScheduledTask.Remove(item);
|
||||
break;
|
||||
start = true;
|
||||
group.NextFlag = false;
|
||||
TaskContext.Instance().Config.NextScriptGroupName = String.Empty;
|
||||
}
|
||||
|
||||
if (start)
|
||||
{
|
||||
ng.Add(group);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in group.Projects)
|
||||
{
|
||||
item.NextFlag = false;
|
||||
}
|
||||
return ng;
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
return ls;
|
||||
public static List<ScriptGroupProject> GetNextProjects(ScriptGroup group)
|
||||
{
|
||||
SetTaskContextNextFlag(group);
|
||||
List<ScriptGroupProject> ls = new List<ScriptGroupProject>();
|
||||
if (group.Projects.Where(g=>g.NextFlag ?? false).Count() > 0)
|
||||
{
|
||||
bool start = false;
|
||||
foreach (var item in group.Projects)
|
||||
{
|
||||
if (item.NextFlag ?? false)
|
||||
{
|
||||
start = true;
|
||||
}
|
||||
|
||||
if (!start)
|
||||
{
|
||||
item.SkipFlag = true;
|
||||
}
|
||||
ls.Add(item);
|
||||
}
|
||||
|
||||
if (!start)
|
||||
{
|
||||
ls.AddRange(group.Projects);
|
||||
}
|
||||
|
||||
//拿出来后清空,和置状态
|
||||
if (start)
|
||||
{
|
||||
List<ValueTuple<string, int, string, string>> nextScheduledTask = TaskContext.Instance().Config.NextScheduledTask;
|
||||
foreach (var item in nextScheduledTask)
|
||||
{
|
||||
if (item.Item1 == group.Name)
|
||||
{
|
||||
nextScheduledTask.Remove(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in group.Projects)
|
||||
{
|
||||
item.NextFlag = false;
|
||||
}
|
||||
}
|
||||
|
||||
return ls;
|
||||
}
|
||||
|
||||
return group.Projects.Select(g=>g).ToList();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OnContinueMultiScriptGroupAsync()
|
||||
{
|
||||
// 创建一个 StackPanel 来包含全选按钮和所有配置组的 CheckBox
|
||||
|
||||
// 创建一个 StackPanel 来包含全选按钮和所有配置组的 CheckBox
|
||||
var stackPanel = new StackPanel();
|
||||
|
||||
|
||||
// 添加分割线
|
||||
var separator = new Separator
|
||||
{
|
||||
Margin = new Thickness(0, 4, 0, 4)
|
||||
};
|
||||
stackPanel.Children.Add(separator);
|
||||
|
||||
List<TaskProgress> taskProgresses = TaskProgressManager.LoadAllTaskProgress();
|
||||
var checkBox = new ComboBox();;
|
||||
stackPanel.Children.Add(checkBox);
|
||||
ObservableCollection<KeyValuePair<string, string>> kvs=new ObservableCollection<KeyValuePair<string, string>>();
|
||||
foreach (var taskProgress in taskProgresses)
|
||||
{
|
||||
var name = taskProgress.Name+"_"+taskProgress.CurrentScriptGroupName+"_";
|
||||
if (taskProgress.Loop)
|
||||
{
|
||||
name += "循环("+taskProgress.LoopCount+")_";
|
||||
}
|
||||
if (taskProgress.CurrentScriptGroupProjectInfo!=null)
|
||||
{
|
||||
name = name +taskProgress.CurrentScriptGroupProjectInfo.Index+ "_" + taskProgress.CurrentScriptGroupProjectInfo.Name;
|
||||
}
|
||||
kvs.Add(new KeyValuePair<string, string>(taskProgress.Name,name));
|
||||
}
|
||||
|
||||
checkBox.SelectedValuePath = "Key";
|
||||
checkBox.DisplayMemberPath = "Value";
|
||||
checkBox.ItemsSource = kvs;
|
||||
checkBox.SelectedIndex = 0;
|
||||
//SelectedValuePath="Key"
|
||||
// DisplayMemberPath="Value"
|
||||
var uiMessageBox = new Wpf.Ui.Controls.MessageBox
|
||||
{
|
||||
Title = "选择需要继续执行的进度记录",
|
||||
Content = new ScrollViewer
|
||||
{
|
||||
Content = stackPanel,
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
|
||||
Height = 300 // 设置固定高度
|
||||
,Width = 600
|
||||
},
|
||||
CloseButtonText = "关闭",
|
||||
PrimaryButtonText = "确认执行",
|
||||
Owner = Application.Current.MainWindow,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
};
|
||||
|
||||
var result = await uiMessageBox.ShowDialogAsync();
|
||||
if (result == MessageBoxResult.Primary)
|
||||
{
|
||||
|
||||
/*var selectedGroups = checkBoxes
|
||||
.Where(kv => kv.Value.IsChecked == true)
|
||||
.Select(kv => kv.Key)
|
||||
.ToList();*/
|
||||
Object val = checkBox.SelectedValue;
|
||||
if (val == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await OnContinueTaskProgressAsync(Convert.ToString(val), taskProgresses);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnContinueTaskProgressAsync(string name,List<TaskProgress>? taskProgresses = null)
|
||||
{
|
||||
if (taskProgresses == null)
|
||||
{
|
||||
taskProgresses = TaskProgressManager.LoadAllTaskProgress();
|
||||
}
|
||||
TaskProgress? taskProgress = null;
|
||||
if (name == "latest")
|
||||
{
|
||||
if (taskProgresses.Count > 0)
|
||||
{
|
||||
taskProgress = taskProgresses[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
taskProgress=taskProgresses.FirstOrDefault(t=>t.Name == name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (taskProgress!=null)
|
||||
{
|
||||
//await StartGroups(selectedGroups);
|
||||
//taskProgress.Next
|
||||
var sg = ScriptGroups.ToList().Where(sg => taskProgress.ScriptGroupNames.Contains(sg.Name)).ToList();
|
||||
TaskProgressManager.GenerNextProjectInfo(taskProgress,sg);
|
||||
if (taskProgress.Next==null)
|
||||
{
|
||||
_logger.LogWarning("无法定位到下一个要执行的项目:next为空("+taskProgress.Name+")");
|
||||
}
|
||||
else
|
||||
{
|
||||
await StartGroups(sg,taskProgress);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("无法定位到下一个要执行的项目:taskProgress为空");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnStartMultiScriptTaskProgressAsync(params string[] names)
|
||||
{
|
||||
if (ScriptGroups.Count == 0)
|
||||
{
|
||||
ReadScriptGroup();
|
||||
}
|
||||
|
||||
string taskProgressName;
|
||||
if (names == null || names.Length == 0)
|
||||
{
|
||||
taskProgressName = "latest";
|
||||
}
|
||||
else
|
||||
{
|
||||
taskProgressName = names[0];
|
||||
}
|
||||
|
||||
await OnContinueTaskProgressAsync(taskProgressName);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -1334,6 +1539,13 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
var stackPanel = new StackPanel();
|
||||
var checkBoxes = new Dictionary<ScriptGroup, CheckBox>();
|
||||
|
||||
|
||||
var loopCheckBox = new CheckBox
|
||||
{
|
||||
Content = "循环",
|
||||
};
|
||||
|
||||
|
||||
// 创建全选按钮
|
||||
var selectAllCheckBox = new CheckBox
|
||||
{
|
||||
@@ -1353,6 +1565,7 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
checkBox.IsChecked = false;
|
||||
}
|
||||
};
|
||||
stackPanel.Children.Add(loopCheckBox);
|
||||
stackPanel.Children.Add(selectAllCheckBox);
|
||||
// 添加分割线
|
||||
var separator = new Separator
|
||||
@@ -1396,7 +1609,7 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
.Select(kv => kv.Key)
|
||||
.ToList();
|
||||
|
||||
await StartGroups(selectedGroups);
|
||||
await StartGroups(selectedGroups,null,loopCheckBox.IsChecked ?? false);;
|
||||
}
|
||||
}
|
||||
public async Task OnStartMultiScriptGroupWithNamesAsync(params string[] names)
|
||||
@@ -1429,17 +1642,56 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartGroups(List<ScriptGroup> scriptGroups)
|
||||
public async Task StartGroups(List<ScriptGroup> scriptGroups,TaskProgress? taskProgress = null,bool loop = false)
|
||||
{
|
||||
_logger.LogInformation("开始连续执行选中配置组:{Names}", string.Join(",", scriptGroups.Select(x => x.Name)));
|
||||
try
|
||||
{
|
||||
RunnerContext.Instance.IsContinuousRunGroup = true;
|
||||
foreach (var scriptGroup in scriptGroups)
|
||||
if (taskProgress == null)
|
||||
{
|
||||
await _scriptService.RunMulti(GetNextProjects(scriptGroup), scriptGroup.Name);
|
||||
taskProgress = new()
|
||||
{
|
||||
ScriptGroupNames = scriptGroups.Select(x => x.Name).ToList()
|
||||
,Loop = loop
|
||||
};
|
||||
}
|
||||
|
||||
RunnerContext.Instance.taskProgress = taskProgress;
|
||||
var sg = GetNextScriptGroups(scriptGroups);
|
||||
foreach (var scriptGroup in sg)
|
||||
{
|
||||
if (taskProgress.Next!=null)
|
||||
{
|
||||
if (scriptGroup.Name!=taskProgress.Next.GroupName)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
taskProgress.CurrentScriptGroupName = scriptGroup.Name;
|
||||
TaskProgressManager.SaveTaskProgress(taskProgress);
|
||||
await _scriptService.RunMulti(GetNextProjects(scriptGroup), scriptGroup.Name,taskProgress);
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
|
||||
taskProgress.LoopCount++;
|
||||
if (taskProgress is { Loop: true })
|
||||
{
|
||||
taskProgress.LastScriptGroupName = null;
|
||||
taskProgress.LastSuccessScriptGroupProjectInfo = null;
|
||||
taskProgress.Next = null;
|
||||
await StartGroups(scriptGroups, taskProgress);
|
||||
}
|
||||
else
|
||||
{
|
||||
//只有最后一次成功才算
|
||||
if (taskProgress.ConsecutiveFailureCount == 0)
|
||||
{
|
||||
taskProgress.EndTime = DateTime.Now;
|
||||
TaskProgressManager.SaveTaskProgress(taskProgress);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -1449,5 +1701,7 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
{
|
||||
RunnerContext.Instance.Reset();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -433,6 +433,12 @@ public partial class TaskSettingsPageViewModel : ViewModel
|
||||
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/fish.html"));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OnGoToTorchPreviousVersionsAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri("https://pytorch.org/get-started/previous-versions"));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OnOpenLocalScriptRepo()
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.Input;
|
||||
namespace BetterGenshinImpact.ViewModel.Pages.View;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
public partial class HardwareAccelerationViewModel : ObservableObject, IViewModel
|
||||
{
|
||||
@@ -21,7 +22,7 @@ public partial class HardwareAccelerationViewModel : ObservableObject, IViewMode
|
||||
public HardwareAccelerationViewModel()
|
||||
{
|
||||
Config = TaskContext.Instance().Config.HardwareAccelerationConfig;
|
||||
Status = BgiOnnxFactory.Instance;
|
||||
Status = App.ServiceProvider.GetRequiredService<BgiOnnxFactory>();
|
||||
_providerTypesText = string.Join(",", Status.ProviderTypes);
|
||||
}
|
||||
[RelayCommand]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
using BetterGenshinImpact.View.Windows;
|
||||
@@ -24,7 +26,13 @@ public partial class ScriptGroupConfigViewModel : ObservableObject, IViewModel
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _enableShellConfig;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<KeyValuePair<string, string>> _onlyPickEliteDropsSource = new()
|
||||
{
|
||||
new KeyValuePair<string, string>("Closed", "关闭功能"),
|
||||
new KeyValuePair<string, string>("AllowAutoPickupForNonElite", "非精英允许自动拾取"),
|
||||
new KeyValuePair<string, string>("DisableAutoPickupForNonElite", "非精英关闭自动拾取")
|
||||
};
|
||||
public ScriptGroupConfigViewModel(AllConfig config, ScriptGroupConfig scriptGroupConfig)
|
||||
{
|
||||
ScriptGroupConfig = scriptGroupConfig;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<PackageReference Include="Vanara.Windows.Extensions" Version="4.1.3" />
|
||||
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.10.0.20241108" />
|
||||
<PackageReference Include="OpenCvSharp4.Windows" Version="4.10.0.20241108" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="9.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.5.3" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BetterGenshinImpact.Core.Recognition.OCR;
|
||||
using BetterGenshinImpact.Core.Recognition.ONNX;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@@ -8,18 +9,13 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests
|
||||
{
|
||||
[CollectionDefinition("Paddle Collection")]
|
||||
public class PaddleCollection : ICollectionFixture<PaddleFixture>
|
||||
{
|
||||
}
|
||||
|
||||
public class PaddleFixture
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, PaddleOcrService> paddleOcrServices = new ConcurrentDictionary<string, PaddleOcrService>();
|
||||
|
||||
public PaddleOcrService Get(string cultureInfoName = "zh-Hans")
|
||||
{
|
||||
return paddleOcrServices.GetOrAdd(cultureInfoName, name => { lock (paddleOcrServices) { return new PaddleOcrService(name); } });
|
||||
return paddleOcrServices.GetOrAdd(cultureInfoName, name => { lock (paddleOcrServices) { return new PaddleOcrService(name, new BgiOnnxFactory(new Core.Config.HardwareAccelerationConfig(), new FakeLogger<BgiOnnxFactory>())); } });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ using OpenCvSharp.Extensions;
|
||||
|
||||
namespace BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests
|
||||
{
|
||||
[Collection("Paddle Collection")]
|
||||
[Collection("Init Collection")]
|
||||
public partial class PaddleOcrServiceTests
|
||||
{
|
||||
private readonly PaddleFixture paddle;
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
namespace BetterGenshinImpact.UnitTest
|
||||
{
|
||||
internal class FakeLogger : ILogger
|
||||
{
|
||||
@@ -23,4 +23,21 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal class FakeLogger<T> : ILogger<T>
|
||||
{
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using BetterGenshinImpact.GameTask.AutoArtifactSalvage;
|
||||
using BetterGenshinImpact.GameTask.AutoFight.Model;
|
||||
using BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests;
|
||||
using OpenCvSharp;
|
||||
using System;
|
||||
@@ -8,12 +7,11 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Media3D;
|
||||
using static BetterGenshinImpact.GameTask.AutoArtifactSalvage.AutoArtifactSalvageTask;
|
||||
|
||||
namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoArtifactSalvageTests
|
||||
{
|
||||
[Collection("Paddle Collection")]
|
||||
[Collection("Init Collection")]
|
||||
public partial class AutoArtifactSalvageTaskTests
|
||||
{
|
||||
private readonly PaddleFixture paddle;
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
FakeTimeProvider fakeTimeProvider = new FakeTimeProvider();
|
||||
|
||||
//
|
||||
ThrowRod sut = new ThrowRod("-", blackboard, new FakeLogger(), false, new FakeInputSimulator(), fakeTimeProvider, drawContent: new FakeDrawContent());
|
||||
ThrowRod sut = new ThrowRod("-", blackboard, true, new FakeLogger(), false, new FakeInputSimulator(), fakeTimeProvider, drawContent: new FakeDrawContent());
|
||||
BehaviourStatus actual = sut.Tick(imageRegion);
|
||||
|
||||
//
|
||||
@@ -59,7 +59,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
FakeTimeProvider fakeTimeProvider = new FakeTimeProvider();
|
||||
|
||||
//
|
||||
ThrowRod sut = new ThrowRod("-", blackboard, new FakeLogger(), false, new FakeInputSimulator(), fakeTimeProvider, drawContent: new FakeDrawContent());
|
||||
ThrowRod sut = new ThrowRod("-", blackboard, this.useTorch, new FakeLogger(), false, new FakeInputSimulator(), fakeTimeProvider, drawContent: new FakeDrawContent());
|
||||
BehaviourStatus actual = sut.Tick(imageRegion);
|
||||
|
||||
//
|
||||
@@ -86,7 +86,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
FakeTimeProvider fakeTimeProvider = new FakeTimeProvider();
|
||||
|
||||
//
|
||||
ThrowRod sut = new ThrowRod("-", blackboard, new FakeLogger(), false, new FakeInputSimulator(), fakeTimeProvider, drawContent: new FakeDrawContent());
|
||||
ThrowRod sut = new ThrowRod("-", blackboard, this.useTorch, new FakeLogger(), false, new FakeInputSimulator(), fakeTimeProvider, drawContent: new FakeDrawContent());
|
||||
BehaviourStatus actual = sut.Tick(imageRegion);
|
||||
|
||||
//
|
||||
@@ -123,7 +123,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
};
|
||||
|
||||
//
|
||||
ThrowRod sut = new ThrowRod("-", blackboard, new FakeLogger(), false, new FakeInputSimulator(), new FakeTimeProvider(), drawContent: new FakeDrawContent());
|
||||
ThrowRod sut = new ThrowRod("-", blackboard, this.useTorch, new FakeLogger(), false, new FakeInputSimulator(), new FakeTimeProvider(), drawContent: new FakeDrawContent());
|
||||
sut.Tick(imageRegion);
|
||||
var actual = sut.currentFish;
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
FakeTimeProvider fakeTimeProvider = new FakeTimeProvider();
|
||||
|
||||
//
|
||||
ThrowRod sut = new ThrowRod("-", blackboard, new FakeLogger(), false, new FakeInputSimulator(), fakeTimeProvider, drawContent: new FakeDrawContent());
|
||||
ThrowRod sut = new ThrowRod("-", blackboard, this.useTorch, new FakeLogger(), false, new FakeInputSimulator(), fakeTimeProvider, drawContent: new FakeDrawContent());
|
||||
BehaviourStatus actual = sut.Tick(imageRegion);
|
||||
|
||||
//
|
||||
@@ -205,7 +205,7 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
.UntilSuccess("重复抛竿")
|
||||
.Sequence("-")
|
||||
.PushLeaf(() => new MoveViewpointDown("调整视角至俯视", blackboard, logger, false, input))
|
||||
.PushLeaf(() => new ThrowRod("抛竿", blackboard, logger, false, input, timeProvider, drawContent))
|
||||
.PushLeaf(() => new ThrowRod("抛竿", blackboard, this.useTorch, logger, false, input, timeProvider, drawContent))
|
||||
.End()
|
||||
.End()
|
||||
.Do("抛竿检查", _ => (blackboard.abort || blackboard.throwRodNoTarget || blackboard.throwRodNoBaitFish) ? BehaviourStatus.Failed : BehaviourStatus.Running)
|
||||
|
||||
@@ -5,7 +5,7 @@ using BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests;
|
||||
|
||||
namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
{
|
||||
[Collection("Paddle Collection")]
|
||||
[Collection("Init Collection")]
|
||||
public partial class BehavioursTests
|
||||
{
|
||||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 "required" 修饰符或声明为可为 null。
|
||||
@@ -13,9 +13,11 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 "required" 修饰符或声明为可为 null。
|
||||
|
||||
private readonly PaddleFixture paddle;
|
||||
public BehavioursTests(PaddleFixture paddle)
|
||||
|
||||
public BehavioursTests(PaddleFixture paddle, TorchFixture torch)
|
||||
{
|
||||
this.paddle = paddle;
|
||||
this.useTorch = torch.UseTorch;
|
||||
}
|
||||
|
||||
private IOcrService OcrService => paddle.Get();
|
||||
@@ -24,8 +26,10 @@ namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
{
|
||||
get
|
||||
{
|
||||
return LazyInitializer.EnsureInitialized(ref predictor,()=>BgiOnnxFactory.Instance.CreateYoloPredictor(BgiOnnxModel.BgiFish));
|
||||
return LazyInitializer.EnsureInitialized(ref predictor, () => new BgiOnnxFactory(new HardwareAccelerationConfig(), new FakeLogger<BgiOnnxFactory>()).CreateYoloPredictor(BgiOnnxModel.BgiFish));
|
||||
}
|
||||
}
|
||||
|
||||
private readonly bool useTorch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using BetterGenshinImpact.GameTask.AutoFishing;
|
||||
using BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static BetterGenshinImpact.GameTask.AutoFishing.RodNet;
|
||||
using static TorchSharp.torch;
|
||||
|
||||
namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
{
|
||||
[Collection("Init Collection")]
|
||||
public class RodNetTests
|
||||
{
|
||||
public RodNetTests(TorchFixture torch)
|
||||
{
|
||||
if (!torch.UseTorch)
|
||||
throw new NotSupportedException("torch加载失败,请检查BetterGenshinImpact项目编译环境的配置");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(517.6326F, 548.49023F, 255.25723F, 263.55743F, 256.57538F, 351.56964F, 274.65656F, 333.1523F, 5)]
|
||||
/// <summary>
|
||||
/// 测试计算给到后处理之前的浮点数输出,Torch推理的结果和直接用数学计算的结果,两者的数值应该在转换到单精度时相同
|
||||
/// </summary>
|
||||
public void ComputeScoresTest_ShouldBeTheSame(double rod_x1, double rod_x2, double rod_y1, double rod_y2, double fish_x1, double fish_x2, double fish_y1, double fish_y2, int fish_label)
|
||||
{
|
||||
//
|
||||
RodInput rodInput = new RodInput
|
||||
{
|
||||
rod_x1 = rod_x1,
|
||||
rod_x2 = rod_x2,
|
||||
rod_y1 = rod_y1,
|
||||
rod_y2 = rod_y2,
|
||||
fish_x1 = fish_x1,
|
||||
fish_x2 = fish_x2,
|
||||
fish_y1 = fish_y1,
|
||||
fish_y2 = fish_y2,
|
||||
fish_label = fish_label
|
||||
};
|
||||
RodNet sut = new RodNet();
|
||||
|
||||
//
|
||||
NetInput netInput = GeometryProcessing(rodInput) ?? throw new NullReferenceException();
|
||||
Tensor outputTensor = sut.ComputeScores_Torch(netInput);
|
||||
double[] pred = ComputeScores(netInput);
|
||||
|
||||
//
|
||||
Assert.Equal((float)pred[0], (float)outputTensor.data<double>()[0]); // 对比时降低精度,差不多就行
|
||||
Assert.Equal((float)pred[1], (float)outputTensor.data<double>()[1]);
|
||||
Assert.Equal((float)pred[2], (float)outputTensor.data<double>()[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using BetterGenshinImpact.GameTask.AutoFishing;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TorchSharp;
|
||||
|
||||
namespace BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests
|
||||
{
|
||||
public class TorchFixture
|
||||
{
|
||||
private readonly Lazy<TorchLoader> torch = new Lazy<TorchLoader>();
|
||||
public bool UseTorch
|
||||
{
|
||||
get
|
||||
{
|
||||
return torch.Value.UseTorch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class TorchLoader
|
||||
{
|
||||
public TorchLoader()
|
||||
{
|
||||
// 需要读取主项目编译目录中的配置
|
||||
string configFullPath = Path.Combine(Path.GetFullPath(@"..\..\..\..\..\"), @"BetterGenshinImpact\bin\x64\Debug\net8.0-windows10.0.22621.0\User\config.json");
|
||||
IConfigurationRoot configurationRoot = new ConfigurationBuilder().AddJsonFile(configFullPath, optional: false).Build();
|
||||
AutoFishingConfig autoFishingConfig = configurationRoot.GetRequiredSection("autoFishingConfig").Get<AutoFishingConfig>() ?? throw new ArgumentNullException();
|
||||
try
|
||||
{
|
||||
NativeLibrary.Load(autoFishingConfig.TorchDllFullPath);
|
||||
if (torch.TryInitializeDeviceType(DeviceType.CUDA))
|
||||
{
|
||||
torch.set_default_device(new torch.Device(DeviceType.CUDA));
|
||||
}
|
||||
UseTorch = true;
|
||||
}
|
||||
catch (Exception e) when (e is DllNotFoundException || e is NotSupportedException)
|
||||
{
|
||||
UseTorch = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseTorch { get; private set; }
|
||||
}
|
||||
}
|
||||
15
Test/BetterGenshinImpact.UnitTest/InitCollection.cs
Normal file
15
Test/BetterGenshinImpact.UnitTest/InitCollection.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests;
|
||||
using BetterGenshinImpact.UnitTest.GameTaskTests.AutoFishingTests;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterGenshinImpact.UnitTest
|
||||
{
|
||||
[CollectionDefinition("Init Collection")]
|
||||
public class InitCollection : ICollectionFixture<PaddleFixture>, ICollectionFixture<TorchFixture>
|
||||
{
|
||||
}
|
||||
}
|
||||
19
Test/BetterGenshinImpact.UnitTest/README.md
Normal file
19
Test/BetterGenshinImpact.UnitTest/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## 使用单元测试前准备
|
||||
|
||||
### 准备Assets
|
||||
|
||||
Assets目录下面是一个子模块(submodule)
|
||||
为了避免不关注测试的用户下载不必要的数据,子模块没有保持最新,因此为了进行测试,要手动下载Assets目录下的最新数据资源:
|
||||
`git submodule update --remote`
|
||||
可能需要先init:
|
||||
`git submodule init`
|
||||
|
||||
子模块被定义在 [.gitmodules](../../.gitmodules) 文件中,因此上述git命令也应在该文件所在目录执行
|
||||
|
||||
Assets项目地址:[https://github.com/huiyadanli/BetterGI.UnitTest.Assets](https://github.com/huiyadanli/BetterGI.UnitTest.Assets)
|
||||
|
||||
|
||||
### 准备配置文件
|
||||
|
||||
有的单元测试要读取配置,目前采取读取主项目BetterGenshinImpact编译环境相同配置的方式
|
||||
因此须要编译运行一次主项目BetterGenshinImpact,使得User/config.json被创建出来
|
||||
Reference in New Issue
Block a user