Merge branch 'main' into feat/new_fishing

This commit is contained in:
辉鸭蛋
2025-02-05 12:36:04 +08:00
99 changed files with 3004 additions and 982 deletions

View File

@@ -61,7 +61,7 @@ internal class HsvTestWindow
Cv2.CreateTrackbar("High S", _windowDetectionName, ref _highS, MaxValue, on_high_S_thresh_trackbar);
Cv2.CreateTrackbar("Low V", _windowDetectionName, ref _lowV, MaxValue, on_low_V_thresh_trackbar);
Cv2.CreateTrackbar("High V", _windowDetectionName, ref _highV, MaxValue, on_high_V_thresh_trackbar);
var frame = Cv2.ImRead(@"E:\HuiTask\更好的原神\自动秘境\自动战斗\队伍识别\bsz1.png", ImreadModes.Color);
var frame = Cv2.ImRead(@"E:\HuiTask\更好的原神\自动秘境\Q识别\拼图\2.png", ImreadModes.Color);
Mat frameHsv = new Mat();
// Convert from BGR to HSV colorspace
Cv2.CvtColor(frame, frameHsv, ColorConversionCodes.BGR2HSV);

View File

@@ -185,6 +185,8 @@ public partial class App : Application
protected override async void OnExit(ExitEventArgs e)
{
base.OnExit(e);
TempManager.CleanUp();
await _host.StopAsync();
_host.Dispose();

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@@ -10,7 +10,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationIcon>Assets\Images\logo.ico</ApplicationIcon>
<AssemblyName>BetterGI</AssemblyName>
<AssemblyVersion>0.39.6</AssemblyVersion>
<AssemblyVersion>0.40.1</AssemblyVersion>
<Platforms>x64</Platforms>
<DebugType>embedded</DebugType>
</PropertyGroup>
@@ -48,6 +48,7 @@
<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.Toolkit.Uwp.Notifications" Version="7.1.3" />
<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" />
@@ -148,6 +149,7 @@
<ItemGroup>
<Folder Include="GameTask\OneDragon\" />
<Folder Include="Service\Notification\Builder\" />
<Folder Include="User\AutoPathing\" />
</ItemGroup>

View File

@@ -39,4 +39,10 @@ public partial class CommonConfig : ObservableObject
/// </summary>
[ObservableProperty]
private bool _isFirstRun = true;
/// <summary>
/// 这个版本是否运行过
/// </summary>
[ObservableProperty]
private string _runForVersion = string.Empty;
}

View File

@@ -69,6 +69,10 @@ public partial class PathingPartyConfig : ObservableObject
[ObservableProperty]
private bool _soloTaskUseFightEnabled = false;
//不在某时执行
[ObservableProperty]
private string _skipDuring = "";
// 使用小道具的间隔时间
[ObservableProperty]
private int _useGadgetIntervalMs = 0;

View File

@@ -92,6 +92,11 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
// 解析信息
var fastProxyUrl = res.Item1;
var jsonString = res.Item2;
if (string.IsNullOrEmpty(jsonString))
{
throw new Exception("获取仓库信息失败");
}
var (time, url, file) = ParseJson(jsonString);
var updated = false;
@@ -110,6 +115,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
{
needDownload = true;
}
if (needDownload)
{
await DownloadRepoAndUnzip(string.Format(fastProxyUrl, url));
@@ -246,6 +252,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
Content = $"检测到{(formClipboard ? "" : "")}脚本订阅链接,解析后需要导入的脚本为:{pathJson}。\n是否导入并覆盖此文件或者文件夹下的脚本",
CloseButtonText = "关闭",
PrimaryButtonText = "确认导入",
Owner = Application.Current.MainWindow,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};
@@ -393,7 +400,7 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
{
// 目标文件所在文件夹不存在时创建它
Directory.CreateDirectory(Path.GetDirectoryName(destPath)!);
if (File.Exists(destPath))
{
File.Delete(destPath);
@@ -490,4 +497,4 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
}
}
}
}
}

View File

@@ -30,6 +30,7 @@ using BetterGenshinImpact.GameTask.AutoTrackPath;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.Common.Job;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using Vanara.PInvoke;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
using static Vanara.PInvoke.Kernel32;
@@ -43,8 +44,6 @@ public class AutoDomainTask : ISoloTask
private readonly AutoDomainParam _taskParam;
private readonly PostMessageSimulator _simulator;
private readonly YoloV8Predictor _predictor;
private readonly AutoDomainConfig _config;
@@ -55,10 +54,8 @@ public class AutoDomainTask : ISoloTask
public AutoDomainTask(AutoDomainParam taskParam)
{
_taskParam = taskParam;
AutoFightAssets.DestroyInstance();
_simulator = TaskContext.Instance().PostMessageSimulator;
_taskParam = taskParam;
_predictor = YoloV8Builder.CreateDefaultBuilder()
.UseOnnxModel(Global.Absolute(@"Assets\Model\Domain\bgi_tree.onnx"))
.WithSessionOptions(BgiSessionOption.Instance.Options)
@@ -72,9 +69,9 @@ public class AutoDomainTask : ISoloTask
public async Task Start(CancellationToken ct)
{
_ct = ct;
Init();
NotificationHelper.SendTaskNotificationWithScreenshotUsing(b => b.Domain().Started().Build()); // TODO: 通知后续需要删除迁移
Notify.Event(NotificationEvent.DomainStart).Success("自动秘境启动");
// 3次复活重试
for (int i = 0; i < 3; i++)
@@ -91,6 +88,7 @@ public class AutoDomainTask : ISoloTask
{
Logger.LogWarning("自动秘境:{Text}", "复活后重试秘境...");
await Delay(2000, ct);
Notify.Event(NotificationEvent.DomainRetry).Error("存在角色死亡,复活后重试秘境...");
continue;
}
else
@@ -106,6 +104,7 @@ public class AutoDomainTask : ISoloTask
await Delay(2000, ct);
await ArtifactSalvage();
Notify.Event(NotificationEvent.DomainEnd).Success("自动秘境结束");
}
private async Task DoDomain()
@@ -161,12 +160,10 @@ public class AutoDomainTask : ISoloTask
{
Logger.LogInformation("体力已经耗尽,结束自动秘境");
}
NotificationHelper.SendTaskNotificationWithScreenshotUsing(b => b.Domain().Success().Build());
break;
}
NotificationHelper.SendTaskNotificationWithScreenshotUsing(b => b.Domain().Progress().Build());
Notify.Event(NotificationEvent.DomainReward).Success("自动秘境奖励领取");
}
}
@@ -287,7 +284,7 @@ public class AutoDomainTask : ISoloTask
private async Task EnterDomain()
{
var fightAssets = AutoFightContext.Instance.FightAssets;
var fightAssets = AutoFightAssets.Instance;
// 进入秘境
for (int i = 0; i < 3; i++) // 3次重试 有时候会拾取晶蝶
@@ -299,9 +296,9 @@ public class AutoDomainTask : ISoloTask
Logger.LogInformation("自动秘境:{Text}", "进入秘境");
// 秘境开门动画 5s
await Delay(5000, _ct);
}
}
else
{
{
await Delay(800, _ct);
}
}
@@ -332,7 +329,7 @@ public class AutoDomainTask : ISoloTask
while (retryTimes < 120)
{
retryTimes++;
using var cactRectArea = CaptureToRectArea().Find(AutoFightContext.Instance.FightAssets.ClickAnyCloseTipRa);
using var cactRectArea = CaptureToRectArea().Find(AutoFightAssets.Instance.ClickAnyCloseTipRa);
if (!cactRectArea.IsEmpty())
{
await Delay(1000, _ct);
@@ -368,7 +365,7 @@ public class AutoDomainTask : ISoloTask
await Task.Run((Action)(() =>
{
_simulator.SimulateAction(GIActions.MoveForward, KeyType.KeyDown);
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyDown);
Sleep(30, _ct);
// 组合键好像不能直接用 postmessage
if (!_config.WalkToF)
@@ -395,7 +392,7 @@ public class AutoDomainTask : ISoloTask
}
finally
{
_simulator.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp);
Sleep(50);
if (!_config.WalkToF)
{
@@ -495,7 +492,7 @@ public class AutoDomainTask : ISoloTask
{
using var ra = CaptureToRectArea();
var endTipsRect = ra.DeriveCrop(AutoFightContext.Instance.FightAssets.EndTipsUpperRect);
var endTipsRect = ra.DeriveCrop(AutoFightAssets.Instance.EndTipsUpperRect);
var text = OcrFactory.Paddle.Ocr(endTipsRect.SrcGreyMat);
if (text.Contains("挑战") || text.Contains("达成"))
{
@@ -503,7 +500,7 @@ public class AutoDomainTask : ISoloTask
return true;
}
endTipsRect = ra.DeriveCrop(AutoFightContext.Instance.FightAssets.EndTipsRect);
endTipsRect = ra.DeriveCrop(AutoFightAssets.Instance.EndTipsRect);
text = OcrFactory.Paddle.Ocr(endTipsRect.SrcGreyMat);
if (text.Contains("自动") || text.Contains("退出"))
{
@@ -611,13 +608,13 @@ public class AutoDomainTask : ISoloTask
if (rightKeyDown)
{
// 先松开D键
_simulator.KeyUp(moveRightKey);
Simulation.SendInput.Keyboard.KeyUp(moveRightKey);
rightKeyDown = false;
}
if (!leftKeyDown)
{
_simulator.KeyDown(moveLeftKey);
Simulation.SendInput.Keyboard.KeyDown(moveLeftKey);
leftKeyDown = true;
}
}
@@ -629,13 +626,13 @@ public class AutoDomainTask : ISoloTask
if (leftKeyDown)
{
// 先松开A键
_simulator.KeyUp(moveLeftKey);
Simulation.SendInput.Keyboard.KeyUp(moveLeftKey);
leftKeyDown = false;
}
if (!rightKeyDown)
{
_simulator.KeyDown(moveRightKey);
Simulation.SendInput.Keyboard.KeyDown(moveRightKey);
rightKeyDown = true;
}
}
@@ -644,14 +641,14 @@ public class AutoDomainTask : ISoloTask
// 树在中间 松开所有键
if (rightKeyDown)
{
_simulator.KeyUp(moveRightKey);
Simulation.SendInput.Keyboard.KeyUp(moveRightKey);
prevKey = moveRightKey;
rightKeyDown = false;
}
if (leftKeyDown)
{
_simulator.KeyUp(moveLeftKey);
Simulation.SendInput.Keyboard.KeyUp(moveLeftKey);
prevKey = moveLeftKey;
leftKeyDown = false;
}
@@ -664,7 +661,9 @@ public class AutoDomainTask : ISoloTask
backwardsAndForwardsCount++;
}
_simulator.KeyPress(moveLeftKey, 60);
Simulation.SendInput.Keyboard.KeyDown(moveLeftKey);
Sleep(60);
Simulation.SendInput.Keyboard.KeyUp(moveLeftKey);
prevKey = moveLeftKey;
}
else if (treeMiddleX > middleX)
@@ -674,12 +673,16 @@ public class AutoDomainTask : ISoloTask
backwardsAndForwardsCount++;
}
_simulator.KeyPress(moveRightKey, 60);
Simulation.SendInput.Keyboard.KeyDown(moveRightKey);
Sleep(60);
Simulation.SendInput.Keyboard.KeyUp(moveRightKey);
prevKey = moveRightKey;
}
else
{
_simulator.KeyPress(moveForwardKey, 60);
Simulation.SendInput.Keyboard.KeyDown(moveForwardKey);
Sleep(60);
Simulation.SendInput.Keyboard.KeyUp(moveForwardKey);
Sleep(500, _ct);
treeCts.Cancel();
break;
@@ -695,13 +698,13 @@ public class AutoDomainTask : ISoloTask
{
if (leftKeyDown)
{
_simulator.KeyUp(moveLeftKey);
Simulation.SendInput.Keyboard.KeyUp(moveLeftKey);
leftKeyDown = false;
}
if (!rightKeyDown)
{
_simulator.KeyDown(moveRightKey);
Simulation.SendInput.Keyboard.KeyDown(moveRightKey);
rightKeyDown = true;
}
}
@@ -709,13 +712,13 @@ public class AutoDomainTask : ISoloTask
{
if (rightKeyDown)
{
_simulator.KeyUp(moveRightKey);
Simulation.SendInput.Keyboard.KeyUp(moveRightKey);
rightKeyDown = false;
}
if (!leftKeyDown)
{
_simulator.KeyDown(moveLeftKey);
Simulation.SendInput.Keyboard.KeyDown(moveLeftKey);
leftKeyDown = true;
}
}
@@ -724,7 +727,9 @@ public class AutoDomainTask : ISoloTask
if (backwardsAndForwardsCount >= _config.LeftRightMoveTimes)
{
// 左右移动5次说明已经在树中心了
_simulator.KeyPress(moveForwardKey, 60);
Simulation.SendInput.Keyboard.KeyDown(moveForwardKey);
Sleep(60);
Simulation.SendInput.Keyboard.KeyUp(moveForwardKey);
Sleep(500, _ct);
treeCts.Cancel();
break;
@@ -843,7 +848,7 @@ public class AutoDomainTask : ISoloTask
break;
}
var useCondensedResinRa = CaptureToRectArea().Find(AutoFightContext.Instance.FightAssets.UseCondensedResinRa);
var useCondensedResinRa = CaptureToRectArea().Find(AutoFightAssets.Instance.UseCondensedResinRa);
if (!useCondensedResinRa.IsEmpty())
{
useCondensedResinRa.Click();
@@ -883,13 +888,13 @@ public class AutoDomainTask : ISoloTask
// 优先点击继续
using var confirmRectArea = ra.Find(AutoFightContext.Instance.FightAssets.ConfirmRa);
using var confirmRectArea = ra.Find(AutoFightAssets.Instance.ConfirmRa);
if (!confirmRectArea.IsEmpty())
{
if (isLastTurn)
{
// 最后一回合 退出
var exitRectArea = ra.Find(AutoFightContext.Instance.FightAssets.ExitRa);
var exitRectArea = ra.Find(AutoFightAssets.Instance.ExitRa);
if (!exitRectArea.IsEmpty())
{
exitRectArea.Click();
@@ -907,7 +912,7 @@ public class AutoDomainTask : ISoloTask
if (condensedResinCount == 0 && fragileResinCount < 20)
{
// 没有体力了退出
var exitRectArea = ra.Find(AutoFightContext.Instance.FightAssets.ExitRa);
var exitRectArea = ra.Find(AutoFightAssets.Instance.ExitRa);
if (!exitRectArea.IsEmpty())
{
exitRectArea.Click();
@@ -938,7 +943,7 @@ public class AutoDomainTask : ISoloTask
var ra = CaptureToRectArea();
// 浓缩树脂
var condensedResinCountRa = ra.Find(AutoFightContext.Instance.FightAssets.CondensedResinCountRa);
var condensedResinCountRa = ra.Find(AutoFightAssets.Instance.CondensedResinCountRa);
if (!condensedResinCountRa.IsEmpty())
{
// 图像右侧就是浓缩树脂数量
@@ -949,7 +954,7 @@ public class AutoDomainTask : ISoloTask
}
// 脆弱树脂
var fragileResinCountRa = ra.Find(AutoFightContext.Instance.FightAssets.FragileResinCountRa);
var fragileResinCountRa = ra.Find(AutoFightAssets.Instance.FragileResinCountRa);
if (!fragileResinCountRa.IsEmpty())
{
// 图像右侧就是脆弱树脂数量

View File

@@ -12,6 +12,7 @@ public class AutoFightAssets : BaseAssets<AutoFightAssets>
public List<Rect> AvatarSideIconRectList; // 侧边栏角色头像 非联机状态下
public List<Rect> AvatarIndexRectList; // 侧边栏角色头像对应的白色块 非联机状态下
public Rect ERect;
public Rect ECooldownRect;
public Rect QRect;
public Rect EndTipsUpperRect; // 挑战达成提示
public Rect EndTipsRect;
@@ -47,6 +48,8 @@ public class AutoFightAssets : BaseAssets<AutoFightAssets>
(int)(355 * AssetScale), (int)(465 * AssetScale));
ERect = new Rect(CaptureRect.Width - (int)(267 * AssetScale), CaptureRect.Height - (int)(132 * AssetScale),
(int)(77 * AssetScale), (int)(77 * AssetScale));
ECooldownRect = new Rect(CaptureRect.Width - (int)(241 * AssetScale), CaptureRect.Height - (int)(97 * AssetScale),
(int)(41 * AssetScale), (int)(18 * AssetScale));
QRect = new Rect(CaptureRect.Width - (int)(157 * AssetScale), CaptureRect.Height - (int)(165 * AssetScale),
(int)(110 * AssetScale), (int)(110 * AssetScale));
// 小道具位置 1920-133,800,60,50

View File

@@ -1,27 +1,27 @@
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using BetterGenshinImpact.Model;
// using BetterGenshinImpact.Core.Simulator;
// using BetterGenshinImpact.GameTask.AutoFight.Assets;
// using BetterGenshinImpact.Model;
//
// namespace BetterGenshinImpact.GameTask.AutoFight;
namespace BetterGenshinImpact.GameTask.AutoFight;
/// <summary>
/// 自动战斗上下文
/// 请在启动BetterGI以后再初始化
/// </summary>
public class AutoFightContext : Singleton<AutoFightContext>
{
private AutoFightContext()
{
Simulator = TaskContext.Instance().PostMessageSimulator;
}
/// <summary>
/// find资源
/// </summary>
public AutoFightAssets FightAssets => AutoFightAssets.Instance;
/// <summary>
/// 战斗专用的PostMessage模拟键鼠操作
/// </summary>
public readonly PostMessageSimulator Simulator;
}
// /// <summary>
// /// 自动战斗上下文
// /// 请在启动BetterGI以后再初始化
// /// </summary>
// public class AutoFightContext : Singleton<AutoFightContext>
// {
// private AutoFightContext()
// {
// Simulator = TaskContext.Instance().PostMessageSimulator;
// }
//
// /// <summary>
// /// find资源
// /// </summary>
// public AutoFightAssets FightAssets => AutoFightAssets.Instance;
//
// /// <summary>
// /// 战斗专用的PostMessage模拟键鼠操作
// /// </summary>
// public readonly PostMessageSimulator Simulator;
// }

View File

@@ -248,7 +248,11 @@ public class AutoFightTask : ISoloTask
double skillCd;
if (lastFightName != command.Name && actionSchedulerByCd.TryGetValue(command.Name,out skillCd))
{
var avatar = combatScenes.Avatars.FirstOrDefault(a => a.Name == command.Name);
var avatar = combatScenes.SelectAvatar(command.Name);
if (avatar == null)
{
continue;
}
if (skillCd < 0)
{
skillCd = FindMax([avatar.SkillCd,avatar.SkillHoldCd]);
@@ -453,7 +457,7 @@ public class AutoFightTask : ISoloTask
await Delay(450, _ct);
var ra = CaptureToRectArea();
var b3 = ra.SrcMat.At<Vec3b>(50, 790); //进度条颜色
var whiteTile = ra.SrcMat.At<Vec3b>(50, 772); //白块
var whiteTile = ra.SrcMat.At<Vec3b>(50, 768); //白块
if (IsWhite(whiteTile.Item2, whiteTile.Item1, whiteTile.Item0) && IsYellow(b3.Item2, b3.Item1, b3.Item0) /* AreDifferencesWithinBounds(_finishDetectConfig.BattleEndProgressBarColor, (b3.Item0, b3.Item1, b3.Item2), _finishDetectConfig.BattleEndProgressBarColorTolerance)*/)
{
Logger.LogInformation("识别到战斗结束");
@@ -462,7 +466,8 @@ public class AutoFightTask : ISoloTask
}
Simulation.SendInput.SimulateAction(GIActions.Drop);
Logger.LogInformation($"未识别到战斗结束{b3.Item0},{b3.Item1},{b3.Item2}");
Logger.LogInformation($"未识别到战斗结束yellow{b3.Item0},{b3.Item1},{b3.Item2}");
Logger.LogInformation($"未识别到战斗结束white{whiteTile.Item0},{whiteTile.Item1},{whiteTile.Item2}");
/**
if (!Bv.IsInMainUi(ra))
{
@@ -473,10 +478,6 @@ public class AutoFightTask : ISoloTask
_lastFightFlagTime = DateTime.Now;
return false;
// }
return false;
}
bool IsYellow(int r, int g, int b)

View File

@@ -18,6 +18,7 @@ using BetterGenshinImpact.GameTask.Common.BgiVision;
using Vanara.PInvoke;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
namespace BetterGenshinImpact.GameTask.AutoFight.Model;
@@ -55,7 +56,7 @@ public class Avatar
/// 长按元素战技CD
/// </summary>
public double SkillHoldCd { get; set; }
/// <summary>
/// 最近一次使用元素战技的时间
/// </summary>
@@ -119,7 +120,7 @@ public class Avatar
{
Logger.LogWarning("检测到复苏界面,存在角色被击败,前往七天神像复活");
// 先打开地图
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_ESCAPE); // NOTE: 此处按下Esc是为了关闭复苏界面无需改键
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_ESCAPE); // NOTE: 此处按下Esc是为了关闭复苏界面无需改键
Sleep(600, Ct);
Simulation.SendInput.SimulateAction(GIActions.OpenMap);
// tp 到七天神像复活
@@ -155,23 +156,24 @@ public class Avatar
switch (Index)
{
case 1:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember1);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember1);
break;
case 2:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember2);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember2);
break;
case 3:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember3);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember3);
break;
case 4:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember4);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember4);
break;
case 5:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember5);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember5);
break;
default:
break;
}
// Debug.WriteLine($"切换到{Index}号位");
// Cv2.ImWrite($"log/切换.png", region.SrcMat);
Sleep(250, Ct);
@@ -210,19 +212,19 @@ public class Avatar
switch (Index)
{
case 1:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember1);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember1);
break;
case 2:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember2);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember2);
break;
case 3:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember3);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember3);
break;
case 4:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember4);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember4);
break;
case 5:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember5);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember5);
break;
default:
break;
@@ -254,23 +256,24 @@ public class Avatar
switch (Index)
{
case 1:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember1);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember1);
break;
case 2:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember2);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember2);
break;
case 3:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember3);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember3);
break;
case 4:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember4);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember4);
break;
case 5:
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SwitchMember5);
Simulation.SendInput.SimulateAction(GIActions.SwitchMember5);
break;
default:
break;
}
Sleep(250);
}
}
@@ -314,7 +317,7 @@ public class Avatar
{
var assetScale = TaskContext.Instance().SystemInfo.AssetScale;
// 剪裁出队伍区域
var teamRa = region.DeriveCrop(AutoFightContext.Instance.FightAssets.TeamRect);
var teamRa = region.DeriveCrop(AutoFightAssets.Instance.TeamRect);
var blockX = NameRect.X + NameRect.Width * 2 - 10;
var block = teamRa.DeriveCrop(new Rect(blockX, NameRect.Y, teamRa.Width - blockX, NameRect.Height * 2));
// Cv2.ImWrite($"block_{Name}.png", block.SrcMat);
@@ -337,7 +340,7 @@ public class Avatar
else
{
// 剪裁出IndexRect区域
var teamRa = region.DeriveCrop(AutoFightContext.Instance.FightAssets.TeamRect);
var teamRa = region.DeriveCrop(AutoFightAssets.Instance.TeamRect);
var blockX = NameRect.X + NameRect.Width * 2 - 10;
var indexBlock = teamRa.DeriveCrop(new Rect(blockX + IndexRect.X, NameRect.Y + IndexRect.Y, IndexRect.Width, IndexRect.Height));
// Cv2.ImWrite($"indexBlock_{Name}.png", indexBlock.SrcMat);
@@ -365,7 +368,7 @@ public class Avatar
return;
}
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.NormalAttack);
Simulation.SendInput.SimulateAction(GIActions.NormalAttack);
ms -= 200;
Sleep(200, Ct);
}
@@ -387,7 +390,7 @@ public class Avatar
{
if (Name == "纳西妲")
{
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.ElementalSkill, KeyType.KeyDown);
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill, KeyType.KeyDown);
Sleep(300, Ct);
for (int j = 0; j < 10; j++)
{
@@ -396,22 +399,22 @@ public class Avatar
}
Sleep(300); // 持续操作不应该被cts取消
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.ElementalSkill, KeyType.KeyUp);
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill, KeyType.KeyUp);
}
else if (Name == "坎蒂丝")
{
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.ElementalSkill, KeyType.KeyDown);
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill, KeyType.KeyDown);
Thread.Sleep(3000);
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.ElementalSkill, KeyType.KeyUp);
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill, KeyType.KeyUp);
}
else
{
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.ElementalSkill, KeyType.Hold);
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill, KeyType.Hold);
}
}
else
{
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.ElementalSkill, KeyType.KeyPress);
Simulation.SendInput.SimulateAction(GIActions.ElementalSkill, KeyType.KeyPress);
}
Sleep(200, Ct);
@@ -436,8 +439,9 @@ public class Avatar
/// </summary>
public double GetSkillCurrentCd(ImageRegion imageRegion)
{
var eRa = imageRegion.DeriveCrop(AutoFightContext.Instance.FightAssets.ERect);
var text = OcrFactory.Paddle.Ocr(eRa.SrcGreyMat);
var eRa = imageRegion.DeriveCrop(AutoFightAssets.Instance.ECooldownRect);
var eRaWhite = OpenCvCommonHelper.InRangeHsv(eRa.SrcMat, new Scalar(0, 0, 235), new Scalar(0, 25, 255));
var text = OcrFactory.Paddle.OcrWithoutDetector(eRaWhite);
return StringUtils.TryParseDouble(text);
}
@@ -455,7 +459,7 @@ public class Avatar
return;
}
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.ElementalBurst);
Simulation.SendInput.SimulateAction(GIActions.ElementalBurst);
Sleep(200, Ct);
var region = CaptureToRectArea();
@@ -490,7 +494,7 @@ public class Avatar
// /// </summary>
// public double GetBurstCurrentCd(CaptureContent content)
// {
// var qRa = content.CaptureRectArea.Crop(AutoFightContext.Instance.FightAssets.QRect);
// var qRa = content.CaptureRectArea.Crop(AutoFightAssets.Instance.QRect);
// var text = OcrFactory.Paddle.Ocr(qRa.SrcGreyMat);
// return StringUtils.TryParseDouble(text);
// }
@@ -510,9 +514,9 @@ public class Avatar
ms = 200;
}
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SprintMouse, KeyType.KeyDown);
Simulation.SendInput.SimulateAction(GIActions.SprintMouse, KeyType.KeyDown);
Sleep(ms); // 冲刺不能被cts取消
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.SprintMouse, KeyType.KeyUp);
Simulation.SendInput.SimulateAction(GIActions.SprintMouse, KeyType.KeyUp);
}
public void Walk(string key, int ms)
@@ -545,9 +549,9 @@ public class Avatar
return;
}
AutoFightContext.Instance.Simulator.KeyDown(vk);
Simulation.SendInput.Keyboard.KeyDown(vk);
Sleep(ms); // 行走不能被cts取消
AutoFightContext.Instance.Simulator.KeyUp(vk);
Simulation.SendInput.Keyboard.KeyUp(vk);
}
/// <summary>
@@ -574,7 +578,7 @@ public class Avatar
/// </summary>
public void Jump()
{
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.Jump);
Simulation.SendInput.SimulateAction(GIActions.Jump);
}
/// <summary>
@@ -590,7 +594,7 @@ public class Avatar
if (Name == "那维莱特")
{
var dpi = TaskContext.Instance().DpiScale;
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.NormalAttack, KeyType.KeyDown);
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyDown);
while (ms >= 0)
{
if (Ct is { IsCancellationRequested: true })
@@ -603,13 +607,13 @@ public class Avatar
Sleep(50); // 持续操作不应该被cts取消
}
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.NormalAttack, KeyType.KeyUp);
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyUp);
}
else
{
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.NormalAttack, KeyType.KeyDown);
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyDown);
Sleep(ms); // 持续操作不应该被cts取消
AutoFightContext.Instance.Simulator.SimulateAction(GIActions.NormalAttack, KeyType.KeyUp);
Simulation.SendInput.SimulateAction(GIActions.NormalAttack, KeyType.KeyUp);
}
}
@@ -618,11 +622,11 @@ public class Avatar
key = key.ToLower();
if (key == "left")
{
AutoFightContext.Instance.Simulator.LeftButtonDown();
Simulation.SendInput.Mouse.LeftButtonDown();
}
else if (key == "right")
{
AutoFightContext.Instance.Simulator.RightButtonDown();
Simulation.SendInput.Mouse.RightButtonDown();
}
else if (key == "middle")
{
@@ -635,11 +639,11 @@ public class Avatar
key = key.ToLower();
if (key == "left")
{
AutoFightContext.Instance.Simulator.LeftButtonUp();
Simulation.SendInput.Mouse.LeftButtonUp();
}
else if (key == "right")
{
AutoFightContext.Instance.Simulator.RightButtonUp();
Simulation.SendInput.Mouse.RightButtonUp();
}
else if (key == "middle")
{
@@ -652,11 +656,11 @@ public class Avatar
key = key.ToLower();
if (key == "left")
{
AutoFightContext.Instance.Simulator.LeftButtonClick();
Simulation.SendInput.Mouse.LeftButtonClick();
}
else if (key == "right")
{
AutoFightContext.Instance.Simulator.RightButtonClick();
Simulation.SendInput.Mouse.RightButtonClick();
}
else if (key == "middle")
{
@@ -672,18 +676,18 @@ public class Avatar
public void KeyDown(string key)
{
var vk = User32Helper.ToVk(key);
AutoFightContext.Instance.Simulator.KeyDown(vk);
Simulation.SendInput.Keyboard.KeyDown(vk);
}
public void KeyUp(string key)
{
var vk = User32Helper.ToVk(key);
AutoFightContext.Instance.Simulator.KeyUp(vk);
Simulation.SendInput.Keyboard.KeyUp(vk);
}
public void KeyPress(string key)
{
var vk = User32Helper.ToVk(key);
AutoFightContext.Instance.Simulator.KeyPress(vk);
Simulation.SendInput.Keyboard.KeyPress(vk);
}
}
}

View File

@@ -19,6 +19,7 @@ using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using BetterGenshinImpact.Core.Simulator;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
namespace BetterGenshinImpact.GameTask.AutoFight.Model;
@@ -168,7 +169,7 @@ public class CombatScenes : IDisposable
if (result.TopClass.Confidence < 0.51)
{
Cv2.ImWrite(@"log\avatar_side_classify_error.png", src.ToMat());
throw new Exception($"无法识别第{index}位角色,置信度{result.TopClass.Confidence:F1},结果:{result.TopClass.Name.Name}。请重新阅读文档中的《快速上手》!");
throw new Exception($"无法识别第{index}位角色,置信度{result.TopClass.Confidence:F1},结果:{result.TopClass.Name.Name}。请重新阅读 BetterGI 文档中的《快速上手》!");
}
}
else
@@ -176,7 +177,7 @@ public class CombatScenes : IDisposable
if (result.TopClass.Confidence < 0.7)
{
Cv2.ImWrite(@"log\avatar_side_classify_error.png", src.ToMat());
throw new Exception($"无法识别第{index}位角色,置信度{result.TopClass.Confidence:F1},结果:{result.TopClass.Name.Name}。请重新阅读文档中的《快速上手》!");
throw new Exception($"无法识别第{index}位角色,置信度{result.TopClass.Confidence:F1},结果:{result.TopClass.Name.Name}。请重新阅读 BetterGI 文档中的《快速上手》!");
}
}
@@ -217,7 +218,7 @@ public class CombatScenes : IDisposable
{
if (avatarIndexRectList == null && ExpectedTeamAvatarNum == 4)
{
avatarIndexRectList = AutoFightContext.Instance.FightAssets.AvatarIndexRectList;
avatarIndexRectList = AutoFightAssets.Instance.AvatarIndexRectList;
}
if (avatarIndexRectList == null)
@@ -249,6 +250,9 @@ public class CombatScenes : IDisposable
public void AfterTask()
{
// 释放所有按键
Simulation.ReleaseAllKey();
var mwk = SelectAvatar("玛薇卡");
if (mwk != null)
{
@@ -284,7 +288,7 @@ public class CombatScenes : IDisposable
}
// 剪裁出队伍区域
var teamRa = content.CaptureRectArea.DeriveCrop(AutoFightContext.Instance.FightAssets.TeamRectNoIndex);
var teamRa = content.CaptureRectArea.DeriveCrop(AutoFightAssets.Instance.TeamRectNoIndex);
// 过滤出白色
var hsvFilterMat = OpenCvCommonHelper.InRangeHsv(teamRa.SrcMat, new Scalar(0, 0, 210), new Scalar(255, 30, 255));
@@ -319,10 +323,10 @@ public class CombatScenes : IDisposable
{
// 流浪者特殊处理
// 4人以上的队伍不支持流浪者的识别
var wanderer = rectArea.Find(AutoFightContext.Instance.FightAssets.WandererIconRa);
var wanderer = rectArea.Find(AutoFightAssets.Instance.WandererIconRa);
if (wanderer.IsEmpty())
{
wanderer = rectArea.Find(AutoFightContext.Instance.FightAssets.WandererIconNoActiveRa);
wanderer = rectArea.Find(AutoFightAssets.Instance.WandererIconNoActiveRa);
}
if (wanderer.IsEmpty())

View File

@@ -67,35 +67,34 @@ public class CombatCommand
public void Execute(CombatScenes combatScenes)
{
// 如果是当前角色
Avatar? avatar;
if (Name == CombatScriptParser.CurrentAvatarName)
{
// 如果是当前角色,不进行角色切换
avatar = combatScenes.Avatars[0]; // 随便取一个角色
}
else
{
// 其余情况要进行角色切换
avatar = combatScenes.SelectAvatar(Name);
if (avatar == null)
{
return;
}
// 非宏类脚本,等待切换角色成功
if (Method != Method.Wait
&& Method != Method.MouseDown
&& Method != Method.MouseUp
&& Method != Method.Click
&& Method != Method.MoveBy
&& Method != Method.KeyDown
&& Method != Method.KeyUp
&& Method != Method.KeyPress)
{
avatar.Switch();
}
}
if (avatar == null)
{
return;
}
// 非宏类脚本,等待切换角色成功
if (Method != Method.Wait
&& Method != Method.MouseDown
&& Method != Method.MouseUp
&& Method != Method.Click
&& Method != Method.MoveBy
&& Method != Method.KeyDown
&& Method != Method.KeyUp
&& Method != Method.KeyPress)
{
avatar.Switch();
}
Execute(avatar);
}

View File

@@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.Service.Notification.Model.Enum;
namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model;
@@ -60,9 +61,10 @@ public class Duel
LogScreenResolution();
try
{
Notify.Event(NotificationEvent.TcgStart).Success("自动七胜召唤启动");
AutoGeniusInvokationAssets.DestroyInstance();
NotificationHelper.SendTaskNotificationUsing(b => b.GeniusInvocation().Started().Build()); // TODO 需要移动
GeniusInvokationControl.GetInstance().Init(ct);
// 对局准备 选择初始手牌
@@ -288,14 +290,12 @@ public class Duel
}
catch (TaskCanceledException ex)
{
NotificationHelper.SendTaskNotificationUsing(b => b.GeniusInvocation().Cancelled().Build());
throw;
}
catch (NormalEndException ex)
{
_logger.LogInformation("对局结束");
NotificationHelper.SendTaskNotificationUsing(b => b.GeniusInvocation().Success().Build());
throw;
// throw;
}
catch (System.Exception ex)
{
@@ -303,9 +303,10 @@ public class Duel
{
_logger.LogError(ex.StackTrace);
}
NotificationHelper.SendTaskNotificationUsing(b => b.GeniusInvocation().Failure().Build());
throw;
}
Notify.Event(NotificationEvent.TcgEnd).Success("自动七胜召唤结束");
}
private HashSet<ElementalType> PredictionDiceType()

View File

@@ -1,10 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.Core.Recognition;
using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
using BetterGenshinImpact.GameTask.AutoMusicGame.Assets;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.Service.Notification;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using Microsoft.Extensions.Logging;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
@@ -24,21 +29,38 @@ public class AutoAlbumTask(AutoMusicGameParam taskParam) : ISoloTask
try
{
AutoMusicGameTask.Init();
Notify.Event(NotificationEvent.AlbumStart).Success("自动音游专辑启动");
Logger.LogInformation("开始自动演奏整个专辑未完成的音乐");
await StartOneAlbum(ct);
Notify.Event(NotificationEvent.AlbumEnd).Success("自动音游专辑结束");
}
catch (NormalEndException e)
{
Logger.LogError("手动取消任务 - {Msg}", e.Message);
}
catch (Exception e)
{
Logger.LogError("自动音乐专辑任务异常:{Msg}", e.Message);
Notify.Event(NotificationEvent.AlbumError).Error("自动音游专辑异常", e);
}
}
public async Task StartOneAlbum(CancellationToken ct)
{
using var iconRa = CaptureToRectArea().Find(AutoMusicAssets.Instance.UiLeftTopAlbumIcon);
using var ra1 = CaptureToRectArea();
using var iconRa = ra1.Find(AutoMusicAssets.Instance.UiLeftTopAlbumIcon);
if (!iconRa.IsExist())
{
throw new Exception("当前未处于专辑界面,请在专辑界面运行本任务");
throw new Exception("当前未处于主题专辑界面,请在专辑界面运行本任务。注意全部歌曲列表页面无法运行本任务!");
}
else
{
// OCR 后再次判断,区分是否是全部歌曲页面
var ocrRes = ra1.DeriveCrop(iconRa.Right, iconRa.Top, ra1.Width * 0.16, iconRa.Height).FindMulti(RecognitionObject.OcrThis);
if (ocrRes.Any(region => region.Text.Contains("全部")))
{
throw new Exception("当前在全部歌曲页面,此页面无法运行本任务。请返回到主界面选择专辑列表中以国家为主题的专辑页!");
}
}
var musicLevel = TaskContext.Instance().Config.AutoMusicGameConfig.MusicLevel;
@@ -46,13 +68,14 @@ public class AutoAlbumTask(AutoMusicGameParam taskParam) : ISoloTask
{
musicLevel = "传说";
}
Logger.LogInformation("自动音游乐曲难度等级:{Text}", musicLevel);
// 遍历4个难度等级
var defaultDifficultyLevels = new[]
{
("普通", 480, 600, AutoMusicAssets.Instance.MusicCanorusLevel1),
("困难", 800, 600, AutoMusicAssets.Instance.MusicCanorusLevel2),
("困难", 800, 600, AutoMusicAssets.Instance.MusicCanorusLevel2),
("大师", 1150, 600, AutoMusicAssets.Instance.MusicCanorusLevel3),
("传说", 1400, 600, AutoMusicAssets.Instance.MusicCanorusLevel4)
};
@@ -66,34 +89,51 @@ public class AutoAlbumTask(AutoMusicGameParam taskParam) : ISoloTask
foreach (var (difficultyName, xPos, yPos, canorusAsset) in difficultyLevels)
{
Logger.LogInformation("开始演奏{Difficulty}难度的乐曲", difficultyName);
// 每个难度12首曲子
for (int i = 0; i < 13; i++)
{
using var canoraRa = CaptureToRectArea().Find(canorusAsset);
if (canoraRa.IsExist())
if (TaskContext.Instance().Config.AutoMusicGameConfig.MustCanorusLevel)
{
Logger.LogInformation("乐曲{Num} - {Difficulty}级别:已完成【大音天籁】,切换下一首", i + 1, difficultyName);
GameCaptureRegion.GameRegion1080PPosClick(310, 220);
await Delay(800, ct);
continue;
using var canoraRa = CaptureToRectArea().Find(canorusAsset);
if (canoraRa.IsExist())
{
Logger.LogInformation("乐曲{Num} - {Difficulty}级别:已完成【大音天籁】,切换下一首", i + 1, difficultyName);
GameCaptureRegion.GameRegion1080PPosClick(310, 220);
await Delay(800, ct);
continue;
}
Logger.LogInformation("第{Num}首{Difficulty}难度的乐曲:{Message}", i + 1, difficultyName, "没有完成【大音天籁】");
}
else
{
using var doneRa = CaptureToRectArea().Find(AutoMusicAssets.Instance.AlbumMusicComplate);
if (doneRa.IsExist())
{
Logger.LogInformation("当前乐曲{Num}所有奖励已领取,切换下一首", i + 1);
GameCaptureRegion.GameRegion1080PPosClick(310, 220);
await Delay(800, ct);
continue;
}
Logger.LogInformation("当前乐曲{Num}存在未领取奖励,前往演奏", i + 1);
}
Logger.LogInformation("第{Num}首{Difficulty}难度的乐曲:{Message}", i + 1, difficultyName, "没有完成【大音天籁】");
// 点击确认按钮
Bv.ClickWhiteConfirmButton(CaptureToRectArea());
await Delay(800, ct);
// 选择难度
GameCaptureRegion.GameRegion1080PPosClick(xPos, yPos);
await Delay(200, ct);
// 开始演奏
Bv.ClickWhiteConfirmButton(CaptureToRectArea());
await Delay(500, ct);
using var cts = new CancellationTokenSource();
var cts = new CancellationTokenSource();
ct.Register(cts.Cancel);
// 演奏结束检查任务
@@ -118,16 +158,16 @@ public class AutoAlbumTask(AutoMusicGameParam taskParam) : ISoloTask
// 等待任意一个任务完成
await Task.WhenAny(checkTask, musicTask);
await cts.CancelAsync();
Logger.LogInformation("第{Num}首{Difficulty}难度乐曲演奏完成", i + 1, difficultyName);
await Delay(2000, ct);
await Bv.WaitUntilFound(AutoMusicAssets.Instance.UiLeftTopAlbumIcon, ct);
Logger.LogDebug("切换到下一首乐曲");
GameCaptureRegion.GameRegion1080PPosClick(310, 220);
await Delay(800, ct);
}
Logger.LogInformation("完成{Difficulty}难度所有乐曲的演奏", difficultyName);
}

View File

@@ -11,6 +11,10 @@ namespace BetterGenshinImpact.GameTask.AutoMusicGame;
[Serializable]
public partial class AutoMusicGameConfig : ObservableObject
{
// 自动达到大音天籁的级别
[ObservableProperty]
private bool _mustCanorusLevel = false;
// 乐曲级别
[ObservableProperty]
private string _musicLevel = "";

View File

@@ -112,6 +112,7 @@ public class ElementalCollectAvatarConfigs
new ElementalCollectAvatar("鹿野院平藏", ElementalType.Anemo, true, true),
new ElementalCollectAvatar("流浪者", ElementalType.Anemo, true, false),
new ElementalCollectAvatar("闲云", ElementalType.Anemo, true, false),
new ElementalCollectAvatar("蓝砚", ElementalType.Anemo, true, false),
new ElementalCollectAvatar("枫原万叶", ElementalType.Anemo, false, true),
new ElementalCollectAvatar("珐露珊", ElementalType.Anemo, false, true),
new ElementalCollectAvatar("琳妮特", ElementalType.Anemo, false, true),

View File

@@ -2,23 +2,23 @@
namespace BetterGenshinImpact.GameTask.AutoPathing.Model.Enum;
public class ActionEnum(string code, string msg)
public class ActionEnum(string code, string msg, ActionUseWaypointTypeEnum useWaypointTypeEnum)
{
public static readonly ActionEnum StopFlying = new("stop_flying", "下落攻击");
public static readonly ActionEnum ForceTp = new("force_tp", "当前点传送");
public static readonly ActionEnum NahidaCollect = new("nahida_collect", "纳西妲长按E收集");
public static readonly ActionEnum PickAround = new("pick_around", "尝试在周围拾取");
public static readonly ActionEnum Fight = new("fight", "战斗");
public static readonly ActionEnum UpDownGrabLeaf = new("up_down_grab_leaf", "四叶印");
public static readonly ActionEnum StopFlying = new("stop_flying", "下落攻击", ActionUseWaypointTypeEnum.Custom);
public static readonly ActionEnum ForceTp = new("force_tp", "当前点传送", ActionUseWaypointTypeEnum.Custom);
public static readonly ActionEnum NahidaCollect = new("nahida_collect", "纳西妲长按E收集", ActionUseWaypointTypeEnum.Custom);
public static readonly ActionEnum PickAround = new("pick_around", "尝试在周围拾取", ActionUseWaypointTypeEnum.Custom);
public static readonly ActionEnum Fight = new("fight", "战斗", ActionUseWaypointTypeEnum.Path);
public static readonly ActionEnum UpDownGrabLeaf = new("up_down_grab_leaf", "四叶印", ActionUseWaypointTypeEnum.Custom);
public static readonly ActionEnum HydroCollect = new("hydro_collect", "水元素力采集");
public static readonly ActionEnum ElectroCollect = new("electro_collect", "雷元素力采集");
public static readonly ActionEnum AnemoCollect = new("anemo_collect", "风元素力采集");
public static readonly ActionEnum CombatScript = new("combat_script", "战斗策略脚本"); // 这个必须要 action_params 里面有脚本
public static readonly ActionEnum Mining = new("mining", "挖矿");
public static readonly ActionEnum LogOutput = new("log_output", "输出日志");
public static readonly ActionEnum HydroCollect = new("hydro_collect", "水元素力采集", ActionUseWaypointTypeEnum.Target);
public static readonly ActionEnum ElectroCollect = new("electro_collect", "雷元素力采集", ActionUseWaypointTypeEnum.Target);
public static readonly ActionEnum AnemoCollect = new("anemo_collect", "风元素力采集", ActionUseWaypointTypeEnum.Target);
public static readonly ActionEnum CombatScript = new("combat_script", "战斗策略脚本", ActionUseWaypointTypeEnum.Custom); // 这个必须要 action_params 里面有脚本
public static readonly ActionEnum Mining = new("mining", "挖矿", ActionUseWaypointTypeEnum.Custom);
public static readonly ActionEnum LogOutput = new("log_output", "输出日志", ActionUseWaypointTypeEnum.Custom);
// 还有要加入的其他动作
// 滚轮F
@@ -29,15 +29,32 @@ public class ActionEnum(string code, string msg)
public static IEnumerable<ActionEnum> Values
{
get
{
yield return StopFlying;
}
get { yield return StopFlying; }
}
public string Code { get; private set; } = code;
public string Msg { get; private set; } = msg;
public ActionUseWaypointTypeEnum UseWaypointTypeEnum { get; private set; } = useWaypointTypeEnum;
public static ActionEnum? GetEnumByCode(string? code)
{
if (string.IsNullOrEmpty(code))
{
return null;
}
foreach (var item in Values)
{
if (item.Code == code)
{
return item;
}
}
return null;
}
public static string GetMsgByCode(string code)
{
foreach (var item in Values)
@@ -47,6 +64,14 @@ public class ActionEnum(string code, string msg)
return item.Msg;
}
}
return code;
}
}
public enum ActionUseWaypointTypeEnum
{
Custom, // 跟随路径点 Type
Path, // 强制 Path
Target // 强制 Target
}

View File

@@ -7,6 +7,7 @@ public class WaypointType(string code, string msg)
public static readonly WaypointType Path = new("path", "途径点");
public static readonly WaypointType Target = new("target", "目标点");
public static readonly WaypointType Teleport = new("teleport", "传送点");
public static readonly WaypointType Orientation = new("orientation", "方位点");
public static IEnumerable<WaypointType> Values
{
@@ -15,6 +16,7 @@ public class WaypointType(string code, string msg)
yield return Path;
yield return Target;
yield return Teleport;
yield return Orientation;
}
}

View File

@@ -143,7 +143,7 @@ public class PathExecutor
{
return;
}
InitializePathing(task);
// 转换、按传送点分割路径
var waypointsList = ConvertWaypointsForTrack(task.Positions);
@@ -154,13 +154,13 @@ public class PathExecutor
foreach (var waypoints in waypointsList) // 按传送点分割的路径
{
CurWaypoints = (waypointsList.FindIndex(wps => wps == waypoints), waypoints);
for (var i = 0; i < RetryTimes; i++)
{
try
{
await ResolveAnomalies(); // 异常场景处理
foreach (var waypoint in waypoints) // 一条路径
foreach (var waypoint in waypoints) // 一条路径
{
CurWaypoint = (waypoints.FindIndex(wps => wps == waypoint), waypoint);
TryCloseSkipOtherOperations();
@@ -179,20 +179,22 @@ public class PathExecutor
await BeforeMoveToTarget(waypoint);
// Path不用走得很近Target需要接近但都需要先移动到对应位置
await MoveTo(waypoint);
if (waypoint.Type == WaypointType.Target.Code
// 除了 fight mining stop_flying 之外的 action 都需要接近
|| (!string.IsNullOrEmpty(waypoint.Action)
&& waypoint.Action != ActionEnum.NahidaCollect.Code
&& waypoint.Action != ActionEnum.Fight.Code
&& waypoint.Action != ActionEnum.CombatScript.Code
&& waypoint.Action != ActionEnum.Mining.Code))
if (waypoint.Type == WaypointType.Orientation.Code)
{
if (waypoint.Action != ActionEnum.Fight.Code) // 战斗action强制不接近
{
await MoveCloseTo(waypoint);
}
// 方位点,只需要朝向
// 考虑到方位点大概率是作为执行action的最后一个点所以放在此处处理不和传送点一样单独处理
await FaceTo(waypoint);
}
else
{
await MoveTo(waypoint);
}
await BeforeMoveCloseToTarget(waypoint);
if (IsTargetPoint(waypoint))
{
await MoveCloseTo(waypoint);
}
//skipOtherOperations如果重试则跳过相关操作
@@ -251,6 +253,25 @@ public class PathExecutor
}
}
private bool IsTargetPoint(WaypointForTrack waypoint)
{
// 方位点不需要接近
if (waypoint.Type == WaypointType.Orientation.Code)
{
return false;
}
var action = ActionEnum.GetEnumByCode(waypoint.Action);
if (action is not null && action.UseWaypointTypeEnum != ActionUseWaypointTypeEnum.Custom)
{
// 强制点位类型的 action以 action 为准
return action.UseWaypointTypeEnum == ActionUseWaypointTypeEnum.Target;
}
// 其余情况和没有action的情况以点位类型为准
return waypoint.Type == WaypointType.Target.Code;
}
private async Task<bool> SwitchPartyBefore(PathingTask task)
{
var ra = CaptureToRectArea();
@@ -564,6 +585,16 @@ public class PathExecutor
await Delay(500, ct); // 多等一会
}
private async Task FaceTo(WaypointForTrack waypoint)
{
var screen = CaptureToRectArea();
var position = await GetPosition(screen);
var targetOrientation = Navigation.GetTargetOrientation(waypoint, position);
Logger.LogInformation("朝向点,位置({x2},{y2})", $"{waypoint.GameX:F1}", $"{waypoint.GameY:F1}");
await _rotateTask.WaitUntilRotatedTo(targetOrientation, 2);
await Delay(500, ct);
}
private async Task MoveTo(WaypointForTrack waypoint)
{
// 切人
@@ -806,20 +837,11 @@ public class PathExecutor
private async Task MoveCloseTo(WaypointForTrack waypoint)
{
var screen = CaptureToRectArea();
var position = await GetPosition(screen);
var targetOrientation = Navigation.GetTargetOrientation(waypoint, position);
ImageRegion screen;
Point2f position;
int targetOrientation;
Logger.LogInformation("精确接近目标点,位置({x2},{y2})", $"{waypoint.GameX:F1}", $"{waypoint.GameY:F1}");
if (waypoint.MoveMode == MoveModeEnum.Fly.Code && waypoint.Action == ActionEnum.StopFlying.Code)
{
//下落攻击接近目的地
Logger.LogInformation("动作:下落攻击");
Simulation.SendInput.SimulateAction(GIActions.NormalAttack);
await Delay(1000, ct);
return;
}
await _rotateTask.WaitUntilRotatedTo(targetOrientation, 2);
var stepsTaken = 0;
while (!ct.IsCancellationRequested)
{
@@ -857,6 +879,17 @@ public class PathExecutor
await Delay(1000, ct);
}
private async Task BeforeMoveCloseToTarget(WaypointForTrack waypoint)
{
if (waypoint.MoveMode == MoveModeEnum.Fly.Code && waypoint.Action == ActionEnum.StopFlying.Code)
{
//下落攻击接近目的地
Logger.LogInformation("动作:下落攻击");
Simulation.SendInput.SimulateAction(GIActions.NormalAttack);
await Delay(1000, ct);
}
}
private async Task BeforeMoveToTarget(WaypointForTrack waypoint)
{
if (waypoint.Action == ActionEnum.UpDownGrabLeaf.Code)
@@ -968,6 +1001,8 @@ public class PathExecutor
{
_autoSkipTrigger = new AutoSkipTrigger(new AutoSkipConfig
{
QuicklySkipConversationsEnabled = true, // 快速点击过剧情
ClosePopupPagedEnabled = true,
ClickChatOption = "优先选择最后一个选项",
});
_autoSkipTrigger.Init();

View File

@@ -63,13 +63,11 @@ public class AutoTrackTask(AutoTrackParam param) : BaseIndependentTask
catch (NormalEndException e)
{
Logger.LogInformation("自动追踪中断:" + e.Message);
// NotificationHelper.SendTaskNotificationWithScreenshotUsing(b => b.Domain().Cancelled().Build());
}
catch (Exception e)
{
Logger.LogError(e.Message);
Logger.LogDebug(e.StackTrace);
// NotificationHelper.SendTaskNotificationWithScreenshotUsing(b => b.Domain().Failure().Build());
}
finally
{

View File

@@ -1,5 +1,6 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Text.Json.Serialization;
namespace BetterGenshinImpact.GameTask.AutoTrackPath;
@@ -9,47 +10,55 @@ public partial class TpConfig : ObservableObject
private bool _mapZoomEnabled = true; // 地图缩放开关
[ObservableProperty]
private int _mapZoomOutDistance = 2000; // 地图缩小的最小距离,单位:像素
private int _mapZoomOutDistance = 1000; // 地图缩小的最小距离,单位:像素
[ObservableProperty]
private int _mapZoomInDistance = 400; // 地图放大的最大距离,单位:像素
[ObservableProperty]
private int _stepIntervalMilliseconds = 20; // 鼠标移动时间间隔单位ms
[ObservableProperty]
private double _maxZoomLevel = 5.0; // 最大缩放等级
private int _mapZoomInDistance = 400; // 地图放大的最大距离,单位:像素
[ObservableProperty]
private double _minZoomLevel = 1.7; // 最小缩放等级
private int _stepIntervalMilliseconds = 20; // 鼠标移动时间间隔单位ms
[ObservableProperty]
private double _reviveStatueOfTheSevenPointX = 2296.4; // 七天神像点位X坐标
private double _maxZoomLevel = 5.0; // 最大缩放等级
[ObservableProperty]
private double _reviveStatueOfTheSevenPointY = -824.4; // 七天神像点位Y坐标
private double _minZoomLevel = 2.0; // 最小缩放等级
[ObservableProperty]
private double _reviveStatueOfTheSevenPointX = 2296.4; // 七天神像点位X坐标
[ObservableProperty]
private double _reviveStatueOfTheSevenPointY = -824.4; // 七天神像点位Y坐标
[ObservableProperty]
[property: JsonIgnore]
private int _zoomOutButtonY = 654; // y-coordinate for zoom-out button
[ObservableProperty]
private int _zoomInButtonY = 428; // y-coordinate for zoom-in button
[property: JsonIgnore]
private int _zoomInButtonY = 428; // y-coordinate for zoom-in button
[ObservableProperty]
[property: JsonIgnore]
private int _zoomButtonX = 49; // x-coordinate for zoom button
[ObservableProperty]
[property: JsonIgnore]
private int _zoomStartY = 453; // y-coordinate for zoom start
[ObservableProperty]
[property: JsonIgnore]
private int _zoomEndY = 628; // y-coordinate for zoom end
[ObservableProperty]
private double _tolerance = 200; // 允许的移动误差
[ObservableProperty]
private int _maxIterations = 30; // 移动最大次数
[ObservableProperty]
private int _maxMouseMove = 300; // 单次移动最大距离
[ObservableProperty]
private int _zoomEndY = 628; // y-coordinate for zoom end
[ObservableProperty]
private double _tolerance = 200; // 允许的移动误差
[ObservableProperty]
private int _maxIterations = 30; // 移动最大次数
[ObservableProperty]
private int _maxMouseMove = 300; // 单次移动最大距离
private double _mapScaleFactor = 2.661; // 游戏坐标和 mapZoomLevel=1 时的像素比例因子。
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
@@ -13,6 +14,7 @@ using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.Common.Exceptions;
using BetterGenshinImpact.GameTask.Common.Job;
using BetterGenshinImpact.GameTask.Common.Map;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.GameTask.QuickTeleport.Assets;
@@ -21,6 +23,7 @@ using Microsoft.Extensions.Logging;
using OpenCvSharp;
using Vanara.PInvoke;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
using Log = Serilog.Log;
namespace BetterGenshinImpact.GameTask.AutoTrackPath;
@@ -33,7 +36,7 @@ public class TpTask(CancellationToken ct)
private readonly Rect _captureRect = TaskContext.Instance().SystemInfo.ScaleMax1080PCaptureRect;
private readonly double _zoomOutMax1080PRatio = TaskContext.Instance().SystemInfo.ZoomOutMax1080PRatio;
private readonly TpConfig _tpConfig = TaskContext.Instance().Config.TpConfig;
/// <summary>
/// 传送到须弥七天神像
/// </summary>
@@ -52,8 +55,10 @@ public class TpTask(CancellationToken ct)
await AdjustMapZoomLevel(currentZoomLevel, 3);
}
}
await Tp(_tpConfig.ReviveStatueOfTheSevenPointX, _tpConfig.ReviveStatueOfTheSevenPointY);
}
/// <summary>
/// 通过大地图传送到指定坐标最近的传送点,然后移动到指定坐标
/// </summary>
@@ -62,29 +67,52 @@ public class TpTask(CancellationToken ct)
/// <param name="force">强制以当前的tpX,tpY坐标进行自动传送</param>
private async Task<(double, double)> TpOnce(double tpX, double tpY, bool force = false)
{
var (x, y) = (tpX, tpY);
// 先确认在地图界面
await CheckInBigMapUi();
string? country = null;
if (!force)
{
// 获取最近的传送点位置
(x, y, country) = GetRecentlyTpPoint(tpX, tpY);
Logger.LogDebug("({TpX},{TpY}) 最近的传送点位置 ({X},{Y})", $"{tpX:F1}", $"{tpY:F1}", $"{x:F1}", $"{y:F1}");
}
// 传送前的计算准备
var nTpPoints = GetNearestNTpPoints(tpX, tpY, 2);
var (x, y) = force ? (tpX, tpY) : (nTpPoints[0].x, nTpPoints[0].y);
var country = force ? null : nTpPoints[0].country;
double disBetweenTpPoints = Math.Sqrt((nTpPoints[0].x - nTpPoints[1].x) * (nTpPoints[0].x - nTpPoints[1].x)
+ (nTpPoints[0].y - nTpPoints[1].y) * (nTpPoints[0].y - nTpPoints[1].y));
double minZoomLevel = Math.Max(disBetweenTpPoints / 20, 1);
// 计算传送点位置离哪个地图切换后的中心点最近,切换到该地图
await SwitchRecentlyCountryMap(x, y, country);
double zoomLevel = GetBigMapZoomLevel(CaptureToRectArea());
if (_tpConfig.MapZoomEnabled)
{
double zoomLevel = GetBigMapZoomLevel(CaptureToRectArea());
if (zoomLevel > 4.5)
{
// 显示传送锚点和秘境的缩放等级
await AdjustMapZoomLevel(zoomLevel, 4.5);
zoomLevel = 4.5;
Logger.LogInformation("当前缩放等级过大,调整为 {zoomLevel:0.00}", 4.5);
}
}
if (zoomLevel > minZoomLevel)
{
if (_tpConfig.MapZoomEnabled)
{
Logger.LogInformation("目标传送点有相近传送点,到目标传送点附近将缩放到{zoomLevel:0.00}", minZoomLevel);
await MoveMapTo(x, y, minZoomLevel);
await Delay(300, ct); // 等待地图移动完成
}
else
{
Logger.LogInformation("目标传送点有相近传送点,可能传送失败。如果失败请到设置-大地图地图传送设置开启地图缩放。");
// TODO 部分无法区分点位强制缩放 即使没有zoomEnabled。
// await AdjustMapZoomLevel(zoomLevel, minZoomLevel); // 临时修改缩放
// await Delay(300, ct); // 等待缩放修改完成
await MoveMapTo(x, y); // 移动地图
await Delay(300, ct); // 等待地图移动完成
// await AdjustMapZoomLevel(minZoomLevel, zoomLevel); // 恢复修改的缩放
}
}
var bigMapInAllMapRect = GetBigMapRect();
while (!IsPointInBigMapWindow(bigMapInAllMapRect, x, y)) // 左上角 350x400也属于禁止点击区域
{
@@ -173,25 +201,27 @@ public class TpTask(CancellationToken ct)
}
private async Task CheckInBigMapUi()
{
// 尝试打开地图失败后,先回到主界面后再次尝试打开地图
if (!await TryToOpenBigMapUi())
{
await new ReturnMainUiTask().Start(ct);
if (!await TryToOpenBigMapUi())
{
throw new RetryException("打开大地图失败,请检查按键绑定中「打开地图」按键设置是否和原神游戏中一致!");
}
}
}
/// <summary>
/// 尝试打开地图界面
/// </summary>
private async Task<bool> TryToOpenBigMapUi()
{
// M 打开地图识别当前位置,中心点为当前位置
var ra1 = CaptureToRectArea();
if (!Bv.IsInBigMapUi(ra1))
{
// 不在主界面的情况下,先回到主界面
if (!Bv.IsInMainUi(ra1))
{
for (int i = 0; i < 5; i++)
{
Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_ESCAPE);
await Delay(1000, ct);
ra1 = CaptureToRectArea();
if (Bv.IsInMainUi(ra1))
{
break;
}
}
}
Simulation.SendInput.SimulateAction(GIActions.OpenMap);
await Delay(1000, ct);
for (int i = 0; i < 3; i++)
@@ -201,12 +231,22 @@ public class TpTask(CancellationToken ct)
{
await Delay(500, ct);
}
else
{
return true;
}
}
return false;
}
else
{
return true;
}
}
public async Task<(double, double)> Tp(double tpX, double tpY, bool force = false)
{
await CheckInBigMapUi();
for (var i = 0; i < 3; i++)
{
try
@@ -223,7 +263,6 @@ public class TpTask(CancellationToken ct)
}
catch (Exception e)
{
await CheckInBigMapUi();
Logger.LogError("传送失败,重试 {I} 次", i + 1);
Logger.LogDebug(e, "传送失败,重试 {I} 次", i + 1);
}
@@ -238,12 +277,13 @@ public class TpTask(CancellationToken ct)
/// </summary>
/// <param name="x">目标x坐标</param>
/// <param name="y">目标y坐标</param>
private async Task MoveMapTo(double x, double y)
/// <param name="minZoomLevel">到达目标点的最小缩放等级,只在 MapZoomEnabled 为 True 生效</param>
private async Task MoveMapTo(double x, double y, double minZoomLevel = 2)
{
// 获取当前地图中心点并计算到目标传送点的初始偏移
// await AdjustMapZoomLevel(mapZoomLevel);
var bigMapCenterPoint = GetPositionFromBigMap(); // 初始中心
minZoomLevel = Math.Min(minZoomLevel, _tpConfig.MinZoomLevel);
var bigMapCenterPoint = GetPositionFromBigMap(); // 初始中心
var newBigMapCenterPoint = bigMapCenterPoint;
var (xOffset, yOffset) = (x - bigMapCenterPoint.X, y - bigMapCenterPoint.Y);
int moveMouseX = 100 * Math.Sign(xOffset);
@@ -264,56 +304,75 @@ public class TpTask(CancellationToken ct)
{
Logger.LogWarning("中心点识别失败,尝试预测移动的距离。");
newBigMapCenterPoint = new Point2f(
(float)(bigMapCenterPoint.X + xOffset * moveMouseX / totalMoveMouseX),
(float)(bigMapCenterPoint.Y + yOffset * moveMouseY / totalMoveMouseY)
(float)(bigMapCenterPoint.X + xOffset * moveMouseX / totalMoveMouseX),
(float)(bigMapCenterPoint.Y + yOffset * moveMouseY / totalMoveMouseY)
); // 利用移动鼠标的距离获取新的中心
}
// 本次移动的距离
double diffMapX = Math.Abs(newBigMapCenterPoint.X - bigMapCenterPoint.X);
double diffMapY = Math.Abs(newBigMapCenterPoint.Y - bigMapCenterPoint.Y);
double moveDistance = Math.Sqrt(diffMapX * diffMapX + diffMapY * diffMapY);
if (moveDistance > 10) // 移动距离大于10认为本次移动成功
{
(xOffset, yOffset) = (x - newBigMapCenterPoint.X, y - newBigMapCenterPoint.Y); // 更新目标偏移量
totalMoveMouseX = Math.Abs(moveMouseX * xOffset / diffMapX);
totalMoveMouseY = Math.Abs(moveMouseY * yOffset / diffMapY);
// totalMoveMouseX = Math.Abs(moveMouseX * xOffset / diffMapX);
// totalMoveMouseY = Math.Abs(moveMouseY * yOffset / diffMapY);
double currentZoomLevel = GetBigMapZoomLevel(CaptureToRectArea());
totalMoveMouseX = _tpConfig.MapScaleFactor * Math.Abs(xOffset) / currentZoomLevel;
totalMoveMouseY = _tpConfig.MapScaleFactor * Math.Abs(yOffset) / currentZoomLevel;
double mouseDistance = Math.Sqrt(totalMoveMouseX * totalMoveMouseX + totalMoveMouseY * totalMoveMouseY);
if (_tpConfig.MapZoomEnabled)
{
// 调整地图缩放
// mapZoomLevel<5 才显示传送锚点和秘境; mapZoomLevel>1.7 可以避免点错传送锚点
double currentZoomLevel = GetBigMapZoomLevel(CaptureToRectArea());
currentZoomLevel = GetBigMapZoomLevel(CaptureToRectArea());
double oldZoomLevel = currentZoomLevel;
while (mouseDistance > _tpConfig.MapZoomOutDistance || mouseDistance < _tpConfig.MapZoomInDistance)
{
bool zoomOut = mouseDistance > _tpConfig.MapZoomOutDistance;
bool zoomIn = mouseDistance < _tpConfig.MapZoomInDistance;
if (zoomOut && currentZoomLevel < _tpConfig.MaxZoomLevel - 1.0
|| zoomIn && currentZoomLevel > _tpConfig.MinZoomLevel + 1.0)
bool zoomIn = mouseDistance < _tpConfig.MapZoomInDistance;
if (zoomIn)
{
await AdjustMapZoomLevel(zoomIn);
await Delay(50, ct);
currentZoomLevel = GetBigMapZoomLevel(CaptureToRectArea());
totalMoveMouseX *= oldZoomLevel / currentZoomLevel;
totalMoveMouseY *= oldZoomLevel / currentZoomLevel;
mouseDistance *= oldZoomLevel / currentZoomLevel;
oldZoomLevel = currentZoomLevel;
}
else
{
double targetZoom = zoomIn ? _tpConfig.MinZoomLevel : _tpConfig.MaxZoomLevel;
// 考虑调整和识别误差所以相差0.05就不再调整。
if (currentZoomLevel > _tpConfig.MaxZoomLevel - 0.05 || currentZoomLevel < _tpConfig.MinZoomLevel + 0.05)
if (currentZoomLevel > _tpConfig.MinZoomLevel + 1.0)
{
await AdjustMapZoomLevel(zoomIn);
await Delay(50, ct);
}
else if (currentZoomLevel < minZoomLevel + 0.05)
{
break;
}
await AdjustMapZoomLevel(currentZoomLevel, targetZoom);
break;
else
{
await AdjustMapZoomLevel(currentZoomLevel, minZoomLevel);
break;
}
}
else
{
if (currentZoomLevel < _tpConfig.MaxZoomLevel - 1.0)
{
await AdjustMapZoomLevel(zoomIn);
await Delay(50, ct);
}
else if (currentZoomLevel > _tpConfig.MaxZoomLevel - 0.05)
{
break;
}
else
{
await AdjustMapZoomLevel(currentZoomLevel, _tpConfig.MaxZoomLevel);
break;
}
}
currentZoomLevel = GetBigMapZoomLevel(CaptureToRectArea());
totalMoveMouseX *= oldZoomLevel / currentZoomLevel;
totalMoveMouseY *= oldZoomLevel / currentZoomLevel;
mouseDistance *= oldZoomLevel / currentZoomLevel;
oldZoomLevel = currentZoomLevel;
}
}
@@ -323,7 +382,7 @@ public class TpTask(CancellationToken ct)
Logger.LogInformation("移动 {I} 次鼠标后,已经接近目标点,不再移动地图。", iteration + 1);
break;
}
moveMouseX = (int)Math.Min(totalMoveMouseX, _tpConfig.MaxMouseMove * totalMoveMouseX / mouseDistance) * Math.Sign(xOffset);
moveMouseY = (int)Math.Min(totalMoveMouseY, _tpConfig.MaxMouseMove * totalMoveMouseY / mouseDistance) * Math.Sign(yOffset);
double moveMouseLength = Math.Sqrt(moveMouseX * moveMouseX + moveMouseY * moveMouseY);
@@ -373,7 +432,7 @@ public class TpTask(CancellationToken ct)
await Delay(50, ct);
}
/// <summary>
/// 调整地图的缩放等级(整数缩放级别)。
/// </summary>
@@ -437,7 +496,7 @@ public class TpTask(CancellationToken ct)
// 随机起点以避免地图移动无效
GameCaptureRegion.GameRegionMove((rect, _) =>
(rect.Width / 2d + Random.Shared.Next(-rect.Width / 6, rect.Width / 6),
rect.Height / 2d + Random.Shared.Next(-rect.Height / 6, rect.Height / 6)));
rect.Height / 2d + Random.Shared.Next(-rect.Height / 6, rect.Height / 6)));
GlobalMethod.LeftButtonDown();
for (var i = 0; i < steps; i++)
@@ -445,10 +504,10 @@ public class TpTask(CancellationToken ct)
GlobalMethod.MoveMouseBy(stepX[i], stepY[i]);
await Delay(_tpConfig.StepIntervalMilliseconds, ct);
}
GlobalMethod.LeftButtonUp();
}
public Point2f GetPositionFromBigMap()
{
return GetBigMapCenterPoint();
@@ -529,6 +588,7 @@ public class TpTask(CancellationToken ct)
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
[Obsolete]
public (double x, double y, string? country) GetRecentlyTpPoint(double x, double y)
{
double recentX = 0;
@@ -546,9 +606,44 @@ public class TpTask(CancellationToken ct)
country = tpPosition.Country;
}
}
return (recentX, recentY, country);
}
/// <summary>
/// 获取最接近的N个传送点坐标和所处区域
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="n">获取最近的 n 个传送点</param>
/// <returns></returns>
public List<(double x, double y, string? country)> GetNearestNTpPoints(double x, double y, int n = 1)
{
// 检查 n 的合法性
if (n < 1)
{
throw new ArgumentException("The value of n must be greater than or equal to 1.", nameof(n));
}
// 按距离排序并选择前 n 个点
var sortedTpPositions = MapLazyAssets.Instance.TpPositions
.Select(tpPosition => new
{
tpPosition.X,
tpPosition.Y,
tpPosition.Country,
Distance = Math.Sqrt(Math.Pow(tpPosition.X - x, 2) + Math.Pow(tpPosition.Y - y, 2))
})
.OrderBy(tp => tp.Distance)
.Take(n) // 取前 n 个点
.ToList();
// 将结果转换为 List<(x, y, country)>
return sortedTpPositions
.Select(tp => (tp.X, tp.Y, tp.Country))
.ToList();
}
public async Task<bool> SwitchRecentlyCountryMap(double x, double y, string? forceCountry = null)
{
// 可能是地下地图,切换到地上地图
@@ -740,4 +835,4 @@ public class TpTask(CancellationToken ct)
// 1~6 的缩放等级
return (-5 * s) + 6;
}
}
}

View File

@@ -10,6 +10,7 @@ using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.AutoFight;
using Vanara.PInvoke;
using System.Threading;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using BetterGenshinImpact.GameTask.AutoSkip.Assets;
using BetterGenshinImpact.GameTask.GameLoading.Assets;
@@ -148,7 +149,7 @@ public static partial class Bv
/// <returns></returns>
public static bool IsInRevivePrompt(ImageRegion region)
{
using var confirmRectArea = region.Find(AutoFightContext.Instance.FightAssets.ConfirmRa);
using var confirmRectArea = region.Find(AutoFightAssets.Instance.ConfirmRa);
if (!confirmRectArea.IsEmpty())
{
var list = region.FindMulti(new RecognitionObject

View File

@@ -44,7 +44,7 @@ public partial class ChooseTalkOptionTask
return TalkOptionRes.NotFound;
}
await Task.Delay(200, ct);
await Task.Delay(500, ct);
for (var i = 0; i < skipTimes; i++) // 重试3次
{
@@ -63,7 +63,7 @@ public partial class ChooseTalkOptionTask
{
if (isOrange)
{
// region.DeriveCrop(optionRa.ToRect()).SrcMat.SaveImage(Global.Absolute($"log\\t{optionRa.Text}.png"));
region.DeriveCrop(optionRa.ToRect()).SrcMat.SaveImage(Global.Absolute($"log\\t{optionRa.Text}.png"));
if (!IsOrangeOption(region.DeriveCrop(optionRa.ToRect()).SrcMat))
{
return TalkOptionRes.FoundButNotOrange;
@@ -71,7 +71,7 @@ public partial class ChooseTalkOptionTask
}
ClickOcrRegion(optionRa);
await Task.Delay(200, ct);
await Task.Delay(300, ct);
return TalkOptionRes.FoundAndClick;
}
}
@@ -198,16 +198,14 @@ public partial class ChooseTalkOptionTask
private bool IsOrangeOption(Mat textMat)
{
// 只提取橙色
using var bMat = OpenCvCommonHelper.Threshold(textMat, new Scalar(200, 165, 45), new Scalar(255, 205, 55));
var whiteCount = OpenCvCommonHelper.CountGrayMatColor(bMat, 255);
var rate = whiteCount * 1.0 / (bMat.Width * bMat.Height);
Scalar lowerOrange = new Scalar(10, 150, 150);
Scalar upperOrange = new Scalar(25, 255, 255);
var mask = OpenCvCommonHelper.InRangeHsv(textMat, lowerOrange, upperOrange);
int highConfidencePixels = Cv2.CountNonZero(mask);
double rate = highConfidencePixels * 1.0 / (mask.Width * mask.Height);
Debug.WriteLine($"识别到橙色文字区域占比:{rate}");
if (rate > 0.06)
{
return true;
}
return false;
_logger.LogInformation($"识别到橙色文字区域占比:{rate}");
return rate > 0.1;
}
}

View File

@@ -80,7 +80,7 @@ public class GoToAdventurersGuildTask
await new ReturnMainUiTask().Start(ct);
// 结束后重新打开
await Delay(1000, ct);
await Delay(1200, ct);
var ra = CaptureToRectArea();
if (!Bv.FindFAndPress(ra, "凯瑟琳"))
{

View File

@@ -102,6 +102,33 @@ namespace LogParse
if (configTask != null)
{
//前往七天神像复活
if (logstr.EndsWith("前往七天神像复活"))
{
configTask.Fault.ReviveCount++;
}
//传送失败,重试 n 次
result = parseBgiLine($@"传送失败,重试 (\d+) 次", logstr);
if (result.Item1)
{
configTask.Fault.TeleportFailCount = int.Parse(result.Item2[1]);
}
//战斗超时结束
if (logstr == "战斗超时结束")
{
configTask.Fault.BattleTimeoutCount ++;
}
//重试一次路线或放弃此路线!
if (logstr.EndsWith("重试一次路线或放弃此路线!"))
{
configTask.Fault.RetryCount++;
}
if (logstr.StartsWith("→ 脚本执行结束: \"" + configTask.Name + "\""))
{
configTask.EndDate = parsePreDataTime(logLines, i - 1, logrq);
@@ -182,6 +209,8 @@ namespace LogParse
//配置人物列表xxx.json
public List<ConfigTask> ConfigTaskList { get; } = new();
public class ConfigTask
{
public string Name { get; set; }
@@ -204,6 +233,21 @@ namespace LogParse
Picks[val] = Picks[val] + 1;
}
public FaultScenario Fault { get; set; } = new();
public class FaultScenario
{
//复活次数
public int ReviveCount { get; set; } = 0;
//传送失败次数
public int TeleportFailCount { get; set; } = 0;
//重试次数
public int RetryCount { get; set; } = 0;
//战斗超时
public int BattleTimeoutCount { get; set; } = 0;
}
}
}
@@ -307,7 +351,42 @@ namespace LogParse
return customDayStart;
}
public static string GenerHtmlByConfigGroupEntity(List<ConfigGroupEntity> configGroups, GameInfo gameInfo)
public static string FormatNumberWithStyle(int a, int b=3)
{
if (a== 0)
{
return "";
}
// Determine the style based on the condition
string colorStyle = a >= b ? "color:red;" : string.Empty;
// Return the formatted HTML string
return $"<span style=\"font-weight:bold;{colorStyle}\">{a}</span>";
}
public static string GetNumberOrEmptyString(int number)
{
// 如果数字为0返回空字符串否则返回数字的字符串形式
return number == 0 ? string.Empty : number.ToString();
}
public static string SubtractFiveSeconds(string inputTime,int seconds)
{
try
{
// 将输入的字符串解析为 DateTime
DateTime parsedTime = DateTime.ParseExact(inputTime, "yyyy-MM-dd HH:mm:ss", null);
// 减去 5 秒
DateTime resultTime = parsedTime.AddSeconds(-seconds);
// 转换回指定格式的字符串并返回
return resultTime.ToString("yyyy-MM-dd HH:mm:ss");
}
catch (FormatException)
{
return "Invalid input time format. Please use 'yyyy-MM-dd HH:mm:ss'.";
}
}
public static string GenerHtmlByConfigGroupEntity(List<ConfigGroupEntity> configGroups, GameInfo? gameInfo,LogParseConfig.ScriptGroupLogParseConfig scriptGroupLogParseConfig)
{
(string name, Func<ConfigTask, string> value)[] colConfigs =
[
@@ -316,20 +395,28 @@ namespace LogParse
(name: "结束日期", value: task => task.EndDate?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""),
(name: "耗时", value: task => ConvertSecondsToTime((task.EndDate - task.StartDate)?.TotalSeconds ?? 0))
];
List<(string name, Func<ConfigTask, string> value)> colConfigList = new();
colConfigList.AddRange(colConfigs);
if (scriptGroupLogParseConfig.FaultStatsSwitch)
{
colConfigList.Add((name: "复活次数", value: task => FormatNumberWithStyle(task.Fault.ReviveCount)));
colConfigList.Add((name: "重试次数", value: task => FormatNumberWithStyle(task.Fault.RetryCount)));
colConfigList.Add((name: "战斗超时次数", value: task => FormatNumberWithStyle(task.Fault.BattleTimeoutCount)));
colConfigList.Add((name: "传送失败次数", value: task => FormatNumberWithStyle(task.Fault.TeleportFailCount)));
}
(string name, Func<MoraStatistics, string> value)[] msColConfigs =
[
(name: "日期", value: ms => ms.Name), (name: "小怪", value: ms => ms.SmallMonsterStatistics.ToString()),
(name: "日期", value: ms => ms.Name), (name: "小怪", value: ms => GetNumberOrEmptyString(ms.SmallMonsterStatistics)),
(name: "最后小怪日期", value: ms => ms.LastSmallTime),
(name: "精英", value: ms => ms.EliteGameStatistics.ToString()),
(name: "精英", value: ms => GetNumberOrEmptyString(ms.EliteGameStatistics)),
(name: "精英详细", value: ms => ms.EliteDetails), (name: "最后精英日期", value: ms => ms.LastEliteTime),
(name: "总计锄地摩拉", value: ms => ms.TotalMoraKillingMonstersMora.ToString()),
(name: "突发事件获取摩拉", value: ms => ms.EmergencyBonus)
];
//锄地部分新曾字段
(string name, Func<MoraStatistics, string> value)[] col2Configs=[..msColConfigs.ToList().Where(item=>item.name!="日期" && item.name!="最后小怪日期" && item.name!="最后精英日期"),
(string name, Func<MoraStatistics, string> value)[] col2Configs=[..msColConfigs.ToList().Where(item=>item.name!="日期" && item.name!="最后小怪日期" && item.name!="最后精英日期" && item.name!="突发事件获取摩拉"),
(name: "摩拉(每秒)", value: ms => (ms.TotalMoraKillingMonstersMora/(ms.StatisticsEnd-ms.StatisticsStart)?.TotalSeconds ?? 0).ToString("F2")),
];
@@ -340,11 +427,19 @@ namespace LogParse
List<ActionItem> actionItems = new();
if (gameInfo != null)
{
actionItems = TravelsDiaryDetailManager.loadAllActionItems(gameInfo, configGroups);
int hoeingDelay;
if (int.TryParse(scriptGroupLogParseConfig.HoeingDelay, out hoeingDelay))
{
foreach (var actionItem in actionItems)
{
actionItem.Time = SubtractFiveSeconds(actionItem.Time,hoeingDelay);
}
}
}
return GenerHtmlByConfigGroupEntity(configGroups, "日志分析", colConfigs,col2Configs, actionItems, msColConfigs);
return GenerHtmlByConfigGroupEntity(configGroups, "日志分析", colConfigList.ToArray(),col2Configs, actionItems, msColConfigs);
}
public static string ConcatenateStrings(string a, string b)
{
@@ -373,6 +468,8 @@ namespace LogParse
html.AppendLine(" table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }");
html.AppendLine(" th, td { border: 1px solid black; padding: 8px; text-align: left; }");
html.AppendLine(" th { background-color: #f2f2f2; }");
html.AppendLine(" tr:nth-child(odd) { background-color: #eaeaea; /* 奇数行颜色 */ }");
html.AppendLine(" tr:nth-child(even) { background-color: #f9f9f9; /* 偶数行颜色 */}");
html.AppendLine(" </style>");
html.AppendLine("</head>");
html.AppendLine("<body>");

View File

@@ -14,5 +14,7 @@ public partial class LogParseConfig : ObservableObject
[ObservableProperty] private string _rangeValue = "CurrentConfig";
[ObservableProperty] private string _dayRangeValue = "7";
[ObservableProperty] private bool _hoeingStatsSwitch = false;
[ObservableProperty] private bool _faultStatsSwitch = false;
[ObservableProperty] private string _hoeingDelay= "0";
}
}

View File

@@ -31,7 +31,12 @@ namespace LogParse
{
var ls = this.ActionItems.Where(item => item.ActionId == 28).ToList();
var count = ls.Count();
return ls.Sum(item=>item.Num)+((count==0 || count>=10)?"":$"({count}/10)");
if (count == 0)
{
return "";
}
return ls.Sum(item=>item.Num)+(count>=10?"":$"({count}/10)");
}
}

View File

@@ -11,6 +11,8 @@ using BetterGenshinImpact.Helpers;
using Wpf.Ui.Violeta.Controls;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
using BetterGenshinImpact.Service;
using BetterGenshinImpact.Service.Notification;
using BetterGenshinImpact.Service.Notification.Model.Enum;
namespace BetterGenshinImpact.GameTask;
@@ -48,17 +50,13 @@ public class TaskRunner
_logger.LogError("任务启动失败:当前存在正在运行中的独立任务,请不要重复执行任务!");
return;
}
try
{
_logger.LogInformation("→ {Text}", _name + "任务启动!");
// 初始化
Init();
// 发送运行任务通知
SendNotification();
CancellationContext.Instance.Set();
RunnerContext.Instance.Clear();
@@ -66,8 +64,8 @@ public class TaskRunner
}
catch (NormalEndException e)
{
Notify.Event(NotificationEvent.TaskCancel).Success("任务手动取消,或正常结束");
_logger.LogInformation("任务中断:{Msg}", e.Message);
SendNotification();
if (RunnerContext.Instance.IsContinuousRunGroup)
{
// 连续执行时,抛出异常,终止执行
@@ -76,8 +74,8 @@ public class TaskRunner
}
catch (TaskCanceledException e)
{
Notify.Event(NotificationEvent.TaskCancel).Success("任务被手动取消");
_logger.LogInformation("任务中断:{Msg}", "任务被取消");
SendNotification();
if (RunnerContext.Instance.IsContinuousRunGroup)
{
// 连续执行时,抛出异常,终止执行
@@ -86,15 +84,14 @@ public class TaskRunner
}
catch (Exception e)
{
Notify.Event(NotificationEvent.TaskError).Error("任务执行异常", e);
_logger.LogError(e.Message);
_logger.LogDebug(e.StackTrace);
SendNotification();
}
finally
{
End();
_logger.LogInformation("→ {Text}", _name + "任务结束");
SendNotification();
CancellationContext.Instance.Clear();
RunnerContext.Instance.Clear();
@@ -120,7 +117,8 @@ public class TaskRunner
public async Task RunSoloTaskAsync(ISoloTask soloTask)
{
// 没启动的时候先启动
await ScriptService.StartGameTask();
bool waitForMainUi = soloTask.Name != "自动七圣召唤" && !soloTask.Name.Contains("自动音游");
await ScriptService.StartGameTask(waitForMainUi);
await Task.Run(() => RunCurrentAsync(async () => await soloTask.Start(CancellationContext.Instance.Cts.Token)));
}
@@ -190,7 +188,4 @@ public class TaskRunner
}
}
public void SendNotification()
{
}
}

View File

@@ -0,0 +1,62 @@
using System;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.Genshin.Settings;
using Microsoft.Extensions.Logging;
namespace BetterGenshinImpact.Genshin.Settings2;
public class GameSettingsChecker
{
public static void LoadGameSettingsAndCheck()
{
try
{
var settingStr = GenshinGameSettings.GetStrFromRegistry();
if (settingStr == null)
{
TaskControl.Logger.LogDebug("获取原神游戏设置失败");
return;
}
GenshinGameSettings? settings = GenshinGameSettings.Parse(settingStr);
if (settings == null)
{
TaskControl.Logger.LogDebug("获取原神游戏设置失败");
return;
}
GenshinGameInputSettings? inputSettings = GenshinGameInputSettings.Parse(settings.InputData);
if (inputSettings == null)
{
TaskControl.Logger.LogError("获取原神游戏输入设置失败");
return;
}
if (settings.GammaValue != "2.200000047683716")
{
TaskControl.Logger.LogError("检测到游戏亮度非默认值,将会影响功能正常使用,请在原神 游戏设置——图像——亮度 中恢复默认亮度!");
}
if (inputSettings.MouseSenseIndex != 2
|| inputSettings.MouseSenseIndexY != 2
|| inputSettings.MouseFocusSenseIndex != 2
|| inputSettings.MouseFocusSenseIndexY != 2)
{
TaskControl.Logger.LogInformation("当前:镜头水平灵敏度{X1},镜头垂直灵敏度{Y1},镜头水平灵敏度(瞄准模式){X2},镜头垂直灵敏度(瞄准模式){Y2}",
inputSettings.MouseSenseIndex + 1, inputSettings.MouseSenseIndexY + 1,
inputSettings.MouseFocusSenseIndex + 1, inputSettings.MouseFocusSenseIndexY + 1);
TaskControl.Logger.LogError("检测到镜头灵敏度不是默认值3将会影响所有视角移动功能的正常使用请在原神 游戏设置——控制 中恢复默认灵敏度!");
}
var lang = (TextLanguage)settings.DeviceLanguageType;
if (lang != TextLanguage.SimplifiedChinese)
{
TaskControl.Logger.LogWarning("当前游戏语言{Lang}不是简体中文部分功能可能无法正常使用。The game language is not Simplified Chinese, some functions may not work properly", lang);
}
}
catch (Exception e)
{
TaskControl.Logger.LogDebug(e, "获取原神游戏设置失败");
}
}
}

View File

@@ -0,0 +1,122 @@
using Newtonsoft.Json;
namespace BetterGenshinImpact.Genshin.Settings2;
public class GenshinGameInputSettings
{
[JsonProperty("scriptVersion")]
public string ScriptVersion { get; set; } // 脚本版本
[JsonProperty("mouseSensitivity")]
public string MouseSensitivity { get; set; } // 鼠标灵敏度
[JsonProperty("joypadSenseIndex")]
public int JoypadSenseIndex { get; set; } // 手柄灵敏度索引
[JsonProperty("joypadFocusSenseIndex")]
public int JoypadFocusSenseIndex { get; set; } // 手柄焦点灵敏度索引
[JsonProperty("joypadInvertCameraX")]
public bool JoypadInvertCameraX { get; set; } // 手柄X轴反转摄像头
[JsonProperty("joypadInvertCameraY")]
public bool JoypadInvertCameraY { get; set; } // 手柄Y轴反转摄像头
[JsonProperty("joypadInvertFocusCameraX")]
public bool JoypadInvertFocusCameraX { get; set; } // 手柄X轴反转焦点摄像头
[JsonProperty("joypadInvertFocusCameraY")]
public bool JoypadInvertFocusCameraY { get; set; } // 手柄Y轴反转焦点摄像头
[JsonProperty("mouseSenseIndex")]
public int MouseSenseIndex { get; set; } // 鼠标灵敏度索引
[JsonProperty("mouseFocusSenseIndex")]
public int MouseFocusSenseIndex { get; set; } // 鼠标焦点灵敏度索引
[JsonProperty("touchpadSenseIndex")]
public int TouchpadSenseIndex { get; set; } // 触摸板灵敏度索引
[JsonProperty("touchpadFocusSenseIndex")]
public int TouchpadFocusSenseIndex { get; set; } // 触摸板焦点灵敏度索引
[JsonProperty("enableTouchpadFocusAcceleration")]
public bool EnableTouchpadFocusAcceleration { get; set; } // 启用触摸板焦点加速度
[JsonProperty("lastJoypadDefaultScale")]
public float LastJoypadDefaultScale { get; set; } // 最后一次手柄默认缩放
[JsonProperty("lastJoypadFocusScale")]
public float LastJoypadFocusScale { get; set; } // 最后一次手柄焦点缩放
[JsonProperty("lastPCDefaultScale")]
public float LastPCDefaultScale { get; set; } // 最后一次PC默认缩放
[JsonProperty("lastPCFocusScale")]
public float LastPCFocusScale { get; set; } // 最后一次PC焦点缩放
[JsonProperty("lastTouchDefaultScale")]
public float LastTouchDefaultScale { get; set; } // 最后一次触摸默认缩放
[JsonProperty("lastTouchFcousScale")]
public float LastTouchFocusScale { get; set; } // 最后一次触摸焦点缩放
[JsonProperty("switchWalkRunByBtn")]
public bool SwitchWalkRunByBtn { get; set; } // 通过按钮切换行走和奔跑
[JsonProperty("skiffCameraAutoFix")]
public bool SkiffCameraAutoFix { get; set; } // 小艇摄像头自动修正
[JsonProperty("skiffCameraAutoFixInCombat")]
public bool SkiffCameraAutoFixInCombat { get; set; } // 战斗中小艇摄像头自动修正
[JsonProperty("cameraDistanceRatio")]
public float CameraDistanceRatio { get; set; } // 摄像头距离比率
[JsonProperty("wwiseVibration")]
public bool WwiseVibration { get; set; } // Wwise振动
[JsonProperty("isYInited")]
public bool IsYInited { get; set; } // Y轴是否初始化
[JsonProperty("joypadSenseIndexY")]
public int JoypadSenseIndexY { get; set; } // 手柄Y轴灵敏度索引
[JsonProperty("joypadFocusSenseIndexY")]
public int JoypadFocusSenseIndexY { get; set; } // 手柄Y轴焦点灵敏度索引
[JsonProperty("mouseSenseIndexY")]
public int MouseSenseIndexY { get; set; } // 鼠标Y轴灵敏度索引
[JsonProperty("mouseFocusSenseIndexY")]
public int MouseFocusSenseIndexY { get; set; } // 鼠标Y轴焦点灵敏度索引
[JsonProperty("touchpadSenseIndexY")]
public int TouchpadSenseIndexY { get; set; } // 触摸板Y轴灵敏度索引
[JsonProperty("touchpadFocusSenseIndexY")]
public int TouchpadFocusSenseIndexY { get; set; } // 触摸板Y轴焦点灵敏度索引
[JsonProperty("lastJoypadDefaultScaleY")]
public float LastJoypadDefaultScaleY { get; set; } // 最后一次手柄Y轴默认缩放
[JsonProperty("lastJoypadFocusScaleY")]
public float LastJoypadFocusScaleY { get; set; } // 最后一次手柄Y轴焦点缩放
[JsonProperty("lastPCDefaultScaleY")]
public float LastPCDefaultScaleY { get; set; } // 最后一次PC Y轴默认缩放
[JsonProperty("lastPCFocusScaleY")]
public float LastPCFocusScaleY { get; set; } // 最后一次PC Y轴焦点缩放
[JsonProperty("lastTouchDefaultScaleY")]
public float LastTouchDefaultScaleY { get; set; } // 最后一次触摸Y轴默认缩放
[JsonProperty("lastTouchFcousScaleY")]
public float LastTouchFocusScaleY { get; set; } // 最后一次触摸Y轴焦点缩放
public static GenshinGameInputSettings? Parse(string json)
{
return JsonConvert.DeserializeObject<GenshinGameInputSettings>(json);
}
}

View File

@@ -0,0 +1,360 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json.Serialization;
using BetterGenshinImpact.Genshin.Settings;
using Microsoft.Win32;
using Newtonsoft.Json;
using JsonSerializerOptions = System.Text.Json.JsonSerializerOptions;
namespace BetterGenshinImpact.Genshin.Settings2;
public class GenshinGameSettings
{
[JsonProperty("deviceUUID")]
public string DeviceUUID { get; set; } // 设备唯一标识符
[JsonProperty("userLocalDataVersionId")]
public string UserLocalDataVersionId { get; set; } // 用户本地数据版本ID
[JsonProperty("deviceLanguageType")]
public int DeviceLanguageType { get; set; } // 设备语言类型
[JsonProperty("deviceVoiceLanguageType")]
public int DeviceVoiceLanguageType { get; set; } // 设备语音语言类型
[JsonProperty("selectedServerName")]
public string SelectedServerName { get; set; } // 选择的服务器名称
[JsonProperty("localLevelIndex")]
public int LocalLevelIndex { get; set; } // 本地等级索引
[JsonProperty("deviceID")]
public string DeviceID { get; set; } // 设备ID
[JsonProperty("targetUID")]
public string TargetUID { get; set; } // 目标用户ID
[JsonProperty("curAccountName")]
public string CurAccountName { get; set; } // 当前账户名称
[JsonProperty("uiSaveData")]
public string UiSaveData { get; set; } // UI保存数据
[JsonProperty("inputData")]
public string InputData { get; set; } // 输入设置数据
[JsonProperty("graphicsData")]
public string GraphicsData { get; set; } // 图形设置数据
[JsonProperty("globalPerfData")]
public string GlobalPerfData { get; set; } // 全局性能数据
[JsonProperty("miniMapConfig")]
public int MiniMapConfig { get; set; } // 小地图配置
[JsonProperty("enableCameraSlope")]
public bool EnableCameraSlope { get; set; } // 启用相机坡度
[JsonProperty("enableCameraCombatLock")]
public bool EnableCameraCombatLock { get; set; } // 启用相机战斗锁定
[JsonProperty("completionPkg")]
public bool CompletionPkg { get; set; } // 完成的包
[JsonProperty("completionPlayGoPkg")]
public bool CompletionPlayGoPkg { get; set; } // 完成的PlayGo包
[JsonProperty("onlyPlayWithPSPlayer")]
public bool OnlyPlayWithPSPlayer { get; set; } // 仅与PS玩家一起游戏
[JsonProperty("onlyPlayWithXboxPlayer")]
public bool OnlyPlayWithXboxPlayer { get; set; } // 仅与Xbox玩家一起游戏
[JsonProperty("needPlayGoFullPkgPatch")]
public bool NeedPlayGoFullPkgPatch { get; set; } // 需要PlayGo完整包补丁
[JsonProperty("resinNotification")]
public bool ResinNotification { get; set; } // 树脂通知
[JsonProperty("exploreNotification")]
public bool ExploreNotification { get; set; } // 探索通知
[JsonProperty("volumeGlobal")]
public int VolumeGlobal { get; set; } // 全局音量
[JsonProperty("volumeSFX")]
public int VolumeSFX { get; set; } // 音效音量
[JsonProperty("volumeMusic")]
public int VolumeMusic { get; set; } // 音乐音量
[JsonProperty("volumeVoice")]
public int VolumeVoice { get; set; } // 语音音量
[JsonProperty("audioAPI")]
public int AudioAPI { get; set; } // 音频API
[JsonProperty("audioDynamicRange")]
public int AudioDynamicRange { get; set; } // 音频动态范围
[JsonProperty("audioOutput")]
public int AudioOutput { get; set; } // 音频输出
[JsonProperty("_audioSuccessInit")]
public bool AudioSuccessInit { get; set; } // 音频成功初始化
[JsonProperty("enableAudioChangeAndroidMinimumBufferCapacity")]
public bool EnableAudioChangeAndroidMinimumBufferCapacity { get; set; } // 启用更改Android最小缓冲容量的音频
[JsonProperty("audioAndroidMiniumBufferCapacity")]
public int AudioAndroidMiniumBufferCapacity { get; set; } // Android音频最小缓冲容量
[JsonProperty("vibrationLevel")]
public int VibrationLevel { get; set; } // 震动等级
[JsonProperty("vibrationIntensity")]
public int VibrationIntensity { get; set; } // 震动强度
[JsonProperty("usingNewVibrationSetting")]
public bool UsingNewVibrationSetting { get; set; } // 使用新的震动设置
[JsonProperty("motionBlur")]
public bool MotionBlur { get; set; } // 动态模糊
[JsonProperty("gyroAiming")]
public bool GyroAiming { get; set; } // 陀螺仪瞄准
[JsonProperty("gyroHorMoveSpeedIndex")]
public int GyroHorMoveSpeedIndex { get; set; } // 陀螺仪水平移动速度索引
[JsonProperty("gyroVerMoveSpeedIndex")]
public int GyroVerMoveSpeedIndex { get; set; } // 陀螺仪垂直移动速度索引
[JsonProperty("gyroHorReverse")]
public bool GyroHorReverse { get; set; } // 陀螺仪水平反转
[JsonProperty("gyroVerReverse")]
public bool GyroVerReverse { get; set; } // 陀螺仪垂直反转
[JsonProperty("gyroRotateType")]
public int GyroRotateType { get; set; } // 陀螺仪旋转类型
[JsonProperty("gyroExcludeRightStickVerInput")]
public bool GyroExcludeRightStickVerInput { get; set; } // 陀螺仪排除右摇杆垂直输入
[JsonProperty("firstHDRSetting")]
public bool FirstHDRSetting { get; set; } // 首次HDR设置
[JsonProperty("maxLuminosity")]
public float MaxLuminosity { get; set; } // 最大亮度
[JsonProperty("uiPaperWhite")]
public float UiPaperWhite { get; set; } // UI纸白
[JsonProperty("scenePaperWhite")]
public float ScenePaperWhite { get; set; } // 场景纸白
/// <summary>
/// 2.200000047683716
/// </summary>
[JsonProperty("gammaValue")]
public string GammaValue { get; set; } // 伽马值
[JsonProperty("enableHDR")]
public bool EnableHDR { get; set; } // 启用HDR
[JsonProperty("_overrideControllerMapKeyList")]
public List<string> OverrideControllerMapKeyList { get; set; } // 覆盖控制器映射键列表
[JsonProperty("_overrideControllerMapValueList")]
public List<string> OverrideControllerMapValueList { get; set; } // 覆盖控制器映射值列表
[JsonProperty("rewiredMapMigrateRecord")]
public List<string> RewiredMapMigrateRecord { get; set; } // 重布线映射迁移记录
[JsonProperty("rewiredDisableKeyboard")]
public bool RewiredDisableKeyboard { get; set; } // 重布线禁用键盘
[JsonProperty("rewiredEnableKeyboard")]
public bool RewiredEnableKeyboard { get; set; } // 重布线启用键盘
[JsonProperty("rewiredEnableEDS")]
public bool RewiredEnableEDS { get; set; } // 重布线启用EDS
[JsonProperty("disableRewiredDelayInit")]
public bool DisableRewiredDelayInit { get; set; } // 禁用重布线延迟初始化
[JsonProperty("disableRewiredInitProtection")]
public bool DisableRewiredInitProtection { get; set; } // 禁用重布线初始化保护
[JsonProperty("disableSetJoyInfoForWebViewOnPCMobile")]
public bool DisableSetJoyInfoForWebViewOnPCMobile { get; set; } // 禁用在PC和移动设备上的WebView设置Joy信息
[JsonProperty("conflictKeyBindingElementId")]
public List<int> ConflictKeyBindingElementId { get; set; } // 冲突的按键绑定元素ID
[JsonProperty("conflictKeyBindingActionId")]
public List<int> ConflictKeyBindingActionId { get; set; } // 冲突的按键绑定动作ID
[JsonProperty("lastSeenPreDownloadTime")]
public long LastSeenPreDownloadTime { get; set; } // 上次看到的预下载时间
[JsonProperty("lastSeenSettingResourceTabScriptVersion")]
public string LastSeenSettingResourceTabScriptVersion { get; set; } // 上次看到的设置资源标签脚本版本
[JsonProperty("enableEffectAssembleInEditor")]
public bool EnableEffectAssembleInEditor { get; set; } // 启用在编辑器中组装效果
[JsonProperty("forceDisableQuestResourceManagement")]
public bool ForceDisableQuestResourceManagement { get; set; } // 强制禁用任务资源管理
[JsonProperty("needReportQuestResourceDeleteStatusFiles")]
public bool NeedReportQuestResourceDeleteStatusFiles { get; set; } // 需要报告任务资源删除状态文件
[JsonProperty("disableTeamPageBackgroundSwitch")]
public bool DisableTeamPageBackgroundSwitch { get; set; } // 禁用团队页面背景切换
[JsonProperty("disableHttpDns")]
public bool DisableHttpDns { get; set; } // 禁用HTTP DNS
[JsonProperty("mtrCached")]
public bool MtrCached { get; set; } // MTR缓存
[JsonProperty("mtrIsOpen")]
public bool MtrIsOpen { get; set; } // MTR是否开启
[JsonProperty("mtrMaxTTL")]
public int MtrMaxTTL { get; set; } // MTR最大TTL
[JsonProperty("mtrTimeOut")]
public int MtrTimeOut { get; set; } // MTR超时时间
[JsonProperty("mtrTraceCount")]
public int MtrTraceCount { get; set; } // MTR跟踪次数
[JsonProperty("mtrAbortTimeOutCount")]
public int MtrAbortTimeOutCount { get; set; } // MTR中止超时计数
[JsonProperty("mtrAutoTraceInterval")]
public int MtrAutoTraceInterval { get; set; } // MTR自动跟踪间隔
[JsonProperty("mtrTraceCDEachReason")]
public int MtrTraceCDEachReason { get; set; } // MTR每个原因的冷却时间
[JsonProperty("mtrTimeInterval")]
public int MtrTimeInterval { get; set; } // MTR时间间隔
[JsonProperty("mtrBanReasons")]
public List<string> MtrBanReasons { get; set; } // MTR禁止原因
[JsonProperty("_customDataKeyList")]
public List<string> CustomDataKeyList { get; set; } // 自定义数据键列表
[JsonProperty("_customDataValueList")]
public List<string> CustomDataValueList { get; set; } // 自定义数据值列表
[JsonProperty("_serializedCodeSwitches")]
public List<int> SerializedCodeSwitches { get; set; } // 序列化代码开关
[JsonProperty("urlCheckCached")]
public bool UrlCheckCached { get; set; } // URL检查缓存
[JsonProperty("urlCheckIsOpen")]
public bool UrlCheckIsOpen { get; set; } // URL检查是否开启
[JsonProperty("urlCheckAllIP")]
public bool UrlCheckAllIP { get; set; } // URL检查所有IP
[JsonProperty("urlCheckTimeOut")]
public int UrlCheckTimeOut { get; set; } // URL检查超时时间
[JsonProperty("urlCheckSueecssTraceCount")]
public int UrlCheckSuccessTraceCount { get; set; } // URL检查成功跟踪计数
[JsonProperty("urlCheckErrorTraceCount")]
public int UrlCheckErrorTraceCount { get; set; } // URL检查错误跟踪计数
[JsonProperty("urlCheckAbortTimeOutCount")]
public int UrlCheckAbortTimeOutCount { get; set; } // URL检查中止超时计数
[JsonProperty("urlCheckTimeInterval")]
public int UrlCheckTimeInterval { get; set; } // URL检查时间间隔
[JsonProperty("urlCheckCDEachReason")]
public int UrlCheckCDEachReason { get; set; } // URL检查每个原因的冷却时间
[JsonProperty("urlCheckBanReasons")]
public List<string> UrlCheckBanReasons { get; set; } // URL检查禁止原因
[JsonProperty("mtrUseOldWinVersion")]
public bool MtrUseOldWinVersion { get; set; } // 使用旧版Windows的MTR
[JsonProperty("greyTestDeviceUniqueId")]
public string GreyTestDeviceUniqueId { get; set; } // 灰度测试设备唯一ID
[JsonProperty("muteAudioOnAppMinimized")]
public bool MuteAudioOnAppMinimized { get; set; } // 应用最小化时静音
[JsonProperty("disableFallbackControllerType")]
public bool DisableFallbackControllerType { get; set; } // 禁用回退控制器类型
[JsonProperty("lastShowDoorProgress")]
public float LastShowDoorProgress { get; set; } // 上次显示门进度
[JsonProperty("globalPerfSettingVersion")]
public int GlobalPerfSettingVersion { get; set; } // 全局性能设置版本
public static string? GetStrFromRegistry()
{
if (GenshinRegistry.GetRegistryKey() is not { } hk)
{
return null;
}
using (hk)
{
string value_name = SearchRegistryName(hk);
if (hk.GetValue(value_name) is not byte[] rawBytes)
{
return null;
}
var str = Encoding.UTF8.GetString(rawBytes);
// Debug.WriteLine(str);
return str;
}
}
public static GenshinGameSettings? Parse(string json)
{
return JsonConvert.DeserializeObject<GenshinGameSettings>(json);
}
private static string SearchRegistryName(RegistryKey key)
{
string value_name = string.Empty;
string[] names = key.GetValueNames();
foreach (string name in names)
{
if (name.Contains("GENERAL_DATA"))
{
value_name = name;
break;
}
}
if (value_name == string.Empty)
{
throw new ArgumentException(value_name);
}
return value_name;
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace BetterGenshinImpact.Helpers.Http;
@@ -15,9 +16,6 @@ public class ProxySpeedTester
"{0}",
"https://mirror.ghproxy.com/{0}",
"https://hub.gitmirror.com/{0}",
"https://ghproxy.cc/{0}",
"https://www.ghproxy.cc/{0}",
"https://ghproxy.cn/{0}",
"https://ghproxy.net/{0}"
];
@@ -70,11 +68,13 @@ public class ProxySpeedTester
// 模拟代理测试请求
var response = await _httpClient.GetAsync(string.Format(proxyAddress, target), cancellationToken);
response.EnsureSuccessStatusCode();
return (proxyAddress, await response.Content.ReadAsStringAsync(cancellationToken), true);
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var json = JObject.Parse(content);
return (proxyAddress, content, true);
}
catch (Exception e)
{
return (proxyAddress, e.Message, false);
}
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.IO;
using System.Security.AccessControl;
using BetterGenshinImpact.GameTask.Common;
using Microsoft.Extensions.Logging;
namespace BetterGenshinImpact.Helpers;
public static class SecurityControlHelper
{
public static void AllowFullFolderSecurity(string dirPath)
{
if (!RuntimeHelper.IsElevated)
{
return;
}
try
{
DirectoryInfo dir = new(dirPath);
DirectorySecurity dirSecurity = dir.GetAccessControl(AccessControlSections.All);
InheritanceFlags inherits = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;
FileSystemAccessRule everyoneFileSystemAccessRule = new("Everyone", FileSystemRights.FullControl, inherits, PropagationFlags.None, AccessControlType.Allow);
FileSystemAccessRule usersFileSystemAccessRule = new("Users", FileSystemRights.FullControl, inherits, PropagationFlags.None, AccessControlType.Allow);
dirSecurity.ModifyAccessRule(AccessControlModification.Add, everyoneFileSystemAccessRule, out _);
dirSecurity.ModifyAccessRule(AccessControlModification.Add, usersFileSystemAccessRule, out _);
dir.SetAccessControl(dirSecurity);
}
catch (Exception e)
{
TaskControl.Logger.LogError("首次运行自动初始化按键绑定异常:" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message);
MessageBox.Show("检测到当前 BetterGI 位于C盘尝试修改目录权限失败可能会导致WebView2相关的功能无法使用" + e.Message);
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.IO;
using BetterGenshinImpact.Core.Config;
namespace BetterGenshinImpact.Helpers;
public class TempManager
{
public static string GetTempDirectory()
{
var tmp = Global.Absolute("User/Temp");
Directory.CreateDirectory(tmp);
return tmp;
}
public static void CleanUp()
{
try
{
DirectoryHelper.DeleteDirectoryRecursively(GetTempDirectory());
}
catch
{
// Suppress any exceptions to avoid exposing errors
}
}
}

View File

@@ -1,4 +1,7 @@
using BetterGenshinImpact.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using BetterGenshinImpact.Model;
using System.IO;
using System.Linq;
@@ -54,4 +57,103 @@ public class FileTreeNodeHelper
parentNode.Children.Add(fileNode);
}
}
/// <summary>
/// 根据路径过滤树形结构,排除指定路径的节点
/// </summary>
/// <typeparam name="T">节点数据类型</typeparam>
/// <param name="nodes">树形结构的根节点集合</param>
/// <param name="folderNames">要排除的路径集合</param>
/// <returns>过滤后的树形结构</returns>
public static ObservableCollection<FileTreeNode<T>> FilterTree<T>(
ObservableCollection<FileTreeNode<T>> nodes,
List<string> folderNames)
{
// 递归过滤节点
ObservableCollection<FileTreeNode<T>> FilterNodes(ObservableCollection<FileTreeNode<T>> inputNodes, List<string> paths)
{
var filteredNodes = new ObservableCollection<FileTreeNode<T>>();
foreach (var node in inputNodes)
{
// 获取当前层需要排除的路径
var matchedPaths = paths
.Where(path => IsPathMatch(node.FileName ??"", path))
.Select(path => GetRemainingPath(node.FileName ?? "" , path))
.Where(remainingPath => remainingPath != null)
.ToList();
// 如果当前路径完全匹配,跳过当前节点
if (matchedPaths.Any(path => path == ""))
{
continue;
}
// 递归对子节点过滤
node.Children = FilterNodes(node.Children, matchedPaths);
// 添加过滤后的节点
filteredNodes.Add(node);
}
return filteredNodes;
}
return FilterNodes(nodes, folderNames);
}
/// <summary>
/// 判断文件路径是否匹配多级路径的前缀
/// </summary>
/// <param name="fileName">当前节点路径</param>
/// <param name="path">目标路径</param>
/// <returns>是否匹配</returns>
private static bool IsPathMatch(string fileName, string path)
{
// 匹配路径是否以指定前缀开始,路径分隔符对齐
return path.StartsWith(fileName, StringComparison.OrdinalIgnoreCase)
&& (path.Length == fileName.Length || path[fileName.Length] == '\\');
}
/// <summary>
/// 获取路径中去掉当前节点后的剩余路径
/// </summary>
/// <param name="fileName">当前节点路径</param>
/// <param name="path">完整路径</param>
/// <returns>剩余路径,如果不匹配返回 null</returns>
private static string? GetRemainingPath(string fileName, string path)
{
if (IsPathMatch(fileName, path))
{
return path.Length > fileName.Length ? path.Substring(fileName.Length + 1) : "";
}
return null;
}
public static ObservableCollection<FileTreeNode<T>> FilterEmptyNodes<T>(ObservableCollection<FileTreeNode<T>> nodes)
{
// 递归过滤节点
ObservableCollection<FileTreeNode<T>> Filter(ObservableCollection<FileTreeNode<T>> inputNodes)
{
var filteredNodes = new ObservableCollection<FileTreeNode<T>>();
foreach (var node in inputNodes)
{
// 递归处理子节点
node.Children = Filter(node.Children);
// 如果是目录并且没有子节点,跳过当前节点
if (node.IsDirectory && !node.Children.Any())
{
continue;
}
// 其他情况保留节点
filteredNodes.Add(node);
}
return filteredNodes;
}
return Filter(nodes);
}
}

View File

@@ -1,8 +0,0 @@
using BetterGenshinImpact.Service.Notification.Model;
namespace BetterGenshinImpact.Service.Notification.Builder;
public interface INotificationDataBuilder<TNotificationData> where TNotificationData : INotificationData
{
TNotificationData Build();
}

View File

@@ -1,13 +0,0 @@
using BetterGenshinImpact.Service.Notification.Model;
namespace BetterGenshinImpact.Service.Notification.Builder;
public class LifecycleNotificationBuilder : INotificationDataBuilder<LifecycleNotificationData>
{
private readonly LifecycleNotificationData _notificationData = new();
public LifecycleNotificationData Build()
{
return _notificationData;
}
}

View File

@@ -1,88 +0,0 @@
using BetterGenshinImpact.Service.Notification.Model;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using System.Drawing;
namespace BetterGenshinImpact.Service.Notification.Builder;
public class TaskNotificationBuilder : INotificationDataBuilder<TaskNotificationData>
{
private readonly TaskNotificationData _notificationData = new();
public TaskNotificationBuilder WithEvent(NotificationEvent notificationEvent)
{
_notificationData.Event = notificationEvent;
return this;
}
public TaskNotificationBuilder WithAction(NotificationAction notificationAction)
{
_notificationData.Action = notificationAction;
return this;
}
public TaskNotificationBuilder WithConclusion(NotificationConclusion? conclusion)
{
_notificationData.Conclusion = conclusion;
return this;
}
public TaskNotificationBuilder GeniusInvocation()
{
return WithEvent(NotificationEvent.GeniusInvocation);
}
public TaskNotificationBuilder Domain()
{
return WithEvent(NotificationEvent.Domain);
}
public TaskNotificationBuilder Started()
{
return WithAction(NotificationAction.Started);
}
public TaskNotificationBuilder Completed()
{
return WithAction(NotificationAction.Completed);
}
public TaskNotificationBuilder Progress()
{
return WithAction(NotificationAction.Progress);
}
public TaskNotificationBuilder Success()
{
return WithAction(NotificationAction.Completed)
.WithConclusion(NotificationConclusion.Success);
}
public TaskNotificationBuilder Failure()
{
return WithAction(NotificationAction.Completed)
.WithConclusion(NotificationConclusion.Failure);
}
public TaskNotificationBuilder Cancelled()
{
return WithAction(NotificationAction.Completed)
.WithConclusion(NotificationConclusion.Cancelled);
}
public TaskNotificationBuilder WithScreenshot(Image? screenshot)
{
_notificationData.Screenshot = screenshot;
return this;
}
public TaskNotificationBuilder AddTask(object task)
{
_notificationData.Task = task;
return this;
}
public TaskNotificationData Build()
{
return _notificationData;
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace BetterGenshinImpact.Service.Notification.Converter;
public class BaseDateTimeJsonConverter(string format) : JsonConverter<DateTime>
{
// 反序列化方法
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
if (DateTime.TryParseExact(reader.GetString(), format, null, System.Globalization.DateTimeStyles.None, out DateTime date))
{
return date;
}
}
return reader.GetDateTime();
}
// 序列化方法
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(format));
}
}

View File

@@ -0,0 +1,7 @@
using System.Text.Json.Serialization;
namespace BetterGenshinImpact.Service.Notification.Converter;
public class DateTimeJsonConverter() : BaseDateTimeJsonConverter("yyyy-MM-dd HH:mm:ss")
{
}

View File

@@ -11,11 +11,11 @@ public class ImageToBase64Converter : JsonConverter<Image>
{
public override Image? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.GetString() is string base64)
if (reader.GetString() is { } base64)
{
return Image.FromStream(new MemoryStream(Convert.FromBase64String(base64)));
}
return default;
return null;
}
public override void Write(Utf8JsonWriter writer, Image value, JsonSerializerOptions options)

View File

@@ -0,0 +1,9 @@
namespace BetterGenshinImpact.Service.Notification.Model.Base;
// TODO: 需要制定标准 (currently not referenced)
public class DomainDetails
{
public string DomainName { get; set; } = string.Empty;
public int RunCount { get; set; }
public string RewardStatus { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,9 @@
namespace BetterGenshinImpact.Service.Notification.Model.Base;
// TODO: 需要制定标准 (currently not referenced)
public class GeniusInvocationDetails
{
public string GameState { get; set; } = string.Empty;
public int RoundNumber { get; set; }
public string OpponentName { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,8 @@
namespace BetterGenshinImpact.Service.Notification.Model.Base;
// TODO: 需要制定标准 (currently not referenced)
public class ScriptDetails
{
public string ScriptName { get; set; } = string.Empty;
public string ScriptPath { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,90 @@
using System;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using System.Text.Json.Serialization;
using System.Drawing;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.Service.Notification.Converter;
namespace BetterGenshinImpact.Service.Notification.Model;
public class BaseNotificationData
{
/// <summary>
/// NotificationEvent
/// 事件名称
/// </summary>
public string Event { get; set; } = string.Empty;
/// <summary>
/// 事件结果
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public NotificationEventResult Result { get; set; }
/// <summary>
/// 事件触发时间
/// </summary>
[JsonConverter(typeof(DateTimeJsonConverter))]
public DateTime Timestamp { get; set; } = DateTime.Now;
/// <summary>
/// 事件触发时的截图
/// </summary>
[JsonConverter(typeof(ImageToBase64Converter))]
public Image? Screenshot { get; set; }
/// <summary>
/// 事件消息
/// </summary>
public string? Message { get; set; }
/// <summary>
/// 额外的事件数据
/// </summary>
public object? Data { get; set; }
public void Send()
{
if (TaskContext.Instance().Config.NotificationConfig.IncludeScreenShot)
{
Screenshot = (Bitmap)TaskControl.CaptureToRectArea().SrcBitmap.Clone();
}
NotificationService.Instance().NotifyAllNotifiers(this);
}
public void Send(string message)
{
Message = message;
Send();
}
public void Success(string message)
{
Message = message;
Result = NotificationEventResult.Success;
Send();
}
public void Fail(string message)
{
Message = message;
Result = NotificationEventResult.Fail;
Send();
}
public void Error(string message)
{
Message = message;
Result = NotificationEventResult.Fail;
Send();
}
public void Error(string message, Exception exception)
{
Message = message + Environment.NewLine + exception.Message + Environment.NewLine + exception.StackTrace;
Result = NotificationEventResult.Fail;
Send();
}
}

View File

@@ -0,0 +1,11 @@
using BetterGenshinImpact.GameTask.AutoDomain;
using BetterGenshinImpact.Service.Notification.Model.Base;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using System.Text.Json.Serialization;
namespace BetterGenshinImpact.Service.Notification.Model;
public class DomainNotificationData : BaseNotificationData
{
public AutoDomainParam? Domain { get; set; }
}

View File

@@ -1,8 +0,0 @@
namespace BetterGenshinImpact.Service.Notification.Model.Enum;
public enum NotificationAction
{
Started,
Completed,
Progress
}

View File

@@ -1,8 +0,0 @@
namespace BetterGenshinImpact.Service.Notification.Model.Enum;
public enum NotificationConclusion
{
Success,
Failure,
Cancelled
}

View File

@@ -1,8 +1,24 @@
namespace BetterGenshinImpact.Service.Notification.Model.Enum;
public enum NotificationEvent
public class NotificationEvent(string code, string msg)
{
Test,
GeniusInvocation,
Domain
public static readonly NotificationEvent Test = new("notify.test", "测试通知");
public static readonly NotificationEvent DomainReward = new("domain.reward", "自动秘境奖励");
public static readonly NotificationEvent DomainStart = new("domain.start", "自动秘境启动");
public static readonly NotificationEvent DomainEnd = new("domain.end", "自动秘境结束");
public static readonly NotificationEvent DomainRetry = new("domain.retry", "自动秘境重试");
public static readonly NotificationEvent TaskCancel = new("task.cancel", "任务启动");
public static readonly NotificationEvent TaskError = new("task.error", "任务错误");
public static readonly NotificationEvent GroupStart = new("group.start", "配置组启动");
public static readonly NotificationEvent GroupEnd = new("group.end", "配置组结束");
public static readonly NotificationEvent DragonStart = new("dragon.start", "一条龙启动");
public static readonly NotificationEvent DragonEnd = new("dragon.end", "一条龙结束");
public static readonly NotificationEvent TcgStart = new("tcg.start", "七圣召唤启动");
public static readonly NotificationEvent TcgEnd = new("tcg.end", "七圣召唤结束");
public static readonly NotificationEvent AlbumStart = new("album.start", "自动音游专辑启动");
public static readonly NotificationEvent AlbumEnd = new("album.end", "自动音游专辑结束");
public static readonly NotificationEvent AlbumError = new("album.error", "自动音游专辑错误");
public string Code { get; private set; } = code;
public string Msg { get; private set; } = msg;
}

View File

@@ -0,0 +1,8 @@
namespace BetterGenshinImpact.Service.Notification.Model.Enum;
public enum NotificationEventResult
{
Success,
Fail,
PartialSuccess, // 还没想好怎么用,先放在这里
}

View File

@@ -0,0 +1,11 @@
using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model;
using BetterGenshinImpact.Service.Notification.Model.Base;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using System.Text.Json.Serialization;
namespace BetterGenshinImpact.Service.Notification.Model;
public class GeniusInvocationNotificationData : BaseNotificationData
{
public Duel? GeniusInvocation { get; set; }
}

View File

@@ -1,10 +0,0 @@
using BetterGenshinImpact.Service.Notification.Model.Enum;
using System.Text.Json.Serialization;
namespace BetterGenshinImpact.Service.Notification.Model;
public interface INotificationData
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public NotificationEvent Event { get; set; }
}

View File

@@ -1,15 +0,0 @@
using BetterGenshinImpact.Service.Notification.Model.Enum;
using System.Text.Json.Serialization;
namespace BetterGenshinImpact.Service.Notification.Model;
public record LifecycleNotificationData : INotificationData
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public NotificationEvent Event { get; set; }
public static LifecycleNotificationData Test()
{
return new LifecycleNotificationData() { Event = NotificationEvent.Test };
}
}

View File

@@ -7,7 +7,7 @@ public class NotificationTestResult
public static NotificationTestResult Success()
{
return new NotificationTestResult { IsSuccess = true, Message = "成功" };
return new NotificationTestResult { IsSuccess = true, Message = "通知成功" };
}
public static NotificationTestResult Error(string message)

View File

@@ -0,0 +1,11 @@
using BetterGenshinImpact.Core.Script.Group;
using BetterGenshinImpact.Service.Notification.Model.Base;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using System.Text.Json.Serialization;
namespace BetterGenshinImpact.Service.Notification.Model;
public class ScriptNotificationData : BaseNotificationData
{
public ScriptGroupProject? Script { get; set; }
}

View File

@@ -1,23 +0,0 @@
using BetterGenshinImpact.Service.Notification.Converter;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using System.Drawing;
using System.Text.Json.Serialization;
namespace BetterGenshinImpact.Service.Notification.Model;
public record TaskNotificationData : INotificationData
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public NotificationEvent Event { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public NotificationAction Action { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public NotificationConclusion? Conclusion { get; set; }
[JsonConverter(typeof(ImageToBase64Converter))]
public Image? Screenshot { get; set; }
public object? Task { get; set; }
}

View File

@@ -9,6 +9,11 @@ namespace BetterGenshinImpact.Service.Notification;
[Serializable]
public partial class NotificationConfig : ObservableObject
{
[ObservableProperty]
private string _notificationEventSubscribe = string.Empty;
/// <summary>
///
/// </summary>
@@ -20,4 +25,48 @@ public partial class NotificationConfig : ObservableObject
/// </summary>
[ObservableProperty]
private string _webhookEndpoint = string.Empty;
/// <summary>
/// 是否包含截图
/// </summary>
[ObservableProperty]
private bool _includeScreenShot = true;
/// <summary>
/// windows uwp 通知是否启用
/// </summary>
[ObservableProperty]
private bool _windowsUwpNotificationEnabled = false;
// 飞书通知
/// <summary>
/// 飞书通知是否启用
/// </summary>
[ObservableProperty]
private bool _feishuNotificationEnabled = false;
/// <summary>
/// 飞书通知地址
/// </summary>
[ObservableProperty]
private string _feishuWebhookUrl = string.Empty;
// 企业微信通知
/// <summary>
/// 企业微信通知是否启用
/// </summary>
[ObservableProperty]
private bool _workweixinNotificationEnabled = false;
/// <summary>
/// 企业微信通知通知地址
/// </summary>
[ObservableProperty]
private string _workweixinWebhookUrl = string.Empty;
}

View File

@@ -1,28 +0,0 @@
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.Service.Notification.Builder;
using BetterGenshinImpact.Service.Notification.Model;
using System;
using System.Drawing;
namespace BetterGenshinImpact.Service.Notification;
public class NotificationHelper
{
public static void Notify(INotificationData notificationData)
{
NotificationService.Instance().NotifyAllNotifiers(notificationData);
}
public static void SendTaskNotificationUsing(Func<TaskNotificationBuilder, INotificationData> builderFunc)
{
var builder = new TaskNotificationBuilder();
Notify(builderFunc(builder));
}
public static void SendTaskNotificationWithScreenshotUsing(Func<TaskNotificationBuilder, INotificationData> builderFunc)
{
var builder = new TaskNotificationBuilder();
var screenShot = (Bitmap)TaskControl.CaptureToRectArea().SrcBitmap.Clone();
Notify(builderFunc(builder.WithScreenshot(screenShot)));
}
}

View File

@@ -1,16 +1,15 @@
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.Service.Interface;
using BetterGenshinImpact.Service.Notification.Model;
using BetterGenshinImpact.Service.Notification.Model;
using BetterGenshinImpact.Service.Notifier;
using BetterGenshinImpact.Service.Notifier.Exception;
using BetterGenshinImpact.Service.Notifier.Interface;
using Microsoft.Extensions.Hosting;
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.Service.Notification.Model.Enum;
namespace BetterGenshinImpact.Service.Notification;
@@ -18,13 +17,11 @@ public class NotificationService : IHostedService
{
private static NotificationService? _instance;
private static readonly HttpClient _httpClient = new();
private static readonly HttpClient NotifyHttpClient = new();
private readonly NotifierManager _notifierManager;
private AllConfig Config { get; set; }
public NotificationService(IConfigService configService, NotifierManager notifierManager)
public NotificationService(NotifierManager notifierManager)
{
Config = configService.Get();
_notifierManager = notifierManager;
_instance = this;
InitializeNotifiers();
@@ -36,6 +33,7 @@ public class NotificationService : IHostedService
{
throw new Exception("Not instantiated");
}
return _instance;
}
@@ -49,22 +47,27 @@ public class NotificationService : IHostedService
return Task.CompletedTask;
}
private StringContent TransformData(INotificationData notificationData)
{
// using object type here so it serializes the interface correctly
var serializedData = JsonSerializer.Serialize<object>(notificationData, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
return new StringContent(serializedData, Encoding.UTF8, "application/json");
}
private void InitializeNotifiers()
{
if (Config.NotificationConfig.WebhookEnabled)
if (TaskContext.Instance().Config.NotificationConfig.WebhookEnabled)
{
_notifierManager.RegisterNotifier(new WebhookNotifier(_httpClient, Config.NotificationConfig.WebhookEndpoint));
_notifierManager.RegisterNotifier(new WebhookNotifier(NotifyHttpClient, TaskContext.Instance().Config.NotificationConfig.WebhookEndpoint));
}
if (TaskContext.Instance().Config.NotificationConfig.WindowsUwpNotificationEnabled)
{
_notifierManager.RegisterNotifier(new WindowsUwpNotifier());
}
if (TaskContext.Instance().Config.NotificationConfig.FeishuNotificationEnabled)
{
_notifierManager.RegisterNotifier(new FeishuNotifier(NotifyHttpClient, TaskContext.Instance().Config.NotificationConfig.FeishuWebhookUrl));
}
if (TaskContext.Instance().Config.NotificationConfig.WorkweixinNotificationEnabled)
{
_notifierManager.RegisterNotifier(new WorkWeixinNotifier(NotifyHttpClient, TaskContext.Instance().Config.NotificationConfig.WorkweixinWebhookUrl));
}
}
@@ -83,7 +86,19 @@ public class NotificationService : IHostedService
{
return NotificationTestResult.Error("通知类型未启用");
}
await notifier.SendNotificationAsync(TransformData(LifecycleNotificationData.Test()));
var testData = new BaseNotificationData
{
Event = NotificationEvent.Test.Code,
Result = NotificationEventResult.Success,
Message = "这是一条测试通知信息",
};
if (TaskContext.Instance().IsInitialized)
{
testData.Screenshot = TaskControl.CaptureToRectArea().SrcBitmap;
}
await notifier.SendAsync(testData);
return NotificationTestResult.Success();
}
catch (NotifierException ex)
@@ -92,13 +107,22 @@ public class NotificationService : IHostedService
}
}
public async Task NotifyAllNotifiersAsync(INotificationData notificationData)
public async Task NotifyAllNotifiersAsync(BaseNotificationData notificationData)
{
await _notifierManager.SendNotificationToAllAsync(TransformData(notificationData));
var subscribeEventStr = TaskContext.Instance().Config.NotificationConfig.NotificationEventSubscribe;
if (!string.IsNullOrEmpty(subscribeEventStr))
{
if (!subscribeEventStr.Contains(notificationData.Event))
{
return;
}
}
await _notifierManager.SendNotificationToAllAsync(notificationData);
}
public void NotifyAllNotifiers(INotificationData notificationData)
public void NotifyAllNotifiers(BaseNotificationData notificationData)
{
Task.Run(() => NotifyAllNotifiersAsync(notificationData));
}
}
}

View File

@@ -0,0 +1,23 @@
using BetterGenshinImpact.Service.Notification.Model;
using BetterGenshinImpact.Service.Notification.Model.Enum;
namespace BetterGenshinImpact.Service.Notification;
public class Notify
{
public static BaseNotificationData Event(string eventName)
{
return new BaseNotificationData
{
Event = eventName
};
}
public static BaseNotificationData Event(NotificationEvent eventEnum)
{
return new BaseNotificationData
{
Event = eventEnum.Code
};
}
}

View File

@@ -0,0 +1,66 @@
using BetterGenshinImpact.Service.Notifier.Exception;
using BetterGenshinImpact.Service.Notifier.Interface;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using BetterGenshinImpact.Service.Notification.Model;
namespace BetterGenshinImpact.Service.Notifier;
public class FeishuNotifier : INotifier
{
public string Name { get; set; } = "Feishu";
public string Endpoint { get; set; }
private readonly HttpClient _httpClient;
public FeishuNotifier(HttpClient httpClient, string endpoint = "")
{
_httpClient = httpClient;
Endpoint = endpoint;
}
public async Task SendAsync(BaseNotificationData content)
{
if (string.IsNullOrEmpty(Endpoint))
{
throw new NotifierException("Feishu webhook endpoint is not set");
}
try
{
var response = await _httpClient.PostAsync(Endpoint, TransformData(content));
if (!response.IsSuccessStatusCode)
{
throw new NotifierException($"Feishu webhook call failed with code: {response.StatusCode}");
}
}
catch (NotifierException)
{
throw;
}
catch (System.Exception ex)
{
throw new NotifierException($"Error sending Feishu webhook: {ex.Message}");
}
}
private StringContent TransformData(BaseNotificationData notificationData)
{
var feishuMessage = new
{
msg_type = "text",
content = new
{
text = notificationData.Message
}
};
var serializedData = JsonSerializer.Serialize(feishuMessage);
return new StringContent(serializedData, Encoding.UTF8, "application/json");
}
}

View File

@@ -1,5 +1,6 @@
using System.Net.Http;
using System.Threading.Tasks;
using BetterGenshinImpact.Service.Notification.Model;
namespace BetterGenshinImpact.Service.Notifier.Interface;
@@ -7,6 +8,5 @@ public interface INotifier
{
string Name { get; }
// TODO: replace HttpContent with another data structure
Task SendNotificationAsync(HttpContent content);
Task SendAsync(BaseNotificationData data);
}

View File

@@ -1,10 +1,9 @@
using BetterGenshinImpact.Service.Notifier.Interface;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using BetterGenshinImpact.Service.Notification.Model;
namespace BetterGenshinImpact.Service.Notifier;
@@ -38,31 +37,30 @@ public class NotifierManager
return _notifiers.FirstOrDefault(o => o is T);
}
public async Task SendNotificationAsync(INotifier notifier, HttpContent httpContent)
public async Task SendNotificationAsync(INotifier notifier, BaseNotificationData content)
{
try
{
await notifier.SendNotificationAsync(httpContent);
await notifier.SendAsync(content);
}
catch (System.Exception ex)
{
Logger.LogError("{name} 通知发送失败", notifier.Name);
Debug.WriteLine(ex);
Logger.LogWarning("{name} 通知发送失败: {ex}", notifier.Name, ex.Message);
}
}
public async Task SendNotificationAsync<T>(HttpContent httpContent) where T : INotifier
public async Task SendNotificationAsync<T>(BaseNotificationData content) where T : INotifier
{
var notifier = _notifiers.FirstOrDefault(o => o is T);
if (notifier != null)
{
await SendNotificationAsync(notifier, httpContent);
await SendNotificationAsync(notifier, content);
}
}
public async Task SendNotificationToAllAsync(HttpContent httpContent)
public async Task SendNotificationToAllAsync(BaseNotificationData content)
{
await Task.WhenAll(_notifiers.Select(notifier => SendNotificationAsync(notifier, httpContent)));
await Task.WhenAll(_notifiers.Select(notifier => SendNotificationAsync(notifier, content)));
}
}

View File

@@ -1,7 +1,10 @@
using BetterGenshinImpact.Service.Notifier.Exception;
using BetterGenshinImpact.Service.Notifier.Interface;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using BetterGenshinImpact.Service.Notification.Model;
namespace BetterGenshinImpact.Service.Notifier;
@@ -12,6 +15,11 @@ public class WebhookNotifier : INotifier
public string Endpoint { get; set; }
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
};
public WebhookNotifier(HttpClient httpClient, string endpoint = "")
{
@@ -19,11 +27,16 @@ public class WebhookNotifier : INotifier
Endpoint = endpoint;
}
public async Task SendNotificationAsync(HttpContent content)
public async Task SendAsync(BaseNotificationData content)
{
if (string.IsNullOrEmpty(Endpoint))
{
throw new NotifierException("Webhook 地址为空");
}
try
{
var response = await _httpClient.PostAsync(Endpoint, content);
var response = await _httpClient.PostAsync(Endpoint, TransformData(content));
if (!response.IsSuccessStatusCode)
{
@@ -39,4 +52,13 @@ public class WebhookNotifier : INotifier
throw new NotifierException($"Error sending webhook: {ex.Message}");
}
}
}
private StringContent TransformData(BaseNotificationData notificationData)
{
// using object type here so it serializes the interface correctly
var serializedData = JsonSerializer.Serialize<object>(notificationData, _jsonSerializerOptions);
return new StringContent(serializedData, Encoding.UTF8, "application/json");
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.IO;
using System.Threading.Tasks;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Service.Notification.Model;
using BetterGenshinImpact.Service.Notifier.Interface;
using Microsoft.Toolkit.Uwp.Notifications;
namespace BetterGenshinImpact.Service.Notifier;
public class WindowsUwpNotifier : INotifier
{
public string Name => "Windows通知";
public Task SendAsync(BaseNotificationData data)
{
var toastBuilder = new ToastContentBuilder();
if (data.Screenshot != null)
{
string uniqueFileName = $"notification_image_{Guid.NewGuid()}.png";
string imagePath = Path.Combine(TempManager.GetTempDirectory(), uniqueFileName);
data.Screenshot.Save(imagePath, System.Drawing.Imaging.ImageFormat.Png);
toastBuilder.AddHeroImage(new Uri(imagePath));
}
if (!string.IsNullOrEmpty(data.Message))
{
toastBuilder.AddText(data.Message);
}
toastBuilder.Show(toast =>
{
toast.Group = data.Event.ToString();
toast.ExpirationTime = DateTime.Now.AddHours(12);
});
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,66 @@
using BetterGenshinImpact.Service.Notifier.Exception;
using BetterGenshinImpact.Service.Notifier.Interface;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using BetterGenshinImpact.Service.Notification.Model;
namespace BetterGenshinImpact.Service.Notifier;
public class WorkWeixinNotifier : INotifier
{
public string Name { get; set; } = "WorkWeixin";
public string Endpoint { get; set; }
private readonly HttpClient _httpClient;
public WorkWeixinNotifier(HttpClient httpClient, string endpoint = "")
{
_httpClient = httpClient;
Endpoint = endpoint;
}
public async Task SendAsync(BaseNotificationData content)
{
if (string.IsNullOrEmpty(Endpoint))
{
throw new NotifierException("WorkWeixin webhook endpoint is not set");
}
try
{
var response = await _httpClient.PostAsync(Endpoint, TransformData(content));
if (!response.IsSuccessStatusCode)
{
throw new NotifierException($"WorkWeixin webhook call failed with code: {response.StatusCode}");
}
}
catch (NotifierException)
{
throw;
}
catch (System.Exception ex)
{
throw new NotifierException($"Error sending WorkWeixin webhook: {ex.Message}");
}
}
private StringContent TransformData(BaseNotificationData notificationData)
{
var workweixinMessage = new
{
msgtype = "text",
text = new
{
content = notificationData.Message
}
};
var serializedData = JsonSerializer.Serialize(workweixinMessage);
return new StringContent(serializedData, Encoding.UTF8, "application/json");
}
}

View File

@@ -15,13 +15,32 @@ using System.Threading.Tasks;
using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.Service.Notification;
using BetterGenshinImpact.Service.Notification.Model.Enum;
namespace BetterGenshinImpact.Service;
public partial class ScriptService : IScriptService
{
private readonly ILogger<ScriptService> _logger = App.GetLogger<ScriptService>();
private static bool IsCurrentHourEqual(string input)
{
// 尝试将输入字符串转换为整数
if (int.TryParse(input, out int hour))
{
// 验证小时是否在合法范围内0-23
if (hour is >= 0 and <= 23)
{
// 获取当前小时数
int currentHour = DateTime.Now.Hour;
// 判断是否相等
return currentHour == hour;
}
}
// 如果输入非数字或不合法,返回 false
return false;
}
public async Task RunMulti(IEnumerable<ScriptGroupProject> projectList, string? groupName = null)
{
groupName ??= "默认";
@@ -51,7 +70,9 @@ public partial class ScriptService : IScriptService
}
var timerOperation = hasTimer ? DispatcherTimerOperationEnum.UseCacheImageWithTriggerEmpty : DispatcherTimerOperationEnum.UseSelfCaptureImage;
Notify.Event(NotificationEvent.GroupStart).Success($"配置组{groupName}启动");
await new TaskRunner(timerOperation)
.RunThreadAsync(async () =>
{
@@ -59,6 +80,13 @@ public partial class ScriptService : IScriptService
foreach (var project in list)
{
if (project.GroupInfo is { Config.PathingConfig.Enabled: true } && IsCurrentHourEqual(project.GroupInfo.Config.PathingConfig.SkipDuring))
{
_logger.LogInformation($"{project.Name}任务已到禁止执行时段,将跳过!");
continue;
}
if (project.Status != "Enabled")
{
_logger.LogInformation("脚本 {Name} 状态为禁用,跳过执行", project.Name);
@@ -85,6 +113,14 @@ public partial class ScriptService : IScriptService
stopwatch.Reset();
stopwatch.Start();
await ExecuteProject(project);
//多次执行时及时中断
if (project.GroupInfo is { Config.PathingConfig.Enabled: true } && IsCurrentHourEqual(project.GroupInfo.Config.PathingConfig.SkipDuring))
{
_logger.LogInformation($"{project.Name}任务已到禁止执行时段,将跳过!");
break;
}
}
catch (NormalEndException e)
{
@@ -104,10 +140,11 @@ public partial class ScriptService : IScriptService
{
stopwatch.Stop();
var elapsedTime = TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds);
_logger.LogDebug("→ 脚本执行结束: {Name}, 耗时: {ElapsedMilliseconds} 毫秒", project.Name, stopwatch.ElapsedMilliseconds);
// _logger.LogDebug("→ 脚本执行结束: {Name}, 耗时: {ElapsedMilliseconds} 毫秒", project.Name, stopwatch.ElapsedMilliseconds);
_logger.LogInformation("→ 脚本执行结束: {Name}, 耗时: {Minutes}分{Seconds:0.000}秒", project.Name,
elapsedTime.Hours * 60 + elapsedTime.Minutes, elapsedTime.TotalSeconds % 60);
_logger.LogInformation("------------------------------");
}
await Task.Delay(2000);
@@ -119,6 +156,7 @@ public partial class ScriptService : IScriptService
{
_logger.LogInformation("配置组 {Name} 执行结束", groupName);
}
Notify.Event(NotificationEvent.GroupEnd).Success($"配置组{groupName}结束");
}
private List<ScriptGroupProject> ReloadScriptProjects(IEnumerable<ScriptGroupProject> projectList, ref bool hasTimer)
@@ -175,6 +213,7 @@ public partial class ScriptService : IScriptService
private async Task ExecuteProject(ScriptGroupProject project)
{
if (project.Type == "Javascript")
{
if (project.Project == null)
@@ -218,7 +257,7 @@ public partial class ScriptService : IScriptService
private static partial Regex DispatcherAddTimerRegex();
public static async Task StartGameTask()
public static async Task StartGameTask(bool waitForMainUi = true)
{
// 没启动时候,启动截图器
var homePageViewModel = App.GetService<HomePageViewModel>();
@@ -226,29 +265,33 @@ public partial class ScriptService : IScriptService
{
await homePageViewModel.OnStartTriggerAsync();
await Task.Run(() =>
if (waitForMainUi)
{
var first = true;
while (true)
await Task.Run(() =>
{
if (!homePageViewModel.TaskDispatcherEnabled || !TaskContext.Instance().IsInitialized)
var first = true;
while (true)
{
continue;
}
if (!homePageViewModel.TaskDispatcherEnabled || !TaskContext.Instance().IsInitialized)
{
continue;
}
var content = TaskControl.CaptureToRectArea();
if (Bv.IsInMainUi(content) || Bv.IsInAnyClosableUi(content))
{
return;
}
var content = TaskControl.CaptureToRectArea();
if (Bv.IsInMainUi(content) || Bv.IsInAnyClosableUi(content))
{
return;
}
if (first)
{
first = false;
TaskControl.Logger.LogInformation("当前不在游戏主界面,等待进入主界面后执行任务...");
if (first)
{
first = false;
TaskControl.Logger.LogInformation("当前不在游戏主界面,等待进入主界面后执行任务...");
TaskControl.Logger.LogInformation("如果你已经在游戏内的其他界面请自行退出当前界面ESC使当前任务能够继续运行");
}
}
}
});
});
}
}
}
}

View File

@@ -26,9 +26,8 @@ public class UpdateService : IUpdateService
private readonly ILogger<UpdateService> _logger;
private readonly IConfigService _configService;
private const string HashUrl = "https://raw.githubusercontent.com/bettergi/bettergi-installation-data/refs/heads/main/hash.json";
private const string NoticeUrl = "https://hui-config.oss-cn-hangzhou.aliyuncs.com/bgi/notice.json";
private const string DownloadPageUrl = "https://bgi.huiyadan.com/download.html";
private const string DownloadPageUrl = "https://bettergi.com/download.html";
public AllConfig Config { get; set; }
@@ -61,16 +60,22 @@ public class UpdateService : IUpdateService
if (!Global.IsNewVersion(newVersion))
{
if (option.Trigger == UpdateTrigger.Manual)
{
await MessageBox.InformationAsync("当前已是最新版本!");
}
return;
}
if (!string.IsNullOrEmpty(Config.NotShowNewVersionNoticeEndVersion)
&& !Global.IsNewVersion(Config.NotShowNewVersionNoticeEndVersion, newVersion))
&& !Global.IsNewVersion(Config.NotShowNewVersionNoticeEndVersion, newVersion)
&& option.Trigger == UpdateTrigger.Auto)
{
return;
}
CheckUpdateWindow win = new()
CheckUpdateWindow win = new(option)
{
Owner = Application.Current.MainWindow,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
@@ -78,7 +83,6 @@ public class UpdateService : IUpdateService
UserInteraction = async (sender, button) =>
{
CheckUpdateWindow win = (CheckUpdateWindow)sender;
CancellationTokenSource? tokenSource = new();
switch (button)
{
@@ -114,20 +118,8 @@ public class UpdateService : IUpdateService
break;
case CheckUpdateWindow.CheckUpdateWindowButton.Cancel:
if (tokenSource != null)
{
if (MessageBox.Question("正在更新中,确定要取消更新吗?") == MessageBoxResult.Yes)
{
win.ShowUpdateStatus = false;
tokenSource?.Cancel();
win.Close();
}
}
else
{
win.ShowUpdateStatus = false;
win.Close();
}
win.ShowUpdateStatus = false;
win.Close();
break;
}
}

View File

@@ -324,6 +324,7 @@
"百晓",
"伍德",
"蓝川丞",
"蓝砚",
"松本",
"甘乐",
"卯师傅",

View File

@@ -4,6 +4,7 @@ using Microsoft.Web.WebView2.Wpf;
using System;
using System.Diagnostics;
using System.IO;
using System.Security.AccessControl;
using System.Text;
using System.Threading;
using System.Windows;
@@ -31,6 +32,7 @@ public class WebpagePanel : UserControl
}
else
{
EnsureWebView2DataFolder();
_webView = new WebView2()
{
CreationProperties = new CoreWebView2CreationProperties
@@ -155,4 +157,19 @@ public class WebpagePanel : UserControl
return button;
}
private void EnsureWebView2DataFolder()
{
try
{
string folder = Path.Combine(new FileInfo(Environment.ProcessPath!).DirectoryName!, @"WebView2Data\\");
Directory.CreateDirectory(folder);
DirectoryInfo info = new DirectoryInfo(folder);
DirectorySecurity access = info.GetAccessControl();
access.AddAccessRule(new FileSystemAccessRule("Everyone", FileSystemRights.FullControl, AccessControlType.Allow));
info.SetAccessControl(access);
}
catch { }
}
}

View File

@@ -83,7 +83,7 @@
<ui:SymbolIcon Symbol="AnimalTurtle24" />
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem Content="全自动">
<ui:NavigationViewItem.Icon>
<ui:SymbolIcon Symbol="Bot24" />
@@ -125,7 +125,7 @@
</ui:NavigationViewItem>
</ui:NavigationViewItem.MenuItems>
</ui:NavigationViewItem>
<ui:NavigationViewItem Content="辅助操控"
NavigationCacheMode="Enabled"
TargetPageType="{x:Type pages:MacroSettingsPage}">
@@ -180,13 +180,14 @@
<ui:TitleBar.Header>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<ui:Button Width="46"
Height="32"
Background="Transparent"
BorderBrush="Transparent"
Command="{Binding SwitchBackdropCommand}"
CornerRadius="0"
Icon="{ui:SymbolIcon Blur24}"
ToolTip="{Binding CurrentBackdropType}" />
Height="32"
Background="Transparent"
BorderBrush="Transparent"
Command="{Binding SwitchBackdropCommand}"
CornerRadius="0"
Icon="{ui:SymbolIcon Blur24}"
ToolTip="切换毛玻璃效果"
Visibility="{Binding IsWin11Later, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}" />
<ui:Button Width="46"
Height="32"
Background="Transparent"

View File

@@ -19,6 +19,7 @@ using System.Windows.Documents;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using BetterGenshinImpact.Genshin.Settings2;
using Vanara.PInvoke;
using FontFamily = System.Windows.Media.FontFamily;
@@ -174,9 +175,22 @@ public partial class MaskWindow : Window
{
_logger.LogError("当前游戏分辨率不是16:9一条龙、配队识别、地图传送、地图追踪等所有独立任务与全自动任务相关功能都将会无法正常使用");
}
AfterburnerWarning();
// 读取游戏注册表配置
// ReadGameSettings();
GameSettingsChecker.LoadGameSettingsAndCheck();
}
/**
* MSIAfterburner.exe 在左上角会导致识别失败
*/
private void AfterburnerWarning()
{
if (Process.GetProcessesByName("MSIAfterburner").Length > 0)
{
_logger.LogWarning("检测到 MSI Afterburner 正在运行如果信息位于特定UI上遮盖图像识别要素可能导致识别失败请关闭MSI Afterburner 或者调整信息位置后重试!");
}
}
// private void ReadGameSettings()

View File

@@ -3,9 +3,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:BetterGenshinImpact.Helpers"
xmlns:local="clr-namespace:BetterGenshinImpact.View.Pages"
xmlns:markup="clr-namespace:BetterGenshinImpact.Markup"
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"
@@ -81,7 +78,9 @@
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap">
在遮罩内显示日志窗口,<Hyperlink Command="{Binding GoToLogFolderCommand}" Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}">
在遮罩内显示日志窗口,
<Hyperlink Command="{Binding GoToLogFolderCommand}"
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}">
点击打开日志文件夹
</Hyperlink>
</ui:TextBlock>
@@ -333,7 +332,9 @@
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap">
可以通过快捷键保存截图,文件保存在<Hyperlink Command="{Binding GoToFolderCommand}" Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}">
可以通过快捷键保存截图,文件保存在
<Hyperlink Command="{Binding GoToFolderCommand}"
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}">
log/screenshot
</Hyperlink>
</ui:TextBlock>
@@ -431,11 +432,98 @@
</ui:CardControl.Header>
<ui:ToggleSwitch Margin="0,0,36,0" IsChecked="{Binding Config.CommonConfig.ExitToTray, Mode=TwoWay}" />
</ui:CardControl>
<ui:TextBlock Margin="0,0,0,8"
FontTypography="BodyStrong"
Text="通知设置" />
<!-- 全局通知设置 -->
<ui:CardExpander Margin="0,0,0,12"
ContentPadding="0"
Icon="{ui:SymbolIcon Alert24}">
<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:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,24,0"
IsChecked="{Binding Config.NotificationConfig.WebhookEnabled, Mode=TwoWay}" />-->
</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="总是在通知中包含截图"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,36,0"
IsChecked="{Binding Config.NotificationConfig.IncludeScreenShot, 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" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="220"
MaxWidth="800"
Margin="0,0,36,0"
Text="{Binding Config.NotificationConfig.NotificationEventSubscribe, Mode=TwoWay}"/>
</Grid>
</StackPanel>
</ui:CardExpander>
<!-- Webhook -->
<ui:CardExpander Margin="0,0,0,12"
ContentPadding="0"
@@ -453,7 +541,7 @@
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
FontTypography="Body"
Text="启用Webhook"
Text="启用 Webhook"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
@@ -533,6 +621,405 @@
</StackPanel>
</ui:CardExpander>
<!-- Windows 通知 -->
<ui:CardExpander Margin="0,0,0,12"
ContentPadding="0"
Icon="{ui:SymbolIcon CommentArrowLeft24}">
<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="启用 Windows 通知"
TextWrapping="Wrap" />
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="Windows 通知别与游戏界面重叠,否则易误点通知"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,24,0"
IsChecked="{Binding Config.NotificationConfig.WindowsUwpNotificationEnabled, Mode=TwoWay}" />
</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="测试 Windows 通知"
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 TestWindowsUwpNotificationCommand}"
Content="发送" />
</Grid>
</StackPanel>
</ui:CardExpander>
<ui:CardExpander Margin="0,0,0,12"
ContentPadding="0"
Icon="{ui:SymbolIcon CommentArrowLeft24}">
<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:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,24,0"
IsChecked="{Binding Config.NotificationConfig.FeishuNotificationEnabled, Mode=TwoWay}" />
</Grid>
</ui:CardExpander.Header>
<StackPanel>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="2*" />
</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:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="180"
MaxWidth="800"
Margin="0,0,36,0"
Text="{Binding Config.NotificationConfig.FeishuWebhookUrl, Mode=TwoWay}"
TextWrapping="NoWrap" />
</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" />
<ui:Button Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,36,0"
Command="{Binding TestFeishuNotificationCommand}"
Content="发送" />
</Grid>
</StackPanel>
</ui:CardExpander>
<ui:CardExpander Margin="0,0,0,12"
ContentPadding="0"
Icon="{ui:SymbolIcon CommentArrowLeft24}">
<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:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,24,0"
IsChecked="{Binding Config.NotificationConfig.WorkweixinNotificationEnabled, Mode=TwoWay}" />
</Grid>
</ui:CardExpander.Header>
<StackPanel>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="2*" />
</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:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="180"
MaxWidth="800"
Margin="0,0,36,0"
Text="{Binding Config.NotificationConfig.WorkweixinWebhookUrl, Mode=TwoWay}"
TextWrapping="NoWrap" />
</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" />
<ui:Button Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,36,0"
Command="{Binding TestWorkWeixinNotificationCommand}"
Content="发送" />
</Grid>
</StackPanel>
</ui:CardExpander>
<ui:TextBlock Margin="0,0,0,8"
FontTypography="BodyStrong"
Text="通用功能置" />
<!-- 快速传送 -->
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0">
<ui:CardExpander.Icon>
<ui:FontIcon Glyph="&#xf3c5;" Style="{StaticResource FaFontIconStyle}" />
</ui:CardExpander.Icon>
<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" />
</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="建议开启,关闭该设置可能在运行部分脚本时发生错误"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,36,0"
IsChecked="{Binding Config.TpConfig.MapZoomEnabled, 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" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="90"
Margin="0,0,36,0"
Text="{Binding Config.TpConfig.MaxMouseMove, 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" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="90"
Margin="0,0,36,0"
Text="{Binding Config.TpConfig.MapZoomOutDistance, 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" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="90"
Margin="0,0,36,0"
Text="{Binding Config.TpConfig.MapZoomInDistance, 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" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="90"
Margin="0,0,36,0"
Text="{Binding Config.TpConfig.StepIntervalMilliseconds, Mode=TwoWay}" />
</Grid>
</StackPanel>
</ui:CardExpander>
<!-- 地图 -->
<!--<ui:CardControl Margin="0,0,0,12"
Icon="{ui:SymbolIcon Cursor24}"

View File

@@ -29,7 +29,7 @@
<Grid>
<Border ClipToBounds="True" CornerRadius="8">
<Border.Background>
<ImageBrush ImageSource="pack://application:,,,/Assets/Images/banner_sayu.jpg"
<ImageBrush ImageSource="pack://application:,,,/Assets/Images/banner.jpg"
RenderOptions.BitmapScalingMode="HighQuality"
Stretch="UniformToFill" />
</Border.Background>

View File

@@ -473,7 +473,7 @@
<ui:CardControl Margin="0,0,0,12">
<ui:CardControl.Icon>
<ui:FontIcon Glyph="&#xf14e;" Style="{StaticResource FaFontIconStyle}" />
<ui:FontIcon Glyph="&#xf68a;" Style="{StaticResource FaFontIconStyle}" />
</ui:CardControl.Icon>
<ui:CardControl.Header>
<Grid>
@@ -541,7 +541,7 @@
<ui:CardControl Margin="0,0,0,12">
<ui:CardControl.Icon>
<ui:FontIcon Glyph="&#xf14e;" Style="{StaticResource FaFontIconStyle}" />
<ui:FontIcon Glyph="&#xf4c7;" Style="{StaticResource FaFontIconStyle}" />
</ui:CardControl.Icon>
<ui:CardControl.Header>
<Grid>

View File

@@ -217,6 +217,7 @@
<MenuItem Header="日志分析" Command="{Binding OpenLogParseCommand}" />
<MenuItem Header="打开脚本仓库" Command="{Binding OpenLocalScriptRepoCommand}" />
<MenuItem Header="根据文件夹更新" Command="{Binding UpdateTasksCommand}" />
<MenuItem Header="任务倒序排列" Command="{Binding ReverseTaskOrderCommand}" />
</ContextMenu>
</ui:DropDownButton.Flyout>
</ui:DropDownButton>
@@ -330,6 +331,22 @@
<MenuItem Command="{Binding DeleteScriptCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}"
Header="移除" />
<MenuItem Command="{Binding DeleteScriptByFolderCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}"
Header="根据文件夹移除">
<MenuItem.Style>
<Style TargetType="MenuItem">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem.Type}"
Value="Pathing">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
</ContextMenu>
</ListView.ContextMenu>
<ListView.Style>

View File

@@ -1287,7 +1287,7 @@
</ui:TextBlock>
</Grid>
</ui:CardControl.Header>
<controls:TwoStateButton Margin="0,0,36,0"
<controls:TwoStateButton Margin="0,0,40,0"
DisableCommand="{Binding StopSoloTaskCommand}"
DisableContent="停止"
EnableCommand="{Binding SwitchAutoMusicGameCommand}"
@@ -1296,7 +1296,7 @@
</ui:CardControl>
<!-- 自动音游专辑 -->
<ui:CardExpander Margin="0,0,0,12">
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0">
<ui:CardExpander.Icon>
<ui:FontIcon Glyph="&#xf89f;" Style="{StaticResource FaFontIconStyle}" />
</ui:CardExpander.Icon>
@@ -1350,12 +1350,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="默认为【传说】,【大师】即可获取所有奖励,【所有】即全部难度等级的所有乐曲达成【大音天籁】"
Text="关闭时,奖励已经领取就会跳过乐曲。开启时,达成【大音天籁】才会跳过乐曲"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,36,0"
IsChecked="{Binding Config.AutoMusicGameConfig.MustCanorusLevel, 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"

View File

@@ -779,165 +779,7 @@
</StackPanel>
</ui:CardExpander>
<!-- 快速传送 -->
<ui:CardExpander Margin="0,0,0,12" ContentPadding="0">
<ui:CardExpander.Icon>
<ui:FontIcon Glyph="&#xf3c5;" Style="{StaticResource FaFontIconStyle}" />
</ui:CardExpander.Icon>
<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" />
</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="建议开启,关闭该设置可能在运行部分脚本时发生错误"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,36,0"
IsChecked="{Binding Config.TpConfig.MapZoomEnabled, 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" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="90"
Margin="0,0,36,0"
Text="{Binding Config.TpConfig.MaxMouseMove, 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" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="90"
Margin="0,0,36,0"
Text="{Binding Config.TpConfig.MapZoomOutDistance, 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" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="90"
Margin="0,0,36,0"
Text="{Binding Config.TpConfig.MapZoomInDistance, 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" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="90"
Margin="0,0,36,0"
Text="{Binding Config.TpConfig.StepIntervalMilliseconds, Mode=TwoWay}" />
</Grid>
</StackPanel>
</ui:CardExpander>
<!-- 自动烹饪 -->
<ui:CardControl Margin="0,0,0,12">
<ui:CardControl.Icon>

View File

@@ -282,6 +282,32 @@
IsChecked="{Binding PathingConfig.SoloTaskUseFightEnabled, 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="当执行完一个路线后如果时间为当前配置的时间范围0-23则此路径追踪任务后续都将都跳过适用于连续执行的兜底任务例如想通宵挂机并且在4点后开始执行新的任务。"
TextWrapping="Wrap" />
<ui:TextBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
MinWidth="100"
Text="{Binding PathingConfig.SkipDuring, Mode=TwoWay}"
TextWrapping="NoWrap" />
</Grid>
</StackPanel>
</ui:CardExpander>
<ui:CardExpander Margin="0,0,0,12"

View File

@@ -51,7 +51,8 @@
Appearance="Success"
Command="{Binding UpdateCommand}"
Content="立即更新" />
<ui:Button Grid.Column="2"
<ui:Button Name="IgnoreButton"
Grid.Column="2"
MinWidth="90"
Margin="8,0,8,0"
Command="{Binding IgnoreCommand}"

View File

@@ -3,6 +3,8 @@ using CommunityToolkit.Mvvm.Input;
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows;
using BetterGenshinImpact.Model;
using Wpf.Ui.Controls;
namespace BetterGenshinImpact.View.Windows;
@@ -18,11 +20,16 @@ public partial class CheckUpdateWindow : FluentWindow
[ObservableProperty]
private string updateStatusMessage = string.Empty;
public CheckUpdateWindow()
public CheckUpdateWindow(UpdateOption option)
{
DataContext = this;
InitializeComponent();
if (option.Trigger == UpdateTrigger.Manual)
{
IgnoreButton.Visibility = Visibility.Collapsed;
}
Closing += OnClosing;
}
@@ -92,4 +99,4 @@ public partial class CheckUpdateWindow : FluentWindow
Ignore,
Cancel,
}
}
}

View File

@@ -34,13 +34,16 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel
public string Title => $"BetterGI · 更好的原神 · {Global.Version}{(RuntimeHelper.IsDebug ? " · Dev" : string.Empty)}";
[ObservableProperty]
public bool _isVisible = true;
private bool _isVisible = true;
[ObservableProperty]
public WindowState _windowState = WindowState.Normal;
private WindowState _windowState = WindowState.Normal;
[ObservableProperty]
public WindowBackdropType _currentBackdropType = WindowBackdropType.Auto;
private WindowBackdropType _currentBackdropType = WindowBackdropType.Auto;
[ObservableProperty]
private bool _isWin11Later = OsVersionHelper.IsWindows11_OrGreater;
public AllConfig Config { get; set; }
@@ -102,22 +105,30 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel
{
// 预热OCR
await OcrPreheating();
if (Environment.GetCommandLineArgs().Length > 1)
{
return;
}
// 自动处理目录配置
await Patch1();
// 首次运行
if (Config.CommonConfig.IsFirstRun)
{
// 自动初始化键位绑定
InitKeyBinding();
// InitKeyBinding();
Config.AutoFightConfig.TeamNames = ""; // 此配置以后无用
Config.CommonConfig.IsFirstRun = false;
}
// 版本是否运行过
if (Config.CommonConfig.RunForVersion != Global.Version)
{
ModifyFolderSecurity();
Config.CommonConfig.RunForVersion = Global.Version;
}
// 检查更新
@@ -131,9 +142,23 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel
// 更新仓库
ScriptRepoUpdater.Instance.AutoUpdate();
// 清理临时目录
TempManager.CleanUp();
}
private void ModifyFolderSecurity()
{
// 检查程序是否位于C盘
if (Global.StartUpPath.StartsWith(@"C:", StringComparison.OrdinalIgnoreCase))
{
// 修改文件夹权限
SecurityControlHelper.AllowFullFolderSecurity(Global.StartUpPath);
}
}
/*
private void InitKeyBinding()
{
try
@@ -151,6 +176,7 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel
MessageBox.Error("读取原神键位并设置键位绑定数据时发生异常:" + e.Message + ",后续可以手动设置");
}
}
*/
/**
* 不同的安装目录处理
@@ -158,19 +184,28 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel
*/
private async Task Patch1()
{
if (Directory.Exists(Global.Absolute("BetterGI"))
// && File.Exists(Global.Absolute("BetterGI/BetterGI.exe"))
&& Directory.Exists(Global.Absolute("BetterGI/User"))
var embeddedPath = Global.Absolute("BetterGI");
var embeddedUserPath = Global.Absolute("BetterGI/User");
var exePath = Global.Absolute("BetterGI/BetterGI.exe");
if (Directory.Exists(embeddedPath)
&& File.Exists(exePath)
&& Directory.Exists(embeddedUserPath)
)
{
var res = await MessageBox.ShowAsync("检测到旧的 BetterGI 配置,是否迁移配置并清理旧目录?", "BetterGI", System.Windows.MessageBoxButton.YesNo, MessageBoxImage.Question);
if (res == System.Windows.MessageBoxResult.Yes)
var fileVersionInfo = FileVersionInfo.GetVersionInfo(exePath);
// 低版本才需要迁移
if (fileVersionInfo.FileVersion != null && !Global.IsNewVersion(fileVersionInfo.FileVersion))
{
var dir = Global.Absolute("BetterGI/User");
// 迁移配置,拷贝整个目录并覆盖
DirectoryHelper.CopyDirectory(dir, Global.Absolute("User"));
// 删除旧目录
DirectoryHelper.DeleteDirectoryRecursively(Global.Absolute("BetterGI"));
var res = await MessageBox.ShowAsync("检测到旧的 BetterGI 配置,是否迁移配置并清理旧目录?", "BetterGI", System.Windows.MessageBoxButton.YesNo, MessageBoxImage.Question);
if (res == System.Windows.MessageBoxResult.Yes)
{
// 迁移配置,拷贝整个目录并覆盖
DirectoryHelper.CopyDirectory(embeddedUserPath, Global.Absolute("User"));
// 删除旧目录
DirectoryHelper.DeleteReadOnlyDirectory(embeddedPath);
await MessageBox.InformationAsync("迁移配置成功, 软件将自动退出,请手动重新启动 BetterGI");
Application.Current.Shutdown();
}
}
}
}
@@ -189,11 +224,11 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel
catch (Exception e)
{
Console.WriteLine(e);
_logger.LogError("PaddleOcr预热异常解决方案https://bgi.huiyadan.com/faq.html" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message);
_logger.LogError("PaddleOcr预热异常解决方案https://bettergi.com/faq.html" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message);
var innerException = e.InnerException;
if (innerException != null)
{
_logger.LogError("PaddleOcr预热内部异常解决方案https://bgi.huiyadan.com/faq.html" + innerException.Source + "\r\n--" + Environment.NewLine + innerException.StackTrace + "\r\n---" + Environment.NewLine + innerException.Message);
_logger.LogError("PaddleOcr预热内部异常解决方案https://bettergi.com/faq.html" + innerException.Source + "\r\n--" + Environment.NewLine + innerException.StackTrace + "\r\n---" + Environment.NewLine + innerException.Message);
throw innerException;
}
else
@@ -205,7 +240,7 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel
}
catch (Exception e)
{
MessageBox.Warning("PaddleOcr预热失败解决方案https://bgi.huiyadan.com/faq.html" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message);
MessageBox.Warning("PaddleOcr预热失败解决方案https://bettergi.com/faq.html" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message);
}
}
}

View File

@@ -12,6 +12,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using BetterGenshinImpact.Model;
using Vanara.PInvoke;
namespace BetterGenshinImpact.ViewModel;
@@ -44,40 +45,11 @@ public partial class NotifyIconViewModel : ObservableObject
[RelayCommand]
public async Task CheckUpdateAsync()
{
try
// 检查更新
await App.GetService<IUpdateService>()!.CheckUpdateAsync(new UpdateOption
{
using var httpClient = new HttpClient();
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");
var jsonDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonString);
if (jsonDict != null)
{
string? name = jsonDict["name"] as string;
string? body = jsonDict["body"] as string;
string md = $"# {name}{new string('\n', 2)}{body}";
md = WebUtility.HtmlEncode(md);
string md2html = ResourceHelper.GetString($"pack://application:,,,/Assets/Strings/md2html.html", Encoding.UTF8);
var html = md2html.Replace("{{content}}", md);
WebpageWindow win = new()
{
Title = "更新日志",
Width = 800,
Height = 600,
Owner = Application.Current.MainWindow,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
win.NavigateToHtml(html);
win.ShowDialog();
}
}
catch (Exception e)
{
_ = e;
}
Trigger = UpdateTrigger.Manual
});
}
}
@@ -100,6 +72,7 @@ file static class WindowBacktray
{
window.Visibility = Visibility.Visible;
}
if (window.WindowState == WindowState.Minimized)
{
nint hWnd = new WindowInteropHelper(Application.Current.MainWindow).Handle;
@@ -107,4 +80,4 @@ file static class WindowBacktray
}
}
}
}
}

View File

@@ -15,6 +15,7 @@ using BetterGenshinImpact.Service.Notifier;
using BetterGenshinImpact.View;
using Wpf.Ui;
using Wpf.Ui.Controls;
using Wpf.Ui.Violeta.Controls;
namespace BetterGenshinImpact.ViewModel.Pages;
@@ -23,12 +24,14 @@ public partial class CommonSettingsPageViewModel : ObservableObject, INavigation
public AllConfig Config { get; set; }
private readonly INavigationService _navigationService;
private readonly NotificationService _notificationService;
[ObservableProperty] private bool _isLoading;
[ObservableProperty] private string _webhookStatus = string.Empty;
private readonly NotificationService _notificationService;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private string _webhookStatus = string.Empty;
public CommonSettingsPageViewModel(IConfigService configService, INavigationService navigationService, NotificationService notificationService)
@@ -51,7 +54,7 @@ public partial class CommonSettingsPageViewModel : ObservableObject, INavigation
{
WeakReferenceMessenger.Default.Send(new PropertyChangedMessage<object>(this, "RefreshSettings", new object(), "重新计算控件位置"));
}
[RelayCommand]
private void OnSwitchMaskEnabled()
{
@@ -106,7 +109,7 @@ public partial class CommonSettingsPageViewModel : ObservableObject, INavigation
Process.Start("explorer.exe", path);
}
[RelayCommand]
private async Task OnTestWebhook()
{
@@ -119,4 +122,46 @@ public partial class CommonSettingsPageViewModel : ObservableObject, INavigation
IsLoading = false;
}
}
[RelayCommand]
private async Task OnTestWindowsUwpNotification()
{
var res = await _notificationService.TestNotifierAsync<WindowsUwpNotifier>();
if(res.IsSuccess)
{
Toast.Success(res.Message);
}
else
{
Toast.Error(res.Message);
}
}
[RelayCommand]
private async Task OnTestFeishuNotification()
{
var res = await _notificationService.TestNotifierAsync<FeishuNotifier>();
if(res.IsSuccess)
{
Toast.Success(res.Message);
}
else
{
Toast.Error(res.Message);
}
}
[RelayCommand]
private async Task OnTestWorkWeixinNotification()
{
var res = await _notificationService.TestNotifierAsync<WorkWeixinNotifier>();
if(res.IsSuccess)
{
Toast.Success(res.Message);
}
else
{
Toast.Error(res.Message);
}
}
}

View File

@@ -100,19 +100,22 @@ public partial class HomePageViewModel : ObservableObject, INavigationAware, IVi
private void OnLoaded()
{
// OnTest();
var args = Environment.GetCommandLineArgs();
// url protocol
// BetterGI.dll bettergi://start/
if (args.Length > 1)
{
if (args[1].Equals("start"))
{
_ = OnStartTriggerAsync();
}
else if (args[1].Equals("startOneDragon"))
if (args[1].Contains("startOneDragon"))
{
var odVm = App.GetService<OneDragonFlowViewModel>();
odVm?.OneKeyExecuteCommand.Execute(null);
}
else if (args[1].Contains("start"))
{
_ = OnStartTriggerAsync();
}
}
}
@@ -275,6 +278,7 @@ public partial class HomePageViewModel : ObservableObject, INavigationAware, IVi
TaskDispatcherEnabled = false;
_mouseKeyMonitor.Unsubscribe();
TaskContext.Instance().IsInitialized = false;
}
}
}
@@ -300,7 +304,7 @@ public partial class HomePageViewModel : ObservableObject, INavigationAware, IVi
[RelayCommand]
public void OnGoToWikiUrl()
{
Process.Start(new ProcessStartInfo("https://bgi.huiyadan.com/doc.html") { UseShellExecute = true });
Process.Start(new ProcessStartInfo("https://bettergi.com/doc.html") { UseShellExecute = true });
}
[RelayCommand]

View File

@@ -28,9 +28,13 @@ using System.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.GameTask.AutoFight.Assets;
using BetterGenshinImpact.GameTask.Model.Area;
using BetterGenshinImpact.GameTask.QuickTeleport.Assets;
using BetterGenshinImpact.View;
using OpenCvSharp;
using Vanara.PInvoke;
using HotKeySettingModel = BetterGenshinImpact.Model.HotKeySettingModel;
@@ -595,9 +599,20 @@ public partial class HotKeyPageViewModel : ObservableObject, IViewModel
// TaskControl.Logger.LogInformation("大地图界面缩放按钮位置:{Position}", Bv.GetBigMapScale( TaskControl.CaptureToRectArea()));
TaskControl.Logger.LogInformation($"尝试显示遮罩窗口");
var maskWindow = MaskWindow.Instance();
maskWindow.Invoke(() => { maskWindow.Hide(); });
// TaskControl.Logger.LogInformation($"尝试显示遮罩窗口");
// var maskWindow = MaskWindow.Instance();
// maskWindow.Invoke(() => { maskWindow.Show(); });
Task.Run(async () => {
for (int i = 0; i < 100; i++)
{
var imageRegion = TaskControl.CaptureToRectArea();
var eRa = imageRegion.DeriveCrop(AutoFightAssets.Instance.ECooldownRect);
var eRaWhite = OpenCvCommonHelper.InRangeHsv(eRa.SrcMat, new Scalar(0, 0, 235), new Scalar(0, 25, 255));
var text = OcrFactory.Paddle.OcrWithoutDetector(eRaWhite);
TaskControl.Logger.LogInformation("冷却时间 {Num}", StringUtils.TryParseDouble(text));
await Task.Delay(10);
}
});
}
));
debugDirectory.Children.Add(new HotKeySettingModel(

View File

@@ -118,7 +118,7 @@ public partial class JsListViewModel : ObservableObject, INavigationAware, IView
[RelayCommand]
public void OnGoToJsScriptUrl()
{
Process.Start(new ProcessStartInfo("https://bgi.huiyadan.com/feats/autos/jsscript.html") { UseShellExecute = true });
Process.Start(new ProcessStartInfo("https://bettergi.com/feats/autos/jsscript.html") { UseShellExecute = true });
}
[RelayCommand]

View File

@@ -229,7 +229,7 @@ public partial class KeyMouseRecordPageViewModel : ObservableObject, INavigation
[RelayCommand]
public void OnGoToKmScriptUrl()
{
Process.Start(new ProcessStartInfo("https://bgi.huiyadan.com/feats/autos/kmscript.html") { UseShellExecute = true });
Process.Start(new ProcessStartInfo("https://bettergi.com/feats/autos/kmscript.html") { UseShellExecute = true });
}
[RelayCommand]

View File

@@ -49,6 +49,6 @@ public partial class MacroSettingsPageViewModel : ObservableObject, INavigationA
[RelayCommand]
public void OnGoToOneKeyMacroUrl()
{
Process.Start(new ProcessStartInfo("https://bgi.huiyadan.com/feats/macro/onem.html") { UseShellExecute = true });
Process.Start(new ProcessStartInfo("https://bettergi.com/feats/macro/onem.html") { UseShellExecute = true });
}
}

View File

@@ -171,7 +171,7 @@ public partial class MapPathingViewModel : ObservableObject, INavigationAware, I
[RelayCommand]
public void OnGoToPathingUrl()
{
Process.Start(new ProcessStartInfo("https://bgi.huiyadan.com/feats/autos/pathing.html") { UseShellExecute = true });
Process.Start(new ProcessStartInfo("https://bettergi.com/feats/autos/pathing.html") { UseShellExecute = true });
}
[RelayCommand]

View File

@@ -14,6 +14,8 @@ using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.Model.Enum;
using BetterGenshinImpact.Service;
using BetterGenshinImpact.Service.Notification;
using BetterGenshinImpact.Service.Notification.Model.Enum;
using BetterGenshinImpact.View.Windows;
using CommunityToolkit.Mvvm.Input;
using Newtonsoft.Json;
@@ -207,6 +209,7 @@ public partial class OneDragonFlowViewModel : ObservableObject, INavigationAware
await new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage)
.RunThreadAsync(async () =>
{
Notify.Event(NotificationEvent.DragonStart).Success("一条龙启动");
foreach (var task in TaskList)
{
if (task is { IsEnabled: true, Action: not null })
@@ -215,6 +218,7 @@ public partial class OneDragonFlowViewModel : ObservableObject, INavigationAware
await Task.Delay(1000);
}
}
Notify.Event(NotificationEvent.DragonEnd).Success("一条龙结束");
});
}

View File

@@ -29,6 +29,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LogParse;
using Microsoft.Extensions.Logging;
using SharpCompress;
using Wpf.Ui;
using Wpf.Ui.Controls;
using Wpf.Ui.Violeta.Controls;
@@ -106,25 +107,32 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
[RelayCommand]
private void ClearTasks()
{
if (SelectedScriptGroup == null)
{
return;
}
SelectedScriptGroup.Projects.Clear();
WriteScriptGroup(SelectedScriptGroup);
}
[RelayCommand]
private async Task OpenLogParse()
{
GameInfo gameInfo = null;
if (SelectedScriptGroup == null)
{
return;
}
GameInfo? gameInfo = null;
var config = LogParse.LogParse.LoadConfig();
if (!string.IsNullOrEmpty(config.Cookie))
{
config.CookieDictionary.TryGetValue(config.Cookie, out gameInfo);
}
LogParseConfig.ScriptGroupLogParseConfig sgpc;
if (!config.ScriptGroupLogDictionary.TryGetValue(_selectedScriptGroup.Name,out sgpc))
LogParseConfig.ScriptGroupLogParseConfig? sgpc;
if (!config.ScriptGroupLogDictionary.TryGetValue(SelectedScriptGroup.Name,out sgpc))
{
sgpc=new LogParseConfig.ScriptGroupLogParseConfig();
}
// 创建 StackPanel
var stackPanel = new StackPanel
@@ -174,6 +182,16 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
dayRangeComboBox.SelectedValuePath = "Value"; // 绑定的值
dayRangeComboBox.SelectedIndex = 0;
stackPanel.Children.Add(dayRangeComboBox);
// 开关控件ToggleButton 或 CheckBox
CheckBox faultStatsSwitch = new CheckBox
{
Content = "异常情况统计",
VerticalAlignment = VerticalAlignment.Center
};
stackPanel.Children.Add(faultStatsSwitch);
// 开关控件ToggleButton 或 CheckBox
@@ -212,9 +230,39 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
secondRow.Children.Add(questionButton);
StackPanel threeRow = new StackPanel
{
Orientation = Orientation.Horizontal,
Margin = new Thickness(0, 0, 0, 10)
};
// 创建一个 TextBlock
TextBlock hoeingDelayBlock = new TextBlock
{
Text = "锄地延时(秒)",
VerticalAlignment = VerticalAlignment.Center,
FontSize = 16,
Margin = new Thickness(0, 0, 10, 0)
};
TextBox hoeingDelayTextBox = new TextBox
{
Width = 100,
FontSize = 16,
VerticalContentAlignment = VerticalAlignment.Center
};
threeRow.Children.Add(hoeingDelayBlock);
threeRow.Children.Add(hoeingDelayTextBox);
// 将第二行添加到 StackPanel
stackPanel.Children.Add(secondRow);
stackPanel.Children.Add(threeRow);
//PrimaryButtonText
var uiMessageBox = new Wpf.Ui.Controls.MessageBox
{
@@ -224,9 +272,9 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
PrimaryButtonText = "确定",
Owner = Application.Current.MainWindow,
};
questionButton.Click += (sender, args) =>
{
void OnQuestionButtonOnClick(object sender, RoutedEventArgs args)
{
WebpageWindow cookieWin = new()
{
Title = "日志分析",
@@ -237,15 +285,17 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
};
cookieWin.NavigateToHtml(TravelsDiaryDetailManager.generHtmlMessage());
cookieWin.Show();
}
};
questionButton.Click += OnQuestionButtonOnClick;
//对象赋值
rangeComboBox.SelectedValue = sgpc.RangeValue;
dayRangeComboBox.SelectedValue = sgpc.DayRangeValue;
cookieTextBox.Text = config.Cookie;
hoeingStatsSwitch.IsChecked = sgpc.HoeingStatsSwitch;
faultStatsSwitch.IsChecked = sgpc.FaultStatsSwitch;
hoeingDelayTextBox.Text = sgpc.HoeingDelay;
MessageBoxResult result = await uiMessageBox.ShowDialogAsync();
@@ -259,8 +309,11 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
sgpc.DayRangeValue=dayRangeValue;
sgpc.RangeValue = rangeValue;
sgpc.HoeingStatsSwitch = hoeingStatsSwitch.IsChecked ?? false;
sgpc.FaultStatsSwitch = faultStatsSwitch.IsChecked ?? false;
sgpc.HoeingDelay = hoeingDelayTextBox.Text;
config.Cookie = cookieValue;
config.ScriptGroupLogDictionary[_selectedScriptGroup.Name]=sgpc;
config.ScriptGroupLogDictionary[SelectedScriptGroup.Name]=sgpc;
LogParse.LogParse.WriteConfigFile(config);
@@ -294,7 +347,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
Toast.Warning("未填写cookie此次将不启用锄地统计");
}
//真正存储的gameinfo
GameInfo realGameInfo = gameInfo;
GameInfo? realGameInfo = gameInfo;
//统计锄地开关打开并且不为cookie不为空
if ((hoeingStatsSwitch.IsChecked ?? false) && !string.IsNullOrEmpty(cookieValue))
{
@@ -305,7 +358,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
Toast.Success($"米游社数据获取成功,开始进行解析,请耐心等待!");
}
catch (Exception e)
catch (Exception)
{
if (realGameInfo!=null)
{
@@ -333,7 +386,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
var configGroupEntities = LogParse.LogParse.ParseFile(fs);
if (rangeValue == "CurrentConfig") {
//Toast.Success(_selectedScriptGroup.Name);
configGroupEntities =configGroupEntities.Where(item => _selectedScriptGroup.Name == item.Name).ToList();
configGroupEntities =configGroupEntities.Where(item => SelectedScriptGroup.Name == item.Name).ToList();
}
if (configGroupEntities.Count == 0)
{
@@ -343,7 +396,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
configGroupEntities.Reverse();
//realGameInfo
//小怪摩拉统计
win.NavigateToHtml(LogParse.LogParse.GenerHtmlByConfigGroupEntity(configGroupEntities,hoeingStats ? realGameInfo : null));
win.NavigateToHtml(LogParse.LogParse.GenerHtmlByConfigGroupEntity(configGroupEntities,hoeingStats ? realGameInfo : null,sgpc));
win.ShowDialog();
}
@@ -369,11 +422,11 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
ScriptRepoUpdater.Instance.OpenLocalRepoInWebView();
}
[RelayCommand]
private async Task UpdateTasks()
private void UpdateTasks()
{
List<ScriptGroupProject> projects = new();
List<ScriptGroupProject> oldProjects = new();
oldProjects.AddRange(SelectedScriptGroup?.Projects);
oldProjects.AddRange(SelectedScriptGroup?.Projects ?? []);
var oldcount = oldProjects.Count;
List<string> folderNames = new();
foreach (var project in oldProjects)
@@ -406,17 +459,26 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
}
}
SelectedScriptGroup.Projects.Clear();
SelectedScriptGroup?.Projects.Clear();
foreach (var scriptGroupProject in projects)
{
SelectedScriptGroup?.AddProject(scriptGroupProject);
}
Toast.Success($"增加了{projects.Count - oldcount}个路径追踪任务");
WriteScriptGroup(SelectedScriptGroup);
if (SelectedScriptGroup != null) WriteScriptGroup(SelectedScriptGroup);
}
[RelayCommand]
private void ReverseTaskOrder()
{
List<ScriptGroupProject> projects = new();
projects.AddRange(SelectedScriptGroup?.Projects.Reverse() ?? []);
SelectedScriptGroup?.Projects.Clear();
projects.ForEach(item=>SelectedScriptGroup?.Projects.Add(item));
if (SelectedScriptGroup != null) WriteScriptGroup(SelectedScriptGroup);
}
[RelayCommand]
public void OnCopyScriptGroup(ScriptGroup? item)
@@ -444,8 +506,12 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
else
{
var newScriptGroup =JsonSerializer.Deserialize<ScriptGroup>(JsonSerializer.Serialize(item)) ;
newScriptGroup.Name = str;
ScriptGroups.Add(newScriptGroup);
if (newScriptGroup != null)
{
newScriptGroup.Name = str;
ScriptGroups.Add(newScriptGroup);
}
//WriteScriptGroup(newScriptGroup);
}
}
@@ -574,12 +640,26 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
private ScrollViewer CreatePathingScriptSelectionPanel(IEnumerable<FileTreeNode<PathingTask>> list)
{
var stackPanel = new StackPanel();
CheckBox excludeCheckBox = new CheckBox
{
Content = "排除已选择过的目录",
VerticalAlignment = VerticalAlignment.Center,
};
stackPanel.Children.Add(excludeCheckBox);
var filterTextBox = new TextBox
{
Margin = new Thickness(0, 0, 0, 10),
PlaceholderText = "输入筛选条件..."
PlaceholderText = "输入筛选条件...",
};
filterTextBox.TextChanged += delegate
{
ApplyFilter(stackPanel, list, filterTextBox.Text, excludeCheckBox.IsChecked);
};
excludeCheckBox.Click += delegate
{
ApplyFilter(stackPanel, list, filterTextBox.Text, excludeCheckBox.IsChecked);
};
filterTextBox.TextChanged += (s, e) => ApplyFilter(stackPanel, list, filterTextBox.Text);
stackPanel.Children.Add(filterTextBox);
AddNodesToPanel(stackPanel, list, 0, filterTextBox.Text);
@@ -593,14 +673,50 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
return scrollViewer;
}
private void ApplyFilter(StackPanel parentPanel, IEnumerable<FileTreeNode<PathingTask>> nodes, string filter)
private void ApplyFilter(StackPanel parentPanel, IEnumerable<FileTreeNode<PathingTask>> nodes, string filter,bool? excludeSelectedFolder = false)
{
if (parentPanel.Children.Count > 0 && parentPanel.Children[0] is TextBox filterTextBox)
if (parentPanel.Children.Count > 0)
{
List<UIElement> removeElements = new List<UIElement>();
foreach (UIElement parentPanelChild in parentPanel.Children)
{
if (parentPanelChild is FrameworkElement frameworkElement && frameworkElement.Name.StartsWith("dynamic_"))
{
removeElements.Add(frameworkElement);
}
}
removeElements.ForEach(parentPanel.Children.Remove);
}
if (excludeSelectedFolder ?? false)
{
List<string> skipFolderNames = SelectedScriptGroup?.Projects.ToList().Select(item=>item.FolderName).Distinct().ToList() ?? [];
//复制Nodes
string jsonString = JsonSerializer.Serialize(nodes);
var copiedNodes = JsonSerializer.Deserialize<ObservableCollection<FileTreeNode<PathingTask>>>(jsonString);
if (copiedNodes!=null)
{
//路径过滤
copiedNodes = FileTreeNodeHelper.FilterTree(copiedNodes, skipFolderNames);
copiedNodes = FileTreeNodeHelper.FilterEmptyNodes(copiedNodes);
AddNodesToPanel(parentPanel, copiedNodes, 0,filter);
}
}
else
{
AddNodesToPanel(parentPanel, nodes, 0,filter);
}
/*if (parentPanel.Children.Count > 0 && parentPanel.Children[1] is TextBox filterTextBox)
{
parentPanel.Children.Clear();
parentPanel.Children.Add(filterTextBox); // 保留筛选框
AddNodesToPanel(parentPanel, nodes, 0, filter);
}
}*/
}
private void AddNodesToPanel(StackPanel parentPanel, IEnumerable<FileTreeNode<PathingTask>> nodes, int depth, string filter)
@@ -617,6 +733,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
Content = node.FileName,
Tag = node.FilePath,
Margin = new Thickness(depth * 30, 0, 0, 0) // 根据深度计算Margin
,Name = "dynamic_"+Guid.NewGuid().ToString().Replace("-","_")
};
if (node.IsDirectory)
@@ -629,6 +746,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
Header = checkBox,
Content = childPanel,
IsExpanded = false // 默认不展开
,Name = "dynamic_"+Guid.NewGuid().ToString().Replace("-","_")
};
checkBox.Checked += (s, e) => SetChildCheckBoxesState(childPanel, true);
@@ -739,7 +857,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
[RelayCommand]
private void AddNextFlag(ScriptGroupProject? item)
{
if (item == null)
if (item == null || SelectedScriptGroup == null)
{
return;
}
@@ -751,8 +869,8 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
nextScheduledTask.Remove(nst);
}
nextScheduledTask.Add((SelectedScriptGroup?.Name, item.Index, item.FolderName, item.Name));
foreach (var item1 in SelectedScriptGroup.Projects)
nextScheduledTask.Add((SelectedScriptGroup?.Name ?? "", item.Index, item.FolderName, item.Name));
foreach (var item1 in SelectedScriptGroup?.Projects ?? [])
{
item1.NextFlag = false;
}
@@ -824,7 +942,15 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
Toast.Warning("只有JS脚本才有自定义配置");
}
}
[RelayCommand]
public void OnDeleteScriptByFolder(ScriptGroupProject? item)
{
if (item == null)
{
return;
}
SelectedScriptGroup?.Projects.ToList().Where(item2=>item2.FolderName == item.FolderName).ForEach(OnDeleteScript);
}
[RelayCommand]
public void OnDeleteScript(ScriptGroupProject? item)
{
@@ -1009,7 +1135,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
[RelayCommand]
public void OnGoToScriptGroupUrl()
{
Process.Start(new ProcessStartInfo("https://bgi.huiyadan.com/feats/autos/dispatcher.html") { UseShellExecute = true });
Process.Start(new ProcessStartInfo("https://bettergi.com/feats/autos/dispatcher.html") { UseShellExecute = true });
}
[RelayCommand]
@@ -1198,6 +1324,7 @@ public partial class ScriptControlViewModel : ObservableObject, INavigationAware
},
CloseButtonText = "关闭",
PrimaryButtonText = "确认执行",
Owner = Application.Current.MainWindow,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
};

View File

@@ -22,6 +22,7 @@ using System.Threading.Tasks;
using Windows.System;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.Model.Enum;
using BetterGenshinImpact.Helpers;
using Wpf.Ui;
using Wpf.Ui.Controls;
using Wpf.Ui.Violeta.Controls;
@@ -98,10 +99,10 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
[ObservableProperty]
private List<string> _domainNameList;
public static List<string> ArtifactSalvageStarList = ["4", "3", "2", "1"];
[ObservableProperty]
[ObservableProperty]
private List<string> _autoMusicLevelList = ["传说", "大师", "困难", "普通", "所有"];
[ObservableProperty]
@@ -191,7 +192,7 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
[RelayCommand]
public async Task OnGoToAutoGeniusInvokationUrlAsync()
{
await Launcher.LaunchUriAsync(new Uri("https://bgi.huiyadan.com/feats/task/tcg.html"));
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/tcg.html"));
}
[RelayCommand]
@@ -206,7 +207,7 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
[RelayCommand]
public async Task OnGoToAutoWoodUrlAsync()
{
await Launcher.LaunchUriAsync(new Uri("https://bgi.huiyadan.com/feats/task/felling.html"));
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/felling.html"));
}
[RelayCommand]
@@ -228,7 +229,7 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
[RelayCommand]
public async Task OnGoToAutoFightUrlAsync()
{
await Launcher.LaunchUriAsync(new Uri("https://bgi.huiyadan.com/feats/task/domain.html"));
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/domain.html"));
}
[RelayCommand]
@@ -249,7 +250,7 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
{
if (string.IsNullOrEmpty(Config.AutoFightConfig.StrategyName))
{
Toast.Warning("请先在【独立任务——自动战斗】下拉列表配置中选择战斗策略!");
UIDispatcherHelper.Invoke(() => { Toast.Warning("请先在【独立任务——自动战斗】下拉列表配置中选择战斗策略!"); });
path = string.Empty;
return true;
}
@@ -262,7 +263,7 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
if (!File.Exists(path) && !Directory.Exists(path))
{
Toast.Error("当前选择的自动战斗策略文件不存在");
UIDispatcherHelper.Invoke(() => { Toast.Error("当前选择的自动战斗策略文件不存在"); });
return true;
}
@@ -272,7 +273,7 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
[RelayCommand]
public async Task OnGoToAutoDomainUrlAsync()
{
await Launcher.LaunchUriAsync(new Uri("https://bgi.huiyadan.com/feats/task/domain.html"));
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/domain.html"));
}
[RelayCommand]
@@ -313,7 +314,7 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
[RelayCommand]
public async Task OnGoToAutoTrackUrlAsync()
{
await Launcher.LaunchUriAsync(new Uri("https://bgi.huiyadan.com/feats/task/track.html"));
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/track.html"));
}
[Obsolete]
@@ -348,7 +349,7 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
[RelayCommand]
public async Task OnGoToAutoTrackPathUrlAsync()
{
await Launcher.LaunchUriAsync(new Uri("https://bgi.huiyadan.com/feats/task/track.html"));
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/track.html"));
}
[RelayCommand]
@@ -363,7 +364,7 @@ public partial class TaskSettingsPageViewModel : ObservableObject, INavigationAw
[RelayCommand]
public async Task OnGoToAutoMusicGameUrlAsync()
{
await Launcher.LaunchUriAsync(new Uri("https://bgi.huiyadan.com/feats/task/music.html"));
await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/music.html"));
}
[RelayCommand]

View File

@@ -34,16 +34,5 @@ copy /y publish.7z .\MicaSetup\Resources\Setups\publish.7z
if exist "%zipFile%" ( del /f /q "%zipfile%" )
rename publish.7z %archiveFile%
@echo [build uninst using vs2022]
msbuild MicaSetup\MicaSetup.Uninst.csproj /t:Rebuild /p:Configuration=Release /p:DeployOnBuild=true /p:PublishProfile=FolderProfile /restore
@echo [build setup using vs2022]
copy /y .\MicaSetup\bin\Release\net472\MicaSetup.exe .\MicaSetup\Resources\Setups\Uninst.exe
msbuild MicaSetup\MicaSetup.csproj /t:Build /p:Configuration=Release /p:DeployOnBuild=true /p:PublishProfile=FolderProfile /restore
@echo [finish]
del /f /q MicaSetup.exe
copy /y .\MicaSetup\bin\Release\net472\MicaSetup.exe .\
rename MicaSetup.exe %setupFile%
rd /s /q dist\BetterGI

View File

@@ -1,8 +1,8 @@
<div align="center">
<h1 align="center">
<a href="https://bgi.huiyadan.com/"><img src="https://img.alicdn.com/imgextra/i2/2042484851/O1CN014wn1rf1lhoFYjL0gA_!!2042484851.png" width="200"></a>
<a href="https://bettergi.com/"><img src="https://img.alicdn.com/imgextra/i2/2042484851/O1CN014wn1rf1lhoFYjL0gA_!!2042484851.png" width="200"></a>
<br/>
<a href="https://bgi.huiyadan.com/">BetterGI</a>
<a href="https://bettergi.com/">BetterGI</a>
</h1>
<a href="https://trendshift.io/repositories/5269" target="_blank"><img src="https://trendshift.io/api/badge/repositories/5269" alt="babalae%2Fbetter-genshin-impact | Trendshift" style="width: 200px; height: 46px;" width="250" height="46"/></a>
</div>
@@ -20,21 +20,28 @@
BetterGI · 更好的原神, 一个基于计算机视觉技术,意图让原神变的更好的项目。
## 功能
* [自动拾取](https://bgi.huiyadan.com/doc.html#%E8%87%AA%E5%8A%A8%E6%8B%BE%E5%8F%96):遇到可交互/拾取内容时自动按 <kbd>F</kbd>,支持黑白名单配置
* [自动剧情](https://bgi.huiyadan.com/doc.html#%E8%87%AA%E5%8A%A8%E5%89%A7%E6%83%85):快速点击过剧情、自动选择选项、自动提交物品、关闭弹出书页等
* 与凯瑟琳对话时有橙色选项会 [自动领取「每日委托」奖励](https://bgi.huiyadan.com/doc.html#%E8%87%AA%E5%8A%A8%E9%A2%86%E5%8F%96%E3%80%8E%E6%AF%8F%E6%97%A5%E5%A7%94%E6%89%98%E3%80%8F%E5%A5%96%E5%8A%B1)、[自动重新派遣](https://bgi.huiyadan.com/doc.html#%E8%87%AA%E5%8A%A8%E9%87%8D%E6%96%B0%E6%B4%BE%E9%81%A3)
* [自动邀约](https://bgi.huiyadan.com/doc.html#%E8%87%AA%E5%8A%A8%E9%82%80%E7%BA%A6):自动剧情开启的情况下此功能才会生效,自动选择邀约选项
* [自动钓鱼](https://bgi.huiyadan.com/doc.html#%E5%85%A8%E8%87%AA%E5%8A%A8%E9%92%93%E9%B1%BC)AI 识别自动抛竿,鱼上钩时自动收杆,并自动完成钓鱼进度
* [自动烹饪](https://bgi.huiyadan.com/doc.html#%E8%87%AA%E5%8A%A8%E7%83%B9%E9%A5%AA):自动在完美区域完成食物烹饪,暂不支持“仙跳墙”
* [全自动七圣召唤](https://bgi.huiyadan.com/doc.html#%E8%87%AA%E5%8A%A8%E4%B8%83%E5%9C%A3%E5%8F%AC%E5%94%A4):帮助你轻松完成七圣召唤角色邀请、每周来客挑战等 PVE 内容
* [自动伐木](http://bgi.huiyadan.com/doc.html#%E8%87%AA%E5%8A%A8%E4%BC%90%E6%9C%A8):自动 <kbd>Z</kbd> 键使用「王树瑞佑」,利用上下线可以刷新木材的原理,挂机刷满一背包的木材
* [自动战斗](http://bgi.huiyadan.com/feats/domain.html):编写战斗脚本,让队伍按照你的策略进行自动战斗
* [自动秘境](http://bgi.huiyadan.com/feats/domain.html):全自动秘境挂机刷体力,自动循环进入秘境开启钥匙、战斗、走到古树并领取奖励
* [快速传送](http://bgi.huiyadan.com/feats/domain.html):在地图上点击传送点,或者点击后出现的列表中存在传送点,会自动点击传送点并传送
* [那维莱特转圈](https://bgi.huiyadan.com/doc.html#%E9%82%A3%E7%BB%B4%E8%8E%B1%E7%89%B9-%E8%BD%AC%E5%9C%88%E5%9C%88):设置快捷键后,长按可以不断水平旋转视角(当然你也可以用来转草神)
* [快速圣遗物强化](https://bgi.huiyadan.com/doc.html#%E5%9C%A3%E9%81%97%E7%89%A9%E4%B8%80%E9%94%AE%E5%BC%BA%E5%8C%96):通过快速切换“详情”、“强化”页跳过圣遗物强化结果展示,快速+20
* [商店一键购买](https://bgi.huiyadan.com/doc.html#%E5%9C%A3%E9%81%97%E7%89%A9%E4%B8%80%E9%94%AE%E5%BC%BA%E5%8C%96):可以快速以满数量购买商店中的物品,适合快速清空活动兑换,尘歌壶商店兑换等
* [**……**](https://bgi.huiyadan.com/feat.html)
* 实时任务
* [自动拾取](https://bettergi.com/doc.html#%E8%87%AA%E5%8A%A8%E6%8B%BE%E5%8F%96):遇到可交互/拾取内容时自动按 <kbd>F</kbd>,支持黑白名单配置
* [自动剧情](https://bettergi.com/doc.html#%E8%87%AA%E5%8A%A8%E5%89%A7%E6%83%85):快速点击过剧情、自动选择选项、自动提交物品、关闭弹出书页等
* 与凯瑟琳对话时有橙色选项会 [自动领取「每日委托」奖励](https://bettergi.com/doc.html#%E8%87%AA%E5%8A%A8%E9%A2%86%E5%8F%96%E3%80%8E%E6%AF%8F%E6%97%A5%E5%A7%94%E6%89%98%E3%80%8F%E5%A5%96%E5%8A%B1)、[自动重新派遣](https://bettergi.com/doc.html#%E8%87%AA%E5%8A%A8%E9%87%8D%E6%96%B0%E6%B4%BE%E9%81%A3)
* [自动邀约](https://bettergi.com/doc.html#%E8%87%AA%E5%8A%A8%E9%82%80%E7%BA%A6):自动剧情开启的情况下此功能才会生效,自动选择邀约选项
* [快速传送](http://bettergi.com/feats/domain.html):在地图上点击传送点,或者点击后出现的列表中存在传送点,会自动点击传送点并传送
* [全自动钓鱼](https://bettergi.com/doc.html#%E5%85%A8%E8%87%AA%E5%8A%A8%E9%92%93%E9%B1%BC)AI 识别自动抛竿,鱼上钩时自动收杆,并自动完成钓鱼进度
* [自动烹饪](https://bettergi.com/doc.html#%E8%87%AA%E5%8A%A8%E7%83%B9%E9%A5%AA):自动在完美区域完成食物烹饪,暂不支持“仙跳墙”
* 独立任务
* [自动七圣召唤](https://bettergi.com/doc.html#%E8%87%AA%E5%8A%A8%E4%B8%83%E5%9C%A3%E5%8F%AC%E5%94%A4):帮助你轻松完成七圣召唤角色邀请、每周来客挑战等 PVE 内容
* [自动伐木](http://bettergi.com/doc.html#%E8%87%AA%E5%8A%A8%E4%BC%90%E6%9C%A8):自动 <kbd>Z</kbd> 键使用「王树瑞佑」,利用上下线可以刷新木材的原理,挂机刷满一背包的木材
* [自动秘境](http://bettergi.com/feats/domain.html):全自动秘境挂机刷体力,自动循环进入秘境开启钥匙、战斗、走到古树并领取奖励
* [自动音游](https://bettergi.com/feats/task/music.html):一键自动完成千音雅集的专辑,快速获取成就
* 全自动
* [一条龙](https://github.com/babalae/better-genshin-impact/issues/846):一键完成日常(使用历练点),并领取奖励
* [自动采集/挖矿/锄地](https://bettergi.com/feats/autos/pathing.html):通过左上角小地图的识别,完成自动采集、挖矿、锄地等功能
* [键鼠录制](https://bettergi.com/feats/autos/kmscript.html):可以录制回放当前的键鼠操作,建议配合调度器使用
* 操控辅助
* [那维莱特转圈](https://bettergi.com/doc.html#%E9%82%A3%E7%BB%B4%E8%8E%B1%E7%89%B9-%E8%BD%AC%E5%9C%88%E5%9C%88):设置快捷键后,长按可以不断水平旋转视角(当然你也可以用来转草神)
* [快速圣遗物强化](https://bettergi.com/doc.html#%E5%9C%A3%E9%81%97%E7%89%A9%E4%B8%80%E9%94%AE%E5%BC%BA%E5%8C%96):通过快速切换“详情”、“强化”页跳过圣遗物强化结果展示,快速+20
* [商店一键购买](https://bettergi.com/doc.html#%E5%9C%A3%E9%81%97%E7%89%A9%E4%B8%80%E9%94%AE%E5%BC%BA%E5%8C%96):可以快速以满数量购买商店中的物品,适合快速清空活动兑换,尘歌壶商店兑换等
* [**……**](https://bettergi.com/doc.html)
<div align="center">
<img src="https://github.com/babalae/better-genshin-impact/assets/15783049/57ab7c3c-709a-4cf3-8f64-1c78764c364c"/>
@@ -51,9 +58,9 @@ BetterGI · 更好的原神, 一个基于计算机视觉技术,意图让原
> [!NOTE]
> 下载地址:[⚡Github 下载](https://github.com/babalae/better-genshin-impact/releases)
>
> 不知道下载哪个?第一次使用?请看:[快速上手](https://bgi.huiyadan.com/quickstart.html) 遇到问题请先看:[常见问题](https://bgi.huiyadan.com/faq.html)
> 不知道下载哪个?第一次使用?请看:[快速上手](https://bettergi.com/quickstart.html) 遇到问题请先看:[常见问题](https://bettergi.com/faq.html)
最新编译版本(无地图特征数据)可以从自动构建中获取: [![Build status](https://ci.appveyor.com/api/projects/status/cklcy1oj9u66ul4j)](https://ci.appveyor.com/project/huiyadanli/better-genshin-impact/build/artifacts)
最新编译版本可以从自动构建中获取: [![](https://github.com/babalae/bettergi-publish/actions/workflows/publish.yml/badge.svg)](https://github.com/babalae/bettergi-publish/actions)
## 使用方法
由于图像识别比较吃性能,低配置电脑可能无法正常使用部分功能。
@@ -72,16 +79,16 @@ BetterGI · 更好的原神, 一个基于计算机视觉技术,意图让原
**打开软件以后,在“启动”页选择好截图方式,点击启动按钮就可以享受 BetterGI 带来的便利了!**
详细使用指南请看:[快速上手](https://bgi.huiyadan.com/quickstart.html)
详细使用指南请看:[快速上手](https://bettergi.com/quickstart.html)
具体功能效果与使用方式见:[文档](https://bgi.huiyadan.com/doc.html)
具体功能效果与使用方式见:[文档](https://bettergi.com/doc.html)
## FAQ
* 为什么需要管理员权限?
* 因为游戏是以管理员权限启动的,软件不以管理员权限启动的话没有权限模拟鼠标点击。
* 会不会封号?
* 理论上不会被封。 **BetterGI 不会做出任何修改游戏文件、读写游戏内存等任何危害游戏本体的行为,单纯依靠视觉算法和模拟操作实现。** 但是mhy是自由的用户条款上明确说明第三方软件/模拟操作是封号理由之一。当前方案还是存在被检测的可能。只能说请低调使用,请不要跳脸官方。
* [更多常见问题...](https://bgi.huiyadan.com/faq.html)
* [更多常见问题...](https://bettergi.com/faq.html)
## 致谢
@@ -94,6 +101,7 @@ BetterGI · 更好的原神, 一个基于计算机视觉技术,意图让原
* [genshin_impact_assistant](https://github.com/infstellar/genshin_impact_assistant)
* [HutaoFisher](https://github.com/myHuTao-qwq/HutaoFisher)
* [minimap](https://github.com/tignioj/minimap)
* [kachina-installer](https://github.com/YuehaiTeam/kachina-installer)
另外特别感谢 [@Lightczx](https://github.com/Lightczx) 和 [@emako](https://github.com/emako) 对本项目的指导与贡献