剧情+对话完善

This commit is contained in:
SoulliesOfficial
2026-07-21 15:24:42 -04:00
parent 8f230831e9
commit 810d019619
161 changed files with 7271 additions and 1893 deletions

View File

@@ -12,8 +12,8 @@ namespace Ichni
/// 全局存档入口。
/// <para><see cref="SongSaveModule"/> 负责可重复游玩的歌曲成绩与谱面记录;</para>
/// <para><see cref="UnlockSaveModule"/> 负责章节、歌曲及未来内容共用的 Offline 解锁 Key</para>
/// <para><see cref="StorySaveModule"/> 负责章节故事树、对话选项 Yarn 变量。</para>
/// 套数据使用不同文件和不同生命周期,修改任一时不要把另一侧的字段或迁移规则混入其中。
/// <para><see cref="StorySaveModule"/> 负责章节故事树、对话选项、章节 Yarn 变量和 Timeline 回滚快照。</para>
/// 套数据使用不同文件和不同生命周期,修改任一数据域时不要把其它数据域的字段或迁移规则混入其中。
/// </summary>
public class GameSaveManager : SerializedMonoBehaviour
{
@@ -46,9 +46,10 @@ namespace Ichni
UnlockSaveModule = new UnlockSaveModule();
UnlockSaveModule.LoadUnlockKeys();
// 剧情变量属于独立存档,不参与歌曲成绩的 Schema 或 Chart Revision 迁移。
// 剧情变量属于章节存档,不参与歌曲成绩的 Schema 或 Chart Revision 迁移。
// 章节真正被打开时StoryTreeController 才加载对应 ChapterStorySave
// 不在启动阶段加载已经废弃的全局 StoryVariables 文件。
StorySaveModule = new StorySaveModule();
StorySaveModule.LoadVariables();
}
}
@@ -87,6 +88,11 @@ namespace Ichni
songStatus.additionalInfo ??= string.Empty;
songStatus.beatmapSavesByDifficultyId ??= new Dictionary<int, BeatmapSave>();
// 旧存档没有 isTried 字段。任何已拥有有效结算的歌曲都必然曾被尝试过,
// 因而可在不丢失既有 Story SongBlock 进度的前提下安全补齐该字段。
if (songStatus.isCompleted)
songStatus.isTried = true;
foreach (DifficultyData difficulty in song.difficultyDataList)
{
if (difficulty == null || difficulty.saveDifficultyId < 0)
@@ -204,6 +210,7 @@ namespace Ichni
}
songStatus.additionalInfo ??= string.Empty;
songStatus.isTried = true;
BeatmapSave beatmapSave = songStatus.GetOrCreateBeatmapSave(saveDifficultyId);
bool recordChanged = beatmapSave.ApplyPlayResult(recorder, currentChartRevision);
@@ -214,6 +221,38 @@ namespace Ichni
return recordChanged || completionChanged;
}
/// <summary>
/// 标记玩家已确认进入指定歌曲。
/// <para>这是 Story SongBlock 的唯一完成依据:选择歌曲并启动 GameScene 即写入,即使玩家随后中途退出,
/// 也能在下次打开剧情树时恢复对应 Block 的完成状态。该状态不影响 Accuracy、FC、AP、Max Combo 或 isCompleted。</para>
/// </summary>
/// <param name="songName">SongItemData.songName对应 SongSaves 的稳定歌曲 Key。</param>
/// <returns>本次是否首次写入 isTried。</returns>
public bool MarkSongTried(string songName)
{
if (string.IsNullOrWhiteSpace(songName))
{
Debug.LogWarning("Cannot mark a song as tried without a song name.");
return false;
}
songStatusSaves ??= new Dictionary<string, SongStatusSave>();
if (!songStatusSaves.TryGetValue(songName, out SongStatusSave songStatus) || songStatus == null)
{
// 正常流程会在启动时为当前内容创建空记录;此分支仅作为运行时防线,
// 防止新增歌曲尚未完成内容对账时丢失一次已确认的游玩入口。
songStatus = new SongStatusSave(false, string.Empty, new Dictionary<int, BeatmapSave>());
songStatusSaves[songName] = songStatus;
}
if (songStatus.isTried)
return false;
songStatus.isTried = true;
SaveSongStatuses();
return true;
}
/// <summary>
/// 读取歌曲记录并执行内容对账和 Chart Revision 检查。
/// 当前为正式发布前的重置策略:任何非 v1 的旧测试存档都会直接被新 v1 结构覆盖,
@@ -290,5 +329,5 @@ namespace Ichni
}
}
// StorySaveModule 已迁移至 NewStorySystem/Save/StorySaveModule.cs按章节存树/选项,全局存变量)
// StorySaveModule 已迁移至 NewStorySystem/Save/StorySaveModule.cs按章节保存 Block、选项、变量和回滚快照
}

View File

@@ -1,18 +1,22 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Collections.Generic;
namespace Ichni.RhythmGame
{
/// <summary>
/// 单首歌曲的持久化容器。
/// <see cref="isCompleted"/> 只表示玩家曾完成过该歌曲具体难度的成绩、Revision 与 Max Combo
/// <see cref="isTried"/> 表示玩家曾确认进入过该歌曲;<see cref="isCompleted"/> 只表示玩家曾完成过一次有效结算。
/// Story SongBlock 使用 isTried而具体难度的成绩、Revision 与 Max Combo
/// 由 <see cref="beatmapSavesByDifficultyId"/> 中对应稳定难度 ID 的 <see cref="BeatmapSave"/> 管理。
/// </summary>
public class SongStatusSave
{
/// <summary>
/// 玩家是否曾确认进入过本曲的 GameScene。
/// 该字段不代表结算成功,也不要求存在判定;它让 Story SongBlock 能在切场景、退出游戏或重新打开章节后,
/// 通过持久化的歌曲记录稳定恢复“已尝试”状态。
/// </summary>
public bool isTried;
/// <summary>至少有一次有效结算完成;谱面 Revision 变化不会回退该流程状态。</summary>
public bool isCompleted;
@@ -28,11 +32,12 @@ namespace Ichni.RhythmGame
public SongStatusSave()
{
}
public SongStatusSave(bool isCompleted, string additionalInfo, Dictionary<int, BeatmapSave> beatmapSavesByDifficultyId)
{
// 已拥有有效结算的歌曲必然也已尝试,避免两个状态出现矛盾。
isTried = isCompleted;
this.isCompleted = isCompleted;
this.additionalInfo = additionalInfo;
this.beatmapSavesByDifficultyId = beatmapSavesByDifficultyId;