using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Linq;
namespace BulletHellTemplate
{
///
/// Manages the monetization system, including currencies and purchased items.
/// It ensures that the system is initialized and persists across scenes.
///
public class MonetizationManager : MonoBehaviour
{
[Header("Currencies List")]
[Tooltip("List of all the currencies in the game.")]
public List currencies = new List();
// Lists to store purchased items
private List purchasedCharacters = new List();
private List purchasedIcons = new List();
private List purchasedFrames = new List();
private List purchasedShopItems = new List();
private List purchasedInventoryItems = new List();
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
}
}
///
/// Initializes the MonetizationManager and loads purchased items from PlayerPrefs.
///
private void InitializeManager()
{
LoadPurchasedItems();
isInitialized = true;
}
///
/// Returns whether the MonetizationManager has been initialized.
///
/// True if initialized, false otherwise.
public bool IsInitialized()
{
return isInitialized;
}
public static event Action OnCurrencyChanged;
///
/// Sets the currency amount locally and updates it in PlayerPrefs and the backend.
///
/// The currency identifier.
/// The new amount to set.
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));
}
}
///
/// Coroutine that calls the backend to update currency asynchronously.
///
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}");
}
}
///
/// Coroutine that saves conventional purchased items using SavePurchasedItemsAsync
/// and then saves each inventory item individually using SavePurchasedInventoryItemAsync.
///
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}");
}
}
}
///
/// Gets the currency amount from PlayerPrefs.
///
/// The currency identifier.
/// The amount of the specified currency.
public static int GetCurrency(string currency)
{
return PlayerPrefs.GetInt(currency);
}
///
/// Unlocks an item from the Battle Pass and saves the associated rewards.
///
/// The BattlePassItem to be unlocked.
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()
};
}
}
}
// 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}");
}
///
/// Purchases a ShopItem, unlocking its associated characters, icons, frames, and inventory items.
///
/// The ShopItem to be purchased.
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()
};
}
}
}
// 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}");
}
///
/// Purchases an individual InventoryItem with a shorter unique ID (5 characters).
///
/// The itemId of the ScriptableObject representing the item.
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()
};
purchasedInventoryItems.Add(newItem);
SavePurchasedItems(true);
Debug.Log($"Purchased new item {itemId} with ShortID {shortId}");
}
///
/// Generates a short ID with alphanumeric characters (A-Z, 0-9).
/// Default length is 5.
///
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);
}
///
/// Checks if a character is purchased.
///
public bool IsCharacterPurchased(string characterId)
{
return purchasedCharacters.Any(character => character.characterId == characterId);
}
///
/// Checks if an icon is purchased.
///
public bool IsIconPurchased(string iconId)
{
return purchasedIcons.Any(icon => icon.iconId == iconId);
}
///
/// Checks if a frame is purchased.
///
public bool IsFramePurchased(string frameId)
{
return purchasedFrames.Any(frame => frame.frameId == frameId);
}
///
/// Checks if an inventory item is already purchased by matching itemId.
///
public bool IsInventoryItemPurchased(string itemId)
{
return purchasedInventoryItems.Any(inventoryItem => inventoryItem.itemId == itemId);
}
///
/// Checks if a shop item is purchased.
///
public bool IsShopItemPurchased(string itemId)
{
return purchasedShopItems.Any(shopItem => shopItem.itemId == itemId);
}
///
/// Returns the icon of a specific currency if found.
///
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()
};
purchasedInventoryItems.Add(newItem);
Debug.Log($"Purchased (no immediate save) new item {itemId} with ShortID {shortId}");
}
///
/// Saves purchased items to PlayerPrefs, then calls the backend to store them.
/// It also saves each inventory item individually.
///
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());
}
}
///
/// Loads purchased items from PlayerPrefs into the lists at startup.
///
private void LoadPurchasedItems()
{
// Load from PlayerPrefs
string charactersJson = PlayerPrefs.GetString(
"PurchasedCharacters",
JsonUtility.ToJson(new PurchasedCharacterList(new List()))
);
string iconsJson = PlayerPrefs.GetString(
"PurchasedIcons",
JsonUtility.ToJson(new PurchasedIconList(new List()))
);
string framesJson = PlayerPrefs.GetString(
"PurchasedFrames",
JsonUtility.ToJson(new PurchasedFrameList(new List()))
);
string inventoryItemsJson = PlayerPrefs.GetString(
"PurchasedInventoryItems",
JsonUtility.ToJson(new PurchasedInventoryItemList(new List()))
);
string shopItemsJson = PlayerPrefs.GetString(
"PurchasedShopItems",
JsonUtility.ToJson(new PurchasedShopItemList(new List()))
);
purchasedCharacters = JsonUtility.FromJson(charactersJson).purchasedCharacters;
purchasedIcons = JsonUtility.FromJson(iconsJson).purchasedIcons;
purchasedFrames = JsonUtility.FromJson(framesJson).purchasedFrames;
purchasedInventoryItems = JsonUtility.FromJson(inventoryItemsJson).purchasedInventoryItems;
purchasedShopItems = JsonUtility.FromJson(shopItemsJson).purchasedShopItems;
}
///
/// Updates the lists of purchased items with the new data, then saves them.
///
public void UpdatePurchasedItems(
List characters,
List icons,
List frames,
List shopItems,
List inventoryItems
)
{
Debug.Log($"Updating purchased items. Characters: {characters.Count}, Icons: {icons.Count}, Frames: {frames.Count}, ShopItems: {shopItems.Count}, Inventory: {inventoryItems.Count}");
purchasedCharacters = new List(characters);
purchasedIcons = new List(icons);
purchasedFrames = new List(frames);
purchasedInventoryItems = new List(inventoryItems);
purchasedShopItems = new List(shopItems);
SavePurchasedItems(false);
}
// Additional helper methods to set purchased item lists individually
public void SetPurchasedCharacters(List characters) => SetPurchasedList(ref purchasedCharacters, characters);
public void SetPurchasedIcons(List icons) => SetPurchasedList(ref purchasedIcons, icons);
public void SetPurchasedFrames(List frames) => SetPurchasedList(ref purchasedFrames, frames);
public void SetPurchasedInventoryItems(List items) => SetPurchasedList(ref purchasedInventoryItems, items);
public void SetPurchasedShopItems(List shopItems) => SetPurchasedList(ref purchasedShopItems, shopItems);
private void SetPurchasedList(ref List targetList, List newList)
{
targetList = new List(newList);
SavePurchasedItems(false);
}
// Getter methods for purchased items
public List GetPurchasedCharacters() => new List(purchasedCharacters);
public List GetPurchasedIcons() => new List(purchasedIcons);
public List GetPurchasedFrames() => new List(purchasedFrames);
public List GetPurchasedInventoryItems() => new List(purchasedInventoryItems);
public List GetPurchasedShopItems() => new List(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 upgrades;
}
[System.Serializable]
public class PurchasedShopItem
{
public string itemId;
}
[System.Serializable]
public class PurchasedCharacterList
{
public List purchasedCharacters = new List();
public PurchasedCharacterList(List characters)
{
this.purchasedCharacters = characters;
}
}
[System.Serializable]
public class PurchasedIconList
{
public List purchasedIcons = new List();
public PurchasedIconList(List icons)
{
this.purchasedIcons = icons;
}
}
[System.Serializable]
public class PurchasedFrameList
{
public List purchasedFrames = new List();
public PurchasedFrameList(List frames)
{
this.purchasedFrames = frames;
}
}
[System.Serializable]
public class PurchasedInventoryItemList
{
public List purchasedInventoryItems = new List();
public PurchasedInventoryItemList(List inventoryItems)
{
this.purchasedInventoryItems = inventoryItems;
}
}
[System.Serializable]
public class PurchasedShopItemList
{
public List purchasedShopItems = new List();
public PurchasedShopItemList(List shopItems)
{
this.purchasedShopItems = shopItems;
}
}
}