BulletHell/Assets/BulletHellTemplate/Core/MonetizationManager.cs

596 lines
23 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Linq;
namespace BulletHellTemplate
{
/// <summary>
/// Manages the monetization system, including currencies and purchased items.
/// It ensures that the system is initialized and persists across scenes.
/// </summary>
public class MonetizationManager : MonoBehaviour
{
[Header("Currencies List")]
[Tooltip("List of all the currencies in the game.")]
public List<Currency> currencies = new List<Currency>();
// Lists to store purchased items
private List<PurchasedCharacter> purchasedCharacters = new List<PurchasedCharacter>();
private List<PurchasedIcon> purchasedIcons = new List<PurchasedIcon>();
private List<PurchasedFrame> purchasedFrames = new List<PurchasedFrame>();
private List<PurchasedShopItem> purchasedShopItems = new List<PurchasedShopItem>();
private List<PurchasedInventoryItem> purchasedInventoryItems = new List<PurchasedInventoryItem>();
private bool isInitialized = false; // Tracks if the manager is initialized
public static MonetizationManager Singleton;
private void Awake()
{
if (Singleton == null)
{
Singleton = this;
DontDestroyOnLoad(gameObject); // Prevent destruction across scenes
InitializeManager();
}
else
{
Destroy(gameObject); // Ensure only one instance exists
}
}
/// <summary>
/// Initializes the MonetizationManager and loads purchased items from PlayerPrefs.
/// </summary>
private void InitializeManager()
{
LoadPurchasedItems();
isInitialized = true;
}
/// <summary>
/// Returns whether the MonetizationManager has been initialized.
/// </summary>
/// <returns>True if initialized, false otherwise.</returns>
public bool IsInitialized()
{
return isInitialized;
}
public static event Action<string, int> OnCurrencyChanged;
/// <summary>
/// Sets the currency amount locally and updates it in PlayerPrefs and the backend.
/// </summary>
/// <param name="currency">The currency identifier.</param>
/// <param name="amount">The new amount to set.</param>
public static void SetCurrency(string currency, int amount)
{
PlayerPrefs.SetInt(currency, amount);
PlayerPrefs.Save();
OnCurrencyChanged?.Invoke(currency, amount);
if (BackendManager.Singleton.CheckInitialized())
{
Singleton.StartCoroutine(CallUpdateCurrencyCoroutine(currency, amount));
}
}
/// <summary>
/// Coroutine that calls the backend to update currency asynchronously.
/// </summary>
private static IEnumerator CallUpdateCurrencyCoroutine(string currency, int amount)
{
var task = BackendManager.Singleton.UpdateCurrencies(currency, amount);
yield return new WaitUntil(() => task.IsCompleted);
if (task.IsFaulted)
{
Debug.LogError($"Failed to update currency {currency}: " + task.Exception);
}
else
{
Debug.Log($"Currency {currency} successfully updated in Backend. Amount: {amount}");
}
}
/// <summary>
/// Coroutine that saves conventional purchased items using SavePurchasedItemsAsync
/// and then saves each inventory item individually using SavePurchasedInventoryItemAsync.
/// </summary>
private static IEnumerator CallSavePurchasedItemsCoroutine()
{
// 1) Save conventional purchased items (Characters, Icons, Frames, ShopItems)
var task = BackendManager.Singleton.SavePurchasedItemsAsync();
yield return new WaitUntil(() => task.IsCompleted);
if (task.IsFaulted)
{
Debug.LogError($"Failed to save purchased items for user: " + task.Exception);
}
else
{
Debug.Log("Purchased items (characters/icons/frames/shop) saved successfully.");
}
foreach (var invItem in Singleton.purchasedInventoryItems)
{
var invTask = BackendManager.Singleton.SavePurchasedInventoryItemAsync(invItem);
yield return new WaitUntil(() => invTask.IsCompleted);
if (invTask.IsFaulted)
{
Debug.LogError($"Failed to save purchased inventory item {invItem.uniqueItemGuid}: "
+ invTask.Exception);
}
else
{
Debug.Log($"Inventory item saved: {invItem.uniqueItemGuid} -> {invItem.itemId}");
}
}
}
/// <summary>
/// Gets the currency amount from PlayerPrefs.
/// </summary>
/// <param name="currency">The currency identifier.</param>
/// <returns>The amount of the specified currency.</returns>
public static int GetCurrency(string currency)
{
return PlayerPrefs.GetInt(currency);
}
/// <summary>
/// Unlocks an item from the Battle Pass and saves the associated rewards.
/// </summary>
/// <param name="item">The BattlePassItem to be unlocked.</param>
public void BattlePassUnlockItem(BattlePassItem item)
{
// Add characters to purchased list if not already purchased
if (item.rewardType == BattlePassItem.RewardType.CharacterReward)
{
foreach (var character in item.characterData)
{
if (!IsCharacterPurchased(character.characterId.ToString()))
{
purchasedCharacters.Add(new PurchasedCharacter { characterId = character.characterId.ToString() });
}
}
}
// Add icons to purchased list if not already purchased
if (item.rewardType == BattlePassItem.RewardType.IconReward)
{
if (!IsIconPurchased(item.iconReward.iconId.ToString()))
{
purchasedIcons.Add(new PurchasedIcon { iconId = item.iconReward.iconId.ToString() });
}
}
// Add frames to purchased list if not already purchased
if (item.rewardType == BattlePassItem.RewardType.FrameReward)
{
if (!IsFramePurchased(item.frameReward.frameId.ToString()))
{
purchasedFrames.Add(new PurchasedFrame { frameId = item.frameReward.frameId.ToString() });
}
}
// Add inventory items to purchased list if not already purchased
if (item.rewardType == BattlePassItem.RewardType.InventoryItemReward)
{
foreach (var inventoryItem in item.inventoryItems)
{
if (inventoryItem != null)
{
string shortId = GenerateShortID(5);
PurchasedInventoryItem newItem = new PurchasedInventoryItem
{
uniqueItemGuid = shortId,
itemId = inventoryItem.itemId,
itemLevel = 0,
upgrades = new Dictionary<int, int>()
};
}
}
}
// Add currency reward
if (item.rewardType == BattlePassItem.RewardType.CurrencyReward)
{
var currencyName = item.currencyReward.currency.coinID;
var amount = item.currencyReward.amount;
int currentAmount = GetCurrency(currencyName);
SetCurrency(currencyName, currentAmount + amount);
}
SavePurchasedItems(item.rewardType == BattlePassItem.RewardType.InventoryItemReward);
Debug.Log($"Battle Pass item unlocked: {item.itemTitle}");
}
/// <summary>
/// Purchases a ShopItem, unlocking its associated characters, icons, frames, and inventory items.
/// </summary>
/// <param name="item">The ShopItem to be purchased.</param>
public void PurchaseItem(ShopItem item)
{
if (IsShopItemPurchased(item.itemId))
{
Debug.LogWarning("Item already purchased.");
return;
}
// Add characters to purchased list if not already purchased
foreach (var character in item.characterData)
{
if (!IsCharacterPurchased(character.characterId.ToString()))
{
purchasedCharacters.Add(new PurchasedCharacter { characterId = character.characterId.ToString() });
}
}
// Add icons
foreach (var icon in item.icons)
{
if (!IsIconPurchased(icon.iconId.ToString()))
{
purchasedIcons.Add(new PurchasedIcon { iconId = icon.iconId.ToString() });
}
}
// Add frames
foreach (var frame in item.frames)
{
if (!IsFramePurchased(frame.frameId.ToString()))
{
purchasedFrames.Add(new PurchasedFrame { frameId = frame.frameId.ToString() });
}
}
// Add InventoryItems
if (item.inventoryItems != null)
{
foreach (var inventoryItem in item.inventoryItems)
{
if (inventoryItem != null)
{
string shortId = GenerateShortID(5);
PurchasedInventoryItem newItem = new PurchasedInventoryItem
{
uniqueItemGuid = shortId,
itemId = inventoryItem.itemId,
itemLevel = 0,
upgrades = new Dictionary<int, int>()
};
}
}
}
// Add the shop item to purchased list
purchasedShopItems.Add(new PurchasedShopItem { itemId = item.itemId });
// Save purchased items
SavePurchasedItems(item.inventoryItems != null);
Debug.Log($"Purchased item: {item.itemTitle}");
}
/// <summary>
/// Purchases an individual InventoryItem with a shorter unique ID (5 characters).
/// </summary>
/// <param name="itemId">The itemId of the ScriptableObject representing the item.</param>
public void PurchaseInventoryItem(string itemId)
{
// Optional: check if duplicates are allowed
bool alreadyOwned = purchasedInventoryItems.Any(pi => pi.itemId == itemId);
if (alreadyOwned)
{
Debug.Log($"ItemId {itemId} is already owned. Skipping purchase.");
return;
}
// Generate a short ID of 5 characters
string shortId = GenerateShortID(5);
PurchasedInventoryItem newItem = new PurchasedInventoryItem
{
uniqueItemGuid = shortId,
itemId = itemId,
itemLevel = 0,
upgrades = new Dictionary<int, int>()
};
purchasedInventoryItems.Add(newItem);
SavePurchasedItems(true);
Debug.Log($"Purchased new item {itemId} with ShortID {shortId}");
}
/// <summary>
/// Generates a short ID with alphanumeric characters (A-Z, 0-9).
/// Default length is 5.
/// </summary>
private string GenerateShortID(int length = 5)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
System.Random random = new System.Random();
char[] buffer = new char[length];
for (int i = 0; i < length; i++)
{
buffer[i] = chars[random.Next(chars.Length)];
}
return new string(buffer);
}
/// <summary>
/// Checks if a character is purchased.
/// </summary>
public bool IsCharacterPurchased(string characterId)
{
return purchasedCharacters.Any(character => character.characterId == characterId);
}
/// <summary>
/// Checks if an icon is purchased.
/// </summary>
public bool IsIconPurchased(string iconId)
{
return purchasedIcons.Any(icon => icon.iconId == iconId);
}
/// <summary>
/// Checks if a frame is purchased.
/// </summary>
public bool IsFramePurchased(string frameId)
{
return purchasedFrames.Any(frame => frame.frameId == frameId);
}
/// <summary>
/// Checks if an inventory item is already purchased by matching itemId.
/// </summary>
public bool IsInventoryItemPurchased(string itemId)
{
return purchasedInventoryItems.Any(inventoryItem => inventoryItem.itemId == itemId);
}
/// <summary>
/// Checks if a shop item is purchased.
/// </summary>
public bool IsShopItemPurchased(string itemId)
{
return purchasedShopItems.Any(shopItem => shopItem.itemId == itemId);
}
/// <summary>
/// Returns the icon of a specific currency if found.
/// </summary>
public Sprite GetCurrencyIcon(string currency)
{
foreach (Currency item in MonetizationManager.Singleton.currencies)
{
if (item.coinID == currency)
{
return item.icon;
}
}
return null;
}
public void PurchaseInventoryItemNoImmediateSave(string itemId)
{
bool alreadyOwned = purchasedInventoryItems.Any(pi => pi.itemId == itemId);
if (alreadyOwned)
return;
string shortId = GenerateShortID(5);
PurchasedInventoryItem newItem = new PurchasedInventoryItem
{
uniqueItemGuid = shortId,
itemId = itemId,
itemLevel = 0,
upgrades = new Dictionary<int, int>()
};
purchasedInventoryItems.Add(newItem);
Debug.Log($"Purchased (no immediate save) new item {itemId} with ShortID {shortId}");
}
/// <summary>
/// Saves purchased items to PlayerPrefs, then calls the backend to store them.
/// It also saves each inventory item individually.
/// </summary>
public void SavePurchasedItems(bool isInventoryItem)
{
// 1) Convert all purchased item lists to JSON
string charactersJson = JsonUtility.ToJson(new PurchasedCharacterList(purchasedCharacters));
string iconsJson = JsonUtility.ToJson(new PurchasedIconList(purchasedIcons));
string framesJson = JsonUtility.ToJson(new PurchasedFrameList(purchasedFrames));
string inventoryItemsJson = JsonUtility.ToJson(new PurchasedInventoryItemList(purchasedInventoryItems));
string shopItemsJson = JsonUtility.ToJson(new PurchasedShopItemList(purchasedShopItems));
// 2) Save to PlayerPrefs
PlayerPrefs.SetString("PurchasedCharacters", charactersJson);
PlayerPrefs.SetString("PurchasedIcons", iconsJson);
PlayerPrefs.SetString("PurchasedFrames", framesJson);
PlayerPrefs.SetString("PurchasedInventoryItems", inventoryItemsJson);
PlayerPrefs.SetString("PurchasedShopItems", shopItemsJson);
PlayerPrefs.Save();
// 3) If the backend is available, call the coroutine to save them
if (BackendManager.Singleton.CheckInitialized() && isInventoryItem)
{
Singleton.StartCoroutine(CallSavePurchasedItemsCoroutine());
}
}
/// <summary>
/// Loads purchased items from PlayerPrefs into the lists at startup.
/// </summary>
private void LoadPurchasedItems()
{
// Load from PlayerPrefs
string charactersJson = PlayerPrefs.GetString(
"PurchasedCharacters",
JsonUtility.ToJson(new PurchasedCharacterList(new List<PurchasedCharacter>()))
);
string iconsJson = PlayerPrefs.GetString(
"PurchasedIcons",
JsonUtility.ToJson(new PurchasedIconList(new List<PurchasedIcon>()))
);
string framesJson = PlayerPrefs.GetString(
"PurchasedFrames",
JsonUtility.ToJson(new PurchasedFrameList(new List<PurchasedFrame>()))
);
string inventoryItemsJson = PlayerPrefs.GetString(
"PurchasedInventoryItems",
JsonUtility.ToJson(new PurchasedInventoryItemList(new List<PurchasedInventoryItem>()))
);
string shopItemsJson = PlayerPrefs.GetString(
"PurchasedShopItems",
JsonUtility.ToJson(new PurchasedShopItemList(new List<PurchasedShopItem>()))
);
purchasedCharacters = JsonUtility.FromJson<PurchasedCharacterList>(charactersJson).purchasedCharacters;
purchasedIcons = JsonUtility.FromJson<PurchasedIconList>(iconsJson).purchasedIcons;
purchasedFrames = JsonUtility.FromJson<PurchasedFrameList>(framesJson).purchasedFrames;
purchasedInventoryItems = JsonUtility.FromJson<PurchasedInventoryItemList>(inventoryItemsJson).purchasedInventoryItems;
purchasedShopItems = JsonUtility.FromJson<PurchasedShopItemList>(shopItemsJson).purchasedShopItems;
}
/// <summary>
/// Updates the lists of purchased items with the new data, then saves them.
/// </summary>
public void UpdatePurchasedItems(
List<PurchasedCharacter> characters,
List<PurchasedIcon> icons,
List<PurchasedFrame> frames,
List<PurchasedShopItem> shopItems,
List<PurchasedInventoryItem> inventoryItems
)
{
Debug.Log($"Updating purchased items. Characters: {characters.Count}, Icons: {icons.Count}, Frames: {frames.Count}, ShopItems: {shopItems.Count}, Inventory: {inventoryItems.Count}");
purchasedCharacters = new List<PurchasedCharacter>(characters);
purchasedIcons = new List<PurchasedIcon>(icons);
purchasedFrames = new List<PurchasedFrame>(frames);
purchasedInventoryItems = new List<PurchasedInventoryItem>(inventoryItems);
purchasedShopItems = new List<PurchasedShopItem>(shopItems);
SavePurchasedItems(false);
}
// Additional helper methods to set purchased item lists individually
public void SetPurchasedCharacters(List<PurchasedCharacter> characters) => SetPurchasedList(ref purchasedCharacters, characters);
public void SetPurchasedIcons(List<PurchasedIcon> icons) => SetPurchasedList(ref purchasedIcons, icons);
public void SetPurchasedFrames(List<PurchasedFrame> frames) => SetPurchasedList(ref purchasedFrames, frames);
public void SetPurchasedInventoryItems(List<PurchasedInventoryItem> items) => SetPurchasedList(ref purchasedInventoryItems, items);
public void SetPurchasedShopItems(List<PurchasedShopItem> shopItems) => SetPurchasedList(ref purchasedShopItems, shopItems);
private void SetPurchasedList<T>(ref List<T> targetList, List<T> newList)
{
targetList = new List<T>(newList);
SavePurchasedItems(false);
}
// Getter methods for purchased items
public List<PurchasedCharacter> GetPurchasedCharacters() => new List<PurchasedCharacter>(purchasedCharacters);
public List<PurchasedIcon> GetPurchasedIcons() => new List<PurchasedIcon>(purchasedIcons);
public List<PurchasedFrame> GetPurchasedFrames() => new List<PurchasedFrame>(purchasedFrames);
public List<PurchasedInventoryItem> GetPurchasedInventoryItems() => new List<PurchasedInventoryItem>(purchasedInventoryItems);
public List<PurchasedShopItem> GetPurchasedShopItems() => new List<PurchasedShopItem>(purchasedShopItems);
}
// Classes for serializing purchased items
[System.Serializable]
public class PurchasedCharacter
{
public string characterId;
}
[System.Serializable]
public class PurchasedIcon
{
public string iconId;
}
[System.Serializable]
public class PurchasedFrame
{
public string frameId;
}
[System.Serializable]
public class PurchasedInventoryItem
{
public string uniqueItemGuid;
public string itemId;
public int itemLevel;
public Dictionary<int, int> upgrades;
}
[System.Serializable]
public class PurchasedShopItem
{
public string itemId;
}
[System.Serializable]
public class PurchasedCharacterList
{
public List<PurchasedCharacter> purchasedCharacters = new List<PurchasedCharacter>();
public PurchasedCharacterList(List<PurchasedCharacter> characters)
{
this.purchasedCharacters = characters;
}
}
[System.Serializable]
public class PurchasedIconList
{
public List<PurchasedIcon> purchasedIcons = new List<PurchasedIcon>();
public PurchasedIconList(List<PurchasedIcon> icons)
{
this.purchasedIcons = icons;
}
}
[System.Serializable]
public class PurchasedFrameList
{
public List<PurchasedFrame> purchasedFrames = new List<PurchasedFrame>();
public PurchasedFrameList(List<PurchasedFrame> frames)
{
this.purchasedFrames = frames;
}
}
[System.Serializable]
public class PurchasedInventoryItemList
{
public List<PurchasedInventoryItem> purchasedInventoryItems = new List<PurchasedInventoryItem>();
public PurchasedInventoryItemList(List<PurchasedInventoryItem> inventoryItems)
{
this.purchasedInventoryItems = inventoryItems;
}
}
[System.Serializable]
public class PurchasedShopItemList
{
public List<PurchasedShopItem> purchasedShopItems = new List<PurchasedShopItem>();
public PurchasedShopItemList(List<PurchasedShopItem> shopItems)
{
this.purchasedShopItems = shopItems;
}
}
}