87 lines
4.0 KiB
C#
87 lines
4.0 KiB
C#
using System;
|
||
using System.Linq;
|
||
using Yarn.Unity;
|
||
|
||
namespace Ichni.Story.Dialogue
|
||
{
|
||
/// <summary>
|
||
/// Yarn 选项的章节内记忆服务。
|
||
/// <para>玩家第一次完成某组选项后,保存其 <see cref="DialogueOption.TextID"/>;再次进入同一 TextBlock 时,
|
||
/// 若该选项仍存在且可用,则不显示选择 UI,直接返回该 Option 给 Yarn。</para>
|
||
/// <para>记录随 Timeline 回滚快照一起恢复,因此回滚到选项之前会让玩家重新选择。</para>
|
||
/// </summary>
|
||
public static class StoryChoiceMemory
|
||
{
|
||
/// <summary>
|
||
/// 根据来源 Block 与当前全部 Option Text ID 生成选项组 Key。
|
||
/// Text ID 已由 Yarn 编译结果稳定生成;排序使纯显示顺序调整不会把同一组选项误认为新选项组。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查找已保存的选项。若 Yarn 改稿后旧 Text ID 已不存在或暂时不可用,
|
||
/// 会删除陈旧记录并回退为普通选择界面,避免自动选择错误路线。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存玩家首次确认的选项。调用时先捕获所属 Block 的 Marker 快照,
|
||
/// 保证该选项及其随后写入的路线变量都能被重开流程一起回滚。
|
||
/// </summary>
|
||
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
|
||
});
|
||
}
|
||
}
|
||
}
|