219 lines
4.7 KiB
C#
219 lines
4.7 KiB
C#
using System;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
|
|
public sealed class PlayerExperienceLedger : MonoBehaviour
|
|
{
|
|
[Serializable]
|
|
private class PlayerExperiencePayload
|
|
{
|
|
public int version = 1;
|
|
public long lastUpdatedUtcTicks;
|
|
public int playerExp;
|
|
}
|
|
|
|
public static PlayerExperienceLedger Instance { get; private set; }
|
|
|
|
public event Action<int> OnExperienceChanged;
|
|
|
|
private PlayerExperiencePayload payload;
|
|
private bool initialized;
|
|
private bool loadedFromSave;
|
|
private Player_SO boundPlayerData;
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
|
private static void Bootstrap()
|
|
{
|
|
EnsureInstance();
|
|
}
|
|
|
|
public static PlayerExperienceLedger EnsureInstance()
|
|
{
|
|
if (Instance != null)
|
|
{
|
|
return Instance;
|
|
}
|
|
|
|
var host = new GameObject("__runtime_player_exp_bridge");
|
|
host.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector;
|
|
DontDestroyOnLoad(host);
|
|
Instance = host.AddComponent<PlayerExperienceLedger>();
|
|
return Instance;
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (Instance != null && Instance != this)
|
|
{
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
|
|
Instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
InitializeIfNeeded();
|
|
TryAttachDefaultPlayerData();
|
|
}
|
|
|
|
private void OnApplicationPause(bool pauseStatus)
|
|
{
|
|
if (pauseStatus)
|
|
{
|
|
SaveNow();
|
|
}
|
|
}
|
|
|
|
private void OnApplicationQuit()
|
|
{
|
|
SaveNow();
|
|
}
|
|
|
|
public void InitializeIfNeeded()
|
|
{
|
|
if (initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
loadedFromSave = SecureSaveVault.TryLoadJson("player_experience", "runtime", out payload, GetLegacySavePath());
|
|
if (payload == null)
|
|
{
|
|
payload = CreateDefaultPayload();
|
|
}
|
|
|
|
initialized = true;
|
|
}
|
|
|
|
public void AttachPlayerData(Player_SO playerData)
|
|
{
|
|
if (playerData == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InitializeIfNeeded();
|
|
boundPlayerData = playerData;
|
|
|
|
if (!loadedFromSave)
|
|
{
|
|
payload.playerExp = Mathf.Max(0, playerData.player_currentEXP);
|
|
SaveNow();
|
|
loadedFromSave = true;
|
|
return;
|
|
}
|
|
|
|
SyncToPlayerData();
|
|
NotifyExperienceChanged();
|
|
}
|
|
|
|
public int GetExperience()
|
|
{
|
|
InitializeIfNeeded();
|
|
return payload.playerExp;
|
|
}
|
|
|
|
public void AddExperience(int amount)
|
|
{
|
|
if (amount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InitializeIfNeeded();
|
|
long next = (long)payload.playerExp + amount;
|
|
if (next < 0)
|
|
{
|
|
next = 0;
|
|
}
|
|
else if (next > int.MaxValue)
|
|
{
|
|
next = int.MaxValue;
|
|
}
|
|
|
|
payload.playerExp = (int)next;
|
|
SaveNow();
|
|
}
|
|
|
|
public bool TryConsumeExperience(int amount)
|
|
{
|
|
if (amount <= 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
InitializeIfNeeded();
|
|
if (payload.playerExp < amount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
payload.playerExp -= amount;
|
|
SaveNow();
|
|
return true;
|
|
}
|
|
|
|
public void SaveNow()
|
|
{
|
|
if (!initialized)
|
|
{
|
|
InitializeIfNeeded();
|
|
}
|
|
|
|
if (payload == null)
|
|
{
|
|
payload = CreateDefaultPayload();
|
|
}
|
|
|
|
payload.lastUpdatedUtcTicks = DateTime.UtcNow.Ticks;
|
|
SecureSaveVault.SaveJson("player_experience", "runtime", payload, GetLegacySavePath());
|
|
SyncToPlayerData();
|
|
NotifyExperienceChanged();
|
|
}
|
|
|
|
private void SyncToPlayerData()
|
|
{
|
|
if (boundPlayerData == null || payload == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
boundPlayerData.player_currentEXP = Mathf.Max(0, payload.playerExp);
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying)
|
|
{
|
|
UnityEditor.EditorUtility.SetDirty(boundPlayerData);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private void NotifyExperienceChanged()
|
|
{
|
|
OnExperienceChanged?.Invoke(GetExperience());
|
|
}
|
|
|
|
private void TryAttachDefaultPlayerData()
|
|
{
|
|
Player_SO player = RuntimeResourcesCache.LoadDefaultPlayerSo();
|
|
|
|
if (player != null)
|
|
{
|
|
AttachPlayerData(player);
|
|
}
|
|
}
|
|
|
|
private static PlayerExperiencePayload CreateDefaultPayload()
|
|
{
|
|
return new PlayerExperiencePayload
|
|
{
|
|
version = 1,
|
|
lastUpdatedUtcTicks = DateTime.UtcNow.Ticks,
|
|
playerExp = 0,
|
|
};
|
|
}
|
|
|
|
private static string GetLegacySavePath()
|
|
{
|
|
return Path.Combine(Application.persistentDataPath, "player_experience.json");
|
|
}
|
|
}
|