190 lines
7.0 KiB
C#
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);
|
||
|
}
|
||
|
}
|