diff --git a/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs b/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs index 84432bc472..4c13947a03 100644 --- a/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs +++ b/src/MaaWpfGui/ViewModels/UI/CopilotViewModel.cs @@ -46,6 +46,13 @@ namespace MaaWpfGui.ViewModels.UI private readonly RunningState _runningState; private static readonly ILogger _logger = Log.ForContext(); private readonly List _copilotIdList = []; // 用于保存作业列表中的作业的Id,对于同一个作业,只有都执行成功才点赞 + private readonly List _recentlyRatedCopilotId = []; // TODO: 可能考虑加个持久化 + private AsstTaskType _taskType = AsstTaskType.Copilot; + private const string CopilotIdPrefix = "maa://"; + private const string TempCopilotFile = "cache/_temp_copilot.json"; + private static readonly string[] _supportExt = [".json", ".mp4", ".m4s", ".mkv", ".flv", ".avi"]; + private const string CopilotJsonDir = "cache/copilot"; + private const string StageNameRegex = @"(?:[a-z]{0,3})(?:\d{0,2})-(?:(?:A|B|C|D|EX|S|TR|MO)-?)?(?:\d{1,2})(\(Raid\)(?=\.json))?"; /// /// Gets the view models of log items. @@ -110,6 +117,10 @@ namespace MaaWpfGui.ViewModels.UI Idle = e; } + #region UI绑定及操作 + + #region Log + /// /// Adds log. /// @@ -131,6 +142,18 @@ namespace MaaWpfGui.ViewModels.UI // LogItemViewModels.Insert(0, new LogItemViewModel(time + content, color, weight)); } + /// + /// Clears log. + /// + private void ClearLog() + { + Execute.OnUIThread(() => LogItemViewModels.Clear()); + } + + #endregion Log + + #region 属性 + private bool _idle; /// @@ -153,14 +176,6 @@ namespace MaaWpfGui.ViewModels.UI set => SetAndNotify(ref _startEnabled, value); } - /// - /// Clears log. - /// - private void ClearLog() - { - Execute.OnUIThread(() => LogItemViewModels.Clear()); - } - private string _filename = string.Empty; /// @@ -177,6 +192,381 @@ namespace MaaWpfGui.ViewModels.UI } } + private bool _form; + + /// + /// Gets or sets a value indicating whether to use auto-formation. + /// + public bool Form + { + get => _form; + set => SetAndNotify(ref _form, value); + } + + private bool _addTrust; + + /// + /// Gets or sets a value indicating whether to use auto-formation. + /// + public bool AddTrust + { + get => _addTrust; + set => SetAndNotify(ref _addTrust, value); + } + + private bool _useSanityPotion; + + public bool UseSanityPotion + { + get => _useSanityPotion; + set => SetAndNotify(ref _useSanityPotion, value); + } + + private bool _addUserAdditional = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.CopilotAddUserAdditional, bool.FalseString)); + + /// + /// Gets or sets a value indicating whether to use auto-formation. + /// + public bool AddUserAdditional + { + get => _addUserAdditional; + set + { + SetAndNotify(ref _addUserAdditional, value); + ConfigurationHelper.SetValue(ConfigurationKeys.CopilotAddUserAdditional, value.ToString()); + } + } + + private string _userAdditional = ConfigurationHelper.GetValue(ConfigurationKeys.CopilotUserAdditional, string.Empty); + + /// + /// Gets or sets a value indicating whether to use auto-formation. + /// + public string UserAdditional + { + get => _userAdditional; + set + { + SetAndNotify(ref _userAdditional, value); + ConfigurationHelper.SetValue(ConfigurationKeys.CopilotUserAdditional, value); + } + } + + /// + /// Gets or sets a value indicating whether to use auto-formation. + /// + private bool _useCopilotList; + + public bool UseCopilotList + { + get => _useCopilotList; + set + { + if (value) + { + _taskType = AsstTaskType.Copilot; + Form = true; + } + + SetAndNotify(ref _useCopilotList, value); + } + } + + /// + /// Gets or sets a value indicating whether to use auto-formation. + /// + private string? _copilotTaskName = string.Empty; + + public string? CopilotTaskName + { + get => _copilotTaskName; + set + { + SetAndNotify(ref _copilotTaskName, value); + } + } + + public bool Loop { get; set; } + + private int _loopTimes = int.Parse(ConfigurationHelper.GetValue(ConfigurationKeys.CopilotLoopTimes, "1")); + + public int LoopTimes + { + get => _loopTimes; + set + { + SetAndNotify(ref _loopTimes, value); + ConfigurationHelper.SetValue(ConfigurationKeys.CopilotLoopTimes, value.ToString()); + } + } + + private string _urlText = LocalizationHelper.GetString("PrtsPlus"); + + /// + /// Gets or private sets the UrlText. + /// + public string UrlText + { + get => _urlText; + private set => SetAndNotify(ref _urlText, value); + } + + private const string CopilotUiUrl = MaaUrls.PrtsPlus; + + private string _copilotUrl = CopilotUiUrl; + + /// + /// Gets or private sets the copilot URL. + /// + public string CopilotUrl + { + get => _copilotUrl; + private set + { + UrlText = value == CopilotUiUrl ? LocalizationHelper.GetString("PrtsPlus") : LocalizationHelper.GetString("VideoLink"); + SetAndNotify(ref _copilotUrl, value); + } + } + + private const string MapUiUrl = MaaUrls.MapPrts; + + private string _mapUrl = MapUiUrl; + + public string MapUrl + { + get => _mapUrl; + private set => SetAndNotify(ref _mapUrl, value); + } + + private bool _couldLikeWebJson; + + public bool CouldLikeWebJson + { + get => _couldLikeWebJson; + set => SetAndNotify(ref _couldLikeWebJson, value); + } + + #endregion 属性 + + #region 方法 + + /// + /// Selects file. + /// + // ReSharper disable once UnusedMember.Global + public void SelectFile() + { + var dialog = new OpenFileDialog + { + Filter = "JSON|*.json|Video|*.mp4;*.m4s;*.mkv;*.flv;*.avi", + }; + + if (dialog.ShowDialog() == true) + { + Filename = dialog.FileName; + } + } + + /// + /// Paste clipboard contents. + /// + // ReSharper disable once UnusedMember.Global + public void PasteClipboard() + { + if (Clipboard.ContainsText()) + { + Filename = Clipboard.GetText().Trim(); + } + else if (Clipboard.ContainsFileDropList()) + { + DropFile(Clipboard.GetFileDropList()[0]); + } + } + + /// + /// Paste clipboard contents. + /// + // ReSharper disable once UnusedMember.Global + public void PasteClipboardCopilotSet() + { + IsCopilotSet = true; + PasteClipboard(); + } + + /// + /// 批量导入作业 + /// + // ReSharper disable once UnusedMember.Global + public async void ImportFiles() + { + var dialog = new OpenFileDialog + { + Filter = "JSON|*.json", + Multiselect = true, + }; + + if (dialog.ShowDialog() != true) + { + return; + } + + Dictionary taskPairs = new(); + foreach (var file in dialog.FileNames) + { + var fileInfo = new FileInfo(file); + if (!fileInfo.Exists) + { + AddLog($"{file} not exists", showTime: false); + return; + } + + try + { + using var reader = new StreamReader(File.OpenRead(file)); + var jsonStr = await reader.ReadToEndAsync(); + + var json = (JObject?)JsonConvert.DeserializeObject(jsonStr); + if (json is null || !json.ContainsKey("stage_name") || !json.ContainsKey("actions")) + { + AddLog($"{file} is broken", UiLogColor.Error, showTime: false); + return; + } + + var fileName = fileInfo.Name[..^fileInfo.Extension.Length]; + var stageName = FindStageName(fileInfo.Name, fileName); + + if (string.IsNullOrEmpty(stageName)) + { + AddLog($"invalid name to navigate: {fileName}[{fileInfo.FullName}]", UiLogColor.Error, showTime: false); + return; + } + + taskPairs.Add(stageName, file); + } + catch (Exception) + { + AddLog($"{file}: " + LocalizationHelper.GetString("CopilotFileReadError"), UiLogColor.Error, showTime: false); + return; + } + } + + try + { + Directory.CreateDirectory(CopilotJsonDir); + } + catch (Exception) + { + // ignored + } + + foreach (var taskPair in taskPairs) + { + var jsonPath = $"{CopilotJsonDir}/{taskPair.Key}.json"; + if (new FileInfo(jsonPath).FullName != taskPair.Value) + { + // 相同路径跳拷贝 + File.Copy(taskPair.Value, jsonPath, true); + } + + var navigateName = taskPair.Key; + var isRaid = navigateName.EndsWith("(Raid)"); + var item = new CopilotItemViewModel(taskPair.Key.Replace("(Raid)", string.Empty), jsonPath, isRaid) + { + Index = CopilotItemViewModels.Count, + }; + CopilotItemViewModels.Add(item); + AddLog("append task: " + taskPair.Key, showTime: false); + } + + SaveCopilotTask(); + } + + // UI 绑定的方法 + // ReSharper disable once UnusedMember.Global + public void AddCopilotTask() + { + AddCopilotTaskToList(CopilotTaskName, false); + } + + // UI 绑定的方法 + // ReSharper disable once UnusedMember.Global + public void AddCopilotTask_Adverse() + { + AddCopilotTaskToList(CopilotTaskName, true); + } + + // UI 绑定的方法 + // ReSharper disable once UnusedMember.Global + public void SelectCopilotTask(int index) + { + Filename = CopilotItemViewModels[index].FilePath; + ClearLog(); + UpdateFilename(); + } + + // UI 绑定的方法 + // ReSharper disable once UnusedMember.Global + public void DeleteCopilotTask(int index) + { + CopilotItemViewModels.RemoveAt(index); + CopilotItemIndexChanged(); + } + + // UI 绑定的方法 + // ReSharper disable once UnusedMember.Global + public void CleanUnableCopilotTask() + { + foreach (var item in CopilotItemViewModels.Where(model => !model.IsChecked).ToList()) + { + CopilotItemViewModels.Remove(item); + } + + CopilotItemIndexChanged(); + } + + // UI 绑定的方法 + // ReSharper disable once UnusedMember.Global + public void ClearCopilotTask() + { + CopilotItemViewModels.Clear(); + SaveCopilotTask(); + + try + { + Directory.Delete(CopilotJsonDir, true); + } + catch + { + // ignored + } + } + + // ReSharper disable once UnusedMember.Global + public void LikeWebJson() + { + RateWebJson("Like", CopilotId); + } + + // ReSharper disable once UnusedMember.Global + public void DislikeWebJson() + { + RateWebJson("Dislike", CopilotId); + } + + private void EasterEgg(string text) + { + switch (text) + { + case "/help": + AddLog(LocalizationHelper.GetString("HelloWorld"), UiLogColor.Message, showTime: false); + break; + } + } + + #endregion 方法 + + #endregion UI绑定及操作 + private async void UpdateFilename() { StartEnabled = false; @@ -185,8 +575,6 @@ namespace MaaWpfGui.ViewModels.UI StartEnabled = true; } - private const string CopilotIdPrefix = "maa://"; - private async Task UpdateFileDoc(string filename) { ClearLog(); @@ -280,10 +668,6 @@ namespace MaaWpfGui.ViewModels.UI return string.Empty; } - private const string TempCopilotFile = "cache/_temp_copilot.json"; - private AsstTaskType _taskType = AsstTaskType.Copilot; - private const string StageNameRegex = @"(?:[a-z]{0,3})(?:\d{0,2})-(?:(?:A|B|C|D|EX|S|TR|MO)-?)?(?:\d{1,2})(\(Raid\)(?=\.json))?"; - /// /// 为自动战斗列表匹配名字 /// @@ -524,139 +908,6 @@ namespace MaaWpfGui.ViewModels.UI } } - /// - /// Selects file. - /// - // ReSharper disable once UnusedMember.Global - public void SelectFile() - { - var dialog = new OpenFileDialog - { - Filter = "JSON|*.json|Video|*.mp4;*.m4s;*.mkv;*.flv;*.avi", - }; - - if (dialog.ShowDialog() == true) - { - Filename = dialog.FileName; - } - } - - /// - /// Paste clipboard contents. - /// - // ReSharper disable once UnusedMember.Global - public void PasteClipboard() - { - if (Clipboard.ContainsText()) - { - Filename = Clipboard.GetText().Trim(); - } - else if (Clipboard.ContainsFileDropList()) - { - DropFile(Clipboard.GetFileDropList()[0]); - } - } - - /// - /// Paste clipboard contents. - /// - // ReSharper disable once UnusedMember.Global - public void PasteClipboardCopilotSet() - { - IsCopilotSet = true; - PasteClipboard(); - } - - /// - /// 批量导入作业 - /// - // ReSharper disable once UnusedMember.Global - public async void ImportFiles() - { - var dialog = new OpenFileDialog - { - Filter = "JSON|*.json", - Multiselect = true, - }; - - if (dialog.ShowDialog() != true) - { - return; - } - - Dictionary taskPairs = new(); - foreach (var file in dialog.FileNames) - { - var fileInfo = new FileInfo(file); - if (!fileInfo.Exists) - { - AddLog($"{file} not exists", showTime: false); - return; - } - - try - { - using var reader = new StreamReader(File.OpenRead(file)); - var jsonStr = await reader.ReadToEndAsync(); - - var json = (JObject?)JsonConvert.DeserializeObject(jsonStr); - if (json is null || !json.ContainsKey("stage_name") || !json.ContainsKey("actions")) - { - AddLog($"{file} is broken", UiLogColor.Error, showTime: false); - return; - } - - var fileName = fileInfo.Name[..^fileInfo.Extension.Length]; - var stageName = FindStageName(fileInfo.Name, fileName); - - if (string.IsNullOrEmpty(stageName)) - { - AddLog($"invalid name to navigate: {fileName}[{fileInfo.FullName}]", UiLogColor.Error, showTime: false); - return; - } - - taskPairs.Add(stageName, file); - } - catch (Exception) - { - AddLog($"{file}: " + LocalizationHelper.GetString("CopilotFileReadError"), UiLogColor.Error, showTime: false); - return; - } - } - - try - { - Directory.CreateDirectory(CopilotJsonDir); - } - catch (Exception) - { - // ignored - } - - foreach (var taskPair in taskPairs) - { - var jsonPath = $"{CopilotJsonDir}/{taskPair.Key}.json"; - if (new FileInfo(jsonPath).FullName != taskPair.Value) - { - // 相同路径跳拷贝 - File.Copy(taskPair.Value, jsonPath, true); - } - - var navigateName = taskPair.Key; - var isRaid = navigateName.EndsWith("(Raid)"); - var item = new CopilotItemViewModel(taskPair.Key.Replace("(Raid)", string.Empty), jsonPath, isRaid) - { - Index = CopilotItemViewModels.Count, - }; - CopilotItemViewModels.Add(item); - AddLog("append task: " + taskPair.Key, showTime: false); - } - - SaveCopilotTask(); - } - - private static readonly string[] _supportExt = { ".json", ".mp4", ".m4s", ".mkv", ".flv", ".avi" }; - /// /// Drops file. /// @@ -723,118 +974,6 @@ namespace MaaWpfGui.ViewModels.UI } } - private bool _form; - - /// - /// Gets or sets a value indicating whether to use auto-formation. - /// - public bool Form - { - get => _form; - set => SetAndNotify(ref _form, value); - } - - private bool _addTrust; - - /// - /// Gets or sets a value indicating whether to use auto-formation. - /// - public bool AddTrust - { - get => _addTrust; - set => SetAndNotify(ref _addTrust, value); - } - - private bool _useSanityPotion; - - public bool UseSanityPotion - { - get => _useSanityPotion; - set => SetAndNotify(ref _useSanityPotion, value); - } - - private bool _addUserAdditional = Convert.ToBoolean(ConfigurationHelper.GetValue(ConfigurationKeys.CopilotAddUserAdditional, bool.FalseString)); - - /// - /// Gets or sets a value indicating whether to use auto-formation. - /// - public bool AddUserAdditional - { - get => _addUserAdditional; - set - { - SetAndNotify(ref _addUserAdditional, value); - ConfigurationHelper.SetValue(ConfigurationKeys.CopilotAddUserAdditional, value.ToString()); - } - } - - private string _userAdditional = ConfigurationHelper.GetValue(ConfigurationKeys.CopilotUserAdditional, string.Empty); - - /// - /// Gets or sets a value indicating whether to use auto-formation. - /// - public string UserAdditional - { - get => _userAdditional; - set - { - SetAndNotify(ref _userAdditional, value); - ConfigurationHelper.SetValue(ConfigurationKeys.CopilotUserAdditional, value); - } - } - - /// - /// Gets or sets a value indicating whether to use auto-formation. - /// - private bool _useCopilotList; - - public bool UseCopilotList - { - get => _useCopilotList; - set - { - if (value) - { - _taskType = AsstTaskType.Copilot; - Form = true; - } - - SetAndNotify(ref _useCopilotList, value); - } - } - - /// - /// Gets or sets a value indicating whether to use auto-formation. - /// - private string _copilotTaskName = string.Empty; - - public string CopilotTaskName - { - get => _copilotTaskName; - set - { - SetAndNotify(ref _copilotTaskName, value); - } - } - - private const string CopilotJsonDir = "cache/copilot"; - - // UI 绑定的方法 - // ReSharper disable once UnusedMember.Global - public void AddCopilotTask() - { - var stageName = CopilotTaskName.Trim(); - AddCopilotTaskToList(stageName, false); - } - - // UI 绑定的方法 - // ReSharper disable once UnusedMember.Global - public void AddCopilotTask_Adverse() - { - var stageName = CopilotTaskName.Trim(); - AddCopilotTaskToList(stageName, true); - } - private void AddCopilotTaskToList(string? stageName, bool isRaid) { var invalidChar = @"[:',\.\(\)\|\[\]\?,。【】{};:]"; // 无效字符 @@ -896,52 +1035,6 @@ namespace MaaWpfGui.ViewModels.UI ConfigurationHelper.SetValue(ConfigurationKeys.CopilotTaskList, JsonConvert.SerializeObject(jArray)); } - // UI 绑定的方法 - // ReSharper disable once UnusedMember.Global - public void SelectCopilotTask(int index) - { - Filename = CopilotItemViewModels[index].FilePath; - ClearLog(); - UpdateFilename(); - } - - // UI 绑定的方法 - // ReSharper disable once UnusedMember.Global - public void DeleteCopilotTask(int index) - { - CopilotItemViewModels.RemoveAt(index); - CopilotItemIndexChanged(); - } - - // UI 绑定的方法 - // ReSharper disable once UnusedMember.Global - public void CleanUnableCopilotTask() - { - foreach (var item in CopilotItemViewModels.Where(model => !model.IsChecked).ToList()) - { - CopilotItemViewModels.Remove(item); - } - - CopilotItemIndexChanged(); - } - - // UI 绑定的方法 - // ReSharper disable once UnusedMember.Global - public void ClearCopilotTask() - { - CopilotItemViewModels.Clear(); - SaveCopilotTask(); - - try - { - Directory.Delete(CopilotJsonDir, true); - } - catch - { - // ignored - } - } - /// /// 战斗列表的当前战斗任务成功 /// @@ -989,20 +1082,6 @@ namespace MaaWpfGui.ViewModels.UI }); } - public bool Loop { get; set; } - - private int _loopTimes = int.Parse(ConfigurationHelper.GetValue(ConfigurationKeys.CopilotLoopTimes, "1")); - - public int LoopTimes - { - get => _loopTimes; - set - { - SetAndNotify(ref _loopTimes, value); - ConfigurationHelper.SetValue(ConfigurationKeys.CopilotLoopTimes, value.ToString()); - } - } - /// /// Starts copilot. /// @@ -1125,16 +1204,6 @@ namespace MaaWpfGui.ViewModels.UI private bool _isVideoTask; - private readonly List _recentlyRatedCopilotId = new(); // TODO: 可能考虑加个持久化 - - private bool _couldLikeWebJson; - - public bool CouldLikeWebJson - { - get => _couldLikeWebJson; - set => SetAndNotify(ref _couldLikeWebJson, value); - } - private void UpdateCouldLikeWebJson() { CouldLikeWebJson = IsDataFromWeb && @@ -1174,18 +1243,6 @@ namespace MaaWpfGui.ViewModels.UI } } - // ReSharper disable once UnusedMember.Global - public void LikeWebJson() - { - RateWebJson("Like", CopilotId); - } - - // ReSharper disable once UnusedMember.Global - public void DislikeWebJson() - { - RateWebJson("Dislike", CopilotId); - } - private async void RateWebJson(string rating, int copilotId) { if (!CouldLikeWebJson) @@ -1214,44 +1271,6 @@ namespace MaaWpfGui.ViewModels.UI AddLog(LocalizationHelper.GetString("ThanksForLikeWebJson"), UiLogColor.Info, showTime: false); } - private string _urlText = LocalizationHelper.GetString("PrtsPlus"); - - /// - /// Gets or private sets the UrlText. - /// - public string UrlText - { - get => _urlText; - private set => SetAndNotify(ref _urlText, value); - } - - private const string CopilotUiUrl = MaaUrls.PrtsPlus; - - private string _copilotUrl = CopilotUiUrl; - - /// - /// Gets or private sets the copilot URL. - /// - public string CopilotUrl - { - get => _copilotUrl; - private set - { - UrlText = value == CopilotUiUrl ? LocalizationHelper.GetString("PrtsPlus") : LocalizationHelper.GetString("VideoLink"); - SetAndNotify(ref _copilotUrl, value); - } - } - - private const string MapUiUrl = MaaUrls.MapPrts; - - private string _mapUrl = MapUiUrl; - - public string MapUrl - { - get => _mapUrl; - private set => SetAndNotify(ref _mapUrl, value); - } - /// /// 点击后移除界面中元素焦点 /// @@ -1293,15 +1312,5 @@ namespace MaaWpfGui.ViewModels.UI FocusManager.SetFocusedElement(scope, element); Keyboard.ClearFocus(); } - - private void EasterEgg(string text) - { - switch (text) - { - case "/help": - AddLog(LocalizationHelper.GetString("HelloWorld"), UiLogColor.Message, showTime: false); - break; - } - } } }