using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; namespace BulletHellTemplate { /// /// Data class for saving recharge information. /// [Serializable] public class RechargeSaveData { public List entries = new List(); /// /// Sets the last recharge time for a given coin. /// /// Identifier for the coin. /// The Unix time to set as the last recharge time. public void SetValue(string coinID, long unixTime) { for (int i = 0; i < entries.Count; i++) { if (entries[i].coinID == coinID) { entries[i].lastRechargeTime = unixTime; return; } } entries.Add(new RechargeKeyValue { coinID = coinID, lastRechargeTime = unixTime }); } /// /// Gets the last recharge time for a given coin. /// /// Identifier for the coin. /// Last recharge time as Unix time. Returns 0 if not found. public long GetValue(string coinID) { for (int i = 0; i < entries.Count; i++) { if (entries[i].coinID == coinID) return entries[i].lastRechargeTime; } return 0L; } } /// /// Represents a key-value pair for recharge data. /// [Serializable] public class RechargeKeyValue { public string coinID; public long lastRechargeTime; } /// /// Manager class for handling recharge logic for rechargeable currencies. /// public class RechargeablesManager : MonoBehaviour { public static RechargeablesManager Singleton; [Header("Auto Save Interval (seconds)")] [SerializeField] private float autoSaveInterval = 60f; [SerializeField] private string saveFileName = "currencyRecharge.json"; private float saveTimer; private RechargeSaveData localData = new RechargeSaveData(); /// /// Awake is called when the script instance is being loaded. /// Implements the singleton pattern. /// private void Awake() { if (Singleton == null) { Singleton = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); return; } } /// /// Start is called before the first frame update. /// private void Start() { StartCoroutine(WaitForBackendAndInitialize()); } /// /// Waits for the backend to initialize before loading local data and updating all currencies recharge. /// /// IEnumerator for coroutine. private IEnumerator WaitForBackendAndInitialize() { while (!BackendManager.Singleton.CheckInitialized()) yield return null; LoadLocalData(); UpdateAllCurrenciesRecharge(); // Applies offline recharges } /// /// Update is called once per frame. /// Checks if the auto save interval has been reached. /// private void Update() { saveTimer += Time.deltaTime; if (saveTimer >= autoSaveInterval) { saveTimer = 0f; SaveLocalData(); } } /// /// Called on application quit. Saves local recharge data. /// private void OnApplicationQuit() { SaveLocalData(); } /// /// Handles the offline recharge logic for all rechargeable currencies. /// public void UpdateAllCurrenciesRecharge() { if (MonetizationManager.Singleton == null) { Debug.LogWarning("MonetizationManager.Singleton is null. Cannot update recharge."); return; } foreach (Currency currency in MonetizationManager.Singleton.currencies) { if (!currency.isRechargeableCurrency) continue; CheckAndApplyRecharge(currency); } } /// /// Applies all pending recharges (offline or delayed) in a single call to avoid multiple database calls. /// Returns true if the maximum amount is reached. /// /// The currency to apply pending charges. /// True if the maximum amount is reached, false otherwise. public bool ApplyAllPendingCharges(Currency currency) { if (currency == null || !currency.isRechargeableCurrency) return false; int oldAmount = MonetizationManager.GetCurrency(currency.coinID); CheckAndApplyRecharge(currency); // Reuses the same logic int newAmount = MonetizationManager.GetCurrency(currency.coinID); // Returns true if the maximum amount is reached or exceeded. if (currency.useMaxAmount && newAmount >= currency.maxAmount) return true; return false; } /// /// Base logic to calculate how many recharge ticks have occurred since the last recharge and apply them. /// /// The currency to recharge. private void CheckAndApplyRecharge(Currency currency) { int currentAmount = MonetizationManager.GetCurrency(currency.coinID); // If the currency is at maximum, reset last recharge time to avoid infinite offline accumulation. if (currency.useMaxAmount && currentAmount >= currency.maxAmount) { localData.SetValue(currency.coinID, 0); return; } long lastTime = localData.GetValue(currency.coinID); long now = GetCurrentUnixTime(); // If never set, initialize with the current time and exit. if (lastTime == 0) { localData.SetValue(currency.coinID, now); return; } long elapsed = now - lastTime; if (elapsed <= 0) return; float chunkDuration = ConvertToSeconds(currency.rechargeableTime, currency.rechargeableTimeScale); // Calculate how many ticks occurred. int ticks = Mathf.FloorToInt((float)elapsed / chunkDuration); if (ticks > 0) { int totalRecharge = ticks * currency.rechargeAmount; int newAmount = currentAmount + totalRecharge; if (currency.useMaxAmount && newAmount > currency.maxAmount) { newAmount = currency.maxAmount; localData.SetValue(currency.coinID, 0); } else { // Adjust last recharge time to now minus the unused time. long totalSecondsUsed = (long)(ticks * chunkDuration); long newLastTime = lastTime + totalSecondsUsed; localData.SetValue(currency.coinID, newLastTime); } MonetizationManager.SetCurrency(currency.coinID, newAmount); } } /// /// Saves local recharge data to a file. /// public void SaveLocalData() { try { string path = Path.Combine(Application.persistentDataPath, saveFileName); string json = JsonUtility.ToJson(localData, true); File.WriteAllText(path, json); } catch (Exception e) { Debug.LogError($"Error saving local recharge data: {e.Message}"); } } /// /// Loads local recharge data from a file. /// public void LoadLocalData() { try { string path = Path.Combine(Application.persistentDataPath, saveFileName); if (!File.Exists(path)) { localData = new RechargeSaveData(); return; } string json = File.ReadAllText(path); localData = JsonUtility.FromJson(json); if (localData == null) localData = new RechargeSaveData(); } catch (Exception e) { Debug.LogError($"Error loading local recharge data: {e.Message}"); localData = new RechargeSaveData(); } } /// /// Retrieves the current Unix time in seconds. /// /// Current Unix time in seconds. private long GetCurrentUnixTime() { return DateTimeOffset.UtcNow.ToUnixTimeSeconds(); } /// /// Converts a time value based on the provided time scale to seconds. /// /// The time value to convert. /// The time scale (Seconds, Minutes, or Hours). /// Equivalent time in seconds. private float ConvertToSeconds(float timeValue, rechargeableTimeScale scale) { switch (scale) { case rechargeableTimeScale.Seconds: return timeValue; case rechargeableTimeScale.Minutes: return timeValue * 60f; case rechargeableTimeScale.Hours: return timeValue * 3600f; default: return timeValue; } } } }