From a872dde7c8d8725244808a29f883b0faa8379933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Feb 2026 18:02:26 +0800 Subject: [PATCH 001/107] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=8B=BE=E5=8F=96?= =?UTF-8?q?=E6=8E=92=E9=99=A4=EF=BC=9A=E3=80=8C=E6=9C=88=E8=B0=95=E5=9C=A3?= =?UTF-8?q?=E7=89=8C=E3=80=8D=E6=94=B6=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs index 82915c1f..15687ca2 100644 --- a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs +++ b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs @@ -444,6 +444,11 @@ public partial class AutoPickTrigger : ITaskTrigger { return true; } + + if (text.Contains("月谕圣牌")) + { + return true; + } return false; } From 6832d0f8d8daf865cd799eb03aebc9b4f3fb6e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Feb 2026 18:03:52 +0800 Subject: [PATCH 002/107] =?UTF-8?q?revert=20revert=20#2809=20with=20#2763?= =?UTF-8?q?=20,=20=E5=87=8F=E8=BD=BB=E6=AD=BB=E9=94=81=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E7=9A=84=E8=A1=A8=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/Service/ScriptService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/Service/ScriptService.cs b/BetterGenshinImpact/Service/ScriptService.cs index ec6ac093..83d44591 100644 --- a/BetterGenshinImpact/Service/ScriptService.cs +++ b/BetterGenshinImpact/Service/ScriptService.cs @@ -420,7 +420,7 @@ public partial class ScriptService : IScriptService // 还原定时器 - TaskTriggerDispatcher.Instance().SetTriggers(GameTaskManager.LoadInitialTriggers()); + // TaskTriggerDispatcher.Instance().SetTriggers(GameTaskManager.LoadInitialTriggers()); if (!string.IsNullOrEmpty(groupName)&&!RunnerContext.Instance.IsPreExecution) { From fe61549c953db8363404a0fd15d22f7cf54d4bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Feb 2026 18:17:38 +0800 Subject: [PATCH 003/107] =?UTF-8?q?=E5=BD=BB=E5=BA=95=E5=88=A0=E9=99=A4=20?= =?UTF-8?q?=E5=AE=9E=E9=AA=8C=E5=8A=9F=E8=83=BD=20=E9=81=AE=E7=BD=A9?= =?UTF-8?q?=E4=BB=A5=E5=8E=9F=E7=A5=9E=E5=AD=90=E7=AA=97=E4=BD=93=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E5=90=AF=E5=8A=A8=20UseSubform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Config/MaskWindowConfig.cs | 7 ---- .../GameTask/TaskTriggerDispatcher.cs | 38 ++++++++----------- BetterGenshinImpact/View/MaskWindow.xaml.cs | 28 +------------- .../View/Pages/CommonSettingsPage.xaml | 27 ------------- 4 files changed, 17 insertions(+), 83 deletions(-) diff --git a/BetterGenshinImpact/Core/Config/MaskWindowConfig.cs b/BetterGenshinImpact/Core/Config/MaskWindowConfig.cs index 98cfccd1..49143e8b 100644 --- a/BetterGenshinImpact/Core/Config/MaskWindowConfig.cs +++ b/BetterGenshinImpact/Core/Config/MaskWindowConfig.cs @@ -63,13 +63,6 @@ public partial class MaskWindowConfig : ObservableObject [ObservableProperty] private bool _showFps = false; - /// - /// 作为原神子窗体 - /// 有些bug没解决 - /// - [ObservableProperty] - private bool _useSubform = false; - /// /// 遮罩文本透明度 (0.0-1.0) /// diff --git a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs index f274c93a..41e7e5f8 100644 --- a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs +++ b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.GameTask.Common; using BetterGenshinImpact.Helpers; using BetterGenshinImpact.View; @@ -259,14 +259,11 @@ namespace BetterGenshinImpact.GameTask Debug.WriteLine("游戏窗口不在前台, 不再进行截屏"); } - if (!TaskContext.Instance().Config.MaskWindowConfig.UseSubform) + var pName = SystemControl.GetActiveProcessName(); + if (pName != "Idle" && pName != "BetterGI" && pName != "YuanShen" && pName != "GenshinImpact" && pName != "Genshin Impact Cloud Game") { - var pName = SystemControl.GetActiveProcessName(); - if (pName != "Idle" && pName != "BetterGI" && pName != "YuanShen" && pName != "GenshinImpact" && pName != "Genshin Impact Cloud Game") - { - // Debug.WriteLine(pName + ":hide mask window"); - maskWindow.Invoke(() => { maskWindow.HideSelf(); }); - } + // Debug.WriteLine(pName + ":hide mask window"); + maskWindow.Invoke(() => { maskWindow.HideSelf(); }); } _prevGameActive = active; @@ -302,23 +299,20 @@ namespace BetterGenshinImpact.GameTask else { PictureInPictureService.Hide(resetManual: true); - if (!TaskContext.Instance().Config.MaskWindowConfig.UseSubform) + // if (!_prevGameActive) + // { + maskWindow.Invoke(() => { - // if (!_prevGameActive) - // { - maskWindow.Invoke(() => + if (maskWindow.IsExist()) { - if (maskWindow.IsExist()) + maskWindow.Show(); + if (!_prevGameActive) { - maskWindow.Show(); - if (!_prevGameActive) - { - maskWindow.BringToTop(); - } + maskWindow.BringToTop(); } - }); - // } - } + } + }); + // } _prevGameActive = active; // // 移动游戏窗口的时候同步遮罩窗口的位置,此时不进行捕获 @@ -528,4 +522,4 @@ namespace BetterGenshinImpact.GameTask } } } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/View/MaskWindow.xaml.cs b/BetterGenshinImpact/View/MaskWindow.xaml.cs index ba5d0e9b..26d3c830 100644 --- a/BetterGenshinImpact/View/MaskWindow.xaml.cs +++ b/BetterGenshinImpact/View/MaskWindow.xaml.cs @@ -43,7 +43,6 @@ public partial class MaskWindow : Window private static readonly Typeface _typeface; private static readonly Typeface _fgiTypeface; - private nint _hWnd; private MaskWindowViewModel? _viewModel; private IRichTextBox? _richTextBox; @@ -104,14 +103,7 @@ public partial class MaskWindow : Window public void RefreshPosition() { - if (TaskContext.Instance().Config.MaskWindowConfig.UseSubform) - { - RefreshPositionForSubform(); - } - else - { - RefreshPositionForNormal(); - } + RefreshPositionForNormal(); } public void RefreshPositionForNormal() @@ -130,13 +122,6 @@ public partial class MaskWindow : Window }); } - public void RefreshPositionForSubform() - { - nint targetHWnd = TaskContext.Instance().GameHandle; - _ = User32.GetClientRect(targetHWnd, out RECT targetRect); - _ = User32.SetWindowPos(_hWnd, IntPtr.Zero, 0, 0, targetRect.Width, targetRect.Height, User32.SetWindowPosFlags.SWP_SHOWWINDOW); - } - public MaskWindow() { _maskWindow = this; @@ -204,17 +189,6 @@ public partial class MaskWindow : Window UpdateClickThroughState(); - if (TaskContext.Instance().Config.MaskWindowConfig.UseSubform) - { - _hWnd = new WindowInteropHelper(this).Handle; - nint targetHWnd = TaskContext.Instance().GameHandle; - - if (User32.GetParent(_hWnd) != targetHWnd) - { - _ = User32.SetParent(_hWnd, targetHWnd); - } - } - RefreshPosition(); PrintSystemInfo(); if (_viewModel != null) diff --git a/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml b/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml index 578bb8a4..1f223a77 100644 --- a/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml @@ -449,33 +449,6 @@ FontSize="14" Text="{Binding Config.MaskWindowConfig.TextOpacity, StringFormat=F2}" /> - - - From d632bdd9fc24f1e7f2e8d926cca953005c56f9fe Mon Sep 17 00:00:00 2001 From: DarkFlameMaster <1004452714@qq.com> Date: Sun, 22 Feb 2026 19:37:47 +0800 Subject: [PATCH 004/107] =?UTF-8?q?fix:=E8=87=AA=E5=8A=A8=E6=8B=BE?= =?UTF-8?q?=E5=8F=96=E9=BB=91=E5=90=8D=E5=8D=95=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E6=A1=86=E5=A4=8D=E5=88=B6=E5=89=AA=E5=88=87=E6=97=B6?= =?UTF-8?q?UI=E7=9F=AD=E6=9A=82=E6=97=A0=E5=93=8D=E5=BA=94=20(#2818)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Pages/TriggerSettingsPageViewModel.cs | 94 ++++--------------- 1 file changed, 20 insertions(+), 74 deletions(-) diff --git a/BetterGenshinImpact/ViewModel/Pages/TriggerSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/TriggerSettingsPageViewModel.cs index 0c3559f0..5474c255 100644 --- a/BetterGenshinImpact/ViewModel/Pages/TriggerSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/TriggerSettingsPageViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.GameTask.AutoPick; using BetterGenshinImpact.GameTask.AutoSkip.Assets; @@ -15,8 +15,6 @@ using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; using BetterGenshinImpact.GameTask; using BetterGenshinImpact.GameTask.Common; using BetterGenshinImpact.GameTask.SkillCd; @@ -70,92 +68,44 @@ public partial class TriggerSettingsPageViewModel : ViewModel { // 读取精确匹配黑名单 var exactPath = @"User\pick_black_lists.txt"; - var exactText = Global.ReadAllTextIfExist(exactPath); + var exactText = Global.ReadAllTextIfExist(exactPath) ?? string.Empty; // 读取模糊匹配黑名单 var fuzzyPath = @"User\pick_fuzzy_black_lists.txt"; - var fuzzyText = Global.ReadAllTextIfExist(fuzzyPath); + var fuzzyText = Global.ReadAllTextIfExist(fuzzyPath) ?? string.Empty; // 创建精确匹配黑名单输入框 - var exactRichTextBox = new System.Windows.Controls.RichTextBox + var exactTextBox = new TextBox { Height = 150, Width = 400, - VerticalScrollBarVisibility = ScrollBarVisibility.Auto, - HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, + TextWrapping = TextWrapping.Wrap, AcceptsReturn = true, - AcceptsTab = true + VerticalScrollBarVisibility = ScrollBarVisibility.Auto, + PlaceholderText = "每行一条记录", + Text = exactText }; - - // 创建 FlowDocument 并设置紧凑的行间距 - var exactFlowDocument = new FlowDocument(); - exactFlowDocument.LineHeight = 1.0; // 设置行高为1倍 - exactFlowDocument.PagePadding = new Thickness(2); // 减小页面内边距 - - // 设置 RichTextBox 的文本内容 - if (!string.IsNullOrEmpty(exactText)) - { - var paragraph = new Paragraph(new Run(exactText)); - paragraph.Margin = new Thickness(0); // 移除段落边距 - paragraph.LineHeight = 1.0; // 设置段落行高 - exactFlowDocument.Blocks.Add(paragraph); - } - else - { - // 即使没有文本也要设置默认段落样式 - var paragraph = new Paragraph(); - paragraph.Margin = new Thickness(0); - paragraph.LineHeight = 1.0; - exactFlowDocument.Blocks.Add(paragraph); - } - - exactRichTextBox.Document = exactFlowDocument; - // 创建模糊匹配黑名单输入框 - var fuzzyRichTextBox = new System.Windows.Controls.RichTextBox + var fuzzyTextBox = new TextBox { Height = 150, Width = 400, - VerticalScrollBarVisibility = ScrollBarVisibility.Auto, - HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, + TextWrapping = TextWrapping.Wrap, AcceptsReturn = true, - AcceptsTab = true + VerticalScrollBarVisibility = ScrollBarVisibility.Auto, + PlaceholderText = "每行一条记录", + Text = fuzzyText }; - - // 创建 FlowDocument 并设置紧凑的行间距 - var fuzzyFlowDocument = new FlowDocument(); - fuzzyFlowDocument.LineHeight = 1.0; // 设置行高为1倍 - fuzzyFlowDocument.PagePadding = new Thickness(2); // 减小页面内边距 - - // 设置 RichTextBox 的文本内容 - if (!string.IsNullOrEmpty(fuzzyText)) - { - var paragraph = new Paragraph(new Run(fuzzyText)); - paragraph.Margin = new Thickness(0); // 移除段落边距 - paragraph.LineHeight = 1.0; // 设置段落行高 - fuzzyFlowDocument.Blocks.Add(paragraph); - } - else - { - // 即使没有文本也要设置默认段落样式 - var paragraph = new Paragraph(); - paragraph.Margin = new Thickness(0); - paragraph.LineHeight = 1.0; - fuzzyFlowDocument.Blocks.Add(paragraph); - } - - fuzzyRichTextBox.Document = fuzzyFlowDocument; - // 创建包含两个输入框的容器 var stackPanel = new StackPanel(); - + var exactLabel = new Wpf.Ui.Controls.TextBlock { Text = "精确匹配黑名单:", FontWeight = FontWeights.Bold, Margin = new Thickness(0, 0, 0, 5) }; - + var fuzzyLabel = new Wpf.Ui.Controls.TextBlock { Text = "模糊匹配黑名单:", @@ -164,9 +114,9 @@ public partial class TriggerSettingsPageViewModel : ViewModel }; stackPanel.Children.Add(exactLabel); - stackPanel.Children.Add(exactRichTextBox); + stackPanel.Children.Add(exactTextBox); stackPanel.Children.Add(fuzzyLabel); - stackPanel.Children.Add(fuzzyRichTextBox); + stackPanel.Children.Add(fuzzyTextBox); var p = new PromptDialog( "黑名单配置\n" + @@ -181,15 +131,11 @@ public partial class TriggerSettingsPageViewModel : ViewModel p.Height = 600; p.Width = 500; p.ShowDialog(); - + if (p.DialogResult == true) { - // 从 RichTextBox 获取纯文本内容 - var exactTextRange = new TextRange(exactRichTextBox.Document.ContentStart, exactRichTextBox.Document.ContentEnd); - var fuzzyTextRange = new TextRange(fuzzyRichTextBox.Document.ContentStart, fuzzyRichTextBox.Document.ContentEnd); - - File.WriteAllText(Global.Absolute(exactPath), exactTextRange.Text); - File.WriteAllText(Global.Absolute(fuzzyPath), fuzzyTextRange.Text); + Global.WriteAllText(exactPath, exactTextBox.Text); + Global.WriteAllText(fuzzyPath, fuzzyTextBox.Text); GameTaskManager.RefreshTriggerConfigs(); } } From b1c6e9e4e3e7af2058cfaeb5358a3f306b838e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BA=81=E5=8A=A8=E7=9A=84=E6=B0=A8=E6=B0=94?= <131591012+zaodonganqi@users.noreply.github.com> Date: Sun, 22 Feb 2026 19:38:10 +0800 Subject: [PATCH 005/107] =?UTF-8?q?Revert=20"feat:=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=8B=BE=E5=8F=96=E6=96=87=E6=9C=AC=E8=AF=86=E5=88=AB=E5=B8=A7?= =?UTF-8?q?=E9=97=B4=E8=BF=9E=E7=BB=AD=E6=80=A7=E6=A3=80=E6=9F=A5=20(#2676?= =?UTF-8?q?)"=20(#2817)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoPick/AutoPickTrigger.cs | 56 +++---------------- 1 file changed, 9 insertions(+), 47 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs index 15687ca2..b4c19cf5 100644 --- a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs +++ b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs @@ -54,47 +54,6 @@ public partial class AutoPickTrigger : ITaskTrigger // 外部配置 private AutoPickExternalConfig? _externalConfig; - /// - /// 上一帧 OCR 结果 - /// - private string? _lastOcrText; - - /// - /// 连续相同 OCR 结果的帧数 - /// - private int _sameOcrFrameCount; - - /// - /// 判定为稳定所需的最小连续帧数 - /// - private const int OcrStableFrameThreshold = 2; - - /// - /// OCR 帧稳定确认 - /// - private bool IsOcrTextStable(string text) - { - if (text == _lastOcrText) - { - _sameOcrFrameCount++; - } - else - { - _lastOcrText = text; - _sameOcrFrameCount = 1; - } - - if (_sameOcrFrameCount >= OcrStableFrameThreshold) - { - // 重置 - _lastOcrText = null; - _sameOcrFrameCount = 0; - return true; - } - - return false; - } - public AutoPickTrigger() { _autoPickAssets = AutoPickAssets.Instance; @@ -356,43 +315,44 @@ public partial class AutoPickTrigger : ITaskTrigger } speedTimer.Record("文字识别"); - if (!string.IsNullOrEmpty(text)) { // 处理OCR识别结果,清理无效字符并确保引号配对 text = ProcessOcrText(text); + if (DoNotPick(text)) { return; } + // 单个字符不拾取 if (text.Length <= 1) { return; } - // 连续多帧识别结果一致,进行下一步 - if (!IsOcrTextStable(text)) - { - return; - } + if (config.WhiteListEnabled && _whiteList.Contains(text)) { LogPick(content, text); Simulation.SendInput.Keyboard.KeyPress(AutoPickAssets.Instance.PickVk); return; } + speedTimer.Record("白名单判断"); + if (isExcludeIcon) { //Debug.WriteLine("AutoPickTrigger: 物品图标是聊天气泡,一般是NPC对话,不拾取"); return; } + if (config.BlackListEnabled) { if (_blackList.Contains(text)) { return; } + if (_fuzzyBlackList.Count > 0) { if (_fuzzyBlackList.Any(item => text.Contains(item))) @@ -401,7 +361,9 @@ public partial class AutoPickTrigger : ITaskTrigger } } } + speedTimer.Record("黑名单判断"); + LogPick(content, text); Simulation.SendInput.Keyboard.KeyPress(AutoPickAssets.Instance.PickVk); } From f86abe3cebe1db2457e3e11120773376b14ca79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Mon, 23 Feb 2026 13:18:18 +0800 Subject: [PATCH 006/107] =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BB=BB=E6=84=8F?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8D=E7=9A=84=E5=90=AF=E5=8A=A8=20(#2819?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/SystemControl.cs | 29 ++++++++++++---- BetterGenshinImpact/GameTask/TaskContext.cs | 34 ++++++++++++++++++- .../ViewModel/Pages/HomePageViewModel.cs | 2 +- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/BetterGenshinImpact/GameTask/SystemControl.cs b/BetterGenshinImpact/GameTask/SystemControl.cs index d87b8f50..18e16a47 100644 --- a/BetterGenshinImpact/GameTask/SystemControl.cs +++ b/BetterGenshinImpact/GameTask/SystemControl.cs @@ -13,7 +13,8 @@ public class SystemControl { public static nint FindGenshinImpactHandle() { - return FindHandleByProcessName("YuanShen", "GenshinImpact", "Genshin Impact Cloud Game", "Genshin Impact Cloud"); + var processNames = TaskContext.Instance().GetGenshinGameProcessNameList(); + return FindHandleByProcessName(processNames.ToArray()); } public static async Task StartFromLocalAsync(string path) @@ -69,7 +70,13 @@ public class SystemControl public static bool IsGenshinImpactActiveByProcess() { var name = GetActiveProcessName(); - return name is "YuanShen" or "yuanshen" or "GenshinImpact" or "Genshin Impact Cloud Game"; + if (string.IsNullOrEmpty(name)) + { + return false; + } + + var processNames = TaskContext.Instance().GetGenshinGameProcessNameList(); + return processNames.Any(p => string.Equals(p, name, StringComparison.OrdinalIgnoreCase)); } public static string GetActiveByProcess() @@ -312,11 +319,19 @@ public class SystemControl { try { - // 尝试通过进程名称查找原神进程 - var processes = Process.GetProcessesByName("YuanShen") - .Concat(Process.GetProcessesByName("GenshinImpact")) - .Concat(Process.GetProcessesByName("Genshin Impact Cloud Game")) - .ToArray(); + var processNames = TaskContext.Instance().GetGenshinGameProcessNameList(); + var allProcesses = processNames + .SelectMany(Process.GetProcessesByName) + .ToList(); + var processMap = allProcesses + .GroupBy(p => p.Id) + .ToDictionary(g => g.Key, g => g.First()); + // 释放重复的 Process 包装对象 + foreach (var p in allProcesses.Where(p => !ReferenceEquals(p, processMap[p.Id]))) + { + p.Dispose(); + } + var processes = processMap.Values.ToArray(); if (processes.Length > 0) { diff --git a/BetterGenshinImpact/GameTask/TaskContext.cs b/BetterGenshinImpact/GameTask/TaskContext.cs index ddd16d5a..4ce0b6cf 100644 --- a/BetterGenshinImpact/GameTask/TaskContext.cs +++ b/BetterGenshinImpact/GameTask/TaskContext.cs @@ -5,6 +5,8 @@ using BetterGenshinImpact.Genshin.Settings; using BetterGenshinImpact.Helpers; using BetterGenshinImpact.Service; using System; +using System.Collections.Generic; +using System.IO; using System.Threading; using BetterGenshinImpact.Core.Script.Group; @@ -74,5 +76,35 @@ namespace BetterGenshinImpact.GameTask /// 注意 IsInitialized = false 时,这个值就会被设置 /// public DateTime LinkedStartGenshinTime { get; set; } = DateTime.MinValue; + + public List GetGenshinGameProcessNameList() + { + if (IsInitialized) + { + return [SystemInfo.GameProcessName]; + } + else + { + List list = ["YuanShen", "GenshinImpact", "Genshin Impact Cloud Game", "Genshin Impact Cloud"]; + try + { + var installPath = Config.GenshinStartConfig.InstallPath; + if (!string.IsNullOrEmpty(installPath)) + { + var customName = Path.GetFileNameWithoutExtension(installPath); + if (!string.IsNullOrEmpty(customName) && !list.Contains(customName)) + { + list.Insert(0, customName); // 将用户自定义的进程名放在列表前面,优先匹配 + } + } + } + catch + { + /* ignore */ + } + + return list; + } + } } -} +} \ No newline at end of file diff --git a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs index 5de056bf..3777949f 100644 --- a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs @@ -383,7 +383,7 @@ public partial class HomePageViewModel : ViewModel // 弹出选择文件夹对话框 var dialog = new Ookii.Dialogs.Wpf.VistaOpenFileDialog { - Filter = "原神|YuanShen.exe|原神国际服|GenshinImpact.exe|所有文件|*.*" + Filter = "原神|YuanShen.exe;GenshinImpact.exe|可执行文件|*.exe|所有文件|*.*" }; if (dialog.ShowDialog() == true) { From d4d99d34bcee658ba2cf7539fbbebdaba4db9457 Mon Sep 17 00:00:00 2001 From: this-Fish Date: Mon, 23 Feb 2026 19:02:45 +0800 Subject: [PATCH 007/107] =?UTF-8?q?feat:=20=E4=B8=BA=E5=BF=AB=E9=80=9F?= =?UTF-8?q?=E5=B0=98=E6=AD=8C=E5=A3=B6=E4=BB=BB=E5=8A=A1=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=A6=BB=E5=BC=80=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=BF=9B=E5=87=BA=E5=8F=8C=E5=90=91=E6=93=8D=E4=BD=9C=20(#2822?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QuickSereniteaPotTask.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/BetterGenshinImpact/GameTask/QuickSereniteaPot/QuickSereniteaPotTask.cs b/BetterGenshinImpact/GameTask/QuickSereniteaPot/QuickSereniteaPotTask.cs index 8d1e647f..814b10b6 100644 --- a/BetterGenshinImpact/GameTask/QuickSereniteaPot/QuickSereniteaPotTask.cs +++ b/BetterGenshinImpact/GameTask/QuickSereniteaPot/QuickSereniteaPotTask.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.Core.Simulator; +using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.Core.Simulator.Extensions; using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; using BetterGenshinImpact.GameTask.Common; @@ -103,22 +103,26 @@ public class QuickSereniteaPotTask } } } - // 校验F交互是否是 进入[尘歌壶] - bool canIn = Bv.FindF(TaskControl.CaptureToRectArea(), "进入","尘歌壶"); + // 校验F交互是否是 进入/离开[尘歌壶] + var capture = TaskControl.CaptureToRectArea(); + bool isEnter = Bv.FindF(capture, "进入", "尘歌壶"); + bool isLeave = Bv.FindF(capture, "离开", "尘歌壶"); - if (canIn) { - TaskControl.Logger.LogInformation("快速进入尘歌壶:识别到 进入尘歌壶"); - // 按F进入 + if (isEnter || isLeave) { + string action = isEnter ? "进入" : "离开"; + TaskControl.Logger.LogInformation($"快速进出尘歌壶:识别到 {action}尘歌壶"); + + // 按F触发交互 Simulation.SendInput.SimulateAction(GIActions.PickUpOrInteract); - TaskControl.Logger.LogInformation("快速进入尘歌壶:F进入尘歌壶"); + TaskControl.Logger.LogInformation($"快速进出尘歌壶:F{action}尘歌壶"); TaskControl.CheckAndSleep(200); - // 点击进入尘歌壶 + // 点击进入/离开尘歌壶 // 如果不是联机状态,此时玩家应已进入传送界面,本次点击不会影响实际功能 GameCaptureRegion.GameRegion1080PPosClick(1010, 760); } else { - TaskControl.Logger.LogInformation("快速进入尘歌壶:未识别到 进入尘歌壶"); + TaskControl.Logger.LogInformation("快速进出尘歌壶:未识别到 进入或离开尘歌壶"); } } catch (Exception e) From 847bc8970e26dfa6c6c8060ef37ded36be0857ed Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:03:39 +0000 Subject: [PATCH 008/107] Update version to 0.57.1-alpha.2 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index da5a0acc..9e76ca01 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.57.1-alpha.1 + 0.57.1-alpha.2 false WinExe net8.0-windows10.0.22621.0 From cac0dfc75412d67aa83a6f63c41d78608bc7b22b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=91=E7=AB=AF=E5=AE=A2?= <107686912+Kirito520Asuna@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:12:16 +0800 Subject: [PATCH 009/107] =?UTF-8?q?=E5=BC=80=E6=94=BE=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=9C=B0=E8=84=89=E8=8A=B1=20JS=E8=B0=83=E7=94=A8=20=20(#2789)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 辉鸭蛋 --- .../Core/Script/Dependence/Dispatcher.cs | 18 ++++ .../Core/Script/EngineExtend.cs | 2 + .../AutoLeyLineOutcropParam.cs | 70 ++++++++++++++ .../AutoLeyLineOutcropTask.cs | 94 +++++++++---------- .../Model/OneDragonTaskItem.cs | 5 +- .../Pages/TaskSettingsPageViewModel.cs | 4 +- 6 files changed, 144 insertions(+), 49 deletions(-) create mode 100644 BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs diff --git a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs index dd7e7bc6..408db45c 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs @@ -19,6 +19,7 @@ using System.Dynamic; using System.Threading; using System.Threading.Tasks; using BetterGenshinImpact.GameTask.AutoFight; +using BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; namespace BetterGenshinImpact.Core.Script.Dependence; @@ -339,4 +340,21 @@ public class Dispatcher CancellationToken cancellationToken = customCt ?? CancellationContext.Instance.Cts.Token; await new AutoFightTask(param).Start(cancellationToken); } + + /// + /// 运行自动地脉花任务 + /// + /// 自动地脉花任务参数 + /// 自定义取消令牌 + /// + public async Task RunAutoLeyLineOutcropTask(AutoLeyLineOutcropParam param, CancellationToken? customCt = null) + { + if (param == null) + { + throw new ArgumentNullException(nameof(param), "自动地脉花任务参数不能为空"); + } + + CancellationToken cancellationToken = customCt ?? CancellationContext.Instance.Cts.Token; + await new AutoLeyLineOutcropTask(param).Start(cancellationToken); + } } diff --git a/BetterGenshinImpact/Core/Script/EngineExtend.cs b/BetterGenshinImpact/Core/Script/EngineExtend.cs index 6bcd472f..982b70e2 100644 --- a/BetterGenshinImpact/Core/Script/EngineExtend.cs +++ b/BetterGenshinImpact/Core/Script/EngineExtend.cs @@ -13,6 +13,7 @@ using BetterGenshinImpact.Core.Script.Utils; using BetterGenshinImpact.GameTask.AutoDomain; using BetterGenshinImpact.GameTask.AutoFight; using BetterGenshinImpact.GameTask.AutoFight.Model; +using BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; using BetterGenshinImpact.GameTask.AutoSkip; namespace BetterGenshinImpact.Core.Script; @@ -73,6 +74,7 @@ public class EngineExtend engine.AddHostType("AutoDomainParam", typeof(AutoDomainParam)); engine.AddHostType("AutoFightParam", typeof(AutoFightParam)); + engine.AddHostType("AutoLeyLineOutcropParam", typeof(AutoLeyLineOutcropParam)); //鼠标回调 engine.AddHostType("KeyMouseHook", typeof(KeyMouseHook)); // 添加C#的类型 diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs new file mode 100644 index 00000000..8f728989 --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs @@ -0,0 +1,70 @@ +using BetterGenshinImpact.GameTask.Model; + +namespace BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; + +public class AutoLeyLineOutcropParam:BaseTaskParam +{ + + // 刷取次数 + public int Count { get; set; } + // 地区 + public string Country { get; set; } + //地脉花类型 + public string LeyLineOutcropType { get; set; } + // 开启取小值模式 + public bool OpenModeCountMin { get; set; } + // 是否开启树脂耗尽模式 + public bool IsResinExhaustionMode { get; set; } + //是否使用冒险之证寻找地脉花 + public bool UseAdventurerHandbook { get; set; } + //好感队名称 + public string FriendshipTeam { get; set; } + //战斗的队伍名称 + public string Team { get; set; } + //战斗超时时间 + public int Timeout { get; set; } + //是否前往合成台合成浓缩树脂 + public bool IsGoToSynthesizer { get; set; } + //是否使用脆弱树脂 + public bool UseFragileResin { get; set; } + //是否使用须臾树脂 + public bool UseTransientResin { get; set; } + //通过BGI通知系统发送详细通知 + public bool IsNotification { get; set; } + + public void SetDefault() + { + var config = TaskContext.Instance().Config.AutoLeyLineOutcropConfig; + SetAutoLeyLineOutcropConfig(config); + } + + public void SetAutoLeyLineOutcropConfig(AutoLeyLineOutcropConfig config) + { + OpenModeCountMin= config.OpenModeCountMin; + IsResinExhaustionMode= config.IsResinExhaustionMode; + UseAdventurerHandbook= config.UseAdventurerHandbook; + FriendshipTeam= config.FriendshipTeam; + Team= config.Team; + Timeout= config.Timeout; + IsGoToSynthesizer=config.IsGoToSynthesizer; + UseFragileResin= config.UseFragileResin; + UseTransientResin= config.UseTransientResin; + IsNotification= config.IsNotification; + Count = config.Count; + Country = config.Country; + LeyLineOutcropType = config.LeyLineOutcropType; + } + + public AutoLeyLineOutcropParam() : base(null, null) + { + SetDefault(); + } + + public AutoLeyLineOutcropParam(int count, string country, string leyLineOutcropType) : base(null, null) + { + SetDefault(); + this.Count = count; + this.Country = country; + this.LeyLineOutcropType = leyLineOutcropType; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs index 3fe2f271..ed8e5a16 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs @@ -37,7 +37,7 @@ namespace BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; public class AutoLeyLineOutcropTask : ISoloTask { private readonly ILogger _logger = App.GetLogger(); - private readonly AutoLeyLineOutcropConfig _config; + private readonly AutoLeyLineOutcropParam _taskParam; private readonly bool _oneDragonMode; private TpTask _tpTask = null!; private readonly ReturnMainUiTask _returnMainUiTask = new(); @@ -73,9 +73,9 @@ public class AutoLeyLineOutcropTask : ISoloTask public string Name => "自动地脉花"; - public AutoLeyLineOutcropTask(AutoLeyLineOutcropConfig config, bool oneDragonMode = false) + public AutoLeyLineOutcropTask(AutoLeyLineOutcropParam taskParam, bool oneDragonMode = false) { - _config = config; + _taskParam = taskParam; _oneDragonMode = oneDragonMode; } @@ -95,7 +95,7 @@ public class AutoLeyLineOutcropTask : ISoloTask await PrepareForLeyLineRun(); await RunLeyLineChallenges(); - if (_config.IsResinExhaustionMode) + if (_taskParam.IsResinExhaustionMode) { await RecheckResinAndContinue(); } @@ -108,7 +108,7 @@ public class AutoLeyLineOutcropTask : ISoloTask { _logger.LogDebug(e, "自动地脉花执行失败"); _logger.LogError("自动地脉花执行失败:" + e.Message); - if (_config.IsNotification) + if (_taskParam.IsNotification) { Notify.Event("AutoLeyLineOutcrop").Error($"任务失败: {e.Message}"); } @@ -144,29 +144,29 @@ public class AutoLeyLineOutcropTask : ISoloTask private void ValidateSettings() { - if (string.IsNullOrWhiteSpace(_config.LeyLineOutcropType)) + if (string.IsNullOrWhiteSpace(_taskParam.LeyLineOutcropType)) { throw new Exception("地脉花类型未选择"); } - if (_config.LeyLineOutcropType != "启示之花" && _config.LeyLineOutcropType != "藏金之花") + if (_taskParam.LeyLineOutcropType != "启示之花" && _taskParam.LeyLineOutcropType != "藏金之花") { throw new Exception("地脉花类型无效,请重新选择"); } - if (string.IsNullOrWhiteSpace(_config.Country)) + if (string.IsNullOrWhiteSpace(_taskParam.Country)) { throw new Exception("国家未配置"); } - if (!string.IsNullOrWhiteSpace(_config.FriendshipTeam) && string.IsNullOrWhiteSpace(_config.Team)) + if (!string.IsNullOrWhiteSpace(_taskParam.FriendshipTeam) && string.IsNullOrWhiteSpace(_taskParam.Team)) { throw new Exception("配置好感队时必须配置战斗队伍"); } - if (_config.Count < 1) + if (_taskParam.Count < 1) { - _config.Count = 1; + _taskParam.Count = 1; } } @@ -242,9 +242,9 @@ public class AutoLeyLineOutcropTask : ISoloTask private async Task HandleResinExhaustionMode() { - if (!_config.IsResinExhaustionMode) + if (!_taskParam.IsResinExhaustionMode) { - return _config.Count; + return _taskParam.Count; } var result = await CalCountByResin(); @@ -253,16 +253,16 @@ public class AutoLeyLineOutcropTask : ISoloTask return 0; } - if (_config.OpenModeCountMin) + if (_taskParam.OpenModeCountMin) { - _config.Count = Math.Min(result.Count, _config.Count); + _taskParam.Count = Math.Min(result.Count, _taskParam.Count); } else { - _config.Count = result.Count; + _taskParam.Count = result.Count; } - if (_config.IsNotification) + if (_taskParam.IsNotification) { var text = "树脂耗尽模式统计结果:\n" + @@ -274,7 +274,7 @@ public class AutoLeyLineOutcropTask : ISoloTask Notify.Event("AutoLeyLineOutcrop").Send(text); } - return _config.Count; + return _taskParam.Count; } private async Task PrepareForLeyLineRun() @@ -286,13 +286,13 @@ public class AutoLeyLineOutcropTask : ISoloTask await _tpTask.TpToStatueOfTheSeven(); } - if (!string.IsNullOrWhiteSpace(_config.Team)) + if (!string.IsNullOrWhiteSpace(_taskParam.Team)) { _switchPartyTask ??= new SwitchPartyTask(); - await _switchPartyTask.Start(_config.Team, _ct); + await _switchPartyTask.Start(_taskParam.Team, _ct); } - if (_config.UseAdventurerHandbook) + if (_taskParam.UseAdventurerHandbook) { // The config flag means "do NOT use handbook"; close custom marks for manual navigation. await CloseCustomMarks(); @@ -303,17 +303,17 @@ public class AutoLeyLineOutcropTask : ISoloTask private async Task RunLeyLineChallenges() { - while (_currentRunTimes < _config.Count) + while (_currentRunTimes < _taskParam.Count) { - if (!_config.UseAdventurerHandbook) + if (!_taskParam.UseAdventurerHandbook) { // Handbook flow: open the book and track a ley line target. - await FindLeyLineOutcropByBook(_config.Country, _config.LeyLineOutcropType); + await FindLeyLineOutcropByBook(_taskParam.Country, _taskParam.LeyLineOutcropType); } else { // Manual flow: detect the ley line on the big map. - await FindLeyLineOutcrop(_config.Country, _config.LeyLineOutcropType); + await FindLeyLineOutcrop(_taskParam.Country, _taskParam.LeyLineOutcropType); } var foundStrategy = await ExecuteMatchingStrategy(); @@ -332,7 +332,7 @@ public class AutoLeyLineOutcropTask : ISoloTask throw new Exception("地脉花策略配置缺失"); } - if (!_configData.LeyLinePositions.TryGetValue(_config.Country, out var positions)) + if (!_configData.LeyLinePositions.TryGetValue(_taskParam.Country, out var positions)) { return false; } @@ -379,13 +379,13 @@ public class AutoLeyLineOutcropTask : ISoloTask await ExecutePath(optimal); _currentRunTimes++; - if (_currentRunTimes >= _config.Count) + if (_currentRunTimes >= _taskParam.Count) { return; } var currentNode = targetNode; - while (currentNode.Next.Count > 0 && _currentRunTimes < _config.Count) + while (currentNode.Next.Count > 0 && _currentRunTimes < _taskParam.Count) { if (currentNode.Next.Count == 1) { @@ -415,7 +415,7 @@ public class AutoLeyLineOutcropTask : ISoloTask await _returnMainUiTask.Start(_ct); await _tpTask.OpenBigMapUi(); - var found = await LocateLeyLineOutcrop(_config.LeyLineOutcropType); + var found = await LocateLeyLineOutcrop(_taskParam.LeyLineOutcropType); await _returnMainUiTask.Start(_ct); if (!found) @@ -635,7 +635,7 @@ public class AutoLeyLineOutcropTask : ISoloTask var lastRoute = path.Routes.Last(); var targetRoute = lastRoute.Replace("Assets/pathing/", "Assets/pathing/target/").Replace("-rerun", ""); - await ProcessLeyLineOutcrop(_config.Timeout, targetRoute); + await ProcessLeyLineOutcrop(_taskParam.Timeout, targetRoute); var rewardSuccess = await AttemptReward(); if (!rewardSuccess) @@ -762,7 +762,7 @@ public class AutoLeyLineOutcropTask : ISoloTask } await EnsureExitRewardPage(); - if (_config.UseAdventurerHandbook) + if (_taskParam.UseAdventurerHandbook) { _logger.LogWarning("寻找地脉花失败:当前已勾选“不使用冒险之证寻路”,可尝试关闭该选项后重试!"); throw new Exception("寻找地脉花失败:未在地图上识别到地脉花图标。当前已勾选“不使用冒险之证寻路”,可尝试关闭该选项后重试!"); @@ -801,7 +801,7 @@ public class AutoLeyLineOutcropTask : ISoloTask private void HandleNoStrategyFound() { _logger.LogError("未找到对应的地脉花策略"); - if (_config.IsNotification) + if (_taskParam.IsNotification) { Notify.Event("AutoLeyLineOutcrop").Error("未找到对应的地脉花策略"); } @@ -1186,7 +1186,7 @@ public class AutoLeyLineOutcropTask : ISoloTask private async Task SwitchToFriendshipTeamIfNeeded() { - if (string.IsNullOrWhiteSpace(_config.FriendshipTeam)) + if (string.IsNullOrWhiteSpace(_taskParam.FriendshipTeam)) { return; } @@ -1195,7 +1195,7 @@ public class AutoLeyLineOutcropTask : ISoloTask try { _switchPartyTask ??= new SwitchPartyTask(); - await _switchPartyTask.Start(_config.FriendshipTeam, _ct); + await _switchPartyTask.Start(_taskParam.FriendshipTeam, _ct); } catch (Exception ex) { @@ -1205,14 +1205,14 @@ public class AutoLeyLineOutcropTask : ISoloTask private async Task SwitchBackToCombatTeam() { - if (string.IsNullOrWhiteSpace(_config.Team)) + if (string.IsNullOrWhiteSpace(_taskParam.Team)) { return; } Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp); _switchPartyTask ??= new SwitchPartyTask(); - await _switchPartyTask.Start(_config.Team, _ct); + await _switchPartyTask.Start(_taskParam.Team, _ct); } private async Task AttemptReward(int retryCount = 0) @@ -1251,7 +1251,7 @@ public class AutoLeyLineOutcropTask : ISoloTask resinChoice.Value.Click(); await Delay(1000, _ct); - if (!string.IsNullOrWhiteSpace(_config.FriendshipTeam)) + if (!string.IsNullOrWhiteSpace(_taskParam.FriendshipTeam)) { await SwitchBackToCombatTeam(); } @@ -1333,12 +1333,12 @@ public class AutoLeyLineOutcropTask : ISoloTask return sortedButtons[0]; } - if (hasTransient && _config.UseTransientResin && sortedButtons.Count >= 1) + if (hasTransient && _taskParam.UseTransientResin && sortedButtons.Count >= 1) { return sortedButtons[0]; } - if (hasFragile && _config.UseFragileResin && sortedButtons.Count >= 1) + if (hasFragile && _taskParam.UseFragileResin && sortedButtons.Count >= 1) { return sortedButtons[0]; } @@ -1361,7 +1361,7 @@ public class AutoLeyLineOutcropTask : ISoloTask return sortedButtons[1]; } - if (hasTransient && _config.UseTransientResin && sortedButtons.Count >= 2) + if (hasTransient && _taskParam.UseTransientResin && sortedButtons.Count >= 2) { return sortedButtons[1]; } @@ -1376,7 +1376,7 @@ public class AutoLeyLineOutcropTask : ISoloTask return sortedButtons.FirstOrDefault(); } - if (hasFragile && _config.UseFragileResin && sortedButtons.Count >= 2) + if (hasFragile && _taskParam.UseFragileResin && sortedButtons.Count >= 2) { return sortedButtons[1]; } @@ -1590,9 +1590,9 @@ public class AutoLeyLineOutcropTask : ISoloTask private async Task RecheckResinAndContinue() { _recheckCount++; - if (_config.OpenModeCountMin) + if (_taskParam.OpenModeCountMin) { - if (_currentRunTimes >= _config.Count) + if (_currentRunTimes >= _taskParam.Count) { return; } @@ -1615,7 +1615,7 @@ public class AutoLeyLineOutcropTask : ISoloTask } _currentRunTimes = 0; - _config.Count = result.Count; + _taskParam.Count = result.Count; await RunLeyLineChallenges(); await RecheckResinAndContinue(); } @@ -1632,8 +1632,8 @@ public class AutoLeyLineOutcropTask : ISoloTask } var condensedTimes = counts.CondensedResinCount; - var transientTimes = _config.UseTransientResin ? counts.TransientResinCount : 0; - var fragileTimes = _config.UseFragileResin ? counts.FragileResinCount : 0; + var transientTimes = _taskParam.UseTransientResin ? counts.TransientResinCount : 0; + var fragileTimes = _taskParam.UseFragileResin ? counts.FragileResinCount : 0; return new ResinCountResult { @@ -1659,7 +1659,7 @@ public class AutoLeyLineOutcropTask : ISoloTask CondensedResinCount = await CountCondensedResin() }; - if (_config.UseTransientResin || _config.UseFragileResin) + if (_taskParam.UseTransientResin || _taskParam.UseFragileResin) { await OpenReplenishResinUi(); await Delay(1500, _ct); diff --git a/BetterGenshinImpact/Model/OneDragonTaskItem.cs b/BetterGenshinImpact/Model/OneDragonTaskItem.cs index 23cb792a..2c16502e 100644 --- a/BetterGenshinImpact/Model/OneDragonTaskItem.cs +++ b/BetterGenshinImpact/Model/OneDragonTaskItem.cs @@ -171,7 +171,10 @@ public partial class OneDragonTaskItem : ObservableObject { taskConfig.Count = config.LeyLineRunCount; } - await new AutoLeyLineOutcropTask(taskConfig, config.LeyLineOneDragonMode) + + AutoLeyLineOutcropParam param = new AutoLeyLineOutcropParam(); + param.SetAutoLeyLineOutcropConfig(taskConfig); + await new AutoLeyLineOutcropTask(param, config.LeyLineOneDragonMode) .Start(CancellationContext.Instance.Cts.Token); } finally diff --git a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs index 7b955489..d99fe3a8 100644 --- a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs @@ -554,8 +554,10 @@ public partial class TaskSettingsPageViewModel : ViewModel private async Task OnSwitchAutoLeyLineOutcrop() { SwitchAutoLeyLineOutcropEnabled = true; + AutoLeyLineOutcropParam autoLeyLineOutcropParam = new AutoLeyLineOutcropParam(); + autoLeyLineOutcropParam.SetAutoLeyLineOutcropConfig(Config.AutoLeyLineOutcropConfig); await new TaskRunner() - .RunSoloTaskAsync(new AutoLeyLineOutcropTask(Config.AutoLeyLineOutcropConfig)); + .RunSoloTaskAsync(new AutoLeyLineOutcropTask(autoLeyLineOutcropParam)); SwitchAutoLeyLineOutcropEnabled = false; } From 4aaca366f05c5fc04484afe948dc70332a6817e1 Mon Sep 17 00:00:00 2001 From: DarkFlameMaster <1004452714@qq.com> Date: Tue, 24 Feb 2026 15:21:20 +0800 Subject: [PATCH 010/107] =?UTF-8?q?feat:=20JS=E7=BA=A7=E8=81=94=E9=80=89?= =?UTF-8?q?=E6=8B=A9=20=E5=92=8C=20=E7=A7=98=E5=A2=83=E9=80=89=E6=8B=A9=20?= =?UTF-8?q?=E6=BB=9A=E8=BD=AE=E4=BA=8B=E4=BB=B6=E7=A6=81=E6=AD=A2=E7=A9=BF?= =?UTF-8?q?=E9=80=8F=E8=87=B3=E5=85=B6=E4=BB=96=E7=AA=97=E5=8F=A3=20(#2828?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Controls/CascadeSelector.xaml | 13 +- .../View/Controls/CascadeSelector.xaml.cs | 119 +++++++++++++++++ .../View/Controls/DomainSelector.xaml | 24 ++-- .../View/Controls/DomainSelector.xaml.cs | 120 ++++++++++++++++++ 4 files changed, 264 insertions(+), 12 deletions(-) diff --git a/BetterGenshinImpact/View/Controls/CascadeSelector.xaml b/BetterGenshinImpact/View/Controls/CascadeSelector.xaml index c0b874bb..cd41177c 100644 --- a/BetterGenshinImpact/View/Controls/CascadeSelector.xaml +++ b/BetterGenshinImpact/View/Controls/CascadeSelector.xaml @@ -44,7 +44,9 @@ PlacementTarget="{Binding ElementName=MainToggle}" StaysOpen="False" AllowsTransparency="True" - PopupAnimation="Slide"> + PopupAnimation="Slide" + Opened="MainPopup_Opened" + Closed="MainPopup_Closed"> + MaxWidth="600" + PreviewMouseWheel="PopupBorder_PreviewMouseWheel"> @@ -70,7 +73,8 @@ ItemsSource="{Binding FirstLevelOptions, ElementName=Root}" BorderThickness="0" SelectionChanged="FirstLevelListView_SelectionChanged" - ScrollViewer.VerticalScrollBarVisibility="Auto"> + ScrollViewer.VerticalScrollBarVisibility="Auto" + ScrollViewer.CanContentScroll="False"> @@ -86,7 +90,8 @@ ItemsSource="{Binding SecondLevelOptions, ElementName=Root}" BorderThickness="0" SelectionChanged="SecondLevelListView_SelectionChanged" - ScrollViewer.VerticalScrollBarVisibility="Auto"> + ScrollViewer.VerticalScrollBarVisibility="Auto" + ScrollViewer.CanContentScroll="False"> diff --git a/BetterGenshinImpact/View/Controls/CascadeSelector.xaml.cs b/BetterGenshinImpact/View/Controls/CascadeSelector.xaml.cs index de66604e..343949f1 100644 --- a/BetterGenshinImpact/View/Controls/CascadeSelector.xaml.cs +++ b/BetterGenshinImpact/View/Controls/CascadeSelector.xaml.cs @@ -1,8 +1,10 @@ +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; using System.Windows.Media; namespace BetterGenshinImpact.View.Controls; @@ -21,6 +23,7 @@ public partial class CascadeSelector : UserControl { InitializeComponent(); Loaded += OnLoaded; + Unloaded += OnUnloaded; } private void OnLoaded(object sender, RoutedEventArgs e) @@ -29,6 +32,14 @@ public partial class CascadeSelector : UserControl UpdatePopupWidth(); } + /// + /// 控件卸载时清理事件处理器,防止内存泄漏 + /// + private void OnUnloaded(object sender, RoutedEventArgs e) + { + RemoveWindowMouseWheelHandler(); + } + public Dictionary>? CascadeOptions { get { return (Dictionary>?)GetValue(CascadeOptionsProperty); } @@ -277,4 +288,112 @@ public partial class CascadeSelector : UserControl } } } + + /// + /// Popup 打开时添加全局滚轮事件拦截 + /// + private void MainPopup_Opened(object sender, EventArgs e) + { + var window = Window.GetWindow(this); + if (window != null) + { + window.PreviewMouseWheel -= Window_PreviewMouseWheel; + window.PreviewMouseWheel += Window_PreviewMouseWheel; + } + } + + /// + /// Popup 关闭时移除全局滚轮事件拦截 + /// + private void MainPopup_Closed(object sender, EventArgs e) + { + RemoveWindowMouseWheelHandler(); + } + + /// + /// 移除窗口级滚轮事件处理器 + /// + private void RemoveWindowMouseWheelHandler() + { + var window = Window.GetWindow(this); + if (window != null) + { + window.PreviewMouseWheel -= Window_PreviewMouseWheel; + } + } + + /// + /// 全局滚轮事件处理,当 Popup 打开时拦截所有滚轮事件 + /// + private void Window_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + if (MainPopup.IsOpen) + { + e.Handled = true; + + var scrollViewer1 = FindScrollViewer(FirstLevelListView); + var scrollViewer2 = FindScrollViewer(SecondLevelListView); + + if (scrollViewer1 != null && scrollViewer1.IsMouseOver) + { + scrollViewer1.ScrollToVerticalOffset(scrollViewer1.VerticalOffset - e.Delta / 2.0); + return; + } + + if (scrollViewer2 != null && scrollViewer2.IsMouseOver) + { + scrollViewer2.ScrollToVerticalOffset(scrollViewer2.VerticalOffset - e.Delta / 2.0); + return; + } + } + } + + /// + /// 处理 Popup 内的鼠标滚轮事件,防止滚动穿透到外部页面 + /// + private void PopupBorder_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + e.Handled = true; + + var scrollViewer1 = FindScrollViewer(FirstLevelListView); + var scrollViewer2 = FindScrollViewer(SecondLevelListView); + + if (scrollViewer1 != null && scrollViewer1.IsMouseOver) + { + scrollViewer1.ScrollToVerticalOffset(scrollViewer1.VerticalOffset - e.Delta / 2.0); + return; + } + + if (scrollViewer2 != null && scrollViewer2.IsMouseOver) + { + scrollViewer2.ScrollToVerticalOffset(scrollViewer2.VerticalOffset - e.Delta / 2.0); + return; + } + } + + /// + /// 在视觉树中查找 ScrollViewer + /// + private ScrollViewer? FindScrollViewer(DependencyObject parent) + { + if (parent == null) return null; + + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) + { + var child = VisualTreeHelper.GetChild(parent, i); + + if (child is ScrollViewer scrollViewer) + { + return scrollViewer; + } + + var result = FindScrollViewer(child); + if (result != null) + { + return result; + } + } + + return null; + } } diff --git a/BetterGenshinImpact/View/Controls/DomainSelector.xaml b/BetterGenshinImpact/View/Controls/DomainSelector.xaml index a20b284a..28043acf 100644 --- a/BetterGenshinImpact/View/Controls/DomainSelector.xaml +++ b/BetterGenshinImpact/View/Controls/DomainSelector.xaml @@ -38,12 +38,16 @@ - - + + Width="350" + PreviewMouseWheel="PopupBorder_PreviewMouseWheel"> @@ -63,10 +68,12 @@ - + ScrollViewer.VerticalScrollBarVisibility="Auto" + ScrollViewer.CanContentScroll="False"> @@ -78,13 +85,14 @@ - + ScrollViewer.VerticalScrollBarVisibility="Auto" + ScrollViewer.CanContentScroll="False"> diff --git a/BetterGenshinImpact/View/Controls/DomainSelector.xaml.cs b/BetterGenshinImpact/View/Controls/DomainSelector.xaml.cs index cbfb54a3..322a6d4a 100644 --- a/BetterGenshinImpact/View/Controls/DomainSelector.xaml.cs +++ b/BetterGenshinImpact/View/Controls/DomainSelector.xaml.cs @@ -1,9 +1,12 @@ +using System; using BetterGenshinImpact.GameTask.AutoTrackPath.Model; using BetterGenshinImpact.GameTask.Common.Element.Assets; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; namespace BetterGenshinImpact.View.Controls; @@ -13,6 +16,15 @@ public partial class DomainSelector : UserControl { InitializeComponent(); Countries = MapLazyAssets.Instance.CountryToDomains.Keys.Reverse().ToList(); + Unloaded += OnUnloaded; + } + + /// + /// 控件卸载时清理事件处理器,防止内存泄漏 + /// + private void OnUnloaded(object sender, RoutedEventArgs e) + { + RemoveWindowMouseWheelHandler(); } public List Countries @@ -95,4 +107,112 @@ public partial class DomainSelector : UserControl MainToggle.IsChecked = false; } } + + /// + /// Popup 打开时添加全局滚轮事件拦截 + /// + private void MainPopup_Opened(object sender, EventArgs e) + { + var window = Window.GetWindow(this); + if (window != null) + { + window.PreviewMouseWheel -= Window_PreviewMouseWheel; + window.PreviewMouseWheel += Window_PreviewMouseWheel; + } + } + + /// + /// Popup 关闭时移除全局滚轮事件拦截 + /// + private void MainPopup_Closed(object sender, EventArgs e) + { + RemoveWindowMouseWheelHandler(); + } + + /// + /// 移除窗口级滚轮事件处理器 + /// + private void RemoveWindowMouseWheelHandler() + { + var window = Window.GetWindow(this); + if (window != null) + { + window.PreviewMouseWheel -= Window_PreviewMouseWheel; + } + } + + /// + /// 全局滚轮事件处理,当 Popup 打开时拦截所有滚轮事件 + /// + private void Window_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + if (MainPopup.IsOpen) + { + e.Handled = true; + + var scrollViewer1 = FindScrollViewer(CountriesListView); + var scrollViewer2 = FindScrollViewer(DomainsListView); + + if (scrollViewer1 != null && scrollViewer1.IsMouseOver) + { + scrollViewer1.ScrollToVerticalOffset(scrollViewer1.VerticalOffset - e.Delta / 2.0); + return; + } + + if (scrollViewer2 != null && scrollViewer2.IsMouseOver) + { + scrollViewer2.ScrollToVerticalOffset(scrollViewer2.VerticalOffset - e.Delta / 2.0); + return; + } + } + } + + /// + /// 处理 Popup 内的鼠标滚轮事件,防止滚动穿透到外部页面 + /// + private void PopupBorder_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + e.Handled = true; + + var scrollViewer1 = FindScrollViewer(CountriesListView); + var scrollViewer2 = FindScrollViewer(DomainsListView); + + if (scrollViewer1 != null && scrollViewer1.IsMouseOver) + { + scrollViewer1.ScrollToVerticalOffset(scrollViewer1.VerticalOffset - e.Delta / 2.0); + return; + } + + if (scrollViewer2 != null && scrollViewer2.IsMouseOver) + { + scrollViewer2.ScrollToVerticalOffset(scrollViewer2.VerticalOffset - e.Delta / 2.0); + return; + } + } + + /// + /// 在视觉树中查找 ScrollViewer + /// + private ScrollViewer? FindScrollViewer(DependencyObject parent) + { + if (parent == null) return null; + + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) + { + var child = VisualTreeHelper.GetChild(parent, i); + + if (child is ScrollViewer scrollViewer) + { + return scrollViewer; + } + + var result = FindScrollViewer(child); + if (result != null) + { + return result; + } + } + + return null; + } } From 95e0f0175313cca7a7dd55fcb0a9c811a5deca6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Tue, 24 Feb 2026 15:34:50 +0800 Subject: [PATCH 011/107] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=B0=8F=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE=E9=81=AE=E7=BD=A9=EF=BC=8C=E5=9C=A8=E5=B0=8F=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE=E4=B8=8A=E5=B1=95=E7=A4=BA=E8=B5=84=E6=BA=90=E7=82=B9?= =?UTF-8?q?=E4=BD=8D=20(#2830)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/Element/Assets/MapAssets.cs | 3 + .../GameTask/MapMask/MapMaskConfig.cs | 12 + .../GameTask/MapMask/MapMaskTrigger.cs | 49 ++- BetterGenshinImpact/Helpers/DpiHelper.cs | 12 +- .../Model/MaskMap/MaskMapPoint.cs | 4 +- .../View/Controls/MiniMapPointsCanvas.cs | 355 ++++++++++++++++++ .../View/Controls/PointsCanvas.cs | 44 ++- BetterGenshinImpact/View/MaskWindow.xaml | 33 ++ BetterGenshinImpact/View/MaskWindow.xaml.cs | 26 -- .../View/Pages/TriggerSettingsPage.xaml | 49 ++- .../ViewModel/MaskWindowViewModel.cs | 15 +- 11 files changed, 549 insertions(+), 53 deletions(-) create mode 100644 BetterGenshinImpact/View/Controls/MiniMapPointsCanvas.cs diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/MapAssets.cs b/BetterGenshinImpact/GameTask/Common/Element/Assets/MapAssets.cs index 3c34b36c..1e6d768a 100644 --- a/BetterGenshinImpact/GameTask/Common/Element/Assets/MapAssets.cs +++ b/BetterGenshinImpact/GameTask/Common/Element/Assets/MapAssets.cs @@ -14,6 +14,9 @@ namespace BetterGenshinImpact.GameTask.Common.Element.Assets; public class MapAssets : BaseAssets { public Rect MimiMapRect { get; } + + public static Rect MimiMapRect1080P = new Rect(62, 19,212,212); + public MapAssets() { diff --git a/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs b/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs index af3fa94f..4f9cc640 100644 --- a/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs +++ b/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs @@ -15,6 +15,18 @@ public partial class MapMaskConfig : ObservableObject /// [ObservableProperty] private bool _enabled = true; + + /// + /// 小地图遮罩是否启用 + /// + [ObservableProperty] + private bool _miniMapMaskEnabled = false; + + /// + /// 自动记录路径功能是否启用 + /// + [ObservableProperty] + private bool _pathAutoRecordEnabled = false; private MapPointApiProvider _mapPointApiProvider = MapPointApiProvider.MihoyoMap; diff --git a/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs b/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs index cd9a7f23..8f21ee61 100644 --- a/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs +++ b/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs @@ -1,7 +1,8 @@ using System; -using System.Windows; using BetterGenshinImpact.Core.Recognition.OpenCv; +using BetterGenshinImpact.GameTask.AutoPathing; using BetterGenshinImpact.GameTask.Common.BgiVision; +using BetterGenshinImpact.GameTask.Common.Element.Assets; using BetterGenshinImpact.GameTask.Common.Map.Maps; using BetterGenshinImpact.GameTask.Common.Map.Maps.Base; using BetterGenshinImpact.GameTask.Common.Map.Maps.Layer; @@ -9,6 +10,8 @@ using BetterGenshinImpact.Helpers; using BetterGenshinImpact.View; using BetterGenshinImpact.ViewModel; using Microsoft.Extensions.Logging; +using OpenCvSharp; +using Rect = System.Windows.Rect; namespace BetterGenshinImpact.GameTask.MapMask; @@ -23,8 +26,8 @@ public class MapMaskTrigger : ITaskTrigger public bool IsEnabled { get; set; } public int Priority => 1; // 低优先级 public bool IsExclusive => false; - - public GameUiCategory SupportedGameUiCategory => GameUiCategory.BigMap; + + public GameUiCategory SupportedGameUiCategory => GameUiCategory.Unknown; private readonly MapMaskConfig _config = TaskContext.Instance().Config.MapMaskConfig; private readonly string _mapMatchingMethod = TaskContext.Instance().Config.PathingConditionConfig.MapMatchingMethod; @@ -41,10 +44,12 @@ public class MapMaskTrigger : ITaskTrigger private const int RectDebounceThreshold = 3; + private readonly NavigationInstance _navigationInstance = new(); + public void Init() { IsEnabled = _config.Enabled; - + // 关闭时隐藏UI if (!IsEnabled) { @@ -108,8 +113,8 @@ public class MapMaskTrigger : ITaskTrigger _prevRect = default; return; } - - + + // if (_prevRect != default) // { // var dx = Math.Abs(rect256.X - _prevRect.X); @@ -130,6 +135,38 @@ public class MapMaskTrigger : ITaskTrigger } else { + if ((_config.MiniMapMaskEnabled || _config.PathAutoRecordEnabled) && Bv.IsInMainUi(region)) + { + // 主界面上展示小地图 + if (_config.MiniMapMaskEnabled) + { + var miniPoint = _navigationInstance.GetPositionStable(region, nameof(MapTypes.Teyvat), TaskContext.Instance().Config.PathingConditionConfig.MapMatchingMethod); + if (miniPoint != default) + { + // 展示窗口是 212 + double viewportSize = MapAssets.MimiMapRect1080P.Width / 3.0 * 10; + UIDispatcherHelper.Invoke(() => + { + MaskWindow.Instance().MiniMapPointsCanvasControl.UpdateViewport( + miniPoint.X - viewportSize / 2.0, + miniPoint.Y - viewportSize / 2.0, + viewportSize, + viewportSize); + }); + } + else + { + UIDispatcherHelper.Invoke(() => { MaskWindow.Instance().MiniMapPointsCanvasControl.UpdateViewport(0, 0, 0, 0); }); + } + + // 自动记录路径 + if (_config.PathAutoRecordEnabled) + { + // ... + } + } + } + _prevRect = default; } } diff --git a/BetterGenshinImpact/Helpers/DpiHelper.cs b/BetterGenshinImpact/Helpers/DpiHelper.cs index 7abd2afb..58226bf7 100644 --- a/BetterGenshinImpact/Helpers/DpiHelper.cs +++ b/BetterGenshinImpact/Helpers/DpiHelper.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Windows; using System.Windows.Interop; +using BetterGenshinImpact.GameTask; using Vanara.PInvoke; namespace BetterGenshinImpact.Helpers; @@ -18,7 +19,16 @@ public class DpiHelper if (Environment.OSVersion.Version >= new Version(6, 3) && UIDispatcherHelper.MainWindow != null) { - HWND hWnd = new WindowInteropHelper(Application.Current?.MainWindow).Handle; + HWND hWnd = HWND.NULL; + if (TaskContext.Instance().IsInitialized) + { + hWnd = TaskContext.Instance().GameHandle; + } + else + { + hWnd = new WindowInteropHelper(Application.Current?.MainWindow).Handle; + } + HMONITOR hMonitor = User32.MonitorFromWindow(hWnd, User32.MonitorFlags.MONITOR_DEFAULTTONEAREST); SHCore.GetDpiForMonitor(hMonitor, SHCore.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out _, out uint dpiY); return dpiY / 96f; diff --git a/BetterGenshinImpact/Model/MaskMap/MaskMapPoint.cs b/BetterGenshinImpact/Model/MaskMap/MaskMapPoint.cs index 773439b1..76b95352 100644 --- a/BetterGenshinImpact/Model/MaskMap/MaskMapPoint.cs +++ b/BetterGenshinImpact/Model/MaskMap/MaskMapPoint.cs @@ -19,12 +19,12 @@ public class MaskMapPoint public double GameY { get; set; } /// - /// 游戏图像地图的坐标 X + /// 2048级别游戏图像地图的坐标 X /// public double ImageX { get; set; } /// - /// 游戏图像地图的坐标 Y + /// 2048级别游戏图像地图的坐标 Y /// public double ImageY { get; set; } diff --git a/BetterGenshinImpact/View/Controls/MiniMapPointsCanvas.cs b/BetterGenshinImpact/View/Controls/MiniMapPointsCanvas.cs new file mode 100644 index 00000000..a7ded614 --- /dev/null +++ b/BetterGenshinImpact/View/Controls/MiniMapPointsCanvas.cs @@ -0,0 +1,355 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using System.Windows; +using System.Windows.Media; +using BetterGenshinImpact.Model.MaskMap; +using BetterGenshinImpact.ViewModel; + +namespace BetterGenshinImpact.View.Controls; + +public sealed class MiniMapPointsCanvas : FrameworkElement +{ + public static readonly DependencyProperty PointsSourceProperty = + DependencyProperty.Register( + nameof(PointsSource), + typeof(ObservableCollection), + typeof(MiniMapPointsCanvas), + new PropertyMetadata(null, OnPointsSourceChanged)); + + public static readonly DependencyProperty LabelsSourceProperty = + DependencyProperty.Register( + nameof(LabelsSource), + typeof(IEnumerable), + typeof(MiniMapPointsCanvas), + new PropertyMetadata(null, OnLabelsSourceChanged)); + + private readonly VisualCollection _children; + private readonly DrawingVisual _drawingVisual; + private readonly Dictionary _colorBrushCache; + private int _refreshQueued; + + private ObservableCollection? _points; + private List _allPoints = new(); + private Dictionary _labelMap = new(); + private Rect _viewportRect = Rect.Empty; + + public ObservableCollection? PointsSource + { + get => (ObservableCollection?)GetValue(PointsSourceProperty); + set => SetValue(PointsSourceProperty, value); + } + + public IEnumerable? LabelsSource + { + get => (IEnumerable?)GetValue(LabelsSourceProperty); + set => SetValue(LabelsSourceProperty, value); + } + + public MiniMapPointsCanvas() + { + _children = new VisualCollection(this); + _drawingVisual = new DrawingVisual(); + _children.Add(_drawingVisual); + _colorBrushCache = new Dictionary(); + + IsHitTestVisible = false; + + MapIconImageCache.ImageUpdated += PointImageCacheManagerOnImageUpdated; + } + + private static void OnPointsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var canvas = (MiniMapPointsCanvas)d; + canvas.UpdatePoints(e.NewValue as ObservableCollection); + } + + private static void OnLabelsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var canvas = (MiniMapPointsCanvas)d; + canvas.UpdateLabels(e.NewValue as IEnumerable); + } + + protected override void OnVisualParentChanged(DependencyObject oldParent) + { + base.OnVisualParentChanged(oldParent); + if (VisualParent == null) + { + MapIconImageCache.ImageUpdated -= PointImageCacheManagerOnImageUpdated; + } + } + + private void PointImageCacheManagerOnImageUpdated(object? sender, string e) + { + if (Interlocked.Exchange(ref _refreshQueued, 1) != 0) + { + return; + } + + Dispatcher.BeginInvoke(() => + { + Interlocked.Exchange(ref _refreshQueued, 0); + Refresh(); + }, System.Windows.Threading.DispatcherPriority.Background); + } + + protected override int VisualChildrenCount => _children.Count; + + protected override Visual GetVisualChild(int index) + { + if (index < 0 || index >= _children.Count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _children[index]; + } + + private void OnPointsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) + { + foreach (MaskMapPoint point in e.OldItems) + { + UnsubscribePoint(point); + } + } + + if (e.NewItems != null) + { + foreach (MaskMapPoint point in e.NewItems) + { + SubscribePoint(point); + } + } + + _allPoints = _points?.ToList() ?? new List(); + Refresh(); + } + + private void SubscribePoint(MaskMapPoint point) + { + if (point is INotifyPropertyChanged notifyPoint) + { + notifyPoint.PropertyChanged += OnPointPropertyChanged; + } + } + + private void UnsubscribePoint(MaskMapPoint point) + { + if (point is INotifyPropertyChanged notifyPoint) + { + notifyPoint.PropertyChanged -= OnPointPropertyChanged; + } + } + + private void OnPointPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + Refresh(); + } + + private void RenderPoints() + { + using var dc = _drawingVisual.RenderOpen(); + if (_allPoints.Count == 0 || _viewportRect.IsEmpty || _viewportRect.Width == 0) + { + return; + } + + var aw = ActualWidth; + var ah = ActualHeight; + if (aw <= 0 || ah <= 0) + { + return; + } + + var side = Math.Min(aw, ah); + if (side <= 0) + { + return; + } + + var clipRect = new Rect((aw - side) / 2.0, (ah - side) / 2.0, side, side); + var clip = new EllipseGeometry(clipRect); + dc.PushClip(clip); + + var expandedViewport = _viewportRect; + expandedViewport.Inflate(MaskMapPointStatic.Width, MaskMapPointStatic.Height); + + var scaleX = side / _viewportRect.Width; + var scaleY = side / _viewportRect.Height; + + var pointSide = Math.Max(8, Math.Min(16, side / 12.0)); + + foreach (var point in _allPoints) + { + if (!expandedViewport.Contains(point.ImageX, point.ImageY)) + { + continue; + } + + var localX = clipRect.X + (point.ImageX - _viewportRect.X) * scaleX; + var localY = clipRect.Y + (point.ImageY - _viewportRect.Y) * scaleY; + DrawPoint(dc, point, localX, localY, pointSide, pointSide); + } + + dc.Pop(); + } + + private void DrawPoint(DrawingContext dc, MaskMapPoint point, double centerX, double centerY, double width, double height) + { + var radius = width / 2.0; + const double strokeThickness = 2.0; + + var circleCenter = new Point(centerX, centerY); + + var fillBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#323947")); + fillBrush.Freeze(); + + var borderBrush = new SolidColorBrush(Color.FromRgb(0xD3, 0xBC, 0x8E)); + borderBrush.Freeze(); + + var borderPen = new Pen(borderBrush, strokeThickness); + borderPen.Freeze(); + + var shadowBrush = new SolidColorBrush(Color.FromArgb(30, 0, 0, 0)); + shadowBrush.Freeze(); + + var shadowOffset = new Point(2, 2); + + var shadowCircleGeometry = new EllipseGeometry( + new Point(circleCenter.X + shadowOffset.X, circleCenter.Y + shadowOffset.Y), + radius, radius); + dc.DrawGeometry(shadowBrush, null, shadowCircleGeometry); + + var circleGeometry = new EllipseGeometry(circleCenter, radius, radius); + dc.DrawGeometry(fillBrush, borderPen, circleGeometry); + + if (_labelMap.TryGetValue(point.LabelId, out var label)) + { + var image = MapIconImageCache.TryGet(label.IconUrl); + if (image != null) + { + var imageRect = new Rect(circleCenter.X - radius, circleCenter.Y - radius, width, height); + dc.PushClip(circleGeometry); + dc.DrawImage(image, imageRect); + dc.Pop(); + } + else + { + _ = MapIconImageCache.GetAsync(label.IconUrl, CancellationToken.None); + + var brush = GetColorBrush(label); + dc.DrawEllipse(brush, null, new Point(centerX, centerY), width / 2.0, height / 2.0); + } + } + else + { + var brush = new SolidColorBrush(GenerateRandomColor(point.Id)); + brush.Freeze(); + dc.DrawEllipse(brush, null, new Point(centerX, centerY), width / 2.0, height / 2.0); + } + } + + private Brush GetColorBrush(MaskMapPointLabel label) + { + if (_colorBrushCache.TryGetValue(label.LabelId, out var cachedBrush)) + { + return cachedBrush; + } + + Color color; + if (label.Color.HasValue) + { + var c = label.Color.Value; + color = Color.FromArgb(c.A, c.R, c.G, c.B); + } + else + { + color = GenerateRandomColor(label.LabelId); + } + + var brush = new SolidColorBrush(color); + brush.Freeze(); + _colorBrushCache[label.LabelId] = brush; + return brush; + } + + private static Color GenerateRandomColor(string seed) + { + var hash = seed?.GetHashCode() ?? 0; + var random = new Random(hash); + return Color.FromRgb( + (byte)random.Next(80, 256), + (byte)random.Next(80, 256), + (byte)random.Next(80, 256)); + } + + public void UpdatePoints(ObservableCollection? points) + { + if (_points != null) + { + _points.CollectionChanged -= OnPointsCollectionChanged; + foreach (var point in _points) + { + UnsubscribePoint(point); + } + } + + _points = points; + + if (_points != null) + { + _points.CollectionChanged += OnPointsCollectionChanged; + foreach (var point in _points) + { + SubscribePoint(point); + } + + _allPoints = _points.ToList(); + } + else + { + _allPoints.Clear(); + } + + Refresh(); + } + + public void UpdateLabels(IEnumerable? labels) + { + if (labels != null) + { + _labelMap = labels.ToDictionary(l => l.LabelId, l => l); + _colorBrushCache.Clear(); + } + else + { + _labelMap.Clear(); + _colorBrushCache.Clear(); + } + + Refresh(); + } + + public void UpdateViewport(double x, double y, double width, double height) + { + var newRect = new Rect(x, y, width, height); + if (newRect.Equals(_viewportRect)) + { + return; + } + + _viewportRect = newRect; + Refresh(); + } + + public void Refresh() + { + RenderPoints(); + } +} diff --git a/BetterGenshinImpact/View/Controls/PointsCanvas.cs b/BetterGenshinImpact/View/Controls/PointsCanvas.cs index f82ed17f..721e62b9 100644 --- a/BetterGenshinImpact/View/Controls/PointsCanvas.cs +++ b/BetterGenshinImpact/View/Controls/PointsCanvas.cs @@ -28,7 +28,7 @@ public class PointsCanvas : FrameworkElement private int _refreshQueued; // 私有字段 - private ObservableCollection _points; + private ObservableCollection? _points; private List _allPoints = new(); private Dictionary _labelMap = new(); private Rect _viewportRect = Rect.Empty; @@ -37,6 +37,20 @@ public class PointsCanvas : FrameworkElement #region 依赖属性 + public static readonly DependencyProperty PointsSourceProperty = + DependencyProperty.Register( + nameof(PointsSource), + typeof(ObservableCollection), + typeof(PointsCanvas), + new PropertyMetadata(null, OnPointsSourceChanged)); + + public static readonly DependencyProperty LabelsSourceProperty = + DependencyProperty.Register( + nameof(LabelsSource), + typeof(IEnumerable), + typeof(PointsCanvas), + new PropertyMetadata(null, OnLabelsSourceChanged)); + public static readonly DependencyProperty PointClickCommandProperty = DependencyProperty.Register( nameof(PointClickCommand), @@ -67,6 +81,18 @@ public class PointsCanvas : FrameworkElement set => SetValue(PointClickCommandProperty, value); } + public ObservableCollection? PointsSource + { + get => (ObservableCollection?)GetValue(PointsSourceProperty); + set => SetValue(PointsSourceProperty, value); + } + + public IEnumerable? LabelsSource + { + get => (IEnumerable?)GetValue(LabelsSourceProperty); + set => SetValue(LabelsSourceProperty, value); + } + /// /// 右键点击命令 /// @@ -87,6 +113,18 @@ public class PointsCanvas : FrameworkElement #endregion + private static void OnPointsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var canvas = (PointsCanvas)d; + canvas.UpdatePoints(e.NewValue as ObservableCollection); + } + + private static void OnLabelsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var canvas = (PointsCanvas)d; + canvas.UpdateLabels(e.NewValue as IEnumerable); + } + public PointsCanvas() { _children = new VisualCollection(this); @@ -508,7 +546,7 @@ public class PointsCanvas : FrameworkElement /// /// 更新点位数据 /// - public void UpdatePoints(ObservableCollection points) + public void UpdatePoints(ObservableCollection? points) { // 取消订阅旧集合 if (_points != null) @@ -541,7 +579,7 @@ public class PointsCanvas : FrameworkElement /// /// 更新标签数据 /// - public void UpdateLabels(IEnumerable labels) + public void UpdateLabels(IEnumerable? labels) { if (labels != null) { diff --git a/BetterGenshinImpact/View/MaskWindow.xaml b/BetterGenshinImpact/View/MaskWindow.xaml index 5701c577..c1d9b363 100644 --- a/BetterGenshinImpact/View/MaskWindow.xaml +++ b/BetterGenshinImpact/View/MaskWindow.xaml @@ -86,6 +86,37 @@ Grid.Column="0" Grid.ColumnSpan="6" ClipToBounds="True"> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BetterGenshinImpact/View/MaskWindow.xaml.cs b/BetterGenshinImpact/View/MaskWindow.xaml.cs index 26d3c830..9794db2e 100644 --- a/BetterGenshinImpact/View/MaskWindow.xaml.cs +++ b/BetterGenshinImpact/View/MaskWindow.xaml.cs @@ -191,11 +191,6 @@ public partial class MaskWindow : Window RefreshPosition(); PrintSystemInfo(); - if (_viewModel != null) - { - PointsCanvasControl.UpdateLabels(_viewModel.MapPointLabels); - PointsCanvasControl.UpdatePoints(_viewModel.MapPoints); - } PointsCanvasControl.ViewportChanged += PointsCanvasControlOnViewportChanged; } @@ -258,27 +253,6 @@ public partial class MaskWindow : Window } } - if (e.PropertyName == nameof(MaskWindowViewModel.MapPointLabels)) - { - Dispatcher.Invoke(() => - { - if (_viewModel != null) - { - PointsCanvasControl.UpdateLabels(_viewModel.MapPointLabels); - } - }); - } - - if (e.PropertyName == nameof(MaskWindowViewModel.MapPoints)) - { - Dispatcher.Invoke(() => - { - if (_viewModel != null) - { - PointsCanvasControl.UpdatePoints(_viewModel.MapPoints); - } - }); - } } private void UpdateClickThroughState() diff --git a/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml index 172fad45..4b2b6ba5 100644 --- a/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml @@ -975,16 +975,20 @@ - - + + - - + + + + + + 在遮罩窗口中显示大地图位置与标点信息 + - - - + + + + + + + + + + + + + + + + + MapAssets.MimiMapRect1080P.X / 1920d; + + public double MiniMapOverlayTopRatio => MapAssets.MimiMapRect1080P.Y / 1080d; + + public double MiniMapOverlaySizeRatio => MapAssets.MimiMapRect1080P.Width / 1080d; + public sealed record MapPointApiProviderOption(MapPointApiProvider Provider, string DisplayName); public IReadOnlyList MapPointApiProviderOptions { get; } = From 01c506fa133619f97728c992822182b73b49aa41 Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Tue, 24 Feb 2026 07:36:36 +0000 Subject: [PATCH 012/107] Update version to 0.57.1-alpha.3 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 9e76ca01..03163fd8 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.57.1-alpha.2 + 0.57.1-alpha.3 false WinExe net8.0-windows10.0.22621.0 From 53854e6d281c90649b2647de9a2aa88adf531212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Tue, 24 Feb 2026 19:29:09 +0800 Subject: [PATCH 013/107] =?UTF-8?q?refactor:=20=E7=AE=80=E5=8C=96=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E5=8E=BB=E9=87=8D=E9=80=BB=E8=BE=91=EF=BC=8C=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E4=BD=BF=E7=94=A8=20GroupBy=20=E5=92=8C=20Select?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用 GroupBy 和 Select 直接获取唯一的进程对象,避免创建中间字典和手动释放重复的 Process 对象,使代码更简洁。 --- BetterGenshinImpact/GameTask/SystemControl.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/BetterGenshinImpact/GameTask/SystemControl.cs b/BetterGenshinImpact/GameTask/SystemControl.cs index 18e16a47..e2ad99d8 100644 --- a/BetterGenshinImpact/GameTask/SystemControl.cs +++ b/BetterGenshinImpact/GameTask/SystemControl.cs @@ -320,18 +320,11 @@ public class SystemControl try { var processNames = TaskContext.Instance().GetGenshinGameProcessNameList(); - var allProcesses = processNames + var processes = processNames .SelectMany(Process.GetProcessesByName) - .ToList(); - var processMap = allProcesses .GroupBy(p => p.Id) - .ToDictionary(g => g.Key, g => g.First()); - // 释放重复的 Process 包装对象 - foreach (var p in allProcesses.Where(p => !ReferenceEquals(p, processMap[p.Id]))) - { - p.Dispose(); - } - var processes = processMap.Values.ToArray(); + .Select(g => g.First()) + .ToArray(); if (processes.Length > 0) { From ce31169f6662bc813fa392417e547e3df512e101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Thu, 26 Feb 2026 00:44:26 +0800 Subject: [PATCH 014/107] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B0=8F?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E9=81=AE=E7=BD=A9=E5=9C=A8=E4=B8=BB=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E5=A4=96=E7=9A=84=E6=98=BE=E7=A4=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs b/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs index 8f21ee61..8ebc17e2 100644 --- a/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs +++ b/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs @@ -135,10 +135,10 @@ public class MapMaskTrigger : ITaskTrigger } else { - if ((_config.MiniMapMaskEnabled || _config.PathAutoRecordEnabled) && Bv.IsInMainUi(region)) + // 主界面上展示小地图 + if (_config.MiniMapMaskEnabled) { - // 主界面上展示小地图 - if (_config.MiniMapMaskEnabled) + if (Bv.IsInMainUi(region)) { var miniPoint = _navigationInstance.GetPositionStable(region, nameof(MapTypes.Teyvat), TaskContext.Instance().Config.PathingConditionConfig.MapMatchingMethod); if (miniPoint != default) @@ -165,6 +165,10 @@ public class MapMaskTrigger : ITaskTrigger // ... } } + else + { + UIDispatcherHelper.Invoke(() => { MaskWindow.Instance().MiniMapPointsCanvasControl.UpdateViewport(0, 0, 0, 0); }); + } } _prevRect = default; From 9e3c8920baa868414554e6f2f17400311df40e41 Mon Sep 17 00:00:00 2001 From: Guest Liang <47164120+Guest-Liang@users.noreply.github.com> Date: Thu, 26 Feb 2026 02:08:44 +0000 Subject: [PATCH 015/107] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=83=E5=9C=A3?= =?UTF-8?q?=E5=8F=AC=E5=94=A4=E6=9C=88=E4=B9=8B=E4=BA=94=E4=BC=8A=E6=B6=85?= =?UTF-8?q?=E8=8A=99=E4=BC=9A=E5=AF=BC=E8=87=B4=E6=89=8B=E7=89=8C=E4=B8=8D?= =?UTF-8?q?=E5=8F=AF=E8=B0=83=E5=92=8C=E7=9A=84=E6=AD=BB=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=20(#2848)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GeniusInvokationControl.cs | 84 +++++++++++++++++-- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs index 684cccdf..facb4c19 100644 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs @@ -564,6 +564,75 @@ public class GeniusInvokationControl m.LeftButtonUp(); } + /// + /// 月之五伊涅芙会导致手牌不可调和,根据传入的手牌数量,从左往右尝试 + /// + private bool ActionPhaseElementalTuningAlternatives(int currentCardCount) + { + var rect = TaskContext.Instance().SystemInfo.CaptureAreaRect; + var info = TaskContext.Instance().SystemInfo; + var m = Simulation.SendInput.Mouse; + + var startY = rect.Y + rect.Height - 50; + var endX = rect.X + rect.Width - 50; + var endY = rect.Y + rect.Height / 2d; + + // 手牌数量对应的起点(中心)和间距 + var table = new Dictionary + { + {10, (570.0, 120.0)}, + {9, (570.0, 130.0)}, + {8, (600.0, 145.0)}, + {7, (630.0, 160.0)}, + {6, (620.0, 200.0)}, + {5, (720.0, 200.0)}, + {4, (820.0, 200.0)}, + {3, (920.0, 200.0)}, + {2, (1020.0, 200.0)}, + {1, (1120.0, 200.0)} + }; + + if (!table.ContainsKey(currentCardCount)) + { + _logger.LogWarning("未找到手牌数量对应的起始位置配置: {Count}", currentCardCount); + return false; + } + + var (startX, spacing) = table[currentCardCount]; + + // 先点中间位置,确保牌是展开状态 + ClickExtension.Click(rect.X + rect.Width / 2d, rect.Y + rect.Height - 50); + Sleep(1500); + + // 从左往右尝试 + for (int idx = 0; idx < currentCardCount; idx++) + { + var x = rect.X + startX * info.AssetScale + idx * spacing * info.AssetScale; + + ClickExtension.Click(x, startY); + Sleep(500); + + m.LeftButtonDown(); + Sleep(100); + m = ClickExtension.Move(endX, endY); + Sleep(100); + m.LeftButtonUp(); + + // 等待动画并确认 + Sleep(1200); + if (ActionPhaseElementalTuningConfirm()) + { + return true; + } + + _logger.LogWarning("烧牌位置尝试失败,手牌数:{Count},位置索引:{Idx}", currentCardCount, idx); + ClickGameWindowCenter(); // 复位 + Sleep(1000); + } + + return false; + } + /// /// 烧牌确认(元素调和按钮) /// @@ -692,16 +761,13 @@ public class GeniusInvokationControl for (var i = 0; i < needSpecifyElementDiceCount; i++) { _logger.LogInformation("- 烧第{Count}张牌", i + 1); - ActionPhaseElementalTuning(duel.CurrentCardCount); - Sleep(1200); - var res = ActionPhaseElementalTuningConfirm(); - if (res == false) + + // 尝试对当前手牌从左往右依次进行烧牌 + var tuned = ActionPhaseElementalTuningAlternatives(duel.CurrentCardCount); + if (!tuned) { - _logger.LogWarning("烧牌失败,重试"); - i--; - ClickGameWindowCenter(); // 复位 - Sleep(1000); - continue; + _logger.LogWarning("所有手牌尝试烧牌均失败,取消释放技能"); + return false; } Sleep(1000); // 烧牌动画 From 01f1beba0b026af91254c8ca132fd7a33d8af40f Mon Sep 17 00:00:00 2001 From: ddaodan <40017293+ddaodan@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:13:38 +0800 Subject: [PATCH 016/107] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E5=9C=B0?= =?UTF-8?q?=E8=84=89=E8=8A=B1=E7=8B=AC=E7=AB=8B=E6=88=98=E6=96=97=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=B8=8EOCR=E9=81=AE=E7=BD=A9=20(#2829)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoLeyLineOutcropConfig.cs | 33 ++ .../AutoLeyLineOutcropFightConfig.cs | 116 +++++++ .../AutoLeyLineOutcropParam.cs | 5 +- .../AutoLeyLineOutcropTask.cs | 284 ++++++++++++++++-- BetterGenshinImpact/View/MaskWindow.xaml.cs | 137 ++++----- .../View/Pages/TaskSettingsPage.xaml | 79 ++++- 6 files changed, 561 insertions(+), 93 deletions(-) create mode 100644 BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs index 88219585..79b3ec8f 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs @@ -1,11 +1,17 @@ using CommunityToolkit.Mvvm.ComponentModel; using System; +using System.ComponentModel; namespace BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; [Serializable] public partial class AutoLeyLineOutcropConfig : ObservableObject { + public AutoLeyLineOutcropConfig() + { + AttachFightConfigEvents(_fightConfig); + } + [ObservableProperty] private string _leyLineOutcropType = "启示之花"; @@ -44,4 +50,31 @@ public partial class AutoLeyLineOutcropConfig : ObservableObject [ObservableProperty] private bool _isGoToSynthesizer = false; + + [ObservableProperty] + private AutoLeyLineOutcropFightConfig _fightConfig = new(); + + partial void OnFightConfigChanged(AutoLeyLineOutcropFightConfig value) + { + AttachFightConfigEvents(value); + OnPropertyChanged(nameof(FightConfig)); + } + + private void AttachFightConfigEvents(AutoLeyLineOutcropFightConfig? config) + { + if (config == null) + { + return; + } + + config.PropertyChanged -= OnFightConfigPropertyChanged; + config.PropertyChanged += OnFightConfigPropertyChanged; + config.FinishDetectConfig.PropertyChanged -= OnFightConfigPropertyChanged; + config.FinishDetectConfig.PropertyChanged += OnFightConfigPropertyChanged; + } + + private void OnFightConfigPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + OnPropertyChanged(nameof(FightConfig)); + } } diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs new file mode 100644 index 00000000..36cb4478 --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs @@ -0,0 +1,116 @@ +using BetterGenshinImpact.GameTask.AutoFight; +using CommunityToolkit.Mvvm.ComponentModel; +using System; + +namespace BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; + +[Serializable] +public partial class AutoLeyLineOutcropFightConfig : ObservableObject +{ + [ObservableProperty] private string _strategyName = ""; + + /// + /// 英文逗号分割,强制指定队伍角色。 + /// + [ObservableProperty] private string _teamNames = ""; + + /// + /// 检测战斗结束。 + /// + [ObservableProperty] private bool _fightFinishDetectEnabled = true; + + /// + /// 根据技能CD优化出招人员。 + /// + [ObservableProperty] private string _actionSchedulerByCd = ""; + + [Serializable] + public partial class FightFinishDetectConfig : ObservableObject + { + [ObservableProperty] private string _battleEndProgressBarColor = ""; + [ObservableProperty] private string _battleEndProgressBarColorTolerance = ""; + [ObservableProperty] private bool _fastCheckEnabled = false; + [ObservableProperty] private bool _rotateFindEnemyEnabled = false; + [ObservableProperty] private string _fastCheckParams = ""; + [ObservableProperty] private string _checkEndDelay = ""; + [ObservableProperty] private string _beforeDetectDelay = ""; + [ObservableProperty] private int _rotaryFactor = 10; + [ObservableProperty] private bool _isFirstCheck = false; + [ObservableProperty] private bool _checkBeforeBurst = false; + } + + [ObservableProperty] private FightFinishDetectConfig _finishDetectConfig = new(); + [ObservableProperty] private string _guardianAvatar = string.Empty; + [ObservableProperty] private bool _guardianCombatSkip = false; + [ObservableProperty] private bool _guardianAvatarHold = false; + [ObservableProperty] private bool _burstEnabled = false; + [ObservableProperty] private bool _swimmingEnabled = false; + [ObservableProperty] private int _timeout = 120; + + public void CopyFromAutoFightConfig(AutoFightConfig source) + { + StrategyName = source.StrategyName; + TeamNames = source.TeamNames; + FightFinishDetectEnabled = source.FightFinishDetectEnabled; + ActionSchedulerByCd = source.ActionSchedulerByCd; + GuardianAvatar = source.GuardianAvatar; + GuardianCombatSkip = source.GuardianCombatSkip; + GuardianAvatarHold = source.GuardianAvatarHold; + BurstEnabled = source.BurstEnabled; + SwimmingEnabled = source.SwimmingEnabled; + Timeout = source.Timeout; + + FinishDetectConfig.BattleEndProgressBarColor = source.FinishDetectConfig.BattleEndProgressBarColor; + FinishDetectConfig.BattleEndProgressBarColorTolerance = source.FinishDetectConfig.BattleEndProgressBarColorTolerance; + FinishDetectConfig.FastCheckEnabled = source.FinishDetectConfig.FastCheckEnabled; + FinishDetectConfig.RotateFindEnemyEnabled = source.FinishDetectConfig.RotateFindEnemyEnabled; + FinishDetectConfig.FastCheckParams = source.FinishDetectConfig.FastCheckParams; + FinishDetectConfig.CheckEndDelay = source.FinishDetectConfig.CheckEndDelay; + FinishDetectConfig.BeforeDetectDelay = source.FinishDetectConfig.BeforeDetectDelay; + FinishDetectConfig.RotaryFactor = source.FinishDetectConfig.RotaryFactor; + FinishDetectConfig.IsFirstCheck = source.FinishDetectConfig.IsFirstCheck; + FinishDetectConfig.CheckBeforeBurst = source.FinishDetectConfig.CheckBeforeBurst; + } + + public AutoFightConfig ToAutoFightConfig() + { + var config = new AutoFightConfig + { + StrategyName = StrategyName, + TeamNames = TeamNames, + FightFinishDetectEnabled = FightFinishDetectEnabled, + ActionSchedulerByCd = ActionSchedulerByCd, + GuardianAvatar = GuardianAvatar, + GuardianCombatSkip = GuardianCombatSkip, + GuardianAvatarHold = GuardianAvatarHold, + BurstEnabled = BurstEnabled, + SwimmingEnabled = SwimmingEnabled, + Timeout = Timeout, + + // 地脉花战斗不启用战斗后拾取逻辑。 + PickDropsAfterFightEnabled = false, + PickDropsAfterFightSeconds = 0, + KazuhaPickupEnabled = false, + QinDoublePickUp = false, + OnlyPickEliteDropsMode = "DisableAutoPickupForNonElite", + KazuhaPartyName = string.Empty, + BattleThresholdForLoot = null + }; + + config.FinishDetectConfig = new AutoFightConfig.FightFinishDetectConfig + { + BattleEndProgressBarColor = FinishDetectConfig.BattleEndProgressBarColor, + BattleEndProgressBarColorTolerance = FinishDetectConfig.BattleEndProgressBarColorTolerance, + FastCheckEnabled = FinishDetectConfig.FastCheckEnabled, + RotateFindEnemyEnabled = FinishDetectConfig.RotateFindEnemyEnabled, + FastCheckParams = FinishDetectConfig.FastCheckParams, + CheckEndDelay = FinishDetectConfig.CheckEndDelay, + BeforeDetectDelay = FinishDetectConfig.BeforeDetectDelay, + RotaryFactor = FinishDetectConfig.RotaryFactor, + IsFirstCheck = FinishDetectConfig.IsFirstCheck, + CheckBeforeBurst = FinishDetectConfig.CheckBeforeBurst + }; + + return config; + } +} diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs index 8f728989..527282d6 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs @@ -23,6 +23,8 @@ public class AutoLeyLineOutcropParam:BaseTaskParam public string Team { get; set; } //战斗超时时间 public int Timeout { get; set; } + //地脉花独立战斗配置 + public AutoLeyLineOutcropFightConfig FightConfig { get; set; } = new(); //是否前往合成台合成浓缩树脂 public bool IsGoToSynthesizer { get; set; } //是否使用脆弱树脂 @@ -46,6 +48,7 @@ public class AutoLeyLineOutcropParam:BaseTaskParam FriendshipTeam= config.FriendshipTeam; Team= config.Team; Timeout= config.Timeout; + FightConfig = config.FightConfig ?? new AutoLeyLineOutcropFightConfig(); IsGoToSynthesizer=config.IsGoToSynthesizer; UseFragileResin= config.UseFragileResin; UseTransientResin= config.UseTransientResin; @@ -67,4 +70,4 @@ public class AutoLeyLineOutcropParam:BaseTaskParam this.Country = country; this.LeyLineOutcropType = leyLineOutcropType; } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs index ed8e5a16..ab73f075 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs @@ -17,6 +17,7 @@ using BetterGenshinImpact.GameTask; using BetterGenshinImpact.GameTask.Model; using BetterGenshinImpact.GameTask.Model.Area; using BetterGenshinImpact.Service.Notification; +using BetterGenshinImpact.View.Drawable; using Microsoft.Extensions.Logging; using OpenCvSharp; using System; @@ -31,6 +32,7 @@ using System.Threading.Tasks; using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; using Vanara.PInvoke; using static BetterGenshinImpact.GameTask.Common.TaskControl; +using BetterGenshinImpact.View; namespace BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; @@ -70,6 +72,13 @@ public class AutoLeyLineOutcropTask : ISoloTask private const int MaxRecheckCount = 3; private const int MaxConsecutiveFailures = 5; + private const string OcrFlowOverlayKey = "AutoLeyLineOutcrop.OcrFlow"; + private const string OcrFightOverlayKey = "AutoLeyLineOutcrop.OcrFight"; + private const int OcrOverlayRenderLeadMs = 300; + private static readonly System.Drawing.Pen OcrOverlayPen = new(System.Drawing.Color.Lime, 2); + private bool _overlayDisplayTemporarilyEnabled; + private bool _overlayDisplayOriginalValue; + private DateTime _lastMaskBringTopTime = DateTime.MinValue; public string Name => "自动地脉花"; @@ -86,6 +95,7 @@ public class AutoLeyLineOutcropTask : ISoloTask try { Initialize(); + EnsureMaskOverlayVisible(); var runTimesValue = await HandleResinExhaustionMode(); if (runTimesValue <= 0) { @@ -119,16 +129,24 @@ public class AutoLeyLineOutcropTask : ISoloTask { try { - await EnsureExitRewardPage(); - } - catch (Exception ex) - { - _logger.LogDebug(ex, "地脉花结束后尝试退出奖励界面失败"); - } + try + { + await EnsureExitRewardPage(); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "地脉花结束后尝试退出奖励界面失败"); + } - if (!_marksStatus) + if (!_marksStatus) + { + await OpenCustomMarks(); + } + } + finally { - await OpenCustomMarks(); + ClearOcrOverlayKeys(); + RestoreMaskOverlayVisible(); } } } @@ -144,6 +162,8 @@ public class AutoLeyLineOutcropTask : ISoloTask private void ValidateSettings() { + _taskParam.FightConfig ??= new AutoLeyLineOutcropFightConfig(); + if (string.IsNullOrWhiteSpace(_taskParam.LeyLineOutcropType)) { throw new Exception("地脉花类型未选择"); @@ -168,6 +188,20 @@ public class AutoLeyLineOutcropTask : ISoloTask { _taskParam.Count = 1; } + + if (string.IsNullOrWhiteSpace(_taskParam.FightConfig.StrategyName) && _taskParam.Timeout > 0) + { + _taskParam.FightConfig.Timeout = _taskParam.Timeout; + } + + if (_taskParam.FightConfig.Timeout <= 0) + { + _taskParam.FightConfig.Timeout = _taskParam.Timeout > 0 ? _taskParam.Timeout : 120; + } + else + { + _taskParam.Timeout = _taskParam.FightConfig.Timeout; + } } private void LoadConfigData() @@ -635,7 +669,7 @@ public class AutoLeyLineOutcropTask : ISoloTask var lastRoute = path.Routes.Last(); var targetRoute = lastRoute.Replace("Assets/pathing/", "Assets/pathing/target/").Replace("-rerun", ""); - await ProcessLeyLineOutcrop(_taskParam.Timeout, targetRoute); + await ProcessLeyLineOutcrop(_taskParam.FightConfig.Timeout, targetRoute); var rewardSuccess = await AttemptReward(); if (!rewardSuccess) @@ -819,18 +853,24 @@ public class AutoLeyLineOutcropTask : ISoloTask await Delay(500, _ct); _logger.LogDebug("检测地脉花交互状态,重试次数: {Retries}/{MaxRetries}", retries + 1, maxRetries); using var capture = CaptureToRectArea(); + using var ocrOverlayScope = DrawOcrOverlayScope(capture, OcrFlowOverlayKey, _ocrRo2!.RegionOfInterest, _ocrRo3!.RegionOfInterest); + await WaitOcrOverlayRenderTick(); + string result1Text; + string result2Text; var result1 = FindSafe(capture, _ocrRo2!); var result2 = FindSafe(capture, _ocrRo3!); - _logger.LogDebug("OCR结果: result1='{Text1}', result2='{Text2}'", result1.Text, result2.Text); + result1Text = result1.Text; + result2Text = result2.Text; + _logger.LogDebug("OCR结果: result1='{Text1}', result2='{Text2}'", result1Text, result2Text); - if (result2.Text.Contains("之花", StringComparison.Ordinal)) + if (result2Text.Contains("之花", StringComparison.Ordinal)) { _logger.LogDebug("识别到地脉之花入口"); await SwitchToFriendshipTeamIfNeeded(); return true; } - if (result2.Text.Contains("溢口", StringComparison.Ordinal)) + if (result2Text.Contains("溢口", StringComparison.Ordinal)) { _logger.LogDebug("识别到溢口提示,尝试交互"); Simulation.SendInput.SimulateAction(GIActions.PickUpOrInteract); @@ -838,7 +878,7 @@ public class AutoLeyLineOutcropTask : ISoloTask Simulation.SendInput.SimulateAction(GIActions.PickUpOrInteract); await Delay(500, _ct); } - else if (!ContainsFightText(result1.Text)) + else if (!ContainsFightText(result1Text)) { _logger.LogDebug("未识别到战斗提示,执行路径: {Path}", targetPath); await RunPathingFile(targetPath); @@ -889,6 +929,7 @@ public class AutoLeyLineOutcropTask : ISoloTask private async Task AutoFight(int timeoutSeconds) { var fightCts = CancellationTokenSource.CreateLinkedTokenSource(_ct); + using var autoFightConfigScope = UseLeyLineAutoFightConfigScope(); // Ley line uses OCR-based finish detection; disable auto-fight finish detect. var fightTask = StartAutoFightWithoutFinishDetect(fightCts.Token); var fightResult = await RecognizeTextInRegion(timeoutSeconds * 1000); @@ -915,7 +956,7 @@ public class AutoLeyLineOutcropTask : ISoloTask private Task StartAutoFightWithoutFinishDetect(CancellationToken ct) { - var autoFightConfig = TaskContext.Instance().Config.AutoFightConfig; + var autoFightConfig = BuildLeyLineAutoFightConfig(); var strategyPath = BuildAutoFightStrategyPath(autoFightConfig); var taskParam = new AutoFightParam(strategyPath, autoFightConfig) { @@ -925,9 +966,39 @@ public class AutoLeyLineOutcropTask : ISoloTask // Avoid false finish signals for ley line fights. taskParam.FinishDetectConfig.FastCheckEnabled = false; taskParam.FinishDetectConfig.RotateFindEnemyEnabled = false; + taskParam.PickDropsAfterFightEnabled = false; + taskParam.KazuhaPickupEnabled = false; + taskParam.QinDoublePickUp = false; + taskParam.OnlyPickEliteDropsMode = "DisableAutoPickupForNonElite"; return new AutoFightTask(taskParam).Start(ct); } + private IDisposable UseLeyLineAutoFightConfigScope() + { + var allConfig = TaskContext.Instance().Config; + var original = allConfig.AutoFightConfig; + allConfig.AutoFightConfig = BuildLeyLineAutoFightConfig(); + return new AutoFightConfigScope(allConfig, original); + } + + private AutoFightConfig BuildLeyLineAutoFightConfig() + { + var globalAutoFightConfig = TaskContext.Instance().Config.AutoFightConfig; + var fightConfig = _taskParam.FightConfig; + if (string.IsNullOrWhiteSpace(fightConfig.StrategyName)) + { + fightConfig.CopyFromAutoFightConfig(globalAutoFightConfig); + } + + if (fightConfig.Timeout <= 0) + { + fightConfig.Timeout = _taskParam.Timeout > 0 ? _taskParam.Timeout : Math.Max(globalAutoFightConfig.Timeout, 1); + } + + _taskParam.Timeout = fightConfig.Timeout; + return fightConfig.ToAutoFightConfig(); + } + private static string BuildAutoFightStrategyPath(AutoFightConfig config) { var path = Global.Absolute(@"User\AutoFight\" + config.StrategyName + ".txt"); @@ -954,8 +1025,13 @@ public class AutoLeyLineOutcropTask : ISoloTask while ((DateTime.UtcNow - start).TotalMilliseconds < timeoutMs) { using var capture = CaptureToRectArea(); + using var ocrOverlayScope = DrawOcrOverlayScope(capture, OcrFightOverlayKey, _ocrRo1!.RegionOfInterest, _ocrRo2!.RegionOfInterest); + await WaitOcrOverlayRenderTick(); + string text; + bool foundText; var result = capture.Find(_ocrRo1!); - var text = result.Text; + text = result.Text; + foundText = RecognizeFightText(capture); if (successKeywords.Any(text.Contains)) { @@ -969,7 +1045,6 @@ public class AutoLeyLineOutcropTask : ISoloTask return false; } - var foundText = RecognizeFightText(capture); if (!foundText) { noTextCount++; @@ -1278,6 +1353,110 @@ public class AutoLeyLineOutcropTask : ISoloTask return false; } + private IDisposable DrawOcrOverlayScope(ImageRegion capture, string key, params Rect[] rois) + { + var drawList = new List(rois.Length); + foreach (var roi in rois) + { + var clamped = roi.ClampTo(capture.Width, capture.Height); + if (clamped.Width <= 0 || clamped.Height <= 0) + { + continue; + } + + drawList.Add(capture.ToRectDrawable(clamped, key, OcrOverlayPen)); + } + + var drawContent = VisionContext.Instance().DrawContent; + drawContent.PutOrRemoveRectList(key, drawList.Count > 0 ? drawList : null); + RefreshMaskWindowForOverlay(); + return new OcrOverlayScope(drawContent, key); + } + + private void ClearOcrOverlayKeys() + { + var drawContent = VisionContext.Instance().DrawContent; + drawContent.RemoveRect(OcrFlowOverlayKey); + drawContent.RemoveRect(OcrFightOverlayKey); + drawContent.PutOrRemoveTextList(OcrFlowOverlayKey, null); + drawContent.PutOrRemoveTextList(OcrFightOverlayKey, null); + } + + private async Task WaitOcrOverlayRenderTick() + { + await Task.Yield(); + await Task.Delay(OcrOverlayRenderLeadMs, _ct); + } + + private void EnsureMaskOverlayVisible() + { + var config = TaskContext.Instance().Config.MaskWindowConfig; + _overlayDisplayOriginalValue = config.DisplayRecognitionResultsOnMask; + if (!config.DisplayRecognitionResultsOnMask) + { + config.DisplayRecognitionResultsOnMask = true; + _overlayDisplayTemporarilyEnabled = true; + } + + var maskWindow = MaskWindow.InstanceNullable(); + if (maskWindow != null) + { + maskWindow.Invoke(() => + { + maskWindow.Topmost = true; + if (!maskWindow.IsVisible) + { + maskWindow.Show(); + } + + maskWindow.BringToTop(); + }); + } + } + + private void RestoreMaskOverlayVisible() + { + if (!_overlayDisplayTemporarilyEnabled) + { + return; + } + + TaskContext.Instance().Config.MaskWindowConfig.DisplayRecognitionResultsOnMask = _overlayDisplayOriginalValue; + _overlayDisplayTemporarilyEnabled = false; + } + + private void RefreshMaskWindowForOverlay() + { + var maskWindow = MaskWindow.InstanceNullable(); + if (maskWindow == null) + { + return; + } + + var now = DateTime.UtcNow; + var shouldBringTop = now - _lastMaskBringTopTime > TimeSpan.FromSeconds(1); + if (shouldBringTop) + { + _lastMaskBringTopTime = now; + } + + maskWindow.Invoke(() => + { + maskWindow.Topmost = true; + if (!maskWindow.IsVisible) + { + maskWindow.Show(); + } + + if (shouldBringTop) + { + maskWindow.BringToTop(); + } + + maskWindow.Refresh(); + }); + } + private async Task CheckOriginalResinEmpty() { using var capture = CaptureToRectArea(); @@ -1497,12 +1676,22 @@ public class AutoLeyLineOutcropTask : ISoloTask await Delay(1000, _ct); await FindAndClickCountry(country); - await FindAndCancelTrackingInBook(); + var trackButton = await FindAndCancelTrackingInBook(); for (var retry = 0; retry < 3; retry++) { await Delay(1000, _ct); - GameCaptureRegion.GameRegion1080PPosClick(1500, 850); + if (trackButton != null) + { + trackButton.Click(); + _logger.LogDebug("通过 OCR 结果点击追踪按钮,text={Text}", trackButton.Text); + } + else + { + GameCaptureRegion.GameRegion1080PPosClick(1500, 850); + _logger.LogDebug("未识别到追踪按钮,回退固定坐标点击"); + } + await Delay(2500, _ct); if (await CheckBigMapOpened()) @@ -1514,7 +1703,7 @@ public class AutoLeyLineOutcropTask : ISoloTask { await _returnMainUiTask.Start(_ct); await FindAndClickCountry(country); - await FindAndCancelTrackingInBook(); + trackButton = await FindAndCancelTrackingInBook(); } else { @@ -1554,13 +1743,29 @@ public class AutoLeyLineOutcropTask : ISoloTask target.Click(); } - private async Task FindAndCancelTrackingInBook() + private static bool IsTrackButtonText(string text) + { + return (text.Contains("追踪", StringComparison.Ordinal) || text.Contains("追蹤", StringComparison.Ordinal)) + && !text.Contains("停止", StringComparison.Ordinal); + } + + private async Task FindAndCancelTrackingInBook() { using var capture = CaptureToRectArea(); var list = capture.FindMulti(_ocrRoThis); + var track = list.FirstOrDefault(r => IsTrackButtonText(r.Text)); var stop = list.FirstOrDefault(r => r.Text.Contains("停止", StringComparison.Ordinal)); - stop?.Click(); - await Delay(1000, _ct); + if (stop != null) + { + stop.Click(); + await Delay(1000, _ct); + + using var refreshCapture = CaptureToRectArea(); + var refreshList = refreshCapture.FindMulti(_ocrRoThis); + track = refreshList.FirstOrDefault(r => IsTrackButtonText(r.Text)); + } + + return track; } private async Task CancelTrackingInMap() @@ -1940,6 +2145,39 @@ public class AutoLeyLineOutcropTask : ISoloTask public int FragileResinTimes { get; set; } } + private sealed class OcrOverlayScope(DrawContent drawContent, string key) : IDisposable + { + private bool _disposed; + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + drawContent.RemoveRect(key); + drawContent.PutOrRemoveTextList(key, null); + } + } + + private sealed class AutoFightConfigScope(AllConfig allConfig, AutoFightConfig originalConfig) : IDisposable + { + private bool _disposed; + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + allConfig.AutoFightConfig = originalConfig; + } + } + private readonly struct UseButton { public int X { get; } @@ -1958,4 +2196,4 @@ public class AutoLeyLineOutcropTask : ISoloTask GameCaptureRegion.GameRegion1080PPosClick(X, Y); } } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/View/MaskWindow.xaml.cs b/BetterGenshinImpact/View/MaskWindow.xaml.cs index 9794db2e..7e0dba17 100644 --- a/BetterGenshinImpact/View/MaskWindow.xaml.cs +++ b/BetterGenshinImpact/View/MaskWindow.xaml.cs @@ -52,7 +52,6 @@ public partial class MaskWindow : Window private MaskWindowConfig? _maskWindowConfig; private MapLabelSearchWindow? _mapLabelSearchWindow; private CancellationTokenSource? _mapLabelCategorySelectCts; - static MaskWindow() { if (Application.Current.TryFindResource("TextThemeFontFamily") is FontFamily fontFamily) @@ -463,96 +462,98 @@ public partial class MaskWindow : Window return; } - // 先有上方判断的原因是,有可能Render的时候,配置还未初始化 - if (!TaskContext.Instance().Config.MaskWindowConfig.DisplayRecognitionResultsOnMask) + var displayRecognitionResults = TaskContext.Instance().Config.MaskWindowConfig.DisplayRecognitionResultsOnMask; + if (!displayRecognitionResults) { return; } - foreach (var kv in VisionContext.Instance().DrawContent.RectList) + if (displayRecognitionResults) { - foreach (var drawable in kv.Value) + foreach (var kv in VisionContext.Instance().DrawContent.RectList) { - if (!drawable.IsEmpty) + foreach (var drawable in kv.Value) { - drawingContext.DrawRectangle(Brushes.Transparent, - new Pen(new SolidColorBrush(drawable.Pen.Color.ToWindowsColor()), drawable.Pen.Width), - drawable.Rect); + if (!drawable.IsEmpty) + { + drawingContext.DrawRectangle( + Brushes.Transparent, + new Pen(new SolidColorBrush(drawable.Pen.Color.ToWindowsColor()), drawable.Pen.Width), + drawable.Rect); + } } } - } - foreach (var kv in VisionContext.Instance().DrawContent.LineList) - { - foreach (var drawable in kv.Value) + foreach (var kv in VisionContext.Instance().DrawContent.LineList) { - drawingContext.DrawLine(new Pen(new SolidColorBrush(drawable.Pen.Color.ToWindowsColor()), drawable.Pen.Width), drawable.P1, drawable.P2); - } - } - - foreach (var kv in VisionContext.Instance().DrawContent.TextList) - { - bool isSkillCd = kv.Key == "SkillCdText"; - var systemInfo = TaskContext.Instance().SystemInfo; - // 使用不封顶的物理比例进行 UI 大小缩放 - var scaleTo1080 = systemInfo.ScaleTo1080PRatio; - - foreach (var drawable in kv.Value) - { - if (!drawable.IsEmpty) + foreach (var drawable in kv.Value) { - var pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip; - var renderPoint = new Point(drawable.Point.X / pixelsPerDip, drawable.Point.Y / pixelsPerDip); + drawingContext.DrawLine(new Pen(new SolidColorBrush(drawable.Pen.Color.ToWindowsColor()), drawable.Pen.Width), drawable.P1, drawable.P2); + } + } - if (isSkillCd) + foreach (var kv in VisionContext.Instance().DrawContent.TextList) + { + bool isSkillCd = kv.Key == "SkillCdText"; + var systemInfo = TaskContext.Instance().SystemInfo; + var scaleTo1080 = systemInfo.ScaleTo1080PRatio; + + foreach (var drawable in kv.Value) + { + if (!drawable.IsEmpty) { - // 自定义缩放 - var skillConfigScale = TaskContext.Instance().Config.SkillCdConfig.Scale; - double scaledFontSize = (26 * scaleTo1080 * skillConfigScale) / pixelsPerDip; - var mediumTypeface = new Typeface(_fgiTypeface.FontFamily, _fgiTypeface.Style, FontWeights.Medium, _fgiTypeface.Stretch); - bool isZeroCd = - double.TryParse(drawable.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out var cdValue) - && Math.Abs(cdValue) < 0.8; + var pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip; + var renderPoint = new Point(drawable.Point.X / pixelsPerDip, drawable.Point.Y / pixelsPerDip); - // 从配置读取颜色 - var skillConfig = TaskContext.Instance().Config.SkillCdConfig; - string textColorStr = isZeroCd ? skillConfig.TextReadyColor : skillConfig.TextNormalColor; - string bgColorStr = isZeroCd ? skillConfig.BackgroundReadyColor : skillConfig.BackgroundNormalColor; + if (isSkillCd) + { + var skillConfigScale = TaskContext.Instance().Config.SkillCdConfig.Scale; + double scaledFontSize = (26 * scaleTo1080 * skillConfigScale) / pixelsPerDip; + var mediumTypeface = new Typeface(_fgiTypeface.FontFamily, _fgiTypeface.Style, FontWeights.Medium, _fgiTypeface.Stretch); + bool isZeroCd = + double.TryParse(drawable.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out var cdValue) + && Math.Abs(cdValue) < 0.8; - Color textColor = ParseColor(textColorStr) ?? (isZeroCd ? Color.FromRgb(93, 204, 23) : Color.FromRgb(218, 74, 35)); - Color bgColor = ParseColor(bgColorStr) ?? Colors.White; + var skillConfig = TaskContext.Instance().Config.SkillCdConfig; + string textColorStr = isZeroCd ? skillConfig.TextReadyColor : skillConfig.TextNormalColor; + string bgColorStr = isZeroCd ? skillConfig.BackgroundReadyColor : skillConfig.BackgroundNormalColor; - Brush textBrush = new SolidColorBrush(textColor); - Brush bgBrush = new SolidColorBrush(bgColor); + Color textColor = ParseColor(textColorStr) ?? (isZeroCd ? Color.FromRgb(93, 204, 23) : Color.FromRgb(218, 74, 35)); + Color bgColor = ParseColor(bgColorStr) ?? Colors.White; - var formattedText = new FormattedText( - drawable.Text, - CultureInfo.GetCultureInfo("zh-cn"), - FlowDirection.LeftToRight, - mediumTypeface, - scaledFontSize, - textBrush, - pixelsPerDip); + Brush textBrush = new SolidColorBrush(textColor); + Brush bgBrush = new SolidColorBrush(bgColor); - double px = (6 * scaleTo1080 * skillConfigScale) / pixelsPerDip; - double py = (2 * scaleTo1080 * skillConfigScale) / pixelsPerDip; - double radius = (5 * scaleTo1080 * skillConfigScale) / pixelsPerDip; - var bgRect = new Rect(renderPoint.X - px, renderPoint.Y - py, formattedText.Width + px * 2, formattedText.Height + py * 2); - drawingContext.DrawRoundedRectangle(bgBrush, null, bgRect, radius, radius); - drawingContext.DrawText(formattedText, renderPoint); - } - else - { - double defaultFontSize = (36 * scaleTo1080) / pixelsPerDip; - drawingContext.DrawText(new FormattedText(drawable.Text, - CultureInfo.GetCultureInfo("zh-cn"), - FlowDirection.LeftToRight, - _typeface, - defaultFontSize, Brushes.Black, pixelsPerDip), renderPoint); + var formattedText = new FormattedText( + drawable.Text, + CultureInfo.GetCultureInfo("zh-cn"), + FlowDirection.LeftToRight, + mediumTypeface, + scaledFontSize, + textBrush, + pixelsPerDip); + + double px = (6 * scaleTo1080 * skillConfigScale) / pixelsPerDip; + double py = (2 * scaleTo1080 * skillConfigScale) / pixelsPerDip; + double radius = (5 * scaleTo1080 * skillConfigScale) / pixelsPerDip; + var bgRect = new Rect(renderPoint.X - px, renderPoint.Y - py, formattedText.Width + px * 2, formattedText.Height + py * 2); + drawingContext.DrawRoundedRectangle(bgBrush, null, bgRect, radius, radius); + drawingContext.DrawText(formattedText, renderPoint); + } + else + { + double defaultFontSize = (36 * scaleTo1080) / pixelsPerDip; + drawingContext.DrawText(new FormattedText(drawable.Text, + CultureInfo.GetCultureInfo("zh-cn"), + FlowDirection.LeftToRight, + _typeface, + defaultFontSize, Brushes.Black, pixelsPerDip), renderPoint); + } } } } } + } catch (Exception e) { diff --git a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml index 88407f4f..54318dc2 100644 --- a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml @@ -2476,6 +2476,83 @@ ItemsSource="{Binding Source={x:Static pages:TaskSettingsPageViewModel.LeyLineOutcropCountryList}}" SelectedItem="{Binding Config.AutoLeyLineOutcropConfig.Country, Mode=TwoWay}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2696,7 +2773,7 @@ Maximum="9999" Minimum="1" ValidationMode="InvalidInputOverwritten" - Value="{Binding Config.AutoLeyLineOutcropConfig.Timeout, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> + Value="{Binding Config.AutoLeyLineOutcropConfig.FightConfig.Timeout, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> From 6668a89fa7293814e657da40853ffe94f6782ae9 Mon Sep 17 00:00:00 2001 From: HSHMENG <141244101+hshmeng@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:15:53 +0800 Subject: [PATCH 017/107] =?UTF-8?q?=E4=B8=83=E5=9C=A3=E5=8F=AC=E5=94=A4?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E4=B8=AD=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=AA=B0=E5=AD=90=E6=95=B0=E9=87=8F=E5=A2=9E?= =?UTF-8?q?=E5=87=8F=20(#2832)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/ActionCommand.cs | 11 +++++++--- .../AutoGeniusInvokation/Model/Duel.cs | 8 ++++--- .../AutoGeniusInvokation/ScriptParser.cs | 22 ++++++++++++++++++- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/ActionCommand.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/ActionCommand.cs index b64a28f9..6ee87be4 100644 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/ActionCommand.cs +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/ActionCommand.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model { @@ -15,6 +15,11 @@ namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model /// 目标编号(技能编号,从右往左) /// public int TargetIndex { get; set; } + + /// + /// 灵活改变骰子的数量(因为在不同的牌局中或者角色技能中会发生骰子实际需要的数量增加或减少) + /// + public int DiceDelta { get; set; } = 0; public override string? ToString() { @@ -22,11 +27,11 @@ namespace BetterGenshinImpact.GameTask.AutoGeniusInvokation.Model { if (string.IsNullOrEmpty(Character.Skills[TargetIndex].Name)) { - return $"【{Character.Name}】使用【技能{TargetIndex}】"; + return $"【{Character.Name}】使用【技能{TargetIndex}】{(DiceDelta != 0 ? $"(骰子{(DiceDelta > 0 ? "增加" : "减少")}{Math.Abs(DiceDelta)})" : "")}"; } else { - return $"【{Character.Name}】使用【{Character.Skills[TargetIndex].Name}】"; + return $"【{Character.Name}】使用【{Character.Skills[TargetIndex].Name}】{(DiceDelta != 0 ? $"(骰子{(DiceDelta > 0 ? "增加" : "减少")}{Math.Abs(DiceDelta)})" : "")}"; } } else if (Action == ActionEnum.SwitchLater) diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs index 0c9a12ab..197fde4a 100644 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Model/Duel.cs @@ -203,7 +203,7 @@ public class Duel } // 2. 判断使用技能 - if (actionCommand.GetAllDiceUseCount() > CurrentDiceCount) + if (actionCommand.GetAllDiceUseCount() + actionCommand.DiceDelta > CurrentDiceCount)// 判断条件加上 DiceDelta 骰子变化 { _logger.LogInformation("骰子不足以进行下一步:{Action}", actionCommand); break; @@ -213,7 +213,9 @@ public class Duel bool useSkillRes = actionCommand.UseSkill(this); if (useSkillRes) { - CurrentDiceCount -= actionCommand.GetAllDiceUseCount(); + int realCost = actionCommand.GetAllDiceUseCount() + actionCommand.DiceDelta; + realCost = Math.Max(realCost, 0); + CurrentDiceCount -= realCost; alreadyExecutedActionIndex.Add(i); alreadyExecutedActionCommand.Add(actionCommand); _logger.LogInformation("→指令执行完成:{Action}", actionCommand); @@ -344,7 +346,7 @@ public class Duel } // 2. 判断使用技能 - actionUseDiceSum += actionCommand.GetAllDiceUseCount(); + actionUseDiceSum += Math.Max(actionCommand.GetAllDiceUseCount() + actionCommand.DiceDelta, 0); if (actionUseDiceSum > CurrentDiceCount) { break; diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/ScriptParser.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/ScriptParser.cs index 0ba12071..6397228a 100644 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/ScriptParser.cs +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/ScriptParser.cs @@ -60,7 +60,7 @@ public class ScriptParser MyAssert(duel.Characters[3] != null, "角色未定义"); string[] actionParts = line.Split(" ", StringSplitOptions.RemoveEmptyEntries); - MyAssert(actionParts.Length == 3, "策略中的行动命令解析错误"); + MyAssert(actionParts.Length >= 3 && actionParts.Length <= 4, "策略中的行动命令解析错误"); MyAssert(actionParts[1] == "使用", "策略中的行动命令解析错误"); var actionCommand = new ActionCommand(); @@ -83,6 +83,26 @@ public class ScriptParser int skillNum = int.Parse(RegexHelper.ExcludeNumberRegex().Replace(actionParts[2], "")); MyAssert(skillNum < 5, "策略中的行动命令解析错误:技能编号错误"); actionCommand.TargetIndex = skillNum; + + // 解析骰子增减 + actionCommand.DiceDelta = 0; + if (actionParts.Length >= 4) + { + if (actionParts[3].StartsWith("骰子增加")) + { + int delta = int.Parse(RegexHelper.ExcludeNumberRegex().Replace(actionParts[3], "")); + actionCommand.DiceDelta = delta; + } + else if (actionParts[3].StartsWith("骰子减少")) + { + int delta = int.Parse(RegexHelper.ExcludeNumberRegex().Replace(actionParts[3], "")); + actionCommand.DiceDelta = -delta; + } + else + { + MyAssert(false, $"策略中的行动命令解析错误:骰子增减参数格式不正确(应为 骰子增加N 或 骰子减少N ),实际:{actionParts[3]}"); + } + } duel.ActionCommandQueue.Add(actionCommand); } else From c2b68cfee9a24045cd772de0b611906c6acb91e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=9D=E5=87=AF=E9=98=B3?= <10895490+haokaiyang@users.noreply.github.com> Date: Thu, 26 Feb 2026 18:32:33 +0800 Subject: [PATCH 018/107] auto tcg: update character card config to v6.4 (#2849) --- .../Assets/tcg_character_card.json | 387 ++++++++++++++---- 1 file changed, 309 insertions(+), 78 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Assets/tcg_character_card.json b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Assets/tcg_character_card.json index 2c9ee916..4ea6be57 100644 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Assets/tcg_character_card.json +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/Assets/tcg_character_card.json @@ -1001,7 +1001,7 @@ "nameEn": "escoffier", "type": "character", "name": "爱可菲", - "hp": 11, + "hp": 10, "energy": 2, "element": "冰元素", "weapon": "长柄武器", @@ -1073,6 +1073,154 @@ } ] }, + { + "id": 1116, + "nameEn": "skirk", + "type": "character", + "name": "丝柯克", + "hp": 10, + "energy": 0, + "element": "冰元素", + "weapon": "单手剑", + "skills": [ + { + "nameEn": "havoc_sunder", + "name": "极恶技·断", + "skillTag": [ + "普通攻击" + ], + "cost": [ + { + "id": 1101, + "nameEn": "cryo", + "type": "冰元素", + "count": 1 + }, + { + "id": 1109, + "nameEn": "unaligned_element", + "type": "无色元素", + "count": 2 + } + ] + }, + { + "nameEn": "havoc_warp", + "name": "极恶技·闪", + "skillTag": [ + "元素战技" + ], + "cost": [ + { + "id": 1101, + "nameEn": "cryo", + "type": "冰元素", + "count": 2 + } + ] + }, + { + "nameEn": "havoc_ruin", + "name": "极恶技·灭", + "skillTag": [ + "元素爆发" + ], + "cost": [ + { + "id": 1101, + "nameEn": "cryo", + "type": "冰元素", + "count": 3 + } + ] + }, + { + "nameEn": "reason_beyond_reason", + "name": "理外之理", + "skillTag": [ + "被动技能" + ], + "cost": [] + } + ] + }, + { + "id": 1117, + "nameEn": "mika", + "type": "character", + "name": "米卡", + "hp": 10, + "energy": 2, + "element": "冰元素", + "weapon": "长柄武器", + "skills": [ + { + "nameEn": "spear_of_favonius_arrows_passage", + "name": "西风枪术·镝传", + "skillTag": [ + "普通攻击" + ], + "cost": [ + { + "id": 1101, + "nameEn": "cryo", + "type": "冰元素", + "count": 1 + }, + { + "id": 1109, + "nameEn": "unaligned_element", + "type": "无色元素", + "count": 2 + } + ] + }, + { + "nameEn": "starfrost_swirl", + "name": "星霜的流旋", + "skillTag": [ + "元素战技" + ], + "cost": [ + { + "id": 1101, + "nameEn": "cryo", + "type": "冰元素", + "count": 3 + } + ] + }, + { + "nameEn": "skyfeather_song", + "name": "苍翎的颂愿", + "skillTag": [ + "元素爆发" + ], + "cost": [ + { + "id": 1101, + "nameEn": "cryo", + "type": "冰元素", + "count": 3 + }, + { + "id": 1110, + "nameEn": "energy", + "type": "充能", + "count": 2 + } + ] + }, + { + "nameEn": "suppressive_barrage", + "name": "速射牵制", + "skillTag": [ + "被动技能" + ], + "cost": [] + } + ] + }, { "id": 1201, "nameEn": "barbara", @@ -1646,7 +1794,7 @@ "nameEn": "yelan", "type": "character", "name": "夜兰", - "hp": 10, + "hp": 11, "energy": 3, "element": "水元素", "weapon": "弓", @@ -4299,6 +4447,83 @@ } ] }, + { + "id": 1417, + "nameEn": "ineffa", + "type": "character", + "name": "伊涅芙", + "hp": 10, + "energy": 2, + "element": "雷元素", + "weapon": "长柄武器", + "skills": [ + { + "nameEn": "cyclonic_duster", + "name": "除尘旋刃", + "skillTag": [ + "普通攻击" + ], + "cost": [ + { + "id": 1104, + "nameEn": "electro", + "type": "雷元素", + "count": 1 + }, + { + "id": 1109, + "nameEn": "unaligned_element", + "type": "无色元素", + "count": 2 + } + ] + }, + { + "nameEn": "cleaning_mode_carrier_frequency", + "name": "涤净模式·稳态载频", + "skillTag": [ + "元素战技" + ], + "cost": [ + { + "id": 1104, + "nameEn": "electro", + "type": "雷元素", + "count": 3 + } + ] + }, + { + "nameEn": "supreme_instruction_cyclonic_exterminator", + "name": "至高律令·全域扫灭", + "skillTag": [ + "元素爆发" + ], + "cost": [ + { + "id": 1104, + "nameEn": "electro", + "type": "雷元素", + "count": 3 + }, + { + "id": 1110, + "nameEn": "energy", + "type": "充能", + "count": 2 + } + ] + }, + { + "nameEn": "moonsign_benediction_assemblage_hub", + "name": "月兆祝赐·象拟中继", + "skillTag": [ + "被动技能" + ], + "cost": [] + } + ] + }, { "id": 1501, "nameEn": "sucrose", @@ -7388,7 +7613,7 @@ "nameEn": "alldevouring_narwhal", "type": "character", "name": "吞星之鲸", - "hp": 5, + "hp": 6, "energy": 2, "element": "水元素", "weapon": "其他武器", @@ -7534,7 +7759,7 @@ "nameEn": "hydro_tulpa", "type": "character", "name": "水形幻人", - "hp": 11, + "hp": 10, "energy": 3, "element": "水元素", "weapon": "其他武器", @@ -7688,7 +7913,7 @@ "nameEn": "fatui_pyro_agent", "type": "character", "name": "愚人众·火之债务处理人", - "hp": 9, + "hp": 11, "energy": 2, "element": "火元素", "weapon": "其他武器", @@ -8068,6 +8293,83 @@ } ] }, + { + "id": 2306, + "nameEn": "goldflame_qucusaur_tyrant", + "type": "character", + "name": "金焰绒翼龙暴君", + "hp": 11, + "energy": 2, + "element": "火元素", + "weapon": "其他武器", + "skills": [ + { + "nameEn": "wingcleave", + "name": "翼斩", + "skillTag": [ + "普通攻击" + ], + "cost": [ + { + "id": 1103, + "nameEn": "pyro", + "type": "火元素", + "count": 1 + }, + { + "id": 1109, + "nameEn": "unaligned_element", + "type": "无色元素", + "count": 2 + } + ] + }, + { + "nameEn": "rising_scorchwind", + "name": "升腾炽风", + "skillTag": [ + "元素战技" + ], + "cost": [ + { + "id": 1103, + "nameEn": "pyro", + "type": "火元素", + "count": 3 + } + ] + }, + { + "nameEn": "goldflame_detonation", + "name": "金焰爆轰", + "skillTag": [ + "元素爆发" + ], + "cost": [ + { + "id": 1103, + "nameEn": "pyro", + "type": "火元素", + "count": 3 + }, + { + "id": 1110, + "nameEn": "energy", + "type": "充能", + "count": 2 + } + ] + }, + { + "nameEn": "ancient_bloodline", + "name": "古老者的血脉", + "skillTag": [ + "被动技能" + ], + "cost": [] + } + ] + }, { "id": 2401, "nameEn": "electro_hypostasis", @@ -8772,7 +9074,7 @@ "nameEn": "stonehide_lawachurl", "type": "character", "name": "丘丘岩盔王", - "hp": 8, + "hp": 10, "energy": 2, "element": "岩元素", "weapon": "其他武器", @@ -9072,7 +9374,7 @@ "nameEn": "jadeplume_terrorshroom", "type": "character", "name": "翠翎恐蕈", - "hp": 10, + "hp": 12, "energy": 2, "element": "草元素", "weapon": "其他武器", @@ -9374,76 +9676,5 @@ "cost": [] } ] - }, - { - "id": 6605, - "nameEn": "skirk", - "type": "character", - "name": "丝柯克", - "hp": 10, - "energy": 0, - "element": "冰元素", - "weapon": "单手剑", - "skills": [ - { - "nameEn": "havoc_sunder", - "name": "极恶技·断", - "skillTag": [ - "普通攻击" - ], - "cost": [ - { - "id": 1101, - "nameEn": "cryo", - "type": "冰元素", - "count": 1 - }, - { - "id": 1109, - "nameEn": "unaligned_element", - "type": "无色元素", - "count": 2 - } - ] - }, - { - "nameEn": "havoc_warp", - "name": "极恶技·闪", - "skillTag": [ - "元素战技" - ], - "cost": [ - { - "id": 1101, - "nameEn": "cryo", - "type": "冰元素", - "count": 2 - } - ] - }, - { - "nameEn": "havoc_extinction", - "name": "极恶技·尽", - "skillTag": [ - "元素爆发" - ], - "cost": [ - { - "id": 1101, - "nameEn": "cryo", - "type": "冰元素", - "count": 1 - } - ] - }, - { - "nameEn": "reason_beyond_reason", - "name": "理外之理", - "skillTag": [ - "被动技能" - ], - "cost": [] - } - ] } ] \ No newline at end of file From b2d5988d245ecb5dc78547fd462523ccb6681571 Mon Sep 17 00:00:00 2001 From: DarkFlameMaster <1004452714@qq.com> Date: Thu, 26 Feb 2026 21:38:36 +0800 Subject: [PATCH 019/107] =?UTF-8?q?fix:=E9=80=82=E9=85=8D=E7=A7=98?= =?UTF-8?q?=E5=A2=83=E9=99=90=E6=97=B6=E5=85=A8=E5=BC=80=E7=9A=84UI?= =?UTF-8?q?=E6=94=B9=E5=8A=A8=20(#2854)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs index 41e1dce6..47465cd1 100644 --- a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs +++ b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs @@ -95,7 +95,7 @@ public class AutoDomainTask : ISoloTask this.clickanywheretocloseLocalizedString = stringLocalizer.WithCultureGet(cultureInfo, "点击任意位置关闭"); this.matchingChallengeString = stringLocalizer.WithCultureGet(cultureInfo, "匹配挑战"); this.rapidformationString = stringLocalizer.WithCultureGet(cultureInfo, "快速编队"); - this.limitedFullyString = stringLocalizer.WithCultureGet(cultureInfo, "限时全开"); + this.limitedFullyString = stringLocalizer.WithCultureGet(cultureInfo, "限时全部开放"); this.limitedFullyAllString = stringLocalizer.WithCultureGet(cultureInfo, "限时开放"); } @@ -382,7 +382,7 @@ public class AutoDomainTask : ISoloTask using var limitedFullyStringRa = CaptureToRectArea(); var limitedFullyStringRaocrList = limitedFullyStringRa.FindMulti(RecognitionObject.Ocr(0, 0, limitedFullyStringRa.Width * 0.5, - limitedFullyStringRa.Height)); + limitedFullyStringRa.Height * 0.5)); var limitedFullyStringRaocrListdone = limitedFullyStringRaocrList.LastOrDefault(t => Regex.IsMatch(t.Text, this.limitedFullyString) || Regex.IsMatch(t.Text, this.limitedFullyAllString)); // 检测是否为限时全开秘境 From f1154e6ef7645c839d462c36da24918715edf61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 28 Feb 2026 00:36:10 +0800 Subject: [PATCH 020/107] =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E9=81=AE=E7=BD=A9?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=BC=82=E6=AD=A5=E4=BB=BB=E5=8A=A1=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E5=BD=B1=E5=93=8D=E8=87=AA=E5=8A=A8=E6=8B=BE=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BetterGenshinImpact.csproj | 2 +- .../GameTask/MapMask/MapMaskConfig.cs | 2 +- .../GameTask/MapMask/MapMaskTrigger.cs | 347 +++++++++++++++--- 3 files changed, 295 insertions(+), 56 deletions(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 03163fd8..f7736e44 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -44,7 +44,7 @@ - + diff --git a/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs b/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs index 4f9cc640..63979f47 100644 --- a/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs +++ b/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs @@ -14,7 +14,7 @@ public partial class MapMaskConfig : ObservableObject /// 是否启用 /// [ObservableProperty] - private bool _enabled = true; + private bool _enabled = false; /// /// 小地图遮罩是否启用 diff --git a/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs b/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs index 8ebc17e2..d24d5ed3 100644 --- a/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs +++ b/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using BetterGenshinImpact.Core.Recognition.OpenCv; using BetterGenshinImpact.GameTask.AutoPathing; using BetterGenshinImpact.GameTask.Common.BgiVision; @@ -6,6 +8,7 @@ using BetterGenshinImpact.GameTask.Common.Element.Assets; using BetterGenshinImpact.GameTask.Common.Map.Maps; using BetterGenshinImpact.GameTask.Common.Map.Maps.Base; using BetterGenshinImpact.GameTask.Common.Map.Maps.Layer; +using BetterGenshinImpact.GameTask.Model.Area; using BetterGenshinImpact.Helpers; using BetterGenshinImpact.View; using BetterGenshinImpact.ViewModel; @@ -41,11 +44,42 @@ public class MapMaskTrigger : ITaskTrigger private ISceneMap _teyvatMap => MapManager.GetMap(MapTypes.Teyvat, _mapMatchingMethod); private OpenCvSharp.Rect _prevRect = default; + private readonly object _prevRectLock = new(); private const int RectDebounceThreshold = 3; private readonly NavigationInstance _navigationInstance = new(); + private sealed class PendingUiUpdate + { + public bool? IsInBigMapUi { get; init; } + public Rect? BigMapViewport { get; init; } + public Rect? MiniMapViewport { get; init; } + } + + private PendingUiUpdate? _pendingUiUpdate; + private int _uiApplyScheduled; + + private sealed class ComputeWorkItem : IDisposable + { + public required string MapMatchingMethod { get; init; } + public Mat? Mat { get; set; } + + public void Dispose() + { + Mat?.Dispose(); + Mat = null; + } + } + + private ComputeWorkItem? _pendingBigMapCompute; + private int _bigMapWorkerRunning; + private ComputeWorkItem? _pendingMiniMapCompute; + private int _miniMapWorkerRunning; + + /// + /// 初始化触发器状态,并在关闭时同步隐藏遮罩UI + /// public void Init() { IsEnabled = _config.Enabled; @@ -53,7 +87,7 @@ public class MapMaskTrigger : ITaskTrigger // 关闭时隐藏UI if (!IsEnabled) { - UIDispatcherHelper.Invoke(() => + UIDispatcherHelper.BeginInvoke(() => { if (MaskWindow.InstanceNullable() != null) { @@ -66,6 +100,10 @@ public class MapMaskTrigger : ITaskTrigger } } + /// + /// 接收每帧截图内容并驱动大地图/小地图的异步定位与UI更新 + /// + /// 捕获到的画面内容 public void OnCapture(CaptureContent content) { if ((DateTime.Now - _prevExecute).TotalMilliseconds <= 50) @@ -79,13 +117,8 @@ public class MapMaskTrigger : ITaskTrigger { var region = content.CaptureRectArea; var inBigMapUi = content.CurrentGameUiCategory == GameUiCategory.BigMap || Bv.IsInBigMapUi(region); - UIDispatcherHelper.Invoke(() => - { - if (MaskWindow.Instance().DataContext is MaskWindowViewModel vm) - { - vm.IsInBigMapUi = inBigMapUi; - } - }); + var mapMatchingMethod = TaskContext.Instance().Config.PathingConditionConfig.MapMatchingMethod; + PendingUiUpdate? update = null; if (inBigMapUi) { @@ -104,33 +137,12 @@ public class MapMaskTrigger : ITaskTrigger if (_stableCount == 0) { - var rect256 = BigMapTeyvat256Layer.GetInstance((SceneBaseMap)_teyvatMap).GetBigMapRect(region.CacheGreyMat, _prevRect); - if (rect256 != default) + var greyMat = region.CacheGreyMat.Clone(); + EnqueueBigMapCompute(new ComputeWorkItem { - // 过大或过小的区域不处理 - if (rect256 is { Width: < 50, Height: < 40 } || rect256 is { Width: > 3000, Height: > 1800 }) - { - _prevRect = default; - return; - } - - - // if (_prevRect != default) - // { - // var dx = Math.Abs(rect256.X - _prevRect.X); - // var dy = Math.Abs(rect256.Y - _prevRect.Y); - // if (dx <= RectDebounceThreshold && dy <= RectDebounceThreshold) - // { - // return; - // } - // } - - _prevRect = rect256; - } - - const int s = TeyvatMap.BigMap256ScaleTo2048; // 相对2048做8倍缩放 - var rect2048 = new Rect(rect256.X * s, rect256.Y * s, rect256.Width * s, rect256.Height * s); - UIDispatcherHelper.Invoke(() => { MaskWindow.Instance().PointsCanvasControl.UpdateViewport(rect2048.X, rect2048.Y, rect2048.Width, rect2048.Height); }); + MapMatchingMethod = mapMatchingMethod, + Mat = greyMat + }); } } else @@ -140,24 +152,12 @@ public class MapMaskTrigger : ITaskTrigger { if (Bv.IsInMainUi(region)) { - var miniPoint = _navigationInstance.GetPositionStable(region, nameof(MapTypes.Teyvat), TaskContext.Instance().Config.PathingConditionConfig.MapMatchingMethod); - if (miniPoint != default) + var srcMat = region.SrcMat.Clone(); + EnqueueMiniMapCompute(new ComputeWorkItem { - // 展示窗口是 212 - double viewportSize = MapAssets.MimiMapRect1080P.Width / 3.0 * 10; - UIDispatcherHelper.Invoke(() => - { - MaskWindow.Instance().MiniMapPointsCanvasControl.UpdateViewport( - miniPoint.X - viewportSize / 2.0, - miniPoint.Y - viewportSize / 2.0, - viewportSize, - viewportSize); - }); - } - else - { - UIDispatcherHelper.Invoke(() => { MaskWindow.Instance().MiniMapPointsCanvasControl.UpdateViewport(0, 0, 0, 0); }); - } + MapMatchingMethod = mapMatchingMethod, + Mat = srcMat + }); // 自动记录路径 if (_config.PathAutoRecordEnabled) @@ -167,16 +167,255 @@ public class MapMaskTrigger : ITaskTrigger } else { - UIDispatcherHelper.Invoke(() => { MaskWindow.Instance().MiniMapPointsCanvasControl.UpdateViewport(0, 0, 0, 0); }); + update = new PendingUiUpdate { MiniMapViewport = new Rect(0, 0, 0, 0) }; } } - _prevRect = default; + lock (_prevRectLock) + { + _prevRect = default; + } } + + update = update == null + ? new PendingUiUpdate { IsInBigMapUi = inBigMapUi } + : new PendingUiUpdate + { + IsInBigMapUi = inBigMapUi, + BigMapViewport = update.BigMapViewport, + MiniMapViewport = update.MiniMapViewport + }; + + QueueUiUpdate(update); } catch (Exception e) { _logger.LogDebug(e, "实时地图定位时发生异常"); } } -} \ No newline at end of file + + /// + /// 入队大地图定位计算,仅保留正在执行与最新任务 + /// + /// 计算任务 + private void EnqueueBigMapCompute(ComputeWorkItem workItem) + { + var previous = Interlocked.Exchange(ref _pendingBigMapCompute, workItem); + previous?.Dispose(); + + if (Interlocked.Exchange(ref _bigMapWorkerRunning, 1) == 0) + { + _ = Task.Run(BigMapWorkerLoop); + } + } + + /// + /// 入队小地图定位计算,仅保留正在执行与最新任务 + /// + /// 计算任务 + private void EnqueueMiniMapCompute(ComputeWorkItem workItem) + { + var previous = Interlocked.Exchange(ref _pendingMiniMapCompute, workItem); + previous?.Dispose(); + + if (Interlocked.Exchange(ref _miniMapWorkerRunning, 1) == 0) + { + _ = Task.Run(MiniMapWorkerLoop); + } + } + + /// + /// 大地图计算工作线程循环 + /// + private void BigMapWorkerLoop() + { + while (true) + { + var workItem = Interlocked.Exchange(ref _pendingBigMapCompute, null); + if (workItem == null) + { + Interlocked.Exchange(ref _bigMapWorkerRunning, 0); + if (Volatile.Read(ref _pendingBigMapCompute) != null && Interlocked.Exchange(ref _bigMapWorkerRunning, 1) == 0) + { + continue; + } + + return; + } + + try + { + ProcessBigMapCompute(workItem); + } + catch (Exception e) + { + _logger.LogDebug(e, "地图遮罩异步计算时发生异常"); + } + finally + { + workItem.Dispose(); + } + } + } + + /// + /// 小地图计算工作线程循环 + /// + private void MiniMapWorkerLoop() + { + while (true) + { + var workItem = Interlocked.Exchange(ref _pendingMiniMapCompute, null); + if (workItem == null) + { + Interlocked.Exchange(ref _miniMapWorkerRunning, 0); + if (Volatile.Read(ref _pendingMiniMapCompute) != null && Interlocked.Exchange(ref _miniMapWorkerRunning, 1) == 0) + { + continue; + } + + return; + } + + try + { + ProcessMiniMapCompute(workItem); + } + catch (Exception e) + { + _logger.LogDebug(e, "地图遮罩异步计算时发生异常"); + } + finally + { + workItem.Dispose(); + } + } + } + + /// + /// 执行大地图定位计算并产出UI更新 + /// + /// 计算任务 + private void ProcessBigMapCompute(ComputeWorkItem workItem) + { + if (workItem.Mat == null) + { + return; + } + + OpenCvSharp.Rect prevRect; + lock (_prevRectLock) + { + prevRect = _prevRect; + } + + var sceneMap = (SceneBaseMap)MapManager.GetMap(MapTypes.Teyvat, workItem.MapMatchingMethod); + var rect256 = BigMapTeyvat256Layer.GetInstance(sceneMap).GetBigMapRect(workItem.Mat, prevRect); + if (rect256 != default) + { + if (rect256 is { Width: < 50, Height: < 40 } || rect256 is { Width: > 3000, Height: > 1800 }) + { + lock (_prevRectLock) + { + _prevRect = default; + } + return; + } + + lock (_prevRectLock) + { + _prevRect = rect256; + } + } + + const int s = TeyvatMap.BigMap256ScaleTo2048; + var rect2048 = new Rect(rect256.X * s, rect256.Y * s, rect256.Width * s, rect256.Height * s); + QueueUiUpdate(new PendingUiUpdate { BigMapViewport = rect2048 }); + } + + /// + /// 执行小地图定位计算并产出UI更新 + /// + /// 计算任务 + private void ProcessMiniMapCompute(ComputeWorkItem workItem) + { + if (workItem.Mat == null) + { + return; + } + + using var imageRegion = new ImageRegion(workItem.Mat, 0, 0); + workItem.Mat = null; + + var miniPoint = _navigationInstance.GetPositionStable(imageRegion, nameof(MapTypes.Teyvat), workItem.MapMatchingMethod); + if (miniPoint != default) + { + double viewportSize = MapAssets.MimiMapRect1080P.Width / 3.0 * 10; + QueueUiUpdate(new PendingUiUpdate + { + MiniMapViewport = new Rect( + miniPoint.X - viewportSize / 2.0, + miniPoint.Y - viewportSize / 2.0, + viewportSize, + viewportSize) + }); + } + else + { + QueueUiUpdate(new PendingUiUpdate { MiniMapViewport = new Rect(0, 0, 0, 0) }); + } + } + + /// + /// 合并并异步投递UI更新 + /// + /// 待应用的UI更新 + private void QueueUiUpdate(PendingUiUpdate update) + { + Interlocked.Exchange(ref _pendingUiUpdate, update); + TryScheduleUiApply(); + } + + /// + /// 确保仅有一个UI更新调度在队列中 + /// + private void TryScheduleUiApply() + { + if (Interlocked.Exchange(ref _uiApplyScheduled, 1) == 0) + { + UIDispatcherHelper.BeginInvoke(ApplyPendingUiUpdate); + } + } + + /// + /// 在UI线程应用合并后的更新 + /// + private void ApplyPendingUiUpdate() + { + var update = Interlocked.Exchange(ref _pendingUiUpdate, null); + if (update != null) + { + var window = MaskWindow.Instance(); + if (update.IsInBigMapUi is { } isInBigMapUi && window.DataContext is MaskWindowViewModel vm) + { + vm.IsInBigMapUi = isInBigMapUi; + } + + if (update.BigMapViewport is { } bigMapViewport) + { + window.PointsCanvasControl.UpdateViewport(bigMapViewport.X, bigMapViewport.Y, bigMapViewport.Width, bigMapViewport.Height); + } + + if (update.MiniMapViewport is { } miniMapViewport) + { + window.MiniMapPointsCanvasControl.UpdateViewport(miniMapViewport.X, miniMapViewport.Y, miniMapViewport.Width, miniMapViewport.Height); + } + } + + Interlocked.Exchange(ref _uiApplyScheduled, 0); + if (Volatile.Read(ref _pendingUiUpdate) != null) + { + TryScheduleUiApply(); + } + } +} From 7e944e18fba1c97a14807be6ac88a93a05d49ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 28 Feb 2026 01:04:47 +0800 Subject: [PATCH 021/107] =?UTF-8?q?=E6=B3=95=E5=B0=94=E4=BC=BD=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoFight/Assets/combat_avatar.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/BetterGenshinImpact/GameTask/AutoFight/Assets/combat_avatar.json b/BetterGenshinImpact/GameTask/AutoFight/Assets/combat_avatar.json index a5c84a79..d8c8101a 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Assets/combat_avatar.json +++ b/BetterGenshinImpact/GameTask/AutoFight/Assets/combat_avatar.json @@ -2049,5 +2049,19 @@ "nameEn": "Illuga", "skillCD": 15, "weapon": "10" + }, + { + "alias": [ + "法尔伽", + "Varka", + "团长", + "法尔加" + ], + "burstCD": 16, + "id": "10000128", + "name": "法尔伽", + "nameEn": "Varka", + "skillCD": 16, + "weapon": "10" } ] \ No newline at end of file From dad2e8ef7dc591987a633579b03b6ae72d93b986 Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:14:21 +0000 Subject: [PATCH 022/107] Update version to 0.57.2-alpha.1 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index f7736e44..ea46a780 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.57.1-alpha.3 + 0.57.2-alpha.1 false WinExe net8.0-windows10.0.22621.0 From 23de87d1a671f3206cb06d69e9ed1f5301199568 Mon Sep 17 00:00:00 2001 From: ddaodan <40017293+ddaodan@users.noreply.github.com> Date: Sat, 28 Feb 2026 23:59:04 +0800 Subject: [PATCH 023/107] =?UTF-8?q?feat:=20=E4=B8=BA=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=9C=B0=E8=84=89=E8=8A=B1=E9=85=8D=E7=BD=AE=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=B8=87=E5=8F=B6=E4=B8=8E=E7=90=B4=E7=9A=84=E6=8B=BE=E5=8F=96?= =?UTF-8?q?=E9=80=89=E9=A1=B9=20(#2862)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoLeyLineOutcropFightConfig.cs | 4 + .../AutoLeyLineOutcropTask.cs | 198 +++++++++++++++++- .../View/Pages/TaskSettingsPage.xaml | 55 +++++ 3 files changed, 248 insertions(+), 9 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs index 36cb4478..decf6025 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropFightConfig.cs @@ -45,6 +45,8 @@ public partial class AutoLeyLineOutcropFightConfig : ObservableObject [ObservableProperty] private bool _guardianAvatarHold = false; [ObservableProperty] private bool _burstEnabled = false; [ObservableProperty] private bool _swimmingEnabled = false; + [ObservableProperty] private bool _kazuhaPickupEnabled = true; + [ObservableProperty] private bool _qinDoublePickUp = false; [ObservableProperty] private int _timeout = 120; public void CopyFromAutoFightConfig(AutoFightConfig source) @@ -58,6 +60,8 @@ public partial class AutoLeyLineOutcropFightConfig : ObservableObject GuardianAvatarHold = source.GuardianAvatarHold; BurstEnabled = source.BurstEnabled; SwimmingEnabled = source.SwimmingEnabled; + KazuhaPickupEnabled = source.KazuhaPickupEnabled; + QinDoublePickUp = source.QinDoublePickUp; Timeout = source.Timeout; FinishDetectConfig.BattleEndProgressBarColor = source.FinishDetectConfig.BattleEndProgressBarColor; diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs index ab73f075..fc6dfa6d 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs @@ -6,9 +6,13 @@ using BetterGenshinImpact.Core.Script; using BetterGenshinImpact.Core.Simulator; using BetterGenshinImpact.Core.Simulator.Extensions; using BetterGenshinImpact.GameTask.AutoPathing; +using BetterGenshinImpact.GameTask.AutoPathing.Handler; using BetterGenshinImpact.GameTask.AutoPathing.Model; using BetterGenshinImpact.GameTask.AutoTrackPath; using BetterGenshinImpact.GameTask.AutoFight; +using BetterGenshinImpact.GameTask.AutoPick.Assets; +using BetterGenshinImpact.GameTask.AutoFight.Model; +using BetterGenshinImpact.GameTask.AutoFight.Script; using BetterGenshinImpact.GameTask.Common; using BetterGenshinImpact.GameTask.Common.BgiVision; using BetterGenshinImpact.GameTask.Common.Job; @@ -76,6 +80,7 @@ public class AutoLeyLineOutcropTask : ISoloTask private const string OcrFightOverlayKey = "AutoLeyLineOutcrop.OcrFight"; private const int OcrOverlayRenderLeadMs = 300; private static readonly System.Drawing.Pen OcrOverlayPen = new(System.Drawing.Color.Lime, 2); + private static readonly object PickLock = new(); private bool _overlayDisplayTemporarilyEnabled; private bool _overlayDisplayOriginalValue; private DateTime _lastMaskBringTopTime = DateTime.MinValue; @@ -668,8 +673,10 @@ public class AutoLeyLineOutcropTask : ISoloTask } var lastRoute = path.Routes.Last(); - var targetRoute = lastRoute.Replace("Assets/pathing/", "Assets/pathing/target/").Replace("-rerun", ""); - await ProcessLeyLineOutcrop(_taskParam.FightConfig.Timeout, targetRoute); + var targetRoute = BuildTargetRoute(lastRoute); + var rerunRoute = BuildRerunRoute(lastRoute); + var fromTeleportStart = "teleport".Equals(path.StartNode.Type, StringComparison.OrdinalIgnoreCase); + await ProcessLeyLineOutcrop(_taskParam.FightConfig.Timeout, targetRoute, rerunRoute, fromTeleportStart); var rewardSuccess = await AttemptReward(); if (!rewardSuccess) @@ -680,6 +687,35 @@ public class AutoLeyLineOutcropTask : ISoloTask _consecutiveFailureCount = 0; } + private static string BuildTargetRoute(string routePath) + { + return routePath + .Replace("assets/pathing/", "assets/pathing/target/", StringComparison.OrdinalIgnoreCase) + .Replace("-rerun", "", StringComparison.OrdinalIgnoreCase); + } + + private static string BuildRerunRoute(string routePath) + { + var rerunRoute = routePath + .Replace("assets/pathing/target/", "assets/pathing/rerun/", StringComparison.OrdinalIgnoreCase) + .Replace("assets/pathing/", "assets/pathing/rerun/", StringComparison.OrdinalIgnoreCase); + + if (!rerunRoute.Contains("-rerun", StringComparison.OrdinalIgnoreCase)) + { + rerunRoute = rerunRoute.Replace(".json", "-rerun.json", StringComparison.OrdinalIgnoreCase); + } + + return rerunRoute; + } + + private bool PathingFileExists(string routePath) + { + var workDir = Global.Absolute(@"GameTask\AutoLeyLineOutcrop"); + var localPath = routePath.Replace("/", Path.DirectorySeparatorChar.ToString()); + var fullPath = Path.Combine(workDir, localPath); + return File.Exists(fullPath); + } + private async Task RunPathingFile(string routePath) { await _returnMainUiTask.Start(_ct); @@ -841,7 +877,7 @@ public class AutoLeyLineOutcropTask : ISoloTask } } - private async Task ProcessLeyLineOutcrop(int timeoutSeconds, string targetPath, int retries = 0) + private async Task ProcessLeyLineOutcrop(int timeoutSeconds, string targetPath, string rerunPath, bool fromTeleportStart, int retries = 0) { const int maxRetries = 3; if (retries >= maxRetries) @@ -880,9 +916,20 @@ public class AutoLeyLineOutcropTask : ISoloTask } else if (!ContainsFightText(result1Text)) { - _logger.LogDebug("未识别到战斗提示,执行路径: {Path}", targetPath); - await RunPathingFile(targetPath); - return await ProcessLeyLineOutcrop(timeoutSeconds, targetPath, retries + 1); + var recoverPath = retries == 0 + ? targetPath + : fromTeleportStart + ? targetPath + : rerunPath; + if (!PathingFileExists(recoverPath)) + { + _logger.LogWarning("纠偏路径不存在,回退target路径: {Path}", recoverPath); + recoverPath = targetPath; + } + + _logger.LogDebug("未识别到战斗提示,执行纠偏路径: {Path}", recoverPath); + await RunPathingFile(recoverPath); + return await ProcessLeyLineOutcrop(timeoutSeconds, targetPath, rerunPath, fromTeleportStart, retries + 1); } var fightResult = await AutoFight(timeoutSeconds); @@ -891,17 +938,148 @@ public class AutoLeyLineOutcropTask : ISoloTask await EnsureExitRewardPage(); if (await ProcessResurrect()) { - return await ProcessLeyLineOutcrop(timeoutSeconds, targetPath, retries + 1); + return await ProcessLeyLineOutcrop(timeoutSeconds, targetPath, rerunPath, fromTeleportStart, retries + 1); } throw new Exception("战斗失败"); } + await TryCollectDropsAfterFight(); await SwitchToFriendshipTeamIfNeeded(); await AutoNavigateToReward(); return true; } + private async Task TryCollectDropsAfterFight() + { + if (!_taskParam.FightConfig.KazuhaPickupEnabled) + { + return; + } + + try + { + var combatScenes = await RunnerContext.Instance.GetCombatScenes(_ct); + if (combatScenes == null) + { + _logger.LogWarning("战后聚集拾取:队伍识别失败,跳过"); + return; + } + + var kazuha = combatScenes.SelectAvatar("枫原万叶"); + if (kazuha != null) + { + await TryKazuhaCollect(kazuha); + return; + } + + var qin = combatScenes.SelectAvatar("琴"); + if (qin != null) + { + await TryQinCollect(combatScenes, qin); + return; + } + + _logger.LogDebug("战后聚集拾取:当前队伍无万叶/琴,跳过"); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "战后聚集拾取异常"); + } + finally + { + Simulation.ReleaseAllKey(); + } + } + + private async Task TryKazuhaCollect(Avatar kazuha) + { + _logger.LogInformation("战后聚集拾取:开始使用枫原万叶执行长E拾取"); + await Delay(200, _ct); + if (!kazuha.TrySwitch(10)) + { + _logger.LogWarning("战后聚集拾取:切换到万叶失败,跳过"); + return; + } + + _logger.LogInformation("战后聚集拾取:万叶已切换,等待元素战技CD"); + await kazuha.WaitSkillCd(_ct); + kazuha.UseSkill(true); + await Delay(50, _ct); + Simulation.SendInput.SimulateAction(GIActions.NormalAttack); + await Delay(1500, _ct); + kazuha.AfterUseSkill(); + _logger.LogInformation("战后聚集拾取:万叶长E聚集动作执行完成"); + } + + private async Task TryQinCollect(CombatScenes combatScenes, Avatar qin) + { + _logger.LogInformation("战后聚集拾取:使用琴-长E拾取掉落物"); + var find = _taskParam.FightConfig.QinDoublePickUp; + await Delay(150, _ct); + if (qin.TrySwitch(10)) + { + var actionsToUse = PickUpCollectHandler.PickUpActions + .Where(action => action.StartsWith("琴-长E ", StringComparison.OrdinalIgnoreCase)) + .Select(action => action.Replace("琴-长E", "琴", StringComparison.OrdinalIgnoreCase)) + .ToArray(); + foreach (var actionStr in actionsToUse) + { + var pickUpAction = CombatScriptParser.ParseContext(actionStr); + for (var i = 0; i < 2; i++) + { + await qin.WaitSkillCd(_ct); + foreach (var command in pickUpAction.CombatCommands) + { + command.Execute(combatScenes); + Task.Run(() => + { + if (Monitor.TryEnter(PickLock)) + { + try + { + if (find) + { + using var imagePick = CaptureToRectArea(); + if (imagePick.Find(AutoPickAssets.Instance.PickRo).IsExist()) + { + find = false; + } + } + } + finally + { + Monitor.Exit(PickLock); + } + } + }); + } + + if (!find) + { + break; + } + + if (i == 0) + { + _logger.LogInformation("战后聚集拾取:尝试再次执行琴-长E拾取"); + qin.AfterUseSkill(); + } + else + { + break; + } + } + + Simulation.ReleaseAllKey(); + } + } + else + { + _logger.LogWarning("战后聚集拾取:切换到琴失败,跳过"); + } + } + private Region FindSafe(ImageRegion capture, RecognitionObject ro) { var roi = ro.RegionOfInterest; @@ -1370,7 +1548,7 @@ public class AutoLeyLineOutcropTask : ISoloTask var drawContent = VisionContext.Instance().DrawContent; drawContent.PutOrRemoveRectList(key, drawList.Count > 0 ? drawList : null); RefreshMaskWindowForOverlay(); - return new OcrOverlayScope(drawContent, key); + return new OcrOverlayScope(drawContent, key, RefreshMaskWindowForOverlay); } private void ClearOcrOverlayKeys() @@ -1380,6 +1558,7 @@ public class AutoLeyLineOutcropTask : ISoloTask drawContent.RemoveRect(OcrFightOverlayKey); drawContent.PutOrRemoveTextList(OcrFlowOverlayKey, null); drawContent.PutOrRemoveTextList(OcrFightOverlayKey, null); + RefreshMaskWindowForOverlay(); } private async Task WaitOcrOverlayRenderTick() @@ -2145,7 +2324,7 @@ public class AutoLeyLineOutcropTask : ISoloTask public int FragileResinTimes { get; set; } } - private sealed class OcrOverlayScope(DrawContent drawContent, string key) : IDisposable + private sealed class OcrOverlayScope(DrawContent drawContent, string key, Action refreshAction) : IDisposable { private bool _disposed; @@ -2159,6 +2338,7 @@ public class AutoLeyLineOutcropTask : ISoloTask _disposed = true; drawContent.RemoveRect(key); drawContent.PutOrRemoveTextList(key, null); + refreshAction(); } } diff --git a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml index 54318dc2..4a9a0291 100644 --- a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml @@ -2553,6 +2553,61 @@ Text="{Binding Config.AutoLeyLineOutcropConfig.FightConfig.ActionSchedulerByCd, Mode=TwoWay}" TextWrapping="Wrap" /> + + + + + + + + + + + + + + + + + + + + From e69283c3f20ff13968d0759548ec98f8f0c92f89 Mon Sep 17 00:00:00 2001 From: DarkFlameMaster <1004452714@qq.com> Date: Sat, 28 Feb 2026 23:59:29 +0800 Subject: [PATCH 024/107] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=92=99=E5=BE=B7?= =?UTF-8?q?=E5=87=AF=E7=91=9F=E7=90=B3=E5=9C=B0=E5=9B=BE=E8=BF=BD=E8=B8=AA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=20(#2861)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/Element/Assets/Json/冒险家协会_蒙德.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/Json/冒险家协会_蒙德.json b/BetterGenshinImpact/GameTask/Common/Element/Assets/Json/冒险家协会_蒙德.json index 97759bfb..fbeae4ca 100644 --- a/BetterGenshinImpact/GameTask/Common/Element/Assets/Json/冒险家协会_蒙德.json +++ b/BetterGenshinImpact/GameTask/Common/Element/Assets/Json/冒险家协会_蒙德.json @@ -34,11 +34,11 @@ }, { "id": 4, - "x": -913.5625, - "y": 2233.5625, + "x": -913.51, + "y": 2232.67, "action": "", "move_mode": "walk", - "type": "path" + "type": "target" } ] -} \ No newline at end of file +} From e3c451cd926ba2234ed30d0df0140ccc90a18095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 1 Mar 2026 01:59:52 +0800 Subject: [PATCH 025/107] =?UTF-8?q?=E4=B8=8D=E5=86=8D=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/App.xaml.cs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/BetterGenshinImpact/App.xaml.cs b/BetterGenshinImpact/App.xaml.cs index 92bdc3f2..a327e0d2 100644 --- a/BetterGenshinImpact/App.xaml.cs +++ b/BetterGenshinImpact/App.xaml.cs @@ -89,21 +89,22 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); - if ("zh-Hans".Equals(all.OtherConfig.UiCultureInfoName, StringComparison.OrdinalIgnoreCase)) - { - services.AddLogging(c => c.AddSerilog()); - } - else - { - services.AddLogging(logging => - { - logging.ClearProviders(); - logging.SetMinimumLevel(LogLevel.Debug); - logging.AddFilter("Microsoft", LogLevel.Warning); - logging.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.Warning); - logging.Services.AddSingleton(); - }); - } + services.AddLogging(c => c.AddSerilog()); + // if ("zh-Hans".Equals(all.OtherConfig.UiCultureInfoName, StringComparison.OrdinalIgnoreCase)) + // { + // services.AddLogging(c => c.AddSerilog()); + // } + // else + // { + // services.AddLogging(logging => + // { + // logging.ClearProviders(); + // logging.SetMinimumLevel(LogLevel.Debug); + // logging.AddFilter("Microsoft", LogLevel.Warning); + // logging.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.Warning); + // logging.Services.AddSingleton(); + // }); + // } services.AddLocalization(); From ea6a9a4af2ac310973fbbc19d1200d0910696ecc Mon Sep 17 00:00:00 2001 From: Jamis Date: Mon, 2 Mar 2026 01:10:14 +1100 Subject: [PATCH 026/107] More granular control over pre-teleport delay (#2866) --- .../GameTask/AutoPathing/PathExecutor.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs b/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs index af480387..1d24b318 100644 --- a/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs +++ b/BetterGenshinImpact/GameTask/AutoPathing/PathExecutor.cs @@ -188,7 +188,19 @@ public class PathExecutor { if (CurWaypoints.Item1 > 0) { - await Delay(1000, ct); + var prevWaypoints = waypointsList[CurWaypoints.Item1 - 1]; + var prevWaypoint = prevWaypoints[prevWaypoints.Count - 1]; + if (prevWaypoint.Type == WaypointType.Teleport.Code + || prevWaypoint.Action == ActionEnum.Fight.Code + || prevWaypoint.Action == ActionEnum.NahidaCollect.Code + || prevWaypoint.Action == ActionEnum.PickAround.Code) + { + // No delay + } + else + { + await Delay(1000, ct); + } } await HandleTeleportWaypoint(waypoint); } From b49910873e9dd15bcf451e84890b9fea55e3c643 Mon Sep 17 00:00:00 2001 From: mno <718135749@qq.com> Date: Sun, 1 Mar 2026 22:10:33 +0800 Subject: [PATCH 027/107] =?UTF-8?q?=E6=B7=BB=E5=8A=A0check=E5=8A=A8?= =?UTF-8?q?=E4=BD=9C=20(#2864)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs | 9 ++++++++- .../GameTask/AutoFight/Script/CombatCommand.cs | 4 ++++ BetterGenshinImpact/GameTask/AutoFight/Script/Method.cs | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs b/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs index a6ffa6d6..e2da60fc 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs @@ -418,7 +418,14 @@ public class AutoFightTask : ISoloTask fightEndFlag = await CheckFightFinish(delayTime, detectDelayTime); } #endregion - + + #region check动作触发战斗结束检测 + if (command.Method == Method.Check) + { + fightEndFlag = await CheckFightFinish(delayTime, detectDelayTime); + } + #endregion + command.Execute(combatScenes, lastCommand); //统计战斗人次 if (i == combatCommands.Count - 1 || command.Name != combatCommands[i + 1].Name) diff --git a/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs b/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs index 7ca24110..9b452d9d 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs @@ -202,6 +202,10 @@ public class CombatCommand { avatar.Ready(); } + else if (Method == Method.Check) + { + // check动作在AutoFightTask主循环中处理,此处不做任何操作 + } else if (Method == Method.Aim) { throw new NotImplementedException(); diff --git a/BetterGenshinImpact/GameTask/AutoFight/Script/Method.cs b/BetterGenshinImpact/GameTask/AutoFight/Script/Method.cs index fbcb2fa0..74c1d868 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Script/Method.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Script/Method.cs @@ -13,6 +13,7 @@ public class Method public static readonly Method Charge = new(["charge", "重击"]); public static readonly Method Wait = new(["wait", "after", "等待"]); public static readonly Method Ready = new(["ready", "完成"]); + public static readonly Method Check = new(["check", "检测"]); public static readonly Method Walk = new(["walk", "行走"]); public static readonly Method W = new(["w"]); @@ -45,6 +46,7 @@ public class Method yield return Charge; yield return Wait; yield return Ready; + yield return Check; yield return Walk; yield return W; From c3b8c9e78fdd1dbc45b136bb8487d39eb742b3be Mon Sep 17 00:00:00 2001 From: ShadowLemoon <119576779+ShadowLemoon@users.noreply.github.com> Date: Thu, 5 Mar 2026 01:12:44 +0800 Subject: [PATCH 028/107] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=B9=BD=E5=A2=83?= =?UTF-8?q?=E5=8D=B1=E6=88=98=E8=B0=83=E8=AF=95=E6=97=A5=E5=BF=97=20(#2881?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoStygianOnslaughtTask.cs | 46 ++++--------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs index a3482931..c740de29 100644 --- a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs +++ b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs @@ -252,14 +252,14 @@ public class AutoStygianOnslaughtTask : StateMachineBase, { return ra.Find(ElementAssets.Instance.BtnWhiteCancel).IsExist() && ra.FindMulti(RecognitionObject.Ocr(ra.Width * 0.35, ra.Height * 0.7, ra.Width * 0.3, ra.Height * 0.2)) - .Any(o => o.Text.Contains("返回")); + .Any(o => o.Text.Contains("返回")); } private bool DetectBattleResultLose(ImageRegion ra) { return ra.Find(ElementAssets.Instance.BtnWhiteConfirm).IsExist() && ra.FindMulti(RecognitionObject.Ocr(ra.Width * 0.2, ra.Height * 0.3, ra.Width * 0.6, ra.Height * 0.3)) - .Any(o => o.Text.Contains("挑战失败") || o.Text.Contains("重新挑战")); + .Any(o => o.Text.Contains("挑战失败") || o.Text.Contains("重新挑战")); } // ========== 第三优先级:OCR 检测 ========== @@ -275,13 +275,6 @@ public class AutoStygianOnslaughtTask : StateMachineBase, { var ocrResult = ra.FindMulti(RecognitionObject.Ocr(ra.Width * 0.2, ra.Height * 0.2, ra.Width * 0.6, ra.Height * 0.6)); var found = ocrResult.Any(t => t.Text.Contains("地脉之花")); - - // 调试日志 - var texts = ocrResult.Any() - ? string.Join(", ", ocrResult.Select(o => $"'{o.Text}'")) - : "(无结果)"; - Logger.LogInformation($"DetectLeylineFlowerPrompt: OCR结果=[{texts}], 地脉之花={found}"); - return found; } @@ -293,29 +286,14 @@ public class AutoStygianOnslaughtTask : StateMachineBase, var hasPreview = ocrResult.Any(o => o.Text.Contains("角色预览")); var hasStart = ocrResult.Any(o => o.Text.Contains("开始挑战")); var found = hasPreview && hasStart; - - // 调试日志 - var texts = ocrResult.Any() - ? string.Join(", ", ocrResult.Select(o => $"'{o.Text}'")) - : "(无结果)"; - Logger.LogInformation($"DetectBossSelect: 右侧OCR结果=[{texts}], 角色预览={hasPreview}, 开始挑战={hasStart}"); - return found; } private bool DetectDifficultySelect(ImageRegion ra) { // "单人挑战" 在右下角 - var ocrResult = ra.FindMulti(RecognitionObject.Ocr(ra.Width * 0.5, ra.Height * 0.7, ra.Width * 0.5, ra.Height * 0.3)); - var found = ocrResult.Any(o => o.Text.Contains("单人挑战")); - - // 调试日志 - var texts = ocrResult.Any() - ? string.Join(", ", ocrResult.Select(o => $"'{o.Text}'")) - : "(无结果)"; - Logger.LogInformation($"DetectDifficultySelect: 右下角OCR结果=[{texts}], 包含单人挑战={found}"); - - return found; + return ra.FindMulti(RecognitionObject.Ocr(ra.Width * 0.5, ra.Height * 0.7, ra.Width * 0.5, ra.Height * 0.3)) + .Any(o => o.Text.Contains("单人挑战")); } private bool DetectDomainEntrance(ImageRegion ra) @@ -323,16 +301,8 @@ public class AutoStygianOnslaughtTask : StateMachineBase, // 秘境入口特征:屏幕右侧有"幽境危战"四个字 // 坐标:左上角(1223, 510), 右下角(1376, 566) // 宽度=153, 高度=56 - var ocrResult = ra.FindMulti(RecognitionObject.Ocr(1223, 510, 153, 56)); - var found = ocrResult.Any(o => o.Text.Contains("幽境危战")); - - // 始终输出日志,帮助调试 - var texts = ocrResult.Any() - ? string.Join(", ", ocrResult.Select(o => $"'{o.Text}'")) - : "(无结果)"; - Logger.LogInformation($"DetectDomainEntrance: 区域(1223,510,153,56) OCR结果=[{texts}], 包含幽境危战={found}"); - - return found; + return ra.FindMulti(RecognitionObject.Ocr(1223, 510, 153, 56)) + .Any(o => o.Text.Contains("幽境危战")); } private bool DetectEventMenu(ImageRegion ra) @@ -340,13 +310,13 @@ public class AutoStygianOnslaughtTask : StateMachineBase, // 活动一览位置:左上角(125, 142), 右下角(238, 170) // OCR 参数:(x, y, width, height) return ra.FindMulti(RecognitionObject.Ocr(125, 142, 238 - 125, 170 - 142)) - .Any(o => o.Text.Contains("活动一览")); + .Any(o => o.Text.Contains("活动一览")); } private bool DetectStygianOnslaughtPage(ImageRegion ra) { return ra.FindMulti(RecognitionObject.Ocr(ra.Width * 0.55, ra.Height * 0.3, ra.Width * 0.4, ra.Height * 0.6)) - .Any(o => o.Text.Contains("前往挑战")); + .Any(o => o.Text.Contains("前往挑战")); } #endregion From 8b7d2353e5a0b40ce263ddfe1a3cc217c2adc705 Mon Sep 17 00:00:00 2001 From: DarkFlameMaster <1004452714@qq.com> Date: Thu, 5 Mar 2026 01:13:52 +0800 Subject: [PATCH 029/107] =?UTF-8?q?feat(js):=E5=9C=B0=E5=9B=BE=E8=BF=BD?= =?UTF-8?q?=E8=B8=AA=E5=A2=9E=E5=8A=A0=E8=AF=BB=E5=8F=96=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E4=B8=8B=E7=9A=84=E5=86=85=E5=AE=B9=E7=AD=89=E6=96=B9=E6=B3=95?= =?UTF-8?q?=20(#2875)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(js):地图追踪增加读取目录下的内容等方法 * refactor(AutoPathingScript): 重构文件操作方法到LimitedFile类 * fix * docs(AutoPathingScript): 修正方法参数注释中的默认值描述 * 避免重复初始化实例,给恢复游戏窗口焦点的日志也加上当前窗口名 --- .../Script/Dependence/AutoPathingScript.cs | 39 +++++++++++++++++- .../Core/Script/Dependence/LimitedFile.cs | 40 +++++++++++++++++++ .../GameTask/Common/TaskControl.cs | 3 +- 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/BetterGenshinImpact/Core/Script/Dependence/AutoPathingScript.cs b/BetterGenshinImpact/Core/Script/Dependence/AutoPathingScript.cs index 9dd15fde..ba5d76b3 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/AutoPathingScript.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/AutoPathingScript.cs @@ -1,4 +1,4 @@ -using System; +using System; using BetterGenshinImpact.GameTask.AutoPathing; using BetterGenshinImpact.GameTask.AutoPathing.Model; using System.Threading.Tasks; @@ -12,11 +12,13 @@ public class AutoPathingScript { private object? _config = null; private string _rootPath; + private readonly LimitedFile _autoPathingFile; public AutoPathingScript(string rootPath, object? config) { _config = config; _rootPath = rootPath; + _autoPathingFile = new LimitedFile(Global.Absolute(@"User\AutoPathing")); } public async Task Run(string json) @@ -59,7 +61,40 @@ public class AutoPathingScript /// 在 `\User\AutoPathing` 目录下获取文件 public async Task RunFileFromUser(string path) { - var json = await new LimitedFile(Global.Absolute(@"User\AutoPathing")).ReadText(path); + var json = await AutoPathingFile.ReadText(path); await Run(json); } + + /// + /// 判断 AutoPathing 目录下的路径是否存在 + /// + /// 相对于 User\AutoPathing 的路径 + /// 存在返回 true,否则返回 false + public bool IsExists(string subPath) => AutoPathingFile.IsExists(subPath); + + /// + /// 判断 AutoPathing 目录下的路径是否为文件 + /// + /// 相对于 User\AutoPathing 的路径 + /// 是文件返回 true,否则返回 false + public bool IsFile(string subPath) => AutoPathingFile.IsFile(subPath); + + /// + /// 判断 AutoPathing 目录下的路径是否为文件夹 + /// + /// 相对于 User\AutoPathing 的路径 + /// 是文件夹返回 true,否则返回 false + public bool IsFolder(string subPath) => AutoPathingFile.IsFolder(subPath); + + /// + /// 读取 AutoPathing 目录下指定文件夹的内容(非递归方式) + /// + /// 相对于 User\AutoPathing 的子目录路径,默认为相对根目录 + /// 文件夹内所有文件和文件夹的相对路径数组,出错时返回空数组 + public string[] ReadPathSync(string subPath = "./") => AutoPathingFile.ReadPathSync(subPath); + + /// + /// LimitedFile 实例,用于操作 AutoPathing 目录 + /// + private LimitedFile AutoPathingFile => _autoPathingFile; } \ No newline at end of file diff --git a/BetterGenshinImpact/Core/Script/Dependence/LimitedFile.cs b/BetterGenshinImpact/Core/Script/Dependence/LimitedFile.cs index 7a17e681..4ce53172 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/LimitedFile.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/LimitedFile.cs @@ -1,5 +1,7 @@ using BetterGenshinImpact.Core.Script.Utils; +using BetterGenshinImpact.Core.Config; using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using OpenCvSharp; @@ -72,6 +74,44 @@ public class LimitedFile(string rootPath) } } + /// + /// 判断指定路径是否为文件 + /// + /// 文件路径(相对于根目录) + /// 如果是文件则返回 true,否则返回 false + public bool IsFile(string path) + { + try + { + string normalizedPath = NormalizePath(path); + return File.Exists(normalizedPath); + } + catch (Exception ex) + { + TaskControl.Logger.LogError("IsFile 异常: {Message}", ex.Message); + return false; + } + } + + /// + /// 判断指定的文件或目录是否存在 + /// + /// 文件或目录路径(相对于根目录) + /// 如果存在返回 true,否则返回 false + public bool IsExists(string path) + { + try + { + string normalizedPath = NormalizePath(path); + return File.Exists(normalizedPath) || Directory.Exists(normalizedPath); + } + catch (Exception ex) + { + TaskControl.Logger.LogError("IsExists 异常: {Message}", ex.Message); + return false; + } + } + /// /// Normalize and validate a path. /// diff --git a/BetterGenshinImpact/GameTask/Common/TaskControl.cs b/BetterGenshinImpact/GameTask/Common/TaskControl.cs index 98bcef59..f582bce0 100644 --- a/BetterGenshinImpact/GameTask/Common/TaskControl.cs +++ b/BetterGenshinImpact/GameTask/Common/TaskControl.cs @@ -116,7 +116,8 @@ public class TaskControl } else { - Logger.LogInformation("当前获取焦点的窗口不是原神,尝试恢复窗口"); + var name = SystemControl.GetActiveByProcess(); + Logger.LogInformation("当前获取焦点的窗口为: {Name},不是原神,尝试恢复窗口", name); SystemControl.FocusWindow(TaskContext.Instance().GameHandle); } From a5829ed6bb960b0c5a675a8fe3186974ee67457e Mon Sep 17 00:00:00 2001 From: guamian <92618050+guamian1337@users.noreply.github.com> Date: Fri, 6 Mar 2026 23:06:40 +0800 Subject: [PATCH 030/107] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=B8=B8=E6=B3=B3=E6=A3=80=E6=B5=8B=E7=82=B8=E5=86=85=E5=AD=98?= =?UTF-8?q?=20(#2883)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs index 15536f45..c6c0f710 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs @@ -175,8 +175,9 @@ public class Avatar /// private static bool SwimmingConfirm(Region region) { - using var mask = OpenCvCommonHelper.Threshold(region.ToImageRegion().DeriveCrop(1819, 1025, 9, 11).SrcMat, - new Scalar(242, 223, 39),new Scalar(255, 233, 44)); + using var imageRegion = region.ToImageRegion(); + using var cropped = imageRegion.DeriveCrop(1819, 1025, 9, 11); + using var mask = OpenCvCommonHelper.Threshold(cropped.SrcMat, new Scalar(242, 223, 39), new Scalar(255, 233, 44)); using var labels = new Mat(); using var stats = new Mat(); using var centroids = new Mat(); From b9e1c37b67bc28c3a2f956b235735998803cb362 Mon Sep 17 00:00:00 2001 From: DarkFlameMaster <1004452714@qq.com> Date: Sun, 8 Mar 2026 18:01:11 +0800 Subject: [PATCH 031/107] =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=B8=80=E6=9D=A1?= =?UTF-8?q?=E9=BE=99=E9=85=8D=E7=BD=AE=E7=9A=84=E5=BC=B9=E7=AA=97=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E4=B8=8E=E4=B8=BB=E7=AA=97=E5=8F=A3=E4=BF=9D=E6=8C=81?= =?UTF-8?q?=E4=B8=80=E8=87=B4=20(#2887)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/Pages/OneDragonFlowViewModel.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs index 77356dd4..142c81df 100644 --- a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs @@ -741,7 +741,7 @@ public partial class OneDragonFlowViewModel : ViewModel } [RelayCommand] - private void DeleteConfig() + private async Task DeleteConfig() { if (SelectedConfig == null) { @@ -749,7 +749,14 @@ public partial class OneDragonFlowViewModel : ViewModel return; } - var result = System.Windows.MessageBox.Show($"确定要删除配置「{SelectedConfig.Name}」吗?", "删除配置", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question); + var displayName = SelectedConfig.Name.Length > 14 + ? $"{SelectedConfig.Name[..4]}...{SelectedConfig.Name[^4..]}" + : SelectedConfig.Name; + var result = await ThemedMessageBox.ShowAsync( + $"确定要删除配置「{displayName}」吗?", + "删除配置", + System.Windows.MessageBoxButton.YesNo, + ThemedMessageBox.MessageBoxIcon.Question); if (result != System.Windows.MessageBoxResult.Yes) { return; @@ -789,6 +796,8 @@ public partial class OneDragonFlowViewModel : ViewModel // 刷新任务列表 LoadDisplayTaskListFromConfig(); + SelectedTask = null!; + InputScriptGroupName = string.Empty; // 保存配置 SaveConfig(); From 65b5032e6ed4abd8cd562ade3f63cf4d57ccff45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=91=E7=AB=AF=E5=AE=A2?= <107686912+Kirito520Asuna@users.noreply.github.com> Date: Sun, 8 Mar 2026 21:13:18 +0800 Subject: [PATCH 032/107] =?UTF-8?q?[=E5=BC=80=E6=94=BEJS=20=E8=B0=83?= =?UTF-8?q?=E7=94=A8=20API]=E8=87=AA=E5=8A=A8=E5=B9=BD=E5=A2=83=E5=8D=B1?= =?UTF-8?q?=E6=88=98=20(#2882)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Script/Dependence/Dispatcher.cs | 19 ++++ .../Core/Script/EngineExtend.cs | 2 + .../AutoDomain/Model/ResinUseRecord.cs | 2 +- .../AutoStygianOnslaughtParam.cs | 87 +++++++++++++++++++ .../AutoStygianOnslaughtTask.cs | 13 ++- .../Model/OneDragonTaskItem.cs | 4 +- .../Pages/TaskSettingsPageViewModel.cs | 4 +- 7 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtParam.cs diff --git a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs index 408db45c..5c8f1070 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs @@ -20,6 +20,7 @@ using System.Threading; using System.Threading.Tasks; using BetterGenshinImpact.GameTask.AutoFight; using BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; +using BetterGenshinImpact.GameTask.AutoStygianOnslaught; namespace BetterGenshinImpact.Core.Script.Dependence; @@ -357,4 +358,22 @@ public class Dispatcher CancellationToken cancellationToken = customCt ?? CancellationContext.Instance.Cts.Token; await new AutoLeyLineOutcropTask(param).Start(cancellationToken); } + + + /// + /// 运行自动幽境危战任务 + /// + /// 自动幽境危战任务参数 + /// 自定义取消令牌 + /// + public async Task RunAutoStygianOnslaughtTask(AutoStygianOnslaughtParam param, CancellationToken? customCt = null) + { + if (param == null) + { + throw new ArgumentNullException(nameof(param), "自动幽境危战任务参数不能为空"); + } + + CancellationToken cancellationToken = customCt ?? CancellationContext.Instance.Cts.Token; + await new AutoStygianOnslaughtTask(param).Start(cancellationToken); + } } diff --git a/BetterGenshinImpact/Core/Script/EngineExtend.cs b/BetterGenshinImpact/Core/Script/EngineExtend.cs index 982b70e2..176f6737 100644 --- a/BetterGenshinImpact/Core/Script/EngineExtend.cs +++ b/BetterGenshinImpact/Core/Script/EngineExtend.cs @@ -15,6 +15,7 @@ using BetterGenshinImpact.GameTask.AutoFight; using BetterGenshinImpact.GameTask.AutoFight.Model; using BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; using BetterGenshinImpact.GameTask.AutoSkip; +using BetterGenshinImpact.GameTask.AutoStygianOnslaught; namespace BetterGenshinImpact.Core.Script; @@ -75,6 +76,7 @@ public class EngineExtend engine.AddHostType("AutoDomainParam", typeof(AutoDomainParam)); engine.AddHostType("AutoFightParam", typeof(AutoFightParam)); engine.AddHostType("AutoLeyLineOutcropParam", typeof(AutoLeyLineOutcropParam)); + engine.AddHostType("AutoStygianOnslaughtParam", typeof(AutoStygianOnslaughtParam)); //鼠标回调 engine.AddHostType("KeyMouseHook", typeof(KeyMouseHook)); // 添加C#的类型 diff --git a/BetterGenshinImpact/GameTask/AutoDomain/Model/ResinUseRecord.cs b/BetterGenshinImpact/GameTask/AutoDomain/Model/ResinUseRecord.cs index 0edd13b0..b00c489a 100644 --- a/BetterGenshinImpact/GameTask/AutoDomain/Model/ResinUseRecord.cs +++ b/BetterGenshinImpact/GameTask/AutoDomain/Model/ResinUseRecord.cs @@ -62,7 +62,7 @@ public class ResinUseRecord return list; } - public static List BuildFromDomainParam(AutoStygianOnslaughtConfig taskParam) + public static List BuildFromDomainParam(AutoStygianOnslaughtParam taskParam) { List list = []; if (taskParam.SpecifyResinUse) diff --git a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtParam.cs b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtParam.cs new file mode 100644 index 00000000..3c54d42e --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtParam.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.GameTask.Model; + +namespace BetterGenshinImpact.GameTask.AutoStygianOnslaught; + +public class AutoStygianOnslaughtParam:BaseTaskParam +{ + + public int BossNum { get; set; } + // 结束后是否自动分解圣遗物 + public bool AutoArtifactSalvage { get; set; } + + // 指定树脂的使用次数 + public bool SpecifyResinUse{ get; set; } + + // 自定义使用树脂优先级 + public List ResinPriorityList{ get; set; }=["浓缩树脂","原粹树脂"]; + // 使用原粹树脂刷取副本次数 + public int OriginalResinUseCount { get; set; } + + //使用浓缩树脂刷取副本次数 + public int CondensedResinUseCount { get; set; } + + // 使用须臾树脂刷取副本次数 + public int TransientResinUseCount { get; set; } + + // 使用脆弱树脂刷取副本次数 + public int FragileResinUseCount { get; set; } + // 指定战斗队伍 + public string FightTeamName { get; set; } + // 战斗脚本包路径 + public string CombatScriptBagPath { get; set; } + public void SetDefault() + { + var config = TaskContext.Instance().Config.AutoStygianOnslaughtConfig; + SetAutoStygianOnslaughtConfig(config); + } + public void SetAutoStygianOnslaughtConfig(AutoStygianOnslaughtConfig config) + { + BossNum = config.BossNum; + AutoArtifactSalvage = config.AutoArtifactSalvage; + SpecifyResinUse = config.SpecifyResinUse; + ResinPriorityList = config.ResinPriorityList == null ? new List { "浓缩树脂", "原粹树脂" }: new List(config.ResinPriorityList); + OriginalResinUseCount = config.OriginalResinUseCount; + CondensedResinUseCount = config.CondensedResinUseCount; + TransientResinUseCount = config.TransientResinUseCount; + FragileResinUseCount = config.FragileResinUseCount; + FightTeamName = config.FightTeamName; + SetCombatStrategyPath(config.StrategyName); + } + public AutoStygianOnslaughtParam() : base(null, null) + { + SetDefault(); + } + public AutoStygianOnslaughtParam(string combatScriptBagPath) : base(null, null) + { + SetDefault(); + CombatScriptBagPath=combatScriptBagPath; + } + public void SetResinPriorityList(params string[] priorities) + { + ResinPriorityList.Clear(); + ResinPriorityList.AddRange(priorities); + } + + + /// + /// 设置战斗策略路径 + /// + /// 策略名称 + public void SetCombatStrategyPath(string? strategyName = null) + { + if (string.IsNullOrEmpty(strategyName)) + { + strategyName = TaskContext.Instance().Config.AutoFightConfig.StrategyName; + } + + if (string.IsNullOrWhiteSpace(strategyName) ||"根据队伍自动选择".Equals(strategyName)) + { + CombatScriptBagPath= Global.Absolute(@"User\AutoFight\"); + return; + } + + CombatScriptBagPath= Global.Absolute(@"User\AutoFight\" + strategyName + ".txt"); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs index c740de29..a8d6d1e3 100644 --- a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs +++ b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs @@ -79,12 +79,21 @@ public class AutoStygianOnslaughtTask : StateMachineBase, /// protected override ILogger Logger => TaskControl.Logger; - private readonly AutoStygianOnslaughtConfig _taskParam; + private readonly AutoStygianOnslaughtParam _taskParam; private readonly CombatScriptBag _combatScriptBag; private List _resinPriorityListWhenSpecifyUse; private LowerHeadThenWalkToTask? _lowerHeadThenWalkToTask; + public AutoStygianOnslaughtTask(AutoStygianOnslaughtParam taskParam) + { + AutoFightAssets.DestroyInstance(); + _taskParam = taskParam; + _combatScriptBag = CombatScriptParser.ReadAndParse(taskParam.CombatScriptBagPath); + _resinPriorityListWhenSpecifyUse = ResinUseRecord.BuildFromDomainParam(taskParam); - public AutoStygianOnslaughtTask(AutoStygianOnslaughtConfig taskParam, string path) + // 注册所有状态处理器 + RegisterAllStateHandlers(); + } + public AutoStygianOnslaughtTask(AutoStygianOnslaughtParam taskParam, string path) { AutoFightAssets.DestroyInstance(); _taskParam = taskParam; diff --git a/BetterGenshinImpact/Model/OneDragonTaskItem.cs b/BetterGenshinImpact/Model/OneDragonTaskItem.cs index 2c16502e..0dd568f1 100644 --- a/BetterGenshinImpact/Model/OneDragonTaskItem.cs +++ b/BetterGenshinImpact/Model/OneDragonTaskItem.cs @@ -127,7 +127,9 @@ public partial class OneDragonTaskItem : ObservableObject return; } - await new AutoStygianOnslaughtTask(TaskContext.Instance().Config.AutoStygianOnslaughtConfig, path).Start(CancellationContext.Instance.Cts.Token); + AutoStygianOnslaughtParam param = new AutoStygianOnslaughtParam(); + param.SetAutoStygianOnslaughtConfig(TaskContext.Instance().Config.AutoStygianOnslaughtConfig); + await new AutoStygianOnslaughtTask(param, path).Start(CancellationContext.Instance.Cts.Token); }; break; case "领取每日奖励": diff --git a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs index d99fe3a8..3c6d2a8a 100644 --- a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs @@ -422,8 +422,10 @@ public partial class TaskSettingsPageViewModel : ViewModel } SwitchAutoStygianOnslaughtEnabled = true; + AutoStygianOnslaughtParam param = new AutoStygianOnslaughtParam(); + param.SetAutoStygianOnslaughtConfig(Config.AutoStygianOnslaughtConfig); await new TaskRunner() - .RunSoloTaskAsync(new AutoStygianOnslaughtTask(Config.AutoStygianOnslaughtConfig, path)); + .RunSoloTaskAsync(new AutoStygianOnslaughtTask(param, path)); SwitchAutoStygianOnslaughtEnabled = false; } From 6bcb63e9967e929e2b0f5d0ccfb2b2cc5d2ec46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 8 Mar 2026 21:30:38 +0800 Subject: [PATCH 033/107] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=9D=E7=AE=B1?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E5=88=9A=E5=A5=BD=E8=A2=AB=E9=81=AE=E4=BD=8F?= =?UTF-8?q?=E7=9A=84=E5=9C=BA=E6=99=AF=20#2889?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoStygianOnslaught/AutoStygianOnslaughtTask.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs index a3482931..8dd17b60 100644 --- a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs +++ b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs @@ -825,7 +825,13 @@ public class AutoStygianOnslaughtTask : StateMachineBase, private async Task FindAndInteractLeylineFlowerLoop() { - await _lowerHeadThenWalkToTask!.Start(_ct); + // 先看看当前身边是否有F,有的话,直接F + using var ra1 = CaptureToRectArea(); + var text = Bv.FindFKeyText(ra1); + if (!string.IsNullOrEmpty(text) && text.Contains("激活")) + { + await _lowerHeadThenWalkToTask!.Start(_ct); + } await NewRetry.WaitForAction(() => { From 864efb42a336e03219f3e4f451552457f875a02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Mon, 9 Mar 2026 03:07:21 +0800 Subject: [PATCH 034/107] fix bug --- .../GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs index ca6f7a37..757fb69a 100644 --- a/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs +++ b/BetterGenshinImpact/GameTask/AutoStygianOnslaught/AutoStygianOnslaughtTask.cs @@ -807,7 +807,7 @@ public class AutoStygianOnslaughtTask : StateMachineBase, // 先看看当前身边是否有F,有的话,直接F using var ra1 = CaptureToRectArea(); var text = Bv.FindFKeyText(ra1); - if (!string.IsNullOrEmpty(text) && text.Contains("激活")) + if (string.IsNullOrEmpty(text) || !text.Contains("激活")) { await _lowerHeadThenWalkToTask!.Start(_ct); } From 5a5b77266eaf89540e04d75169d4b07e5468ed90 Mon Sep 17 00:00:00 2001 From: DarkFlameMaster <1004452714@qq.com> Date: Mon, 9 Mar 2026 23:42:44 +0800 Subject: [PATCH 035/107] =?UTF-8?q?feat(JS):=20=E6=94=AF=E6=8C=81=E8=B0=83?= =?UTF-8?q?=E7=94=A8=20C#=E4=B8=AD=E5=90=ABout/ref=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E6=B3=95=20(#2884)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(JS): 支持调用 C#中含out/ref参数的方法 * 新增NewVarOfArr方法用于支持交错数组 * 多删了一行 --- .../Script/Dependence/CustomHostFunctions.cs | 34 +++++++++++++++++++ .../Core/Script/EngineExtend.cs | 1 + 2 files changed, 35 insertions(+) create mode 100644 BetterGenshinImpact/Core/Script/Dependence/CustomHostFunctions.cs diff --git a/BetterGenshinImpact/Core/Script/Dependence/CustomHostFunctions.cs b/BetterGenshinImpact/Core/Script/Dependence/CustomHostFunctions.cs new file mode 100644 index 00000000..be572296 --- /dev/null +++ b/BetterGenshinImpact/Core/Script/Dependence/CustomHostFunctions.cs @@ -0,0 +1,34 @@ +using Microsoft.ClearScript; +using System; +using System.Reflection; + +namespace BetterGenshinImpact.Core.Script.Dependence; + +public class CustomHostFunctions : HostFunctions +{ + /// + /// 创建指定维度的交错数组变量 + /// + /// 数组元素类型 + /// 数组维度 + /// 交错数组变量 + public object NewVarOfArr(int dimensions) + { + try + { + Type arrayType = typeof(T); + for (int i = 0; i < dimensions; i++) + { + arrayType = arrayType.MakeArrayType(); + } + + MethodInfo newVarMethod = typeof(HostFunctions).GetMethod(nameof(newVar))!; + MethodInfo genericMethod = newVarMethod.MakeGenericMethod(arrayType); + return genericMethod.Invoke(this, new object?[] { null })!; + } + catch (Exception ex) + { + throw new InvalidOperationException($"创建维度为 {dimensions} 的数组失败: {ex.Message}", ex); + } + } +} diff --git a/BetterGenshinImpact/Core/Script/EngineExtend.cs b/BetterGenshinImpact/Core/Script/EngineExtend.cs index 176f6737..e5c487f6 100644 --- a/BetterGenshinImpact/Core/Script/EngineExtend.cs +++ b/BetterGenshinImpact/Core/Script/EngineExtend.cs @@ -87,6 +87,7 @@ public class EngineExtend engine.AddHostType("BvLocator", typeof(BvLocator)); engine.AddHostType("BvImage", typeof(BvImage)); + engine.AddHostObject("host", new CustomHostFunctions()); // 导入 JavaScript 模块 // https://microsoft.github.io/ClearScript/2023/01/24/module-interop.html From 226785e8a496116f2e10b9a4748936606cd1f828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Mon, 9 Mar 2026 23:43:20 +0800 Subject: [PATCH 036/107] =?UTF-8?q?Yap=20=E7=9A=84=E6=8E=A8=E7=90=86?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E6=94=B9=E4=B8=BA=E6=87=92=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E3=80=82index=5F2=5Fword.json=20=E4=BF=AE=E6=94=B9=E6=88=90=20?= =?UTF-8?q?Newtonsoft.Json=20=E5=8F=8D=E5=BA=8F=E5=88=97=E5=8C=96=20#2890?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Recognition/ONNX/SVTR/PickTextInference.cs | 8 ++++---- .../Core/Recognition/ONNX/SVTR/TextInferenceFactory.cs | 2 +- BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/PickTextInference.cs b/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/PickTextInference.cs index ded28ffc..70b22c21 100644 --- a/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/PickTextInference.cs +++ b/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/PickTextInference.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Config; using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; @@ -8,9 +8,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; -using System.Text.Json; using BetterGenshinImpact.Core.Recognition.OCR.Engine; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; namespace BetterGenshinImpact.Core.Recognition.ONNX.SVTR; @@ -31,7 +31,7 @@ public class PickTextInference : ITextInference if (!File.Exists(wordJsonPath)) throw new FileNotFoundException("Yap字典文件不存在", wordJsonPath); var json = File.ReadAllText(wordJsonPath); - _wordDictionary = JsonSerializer.Deserialize>(json) ?? + _wordDictionary = JsonConvert.DeserializeObject>(json) ?? throw new Exception("index_2_word.json deserialize failed"); } @@ -116,4 +116,4 @@ public class PickTextInference : ITextInference return new DenseTensor(memory, [1, 1, 32, 384]); } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/TextInferenceFactory.cs b/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/TextInferenceFactory.cs index d4606e45..e6e9700f 100644 --- a/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/TextInferenceFactory.cs +++ b/BetterGenshinImpact/Core/Recognition/ONNX/SVTR/TextInferenceFactory.cs @@ -6,7 +6,7 @@ namespace BetterGenshinImpact.Core.Recognition.ONNX.SVTR; public class TextInferenceFactory { - public static ITextInference Pick { get; } = Create(OcrEngineTypes.YapModel); + public static readonly Lazy Pick = new(() => Create(OcrEngineTypes.YapModel)); public static ITextInference Create(OcrEngineTypes type) { diff --git a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs index b4c19cf5..1490ce30 100644 --- a/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs +++ b/BetterGenshinImpact/GameTask/AutoPick/AutoPickTrigger.cs @@ -25,7 +25,6 @@ namespace BetterGenshinImpact.GameTask.AutoPick; public partial class AutoPickTrigger : ITaskTrigger { private readonly ILogger _logger = App.GetLogger(); - private readonly ITextInference _pickTextInference = TextInferenceFactory.Pick; public string Name => "自动拾取"; public bool IsEnabled { get; set; } @@ -276,7 +275,7 @@ public partial class AutoPickTrigger : ITaskTrigger if (config.OcrEngine == nameof(PickOcrEngineEnum.Yap)) { var textMat = new Mat(content.CaptureRectArea.CacheGreyMat, textRect); - text = _pickTextInference.Inference(textMat); + text = TextInferenceFactory.Pick.Value.Inference(textMat); } else { From 77306168f9d57b4e464294a9c5d7f51a041d8047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Mon, 9 Mar 2026 23:47:09 +0800 Subject: [PATCH 037/107] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=20BetterGI.?= =?UTF-8?q?Assets.Other=20=E4=BE=9D=E8=B5=96=E8=87=B3=E7=89=88=E6=9C=AC=20?= =?UTF-8?q?1.0.13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index ea46a780..2f5d176c 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -45,7 +45,7 @@ - + From f614695a3a4e50fd3981e7a84aab20121470344e Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:55:41 +0000 Subject: [PATCH 038/107] Update version to 0.58.0 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 2f5d176c..0aff201d 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.57.2-alpha.1 + 0.58.0 false WinExe net8.0-windows10.0.22621.0 From d4ef75344e1c0c903aed7c2304407251f784a791 Mon Sep 17 00:00:00 2001 From: FishmanTheMurloc <162452111+FishmanTheMurloc@users.noreply.github.com> Date: Tue, 10 Mar 2026 20:41:38 +0800 Subject: [PATCH 039/107] =?UTF-8?q?=E9=80=89=E6=8B=A9=E9=B1=BC=E9=A5=B5?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=AD=89=E5=BE=85=E6=97=B6=E9=97=B4=EF=BC=8C?= =?UTF-8?q?=E4=BB=A5=E9=99=8D=E4=BD=8E=E9=B1=BC=E9=A5=B5=E5=B0=9A=E6=9C=AA?= =?UTF-8?q?=E5=87=BA=E7=8E=B0=E6=97=B6=E6=89=A7=E8=A1=8C=E5=8A=A8=E4=BD=9C?= =?UTF-8?q?=E7=9A=84=E9=A2=91=E7=8E=87=20(#2900)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs b/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs index d6d32e60..9390199e 100644 --- a/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs +++ b/BetterGenshinImpact/GameTask/AutoFishing/Behaviours.cs @@ -151,7 +151,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing // 寻找鱼饵 var boxAndBaits = FindBait(imageRegion); - ; + foreach ((Rect box, string? predName) in boxAndBaits) { if (predName == blackboard.selectedBait.GetDescription()) @@ -201,6 +201,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing } else { + blackboard.Sleep(200); return BehaviourStatus.Running; } } From bb1f6d72814b3a8654877ac1a9da6a702c632798 Mon Sep 17 00:00:00 2001 From: MistEO Date: Wed, 11 Mar 2026 09:53:44 +0800 Subject: [PATCH 040/107] ci: check owner for mirrorc (#2901) --- .github/workflows/mirrorchyan_release_note.yml | 1 + .github/workflows/mirrorchyan_uploading.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/mirrorchyan_release_note.yml b/.github/workflows/mirrorchyan_release_note.yml index b43c6f2a..bb628028 100644 --- a/.github/workflows/mirrorchyan_release_note.yml +++ b/.github/workflows/mirrorchyan_release_note.yml @@ -7,6 +7,7 @@ on: jobs: mirrorchyan_release_note: + if: github.repository_owner == 'babalae' runs-on: macos-latest steps: diff --git a/.github/workflows/mirrorchyan_uploading.yml b/.github/workflows/mirrorchyan_uploading.yml index e650ef7b..438c2340 100644 --- a/.github/workflows/mirrorchyan_uploading.yml +++ b/.github/workflows/mirrorchyan_uploading.yml @@ -7,6 +7,7 @@ on: jobs: mirrorchyan: + if: github.repository_owner == 'babalae' runs-on: windows-latest steps: - name: 📥 Download release From 5dcbd9b5775caa143aa4dd93a927fa749eae7649 Mon Sep 17 00:00:00 2001 From: Shadow-Lemon <217746835+Shadow-Lemon@users.noreply.github.com> Date: Wed, 11 Mar 2026 09:56:01 +0800 Subject: [PATCH 041/107] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=A1=8C=E5=90=AF=E5=8A=A8=E6=97=B6=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=98=BB=E5=A1=9E=E5=AF=BC=E8=87=B4=20StartG?= =?UTF-8?q?ameTask=20=E8=B7=B3=E8=BF=87=E7=AD=89=E5=BE=85=E4=B8=BB?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E7=9A=84=E9=97=AE=E9=A2=98=20(#2902)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ShadowLemoon <119576779+ShadowLemoon@users.noreply.github.com> --- .../Core/Script/ScriptRepoUpdater.cs | 6 ++ .../Helpers/CommandLineOptions.cs | 96 +++++++++++++++++++ .../Service/ApplicationHostService.cs | 87 ++++++++--------- BetterGenshinImpact/Service/ScriptService.cs | 8 ++ .../ViewModel/MainWindowViewModel.cs | 2 +- .../ViewModel/Pages/HomePageViewModel.cs | 5 +- .../ViewModel/Pages/OneDragonFlowViewModel.cs | 11 ++- 7 files changed, 158 insertions(+), 57 deletions(-) create mode 100644 BetterGenshinImpact/Helpers/CommandLineOptions.cs diff --git a/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs b/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs index c839e38f..d4f75def 100644 --- a/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs +++ b/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs @@ -56,6 +56,12 @@ public class ScriptRepoUpdater : Singleton /// public event EventHandler? AutoUpdateStateChanged; + /// + /// 命令行启动时并行执行的自动更新 Task。 + /// StartGameTask 结束后会 await 此 Task,确保更新完成后再执行任务。 + /// + public Task? CommandLineAutoUpdateTask { get; set; } + // 仓储位置 public static readonly string ReposPath = Global.Absolute("Repos"); diff --git a/BetterGenshinImpact/Helpers/CommandLineOptions.cs b/BetterGenshinImpact/Helpers/CommandLineOptions.cs new file mode 100644 index 00000000..b48c40cc --- /dev/null +++ b/BetterGenshinImpact/Helpers/CommandLineOptions.cs @@ -0,0 +1,96 @@ +using System; +using System.Linq; + +namespace BetterGenshinImpact.Helpers; + +/// +/// 命令行参数统一解析,启动时解析一次,各处查询解析结果。 +/// +public class CommandLineOptions +{ + private static CommandLineOptions? _instance; + + public static CommandLineOptions Instance => _instance ??= Parse(Environment.GetCommandLineArgs()); + + public CommandLineAction Action { get; } + + /// + /// startOneDragon 时可选的配置名称(第 3 个参数) + /// + public string? OneDragonConfigName { get; } + + /// + /// --startGroups / --TaskProgress 时传入的组名列表(第 3 个参数起) + /// + public string[] GroupNames { get; } = []; + + /// + /// 是否有命令行任务参数(startOneDragon / --startGroups / --TaskProgress / start) + /// + public bool HasTaskArgs => Action != CommandLineAction.None; + + /// + /// 是否是需要 StartGameTask 自行处理游戏启动的命令 + /// (一条龙、配置组、任务进度由各自流程中的 StartGameTask 启动游戏) + /// + public bool ShouldDeferGameStart => Action is CommandLineAction.StartOneDragon + or CommandLineAction.StartGroups + or CommandLineAction.TaskProgress; + + private CommandLineOptions(CommandLineAction action, string? oneDragonConfigName = null, string[]? groupNames = null) + { + Action = action; + OneDragonConfigName = oneDragonConfigName; + GroupNames = groupNames ?? []; + } + + internal static CommandLineOptions Parse(string[] args) + { + if (args.Length <= 1) + return new CommandLineOptions(CommandLineAction.None); + + var arg1 = args[1].Trim(); + var extra = args.Skip(2).Select(x => x.Trim()).ToArray(); + + if (arg1.Contains("startOneDragon", StringComparison.OrdinalIgnoreCase)) + { + return new CommandLineOptions(CommandLineAction.StartOneDragon, + oneDragonConfigName: extra.Length > 0 ? extra[0] : null); + } + + if (arg1.Equals("--startGroups", StringComparison.OrdinalIgnoreCase)) + { + return new CommandLineOptions(CommandLineAction.StartGroups, groupNames: extra); + } + + if (arg1.Equals("--TaskProgress", StringComparison.OrdinalIgnoreCase)) + { + return new CommandLineOptions(CommandLineAction.TaskProgress, groupNames: extra); + } + + if (arg1.Contains("start", StringComparison.OrdinalIgnoreCase)) + { + return new CommandLineOptions(CommandLineAction.Start); + } + + return new CommandLineOptions(CommandLineAction.None); + } +} + +public enum CommandLineAction +{ + /// 双击启动,无命令行参数 + None, + + /// 纯 "start" — 仅启动截图器 + Start, + + /// startOneDragon — 启动一条龙 + StartOneDragon, + + /// --startGroups — 启动调度组 + StartGroups, + + /// --TaskProgress — 启动任务进度 + TaskProgress, +} diff --git a/BetterGenshinImpact/Service/ApplicationHostService.cs b/BetterGenshinImpact/Service/ApplicationHostService.cs index a05c9c60..5baa95b4 100644 --- a/BetterGenshinImpact/Service/ApplicationHostService.cs +++ b/BetterGenshinImpact/Service/ApplicationHostService.cs @@ -9,8 +9,7 @@ using System.Threading.Tasks; using System.Windows; using BetterGenshinImpact.Core.Script; using BetterGenshinImpact.GameTask; -using BetterGenshinImpact.GameTask.Common; -using Microsoft.Extensions.Logging; +using BetterGenshinImpact.Helpers; using Wpf.Ui; namespace BetterGenshinImpact.Service; @@ -49,66 +48,56 @@ public class ApplicationHostService(IServiceProvider serviceProvider) : IHostedS { _navigationWindow = (serviceProvider.GetService(typeof(INavigationWindow)) as INavigationWindow)!; _navigationWindow!.ShowWindow(); - // - var args = Environment.GetCommandLineArgs(); - if (args.Length > 1) + var cmdOptions = CommandLineOptions.Instance; + + if (cmdOptions.HasTaskArgs) { - //无论如何,先跳到主页,否则在通过参数的任务在执行完之前,不会加载快捷键 _ = _navigationWindow.Navigate(typeof(HomePage)); - // 命令行启动时,先等待自动更新订阅脚本完成,再运行配置组/一条龙 - // (正常双击启动在 MainWindowViewModel.OnLoaded 中以 fire-and-forget 方式调用) + // 命令行启动时,并行更新订阅脚本(不阻塞游戏启动和导航) + // StartGameTask 会在游戏进入主界面后等待此 Task 完成,再开始执行任务 var scriptConfig = TaskContext.Instance().Config.ScriptConfig; if (scriptConfig.AutoUpdateBeforeCommandLineRun) { - await Task.Run(() => ScriptRepoUpdater.Instance.AutoUpdateSubscribedScripts()); + ScriptRepoUpdater.Instance.CommandLineAutoUpdateTask = + Task.Run(() => ScriptRepoUpdater.Instance.AutoUpdateSubscribedScripts()); } - if (args[1].Contains("startOneDragon", StringComparison.InvariantCultureIgnoreCase)) + switch (cmdOptions.Action) { + case CommandLineAction.StartOneDragon: + // 通过命令行参数启动「一条龙」 => 跳转到一条龙配置页。 + _ = _navigationWindow.Navigate(typeof(OneDragonFlowPage)); + // 后续代码在 OneDragonFlowViewModel / OnLoaded 中。 + break; - // 通过命令行参数启动「一条龙」 => 跳转到一条龙配置页。 - _ = _navigationWindow.Navigate(typeof(OneDragonFlowPage)); - // 后续代码在 OneDragonFlowViewModel / OnLoaded 中。 - } - else if (args[1].Trim().Equals("--startGroups", StringComparison.InvariantCultureIgnoreCase)) - { - // 通过命令行参数启动「调度组」 => 跳转到调度器配置页。 - _ = _navigationWindow.Navigate(typeof(ScriptControlPage)); - if (args.Length > 2) - { - // 获取调度组 - var names = args.Skip(2).ToArray().Select(x => x.Trim()).ToArray(); - // 启动调度器 - var scheduler = App.GetService(); - scheduler?.OnStartMultiScriptGroupWithNamesAsync(names); - } - }else if (args[1].Trim().Equals("--TaskProgress", StringComparison.InvariantCultureIgnoreCase)) - { + case CommandLineAction.StartGroups: + // 通过命令行参数启动「调度组」 => 跳转到调度器配置页。 + _ = _navigationWindow.Navigate(typeof(ScriptControlPage)); + if (cmdOptions.GroupNames.Length > 0) + { + var scheduler = App.GetService(); + scheduler?.OnStartMultiScriptGroupWithNamesAsync(cmdOptions.GroupNames); + } + break; - // 通过命令行参数启动「调度组」 => 跳转到调度器配置页。 - _ = _navigationWindow.Navigate(typeof(ScriptControlPage)); - if (args.Length > 1) - { - // 获取调度组 - var names = args.Skip(2).ToArray().Select(x => x.Trim()).ToArray(); - // 启动调度器 - var scheduler = App.GetService(); - scheduler?.OnStartMultiScriptTaskProgressAsync(names); - } - } - else if (args[1].Contains("start")) - { - // 通过命令行参数打开「启动页开关」 => 跳转到主页。 - _ = _navigationWindow.Navigate(typeof(HomePage)); - // 后续代码在 HomePageViewModel / OnLoaded 中。 - } - else - { - // 其它命令行参数 => 跳转到主页。 - _ = _navigationWindow.Navigate(typeof(HomePage)); + case CommandLineAction.TaskProgress: + // 通过命令行参数启动「任务进度」 => 跳转到调度器配置页。 + _ = _navigationWindow.Navigate(typeof(ScriptControlPage)); + if (cmdOptions.GroupNames.Length > 0) + { + var scheduler = App.GetService(); + scheduler?.OnStartMultiScriptTaskProgressAsync(cmdOptions.GroupNames); + } + break; + + case CommandLineAction.Start: + // 通过命令行参数打开「启动页开关」 => 跳转到主页。 + _ = _navigationWindow.Navigate(typeof(HomePage)); + // 后续代码在 HomePageViewModel / OnLoaded 中。 + break; } } else diff --git a/BetterGenshinImpact/Service/ScriptService.cs b/BetterGenshinImpact/Service/ScriptService.cs index 83d44591..f30fd8eb 100644 --- a/BetterGenshinImpact/Service/ScriptService.cs +++ b/BetterGenshinImpact/Service/ScriptService.cs @@ -621,5 +621,13 @@ public partial class ScriptService : IScriptService }); } } + + // 等待命令行启动时并行执行的自动更新完成(如果有) + var pendingUpdate = ScriptRepoUpdater.Instance.CommandLineAutoUpdateTask; + if (pendingUpdate != null) + { + await pendingUpdate; + ScriptRepoUpdater.Instance.CommandLineAutoUpdateTask = null; + } } } diff --git a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs index 2320885b..77ee76be 100644 --- a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs @@ -250,7 +250,7 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel // 预热OCR await OcrPreheating(); - if (Environment.GetCommandLineArgs().Length > 1) + if (CommandLineOptions.Instance.HasTaskArgs) { return; } diff --git a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs index 3777949f..c5462997 100644 --- a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs @@ -133,8 +133,9 @@ public partial class HomePageViewModel : ViewModel _autoRun = false; - var args = Environment.GetCommandLineArgs(); - if (args.Length > 1 && args[1].Contains("start")) + // 只对纯 "start" 参数自动启动截图器 + // startOneDragon、--startGroups 等由各自流程中的 StartGameTask 处理 + if (CommandLineOptions.Instance.Action == CommandLineAction.Start) { _ = OnStartTriggerAsync(); } diff --git a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs index 142c81df..97413ba3 100644 --- a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs @@ -545,15 +545,16 @@ public partial class OneDragonFlowViewModel : ViewModel } _autoRun = false; // - var args = Environment.GetCommandLineArgs(); - if (args.Length > 1 && args[1].Contains("startOneDragon")) + var cmdOptions = CommandLineOptions.Instance; + if (cmdOptions.Action == CommandLineAction.StartOneDragon) { // 通过命令行参数启动一条龙。 - if (args.Length > 2) + if (cmdOptions.OneDragonConfigName != null) { // 从命令行参数中提取一条龙配置名称。 - _logger.LogInformation($"参数指定的一条龙配置:{args[2]}"); - var argsOneDragonConfig = ConfigList.FirstOrDefault(x => x.Name == args[2], null); + _logger.LogInformation($"参数指定的一条龙配置:{cmdOptions.OneDragonConfigName}"); + var argsOneDragonConfig = ConfigList.FirstOrDefault(x => + string.Equals(x.Name, cmdOptions.OneDragonConfigName, StringComparison.Ordinal)); if (argsOneDragonConfig != null) { // 设定配置,配置下拉框会选定。 From 48bbad9853626c8b726cbd028b987dc3fac89ac8 Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Wed, 11 Mar 2026 05:35:14 +0000 Subject: [PATCH 042/107] Update version to 0.58.1-alpha.1 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 0aff201d..390118a3 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.58.0 + 0.58.1-alpha.1 false WinExe net8.0-windows10.0.22621.0 From d660f79b4cb3ce7c5a1f0c34ab0f13718b62cc02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Wed, 11 Mar 2026 23:06:06 +0800 Subject: [PATCH 043/107] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=88=98=E6=96=97ready=E6=8C=87=E4=BB=A4=E7=9A=84=E8=A1=A8?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs | 2 +- BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs index c6c0f710..cbe68519 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs @@ -638,7 +638,7 @@ public class Avatar /// public void Ready() { - Sleep(200, Ct); + Sleep(10, Ct); for (int i = 0; i < 20; i++) { diff --git a/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs b/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs index 9b452d9d..a2aaa8f6 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs @@ -108,7 +108,8 @@ public class CombatCommand && Method != Method.KeyDown && Method != Method.KeyUp && Method != Method.KeyPress - && Method != Method.Scroll) + && Method != Method.Scroll + && Method != Method.Ready) { avatar.Switch(); } From 1e9b72e5475965ab03c9ba17285fa306735f9913 Mon Sep 17 00:00:00 2001 From: Shadow-Lemon <217746835+Shadow-Lemon@users.noreply.github.com> Date: Thu, 12 Mar 2026 00:08:26 +0800 Subject: [PATCH 044/107] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E5=AD=98=E5=9C=A8=E5=A4=9A=E4=B8=AA=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E4=BB=93=E5=BA=93=E6=96=87=E4=BB=B6=E5=A4=B9=E6=97=B6=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E4=BC=98=E5=85=88=E7=BA=A7=E5=87=BA=E9=94=99=20(#2906?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ShadowLemoon <119576779+ShadowLemoon@users.noreply.github.com> --- .../Core/Script/ScriptRepoUpdater.cs | 147 +++++++----------- 1 file changed, 54 insertions(+), 93 deletions(-) diff --git a/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs b/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs index d4f75def..a4d203a3 100644 --- a/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs +++ b/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs @@ -286,13 +286,26 @@ public class ScriptRepoUpdater : Singleton return (0, 0); } - // 展开所有订阅路径,直接全部更新 + // 展开所有订阅路径 var expandedPaths = ExpandTopLevelPaths(subscribedPaths, repoPath); + // 过滤掉仓库中已不存在的路径(幽灵订阅),避免删除用户文件后检出空内容 + var validPaths = FilterExistingPaths(expandedPaths, repoPath); + + // 清理订阅文件中的幽灵项:直接对原始订阅路径做过滤 + if (validPaths.Count < expandedPaths.Count) + { + var cleaned = FilterExistingPaths(subscribedPaths, repoPath); + if (cleaned.Count < subscribedPaths.Count) + { + SetSubscribedPathsForCurrentRepo(cleaned); + } + } + int successCount = 0; int failCount = 0; - foreach (var path in expandedPaths) + foreach (var path in validPaths) { try { @@ -418,6 +431,43 @@ public class ScriptRepoUpdater : Singleton return result; } + /// + /// 过滤掉仓库中已不存在的路径,防止幽灵订阅导致误删用户文件。 + /// + private List FilterExistingPaths(List paths, string repoPath) + { + bool isGitRepo = IsGitRepository(repoPath); + + if (isGitRepo) + { + using var repo = new Repository(repoPath); + if (repo.Head.Tip == null) return paths; + var repoTree = GetRepoSubdirectoryTree(repo); + + return paths.Where(path => + { + var parts = path.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + var currentTree = repoTree; + foreach (var part in parts) + { + var entry = currentTree[part]; + if (entry == null) return false; + if (entry.TargetType == TreeEntryTargetType.Tree) + currentTree = (Tree)entry.Target; + } + return true; + }).ToList(); + } + else + { + return paths.Where(path => + { + var fullPath = Path.Combine(repoPath, path); + return Directory.Exists(fullPath) || File.Exists(fullPath); + }).ToList(); + } + } + /// /// 静默更新中央仓库(用于自动更新订阅脚本前同步最新仓库内容)。 /// 注意:此方法设计为在 _repoWriteLock 持有期间调用, @@ -2420,73 +2470,8 @@ public class ScriptRepoUpdater : Singleton if (oldPaths.Count == 0) return; - // 默认归入当前仓库 - var repoFolderName = GetCurrentRepoFolderName(); - - // 如果存在多个仓库,尝试按 repo.json 分配路径 - if (Directory.Exists(ReposPath)) - { - var repoDirs = Directory.GetDirectories(ReposPath) - .Where(d => !Path.GetFileName(d).Equals("Temp", StringComparison.OrdinalIgnoreCase)) - .ToList(); - - if (repoDirs.Count > 1) - { - var repoPathSets = new Dictionary>(); - foreach (var repoDir in repoDirs) - { - var repoJsonFile = Directory.GetFiles(repoDir, "repo.json", SearchOption.AllDirectories).FirstOrDefault(); - if (string.IsNullOrEmpty(repoJsonFile)) continue; - try - { - var json = File.ReadAllText(repoJsonFile); - var jsonObj = JObject.Parse(json); - if (jsonObj["indexes"] is JArray indexes) - { - var pathSet = new HashSet(); - CollectAllPathsFromIndexes(indexes, "", pathSet); - repoPathSets[Path.GetFileName(repoDir)] = pathSet; - } - } - catch { /* ignore */ } - } - - if (repoPathSets.Count > 1) - { - // 按仓库聚合后批量写入 - var repoSubscriptions = new Dictionary>(); - foreach (var path in oldPaths) - { - var targetRepo = repoFolderName; // 默认归入当前仓库 - foreach (var (repoName, pathSet) in repoPathSets) - { - if (pathSet.Contains(path)) - { - targetRepo = repoName; - break; - } - } - - if (!repoSubscriptions.ContainsKey(targetRepo)) - repoSubscriptions[targetRepo] = new List(); - repoSubscriptions[targetRepo].Add(path); - } - - foreach (var (repoName, paths) in repoSubscriptions) - { - WriteSubscriptionFile(GetSubscriptionFilePath(repoName), paths); - } - - // 清空配置属性,框架自动保存 - scriptConfig.SubscribedScriptPaths = new List(); - _logger.LogInformation("已完成订阅路径迁移到独立文件(多仓库分配)"); - return; - } - } - } - - // 单仓库:直接写入 - WriteSubscriptionFile(GetSubscriptionFilePath(repoFolderName), new List(oldPaths)); + // 全部归入当前仓库,幽灵路径由后续 UpdateAllSubscribedScriptsCore 统一清理 + WriteSubscriptionFile(GetSubscriptionFilePath(GetCurrentRepoFolderName()), [.. oldPaths]); // 清空配置属性,框架自动保存 scriptConfig.SubscribedScriptPaths = new List(); @@ -2498,30 +2483,6 @@ public class ScriptRepoUpdater : Singleton } } - /// - /// 递归收集 indexes 中所有路径(用于迁移时匹配) - /// - private static void CollectAllPathsFromIndexes(JArray nodes, string currentPath, HashSet result) - { - foreach (var node in nodes) - { - if (node is JObject nodeObj) - { - var name = nodeObj["name"]?.ToString(); - if (!string.IsNullOrEmpty(name)) - { - var fullPath = string.IsNullOrEmpty(currentPath) ? name : $"{currentPath}/{name}"; - result.Add(fullPath); - - if (nodeObj["children"] is JArray children) - { - CollectAllPathsFromIndexes(children, fullPath, result); - } - } - } - } - } - // 更新订阅脚本路径列表,移除无效路径(仅处理当前仓库的订阅) public void UpdateSubscribedScriptPaths() { From 54301e1e02c8d0f1055e2d61230f66ee1cdaaa9b Mon Sep 17 00:00:00 2001 From: mno <718135749@qq.com> Date: Thu, 12 Mar 2026 10:01:55 +0800 Subject: [PATCH 045/107] =?UTF-8?q?=E4=BF=AE=E6=94=B9check=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E6=A3=80=E6=9F=A5=E7=9A=84=E6=97=B6=E6=9C=BA=20(#2904?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoFight/AutoFightTask.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs b/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs index e2da60fc..80ededa9 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/AutoFightTask.cs @@ -419,13 +419,6 @@ public class AutoFightTask : ISoloTask } #endregion - #region check动作触发战斗结束检测 - if (command.Method == Method.Check) - { - fightEndFlag = await CheckFightFinish(delayTime, detectDelayTime); - } - #endregion - command.Execute(combatScenes, lastCommand); //统计战斗人次 if (i == combatCommands.Count - 1 || command.Name != combatCommands[i + 1].Name) @@ -433,6 +426,13 @@ public class AutoFightTask : ISoloTask countFight++; } + #region check动作触发战斗结束检测 + if (command.Method == Method.Check) + { + fightEndFlag = await CheckFightFinish(delayTime, detectDelayTime); + } + #endregion + lastFightName = command.Name; if (!fightEndFlag && _taskParam is { FightFinishDetectEnabled: true }) { From e958e24d4e6902e44c41490995596c879cabead2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 14 Mar 2026 16:12:11 +0800 Subject: [PATCH 046/107] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E4=BF=AE=E5=A4=8DJSO?= =?UTF-8?q?N=E5=BA=8F=E5=88=97=E5=8C=96=E9=97=AE=E9=A2=98=EF=BC=8C?= =?UTF-8?q?=E6=9C=AC=E8=B4=A8=E9=97=AE=E9=A2=98=E6=98=AFOCR=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=EF=BC=8C=E7=94=A8=E7=BA=AF=E6=89=98=E7=AE=A1=20argmax?= =?UTF-8?q?=20=E6=9B=BF=E4=BB=A3=E8=AF=A5=E8=B7=AF=E5=BE=84=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Recognition/OCR/Paddle/Rec.cs | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Rec.cs b/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Rec.cs index 68d2a1d2..b965a36e 100644 --- a/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Rec.cs +++ b/BetterGenshinImpact/Core/Recognition/OCR/Paddle/Rec.cs @@ -101,45 +101,41 @@ public class Rec : IDisposable return resultTensors.SelectMany(tensor => { - GCHandle dataHandle = default; - try - { - dataHandle = GCHandle.Alloc(tensor.Data, GCHandleType.Pinned); - var dataPtr = dataHandle.AddrOfPinnedObject(); - - return Enumerable.Range(0, tensor.Batch) - .Select(i => + return Enumerable.Range(0, tensor.Batch) + .Select(i => + { + StringBuilder sb = new(); + var lastIndex = 0; + float score = 0; + var batchOffset = i * tensor.TimeSteps * tensor.LabelCount; + for (var n = 0; n < tensor.TimeSteps; ++n) { - StringBuilder sb = new(); - var lastIndex = 0; - float score = 0; - var maxIdx = new int[2]; - using var fullMat = Mat.FromPixelData(tensor.TimeSteps, tensor.LabelCount, - MatType.CV_32FC1, - dataPtr + i * tensor.TimeSteps * tensor.LabelCount * sizeof(float)); - for (var n = 0; n < tensor.TimeSteps; ++n) + var rowOffset = batchOffset + n * tensor.LabelCount; + var maxVal = float.MinValue; + var maxIndex = 0; + for (var c = 0; c < tensor.LabelCount; c++) { - using var row = fullMat.Row(n); - row.MinMaxIdx(out _, out var maxVal, [], maxIdx); - - if (maxIdx[1] > 0 && maxVal >= _threshold && (_allowDuplicateChar || !(n > 0 && maxIdx[1] == lastIndex))) + var value = tensor.Data[rowOffset + c]; + if (value > maxVal) { - score += (float)maxVal; - sb.Append(OcrUtils.GetLabelByIndex(maxIdx[1], _labels)); + maxVal = value; + maxIndex = c; } - - lastIndex = maxIdx[1]; } - var text = sb.ToString(); - return new OcrRecognizerResult(text, text.Length > 0 ? score / text.Length : 0); - }) - .ToArray(); - } - finally - { - if (dataHandle.IsAllocated) dataHandle.Free(); - } + if (maxIndex > 0 && maxVal >= _threshold && (_allowDuplicateChar || !(n > 0 && maxIndex == lastIndex))) + { + score += maxVal; + sb.Append(OcrUtils.GetLabelByIndex(maxIndex, _labels)); + } + + lastIndex = maxIndex; + } + + var text = sb.ToString(); + return new OcrRecognizerResult(text, text.Length > 0 ? score / text.Length : 0); + }) + .ToArray(); }).ToArray(); } From cf344b2d39f79526c7f04cdc9a521867100ec156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 14 Mar 2026 16:15:56 +0800 Subject: [PATCH 047/107] =?UTF-8?q?=E4=BF=AE=E5=A4=8DUser=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E6=B8=85=E7=A9=BA=E5=90=8E=EF=BC=8C=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=88=9B=E5=BB=BA=E7=9B=AE=E5=BD=95=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/ViewModel/Pages/View/AutoFightViewModel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/BetterGenshinImpact/ViewModel/Pages/View/AutoFightViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/View/AutoFightViewModel.cs index d9e8f3f1..2d2d93ab 100644 --- a/BetterGenshinImpact/ViewModel/Pages/View/AutoFightViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/View/AutoFightViewModel.cs @@ -39,6 +39,7 @@ public partial class AutoFightViewModel : ObservableObject, IViewModel private string[] LoadCustomScript(string folder) { + Directory.CreateDirectory(folder); var files = Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories); From 12e62960594ef941dd29af92bcac4dc8fc1c7c31 Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Sat, 14 Mar 2026 08:22:23 +0000 Subject: [PATCH 048/107] Update version to 0.58.3-alpha.1 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 390118a3..681ebb80 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.58.1-alpha.1 + 0.58.3-alpha.1 false WinExe net8.0-windows10.0.22621.0 From c5a9427ca79dc48649c54598442045eac3181d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 15 Mar 2026 10:43:25 +0800 Subject: [PATCH 049/107] =?UTF-8?q?=E6=9C=80=E5=90=8E=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=B8=B8=E6=88=8F=E8=BF=9B=E7=A8=8B?= =?UTF-8?q?=E5=90=8D=20#2909?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/TaskContext.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/TaskContext.cs b/BetterGenshinImpact/GameTask/TaskContext.cs index 4ce0b6cf..ba346ea4 100644 --- a/BetterGenshinImpact/GameTask/TaskContext.cs +++ b/BetterGenshinImpact/GameTask/TaskContext.cs @@ -94,7 +94,8 @@ namespace BetterGenshinImpact.GameTask var customName = Path.GetFileNameWithoutExtension(installPath); if (!string.IsNullOrEmpty(customName) && !list.Contains(customName)) { - list.Insert(0, customName); // 将用户自定义的进程名放在列表前面,优先匹配 + // list.Insert(0, customName); // 将用户自定义的进程名放在列表前面,优先匹配 + list.Add(customName); } } } From 2c28cc2335bb50bd9bd1992924b7675656cf9d9b Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Sun, 15 Mar 2026 02:47:28 +0000 Subject: [PATCH 050/107] Update version to 0.58.3-alpha.2 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 681ebb80..3a382c7c 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.58.3-alpha.1 + 0.58.3-alpha.2 false WinExe net8.0-windows10.0.22621.0 From 68ebf2eb4bb85870e7e494771ef67b41f46acdd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Mon, 16 Mar 2026 21:47:25 +0800 Subject: [PATCH 051/107] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20E=5FACCESSDENIED?= =?UTF-8?q?=20(0x80070005)=20=E6=8A=A5=E9=94=99=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这是一种导致 he BetterGenshinImpact.View.Pages.HomePage page does not have a parameterless constructor. If you are using Wpf.Ui.Abstractions.INavigationViewPageProvider do not navigate initially and don't use Cache or Precache. 错误的场景 --- BetterGenshinImpact/App.xaml.cs | 28 ++++++++++++------- .../DpiAwareness/DpiAwarenessController.cs | 3 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/BetterGenshinImpact/App.xaml.cs b/BetterGenshinImpact/App.xaml.cs index a327e0d2..382e0da9 100644 --- a/BetterGenshinImpact/App.xaml.cs +++ b/BetterGenshinImpact/App.xaml.cs @@ -52,8 +52,7 @@ public partial class App : Application .UseElevated() .UseSingleInstance("BetterGI") .ConfigureLogging(builder => { builder.ClearProviders(); }) - .ConfigureServices( - (context, services) => + .ConfigureServices((context, services) => { // 提前初始化配置 var configService = new ConfigService(); @@ -74,7 +73,7 @@ public partial class App : Application rollingInterval: RollingInterval.Day, retainedFileCountLimit: 31, retainedFileTimeLimit: TimeSpan.FromDays(21)) - .WriteTo.Console(outputTemplate: + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) @@ -88,7 +87,7 @@ public partial class App : Application Log.Logger = loggerConfiguration.CreateLogger(); services.AddSingleton(); services.AddSingleton(); - + services.AddLogging(c => c.AddSerilog()); // if ("zh-Hans".Equals(all.OtherConfig.UiCultureInfoName, StringComparison.OrdinalIgnoreCase)) // { @@ -166,13 +165,13 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - + services.AddSingleton(TimeProvider.System); services.AddSingleton(); // Configuration //services.Configure(context.Configuration.GetSection(nameof(AppConfig))); - + I18N.Culture = new CultureInfo("zh-Hans"); // #1846 } ) @@ -225,10 +224,19 @@ public partial class App : Application } catch (Exception ex) { - // DEBUG only, no overhead Debug.WriteLine(ex); ConsoleHelper.WriteError($"应用程序启动失败: {ex.Message}"); + try + { + HandleException(ex); + } + catch (Exception ex2) + { + Debug.WriteLine(ex2); + ConsoleHelper.WriteError($"应用程序启动失败打印日志时又失败了: {ex2.Message}"); + } + if (Debugger.IsAttached) { Debugger.Break(); @@ -244,12 +252,12 @@ public partial class App : Application base.OnExit(e); ConsoleHelper.WriteLine("BetterGI 应用程序正在关闭..."); - + TempManager.CleanUp(); await _host.StopAsync(); _host.Dispose(); - + // 释放控制台窗口 ConsoleHelper.FreeConsoleWindow(); } @@ -351,4 +359,4 @@ public partial class App : Application // log GetLogger().LogDebug(e, "UnHandle Exception"); } -} +} \ No newline at end of file diff --git a/BetterGenshinImpact/Helpers/DpiAwareness/DpiAwarenessController.cs b/BetterGenshinImpact/Helpers/DpiAwareness/DpiAwarenessController.cs index 604fdbfe..233a838c 100644 --- a/BetterGenshinImpact/Helpers/DpiAwareness/DpiAwarenessController.cs +++ b/BetterGenshinImpact/Helpers/DpiAwareness/DpiAwarenessController.cs @@ -40,8 +40,7 @@ internal class DpiAwarenessController } else{ SHCore - .SetProcessDpiAwareness(SHCore.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE) - .ThrowIfFailed(); + .SetProcessDpiAwareness(SHCore.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE); } } From 33013fd7a565d2b0e80fb65e289ba26f90d4d0bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Tue, 17 Mar 2026 01:28:14 +0800 Subject: [PATCH 052/107] =?UTF-8?q?OCR=E4=BF=AE=E6=94=B9=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=BC=BA=E5=88=B6=E6=8E=A8=E7=90=86=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/Core/Config/HardwareAccelerationConfig.cs | 4 ++-- .../View/Pages/View/HardwareAccelerationView.xaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BetterGenshinImpact/Core/Config/HardwareAccelerationConfig.cs b/BetterGenshinImpact/Core/Config/HardwareAccelerationConfig.cs index 100e9d85..fff63370 100644 --- a/BetterGenshinImpact/Core/Config/HardwareAccelerationConfig.cs +++ b/BetterGenshinImpact/Core/Config/HardwareAccelerationConfig.cs @@ -14,10 +14,10 @@ public partial class HardwareAccelerationConfig : ObservableObject private InferenceDeviceType _inferenceDevice = InferenceDeviceType.Cpu; /// - /// 是否强制OCR使用CPU推理。在某些环境上使用GPU进行OCR推理会导致性能下降(比如很多使用DirectML推理的情况下)。默认关闭。 + /// 是否强制OCR使用CPU推理。在某些环境上使用GPU进行OCR推理会导致性能下降(比如很多使用DirectML推理的情况下)。默认开启。 /// [ObservableProperty] - private bool _cpuOcr = false; + private bool _cpuOcr = true; #region 一般GPU加速设置 diff --git a/BetterGenshinImpact/View/Pages/View/HardwareAccelerationView.xaml b/BetterGenshinImpact/View/Pages/View/HardwareAccelerationView.xaml index d0277f16..27847040 100644 --- a/BetterGenshinImpact/View/Pages/View/HardwareAccelerationView.xaml +++ b/BetterGenshinImpact/View/Pages/View/HardwareAccelerationView.xaml @@ -133,7 +133,7 @@ + Text="解决部分GPU推理性能和报错问题 默认开启" /> Date: Tue, 17 Mar 2026 02:10:06 +0800 Subject: [PATCH 053/107] =?UTF-8?q?=E5=85=B3=E9=97=AD=E5=BC=80=E4=B9=A6=20?= =?UTF-8?q?#2686?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/Common/Job/CheckRewardsTask.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BetterGenshinImpact/GameTask/Common/Job/CheckRewardsTask.cs b/BetterGenshinImpact/GameTask/Common/Job/CheckRewardsTask.cs index f7d218bd..6b5b91f9 100644 --- a/BetterGenshinImpact/GameTask/Common/Job/CheckRewardsTask.cs +++ b/BetterGenshinImpact/GameTask/Common/Job/CheckRewardsTask.cs @@ -78,6 +78,8 @@ public class CheckRewardsTask Logger.LogWarning("检查每日奖励结果:{Msg},请手动检查!", "未领取"); Notify.Event(NotificationEvent.DailyReward).Error("检查到每日奖励未领取,请手动查看!"); } + await Delay(200, ct); + await new ReturnMainUiTask().Start(ct); } catch (Exception e) { From eec45186058e30e1432702137a1547d4605dc56c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Tue, 17 Mar 2026 02:25:31 +0800 Subject: [PATCH 054/107] =?UTF-8?q?=E5=88=87=E6=8D=A2=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoFight/AutoFightSeek.cs | 4 ++-- .../GameTask/AutoFight/Model/Avatar.cs | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoFight/AutoFightSeek.cs b/BetterGenshinImpact/GameTask/AutoFight/AutoFightSeek.cs index 322787fd..b6036852 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/AutoFightSeek.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/AutoFightSeek.cs @@ -461,7 +461,7 @@ namespace BetterGenshinImpact.GameTask.AutoFight { while (attempt < retryCount) { - if (guardianAvatar.TrySwitch(10, false)) + if (guardianAvatar.TrySwitch(10)) { guardianAvatar.ManualSkillCd = -1; if (await AvatarSkillAsync(Logger, guardianAvatar, false, 1, ct)) @@ -550,7 +550,7 @@ namespace BetterGenshinImpact.GameTask.AutoFight Logger.LogInformation("优先第 {text} 盾奶位 {GuardianAvatar} 元素爆发状态:{attempt},尝试释放", guardianAvatarName, guardianAvatar.Name, "就绪"); - if (guardianAvatar.TrySwitch(8, false)) + if (guardianAvatar.TrySwitch(8)) { Simulation.SendInput.SimulateAction(GIActions.ElementalBurst); Sleep(500, ct); diff --git a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs index cbe68519..3181b6b6 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Model/Avatar.cs @@ -239,7 +239,7 @@ public class Avatar /// /// /// - public bool TrySwitch(int tryTimes = 4, bool needLog = true) + public bool TrySwitch(int tryTimes = 4) { var context = new AvatarActiveCheckContext(); for (var i = 0; i < tryTimes; i++) @@ -255,13 +255,16 @@ public class Avatar // 切换成功 if (CombatScenes.GetActiveAvatarIndex(region, context) == Index) { - // if (needLog && i > 0) - // { - // Logger.LogInformation("成功切换角色:{Name}", Name); - // } - return true; } + else + { + if (i == tryTimes - 1) + { + Logger.LogWarning("切换角色失败,最后一次尝试,当前角色编号:{CurrentIndex},期望角色编号:{ExpectedIndex}", CombatScenes.GetActiveAvatarIndex(region, context), Index); + region.SrcMat.SaveImage($"log/{Name}_切换失败.png"); + } + } SimulateSwitchAction(Index); From 565510ebe680ac3b3a9be95ef0ea68581e416ad0 Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Mon, 16 Mar 2026 18:27:45 +0000 Subject: [PATCH 055/107] Update version to 0.58.3-alpha.3 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 3a382c7c..ab58203e 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.58.3-alpha.2 + 0.58.3-alpha.3 false WinExe net8.0-windows10.0.22621.0 From 875434b4920bcd55dc5307f4235bd77db2fa32aa Mon Sep 17 00:00:00 2001 From: ddaodan <40017293+ddaodan@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:57:37 +0800 Subject: [PATCH 056/107] =?UTF-8?q?chore:=20=E6=8E=A5=E5=85=A5=20Repo=20Bo?= =?UTF-8?q?t=20Issue=20=E5=8A=A9=E6=89=8B=20(#2916)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/bug.md | 22 --- .github/ISSUE_TEMPLATE/bug.yml | 41 +++++ .github/ISSUE_TEMPLATE/feature.md | 9 - .github/ISSUE_TEMPLATE/feature.yml | 30 ++++ .github/ISSUE_TEMPLATE/normal.md | 5 - .github/ISSUE_TEMPLATE/normal.yml | 12 ++ .github/ISSUE_TEMPLATE/question.md | 9 - .github/ISSUE_TEMPLATE/question.yml | 22 +++ .github/ISSUE_TEMPLATE/suggestion.md | 9 - .github/ISSUE_TEMPLATE/suggestion.yml | 22 +++ .github/repo-bot.yml | 227 ++++++++++++++++++++++++++ .github/workflows/repo-bot.yml | 55 +++++++ 12 files changed, 409 insertions(+), 54 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug.md create mode 100644 .github/ISSUE_TEMPLATE/bug.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature.md create mode 100644 .github/ISSUE_TEMPLATE/feature.yml delete mode 100644 .github/ISSUE_TEMPLATE/normal.md create mode 100644 .github/ISSUE_TEMPLATE/normal.yml delete mode 100644 .github/ISSUE_TEMPLATE/question.md create mode 100644 .github/ISSUE_TEMPLATE/question.yml delete mode 100644 .github/ISSUE_TEMPLATE/suggestion.md create mode 100644 .github/ISSUE_TEMPLATE/suggestion.yml create mode 100644 .github/repo-bot.yml create mode 100644 .github/workflows/repo-bot.yml diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md deleted file mode 100644 index 7ea0001c..00000000 --- a/.github/ISSUE_TEMPLATE/bug.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Bug report / 错误报告 -about: Create a report to help us improve / 创建报告以帮助我们改进 -title: "[bug]请补充标题内容" -labels: bug ---- - - - -- 系统环境 / System Environment: - - -- BetterGI版本号 / BetterGI Version: - - -- 问题描述 / Description of the issue: - - -- 复现步骤 / Reproduction steps: - diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 00000000..aee9294e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,41 @@ +name: Bug report / 错误报告 +description: 提交可复现的问题,帮助我们定位错误 / Report a reproducible problem +title: "[bug] " +labels: + - BUG +body: + - type: textarea + id: environment + attributes: + label: 系统环境 + description: System Environment,例如系统版本、运行环境 + placeholder: Win11 / BetterGI 0.58.0 / .NET 8 + validations: + required: true + - type: input + id: version + attributes: + label: BetterGI 版本号 + description: BetterGI Version + placeholder: 0.58.0 + validations: + required: true + - type: textarea + id: description + attributes: + label: 问题描述 + description: Description of the issue + placeholder: 请详细描述你遇到的问题,可直接粘贴日志并附带截图 + validations: + required: true + - type: textarea + id: steps + attributes: + label: 复现步骤 + description: Reproduction steps + placeholder: |- + 1. 打开…… + 2. 点击…… + 3. 出现…… + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md deleted file mode 100644 index 9e8b792b..00000000 --- a/.github/ISSUE_TEMPLATE/feature.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: I have feature requesting / 我有新功能请求 -about: Your request may come as a surprise. / 你的请求也许成为惊喜 -title: "[feature] " -labels: feature 功能请求 ---- - -- Your feature requesting / 你的新功能请求 - diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 00000000..35820331 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,30 @@ +name: I have feature requesting / 我有功能需求 +description: 提出新的功能想法 / Share a feature request +title: "[feature] " +labels: + - 功能建议 +body: + - type: textarea + id: request + attributes: + label: 功能请求 + description: Feature Request + placeholder: 请简要说明你希望新增什么能力 + validations: + required: true + - type: textarea + id: scenario + attributes: + label: 使用场景 + description: Use Case + placeholder: 这个功能要解决什么问题?在哪些场景下使用? + validations: + required: true + - type: textarea + id: extra + attributes: + label: 补充信息 + description: Optional + placeholder: 可选,补充示意图、参考方案或限制条件 + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/normal.md b/.github/ISSUE_TEMPLATE/normal.md deleted file mode 100644 index 2b03cdcf..00000000 --- a/.github/ISSUE_TEMPLATE/normal.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -name: Feel free to express / 随便写写 -about: Record something / 简单写写记录记录 ---- - diff --git a/.github/ISSUE_TEMPLATE/normal.yml b/.github/ISSUE_TEMPLATE/normal.yml new file mode 100644 index 00000000..ad71e485 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/normal.yml @@ -0,0 +1,12 @@ +name: Feel free to express / 自由描述 +description: 记录你想反馈的内容 / Share general feedback +title: "[feedback] " +body: + - type: textarea + id: description + attributes: + label: 描述 + description: Description + placeholder: 请描述你想反馈的内容 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 1f03a9e0..00000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: I have a question / 我有疑问 -about: Solve your problem / 解决你的疑问 -title: "[question] " -labels: question ---- - -- Problem Description / 问题描述 -- 请尽量提供问题相关的截图/日志等内容 diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 00000000..81c97c14 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,22 @@ +name: I have a question / 我有疑问 +description: 提交你的问题,我们会尽量帮助你解决 / Ask a question +title: "[question] " +labels: + - 问题咨询 +body: + - type: textarea + id: question + attributes: + label: 问题描述 + description: Problem Description + placeholder: 请清楚描述你的问题 + validations: + required: true + - type: textarea + id: tried + attributes: + label: 已尝试内容 + description: Screenshots / logs / what you already tried + placeholder: 可附上截图、日志,以及你已经尝试过的排查过程 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/suggestion.md b/.github/ISSUE_TEMPLATE/suggestion.md deleted file mode 100644 index 7c3bf708..00000000 --- a/.github/ISSUE_TEMPLATE/suggestion.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: I have suggestions / 我有建议 -about: Your suggestions may benefit everyone / 你的建议可能让所有人受益 -title: "[suggestion] " -labels: suggestion ---- - -- Your suggestions / 你的建议 - diff --git a/.github/ISSUE_TEMPLATE/suggestion.yml b/.github/ISSUE_TEMPLATE/suggestion.yml new file mode 100644 index 00000000..8d24b45e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/suggestion.yml @@ -0,0 +1,22 @@ +name: I have suggestions / 我有建议 +description: 分享对项目的改进建议 / Share an improvement suggestion +title: "[suggestion] " +labels: + - 功能建议 +body: + - type: textarea + id: suggestion + attributes: + label: 建议内容 + description: Suggestion + placeholder: 请描述你的建议内容 + validations: + required: true + - type: textarea + id: benefit + attributes: + label: 预期收益 + description: Expected Benefit + placeholder: 这个建议会给用户或项目带来什么帮助? + validations: + required: true diff --git a/.github/repo-bot.yml b/.github/repo-bot.yml new file mode 100644 index 00000000..c4bd5745 --- /dev/null +++ b/.github/repo-bot.yml @@ -0,0 +1,227 @@ +runtime: + languageMode: auto + dryRun: false + +providers: + openAiCompatible: + enabled: true + baseUrl: https://api.openai.com/v1 + model: gpt-5.4 + apiStyle: responses + timeoutMs: 30000 + +issues: + validation: + enabled: true + fallbackTemplateKey: normal + commentAnchor: issue-bot:validation + templates: + - key: bug + detect: + markers: + - bug + titlePrefixes: + - "[bug]" + requiredSections: + - id: environment + aliases: + - 系统环境 + - System Environment + - id: version + aliases: + - BetterGI 版本号 + - BetterGI Version + - id: description + aliases: + - 问题描述 + - Description of the issue + - id: steps + aliases: + - 复现步骤 + - Reproduction steps + labels: + whenValid: + - BUG + whenInvalid: + - 需要更多信息 + - key: feature + detect: + markers: + - feature + titlePrefixes: + - "[feature]" + requiredSections: + - id: request + aliases: + - 功能请求 + - Feature Request + - id: scenario + aliases: + - 使用场景 + - Use Case + labels: + whenValid: + - 功能建议 + whenInvalid: + - 需要更多信息 + - key: suggestion + detect: + markers: + - suggestion + titlePrefixes: + - "[suggestion]" + requiredSections: + - id: suggestion + aliases: + - 建议内容 + - Suggestion + - id: benefit + aliases: + - 预期收益 + - Expected Benefit + labels: + whenValid: + - 功能建议 + whenInvalid: + - 需要更多信息 + - key: question + detect: + markers: + - question + titlePrefixes: + - "[question]" + requiredSections: + - id: question + aliases: + - 问题描述 + - Problem Description + - id: tried + aliases: + - 已尝试内容 + - What I Tried + labels: + whenValid: + - 问题咨询 + whenInvalid: + - 需要更多信息 + - key: normal + detect: + markers: + - normal + titlePrefixes: + - "[feedback]" + - "[normal]" + requiredSections: + - id: description + aliases: + - 描述 + - Description + labels: + whenValid: [] + whenInvalid: + - 需要更多信息 + duplicateDetection: + enabled: true + bypassLabels: + - 跳过重复检测 + duplicateLabel: 重复 + searchResultLimit: 50 + candidateLimit: 20 + aiReviewMaxCandidates: 3 + thresholds: + exact: 0.995 + highConfidence: 0.93 + reviewMin: 0.82 + similarityComment: + enabled: true + commentAnchor: issue-bot:similar-issues + minScore: 0.3 + maxCandidates: 3 + labeling: + enabled: true + autoCreateMissing: true + managed: + - BUG + - 功能建议 + - 问题咨询 + - 需要更多信息 + - 重复 + definitions: + BUG: + color: d73a4a + description: 程序存在缺陷或异常行为。 + 功能建议: + color: a2eeef + description: 新功能建议或现有功能改进。 + 问题咨询: + color: d876e3 + description: 使用问题、求助或咨询。 + 需要更多信息: + color: fbca04 + description: 当前 Issue 缺少必要信息。 + 重复: + color: cfd3d7 + description: 已存在相同或高度相似的问题。 + keywordRules: [] + aiClassification: + enabled: true + maxLabels: 3 + minConfidence: 0.65 + include: [] + exclude: + - BUG + - 功能请求 + - 重复 + - 不会修复 + - 使用问题 + - 需要帮助 + - 已完成 + - 未解决 + - 待确认 + - P0 + - P1 + - 需要文档 + - 优化点 + - 急急急 + prompt: 优先给 BetterGI Issue 选择能反映具体功能模块、子系统或使用场景的标签,例如一条龙、调度器、脚本、设置项、地图追踪;避免只选择宽泛的流程或状态标签。 + sourceRepository: + owner: babalae + repo: better-genshin-impact + aiHelp: + enabled: true + triggerLabels: [] + commentAnchor: issue-bot:ai + projectContext: + enabled: true + includeRepositoryMetadata: true + includeReadme: true + readmeMaxChars: 3000 + profile: + name: BetterGI + aliases: + - BGI + - Better Genshin Impact + summary: BetterGI is a desktop automation assistant for Genshin Impact. + techStack: + - C# + - WPF + - .NET + commands: + enabled: true + mentions: + - "@bot" + - "@bettergi-repo-bot" + access: collaborators + fix: + enabled: true + commentAnchor: issue-bot:fix + refresh: + enabled: true + +pullRequests: + review: + enabled: false + labeling: + enabled: false + summary: + enabled: false diff --git a/.github/workflows/repo-bot.yml b/.github/workflows/repo-bot.yml new file mode 100644 index 00000000..ce64b5ab --- /dev/null +++ b/.github/workflows/repo-bot.yml @@ -0,0 +1,55 @@ +name: Repo Bot + +on: + issues: + types: + - opened + - edited + - reopened + - labeled + issue_comment: + types: + - created + - edited + +jobs: + repo-bot: + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + steps: + - name: Detect GitHub App configuration + id: auth-mode + shell: bash + env: + REPO_BOT_GITHUB_APP_ID: ${{ vars.REPO_BOT_GITHUB_APP_ID }} + REPO_BOT_GITHUB_APP_PRIVATE_KEY: ${{ secrets.REPO_BOT_GITHUB_APP_PRIVATE_KEY }} + run: | + if [ -n "$REPO_BOT_GITHUB_APP_ID" ] && [ -n "$REPO_BOT_GITHUB_APP_PRIVATE_KEY" ]; then + echo "use_app=true" >> "$GITHUB_OUTPUT" + else + echo "use_app=false" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create GitHub App token + if: ${{ steps.auth-mode.outputs.use_app == 'true' }} + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ vars.REPO_BOT_GITHUB_APP_ID }} + private-key: ${{ secrets.REPO_BOT_GITHUB_APP_PRIVATE_KEY }} + + - name: Run Repo Bot + uses: ddaodan/bettergi-github-bot@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + REPO_BOT_GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + REPO_BOT_AI_API_KEY: ${{ secrets.REPO_BOT_AI_API_KEY }} + REPO_BOT_AI_BASE_URL: ${{ vars.REPO_BOT_AI_BASE_URL }} + with: + config-path: .github/repo-bot.yml + config-overrides-json: ${{ vars.REPO_BOT_CONFIG_OVERRIDES_JSON }} From 53810d34b8f82a6f6d7b99821fe1765745ef0038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=E4=BA=91?= Date: Tue, 17 Mar 2026 20:12:48 +0800 Subject: [PATCH 057/107] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96TpTask?= =?UTF-8?q?=E6=83=AF=E6=80=A7=E5=AF=BC=E8=88=AA=EF=BC=88=E8=AF=AF=E8=AF=86?= =?UTF-8?q?=E5=88=AB=EF=BC=89=EF=BC=8C=E5=A4=84=E7=90=86=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E6=9C=AA=E8=AF=86=E5=88=AB=20(#2920)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoTrackPath/TpTask.cs | 127 ++++++++++++++++-- 1 file changed, 113 insertions(+), 14 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs index 121c61cd..b8921f7d 100644 --- a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs +++ b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs @@ -496,10 +496,54 @@ public class TpTask { mapCenterPoint = GetPositionFromBigMap(mapName); // 初始中心 } - catch (Exception e) + catch (MapPositionNotRecognizedException) { - ++exceptionTimes; - mapCenterPoint = new Point2f(0f, 0f); // 其他恰当的初始值? + Logger.LogDebug("初始中心点识别失败,开启自救策略"); + // 判断当前缩放是否离最佳识别缩放(4.4)较远,如果是,则先调整到最佳视角尝试 + if (_tpConfig.MapZoomEnabled && Math.Abs(currentZoomLevel - DisplayTpPointZoomLevel) > 0.3) + { + await AdjustMapZoomLevel(currentZoomLevel, DisplayTpPointZoomLevel); + currentZoomLevel = DisplayTpPointZoomLevel; + await Delay(300, ct); + + try + { + mapCenterPoint = GetPositionFromBigMap(mapName); + Logger.LogDebug("调整缩放后识别恢复成功"); + } + catch (MapPositionNotRecognizedException) + { + Logger.LogDebug("缩放后依然失败,尝试强制跃迁..."); + await ForceJumpToTargetArea(x, y, mapName); + await Delay(300, ct); + + try + { + mapCenterPoint = GetPositionFromBigMap(mapName); + Logger.LogDebug("强制切换区域后识别恢复成功"); + } + catch (MapPositionNotRecognizedException ex) + { + throw new Exception("所有脱困策略均失效,无法获取初始点", ex); + } + } + } + else + { + Logger.LogDebug("缩放已在最佳区间附近,直接尝试强制跃迁..."); + await ForceJumpToTargetArea(x, y, mapName); + await Delay(300, ct); + + try + { + mapCenterPoint = GetPositionFromBigMap(mapName); + Logger.LogDebug("强制切换区域后识别恢复成功"); + } + catch (MapPositionNotRecognizedException ex) + { + throw new Exception("初始识别失败且切换区域后依然无效", ex); + } + } } var (xOffset, yOffset) = (x - mapCenterPoint.X, y - mapCenterPoint.Y); @@ -556,21 +600,41 @@ public class TpTask int moveSteps = Math.Max((int)moveMouseLength / 10, 3); // 每次移动的步数最小为 3,避免除 0 错误 await MouseMoveMap(moveMouseX, moveMouseY, moveSteps); + + // 推算理论上的移动后坐标 (惯性预测) + Point2f predictedPoint = mapCenterPoint + new Point2f( + (float)(moveMouseX * currentZoomLevel / _tpConfig.MapScaleFactor), + (float)(moveMouseY * currentZoomLevel / _tpConfig.MapScaleFactor)); + try { - exceptionTimes = 0; - mapCenterPoint = GetPositionFromBigMap(mapName); // 随循环更新的地图中心 - } - catch (Exception) - { - if (++exceptionTimes > 2) + var newCenterPoint = GetPositionFromBigMap(mapName); // 随循环更新的地图中心 + + // 计算识别坐标与预测坐标的偏差 + double jumpDistance = Math.Sqrt(Math.Pow(newCenterPoint.X - predictedPoint.X, 2) + Math.Pow(newCenterPoint.Y - predictedPoint.Y, 2)); + double expectedMoveLen = Math.Sqrt(moveMouseX * moveMouseX + moveMouseY * moveMouseY) * currentZoomLevel / _tpConfig.MapScaleFactor; + + // 如果实际识别坐标产生超出物理可能的远距离跳跃 (比如原本只移动了50单位,但是坐标跳跃了300单位以上) + // 则判定为低特征区域产生的误识别(假阳性),抛出异常进入下面的盲走抓取逻辑 + if (jumpDistance > Math.Max(200, expectedMoveLen * 2)) { - throw new Exception("多次中心点识别失败,重新传送"); + Logger.LogDebug("坐标异常跳跃({dist:0.0}),判定为误识别", jumpDistance); + throw new MapPositionNotRecognizedException("中心点识别坐标异常跳跃"); } - Logger.LogWarning("中心点识别失败,预测移动的距离"); - mapCenterPoint += new Point2f((float)(moveMouseX * currentZoomLevel / _tpConfig.MapScaleFactor), - (float)(moveMouseY * currentZoomLevel / _tpConfig.MapScaleFactor)); + mapCenterPoint = newCenterPoint; + exceptionTimes = 0; + } + catch (MapPositionNotRecognizedException) + { + exceptionTimes++; + if (exceptionTimes > 5) + { + throw new Exception("多次中心点识别失败或异常,惯性推算失效,重新传送"); + } + + Logger.LogDebug("进入盲走推算 (跳过次数: {times})", exceptionTimes); + mapCenterPoint = predictedPoint; } (xOffset, yOffset) = (x - mapCenterPoint.X, y - mapCenterPoint.Y); @@ -784,7 +848,7 @@ public class TpTask var p = MapManager.GetMap(mapName, _mapMatchingMethod).GetBigMapPosition(ra.CacheGreyMat); if (p.IsEmpty()) { - throw new InvalidOperationException("识别大地图位置失败"); + throw new MapPositionNotRecognizedException("大地图特征点匹配识别位置失败"); } Debug.WriteLine("识别大地图在全地图位置:" + p); @@ -803,6 +867,36 @@ public class TpTask } } + /// + /// 当无法获取当前位置时,直接根据目标坐标强制计算并跃迁到对应区域的地图 + /// + private async Task ForceJumpToTargetArea(double x, double y, string mapName) + { + if (mapName == MapTypes.Teyvat.ToString()) + { + string targetCountry = "当前位置"; + double minDistance = double.MaxValue; + foreach (var (country, position) in MapLazyAssets.Instance.CountryPositions) + { + var distance = Math.Sqrt(Math.Pow(position[0] - x, 2) + Math.Pow(position[1] - y, 2)); + if (distance < minDistance) + { + minDistance = distance; + targetCountry = country; + } + } + + if (targetCountry != "当前位置") + { + await SwitchArea(targetCountry); + } + } + else + { + await SwitchArea(MapTypesExtensions.ParseFromName(mapName).GetDescription()); + } + } + /// /// 获取最接近的N个传送点坐标和所处区域 /// @@ -1040,3 +1134,8 @@ public class TpTask return (-5 * s) + 6; } } + +public class MapPositionNotRecognizedException : Exception +{ + public MapPositionNotRecognizedException(string message) : base(message) { } +} From c553a49f1854def1b4b775a5e3a82dcd47d76292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Wed, 18 Mar 2026 00:29:16 +0800 Subject: [PATCH 058/107] Update repo-bot.yml --- .github/repo-bot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/repo-bot.yml b/.github/repo-bot.yml index c4bd5745..e2105fc7 100644 --- a/.github/repo-bot.yml +++ b/.github/repo-bot.yml @@ -6,7 +6,7 @@ providers: openAiCompatible: enabled: true baseUrl: https://api.openai.com/v1 - model: gpt-5.4 + model: gpt-5.2-codex apiStyle: responses timeoutMs: 30000 From e46076eaaa9d96c2391b154b450a311c4fe82f06 Mon Sep 17 00:00:00 2001 From: ddaodan <40017293+ddaodan@users.noreply.github.com> Date: Thu, 19 Mar 2026 00:16:12 +0800 Subject: [PATCH 059/107] =?UTF-8?q?chore:=20=E8=87=AA=E5=8A=A8=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=E5=8E=86=E5=8F=B2=20Issue=20(#2931)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/repo-bot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/repo-bot.yml b/.github/repo-bot.yml index e2105fc7..0e648c0e 100644 --- a/.github/repo-bot.yml +++ b/.github/repo-bot.yml @@ -11,6 +11,8 @@ providers: timeoutMs: 30000 issues: + autoProcessing: + skipCreatedBefore: auto validation: enabled: true fallbackTemplateKey: normal From 5831e3671c72862e87c321b223f24dcdec52115a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 21 Mar 2026 12:31:41 +0800 Subject: [PATCH 060/107] =?UTF-8?q?=E5=8A=A0=E5=A4=A7=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E5=8C=BA=E5=88=A4=E5=AE=9A=E6=AC=A1=E6=95=B0=20#2936?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs b/BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs index 6299a9f0..228d4e4f 100644 --- a/BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs +++ b/BetterGenshinImpact/GameTask/Common/Job/GoToSereniteaPotTask.cs @@ -591,7 +591,7 @@ internal class GoToSereniteaPotTask await Delay(1000, ct); } - var quitOption = await _chooseTalkOptionTask.SingleSelectText(this.ayuanByeString, ct); + var quitOption = await _chooseTalkOptionTask.SingleSelectText(this.ayuanByeString, ct, skipTimes: 20); if (quitOption != TalkOptionRes.FoundAndClick) { if (!Bv.IsInMainUi(CaptureToRectArea())) From eedce17a9c18825de40ef7f41b7c82606c3e0aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 21 Mar 2026 12:55:54 +0800 Subject: [PATCH 061/107] chore: update BetterGI.Assets.Other package version to 1.0.15 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index ab58203e..f405acb1 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -45,7 +45,7 @@ - + From 713589734b2f055a8877ebad088eecac94cd93d1 Mon Sep 17 00:00:00 2001 From: Tristan <202200201039@mail.sdu.edu.cn> Date: Sat, 21 Mar 2026 16:37:29 +0800 Subject: [PATCH 062/107] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E5=9C=B0?= =?UTF-8?q?=E8=84=89=E8=8A=B1=E6=94=AF=E6=8C=81=E9=A2=86=E5=A5=96=E5=90=8E?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E6=8E=89=E8=90=BD=E7=89=A9=20(#2928)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoLeyLineOutcropConfig.cs | 12 ++++ .../AutoLeyLineOutcropParam.cs | 11 ++++ .../AutoLeyLineOutcropTask.cs | 42 ++++++++++++++ .../View/Pages/TaskSettingsPage.xaml | 56 +++++++++++++++++++ .../Pages/TaskSettingsPageViewModel.cs | 56 +++++++++++++++++++ 5 files changed, 177 insertions(+) diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs index 79b3ec8f..e2b56371 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropConfig.cs @@ -51,6 +51,18 @@ public partial class AutoLeyLineOutcropConfig : ObservableObject [ObservableProperty] private bool _isGoToSynthesizer = false; + /// + /// 是否在领取地脉花奖励后扫描周围掉落物光柱。 + /// + [ObservableProperty] + private bool _scanDropsAfterRewardEnabled = true; + + /// + /// 领取奖励后扫描掉落物的最长时长,单位为秒。 + /// + [ObservableProperty] + private int _scanDropsAfterRewardSeconds = 12; + [ObservableProperty] private AutoLeyLineOutcropFightConfig _fightConfig = new(); diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs index 527282d6..6fc76269 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropParam.cs @@ -33,6 +33,15 @@ public class AutoLeyLineOutcropParam:BaseTaskParam public bool UseTransientResin { get; set; } //通过BGI通知系统发送详细通知 public bool IsNotification { get; set; } + /// + /// 是否在领取奖励后扫描掉落物光柱。 + /// + public bool ScanDropsAfterRewardEnabled { get; set; } + + /// + /// 领取奖励后扫描掉落物光柱的最长时长,单位为秒。 + /// + public int ScanDropsAfterRewardSeconds { get; set; } public void SetDefault() { @@ -53,6 +62,8 @@ public class AutoLeyLineOutcropParam:BaseTaskParam UseFragileResin= config.UseFragileResin; UseTransientResin= config.UseTransientResin; IsNotification= config.IsNotification; + ScanDropsAfterRewardEnabled = config.ScanDropsAfterRewardEnabled; + ScanDropsAfterRewardSeconds = config.ScanDropsAfterRewardSeconds; Count = config.Count; Country = config.Country; LeyLineOutcropType = config.LeyLineOutcropType; diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs index fc6dfa6d..3b504ead 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs @@ -684,6 +684,7 @@ public class AutoLeyLineOutcropTask : ISoloTask throw new Exception("无法领取奖励"); } + await TryScanDropsAfterReward(); _consecutiveFailureCount = 0; } @@ -992,6 +993,47 @@ public class AutoLeyLineOutcropTask : ISoloTask } } + /// + /// 在地脉花奖励领取完成后,短时间扫描周围掉落物光柱并尝试靠近拾取。 + /// + private async Task TryScanDropsAfterReward() + { + if (!_taskParam.ScanDropsAfterRewardEnabled) + { + return; + } + + const int maxScanSeconds = 60; + var scanSeconds = Math.Clamp(_taskParam.ScanDropsAfterRewardSeconds, 0, maxScanSeconds); + if (scanSeconds <= 0) + { + return; + } + + var autoFightConfig = TaskContext.Instance().Config.AutoFightConfig; + var originalSeconds = autoFightConfig.PickDropsAfterFightSeconds; + + try + { + autoFightConfig.PickDropsAfterFightSeconds = scanSeconds; + _logger.LogInformation("领取奖励后开始扫描掉落物光柱,时长 {Seconds} 秒", scanSeconds); + await new ScanPickTask().Start(_ct); + } + catch (Exception ex) when (ex is OperationCanceledException or TaskCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.LogDebug(ex, "领取奖励后扫描掉落物光柱异常"); + } + finally + { + autoFightConfig.PickDropsAfterFightSeconds = originalSeconds; + Simulation.ReleaseAllKey(); + } + } + private async Task TryKazuhaCollect(Avatar kazuha) { _logger.LogInformation("战后聚集拾取:开始使用枫原万叶执行长E拾取"); diff --git a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml index 4a9a0291..9508881b 100644 --- a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml @@ -2608,6 +2608,62 @@ Text="二次拾取" TextWrapping="Wrap" /> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs index 3c6d2a8a..c5822c74 100644 --- a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs @@ -149,6 +149,9 @@ public partial class TaskSettingsPageViewModel : ViewModel [ObservableProperty] private string _switchAutoLeyLineOutcropButtonText = "启动"; + [ObservableProperty] + private bool _scanDropsAfterRewardEnabledUi; + [ObservableProperty] private FrozenDictionary _fishingTimePolicyDict = Enum.GetValues(typeof(FishingTimePolicy)) .Cast() @@ -160,6 +163,8 @@ public partial class TaskSettingsPageViewModel : ViewModel .Description ?? e.ToString()); private bool saveScreenshotOnKeyTick; + private bool _suppressScanDropsAfterRewardPrompt; + private int _scanDropsAfterRewardPromptVersion; public bool SaveScreenshotOnKeyTick { get => Config.CommonConfig.ScreenshotEnabled && saveScreenshotOnKeyTick; @@ -208,6 +213,7 @@ public partial class TaskSettingsPageViewModel : ViewModel _navigationService = navigationService; _taskDispatcher = taskTriggerDispatcher; NormalizeLeyLineOutcropType(); + _scanDropsAfterRewardEnabledUi = Config.AutoLeyLineOutcropConfig.ScanDropsAfterRewardEnabled; //_strategyList = LoadCustomScript(Global.Absolute(@"User\AutoGeniusInvokation")); @@ -218,6 +224,56 @@ public partial class TaskSettingsPageViewModel : ViewModel _oneDragonFlowViewModel = new OneDragonFlowViewModel(); } + partial void OnScanDropsAfterRewardEnabledUiChanged(bool value) + { + if (_suppressScanDropsAfterRewardPrompt) + { + return; + } + + if (!value) + { + Interlocked.Increment(ref _scanDropsAfterRewardPromptVersion); + Config.AutoLeyLineOutcropConfig.ScanDropsAfterRewardEnabled = false; + return; + } + + var version = Interlocked.Increment(ref _scanDropsAfterRewardPromptVersion); + _ = ConfirmScanDropsAfterRewardRiskAsync(version); + } + + private async Task ConfirmScanDropsAfterRewardRiskAsync(int version) + { + var messageBox = new Wpf.Ui.Controls.MessageBox + { + Title = "风险提示", + Content = "开启“领取奖励后扫描掉落物光柱”后,角色会在领奖完成后主动移动拾取。部分地脉花点位或特定配队下,可能因为移动范围较大而卡住。\n\n如果你愿意接受这个风险,请继续开启;否则将保持关闭。", + PrimaryButtonText = "接受风险并开启", + CloseButtonText = "不接受,保持关闭", + Owner = Application.Current.MainWindow, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + }; + + var result = await messageBox.ShowDialogAsync(); + var accepted = result == Wpf.Ui.Controls.MessageBoxResult.Primary; + + if (version != _scanDropsAfterRewardPromptVersion) + { + return; + } + + _suppressScanDropsAfterRewardPrompt = true; + try + { + ScanDropsAfterRewardEnabledUi = accepted; + Config.AutoLeyLineOutcropConfig.ScanDropsAfterRewardEnabled = accepted; + } + finally + { + _suppressScanDropsAfterRewardPrompt = false; + } + } + private void NormalizeLeyLineOutcropType() { var type = Config.AutoLeyLineOutcropConfig.LeyLineOutcropType; From beb36ff41acf368d3675917ff86bbba212b84e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Mar 2026 01:50:44 +0800 Subject: [PATCH 063/107] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE=E9=81=AE=E7=BD=A9=E5=85=B3=E9=97=AD=E6=97=B6UI?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=9C=AA=E6=AD=A3=E7=A1=AE=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=20#2940?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当关闭地图遮罩功能时,确保所有挂起的计算任务被清理,并重置UI状态到初始值。同时修复了在配置禁用时UI状态未同步更新的问题。 --- .../GameTask/MapMask/MapMaskTrigger.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs b/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs index d24d5ed3..f8eba132 100644 --- a/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs +++ b/BetterGenshinImpact/GameTask/MapMask/MapMaskTrigger.cs @@ -87,14 +87,25 @@ public class MapMaskTrigger : ITaskTrigger // 关闭时隐藏UI if (!IsEnabled) { + var pendingBigMapCompute = Interlocked.Exchange(ref _pendingBigMapCompute, null); + pendingBigMapCompute?.Dispose(); + var pendingMiniMapCompute = Interlocked.Exchange(ref _pendingMiniMapCompute, null); + pendingMiniMapCompute?.Dispose(); + + Interlocked.Exchange(ref _pendingUiUpdate, null); + UIDispatcherHelper.BeginInvoke(() => { if (MaskWindow.InstanceNullable() != null) { - if (MaskWindow.Instance().DataContext is MaskWindowViewModel vm) + var window = MaskWindow.Instance(); + if (window.DataContext is MaskWindowViewModel vm) { vm.IsInBigMapUi = false; } + + window.PointsCanvasControl.UpdateViewport(0, 0, 0, 0); + window.MiniMapPointsCanvasControl.UpdateViewport(0, 0, 0, 0); } }); } @@ -396,6 +407,19 @@ public class MapMaskTrigger : ITaskTrigger if (update != null) { var window = MaskWindow.Instance(); + if (!_config.Enabled) + { + if (window.DataContext is MaskWindowViewModel vmWhenDisabled) + { + vmWhenDisabled.IsInBigMapUi = false; + } + + window.PointsCanvasControl.UpdateViewport(0, 0, 0, 0); + window.MiniMapPointsCanvasControl.UpdateViewport(0, 0, 0, 0); + Interlocked.Exchange(ref _uiApplyScheduled, 0); + return; + } + if (update.IsInBigMapUi is { } isInBigMapUi && window.DataContext is MaskWindowViewModel vm) { vm.IsInBigMapUi = isInBigMapUi; From b0b13cb36d5a5fd24b5770f356ec7d044a9b88c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Mar 2026 01:52:54 +0800 Subject: [PATCH 064/107] =?UTF-8?q?=E5=88=A0=E9=99=A4=20Starward=20?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E6=B3=A8=E5=86=8C=20=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs b/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs index 219ad711..bedd7a96 100644 --- a/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs +++ b/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs @@ -150,7 +150,7 @@ public class GameLoadingTrigger : ITaskTrigger } else { - TaskControl.Logger.LogWarning("没有检测到 Starward 协议注册,请查看帮助文档!"); + // TaskControl.Logger.LogWarning("没有检测到 Starward 协议注册,请查看帮助文档!"); return false; } } From cd978327fca24582ed21d21a63f4d62ca6a493ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Mar 2026 02:13:55 +0800 Subject: [PATCH 065/107] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=9C=80?= =?UTF-8?q?=E5=B0=8F=E5=8C=96=E6=97=B6=E5=80=99=E6=88=AA=E5=9B=BE=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E5=AF=BC=E8=87=B4=E6=88=AA=E5=9B=BE=E5=99=A8=E6=B2=A1?= =?UTF-8?q?=E6=B3=95=E6=AD=A3=E5=B8=B8=E6=88=AA=E5=9B=BE=EF=BC=8C=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E4=B8=BB=E7=95=8C=E9=9D=A2=E5=8D=A1=E9=A1=BF=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=20#2939=20=20#2851?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/SystemControl.cs | 5 +++++ BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/BetterGenshinImpact/GameTask/SystemControl.cs b/BetterGenshinImpact/GameTask/SystemControl.cs index e2ad99d8..43539eb0 100644 --- a/BetterGenshinImpact/GameTask/SystemControl.cs +++ b/BetterGenshinImpact/GameTask/SystemControl.cs @@ -90,6 +90,11 @@ public class SystemControl return hWnd == TaskContext.Instance().GameHandle; } + public static bool IsGenshinImpactMinimized() + { + return User32.IsIconic(TaskContext.Instance().GameHandle); + } + public static nint GetForegroundWindowHandle() { return (nint)User32.GetForegroundWindow(); diff --git a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs index 41e7e5f8..bc8eb7f2 100644 --- a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs +++ b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs @@ -235,6 +235,13 @@ namespace BetterGenshinImpact.GameTask maskWindow.Invoke(maskWindow.HideSelf); return; } + + // 如果是最小化状态,直接不进行截图 + if (SystemControl.IsGenshinImpactMinimized()) + { + PictureInPictureService.Hide(); + return; + } // 检查游戏是否在前台 var hasBackgroundTriggerToRun = false; From c8a8d6217a9ee36a869ae8e1a79cdcef2a263a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Mar 2026 02:48:02 +0800 Subject: [PATCH 066/107] =?UTF-8?q?=E6=A3=80=E6=B5=8B=E5=88=B0=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E5=86=85=E5=B0=8F=E5=9C=B0=E5=9B=BE=E9=94=81=E5=AE=9A?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=20#1371?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Genshin/Settings2/GameSettingsChecker.cs | 9 +++++++-- .../Genshin/Settings2/GenshinGameSettings.cs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs b/BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs index 9faf8098..c7c3ab90 100644 --- a/BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs +++ b/BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs @@ -1,4 +1,4 @@ -using System; +using System; using BetterGenshinImpact.GameTask.Common; using BetterGenshinImpact.Genshin.Settings; using Microsoft.Extensions.Logging; @@ -37,6 +37,11 @@ public class GameSettingsChecker TaskControl.Logger.LogError("检测到游戏亮度非默认值,将会影响功能正常使用,请在原神 游戏设置——图像——亮度 中恢复默认亮度!"); } + if (settings.MiniMapConfig != 1) + { + TaskControl.Logger.LogWarning("检测到游戏小地图锁定配置不是【锁定方向】!,无法正常使用地图追踪功能。请在原神 游戏设置——其他——小地图锁定 中调整为【锁定方向】!"); + } + if (inputSettings.MouseSenseIndex != 2 || inputSettings.MouseSenseIndexY != 2 || inputSettings.MouseFocusSenseIndex != 2 @@ -59,4 +64,4 @@ public class GameSettingsChecker TaskControl.Logger.LogDebug(e, "获取原神游戏设置失败"); } } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/Genshin/Settings2/GenshinGameSettings.cs b/BetterGenshinImpact/Genshin/Settings2/GenshinGameSettings.cs index e154d362..7b27dd08 100644 --- a/BetterGenshinImpact/Genshin/Settings2/GenshinGameSettings.cs +++ b/BetterGenshinImpact/Genshin/Settings2/GenshinGameSettings.cs @@ -53,7 +53,7 @@ public class GenshinGameSettings public string GlobalPerfData { get; set; } // 全局性能数据 [JsonProperty("miniMapConfig")] - public int MiniMapConfig { get; set; } // 小地图配置 + public int MiniMapConfig { get; set; } // 小地图锁定 0:锁定玩家视角 1:锁定方向 [JsonProperty("enableCameraSlope")] public bool EnableCameraSlope { get; set; } // 启用相机坡度 From 99f4f54ff7c60085a5f7b8e279de19dee357759c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Mar 2026 02:51:56 +0800 Subject: [PATCH 067/107] =?UTF-8?q?=E6=96=87=E6=A1=88=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs b/BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs index c7c3ab90..fbebed02 100644 --- a/BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs +++ b/BetterGenshinImpact/Genshin/Settings2/GameSettingsChecker.cs @@ -39,7 +39,7 @@ public class GameSettingsChecker if (settings.MiniMapConfig != 1) { - TaskControl.Logger.LogWarning("检测到游戏小地图锁定配置不是【锁定方向】!,无法正常使用地图追踪功能。请在原神 游戏设置——其他——小地图锁定 中调整为【锁定方向】!"); + TaskControl.Logger.LogWarning("检测到游戏小地图锁定配置不是【锁定方向】,无法正常使用地图追踪功能。请在原神 游戏设置——其他——小地图锁定 中调整为【锁定方向】!"); } if (inputSettings.MouseSenseIndex != 2 From a0d81335581bfa13e8f34056382aa9bfcf8ea712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Mar 2026 14:14:49 +0800 Subject: [PATCH 068/107] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=9A=84=E5=90=AF=E5=8A=A8=E6=B8=B8=E6=88=8F=E9=80=BB=E8=BE=91?= =?UTF-8?q?=20#2943=20#2942?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs index 97413ba3..37eb5679 100644 --- a/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/OneDragonFlowViewModel.cs @@ -587,9 +587,7 @@ public partial class OneDragonFlowViewModel : ViewModel int finishTaskcount = 1; int enabledTaskCountall = SelectedConfig.TaskEnabledList.Count(t => t.Value); _logger.LogInformation($"启用任务总数量: {enabledTaskCountall}"); - - await ScriptService.StartGameTask(); - + ReadScriptGroup(); foreach (var task in ScriptGroupsdefault) { From b4053e9357354d4c55cdb30dbfbc579dc474379b Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Sun, 22 Mar 2026 06:53:31 +0000 Subject: [PATCH 069/107] Update version to 0.58.3-alpha.4 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index f405acb1..2f08b17c 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.58.3-alpha.3 + 0.58.3-alpha.4 false WinExe net8.0-windows10.0.22621.0 From 955d7d51d92a26dc845ff73a79e7c61b02e34863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Mar 2026 15:20:45 +0800 Subject: [PATCH 070/107] Disable AI classification in repo-bot configuration --- .github/repo-bot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/repo-bot.yml b/.github/repo-bot.yml index 0e648c0e..431e2ac6 100644 --- a/.github/repo-bot.yml +++ b/.github/repo-bot.yml @@ -166,7 +166,7 @@ issues: description: 已存在相同或高度相似的问题。 keywordRules: [] aiClassification: - enabled: true + enabled: false maxLabels: 3 minConfidence: 0.65 include: [] From da6d956ef11f6521a4d14724760462580959d8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Mar 2026 15:26:56 +0800 Subject: [PATCH 071/107] =?UTF-8?q?Comment=20out=20'=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E6=9B=B4=E5=A4=9A=E4=BF=A1=E6=81=AF'=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comment out the '需要更多信息' section in the YAML configuration. --- .github/repo-bot.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/repo-bot.yml b/.github/repo-bot.yml index 431e2ac6..465dda94 100644 --- a/.github/repo-bot.yml +++ b/.github/repo-bot.yml @@ -158,9 +158,9 @@ issues: 问题咨询: color: d876e3 description: 使用问题、求助或咨询。 - 需要更多信息: - color: fbca04 - description: 当前 Issue 缺少必要信息。 + # 需要更多信息: + # color: fbca04 + # description: 当前 Issue 缺少必要信息。 重复: color: cfd3d7 description: 已存在相同或高度相似的问题。 From 1a478affb2391878eb2aef01b4821ba3ed73f7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 22 Mar 2026 15:53:10 +0800 Subject: [PATCH 072/107] =?UTF-8?q?=E5=90=8E=E5=8F=B0=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E5=AE=8C=E4=B9=8B=E5=90=8E=E8=87=AA=E5=8A=A8=E5=B0=86=E5=8E=9F?= =?UTF-8?q?=E7=A5=9E=E5=88=87=E5=88=B0=E5=89=8D=E5=8F=B0=20#2350?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoSkip/AutoSkipConfig.cs | 8 ++++- .../GameTask/AutoSkip/AutoSkipTrigger.cs | 36 +++++++++++++++++++ .../View/Pages/TriggerSettingsPage.xaml | 25 +++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipConfig.cs b/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipConfig.cs index 7369b293..b1b689bf 100644 --- a/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipConfig.cs +++ b/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipConfig.cs @@ -124,6 +124,12 @@ public partial class AutoSkipConfig : ObservableObject /// [ObservableProperty] private bool _runBackgroundEnabled = false; + + /// + /// 后台剧情结束后切回游戏前台 + /// + [ObservableProperty] + private bool _bringGameToFrontAfterBackgroundDialogEnabled = false; /// /// 提交物品 @@ -170,4 +176,4 @@ public enum PictureSourceType { TriggerDispatcher, CaptureLoop -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs b/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs index 951816f0..14f7c792 100644 --- a/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs +++ b/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs @@ -159,6 +159,8 @@ public partial class AutoSkipTrigger : ITaskTrigger private DateTime _prevGetDailyRewardsTime = DateTime.MinValue; private DateTime _prevClickTime = DateTime.MinValue; + private DateTime _prevBringToFrontTime = DateTime.MinValue; + private bool _pendingBringToFront; public void OnCapture(CaptureContent content) { @@ -177,6 +179,15 @@ public partial class AutoSkipTrigger : ITaskTrigger var isPlaying = content.CurrentGameUiCategory == GameUiCategory.Talk || Bv.IsInTalkUi(content.CaptureRectArea); // 播放中 + if (isPlaying && UseBackgroundOperation) + { + _pendingBringToFront = true; + } + else if (!isPlaying) + { + TryBringToFrontAfterBackgroundDialog(); + } + if (!isPlaying && (DateTime.Now - _prevPlayingTime).TotalSeconds <= 5) { // 关闭弹出页 @@ -253,6 +264,31 @@ public partial class AutoSkipTrigger : ITaskTrigger } } + private void TryBringToFrontAfterBackgroundDialog() + { + if (!_config.BringGameToFrontAfterBackgroundDialogEnabled || !_pendingBringToFront) + { + return; + } + + if (SystemControl.IsGenshinImpactActive()) + { + _pendingBringToFront = false; + return; + } + + if ((DateTime.Now - _prevPlayingTime).TotalMilliseconds <= 800 + || (DateTime.Now - _prevBringToFrontTime).TotalSeconds <= 2) + { + return; + } + + _prevBringToFrontTime = DateTime.Now; + SystemControl.ActivateWindow(); + _pendingBringToFront = false; + _logger.LogInformation("自动剧情:后台对话结束,已自动切回游戏前台"); + } + /// /// 黑屏点击判断 /// diff --git a/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml index 4b2b6ba5..c2132133 100644 --- a/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TriggerSettingsPage.xaml @@ -260,6 +260,31 @@ Margin="0,0,8,0" IsChecked="{Binding Config.AutoSkipConfig.RunBackgroundEnabled, Mode=TwoWay}" /> + + + + + + + + + + + + + From d55b1d867b8c2eac7261e90e805617a34eed70ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BA=81=E5=8A=A8=E7=9A=84=E6=B0=A8=E6=B0=94?= <131591012+zaodonganqi@users.noreply.github.com> Date: Mon, 23 Mar 2026 02:04:54 +0800 Subject: [PATCH 073/107] =?UTF-8?q?feat:=20=E5=BC=80=E9=97=A8=E6=97=B6?= =?UTF-8?q?=E9=80=82=E9=BE=84=E6=8F=90=E7=A4=BA=E8=87=AA=E5=8A=A8=E5=85=B3?= =?UTF-8?q?=E9=97=AD=20(#2948)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/GameLoading/GameLoading.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs b/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs index bedd7a96..ff80cdc9 100644 --- a/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs +++ b/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs @@ -35,6 +35,7 @@ public class GameLoadingTrigger : ITaskTrigger public bool IsBackgroundRunning => true; private readonly GameLoadingAssets _assets; + private readonly ElementAssets _elementAssets; private readonly GenshinStartConfig _config = TaskContext.Instance().Config.GenshinStartConfig; private static ILogger _logger = App.GetLogger(); @@ -61,6 +62,7 @@ public class GameLoadingTrigger : ITaskTrigger { GameLoadingAssets.DestroyInstance(); _assets = GameLoadingAssets.Instance; + _elementAssets = ElementAssets.Instance; } public void InnerSetEnabled(bool enabled) @@ -247,6 +249,7 @@ public class GameLoadingTrigger : ITaskTrigger InnerSetEnabled(false); return; } + // 成功进入游戏判断 if (Bv.IsInMainUi(content.CaptureRectArea) || Bv.IsInAnyClosableUi(content.CaptureRectArea) || Bv.IsInDomain(content.CaptureRectArea)) { @@ -254,6 +257,13 @@ public class GameLoadingTrigger : ITaskTrigger InnerSetEnabled(false); return; } + + // 适龄提示窗口自动关闭 + var agePopup = content.CaptureRectArea.Find(_elementAssets.BtnWhiteConfirm); + if (!agePopup.IsEmpty()) + { + agePopup.Click(); + } // B服判断 if (!IsBiliJudged) From aef3fa913b56cc8255be158d3c54db670acec8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Mon, 23 Mar 2026 02:31:14 +0800 Subject: [PATCH 074/107] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=83=B9=E9=A5=AA?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=88=90=E7=8B=AC=E7=AB=8B=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=20(#2949)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/Core/Config/AllConfig.cs | 11 +- .../Core/Config/HotKeyConfig.cs | 7 + .../Core/Script/Dependence/Dispatcher.cs | 4 + .../GameTask/AutoCook/AutoCookConfig.cs | 10 +- .../GameTask/AutoCook/AutoCookTask.cs | 161 +++++++ .../GameTask/AutoCook/AutoCookTrigger.cs | 54 --- .../GameTask/GameTaskManager.cs | 4 +- .../View/Pages/TaskSettingsPage.xaml | 418 ++++++++++-------- .../View/Pages/TriggerSettingsPage.xaml | 27 -- .../ViewModel/Pages/HotKeyPageViewModel.cs | 9 +- .../Pages/TaskSettingsPageViewModel.cs | 17 + 11 files changed, 447 insertions(+), 275 deletions(-) create mode 100644 BetterGenshinImpact/GameTask/AutoCook/AutoCookTask.cs delete mode 100644 BetterGenshinImpact/GameTask/AutoCook/AutoCookTrigger.cs diff --git a/BetterGenshinImpact/Core/Config/AllConfig.cs b/BetterGenshinImpact/Core/Config/AllConfig.cs index ad793b3a..086982a1 100644 --- a/BetterGenshinImpact/Core/Config/AllConfig.cs +++ b/BetterGenshinImpact/Core/Config/AllConfig.cs @@ -1,5 +1,4 @@ using BetterGenshinImpact.GameTask; -using BetterGenshinImpact.GameTask.AutoCook; using BetterGenshinImpact.GameTask.AutoDomain; using BetterGenshinImpact.GameTask.AutoFight; using BetterGenshinImpact.GameTask.AutoFishing; @@ -23,6 +22,7 @@ using BetterGenshinImpact.GameTask.AutoStygianOnslaught; using BetterGenshinImpact.GameTask.GetGridIcons; using BetterGenshinImpact.GameTask.AutoEat; using BetterGenshinImpact.GameTask.AutoLeyLineOutcrop; +using BetterGenshinImpact.GameTask.AutoCook; using BetterGenshinImpact.GameTask.MapMask; using BetterGenshinImpact.GameTask.SkillCd; using BetterGenshinImpact.GameTask.UseRedeemCode; @@ -129,11 +129,6 @@ public partial class AllConfig : ObservableObject /// public QuickTeleportConfig QuickTeleportConfig { get; set; } = new(); - /// - /// 自动烹饪配置 - /// - public AutoCookConfig AutoCookConfig { get; set; } = new(); - /// /// 自动打牌配置 /// @@ -179,6 +174,8 @@ public partial class AllConfig : ObservableObject /// 自动地脉花配置 /// public AutoLeyLineOutcropConfig AutoLeyLineOutcropConfig { get; set; } = new(); + + public AutoCookConfig AutoCookConfig { get; set; } = new(); /// /// 地图遮罩 @@ -269,7 +266,6 @@ public partial class AllConfig : ObservableObject AutoSkipConfig.PropertyChanged += OnAnyPropertyChanged; AutoFishingConfig.PropertyChanged += OnAnyPropertyChanged; QuickTeleportConfig.PropertyChanged += OnAnyPropertyChanged; - AutoCookConfig.PropertyChanged += OnAnyPropertyChanged; MacroConfig.PropertyChanged += OnAnyPropertyChanged; HotKeyConfig.PropertyChanged += OnAnyPropertyChanged; AutoWoodConfig.PropertyChanged += OnAnyPropertyChanged; @@ -280,6 +276,7 @@ public partial class AllConfig : ObservableObject AutoRedeemCodeConfig.PropertyChanged += OnAnyPropertyChanged; AutoEatConfig.PropertyChanged += OnAnyPropertyChanged; AutoLeyLineOutcropConfig.PropertyChanged += OnAnyPropertyChanged; + AutoCookConfig.PropertyChanged += OnAnyPropertyChanged; MapMaskConfig.PropertyChanged += OnAnyPropertyChanged; AutoMusicGameConfig.PropertyChanged += OnAnyPropertyChanged; TpConfig.PropertyChanged += OnAnyPropertyChanged; diff --git a/BetterGenshinImpact/Core/Config/HotKeyConfig.cs b/BetterGenshinImpact/Core/Config/HotKeyConfig.cs index c511e611..a174d6e3 100644 --- a/BetterGenshinImpact/Core/Config/HotKeyConfig.cs +++ b/BetterGenshinImpact/Core/Config/HotKeyConfig.cs @@ -163,6 +163,13 @@ public partial class HotKeyConfig : ObservableObject [ObservableProperty] private string _autoFishingGameHotkeyType = HotKeyTypeEnum.KeyboardMonitor.ToString(); + // 自动烹饪开始/停止 + [ObservableProperty] + private string _autoCookGameHotkey = ""; + + [ObservableProperty] + private string _autoCookGameHotkeyType = HotKeyTypeEnum.KeyboardMonitor.ToString(); + // 自动寻路 [ObservableProperty] private string _autoTrackPathHotkey = ""; diff --git a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs index 5c8f1070..b4a2aca6 100644 --- a/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs +++ b/BetterGenshinImpact/Core/Script/Dependence/Dispatcher.cs @@ -4,6 +4,7 @@ using BetterGenshinImpact.GameTask; using BetterGenshinImpact.GameTask.AutoDomain; using BetterGenshinImpact.GameTask.AutoEat; using BetterGenshinImpact.GameTask.AutoFishing; +using BetterGenshinImpact.GameTask.AutoCook; using BetterGenshinImpact.GameTask.AutoGeniusInvokation; using BetterGenshinImpact.GameTask.AutoPathing.Handler; using BetterGenshinImpact.GameTask.AutoWood; @@ -195,6 +196,9 @@ public class Dispatcher await new AutoFishingTask(AutoFishingTaskParam.BuildFromSoloTaskConfig(soloTask.Config)).Start( cancellationToken); return null; + case "AutoCook": + await new AutoCookTask().Start(cancellationToken); + return null; case "AutoEat": { string? foodName = soloTask.Config == null ? null : ScriptObjectConverter.GetValue((ScriptObject)soloTask.Config, "foodName", (string?)null); diff --git a/BetterGenshinImpact/GameTask/AutoCook/AutoCookConfig.cs b/BetterGenshinImpact/GameTask/AutoCook/AutoCookConfig.cs index 502300ae..9c1e7b3a 100644 --- a/BetterGenshinImpact/GameTask/AutoCook/AutoCookConfig.cs +++ b/BetterGenshinImpact/GameTask/AutoCook/AutoCookConfig.cs @@ -1,17 +1,11 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.ComponentModel; using System; namespace BetterGenshinImpact.GameTask.AutoCook; -/// -///自动烹饪配置 -/// [Serializable] public partial class AutoCookConfig : ObservableObject { - /// - /// 触发器是否启用 - /// [ObservableProperty] - private bool _enabled = false; + private int _checkIntervalMs = 10; } diff --git a/BetterGenshinImpact/GameTask/AutoCook/AutoCookTask.cs b/BetterGenshinImpact/GameTask/AutoCook/AutoCookTask.cs new file mode 100644 index 00000000..57cca2fd --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoCook/AutoCookTask.cs @@ -0,0 +1,161 @@ +using BetterGenshinImpact.Core.Simulator; +using BetterGenshinImpact.GameTask.Common.Element.Assets; +using BetterGenshinImpact.GameTask.Model.Area; +using Microsoft.Extensions.Logging; +using OpenCvSharp; +using System; +using System.Threading; +using System.Threading.Tasks; +using BetterGenshinImpact.GameTask.Common.BgiVision; +using Vanara.PInvoke; +using static BetterGenshinImpact.GameTask.Common.TaskControl; + +namespace BetterGenshinImpact.GameTask.AutoCook; + +public class AutoCookTask : ISoloTask +{ + private readonly ILogger _logger = App.GetLogger(); + private const int UiCheckIntervalMs = 400; + private const int PeakMinCount = 600; // 最小仙跳墙 700 多 + private const int PeakTolerance = 20; + private const int PeakStableFrameCount = 3; + private const int TriggerDropCount = 300; // 正常是 400多 + private static readonly Rect CookColorRect1080P = new(600, 660, 730, 190); + private static readonly Scalar TargetCookColor = new(255, 192, 64); + + public string Name => "自动烹饪"; + + public async Task Start(CancellationToken ct) + { + var assetScale = TaskContext.Instance().SystemInfo.AssetScale; + var checkIntervalMs = Math.Max(1, TaskContext.Instance().Config.AutoCookConfig.CheckIntervalMs); + var peakMinCount = (int)(PeakMinCount * assetScale); + var triggerDropCount = (int)(TriggerDropCount * assetScale); + var cookColorRect = ScaleRect(CookColorRect1080P, assetScale); + _logger.LogInformation("自动烹饪任务启动"); + var lastUiCheckTime = DateTime.MinValue; + var inCookUi = false; + int? peakColorCount = null; + int? peakCandidate = null; + var peakCandidateStableFrames = 0; + + while (!ct.IsCancellationRequested) + { + using var captureRegion = CaptureToRectArea(); + var now = DateTime.UtcNow; + if (!inCookUi || (now - lastUiCheckTime).TotalMilliseconds >= UiCheckIntervalMs) + { + var currentInCookUi = IsInCookUi(captureRegion); + if (currentInCookUi != inCookUi) + { + ResetPeakState(ref peakColorCount, ref peakCandidate, ref peakCandidateStableFrames); + } + + inCookUi = currentInCookUi; + lastUiCheckTime = now; + if (!inCookUi) + { + ResetPeakState(ref peakColorCount, ref peakCandidate, ref peakCandidateStableFrames); + } + else + { + if (Bv.ClickWhiteConfirmButton(captureRegion)) + { + ResetPeakState(ref peakColorCount, ref peakCandidate, ref peakCandidateStableFrames); + _logger.LogInformation("自动烹饪:{Text}", "自动点击确认"); + } + } + } + + if (inCookUi) + { + var currentColorCount = CountTargetColor(captureRegion, cookColorRect); + if (peakColorCount.HasValue) + { + if (currentColorCount <= peakColorCount.Value - triggerDropCount) + { + Simulation.SendInput.Keyboard.KeyPress(User32.VK.VK_SPACE); + _logger.LogInformation("自动烹饪:{Text}", $"烹饪条像素数量较峰值下降超过{triggerDropCount},按下空格。峰值:{peakColorCount.Value} 当前:{currentColorCount}"); + ResetPeakState(ref peakColorCount, ref peakCandidate, ref peakCandidateStableFrames); + } + } + else if (TryBuildPeak(currentColorCount, peakMinCount, ref peakCandidate, ref peakCandidateStableFrames, out var builtPeak)) + { + peakColorCount = builtPeak; + _logger.LogInformation("自动烹饪:{Text}", $"识别到完美烹饪条峰值像素数:{builtPeak}"); + } + } + + await Delay(checkIntervalMs, ct); + } + } + + private static void ResetPeakState(ref int? peakColorCount, ref int? peakCandidate, ref int peakCandidateStableFrames) + { + peakColorCount = null; + peakCandidate = null; + peakCandidateStableFrames = 0; + } + + private static bool TryBuildPeak(int currentColorCount, int peakMinCount, ref int? peakCandidate, ref int peakCandidateStableFrames, out int builtPeak) + { + builtPeak = 0; + if (currentColorCount <= peakMinCount) + { + peakCandidate = null; + peakCandidateStableFrames = 0; + return false; + } + + if (!peakCandidate.HasValue) + { + peakCandidate = currentColorCount; + peakCandidateStableFrames = 1; + return false; + } + + if (Math.Abs(currentColorCount - peakCandidate.Value) <= PeakTolerance) + { + peakCandidate = Math.Max(peakCandidate.Value, currentColorCount); + peakCandidateStableFrames++; + if (peakCandidateStableFrames >= PeakStableFrameCount && peakCandidate.Value > peakMinCount) + { + builtPeak = peakCandidate.Value; + peakCandidate = null; + peakCandidateStableFrames = 0; + return true; + } + + return false; + } + + peakCandidate = currentColorCount; + peakCandidateStableFrames = 1; + return false; + } + + private static Rect ScaleRect(Rect rect, double scale) + { + return new Rect( + (int)(rect.X * scale), + (int)(rect.Y * scale), + (int)(rect.Width * scale), + (int)(rect.Height * scale)); + } + + private bool IsInCookUi(ImageRegion captureRegion) + { + using var cookIcon = captureRegion.Find(ElementAssets.Instance.UiLeftTopCookIcon); + return cookIcon.IsExist(); + } + + private int CountTargetColor(ImageRegion captureRegion, Rect cookColorRect) + { + using var crop = captureRegion.DeriveCrop(cookColorRect); + using var rgb = new Mat(); + using var mask = new Mat(); + Cv2.CvtColor(crop.SrcMat, rgb, ColorConversionCodes.BGR2RGB); + Cv2.InRange(rgb, TargetCookColor, TargetCookColor, mask); + return Cv2.CountNonZero(mask); + } +} diff --git a/BetterGenshinImpact/GameTask/AutoCook/AutoCookTrigger.cs b/BetterGenshinImpact/GameTask/AutoCook/AutoCookTrigger.cs deleted file mode 100644 index 9079787b..00000000 --- a/BetterGenshinImpact/GameTask/AutoCook/AutoCookTrigger.cs +++ /dev/null @@ -1,54 +0,0 @@ -using BetterGenshinImpact.Core.Recognition.OpenCv; -using BetterGenshinImpact.GameTask.Common.Element.Assets; -using Microsoft.Extensions.Logging; -using OpenCvSharp; -using System.Linq; - -namespace BetterGenshinImpact.GameTask.AutoCook; - -public class AutoCookTrigger : ITaskTrigger -{ - private readonly ILogger _logger = App.GetLogger(); - - public string Name => "自动烹饪"; - public bool IsEnabled { get; set; } - public int Priority => 50; - public bool IsExclusive { get; set; } - - public void Init() - { - IsEnabled = TaskContext.Instance().Config.AutoCookConfig.Enabled; - IsExclusive = false; - } - - public void OnCapture(CaptureContent content) - { - // 判断是否处于烹饪界面 - IsExclusive = false; - content.CaptureRectArea.Find(ElementAssets.Instance.UiLeftTopCookIcon, _ => - { - IsExclusive = true; - var captureRect = TaskContext.Instance().SystemInfo.ScaleMax1080PCaptureRect; - using var region = content.CaptureRectArea.DeriveCrop(0, captureRect.Height / 2, captureRect.Width, captureRect.Height / 2); - var perfectBarRects = ContoursHelper.FindSpecifyColorRects(region.SrcMat, new Scalar(255, 192, 64), 0, 8); - if (perfectBarRects.Count >= 2) - { - // 点击烹饪按钮 - var btnList = ContoursHelper.FindSpecifyColorRects(region.SrcMat, new Scalar(255, 255, 192), 12, 12); - if (btnList.Count >= 1) - { - if (btnList.Count > 1) - { - _logger.LogWarning("自动烹饪:{Text}", "识别到多个结束烹饪按钮"); - btnList = [.. btnList.OrderByDescending(r => r.Width)]; - } - var btn = btnList[0]; - var x = btn.X + btn.Width / 2; - var y = btn.Y + btn.Height / 2; - region.ClickTo(x, y); - _logger.LogInformation("自动烹饪:{Text}", "点击结束按钮"); - } - } - }); - } -} diff --git a/BetterGenshinImpact/GameTask/GameTaskManager.cs b/BetterGenshinImpact/GameTask/GameTaskManager.cs index baff5531..674e6add 100644 --- a/BetterGenshinImpact/GameTask/GameTaskManager.cs +++ b/BetterGenshinImpact/GameTask/GameTaskManager.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Recognition.OpenCv; using BetterGenshinImpact.Core.Script.Dependence.Model.TimerConfig; using BetterGenshinImpact.GameTask.AutoFight.Assets; @@ -47,7 +47,6 @@ internal class GameTaskManager TriggerDictionary.TryAdd("QuickTeleport", new QuickTeleport.QuickTeleportTrigger()); TriggerDictionary.TryAdd("AutoSkip", new AutoSkip.AutoSkipTrigger()); TriggerDictionary.TryAdd("AutoFish", new AutoFishing.AutoFishingTrigger()); - TriggerDictionary.TryAdd("AutoCook", new AutoCook.AutoCookTrigger()); TriggerDictionary.TryAdd("AutoEat", new AutoEat.AutoEatTrigger()); TriggerDictionary.TryAdd("MapMask", new MapMaskTrigger()); TriggerDictionary.TryAdd("SkillCd", new SkillCdTrigger()); @@ -123,7 +122,6 @@ internal class GameTaskManager TriggerDictionary.GetValueOrDefault("AutoFish")?.Init(); TriggerDictionary.GetValueOrDefault("QuickTeleport")?.Init(); // TriggerDictionary.GetValueOrDefault("GameLoading")?.Init(); - TriggerDictionary.GetValueOrDefault("AutoCook")?.Init(); TriggerDictionary.GetValueOrDefault("AutoEat")?.Init(); TriggerDictionary.GetValueOrDefault("MapMask")?.Init(); TriggerDictionary.GetValueOrDefault("SkillCd")?.Init(); diff --git a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml index 9508881b..93052f00 100644 --- a/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/TaskSettingsPage.xaml @@ -1,4 +1,4 @@ - --> - - - - - - - - - - - - - - - - 可以自动演奏单个,也可以全自动完成整个专辑 - - - 点击查看使用教程 - - - - - - - - - - - - - - - - - - 进入演奏界面使用,下落模式必须选择垂落模式 - - - 点击查看使用教程 - - - - - - - - - - - - - - - - 进入专辑界面使用,自动演奏未完成乐曲 - - - 点击查看使用教程 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -2940,9 +2784,10 @@ - - - + + + @@ -2956,23 +2801,179 @@ - 自动使用输入的兑换码 + 可以自动演奏单个,也可以全自动完成整个专辑 - + + 点击查看使用教程 + + + + + + + + + + + + + + + + + 进入演奏界面使用,下落模式必须选择垂落模式 - + + 点击查看使用教程 + + + + + + + + + + + + + + + + 进入专辑界面使用,自动演奏未完成乐曲 - + + 点击查看使用教程 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EnableCommand="{Binding SwitchAutoCookCommand}" + EnableContent="{Binding SwitchAutoCookButtonText}" + IsChecked="{Binding SwitchAutoCookEnabled}" /> @@ -2988,23 +2989,27 @@ - + - - + + @@ -3232,6 +3237,69 @@ + + + + + + + + + + + + + + + + 自动使用输入的兑换码 + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - 手动烹饪时,自动在完美状态下结束烹饪(不用的时候请关闭) - - - - - - - + @@ -1211,10 +1212,10 @@ Grid.Column="1" MinWidth="100" Margin="0,0,36,0" - DisplayMemberPath="Item2" ItemsSource="{Binding ServerTimeZones}" SelectedValue="{Binding Config.OtherConfig.ServerTimeZoneOffset}" - SelectedValuePath="Item1" /> + SelectedValuePath="Item1" + DisplayMemberPath="Item2" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/OcrMatchFallbackServiceTests.cs b/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/OcrMatchFallbackServiceTests.cs deleted file mode 100644 index 3e5dd144..00000000 --- a/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/OcrMatchFallbackServiceTests.cs +++ /dev/null @@ -1,195 +0,0 @@ -using BetterGenshinImpact.Core.Recognition.OCR; -using OpenCvSharp; - -namespace BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests; - -public class OcrMatchFallbackServiceTests -{ - #region LevenshteinDistance - - [Fact] - public void LevenshteinDistance_IdenticalStrings_ReturnsZero() - { - Assert.Equal(0, OcrMatchFallbackService.LevenshteinDistance("abc", "abc")); - } - - [Fact] - public void LevenshteinDistance_EmptyAndNonEmpty_ReturnsLength() - { - Assert.Equal(3, OcrMatchFallbackService.LevenshteinDistance("", "abc")); - Assert.Equal(3, OcrMatchFallbackService.LevenshteinDistance("abc", "")); - } - - [Fact] - public void LevenshteinDistance_BothEmpty_ReturnsZero() - { - Assert.Equal(0, OcrMatchFallbackService.LevenshteinDistance("", "")); - } - - [Fact] - public void LevenshteinDistance_SingleSubstitution() - { - // "确认" vs "确忍" — 一个字符替换 - Assert.Equal(1, OcrMatchFallbackService.LevenshteinDistance("确认", "确忍")); - } - - [Fact] - public void LevenshteinDistance_Insertion() - { - Assert.Equal(1, OcrMatchFallbackService.LevenshteinDistance("ac", "abc")); - } - - [Fact] - public void LevenshteinDistance_Deletion() - { - Assert.Equal(1, OcrMatchFallbackService.LevenshteinDistance("abc", "ac")); - } - - [Fact] - public void LevenshteinDistance_CompletelyDifferent() - { - Assert.Equal(3, OcrMatchFallbackService.LevenshteinDistance("abc", "xyz")); - } - - #endregion - - #region ComputeTextSimilarity - - [Fact] - public void ComputeTextSimilarity_ExactMatch_ReturnsOne() - { - Assert.Equal(1.0, OcrMatchFallbackService.ComputeTextSimilarity("确认", "确认")); - } - - [Fact] - public void ComputeTextSimilarity_TextContainsTarget_ReturnsOne() - { - // "确认购买" 包含 "确认" - Assert.Equal(1.0, OcrMatchFallbackService.ComputeTextSimilarity("确认购买", "确认")); - } - - [Fact] - public void ComputeTextSimilarity_TargetContainsText_ReturnsRatio() - { - // "确认" 被 "确认购买" 包含,长度比 = 2/4 - Assert.Equal(0.5, OcrMatchFallbackService.ComputeTextSimilarity("确认", "确认购买")); - } - - [Fact] - public void ComputeTextSimilarity_EmptyTarget_ReturnsOne() - { - Assert.Equal(1.0, OcrMatchFallbackService.ComputeTextSimilarity("任意文字", "")); - } - - [Fact] - public void ComputeTextSimilarity_EmptyText_ReturnsZero() - { - Assert.Equal(0.0, OcrMatchFallbackService.ComputeTextSimilarity("", "确认")); - } - - [Fact] - public void ComputeTextSimilarity_SingleCharDifference() - { - // "确忍" vs "确认" — 距离1, 最大长度2, 相似度 = 1 - 1/2 = 0.5 - Assert.Equal(0.5, OcrMatchFallbackService.ComputeTextSimilarity("确忍", "确认")); - } - - [Fact] - public void ComputeTextSimilarity_CompletelyDifferent_ReturnsZero() - { - // 完全不同的字符串 - Assert.Equal(0.0, OcrMatchFallbackService.ComputeTextSimilarity("甲乙", "丙丁")); - } - - #endregion - - #region OcrMatch / OcrMatchDirect 集成测试(使用 FakeOcrService) - - [Fact] - public void OcrMatch_WhenRegionContainsTarget_ReturnsOne() - { - var fakeOcr = new FakeOcrService(new OcrResult([ - new OcrResultRegion(default, "确认购买", 0.9f) - ])); - var sut = new OcrMatchFallbackService(fakeOcr); - - using var mat = new Mat(50, 200, MatType.CV_8UC3, Scalar.White); - var score = sut.OcrMatch(mat, "确认"); - - Assert.Equal(1.0, score); - } - - [Fact] - public void OcrMatch_MultipleRegions_ReturnsBestScore() - { - var fakeOcr = new FakeOcrService(new OcrResult([ - new OcrResultRegion(default, "其他文字", 0.9f), - new OcrResultRegion(default, "确认", 0.9f) - ])); - var sut = new OcrMatchFallbackService(fakeOcr); - - using var mat = new Mat(50, 200, MatType.CV_8UC3, Scalar.White); - var score = sut.OcrMatch(mat, "确认"); - - Assert.Equal(1.0, score); - } - - [Fact] - public void OcrMatch_NoRegions_ReturnsZero() - { - var fakeOcr = new FakeOcrService(new OcrResult([])); - var sut = new OcrMatchFallbackService(fakeOcr); - - using var mat = new Mat(50, 200, MatType.CV_8UC3, Scalar.White); - var score = sut.OcrMatch(mat, "确认"); - - Assert.Equal(0.0, score); - } - - [Fact] - public void OcrMatchDirect_ExactMatch_ReturnsOne() - { - var fakeOcr = new FakeOcrService(ocrWithoutDetectorResult: "确认"); - var sut = new OcrMatchFallbackService(fakeOcr); - - using var mat = new Mat(50, 200, MatType.CV_8UC3, Scalar.White); - var score = sut.OcrMatchDirect(mat, "确认"); - - Assert.Equal(1.0, score); - } - - [Fact] - public void OcrMatchDirect_PartialMatch_ReturnsPartialScore() - { - var fakeOcr = new FakeOcrService(ocrWithoutDetectorResult: "确忍"); - var sut = new OcrMatchFallbackService(fakeOcr); - - using var mat = new Mat(50, 200, MatType.CV_8UC3, Scalar.White); - var score = sut.OcrMatchDirect(mat, "确认"); - - Assert.Equal(0.5, score, 0.01); - } - - #endregion - - /// - /// 用于测试 OcrMatchFallbackService 的假 IOcrService。 - /// - private class FakeOcrService : IOcrService - { - private readonly OcrResult? _ocrResult; - private readonly string _ocrWithoutDetectorResult; - - public FakeOcrService(OcrResult? ocrResult = null, string ocrWithoutDetectorResult = "") - { - _ocrResult = ocrResult; - _ocrWithoutDetectorResult = ocrWithoutDetectorResult; - } - - public string Ocr(Mat mat) => _ocrResult?.Text ?? ""; - - public string OcrWithoutDetector(Mat mat) => _ocrWithoutDetectorResult; - - public OcrResult OcrResult(Mat mat) => _ocrResult ?? new OcrResult([]); - } -} diff --git a/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/OcrUtilsTests.cs b/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/OcrUtilsTests.cs deleted file mode 100644 index 2e8ae821..00000000 --- a/Test/BetterGenshinImpact.UnitTest/CoreTests/RecognitionTests/OCRTests/OcrUtilsTests.cs +++ /dev/null @@ -1,291 +0,0 @@ -using BetterGenshinImpact.Core.Recognition.OCR.Engine; - -namespace BetterGenshinImpact.UnitTest.CoreTests.RecognitionTests.OCRTests; - -public class OcrUtilsTests -{ - #region CreateLabelDict - - [Fact] - public void CreateLabelDict_SingleCharLabels_MapsCorrectly() - { - // 标签 ["a","b","c"] → a=1, b=2, c=3, " "=4 - IReadOnlyList labels = ["a", "b", "c"]; - var dict = OcrUtils.CreateLabelDict(labels, out var lengths); - - Assert.Equal(1, dict["a"]); - Assert.Equal(2, dict["b"]); - Assert.Equal(3, dict["c"]); - Assert.Equal(4, dict[" "]); - // 所有标签都是长度1,labelLengths = [1] - Assert.Single(lengths); - Assert.Equal(1, lengths[0]); - } - - [Fact] - public void CreateLabelDict_NoZeroLength() - { - // 不应包含长度为0的项(防止无限循环) - IReadOnlyList labels = ["x", "y"]; - OcrUtils.CreateLabelDict(labels, out var lengths); - Assert.DoesNotContain(0, lengths); - } - - [Fact] - public void CreateLabelDict_LengthsDescendingOrder() - { - // 多字节标签时,labelLengths 应降序排列(先试长匹配) - IReadOnlyList labels = ["a", "ab", "b"]; - OcrUtils.CreateLabelDict(labels, out var lengths); - for (var i = 0; i < lengths.Length - 1; i++) - { - Assert.True(lengths[i] >= lengths[i + 1], "labelLengths 应为降序"); - } - } - - #endregion - - #region MapStringToLabelIndices - - [Fact] - public void MapStringToLabelIndices_SimpleMatch() - { - // labels: ["a","b","c"] → a=1, b=2, c=3 - IReadOnlyList labels = ["a", "b", "c"]; - var dict = OcrUtils.CreateLabelDict(labels, out var lengths); - - var result = OcrUtils.MapStringToLabelIndices("abc", dict, lengths); - - Assert.Equal([1, 2, 3], result); - } - - [Fact] - public void MapStringToLabelIndices_SkipsUnknownChars() - { - // "aXb" 中 X 不在标签里,应被跳过 - IReadOnlyList labels = ["a", "b"]; - var dict = OcrUtils.CreateLabelDict(labels, out var lengths); - - var result = OcrUtils.MapStringToLabelIndices("aXb", dict, lengths); - - Assert.Equal([1, 2], result); - } - - [Fact] - public void MapStringToLabelIndices_PrefersLongerMatch() - { - // 标签含 "ab" 和 "a",输入 "ab" 应优先匹配长标签 "ab" - IReadOnlyList labels = ["a", "ab", "b"]; - var dict = OcrUtils.CreateLabelDict(labels, out var lengths); - - var result = OcrUtils.MapStringToLabelIndices("ab", dict, lengths); - - // "ab" 整体匹配为 index 2(labels 中第2个元素) - Assert.Single(result); - Assert.Equal(2, result[0]); - } - - [Fact] - public void MapStringToLabelIndices_EmptyString_ReturnsEmpty() - { - IReadOnlyList labels = ["a", "b"]; - var dict = OcrUtils.CreateLabelDict(labels, out var lengths); - - var result = OcrUtils.MapStringToLabelIndices("", dict, lengths); - - Assert.Empty(result); - } - - [Fact] - public void MapStringToLabelIndices_AllUnknown_ReturnsEmpty() - { - IReadOnlyList labels = ["a", "b"]; - var dict = OcrUtils.CreateLabelDict(labels, out var lengths); - - var result = OcrUtils.MapStringToLabelIndices("XYZ", dict, lengths); - - Assert.Empty(result); - } - - [Fact] - public void MapStringToLabelIndices_SpaceChar_MapsToSpaceIndex() - { - // 空格字符映射到 labels.Count + 1 - IReadOnlyList labels = ["a", "b"]; - var dict = OcrUtils.CreateLabelDict(labels, out var lengths); - - var result = OcrUtils.MapStringToLabelIndices("a b", dict, lengths); - - // a=1, " "=3, b=2 - Assert.Equal([1, 3, 2], result); - } - - #endregion - - #region GetMaxScoreDP - - [Fact] - public void GetMaxScoreDP_PerfectMatch_ReturnsFullScore() - { - // result 中按顺序包含 target 的所有元素,置信度均为 1.0 - (int, float)[] result = [(1, 1.0f), (2, 1.0f), (3, 1.0f)]; - int[] target = [1, 2, 3]; - - var score = OcrUtils.GetMaxScoreDp(result, target, target.Length); - - Assert.Equal(1.0, score); - } - - [Fact] - public void GetMaxScoreDP_NoMatch_ReturnsZero() - { - // result 中不包含 target 的任何元素 - (int, float)[] result = [(4, 1.0f), (5, 1.0f)]; - int[] target = [1, 2]; - - var score = OcrUtils.GetMaxScoreDp(result, target, target.Length); - - Assert.Equal(0, score); - } - - [Fact] - public void GetMaxScoreDP_EmptyTarget_ReturnsZero() - { - (int, float)[] result = [(1, 1.0f)]; - int[] target = []; - - var score = OcrUtils.GetMaxScoreDp(result, target, 1); - - Assert.Equal(0, score); - } - - [Fact] - public void GetMaxScoreDP_PartialMatch_ReturnsZero() - { - // target 需要 [1,2,3],但 result 只有 [1,2],无法完整匹配 - (int, float)[] result = [(1, 1.0f), (2, 1.0f)]; - int[] target = [1, 2, 3]; - - var score = OcrUtils.GetMaxScoreDp(result, target, target.Length); - - Assert.Equal(0, score); - } - - [Fact] - public void GetMaxScoreDP_SubsequenceMatch_SkipsNoise() - { - // result 中有噪声,但子序列 [1,2,3] 可匹配 - (int, float)[] result = [(9, 0.5f), (1, 0.8f), (9, 0.3f), (2, 0.9f), (3, 0.7f)]; - int[] target = [1, 2, 3]; - - var score = OcrUtils.GetMaxScoreDp(result, target, target.Length); - - // (0.8 + 0.9 + 0.7) / 3 = 0.8 - Assert.Equal(0.8, score, 0.01); - } - - [Fact] - public void GetMaxScoreDP_PicksBestConfidence() - { - // target [1],result 中有两个 index=1,应选置信度最高的 - (int, float)[] result = [(1, 0.3f), (1, 0.9f)]; - int[] target = [1]; - - var score = OcrUtils.GetMaxScoreDp(result, target, 1); - - Assert.Equal(0.9, score, 0.01); - } - - [Fact] - public void GetMaxScoreDP_NormalizesWithAvailableCount() - { - // availableCount > target.Length 时分数被稀释 - (int, float)[] result = [(1, 1.0f), (2, 1.0f)]; - int[] target = [1, 2]; - - var score = OcrUtils.GetMaxScoreDp(result, target, 4); - - // (1.0 + 1.0) / 4 = 0.5 - Assert.Equal(0.5, score, 0.01); - } - - [Fact] - public void GetMaxScoreDP_ManyFrames_TargetLengthDenominator_ScoresHigh() - { - // 模拟多个文字区域的字符帧合并后做匹配,分母应为 target.Length - // 即使有很多噪声帧,只要 target 完整匹配,分数仍应很高 - (int, float)[] result = [ - (9, 0.5f), (8, 0.6f), (7, 0.4f), // 噪声区域1 - (1, 0.9f), (2, 0.85f), // 匹配目标 [1,2] - (6, 0.7f), (5, 0.3f), (4, 0.5f), // 噪声区域2 - (9, 0.2f), (8, 0.4f) // 噪声区域3 - ]; - int[] target = [1, 2]; - - // 使用 target.Length 作为分母:(0.9 + 0.85) / 2 = 0.875 - var score = OcrUtils.GetMaxScoreDp(result, target, target.Length); - - Assert.Equal(0.875, score, 0.01); - } - - #endregion - - #region CreateWeights - - [Fact] - public void CreateWeights_DefaultsToOne() - { - IReadOnlyList labels = ["a", "b", "c"]; - var labelDict = OcrUtils.CreateLabelDict(labels, out _); - var weights = OcrUtils.CreateWeights(new Dictionary(), labelDict, labels.Count); - - // labels.Count + 2 = 5 - Assert.Equal(5, weights.Length); - Assert.All(weights, w => Assert.Equal(1.0f, w)); - } - - [Fact] - public void CreateWeights_AppliesExtraWeights() - { - IReadOnlyList labels = ["a", "b", "c"]; - var extra = new Dictionary { { "b", 2.5f } }; - var labelDict = OcrUtils.CreateLabelDict(labels, out _); - - var weights = OcrUtils.CreateWeights(extra, labelDict, labels.Count); - - // "b" 是 labels[1],index=2 - Assert.Equal(1.0f, weights[1]); // "a" - Assert.Equal(2.5f, weights[2]); // "b" - Assert.Equal(1.0f, weights[3]); // "c" - } - - [Fact] - public void CreateWeights_IgnoresUnknownKeys() - { - IReadOnlyList labels = ["a", "b"]; - var extra = new Dictionary { { "z", 5.0f } }; - var labelDict = OcrUtils.CreateLabelDict(labels, out _); - - var weights = OcrUtils.CreateWeights(extra, labelDict, labels.Count); - - Assert.All(weights, w => Assert.Equal(1.0f, w)); - } - - [Fact] - public void CreateWeights_SpaceKey_MapsToCorrectIndex() - { - // 空格权重应写入 labels.Count + 1 位置,与 CreateLabelDict 一致 - IReadOnlyList labels = ["a", " ", "b"]; - var extra = new Dictionary { { " ", 3.0f } }; - var labelDict = OcrUtils.CreateLabelDict(labels, out _); - - var weights = OcrUtils.CreateWeights(extra, labelDict, labels.Count); - - // labels.Count + 1 = 4,空格权重应在 weights[4] - Assert.Equal(3.0f, weights[labels.Count + 1]); - // labels 中 " " 的位置 index=2(即 weights[2])不应被错误写入 - Assert.Equal(1.0f, weights[2]); - } - - #endregion -} diff --git a/Test/BetterGenshinImpact.UnitTest/HelperTests/LruCacheTests.cs b/Test/BetterGenshinImpact.UnitTest/HelperTests/LruCacheTests.cs deleted file mode 100644 index 2bff491f..00000000 --- a/Test/BetterGenshinImpact.UnitTest/HelperTests/LruCacheTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using BetterGenshinImpact.Helpers; - -namespace BetterGenshinImpact.UnitTest.HelperTests; - -public class LruCacheTests -{ - [Fact] - public void BasicSetGetTest() - { - var cache = new CacheHelper.LruCache(3); - cache.Set("a", "1"); - cache.Set("b", "2"); - cache.Set("c", "3"); - Assert.True(cache.TryGet("a", out var v1) && v1 == "1"); - Assert.True(cache.TryGet("b", out var v2) && v2 == "2"); - Assert.True(cache.TryGet("c", out var v3) && v3 == "3"); - } - - [Fact] - public void LruEvictionTest() - { - var cache = new CacheHelper.LruCache(2); - cache.Set("a", "1"); - cache.Set("b", "2"); - cache.Set("c", "3"); // "a" 应被淘汰 - Assert.False(cache.TryGet("a", out _)); - Assert.True(cache.TryGet("b", out var v2) && v2 == "2"); - Assert.True(cache.TryGet("c", out var v3) && v3 == "3"); - } - - [Fact] - public void UpdateMovesToHeadTest() - { - var cache = new CacheHelper.LruCache(2); - cache.Set("a", "1"); - cache.Set("b", "2"); - cache.TryGet("a", out _); // a 变为最新 - cache.Set("c", "3"); // b 应被淘汰 - Assert.True(cache.TryGet("a", out _)); - Assert.False(cache.TryGet("b", out _)); - Assert.True(cache.TryGet("c", out _)); - } - - [Fact] - public void ExpireTest() - { - var cache = new CacheHelper.LruCache(2, TimeSpan.FromMilliseconds(500)); - cache.Set("a", "1"); - Assert.True(cache.TryGet("a", out var v) && v == "1"); - Thread.Sleep(650); - Assert.False(cache.TryGet("a", out _)); - } - - [Fact] - public void RemoveTest() - { - var cache = new CacheHelper.LruCache(2); - cache.Set("a", "1"); - Assert.True(cache.Remove("a")); - Assert.False(cache.TryGet("a", out _)); - } - - [Fact] - public void ClearTest() - { - var cache = new CacheHelper.LruCache(2); - Assert.Equal(0, cache.Count); - cache.Set("a", "1"); - cache.Set("b", "2"); - Assert.Equal(2, cache.Count); - cache.Clear(); - Assert.Equal(0, cache.Count); - } -} From 3f1dc374ebe884cf98fad6331c19c54d306ff517 Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Sat, 28 Mar 2026 16:33:40 +0000 Subject: [PATCH 087/107] Update version to 0.59.2-alpha.1 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index cbe4d02b..df391694 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.59.1 + 0.59.2-alpha.1 false WinExe net8.0-windows10.0.22621.0 From e0c56a74ea92d516f16f7c2a5034ab787f28b0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 29 Mar 2026 01:15:15 +0800 Subject: [PATCH 088/107] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=B0=E7=89=88?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=A7=98=E5=A2=83=E5=A5=96=E5=8A=B1=E9=A2=86?= =?UTF-8?q?=E5=8F=96=E7=9A=84=E9=80=9A=E7=9F=A5=E6=97=B6=E6=9C=BA=EF=BC=8C?= =?UTF-8?q?=E6=88=AA=E5=9B=BE=E8=83=BD=E5=A4=9F=E6=88=AA=E5=88=B0=E5=A5=96?= =?UTF-8?q?=E5=8A=B1=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs index 47465cd1..c18f3135 100644 --- a/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs +++ b/BetterGenshinImpact/GameTask/AutoDomain/AutoDomainTask.cs @@ -208,8 +208,6 @@ public class AutoDomainTask : ISoloTask Logger.LogInformation("体力耗尽或者设置轮次已达标,结束自动秘境"); break; } - - Notify.Event(NotificationEvent.DomainReward).Success("自动秘境奖励领取"); } } @@ -1204,6 +1202,8 @@ public class AutoDomainTask : ISoloTask // 如果没有选择树脂的提示,说明只有原粹树脂 // 继续向下执行 } + + Notify.Event(NotificationEvent.DomainReward).Success("自动秘境奖励领取"); Sleep(1000, _ct); From c5f7a68e794984f07cf917fcee657911c51e44c6 Mon Sep 17 00:00:00 2001 From: ShadowLemoon <119576779+ShadowLemoon@users.noreply.github.com> Date: Sun, 29 Mar 2026 21:45:36 +0800 Subject: [PATCH 089/107] =?UTF-8?q?feat:=20=E9=80=9A=E7=9F=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E6=B7=BB=E5=8A=A0=E8=B6=85=E9=93=BE=E6=8E=A5=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E4=BA=8B=E4=BB=B6id=20(#2976)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Pages/NotificationSettingsPage.xaml | 15 +++++++++++++-- .../Pages/NotificationSettingsPageViewModel.cs | 10 +++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/BetterGenshinImpact/View/Pages/NotificationSettingsPage.xaml b/BetterGenshinImpact/View/Pages/NotificationSettingsPage.xaml index 9b167784..edb69951 100644 --- a/BetterGenshinImpact/View/Pages/NotificationSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/NotificationSettingsPage.xaml @@ -114,8 +114,19 @@ + TextWrapping="Wrap"> + + + + + + + + + Date: Tue, 31 Mar 2026 11:01:02 +0800 Subject: [PATCH 090/107] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=9C=B0=E8=84=89=E8=8A=B1=E9=A2=86=E5=A5=96=E6=B5=81?= =?UTF-8?q?=E8=AF=AF=E5=88=A4=E5=92=8C=E9=98=9F=E4=BC=8D=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E5=9B=9E=E9=80=80=20(#2981)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoLeyLineOutcropTask.cs | 268 +++++++++++++++--- 1 file changed, 221 insertions(+), 47 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs index 5862d86d..99e8288e 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/AutoLeyLineOutcropTask.cs @@ -87,6 +87,7 @@ public class AutoLeyLineOutcropTask : ISoloTask private bool _overlayDisplayTemporarilyEnabled; private bool _overlayDisplayOriginalValue; private DateTime _lastMaskBringTopTime = DateTime.MinValue; + private bool _friendshipTeamSwitched; public string Name => "自动地脉花"; @@ -911,13 +912,34 @@ public class AutoLeyLineOutcropTask : ISoloTask result2Text = result2.Text; _logger.LogDebug("OCR结果: result1='{Text1}', result2='{Text2}'", result1Text, result2Text); - if (result2Text.Contains("之花", StringComparison.Ordinal)) + if (IsLeyLineRewardReadyState(capture, result1Text, result2Text)) { - _logger.LogDebug("识别到地脉之花入口"); + _logger.LogDebug("识别到地脉花领奖状态"); await SwitchToFriendshipTeamIfNeeded(); return true; } + if (ContainsLeyLineFlowerText(result2Text)) + { + _logger.LogDebug("识别到地脉之花入口,尝试接触"); + Simulation.SendInput.SimulateAction(GIActions.PickUpOrInteract); + await Delay(800, _ct); + + using var postInteractCapture = CaptureToRectArea(); + result1 = FindSafe(postInteractCapture, _ocrRo2!); + result2 = FindSafe(postInteractCapture, _ocrRo3!); + result1Text = result1.Text; + result2Text = result2.Text; + _logger.LogDebug("接触后OCR结果: result1='{Text1}', result2='{Text2}'", result1Text, result2Text); + + if (IsLeyLineRewardReadyState(postInteractCapture, result1Text, result2Text)) + { + _logger.LogDebug("接触后识别到地脉花领奖状态"); + await SwitchToFriendshipTeamIfNeeded(); + return true; + } + } + if (result2Text.Contains("溢口", StringComparison.Ordinal)) { _logger.LogDebug("识别到溢口提示,尝试交互"); @@ -1302,8 +1324,19 @@ public class AutoLeyLineOutcropTask : ISoloTask return ContainsFightText(text); } + private bool IsLeyLineRewardReadyState(ImageRegion capture, string result1Text, string result2Text) + { + if (ContainsFightText(result1Text)) + { + return false; + } + + return ContainsRewardPromptActionText(result2Text) || HasRewardPrompt(capture); + } + private static bool ContainsFightText(string text) { + text = NormalizeLeyLineOcrText(text); var keywords = new[] { "打倒", "所有", "敌人" }; return keywords.Any(text.Contains); } @@ -1494,29 +1527,36 @@ public class AutoLeyLineOutcropTask : ISoloTask { if (string.IsNullOrWhiteSpace(_taskParam.FriendshipTeam)) { + _friendshipTeamSwitched = false; return; } Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp); try { - await TrySwitchPartyAndSync(_taskParam.FriendshipTeam); + _friendshipTeamSwitched = await TrySwitchPartyAndSync(_taskParam.FriendshipTeam); + if (!_friendshipTeamSwitched) + { + _logger.LogWarning("切换好感队失败,保持当前队伍"); + } } catch (Exception ex) { + _friendshipTeamSwitched = false; _logger.LogWarning(ex, "切换好感队失败!"); } } private async Task SwitchBackToCombatTeam() { - if (string.IsNullOrWhiteSpace(_taskParam.Team)) + if (!_friendshipTeamSwitched || string.IsNullOrWhiteSpace(_taskParam.Team)) { return; } Simulation.SendInput.SimulateAction(GIActions.MoveForward, KeyType.KeyUp); await TrySwitchPartyAndSync(_taskParam.Team); + _friendshipTeamSwitched = false; } private async Task TrySwitchPartyAndSync(string partyName) @@ -1560,10 +1600,14 @@ public class AutoLeyLineOutcropTask : ISoloTask return false; } - if (!string.IsNullOrWhiteSpace(_taskParam.FriendshipTeam)) + if (_friendshipTeamSwitched) { await SwitchBackToCombatTeam(); } + else if (!string.IsNullOrWhiteSpace(_taskParam.FriendshipTeam)) + { + _logger.LogDebug("本次未成功切换到好感队,跳过切回战斗队"); + } await Delay(1200, _ct); await EnsureExitRewardPage(); @@ -1573,18 +1617,7 @@ public class AutoLeyLineOutcropTask : ISoloTask private async Task VerifyRewardPage() { using var capture = CaptureToRectArea(); - var roi = new Rect(0, 0, capture.Width, capture.Height / 2); - var list = capture.FindMulti(RecognitionObject.Ocr(roi)); - foreach (var res in list) - { - var text = res.Text; - if (text.Contains("激活地脉之花", StringComparison.Ordinal) || text.Contains("选择激活方式", StringComparison.Ordinal)) - { - return true; - } - } - - return false; + return HasRewardPrompt(capture); } private IDisposable DrawOcrOverlayScope(ImageRegion capture, string key, params Rect[] rois) @@ -1728,13 +1761,17 @@ public class AutoLeyLineOutcropTask : ISoloTask return false; } - var isOriginalResinEmpty = promptRegions.Any(r => r.Text.Contains("补充", StringComparison.Ordinal)); - var hasDoubleReward = promptRegions.Any(r => r.Text.Contains("双倍", StringComparison.Ordinal) || r.Text.Contains("2倍产出", StringComparison.Ordinal) || r.Text.Contains("2倍", StringComparison.Ordinal)); - var hasOriginal20 = !isOriginalResinEmpty && promptRegions.Any(r => r.Text.Contains("20", StringComparison.Ordinal) && r.Text.Contains("原粹", StringComparison.Ordinal)); - var hasOriginal40 = !isOriginalResinEmpty && promptRegions.Any(r => r.Text.Contains("40", StringComparison.Ordinal) && r.Text.Contains("原粹", StringComparison.Ordinal)); - var hasCondensed = promptRegions.Any(r => r.Text.Contains("浓缩", StringComparison.Ordinal)); - var hasTransient = promptRegions.Any(r => r.Text.Contains("须臾", StringComparison.Ordinal)); - var hasFragile = promptRegions.Any(r => r.Text.Contains("脆弱", StringComparison.Ordinal)); + var lineTexts = BuildPromptTextLines(promptRegions); + var isOriginalResinEmpty = lineTexts.Any(text => text.Contains("补充", StringComparison.Ordinal)); + var hasDoubleReward = lineTexts.Any(text => text.Contains("双倍", StringComparison.Ordinal) + || text.Contains("2倍产出", StringComparison.Ordinal) + || text.Contains("2倍", StringComparison.Ordinal)); + var originalResinLines = lineTexts.Where(text => text.Contains("原粹", StringComparison.Ordinal)).ToList(); + var hasOriginal20 = !isOriginalResinEmpty && originalResinLines.Any(text => text.Contains("20", StringComparison.Ordinal)); + var hasOriginal40 = !isOriginalResinEmpty && originalResinLines.Any(text => text.Contains("40", StringComparison.Ordinal)); + var hasCondensed = lineTexts.Any(text => text.Contains("浓缩", StringComparison.Ordinal)); + var hasTransient = lineTexts.Any(text => text.Contains("须臾", StringComparison.Ordinal)); + var hasFragile = lineTexts.Any(text => text.Contains("脆弱", StringComparison.Ordinal)); // 双倍奖励下优先切到 40 树脂,避免误用 20 树脂。 if (hasDoubleReward && hasOriginal20 && !hasOriginal40) @@ -1748,13 +1785,17 @@ public class AutoLeyLineOutcropTask : ISoloTask return false; } - isOriginalResinEmpty = promptRegions.Any(r => r.Text.Contains("补充", StringComparison.Ordinal)); - hasDoubleReward = promptRegions.Any(r => r.Text.Contains("双倍", StringComparison.Ordinal) || r.Text.Contains("2倍产出", StringComparison.Ordinal) || r.Text.Contains("2倍", StringComparison.Ordinal)); - hasOriginal20 = !isOriginalResinEmpty && promptRegions.Any(r => r.Text.Contains("20", StringComparison.Ordinal) && r.Text.Contains("原粹", StringComparison.Ordinal)); - hasOriginal40 = !isOriginalResinEmpty && promptRegions.Any(r => r.Text.Contains("40", StringComparison.Ordinal) && r.Text.Contains("原粹", StringComparison.Ordinal)); - hasCondensed = promptRegions.Any(r => r.Text.Contains("浓缩", StringComparison.Ordinal)); - hasTransient = promptRegions.Any(r => r.Text.Contains("须臾", StringComparison.Ordinal)); - hasFragile = promptRegions.Any(r => r.Text.Contains("脆弱", StringComparison.Ordinal)); + lineTexts = BuildPromptTextLines(promptRegions); + isOriginalResinEmpty = lineTexts.Any(text => text.Contains("补充", StringComparison.Ordinal)); + hasDoubleReward = lineTexts.Any(text => text.Contains("双倍", StringComparison.Ordinal) + || text.Contains("2倍产出", StringComparison.Ordinal) + || text.Contains("2倍", StringComparison.Ordinal)); + originalResinLines = lineTexts.Where(text => text.Contains("原粹", StringComparison.Ordinal)).ToList(); + hasOriginal20 = !isOriginalResinEmpty && originalResinLines.Any(text => text.Contains("20", StringComparison.Ordinal)); + hasOriginal40 = !isOriginalResinEmpty && originalResinLines.Any(text => text.Contains("40", StringComparison.Ordinal)); + hasCondensed = lineTexts.Any(text => text.Contains("浓缩", StringComparison.Ordinal)); + hasTransient = lineTexts.Any(text => text.Contains("须臾", StringComparison.Ordinal)); + hasFragile = lineTexts.Any(text => text.Contains("脆弱", StringComparison.Ordinal)); } } @@ -1811,36 +1852,44 @@ public class AutoLeyLineOutcropTask : ISoloTask } } - _logger.LogDebug("奖励页面树脂识别结果未匹配成功,ocr={Texts}", string.Join(" | ", promptRegions.Select(r => r.Text))); + _logger.LogDebug("奖励页面树脂识别结果未匹配成功,ocr={Texts}", string.Join(" | ", lineTexts)); return false; } private async Task ActivateRewardPrompt() { - var titleRegion = CaptureRewardPromptTitleRegion(); - if (titleRegion == null) - { - _logger.LogDebug("奖励页面未识别到可点击的标题区域"); - return; - } + using var capture = CaptureToRectArea(); + var titleRoi = GetRewardPromptTitleRoi(capture); + var titleRegion = CaptureRewardPromptTitleRegion(capture, titleRoi); // 对齐自动秘境的处理,先点一次标题区域激活弹窗,再点树脂使用按钮。 Simulation.SendInput.Mouse.LeftButtonUp(); await Delay(60, _ct); - titleRegion.Click(); - _logger.LogDebug("奖励页面已点击标题激活弹窗:text={Text}, x={X}, y={Y}", titleRegion.Text, titleRegion.X, titleRegion.Y); + + if (titleRegion != null) + { + titleRegion.Click(); + _logger.LogDebug("奖励页面已点击标题激活弹窗:text={Text}, x={X}, y={Y}", titleRegion.Text, titleRegion.X, titleRegion.Y); + } + else + { + capture.Derive(titleRoi).Click(); + _logger.LogDebug("奖励页面标题 OCR 被拆分,回退点击标题区域中心激活弹窗"); + } + await Delay(800, _ct); } - private Region? CaptureRewardPromptTitleRegion() + private Region? CaptureRewardPromptTitleRegion(ImageRegion capture, Rect titleRoi) { - using var capture = CaptureToRectArea(); - var titleRegions = capture.FindMulti(RecognitionObject.Ocr(capture.Width * 0.25, capture.Height * 0.15, capture.Width * 0.5, capture.Height * 0.25)); - return titleRegions.FirstOrDefault(r => IsRewardPromptTitleText(r.Text)); + var titleRegions = capture.FindMulti(RecognitionObject.Ocr(titleRoi)); + var mergedLines = MergeTextRegionsByLine(capture, titleRegions); + return mergedLines.FirstOrDefault(r => IsRewardPromptTitleText(r.Text)); } private static bool IsRewardPromptTitleText(string text) { + text = NormalizeLeyLineOcrText(text); return text.Contains("激活地脉之花", StringComparison.Ordinal) || text.Contains("选择激活方式", StringComparison.Ordinal) || text.Contains("地脉之花", StringComparison.Ordinal); @@ -1849,7 +1898,7 @@ public class AutoLeyLineOutcropTask : ISoloTask private List CaptureRewardPromptRegions() { using var capture = CaptureToRectArea(); - return capture.FindMulti(RecognitionObject.Ocr(capture.Width * 0.25, capture.Height * 0.2, capture.Width * 0.5, capture.Height * 0.6)); + return capture.FindMulti(RecognitionObject.Ocr(GetRewardPromptContentRoi(capture))); } private async Task TryPressRewardResin(List promptRegions, string resinName) @@ -1881,8 +1930,133 @@ public class AutoLeyLineOutcropTask : ISoloTask await Delay(800, _ct); using var check = CaptureToRectArea(); - var list = check.FindMulti(_ocrRoThis); - return list.Any(r => r.Text.Contains("40", StringComparison.Ordinal) && r.Text.Contains("原粹", StringComparison.Ordinal)); + var lineTexts = BuildPromptTextLines(check.FindMulti(_ocrRoThis)); + return lineTexts.Any(text => text.Contains("40", StringComparison.Ordinal) && text.Contains("原粹", StringComparison.Ordinal)); + } + + private static Rect GetRewardPromptTitleRoi(ImageRegion capture) + { + return new Rect(capture.Width / 4, capture.Height * 3 / 20, capture.Width / 2, capture.Height / 4); + } + + private static Rect GetRewardPromptContentRoi(ImageRegion capture) + { + return new Rect(capture.Width / 4, capture.Height / 5, capture.Width / 2, capture.Height * 3 / 5); + } + + private static List BuildPromptTextLines(IEnumerable regions) + { + return GroupPromptRegionsByLine(regions) + .Select(line => NormalizeLeyLineOcrText(string.Concat(line.OrderBy(r => r.X).Select(r => r.Text.Trim())))) + .Where(text => !string.IsNullOrWhiteSpace(text)) + .ToList(); + } + + private bool HasRewardPrompt(ImageRegion capture) + { + var titleRoi = GetRewardPromptTitleRoi(capture); + if (CaptureRewardPromptTitleRegion(capture, titleRoi) != null) + { + return true; + } + + var promptRegions = capture.FindMulti(RecognitionObject.Ocr(GetRewardPromptContentRoi(capture))); + var lineTexts = BuildPromptTextLines(promptRegions); + return lineTexts.Any(ContainsRewardPromptContentText); + } + + private static string NormalizeLeyLineOcrText(string? text) + { + if (string.IsNullOrWhiteSpace(text)) + { + return string.Empty; + } + + return text + .Replace("脈", "脉", StringComparison.Ordinal) + .Replace("觸", "触", StringComparison.Ordinal) + .Replace("樹", "树", StringComparison.Ordinal) + .Replace("選", "选", StringComparison.Ordinal) + .Replace("擇", "择", StringComparison.Ordinal) + .Replace("\r", string.Empty, StringComparison.Ordinal) + .Trim(); + } + + private static bool ContainsLeyLineFlowerText(string text) + { + text = NormalizeLeyLineOcrText(text); + return text.Contains("地脉之花", StringComparison.Ordinal) + || (text.Contains("地脉", StringComparison.Ordinal) && text.Contains("之花", StringComparison.Ordinal)); + } + + private static bool ContainsRewardPromptActionText(string text) + { + text = NormalizeLeyLineOcrText(text); + return text.Contains("使用", StringComparison.Ordinal); + } + + private static bool ContainsRewardPromptContentText(string text) + { + text = NormalizeLeyLineOcrText(text); + return text.Contains("原粹树脂", StringComparison.Ordinal) + || text.Contains("浓缩树脂", StringComparison.Ordinal) + || text.Contains("须臾树脂", StringComparison.Ordinal) + || text.Contains("脆弱树脂", StringComparison.Ordinal) + || text.Contains("激活地脉之花", StringComparison.Ordinal) + || text.Contains("选择激活方式", StringComparison.Ordinal) + || (text.Contains("树脂", StringComparison.Ordinal) && text.Contains("使用", StringComparison.Ordinal)) + || text.Contains("补充", StringComparison.Ordinal); + } + + private static List MergeTextRegionsByLine(Region owner, IEnumerable regions) + { + var merged = new List(); + foreach (var line in GroupPromptRegionsByLine(regions)) + { + var orderedLine = line.OrderBy(r => r.X).ToList(); + var left = orderedLine.Min(r => r.X); + var top = orderedLine.Min(r => r.Y); + var right = orderedLine.Max(r => r.Right); + var bottom = orderedLine.Max(r => r.Bottom); + var mergedRegion = owner.Derive(left, top, right - left, bottom - top); + mergedRegion.Text = string.Concat(orderedLine.Select(r => r.Text.Trim())); + merged.Add(mergedRegion); + } + + return merged.OrderBy(r => r.Y).ThenBy(r => r.X).ToList(); + } + + private static List> GroupPromptRegionsByLine(IEnumerable regions) + { + var ordered = regions + .Where(r => !string.IsNullOrWhiteSpace(r.Text)) + .OrderBy(r => r.Y) + .ThenBy(r => r.X) + .ToList(); + + var lines = new List>(); + foreach (var region in ordered) + { + var line = lines.FirstOrDefault(candidate => IsSamePromptLine(candidate[0], region)); + if (line == null) + { + lines.Add(new List { region }); + } + else + { + line.Add(region); + } + } + + return lines; + } + + private static bool IsSamePromptLine(Region first, Region second) + { + var firstCenter = first.Y + first.Height / 2.0; + var secondCenter = second.Y + second.Height / 2.0; + var tolerance = Math.Max(first.Height, second.Height) * 0.6; + return Math.Abs(firstCenter - secondCenter) <= tolerance; } private async Task EnsureExitRewardPage() From 091bbb28653faba1cd62a143d79aa6f76adf3219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=E4=BA=91?= Date: Wed, 1 Apr 2026 10:56:11 +0800 Subject: [PATCH 091/107] =?UTF-8?q?fix:=20=E6=8D=95=E8=8E=B7OpenCV?= =?UTF-8?q?=E6=8A=9B=E5=87=BA=E7=9A=84=E5=BC=82=E5=B8=B8=20(#2985)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoTrackPath/TpTask.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs index b8921f7d..0cfd2ccf 100644 --- a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs +++ b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs @@ -1,4 +1,4 @@ -using BetterGenshinImpact.Core.Recognition; +using BetterGenshinImpact.Core.Recognition; using BetterGenshinImpact.Core.Recognition.OpenCv; using BetterGenshinImpact.Core.Script.Dependence; using BetterGenshinImpact.Core.Simulator; @@ -807,7 +807,15 @@ public class TpTask using var mapScaleButtonRa = ra.Find(QuickTeleportAssets.Instance.MapScaleButtonRo); if (mapScaleButtonRa.IsExist()) { - rect = MapManager.GetMap(mapName, _mapMatchingMethod).GetBigMapRect(ra.CacheGreyMat); + try + { + rect = MapManager.GetMap(mapName, _mapMatchingMethod).GetBigMapRect(ra.CacheGreyMat); + } + catch (Exception) + { + rect = default; // 发生异常视为识别失败 + } + if (rect == default) { // 滚轮调整后再次识别 @@ -845,7 +853,16 @@ public class TpTask using var mapScaleButtonRa = ra.Find(QuickTeleportAssets.Instance.MapScaleButtonRo); if (mapScaleButtonRa.IsExist()) { - var p = MapManager.GetMap(mapName, _mapMatchingMethod).GetBigMapPosition(ra.CacheGreyMat); + Point2f p; + try + { + p = MapManager.GetMap(mapName, _mapMatchingMethod).GetBigMapPosition(ra.CacheGreyMat); + } + catch (Exception ex) + { + throw new MapPositionNotRecognizedException("大地图特征点匹配引发异常:" + ex.Message, ex); + } + if (p.IsEmpty()) { throw new MapPositionNotRecognizedException("大地图特征点匹配识别位置失败"); @@ -1138,4 +1155,5 @@ public class TpTask public class MapPositionNotRecognizedException : Exception { public MapPositionNotRecognizedException(string message) : base(message) { } + public MapPositionNotRecognizedException(string message, Exception innerException) : base(message, innerException) { } } From 399b9d4adbc4abf203b251716090eddc7342d274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Thu, 2 Apr 2026 00:36:31 +0800 Subject: [PATCH 092/107] =?UTF-8?q?=E5=B9=B6=E5=8F=91=E7=94=9F=E6=88=90SIF?= =?UTF-8?q?T?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Simple/AllMap/LargeSIFTExtractor.cs | 66 +++++++++++-------- .../Simple/AllMap/MapPuzzle.cs | 2 +- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/Test/BetterGenshinImpact.Test/Simple/AllMap/LargeSIFTExtractor.cs b/Test/BetterGenshinImpact.Test/Simple/AllMap/LargeSIFTExtractor.cs index 536fc95a..e72c6a4d 100644 --- a/Test/BetterGenshinImpact.Test/Simple/AllMap/LargeSIFTExtractor.cs +++ b/Test/BetterGenshinImpact.Test/Simple/AllMap/LargeSIFTExtractor.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; +using System.Threading.Tasks; using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Recognition.OpenCv.FeatureMatch; using OpenCvSharp; @@ -17,8 +18,9 @@ public class LargeSiftExtractor { private const int BLOCK_SIZE = 1024; private const int OVERLAP_SIZE = BLOCK_SIZE * 3; - - private readonly Feature2D _sift = SIFT.Create(); + private const int MAX_PARALLELISM = 24; + private const string MAP_VERSION = "6.5"; + private static readonly string ROOT_PATH = $@"E:\HuiTask\更好的原神\地图匹配\拼图结果\{MAP_VERSION}"; // public static void Gen1024() // { @@ -38,7 +40,7 @@ public class LargeSiftExtractor { Environment.SetEnvironmentVariable("OPENCV_IO_MAX_IMAGE_PIXELS", Math.Pow(2, 40).ToString("F0")); - var rootPath = @"E:\HuiTask\更好的原神\地图匹配\拼图结果\6.4"; + var rootPath = ROOT_PATH; // 缩小 2048/256 = 8 var targetFilePath = $@"{rootPath}\Teyvat_0_256.png"; @@ -63,8 +65,8 @@ public class LargeSiftExtractor { Environment.SetEnvironmentVariable("OPENCV_IO_MAX_IMAGE_PIXELS", Math.Pow(2, 40).ToString("F0")); var extractor = new LargeSiftExtractor(); - extractor.ExtractAndSaveSift(@"E:\HuiTask\更好的原神\地图匹配\拼图结果\6.4\map_2048.png", - @"E:\HuiTask\更好的原神\地图匹配\拼图结果\6.4\"); + extractor.ExtractAndSaveSift(Path.Combine(ROOT_PATH, "map_2048.png"), + ROOT_PATH); } public void ExtractAndSaveSift(string imagePath, string outputPath) @@ -77,29 +79,37 @@ public class LargeSiftExtractor // 计算需要切分的块数 int rows = (int)Math.Ceiling(img.Height / (double)BLOCK_SIZE); int cols = (int)Math.Ceiling(img.Width / (double)BLOCK_SIZE); + int totalBlocks = rows * cols; Debug.WriteLine($"图像被分成 {rows} 行 {cols} 列的块。"); - - // 遍历每个块 - for (int row = 0; row < rows; row++) - { - for (int col = 0; col < cols; col++) + var blockResults = new BlockProcessResult[totalBlocks]; + Parallel.For(0, totalBlocks, + new ParallelOptions { MaxDegreeOfParallelism = MAX_PARALLELISM }, + () => SIFT.Create(), + (index, _, sift) => { + int row = index / cols; + int col = index % cols; Debug.WriteLine($"处理第 {row} 行,第 {col} 列的块"); - var (keypoints, descriptors) = ProcessBlock(img, row, col); + var (keypoints, descriptors) = ProcessBlock(img, row, col, sift); + blockResults[index] = new BlockProcessResult(row, col, keypoints, descriptors); + return sift; + }, + sift => sift.Dispose()); - // 调整keypoints的坐标 - // 修改这里:使用 for 循环而不是 foreach - for (int i = 0; i < keypoints.Length; i++) - { - var kp = keypoints[i]; - kp.Pt.X += col * BLOCK_SIZE; - kp.Pt.Y += row * BLOCK_SIZE; - keypoints[i] = kp; - } - - allKeypoints.AddRange(keypoints); - allDescriptors.Add(descriptors); + for (int index = 0; index < totalBlocks; index++) + { + var blockResult = blockResults[index]; + var keypoints = blockResult.Keypoints; + for (int i = 0; i < keypoints.Length; i++) + { + var kp = keypoints[i]; + kp.Pt.X += blockResult.Col * BLOCK_SIZE; + kp.Pt.Y += blockResult.Row * BLOCK_SIZE; + keypoints[i] = kp; } + + allKeypoints.AddRange(keypoints); + allDescriptors.Add(blockResult.Descriptors); } // 合并所有descriptors @@ -118,7 +128,7 @@ public class LargeSiftExtractor Debug.WriteLine("SIFT特征提取和保存完成。"); } - private (KeyPoint[] keypoints, Mat descriptors) ProcessBlock(Mat img, int row, int col) + private static (KeyPoint[] keypoints, Mat descriptors) ProcessBlock(Mat img, int row, int col, Feature2D sift) { // 计算当前块的范围 int startY = row * BLOCK_SIZE; @@ -142,7 +152,7 @@ public class LargeSiftExtractor KeyPoint[] kps; var desc = new Mat(); - _sift.DetectAndCompute(blockRegion, null, out kps, desc); + sift.DetectAndCompute(blockRegion, null, out kps, desc); Debug.WriteLine($"Block at ({row},{col}) - Original keypoints count: {kps.Length}"); // 找出中心区域关键点的索引 @@ -184,7 +194,7 @@ public class LargeSiftExtractor Math.Min(BLOCK_SIZE, img.Height - startY))); var desc = new Mat(); - _sift.DetectAndCompute(blockRegion, null, out var kps, desc); + sift.DetectAndCompute(blockRegion, null, out var kps, desc); Debug.WriteLine($"边缘区域处理了 {kps.Length} 个关键点。"); return (kps, desc); @@ -225,4 +235,6 @@ public class LargeSiftExtractor using var fs = new FileStream(outputPath, FileMode.Create); fs.Write(kpSpan); } -} \ No newline at end of file + + private readonly record struct BlockProcessResult(int Row, int Col, KeyPoint[] Keypoints, Mat Descriptors); +} diff --git a/Test/BetterGenshinImpact.Test/Simple/AllMap/MapPuzzle.cs b/Test/BetterGenshinImpact.Test/Simple/AllMap/MapPuzzle.cs index f410d09a..5d736d9c 100644 --- a/Test/BetterGenshinImpact.Test/Simple/AllMap/MapPuzzle.cs +++ b/Test/BetterGenshinImpact.Test/Simple/AllMap/MapPuzzle.cs @@ -27,7 +27,7 @@ public class MapPuzzle public static void PutAll() { - var folder = @"E:\HuiTask\更好的原神\地图匹配\拼图结果\6.4"; + var folder = @"E:\HuiTask\更好的原神\地图匹配\拼图结果\6.5"; Directory.CreateDirectory(folder); // 保存2048大图 From f80814952bc9572a72a2b626a000026a7787bdd7 Mon Sep 17 00:00:00 2001 From: ddaodan <40017293+ddaodan@users.noreply.github.com> Date: Fri, 3 Apr 2026 17:31:05 +0800 Subject: [PATCH 093/107] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E5=9C=B0?= =?UTF-8?q?=E8=84=89=E8=8A=B1=E6=9B=B4=E6=96=B0=E6=8C=AA=E5=BE=B7=E5=8D=A1?= =?UTF-8?q?=E8=8E=B13-=E6=9C=88=E7=9F=A9=E5=8A=9B=E8=AF=95=E9=AA=8C?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E5=B1=80=E7=9A=84=E8=B7=AF=E5=BE=84=20(#2991?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pathing/挪德卡莱3-月矩力试验设计局-2.json | 48 ++++++++++++------- .../pathing/挪德卡莱3-月矩力试验设计局-3.json | 26 +++++++--- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/Assets/pathing/挪德卡莱3-月矩力试验设计局-2.json b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/Assets/pathing/挪德卡莱3-月矩力试验设计局-2.json index 7dbee52f..626404cc 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/Assets/pathing/挪德卡莱3-月矩力试验设计局-2.json +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/Assets/pathing/挪德卡莱3-月矩力试验设计局-2.json @@ -1,16 +1,21 @@ -{ +{ "info": { - "authors": [], + "authors": [ + { + "links": "", + "name": "ddaodan" + } + ], "bgi_version": "0.45.0", "description": "", "enable_monster_loot_split": false, - "last_modified_time": 1760586516234, + "last_modified_time": 1775105055739, "map_match_method": "", "map_name": "Teyvat", "name": "挪德卡莱3-月矩力试验设计局-2", "tags": [], "type": "collect", - "version": "1.0" + "version": "1.1" }, "positions": [ { @@ -19,17 +24,17 @@ "id": 1, "move_mode": "walk", "type": "teleport", - "x": 9375.3896484375, - "y": 3150.5361328125 + "x": 9375.0771484375, + "y": 3150.26611328125 }, { "action": "", "action_params": "", "id": 2, - "move_mode": "walk", - "type": "target", - "x": 9379.1904296875, - "y": 3148.973876953125 + "move_mode": "dash", + "type": "path", + "x": 9377.501953125, + "y": 3145.7734375 }, { "action": "", @@ -37,26 +42,35 @@ "id": 3, "move_mode": "jump", "type": "path", - "x": 9384.3466796875, - "y": 3152.6689453125 + "x": 9380.072265625, + "y": 3141.0322265625 }, { "action": "", "action_params": "", "id": 4, - "move_mode": "jump", + "move_mode": "walk", "type": "path", - "x": 9392.90234375, - "y": 3154.693359375 + "x": 9385.978515625, + "y": 3134.896484375 }, { "action": "", "action_params": "", "id": 5, + "move_mode": "jump", + "type": "path", + "x": 9396.578125, + "y": 3136.884765625 + }, + { + "action": "", + "action_params": "", + "id": 6, "move_mode": "dash", "type": "target", - "x": 9424.498046875, - "y": 3135.7734375 + "x": 9423.1064453125, + "y": 3135.43310546875 } ] } \ No newline at end of file diff --git a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/Assets/pathing/挪德卡莱3-月矩力试验设计局-3.json b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/Assets/pathing/挪德卡莱3-月矩力试验设计局-3.json index 0a9ad7af..a30b7744 100644 --- a/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/Assets/pathing/挪德卡莱3-月矩力试验设计局-3.json +++ b/BetterGenshinImpact/GameTask/AutoLeyLineOutcrop/Assets/pathing/挪德卡莱3-月矩力试验设计局-3.json @@ -1,16 +1,21 @@ -{ +{ "info": { - "authors": [], + "authors": [ + { + "links": "", + "name": "ddaodan" + } + ], "bgi_version": "0.45.0", "description": "", "enable_monster_loot_split": false, - "last_modified_time": 1758088156049, + "last_modified_time": 1775105252724, "map_match_method": "", "map_name": "Teyvat", "name": "挪德卡莱3-月矩力试验设计局-3", "tags": [], "type": "collect", - "version": "1.0" + "version": "1.1" }, "positions": [ { @@ -19,14 +24,23 @@ "id": 1, "move_mode": "dash", "type": "path", - "x": 9463.578125, - "y": 3170.335205078125 + "x": 9463.2578125, + "y": 3147.61328125 }, { "action": "", "action_params": "", "id": 2, "move_mode": "dash", + "type": "path", + "x": 9471.43359375, + "y": 3168.97900390625 + }, + { + "action": "", + "action_params": "", + "id": 3, + "move_mode": "dash", "type": "target", "x": 9461.056640625, "y": 3200.0498046875 From ca249206ae649012239375ea9df3c44de5a22b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 4 Apr 2026 20:21:54 +0800 Subject: [PATCH 094/107] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E5=8C=85=E7=89=88=E6=9C=AC=E4=B8=8E=E5=85=83=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/BetterGenshinImpact.csproj | 4 ++-- .../GameTask/AutoFight/Assets/combat_avatar.json | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index df391694..7f515f1a 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -43,8 +43,8 @@ - - + + diff --git a/BetterGenshinImpact/GameTask/AutoFight/Assets/combat_avatar.json b/BetterGenshinImpact/GameTask/AutoFight/Assets/combat_avatar.json index d8c8101a..dc28df7b 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Assets/combat_avatar.json +++ b/BetterGenshinImpact/GameTask/AutoFight/Assets/combat_avatar.json @@ -2063,5 +2063,17 @@ "nameEn": "Varka", "skillCD": 16, "weapon": "10" + }, + { + "alias": [ + "莉奈娅", + "Linnea" + ], + "burstCD": 15, + "id": "10000130", + "name": "莉奈娅", + "nameEn": "Linnea", + "skillCD": 18, + "weapon": "10" } ] \ No newline at end of file From da068549fe910c484a430715807b16bd8462b328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sat, 4 Apr 2026 20:23:34 +0800 Subject: [PATCH 095/107] chore: update MimeKit package version to 4.15.1 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 7f515f1a..dfd2208c 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -67,7 +67,7 @@ - + From 3d394475e1b8fbc0d0ecf774eb89fdcde564bd8a Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Sat, 4 Apr 2026 12:26:10 +0000 Subject: [PATCH 096/107] Update version to 0.59.2-alpha.2 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index dfd2208c..1d9638ff 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.59.2-alpha.1 + 0.59.2-alpha.2 false WinExe net8.0-windows10.0.22621.0 From c8be4429433163728f987c38fd49742c56b988f5 Mon Sep 17 00:00:00 2001 From: DarkFlameMaster <1004452714@qq.com> Date: Sun, 5 Apr 2026 01:35:15 +0800 Subject: [PATCH 097/107] =?UTF-8?q?docs:=20=E7=BB=99=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=E5=A2=9E=E5=8A=A0=20www=20=E5=89=8D=E7=BC=80?= =?UTF-8?q?=20(#2996)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Notification/NotificationConfig.cs | 2 +- BetterGenshinImpact/Service/UpdateService.cs | 2 +- .../View/Windows/ScriptRepoWindow.xaml | 2 +- .../View/Windows/WelcomeDialog.xaml | 2 +- .../ViewModel/MainWindowViewModel.cs | 8 +-- .../ViewModel/Pages/HomePageViewModel.cs | 2 +- .../ViewModel/Pages/JsListViewModel.cs | 2 +- .../Pages/KeyMouseRecordPageViewModel.cs | 2 +- .../Pages/MacroSettingsPageViewModel.cs | 2 +- .../ViewModel/Pages/MapPathingViewModel.cs | 2 +- .../ViewModel/Pages/ScriptControlViewModel.cs | 2 +- .../Pages/TaskSettingsPageViewModel.cs | 22 ++++---- .../Pages/View/ScriptGroupConfigViewModel.cs | 2 +- Docs/readme_en.md | 55 ++++++++++--------- Docs/readme_tcn.md | 55 ++++++++++--------- README.md | 53 +++++++++--------- 16 files changed, 112 insertions(+), 103 deletions(-) diff --git a/BetterGenshinImpact/Service/Notification/NotificationConfig.cs b/BetterGenshinImpact/Service/Notification/NotificationConfig.cs index 0bd1a7e6..c70948b8 100644 --- a/BetterGenshinImpact/Service/Notification/NotificationConfig.cs +++ b/BetterGenshinImpact/Service/Notification/NotificationConfig.cs @@ -268,7 +268,7 @@ public partial class NotificationConfig : ObservableObject /// /// Discord Webhook头像地址 - /// Default url from https://bettergi.com/ + /// Default url from https://www.bettergi.com/ /// [ObservableProperty] private string _discordWebhookAvatarUrl = "https://img.alicdn.com/imgextra/i2/2042484851/O1CN01LQfLIG1lhoEZwz1Gt_!!2042484851.png"; diff --git a/BetterGenshinImpact/Service/UpdateService.cs b/BetterGenshinImpact/Service/UpdateService.cs index 5434e288..75e0bf4f 100644 --- a/BetterGenshinImpact/Service/UpdateService.cs +++ b/BetterGenshinImpact/Service/UpdateService.cs @@ -30,7 +30,7 @@ public class UpdateService : IUpdateService private readonly IConfigService _configService; private const string NoticeUrl = "https://hui-config.oss-cn-hangzhou.aliyuncs.com/bgi/notice.json"; - private const string DownloadPageUrl = "https://bettergi.com/download.html"; + private const string DownloadPageUrl = "https://www.bettergi.com/download.html"; public AllConfig Config { get; set; } diff --git a/BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml b/BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml index 6ff07f1e..f732b46f 100644 --- a/BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml +++ b/BetterGenshinImpact/View/Windows/ScriptRepoWindow.xaml @@ -279,7 +279,7 @@ Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}" TextWrapping="Wrap"> - + diff --git a/BetterGenshinImpact/View/Windows/WelcomeDialog.xaml b/BetterGenshinImpact/View/Windows/WelcomeDialog.xaml index 8944a8a7..478976a7 100644 --- a/BetterGenshinImpact/View/Windows/WelcomeDialog.xaml +++ b/BetterGenshinImpact/View/Windows/WelcomeDialog.xaml @@ -34,7 +34,7 @@ - 《快速上手教程》 diff --git a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs index 77ee76be..2f407b38 100644 --- a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs +++ b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs @@ -412,12 +412,12 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel catch (Exception e) { Console.WriteLine(e); - _logger.LogError("PaddleOcr预热异常,解决方案:【https://bettergi.com/faq.html】\r\n" + e.Source + "\r\n--" + + _logger.LogError("PaddleOcr预热异常,解决方案:【https://www.bettergi.com/faq.html】\r\n" + 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://bettergi.com/faq.html】\r\n" + + _logger.LogError("PaddleOcr预热内部异常,解决方案:【https://www.bettergi.com/faq.html】\r\n" + innerException.Source + "\r\n--" + Environment.NewLine + innerException.StackTrace + "\r\n---" + Environment.NewLine + innerException.Message); @@ -432,11 +432,11 @@ public partial class MainWindowViewModel : ObservableObject, IViewModel } catch (Exception e) { - ThemedMessageBox.Warning("PaddleOcr预热失败,解决方案:【https://bettergi.com/faq.html】 \r\n" + e.Source + "\r\n--" + + ThemedMessageBox.Warning("PaddleOcr预热失败,解决方案:【https://www.bettergi.com/faq.html】 \r\n" + e.Source + "\r\n--" + Environment.NewLine + e.StackTrace + "\r\n---" + Environment.NewLine + e.Message); Process.Start( new ProcessStartInfo( - "https://bettergi.com/faq.html#%E2%9D%93%E6%8F%90%E7%A4%BA-paddleocr%E9%A2%84%E7%83%AD%E5%A4%B1%E8%B4%A5-%E5%BA%94%E8%AF%A5%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3") + "https://www.bettergi.com/faq.html#%E2%9D%93-%E6%8F%90%E7%A4%BA-paddleocr-%E9%A2%84%E7%83%AD%E5%A4%B1%E8%B4%A5-%E5%BA%94%E8%AF%A5%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3") { UseShellExecute = true }); } } diff --git a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs index c5462997..d3ab15b4 100644 --- a/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/HomePageViewModel.cs @@ -336,7 +336,7 @@ public partial class HomePageViewModel : ViewModel [RelayCommand] public void OnGoToWikiUrl() { - Process.Start(new ProcessStartInfo("https://bettergi.com/doc.html") { UseShellExecute = true }); + Process.Start(new ProcessStartInfo("https://www.bettergi.com/doc.html") { UseShellExecute = true }); } [RelayCommand] diff --git a/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs index 7bde48a2..3b064365 100644 --- a/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/JsListViewModel.cs @@ -185,7 +185,7 @@ public partial class JsListViewModel : ViewModel [RelayCommand] public void OnGoToJsScriptUrl() { - Process.Start(new ProcessStartInfo("https://bettergi.com/feats/autos/jsscript.html") + Process.Start(new ProcessStartInfo("https://www.bettergi.com/feats/autos/jsscript.html") { UseShellExecute = true }); } diff --git a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs index 23b9ea27..1a3a72b1 100644 --- a/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/KeyMouseRecordPageViewModel.cs @@ -224,7 +224,7 @@ public partial class KeyMouseRecordPageViewModel : ViewModel [RelayCommand] public void OnGoToKmScriptUrl() { - Process.Start(new ProcessStartInfo("https://bettergi.com/feats/autos/kmscript.html") { UseShellExecute = true }); + Process.Start(new ProcessStartInfo("https://www.bettergi.com/feats/autos/kmscript.html") { UseShellExecute = true }); } [RelayCommand] diff --git a/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs index cb26c528..1ab58c32 100644 --- a/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs @@ -41,6 +41,6 @@ public partial class MacroSettingsPageViewModel : ViewModel [RelayCommand] public void OnGoToOneKeyMacroUrl() { - Process.Start(new ProcessStartInfo("https://bettergi.com/feats/macro/onem.html") { UseShellExecute = true }); + Process.Start(new ProcessStartInfo("https://www.bettergi.com/feats/macro/onem.html") { UseShellExecute = true }); } } diff --git a/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs index ad91e3dd..ac13206a 100644 --- a/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/MapPathingViewModel.cs @@ -186,7 +186,7 @@ public partial class MapPathingViewModel : ViewModel [RelayCommand] public void OnGoToPathingUrl() { - Process.Start(new ProcessStartInfo("https://bettergi.com/feats/autos/pathing.html") { UseShellExecute = true }); + Process.Start(new ProcessStartInfo("https://www.bettergi.com/feats/autos/pathing.html") { UseShellExecute = true }); } [RelayCommand] diff --git a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs index 3b3500e5..c4fc742e 100644 --- a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs @@ -1937,7 +1937,7 @@ public partial class ScriptControlViewModel : ViewModel [RelayCommand] public void OnGoToScriptGroupUrl() { - Process.Start(new ProcessStartInfo("https://bettergi.com/feats/autos/dispatcher.html") { UseShellExecute = true }); + Process.Start(new ProcessStartInfo("https://www.bettergi.com/feats/autos/dispatcher.html") { UseShellExecute = true }); } [RelayCommand] diff --git a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs index a50d3ce7..c92e7823 100644 --- a/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/TaskSettingsPageViewModel.cs @@ -388,7 +388,7 @@ public partial class TaskSettingsPageViewModel : ViewModel [RelayCommand] public async Task OnGoToAutoGeniusInvokationUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/tcg.html")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/feats/task/tcg.html")); } [RelayCommand] @@ -403,7 +403,7 @@ public partial class TaskSettingsPageViewModel : ViewModel [RelayCommand] public async Task OnGoToAutoWoodUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/felling.html")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/feats/task/felling.html")); } [RelayCommand] @@ -425,7 +425,7 @@ public partial class TaskSettingsPageViewModel : ViewModel [RelayCommand] public async Task OnGoToAutoFightUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/domain.html")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/feats/task/domain.html")); } [RelayCommand] @@ -474,7 +474,7 @@ public partial class TaskSettingsPageViewModel : ViewModel [RelayCommand] public async Task OnGoToAutoDomainUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/domain.html")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/feats/task/domain.html")); } [RelayCommand] @@ -496,7 +496,7 @@ public partial class TaskSettingsPageViewModel : ViewModel [RelayCommand] private async Task OnGoToAutoStygianOnslaughtUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/stygian.html")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/feats/task/stygian.html")); } [RelayCommand] @@ -544,7 +544,7 @@ public partial class TaskSettingsPageViewModel : ViewModel [RelayCommand] public async Task OnGoToAutoTrackUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/track.html")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/feats/task/track.html")); } [Obsolete] @@ -579,7 +579,7 @@ public partial class TaskSettingsPageViewModel : ViewModel [RelayCommand] private async Task OnGoToAutoTrackPathUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/track.html")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/feats/task/track.html")); } [RelayCommand] @@ -594,7 +594,7 @@ public partial class TaskSettingsPageViewModel : ViewModel [RelayCommand] private async Task OnGoToAutoMusicGameUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/music.html")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/feats/task/music.html")); } [RelayCommand] @@ -639,7 +639,7 @@ public partial class TaskSettingsPageViewModel : ViewModel [RelayCommand] private async Task OnGoToAutoFishingUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/fish.html")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/feats/task/fish.html")); } [RelayCommand] @@ -672,7 +672,7 @@ public partial class TaskSettingsPageViewModel : ViewModel [RelayCommand] private async Task OnGoToArtifactSalvageUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/feats/task/artifactSalvage.html")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/feats/task/artifactSalvage.html")); } [RelayCommand] @@ -757,7 +757,7 @@ public partial class TaskSettingsPageViewModel : ViewModel [RelayCommand] private async Task OnGoToGetGridIconsUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/dev/getGridIcons.html")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/dev/getGridIcons.html")); } [RelayCommand] diff --git a/BetterGenshinImpact/ViewModel/Pages/View/ScriptGroupConfigViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/View/ScriptGroupConfigViewModel.cs index cec930b6..0d608235 100644 --- a/BetterGenshinImpact/ViewModel/Pages/View/ScriptGroupConfigViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/View/ScriptGroupConfigViewModel.cs @@ -101,6 +101,6 @@ public partial class ScriptGroupConfigViewModel : ObservableObject, IViewModel [RelayCommand] private async Task OnGoToAutoEatUrlAsync() { - await Launcher.LaunchUriAsync(new Uri("https://bettergi.com/dev/js/dispatcher.html#autoeat-自动吃食物")); + await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/dev/js/dispatcher.html#autoeat-自动吃食物")); } } \ No newline at end of file diff --git a/Docs/readme_en.md b/Docs/readme_en.md index 0d3ca647..8f9162d9 100644 --- a/Docs/readme_en.md +++ b/Docs/readme_en.md @@ -1,8 +1,8 @@ -
+

- +
- BetterGI + BetterGI

babalae%2Fbetter-genshin-impact | Trendshift
@@ -36,31 +36,34 @@ BetterGI · A Better Genshin Impact experience, powered by computer vision techn ## Features * **Real-Time Tasks** - * **[Auto Pickup](https://bettergi.com/feats/timer/pick.html):** Automatically press F for interactions/pickups. Supports whitelist/blacklist configuration. - * **[Auto Dialogue/Skip](https://bettergi.com/feats/timer/skip.html):** Fast-click through dialogues, auto-select options, auto-submit items, close popups, etc. - * Automatically claims Daily Commission rewards **[and re-dispatches expeditions](https://bettergi.com/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%87%8D%E6%96%B0%E6%B4%BE%E9%81%A3)** when talking to Katherine. - * **[Auto Hangout](https://bettergi.com/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%82%80%E7%BA%A6):** Automatically selects Hangout options (requires Auto Dialogue enabled). - * **[Quick Teleport](https://bettergi.com/feats/timer/tp.html):** Auto-clicks map teleport points and initiates teleportation. - * **[Semi-Auto Fishing](https://bettergi.com/feats/timer/fish.html):** AI-based auto-casting, auto-hook detection, and auto-catch mechanics. - * **[Auto Cooking](https://bettergi.com/feats/timer/cook.html):** Perfectly cooks dishes (excluding "Adeptus' Temptation" recipes). + * **[Auto Pickup](https://www.bettergi.com/feats/timer/pick.html):** Automatically press F for interactions/pickups. Supports whitelist/blacklist configuration. + * **[Auto Dialogue/Skip](https://www.bettergi.com/feats/timer/skip.html):** Fast-click through dialogues, auto-select options, auto-submit items, close popups, etc. + * Automatically claims Daily Commission rewards **[and re-dispatches expeditions](https://www.bettergi.com/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%87%8D%E6%96%B0%E6%B4%BE%E9%81%A3)** when talking to Katherine. + * **[Auto Hangout](https://www.bettergi.com/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%82%80%E7%BA%A6):** Automatically selects Hangout options (requires Auto Dialogue enabled). + * **[Quick Teleport](https://www.bettergi.com/feats/timer/tp.html):** Auto-clicks map teleport points and initiates teleportation. + * **[Semi-Auto Fishing](https://www.bettergi.com/feats/timer/fish.html):** AI-based auto-casting, auto-hook detection, and auto-catch mechanics. * **Standalone Tasks** - * **[Auto Genius Invokation TCG](https://bettergi.com/feats/task/tcg.html):** Automates PvE TCG challenges like character invites and weekly duels. - * **[Auto Woodcutting](https://bettergi.com/feats/task/felling.html):** Uses "The Boon of the Elder Tree" (Z) and relogs to farm wood efficiently. - * **[Auto Domain Runs](https://bettergi.com/feats/task/domain.html):** Fully automated domain clears, including starting, combat, and claiming rewards. - * **[Auto Rhythm Game](https://bettergi.com/feats/task/music.html):** Completes achievements for Repertoire of Myriad Melodies. - * **[Full Auto Fishing](https://bettergi.com/feats/task/fish.html):** Fully automates fishing at designated spots, including day/night transitions. + * **[Auto Genius Invokation TCG](https://www.bettergi.com/feats/task/tcg.html):** Automates PvE TCG challenges like character invites and weekly duels. + * **[Auto Woodcutting](https://www.bettergi.com/feats/task/felling.html):** Uses "The Boon of the Elder Tree" (Z) and relogs to farm wood efficiently. + * **[Auto Domain Runs](https://www.bettergi.com/feats/task/domain.html):** Fully automated domain clears, including starting, combat, and claiming rewards. + * **[Auto Stygian Onslaught](https://www.bettergi.com/feats/task/stygian.html):** Auto-teleport and complete Stygian Onslaught (mainly difficulty 3 for artifact farming). + * **[Full Auto Fishing](https://www.bettergi.com/feats/task/fish.html):** Fully automates fishing at designated spots, including day/night transitions. + * **[Auto Ley Line Outcrops](https://www.bettergi.com/feats/task/leyline.html):** Auto-farm Ley Line Outcrops continuously at most locations. + * **[Auto Rhythm Game](https://www.bettergi.com/feats/task/music.html):** One-click to complete Repertoire of Myriad Melodies albums for quick achievements. + * **[Auto Cooking](https://www.bettergi.com/feats/task/cook.html):** Automatically cooks dishes in the perfect zone. + * **[Auto Artifact Salvage](https://www.bettergi.com/feats/task/artifactSalvage.html):** Supports **Quick Salvage** and **Rule-based Artifact Salvage**. * **Full Automation** * **[One-Click Daily Routine](https://github.com/babalae/better-genshin-impact/issues/846):** Completes daily tasks (using Adventure EXP) and claims rewards. - * **[Auto Gather/Mine/Farm](https://bettergi.com/feats/autos/pathing.html):** Auto-collect resources via minimap detection. - * **[Input Macro Recording](https://bettergi.com/feats/autos/kmscript.html):** Records and replays keyboard/mouse actions (works with scheduler). + * **[Auto Gather/Mine/Farm](https://www.bettergi.com/feats/autos/pathing.html):** Auto-collect resources via minimap detection. + * **[Input Macro Recording](https://www.bettergi.com/feats/autos/kmscript.html):** Records and replays keyboard/mouse actions (works with scheduler). * **Quality-of-Life** - * **[Neuvillette Spin](https://bettergi.com/feats/macro/other.html#%E9%82%A3%E7%BB%B4%E8%8E%B1%E7%89%B9-%E8%BD%AC%E5%9C%88%E5%9C%88):** Hold a key to spin the camera horizontally (also works for Nahida). - * **[Quick Artifact Enhancement](https://bettergi.com/feats/macro/other.html#%E5%9C%A3%E9%81%97%E7%89%A9%E4%B8%80%E9%94%AE%E5%BC%BA%E5%8C%96):** Skip animation by switching between "Details" and "Enhance" tabs. - * **[Shop Quick Buy](https://bettergi.com/feats/macro/other.html#%E4%B8%80%E9%94%AE%E8%B4%AD%E4%B9%B0):** Instantly buy max quantities of shop items (events/Serenitea Pot). -* **[...and more](https://bettergi.com/doc.html)** + * **[Neuvillette Spin](https://www.bettergi.com/feats/macro/other.html#%E9%82%A3%E7%BB%B4%E8%8E%B1%E7%89%B9-%E8%BD%AC%E5%9C%88%E5%9C%88):** Hold a key to spin the camera horizontally (also works for Nahida). + * **[Quick Artifact Enhancement](https://www.bettergi.com/feats/macro/other.html#%E5%9C%A3%E9%81%97%E7%89%A9%E4%B8%80%E9%94%AE%E5%BC%BA%E5%8C%96):** Skip animation by switching between "Details" and "Enhance" tabs. + * **[Shop Quick Buy](https://www.bettergi.com/feats/macro/other.html#%E4%B8%80%E9%94%AE%E8%B4%AD%E4%B9%B0):** Instantly buy max quantities of shop items (events/Serenitea Pot). +* **[...and more](https://www.bettergi.com/doc.html)**
@@ -69,14 +72,14 @@ BetterGI · A Better Genshin Impact experience, powered by computer vision techn ## Screenshots -![0 39 1](https://github.com/user-attachments/assets/8fb0bfd9-e0db-4289-800f-1bc2efb221aa) +![0 39 1](https://github.com/user-attachments/assets/a65aafe9-d8d7-4ffb-8cdc-9939c2fb3bdf) ## Download > [!NOTE] > Download: [⚡GitHub Releases](https://github.com/babalae/better-genshin-impact/releases) > -> New user? See: [Quick Start](https://bettergi.com/quickstart.html). Issues? Check [FAQ](https://bettergi.com/faq.html). +> New user? See: [Quick Start](https://www.bettergi.com/quickstart.html). Issues? Check [FAQ](https://www.bettergi.com/faq.html). Latest builds: [![](https://github.com/babalae/better-genshin-impact/actions/workflows/publish.yml/badge.svg)](https://github.com/babalae/better-genshin-impact/actions/workflows/publish.yml) @@ -93,16 +96,16 @@ Latest builds: [![](https://github.com/babalae/better-genshin-impact/actions/wor Start the app, select a capture method on the "Launch" page, then click "Start"! -Detailed guide: [Quick Start](https://bettergi.com/quickstart.html) +Detailed guide: [Quick Start](https://www.bettergi.com/quickstart.html) -Full documentation: [Documentation](https://bettergi.com/doc.html) +Full documentation: [Documentation](https://www.bettergi.com/doc.html) ## FAQ * **Why admin rights?** - The game runs as admin. Simulated clicks require matching permissions. * **Ban risk?** - **No game files/memory are modified.** Only visual detection and simulated inputs. However, miHoYo's Terms of Service prohibit third-party tools. Use at your own discretion. -* [More FAQs...](https://bettergi.com/faq.html) +* [More FAQs...](https://www.bettergi.com/faq.html) ## Credits Special thanks to these projects: diff --git a/Docs/readme_tcn.md b/Docs/readme_tcn.md index 55653118..45ebb592 100644 --- a/Docs/readme_tcn.md +++ b/Docs/readme_tcn.md @@ -1,8 +1,8 @@ -
+

- +
- BetterGI + BetterGI

babalae%2Fbetter-genshin-impact | Trendshift
@@ -39,28 +39,31 @@ BetterGI · 更好的原神, 一個基於電腦視覺技術,意圖讓原神 ## 功能 * 實時任務 - * [自動拾取](https://bettergi.com/feats/timer/pick.html):遇到可互動/拾取內容時自動按 F,支援黑白名單配置 - * [自動劇情](https://bettergi.com/feats/timer/skip.html):快速點擊過劇情、自動選擇選項、自動提交物品、關閉彈出書頁等 - * 與凱瑟琳對話時有橙色選項會 [自動領取「每日委託」獎勵](https://bettergi.com/feats/timer/skip.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/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%87%8D%E6%96%B0%E6%B4%BE%E9%81%A3) - * [自動邀約](https://bettergi.com/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%82%80%E7%B4%84):自動劇情開啟的情況下此功能才會生效,自動選擇邀約選項 - * [快速傳送](https://bettergi.com/feats/timer/tp.html):在地圖上點擊傳送點,或者點擊後出現的列表中存在傳送點,會自動點擊傳送點並傳送 - * [半自動釣魚](https://bettergi.com/feats/timer/fish.html):AI 識別自動拋竿,魚上鉤時自動收杆,並自動完成釣魚進度 - * [自動烹飪](https://bettergi.com/feats/timer/cook.html):自動在完美區域完成食物烹飪,暫不支援「仙跳牆」 + * [自動拾取](https://www.bettergi.com/feats/timer/pick.html):遇到可互動/拾取內容時自動按 F,支援黑白名單配置 + * [自動劇情](https://www.bettergi.com/feats/timer/skip.html):快速點擊過劇情、自動選擇選項、自動提交物品、關閉彈出書頁等 + * 與凱瑟琳對話時有橙色選項會 [自動領取「每日委託」獎勵](https://www.bettergi.com/feats/timer/skip.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://www.bettergi.com/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%87%8D%E6%96%B0%E6%B4%BE%E9%81%A3) + * [自動邀約](https://www.bettergi.com/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%82%80%E7%B4%84):自動劇情開啟的情況下此功能才會生效,自動選擇邀約選項 + * [快速傳送](https://www.bettergi.com/feats/timer/tp.html):在地圖上點擊傳送點,或者點擊後出現的列表中存在傳送點,會自動點擊傳送點並傳送 + * [半自動釣魚](https://www.bettergi.com/feats/timer/fish.html):AI 識別自動拋竿,魚上鉤時自動收杆,並自動完成釣魚進度 * 獨立任務 - * [全自動七聖召喚](https://bettergi.com/feats/task/tcg.html):幫助你輕鬆完成七聖召喚角色邀請、每週來客挑戰等 PVE 內容 - * [自動伐木](https://bettergi.com/feats/task/felling.html):自動 Z 鍵使用「王樹瑞佑」,利用上下線可以刷新木材的原理,掛機刷滿一背包的木材 - * [自動秘境](https://bettergi.com/feats/task/domain.html):全自動秘境掛機刷體力,自動循環進入秘境開啟鑰匙、戰鬥、走到古樹並領取獎勵 - * [自動音遊](https://bettergi.com/feats/task/music.html):一鍵自動完成千音雅集的專輯,快速獲取成就 - * [全自動釣魚](https://bettergi.com/feats/task/fish.html):在出現釣魚 F 按鈕的位置面向魚塘,然後啟動全自動釣魚,啟動後程式會自動完成釣魚,並切換白天和晚上 + * [全自動七聖召喚](https://www.bettergi.com/feats/task/tcg.html):幫助你輕鬆完成七聖召喚角色邀請、每週來客挑戰等 PVE 內容 + * [自動伐木](https://www.bettergi.com/feats/task/felling.html):自動 Z 鍵使用「王樹瑞佑」,利用上下線可以刷新木材的原理,掛機刷滿一背包的木材 + * [自動秘境](https://www.bettergi.com/feats/task/domain.html):全自動秘境掛機刷體力,自動循環進入秘境開啟鑰匙、戰鬥、走到古樹並領取獎勵 + * [自動幽境危戰](https://www.bettergi.com/feats/task/stygian.html):自動傳送並前往幽境危戰,主要用於難度三刷聖遺物 + * [全自動釣魚](https://www.bettergi.com/feats/task/fish.html):在出現釣魚 F 按鈕的位置面向魚塘,然後啟動全自動釣魚,啟動後程式會自動完成釣魚,並切換白天和晚上 + * [自動地脈花](https://www.bettergi.com/feats/task/leyline.html):自動連續刷地脈花,支援絕大部分地脈花位置。 + * [自動音遊](https://www.bettergi.com/feats/task/music.html):一鍵自動完成千音雅集的專輯,快速獲取成就 + * [自動烹飪](https://www.bettergi.com/feats/task/cook.html):自動在完美區域完成食物烹飪 + * [自動分解聖遺物](https://www.bettergi.com/feats/task/artifactSalvage.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://www.bettergi.com/feats/autos/pathing.html):透過左上角小地圖的識別,完成自動採集、挖礦、鋤地等功能 + * [鍵鼠錄製](https://www.bettergi.com/feats/autos/kmscript.html):可以錄製回放當前的鍵鼠操作,建議配合調度器使用 * 操控輔助 - * [那維萊特轉圈](https://bettergi.com/feats/macro/other.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/feats/macro/other.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/feats/macro/other.html#%E4%B8%80%E9%94%AE%E8%B3%BC%E8%B2%B7):可以快速以滿數量購買商店中的物品,適合快速清空活動兌換,塵歌壺商店兌換等 -* [**……**](https://bettergi.com/doc.html) + * [那維萊特轉圈](https://www.bettergi.com/feats/macro/other.html#%E9%82%A3%E7%BB%B4%E8%8E%B1%E7%89%B9-%E8%BD%AC%E5%9C%88%E5%9C%88):設定快捷鍵後,長按可以不斷水平旋轉視角(當然你也可以用來轉草神) + * [快速聖遺物強化](https://www.bettergi.com/feats/macro/other.html#%E5%9C%A3%E9%81%97%E7%89%A9%E4%B8%80%E9%94%AE%E5%BC%BA%E5%8C%96):透過快速切換「詳情」、「強化」頁跳過聖遺物強化結果展示,快速+20 + * [商店一鍵購買](https://www.bettergi.com/feats/macro/other.html#%E4%B8%80%E9%94%AE%E8%B3%BC%E8%B2%B7):可以快速以滿數量購買商店中的物品,適合快速清空活動兌換,塵歌壺商店兌換等 +* [**……**](https://www.bettergi.com/doc.html)
@@ -69,7 +72,7 @@ BetterGI · 更好的原神, 一個基於電腦視覺技術,意圖讓原神 ## 截圖 -![0 39 1](https://github.com/user-attachments/assets/8fb0bfd9-e0db-4289-800f-1bc2efb221aa) +![0 39 1](https://github.com/user-attachments/assets/a65aafe9-d8d7-4ffb-8cdc-9939c2fb3bdf) ## 下載 @@ -77,7 +80,7 @@ BetterGI · 更好的原神, 一個基於電腦視覺技術,意圖讓原神 > [!NOTE] > 下載地址:[⚡Github 下載](https://github.com/babalae/better-genshin-impact/releases) > -> 不知道下載哪個?第一次使用?請看:[快速上手](https://bettergi.com/quickstart.html) , 遇到問題請先看:[常見問題](https://bettergi.com/faq.html) +> 不知道下載哪個?第一次使用?請看:[快速上手](https://www.bettergi.com/quickstart.html) , 遇到問題請先看:[常見問題](https://www.bettergi.com/faq.html) 最新編譯版本可以從自動構建中獲取: [![](https://github.com/babalae/better-genshin-impact/actions/workflows/publish.yml/badge.svg)](https://github.com/babalae/better-genshin-impact/actions/workflows/publish.yml) @@ -98,16 +101,16 @@ BetterGI · 更好的原神, 一個基於電腦視覺技術,意圖讓原神 **打開軟件以後,在「啟動」頁選擇好截圖方式,點擊啟動按鈕就可以享受 BetterGI 帶來的便利了!** -詳細使用指南請看:[快速上手](https://bettergi.com/quickstart.html) +詳細使用指南請看:[快速上手](https://www.bettergi.com/quickstart.html) -具體功能效果與使用方式見:[文件](https://bettergi.com/doc.html) +具體功能效果與使用方式見:[文件](https://www.bettergi.com/doc.html) ## 常見問題 * 為什麼需要管理員權限? * 因為遊戲是以管理員權限啟動的,軟件不以管理員權限啟動的話沒有權限模擬鼠標點擊。 * 會不會封號? * 理論上不會被封。 **BetterGI 不會做出任何修改遊戲文件、讀寫遊戲記憶體等任何危害遊戲本體的行為,單純依靠視覺算法和模擬操作實現。** 但是mhy是自由的,用戶條款上明確說明第三方軟件/模擬操作是封號理由之一。當前方案還是存在被檢測的可能。只能說請低調使用,請不要跳臉官方。 -* [更多常見問題...](https://bettergi.com/faq.html) +* [更多常見問題...](https://www.bettergi.com/faq.html) ## 致謝 diff --git a/README.md b/README.md index 9565f811..6ec07a0b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@

- +
- BetterGI + BetterGI

babalae%2Fbetter-genshin-impact | Trendshift
@@ -34,28 +34,31 @@ BetterGI · 更好的原神, 一个基于计算机视觉技术,意图让原 ## 功能 * 实时任务 - * [自动拾取](https://bettergi.com/feats/timer/pick.html):遇到可交互/拾取内容时自动按 F,支持黑白名单配置 - * [自动剧情](https://bettergi.com/feats/timer/skip.html):快速点击过剧情、自动选择选项、自动提交物品、关闭弹出书页等 - * 与凯瑟琳对话时有橙色选项会 [自动领取「每日委托」奖励](https://bettergi.com/feats/timer/skip.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/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%87%8D%E6%96%B0%E6%B4%BE%E9%81%A3) - * [自动邀约](https://bettergi.com/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%82%80%E7%BA%A6):自动剧情开启的情况下此功能才会生效,自动选择邀约选项 - * [快速传送](https://bettergi.com/feats/timer/tp.html):在地图上点击传送点,或者点击后出现的列表中存在传送点,会自动点击传送点并传送 - * [半自动钓鱼](https://bettergi.com/feats/timer/fish.html):AI 识别自动抛竿,鱼上钩时自动收杆,并自动完成钓鱼进度 - * [自动烹饪](https://bettergi.com/feats/timer/cook.html):自动在完美区域完成食物烹饪,暂不支持“仙跳墙” + * [自动拾取](https://www.bettergi.com/feats/timer/pick.html):遇到可交互/拾取内容时自动按 F,支持黑白名单配置 + * [自动剧情](https://www.bettergi.com/feats/timer/skip.html):快速点击过剧情、自动选择选项、自动提交物品、关闭弹出书页等 + * 与凯瑟琳对话时有橙色选项会 [自动领取「每日委托」奖励](https://www.bettergi.com/feats/timer/skip.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://www.bettergi.com/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%87%8D%E6%96%B0%E6%B4%BE%E9%81%A3) + * [自动邀约](https://www.bettergi.com/feats/timer/skip.html#%E8%87%AA%E5%8A%A8%E9%82%80%E7%BA%A6):自动剧情开启的情况下此功能才会生效,自动选择邀约选项 + * [快速传送](https://www.bettergi.com/feats/timer/tp.html):在地图上点击传送点,或者点击后出现的列表中存在传送点,会自动点击传送点并传送 + * [半自动钓鱼](https://www.bettergi.com/feats/timer/fish.html):AI 识别自动抛竿,鱼上钩时自动收杆,并自动完成钓鱼进度 * 独立任务 - * [全自动七圣召唤](https://bettergi.com/feats/task/tcg.html):帮助你轻松完成七圣召唤角色邀请、每周来客挑战等 PVE 内容 - * [自动伐木](https://bettergi.com/feats/task/felling.html):自动 Z 键使用「王树瑞佑」,利用上下线可以刷新木材的原理,挂机刷满一背包的木材 - * [自动秘境](https://bettergi.com/feats/task/domain.html):全自动秘境挂机刷体力,自动循环进入秘境开启钥匙、战斗、走到古树并领取奖励 - * [自动音游](https://bettergi.com/feats/task/music.html):一键自动完成千音雅集的专辑,快速获取成就 - * [全自动钓鱼](https://bettergi.com/feats/task/fish.html):在出现钓鱼 F 按钮的位置面向鱼塘,然后启动全自动钓鱼,启动后程序会自动完成钓鱼,并切换白天和晚上 + * [全自动七圣召唤](https://www.bettergi.com/feats/task/tcg.html):帮助你轻松完成七圣召唤角色邀请、每周来客挑战等 PVE 内容 + * [自动伐木](https://www.bettergi.com/feats/task/felling.html):自动 Z 键使用「王树瑞佑」,利用上下线可以刷新木材的原理,挂机刷满一背包的木材 + * [自动秘境](https://www.bettergi.com/feats/task/domain.html):全自动秘境挂机刷体力,自动循环进入秘境开启钥匙、战斗、走到古树并领取奖励 + * [自动幽境危战](https://www.bettergi.com/feats/task/stygian.html):自动传送并前往幽境危战,主要用于难度三刷圣遗物 + * [全自动钓鱼](https://www.bettergi.com/feats/task/fish.html):在出现钓鱼 F 按钮的位置面向鱼塘,然后启动全自动钓鱼,启动后程序会自动完成钓鱼,并切换白天和晚上 + * [自动地脉花](https://www.bettergi.com/feats/task/leyline.html):自动连续刷地脉花,支持绝大部分地脉花位置。 + * [自动音游](https://www.bettergi.com/feats/task/music.html):一键自动完成千音雅集的专辑,快速获取成就 + * [自动烹饪](https://www.bettergi.com/feats/task/cook.html):自动在完美区域完成食物烹饪 + * [自动分解圣遗物](https://www.bettergi.com/feats/task/artifactSalvage.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://www.bettergi.com/feats/autos/pathing.html):通过左上角小地图的识别,完成自动采集、挖矿、锄地等功能 + * [键鼠录制](https://www.bettergi.com/feats/autos/kmscript.html):可以录制回放当前的键鼠操作,建议配合调度器使用 * 操控辅助 - * [那维莱特转圈](https://bettergi.com/feats/macro/other.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/feats/macro/other.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/feats/macro/other.html#%E4%B8%80%E9%94%AE%E8%B4%AD%E4%B9%B0):可以快速以满数量购买商店中的物品,适合快速清空活动兑换,尘歌壶商店兑换等 -* [**……**](https://bettergi.com/doc.html) + * [那维莱特转圈](https://www.bettergi.com/feats/macro/other.html#%E9%82%A3%E7%BB%B4%E8%8E%B1%E7%89%B9-%E8%BD%AC%E5%9C%88%E5%9C%88):设置快捷键后,长按可以不断水平旋转视角(当然你也可以用来转草神) + * [快速圣遗物强化](https://www.bettergi.com/feats/macro/other.html#%E5%9C%A3%E9%81%97%E7%89%A9%E4%B8%80%E9%94%AE%E5%BC%BA%E5%8C%96):通过快速切换“详情”、“强化”页跳过圣遗物强化结果展示,快速+20 + * [商店一键购买](https://www.bettergi.com/feats/macro/other.html#%E4%B8%80%E9%94%AE%E8%B4%AD%E4%B9%B0):可以快速以满数量购买商店中的物品,适合快速清空活动兑换,尘歌壶商店兑换等 +* [**……**](https://www.bettergi.com/doc.html)
@@ -64,7 +67,7 @@ BetterGI · 更好的原神, 一个基于计算机视觉技术,意图让原 ## 截图 -![0 39 1](https://github.com/user-attachments/assets/8fb0bfd9-e0db-4289-800f-1bc2efb221aa) +![0 59 1](https://github.com/user-attachments/assets/a65aafe9-d8d7-4ffb-8cdc-9939c2fb3bdf) ## 下载 @@ -72,7 +75,7 @@ BetterGI · 更好的原神, 一个基于计算机视觉技术,意图让原 > [!NOTE] > 下载地址:[⚡Github 下载](https://github.com/babalae/better-genshin-impact/releases) > -> 不知道下载哪个?第一次使用?请看:[快速上手](https://bettergi.com/quickstart.html) , 遇到问题请先看:[常见问题](https://bettergi.com/faq.html) +> 不知道下载哪个?第一次使用?请看:[快速上手](https://www.bettergi.com/quickstart.html) , 遇到问题请先看:[常见问题](https://www.bettergi.com/faq.html) 最新测试版本可以从自动构建中获取: [![](https://github.com/babalae/better-genshin-impact/actions/workflows/publish.yml/badge.svg)](https://github.com/babalae/better-genshin-impact/actions/workflows/publish.yml) 或者从CNB上直接下载(速度快):[CNB Releases](https://cnb.cool/bettergi/better-genshin-impact/-/releases) @@ -94,16 +97,16 @@ BetterGI · 更好的原神, 一个基于计算机视觉技术,意图让原 **打开软件以后,在“启动”页选择好截图方式,点击启动按钮就可以享受 BetterGI 带来的便利了!** -详细使用指南请看:[快速上手](https://bettergi.com/quickstart.html) +详细使用指南请看:[快速上手](https://www.bettergi.com/quickstart.html) -具体功能效果与使用方式见:[文档](https://bettergi.com/doc.html) +具体功能效果与使用方式见:[文档](https://www.bettergi.com/doc.html) ## FAQ * 为什么需要管理员权限? * 因为游戏是以管理员权限启动的,软件不以管理员权限启动的话没有权限模拟鼠标点击。 * 会不会封号? * 理论上不会被封。 **BetterGI 不会做出任何修改游戏文件、读写游戏内存等任何危害游戏本体的行为,单纯依靠视觉算法和模拟操作实现。** 但是mhy是自由的,用户条款上明确说明第三方软件/模拟操作是封号理由之一。当前方案还是存在被检测的可能。只能说请低调使用,请不要跳脸官方。 -* [更多常见问题...](https://bettergi.com/faq.html) +* [更多常见问题...](https://www.bettergi.com/faq.html) ## 致谢 From b1f1fe913a800dd6b7c664a5f80a433722ddfbdf Mon Sep 17 00:00:00 2001 From: ddaodan <40017293+ddaodan@users.noreply.github.com> Date: Sun, 5 Apr 2026 01:36:02 +0800 Subject: [PATCH 098/107] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=BA=AA?= =?UTF-8?q?=E8=A1=8C=E5=8F=AF=E9=80=89=E5=A5=96=E5=8A=B1=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=8D=A1=E4=BD=8F=E5=B9=B6=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=8F=90=E9=86=92=20(#2995)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/Job/ClaimBattlePassRewardsTask.cs | 57 +++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/BetterGenshinImpact/GameTask/Common/Job/ClaimBattlePassRewardsTask.cs b/BetterGenshinImpact/GameTask/Common/Job/ClaimBattlePassRewardsTask.cs index 75413de7..e33e4c0c 100644 --- a/BetterGenshinImpact/GameTask/Common/Job/ClaimBattlePassRewardsTask.cs +++ b/BetterGenshinImpact/GameTask/Common/Job/ClaimBattlePassRewardsTask.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; @@ -7,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using BetterGenshinImpact.Core.Recognition; using BetterGenshinImpact.Core.Simulator.Extensions; +using BetterGenshinImpact.GameTask.Common.BgiVision; using BetterGenshinImpact.GameTask.Common.Element.Assets; using BetterGenshinImpact.GameTask.Model.Area; using BetterGenshinImpact.Helpers; @@ -27,6 +27,8 @@ public class ClaimBattlePassRewardsTask private readonly string[] claimAllLocalizedStrings; + private bool _manualSelectionReminderLogged; + public ClaimBattlePassRewardsTask() { IStringLocalizer stringLocalizer = App.GetService>() ?? throw new NullReferenceException(); @@ -54,18 +56,13 @@ public class ClaimBattlePassRewardsTask await Delay(200, ct); TaskContext.Instance().PostMessageSimulator.SimulateAction(GIActions.OpenBattlePassScreen); // F4 开纪行 - // 领取战令1 - await Delay(1000, ct); - await ClaimAll(ct); - - - // 领取点数 + // 先领取纪行点数,避免一进入纪行就因可选奖励弹窗阻塞后续流程 await Delay(1000, ct); GameCaptureRegion.GameRegion1080PPosClick(960, 45); // 点中间 await Delay(500, ct); await ClaimAll(ct); - // 领取战令2 + // 最后再回到奖励页领取,若存在手动选择奖励则仅记录日志提醒 await Delay(2500, ct); // 等待升级动画 // 还可能存在领取到原石的情况 if (CaptureToRectArea().Find(ElementAssets.Instance.PrimogemRo).IsExist()) @@ -89,13 +86,18 @@ public class ClaimBattlePassRewardsTask using var ra = CaptureToRectArea(); var ocrList = ra.FindMulti(RecognitionObject.Ocr(ra.ToRect().CutRightBottom(0.3, 0.2))); var wt = ocrList.FirstOrDefault(txt => this.claimAllLocalizedStrings.Any(i => Regex.IsMatch(txt.Text, i))); - Debug.WriteLine(this.claimAllLocalizedStrings); if (wt != null) { wt.Click(); Logger.LogInformation("纪行:{Text}", "一键领取"); await Delay(1000, ct); using var ra2 = CaptureToRectArea(); + if (IsManualSelectionDialog(ra2)) + { + LogManualSelectionReminder(); + return true; + } + if (ra2.Find(ElementAssets.Instance.PrimogemRo).IsExist()) { TaskContext.Instance().PostMessageSimulator.KeyPress(User32.VK.VK_ESCAPE); @@ -109,4 +111,39 @@ public class ClaimBattlePassRewardsTask return false; } } -} \ No newline at end of file + + private static bool IsManualSelectionDialog(ImageRegion region) + { + if (Bv.IsInPromptDialog(region)) + { + return true; + } + + var hasCancelButton = HasDialogButton(region, ElementAssets.Instance.BtnBlackCancel) + || HasDialogButton(region, ElementAssets.Instance.BtnWhiteCancel); + if (!hasCancelButton) + { + return false; + } + + return HasDialogButton(region, ElementAssets.Instance.BtnBlackConfirm) + || HasDialogButton(region, ElementAssets.Instance.BtnWhiteConfirm); + } + + private static bool HasDialogButton(ImageRegion region, RecognitionObject recognitionObject) + { + using var buttonRegion = region.Find(recognitionObject); + return buttonRegion.IsExist(); + } + + private void LogManualSelectionReminder() + { + if (_manualSelectionReminderLogged) + { + return; + } + + _manualSelectionReminderLogged = true; + Logger.LogWarning("纪行:检测到需手动选择的奖励,请手动处理"); + } +} From 8a6194f3746ebddd738c8c8ed80e1a4aefba51a8 Mon Sep 17 00:00:00 2001 From: ToXz <108231126+richardtoxz@users.noreply.github.com> Date: Sun, 5 Apr 2026 08:45:57 -0300 Subject: [PATCH 099/107] feat : Hoyolab map provider with 3 language selector (#2987) --- BetterGenshinImpact/App.xaml.cs | 1 + .../GameTask/MapMask/MapMaskConfig.cs | 12 ++ .../GameTask/MapMask/MapPointApiProvider.cs | 3 +- .../Service/HoYoLabMapApiService.cs | 104 ++++++++++++++++++ .../Interface/IHoYoLabMapApiService.cs | 5 + .../Service/MaskMapPointService.cs | 22 +++- BetterGenshinImpact/View/MaskWindow.xaml | 17 ++- .../ViewModel/MaskWindowViewModel.cs | 64 ++++++++++- 8 files changed, 220 insertions(+), 8 deletions(-) create mode 100644 BetterGenshinImpact/Service/HoYoLabMapApiService.cs create mode 100644 BetterGenshinImpact/Service/Interface/IHoYoLabMapApiService.cs diff --git a/BetterGenshinImpact/App.xaml.cs b/BetterGenshinImpact/App.xaml.cs index 382e0da9..fcb4df7c 100644 --- a/BetterGenshinImpact/App.xaml.cs +++ b/BetterGenshinImpact/App.xaml.cs @@ -164,6 +164,7 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(TimeProvider.System); diff --git a/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs b/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs index 63979f47..eb1dbced 100644 --- a/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs +++ b/BetterGenshinImpact/GameTask/MapMask/MapMaskConfig.cs @@ -10,6 +10,10 @@ namespace BetterGenshinImpact.GameTask.MapMask; [Serializable] public partial class MapMaskConfig : ObservableObject { + public const string HoYoLabLanguageEnUs = "en-us"; + public const string HoYoLabLanguagePtPt = "pt-pt"; + public const string HoYoLabLanguageEsEs = "es-es"; + /// /// 是否启用 /// @@ -30,10 +34,18 @@ public partial class MapMaskConfig : ObservableObject private MapPointApiProvider _mapPointApiProvider = MapPointApiProvider.MihoyoMap; + private string _hoYoLabLanguage = HoYoLabLanguageEnUs; + [JsonConverter(typeof(JsonStringEnumConverter))] public MapPointApiProvider MapPointApiProvider { get => _mapPointApiProvider; set => SetProperty(ref _mapPointApiProvider, value); } + + public string HoYoLabLanguage + { + get => _hoYoLabLanguage; + set => SetProperty(ref _hoYoLabLanguage, value); + } } diff --git a/BetterGenshinImpact/GameTask/MapMask/MapPointApiProvider.cs b/BetterGenshinImpact/GameTask/MapMask/MapPointApiProvider.cs index a32af914..bb9c8caa 100644 --- a/BetterGenshinImpact/GameTask/MapMask/MapPointApiProvider.cs +++ b/BetterGenshinImpact/GameTask/MapMask/MapPointApiProvider.cs @@ -3,5 +3,6 @@ namespace BetterGenshinImpact.GameTask.MapMask; public enum MapPointApiProvider { MihoyoMap = 0, - KongyingTavern = 1 + KongyingTavern = 1, + HoYoLab = 2 } diff --git a/BetterGenshinImpact/Service/HoYoLabMapApiService.cs b/BetterGenshinImpact/Service/HoYoLabMapApiService.cs new file mode 100644 index 00000000..da7bc724 --- /dev/null +++ b/BetterGenshinImpact/Service/HoYoLabMapApiService.cs @@ -0,0 +1,104 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using BetterGenshinImpact.Helpers.Http; +using BetterGenshinImpact.GameTask; +using BetterGenshinImpact.GameTask.MapMask; +using BetterGenshinImpact.Service.Interface; +using BetterGenshinImpact.Service.Model.MihoyoMap.Requests; +using BetterGenshinImpact.Service.Model.MihoyoMap.Responses; +using Newtonsoft.Json; + +namespace BetterGenshinImpact.Service; + +public class HoYoLabMapApiService : IHoYoLabMapApiService +{ + private readonly HttpClient _httpClient; + private const string TreeEndpoint = "https://sg-public-api-static.hoyolab.com/common/map_user/ys_obc/v2/map/label/tree"; + private const string ListEndpoint = "https://sg-public-api-static.hoyolab.com/common/map_user/ys_obc/v3/map/point/list"; + private const string InfoEndpoint = "https://sg-public-api-static.hoyolab.com/common/map_user/ys_obc/v1/map/point/info"; + private const string DefaultLang = MapMaskConfig.HoYoLabLanguageEnUs; + + public HoYoLabMapApiService() + { + _httpClient = HttpClientFactory.GetCommonSendClient(); + } + + private static HttpRequestMessage CreateRequest(HttpMethod method, string url) + { + var request = new HttpRequestMessage(method, url); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Headers.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"); + request.Headers.Referrer = new Uri("https://act.hoyolab.com/"); + return request; + } + + private static T DeserializeRequired(string json) + { + var result = JsonConvert.DeserializeObject(json); + if (result == null) + { + throw new JsonException($"Failed to deserialize {typeof(T).Name}. The API returned an empty or invalid JSON."); + } + + return result; + } + + public static string NormalizeLanguage(string? lang) + { + var normalized = (lang ?? string.Empty).Trim().Replace('_', '-').Replace(' ', '-').ToLowerInvariant(); + return normalized switch + { + MapMaskConfig.HoYoLabLanguagePtPt => MapMaskConfig.HoYoLabLanguagePtPt, + MapMaskConfig.HoYoLabLanguageEsEs => MapMaskConfig.HoYoLabLanguageEsEs, + MapMaskConfig.HoYoLabLanguageEnUs => MapMaskConfig.HoYoLabLanguageEnUs, + _ => DefaultLang + }; + } + + private static string GetCurrentLanguage() + { + var lang = TaskContext.Instance().Config.MapMaskConfig.HoYoLabLanguage; + return NormalizeLanguage(lang); + } + + public async Task> GetLabelTreeAsync(LabelTreeRequest request, CancellationToken ct = default) + { + var lang = GetCurrentLanguage(); + var url = $"{TreeEndpoint}?map_id={request.MapId}&app_sn={Uri.EscapeDataString(request.AppSn)}&lang={lang}"; + using var httpRequest = CreateRequest(HttpMethod.Get, url); + using var resp = await _httpClient.SendAsync(httpRequest, ct); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync(ct); + return DeserializeRequired>(json); + } + + public async Task> GetPointInfoAsync(PointInfoRequest request, CancellationToken ct = default) + { + var lang = GetCurrentLanguage(); + var url = $"{InfoEndpoint}?map_id={request.MapId}&point_id={request.PointId}&app_sn={Uri.EscapeDataString(request.AppSn)}&lang={lang}"; + using var httpRequest = CreateRequest(HttpMethod.Get, url); + httpRequest.Headers.Add("x-rpc-map_version", "4.5"); + using var resp = await _httpClient.SendAsync(httpRequest, ct); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync(ct); + return DeserializeRequired>(json); + } + + public async Task> GetPointListAsync(PointListRequest request, CancellationToken ct = default) + { + var lang = GetCurrentLanguage(); + var labelIds = request.LabelIds != null && request.LabelIds.Count > 0 + ? string.Join(",", request.LabelIds.Select(x => x.ToString())) + : string.Empty; + var url = $"{ListEndpoint}?map_id={request.MapId}&app_sn={Uri.EscapeDataString(request.AppSn)}&lang={lang}&label_ids={Uri.EscapeDataString(labelIds)}"; + using var httpRequest = CreateRequest(HttpMethod.Get, url); + using var resp = await _httpClient.SendAsync(httpRequest, ct); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync(ct); + return DeserializeRequired>(json); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/Interface/IHoYoLabMapApiService.cs b/BetterGenshinImpact/Service/Interface/IHoYoLabMapApiService.cs new file mode 100644 index 00000000..7485cf4e --- /dev/null +++ b/BetterGenshinImpact/Service/Interface/IHoYoLabMapApiService.cs @@ -0,0 +1,5 @@ +namespace BetterGenshinImpact.Service.Interface; + +public interface IHoYoLabMapApiService : IMihoyoMapApiService +{ +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/MaskMapPointService.cs b/BetterGenshinImpact/Service/MaskMapPointService.cs index 2043b653..9578079e 100644 --- a/BetterGenshinImpact/Service/MaskMapPointService.cs +++ b/BetterGenshinImpact/Service/MaskMapPointService.cs @@ -44,17 +44,20 @@ public sealed class MaskMapPointService : IMaskMapPointService private readonly ILogger _logger; private readonly IAppCache _cache; + private readonly IHoYoLabMapApiService _hoyolabMapApi; private readonly IMihoyoMapApiService _mihoyoMapApi; private readonly IKongyingTavernApiService _kongyingTavernApi; public MaskMapPointService( ILogger logger, IAppCache cache, + IHoYoLabMapApiService hoyolabMapApi, IMihoyoMapApiService mihoyoMapApi, IKongyingTavernApiService kongyingTavernApi) { _logger = logger; _cache = cache; + _hoyolabMapApi = hoyolabMapApi; _mihoyoMapApi = mihoyoMapApi; _kongyingTavernApi = kongyingTavernApi; } @@ -91,12 +94,17 @@ public sealed class MaskMapPointService : IMaskMapPointService return TaskContext.Instance().Config.MapMaskConfig.MapPointApiProvider; } + private IMihoyoMapApiService GetMihoyoCompatibleApi() + { + return GetProvider() == MapPointApiProvider.HoYoLab ? _hoyolabMapApi : _mihoyoMapApi; + } + private async Task> GetMihoyoLabelCategoriesAsync(CancellationToken ct) { ApiResponse? resp = null; try { - resp = await _mihoyoMapApi.GetLabelTreeAsync(new LabelTreeRequest(), ct); + resp = await GetMihoyoCompatibleApi().GetLabelTreeAsync(new LabelTreeRequest(), ct); } catch (Exception ex) { @@ -230,18 +238,24 @@ public sealed class MaskMapPointService : IMaskMapPointService private Task> GetMihoyoPointListCacheAsync(IReadOnlyList parentLabelIds, CancellationToken ct) { var labelIds = parentLabelIds?.Distinct().OrderBy(x => x).ToArray() ?? Array.Empty(); - var key = $"mihoyo-map:point-list:2:ys_obc:zh-cn:{string.Join(",", labelIds)}"; + var provider = GetProvider(); + var providerKey = provider == MapPointApiProvider.HoYoLab ? "hoyolab" : "mihoyo-map"; + var langSegment = provider == MapPointApiProvider.HoYoLab + ? $":lang:{HoYoLabMapApiService.NormalizeLanguage(TaskContext.Instance().Config.MapMaskConfig.HoYoLabLanguage)}" + : string.Empty; + var key = $"{providerKey}:point-list:2:ys_obc{langSegment}:{string.Join(",", labelIds)}"; var request = new PointListRequest { LabelIds = labelIds.ToList() }; + var api = GetMihoyoCompatibleApi(); return _cache.GetOrAddAsync( key, async entry => { entry.AbsoluteExpirationRelativeToNow = CacheDuration; - return await _mihoyoMapApi.GetPointListAsync(request, CancellationToken.None); + return await api.GetPointListAsync(request, CancellationToken.None); }) .WaitAsync(ct); } @@ -255,7 +269,7 @@ public sealed class MaskMapPointService : IMaskMapPointService try { - var resp = await _mihoyoMapApi.GetPointInfoAsync(new PointInfoRequest { PointId = pointId }, ct); + var resp = await GetMihoyoCompatibleApi().GetPointInfoAsync(new PointInfoRequest { PointId = pointId }, ct); if (resp.Retcode != 0 || resp.Data == null) { return new MaskMapPointInfo { Text = $"查询失败: {resp.Retcode} {resp.Message}" }; diff --git a/BetterGenshinImpact/View/MaskWindow.xaml b/BetterGenshinImpact/View/MaskWindow.xaml index c1d9b363..dee41961 100644 --- a/BetterGenshinImpact/View/MaskWindow.xaml +++ b/BetterGenshinImpact/View/MaskWindow.xaml @@ -777,6 +777,7 @@ + @@ -793,14 +794,26 @@ SelectedItem="{Binding SelectedMapPointApiProviderOption, Mode=TwoWay}" DisplayMemberPath="DisplayName" /> + + - + MapPointApiProviderOptions { get; } = [ new(MapPointApiProvider.MihoyoMap, "米游社大地图"), - new(MapPointApiProvider.KongyingTavern, "空荧酒馆") + new(MapPointApiProvider.KongyingTavern, "空荧酒馆"), + new(MapPointApiProvider.HoYoLab, "HoYoLab") + ]; + + public IReadOnlyList HoYoLabLanguageOptions { get; } = + [ + new(MapMaskConfig.HoYoLabLanguageEnUs, "English (en-us)"), + new(MapMaskConfig.HoYoLabLanguagePtPt, "Português (pt-pt)"), + new(MapMaskConfig.HoYoLabLanguageEsEs, "Español (es-es)") ]; [ObservableProperty] private MapPointApiProviderOption? _selectedMapPointApiProviderOption; + [ObservableProperty] private MapLanguageOption? _selectedHoYoLabLanguageOption; + + public bool IsHoYoLabProviderSelected => SelectedMapPointApiProviderOption?.Provider == MapPointApiProvider.HoYoLab; + public MaskMapPointInfoPopupViewModel PointInfoPopup { get; } = new(); private bool _isMapLabelTreeLoaded; @@ -194,6 +209,7 @@ namespace BetterGenshinImpact.ViewModel } SyncSelectedMapPointApiProviderFromConfig(); + SyncSelectedHoYoLabLanguageFromConfig(); } /// @@ -220,6 +236,7 @@ namespace BetterGenshinImpact.ViewModel partial void OnSelectedMapPointApiProviderOptionChanged(MapPointApiProviderOption? value) { + OnPropertyChanged(nameof(IsHoYoLabProviderSelected)); if (value == null) { return; @@ -228,6 +245,51 @@ namespace BetterGenshinImpact.ViewModel _ = SwitchMapPointApiProviderAsync(value.Provider); } + partial void OnSelectedHoYoLabLanguageOptionChanged(MapLanguageOption? value) + { + if (value == null) + { + return; + } + + _ = SwitchHoYoLabLanguageAsync(value.Code); + } + + private void SyncSelectedHoYoLabLanguageFromConfig() + { + var lang = HoYoLabMapApiService.NormalizeLanguage(TaskContext.Instance().Config.MapMaskConfig.HoYoLabLanguage); + SelectedHoYoLabLanguageOption = HoYoLabLanguageOptions.FirstOrDefault(x => x.Code == lang) + ?? HoYoLabLanguageOptions.FirstOrDefault(); + } + + private async Task SwitchHoYoLabLanguageAsync(string language) + { + try + { + var normalized = HoYoLabMapApiService.NormalizeLanguage(language); + var mapMaskConfig = TaskContext.Instance().Config.MapMaskConfig; + if (mapMaskConfig.HoYoLabLanguage == normalized) + { + return; + } + + mapMaskConfig.HoYoLabLanguage = normalized; + if (Config != null) + { + Config.MapMaskConfig.HoYoLabLanguage = normalized; + } + + if (mapMaskConfig.MapPointApiProvider == MapPointApiProvider.HoYoLab) + { + await ResetAndReloadMapPointPickerAsync(); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "切换HoYoLab语言时发生异常"); + } + } + private async Task SwitchMapPointApiProviderAsync(MapPointApiProvider provider) { try From 4ad7db06ae820bf786a6ad062a00b648ca77aca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 5 Apr 2026 20:21:54 +0800 Subject: [PATCH 100/107] =?UTF-8?q?fix(ui):=20=E5=A4=84=E7=90=86=E6=A1=8C?= =?UTF-8?q?=E9=9D=A2=E7=BB=84=E5=90=88=E7=A6=81=E7=94=A8=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E4=B8=BB=E9=A2=98=E5=BA=94=E7=94=A8=E5=BC=82=E5=B8=B8=20#2678?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当系统桌面组合功能被禁用时,应用 Mica/Acrylic 主题会抛出 COM 异常(HRESULT: 0x80263001)。现通过异常捕获机制,在检测到此特定错误时回退到纯色背景方案,确保程序在受限环境下仍能正常运行。 - 在 ApplyThemeToWindow 方法中添加异常处理逻辑 - 引入 ApplyFallbackTheme 方法应用纯色后备主题 - 根据主题类型提供对应的后备背景色 --- .../Helpers/Ui/WindowHelper.cs | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs b/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs index d7655fd2..0b859e93 100644 --- a/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs +++ b/BetterGenshinImpact/Helpers/Ui/WindowHelper.cs @@ -1,4 +1,6 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; using System.Windows.Media; using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.GameTask; @@ -9,6 +11,8 @@ namespace BetterGenshinImpact.Helpers.Ui; public class WindowHelper { + private const uint DesktopCompositionDisabledHResult = 0x80263001; + public static void TryApplySystemBackdrop(System.Windows.Window window) { var themeType = TaskContext.Instance().Config.CommonConfig.CurrentThemeType; @@ -37,6 +41,22 @@ public class WindowHelper /// 要应用主题的窗口 /// 主题类型 public static void ApplyThemeToWindow(System.Windows.Window window, ThemeType themeType) + { + try + { + ApplyThemeCore(window, themeType); + } + catch (COMException ex) when ((uint)ex.HResult == DesktopCompositionDisabledHResult) + { + ApplyFallbackTheme(window, themeType); + } + catch + { + ApplyFallbackTheme(window, themeType); + } + } + + private static void ApplyThemeCore(System.Windows.Window window, ThemeType themeType) { switch (themeType) { @@ -76,4 +96,21 @@ public class WindowHelper break; } } -} \ No newline at end of file + + private static void ApplyFallbackTheme(System.Windows.Window window, ThemeType themeType) + { + window.Background = new SolidColorBrush(GetFallbackBackgroundColor(themeType)); + WindowBackdrop.ApplyBackdrop(window, WindowBackdropType.None); + } + + private static Color GetFallbackBackgroundColor(ThemeType themeType) + { + return themeType switch + { + ThemeType.LightNone => Color.FromArgb(255, 243, 243, 243), + ThemeType.LightMica => Color.FromArgb(255, 243, 243, 243), + ThemeType.LightAcrylic => Color.FromArgb(255, 243, 243, 243), + _ => Color.FromArgb(255, 32, 32, 32) + }; + } +} From ee928a684783ee4e1ec7a9b66d6dd24b7d6d7a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 5 Apr 2026 20:36:26 +0800 Subject: [PATCH 101/107] =?UTF-8?q?=E4=BC=98=E5=8C=96config.json=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E6=88=96=E8=80=85=E5=86=99=E5=85=A5=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E6=97=B6=E5=80=99=E7=9A=84=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86?= =?UTF-8?q?=E3=80=82=E8=AF=BB=E5=8F=96=E5=A4=B1=E8=B4=A5=E4=BC=9A=E5=A4=87?= =?UTF-8?q?=E4=BB=BD=E5=8E=9F=E6=9D=A5=E7=9A=84config=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=B9=B6=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/Service/ConfigService.cs | 56 ++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/BetterGenshinImpact/Service/ConfigService.cs b/BetterGenshinImpact/Service/ConfigService.cs index 1996dc8e..8aed9afa 100644 --- a/BetterGenshinImpact/Service/ConfigService.cs +++ b/BetterGenshinImpact/Service/ConfigService.cs @@ -1,11 +1,13 @@ -using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Service.Interface; +using BetterGenshinImpact.View.Windows; using OpenCvSharp; using System; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; +using Application = System.Windows.Application; namespace BetterGenshinImpact.Service; @@ -13,6 +15,8 @@ public class ConfigService : IConfigService { private readonly object _locker = new(); // 只有UI线程会调用这个方法,lock好像意义不大,而且浪费了下面的读写锁hhh private readonly ReaderWriterLockSlim _rwLock = new(); + private const string ConfigRelativePath = @"User/config.json"; + private const string BackupFolderName = "backup"; public static readonly JsonSerializerOptions JsonOptions = new() { @@ -61,9 +65,9 @@ public class ConfigService : IConfigService public AllConfig Read() { _rwLock.EnterReadLock(); + var filePath = Global.Absolute(ConfigRelativePath); try { - var filePath = Global.Absolute(@"User/config.json"); if (!File.Exists(filePath)) { return new AllConfig(); @@ -83,6 +87,8 @@ public class ConfigService : IConfigService { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); + BackupConfigFile(filePath); + ShowConfigExceptionDialog("读取", e); return new AllConfig(); } finally @@ -94,6 +100,7 @@ public class ConfigService : IConfigService public void Write(AllConfig config) { _rwLock.EnterWriteLock(); + var file = Global.Absolute(ConfigRelativePath); try { var path = Global.Absolute("User"); @@ -102,19 +109,62 @@ public class ConfigService : IConfigService Directory.CreateDirectory(path); } - var file = Path.Combine(path, "config.json"); File.WriteAllText(file, JsonSerializer.Serialize(config, JsonOptions)); } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); + ShowConfigExceptionDialog("写入", e); } finally { _rwLock.ExitWriteLock(); } } + + private static void BackupConfigFile(string filePath) + { + try + { + if (!File.Exists(filePath)) + { + return; + } + + var directoryPath = Path.GetDirectoryName(filePath); + if (string.IsNullOrWhiteSpace(directoryPath)) + { + return; + } + + var backupDirectory = Path.Combine(directoryPath, BackupFolderName); + Directory.CreateDirectory(backupDirectory); + + var backupFileName = $"config_{DateTime.Now:yyyyMMdd_HHmmss_fff}.json.bak"; + var backupFilePath = Path.Combine(backupDirectory, backupFileName); + File.Copy(filePath, backupFilePath, false); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + Console.WriteLine(ex.StackTrace); + } + } + + private static void ShowConfigExceptionDialog(string operation, Exception exception) + { + var current = Application.Current; + if (current?.Dispatcher == null) + { + return; + } + + var coreException = exception.GetBaseException(); + var coreStack = string.IsNullOrWhiteSpace(coreException.StackTrace) ? "无可用堆栈信息" : coreException.StackTrace; + var message = $"配置文件{operation}失败\n错误:{coreException.Message}\n堆栈:\n{coreStack}"; + _ = ThemedMessageBox.ErrorAsync(message, "配置文件异常"); + } } public class OpenCvRectJsonConverter : JsonConverter From 1690a727fc2dc538236c7cbb3acbbfc3d98ece0e Mon Sep 17 00:00:00 2001 From: huiyadanli <15783049+huiyadanli@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:38:22 +0000 Subject: [PATCH 102/107] Update version to 0.59.2-alpha.3 --- BetterGenshinImpact/BetterGenshinImpact.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj index 1d9638ff..185c6eb4 100644 --- a/BetterGenshinImpact/BetterGenshinImpact.csproj +++ b/BetterGenshinImpact/BetterGenshinImpact.csproj @@ -2,7 +2,7 @@ BetterGI - 0.59.2-alpha.2 + 0.59.2-alpha.3 false WinExe net8.0-windows10.0.22621.0 From c1c578d25621fc98c54c26cda3cc7d87c0568758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Sun, 5 Apr 2026 20:57:43 +0800 Subject: [PATCH 103/107] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E9=80=82?= =?UTF-8?q?=E9=BE=84=E6=8F=90=E7=A4=BA=E7=AA=97=E5=8F=A3=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E9=81=BF=E5=85=8D=E9=A2=91=E7=B9=81=E7=82=B9?= =?UTF-8?q?=E5=87=BB=20#2992?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用OCR识别适龄提示文字,避免仅依赖按钮检测导致的误点击 - 添加1秒检测间隔防止频繁触发OCR识别 - 记录最近识别的文字区域用于后续判断 --- .../GameTask/GameLoading/GameLoading.cs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs b/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs index ff80cdc9..699fcef8 100644 --- a/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs +++ b/BetterGenshinImpact/GameTask/GameLoading/GameLoading.cs @@ -1,6 +1,8 @@ using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.Core.Recognition; using BetterGenshinImpact.GameTask.GameLoading.Assets; using System; +using System.Collections.Generic; using System.Diagnostics; using BetterGenshinImpact.GameTask.Common; using BetterGenshinImpact.GameTask.Common.BgiVision; @@ -57,6 +59,9 @@ public class GameLoadingTrigger : ITaskTrigger private bool biliLoginClicked = false; private (double x1080, double y1080)? lastAgreementClickPos = null; + private DateTime _prevAgePromptOcrTime = DateTime.MinValue; + private bool _agePromptTextMatched = false; + private List _latestLoadingOcrRegions = []; public GameLoadingTrigger() { @@ -257,13 +262,25 @@ public class GameLoadingTrigger : ITaskTrigger InnerSetEnabled(false); return; } - - // 适龄提示窗口自动关闭 - var agePopup = content.CaptureRectArea.Find(_elementAssets.BtnWhiteConfirm); - if (!agePopup.IsEmpty()) + + if ((DateTime.Now - _prevAgePromptOcrTime).TotalMilliseconds >= 1000) { - agePopup.Click(); + _prevAgePromptOcrTime = DateTime.Now; + _latestLoadingOcrRegions = content.CaptureRectArea.FindMulti(RecognitionObject.OcrThis); + if (_latestLoadingOcrRegions.Any(region => + region.Text.Contains("适龄") || region.Text.Contains("监护"))) + { + // 适龄提示窗口自动关闭 + var agePopup = content.CaptureRectArea.Find(_elementAssets.BtnWhiteConfirm); + if (!agePopup.IsEmpty()) + { + agePopup.Click(); + _logger.LogInformation("检测到适龄提示,自动点击确认"); + } + } } + + // B服判断 if (!IsBiliJudged) @@ -442,4 +459,4 @@ public class GameLoadingTrigger : ITaskTrigger return (bHWnd, windowType); } -}; \ No newline at end of file +}; From 4c81109f0daee4c80b25c01ab1e8fb022fef5c77 Mon Sep 17 00:00:00 2001 From: ddaodan <40017293+ddaodan@users.noreply.github.com> Date: Mon, 6 Apr 2026 01:42:03 +0800 Subject: [PATCH 104/107] =?UTF-8?q?feat:=20=E8=81=8A=E5=A4=A9=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E6=89=93=E5=BC=80=E6=97=B6=E5=B1=8F=E8=94=BD=E7=83=AD?= =?UTF-8?q?=E9=94=AE=20(#3000)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Monitor/MouseKeyMonitor.cs | 5 + .../GameTask/ChatUiHotkeyGuard.cs | 120 +++++++++ .../GameTask/Common/BgiVision/BvChatUi.cs | 237 ++++++++++++++++++ .../Common/Element/Assets/ElementAssets.cs | 14 +- .../GameTask/TaskTriggerDispatcher.cs | 19 +- .../Model/HotKeySettingModel.cs | 40 ++- BetterGenshinImpact/Model/KeyboardHook.cs | 16 +- BetterGenshinImpact/Model/MouseHook.cs | 16 +- 8 files changed, 457 insertions(+), 10 deletions(-) create mode 100644 BetterGenshinImpact/GameTask/ChatUiHotkeyGuard.cs create mode 100644 BetterGenshinImpact/GameTask/Common/BgiVision/BvChatUi.cs diff --git a/BetterGenshinImpact/Core/Monitor/MouseKeyMonitor.cs b/BetterGenshinImpact/Core/Monitor/MouseKeyMonitor.cs index e6a5867a..e33aae14 100644 --- a/BetterGenshinImpact/Core/Monitor/MouseKeyMonitor.cs +++ b/BetterGenshinImpact/Core/Monitor/MouseKeyMonitor.cs @@ -102,6 +102,11 @@ public partial class MouseKeyMonitor // Debug.WriteLine("KeyDown: \t{0}", e.KeyCode); GlobalKeyMouseRecord.Instance.GlobalHookKeyDown(e, Kernel32.GetTickCount()); + if (SystemControl.IsGenshinImpactActive()) + { + ChatUiHotkeyGuard.PrimeFromChatKey(e.KeyCode); + } + // 热键按下事件 HotKeyDown(sender, e); diff --git a/BetterGenshinImpact/GameTask/ChatUiHotkeyGuard.cs b/BetterGenshinImpact/GameTask/ChatUiHotkeyGuard.cs new file mode 100644 index 00000000..382ceb57 --- /dev/null +++ b/BetterGenshinImpact/GameTask/ChatUiHotkeyGuard.cs @@ -0,0 +1,120 @@ +using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.GameTask.Common.BgiVision; +using System; +using System.Windows.Forms; + +namespace BetterGenshinImpact.GameTask; + +public static class ChatUiHotkeyGuard +{ + private const int StableFrameThreshold = 2; + private static readonly TimeSpan ChatKeyPrimeDuration = TimeSpan.FromMilliseconds(280); + private static readonly object Locker = new(); + + private static ChatUiState _chatUiState = ChatUiState.Closed; + private static int _enterFrameCount; + private static int _exitFrameCount; + private static DateTime _chatKeyPrimeUntilUtc = DateTime.MinValue; + + public static void UpdateVisualState(ChatUiDetectionResult detectionResult) + { + var visualState = detectionResult.State; + + lock (Locker) + { + if (visualState == ChatUiState.Closed) + { + _enterFrameCount = 0; + if (_chatUiState == ChatUiState.Closed) + { + _exitFrameCount = 0; + } + else if (++_exitFrameCount >= StableFrameThreshold) + { + SetState(ChatUiState.Closed); + _exitFrameCount = 0; + } + } + else + { + _exitFrameCount = 0; + _chatKeyPrimeUntilUtc = DateTime.MinValue; + if (_chatUiState == ChatUiState.Closed) + { + if (++_enterFrameCount >= StableFrameThreshold) + { + SetState(visualState); + _enterFrameCount = 0; + } + } + else + { + _enterFrameCount = 0; + SetState(visualState); + } + } + + if (_chatKeyPrimeUntilUtc <= DateTime.UtcNow) + { + _chatKeyPrimeUntilUtc = DateTime.MinValue; + } + } + } + + public static void PrimeFromChatKey(Keys keyCode) + { + if (keyCode != TaskContext.Instance().Config.KeyBindingsConfig.OpenChatScreen.ToWinFormKeys()) + { + return; + } + + lock (Locker) + { + if (_chatUiState != ChatUiState.Closed) + { + return; + } + + _chatKeyPrimeUntilUtc = DateTime.UtcNow + ChatKeyPrimeDuration; + } + } + + public static bool ShouldBlockHotkey(string? configPropertyName) + { + if (string.Equals(configPropertyName, nameof(HotKeyConfig.BgiEnabledHotkey), StringComparison.Ordinal)) + { + return false; + } + + lock (Locker) + { + if (_chatUiState != ChatUiState.Closed) + { + return true; + } + + return _chatKeyPrimeUntilUtc > DateTime.UtcNow; + } + } + + public static void Reset() + { + lock (Locker) + { + _chatUiState = ChatUiState.Closed; + _enterFrameCount = 0; + _exitFrameCount = 0; + _chatKeyPrimeUntilUtc = DateTime.MinValue; + } + } + + private static void SetState(ChatUiState nextState) + { + if (_chatUiState == nextState) + { + return; + } + + _chatUiState = nextState; + } +} diff --git a/BetterGenshinImpact/GameTask/Common/BgiVision/BvChatUi.cs b/BetterGenshinImpact/GameTask/Common/BgiVision/BvChatUi.cs new file mode 100644 index 00000000..08178352 --- /dev/null +++ b/BetterGenshinImpact/GameTask/Common/BgiVision/BvChatUi.cs @@ -0,0 +1,237 @@ +using BetterGenshinImpact.Core.Recognition.OpenCv; +using BetterGenshinImpact.GameTask.Common.Element.Assets; +using BetterGenshinImpact.GameTask.Model.Area; +using OpenCvSharp; +using System; +using System.Collections.Generic; + +namespace BetterGenshinImpact.GameTask.Common.BgiVision; + +public enum ChatUiState +{ + Closed, + PanelOpen, + InputOpen +} + +public readonly record struct ChatUiDetectionResult( + ChatUiState State, + bool HasBackButton, + bool HasMoreButton, + bool HasAddConversationButton, + int BottomCircleCount, + bool HasSendButton) +{ + public bool HasInputControls => BottomCircleCount >= 2 || HasSendButton; + + public string ToDebugSummary() + { + return $"back={HasBackButton}, more={HasMoreButton}, add={HasAddConversationButton}, circles={BottomCircleCount}, send={HasSendButton}"; + } +} + +public static partial class Bv +{ + public static ChatUiDetectionResult DetectChatUi(ImageRegion region) + { + using var backButton = region.Find(ElementAssets.Instance.ChatBackButtonRo); + var hasBackButton = backButton.IsExist(); + var hasMoreButton = HasChatMoreButton(region); + var hasAddConversationButton = HasChatAddConversationButton(region); + var bottomCircleCount = CountChatBottomCircleButtons(region); + var hasSendButton = HasChatSendButton(region); + var hasInputControls = bottomCircleCount >= 2 || hasSendButton; + + if (!hasBackButton || !hasAddConversationButton) + { + return new ChatUiDetectionResult( + ChatUiState.Closed, + hasBackButton, + hasMoreButton, + hasAddConversationButton, + bottomCircleCount, + hasSendButton); + } + + if (hasInputControls) + { + return new ChatUiDetectionResult( + ChatUiState.InputOpen, + hasBackButton, + hasMoreButton, + hasAddConversationButton, + bottomCircleCount, + hasSendButton); + } + + var state = hasMoreButton ? ChatUiState.PanelOpen : ChatUiState.Closed; + return new ChatUiDetectionResult( + state, + hasBackButton, + hasMoreButton, + hasAddConversationButton, + bottomCircleCount, + hasSendButton); + } + + public static ChatUiState DetectChatUiState(ImageRegion region) + { + return DetectChatUi(region).State; + } + + private static bool HasChatMoreButton(ImageRegion region) + { + var scale = GetChatUiScale(region); + using var roi = region.DeriveCrop(region.Width - (int)Math.Round(280 * scale), 0, (int)Math.Round(250 * scale), (int)Math.Round(140 * scale)); + return HasEllipsisDots(roi.SrcMat, scale, detectDarkDots: true) || HasEllipsisDots(roi.SrcMat, scale, detectDarkDots: false); + } + + private static bool HasChatAddConversationButton(ImageRegion region) + { + var scale = GetChatUiScale(region); + using var roi = region.DeriveCrop(0, region.Height - (int)Math.Round(260 * scale), (int)Math.Round(320 * scale), (int)Math.Round(260 * scale)); + return HasBrightRoundedButton(roi.SrcMat, scale, minWidth: 28, maxWidth: 92, minHeight: 28, maxHeight: 92, minAspect: 0.72, maxAspect: 1.28); + } + + private static int CountChatBottomCircleButtons(ImageRegion region) + { + var scale = GetChatUiScale(region); + using var roi = region.DeriveCrop((int)Math.Round(620 * scale), region.Height - (int)Math.Round(220 * scale), (int)Math.Round(760 * scale), (int)Math.Round(180 * scale)); + return CountBrightRoundedButtons(roi.SrcMat, scale, minWidth: 26, maxWidth: 92, minHeight: 26, maxHeight: 92, minAspect: 0.72, maxAspect: 1.28); + } + + private static bool HasChatSendButton(ImageRegion region) + { + var scale = GetChatUiScale(region); + using var roi = region.DeriveCrop((int)Math.Round(820 * scale), region.Height - (int)Math.Round(220 * scale), (int)Math.Round(500 * scale), (int)Math.Round(180 * scale)); + return HasBrightRoundedButton(roi.SrcMat, scale, minWidth: 90, maxWidth: 260, minHeight: 26, maxHeight: 92, minAspect: 1.45, maxAspect: 5.5); + } + + private static bool HasBrightRoundedButton(Mat src, double scale, int minWidth, int maxWidth, int minHeight, int maxHeight, double minAspect, double maxAspect) + { + return CountBrightRoundedButtons(src, scale, minWidth, maxWidth, minHeight, maxHeight, minAspect, maxAspect) > 0; + } + + private static int CountBrightRoundedButtons(Mat src, double scale, int minWidth, int maxWidth, int minHeight, int maxHeight, double minAspect, double maxAspect) + { + using var mask = OpenCvCommonHelper.Threshold(src, new Scalar(180, 165, 135), new Scalar(255, 255, 255)); + using var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(ToKernelSize(7 * scale), ToKernelSize(7 * scale))); + Cv2.MorphologyEx(mask, mask, MorphTypes.Close, kernel); + Cv2.FindContours(mask, out var contours, out _, RetrievalModes.External, ContourApproximationModes.ApproxSimple); + + var scaledMinWidth = Math.Max(8, (int)Math.Round(minWidth * scale)); + var scaledMaxWidth = Math.Max(scaledMinWidth + 1, (int)Math.Round(maxWidth * scale)); + var scaledMinHeight = Math.Max(8, (int)Math.Round(minHeight * scale)); + var scaledMaxHeight = Math.Max(scaledMinHeight + 1, (int)Math.Round(maxHeight * scale)); + var minArea = scaledMinWidth * scaledMinHeight * 0.35; + var matches = 0; + + foreach (var contour in contours) + { + var rect = Cv2.BoundingRect(contour); + if (rect.Width < scaledMinWidth || rect.Height < scaledMinHeight || rect.Width > scaledMaxWidth || rect.Height > scaledMaxHeight) + { + continue; + } + + var aspect = rect.Width / (double)Math.Max(rect.Height, 1); + if (aspect < minAspect || aspect > maxAspect) + { + continue; + } + + var contourArea = Cv2.ContourArea(contour); + if (contourArea < minArea) + { + continue; + } + + var fillRatio = contourArea / Math.Max(1d, rect.Width * rect.Height); + if (fillRatio < 0.48) + { + continue; + } + + matches++; + } + + return matches; + } + + private static bool HasEllipsisDots(Mat src, double scale, bool detectDarkDots) + { + using var gray = src.CvtColor(ColorConversionCodes.BGR2GRAY); + using var mask = new Mat(); + Cv2.Threshold(gray, mask, detectDarkDots ? 115 : 210, 255, detectDarkDots ? ThresholdTypes.BinaryInv : ThresholdTypes.Binary); + + using var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(ToKernelSize(3 * scale), ToKernelSize(3 * scale))); + Cv2.MorphologyEx(mask, mask, MorphTypes.Open, kernel); + Cv2.FindContours(mask, out var contours, out _, RetrievalModes.External, ContourApproximationModes.ApproxSimple); + + var minDot = Math.Max(3, (int)Math.Round(4 * scale)); + var maxDot = Math.Max(minDot + 1, (int)Math.Round(22 * scale)); + var maxYOffset = Math.Max(4, (int)Math.Round(10 * scale)); + var minGap = Math.Max(2, (int)Math.Round(3 * scale)); + var maxGap = Math.Max(minGap + 1, (int)Math.Round(36 * scale)); + + var dots = new List<(Rect Rect, Point Center)>(); + foreach (var contour in contours) + { + var rect = Cv2.BoundingRect(contour); + if (rect.Width < minDot || rect.Height < minDot || rect.Width > maxDot || rect.Height > maxDot) + { + continue; + } + + var aspect = rect.Width / (double)Math.Max(rect.Height, 1); + if (aspect < 0.55 || aspect > 1.8) + { + continue; + } + + dots.Add((rect, new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2))); + } + + if (dots.Count < 3) + { + return false; + } + + dots.Sort((a, b) => a.Center.X.CompareTo(b.Center.X)); + for (var i = 0; i <= dots.Count - 3; i++) + { + var first = dots[i]; + var second = dots[i + 1]; + var third = dots[i + 2]; + + if (Math.Abs(first.Center.Y - second.Center.Y) > maxYOffset || + Math.Abs(second.Center.Y - third.Center.Y) > maxYOffset || + Math.Abs(first.Center.Y - third.Center.Y) > maxYOffset) + { + continue; + } + + var gap1 = second.Center.X - first.Center.X; + var gap2 = third.Center.X - second.Center.X; + if (gap1 < minGap || gap2 < minGap || gap1 > maxGap || gap2 > maxGap) + { + continue; + } + + return true; + } + + return false; + } + + private static double GetChatUiScale(ImageRegion region) + { + return region.Width / 1920d; + } + + private static int ToKernelSize(double size) + { + var rounded = Math.Max(1, (int)Math.Round(size)); + return rounded % 2 == 0 ? rounded + 1 : rounded; + } +} diff --git a/BetterGenshinImpact/GameTask/Common/Element/Assets/ElementAssets.cs b/BetterGenshinImpact/GameTask/Common/Element/Assets/ElementAssets.cs index 0b68a2ca..217304b5 100644 --- a/BetterGenshinImpact/GameTask/Common/Element/Assets/ElementAssets.cs +++ b/BetterGenshinImpact/GameTask/Common/Element/Assets/ElementAssets.cs @@ -32,6 +32,7 @@ public class ElementAssets : BaseAssets public RecognitionObject XKey; public RecognitionObject FriendChat; + public RecognitionObject ChatBackButtonRo; public RecognitionObject PartyBtnChooseView; public RecognitionObject PartyBtnDelete; @@ -274,7 +275,16 @@ public class ElementAssets : BaseAssets DrawOnWindow = false }.InitTemplate(); - // 队伍切换 + // 聊天 UI 识别 + ChatBackButtonRo = new RecognitionObject + { + Name = "ChatBackButton", + RecognitionType = RecognitionTypes.TemplateMatch, + TemplateImageMat = GameTaskManager.LoadAssetImage(@"UseRedeemCode", "esc_return_button.png", systemInfo), + RegionOfInterest = new Rect(0, 0, (int)(220 * AssetScale), (int)(160 * AssetScale)), + Threshold = 0.72, + DrawOnWindow = false + }.InitTemplate(); PartyBtnChooseView = new RecognitionObject { Name = "PartyBtnChooseView", @@ -783,4 +793,4 @@ public class ElementAssets : BaseAssets // DrawOnWindow = true }.InitTemplate(); } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs index bc8eb7f2..4702c1e0 100644 --- a/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs +++ b/BetterGenshinImpact/GameTask/TaskTriggerDispatcher.cs @@ -125,6 +125,7 @@ namespace BetterGenshinImpact.GameTask public void Start(IntPtr hWnd, CaptureModes mode, int interval = 50) { // 初始化截图器 + ChatUiHotkeyGuard.Reset(); GameCapture = GameCaptureFactory.Create(mode); // 激活窗口 保证后面能够正常获取窗口信息 SystemControl.ActivateWindow(hWnd); @@ -167,6 +168,7 @@ namespace BetterGenshinImpact.GameTask public void Stop() { _timer.Stop(); + ChatUiHotkeyGuard.Reset(); GameCapture?.Stop(); _gameRect = RECT.Empty; _prevGameActive = false; @@ -198,6 +200,8 @@ namespace BetterGenshinImpact.GameTask { _timer.Stop(); } + + ChatUiHotkeyGuard.Reset(); } public void Dispose() @@ -221,6 +225,7 @@ namespace BetterGenshinImpact.GameTask var maskWindow = MaskWindow.Instance(); if (GameCapture == null || !GameCapture.IsCapturing) { + ChatUiHotkeyGuard.Reset(); if (!TaskContext.Instance().SystemInfo.GameProcess.HasExited) { _logger.LogError("截图器未初始化!"); @@ -239,6 +244,7 @@ namespace BetterGenshinImpact.GameTask // 如果是最小化状态,直接不进行截图 if (SystemControl.IsGenshinImpactMinimized()) { + ChatUiHotkeyGuard.Reset(); PictureInPictureService.Hide(); return; } @@ -253,6 +259,7 @@ namespace BetterGenshinImpact.GameTask var active = SystemControl.IsGenshinImpactActive(); if (!active) { + ChatUiHotkeyGuard.Reset(); // 检查游戏是否已结束 if (TaskContext.Instance().SystemInfo.GameProcess.HasExited) { @@ -329,7 +336,8 @@ namespace BetterGenshinImpact.GameTask } } - if (_triggers == null || !_triggers.Exists(t => t.IsEnabled)) + var hasEnabledTriggers = _triggers != null && _triggers.Exists(t => t.IsEnabled); + if (!hasEnabledTriggers && !active) { // Debug.WriteLine("没有可用的触发器且不处于仅截屏状态, 不再进行截屏"); return; @@ -359,7 +367,13 @@ namespace BetterGenshinImpact.GameTask } // 循环执行所有触发器 有独占状态的触发器的时候只执行独占触发器 - var content = new CaptureContent(bitmap, _frameIndex, _timer.Interval); + using var content = new CaptureContent(bitmap, _frameIndex, _timer.Interval); + ChatUiHotkeyGuard.UpdateVisualState(Bv.DetectChatUi(content.CaptureRectArea)); + + if (!hasEnabledTriggers) + { + return; + } lock (_triggerListLocker) { @@ -405,7 +419,6 @@ namespace BetterGenshinImpact.GameTask } speedTimer.DebugPrint(); - content.Dispose(); } finally { diff --git a/BetterGenshinImpact/Model/HotKeySettingModel.cs b/BetterGenshinImpact/Model/HotKeySettingModel.cs index f8519ec1..ce134e22 100644 --- a/BetterGenshinImpact/Model/HotKeySettingModel.cs +++ b/BetterGenshinImpact/Model/HotKeySettingModel.cs @@ -1,4 +1,7 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.GameTask; +using BetterGenshinImpact.GameTask.AutoFight; +using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Fischless.HotkeyCapture; using System; @@ -108,7 +111,8 @@ public partial class HotKeySettingModel : ObservableObject { MouseMonitorHook = new MouseHook { - IsHold = IsHold + IsHold = IsHold, + ConfigPropertyName = ConfigPropertyName }; if (OnKeyPressAction != null) @@ -138,7 +142,8 @@ public partial class HotKeySettingModel : ObservableObject } KeyboardMonitorHook = new KeyboardHook { - IsHold = IsHold + IsHold = IsHold, + ConfigPropertyName = ConfigPropertyName }; if (OnKeyPressAction != null) { @@ -169,19 +174,48 @@ public partial class HotKeySettingModel : ObservableObject private void OnKeyPressed(object? sender, KeyPressedEventArgs e) { + if (ShouldBlockGlobalRegister()) + { + return; + } + OnKeyPressAction?.Invoke(sender, e); } private void OnKeyDown(object? sender, KeyPressedEventArgs e) { + if (ShouldBlockGlobalRegister()) + { + return; + } + OnKeyDownAction?.Invoke(sender, e); } private void OnKeyUp(object? sender, KeyPressedEventArgs e) { + if (ShouldBlockGlobalRegister()) + { + ResetBlockedKeyUpState(); + return; + } + OnKeyUpAction?.Invoke(sender, e); } + private bool ShouldBlockGlobalRegister() + { + return HotKeyType == HotKeyTypeEnum.GlobalRegister && ChatUiHotkeyGuard.ShouldBlockHotkey(ConfigPropertyName); + } + + private void ResetBlockedKeyUpState() + { + if (string.Equals(ConfigPropertyName, nameof(HotKeyConfig.OneKeyFightHotkey), StringComparison.Ordinal)) + { + OneKeyFightTask.Instance.KeyUp(); + } + } + public void UnRegisterHotKey() { GlobalRegisterHook?.Dispose(); diff --git a/BetterGenshinImpact/Model/KeyboardHook.cs b/BetterGenshinImpact/Model/KeyboardHook.cs index 9021255b..5c0dc5c9 100644 --- a/BetterGenshinImpact/Model/KeyboardHook.cs +++ b/BetterGenshinImpact/Model/KeyboardHook.cs @@ -2,6 +2,7 @@ using Fischless.HotkeyCapture; using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Vanara.PInvoke; @@ -24,6 +25,8 @@ public class KeyboardHook public bool IsPressed { get; set; } + public string ConfigPropertyName { get; set; } = string.Empty; + /// /// 注意长按的时候会一直触发KeyDown /// @@ -38,6 +41,11 @@ public class KeyboardHook if (e.KeyCode == BindKey) { + if (ChatUiHotkeyGuard.ShouldBlockHotkey(ConfigPropertyName)) + { + return; + } + IsPressed = true; KeyDownEvent?.Invoke(this, new KeyPressedEventArgs(User32.HotKeyModifiers.MOD_NONE, e.KeyCode)); if (IsHold) @@ -65,6 +73,12 @@ public class KeyboardHook { while (IsPressed && KeyPressedEvent != null) { + if (ChatUiHotkeyGuard.ShouldBlockHotkey(ConfigPropertyName)) + { + Thread.Sleep(10); + continue; + } + KeyPressedEvent?.Invoke(this, new KeyPressedEventArgs(User32.HotKeyModifiers.MOD_NONE, e.KeyCode)); } } @@ -75,7 +89,7 @@ public class KeyboardHook if (e.KeyCode == BindKey) { IsPressed = false; - if (SystemControl.IsGenshinImpactActive()) + if (SystemControl.IsGenshinImpactActive() && !ChatUiHotkeyGuard.ShouldBlockHotkey(ConfigPropertyName)) { KeyUpEvent?.Invoke(this, new KeyPressedEventArgs(User32.HotKeyModifiers.MOD_NONE, e.KeyCode)); } diff --git a/BetterGenshinImpact/Model/MouseHook.cs b/BetterGenshinImpact/Model/MouseHook.cs index f9b336ec..d5e110d2 100644 --- a/BetterGenshinImpact/Model/MouseHook.cs +++ b/BetterGenshinImpact/Model/MouseHook.cs @@ -3,6 +3,7 @@ using Fischless.HotkeyCapture; using Gma.System.MouseKeyHook; using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Vanara.PInvoke; @@ -25,6 +26,8 @@ public class MouseHook public bool IsPressed { get; set; } + public string ConfigPropertyName { get; set; } = string.Empty; + public void MouseDown(object? sender, MouseEventExtArgs e) { if (!SystemControl.IsGenshinImpactActive()) @@ -34,6 +37,11 @@ public class MouseHook if (e.Button != MouseButtons.Left && e.Button != MouseButtons.None && e.Button == BindMouse) { + if (ChatUiHotkeyGuard.ShouldBlockHotkey(ConfigPropertyName)) + { + return; + } + IsPressed = true; MouseDownEvent?.Invoke(this, new KeyPressedEventArgs(User32.HotKeyModifiers.MOD_NONE, Keys.None)); if (IsHold) @@ -58,6 +66,12 @@ public class MouseHook { while (IsPressed) { + if (ChatUiHotkeyGuard.ShouldBlockHotkey(ConfigPropertyName)) + { + Thread.Sleep(10); + continue; + } + MousePressed?.Invoke(this, new KeyPressedEventArgs(User32.HotKeyModifiers.MOD_NONE, Keys.None)); } } @@ -68,7 +82,7 @@ public class MouseHook if (e.Button != MouseButtons.Left && e.Button != MouseButtons.None && e.Button == BindMouse) { IsPressed = false; - if (SystemControl.IsGenshinImpactActive()) + if (SystemControl.IsGenshinImpactActive() && !ChatUiHotkeyGuard.ShouldBlockHotkey(ConfigPropertyName)) { MouseUpEvent?.Invoke(this, new KeyPressedEventArgs(User32.HotKeyModifiers.MOD_NONE, Keys.None)); } From 907fe5b3c22623fca65d07df1f2d725013f50085 Mon Sep 17 00:00:00 2001 From: ddaodan <40017293+ddaodan@users.noreply.github.com> Date: Mon, 6 Apr 2026 13:08:28 +0800 Subject: [PATCH 105/107] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E9=80=89=E6=8B=A9=E7=95=8C=E9=9D=A2=20(#3001?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BetterGenshinImpact/App.xaml | 1 + .../Model/Enum/NotificationEvent.cs | 25 ++- .../NotificationEventSubscriptionHelper.cs | 87 ++++++++++ .../Notification/NotificationService.cs | 9 +- .../AdaptiveUniformGridColumnsConverter.cs | 47 +++++ .../View/Pages/NotificationSettingsPage.xaml | 63 ++++--- .../Pages/NotificationEventOption.cs | 12 ++ .../NotificationSettingsPageViewModel.cs | 160 +++++++++++++++++- ...otificationEventSubscriptionHelperTests.cs | 59 +++++++ 9 files changed, 430 insertions(+), 33 deletions(-) create mode 100644 BetterGenshinImpact/Service/Notification/NotificationEventSubscriptionHelper.cs create mode 100644 BetterGenshinImpact/View/Converters/AdaptiveUniformGridColumnsConverter.cs create mode 100644 BetterGenshinImpact/ViewModel/Pages/NotificationEventOption.cs create mode 100644 Test/BetterGenshinImpact.UnitTest/ServiceTests/NotificationTests/NotificationEventSubscriptionHelperTests.cs diff --git a/BetterGenshinImpact/App.xaml b/BetterGenshinImpact/App.xaml index dd127137..e737fd5f 100644 --- a/BetterGenshinImpact/App.xaml +++ b/BetterGenshinImpact/App.xaml @@ -26,6 +26,7 @@ + diff --git a/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationEvent.cs b/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationEvent.cs index ec1a82de..7201e894 100644 --- a/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationEvent.cs +++ b/BetterGenshinImpact/Service/Notification/Model/Enum/NotificationEvent.cs @@ -1,7 +1,23 @@ -namespace BetterGenshinImpact.Service.Notification.Model.Enum; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace BetterGenshinImpact.Service.Notification.Model.Enum; public class NotificationEvent(string code, string msg) { + private static readonly Lazy> AllEvents = new(() => + typeof(NotificationEvent) + .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) + .Where(field => field.FieldType == typeof(NotificationEvent)) + .OrderBy(field => field.MetadataToken) + .Select(field => field.GetValue(null)) + .OfType() + .GroupBy(notificationEvent => notificationEvent.Code, StringComparer.OrdinalIgnoreCase) + .Select(group => group.First()) + .ToArray()); + public static readonly NotificationEvent Test = new("notify.test", "测试通知"); public static readonly NotificationEvent DomainReward = new("domain.reward", "自动秘境奖励"); public static readonly NotificationEvent DomainStart = new("domain.start", "自动秘境启动"); @@ -24,7 +40,12 @@ public class NotificationEvent(string code, string msg) public static readonly NotificationEvent AutoEatStart = new("autoeat.start", "自动吃药启动"); public static readonly NotificationEvent AutoEatEnd = new("autoeat.end", "自动吃药结束"); public static readonly NotificationEvent AutoEatInfo = new("autoeat.info", "自动吃药信息"); - + + public static IReadOnlyList GetAll() + { + return AllEvents.Value; + } + public string Code { get; private set; } = code; public string Msg { get; private set; } = msg; } diff --git a/BetterGenshinImpact/Service/Notification/NotificationEventSubscriptionHelper.cs b/BetterGenshinImpact/Service/Notification/NotificationEventSubscriptionHelper.cs new file mode 100644 index 00000000..50cbe7fc --- /dev/null +++ b/BetterGenshinImpact/Service/Notification/NotificationEventSubscriptionHelper.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; + +namespace BetterGenshinImpact.Service.Notification; + +public static class NotificationEventSubscriptionHelper +{ + public static IReadOnlyList ParseEventCodes(string? subscribeEventStr) + { + if (string.IsNullOrWhiteSpace(subscribeEventStr)) + { + return Array.Empty(); + } + + var eventCodes = new List(); + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var eventCode in subscribeEventStr.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + if (string.IsNullOrWhiteSpace(eventCode)) + { + continue; + } + + if (!seen.Add(eventCode)) + { + continue; + } + + eventCodes.Add(eventCode); + } + + return eventCodes; + } + + public static string NormalizeEventCodes(IEnumerable? eventCodes) + { + if (eventCodes == null) + { + return string.Empty; + } + + var normalizedEventCodes = new List(); + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var eventCode in eventCodes) + { + if (string.IsNullOrWhiteSpace(eventCode)) + { + continue; + } + + var trimmedCode = eventCode.Trim(); + if (!seen.Add(trimmedCode)) + { + continue; + } + + normalizedEventCodes.Add(trimmedCode); + } + + return string.Join(',', normalizedEventCodes); + } + + public static bool ShouldSendNotification(string? subscribeEventStr, string? eventCode) + { + if (string.IsNullOrWhiteSpace(subscribeEventStr)) + { + return true; + } + + if (string.IsNullOrWhiteSpace(eventCode)) + { + return false; + } + + foreach (var subscribeEventCode in ParseEventCodes(subscribeEventStr)) + { + if (string.Equals(subscribeEventCode, eventCode, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } +} diff --git a/BetterGenshinImpact/Service/Notification/NotificationService.cs b/BetterGenshinImpact/Service/Notification/NotificationService.cs index 6f887c84..1fbcaae2 100644 --- a/BetterGenshinImpact/Service/Notification/NotificationService.cs +++ b/BetterGenshinImpact/Service/Notification/NotificationService.cs @@ -424,10 +424,9 @@ public class NotificationService : IHostedService, IDisposable /// private bool ShouldSendNotification(string eventCode) { - var subscribeEventStr = _notificationConfig?.NotificationEventSubscribe; - if (string.IsNullOrEmpty(subscribeEventStr)) return true; - - return subscribeEventStr.Contains(eventCode); + return NotificationEventSubscriptionHelper.ShouldSendNotification( + _notificationConfig?.NotificationEventSubscribe, + eventCode); } /// @@ -476,4 +475,4 @@ public class NotificationService : IHostedService, IDisposable } }); } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/View/Converters/AdaptiveUniformGridColumnsConverter.cs b/BetterGenshinImpact/View/Converters/AdaptiveUniformGridColumnsConverter.cs new file mode 100644 index 00000000..1b922abd --- /dev/null +++ b/BetterGenshinImpact/View/Converters/AdaptiveUniformGridColumnsConverter.cs @@ -0,0 +1,47 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace BetterGenshinImpact.View.Converters; + +[ValueConversion(typeof(double), typeof(int))] +public sealed class AdaptiveUniformGridColumnsConverter : IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is not double width || double.IsNaN(width) || width <= 0) + { + return 1; + } + + var minimumColumnWidth = 280d; + var maxColumns = 4; + + if (parameter is string parameterText && !string.IsNullOrWhiteSpace(parameterText)) + { + var parts = parameterText.Split([',', '|'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + if (parts.Length > 0 && + double.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var parsedMinimumWidth) && + parsedMinimumWidth > 0) + { + minimumColumnWidth = parsedMinimumWidth; + } + + if (parts.Length > 1 && + int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedMaxColumns) && + parsedMaxColumns > 0) + { + maxColumns = parsedMaxColumns; + } + } + + var columns = Math.Max(1, (int)Math.Floor(width / minimumColumnWidth)); + return Math.Min(columns, maxColumns); + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } +} diff --git a/BetterGenshinImpact/View/Pages/NotificationSettingsPage.xaml b/BetterGenshinImpact/View/Pages/NotificationSettingsPage.xaml index edb69951..1744fcb0 100644 --- a/BetterGenshinImpact/View/Pages/NotificationSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/NotificationSettingsPage.xaml @@ -102,18 +102,8 @@ Margin="0,0,36,0" IsChecked="{Binding Config.NotificationConfig.JsNotificationEnabled, Mode=TwoWay}" /> - - - - - - - - - - + @@ -127,19 +117,44 @@ - - - + + + + + + + + + + + + + + + + + + + + @@ -2103,4 +2118,4 @@ - \ No newline at end of file + diff --git a/BetterGenshinImpact/ViewModel/Pages/NotificationEventOption.cs b/BetterGenshinImpact/ViewModel/Pages/NotificationEventOption.cs new file mode 100644 index 00000000..10887b9e --- /dev/null +++ b/BetterGenshinImpact/ViewModel/Pages/NotificationEventOption.cs @@ -0,0 +1,12 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace BetterGenshinImpact.ViewModel.Pages; + +public partial class NotificationEventOption(string code, string displayName) : ObservableObject +{ + [ObservableProperty] private bool _isSelected; + + public string Code { get; } = code; + + public string DisplayName { get; } = displayName; +} diff --git a/BetterGenshinImpact/ViewModel/Pages/NotificationSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/NotificationSettingsPageViewModel.cs index 8ba12b97..1d00d77d 100644 --- a/BetterGenshinImpact/ViewModel/Pages/NotificationSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/NotificationSettingsPageViewModel.cs @@ -1,8 +1,13 @@ -using System; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; using System.Threading.Tasks; using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Service.Interface; using BetterGenshinImpact.Service.Notification; +using BetterGenshinImpact.Service.Notification.Model.Enum; using BetterGenshinImpact.Service.Notifier; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -14,6 +19,8 @@ namespace BetterGenshinImpact.ViewModel.Pages; public partial class NotificationSettingsPageViewModel : ObservableObject, IViewModel { private readonly NotificationService _notificationService; + private readonly HashSet _knownNotificationEventCodes; + private bool _isSyncingNotificationEventSelection; [ObservableProperty] private string _barkStatus = string.Empty; @@ -30,6 +37,10 @@ public partial class NotificationSettingsPageViewModel : ObservableObject, IView [ObservableProperty] private bool _isLoading; + [ObservableProperty] private ObservableCollection _notificationEventOptions = []; + + [ObservableProperty] private string _notificationEventSelectionSummary = string.Empty; + [ObservableProperty] private string _telegramStatus = string.Empty; [ObservableProperty] private string _webhookStatus = string.Empty; @@ -57,10 +68,155 @@ public partial class NotificationSettingsPageViewModel : ObservableObject, IView { Config = configService.Get(); _notificationService = notificationService; + + _knownNotificationEventCodes = NotificationEvent + .GetAll() + .Select(notificationEvent => notificationEvent.Code) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + NotificationEventOptions = new ObservableCollection( + NotificationEvent + .GetAll() + .Select(notificationEvent => new NotificationEventOption(notificationEvent.Code, notificationEvent.Msg))); + + foreach (var option in NotificationEventOptions) + { + option.PropertyChanged += OnNotificationEventOptionPropertyChanged; + } + + Config.NotificationConfig.PropertyChanged += OnNotificationConfigPropertyChanged; + ApplyNotificationEventSelectionFromConfig(); } public AllConfig Config { get; set; } + [RelayCommand] + private void SelectAllNotificationEvents() + { + SetNotificationEventSelection(true); + } + + [RelayCommand] + private void ClearNotificationEventSelection() + { + SetNotificationEventSelection(false); + } + + private void SetNotificationEventSelection(bool isSelected) + { + _isSyncingNotificationEventSelection = true; + try + { + foreach (var option in NotificationEventOptions) + { + option.IsSelected = isSelected; + } + } + finally + { + _isSyncingNotificationEventSelection = false; + } + + UpdateNotificationEventSubscribeFromSelection(); + } + + private void OnNotificationConfigPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName != nameof(NotificationConfig.NotificationEventSubscribe)) + { + return; + } + + if (_isSyncingNotificationEventSelection) + { + return; + } + + ApplyNotificationEventSelectionFromConfig(); + } + + private void OnNotificationEventOptionPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName != nameof(NotificationEventOption.IsSelected)) + { + return; + } + + if (_isSyncingNotificationEventSelection) + { + return; + } + + UpdateNotificationEventSubscribeFromSelection(); + } + + private void ApplyNotificationEventSelectionFromConfig() + { + var parsedEventCodes = NotificationEventSubscriptionHelper.ParseEventCodes( + Config.NotificationConfig.NotificationEventSubscribe); + var selectedEventCodes = parsedEventCodes + .Where(eventCode => _knownNotificationEventCodes.Contains(eventCode)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + var unknownEventCodeCount = parsedEventCodes.Count - selectedEventCodes.Count; + + _isSyncingNotificationEventSelection = true; + try + { + foreach (var option in NotificationEventOptions) + { + option.IsSelected = selectedEventCodes.Contains(option.Code); + } + } + finally + { + _isSyncingNotificationEventSelection = false; + } + + UpdateNotificationEventSelectionSummary(unknownEventCodeCount); + } + + private void UpdateNotificationEventSubscribeFromSelection() + { + var normalizedEventCodes = NotificationEventSubscriptionHelper.NormalizeEventCodes( + NotificationEventOptions + .Where(option => option.IsSelected) + .Select(option => option.Code)); + + UpdateNotificationEventSelectionSummary(); + + if (string.Equals( + Config.NotificationConfig.NotificationEventSubscribe, + normalizedEventCodes, + StringComparison.Ordinal)) + { + return; + } + + Config.NotificationConfig.NotificationEventSubscribe = normalizedEventCodes; + } + + private void UpdateNotificationEventSelectionSummary(int unknownEventCodeCount = 0) + { + if (NotificationEventOptions.Count == 0) + { + NotificationEventSelectionSummary = "当前版本没有可配置的通知事件"; + return; + } + + var selectedCount = NotificationEventOptions.Count(option => option.IsSelected); + if (unknownEventCodeCount > 0) + { + NotificationEventSelectionSummary = selectedCount == 0 + ? $"检测到 {unknownEventCodeCount} 个未知事件代码,当前未显示;修改后会自动清理。未勾选任何事件时按“全部通知”处理" + : $"已选择 {selectedCount} / {NotificationEventOptions.Count} 个事件;另有 {unknownEventCodeCount} 个未知事件代码,修改后会自动清理"; + return; + } + + NotificationEventSelectionSummary = selectedCount == 0 + ? "当前未勾选任何事件,将按“全部通知”处理" + : $"已选择 {selectedCount} / {NotificationEventOptions.Count} 个事件"; + } + [RelayCommand] private async Task OnTestWebhook() { @@ -313,4 +469,4 @@ public partial class NotificationSettingsPageViewModel : ObservableObject, IView { await Launcher.LaunchUriAsync(new Uri("https://www.bettergi.com/dev/webhook.html#%E4%BA%8B%E4%BB%B6%E5%88%97%E8%A1%A8")); } -} \ No newline at end of file +} diff --git a/Test/BetterGenshinImpact.UnitTest/ServiceTests/NotificationTests/NotificationEventSubscriptionHelperTests.cs b/Test/BetterGenshinImpact.UnitTest/ServiceTests/NotificationTests/NotificationEventSubscriptionHelperTests.cs new file mode 100644 index 00000000..14df255b --- /dev/null +++ b/Test/BetterGenshinImpact.UnitTest/ServiceTests/NotificationTests/NotificationEventSubscriptionHelperTests.cs @@ -0,0 +1,59 @@ +using BetterGenshinImpact.Service.Notification; +using BetterGenshinImpact.Service.Notification.Model.Enum; + +namespace BetterGenshinImpact.UnitTest.ServiceTests.NotificationTests; + +public class NotificationEventSubscriptionHelperTests +{ + [Fact] + public void ParseEventCodes_ShouldTrimAndDeduplicate() + { + var result = NotificationEventSubscriptionHelper.ParseEventCodes( + " domain.start, task.error ,domain.start,,TASK.ERROR "); + + Assert.Equal(["domain.start", "task.error"], result); + } + + [Fact] + public void NormalizeEventCodes_ShouldReturnStableCommaSeparatedString() + { + var result = NotificationEventSubscriptionHelper.NormalizeEventCodes( + [" domain.start ", "", "task.error", "domain.start"]); + + Assert.Equal("domain.start,task.error", result); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void ShouldSendNotification_ShouldTreatEmptySubscriptionAsAll(string? subscribeEventStr) + { + var result = NotificationEventSubscriptionHelper.ShouldSendNotification( + subscribeEventStr, + NotificationEvent.DomainStart.Code); + + Assert.True(result); + } + + [Fact] + public void ShouldSendNotification_ShouldMatchExactEventCode() + { + const string subscribeEventStr = "domain.start,task.error"; + + Assert.True(NotificationEventSubscriptionHelper.ShouldSendNotification(subscribeEventStr, "domain.start")); + Assert.False(NotificationEventSubscriptionHelper.ShouldSendNotification(subscribeEventStr, "domain")); + Assert.False(NotificationEventSubscriptionHelper.ShouldSendNotification(subscribeEventStr, "task")); + Assert.False(NotificationEventSubscriptionHelper.ShouldSendNotification(subscribeEventStr, "domain.start.extra")); + } + + [Fact] + public void GetAll_ShouldReturnStaticNotificationEvents() + { + var events = NotificationEvent.GetAll(); + + Assert.NotEmpty(events); + Assert.Contains(events, notificationEvent => notificationEvent.Code == NotificationEvent.Test.Code); + Assert.Contains(events, notificationEvent => notificationEvent.Code == NotificationEvent.DomainStart.Code); + } +} From 5c7e8e8f1198a050c784dc924c82fcf942633a9d Mon Sep 17 00:00:00 2001 From: Jamis Date: Mon, 6 Apr 2026 17:17:35 +1000 Subject: [PATCH 106/107] =?UTF-8?q?=E6=9B=B4=E6=96=B06.5.0=E4=BC=A0?= =?UTF-8?q?=E9=80=81=E9=94=9A=E7=82=B9=E4=BF=A1=E6=81=AF=20(#3004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GameTask/AutoTrackPath/Assets/tp.json | 254 +++++++++++++++++- 1 file changed, 253 insertions(+), 1 deletion(-) diff --git a/BetterGenshinImpact/GameTask/AutoTrackPath/Assets/tp.json b/BetterGenshinImpact/GameTask/AutoTrackPath/Assets/tp.json index 53e0b15e..25459c3a 100644 --- a/BetterGenshinImpact/GameTask/AutoTrackPath/Assets/tp.json +++ b/BetterGenshinImpact/GameTask/AutoTrackPath/Assets/tp.json @@ -1,6 +1,6 @@ { "language": "CHS", - "version": "a045b7e5f0685fe93d5d963ee55bb6cb78b81560", + "version": "56882a11588d5e79a1b6b51739f9fb27e9952fd7", "data": [ { "sceneId": 3, @@ -13904,6 +13904,26 @@ 9554.542 ] }, + { + "id": 1865, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "风息山", + "疗养院旧址" + ], + "position": [ + 3877.027, + 438.402, + -531.106 + ], + "tranPosition": [ + 3877, + 437.802, + -527.9 + ] + }, { "id": 1866, "type": "TeleportWaypoint", @@ -13942,6 +13962,44 @@ 9707.631 ] }, + { + "id": 1884, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "明冠山地" + ], + "position": [ + 2877.141, + 266.934, + -318.6989 + ], + "tranPosition": [ + 2874.4412, + 267.68185, + -312.82062 + ] + }, + { + "id": 1885, + "type": "Goddess", + "name": "七天神像-风", + "country": "蒙德", + "areas": [ + "风息山" + ], + "position": [ + 3309.774, + 236.7597, + -77.536 + ], + "tranPosition": [ + 3310.141, + 237.4942, + -82.18768 + ] + }, { "id": 1919, "type": "TrounceDomain", @@ -13967,6 +14025,86 @@ "贤医的假面" ] }, + { + "id": 1922, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "风息山", + "风车镇" + ], + "position": [ + 3085.12, + 223.57, + -153.5 + ], + "tranPosition": [ + 3092.045, + 223.5, + -156.0731 + ] + }, + { + "id": 1923, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "风息山", + "荆夫港" + ], + "position": [ + 3505.681, + 229.5591, + -205.1554 + ], + "tranPosition": [ + 3498.978, + 229.65445, + -202.25732 + ] + }, + { + "id": 1924, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "风息山", + "荆夫港" + ], + "position": [ + 3545.853, + 216.2449, + -293.6843 + ], + "tranPosition": [ + 3541.3232, + 216.2449, + -295.43365 + ] + }, + { + "id": 1925, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "风息山", + "荆夫港" + ], + "position": [ + 3590.147, + 236.6072, + -251.4925 + ], + "tranPosition": [ + 3591.2407, + 236.71031, + -245.17517 + ] + }, { "id": 1953, "type": "TeleportWaypoint", @@ -14047,6 +14185,25 @@ 9920.595 ] }, + { + "id": 1959, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "风息山" + ], + "position": [ + 4106.138, + 108.106, + -223.881 + ], + "tranPosition": [ + 4110.4688, + 107.4543, + -227.65422 + ] + }, { "id": 1960, "type": "TeleportWaypoint", @@ -14066,6 +14223,101 @@ 282.2899, 9669.12 ] + }, + { + "id": 2009, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "风息山" + ], + "position": [ + 2881.072, + 229.189, + -743.8425 + ], + "tranPosition": [ + 2888.099, + 228.76279, + -748.8444 + ] + }, + { + "id": 2033, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "风息山" + ], + "position": [ + 2990.598, + 226.2524, + -461.8188 + ], + "tranPosition": [ + 2989.9167, + 226.4, + -453.02206 + ] + }, + { + "id": 2034, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "风息山" + ], + "position": [ + 3626.978, + 157.5714, + -320.7664 + ], + "tranPosition": [ + 3622.09, + 157.84663, + -317.44318 + ] + }, + { + "id": 2051, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "风息山" + ], + "position": [ + 3603.124, + 244.0258, + -515.0546 + ], + "tranPosition": [ + 3600.2837, + 243.72385, + -509.51843 + ] + }, + { + "id": 2052, + "type": "TeleportWaypoint", + "name": "传送锚点", + "country": "蒙德", + "areas": [ + "风息山" + ], + "position": [ + 3791.413, + 348.205, + -209.356 + ], + "tranPosition": [ + 3788.4019, + 347.94803, + -217.91002 + ] } ] }, From c8d10614c5a4ca1279e4664459a860e20d60b7a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Mon, 6 Apr 2026 16:02:56 +0800 Subject: [PATCH 107/107] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E6=8E=92=E9=99=A4?= =?UTF-8?q?=E4=BA=86=E4=B8=80=E4=BA=9B=E5=8A=A8=E6=80=81=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E7=9A=84=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SupabaseMissingTranslationReporter.cs | 51 +++++++++++++++++++ .../View/Behavior/AutoTranslateInterceptor.cs | 17 ++++++- .../View/Pages/JsListPage.xaml | 6 ++- .../View/Windows/PromptDialog.xaml.cs | 10 +++- .../ViewModel/Pages/ScriptControlViewModel.cs | 12 ++++- 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/BetterGenshinImpact/Service/SupabaseMissingTranslationReporter.cs b/BetterGenshinImpact/Service/SupabaseMissingTranslationReporter.cs index 48db465f..768a8192 100644 --- a/BetterGenshinImpact/Service/SupabaseMissingTranslationReporter.cs +++ b/BetterGenshinImpact/Service/SupabaseMissingTranslationReporter.cs @@ -47,6 +47,11 @@ public sealed class SupabaseMissingTranslationReporter : IMissingTranslationRepo return false; } + if (ShouldSkipReporting(language, sourceInfo)) + { + return false; + } + return _channel.Writer.TryWrite( new MissingTranslationEvent( language, @@ -153,6 +158,8 @@ public sealed class SupabaseMissingTranslationReporter : IMissingTranslationRepo var payload = JsonSerializer.Serialize( batch.Select(r => new SupabaseMissingRowSnake(r.Language, r.Key, r.Source, r.SourceInfo)), SupabaseJsonOptions); + Debug.WriteLine( + $"[MissingTranslation][Supabase][Payload] batch={batch.Count}{Environment.NewLine}{FormatBatchKeysForDebug(batch, 50, 4000)}"); using var request = new HttpRequestMessage(HttpMethod.Post, requestUri) { @@ -217,6 +224,25 @@ public sealed class SupabaseMissingTranslationReporter : IMissingTranslationRepo return text.Substring(0, maxLength) + "...(truncated)"; } + private static string FormatBatchKeysForDebug(IReadOnlyList batch, int maxItems, int maxLength) + { + if (batch.Count == 0) + { + return string.Empty; + } + + var lines = batch + .Take(maxItems) + .Select((row, index) => $"{index + 1}. [{row.Language}] {row.Key}"); + var text = string.Join(Environment.NewLine, lines); + if (batch.Count > maxItems) + { + text += $"{Environment.NewLine}... and {batch.Count - maxItems} more"; + } + + return TruncateForLog(text, maxLength); + } + private static TranslationSourceInfo NormalizeSourceInfo(TranslationSourceInfo? sourceInfo) { if (sourceInfo == null) @@ -396,4 +422,29 @@ public sealed class SupabaseMissingTranslationReporter : IMissingTranslationRepo { return ((int)source).ToString(CultureInfo.InvariantCulture); } + + private static bool ShouldSkipReporting(string language, TranslationSourceInfo? sourceInfo) + { + // 中文语言不采集 + if (language.StartsWith("zh", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // 动态命名控件不采集 + if (!string.IsNullOrWhiteSpace(sourceInfo?.ElementName) + && sourceInfo.ElementName.StartsWith("dynamic", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Snackbar 弹出提示不采集 + if (string.Equals(sourceInfo?.ElementType, "Wpf.Ui.Controls.Snackbar", StringComparison.Ordinal)) + { + return true; + } + + return false; + } } + diff --git a/BetterGenshinImpact/View/Behavior/AutoTranslateInterceptor.cs b/BetterGenshinImpact/View/Behavior/AutoTranslateInterceptor.cs index ccf443d9..e82ff0ba 100644 --- a/BetterGenshinImpact/View/Behavior/AutoTranslateInterceptor.cs +++ b/BetterGenshinImpact/View/Behavior/AutoTranslateInterceptor.cs @@ -341,7 +341,12 @@ namespace BetterGenshinImpact.View.Behavior { continue; } - + + if (IsAutoTranslateExplicitlyDisabled(current)) + { + continue; + } + if (IsInGridViewRowPresenter(current)) { continue; @@ -881,6 +886,16 @@ namespace BetterGenshinImpact.View.Behavior return false; } + + private static bool IsAutoTranslateExplicitlyDisabled(DependencyObject obj) + { + return obj switch + { + FrameworkElement fe => fe.ReadLocalValue(EnableAutoTranslateProperty) is bool enable && !enable, + FrameworkContentElement fce => fce.ReadLocalValue(EnableAutoTranslateProperty) is bool enable && !enable, + _ => false + }; + } } } } diff --git a/BetterGenshinImpact/View/Pages/JsListPage.xaml b/BetterGenshinImpact/View/Pages/JsListPage.xaml index 5c0faffc..4664e033 100644 --- a/BetterGenshinImpact/View/Pages/JsListPage.xaml +++ b/BetterGenshinImpact/View/Pages/JsListPage.xaml @@ -1,4 +1,4 @@ - @@ -186,4 +188,4 @@ - \ No newline at end of file + diff --git a/BetterGenshinImpact/View/Windows/PromptDialog.xaml.cs b/BetterGenshinImpact/View/Windows/PromptDialog.xaml.cs index 3257260c..89e5cb90 100644 --- a/BetterGenshinImpact/View/Windows/PromptDialog.xaml.cs +++ b/BetterGenshinImpact/View/Windows/PromptDialog.xaml.cs @@ -1,4 +1,5 @@ using BetterGenshinImpact.Helpers.Ui; +using BetterGenshinImpact.View.Behavior; using System; using System.Windows; using System.Windows.Controls; @@ -24,6 +25,8 @@ public class PromptDialogConfig /// 左下角按钮的点击事件 /// public RoutedEventHandler? LeftButtonClick { get; set; } + + public bool DisableAutoTranslate { get; set; } = false; } public partial class PromptDialog @@ -37,6 +40,11 @@ public partial class PromptDialog TxtQuestion.Text = question; _config = config ?? new PromptDialogConfig(); + if (_config.DisableAutoTranslate) + { + AutoTranslateInterceptor.SetEnableAutoTranslate(this, false); + } + DynamicContent.Content = uiElement; if (DynamicContent.Content is TextBox textBox && defaultValue != null) { @@ -139,4 +147,4 @@ public partial class PromptDialog { Close(); } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs index c4fc742e..caa2df84 100644 --- a/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/ScriptControlViewModel.cs @@ -12,6 +12,7 @@ using BetterGenshinImpact.Model; using BetterGenshinImpact.Service.Interface; using BetterGenshinImpact.View.Controls.Webview; using BetterGenshinImpact.View.Pages.View; +using BetterGenshinImpact.View.Behavior; using BetterGenshinImpact.View.Windows; using BetterGenshinImpact.View.Windows.Editable; using BetterGenshinImpact.ViewModel.Pages.View; @@ -884,7 +885,15 @@ public partial class ScriptControlViewModel : ViewModel var stackPanel = await CreatePathingScriptSelectionPanelAsync(root.Children); // 显示选择对话框 - var result = PromptDialog.Prompt("请选择需要添加的地图追踪任务", "请选择需要添加的地图追踪任务", stackPanel, new Size(600, 720)); + var result = PromptDialog.Prompt( + "请选择需要添加的地图追踪任务", + "请选择需要添加的地图追踪任务", + stackPanel, + new Size(600, 720), + new PromptDialogConfig + { + DisableAutoTranslate = true + }); if (!string.IsNullOrEmpty(result)) { @@ -1666,6 +1675,7 @@ public partial class ScriptControlViewModel : ViewModel Owner = Application.Current.MainWindow, WindowStartupLocation = WindowStartupLocation.CenterOwner, }; + AutoTranslateInterceptor.SetEnableAutoTranslate(uiMessageBox, false); uiMessageBox.ShowDialogAsync(); // 由于 JsScriptSettingsObject 的存在,这里只能手动再次保存配置