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)