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