Files
aibis-dream/Assets/Scripts/SaveSystem/SavePointEvaluator.cs
T

197 lines
6.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using UnityEngine;
namespace AibisDream.SaveSystem
{
/// <summary>
/// 可存点被拒绝的原因,供日志与 UI 提示使用。
/// </summary>
public enum SavePointRejectReason
{
None,
AutoSaveSuppressed,
Restoring,
GamePaused,
// 遗留值:早期逻辑曾返回,当前实现中不再使用,保留以避免外部代码编译失败。
NoActiveNode,
StartNode,
FunctionNode,
NoAutoSave,
/// <summary>节点 tag 在默认禁止保存集合中。</summary>
DeniedByTag,
/// <summary>节点显式附加了 no_save 标记。</summary>
MarkedNoSave,
}
/// <summary>
/// 可存点判定器。
/// <para>
/// 基于 Yarn 节点 tag 判定是否允许自动存档:
/// - 白名单 taghub / linear / content)默认允许;
/// - 黑名单 tagstart / init / function / detour / center / performance / event / end)默认禁止;
/// - no_save 附加标记覆盖默认语义,一律禁止;
/// - 无活跃节点、或节点未声明 tag,默认允许并打 warning
/// - 未知 tag 默认允许并打 warning。
/// </para>
/// </summary>
public static class SavePointEvaluator
{
/// <summary>默认可作为自动存档边界的节点 tag。</summary>
private static readonly HashSet<string> AutoSaveAllowedTags = new(StringComparer.OrdinalIgnoreCase)
{
"hub", // 梦境:场景导航中枢
"linear", // 梦境:线性剧情推进
"content", // 维修:阶段内容节点
};
/// <summary>默认禁止作为自动存档边界的节点 tag。</summary>
private static readonly HashSet<string> AutoSaveDeniedTags = new(StringComparer.OrdinalIgnoreCase)
{
"start", // 引擎入口
"init", // 变量声明/初始化
"function", // 纯指令函数
"detour", // 梦境内容片段
"center", // 维修阶段调度中枢
"performance", // 维修原子化演出
"event", // 维修 C# 事件响应入口
"end", // 维修结束/收尾
};
/// <summary>
/// 当前是否可以自动存档。
/// </summary>
public static bool CanAutoSave(out SavePointRejectReason reason)
{
if (SaveRestoreOrchestrator.IsAutoSaveSuppressed)
{
reason = SavePointRejectReason.AutoSaveSuppressed;
return false;
}
if (SaveRestoreOrchestrator.IsRestoring)
{
reason = SavePointRejectReason.Restoring;
return false;
}
if (GameManager.Instance != null && GameManager.Instance.state.isInPause)
{
reason = SavePointRejectReason.GamePaused;
return false;
}
var (nodeName, tags) = DialogController.Instance != null
? DialogController.Instance.GetCurrentNodeContext()
: (null, null);
// no_save 标记覆盖一切:即使节点类型默认可存,也不允许保存。
if (tags != null && IsTagPresent(tags, "no_save"))
{
reason = SavePointRejectReason.MarkedNoSave;
return false;
}
// 无活跃节点,或节点未声明任何 tag:允许保存。
// 注:FixSystemNew 等交互状态可能没有活跃 Yarn 节点,仍需保存当前状态。
if (string.IsNullOrEmpty(nodeName) || tags == null || tags.Count == 0)
{
if (!string.IsNullOrEmpty(nodeName) && (tags == null || tags.Count == 0))
{
Debug.LogWarning(
$"[SavePointEvaluator] 节点 {nodeName} 未声明任何 tag,默认允许自动存档。请补全节点类型标签。");
}
reason = SavePointRejectReason.None;
return true;
}
// 按主 tag 判定。
var primaryTag = FindPrimaryTag(tags);
// 未识别到已知主 tag:放行但警告,方便脚本作者补标。
if (primaryTag == null)
{
Debug.LogWarning(
$"[SavePointEvaluator] 节点 {nodeName} 的 tags [{string.Join(", ", tags)}] 未声明保存语义,默认允许自动存档。");
reason = SavePointRejectReason.None;
return true;
}
if (AutoSaveDeniedTags.Contains(primaryTag))
{
reason = SavePointRejectReason.DeniedByTag;
return false;
}
if (AutoSaveAllowedTags.Contains(primaryTag))
{
reason = SavePointRejectReason.None;
return true;
}
// 理论上不会到达:已识别主 tag 但既不在允许集合也不在禁止集合。
// 保持放行并警告,避免未来新增 tag 时意外阻断存档。
Debug.LogWarning(
$"[SavePointEvaluator] 节点 {nodeName} 的 tag '{primaryTag}' 未声明保存语义,默认允许自动存档。");
reason = SavePointRejectReason.None;
return true;
}
/// <summary>
/// 当前是否可以手动存档。
/// 手动档 = 复制最近自动档,因此需要当前可自动存档且自动档存在。
/// </summary>
public static bool CanManualSave(out SavePointRejectReason reason)
{
if (!CanAutoSave(out reason))
{
return false;
}
if (!SlotDirectory.Exists(SlotIndex.Auto))
{
reason = SavePointRejectReason.NoAutoSave;
return false;
}
reason = SavePointRejectReason.None;
return true;
}
private static bool IsTagPresent(IReadOnlyList<string> tags, string tag)
{
if (tags == null) return false;
foreach (var t in tags)
{
if (t == tag)
{
return true;
}
}
return false;
}
/// <summary>
/// 从 tags 中找到第一个已知的保存语义主 tag;若均未知则返回 null。
/// </summary>
private static string FindPrimaryTag(IReadOnlyList<string> tags)
{
foreach (var tag in tags)
{
if (AutoSaveAllowedTags.Contains(tag) || AutoSaveDeniedTags.Contains(tag))
{
return tag;
}
}
return null;
}
}
}