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; } } }