#if UNITY_EDITOR || DEVELOPMENT_BUILD using System.Collections; using System.IO; using AibisDream.SaveSystem; using UnityEngine; namespace AibisDream { public class PeipeiMemoryJumpTool : MonoBehaviour { private const float UiMargin = 16f; private const float DevButtonWidth = 96f; private const float DevButtonHeight = 34f; private const float MenuWidth = 300f; private const float MaxMenuHeight = 560f; private const float RowHeight = 30f; private const float HeaderHeight = 24f; private const float StatusWidth = 360f; private const float StatusHeight = 28f; private const string SaveRootFolder = "AllOurBrokenParts"; private const string TestSaveFolder = "testsavs"; private const string SnapshotFileName = "snapshot"; private static readonly DevSaveEntry[] Peipei1Saves = { new("PP1 01 Intro", "20260629_191732_683_"), new("PP1 02 Enter Room", "20260629_191737_198_"), new("PP1 03 Body UF", "20260629_191745_604_"), new("PP1 04 UF Deep", "20260629_191747_751_"), new("PP1 05 Body Vision", "20260629_191751_564_"), new("PP1 06 Memory Start", "20260629_191806_710_"), new("PP1 07 Memory Check", "20260629_191807_884_"), new("PP1 08 Analysis", "20260629_191947_383_"), new("PP1 09 Pre Talk", "20260629_191953_429_"), new("PP1 10 Surgery", "20260629_191957_937_"), new("PP1 11 CutLine", "20260629_192000_426_"), new("PP1 12 Recheck", "20260629_192000_744_"), new("PP1 13 Recheck Vision", "20260629_192002_368_"), new("PP1 14 Ending Talk", "20260629_192010_965_") }; private static readonly DevSaveEntry[] Peipei2Saves = { new("PP2 01 Start", "20260630_142906_700_"), new("PP2 02 Intro", "20260630_142906_849_"), new("PP2 03 First Heat", "20260630_142915_334_"), new("PP2 04 Heat System", "20260630_142918_072_"), new("PP2 05 Body Check", "20260630_142921_707_"), new("PP2 06 UF Locked", "20260630_142923_878_"), new("PP2 07 UF Shell", "20260630_142928_045_"), new("PP2 08 UF Check", "20260630_142931_587_"), new("PP2 09 Eye View", "20260630_142938_533_"), new("PP2 10 Analysis", "20260630_142956_453_"), new("PP2 11 Pre Talk", "20260630_143007_149_"), new("PP2 12 Stop Image", "20260630_143014_716_"), new("PP2 13 Stop Check", "20260630_143020_779_"), new("PP2 14 Cool Fail Talk", "20260630_143042_615_"), new("PP2 15 Remove UF", "20260630_143043_024_"), new("PP2 16 Pre Talk 2", "20260630_143044_410_"), new("PP2 17 Surgery", "20260630_143058_329_"), new("PP2 18 Memory Plugin", "20260630_143100_380_"), new("PP2 19 Ending Talk", "20260630_143118_278_") }; private static PeipeiMemoryJumpTool instance; private bool isJumping; private bool menuOpen; private string status = ""; private float statusVisibleUntil; private Vector2 menuScroll; private GUIStyle buttonStyle; private GUIStyle headerStyle; private GUIStyle menuBoxStyle; private GUIStyle statusStyle; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] private static void Bootstrap() { if (instance != null) return; var existing = FindObjectOfType(); if (existing != null) { instance = existing; DontDestroyOnLoad(existing.gameObject); return; } var toolObject = new GameObject("[Dev] Peipei Jump Tool"); DontDestroyOnLoad(toolObject); instance = toolObject.AddComponent(); } private void OnGUI() { EnsureStyles(); float buttonX = Screen.width - UiMargin - DevButtonWidth; float buttonY = Screen.height - UiMargin - DevButtonHeight; var devButtonRect = new Rect(buttonX, buttonY, DevButtonWidth, DevButtonHeight); if (menuOpen) DrawCommandMenu(devButtonRect); string label = isJumping ? "Busy..." : "Dev"; if (GUI.Button(devButtonRect, label, buttonStyle)) menuOpen = !menuOpen; if (!string.IsNullOrEmpty(status) && Time.unscaledTime < statusVisibleUntil) { float menuHeight = GetMenuHeight(); float statusY = menuOpen ? buttonY - menuHeight - StatusHeight - UiMargin : buttonY - StatusHeight - 8f; var statusRect = new Rect( Screen.width - UiMargin - StatusWidth, Mathf.Max(UiMargin, statusY), StatusWidth, StatusHeight); GUI.Label(statusRect, status, statusStyle); } } private void DrawCommandMenu(Rect devButtonRect) { float menuHeight = GetMenuHeight(); float menuX = Screen.width - UiMargin - MenuWidth; float menuY = Mathf.Max(UiMargin, devButtonRect.y - menuHeight - 8f); var menuRect = new Rect(menuX, menuY, MenuWidth, menuHeight); GUI.Box(menuRect, GUIContent.none, menuBoxStyle); var viewRect = new Rect(0f, 0f, MenuWidth - 28f, GetContentHeight()); var scrollRect = new Rect( menuRect.x + 6f, menuRect.y + 6f, menuRect.width - 12f, menuRect.height - 12f); menuScroll = GUI.BeginScrollView(scrollRect, menuScroll, viewRect); float y = 0f; DrawHeader(ref y, "Peipei 1"); DrawSaveEntries(ref y, Peipei1Saves); y += 6f; DrawHeader(ref y, "Peipei 2"); DrawSaveEntries(ref y, Peipei2Saves); GUI.EndScrollView(); } private void DrawHeader(ref float y, string label) { GUI.Label(new Rect(0f, y, MenuWidth - 32f, HeaderHeight), label, headerStyle); y += HeaderHeight; } private void DrawSaveEntries(ref float y, DevSaveEntry[] entries) { foreach (var entry in entries) { var rect = new Rect(0f, y, MenuWidth - 32f, RowHeight); DrawCommandButton(rect, entry.Label, !isJumping, () => StartCoroutine(LoadDevSave(entry))); y += RowHeight; } } private static float GetMenuHeight() { return Mathf.Min(MaxMenuHeight, GetContentHeight() + 12f); } private static float GetContentHeight() { return HeaderHeight * 2f + RowHeight * (Peipei1Saves.Length + Peipei2Saves.Length) + 6f; } private void DrawCommandButton(Rect rect, string label, bool enabled, System.Action action) { GUI.enabled = enabled; if (GUI.Button(rect, label, buttonStyle)) { menuOpen = false; action?.Invoke(); } GUI.enabled = true; } private IEnumerator LoadDevSave(DevSaveEntry entry) { if (isJumping) yield break; string savePath = ResolveSnapshotPath(entry); if (string.IsNullOrEmpty(savePath)) { SetStatus($"Save missing: {entry.Label}", 5f); yield break; } isJumping = true; if (NeedsResetToMainMenu()) { SetStatus($"Resetting: {entry.Label}"); GameManager.Instance.QuitGame(); yield return WaitForMainMenuReady(); } SetStatus($"Loading: {entry.Label}"); yield return SaveRestoreOrchestrator.RestoreFromFile(savePath); SetStatus($"Loaded: {entry.Label}", 3f); isJumping = false; } private static bool NeedsResetToMainMenu() { var gameManager = GameManager.Instance; if (gameManager == null) { return false; } if (gameManager.state.isInGame) { return true; } var sceneLoader = SceneLoader.Instance; return sceneLoader != null && !string.IsNullOrEmpty(sceneLoader.CurrentSceneName); } private static IEnumerator WaitForMainMenuReady() { const float timeoutSeconds = 15f; float elapsed = 0f; while (elapsed < timeoutSeconds) { var gameManager = GameManager.Instance; var sceneLoader = SceneLoader.Instance; bool sceneCleared = sceneLoader == null || string.IsNullOrEmpty(sceneLoader.CurrentSceneName); bool notInGame = gameManager == null || !gameManager.state.isInGame; if (sceneCleared && notInGame) { yield break; } elapsed += Time.unscaledDeltaTime; yield return null; } Debug.LogWarning("[PeipeiMemoryJumpTool] 等待主界面超时,继续读档。"); } private static string ResolveSnapshotPath(DevSaveEntry entry) { string archiveRoot = Path.Combine(Application.persistentDataPath, SaveRootFolder, TestSaveFolder); if (!Directory.Exists(archiveRoot)) return null; var matches = Directory.GetDirectories(archiveRoot, entry.DirectoryPrefix + "*"); if (matches.Length == 0) return null; string path = Path.Combine(matches[0], SnapshotFileName); return File.Exists(path + ".json") ? path : null; } private void SetStatus(string message, float visibleSeconds = 30f) { status = message; statusVisibleUntil = Time.unscaledTime + visibleSeconds; Debug.Log($"[PeipeiMemoryJumpTool] {message}"); } private void EnsureStyles() { if (buttonStyle != null && statusStyle != null) return; buttonStyle = new GUIStyle(GUI.skin.button) { fontSize = 14, alignment = TextAnchor.MiddleCenter }; headerStyle = new GUIStyle(GUI.skin.label) { fontSize = 13, fontStyle = FontStyle.Bold, alignment = TextAnchor.MiddleLeft, padding = new RectOffset(6, 0, 3, 0) }; menuBoxStyle = new GUIStyle(GUI.skin.box) { padding = new RectOffset(6, 6, 6, 6) }; statusStyle = new GUIStyle(GUI.skin.box) { fontSize = 13, alignment = TextAnchor.MiddleLeft, padding = new RectOffset(8, 8, 4, 4) }; } private readonly struct DevSaveEntry { public readonly string Label; public readonly string DirectoryPrefix; public DevSaveEntry(string label, string directoryPrefix) { Label = label; DirectoryPrefix = directoryPrefix; } } } } #endif