304 lines
10 KiB
C#
304 lines
10 KiB
C#
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.IO;
|
||
|
using UnityEngine;
|
||
|
|
||
|
namespace BulletHellTemplate
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Data class for saving recharge information.
|
||
|
/// </summary>
|
||
|
[Serializable]
|
||
|
public class RechargeSaveData
|
||
|
{
|
||
|
public List<RechargeKeyValue> entries = new List<RechargeKeyValue>();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Sets the last recharge time for a given coin.
|
||
|
/// </summary>
|
||
|
/// <param name="coinID">Identifier for the coin.</param>
|
||
|
/// <param name="unixTime">The Unix time to set as the last recharge time.</param>
|
||
|
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 });
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the last recharge time for a given coin.
|
||
|
/// </summary>
|
||
|
/// <param name="coinID">Identifier for the coin.</param>
|
||
|
/// <returns>Last recharge time as Unix time. Returns 0 if not found.</returns>
|
||
|
public long GetValue(string coinID)
|
||
|
{
|
||
|
for (int i = 0; i < entries.Count; i++)
|
||
|
{
|
||
|
if (entries[i].coinID == coinID)
|
||
|
return entries[i].lastRechargeTime;
|
||
|
}
|
||
|
return 0L;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Represents a key-value pair for recharge data.
|
||
|
/// </summary>
|
||
|
[Serializable]
|
||
|
public class RechargeKeyValue
|
||
|
{
|
||
|
public string coinID;
|
||
|
public long lastRechargeTime;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Manager class for handling recharge logic for rechargeable currencies.
|
||
|
/// </summary>
|
||
|
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();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Awake is called when the script instance is being loaded.
|
||
|
/// Implements the singleton pattern.
|
||
|
/// </summary>
|
||
|
private void Awake()
|
||
|
{
|
||
|
if (Singleton == null)
|
||
|
{
|
||
|
Singleton = this;
|
||
|
DontDestroyOnLoad(gameObject);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Destroy(gameObject);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Start is called before the first frame update.
|
||
|
/// </summary>
|
||
|
private void Start()
|
||
|
{
|
||
|
StartCoroutine(WaitForBackendAndInitialize());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Waits for the backend to initialize before loading local data and updating all currencies recharge.
|
||
|
/// </summary>
|
||
|
/// <returns>IEnumerator for coroutine.</returns>
|
||
|
private IEnumerator WaitForBackendAndInitialize()
|
||
|
{
|
||
|
while (!BackendManager.Singleton.CheckInitialized())
|
||
|
yield return null;
|
||
|
|
||
|
LoadLocalData();
|
||
|
UpdateAllCurrenciesRecharge(); // Applies offline recharges
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Update is called once per frame.
|
||
|
/// Checks if the auto save interval has been reached.
|
||
|
/// </summary>
|
||
|
private void Update()
|
||
|
{
|
||
|
saveTimer += Time.deltaTime;
|
||
|
if (saveTimer >= autoSaveInterval)
|
||
|
{
|
||
|
saveTimer = 0f;
|
||
|
SaveLocalData();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Called on application quit. Saves local recharge data.
|
||
|
/// </summary>
|
||
|
private void OnApplicationQuit()
|
||
|
{
|
||
|
SaveLocalData();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Handles the offline recharge logic for all rechargeable currencies.
|
||
|
/// </summary>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Applies all pending recharges (offline or delayed) in a single call to avoid multiple database calls.
|
||
|
/// Returns true if the maximum amount is reached.
|
||
|
/// </summary>
|
||
|
/// <param name="currency">The currency to apply pending charges.</param>
|
||
|
/// <returns>True if the maximum amount is reached, false otherwise.</returns>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Base logic to calculate how many recharge ticks have occurred since the last recharge and apply them.
|
||
|
/// </summary>
|
||
|
/// <param name="currency">The currency to recharge.</param>
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Saves local recharge data to a file.
|
||
|
/// </summary>
|
||
|
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}");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Loads local recharge data from a file.
|
||
|
/// </summary>
|
||
|
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<RechargeSaveData>(json);
|
||
|
|
||
|
if (localData == null)
|
||
|
localData = new RechargeSaveData();
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
Debug.LogError($"Error loading local recharge data: {e.Message}");
|
||
|
localData = new RechargeSaveData();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Retrieves the current Unix time in seconds.
|
||
|
/// </summary>
|
||
|
/// <returns>Current Unix time in seconds.</returns>
|
||
|
private long GetCurrentUnixTime()
|
||
|
{
|
||
|
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Converts a time value based on the provided time scale to seconds.
|
||
|
/// </summary>
|
||
|
/// <param name="timeValue">The time value to convert.</param>
|
||
|
/// <param name="scale">The time scale (Seconds, Minutes, or Hours).</param>
|
||
|
/// <returns>Equivalent time in seconds.</returns>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|