diff --git a/BetterGenshinImpact/Core/Config/HotKeyConfig.cs b/BetterGenshinImpact/Core/Config/HotKeyConfig.cs index f42b813d..62df366f 100644 --- a/BetterGenshinImpact/Core/Config/HotKeyConfig.cs +++ b/BetterGenshinImpact/Core/Config/HotKeyConfig.cs @@ -127,4 +127,11 @@ public partial class HotKeyConfig : ObservableObject [ObservableProperty] private string _clickGenshinCancelButtonHotkeyType = HotKeyTypeEnum.KeyboardMonitor.ToString(); + + // 一键战斗宏 + [ObservableProperty] + private string _oneKeyFightHotkey = ""; + + [ObservableProperty] + private string _oneKeyFightHotkeyType = HotKeyTypeEnum.KeyboardMonitor.ToString(); } diff --git a/BetterGenshinImpact/Core/Config/MacroConfig.cs b/BetterGenshinImpact/Core/Config/MacroConfig.cs index 219ae9a2..69e104d5 100644 --- a/BetterGenshinImpact/Core/Config/MacroConfig.cs +++ b/BetterGenshinImpact/Core/Config/MacroConfig.cs @@ -1,4 +1,5 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using BetterGenshinImpact.GameTask.AutoFight; +using CommunityToolkit.Mvvm.ComponentModel; using System; namespace BetterGenshinImpact.Core.Config; @@ -59,7 +60,7 @@ public partial class MacroConfig : ObservableObject /// 一键战斗宏快捷键模式 /// [ObservableProperty] - private string _combatMacroHotkeyMode = "按住时重复"; + private string _combatMacroHotkeyMode = OneKeyFightTask.HoldOnMode; /// /// 一键战斗宏优先级 diff --git a/BetterGenshinImpact/GameTask/AutoFight/Model/AvatarMacro.cs b/BetterGenshinImpact/GameTask/AutoFight/Model/AvatarMacro.cs new file mode 100644 index 00000000..59f36ca4 --- /dev/null +++ b/BetterGenshinImpact/GameTask/AutoFight/Model/AvatarMacro.cs @@ -0,0 +1,42 @@ +using BetterGenshinImpact.GameTask.AutoFight.Script; +using System.Collections.Generic; + +namespace BetterGenshinImpact.GameTask.AutoFight.Model; + +public class AvatarMacro +{ + public string Name { get; set; } = string.Empty; + public string ScriptContent1 { get; set; } = string.Empty; + public string ScriptContent2 { get; set; } = string.Empty; + public string ScriptContent3 { get; set; } = string.Empty; + public string ScriptContent4 { get; set; } = string.Empty; + public string ScriptContent5 { get; set; } = string.Empty; + + public string GetScriptContent(int index) + { + return index switch + { + 1 => ScriptContent1, + 2 => ScriptContent2, + 3 => ScriptContent3, + 4 => ScriptContent4, + 5 => ScriptContent5, + _ => string.Empty + }; + } + + public string GetScriptContent() + { + return GetScriptContent(TaskContext.Instance().Config.MacroConfig.CombatMacroPriority); + } + + public List? LoadCommands() + { + var content = GetScriptContent(); + if (string.IsNullOrWhiteSpace(content)) + { + return null; + } + return CombatScriptParser.ParseLineCommands(content, Name); + } +} diff --git a/BetterGenshinImpact/GameTask/AutoFight/OneKeyFightTask.cs b/BetterGenshinImpact/GameTask/AutoFight/OneKeyFightTask.cs index 3f8eb906..f0f83834 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/OneKeyFightTask.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/OneKeyFightTask.cs @@ -1,6 +1,15 @@ -using BetterGenshinImpact.GameTask.AutoFight.Model; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using BetterGenshinImpact.GameTask.AutoFight.Model; using BetterGenshinImpact.Model; +using System.Threading.Tasks; +using System.Threading; +using BetterGenshinImpact.Core.Config; using static BetterGenshinImpact.GameTask.Common.TaskControl; +using BetterGenshinImpact.GameTask.AutoFight.Script; +using BetterGenshinImpact.Service; namespace BetterGenshinImpact.GameTask.AutoFight; @@ -9,8 +18,106 @@ namespace BetterGenshinImpact.GameTask.AutoFight; /// public class OneKeyFightTask : Singleton { + public static readonly string HoldOnMode = "按住时重复"; + public static readonly string TickMode = "触发"; + + private Dictionary>? _avatarMacros; + private CancellationTokenSource _cts = new(); + private Task? _fightTask; + public void Run() { - var combatScenes = new CombatScenes().InitializeTeam(GetContentFromDispatcher()); + if (!IsEnabled()) + { + return; + } + _avatarMacros ??= LoadAvatarMacros(); + + if (IsHoldOnMode()) + { + if (_fightTask == null || _fightTask.IsCompleted) + { + _fightTask = FightTask(_cts); + _fightTask.Start(); + } + Thread.Sleep(100); + } + else if (IsTickMode()) + { + if (_cts.Token.IsCancellationRequested) + { + _cts = new CancellationTokenSource(); + Task.Run(() => FightTask(_cts)); + } + else + { + _cts.Cancel(); + } + } + } + + /// + /// 循环执行战斗宏 + /// + private Task FightTask(CancellationTokenSource cts) + { + var content = GetContentFromDispatcher(); + var combatScenes = new CombatScenes().InitializeTeam(content); + // 找到出战角色 + var activeAvatar = combatScenes.Avatars.First(avatar => avatar.IsActive(content)); + + if (_avatarMacros != null && _avatarMacros.TryGetValue(activeAvatar.Name, out var combatCommands)) + { + return new Task(() => + { + while (!cts.Token.IsCancellationRequested && IsEnabled()) + { + // 通用化战斗策略 + foreach (var command in combatCommands) + { + command.Execute(combatScenes); + } + } + }); + } + else + { + return Task.CompletedTask; + } + } + + public static Dictionary> LoadAvatarMacros() + { + var json = File.ReadAllText(Global.Absolute("User/avatar_macro.json")); + var avatarMacros = JsonSerializer.Deserialize>(json, ConfigService.JsonOptions); + if (avatarMacros == null) + { + return new Dictionary>(); + } + var result = new Dictionary>(); + foreach (var avatarMacro in avatarMacros) + { + var commands = avatarMacro.LoadCommands(); + if (commands != null) + { + result.Add(avatarMacro.Name, commands); + } + } + return result; + } + + public static bool IsEnabled() + { + return TaskContext.Instance().Config.MacroConfig.CombatMacroEnabled; + } + + public static bool IsHoldOnMode() + { + return TaskContext.Instance().Config.MacroConfig.CombatMacroHotkeyMode == HoldOnMode; + } + + public static bool IsTickMode() + { + return TaskContext.Instance().Config.MacroConfig.CombatMacroHotkeyMode == TickMode; } } diff --git a/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs b/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs index 3093faab..34c73fe8 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Script/CombatCommand.cs @@ -83,6 +83,11 @@ public class CombatCommand avatar.Switch(); } + Execute(avatar); + } + + public void Execute(Avatar avatar) + { if (Method == Method.Skill) { var hold = Args != null && Args.Contains("hold"); @@ -230,4 +235,4 @@ public class CombatCommand throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/BetterGenshinImpact/GameTask/AutoFight/Script/CombatScriptParser.cs b/BetterGenshinImpact/GameTask/AutoFight/Script/CombatScriptParser.cs index d52ad5c4..a6796c56 100644 --- a/BetterGenshinImpact/GameTask/AutoFight/Script/CombatScriptParser.cs +++ b/BetterGenshinImpact/GameTask/AutoFight/Script/CombatScriptParser.cs @@ -78,61 +78,8 @@ public class CombatScriptParser HashSet combatAvatarNames = new(); foreach (var line in lines) { - // 以空格分隔角色和指令 截取第一个空格前的内容为角色名称,后面的为指令 - var firstSpaceIndex = line.IndexOf(' '); - if (firstSpaceIndex < 0) - { - Logger.LogError("战斗脚本格式错误,必须以空格分隔角色和指令"); - throw new Exception("战斗脚本格式错误,必须以空格分隔角色和指令"); - } - - var character = line[..firstSpaceIndex]; - character = DefaultAutoFightConfig.AvatarAliasToStandardName(character); - var commands = line[(firstSpaceIndex + 1)..]; - var commandArray = commands.Split(",", StringSplitOptions.RemoveEmptyEntries); - - for (var i = 0; i < commandArray.Length; i++) - { - var command = commandArray[i]; - if (string.IsNullOrEmpty(command)) - { - continue; - } - - if (command.Contains("(") && !command.Contains(")")) - { - var j = i + 1; - // 括号被逗号分隔,需要合并 - while (j < commandArray.Length) - { - command += "," + commandArray[j]; - if (command.Count("(".Contains) > 1) - { - Logger.LogError("战斗脚本格式错误,指令 {Cmd} 括号无法配对", command); - throw new Exception("战斗脚本格式错误,指令括号无法配对"); - } - - if (command.Contains(")")) - { - i = j; - break; - } - - j++; - } - - if (!(command.Contains("(") && command.Contains(")"))) - { - Logger.LogError("战斗脚本格式错误,指令 {Cmd} 括号不完整", command); - throw new Exception("战斗脚本格式错误,指令括号不完整"); - } - } - - var combatCommand = new CombatCommand(character, command); - combatCommands.Add(combatCommand); - } - - combatAvatarNames.Add(character); + var oneLineCombatCommands = ParseLine(line, combatAvatarNames); + combatCommands.AddRange(oneLineCombatCommands); } var names = string.Join(",", combatAvatarNames); @@ -140,4 +87,72 @@ public class CombatScriptParser return new CombatScript(combatAvatarNames, combatCommands); } + + public static List ParseLine(string line, HashSet combatAvatarNames) + { + var oneLineCombatCommands = new List(); + // 以空格分隔角色和指令 截取第一个空格前的内容为角色名称,后面的为指令 + var firstSpaceIndex = line.IndexOf(' '); + if (firstSpaceIndex < 0) + { + Logger.LogError("战斗脚本格式错误,必须以空格分隔角色和指令"); + throw new Exception("战斗脚本格式错误,必须以空格分隔角色和指令"); + } + + var character = line[..firstSpaceIndex]; + character = DefaultAutoFightConfig.AvatarAliasToStandardName(character); + var commands = line[(firstSpaceIndex + 1)..]; + oneLineCombatCommands.AddRange(ParseLineCommands(commands, character)); + combatAvatarNames.Add(character); + return oneLineCombatCommands; + } + + public static List ParseLineCommands(string lineWithoutAvatar, string avatarName) + { + var oneLineCombatCommands = new List(); + var commandArray = lineWithoutAvatar.Split(",", StringSplitOptions.RemoveEmptyEntries); + + for (var i = 0; i < commandArray.Length; i++) + { + var command = commandArray[i]; + if (string.IsNullOrEmpty(command)) + { + continue; + } + + if (command.Contains("(") && !command.Contains(")")) + { + var j = i + 1; + // 括号被逗号分隔,需要合并 + while (j < commandArray.Length) + { + command += "," + commandArray[j]; + if (command.Count("(".Contains) > 1) + { + Logger.LogError("战斗脚本格式错误,指令 {Cmd} 括号无法配对", command); + throw new Exception("战斗脚本格式错误,指令括号无法配对"); + } + + if (command.Contains(")")) + { + i = j; + break; + } + + j++; + } + + if (!(command.Contains("(") && command.Contains(")"))) + { + Logger.LogError("战斗脚本格式错误,指令 {Cmd} 括号不完整", command); + throw new Exception("战斗脚本格式错误,指令括号不完整"); + } + } + + var combatCommand = new CombatCommand(avatarName, command); + oneLineCombatCommands.Add(combatCommand); + } + + return oneLineCombatCommands; + } } diff --git a/BetterGenshinImpact/View/Pages/MacroSettingsPage.xaml b/BetterGenshinImpact/View/Pages/MacroSettingsPage.xaml index 1a346666..ff3de82f 100644 --- a/BetterGenshinImpact/View/Pages/MacroSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/MacroSettingsPage.xaml @@ -95,7 +95,7 @@ diff --git a/BetterGenshinImpact/ViewModel/Pages/HotKeyPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/HotKeyPageViewModel.cs index 2d0e9846..35e9f4d6 100644 --- a/BetterGenshinImpact/ViewModel/Pages/HotKeyPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/HotKeyPageViewModel.cs @@ -7,6 +7,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Reflection; using System.Threading; +using BetterGenshinImpact.GameTask.AutoFight; using BetterGenshinImpact.GameTask.Common; using BetterGenshinImpact.GameTask.Common.BgiVision; using BetterGenshinImpact.Helpers.Extensions; @@ -317,6 +318,18 @@ public partial class HotKeyPageViewModel : ObservableObject, IViewModel } } )); + + HotKeySettingModels.Add(new HotKeySettingModel( + "一键战斗宏快捷键", + nameof(Config.HotKeyConfig.OneKeyFightHotkey), + Config.HotKeyConfig.OneKeyFightHotkey, + Config.HotKeyConfig.OneKeyFightHotkeyType, + (_, _) => + { + OneKeyFightTask.Instance.Run(); + }, + true + )); } private string ToChinese(bool enabled) diff --git a/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs b/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs index ff55e5dd..2c5b4358 100644 --- a/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs +++ b/BetterGenshinImpact/ViewModel/Pages/MacroSettingsPageViewModel.cs @@ -1,4 +1,5 @@ using BetterGenshinImpact.Core.Config; +using BetterGenshinImpact.GameTask.AutoFight; using BetterGenshinImpact.Service.Interface; using BetterGenshinImpact.View.Pages; using BetterGenshinImpact.View.Windows; @@ -17,7 +18,7 @@ public partial class MacroSettingsPageViewModel : ObservableObject, INavigationA private readonly INavigationService _navigationService; [ObservableProperty] - private string[] _quickFightMacroHotkeyMode = ["按住时重复", "触发"]; + private string[] _quickFightMacroHotkeyMode = [OneKeyFightTask.HoldOnMode, OneKeyFightTask.TickMode]; public MacroSettingsPageViewModel(IConfigService configService, INavigationService navigationService) {