FantasyAsset/Assets/Scripts/ES3GameStore.cs
2025-10-08 01:03:39 +05:00

190 lines
7.0 KiB
C#

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<ObjectiveData> list = new();
public int activeIndex = 0;
public List<string> 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<string>(om._queuedNextObjectives) : null,
queuedNextStartIndex = GetPrivate<int>(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<int>(gm, "currentLevelIndex");
var isLevelRunning = GetPrivate<object>(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<ObjectiveManagerData>(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<string>(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<int>(K_LevelIdx, file, Settings) : -1;
var run = ES3.KeyExists(K_IsRunning, file) ? ES3.Load<bool>(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<string>(K_DSJSON, file, Settings);
if (!string.IsNullOrEmpty(dsJson) && SaveSystem.instance != null)
{
try
{
var sg = JsonUtility.FromJson<SavedGameData>(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<T>(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);
}
}