384 lines
13 KiB
C#
384 lines
13 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using AibisDream.FixSystem;
|
||
using Newtonsoft.Json;
|
||
|
||
namespace AibisDream.SaveSystem
|
||
{
|
||
/// <summary>
|
||
/// 当前快照 schema 的版本号。结构变更时递增。
|
||
/// </summary>
|
||
public static class SaveSnapshotSchema
|
||
{
|
||
public const int CurrentVersion = 2;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 存档快照根对象:描述「进入可存节点那一刻」的完整游戏状态。
|
||
/// <para>
|
||
/// 与槽位/文件路径解耦;序列化后写入 JSON。对应需求§3 四分类:
|
||
/// anchor(恢复锚点)、sections(表现类 + 维修子模块)、yarnVariables。
|
||
/// </para>
|
||
/// <para>
|
||
/// 宏观阶段(场景 / 章节 SO / YarnProject)不单独冗余存储,统一以 sections["scene"] / sections["macro"]
|
||
/// 为唯一来源;槽位 UI 等需要摘要时通过 <see cref="SaveSnapshotSummary"/> 现算派生,避免重复字段漂移。
|
||
/// </para>
|
||
/// </summary>
|
||
[Serializable]
|
||
public class SaveSnapshot
|
||
{
|
||
/// <summary>快照结构版本,对应 <see cref="SaveSnapshotSchema.CurrentVersion"/>。</summary>
|
||
public int schemaVersion = SaveSnapshotSchema.CurrentVersion;
|
||
|
||
/// <summary>保存时的 <c>Application.version</c>。</summary>
|
||
public string gameVersion;
|
||
|
||
/// <summary>保存时间(本地),格式 <c>yyyy-MM-dd HH:mm:ss</c>。</summary>
|
||
public string savedAt;
|
||
|
||
/// <summary>当前场景(恢复的第一步)。</summary>
|
||
public SceneSnapshotDto scene = new();
|
||
|
||
/// <summary>恢复锚点:章节、YarnProject、来源节点与显式恢复动作。</summary>
|
||
public AnchorSnapshot anchor = new();
|
||
|
||
/// <summary>全部 Yarn 运行时变量(来自 <see cref="YarnVariableStorage"/>)。</summary>
|
||
public YarnVariablesSnapshot yarnVariables = new();
|
||
|
||
/// <summary>
|
||
/// 各子系统快照,key 为 <see cref="SnapshotProviderIds"/> 中的稳定 id。
|
||
/// 表现层与维修模块(含 BlockPuzzle / Memory 等)均在此注册,按 RestoreOrder 还原。
|
||
/// </summary>
|
||
public Dictionary<string, object> sections = new();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 恢复锚点:节点名始终记录存档来源;是否重新进入节点由
|
||
/// <see cref="startDialogueOnRestore"/> 独立决定。
|
||
/// </summary>
|
||
[Serializable]
|
||
public class AnchorSnapshot
|
||
{
|
||
/// <summary>当前章节 TalkSceneSO 名称,由读档预检解析为目标章节。</summary>
|
||
public string sceneSoName;
|
||
public string yarnProjectId;
|
||
public string nodeName;
|
||
|
||
[JsonProperty(Required = Required.Always)]
|
||
public bool startDialogueOnRestore;
|
||
}
|
||
|
||
/// <summary>Yarn 变量三分组,与 Yarn Spinner 支持的类型一致。</summary>
|
||
[Serializable]
|
||
public class YarnVariablesSnapshot
|
||
{
|
||
public Dictionary<string, float> floats = new();
|
||
public Dictionary<string, string> strings = new();
|
||
public Dictionary<string, bool> bools = new();
|
||
}
|
||
|
||
#region Provider DTOs
|
||
|
||
/// <summary>sections["scene"]:当前加载的场景。</summary>
|
||
[Serializable]
|
||
public class SceneSnapshotDto
|
||
{
|
||
public string sceneName;
|
||
}
|
||
|
||
/// <summary>sections["env"]:环境时间/天气。</summary>
|
||
[Serializable]
|
||
public class EnvironmentSnapshotDto
|
||
{
|
||
/// <summary><see cref="TimesOfDay"/> 枚举名。</summary>
|
||
public string curTime;
|
||
|
||
/// <summary><see cref="Weather"/> 枚举名。</summary>
|
||
public string curWeather;
|
||
|
||
public bool isInitialized;
|
||
}
|
||
|
||
/// <summary>sections["actor"]:场景中所有角色的逐项状态。</summary>
|
||
[Serializable]
|
||
public class ActorSnapshotDto
|
||
{
|
||
public List<ActorEntrySnapshotDto> actors = new();
|
||
}
|
||
|
||
/// <summary>单个角色的快照条目。</summary>
|
||
[Serializable]
|
||
public class ActorEntrySnapshotDto
|
||
{
|
||
public string actorName;
|
||
public string slotName;
|
||
public string actorType;
|
||
public string stateName;
|
||
/// <summary>Anima:Animator layer 0 当前状态的 normalizedTime。</summary>
|
||
public float stateNormalizedTime;
|
||
public float alpha;
|
||
/// <summary>AnimaEx:最后一次 set_actor_face 的表情名。</summary>
|
||
public string faceName;
|
||
/// <summary>AnimaEx:Animator "Is Talking" 参数。</summary>
|
||
public bool isTalking;
|
||
}
|
||
|
||
/// <summary>sections["audio"]:FMOD 音乐/SFX 终态与 AMB 状态(不记播放进度)。</summary>
|
||
[Serializable]
|
||
public class AudioSnapshotDto
|
||
{
|
||
/// <summary>key → FMOD event path。</summary>
|
||
public Dictionary<string, string> musicPaths = new();
|
||
|
||
public Dictionary<string, string> sfxPaths = new();
|
||
|
||
/// <summary><see cref="AmbState"/> 枚举名。</summary>
|
||
public string ambState;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Timeline 终/初态标记(决策 D1.1:不按时间轴逐帧还原)。
|
||
/// 序列化进 JSON 时使用字符串形式存入 <see cref="TimelineEntrySnapshotDto.phase"/>。
|
||
/// </summary>
|
||
public enum TimelinePlaybackPhase
|
||
{
|
||
AtStart,
|
||
AtEnd,
|
||
/// <summary>从未 Evaluate 过 Timeline 绑定(untouched);非播放中途 Stop。</summary>
|
||
Stopped
|
||
}
|
||
|
||
/// <summary>sections["timeline"]:各 PlayableDirector 的终/初态。</summary>
|
||
[Serializable]
|
||
public class TimelineSnapshotDto
|
||
{
|
||
public List<TimelineEntrySnapshotDto> entries = new();
|
||
}
|
||
|
||
[Serializable]
|
||
public class TimelineEntrySnapshotDto
|
||
{
|
||
public string directorName;
|
||
public bool isActive;
|
||
|
||
/// <summary><see cref="TimelinePlaybackPhase"/> 的字符串名。</summary>
|
||
public string phase;
|
||
|
||
/// <summary>若由 Addressables 加载 Timeline,记录 key;否则为空。</summary>
|
||
public string loadedAddressableKey;
|
||
|
||
/// <summary>是否曾通过 Play/Reset/RestoreAtEnd 等路径 Evaluate 过 Timeline。</summary>
|
||
public bool hasEverAppliedPlayback;
|
||
}
|
||
|
||
/// <summary>sections["screen"]:屏幕饱和度与演出淡入淡出遮罩。</summary>
|
||
[Serializable]
|
||
public class ScreenSnapshotDto
|
||
{
|
||
/// <summary>Post-process Volume weight(0=正常,1=完全褪色)。</summary>
|
||
public float saturationWeight;
|
||
|
||
/// <summary>
|
||
/// <see cref="ScreenTransitionPanel"/> 全局遮罩终态:<c>true</c> = 画面被完全盖住;
|
||
/// <c>false</c> = 画面可见(<c>FadeOut</c> 完成或遮罩未启用)。不记录转场中间 alpha。
|
||
/// </summary>
|
||
public bool isFadeScreenCovered;
|
||
}
|
||
|
||
/// <summary>sections["playTool"]:常驻演出 UI 中的物品图与全屏图终态。</summary>
|
||
[Serializable]
|
||
public class PlayToolSnapshotDto
|
||
{
|
||
public bool isObjVisible;
|
||
public string objPicName;
|
||
public bool isFullScreenVisible;
|
||
public string fullScreenPicName;
|
||
}
|
||
|
||
public enum ShowcaseDisplayMode
|
||
{
|
||
None,
|
||
Small,
|
||
Large
|
||
}
|
||
|
||
/// <summary>sections["showcase"]:梦境通用静态图片与背景终态。</summary>
|
||
[Serializable]
|
||
public class ShowcaseSnapshotDto
|
||
{
|
||
public string displayMode;
|
||
public string picName;
|
||
public bool isBackgroundVisible;
|
||
}
|
||
|
||
public enum Day2SleepPresentationMode
|
||
{
|
||
None,
|
||
SmallMotion,
|
||
Kite,
|
||
LargeEffects
|
||
}
|
||
|
||
/// <summary>sections["day2SleepPresentation"]:D2 Sleep 动态表现语义终态。</summary>
|
||
[Serializable]
|
||
public class Day2SleepPresentationSnapshotDto
|
||
{
|
||
public string mode;
|
||
|
||
public string picName;
|
||
public string motionProfile;
|
||
|
||
public float kiteHeight;
|
||
public int kiteWind;
|
||
public float kiteTurbulence;
|
||
|
||
public bool largeAnimationActive;
|
||
public string largeAnimationPrefix;
|
||
public int largeAnimationFrameCount;
|
||
public float largeAnimationSecondsPerFrame;
|
||
public float largeBlurAmount;
|
||
public float largeBlurSize;
|
||
public float largeScaleMultiplier = 1f;
|
||
public float largeAlpha = 1f;
|
||
}
|
||
|
||
/// <summary>sections["subway"]:地铁场景宏观状态(SubwayCenter / OutSideState)。</summary>
|
||
[Serializable]
|
||
public class SubwaySnapshotDto
|
||
{
|
||
/// <summary><see cref="OutSideState"/> 枚举名。</summary>
|
||
public string state;
|
||
|
||
/// <summary>状态参数(预留,当前各 State 未使用)。</summary>
|
||
public string args;
|
||
|
||
/// <summary>当前新闻图片 Addressable id(<c>Sprite/News[{id}]</c> 中的 id)。</summary>
|
||
public string newsSpriteId;
|
||
}
|
||
|
||
/// <summary>sections["fix"]:Fix 场景宏观 Cue(FixSceneDirector),不含面板壳层。</summary>
|
||
[Serializable]
|
||
public class FixSnapshotDto
|
||
{
|
||
/// <summary><see cref="FixSceneMode"/> 枚举名。</summary>
|
||
public string state;
|
||
|
||
/// <summary>状态参数(如 BodyModule 的 ModuleState 字符串)。</summary>
|
||
public string args;
|
||
}
|
||
|
||
/// <summary>TaskPanel 单条任务条目快照。</summary>
|
||
[Serializable]
|
||
public class TaskEntrySnapshotDto
|
||
{
|
||
/// <summary>Yarn 行 id,如 <c>line:01dade5</c>。</summary>
|
||
public string lineId;
|
||
|
||
/// <summary>存档时已完成 substitutions 的本地化文本,不含 UI 圆点前缀。</summary>
|
||
public string text;
|
||
}
|
||
|
||
/// <summary>sections["fixPanel"]:FixPanel 壳层 + 线缆/插头 + TaskPanel 状态。</summary>
|
||
[Serializable]
|
||
public class FixPanelSnapshotDto
|
||
{
|
||
public bool isSystemOn;
|
||
|
||
/// <summary>当前目标灯态;旧存档缺失时按 Normal 处理。</summary>
|
||
public string lightState;
|
||
|
||
/// <summary>RepairSystemManager 当前激活系统类型名;空则默认 BodyModuleSystem。</summary>
|
||
public string currentRepairSystemType;
|
||
|
||
public bool isCableRetracted;
|
||
|
||
/// <summary>插头插入的 BodyModule 名(data.moduleName);null/空 = 未插入。</summary>
|
||
public string pluggedModuleName;
|
||
|
||
/// <summary>剧情是否要求 TaskPanel 展开;Memory 等临时隐藏状态不写入存档。</summary>
|
||
public bool isTaskPanelRequestedVisible;
|
||
|
||
/// <summary>TaskPanel 当前未完成任务列表(保持插入顺序)。</summary>
|
||
public List<TaskEntrySnapshotDto> tasks = new();
|
||
}
|
||
|
||
/// <summary>sections["bodyModule"]:插线模块持久物理状态。</summary>
|
||
[Serializable]
|
||
public class BodyModuleSnapshotDto
|
||
{
|
||
/// <summary><see cref="ModuleState"/> 枚举名(Body / Head)。</summary>
|
||
public string moduleState;
|
||
public bool isUFCoverRemoved;
|
||
public bool isUFRemoved;
|
||
public bool isColorRemoved;
|
||
public bool isChipRemoved;
|
||
|
||
/// <summary>高亮中的模块名(<c>data.moduleName</c>)。</summary>
|
||
public List<string> highlightedModules = new();
|
||
|
||
/// <summary>报警中的模块名(<c>data.moduleName</c>)。</summary>
|
||
public List<string> alarmedModules = new();
|
||
|
||
/// <summary>模块名 → <see cref="ModuleCondition"/> 枚举名;旧存档缺失时按 Normal 处理。</summary>
|
||
public Dictionary<string, string> moduleConditions = new();
|
||
}
|
||
|
||
/// <summary>sections["eye"]:Eye 维修叙事阶段。</summary>
|
||
[Serializable]
|
||
public class EyeSnapshotDto
|
||
{
|
||
/// <summary>Eye 颜色阶段枚举名。</summary>
|
||
public string eyeColorState;
|
||
}
|
||
|
||
/// <summary>sections["punchTape"]:打孔带收藏列表。</summary>
|
||
[Serializable]
|
||
public class PunchTapeSnapshotDto
|
||
{
|
||
public List<PunchTape> favorites = new();
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 从 <see cref="SaveSnapshot"/> 根对象直接读取的宏观摘要(场景 / 章节 SO / YarnProject)。
|
||
/// <para>
|
||
/// scene 与 anchor 已提升到根对象,无需再从 sections 字典派生。
|
||
/// 槽位 / 「继续游戏」等 UI 在需要展示时调用 <see cref="SaveSnapshotSummary.From"/> 即可。
|
||
/// </para>
|
||
/// </summary>
|
||
public readonly struct SaveSnapshotSummary
|
||
{
|
||
/// <summary>Addressable 场景 key(来自 <see cref="SaveSnapshot.scene"/>)。</summary>
|
||
public readonly string SceneName;
|
||
|
||
/// <summary>当前 <c>TalkSceneSO</c> 的 asset 名称(来自 <see cref="AnchorSnapshot.sceneSoName"/>)。</summary>
|
||
public readonly string SceneSoName;
|
||
|
||
/// <summary>当前 <c>YarnProject</c> 的 name(来自 <see cref="AnchorSnapshot.yarnProjectId"/>)。</summary>
|
||
public readonly string YarnProjectId;
|
||
|
||
public SaveSnapshotSummary(string sceneName, string sceneSoName, string yarnProjectId)
|
||
{
|
||
SceneName = sceneName;
|
||
SceneSoName = sceneSoName;
|
||
YarnProjectId = yarnProjectId;
|
||
}
|
||
|
||
/// <summary>从快照根对象直接读取宏观摘要。</summary>
|
||
public static SaveSnapshotSummary From(SaveSnapshot snapshot)
|
||
{
|
||
if (snapshot == null)
|
||
{
|
||
return new SaveSnapshotSummary(null, null, null);
|
||
}
|
||
|
||
var sceneName = snapshot.scene?.sceneName;
|
||
var sceneSoName = snapshot.anchor?.sceneSoName;
|
||
var yarnProjectId = snapshot.anchor?.yarnProjectId;
|
||
|
||
return new SaveSnapshotSummary(sceneName, sceneSoName, yarnProjectId);
|
||
}
|
||
}
|
||
}
|