diff --git a/BetterGenshinImpact.Test/Simple/HsvTestWindow.cs b/BetterGenshinImpact.Test/Simple/HsvTestWindow.cs index 8af9eef6..21094d00 100644 --- a/BetterGenshinImpact.Test/Simple/HsvTestWindow.cs +++ b/BetterGenshinImpact.Test/Simple/HsvTestWindow.cs @@ -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); diff --git a/BetterGenshinImpact/App.xaml.cs b/BetterGenshinImpact/App.xaml.cs index 8b5fb7dc..24a268b6 100644 --- a/BetterGenshinImpact/App.xaml.cs +++ b/BetterGenshinImpact/App.xaml.cs @@ -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(); diff --git a/BetterGenshinImpact/Assets/Images/banner.jpg b/BetterGenshinImpact/Assets/Images/banner.jpg new file mode 100644 index 00000000..ebc13f72 Binary files /dev/null and b/BetterGenshinImpact/Assets/Images/banner.jpg differ diff --git a/BetterGenshinImpact/Assets/Images/banner_sayu.jpg b/BetterGenshinImpact/Assets/Images/banner_sayu.jpg deleted file mode 100644 index e9ec54be..00000000 Binary files a/BetterGenshinImpact/Assets/Images/banner_sayu.jpg and /dev/null differ diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index ec4eb95b..2cac9456 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -10,7 +10,7 @@ true Assets\Images\logo.ico BetterGI - 0.39.6 + 0.40.1 x64 embedded @@ -48,6 +48,7 @@ + @@ -148,6 +149,7 @@ + diff --git a/BetterGenshinImpact/Core/Config/CommonConfig.cs b/BetterGenshinImpact/Core/Config/CommonConfig.cs index 6da8e1f9..e1f35470 100644 --- a/BetterGenshinImpact/Core/Config/CommonConfig.cs +++ b/BetterGenshinImpact/Core/Config/CommonConfig.cs @@ -39,4 +39,10 @@ public partial class CommonConfig : ObservableObject /// [ObservableProperty] private bool _isFirstRun = true; + + /// + /// 这个版本是否运行过 + /// + [ObservableProperty] + private string _runForVersion = string.Empty; } diff --git a/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs b/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs index a753eae5..47e0a059 100644 --- a/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs +++ b/BetterGenshinImpact/Core/Config/PathingPartyConfig.cs @@ -69,6 +69,10 @@ public partial class PathingPartyConfig : ObservableObject [ObservableProperty] private bool _soloTaskUseFightEnabled = false; + //不在某时执行 + [ObservableProperty] + private string _skipDuring = ""; + // 使用小道具的间隔时间 [ObservableProperty] private int _useGadgetIntervalMs = 0; diff --git a/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs b/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs index 5f958d5a..ba47d8a5 100644 --- a/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs +++ b/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs @@ -92,6 +92,11 @@ public class ScriptRepoUpdater : Singleton // 解析信息 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 { needDownload = true; } + if (needDownload) { await DownloadRepoAndUnzip(string.Format(fastProxyUrl, url)); @@ -246,6 +252,7 @@ public class ScriptRepoUpdater : Singleton Content = $"检测到{(formClipboard ? "剪切板上存在" : "")}脚本订阅链接,解析后需要导入的脚本为:{pathJson}。\n是否导入并覆盖此文件或者文件夹下的脚本?", CloseButtonText = "关闭", PrimaryButtonText = "确认导入", + Owner = Application.Current.MainWindow, WindowStartupLocation = WindowStartupLocation.CenterOwner, }; @@ -393,7 +400,7 @@ public class ScriptRepoUpdater : Singleton { // 目标文件所在文件夹不存在时创建它 Directory.CreateDirectory(Path.GetDirectoryName(destPath)!); - + if (File.Exists(destPath)) { File.Delete(destPath); @@ -490,4 +497,4 @@ public class ScriptRepoUpdater : Singleton } } } -} +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs index 794764f4..47c5bb27 100644 --- a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs +++ b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs @@ -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()) { // 图像右侧就是脆弱树脂数量 diff --git a/BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs b/BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs index 03c7cdc3..e48bcb4f 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Assets/AutoFightAssets.cs @@ -12,6 +12,7 @@ public class AutoFightAssets : BaseAssets public List AvatarSideIconRectList; // 侧边栏角色头像 非联机状态下 public List AvatarIndexRectList; // 侧边栏角色头像对应的白色块 非联机状态下 public Rect ERect; + public Rect ECooldownRect; public Rect QRect; public Rect EndTipsUpperRect; // 挑战达成提示 public Rect EndTipsRect; @@ -47,6 +48,8 @@ public class AutoFightAssets : BaseAssets (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 diff --git a/BetterGenshinImpact/GameTask/AutoFight/AutoFightContext.cs b/BetterGenshinImpact/GameTask/AutoFight/AutoFightContext.cs index ebcb8483..9df06aa7 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/AutoFightContext.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/AutoFightContext.cs @@ -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; - -/// -/// 自动战斗上下文 -/// 请在启动BetterGI以后再初始化 -/// -public class AutoFightContext : Singleton -{ - private AutoFightContext() - { - Simulator = TaskContext.Instance().PostMessageSimulator; - } - - /// - /// find资源 - /// - public AutoFightAssets FightAssets => AutoFightAssets.Instance; - - /// - /// 战斗专用的PostMessage模拟键鼠操作 - /// - public readonly PostMessageSimulator Simulator; -} +// /// +// /// 自动战斗上下文 +// /// 请在启动BetterGI以后再初始化 +// /// +// public class AutoFightContext : Singleton +// { +// private AutoFightContext() +// { +// Simulator = TaskContext.Instance().PostMessageSimulator; +// } +// +// /// +// /// find资源 +// /// +// public AutoFightAssets FightAssets => AutoFightAssets.Instance; +// +// /// +// /// 战斗专用的PostMessage模拟键鼠操作 +// /// +// public readonly PostMessageSimulator Simulator; +// } diff --git a/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs b/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs index 16ab96c9..0b4fa6e3 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs @@ -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(50, 790); //进度条颜色 - var whiteTile = ra.SrcMat.At(50, 772); //白块 + var whiteTile = ra.SrcMat.At(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) diff --git a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs index 3b52ede3..b83b8aa0 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs @@ -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 /// public double SkillHoldCd { get; set; } - + /// /// 最近一次使用元素战技的时间 /// @@ -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 /// 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 // /// // 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); } /// @@ -574,7 +578,7 @@ public class Avatar /// public void Jump() { - AutoFightContext.Instance.Simulator.SimulateAction(GIActions.Jump); + Simulation.SendInput.SimulateAction(GIActions.Jump); } /// @@ -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); } -} +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs b/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs index 7d4c26a0..dc7d8fbf 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Model/CombatScenes.cs @@ -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()) diff --git a/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs b/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs index d503ec35..98311b8b 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs @@ -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); } diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs index 0ec4bc4a..05c25e2e 100644 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs @@ -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 PredictionDiceType() diff --git a/BetterGenshinImpact/GameTask/AutoMusicGame/AutoAlbumTask.cs b/BetterGenshinImpact/GameTask/AutoMusicGame/AutoAlbumTask.cs index 883712c6..2227a34b 100644 --- a/BetterGenshinImpact/GameTask/AutoMusicGame/AutoAlbumTask.cs +++ b/BetterGenshinImpact/GameTask/AutoMusicGame/AutoAlbumTask.cs @@ -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); } diff --git a/BetterGenshinImpact/GameTask/AutoMusicGame/AutoMusicGameConfig.cs b/BetterGenshinImpact/GameTask/AutoMusicGame/AutoMusicGameConfig.cs index d7083ed5..4a1acd2f 100644 --- a/BetterGenshinImpact/GameTask/AutoMusicGame/AutoMusicGameConfig.cs +++ b/BetterGenshinImpact/GameTask/AutoMusicGame/AutoMusicGameConfig.cs @@ -11,6 +11,10 @@ namespace BetterGenshinImpact.GameTask.AutoMusicGame; [Serializable] public partial class AutoMusicGameConfig : ObservableObject { + // 自动达到大音天籁的级别 + [ObservableProperty] + private bool _mustCanorusLevel = false; + // 乐曲级别 [ObservableProperty] private string _musicLevel = ""; diff --git a/BetterGenshinImpact/GameTask/AutoPathing/Handler/ElementalCollectHandler.cs b/BetterGenshinImpact/GameTask/AutoPathing/Handler/ElementalCollectHandler.cs index c8bf9d33..8e576430 100644 --- a/BetterGenshinImpact/GameTask/AutoPathing/Handler/ElementalCollectHandler.cs +++ b/BetterGenshinImpact/GameTask/AutoPathing/Handler/ElementalCollectHandler.cs @@ -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), diff --git a/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/ActionEnum.cs b/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/ActionEnum.cs index 26523d6c..1442720b 100644 --- a/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/ActionEnum.cs +++ b/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/ActionEnum.cs @@ -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 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 +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/WaypointType.cs b/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/WaypointType.cs index 6e0e2da3..c7ff4929 100644 --- a/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/WaypointType.cs +++ b/BetterGenshinImpact/GameTask/AutoPathing/Model/Enum/WaypointType.cs @@ -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 Values { @@ -15,6 +16,7 @@ public class WaypointType(string code, string msg) yield return Path; yield return Target; yield return Teleport; + yield return Orientation; } } diff --git a/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs b/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs index 82e56065..bd51cb40 100644 --- a/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs +++ b/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs @@ -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 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(); diff --git a/BetterGenshinImpact/GameTask/AutoSkip/AutoTrackTask.cs b/BetterGenshinImpact/GameTask/AutoSkip/AutoTrackTask.cs index 913df4ae..3d1cc4f1 100644 --- a/BetterGenshinImpact/GameTask/AutoSkip/AutoTrackTask.cs +++ b/BetterGenshinImpact/GameTask/AutoSkip/AutoTrackTask.cs @@ -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 { diff --git a/BetterGenshinImpact/GameTask/AutoTrackPath/TpConfig.cs b/BetterGenshinImpact/GameTask/AutoTrackPath/TpConfig.cs index 5187dd84..52715dc5 100644 --- a/BetterGenshinImpact/GameTask/AutoTrackPath/TpConfig.cs +++ b/BetterGenshinImpact/GameTask/AutoTrackPath/TpConfig.cs @@ -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 时的像素比例因子。 } \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs index bce6d9e9..8b1e4e0b 100644 --- a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs +++ b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs @@ -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; - + /// /// 传送到须弥七天神像 /// @@ -52,8 +55,10 @@ public class TpTask(CancellationToken ct) await AdjustMapZoomLevel(currentZoomLevel, 3); } } + await Tp(_tpConfig.ReviveStatueOfTheSevenPointX, _tpConfig.ReviveStatueOfTheSevenPointY); } + /// /// 通过大地图传送到指定坐标最近的传送点,然后移动到指定坐标 /// @@ -62,29 +67,52 @@ public class TpTask(CancellationToken ct) /// 强制以当前的tpX,tpY坐标进行自动传送 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("打开大地图失败,请检查按键绑定中「打开地图」按键设置是否和原神游戏中一致!"); + } + } + } + + /// + /// 尝试打开地图界面 + /// + private async Task 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) /// /// 目标x坐标 /// 目标y坐标 - - private async Task MoveMapTo(double x, double y) + /// 到达目标点的最小缩放等级,只在 MapZoomEnabled 为 True 生效 + 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); } - + /// /// 调整地图的缩放等级(整数缩放级别)。 /// @@ -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) /// /// /// + [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); } + /// + /// 获取最接近的N个传送点坐标和所处区域 + /// + /// + /// + /// 获取最近的 n 个传送点 + /// + 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 SwitchRecentlyCountryMap(double x, double y, string? forceCountry = null) { // 可能是地下地图,切换到地上地图 @@ -740,4 +835,4 @@ public class TpTask(CancellationToken ct) // 1~6 的缩放等级 return (-5 * s) + 6; } -} +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/Common/BgiVision/BvStatus.cs b/BetterGenshinImpact/GameTask/Common/BgiVision/BvStatus.cs index 6bb72c25..87ccf3d0 100644 --- a/BetterGenshinImpact/GameTask/Common/BgiVision/BvStatus.cs +++ b/BetterGenshinImpact/GameTask/Common/BgiVision/BvStatus.cs @@ -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 /// 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 diff --git a/BetterGenshinImpact/GameTask/Common/Job/ChooseTalkOptionTask.cs b/BetterGenshinImpact/GameTask/Common/Job/ChooseTalkOptionTask.cs index 44565148..60df0a66 100644 --- a/BetterGenshinImpact/GameTask/Common/Job/ChooseTalkOptionTask.cs +++ b/BetterGenshinImpact/GameTask/Common/Job/ChooseTalkOptionTask.cs @@ -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; } } diff --git a/BetterGenshinImpact/GameTask/Common/Job/GoToAdventurersGuildTask.cs b/BetterGenshinImpact/GameTask/Common/Job/GoToAdventurersGuildTask.cs index e8e5ecd4..3def90d1 100644 --- a/BetterGenshinImpact/GameTask/Common/Job/GoToAdventurersGuildTask.cs +++ b/BetterGenshinImpact/GameTask/Common/Job/GoToAdventurersGuildTask.cs @@ -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, "凯瑟琳")) { diff --git a/BetterGenshinImpact/GameTask/LogParse/LogParse.cs b/BetterGenshinImpact/GameTask/LogParse/LogParse.cs index 83ef540e..ad3e83b1 100644 --- a/BetterGenshinImpact/GameTask/LogParse/LogParse.cs +++ b/BetterGenshinImpact/GameTask/LogParse/LogParse.cs @@ -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 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 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 $"{a}"; + } + 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 configGroups, GameInfo? gameInfo,LogParseConfig.ScriptGroupLogParseConfig scriptGroupLogParseConfig) { (string name, Func 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 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 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 value)[] col2Configs=[..msColConfigs.ToList().Where(item=>item.name!="日期" && item.name!="最后小怪日期" && item.name!="最后精英日期"), + (string name, Func 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 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(" "); html.AppendLine(""); html.AppendLine(""); diff --git a/BetterGenshinImpact/GameTask/LogParse/LogParseConfig.cs b/BetterGenshinImpact/GameTask/LogParse/LogParseConfig.cs index 7588ab5d..688a01ff 100644 --- a/BetterGenshinImpact/GameTask/LogParse/LogParseConfig.cs +++ b/BetterGenshinImpact/GameTask/LogParse/LogParseConfig.cs @@ -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"; } } \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/LogParse/MoraStatistics.cs b/BetterGenshinImpact/GameTask/LogParse/MoraStatistics.cs index ae62e5ea..843039bd 100644 --- a/BetterGenshinImpact/GameTask/LogParse/MoraStatistics.cs +++ b/BetterGenshinImpact/GameTask/LogParse/MoraStatistics.cs @@ -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)"); } } diff --git a/BetterGenshinImpact/GameTask/TaskRunner.cs b/BetterGenshinImpact/GameTask/TaskRunner.cs index f0902164..6662c1d7 100644 --- a/BetterGenshinImpact/GameTask/TaskRunner.cs +++ b/BetterGenshinImpact/GameTask/TaskRunner.cs @@ -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() - { - } } diff --git a/BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs b/BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs new file mode 100644 index 00000000..9faf8098 --- /dev/null +++ b/BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs @@ -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, "获取原神游戏设置失败"); + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Genshin/Settings2/GenshinGameInputSettings.cs b/BetterGenshinImpact/Genshin/Settings2/GenshinGameInputSettings.cs new file mode 100644 index 00000000..86ed4e11 --- /dev/null +++ b/BetterGenshinImpact/Genshin/Settings2/GenshinGameInputSettings.cs @@ -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(json); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Genshin/Settings2/GenshinGameSettings.cs b/BetterGenshinImpact/Genshin/Settings2/GenshinGameSettings.cs new file mode 100644 index 00000000..e154d362 --- /dev/null +++ b/BetterGenshinImpact/Genshin/Settings2/GenshinGameSettings.cs @@ -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; } // 场景纸白 + + /// + /// 2.200000047683716 + /// + [JsonProperty("gammaValue")] + public string GammaValue { get; set; } // 伽马值 + + [JsonProperty("enableHDR")] + public bool EnableHDR { get; set; } // 启用HDR + + [JsonProperty("_overrideControllerMapKeyList")] + public List OverrideControllerMapKeyList { get; set; } // 覆盖控制器映射键列表 + + [JsonProperty("_overrideControllerMapValueList")] + public List OverrideControllerMapValueList { get; set; } // 覆盖控制器映射值列表 + + [JsonProperty("rewiredMapMigrateRecord")] + public List 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 ConflictKeyBindingElementId { get; set; } // 冲突的按键绑定元素ID + + [JsonProperty("conflictKeyBindingActionId")] + public List 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 MtrBanReasons { get; set; } // MTR禁止原因 + + [JsonProperty("_customDataKeyList")] + public List CustomDataKeyList { get; set; } // 自定义数据键列表 + + [JsonProperty("_customDataValueList")] + public List CustomDataValueList { get; set; } // 自定义数据值列表 + + [JsonProperty("_serializedCodeSwitches")] + public List 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 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(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; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Helpers/Http/ProxySpeedTester.cs b/BetterGenshinImpact/Helpers/Http/ProxySpeedTester.cs index c38fb2e9..eafab0d6 100644 --- a/BetterGenshinImpact/Helpers/Http/ProxySpeedTester.cs +++ b/BetterGenshinImpact/Helpers/Http/ProxySpeedTester.cs @@ -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); } } -} +} \ No newline at end of file diff --git a/BetterGenshinImpact/Helpers/SecurityControlHelper.cs b/BetterGenshinImpact/Helpers/SecurityControlHelper.cs new file mode 100644 index 00000000..b7a84a59 --- /dev/null +++ b/BetterGenshinImpact/Helpers/SecurityControlHelper.cs @@ -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); + } + } +} diff --git a/BetterGenshinImpact/Helpers/TempManager.cs b/BetterGenshinImpact/Helpers/TempManager.cs new file mode 100644 index 00000000..5dda1144 --- /dev/null +++ b/BetterGenshinImpact/Helpers/TempManager.cs @@ -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 + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Helpers/Ui/FileTreeNodeHelper.cs b/BetterGenshinImpact/Helpers/Ui/FileTreeNodeHelper.cs index 574cc437..554fe52c 100644 --- a/BetterGenshinImpact/Helpers/Ui/FileTreeNodeHelper.cs +++ b/BetterGenshinImpact/Helpers/Ui/FileTreeNodeHelper.cs @@ -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); } } + + /// + /// 根据路径过滤树形结构,排除指定路径的节点 + /// + /// 节点数据类型 + /// 树形结构的根节点集合 + /// 要排除的路径集合 + /// 过滤后的树形结构 + public static ObservableCollection> FilterTree( + ObservableCollection> nodes, + List folderNames) + { + // 递归过滤节点 + ObservableCollection> FilterNodes(ObservableCollection> inputNodes, List paths) + { + var filteredNodes = new ObservableCollection>(); + + 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); + } + + /// + /// 判断文件路径是否匹配多级路径的前缀 + /// + /// 当前节点路径 + /// 目标路径 + /// 是否匹配 + private static bool IsPathMatch(string fileName, string path) + { + // 匹配路径是否以指定前缀开始,路径分隔符对齐 + return path.StartsWith(fileName, StringComparison.OrdinalIgnoreCase) + && (path.Length == fileName.Length || path[fileName.Length] == '\\'); + } + + /// + /// 获取路径中去掉当前节点后的剩余路径 + /// + /// 当前节点路径 + /// 完整路径 + /// 剩余路径,如果不匹配返回 null + 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> FilterEmptyNodes(ObservableCollection> nodes) + { + // 递归过滤节点 + ObservableCollection> Filter(ObservableCollection> inputNodes) + { + var filteredNodes = new ObservableCollection>(); + + 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); + } } diff --git a/BetterGenshinImpact/Service/Notification/Builder/INotificationDataBuilder.cs b/BetterGenshinImpact/Service/Notification/Builder/INotificationDataBuilder.cs deleted file mode 100644 index c1e916a0..00000000 --- a/BetterGenshinImpact/Service/Notification/Builder/INotificationDataBuilder.cs +++ /dev/null @@ -1,8 +0,0 @@ -using BetterGenshinImpact.Service.Notification.Model; - -namespace BetterGenshinImpact.Service.Notification.Builder; - -public interface INotificationDataBuilder where TNotificationData : INotificationData -{ - TNotificationData Build(); -} diff --git a/BetterGenshinImpact/Service/Notification/Builder/LifecycleNotificationBuilder.cs b/BetterGenshinImpact/Service/Notification/Builder/LifecycleNotificationBuilder.cs deleted file mode 100644 index ba2a2fab..00000000 --- a/BetterGenshinImpact/Service/Notification/Builder/LifecycleNotificationBuilder.cs +++ /dev/null @@ -1,13 +0,0 @@ -using BetterGenshinImpact.Service.Notification.Model; - -namespace BetterGenshinImpact.Service.Notification.Builder; - -public class LifecycleNotificationBuilder : INotificationDataBuilder -{ - private readonly LifecycleNotificationData _notificationData = new(); - - public LifecycleNotificationData Build() - { - return _notificationData; - } -} diff --git a/BetterGenshinImpact/Service/Notification/Builder/TaskNotificationBuilder.cs b/BetterGenshinImpact/Service/Notification/Builder/TaskNotificationBuilder.cs deleted file mode 100644 index 7eb48277..00000000 --- a/BetterGenshinImpact/Service/Notification/Builder/TaskNotificationBuilder.cs +++ /dev/null @@ -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 -{ - 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; - } -} diff --git a/BetterGenshinImpact/Service/Notification/Converter/BaseDateTimeJsonConverter.cs b/BetterGenshinImpact/Service/Notification/Converter/BaseDateTimeJsonConverter.cs new file mode 100644 index 00000000..ea8434d6 --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/Converter/BaseDateTimeJsonConverter.cs @@ -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 +{ + + // 反序列化方法 + 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)); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notification/Converter/DateTimeJsonConverter.cs b/BetterGenshinImpact/Service/Notification/Converter/DateTimeJsonConverter.cs new file mode 100644 index 00000000..ae4b9aea --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/Converter/DateTimeJsonConverter.cs @@ -0,0 +1,7 @@ +using System.Text.Json.Serialization; + +namespace BetterGenshinImpact.Service.Notification.Converter; + +public class DateTimeJsonConverter() : BaseDateTimeJsonConverter("yyyy-MM-dd HH:mm:ss") +{ +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notification/Converter/ImageToBase64Converter.cs b/BetterGenshinImpact/Service/Notification/Converter/ImageToBase64Converter.cs index 2432c3ea..3b244b93 100644 --- a/BetterGenshinImpact/Service/Notification/Converter/ImageToBase64Converter.cs +++ b/BetterGenshinImpact/Service/Notification/Converter/ImageToBase64Converter.cs @@ -11,11 +11,11 @@ public class ImageToBase64Converter : JsonConverter { 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) diff --git a/BetterGenshinImpact/Service/Notification/Model/Base/DomainDetails.cs b/BetterGenshinImpact/Service/Notification/Model/Base/DomainDetails.cs new file mode 100644 index 00000000..e1652052 --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/Model/Base/DomainDetails.cs @@ -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; +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notification/Model/Base/GeniusInvocationDetails.cs b/BetterGenshinImpact/Service/Notification/Model/Base/GeniusInvocationDetails.cs new file mode 100644 index 00000000..542c0162 --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/Model/Base/GeniusInvocationDetails.cs @@ -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; +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notification/Model/Base/ScriptDetails.cs b/BetterGenshinImpact/Service/Notification/Model/Base/ScriptDetails.cs new file mode 100644 index 00000000..be91c330 --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/Model/Base/ScriptDetails.cs @@ -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; +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notification/Model/BaseNotificationData.cs b/BetterGenshinImpact/Service/Notification/Model/BaseNotificationData.cs new file mode 100644 index 00000000..70f1ad97 --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/Model/BaseNotificationData.cs @@ -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 +{ + /// + /// NotificationEvent + /// 事件名称 + /// + public string Event { get; set; } = string.Empty; + + /// + /// 事件结果 + /// + [JsonConverter(typeof(JsonStringEnumConverter))] + public NotificationEventResult Result { get; set; } + + /// + /// 事件触发时间 + /// + [JsonConverter(typeof(DateTimeJsonConverter))] + public DateTime Timestamp { get; set; } = DateTime.Now; + + /// + /// 事件触发时的截图 + /// + [JsonConverter(typeof(ImageToBase64Converter))] + public Image? Screenshot { get; set; } + + /// + /// 事件消息 + /// + public string? Message { get; set; } + + /// + /// 额外的事件数据 + /// + 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(); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notification/Model/DomainNotificationData.cs b/BetterGenshinImpact/Service/Notification/Model/DomainNotificationData.cs new file mode 100644 index 00000000..19489223 --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/Model/DomainNotificationData.cs @@ -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; } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationAction.cs b/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationAction.cs deleted file mode 100644 index 21c0fba3..00000000 --- a/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationAction.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BetterGenshinImpact.Service.Notification.Model.Enum; - -public enum NotificationAction -{ - Started, - Completed, - Progress -} diff --git a/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationConclusion.cs b/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationConclusion.cs deleted file mode 100644 index b978165b..00000000 --- a/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationConclusion.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BetterGenshinImpact.Service.Notification.Model.Enum; - -public enum NotificationConclusion -{ - Success, - Failure, - Cancelled -} diff --git a/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationEvent.cs b/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationEvent.cs index dd3d3fe1..9d73af87 100644 --- a/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationEvent.cs +++ b/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationEvent.cs @@ -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; } diff --git a/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationEventResult.cs b/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationEventResult.cs new file mode 100644 index 00000000..f922ed1e --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationEventResult.cs @@ -0,0 +1,8 @@ +namespace BetterGenshinImpact.Service.Notification.Model.Enum; + +public enum NotificationEventResult +{ + Success, + Fail, + PartialSuccess, // 还没想好怎么用,先放在这里 +} diff --git a/BetterGenshinImpact/Service/Notification/Model/GeniusInvocationNotificationData.cs b/BetterGenshinImpact/Service/Notification/Model/GeniusInvocationNotificationData.cs new file mode 100644 index 00000000..0d51b8a2 --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/Model/GeniusInvocationNotificationData.cs @@ -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; } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notification/Model/INotificationData.cs b/BetterGenshinImpact/Service/Notification/Model/INotificationData.cs deleted file mode 100644 index 94f80910..00000000 --- a/BetterGenshinImpact/Service/Notification/Model/INotificationData.cs +++ /dev/null @@ -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; } -} diff --git a/BetterGenshinImpact/Service/Notification/Model/LifecycleNotificationData.cs b/BetterGenshinImpact/Service/Notification/Model/LifecycleNotificationData.cs deleted file mode 100644 index 84c20a98..00000000 --- a/BetterGenshinImpact/Service/Notification/Model/LifecycleNotificationData.cs +++ /dev/null @@ -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 }; - } -} diff --git a/BetterGenshinImpact/Service/Notification/Model/NotificationTestResult.cs b/BetterGenshinImpact/Service/Notification/Model/NotificationTestResult.cs index 2e909a29..6b5a05ec 100644 --- a/BetterGenshinImpact/Service/Notification/Model/NotificationTestResult.cs +++ b/BetterGenshinImpact/Service/Notification/Model/NotificationTestResult.cs @@ -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) diff --git a/BetterGenshinImpact/Service/Notification/Model/ScriptNotificationData.cs b/BetterGenshinImpact/Service/Notification/Model/ScriptNotificationData.cs new file mode 100644 index 00000000..a45590bc --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/Model/ScriptNotificationData.cs @@ -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; } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notification/Model/TaskNotificationData.cs b/BetterGenshinImpact/Service/Notification/Model/TaskNotificationData.cs deleted file mode 100644 index ccce1d08..00000000 --- a/BetterGenshinImpact/Service/Notification/Model/TaskNotificationData.cs +++ /dev/null @@ -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; } -} diff --git a/BetterGenshinImpact/Service/Notification/NotificationConfig.cs b/BetterGenshinImpact/Service/Notification/NotificationConfig.cs index 280b62de..5e88042c 100644 --- a/BetterGenshinImpact/Service/Notification/NotificationConfig.cs +++ b/BetterGenshinImpact/Service/Notification/NotificationConfig.cs @@ -9,6 +9,11 @@ namespace BetterGenshinImpact.Service.Notification; [Serializable] public partial class NotificationConfig : ObservableObject { + + + [ObservableProperty] + private string _notificationEventSubscribe = string.Empty; + /// /// /// @@ -20,4 +25,48 @@ public partial class NotificationConfig : ObservableObject /// [ObservableProperty] private string _webhookEndpoint = string.Empty; + + + + /// + /// 是否包含截图 + /// + [ObservableProperty] + private bool _includeScreenShot = true; + + /// + /// windows uwp 通知是否启用 + /// + [ObservableProperty] + private bool _windowsUwpNotificationEnabled = false; + + + // 飞书通知 + /// + /// 飞书通知是否启用 + /// + [ObservableProperty] + private bool _feishuNotificationEnabled = false; + + + /// + /// 飞书通知地址 + /// + [ObservableProperty] + private string _feishuWebhookUrl = string.Empty; + + + // 企业微信通知 + /// + /// 企业微信通知是否启用 + /// + [ObservableProperty] + private bool _workweixinNotificationEnabled = false; + + + /// + /// 企业微信通知通知地址 + /// + [ObservableProperty] + private string _workweixinWebhookUrl = string.Empty; } diff --git a/BetterGenshinImpact/Service/Notification/NotificationHelper.cs b/BetterGenshinImpact/Service/Notification/NotificationHelper.cs deleted file mode 100644 index 11b07598..00000000 --- a/BetterGenshinImpact/Service/Notification/NotificationHelper.cs +++ /dev/null @@ -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 builderFunc) - { - var builder = new TaskNotificationBuilder(); - Notify(builderFunc(builder)); - } - - public static void SendTaskNotificationWithScreenshotUsing(Func builderFunc) - { - var builder = new TaskNotificationBuilder(); - var screenShot = (Bitmap)TaskControl.CaptureToRectArea().SrcBitmap.Clone(); - Notify(builderFunc(builder.WithScreenshot(screenShot))); - } -} diff --git a/BetterGenshinImpact/Service/Notification/NotificationService.cs b/BetterGenshinImpact/Service/Notification/NotificationService.cs index 94da9bd1..3d518f4f 100644 --- a/BetterGenshinImpact/Service/Notification/NotificationService.cs +++ b/BetterGenshinImpact/Service/Notification/NotificationService.cs @@ -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(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)); } -} +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notification/Notify.cs b/BetterGenshinImpact/Service/Notification/Notify.cs new file mode 100644 index 00000000..7a3c2f78 --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/Notify.cs @@ -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 + }; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notifier/FeishuNotifier.cs b/BetterGenshinImpact/Service/Notifier/FeishuNotifier.cs new file mode 100644 index 00000000..04e14e63 --- /dev/null +++ b/BetterGenshinImpact/Service/Notifier/FeishuNotifier.cs @@ -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"); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notifier/Interface/INotifier.cs b/BetterGenshinImpact/Service/Notifier/Interface/INotifier.cs index abda991a..3705e03f 100644 --- a/BetterGenshinImpact/Service/Notifier/Interface/INotifier.cs +++ b/BetterGenshinImpact/Service/Notifier/Interface/INotifier.cs @@ -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); } diff --git a/BetterGenshinImpact/Service/Notifier/NotifierManager.cs b/BetterGenshinImpact/Service/Notifier/NotifierManager.cs index cf8e632f..fd563257 100644 --- a/BetterGenshinImpact/Service/Notifier/NotifierManager.cs +++ b/BetterGenshinImpact/Service/Notifier/NotifierManager.cs @@ -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(HttpContent httpContent) where T : INotifier + public async Task SendNotificationAsync(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))); } } diff --git a/BetterGenshinImpact/Service/Notifier/WebhookNotifier.cs b/BetterGenshinImpact/Service/Notifier/WebhookNotifier.cs index 07331f91..e6b6483f 100644 --- a/BetterGenshinImpact/Service/Notifier/WebhookNotifier.cs +++ b/BetterGenshinImpact/Service/Notifier/WebhookNotifier.cs @@ -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(notificationData, _jsonSerializerOptions); + + return new StringContent(serializedData, Encoding.UTF8, "application/json"); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notifier/WindowsUwpNotifier.cs b/BetterGenshinImpact/Service/Notifier/WindowsUwpNotifier.cs new file mode 100644 index 00000000..8cfcf595 --- /dev/null +++ b/BetterGenshinImpact/Service/Notifier/WindowsUwpNotifier.cs @@ -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; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Notifier/WorkWeixinNotifier.cs b/BetterGenshinImpact/Service/Notifier/WorkWeixinNotifier.cs new file mode 100644 index 00000000..65a273e0 --- /dev/null +++ b/BetterGenshinImpact/Service/Notifier/WorkWeixinNotifier.cs @@ -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"); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/ScriptService.cs b/BetterGenshinImpact/Service/ScriptService.cs index 02acc783..51323dd1 100644 --- a/BetterGenshinImpact/Service/ScriptService.cs +++ b/BetterGenshinImpact/Service/ScriptService.cs @@ -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 _logger = App.GetLogger(); + 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 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 ReloadScriptProjects(IEnumerable 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(); @@ -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),使当前任务能够继续运行!"); + } } - } - }); + }); + } } } } \ No newline at end of file diff --git a/BetterGenshinImpact/Service/UpdateService.cs b/BetterGenshinImpact/Service/UpdateService.cs index a483ccff..c3c88bd8 100644 --- a/BetterGenshinImpact/Service/UpdateService.cs +++ b/BetterGenshinImpact/Service/UpdateService.cs @@ -26,9 +26,8 @@ public class UpdateService : IUpdateService private readonly ILogger _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; } } diff --git a/BetterGenshinImpact/User/pick_black_lists.json b/BetterGenshinImpact/User/pick_black_lists.json index f123f4e7..ca35443b 100644 --- a/BetterGenshinImpact/User/pick_black_lists.json +++ b/BetterGenshinImpact/User/pick_black_lists.json @@ -324,6 +324,7 @@ "百晓", "伍德", "蓝川丞", + "蓝砚", "松本", "甘乐", "卯师傅", diff --git a/BetterGenshinImpact/View/Controls/Webview/WebpagePanel.cs b/BetterGenshinImpact/View/Controls/Webview/WebpagePanel.cs index ef645539..0c62195c 100644 --- a/BetterGenshinImpact/View/Controls/Webview/WebpagePanel.cs +++ b/BetterGenshinImpact/View/Controls/Webview/WebpagePanel.cs @@ -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 { } + } + } diff --git a/BetterGenshinImpact/View/MainWindow.xaml b/BetterGenshinImpact/View/MainWindow.xaml index 1c03fa30..a97bd5bc 100644 --- a/BetterGenshinImpact/View/MainWindow.xaml +++ b/BetterGenshinImpact/View/MainWindow.xaml @@ -83,7 +83,7 @@ - + @@ -125,7 +125,7 @@ - + @@ -180,13 +180,14 @@ + Height="32" + Background="Transparent" + BorderBrush="Transparent" + Command="{Binding SwitchBackdropCommand}" + CornerRadius="0" + Icon="{ui:SymbolIcon Blur24}" + ToolTip="切换毛玻璃效果" + Visibility="{Binding IsWin11Later, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}" /> 0) + { + _logger.LogWarning("检测到 MSI Afterburner 正在运行,如果信息位于特定UI上遮盖图像识别要素可能导致识别失败,请关闭MSI Afterburner 或者调整信息位置后重试!"); + } } // private void ReadGameSettings() diff --git a/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml b/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml index 4465f2b9..67c6ce1b 100644 --- a/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml @@ -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"> - 在遮罩内显示日志窗口, + 在遮罩内显示日志窗口, + 点击打开日志文件夹 @@ -333,7 +332,9 @@ Grid.Column="0" Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}" TextWrapping="Wrap"> - 可以通过快捷键保存截图,文件保存在 + 可以通过快捷键保存截图,文件保存在 + log/screenshot @@ -431,11 +432,98 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -1350,12 +1350,37 @@ + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml index 970e8715..fe9cf668 100644 --- a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml +++ b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml @@ -282,6 +282,32 @@ IsChecked="{Binding PathingConfig.SoloTaskUseFightEnabled, Mode=TwoWay}" /> + + + + + + + + + + + + + - $"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); } } } \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/NotifyIconViewModel.cs b/BetterGenshinImpact/ViewModel/NotifyIconViewModel.cs index 0e87a9cd..dd0d514d 100644 --- a/BetterGenshinImpact/ViewModel/NotifyIconViewModel.cs +++ b/BetterGenshinImpact/ViewModel/NotifyIconViewModel.cs @@ -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()!.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>(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 } } } -} +} \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs index 34c74d2c..1f5ab887 100644 --- a/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/CommonSettingsPageViewModel.cs @@ -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(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(); + if(res.IsSuccess) + { + Toast.Success(res.Message); + } + else + { + Toast.Error(res.Message); + } + } + + [RelayCommand] + private async Task OnTestFeishuNotification() + { + var res = await _notificationService.TestNotifierAsync(); + if(res.IsSuccess) + { + Toast.Success(res.Message); + } + else + { + Toast.Error(res.Message); + } + } + + [RelayCommand] + private async Task OnTestWorkWeixinNotification() + { + var res = await _notificationService.TestNotifierAsync(); + if(res.IsSuccess) + { + Toast.Success(res.Message); + } + else + { + Toast.Error(res.Message); + } + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs index bad11fd6..f27dd987 100644 --- a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs @@ -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(); 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] diff --git a/BetterGenshinImpact/ViewModel/Pages/HotKeyPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/HotKeyPageViewModel.cs index 80ce0a34..343eb4e7 100644 --- a/BetterGenshinImpact/ViewModel/Pages/HotKeyPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/HotKeyPageViewModel.cs @@ -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( diff --git a/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs index 74d6552d..ffab9822 100644 --- a/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs @@ -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] diff --git a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs index b2c39aec..cecf68fd 100644 --- a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs @@ -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] diff --git a/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs index 1df4a56a..d28fc063 100644 --- a/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs @@ -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 }); } } diff --git a/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs index 4fd46302..42ad2a8f 100644 --- a/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs @@ -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] diff --git a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs index 088b8b85..d05f2203 100644 --- a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs @@ -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("一条龙结束"); }); } diff --git a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs index 1d8b0387..51c27c24 100644 --- a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs @@ -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 projects = new(); List oldProjects = new(); - oldProjects.AddRange(SelectedScriptGroup?.Projects); + oldProjects.AddRange(SelectedScriptGroup?.Projects ?? []); var oldcount = oldProjects.Count; List 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 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(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> 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> nodes, string filter) + private void ApplyFilter(StackPanel parentPanel, IEnumerable> nodes, string filter,bool? excludeSelectedFolder = false) { - if (parentPanel.Children.Count > 0 && parentPanel.Children[0] is TextBox filterTextBox) + if (parentPanel.Children.Count > 0) + { + List removeElements = new List(); + 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 skipFolderNames = SelectedScriptGroup?.Projects.ToList().Select(item=>item.FolderName).Distinct().ToList() ?? []; + //复制Nodes + string jsonString = JsonSerializer.Serialize(nodes); + var copiedNodes = JsonSerializer.Deserialize>>(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> 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, }; diff --git a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs index 361e1b6f..e9a0ffd0 100644 --- a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs @@ -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 _domainNameList; - + public static List ArtifactSalvageStarList = ["4", "3", "2", "1"]; - [ObservableProperty] + [ObservableProperty] private List _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] diff --git a/Build/setup_build_for_appveyor.cmd b/Build/setup_build_for_appveyor.cmd index 9170c40f..78d811f4 100644 --- a/Build/setup_build_for_appveyor.cmd +++ b/Build/setup_build_for_appveyor.cmd @@ -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 diff --git a/README.md b/README.md index 45254fb9..c81e0091 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@

- +
- BetterGI + BetterGI

babalae%2Fbetter-genshin-impact | Trendshift
@@ -20,21 +20,28 @@ BetterGI · 更好的原神, 一个基于计算机视觉技术,意图让原神变的更好的项目。 ## 功能 - * [自动拾取](https://bgi.huiyadan.com/doc.html#%E8%87%AA%E5%8A%A8%E6%8B%BE%E5%8F%96):遇到可交互/拾取内容时自动按 F,支持黑白名单配置 - * [自动剧情](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):自动 Z 键使用「王树瑞佑」,利用上下线可以刷新木材的原理,挂机刷满一背包的木材 - * [自动战斗](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):遇到可交互/拾取内容时自动按 F,支持黑白名单配置 + * [自动剧情](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):自动 Z 键使用「王树瑞佑」,利用上下线可以刷新木材的原理,挂机刷满一背包的木材 + * [自动秘境](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)
@@ -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) 对本项目的指导与贡献