using System; using System.Collections.Generic; using UnityEngine; public sealed class ExpBottleLedger : MonoBehaviour { public static ExpBottleLedger Instance { get; private set; } public event Action OnBottleCountChanged; public event Action OnLedgerReloaded; private readonly Dictionary countsByKey = new Dictionary(StringComparer.Ordinal); private bool initialized; private bool loadedFromSave; private Player_SO boundPlayerData; public bool IsReady { get { return initialized; } } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void Bootstrap() { EnsureInstance(); } public static ExpBottleLedger EnsureInstance() { if (Instance != null) { return Instance; } var host = new GameObject("__runtime_cache_bridge"); host.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector; DontDestroyOnLoad(host); Instance = host.AddComponent(); return Instance; } private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); InitializeIfNeeded(); } private void OnApplicationPause(bool pauseStatus) { if (pauseStatus) { SaveNow(); } } private void OnApplicationQuit() { SaveNow(); } public void InitializeIfNeeded() { if (initialized) { return; } ExpBottleLedgerPayload payload; loadedFromSave = ExpBottleLedgerStorage.TryLoad(out payload); RebuildFromPayload(payload); initialized = true; SyncMirrorCounts(); SaveNow(); if (OnLedgerReloaded != null) { OnLedgerReloaded(); } } public void AttachPlayerData(Player_SO playerData) { if (playerData == null) { return; } InitializeIfNeeded(); boundPlayerData = playerData; SyncToPlayerData(); } public int GetCount(ExpBottleKind kind) { return GetCountByKey(ExpBottleCatalog.GetKey(kind)); } public int GetCountByKey(string key) { InitializeIfNeeded(); int count; if (!countsByKey.TryGetValue(key, out count)) { return 0; } return count; } public IReadOnlyList GetSnapshot() { InitializeIfNeeded(); var result = new List(ExpBottleCatalog.All.Count); for (int i = 0; i < ExpBottleCatalog.All.Count; i++) { var descriptor = ExpBottleCatalog.All[i]; result.Add(new ExpBottleSnapshotEntry { key = descriptor.Key, count = GetCountByKey(descriptor.Key) }); } return result; } public void Add(ExpBottleKind kind, int amount) { if (amount <= 0) { return; } ChangeCount(ExpBottleCatalog.GetKey(kind), amount); } public void AddByKey(string key, int amount) { if (string.IsNullOrWhiteSpace(key) || amount <= 0) { return; } ChangeCount(key.Trim(), amount); } public bool TryConsume(ExpBottleKind kind, int amount) { if (amount <= 0) { return false; } var key = ExpBottleCatalog.GetKey(kind); var current = GetCountByKey(key); if (current < amount) { return false; } ChangeCount(key, -amount); DailyTaskEventHub.ReportUseItem(amount); return true; } public bool TryConsumeByKey(string key, int amount) { if (string.IsNullOrWhiteSpace(key) || amount <= 0) { return false; } var safeKey = key.Trim(); var current = GetCountByKey(safeKey); if (current < amount) { return false; } ChangeCount(safeKey, -amount); DailyTaskEventHub.ReportUseItem(amount); return true; } public void SetCount(ExpBottleKind kind, int value) { var safeValue = Mathf.Max(0, value); SetCountByKey(ExpBottleCatalog.GetKey(kind), safeValue); } public void ResetAllToZero() { InitializeIfNeeded(); for (int i = 0; i < ExpBottleCatalog.All.Count; i++) { countsByKey[ExpBottleCatalog.All[i].Key] = 0; } SaveNow(); NotifyAllCountsChanged(); } public bool SeedFromPlayerSo(Player_SO playerData, bool overwriteExistingCounts) { if (playerData == null) { return false; } InitializeIfNeeded(); bool changed = false; for (int i = 0; i < ExpBottleCatalog.All.Count; i++) { var descriptor = ExpBottleCatalog.All[i]; if (string.IsNullOrEmpty(descriptor.LegacyPlayerFieldName)) { continue; } var legacyValue = Mathf.Max(0, playerData.GetLegacyExpBottleCount(descriptor.LegacyPlayerFieldName)); var currentValue = GetCountByKey(descriptor.Key); if (!overwriteExistingCounts && currentValue > 0) { continue; } if (currentValue == legacyValue) { continue; } countsByKey[descriptor.Key] = legacyValue; changed = true; } if (!changed) { return false; } SaveNow(); NotifyAllCountsChanged(); return true; } public bool HasEnough(ExpBottleKind kind, int amount) { if (amount <= 0) { return true; } return GetCount(kind) >= amount; } public bool HasEnoughByKey(string key, int amount) { if (amount <= 0) { return true; } if (string.IsNullOrWhiteSpace(key)) { return false; } return GetCountByKey(key.Trim()) >= amount; } public void SaveNow() { InitializeIfNeededForSave(); SyncMirrorCounts(); ExpBottleLedgerStorage.TrySave(BuildPayload()); SyncToPlayerData(); } private void SyncMirrorCounts() { expBottlesSO[] definitions = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < definitions.Length; i++) { expBottlesSO definition = definitions[i]; if (definition == null) { continue; } definition.runtimeOwnedCount = GetCount(definition.bottleKind); } } private void InitializeIfNeededForSave() { if (!initialized) { InitializeIfNeeded(); } } private void ChangeCount(string key, int delta) { InitializeIfNeeded(); var current = GetCountByKey(key); long next = (long)current + delta; if (next < 0) { next = 0; } else if (next > int.MaxValue) { next = int.MaxValue; } SetCountByKey(key, (int)next); } private void SetCountByKey(string key, int value) { InitializeIfNeeded(); int safeValue = Mathf.Max(0, value); int current = GetCountByKey(key); if (current == safeValue) { return; } countsByKey[key] = safeValue; SaveNow(); ExpBottleKind kind; if (ExpBottleCatalog.TryGetKind(key, out kind) && OnBottleCountChanged != null) { OnBottleCountChanged(kind, safeValue); } } private void RebuildFromPayload(ExpBottleLedgerPayload payload) { countsByKey.Clear(); for (int i = 0; i < ExpBottleCatalog.All.Count; i++) { countsByKey[ExpBottleCatalog.All[i].Key] = 0; } if (payload == null || payload.entries == null) { return; } for (int i = 0; i < payload.entries.Count; i++) { var entry = payload.entries[i]; if (entry == null || string.IsNullOrEmpty(entry.key)) { continue; } countsByKey[entry.key] = Mathf.Max(0, entry.count); } } private ExpBottleLedgerPayload BuildPayload() { var payload = ExpBottleLedgerStorage.CreateDefaultPayload(); payload.entries.Clear(); var serializedKeys = new HashSet(StringComparer.Ordinal); for (int i = 0; i < ExpBottleCatalog.All.Count; i++) { var descriptor = ExpBottleCatalog.All[i]; payload.entries.Add(new ExpBottleEntry { key = descriptor.Key, count = GetCountByKey(descriptor.Key) }); serializedKeys.Add(descriptor.Key); } foreach (var pair in countsByKey) { if (serializedKeys.Contains(pair.Key)) { continue; } payload.entries.Add(new ExpBottleEntry { key = pair.Key, count = Mathf.Max(0, pair.Value) }); } return payload; } private void NotifyAllCountsChanged() { if (OnBottleCountChanged == null) { return; } for (int i = 0; i < ExpBottleCatalog.All.Count; i++) { var descriptor = ExpBottleCatalog.All[i]; OnBottleCountChanged(descriptor.Kind, GetCountByKey(descriptor.Key)); } } private void SyncToPlayerData() { if (boundPlayerData == null) { return; } for (int i = 0; i < ExpBottleCatalog.All.Count; i++) { var descriptor = ExpBottleCatalog.All[i]; if (string.IsNullOrEmpty(descriptor.LegacyPlayerFieldName)) { continue; } boundPlayerData.SetLegacyExpBottleCount(descriptor.LegacyPlayerFieldName, GetCountByKey(descriptor.Key)); } } }