Merge branch 'main' into d-v3

This commit is contained in:
辉鸭蛋
2025-06-08 17:53:06 +08:00
85 changed files with 2992 additions and 848 deletions

View File

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

View File

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

View File

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

View File

@@ -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" />

View File

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

View File

@@ -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),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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("全自动运行时,自动切换实时任务中的半自动钓鱼功能为关闭状态");

View File

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

View File

@@ -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>
/// 辣条(误)

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 的话,这个值会存在

View File

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

View File

@@ -527,7 +527,7 @@ public class TpTask(CancellationToken ct)
// 非常接近目标点,不再进一步调整
if (mouseDistance < _tpConfig.Tolerance)
{
Logger.LogInformation("移动 {I} 次鼠标后,已经接近目标点,不再移动地图。", iteration + 1);
Logger.LogDebug("移动 {I} 次鼠标后,已经接近目标点,不再移动地图。", iteration + 1);
break;
}

View File

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

View File

@@ -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 满的情况是怎么样子的

View File

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

View File

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

View File

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

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

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

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

View File

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

View 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}");
}
}
}

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

View File

@@ -3,4 +3,6 @@
public class Notice
{
public string Version { get; set; } = string.Empty;
}
public int Gray { get; set; } = 10;
}

View File

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

View File

@@ -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"))
{

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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="&#xf073;" 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="&#xf784;" 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" />

View File

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

View File

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

View File

@@ -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" />

View File

@@ -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="&#xf4ba;" 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="&#xf0c2;" 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="&#xf0c2;" 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>

View File

@@ -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
{

View File

@@ -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">

View File

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

View File

@@ -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失败使用默认值
}
// 每个设备只运行一次

View File

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

View File

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

View File

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

View File

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

View File

@@ -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]

View File

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

View File

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

View File

@@ -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" />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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>
{
}
}

View 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被创建出来