125 lines
5.7 KiB
C#
125 lines
5.7 KiB
C#
using UnityEngine;
|
||
|
||
namespace Ichni.Story
|
||
{
|
||
/// <summary>
|
||
/// 修改章节剧情进度的唯一业务入口。
|
||
/// <para>StoryVariables 只管理内存字典;本类负责在写入前创建 Timeline 回滚快照、保存当前章节、
|
||
/// 并刷新剧情树状态。Yarn 指令、TutorialBlock 与未来小游戏都应通过这里写入。</para>
|
||
/// </summary>
|
||
public static class StoryProgress
|
||
{
|
||
// 对话开始时由 StoryDialogueController 设定。它用于判断本次变量/选项写入属于哪个 Block,
|
||
// 从而在 Marker 所在列首次被执行前截取正确快照。
|
||
private static string _activeMutationBlockId;
|
||
|
||
/// <summary>
|
||
/// 声明接下来的剧情变量或选项写入属于指定 Block。仅在对话运行期间有效。
|
||
/// </summary>
|
||
public static void BeginBlockMutation(string blockId)
|
||
{
|
||
_activeMutationBlockId = blockId;
|
||
TryCaptureActiveBlockSnapshot();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 结束指定 Block 的写入上下文,避免后续非剧情系统错误沿用旧的来源 Block。
|
||
/// </summary>
|
||
public static void EndBlockMutation(string blockId)
|
||
{
|
||
if (_activeMutationBlockId == blockId)
|
||
_activeMutationBlockId = null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 为当前写入来源捕获 Marker 快照。无章节、无来源 Block 或无对应 Marker 时安全返回 false。
|
||
/// </summary>
|
||
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));
|
||
|
||
/// <summary>
|
||
/// 处理 TutorialBlock 的“游玩”或“跳过”确认。两种选择都会立即写入进度变量并完成 Block;
|
||
/// 完成后的 TutorialBlock 仍可点击,是因为其 View 自身允许 Completed 状态交互。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 统一完成“检查上下文 → 捕获回滚点 → 写变量 → 保存章节 → 刷新树”的事务性流程。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
}
|