using UnityEngine; using System; using System.Collections.Generic; using System.Reflection; // Optional Dialogue System / Quest Machine snapshot: // #define USE_PIXELCRUSHERS #if USE_PIXELCRUSHERS using PixelCrushers; #endif public static class ES3SingleStore { // One file for everything: private const string FILE = "save.es3"; // Stable keys: private const string K_Version = "v"; private const string K_SavedAt = "t"; private const string K_Obj = "obj"; // ObjectiveManager data private const string K_LevelIdx = "lvl"; // currentLevelIndex private const string K_IsRunning = "run"; // currentLevel != null private const string K_DSJSON = "dsj"; // Dialogue System snapshot (optional) private const string K_ActStep = "actStep"; // NEW private static ES3Settings Settings => new ES3Settings { encryptionType = ES3.EncryptionType.AES, encryptionPassword = "change-me-strong-pass", // must match! prettyPrint = false }; // ----- DTOs for ObjectiveManager ----- [Serializable] public class ObjectiveData { public string text; public bool completed; } [Serializable] public class ObjectiveManagerData { public List list = new(); public int activeIndex = 0; public List queuedNextObjectives = null; public int queuedNextStartIndex = 0; } public static void Save() { // header/meta ES3.Save(K_Version, "1.0.0", FILE, Settings); ES3.Save(K_SavedAt, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), FILE, Settings); // ObjectiveManager if (ObjectiveManager.Instance != null) { var om = ObjectiveManager.Instance; var data = new ObjectiveManagerData { activeIndex = Mathf.Clamp(om.activeIndex, 0, Math.Max(0, om.objectives.Count - 1)), queuedNextObjectives = om._queuedNextObjectives != null ? new List(om._queuedNextObjectives) : null, queuedNextStartIndex = GetPrivate(om, "_queuedNextStartIndex") }; foreach (var o in om.objectives) data.list.Add(new ObjectiveData { text = o.text, completed = o.completed }); ES3.Save(K_Obj, data, FILE, Settings); } if (ActivatorManager.Instance != null) { PlayerPrefs.SetInt("StepIndex",ActivatorManager.Instance.stepIndex); } //if (ActivatorManager.Instance != null) //{ // ES3.Save(K_ActStep, ActivatorManager.Instance.stepIndex, FILE, Settings); //} //Debug.Log("[Save] Wrote save.es3"); // GameplayManager var gm = GameplayManager.Instance; if (gm != null) { var currentLevelIndex = GetPrivate(gm, "currentLevelIndex"); var isLevelRunning = GetPrivate(gm, "currentLevel") != null; ES3.Save(K_LevelIdx, currentLevelIndex, FILE, Settings); ES3.Save(K_IsRunning, isLevelRunning, FILE, Settings); } #if USE_PIXELCRUSHERS // Dialogue System/Quest Machine snapshot string dsJson = null; if (SaveSystem.instance != null) { var sg = SaveSystem.Serialize(); // SavedGameData dsJson = sg != null ? JsonUtility.ToJson(sg) : null; } ES3.Save(K_DSJSON, dsJson, FILE, Settings); #endif } public static bool LoadObjectives() { if (!ES3.FileExists(FILE)) return false; try { // ObjectiveManager first (UI ready) if (ObjectiveManager.Instance != null /* no KeyExists call here */) { // If the key doesn't exist, ES3 will throw; so catch and ignore. if (ES3.KeyExists(K_Obj, new ES3Settings(FILE, Settings.encryptionType, Settings.encryptionPassword))) { var data = ES3.Load(K_Obj, FILE, Settings); var om = ObjectiveManager.Instance; om.objectives.Clear(); if (data.list != null) foreach (var od in data.list) om.objectives.Add(new ObjectiveManager.Objective { text = od.text, completed = od.completed }); om.activeIndex = Mathf.Clamp(data.activeIndex, 0, Math.Max(0, om.objectives.Count - 1)); om._queuedNextObjectives = data.queuedNextObjectives != null ? new List(data.queuedNextObjectives) : null; SetPrivate(om, "_queuedNextStartIndex", data.queuedNextStartIndex); om.RefreshUI(); } } // GameplayManager var gm = GameplayManager.Instance; if (gm != null) { int idx = -1; bool run = false; if (ES3.KeyExists(K_LevelIdx, new ES3Settings(FILE, Settings.encryptionType, Settings.encryptionPassword))) idx = ES3.Load(K_LevelIdx, FILE, Settings); if (ES3.KeyExists(K_IsRunning, new ES3Settings(FILE, Settings.encryptionType, Settings.encryptionPassword))) run = ES3.Load(K_IsRunning, FILE, Settings); idx = Mathf.Clamp(idx, -1, gm.levels.Count - 1); if (run && idx >= 0) gm.StartLevel(idx); else { int next = idx < 0 ? 0 : Mathf.Min(idx + 1, gm.levels.Count - 1); InvokePrivate(gm, "EnableOnlyTrigger", new object[] { next }); //if (gm.levels != null && gm.levels.Count > 0 && ObjectiveManager.Instance != null) //{ // int upcoming = Mathf.Clamp(next, 0, gm.levels.Count - 1); // var upcomingLevel = gm.levels[upcoming]; // if (upcomingLevel != null && upcomingLevel.config != null) // ObjectiveManager.Instance.SetList(upcomingLevel.config.objectives, upcomingLevel.config.startObjectiveIndex); //} } } //if (ActivatorManager.Instance != null) //{ // try // { // if (ES3.KeyExists(K_ActStep, new ES3Settings(FILE, Settings.encryptionType, Settings.encryptionPassword))) // { // var savedStep = ES3.Load(K_ActStep, FILE, Settings); // // Set the index: // ActivatorManager.Instance.stepIndex = Mathf.Max(0, savedStep); // // Optional: rebuild world state by replaying events 0..stepIndex // // Comment out if you DON'T want to auto-replay. // ActivatorManager.Instance.ReplayTo(ActivatorManager.Instance.stepIndex); // } // } // catch { /* older saves may not have this key; ignore */ } //} #if USE_PIXELCRUSHERS if (ES3.KeyExists(K_DSJSON, new ES3Settings(FILE, Settings.encryptionType, Settings.encryptionPassword))) { var dsJson = ES3.Load(K_DSJSON, FILE, Settings); if (!string.IsNullOrEmpty(dsJson) && SaveSystem.instance != null) { var sg = JsonUtility.FromJson(dsJson); SaveSystem.ApplySavedGameData(sg); } } #endif Debug.Log("[Save] Loaded save.es3"); return true; } catch (System.Exception ex) { Debug.LogError($"[Save] Load failed: {ex.Message}\n" + "If you changed encryption or reused an old non-ES3 file, delete save.es3 and save again."); return false; } } public static bool Load() { if (ActivatorManager.Instance != null) { ActivatorManager.Instance.stepIndex = PlayerPrefs.GetInt("StepIndex", 0); Debug.Log("Step index: " + ActivatorManager.Instance.stepIndex); ActivatorManager.Instance.ReplayTo(ActivatorManager.Instance.stepIndex); return true; } return false; } public static void Delete() { if (ES3.FileExists(FILE)) ES3.DeleteFile(FILE); } // ---- reflection helpers (so you don’t edit your classes) ---- private static T GetPrivate(object obj, string field) { var f = obj.GetType().GetField(field, BindingFlags.Instance | BindingFlags.NonPublic); return f != null ? (T)f.GetValue(obj) : default; } private static void SetPrivate(object obj, string field, object value) { var f = obj.GetType().GetField(field, BindingFlags.Instance | BindingFlags.NonPublic); if (f != null) f.SetValue(obj, value); } private static void InvokePrivate(object obj, string method, object[] args) { var m = obj.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.NonPublic); m?.Invoke(obj, args); } }