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)
{