using System; using System.Linq; using Yarn.Unity; namespace Ichni.Story.Dialogue { /// /// Yarn 选项的章节内记忆服务。 /// 玩家第一次完成某组选项后,保存其 ;再次进入同一 TextBlock 时, /// 若该选项仍存在且可用,则不显示选择 UI,直接返回该 Option 给 Yarn。 /// 记录随 Timeline 回滚快照一起恢复,因此回滚到选项之前会让玩家重新选择。 /// public static class StoryChoiceMemory { /// /// 根据来源 Block 与当前全部 Option Text ID 生成选项组 Key。 /// Text ID 已由 Yarn 编译结果稳定生成;排序使纯显示顺序调整不会把同一组选项误认为新选项组。 /// public static string CreateChoiceKey(string sourceBlockId, DialogueOption[] options) { if (string.IsNullOrWhiteSpace(sourceBlockId) || options == null || options.Length == 0 || options.Any(option => option == null || string.IsNullOrWhiteSpace(option.TextID))) { return null; } string optionSignature = string.Join("|", options .Select(option => option.TextID) .OrderBy(textId => textId, StringComparer.Ordinal)); return sourceBlockId + "::" + optionSignature; } /// /// 查找已保存的选项。若 Yarn 改稿后旧 Text ID 已不存在或暂时不可用, /// 会删除陈旧记录并回退为普通选择界面,避免自动选择错误路线。 /// public static bool TryGetRememberedOption(string sourceBlockId, DialogueOption[] options, out DialogueOption rememberedOption) { rememberedOption = null; StoryTreeController treeController = StoryManager.instance?.treeController; StorySaveModule saveModule = GameSaveManager.instance?.StorySaveModule; string choiceKey = CreateChoiceKey(sourceBlockId, options); if (treeController == null || saveModule == null || string.IsNullOrEmpty(treeController.ActiveChapterIndex) || string.IsNullOrEmpty(choiceKey)) { return false; } if (!saveModule.TryGetChoice(treeController.ActiveChapterIndex, choiceKey, out StoryChoiceRecord record)) return false; rememberedOption = options.FirstOrDefault(option => option != null && option.IsAvailable && option.TextID == record.selectedOptionTextId); if (rememberedOption != null) return true; saveModule.RemoveChoice(treeController.ActiveChapterIndex, choiceKey); return false; } /// /// 保存玩家首次确认的选项。调用时先捕获所属 Block 的 Marker 快照, /// 保证该选项及其随后写入的路线变量都能被重开流程一起回滚。 /// public static void RememberChoice(string sourceBlockId, DialogueOption[] options, DialogueOption selectedOption) { StoryTreeController treeController = StoryManager.instance?.treeController; StorySaveModule saveModule = GameSaveManager.instance?.StorySaveModule; string choiceKey = CreateChoiceKey(sourceBlockId, options); if (treeController == null || saveModule == null || selectedOption == null || string.IsNullOrEmpty(treeController.ActiveChapterIndex) || string.IsNullOrEmpty(choiceKey)) { return; } StoryProgress.TryCaptureActiveBlockSnapshot(); saveModule.SetChoice(treeController.ActiveChapterIndex, new StoryChoiceRecord { sourceBlockId = sourceBlockId, choiceKey = choiceKey, selectedOptionTextId = selectedOption.TextID }); } } }