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
});
}
}
}