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 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 diff --git a/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs b/BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs index c839e38f..a4d203a3 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"); @@ -280,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 { @@ -412,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 持有期间调用, @@ -2414,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(); @@ -2492,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() { 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 }) { 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) { // 设定配置,配置下拉框会选定。