using UnityEngine;
namespace Ichni.Story
{
///
/// 修改章节剧情进度的唯一业务入口。
/// StoryVariables 只管理内存字典;本类负责在写入前创建 Timeline 回滚快照、保存当前章节、
/// 并刷新剧情树状态。Yarn 指令、TutorialBlock 与未来小游戏都应通过这里写入。
///
public static class StoryProgress
{
// 对话开始时由 StoryDialogueController 设定。它用于判断本次变量/选项写入属于哪个 Block,
// 从而在 Marker 所在列首次被执行前截取正确快照。
private static string _activeMutationBlockId;
///
/// 声明接下来的剧情变量或选项写入属于指定 Block。仅在对话运行期间有效。
///
public static void BeginBlockMutation(string blockId)
{
_activeMutationBlockId = blockId;
TryCaptureActiveBlockSnapshot();
}
///
/// 结束指定 Block 的写入上下文,避免后续非剧情系统错误沿用旧的来源 Block。
///
public static void EndBlockMutation(string blockId)
{
if (_activeMutationBlockId == blockId)
_activeMutationBlockId = null;
}
///
/// 为当前写入来源捕获 Marker 快照。无章节、无来源 Block 或无对应 Marker 时安全返回 false。
///
public static bool TryCaptureActiveBlockSnapshot()
{
StoryTreeController treeController = StoryManager.instance?.treeController;
string blockId = !string.IsNullOrEmpty(_activeMutationBlockId)
? _activeMutationBlockId
: treeController?.currentBlock?.blockId;
return treeController != null && !string.IsNullOrEmpty(blockId) &&
treeController.EnsureRollbackSnapshotsForBlock(blockId);
}
public static bool SetBoolAndRefresh(string variableName, bool value) =>
TryWrite(variableName, () => StoryVariables.SetBool(variableName, value));
public static bool SetIntAndRefresh(string variableName, int value) =>
TryWrite(variableName, () => StoryVariables.SetInt(variableName, value));
public static bool AddIntAndRefresh(string variableName, int delta) =>
SetIntAndRefresh(variableName, StoryVariables.GetInt(variableName) + delta);
public static bool SetFloatAndRefresh(string variableName, float value) =>
TryWrite(variableName, () => StoryVariables.SetFloat(variableName, value));
public static bool AddFloatAndRefresh(string variableName, float delta) =>
SetFloatAndRefresh(variableName, StoryVariables.GetFloat(variableName) + delta);
public static bool SetStringAndRefresh(string variableName, string value) =>
TryWrite(variableName, () => StoryVariables.SetString(variableName, value));
public static bool RemoveVariableAndRefresh(string variableName) =>
TryWrite(variableName, () => StoryVariables.Remove(variableName));
///
/// 处理 TutorialBlock 的“游玩”或“跳过”确认。两种选择都会立即写入进度变量并完成 Block;
/// 完成后的 TutorialBlock 仍可点击,是因为其 View 自身允许 Completed 状态交互。
///
public static bool ResolveTutorial(StoryBlockDefinition tutorialBlock)
{
if (tutorialBlock == null || tutorialBlock.blockType != StoryBlockType.Tutorial ||
string.IsNullOrWhiteSpace(tutorialBlock.blockId) ||
string.IsNullOrWhiteSpace(tutorialBlock.tutorialProgressVariable))
{
Debug.LogWarning("[StoryProgress] TutorialBlock 缺少必要的 Block ID 或 Progress Variable。");
return false;
}
StoryTreeController treeController = StoryManager.instance?.treeController;
if (treeController == null)
{
Debug.LogWarning("[StoryProgress] StoryTreeController 未就绪,拒绝写入教程进度。");
return false;
}
// Tutorial 不经过 Yarn 对话上下文,显式由自己的 Block ID 触发同一套快照逻辑。
treeController.EnsureRollbackSnapshotsForBlock(tutorialBlock.blockId);
if (!SetBoolAndRefresh(tutorialBlock.tutorialProgressVariable, true))
return false;
treeController.OnBlockCompleted(tutorialBlock.blockId);
return true;
}
///
/// 统一完成“检查上下文 → 捕获回滚点 → 写变量 → 保存章节 → 刷新树”的事务性流程。
///
private static bool TryWrite(string variableName, System.Action write)
{
if (string.IsNullOrWhiteSpace(variableName))
{
Debug.LogWarning("[StoryProgress] 剧情变量名为空,拒绝写入。");
return false;
}
if (GameSaveManager.instance?.StorySaveModule == null || StoryManager.instance?.treeController == null)
{
Debug.LogWarning("[StoryProgress] 剧情章节或 StorySaveModule 未就绪,拒绝写入变量。");
return false;
}
// 快照必须在写入前完成,才能保证 Marker 回滚真正恢复到该列之前。
TryCaptureActiveBlockSnapshot();
write();
GameSaveManager.instance.StorySaveModule.SaveActiveChapter();
StoryManager.instance.treeController.RefreshAllStates();
return true;
}
}
}