using UnityEngine; using System; using System.Collections.Generic; using System.Reflection; // Optional Dialogue System snapshot: // #define USE_PIXELCRUSHERS #if USE_PIXELCRUSHERS using PixelCrushers; #endif public static class ES3GameStore { private const string K_Version = "v"; private const string K_SavedAt = "t"; private const string K_Obj = "obj"; private const string K_LevelIdx = "lvl"; private const string K_IsRunning = "run"; private const string K_DSJSON = "dsj"; [System.Serializable] public class ObjectiveData { public string text; public bool completed; } [System.Serializable] public class ObjectiveManagerData { public List list = new(); public int activeIndex = 0; public List queuedNextObjectives = null; public int queuedNextStartIndex = 0; } private static ES3Settings Settings => new ES3Settings { encryptionType = ES3.EncryptionType.AES, encryptionPassword = "change-me-strong-passphrase", prettyPrint = false }; private static string FileForSlot(int slot) { var pm = ES3ProfileManager.Instance; if (pm == null || string.IsNullOrEmpty(pm.ActiveProfileId)) throw new Exception("[ES3GameStore] No active profile set."); // Profile-relative path (ES3 uses persistentDataPath root under the hood) return pm.GetSlotPathRelative(slot); } public static void Save(int slot = 0) { var file = FileForSlot(slot); // ensure directory exists var dirRel = ES3ProfileManager.Instance.GetProfileDirRelative(ES3ProfileManager.Instance.ActiveProfileId); ES3.Save(K_Version, "1.0.0", file, Settings); // touching file also creates directories as needed 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); } // 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 string dsJson = null; if (SaveSystem.instance != null) { var sg = SaveSystem.Serialize(); dsJson = sg != null ? JsonUtility.ToJson(sg) : null; } ES3.Save(K_DSJSON, dsJson, file, Settings); #endif // update last played timestamp on the active profile ES3ProfileManager.Instance.TouchActiveProfileLastPlayed(); } public static bool Load(int slot = 0) { var file = FileForSlot(slot); if (!ES3.FileExists(file)) return false; // ObjectiveManager first (so UI is ready) if (ObjectiveManager.Instance != null && ES3.KeyExists(K_Obj, file)) { 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) { var idx = ES3.KeyExists(K_LevelIdx, file) ? ES3.Load(K_LevelIdx, file, Settings) : -1; var run = ES3.KeyExists(K_IsRunning, file) ? ES3.Load(K_IsRunning, file, Settings) : false; 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 USE_PIXELCRUSHERS if (ES3.KeyExists(K_DSJSON, file)) { var dsJson = ES3.Load(K_DSJSON, file, Settings); if (!string.IsNullOrEmpty(dsJson) && SaveSystem.instance != null) { try { var sg = JsonUtility.FromJson(dsJson); SaveSystem.ApplySavedGameData(sg); } catch (Exception e) { Debug.LogWarning($"[ES3] Dialogue System apply failed: {e}"); } } } #endif ES3ProfileManager.Instance.TouchActiveProfileLastPlayed(); return true; } public static void Delete(int slot = 0) { var file = FileForSlot(slot); if (ES3.FileExists(file)) ES3.DeleteFile(file); } // --- reflection helpers --- 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); } }