diff --git a/Modules/ModuleStore.cs b/Modules/ModuleStore.cs index eb2dcb07..70340db9 100644 --- a/Modules/ModuleStore.cs +++ b/Modules/ModuleStore.cs @@ -476,9 +476,9 @@ public sealed class ModuleStore } /// - /// 回写导入阶段清理过的依赖快照,并保留模块原有文件位置。 + /// 回写已更新的模块依赖快照,并保留模块原有文件位置。 /// - public ModuleDefinition SaveDependencyCleanup(ModuleDefinition module) + public ModuleDefinition SaveDependenciesInPlace(ModuleDefinition module) { Normalize(module); lock (_gate) @@ -501,6 +501,8 @@ public sealed class ModuleStore _modules.Remove(existing); _modules.Add(module.Clone()); _modules = SortModules(_modules).ToList(); + _rejectedModuleIds.Remove(module.Id); + _importIssueModuleIds.Remove(module.Id); return module.Clone(); } } diff --git a/UI/ClassConfigEditorControl.cs b/UI/ClassConfigEditorControl.cs index ef24a273..f42832b9 100644 --- a/UI/ClassConfigEditorControl.cs +++ b/UI/ClassConfigEditorControl.cs @@ -3,6 +3,11 @@ using System.Globalization; namespace Shigure; +public sealed record ClassConfigPostSaveResult( + string? AddonSyncIssue, + int SavedModuleCount, + IReadOnlyList ModuleWarnings); + /// /// 图形化编辑 Fuyutsui class/*.lua 的 ClassBlocks(states / auras / spells / group), /// 并编辑同文件中的 spellsList。 @@ -10,7 +15,7 @@ namespace Shigure; public sealed class ClassConfigEditorControl : UserControl { private readonly Func _resolveClassDirectory; - private readonly Func> _updateConfigAsync; + private readonly Func> _updateConfigAsync; private readonly ListBox _classList = new(); private readonly ListBox _specList = new(); @@ -75,7 +80,7 @@ public sealed class ClassConfigEditorControl : UserControl public ClassConfigEditorControl( Func resolveClassDirectory, - Func> updateConfigAsync) + Func> updateConfigAsync) { _resolveClassDirectory = resolveClassDirectory; _updateConfigAsync = updateConfigAsync; @@ -2759,29 +2764,33 @@ public sealed class ClassConfigEditorControl : UserControl localSaved = true; SetDirty(false); _statusLabel.Text = "本地 Lua 已保存,正在更新配置并同步游戏…"; - var syncIssue = await _updateConfigAsync(_currentDocument.FilePath); + var updateResult = await _updateConfigAsync(_currentDocument.FilePath, _currentClassId.Value); if (IsDisposed) { return; } - if (!string.IsNullOrWhiteSpace(syncIssue)) + if (!string.IsNullOrWhiteSpace(updateResult.AddonSyncIssue)) { _statusLabel.Text = "本地已保存并更新配置,但游戏同步失败"; MessageBox.Show( - $"本地 Lua 已保存,config/keymap 已更新,但游戏插件同步未完成:\n{syncIssue}", + $"本地 Lua、该职业的 {updateResult.SavedModuleCount} 个模块及 config/keymap 已保存," + + $"但游戏插件同步未完成:\n{updateResult.AddonSyncIssue}", "游戏同步失败", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } - _statusLabel.Text = "已保存并更新配置"; + _statusLabel.Text = $"已保存配置及该职业的 {updateResult.SavedModuleCount} 个模块"; + var warningText = updateResult.ModuleWarnings.Count == 0 + ? string.Empty + : $"\n\n有 {updateResult.ModuleWarnings.Count} 个模块未携带依赖,详情见日志。"; MessageBox.Show( - "请在游戏内重载界面, /reload", + $"已一并保存该职业的 {updateResult.SavedModuleCount} 个模块。{warningText}\n\n请在游戏内重载界面, /reload", "保存成功", MessageBoxButtons.OK, - MessageBoxIcon.Information); + updateResult.ModuleWarnings.Count == 0 ? MessageBoxIcon.Information : MessageBoxIcon.Warning); } catch (Exception ex) { diff --git a/UI/MainForm.cs b/UI/MainForm.cs index c4826239..fcfbfab5 100644 --- a/UI/MainForm.cs +++ b/UI/MainForm.cs @@ -123,6 +123,11 @@ public sealed class MainForm : Form, IMessageFilter FuyutsuiKeymapConverter.UpdateResult? Keymap, FuyutsuiAddonSyncResult AddonSync); + private sealed record ClassModuleSaveResult( + int SavedCount, + IReadOnlyList Warnings, + IReadOnlyList Errors); + internal MainForm( AppOptions initialOptions, string baseDirectory, @@ -175,7 +180,7 @@ public sealed class MainForm : Form, IMessageFilter _statusForm.AttachModuleEditor(_moduleEditor); _classConfigEditor = new ClassConfigEditorControl( () => Path.Combine(_addonSyncService.SourceRoot, "class"), - UpdateConfigAfterSaveAsync); + UpdateClassConfigAfterSaveAsync); _statusForm.AttachConfigEditor(_classConfigEditor); _classConfigEditor.DirtyStateChanged += dirty => _statusForm.SetPageDirty(SettingsPage.Config, dirty); _classMacrosEditor = new ClassMacrosEditorControl( @@ -310,7 +315,7 @@ public sealed class MainForm : Form, IMessageFilter { try { - _moduleStore.SaveDependencyCleanup(module); + _moduleStore.SaveDependenciesInPlace(module); cleanedModuleCount++; } catch (Exception ex) @@ -1494,6 +1499,69 @@ public sealed class MainForm : Form, IMessageFilter return DescribeAddonSyncIssue(result.AddonSync); } + private async Task UpdateClassConfigAfterSaveAsync( + string savedAddonFilePath, + int classId) + { + var moduleResult = SaveModulesForClass(classId); + var result = await QueueProjectConfigUpdateAsync(savedAddonFilePath); + if (moduleResult.Errors.Count > 0) + { + throw new InvalidOperationException( + $"配置已更新,但该职业有 {moduleResult.Errors.Count} 个模块保存失败;" + + $"已成功保存 {moduleResult.SavedCount} 个模块。详情见日志。"); + } + + return new ClassConfigPostSaveResult( + DescribeAddonSyncIssue(result.AddonSync), + moduleResult.SavedCount, + moduleResult.Warnings); + } + + private ClassModuleSaveResult SaveModulesForClass(int classId) + { + var modules = _moduleStore.GetModulesForDisplay() + .Where(module => module.Match.ClassId == classId) + .ToList(); + var warnings = new List(); + var errors = new List(); + var savedCount = 0; + + foreach (var module in modules) + { + try + { + module.Version = AppInfo.Version; + var warning = _moduleDependencyService.Capture(module); + _moduleStore.SaveDependenciesInPlace(module); + savedCount++; + if (!string.IsNullOrWhiteSpace(warning)) + { + warnings.Add($"{module.Name}: {warning}"); + } + } + catch (Exception ex) + { + errors.Add($"{module.Name}: {ex.Message}"); + } + } + + var className = ClassNames.GetClassAndSpecName(classId, null).ClassName ?? $"职业{classId}"; + AppendLog($"保存{className}配置时已同步保存 {savedCount}/{modules.Count} 个该职业模块。"); + foreach (var warning in warnings) + { + AppendLog($"模块依赖保存提示: {warning}"); + } + foreach (var error in errors) + { + AppendLog($"模块依赖保存失败: {error}"); + } + + RefreshModuleSelector(_lastSnapshot, forceRefresh: true); + RefreshDefaultModuleSelector(); + return new ClassModuleSaveResult(savedCount, warnings, errors); + } + private Task QueueProjectConfigUpdateAsync(string? savedAddonFilePath) { lock (_configUpdateSync)